Skip to content

Commit 8fa0fb4

Browse files
authored
More sorting options for facet_wrap() (#5855)
* resolve `dir`/`as.table` * new wrap layouts * add tests * add news bullet * skip `as.table` option in vignette * add example
1 parent c5a9c68 commit 8fa0fb4

File tree

5 files changed

+107
-24
lines changed

5 files changed

+107
-24
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ggplot2 (development version)
22

3+
* `facet_wrap()` has new options for the `dir` argument to more precisely
4+
control panel directions (@teunbrand, #5212)
35
* Prevented `facet_wrap(..., drop = FALSE)` from throwing spurious errors when
46
a character facetting variable contained `NA`s (@teunbrand, #5485).
57
* When facets coerce the faceting variables to factors, the 'ordered' class

R/facet-wrap.R

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ NULL
2323
#' either of the four sides by setting \code{strip.position = c("top",
2424
#' "bottom", "left", "right")}
2525
#' @param dir Direction: either `"h"` for horizontal, the default, or `"v"`,
26-
#' for vertical.
26+
#' for vertical. When `"h"` or `"v"` will be combined with `as.table` to
27+
#' set final layout. Alternatively, a combination of `"t"` (top) or
28+
#' `"b"` (bottom) with `"l"` (left) or `"r"` (right) to set a layout directly.
29+
#' These two letters give the starting position and the first letter gives
30+
#' the growing direction. For example `"rt"` will place the first panel in
31+
#' the top-right and starts filling in panels right-to-left.
2732
#' @param axes Determines which axes will be drawn in case of fixed scales.
2833
#' When `"margins"` (default), axes will be drawn at the exterior margins.
2934
#' `"all_x"` and `"all_y"` will draw the respective axes at the interior
@@ -95,13 +100,29 @@ NULL
95100
#' facet_wrap(vars(variable), scales = "free_y", nrow = 2, strip.position = "top") +
96101
#' theme(strip.background = element_blank(), strip.placement = "outside")
97102
#' }
103+
#'
104+
#' # The two letters determine the starting position, so 'tr' starts
105+
#' # in the top-right.
106+
#' # The first letter determines direction, so 'tr' fills top-to-bottom.
107+
#' # `dir = "tr"` is equivalent to `dir = "v", as.table = FALSE`
108+
#' ggplot(mpg, aes(displ, hwy)) +
109+
#' geom_point() +
110+
#' facet_wrap(vars(class), dir = "tr")
98111
facet_wrap <- function(facets, nrow = NULL, ncol = NULL, scales = "fixed",
99112
shrink = TRUE, labeller = "label_value", as.table = TRUE,
100113
switch = deprecated(), drop = TRUE, dir = "h",
101114
strip.position = 'top', axes = "margins",
102115
axis.labels = "all") {
103116
scales <- arg_match0(scales %||% "fixed", c("fixed", "free_x", "free_y", "free"))
104-
dir <- arg_match0(dir, c("h", "v"))
117+
dir <- arg_match0(dir, c("h", "v", "lt", "tl", "lb", "bl", "rt", "tr", "rb", "br"))
118+
if (nchar(dir) == 1) {
119+
dir <- base::switch(
120+
dir,
121+
h = if (as.table) "lt" else "lb",
122+
v = if (as.table) "tl" else "tr"
123+
)
124+
}
125+
105126
free <- list(
106127
x = any(scales %in% c("free_x", "free")),
107128
y = any(scales %in% c("free_y", "free"))
@@ -149,7 +170,6 @@ facet_wrap <- function(facets, nrow = NULL, ncol = NULL, scales = "fixed",
149170
params = list(
150171
facets = facets,
151172
free = free,
152-
as.table = as.table,
153173
strip.position = strip.position,
154174
drop = drop,
155175
ncol = ncol,
@@ -189,21 +209,7 @@ FacetWrap <- ggproto("FacetWrap", Facet,
189209
n <- attr(id, "n")
190210

191211
dims <- wrap_dims(n, params$nrow, params$ncol)
192-
layout <- data_frame0(
193-
PANEL = factor(id, levels = seq_len(n)),
194-
ROW = if (params$as.table) {
195-
as.integer((id - 1L) %/% dims[2] + 1L)
196-
} else {
197-
as.integer(dims[1] - (id - 1L) %/% dims[2])
198-
},
199-
COL = as.integer((id - 1L) %% dims[2] + 1L),
200-
.size = length(id)
201-
)
202-
203-
# For vertical direction, flip row and col
204-
if (identical(params$dir, "v")) {
205-
layout[c("ROW", "COL")] <- layout[c("COL", "ROW")]
206-
}
212+
layout <- wrap_layout(id, dims, params$dir)
207213

208214
panels <- vec_cbind(layout, base)
209215
panels <- panels[order(panels$PANEL), , drop = FALSE]
@@ -576,3 +582,31 @@ measure_axes <- function(empty_idx, axis, margin = 1L, shift = 0) {
576582
cm[set_zero] <- 0
577583
unit(apply(cm, margin, max), "cm")
578584
}
585+
586+
wrap_layout <- function(id, dims, dir) {
587+
as.table <- TRUE
588+
n <- attr(id, "n")
589+
590+
ROW <- switch(
591+
dir,
592+
lt = , rt = (id - 1L) %/% dims[2] + 1L,
593+
tl = , tr = (id - 1L) %% dims[1] + 1L,
594+
lb = , rb = dims[1] - (id - 1L) %/% dims[2],
595+
bl = , br = dims[1] - (id - 1L) %% dims[1]
596+
)
597+
598+
COL <- switch(
599+
dir,
600+
lt = , lb = (id - 1L) %% dims[2] + 1L,
601+
tl = , bl = (id - 1L) %/% dims[1] + 1L,
602+
rt = , rb = dims[2] - (id - 1L) %% dims[2],
603+
tr = , br = dims[2] - (id - 1L) %/% dims[1]
604+
)
605+
606+
data_frame0(
607+
PANEL = factor(id, levels = seq_len(n)),
608+
ROW = as.integer(ROW),
609+
COL = as.integer(COL),
610+
.size = length(id)
611+
)
612+
}

man/facet_wrap.Rd

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-facet-layout.R

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,44 @@ test_that("grid: includes all combinations", {
3232
expect_equal(nrow(all), 4)
3333
})
3434

35+
test_that("wrap: layout sorting is correct", {
36+
37+
dummy <- list(data_frame0(x = 1:5))
38+
39+
test <- panel_layout(facet_wrap(~x, dir = "lt"), dummy)
40+
expect_equal(test$ROW, rep(c(1,2), c(3, 2)))
41+
expect_equal(test$COL, c(1:3, 1:2))
42+
43+
test <- panel_layout(facet_wrap(~x, dir = "tl"), dummy)
44+
expect_equal(test$ROW, c(1, 2, 1, 2, 1))
45+
expect_equal(test$COL, c(1, 1, 2, 2, 3))
46+
47+
test <- panel_layout(facet_wrap(~x, dir = "lb"), dummy)
48+
expect_equal(test$ROW, c(2, 2, 2, 1, 1))
49+
expect_equal(test$COL, c(1, 2, 3, 1, 2))
50+
51+
test <- panel_layout(facet_wrap(~x, dir = "bl"), dummy)
52+
expect_equal(test$ROW, c(2, 1, 2, 1, 2))
53+
expect_equal(test$COL, c(1, 1, 2, 2, 3))
54+
55+
test <- panel_layout(facet_wrap(~x, dir = "rt"), dummy)
56+
expect_equal(test$ROW, c(1, 1, 1, 2, 2))
57+
expect_equal(test$COL, c(3, 2, 1, 3, 2))
58+
59+
test <- panel_layout(facet_wrap(~x, dir = "tr"), dummy)
60+
expect_equal(test$ROW, c(1, 2, 1, 2, 1))
61+
expect_equal(test$COL, c(3, 3, 2, 2, 1))
62+
63+
test <- panel_layout(facet_wrap(~x, dir = "rb"), dummy)
64+
expect_equal(test$ROW, c(2, 2, 2, 1, 1))
65+
expect_equal(test$COL, c(3, 2, 1, 3, 2))
66+
67+
test <- panel_layout(facet_wrap(~x, dir = "br"), dummy)
68+
expect_equal(test$ROW, c(2, 1, 2, 1, 2))
69+
expect_equal(test$COL, c(3, 3, 2, 2, 1))
70+
71+
})
72+
3573
test_that("wrap and grid are equivalent for 1d data", {
3674
rowg <- panel_layout(facet_grid(a~.), list(a))
3775
roww <- panel_layout(facet_wrap(~a, ncol = 1), list(a))

vignettes/extending-ggplot2.Rmd

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,11 +1054,7 @@ FacetBootstrap <- ggproto("FacetBootstrap", FacetWrap,
10541054
dims <- wrap_dims(params$n, params$nrow, params$ncol)
10551055
layout <- data.frame(PANEL = factor(id))
10561056
1057-
if (params$as.table) {
1058-
layout$ROW <- as.integer((id - 1L) %/% dims[2] + 1L)
1059-
} else {
1060-
layout$ROW <- as.integer(dims[1] - (id - 1L) %/% dims[2])
1061-
}
1057+
layout$ROW <- as.integer((id - 1L) %/% dims[2] + 1L)
10621058
layout$COL <- as.integer((id - 1L) %% dims[2] + 1L)
10631059
10641060
layout <- layout[order(layout$PANEL), , drop = FALSE]

0 commit comments

Comments
 (0)