Skip to content

Commit 4bbba83

Browse files
authored
Rework legend spacing (#5456)
* Resolve text margins * Remove gaps * Remove justification fuss in colourbar * Tweak tests * Add news bullet * Use `key.spacing` to control between key spacing * remove vestigial `legend.spacing` from guides * add test for `key.spacing` * document parameters * Fix #5456 for bins * Use `||` instead of `|` * add comment
1 parent 4f05313 commit 4bbba83

9 files changed

+245
-86
lines changed

NEWS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# ggplot2 (development version)
22

3+
* The spacing between legend keys and their labels, in addition to legends
4+
and their titles, is now controlled by the text's `margin` setting. Not
5+
specifying margins will automatically add appropriate text margins. To
6+
control the spacing within a legend between keys, the new
7+
`key.spacing.{x/y}` argument can be used. This leaves the
8+
`legend.spacing` dedicated to controlling the spacing between
9+
different guides (#5455).
10+
311
* In the theme element hierarchy, parent elements that are a strict subclass
412
of child elements now confer their subclass upon the children (#5457).
513

R/guide-bins.R

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -341,13 +341,6 @@ GuideBins <- ggproto(
341341
}
342342
key$.label[c(1, n_labels)[!params$show.limits]] <- ""
343343

344-
just <- switch(
345-
params$direction,
346-
horizontal = elements$text$vjust,
347-
vertical = elements$text$hjust,
348-
0.5
349-
)
350-
351344
if (params$direction == "vertical") {
352345
key$.value <- 1 - key$.value
353346
}
@@ -356,7 +349,6 @@ GuideBins <- ggproto(
356349
elements$text,
357350
label = key$.label,
358351
x = unit(key$.value, "npc"),
359-
y = rep(just, nrow(key)),
360352
margin_x = FALSE,
361353
margin_y = TRUE,
362354
flip = params$direction == "vertical"

R/guide-colorbar.R

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -307,9 +307,6 @@ GuideColourbar <- ggproto(
307307
ticks_length = unit(0.2, "npc"),
308308
background = "legend.background",
309309
margin = "legend.margin",
310-
spacing = "legend.spacing",
311-
spacing.x = "legend.spacing.x",
312-
spacing.y = "legend.spacing.y",
313310
key = "legend.key",
314311
key.height = "legend.key.height",
315312
key.width = "legend.key.width",
@@ -413,17 +410,10 @@ GuideColourbar <- ggproto(
413410
return(list(labels = zeroGrob()))
414411
}
415412

416-
just <- if (params$direction == "horizontal") {
417-
elements$text$vjust
418-
} else {
419-
elements$text$hjust
420-
}
421-
422413
list(labels = flip_element_grob(
423414
elements$text,
424415
label = validate_labels(key$.label),
425416
x = unit(key$.value, "npc"),
426-
y = rep(just, nrow(key)),
427417
margin_x = FALSE,
428418
margin_y = TRUE,
429419
flip = params$direction == "vertical"

R/guide-legend.R

Lines changed: 72 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@
4242
#' @param keyheight A numeric or a [grid::unit()] object specifying
4343
#' the height of the legend key. Default value is `legend.key.height` or
4444
#' `legend.key.size` in [theme()].
45+
#' @param key.spacing,key.spacing.x,key.spacing.y A numeric or [grid::unit()]
46+
#' object specifying the distance between key-label pairs in the horizontal
47+
#' direction (`key.spacing.x`), vertical direction (`key.spacing.y`) or both
48+
#' (`key.spacing`).
4549
#' @param direction A character string indicating the direction of the guide.
4650
#' One of "horizontal" or "vertical."
4751
#' @param default.unit A character string indicating [grid::unit()]
@@ -143,6 +147,9 @@ guide_legend <- function(
143147
# Key size
144148
keywidth = NULL,
145149
keyheight = NULL,
150+
key.spacing = NULL,
151+
key.spacing.x = NULL,
152+
key.spacing.y = NULL,
146153

147154
# General
148155
direction = NULL,
@@ -156,12 +163,24 @@ guide_legend <- function(
156163
...
157164
) {
158165
# Resolve key sizes
159-
if (!inherits(keywidth, c("NULL", "unit"))) {
166+
if (!(is.null(keywidth) || is.unit(keywidth))) {
160167
keywidth <- unit(keywidth, default.unit)
161168
}
162-
if (!inherits(keyheight, c("NULL", "unit"))) {
169+
if (!(is.null(keyheight) || is.unit(keyheight))) {
163170
keyheight <- unit(keyheight, default.unit)
164171
}
172+
173+
# Resolve spacing
174+
key.spacing.x <- key.spacing.x %||% key.spacing
175+
if (!is.null(key.spacing.x) || is.unit(key.spacing.x)) {
176+
key.spacing.x <- unit(key.spacing.x, default.unit)
177+
}
178+
key.spacing.y <- key.spacing.y %||% key.spacing
179+
if (!is.null(key.spacing.y) || is.unit(key.spacing.y)) {
180+
key.spacing.y <- unit(key.spacing.y, default.unit)
181+
}
182+
183+
165184
if (!is.null(title.position)) {
166185
title.position <- arg_match0(title.position, .trbl)
167186
}
@@ -187,6 +206,8 @@ guide_legend <- function(
187206
# Key size
188207
keywidth = keywidth,
189208
keyheight = keyheight,
209+
key.spacing.x = key.spacing.x,
210+
key.spacing.y = key.spacing.y,
190211

191212
# General
192213
direction = direction,
@@ -226,9 +247,10 @@ GuideLegend <- ggproto(
226247

227248
keywidth = NULL,
228249
keyheight = NULL,
250+
key.spacing.x = NULL,
251+
key.spacing.y = NULL,
229252

230253
# General
231-
direction = NULL,
232254
override.aes = list(),
233255
nrow = NULL,
234256
ncol = NULL,
@@ -249,9 +271,6 @@ GuideLegend <- ggproto(
249271
elements = list(
250272
background = "legend.background",
251273
margin = "legend.margin",
252-
spacing = "legend.spacing",
253-
spacing.x = "legend.spacing.x",
254-
spacing.y = "legend.spacing.y",
255274
key = "legend.key",
256275
key.height = "legend.key.height",
257276
key.width = "legend.key.width",
@@ -436,13 +455,35 @@ GuideLegend <- ggproto(
436455
elements$text$size %||% 11
437456
gap <- unit(gap * 0.5, "pt")
438457
# Should maybe be elements$spacing.{x/y} instead of the theme's spacing?
439-
elements$hgap <- width_cm( theme$legend.spacing.x %||% gap)
440-
elements$vgap <- height_cm(theme$legend.spacing.y %||% gap)
458+
459+
if (params$direction == "vertical") {
460+
# For backward compatibility, vertical default is no spacing
461+
vgap <- params$key.spacing.y %||% unit(0, "pt")
462+
} else {
463+
vgap <- params$key.spacing.y %||% gap
464+
}
465+
466+
elements$hgap <- width_cm( params$key.spacing.x %||% gap)
467+
elements$vgap <- height_cm(vgap)
441468
elements$padding <- convertUnit(
442469
elements$margin %||% margin(),
443470
"cm", valueOnly = TRUE
444471
)
445472

473+
# When no explicit margin has been set, either in this guide or in the
474+
# theme, we set a default text margin to leave a small gap in between
475+
# the label and the key.
476+
if (is.null(params$label.theme$margin %||% theme$legend.text$margin) &&
477+
!inherits(elements$text, "element_blank")) {
478+
i <- match(params$label.position, .trbl[c(3, 4, 1, 2)])
479+
elements$text$margin[i] <- elements$text$margin[i] + gap
480+
}
481+
if (is.null(params$title.theme$margin %||% theme$legend.title$margin) &&
482+
!inherits(elements$title, "element_blank")) {
483+
i <- match(params$title.position, .trbl[c(3, 4, 1, 2)])
484+
elements$title$margin[i] <- elements$title$margin[i] + gap
485+
}
486+
446487
# Evaluate backgrounds early
447488
if (!is.null(elements$background)) {
448489
elements$background <- ggname(
@@ -527,22 +568,23 @@ GuideLegend <- ggproto(
527568
hgap <- elements$hgap %||% 0
528569
widths <- switch(
529570
params$label.position,
530-
"left" = list(label_widths, hgap, widths, hgap),
531-
"right" = list(widths, hgap, label_widths, hgap),
532-
list(pmax(label_widths, widths), hgap * (!byrow))
571+
"left" = list(label_widths, widths, hgap),
572+
"right" = list(widths, label_widths, hgap),
573+
list(pmax(label_widths, widths), hgap)
533574
)
534575
widths <- head(vec_interleave(!!!widths), -1)
535576

536577
vgap <- elements$vgap %||% 0
537578
heights <- switch(
538579
params$label.position,
539-
"top" = list(label_heights, vgap, heights, vgap),
540-
"bottom" = list(heights, vgap, label_heights, vgap),
541-
list(pmax(label_heights, heights), vgap * (byrow))
580+
"top" = list(label_heights, heights, vgap),
581+
"bottom" = list(heights, label_heights, vgap),
582+
list(pmax(label_heights, heights), vgap)
542583
)
543584
heights <- head(vec_interleave(!!!heights), -1)
544585

545586
has_title <- !is.zero(grobs$title)
587+
546588
if (has_title) {
547589
# Measure title
548590
title_width <- width_cm(grobs$title)
@@ -551,14 +593,14 @@ GuideLegend <- ggproto(
551593
# Combine title with rest of the sizes based on its position
552594
widths <- switch(
553595
params$title.position,
554-
"left" = c(title_width, hgap, widths),
555-
"right" = c(widths, hgap, title_width),
596+
"left" = c(title_width, widths),
597+
"right" = c(widths, title_width),
556598
c(widths, max(0, title_width - sum(widths)))
557599
)
558600
heights <- switch(
559601
params$title.position,
560-
"top" = c(title_height, vgap, heights),
561-
"bottom" = c(heights, vgap, title_height),
602+
"top" = c(title_height, heights),
603+
"bottom" = c(heights, title_height),
562604
c(heights, max(0, title_height - sum(heights)))
563605
)
564606
}
@@ -595,20 +637,20 @@ GuideLegend <- ggproto(
595637
switch(
596638
params$label.position,
597639
"top" = {
598-
key_row <- key_row * 2
599-
label_row <- label_row * 2 - 2
640+
key_row <- key_row + df$R
641+
label_row <- key_row - 1
600642
},
601643
"bottom" = {
602-
key_row <- key_row * 2 - 2
603-
label_row <- label_row * 2
644+
key_row <- key_row + df$R - 1
645+
label_row <- key_row + 1
604646
},
605647
"left" = {
606-
key_col <- key_col * 2
607-
label_col <- label_col * 2 - 2
648+
key_col <- key_col + df$C
649+
label_col <- key_col - 1
608650
},
609651
"right" = {
610-
key_col <- key_col * 2 - 2
611-
label_col <- label_col * 2
652+
key_col <- key_col + df$C - 1
653+
label_col <- key_col + 1
612654
}
613655
)
614656

@@ -617,8 +659,8 @@ GuideLegend <- ggproto(
617659
switch(
618660
params$title.position,
619661
"top" = {
620-
key_row <- key_row + 2
621-
label_row <- label_row + 2
662+
key_row <- key_row + 1
663+
label_row <- label_row + 1
622664
title_row <- 2
623665
title_col <- seq_along(sizes$widths) + 1
624666
},
@@ -627,8 +669,8 @@ GuideLegend <- ggproto(
627669
title_col <- seq_along(sizes$widths) + 1
628670
},
629671
"left" = {
630-
key_col <- key_col + 2
631-
label_col <- label_col + 2
672+
key_col <- key_col + 1
673+
label_col <- label_col + 1
632674
title_row <- seq_along(sizes$heights) + 1
633675
title_col <- 2
634676
},

man/guide_legend.Rd

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

0 commit comments

Comments
 (0)