Search code examples
rggplot2dplyrr-sfgeom-sf

How to show both colors at a border using geom_sf?


I'm creating a map with two layers- a heatmap (geom_tile) with biome data and layering a map of the different regions on top. I've got a list countries in each region and I got R to display a map with just the outer borders of the regions in different colors. BUT- because the borders are shared they only show one color, making some regions kind of hard to see. Is there a way to make both colors visible at the borders?

NOTE: I have my own list of countries and subregions that's not easily listed here so i'm using one innate to the data

Here is the map:

library(rnaturalearthdata)
library(dplyr)
library(ggplot2)
library(sf)

data1<-ne_countries(scale=50)
crs_target<-st_crs ("+proj=moll +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84")
data2<-data1%>%
   sf::st_transform(crs=crs_target)
africa<-subset(data2,continent=='Africa')
westA<-africa%>%
  group_by(region_wb)%>%
  summarise()

ggplot()+
   #geom_tile(data=test_cou,aes(x=x,y=y,fill=Biome))+   #My biome data- not relavant
  geom_sf(data=westA,fill=NA,aes(colour=region_wb),linewidth=1.1)+
  scale_color_manual(values=c('chocolate3','red3','purple3','maroon3','blue3'))+
  #scale_fill_manual(values=viridis(12))+
   guides(color=guide_legend(position='bottom',direction='horizontal')
         )

Here is my current map- you can see how the red region (Middle Africa) is hard to see because of the blue and purple colors next to it. Any ideas? enter image description here


Solution

  • Here's two of a number of potential solutions, both will require some trial and error. First, some points:

    • in your repex, region_wb should be subregion;
    • when using summarise() or summarise(geometry = st_union(geometry)), internal polygon 'holes' were generated. Not sure why your repex does not show this, but it may be due to different package versions. Either way, I have used the nngeo package function st_remove_holes() to correct this.

    There are other options, such as using st_intersection() to get the shared borders and then st_cast()ing them to LINESTRING, but the ne_countries(scale = 50) data requires a lot of cleaning to get a satisfactory result. So let's keep it simple:

    Load packages and generate data:

    library(dplyr)
    library(nngeo) # Remove internal 'holes', also loads the sf package
    library(rnaturalearth)
    library(ggplot2)
    
    westA <- ne_countries(scale = 50) %>%
      st_transform("+proj=moll +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84") %>%
      filter(continent == "Africa") %>%
      group_by(subregion) %>%
      summarise(geometry = st_union(geometry)) %>%
      st_make_valid() %>%
      st_remove_holes() %>%
      ungroup()
    

    Method 1: Dashed lines

    • pros: preserves line width e.g. linewidth = 1.1
    • cons: requires a lot of trial and error with changing colours and linetype to get a clear result
    ggplot() +
      geom_sf(data = westA, 
              aes(colour = subregion),
              fill = NA,
              linewidth = 1.1) +
      geom_sf(data = westA, 
              aes(colour = subregion),
              fill = NA,
              linewidth = 1.1,
              linetype = "dashed") +
      scale_color_manual(name = "Subregions",
                         values=c("chocolate3", "red3", "purple3", "maroon3", "blue3")) +
      guides(color = guide_legend(position = "bottom",
                                  direction = "horizontal",
                                  title.position = "top"))
    

    Method 1 result:

    method1

    Method 2: Negative buffer as suggested by @VinceGreg

    • pros: much clearer demarcation of borders than method 1
    • cons: difficult to replicate line width. Only borders will be linewidth = 1.1, external boundaries appear thinner. A lot of trial and error to find a good buffer distance to match desired linewidth
    westA1 <- st_buffer(westA, -16000)
    
    ggplot() +
      geom_sf(data = westA,
              aes(colour = subregion),
              fill = NA,
              linewidth = 0.65) +
      geom_sf(data = westA1, 
              aes(colour = subregion),
              fill = NA,
              linewidth = .65) +
      scale_color_manual(name = "Subregions",
                         values=c("chocolate3", "red3", "purple3", "maroon3", "blue3")) +
      guides(color = guide_legend(position = "bottom",
                                  direction = "horizontal",
                                  title.position = "top"))
    

    Method 2 result:

    method2