Skip to content

Commit b9d1c79

Browse files
authored
✨ Justification for legend keys (#6279)
* combine glyph layers into a single key earlier * new `key.justification` theme element * apply key justification * add test * add news bullet
1 parent 09776db commit b9d1c79

File tree

7 files changed

+205
-15
lines changed

7 files changed

+205
-15
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,8 @@
292292
particularly for data-points with a low radius near the center
293293
(@teunbrand, #5023).
294294
* All scales now expose the `aesthetics` parameter (@teunbrand, #5841)
295+
* New `theme(legend.key.justification)` to control the alignment of legend keys
296+
(@teunbrand, #3669).
295297

296298
# ggplot2 3.5.1
297299

R/guide-legend.R

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ GuideLegend <- ggproto(
178178
key = "legend.key",
179179
key_height = "legend.key.height",
180180
key_width = "legend.key.width",
181+
key_just = "legend.key.justification",
181182
text = "legend.text",
182183
theme.title = "legend.title",
183184
spacing_x = "legend.key.spacing.x",
@@ -275,7 +276,6 @@ GuideLegend <- ggproto(
275276
c("horizontal", "vertical"), arg_nm = "direction"
276277
)
277278
params$n_breaks <- n_breaks <- nrow(params$key)
278-
params$n_key_layers <- length(params$decor) + 1 # +1 is key background
279279

280280
# Resolve shape
281281
if (!is.null(params$nrow) && !is.null(params$ncol) &&
@@ -378,6 +378,9 @@ GuideLegend <- ggproto(
378378
elements$key <-
379379
ggname("legend.key", element_grob(elements$key))
380380
}
381+
if (!is.null(elements$key_just)) {
382+
elements$key_just <- valid.just(elements$key_just)
383+
}
381384

382385
elements$text <-
383386
label_angle_heuristic(elements$text, elements$text_position, params$angle)
@@ -391,22 +394,39 @@ GuideLegend <- ggproto(
391394

392395
build_decor = function(decor, grobs, elements, params) {
393396

394-
key_size <- c(elements$width_cm, elements$height_cm) * 10
395-
396-
draw <- function(i) {
397-
bg <- elements$key
398-
keys <- lapply(decor, function(g) {
399-
data <- vec_slice(g$data, i)
400-
if (data$.draw %||% TRUE) {
401-
key <- g$draw_key(data, g$params, key_size)
402-
set_key_size(key, data$linewidth, data$size, key_size / 10)
403-
} else {
404-
zeroGrob()
397+
key_size <- c(elements$width_cm, elements$height_cm)
398+
just <- elements$key_just
399+
idx <- seq_len(params$n_breaks)
400+
401+
key_glyphs <- lapply(idx, function(i) {
402+
glyph <- lapply(decor, function(dec) {
403+
data <- vec_slice(dec$data, i)
404+
if (!(data$.draw %||% TRUE)) {
405+
return(zeroGrob())
405406
}
407+
key <- dec$draw_key(data, dec$params, key_size * 10)
408+
set_key_size(key, data$linewidth, data$size, key_size)
406409
})
407-
c(list(bg), keys)
408-
}
409-
unlist(lapply(seq_len(params$n_breaks), draw), FALSE)
410+
411+
width <- vapply(glyph, get_attr, which = "width", default = 0, numeric(1))
412+
width <- max(width, 0, key_size[1], na.rm = TRUE)
413+
height <- vapply(glyph, get_attr, which = "height", default = 0, numeric(1))
414+
height <- max(height, 0, key_size[2], na.rm = TRUE)
415+
416+
vp <- NULL
417+
if (!is.null(just)) {
418+
vp <- viewport(
419+
x = just[1], y = just[2], just = just,
420+
width = unit(width, "cm"), height = unit(height, "cm")
421+
)
422+
}
423+
424+
grob <- gTree(children = inject(gList(elements$key, !!!glyph)), vp = vp)
425+
attr(grob, "width") <- width
426+
attr(grob, "height") <- height
427+
grob
428+
})
429+
key_glyphs
410430
},
411431

412432
build_labels = function(key, elements, params) {
@@ -795,3 +815,7 @@ deprecated_guide_args <- function(
795815
}
796816
theme
797817
}
818+
819+
get_attr <- function(x, which, exact = TRUE, default = NULL) {
820+
attr(x, which = which, exact = exact) %||% default
821+
}

R/theme-elements.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,7 @@ el_def <- function(class = NULL, inherit = NULL, description = NULL) {
625625
legend.key.spacing = el_def(c("unit", "rel"), "spacing"),
626626
legend.key.spacing.x = el_def(c("unit", "rel"), "legend.key.spacing"),
627627
legend.key.spacing.y = el_def(c("unit", "rel"), "legend.key.spacing"),
628+
legend.key.justification = el_def(c("character", "numeric", "integer")),
628629
legend.frame = el_def("element_rect", "rect"),
629630
legend.axis.line = el_def("element_line", "line"),
630631
legend.ticks = el_def("element_line", "legend.axis.line"),

R/theme.R

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@
8484
#' between legend keys given as a `unit`. Spacing in the horizontal (x) and
8585
#' vertical (y) direction inherit from `legend.key.spacing` or can be
8686
#' specified separately. `legend.key.spacing` inherits from `spacing`.
87+
#' @param legend.key.justification Justification for positioning legend keys
88+
#' when more space is available than needed for display. The default, `NULL`,
89+
#' stretches keys into the available space. Can be a location like `"center"`
90+
#' or `"top"`, or a two-element numeric vector.
8791
#' @param legend.frame frame drawn around the bar ([element_rect()]).
8892
#' @param legend.ticks tick marks shown along bars or axes ([element_line()])
8993
#' @param legend.ticks.length length of tick marks in legend
@@ -393,6 +397,7 @@ theme <- function(...,
393397
legend.key.spacing,
394398
legend.key.spacing.x,
395399
legend.key.spacing.y,
400+
legend.key.justification,
396401
legend.frame,
397402
legend.ticks,
398403
legend.ticks.length,

man/theme.Rd

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 136 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)