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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.0 +1.5 +2.0 +2.5 +3.0 + + + + + + + + + + +1.0 +1.5 +2.0 +2.5 +3.0 +x +y + + +a + + + + + + +a +b +c + +factor(x) + + + + + + +1 +2 +3 +horizontal legends placed apart + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +1.0 +1.5 +2.0 +2.5 +3.0 + + + + + + + + + + +1.0 +1.5 +2.0 +2.5 +3.0 +x +y + + +a + + + + + + +a +b +c + +factor(x) + + + + + + +1 +2 +3 +vertical legends placed apart + + 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")) + ) +})