ggplot2
ggplot2 copied to clipboard
More aesthetics for `geom_label()`
Updates to geom_label()'s aesthetics and parameters
- The legend keys for
geom_label()now have the same rounded rectangle shape and aesthetics as the labels. (See questions below)
Old: New:
-
Deprecated
label.size. Uselinewidthinstead, which is more consistent with the rest of ggplot. ~~Iflinewidthis not set, it'll fall back tolabel.size, so nothing should break.~~ Update: setting up a soft deprecation fallback was too messy when going from an argument to an aesthetic, so I fully deprecatedlabel.size. -
New aesthetic:
linetypefor the label border. Default: "solid". Addresses #5365 -
New parameter:
border_colouris the color of the label's border and is decoupled from the text color. If the value isNULLor not set, it will fall back to the text color, which is how it worked previously. Hopefully, that will avoid any breaking changes. -
I added three examples to the docs. The mtcars graphs are too crowded to show off all the new aesthetics, so I made a dummy dataset for the latter two:
ggplot(mtcars, aes(wt, mpg, label = rownames(mtcars))) +
geom_label(
aes(fill = factor(cyl), linetype = qsec < 15),
border_colour = "black", color = "white", linewidth = 1) +
scale_linetype_manual(values=c("solid", "blank"), limits = TRUE, labels = "1/4 mi < 15s", name = NULL)
# Add aesthetics to the border for geom_label
data.frame(x = 1:10, y = 1:10) |>
ggplot() +
geom_label(aes(
label=month.abb[x],
x=x,
y=y,
color = factor(x%%3),
linewidth = x%%2,
linetype = factor(x%%3)),
fill = NA) +
scale_linewidth(range=c(0.5, 1.5)) +
scale_linetype_manual(values=c("solid", "blank", "dotted"))
# Override the border color
data.frame(x = 1:10, y = 1:10) |>
ggplot() +
geom_label(aes(
label=month.abb[x],
x=x,
y=y,
color = factor(x%%3),
linewidth=x%%2,
linetype=factor(x%%3)),
border_color = "red",
fill=NA) +
scale_linewidth(range=c(0.5, 1.5)) +
scale_linetype_manual(values=c("solid", "blank", "dotted"))
Questions:
- ~~Does the deprecated messaging look right for label.size?~~
- There is a bug where the key_glyph is cropped at the bottom. Can anyone help with that?
- ~~Are the aliases border_colour/border_color dealt with appropriately?~~
I've updated the comment at the beginning. See the two remaining questions.
I was looking for that exactly! Thank you very much.
I guess that before this PR, it was not possible to only apply the color to the label box?
After this PR, some work will be required in ggrepel to make geom_label_repel() understand that?
After this PR, some work will be required in ggrepel to make geom_label_repel() understand that?
The ggrepel package has always been free to do whatever it wants with labels, so there'd be no need to wait on this PR for that.
steveharoz requested a review from teunbrand 20 hours ago
I have to probe Thomas' wisdom on some points, I'll get back on this on tuesday/wednesday.
As a quick comment though: I don't think you'd need all the examples to show all the aesthetics. Most people should be familiar with what 'linewidth' is doing and how this might apply to textboxes. The border_colour and how it interacts with colour probably is worth having an example about.
@teunbrand Thanks. Fair point about the examples. How about these three? The last could be simpler, but I think it's a use case some people may want to copy.
p = ggplot(mtcars, aes(wt, mpg, label = rownames(mtcars)))
# If border.colour is NULL or not set, the border will use the text color
p + geom_label(aes(color = factor(cyl)))
# Alternatively, border.colour can have a static value
p + geom_label(aes(color = factor(cyl)), border.colour = "black")
# Use linetype and linewidth aesthetics to add highlights
p + geom_label(
aes(fill = factor(cyl), linetype = qsec < 15),
border.colour = "black", color = "white", linewidth = 1) +
scale_linetype_manual(values=c("solid", "blank"), limits = TRUE, labels = "1/4 mi < 15s", name = NULL)
Thanks @teunbrand!
If #5465 gets merged, we can fix the key glyph cropping issue.
I'm moving this PR back to a draft to consider to make border.colour into a propper aesthetic (https://blueviewer.pages.dev/view?actor=mjskay.com&rkey=3kbfolzxlpq2s). That would also allow for not needing to worry about the weird special fallback case where NULL uses the text color. Instead, both border.colour and (text)color could be mapped the same way.
Yeah that is exactly what Thomas and I were discussing, but we both thought making border.colour a proper aesthetic opens the floodgates for a new level of complexity that is difficult to understand and teach people. Here, as @bwiernik points out in the thread you linked, one could override a scale's aesthetics argument and that will work out fine. However, in #5423 there would be whisker_linetype aesthetics and linetype scales don't have an aesthetics argument. In fact, I'm planning to roll back the proper aesthetics in #5423 to fixed parameters because of this.
That would also allow for not needing to worry about the weird special fallback case where NULL uses the text color.
The mechanism already has precedence in geom_boxplot() with outlier.colour etc.
Personally, I find fixed parameters extremely frustrating because very quickly collaborators or audiences for plots that use them start to ask for data-dependent customization of them (eg, making the linetype for the whiskers match the linetype for the box or vary depending on whether they cross a threshold).
It would seem that a better solution in the case of #5423 would be to add the aesthetics argument to linetype scales
I agree that customization is key and having a coherent look across geoms would make plots much more readable i.e. linetype of a grouped boxplot follow the same logic as other mapped variable. but yeah this exponentiates possibilities and complexities! (I still liked when I could have the point linetype follow some aesthetics mapping in splus but alas in grid stroke of a point cannot have a different linetype...)
Off topic: @smouksassi You still can, but it is device dependent. With {ragg}:
library(grid)
grid.points(pch = 21, size = unit(2, "char"), gp = gpar(lty = 3), default.units = "npc")

