r-tmap / tmap

R package for thematic maps
https://r-tmap.github.io/tmap
GNU General Public License v3.0
856 stars 119 forks source link

Alignment of plots with tmap_arrange() with different legend size #907

Closed olivroy closed 2 weeks ago

olivroy commented 1 month ago

Something else that I noticed was the alignment of legends (left = v3, right = v4) image

Originally posted by @olivroy in https://github.com/edzer/sdsr/issues/119#issuecomment-2250616905

data(pol_pres15, package = "spDataLarge")
m3 <- tm_shape(pol_pres15) +
  tm_fill("hs_C", 
    palette = RColorBrewer::brewer.pal(4, "Set3")[c(4, 1, 3, 2)],
    colorNA = "grey95", textNA = "Not \"interesting\"",
    title = "First round turnout\nLocal Geary C") +
    tm_layout(legend.outside=TRUE, legend.outside.position="bottom")
m4 <- tm_shape(pol_pres15) +
  tm_fill("hs_C_II", 
    palette = RColorBrewer::brewer.pal(4, "Set3")[c(4, 1, 3, 2)], 
    colorNA = "grey95", textNA = "Not \"interesting\"",
    title="Second round turnout\nLocal Geary C") +
    tm_layout(legend.outside=TRUE, legend.outside.position="bottom")
m5 <- tm_shape(pol_pres15) +
  tm_fill("hs_MvC", 
    palette = RColorBrewer::brewer.pal(4, "Set3")[c(4, 1)],
    colorNA = "grey95", textNA = "Not \"interesting\"",
    title = "Both rounds turnout\nLocal Multivariate Geary C") +
    tm_layout(legend.outside=TRUE, legend.outside.position="bottom")
tmap_arrange(m3, m4, m5, nrow=1)
mtennekes commented 1 month ago

tmap_arrange does not use align layouts of multiple maps. The maps are rendered independently. The difference is that in v3, the outside-legend-margin was fixed, and in v4, this is adjusted to the map aspect ratio and legend size. The downside of this improvement is illustrated by the misalignment in this example. This can be fixed by setting the margin in which the legend is drawn manually. It is called the 'meta.margins' where the first value is bottom margin:

m3 = tm_shape(pol_pres15) +
    tm_fill("hs_C",
            fill.scale = tm_scale_categorical(values = cols4all::c4a("brewer.set3", order = c(4, 1, 3, 2)),
                                              value.na = "grey95",
                                              label.na = "Not \"interesting\""),
            fill.legend = tm_legend("First round turnout\nLocal Geary C", position = tm_pos_out("center", "bottom"))) +
    tm_layout(meta.margins = c(.2, 0, 0, 0))

m4 = tm_shape(pol_pres15) +
    tm_fill("hs_C_II",
            fill.scale = tm_scale_categorical(values = cols4all::c4a("brewer.set3", order = c(4, 1, 3, 2)),
                                              value.na = "grey95",
                                              label.na = "Not \"interesting\""),
            fill.legend = tm_legend("Second round turnout\nLocal Geary C", position = tm_pos_out("center", "bottom"))) +
    tm_layout(meta.margins = c(.2, 0, 0, 0))
m5 <- tm_shape(pol_pres15) +
    tm_fill("hs_MvC", 
            fill.scale = tm_scale_categorical(values = cols4all::c4a("brewer.set3", order = c(4, 1)),
                                              value.na = "grey95",
                                              label.na = "Not \"interesting\""),
            fill.legend = tm_legend("Both rounds turnout\nLocal Multivariate Geary C", position = tm_pos_out("center", "bottom"))) +
    tm_layout(meta.margins = c(.2, 0, 0, 0))

tmap_arrange(m3, m4, m5, nrow=1)
#> [plot mode] fit legend/component: Some legend items or map compoments do not fit well, and are therefore rescaled. Set the tmap option 'component.autoscale' to FALSE to disable rescaling.
#> [plot mode] fit legend/component: Some legend items or map compoments do not fit well, and are therefore rescaled. Set the tmap option 'component.autoscale' to FALSE to disable rescaling.

In case the legend is shared (not sure if that is supposed to be in this example, you can set the .free argument, and use panel labels:

tm_shape(pol_pres15) +
    tm_fill(c("hs_C", "hs_C_II", "hs_MvC"),
            fill.scale = tm_scale_categorical(values = cols4all::c4a("brewer.set3", order = c(4, 1, 3, 2)),
                                              value.na = "grey95",
                                              label.na = "Not \"interesting\""),
            fill.free = FALSE,
            fill.legend = tm_legend("Local Geary C", position = tm_pos_out("center", "bottom"))) +
    tm_layout(panel.labels = c("First round turnout", "Second round turnout", "Both rounds turnout"))