tmap icon indicating copy to clipboard operation
tmap copied to clipboard

`component.position` of `tm_layout` seems not to work

Open Nowosad opened this issue 6 months ago • 3 comments

I've tried to use component.position of tm_layout to place all of the components in the same place -- but it seems not to work. Reprex:

library(tmap)
data("land")
tm_shape(land[1,]) +
  tm_raster() +
  tm_scalebar() +
  tm_layout(component.position = tm_pos("left", "center"))
#> [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 = FALSE` to disable rescaling.
#> Scale bar set for latitude km and will be different at the top and bottom of the map.

Nowosad avatar Jun 11 '25 15:06 Nowosad

Good one.

component.position is currently only used as fallback option, in case x.position is not specified inside the options. However, most component types have those options specified, e.g. legend.position and scalebar.position.

But that's not very user friendly. In the past I've created the shortcut functions like tm_place_legends_bottom, but these only apply to legends, not other map components.

What do we do?

  1. Let component.position (when specified via tm_layout) overrule all x.position defaults.
  2. Add a shortcut tm_place_components(pos)
  3. ...

I guess that option 1 makes more sense from a user perspective. However, I image that it's also desirable to have the legends in a different corner than the other map components.

mtennekes avatar Jun 17 '25 12:06 mtennekes

@Nowosad an idea:

Shall we limit the argument list of tm_layout to those that are intended to be specified, i.e. the "overall" layout options? So all those related to frame, bg, panel, etc.

Now tm_layout is full of default settings, e.g. legend.text.size, which is the default for all legend text sizes. Normally, this is specified in the tm_legend calls.

For changing the default options, one can always use tm_options. For backwards compatibility we can have ... in tm_layout for the non-overall layout options.