Created on 2023-10-10 with reprex v2.0.2
Here is my proposal for the label key drawing function, now that #5465 has been merged.
draw_key_label <- function(data, params, size) {
if(is.null(params$label.padding)) params$label.padding <- unit(0.25, "lines")
if(length(params$label.padding) == 1) params$label.padding[2:4] <- params$label.padding
grob <- labelGrob(
label = data$label %||% "a",
x = 0.5,
y = 0.5,
padding = params$label.padding,
r = data$label.r %||% unit(0.1, "snpc"),
angle = data$angle %||% 0,
text.gp = gpar(
col = alpha(data$colour %||% "white", data$alpha),
fontfamily = data$family %||% "",
fontface = data$fontface %||% 1,
fontsize = (data$size %||% 3.88) * .pt
),
rect.gp = gpar(
col = alpha(params$border.colour %||% params$border.color %||% data$colour %||% "black", data$alpha),
fill = alpha(data$fill %||% "white", data$alpha),
lty = data$linetype,
lwd = len0_null(data$linewidth * .pt)
),
vp = NULL
)
# Linewidth as cm, twice because it is on two sides of textbox
lwd <- (data$linewidth %||% params$label.size) * .pt * 0.2
# Get width/height of text. Margins are baked in.
width <-
convertWidth(widthDetails(grob$children[[2]]), "cm", valueOnly = TRUE)
height <-
convertHeight(heightDetails(grob$children[[2]]), "cm", valueOnly = TRUE)
attr(grob, "width") <- width + lwd
attr(grob, "height") <- height + lwd
grob
}
Is there any chance this will be merged for this release? or will this get looked at after release?
@olivroy This PR is currently set as a draft (so we cannot merge it) and the release process has been kicked off. While ideally this PR would have been flagged in the 3.5.0 milestone, it is currently not too late, as we haven't started the reverse dependency check yet (see https://github.com/tidyverse/ggplot2/issues/5588). However, we will start that soon, so I would expect it will not be included in 3.5.0.
Been busy with other things. But I'll try to work on it this weekend.
There is no rush, I'm reasonably sure that the upcoming release would have to be patched afterwards so we can include it in that release. If someone needs these features right this instant, they can extend geom_label() if they want.