Search code examples
rggplot2terra

How to set transparent plot background but fill map panel for Mollweide or Robinson projection


I would like to remove the white background that is created around the world map when plotting in Mollweide projection using theme_bw() as a base, in R. This is my code:

library(pacman)
p_load(terra, ggspatial, ggthemes, sf, tidyverse, viridis, cowplot)

# load world map
wrld <- ne_countries()

# reproject to mollweide
wrld_moll <- project(terra::vect(wrld), "+proj=moll +lon_0=0 +x_0=0 +y_0=0")

# transform as sf
wlrd_sf <- st_as_sf(wrld_moll)

# plot
p1 <- ggplot() +  
  
  # add world as a background
  geom_sf(data = wlrd_sf, fill = "grey90", col = "grey60", linewidth = 0.25)  +
  
  # add theme map 
  theme_bw() + 
  
  # personalize theme
  theme(axis.title = element_blank(),
        panel.border = element_rect(fill = NA,
                                    colour = "#238B45",
                                    linewidth = 2,
                                    inherit.blank = T)) 

p1

Which gives this map (I pasted the map on a blue background to show the difference with the next one): enter image description here

I try to remove the background by adding these lines to my personalized theme:

p2 <- p1 +   theme(axis.title = element_blank(),
                   plot.background = element_rect(fill = "transparent",
                                                  colour = NA_character_), # necessary to avoid drawing plot outline
                   panel.background = element_rect(fill = "transparent",
                                                   colour = NA_character_), # necessary to avoid drawing panel outline
                   panel.border = element_rect(fill = NA,
                                               colour = "#238B45",
                                               linewidth = 2,
                                               inherit.blank = T)) 

Which removes also the background of the world (eg seas, oceans), not only the background between the world and the panel: enter image description here

What I would like is those areas between the world map and the panel border (where I have put a blue big dot) to be transparent, but keeping oceans and seas white.

enter image description here

I think it is some theme_bw() setting but so far I cannot find/change it.


Solution

  • Updated with better polygon solution below, original gracticule solution first

    Klugey, but it works. It involves generating densely packed graticules to serve as the map extent panel background. I have coloured the rest of the panel background for illustrative purposes. I have also added some sparse graticules to replicate the default ones ggplot2 generates. Omit if not needed.

    If you need the panel background to fit the landmass extent more 'snugly', decrease the spacing in gracticules1 and then you can decrease its linewidth. For this example, I kept it simple and went with the default linewidth.

    library(rnaturalearth)
    library(sf)
    library(terra)
    library(dplyr)
    library(ggplot2)
    
    # Load world map, project to Mollweide
    world <- ne_countries() %>%
      st_transform("ESRI:54009")
    
    # Generate densely packed graticules for map extent panel background
    graticules1 <- st_graticule(lon = seq(-180,180, 1),
                                lat = seq(-90,90, 1)) %>%
      vect() %>%
      st_as_sf() %>%
      st_transform("ESRI:54009")
    
    # Generate map graticules
    graticules2 <- st_graticule(lon = seq(-180,180, 60),
                                lat = seq(-90,90, 30)) %>%
      vect() %>%
      st_as_sf() %>%
      st_transform("ESRI:54009")
    
    # Plot
    ggplot() +
      geom_sf(data = graticules1, colour = "white") +
      geom_sf(data = graticules2, colour = "grey75", linewidth = .25) +
      geom_sf(data = world, fill = "grey90", col = "grey60", linewidth = 0.25) +
      theme_bw() + 
      theme(axis.title = element_blank(),
            plot.background = element_rect(fill = "transparent", colour = NA),
            panel.background = element_rect(fill = "#56B4E9", colour = NA),
            panel.border = element_rect(fill = NA,
                                        colour = "#238B45",
                                        linewidth = 2,
                                        inherit.blank = TRUE))
    

    result

    Edit based on comment from @DavidRomano

    Here's a better approach that creates and uses a polygon instead of graticules to fill the panel background:

    # Create sf polygon using WGS84 extent coordinates then project to Mollweide
    bbox <- data.frame(lon = c(rep(-180, 180), seq(-180, 180, length.out = 100),
                               rep(180, 180), seq(180, -180, length.out = 100)),
                       lat = c(seq(-90, 90, 1), rep(90, 99), 
                               seq(90, -90, -1), rep(-90, 99))) %>%
      st_as_sf(coords = c("lon", "lat"), crs = 4326) %>%
      summarise(geometry = st_combine(geometry)) %>%
      st_cast("POLYGON") %>%
      st_transform("ESRI:54009")
    
    # Plot
    ggplot() +
      geom_sf(data = bbox, fill = "white", col = "white", linewidth = 0.25) +
      geom_sf(data = graticules2, colour = "grey75", linewidth = .25) +
      geom_sf(data = world, fill = "grey90", col = "grey60", linewidth = 0.25) +
      theme_bw() + 
      theme(axis.title = element_blank(),
            plot.background = element_rect(fill = "transparent", colour = NA),
            panel.background = element_rect(fill = "#56B4E9", colour = NA),
            panel.border = element_rect(fill = NA,
                                        colour = "#238B45",
                                        linewidth = 2,
                                        inherit.blank = TRUE))