tidyverse / ggplot2

An implementation of the Grammar of Graphics in R
https://ggplot2.tidyverse.org
Other
6.45k stars 2.02k forks source link

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

Closed teunbrand closed 9 months ago

teunbrand commented 3 years ago

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?

twest820 commented 2 years ago

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.

teunbrand commented 2 years ago

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 commented 2 years ago

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:

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.

georgeblck commented 1 year ago

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"))
teunbrand commented 9 months ago

This was fixed by #5456.