We can still have the most useful 'default' settings in tm_layout, e.g. component.position`.

mtennekes avatar Jun 18 '25 08:06 mtennekes

@mtennekes -- I think this limitation is a good idea. I've been recently working on the layout chapter, and looking at the v. long list of arguments was a bit overwhelming.

Nowosad avatar Jun 18 '25 14:06 Nowosad

Way better now :-)

Just a start:

> formals(tm_layout) |> names()
 [1] "scale"                       "asp"                         "bg"                          "bg.color"                    "outer.bg"                   
 [6] "outer.bg.color"              "frame"                       "frame.color"                 "frame.alpha"                 "frame.lwd"                  
[11] "frame.r"                     "frame.double_line"           "outer.margins"               "inner.margins"               "inner.margins.extra"        
[16] "meta.margins"                "meta.auto_margins"           "between_margin"              "panel.margin"                "panel.type"                 
[21] "panel.wrap.pos"              "panel.xtab.pos"              "color.sepia_intensity"       "color.saturation"            "color_vision_deficiency_sim"
[26] "panel.show"                  "panel.labels"                "panel.label.size"            "panel.label.color"           "panel.label.fontface"       
[31] "panel.label.fontfamily"      "panel.label.alpha"           "panel.label.bg"              "panel.label.bg.color"        "panel.label.bg.alpha"       
[36] "panel.label.frame"           "panel.label.frame.color"     "panel.label.frame.alpha"     "panel.label.frame.lwd"       "panel.label.frame.r"        
[41] "panel.label.height"          "panel.label.rot"             "earth_boundary"              "earth_boundary.color"        "earth_boundary.lwd"         
[46] "earth_datum"                 "space.color"                 "..."  

So all element related arguments (defaults) have been removed (these can still be set via tm_options). However, as you can see there is one 'element' that does not have a stand-alone tm_ function: the panels. Would it make sense to migrate them to tm_panels?

Please review @Nowosad @Rapsodia86 @jguelat @aonojeghuo others. Are there arguments missing? Are there arguments that could be removed? And what are your thoughts about panels?

mtennekes avatar Jun 19 '25 07:06 mtennekes

Other question: are there default options, that apply to all elements, such as text.fontfamily, that are worthwhile to have in tm_layout?

About the initial topic of this issue, the position of all map components: would it be handy to set them like this:

library(tmap)
data("land")
tm_shape(land[1,]) +
  tm_raster() +
  tm_scalebar() +
  tm_comp_group(position = tm_pos("left", "center"))

So calling comp_group without a group_id specification will apply to all components. Do you like this idea?

mtennekes avatar Jun 19 '25 07:06 mtennekes

@mtennekes

  1. Yes -- I think that general options, such as the text.fontfamily argument should be in tm_layout
  2. I like the tm_comp_group idea -- I think it should work as you suggested above
  3. Do we even need tm_panels? Cannot these options be specified either in some tm_facet function or just kept in tm_layout?

Nowosad avatar Jun 19 '25 07:06 Nowosad

Thx @Nowosad

  1. So text.fontfamily and text.fontfacewill be included. Totally agree. Not sure which else. An edge case is attr.color. It determines the default color of all non-data objects, such as text and frame colors, and map components like compass and scalebar. See eg..
tm_shape(World) +
	tm_polygons("HPI") +
	tm_crs("auto") +
	tm_scalebar() +
	tm_title("World") +
	tm_layout(text.fontface = "italic", attr.color = "pink")
  1. Yes, we can keep them in tm_layout

mtennekes avatar Jun 19 '25 08:06 mtennekes

A bit disruptive idea: @mtennekes what do you think about renaming tm_comp_group to tm_components? (Maybe then atrr.color can go there)

Nowosad avatar Jun 19 '25 08:06 Nowosad

Renaming is fine with me. Another advantage is that there is also tm_group() which may be confusing with tm_com_group.

I would still like to keepattr.color in tm_layout, mainly because it also affects the frame color (which is not a component).

mtennekes avatar Jun 19 '25 09:06 mtennekes

Hi everyone. @mtennekes for the panels, I think they should be set through tm_facet if the panel defaults are to be changed. The need to adjust panels is more to do with the appearance of facets in grouped plots. Another thought: I noticed that tmap V3 on Windows only allowed 3 default font families. Is there any upgrade like you did with cols4all that automatically provides a wider range of fonts without needing to load extrafonts?

aonojeghuo avatar Jun 19 '25 14:06 aonojeghuo

Working. Also I had the idea to apply tm_components to all legends, all charts or all other components. So did that:

# the default for group_id is "ALL"

tm_shape(World) +
	tm_polygons("HPI", lwd = "footprint") +
	tm_compass() +
	tm_credits("Some text") +
	tm_components(position = tm_pos_out("left", "center"))

Image

tm_shape(World) +
	tm_polygons("HPI", lwd = "footprint") +
	tm_compass() +
	tm_credits("Some text") +
	tm_components("LEGENDS", position = tm_pos_out("left", "center")) +
	tm_components("OTHERS", position = tm_pos_out("center", "bottom"))

Image

Open questions:

  • If there are conflicts, e.g. a position defined via a specific group_id is different from a position defined via "LEGENDS"? The first, the latter, or the one that is called last?
  • We could also have a special value for titles. Now they fall under "OTHERS".

Hi everyone. @mtennekes for the panels, I think they should be set through tm_facet if the panel defaults are to be changed. The need to adjust panels is more to do with the appearance of facets in grouped plots.

True, but panels could also be useful for single maps, and in case facets are generated without using tm_facets explicitly, it may not be clear that tm_facets() is used rather than tm_layout. The former is used to specify how the facets are generated and not so much the appearance.

Another thought: I noticed that tmap V3 on Windows only allowed 3 default font families. Is there any upgrade like you did with cols4all that automatically provides a wider range of fonts without needing to load extrafonts?

Could you refresh my memory? Where did you find that? I can't recall that we did anything with fonts in cols4all

mtennekes avatar Jun 19 '25 15:06 mtennekes

We could also have a special value for titles. Now they fall under "OTHERS".

So, is it only for a legend that has this special value (LEGENDS)? Or does each component have its own? Is it somewhere explained in the documentation, or is it a new thing? Sorry, there have been many changes recently in this grouping etc, it is easy to lose track. But those changes are needed for improvement, so thank you for your hard work:)

Working. Also I had the idea to apply tm_components to all legends, all charts or all other components.

I really like the idea of having the option for the placement: all in one!

Rapsodia86 avatar Jun 19 '25 15:06 Rapsodia86

Could you refresh my memory? Where did you find that? I can't recall that we did anything with fonts in cols4all

To clarify: You needed RColorBrewer for colors in the past. With cols4all in version 4, you now have a wide range of color selections for mapping built in. I meant that having an extension similar to this but for fonts specifically could be great as the default is just 3 font families.

aonojeghuo avatar Jun 19 '25 15:06 aonojeghuo

tm_shape(World) + tm_polygons("HPI", lwd = "footprint") + tm_compass() + tm_credits("Some text") + tm_components("LEGENDS", position = tm_pos_out("left", "center")) + tm_components("OTHERS", position = tm_pos_out("center", "bottom")) Image

Open questions:

  • If there are conflicts, e.g. a position defined via a specific group_id is different from a position defined via "LEGENDS"? The first, the latter, or the one that is called last?

I think the one that is called last should be used. This allows all other components to retain their position specified by the group_id ("ALL") and only the positioning for "LEGENDS" changes. This is what I mean:

 tm_shape(World) +
tm_polygons("HPI", lwd = "footprint") +
tm_compass() +
tm_credits("Some text") +
tm_components( position = tm_pos_out("left", "center")) +
tm_components("LEGENDS", position = tm_pos_out("center", "bottom"))

Could we add a scale bar to the tests to ensure it still works when the positions of a few components are changed?

On positions in general, I could combine text and numbers (e.g. c("left",0.1)) in the past but this is no longer possible with tmap V4. Is there a different way to specify it or a default number equivalent to "left", for example? It comes in handy when you need to align components to a side but at various heights.

aonojeghuo avatar Jun 19 '25 15:06 aonojeghuo

On positions in general, I could combine text and numbers (e.g. c("left",0.1)) in the past but this is no longer possible with tmap V4. Is there a different way to specify it or a default number equivalent to "left", for example? It comes in handy when you need to align components to a side but at various heights.

See this: https://github.com/r-tmap/tmap/issues/539#issuecomment-2596897887

Rapsodia86 avatar Jun 19 '25 16:06 Rapsodia86

On positions in general, I could combine text and numbers (e.g. c("left",0.1)) in the past but this is no longer possible with tmap V4. Is there a different way to specify it or a default number equivalent to "left", for example? It comes in handy when you need to align components to a side but at various heights.

See this: #539 (comment)

Thanks @Rapsodia86

aonojeghuo avatar Jun 19 '25 19:06 aonojeghuo

@mtennekes, have you considered slightly different options for the group_id argument? I do not know why exactly, but I am not fully sold on "LEGENDS", etc. One alternative I could think of was to list the functions we want to group together, e.g.:

tm_components("tm_legend", position = tm_pos_out("left", "center"))

tm_components(c("tm_scalebar", "tm_compass"), position = tm_pos_out("left", "center"))

I do not know if this is more intuitive, though. Maybe someone else has other ideas...

Nowosad avatar Jun 20 '25 12:06 Nowosad

Absolutely that list is better/more intuitive than "LEGENDS" etc.!

Rapsodia86 avatar Jun 20 '25 13:06 Rapsodia86

Haven't thought about that option @Nowosad I like this! Perhaps for lazy users (like me:-) we can still have something to group all 'other' components, maybe "tm_<other>"? I'll give it a go.

About priority: I'll give priority to the last called. So in case tm_components is called without group_id, so for all components, it will overrule all previously set tm_components. This is needed because otherwise it is not possible to update the map (same behaviour for tmap options).

@Rapsodia86 Yes totally agree that it is confusing with all the changes, also for me:-) But I think we do a good thing here. I'll update the vignettes when it is settled.

@aonojeghuo It would be great to have as many fonts available. However, I don't know how to do that, because to the best of my knowledge this is system dependent. It deserves a separate issue.

mtennekes avatar Jun 20 '25 15:06 mtennekes

@mtennekes -- maybe a solution could be to (as for color palettes) have a minus sign operator, e.g., tm_components("tm_legend", position = tm_pos_out("left", "center")) -- legend only, but tm_components("-tm_legend", position = tm_pos_out("left", "center")) -- everything except the legend

Nowosad avatar Jun 20 '25 18:06 Nowosad

Working now. Please test:

tm = tm_shape(World) +
	tm_polygons("HPI", lwd = "footprint", lwd.legend = tm_legend(group_id = "hpi")) +
	tm_compass() +
	tm_credits("Some text", group_id = "cre")

tm

tm + 
	tm_components("hpi", position = tm_pos_out("right", "center"))

tm + 
	tm_components("hpi", position = tm_pos_out("right", "center")) +
	tm_components("tm_legend", position = tm_pos_out("left", "center"))

tm + 
	tm_components("hpi", position = tm_pos_out("right", "center")) +
	tm_components("tm_legend", position = tm_pos_out("left", "center")) +
	tm_components(position = tm_pos_in("left", "top"))

tm + 
	tm_components("hpi", position = tm_pos_out("right", "center")) +
	tm_components("tm_legend", position = tm_pos_out("left", "center")) +
	tm_components(position = tm_pos_in("left", "top")) +
	tm_components("cre", position = tm_pos_in("center", "bottom"))

Excellent idea @Nowosad to include the minus sign. Also easy to implement.

mtennekes avatar Jun 23 '25 12:06 mtennekes

@mtennekes looks good!

Nowosad avatar Jun 23 '25 13:06 Nowosad

Other question: are there default options, that apply to all elements, such as text.fontfamily, that are worthwhile to have in tm_layout?

Sorry for being late in the discussion... I usually prefer sharp corners for layout elements (sorry 🤭), it would be nice to have a general setting in tm_layout to set r=0 for all frames (map frame, legend, etc.). But I guess this would be actually a feature request since such an argument doesn't exist yet.

Oops, I posted before reading the rest of the discussion... I guess this should be possible using the frame.r argument inside tm_components(), did I understand correctly?

jguelat avatar Jun 24 '25 07:06 jguelat

Working now. Please test:

tm + tm_components("hpi", position = tm_pos_out("right", "center")) + tm_components("tm_legend", position = tm_pos_out("left", "center")) + tm_components(position = tm_pos_in("left", "top"))

Excellent idea @Nowosad to include the minus sign. Also easy to implement.

I'm not sure I understand the output of this one... Shouldn't the north arrow and the credits also move to "left, top"? They stay in the bottom right corner.

jguelat avatar Jun 24 '25 07:06 jguelat

I'm struggling a bit to understand what belongs to tm_layout and what should be in tm_components... Since both functions affect the general layout of the map, I find it a bit confusing to have 2 distinct functions. What do you think of having the tm_components function as an argument of tm_layout (similarly to tm_legend inside tm_polygon)?

Some repeated arguments are also a bit confusing for me (e.g. frame.r is both in tm_layout and tm_components).

jguelat avatar Jun 24 '25 07:06 jguelat

Working now. Please test: tm + tm_components("hpi", position = tm_pos_out("right", "center")) + tm_components("tm_legend", position = tm_pos_out("left", "center")) + tm_components(position = tm_pos_in("left", "top")) Excellent idea @Nowosad to include the minus sign. Also easy to implement.

I'm not sure I understand the output of this one... Shouldn't the north arrow and the credits also move to "left, top"? They stay in the bottom right corner.

It seems to work as expected using the current dev version of tmap. Could you recheck it on your side as well?

library(tmap)
library(spData)
data(World)
tm = tm_shape(World) +
  tm_polygons("HPI", lwd = "footprint", lwd.legend = tm_legend(group_id = "hpi")) +
  tm_compass() +
  tm_credits("Some text", group_id = "cre")

tm + 
  tm_components("hpi", position = tm_pos_out("right", "center")) +
  tm_components("tm_legend", position = tm_pos_out("left", "center")) +
  tm_components(position = tm_pos_in("left", "top"))

Created on 2025-06-24 with reprex v2.1.1

Nowosad avatar Jun 24 '25 08:06 Nowosad

I'm struggling a bit to understand what belongs to tm_layout and what should be in tm_components... Since both functions affect the general layout of the map, I find it a bit confusing to have 2 distinct functions. What do you think of having the tm_components function as an argument of tm_layout (similarly to tm_legend inside tm_polygon)?

Some repeated arguments are also a bit confusing for me (e.g. frame.r is both in tm_layout and tm_components).

Thanks for letting us know about this, @jguelat -- I think we need to make as clear as possible explanation of these two ideas and differences between them. Long story short, with tm_components -- you can modify visual style of one or more components (e.g., legends, scale bars, etc.), but you do not change the general map layout. With tm_layout you can change other, general map elements like the frame, inner and outer margins, etc.

Nowosad avatar Jun 24 '25 08:06 Nowosad

tm_components("hpi", position = tm_pos_out("right", "center"))

"right"? Why to set this if then everything is sent to the left? Or is it not the expected outcome?

Rapsodia86 avatar Jun 24 '25 08:06 Rapsodia86

This is what I'm getting with the current dev version:

Image

jguelat avatar Jun 24 '25 08:06 jguelat

tm_components("hpi", position = tm_pos_out("right", "center"))

"right"? Why to set this if then everything is sent to the left? Or is it not the expected outcome?

I think it was just an example to show that the last positioning has priority

jguelat avatar Jun 24 '25 08:06 jguelat