diff --git a/NEWS.md b/NEWS.md index c69ea43fbc..ebc4be39e9 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). * Fixed axis misplacement in `coor_radial()` when labels are blank (@teunbrand, #6574) # ggplot2 4.0.0 diff --git a/R/facet-.R b/R/facet-.R index e4a010d612..7aa06695d0 100644 --- a/R/facet-.R +++ b/R/facet-.R @@ -731,18 +731,24 @@ 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) + 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))[1] + new_heights <- (new_heights %||% (new_widths * ratio))[1] + } + } + 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)) diff --git a/R/theme.R b/R/theme.R index 07a13db64a..bab9e23b5a 100644 --- a/R/theme.R +++ b/R/theme.R @@ -151,7 +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. +#' 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 ad95fb1d86..66a9be0d32 100644 --- a/man/theme.Rd +++ b/man/theme.Rd @@ -327,7 +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.} +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})} diff --git a/tests/testthat/test-theme.R b/tests/testthat/test-theme.R index 3f3fb9eeb9..d8508f9070 100644 --- a/tests/testthat/test-theme.R +++ b/tests/testthat/test-theme.R @@ -717,11 +717,34 @@ 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("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", {