ggplot2 icon indicating copy to clipboard operation
ggplot2 copied to clipboard

guide_legend ignores legend.spacing.(x/y) depending on byrow

Open teunbrand opened this issue 3 years ago • 3 comments

Hello there,

I apologise in advance if I submit issues too frequently; I do really appreciate all the good work you put into ggplot2. By default, a legend appears to ignore the legend.spacing.y theme setting.

library(ggplot2)

p <- ggplot(mtcars, aes(mpg, fill = as.factor(cyl))) +
  geom_density() +
  theme(legend.spacing.y = unit(0.5, "cm"))
p

However, when byrow = TRUE in the guide, the legend.spacing.y is applied as expected.

p + guides(fill = guide_legend(byrow = TRUE))

Created on 2021-02-22 by the reprex package (v1.0.0)

For horizontal legend layouts, legend.spacing.x is ignored when byrow = TRUE, which is opposite to the vertical case.

ggplot(mtcars, aes(mpg, fill = as.factor(cyl))) +
  geom_density() +
  theme(legend.spacing.x = unit(0.5, "cm"),
        legend.position = "bottom") +
  guides(fill = guide_legend(label.position = "top",
                             byrow = TRUE))

I was surprised by this as I would have expected the legend.spacing to be applied regardless of the layout order of the keys, but I'm likely not aware of the considerations going in to this. I believe the code controlling the legend spacing and gaps sometimes interleave()s the label/key sizes with spacings, and sometimes doesn't, conditional on the byrow argument.

In an earlier comment (https://github.com/tidyverse/ggplot2/issues/3587#issuecomment-546700387), it was proposed to get rid of the legend spacing, but I think there is a use-case for the spacing for the keys (the text margins maybe less so). Would it be a good idea to always interleave the spacing regardless of the byrow argument?

teunbrand avatar Feb 22 '21 22:02 teunbrand

I think there is a use-case for the spacing for the keys (the text margins maybe less so).

100% agree. So far as I know, byrow = TRUE is the only way to space out the keys of a vertical fill legend without having the key expand from a square into a rectangle. Since I depend on this feature it would really suck to have it cut (unless there is some other mechanism to avoid things like too closely packed lines text in vertical fill legends with multiline labels).

There's loads of questions about this on Stackoverflow and all the answers there about introducing spacing between legend keys using legend.spacing{.x, .y}, keywidth, and keyheight don't work for fill legends because they're all for geom_point() markers.

By default, a legend appears to ignore the legend.spacing.y theme setting.

As I understand it, by default legend.spacing.y applies only between the legend title and the first key of a vertical legend. E.g. between as.factor(cyl) and the four cylinder key in the example above. I think what byrow = TRUE then does is apply the spacing to every key in a vertical legend.

It's uncommon, but occasionally I find need to use legend.spacing.y with the default byrow = FALSE to get the spacing between a legend title and first key to look better.

For horizontal legend layouts, legend.spacing.x is ignored when byrow = TRUE, which is opposite to the vertical case.

It's probably not getting ignored. What's probably happening (and what I've seen in earlier versions of ggplot but haven't checked recently) is the spacing is applied between a horizontal legend and the plot. It's just harder to see because a horizontal legend has the title to the side and only one row of keys.

In general, my experience is it's cryptic which legend settings adjust sizing where under which circumstances. If there were figures in the documentation diagramming out how settings apply across different legend configurations what the default spacings and units are that would probably help considerably. But, if these exist, I don't really know where they're included. You can figure some of it out from the examples in various parts of the documentation (and answers to Stackoverflow questions) but these are at the level of individual settings rather than a master diagram.

twest820 avatar Nov 17 '21 17:11 twest820

It's uncommon, but occasionally I find need to use legend.spacing.y with the default byrow = FALSE to get the spacing between a legend title and first key to look better.

Intuitively, I'd look for the legend.title = element_text(margin = margin(b = {your_margin_here}) to control the spacing between the legend title and the keys (vertically at least). I don't find it intuitive to control this through the legend.spacing argument, as I would expect this to control the spacing between keys.

the spacing is applied between a horizontal legend and the plot.

Similarly, I would expect this to be controlled by the legend.box.margin or legend.margin arguments to the theme.

If there were figures in the documentation diagramming out how settings apply across different legend configurations what the default spacings and units are that would probably help considerably.

I agree with that this would be nice to have somewhere.

teunbrand avatar Dec 29 '21 23:12 teunbrand

I've thought about this some more, and have some more concrete suggestions (that can lead to a visual change). Consider the following plots:

library(ggplot2)

plt <- ggplot(mpg, aes(displ, hwy, colour = as.factor(cyl))) +
  geom_point() +
  theme(
    legend.spacing.x = unit(0.5, "cm"),
    legend.spacing.y = unit(0.5, "cm")
  )

p1 <- plt + guides(colour = guide_legend(byrow = TRUE, ncol = 2)) +
  ggtitle("byrow = TRUE")
p2 <- plt + guides(colour = guide_legend(byrow = FALSE, ncol = 2)) +
  ggtitle("byrow = FALSE")
patchwork::wrap_plots(list(p1, p2))

Created on 2022-05-19 by the reprex package (v2.0.1)

Foremost, my issue is that we cannot control the (vertical) spacing between keys when byrow = FALSE. Additionally, we cannot really control spacing in the x-direction before and after the text. See left side in diagram below (forgive me my illustration skills). Similar issues occur with alternative legend/label position placements.

image

In the diagram I've placed some suggestions on how the situation could be improved:

  • Regardless of byrow setting, legend spacing arguments are always applied. Having had a look at the legend drawing code, it seems that this would simplify the code somewhat.
  • To prevent users from having to hustle with element_text(margin = ...), we can introduce a new theme element that controls the spacing of text to keys, e.g. legend.spacing.text. Alternatively, we can just set a default, like how title_fontsize is used below:

https://github.com/tidyverse/ggplot2/blob/d6f5bf4ac10e541d14aaaceb4a50096447a7e6ee/R/guide-legend.r#L331-L338

If this seems like a good idea to the ggplot2 maintainers, I can try to draft a PR to review.

teunbrand avatar May 19 '22 22:05 teunbrand

I was faced with this problem of needing to adjust my vertical space when using guides(colour=guide_legend(ncol=2,byrow=FALSE)). As you have described it is not possible to adjust the vertical spacing between the keys this way.


This SO answer actually worked very well for me, so it is maybe a solution?

guides(colour=guide_legend(ncol=2,byrow=FALSE,
                           keywidth=0.5,
                           keyheight=0.1,
                           default.unit="cm"))

georgeblck avatar Nov 17 '22 16:11 georgeblck

This was fixed by #5456.

teunbrand avatar Dec 01 '23 13:12 teunbrand