Skip to content

Commit 428d122

Browse files
authored
Layer parameter controlling facet layout (#6336)
* append layout attribute to layer data * repeat data for fixed layout * unify `Facet$map_data()` approaches * put comments back in * Implement keywords for fixing rows / columns * use integers to select panels * document the use of the `layout` argument * add news bullet * swap facet data mapping from method to standalone function
1 parent f34ad83 commit 428d122

File tree

11 files changed

+222
-100
lines changed

11 files changed

+222
-100
lines changed

NEWS.md

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

3+
* New `layer(layout)` argument to interact with facets (@teunbrand, #3062)
34
* New `stat_connect()` to connect points via steps or other shapes
45
(@teunbrand, #6228)
56
* Fixed regression with incorrectly drawn gridlines when using `coord_flip()`

R/facet-.R

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,3 +872,95 @@ censor_labels <- function(ranges, layout, labels) {
872872
}
873873
ranges
874874
}
875+
876+
map_facet_data <- function(data, layout, params) {
877+
878+
if (empty(data)) {
879+
return(vec_cbind(data %|W|% NULL, PANEL = integer(0)))
880+
}
881+
882+
vars <- params$facet %||% c(params$rows, params$cols)
883+
884+
if (length(vars) == 0) {
885+
data$PANEL <- layout$PANEL
886+
return(data)
887+
}
888+
889+
grid_layout <- all(c("rows", "cols") %in% names(params))
890+
layer_layout <- attr(data, "layout")
891+
if (identical(layer_layout, "fixed")) {
892+
n <- vec_size(data)
893+
data <- vec_rep(data, vec_size(layout))
894+
data$PANEL <- vec_rep_each(layout$PANEL, n)
895+
return(data)
896+
}
897+
898+
# Compute faceting values
899+
facet_vals <- eval_facets(vars, data, params$.possible_columns)
900+
901+
include_margins <- !isFALSE(params$margin %||% FALSE) &&
902+
nrow(facet_vals) == nrow(data) && grid_layout
903+
if (include_margins) {
904+
# Margins are computed on evaluated faceting values (#1864).
905+
facet_vals <- reshape_add_margins(
906+
vec_cbind(facet_vals, .index = seq_len(nrow(facet_vals))),
907+
list(intersect(names(params$rows), names(facet_vals)),
908+
intersect(names(params$cols), names(facet_vals))),
909+
params$margins %||% FALSE
910+
)
911+
# Apply recycling on original data to fit margins
912+
# We're using base subsetting here because `data` might have a superclass
913+
# that isn't handled well by vctrs::vec_slice
914+
data <- data[facet_vals$.index, , drop = FALSE]
915+
facet_vals$.index <- NULL
916+
}
917+
918+
# If we need to fix rows or columns, we make the corresponding faceting
919+
# variables missing on purpose
920+
if (grid_layout) {
921+
if (identical(layer_layout, "fixed_rows")) {
922+
facet_vals <- facet_vals[setdiff(names(facet_vals), names(params$cols))]
923+
}
924+
if (identical(layer_layout, "fixed_cols")) {
925+
facet_vals <- facet_vals[setdiff(names(facet_vals), names(params$rows))]
926+
}
927+
}
928+
929+
# If any faceting variables are missing, add them in by
930+
# duplicating the data
931+
missing_facets <- setdiff(names(vars), names(facet_vals))
932+
if (length(missing_facets) > 0) {
933+
934+
to_add <- unique0(layout[missing_facets])
935+
936+
data_rep <- rep.int(seq_len(nrow(data)), nrow(to_add))
937+
facet_rep <- rep(seq_len(nrow(to_add)), each = nrow(data))
938+
939+
data <- unrowname(data[data_rep, , drop = FALSE])
940+
facet_vals <- unrowname(vec_cbind(
941+
unrowname(facet_vals[data_rep, , drop = FALSE]),
942+
unrowname(to_add[facet_rep, , drop = FALSE])
943+
))
944+
}
945+
946+
if (nrow(facet_vals) < 1) {
947+
# Add PANEL variable
948+
data$PANEL <- NO_PANEL
949+
return(data)
950+
}
951+
952+
facet_vals[] <- lapply(facet_vals, as_unordered_factor)
953+
facet_vals[] <- lapply(facet_vals, addNA, ifany = TRUE)
954+
layout[] <- lapply(layout, as_unordered_factor)
955+
956+
# Add PANEL variable
957+
keys <- join_keys(facet_vals, layout, by = names(vars))
958+
data$PANEL <- layout$PANEL[match(keys$x, keys$y)]
959+
960+
# Filter panels when layer_layout is an integer
961+
if (is_integerish(layer_layout)) {
962+
data <- vec_slice(data, data$PANEL %in% layer_layout)
963+
}
964+
965+
data
966+
}

R/facet-grid-.R

Lines changed: 12 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ NULL
6969
#' labels and the interior axes get none. When `"all_x"` or `"all_y"`, only
7070
#' draws the labels at the interior axes in the x- or y-direction
7171
#' respectively.
72+
#'
73+
#' @section Layer layout:
74+
#' The [`layer(layout)`][layer()] argument in context of `facet_grid()` can take
75+
#' the following values:
76+
#' * `NULL` (default) to use the faceting variables to assign panels.
77+
#' * An integer vector to include selected panels. Panel numbers not included in
78+
#' the integer vector are excluded.
79+
#' * `"fixed"` to repeat data across every panel.
80+
#' * `"fixed_rows"` to repeat data across rows.
81+
#' * `"fixed_cols"` to repeat data across columns.
82+
#'
7283
#' @export
7384
#' @seealso
7485
#' The `r link_book("facet grid section", "facet#facet-grid")`
@@ -282,69 +293,8 @@ FacetGrid <- ggproto("FacetGrid", Facet,
282293

283294
panels
284295
},
285-
map_data = function(data, layout, params) {
286-
if (empty(data)) {
287-
return(vec_cbind(data %|W|% NULL, PANEL = integer(0)))
288-
}
289-
290-
rows <- params$rows
291-
cols <- params$cols
292-
vars <- c(names(rows), names(cols))
293-
294-
if (length(vars) == 0) {
295-
data$PANEL <- layout$PANEL
296-
return(data)
297-
}
298-
299-
# Compute faceting values
300-
facet_vals <- eval_facets(c(rows, cols), data, params$.possible_columns)
301-
if (nrow(facet_vals) == nrow(data)) {
302-
# Margins are computed on evaluated faceting values (#1864).
303-
facet_vals <- reshape_add_margins(
304-
# We add an index column to track data recycling
305-
vec_cbind(facet_vals, .index = seq_len(nrow(facet_vals))),
306-
list(intersect(names(rows), names(facet_vals)),
307-
intersect(names(cols), names(facet_vals))),
308-
params$margins
309-
)
310-
# Apply recycling on original data to fit margins
311-
# We're using base subsetting here because `data` might have a superclass
312-
# that isn't handled well by vctrs::vec_slice
313-
data <- data[facet_vals$.index, , drop = FALSE]
314-
facet_vals$.index <- NULL
315-
}
316296

317-
# If any faceting variables are missing, add them in by
318-
# duplicating the data
319-
missing_facets <- setdiff(vars, names(facet_vals))
320-
if (length(missing_facets) > 0) {
321-
to_add <- unique0(layout[missing_facets])
322-
323-
data_rep <- rep.int(seq_len(nrow(data)), nrow(to_add))
324-
facet_rep <- rep(seq_len(nrow(to_add)), each = nrow(data))
325-
326-
data <- unrowname(data[data_rep, , drop = FALSE])
327-
facet_vals <- unrowname(vec_cbind(
328-
unrowname(facet_vals[data_rep, , drop = FALSE]),
329-
unrowname(to_add[facet_rep, , drop = FALSE]))
330-
)
331-
}
332-
333-
# Add PANEL variable
334-
if (nrow(facet_vals) == 0) {
335-
# Special case of no faceting
336-
data$PANEL <- NO_PANEL
337-
} else {
338-
facet_vals[] <- lapply(facet_vals[], as_unordered_factor)
339-
facet_vals[] <- lapply(facet_vals[], addNA, ifany = TRUE)
340-
layout[] <- lapply(layout[], as_unordered_factor)
341-
342-
keys <- join_keys(facet_vals, layout, by = vars)
343-
344-
data$PANEL <- layout$PANEL[match(keys$x, keys$y)]
345-
}
346-
data
347-
},
297+
map_data = map_facet_data,
348298

349299
attach_axes = function(table, layout, ranges, coord, theme, params) {
350300

R/facet-null.R

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ NULL
66
#' @inheritParams facet_grid
77
#' @keywords internal
88
#' @export
9+
#' @section Layer layout:
10+
#' The [`layer(layout)`][layer()] argument in context of `facet_null()` is
11+
#' completely ignored.
912
#' @examples
1013
#' # facet_null is the default faceting specification if you
1114
#' # don't override it with facet_grid or facet_wrap

R/facet-wrap.R

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ NULL
4545
#' the exterior axes get labels, and the interior axes get none. When
4646
#' `"all_x"` or `"all_y"`, only draws the labels at the interior axes in the
4747
#' x- or y-direction respectively.
48+
#'
49+
#' @section Layer layout:
50+
#' The [`layer(layout)`][layer()] argument in context of `facet_wrap()` can take
51+
#' the following values:
52+
#' * `NULL` (default) to use the faceting variables to assign panels.
53+
#' * An integer vector to include selected panels. Panel numbers not included in
54+
#' the integer vector are excluded.
55+
#' * `"fixed"` to repeat data across every panel.
56+
#'
4857
#' @inheritParams facet_grid
4958
#' @seealso
5059
#' The `r link_book("facet wrap section", "facet#sec-facet-wrap")`
@@ -247,42 +256,8 @@ FacetWrap <- ggproto("FacetWrap", Facet,
247256

248257
panels
249258
},
250-
map_data = function(data, layout, params) {
251-
if (empty(data)) {
252-
return(vec_cbind(data %|W|% NULL, PANEL = integer(0)))
253-
}
254-
255-
vars <- params$facets
256-
257-
if (length(vars) == 0) {
258-
data$PANEL <- layout$PANEL
259-
return(data)
260-
}
261-
262-
facet_vals <- eval_facets(vars, data, params$.possible_columns)
263-
facet_vals[] <- lapply(facet_vals[], as_unordered_factor)
264-
layout[] <- lapply(layout[], as_unordered_factor)
265259

266-
missing_facets <- setdiff(names(vars), names(facet_vals))
267-
if (length(missing_facets) > 0) {
268-
269-
to_add <- unique0(layout[missing_facets])
270-
271-
data_rep <- rep.int(seq_len(nrow(data)), nrow(to_add))
272-
facet_rep <- rep(seq_len(nrow(to_add)), each = nrow(data))
273-
274-
data <- data[data_rep, , drop = FALSE]
275-
facet_vals <- vec_cbind(
276-
facet_vals[data_rep, , drop = FALSE],
277-
to_add[facet_rep, , drop = FALSE]
278-
)
279-
}
280-
281-
keys <- join_keys(facet_vals, layout, by = names(vars))
282-
283-
data$PANEL <- layout$PANEL[match(keys$x, keys$y)]
284-
data
285-
},
260+
map_data = map_facet_data,
286261

287262
attach_axes = function(table, layout, ranges, coord, theme, params) {
288263

R/layer.R

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@
7171
#' @param params Additional parameters to the `geom` and `stat`.
7272
#' @param key_glyph A legend key drawing function or a string providing the
7373
#' function name minus the `draw_key_` prefix. See [draw_key] for details.
74+
#' @param layout Argument to control layout at the layer level. Consult the
75+
#' faceting documentation to view appropriate values.
7476
#' @param layer_class The type of layer object to be constructed. This is
7577
#' intended for ggplot2 internal use only.
7678
#' @keywords internal
@@ -98,7 +100,7 @@ layer <- function(geom = NULL, stat = NULL,
98100
data = NULL, mapping = NULL,
99101
position = NULL, params = list(),
100102
inherit.aes = TRUE, check.aes = TRUE, check.param = TRUE,
101-
show.legend = NA, key_glyph = NULL, layer_class = Layer) {
103+
show.legend = NA, key_glyph = NULL, layout = NULL, layer_class = Layer) {
102104
call_env <- caller_env()
103105
user_env <- caller_env(2)
104106

@@ -132,7 +134,7 @@ layer <- function(geom = NULL, stat = NULL,
132134
geom_params <- params[intersect(names(params), geom$parameters(TRUE))]
133135
stat_params <- params[intersect(names(params), stat$parameters(TRUE))]
134136

135-
ignore <- c("key_glyph", "name")
137+
ignore <- c("key_glyph", "name", "layout")
136138
all <- c(geom$parameters(TRUE), stat$parameters(TRUE), geom$aesthetics(), position$aesthetics(), ignore)
137139

138140
# Take care of plain patterns provided as aesthetic
@@ -192,7 +194,8 @@ layer <- function(geom = NULL, stat = NULL,
192194
position = position,
193195
inherit.aes = inherit.aes,
194196
show.legend = show.legend,
195-
name = params$name
197+
name = params$name,
198+
layout = layout %||% params$layout
196199
)
197200
}
198201

@@ -282,6 +285,7 @@ Layer <- ggproto("Layer", NULL,
282285
} else {
283286
self$computed_mapping <- self$mapping
284287
}
288+
attr(data, "layout") <- self$layout
285289

286290
data
287291
},

man/facet_grid.Rd

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

man/facet_null.Rd

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/facet_wrap.Rd

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/layer.Rd

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)