diff --git a/NEWS.md b/NEWS.md
index 2e9b4e63eb..57bbab592c 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -350,6 +350,8 @@
(@teunbrand, #6269).
* The default colour and fill scales have a new `palette` argument
(@teunbrand, #6064).
+* The `theme(legend.spacing.{x/y})` setting now accepts `null`-units
+ (@teunbrand, #6417).
# ggplot2 3.5.2
diff --git a/R/guides-.R b/R/guides-.R
index 2f7cd6d317..38a94252b0 100644
--- a/R/guides-.R
+++ b/R/guides-.R
@@ -653,11 +653,14 @@ Guides <- ggproto(
height = heightDetails(grobs[[i]]))
)
}
-
- spacing <- convertWidth(theme$legend.spacing.x, "cm")
+ spacing <- theme$legend.spacing.x
+ stretch_spacing <- any(unitType(spacing) == "null")
+ if (!stretch_spacing) {
+ spacing <- convertWidth(spacing, "cm")
+ }
heights <- unit(height_cm(lapply(heights, sum)), "cm")
- if (stretch_x) {
+ if (stretch_x || stretch_spacing) {
widths <- redistribute_null_units(widths, spacing, margin, "width")
vp_width <- unit(1, "npc")
} else {
@@ -692,10 +695,14 @@ Guides <- ggproto(
)
}
- spacing <- convertHeight(theme$legend.spacing.y, "cm")
+ spacing <- theme$legend.spacing.y
+ stretch_spacing <- any(unitType(spacing) == "null")
+ if (!stretch_spacing) {
+ spacing <- convertWidth(spacing, "cm")
+ }
widths <- unit(width_cm(lapply(widths, sum)), "cm")
- if (stretch_y) {
+ if (stretch_y || stretch_spacing) {
heights <- redistribute_null_units(heights, spacing, margin, "height")
vp_height <- unit(1, "npc")
} else {
@@ -735,10 +742,10 @@ Guides <- ggproto(
)
# Set global margin
- if (stretch_x) {
+ if (stretch_x || stretch_spacing) {
global_margin[c(2, 4)] <- unit(0, "cm")
}
- if (stretch_y) {
+ if (stretch_y || stretch_spacing) {
global_margin[c(1, 3)] <- unit(0, "cm")
}
guides <- gtable_add_padding(guides, global_margin)
@@ -933,26 +940,20 @@ redistribute_null_units <- function(units, spacing, margin, type = "width") {
}
# Get spacing between guides and margins in absolute units
- size <- switch(type, width = convertWidth, height = convertHeight)
- spacing <- size(spacing, "cm", valueOnly = TRUE)
- spacing <- sum(rep(spacing, length(units) - 1))
+ size <- switch(type, width = width_cm, height = height_cm)
+ spacing <- sum(rep(spacing, length.out = length(units) - 1))
margin <- switch(type, width = margin[c(2, 4)], height = margin[c(1, 3)])
- margin <- sum(size(margin, "cm", valueOnly = TRUE))
+ margin <- sum(size(margin))
# Get the absolute parts of the unit
- absolute <- vapply(units, function(u) {
- u <- absolute.size(u)
- u <- size(u, "cm", valueOnly = TRUE)
- sum(u)
- }, numeric(1))
- absolute_sum <- sum(absolute) + spacing + margin
+ absolute <- vapply(units, function(u) sum(size(absolute.size(u))), numeric(1))
+ absolute_sum <- sum(absolute) + sum(size(spacing)) + margin
# Get the null parts of the unit
+ num_null <- function(x) sum(as.numeric(x)[unitType(x) == "null"])
relative <- rep(0, length(units))
- relative[has_null] <- vapply(units[has_null], function(u) {
- sum(as.numeric(u)[unitType(u) == "null"])
- }, numeric(1))
- relative_sum <- sum(relative)
+ relative[has_null] <- vapply(units[has_null], num_null, numeric(1))
+ relative_sum <- sum(relative) + num_null(spacing)
if (relative_sum == 0) {
return(unit(absolute, "cm"))
diff --git a/tests/testthat/_snaps/theme/horizontal-legends-placed-apart.svg b/tests/testthat/_snaps/theme/horizontal-legends-placed-apart.svg
new file mode 100644
index 0000000000..edd353e814
--- /dev/null
+++ b/tests/testthat/_snaps/theme/horizontal-legends-placed-apart.svg
@@ -0,0 +1,100 @@
+
+
diff --git a/tests/testthat/_snaps/theme/vertical-legends-placed-apart.svg b/tests/testthat/_snaps/theme/vertical-legends-placed-apart.svg
new file mode 100644
index 0000000000..b9d674373a
--- /dev/null
+++ b/tests/testthat/_snaps/theme/vertical-legends-placed-apart.svg
@@ -0,0 +1,100 @@
+
+
diff --git a/tests/testthat/test-theme.R b/tests/testthat/test-theme.R
index 10ef91cf95..9b68ae2b09 100644
--- a/tests/testthat/test-theme.R
+++ b/tests/testthat/test-theme.R
@@ -1051,3 +1051,25 @@ test_that("legend margins are correct when using relative key sizes", {
expect_doppelganger("stretched horizontal legends", horizontal)
})
+
+test_that("legends are placed correctly when using stretchy spacing", {
+
+ df <- data.frame(x = 1:3, y = 1:3, a = letters[1:3])
+
+ p <- ggplot(df, aes(x, y, colour = a, shape = factor(x))) +
+ geom_point() +
+ theme(
+ legend.box.background = element_rect(colour = "blue", fill = NA),
+ legend.background = element_rect(colour = "red", fill = NA)
+ )
+
+ expect_doppelganger(
+ "vertical legends placed apart",
+ p + theme(legend.position = "right", legend.spacing.y = unit(1, "null"))
+ )
+
+ expect_doppelganger(
+ "horizontal legends placed apart",
+ p + theme(legend.position = "top", legend.spacing.x = unit(1, "null"))
+ )
+})