From 4b220b60a58557a7d8604b5c8752169fe5458f95 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Mon, 20 Oct 2025 09:41:15 +0200 Subject: [PATCH 1/7] remove aspect ratio warning --- R/facet-.R | 9 --------- tests/testthat/test-theme.R | 7 ------- 2 files changed, 16 deletions(-) diff --git a/R/facet-.R b/R/facet-.R index e4a010d612..a2e616624f 100644 --- a/R/facet-.R +++ b/R/facet-.R @@ -731,15 +731,6 @@ Facet <- ggproto("Facet", NULL, return(table) } - if (isTRUE(table$respect)) { - args <- !c(is.null(new_widths), is.null(new_heights)) - args <- c("panel.widths", "panel.heights")[args] - cli::cli_warn( - "Aspect ratios are overruled by {.arg {args}} theme element{?s}." - ) - table$respect <- FALSE - } - rows <- panel_rows(table) cols <- panel_cols(table) diff --git a/tests/testthat/test-theme.R b/tests/testthat/test-theme.R index 3f3fb9eeb9..920f18ab4b 100644 --- a/tests/testthat/test-theme.R +++ b/tests/testthat/test-theme.R @@ -717,13 +717,6 @@ test_that("panel.widths and panel.heights works with free-space panels", { }) -test_that("panel.widths and panel.heights appropriately warn about aspect override", { - p <- ggplot(mpg, aes(displ, hwy)) + - geom_point() + - theme(aspect.ratio = 1, panel.widths = unit(4, "cm")) - expect_warning(ggplotGrob(p), "Aspect ratios are overruled") -}) - test_that("margin_part() mechanics work as expected", { t <- theme_gray() + From a837ef5e090ca3c8c5c17f76cc299fc309eb8e51 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Mon, 20 Oct 2025 09:44:29 +0200 Subject: [PATCH 2/7] Add note in docs --- R/theme.R | 3 ++- man/theme.Rd | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/R/theme.R b/R/theme.R index 07a13db64a..5d7de7f3ec 100644 --- a/R/theme.R +++ b/R/theme.R @@ -151,7 +151,8 @@ #' from `line` #' @param panel.widths,panel.heights Sizes for panels (`units`). Can be a #' single unit to set the total size for the panel area, or a unit vector to -#' set the size of individual panels. +#' set the size of individual panels. Using this setting overrides any +#' aspect ratio set by the theme, coord or facets. #' @param panel.ontop option to place the panel (background, gridlines) over #' the data layers (`logical`). Usually used with a transparent or blank #' `panel.background`. diff --git a/man/theme.Rd b/man/theme.Rd index ad95fb1d86..c93fe23389 100644 --- a/man/theme.Rd +++ b/man/theme.Rd @@ -327,7 +327,8 @@ the data layers (\code{logical}). Usually used with a transparent or blank \item{panel.widths, panel.heights}{Sizes for panels (\code{units}). Can be a single unit to set the total size for the panel area, or a unit vector to -set the size of individual panels.} +set the size of individual panels. Using this setting overrides any +aspect ratio set by the theme, coord or facets.} \item{plot.background}{background of the entire plot (\code{\link[=element_rect]{element_rect()}}; inherits from \code{rect})} From 3764c97a9b33fdee0b5880f4ad689cfacf794013 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 21 Oct 2025 11:23:59 +0200 Subject: [PATCH 3/7] Try to preserve aspect ratio --- R/facet-.R | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/R/facet-.R b/R/facet-.R index a2e616624f..1e43b9e248 100644 --- a/R/facet-.R +++ b/R/facet-.R @@ -734,6 +734,21 @@ Facet <- ggproto("Facet", NULL, rows <- panel_rows(table) cols <- panel_cols(table) + if (isTRUE(table$respect) && # Has fixed aspect ratio + xor(is.null(new_widths), is.null(new_heights)) && # One dimension is set + nrow(rows) == 1 && nrow(cols) == 1) { # Just a single panel + old_width <- table$widths[cols$l] + old_height <- table$heights[rows$t] + # Try to reconstruct aspect ratio from panel size + # We shouldn't attempt this with mixed or compound (e.g. "sum") units + if (identical(unitType(old_width), "null") && + identical(unitType(old_height), "null")) { + ratio <- as.numeric(old_height) / as.numeric(old_width) + new_widths <- new_widths %||% (new_heights / ratio) + new_heights <- new_heights %||% (new_widths * ratio) + } + } + if (length(new_widths) == 1L && nrow(cols) > 1L) { # Get total size of non-panel widths in between panels extra <- setdiff(seq(min(cols$l), max(cols$r)), union(cols$l, cols$r)) From 6da653265539a619e00be7172d752b69cf7492ca Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 21 Oct 2025 11:25:45 +0200 Subject: [PATCH 4/7] ensure scalar width/height when preserving aspect ratio --- R/facet-.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/facet-.R b/R/facet-.R index 1e43b9e248..7aa06695d0 100644 --- a/R/facet-.R +++ b/R/facet-.R @@ -744,8 +744,8 @@ Facet <- ggproto("Facet", NULL, if (identical(unitType(old_width), "null") && identical(unitType(old_height), "null")) { ratio <- as.numeric(old_height) / as.numeric(old_width) - new_widths <- new_widths %||% (new_heights / ratio) - new_heights <- new_heights %||% (new_widths * ratio) + new_widths <- (new_widths %||% (new_heights / ratio))[1] + new_heights <- (new_heights %||% (new_widths * ratio))[1] } } From cd001d74836cedece7f5dad89e50051565928128 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 21 Oct 2025 11:33:04 +0200 Subject: [PATCH 5/7] document new behaviour --- R/theme.R | 6 ++++-- man/theme.Rd | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/R/theme.R b/R/theme.R index 5d7de7f3ec..bab9e23b5a 100644 --- a/R/theme.R +++ b/R/theme.R @@ -151,8 +151,10 @@ #' from `line` #' @param panel.widths,panel.heights Sizes for panels (`units`). Can be a #' single unit to set the total size for the panel area, or a unit vector to -#' set the size of individual panels. Using this setting overrides any -#' aspect ratio set by the theme, coord or facets. +#' set the size of individual panels. Using this setting overrides the +#' aspect ratio set by the theme, coord or facets. An exception is made when +#' the plot has a single panel and exactly one of the width *or* height is +#' set, in which case an attempt is made to preserve the aspect ratio. #' @param panel.ontop option to place the panel (background, gridlines) over #' the data layers (`logical`). Usually used with a transparent or blank #' `panel.background`. diff --git a/man/theme.Rd b/man/theme.Rd index c93fe23389..66a9be0d32 100644 --- a/man/theme.Rd +++ b/man/theme.Rd @@ -327,8 +327,10 @@ the data layers (\code{logical}). Usually used with a transparent or blank \item{panel.widths, panel.heights}{Sizes for panels (\code{units}). Can be a single unit to set the total size for the panel area, or a unit vector to -set the size of individual panels. Using this setting overrides any -aspect ratio set by the theme, coord or facets.} +set the size of individual panels. Using this setting overrides the +aspect ratio set by the theme, coord or facets. An exception is made when +the plot has a single panel and exactly one of the width \emph{or} height is +set, in which case an attempt is made to preserve the aspect ratio.} \item{plot.background}{background of the entire plot (\code{\link[=element_rect]{element_rect()}}; inherits from \code{rect})} From e1fa69014b33e3291e213d9679b52952140362bc Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 21 Oct 2025 11:52:36 +0200 Subject: [PATCH 6/7] add test for behaviour --- tests/testthat/test-theme.R | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/testthat/test-theme.R b/tests/testthat/test-theme.R index 920f18ab4b..d8508f9070 100644 --- a/tests/testthat/test-theme.R +++ b/tests/testthat/test-theme.R @@ -717,6 +717,36 @@ test_that("panel.widths and panel.heights works with free-space panels", { }) +test_that("panel.withs and panel.heights preserve aspect ratios with single panels", { + + df <- data.frame(x = c(1, 2)) + + p <- ggplotGrob( + ggplot(df, aes(x, x)) + + geom_point() + + theme( + aspect.ratio = 2, + panel.heights = unit(10, "cm") + ) + ) + + width <- p$widths[panel_cols(p)$l] + expect_equal(as.character(width), "5cm") + + p <- ggplotGrob( + ggplot(df, aes(x, x)) + + geom_point() + + facet_wrap(~ x) + # This behaviour doesn't occur in multipanel plots. + theme( + aspect.ratio = 2, + panel.heights = unit(10, "cm") + ) + ) + + width <- p$widths[panel_cols(p)$l] + expect_equal(as.character(width), c("1null", "1null")) +}) + test_that("margin_part() mechanics work as expected", { t <- theme_gray() + From c846b2f0bc411befc8ad20314cf382e66a9c0ff7 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 21 Oct 2025 12:10:02 +0200 Subject: [PATCH 7/7] add news bullet --- NEWS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.md b/NEWS.md index c7df5f90cf..0deb4e3de4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -24,6 +24,9 @@ legend glyphs (@teunbrand, #6594) * Fixed regression where `NULL`-aesthetics contributed to plot labels too insistently. Now they contribute only as fallback labels (@teunbrand, #6616) +* The `theme(panel.widths, panel.heights)` setting attempts to preserve the + plot's aspect ratio when only one of the two settings is given, and the plot + has a single panel (@teunbrand, #6701). # ggplot2 4.0.0