r-tmap/tmap

tm_pos_out() does not work as expected

Closed this issue · 8 comments

I have two maps:

m1 = srn_mcr_joined |>
  tm_shape() +
  tm_lines(
    col = c("foot"),
    lwd = 9,
    title = "Active Travel Potential",
    tm_scale(breaks = c(0, 10, 100, 1000, 10000), values = "viridis"),
    col.legend = tm_legend(position = tm_pos_out())
  )
m1
m2 = srn_mcr_joined |>
  tm_shape() +
  tm_lines(
    col = c("all", "foot", "bicycle", "dutch_slc"),
    lwd = 9,
    title = "Active Travel Potential",
    tm_scale(breaks = c(0, 10, 100, 1000, 10000), values = "viridis"),
    col.legend = tm_legend(position = tm_pos_out()),
    popup.vars = c("all", "foot", "bicycle", "dutch_slc")
  )
m2

I expected the same legend behaviour in the facet but the facetted map has legends inside where space is lacking:

image

Reproducible code: https://github.com/acteng/severance

Working my way through https://raw.githubusercontent.com/acteng/severance/main/README.qmd, but can't find srn_mcr_joined. Is it somewhere else?

It has to do with the fact that the facets are wrapped, usually over multiple rows and columns (all depends on available space and aspect ratios).

If only 2 or 3 facets are generated, they are wrapped in one row or column. And the legends will be drawn next to it:

tm_shape(World) + tm_polygons(c("HPI", "economy", "income_grp"))

image

However, with multiple rows and columns are used, legends are drawn inside:

tm_shape(World) + tm_polygons(c("HPI", "economy", "income_grp", "pop_est"))

image

You can force the legends to be wrapped in one row or column with what I called horizontal or vertical stack:

tm_shape(World) + tm_polygons(c("HPI", "economy", "income_grp", "pop_est")) + tm_facets_vstack()

image

I will look at your example tomorrow, and see how to improve this. By the looks of it: aren't the legends the same (except the title)?

I encountered another, but related bug, so will post it here

tm_shape(World) + tm_polygons("HPI", fill.free = TRUE) + tm_facets_hstack(by = "economy")

image

somehow, no row space is assigned to the legends. Works correctly with fill.free = FALSE

Many thanks for the detailed response Martijn, tm_facets_vstack() looks great. Regarding the example I provided, yes the legends should be the same, I couldn't figure out how to make tmap realise that they are the same and could be placed in a single space outside any of the frames.

Now a message will be shown when the user specifies tm_facets_wrap with per-facet legends and nrow or ncol being 1:

tm_shape(metro) +
	tm_dots(fill = c("pop1960", "pop1970", "pop1980", "pop1990"),
			fill.scale = tm_scale(breaks = c(0,5,15,20,35)*1e6),
			size = 0.5) +
	tm_facets_wrap(ncols = 4)

The new message is: [facets] use tm_facets_hstack() instead of tm_facets_wrap() to put the legends next to and aligned with the facets

The bug I posted has also been fixed.

The remaining issue is: how should the user specifies the legend position when these legends are aligned with the facets? Perhaps something liketm_pos_aligned. Will thing about this...

Amazing Martijn, thank you!

In answer to your question: not sure how the user should specify the legend position if they are aligned.

One more question for you: how to keep only one of the legends vs all, or maybe even legend associated with 1st and 2nd facet? That's a rather specific ask, the issue seems closed so closing and will look to test next week.

Enter the .free arguments :-)

When you assign multiple data variables, by default it assumes that they should have multiple scales (and therefore multiple legends). So the scales are 'free'. In ggplot2 this is all controlled via facet_grid(scales = ...). However, in tmap you can set this for each visual variable separately. That's why it is associated with a visual variable name, like fill.free. It normally takes 3 values, one for each faceting dimension. tm_facets_grid has two dimensions, whereas tm_facets_wrap and stack use one dimension. The third dimension is ony used when using pages/animation frames.

Is this clear? If so, could you check whether the documentation needs to be clearer? Or whether examples need to be added (perhaps the ones below)? (Also pinging @Nowosad )

tm_shape(metro) +
    tm_dots(fill = c("pop1960", "pop1970", "pop1980", "pop1990"),
            fill.scale = tm_scale(breaks = c(0,5,15,20,35)*1e6),
            size = 0.5) +
    tm_facets_hstack()

tm_shape(metro) +
    tm_dots(fill = c("pop1960", "pop1970", "pop1980", "pop1990"),
            fill.scale = tm_scale(breaks = c(0,5,15,20,35)*1e6),
            fill.legend = tm_legend("Population"),
            fill.free = FALSE,
            size = 0.5) +
    tm_facets_hstack()

Created on 2024-05-25 with reprex v2.1.0

Regarding you second question (a legend associated with only the 1st and 2nd map): this is not possible in one tmap call. So then you'll probably have to make two maps (one for the 1st and 2nd facet and one for the others), and stack them via tmap_arrange.

Hey Martijn, yes that's clear, and agree the flexibility of tmap_arrange() can help with more niche use cases, especially as tmap can generate legend-only outputs, as far as I recall. Great work!