Search code examples
r-gridtmap

Draw multiple object of class tmap with just one common legend


I am trying to draw multiple maps from the tmap-package using tm_shape() and tm_layout() in one page using grid.layout() from the grid-package. I would like to plot only one common legend for all maps, similar to the example shown here:

ggplot separate legend and plot

Unfortunately, tmap does not provide a ggplot-object. Does anybody know how to do the same with tmaps? Here is a reproducible example:

data(World, rivers, metro)

# creating two separate maps
africa <- World[World@data$continent=='Africa',]
asia <- World[World@data$continent=='Asia',]

my.breaks <- seq(0,80,20)

africa.map <- tm_shape(africa) + 
  tm_fill("HPI",style = 'fixed',breaks = my.breaks) + 
  tm_layout(bg.color = "white", legend.text.size = 1.3, legend.width = 0.6,
            legend.outside=TRUE, legend.outside.position = 'top', 
            legend.outside.size = .1, legend.position = c(0.8, 0.2))

asia.map <- tm_shape(asia) + 
  tm_fill("HPI",style = 'fixed',breaks = my.breaks) + 
  tm_layout(bg.color = "white", legend.text.size = 1.3, legend.width = 0.6,
            legend.outside=TRUE, legend.outside.position = 'top', 
            legend.outside.size = .1, legend.position = c(0.8, 0.2))

page.layout <- grid.layout(nrow = 8, ncol = 5,
                           widths = unit(c(1), "null"),
                           heights = unit(c(1), "null"),
                           default.units = "null",
                           respect = FALSE,
                           just = "centre")

grid.newpage()
pushViewport(viewport(layout = page.layout))
grid.text(paste('Happy Planet Index'), 
          vp = viewport(layout.pos.row = 1, layout.pos.col = 1:5),gp=gpar(fontsize=20))

grid.text('Africa', vp = viewport(layout.pos.row = 2, layout.pos.col = 1:2),gp=gpar(fontsize=20))
print(africa.map, vp=viewport(layout.pos.row = 3:6, layout.pos.col = 1:2))

grid.text('Asia', vp = viewport(layout.pos.row = 2, layout.pos.col = 3:5),gp=gpar(fontsize=20))
print(asia.map, vp=viewport(layout.pos.row = 3:6, layout.pos.col = 3:5))

Best, Erich


Solution

  • I don't know if this is what you mean:

    data(World)
    
    tm_shape(World) +
        tm_polygons(c("economy", "HPI")) +
        tm_layout(legend.outside = TRUE)
    

    The common legend is plotted outside the maps. Since both maps have different legends, only the legend of the first map is taken.

    There are also ways to use grid.layout. In that case, you'll need to print tmap maps in grid viewports (see print.tmap), and the legend in another one.

    UPDATE:

    Just to be complete: normally, there should be a way to do this with facets:

    data(World)
    AfAs <- World[World@data$continent %in% c('Africa', 'Asia'),]
    tm_shape(AfAs) + 
        tm_fill("HPI",style = 'fixed',breaks = my.breaks) + 
        tm_facets(by = "continent", drop.units = TRUE, free.coords = TRUE)
    

    If you need to plot two tmap calls, it is most convenient to process the legend as a separate map, with legend.only=TRUE:

    africa.map <- tm_shape(africa) + 
        tm_fill("HPI",style = 'fixed',breaks = my.breaks) +
        tm_layout(legend.show = FALSE)
    
    asia.map <- tm_shape(asia) + 
        tm_fill("HPI",style = 'fixed',breaks = my.breaks) +
        tm_layout(legend.show = FALSE)
    
    legend.map <- tm_shape(africa) + 
        tm_fill("HPI",style = 'fixed',breaks = my.breaks) +
        tm_layout(legend.only = TRUE)
    
    grid.newpage()
    page.layout <- grid.layout(nrow = 2, ncol = 2, widths=c(.4,.6), heights=c(.6,.4))
    pushViewport(viewport(layout = page.layout))
    
    print(africa.map, vp=viewport(layout.pos.row = 1:2, layout.pos.col = 1))
    print(asia.map, vp=viewport(layout.pos.row = 1, layout.pos.col = 2))
    print(legend.map, vp=viewport(layout.pos.row = 2, layout.pos.col = 2))
    

    UPDATE 2

    The legend can be enlarged with this code:

    legend.map <- tm_shape(africa) + 
        tm_fill("HPI",style = 'fixed',breaks = my.breaks) +
        tm_layout(legend.only = TRUE, design.mode=TRUE, scale=2, asp=0)
    

    design.mode=TRUE is handy when designing a map. I had to set asp to 0, because it took the aspect ratio of Africa: this is an error, since the aspect ratio should follow the device/viewport when legend.only=TRUE. This is fixed in the devel version.