Skip to content

Commit

Permalink
Variable panel size (space) for facet_wrap() (#5956)
Browse files Browse the repository at this point in the history
* add space argument

* add test

* document

* add news bullet

* accept new snapshot
  • Loading branch information
teunbrand committed Aug 26, 2024
1 parent 78b3f3a commit c108758
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 3 deletions.
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# ggplot2 (development version)

* `facet_wrap()` can have `space = "free_x"` with 1-row layouts and
`space = "free_y"` with 1-column layouts (@teunbrand)
* Secondary axes respect `n.breaks` setting in continuous scales (@teunbrand, #4483).
* Layers can have names (@teunbrand, #4066).
* (internal) improvements to `pal_qualitative()` (@teunbrand, #5013)
Expand Down
37 changes: 34 additions & 3 deletions R/facet-wrap.R
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ NULL
#' @param scales Should scales be fixed (`"fixed"`, the default),
#' free (`"free"`), or free in one dimension (`"free_x"`,
#' `"free_y"`)?
#' @param space If `"fixed"` (default), all panels have the same size and
#' the number of rows and columns in the layout can be arbitrary. If
#' `"free_x"`, panels have widths proportional to the length of the x-scale,
#' but the layout is constrained to one row. If `"free_y"`, panels have
#' heights proportional to the length of the y-scale, but the layout is
#' constrained to one column.
#' @param strip.position By default, the labels are displayed on the top of
#' the plot. Using `strip.position` it is possible to place the labels on
#' either of the four sides by setting \code{strip.position = c("top",
Expand Down Expand Up @@ -109,9 +115,9 @@ NULL
#' geom_point() +
#' facet_wrap(vars(class), dir = "tr")
facet_wrap <- function(facets, nrow = NULL, ncol = NULL, scales = "fixed",
shrink = TRUE, labeller = "label_value", as.table = TRUE,
switch = deprecated(), drop = TRUE, dir = "h",
strip.position = 'top', axes = "margins",
space = "fixed", shrink = TRUE, labeller = "label_value",
as.table = TRUE, switch = deprecated(), drop = TRUE,
dir = "h", strip.position = 'top', axes = "margins",
axis.labels = "all") {
scales <- arg_match0(scales %||% "fixed", c("fixed", "free_x", "free_y", "free"))
dir <- arg_match0(dir, c("h", "v", "lt", "tl", "lb", "bl", "rt", "tr", "rb", "br"))
Expand All @@ -128,6 +134,30 @@ facet_wrap <- function(facets, nrow = NULL, ncol = NULL, scales = "fixed",
y = any(scales %in% c("free_y", "free"))
)

# We cannot have free space in both directions
space <- arg_match0(space, c("free_x", "free_y", "fixed"))
space_free <- list(x = space == "free_x", y = space == "free_y")
if (space_free$x) {
if ((nrow %||% 1) != 1 || !is.null(ncol)) {
cli::cli_warn(
"Cannot use {.code space = \"free_x\"} with custom \\
{.arg nrow} or {.arg ncol}."
)
}
ncol <- NULL
nrow <- 1L
}
if (space_free$y) {
if ((ncol %||% 1) != 1 || !is.null(nrow)) {
cli::cli_warn(
"Cannot use {.code space= \"free_y\"} with custom \\
{.arg nrow} or {.arg ncol}."
)
}
ncol <- 1L
nrow <- NULL
}

# If scales are free, always draw the axes
draw_axes <- arg_match0(axes, c("margins", "all_x", "all_y", "all"))
draw_axes <- list(
Expand Down Expand Up @@ -174,6 +204,7 @@ facet_wrap <- function(facets, nrow = NULL, ncol = NULL, scales = "fixed",
drop = drop,
ncol = ncol,
nrow = nrow,
space_free = space_free,
labeller = labeller,
dir = dir,
draw_axes = draw_axes,
Expand Down
8 changes: 8 additions & 0 deletions man/facet_wrap.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions tests/testthat/_snaps/facet-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@

`nrow` must be a whole number or `NULL`, not the number 1.5.

---

Cannot use `space = "free_x"` with custom `nrow` or `ncol`.

---

Need 3 panels, but together `nrow` and `ncol` only provide 1.
Expand Down
21 changes: 21 additions & 0 deletions tests/testthat/test-facet-layout.R
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,25 @@ test_that("grid: drop = FALSE preserves unused levels", {
expect_equal(as.character(grid_ab$b), as.character(rep(4:1, 4)))
})

test_that("wrap: space = 'free_x/y' sets panel sizes", {

df <- data.frame(x = 1:3)
p <- ggplot(df, aes(x, x)) +
geom_point() +
scale_x_continuous(limits = c(0, NA), expand = c(0, 0)) +
scale_y_continuous(limits = c(0, NA), expand = c(0, 0))

# Test free_x
gt <- ggplotGrob(p + facet_wrap(~x, scales = "free_x", space = "free_x"))
test <- gt$widths[panel_cols(gt)$l]
expect_equal(as.numeric(test), 1:3)

# Test free_y
gt <- ggplotGrob(p + facet_wrap(~x, scales = "free_y", space = "free_y"))
test <- gt$heights[panel_rows(gt)$t]
expect_equal(as.numeric(test), 1:3)
})

# Missing behaviour ----------------------------------------------------------

a3 <- data_frame(
Expand Down Expand Up @@ -207,6 +226,8 @@ test_that("facet_wrap throws errors at bad layout specs", {
expect_snapshot_error(facet_wrap(~test, nrow = -1))
expect_snapshot_error(facet_wrap(~test, nrow = 1.5))

expect_snapshot_warning(facet_wrap(~test, nrow = 2, space = "free_x"))

p <- ggplot(mtcars) +
geom_point(aes(mpg, disp)) +
facet_wrap(~gear, ncol = 1, nrow = 1)
Expand Down

0 comments on commit c108758

Please sign in to comment.