Skip to content

Commit 204c238

Browse files
authored
Axes at interior panels #4064 (#4467)
* Add axis drawing to fixed scale facet_wrap * Add draw.axis argument to facet_grid * Switch to facet_wrap approach when drawing additional axis in facet_grid * Document draw.axis argument * Add unit tests for draw.axes * resolve conflict * Rename user-facing argument to 'axes' * Sync latest changes * Mechanism for label suppression * censoring for wrap * Label censoring for grid * Test censoring logic * Label censoring for wrap * Test logic for wrap censoring * Visual test for censoring * Add NEWS bullet * Better panel spacing with empty panels * Only draw first in stack * Funnel radial r-axis through CoordCartesian * Fix order of theta grobs * use dot.case instead of snake_case for argument * use dot.case instead of snake_case for argument * More snake_case to dot.case conversions * New args before deprecated args * add examples * add `weave_axes` helper * use helper
1 parent 0f9fb64 commit 204c238

14 files changed

+1079
-69
lines changed

NEWS.md

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

3+
* The new argument `axes` in `facet_grid()` and `facet_wrap()` controls the
4+
display of axes at interior panel positions. Additionally, the `axis.labels`
5+
argument can be used to only draw tick marks or fully labelled axes
6+
(@teunbrand, #4064).
7+
38
* The `name` argument in most scales is now explicitly the first argument
49
(#5535)
510

R/coord-cartesian-.R

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,27 @@ CoordCartesian <- ggproto("CoordCartesian", Coord,
117117

118118
render_axis_h = function(panel_params, theme) {
119119
list(
120-
top = panel_guides_grob(panel_params$guides, position = "top", theme = theme),
121-
bottom = panel_guides_grob(panel_params$guides, position = "bottom", theme = theme)
120+
top = panel_guides_grob(
121+
panel_params$guides, position = "top",
122+
theme = theme, labels = panel_params$draw_labels$top
123+
),
124+
bottom = panel_guides_grob(
125+
panel_params$guides, position = "bottom",
126+
theme = theme, labels = panel_params$draw_labels$bottom
127+
)
122128
)
123129
},
124130

125131
render_axis_v = function(panel_params, theme) {
126132
list(
127-
left = panel_guides_grob(panel_params$guides, position = "left", theme = theme),
128-
right = panel_guides_grob(panel_params$guides, position = "right", theme = theme)
133+
left = panel_guides_grob(
134+
panel_params$guides, position = "left",
135+
theme = theme, labels = panel_params$draw_labels$left
136+
),
137+
right = panel_guides_grob(
138+
panel_params$guides, position = "right",
139+
theme = theme, labels = panel_params$draw_labels$right
140+
)
129141
)
130142
}
131143
)
@@ -146,10 +158,11 @@ view_scales_from_scale <- function(scale, coord_limits = NULL, expand = TRUE) {
146158
view_scales
147159
}
148160

149-
panel_guides_grob <- function(guides, position, theme) {
161+
panel_guides_grob <- function(guides, position, theme, labels = NULL) {
150162
if (!inherits(guides, "Guides")) {
151163
return(zeroGrob())
152164
}
153165
pair <- guides$get_position(position)
166+
pair$params$draw_label <- labels %||% NULL
154167
pair$guide$draw(theme, params = pair$params)
155168
}

R/coord-radial.R

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -241,20 +241,14 @@ CoordRadial <- ggproto("CoordRadial", Coord,
241241
if (self$r_axis_inside) {
242242
return(list(left = zeroGrob(), right = zeroGrob()))
243243
}
244-
list(
245-
left = panel_guides_grob(panel_params$guides, position = "left", theme = theme),
246-
right = panel_guides_grob(panel_params$guides, position = "right", theme = theme)
247-
)
244+
CoordCartesian$render_axis_v(panel_params, theme)
248245
},
249246

250247
render_axis_h = function(self, panel_params, theme) {
251248
if (self$r_axis_inside) {
252249
return(list(top = zeroGrob(), bottom = zeroGrob()))
253250
}
254-
list(
255-
top = panel_guides_grob(panel_params$guides, position = "top", theme = theme),
256-
bottom = panel_guides_grob(panel_params$guides, position = "bottom", theme = theme)
257-
)
251+
CoordCartesian$render_axis_h(panel_params, theme)
258252
},
259253

260254
render_bg = function(self, panel_params, theme) {

R/facet-.R

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,3 +701,31 @@ render_strips <- function(x = NULL, y = NULL, labeller, theme) {
701701
y = build_strip(y, labeller, theme, FALSE)
702702
)
703703
}
704+
705+
706+
censor_labels <- function(ranges, layout, labels) {
707+
if (labels$x && labels$y) {
708+
return(ranges)
709+
}
710+
draw <- matrix(
711+
TRUE, length(ranges), 4,
712+
dimnames = list(NULL, c("top", "bottom", "left", "right"))
713+
)
714+
715+
if (!labels$x) {
716+
xmax <- stats::ave(layout$ROW, layout$COL, FUN = max)
717+
xmin <- stats::ave(layout$ROW, layout$COL, FUN = min)
718+
draw[which(layout$ROW != xmax), "bottom"] <- FALSE
719+
draw[which(layout$ROW != xmin), "top"] <- FALSE
720+
}
721+
if (!labels$y) {
722+
ymax <- stats::ave(layout$COL, layout$ROW, FUN = max)
723+
ymin <- stats::ave(layout$COL, layout$ROW, FUN = min)
724+
draw[which(layout$COL != ymax), "right"] <- FALSE
725+
draw[which(layout$COL != ymin), "left"] <- FALSE
726+
}
727+
for (i in seq_along(ranges)) {
728+
ranges[[i]]$draw_labels <- as.list(draw[i, ])
729+
}
730+
ranges
731+
}

R/facet-grid-.R

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ NULL
5959
#' variables for which margins are to be created.
6060
#' @param facets `r lifecycle::badge("deprecated")` Please use `rows`
6161
#' and `cols` instead.
62+
#' @param axes Determines which axes will be drawn. When `"margins"`
63+
#' (default), axes will be drawn at the exterior margins. `"all_x"` and
64+
#' `"all_y"` will draw the respective axes at the interior panels too, whereas
65+
#' `"all"` will draw all axes at all panels.
66+
#' @param axis.labels Determines whether to draw labels for interior axes when
67+
#' the `axes` argument is not `"margins"`. When `"all"` (default), all
68+
#' interior axes get labels. When `"margins"`, only the exterior axes get
69+
#' labels and the interior axes get none. When `"all_x"` or `"all_y"`, only
70+
#' draws the labels at the interior axes in the x- or y-direction
71+
#' respectively.
6272
#' @export
6373
#' @examples
6474
#' p <- ggplot(mpg, aes(displ, cty)) + geom_point()
@@ -79,6 +89,12 @@ NULL
7989
#' facet_grid(cols = vars(cyl)) +
8090
#' geom_point(data = df, colour = "red", size = 2)
8191
#'
92+
#' # When scales are constant, duplicated axes can be shown with
93+
#' # or without labels
94+
#' ggplot(mpg, aes(cty, hwy)) +
95+
#' geom_point() +
96+
#' facet_grid(year ~ drv, axes = "all", axis.labels = "all_x")
97+
#'
8298
#' # Free scales -------------------------------------------------------
8399
#' # You can also choose whether the scales should be constant
84100
#' # across all panels (the default), or whether they should be allowed
@@ -112,6 +128,7 @@ facet_grid <- function(rows = NULL, cols = NULL, scales = "fixed",
112128
space = "fixed", shrink = TRUE,
113129
labeller = "label_value", as.table = TRUE,
114130
switch = NULL, drop = TRUE, margins = FALSE,
131+
axes = "margins", axis.labels = "all",
115132
facets = deprecated()) {
116133
# `facets` is deprecated and renamed to `rows`
117134
if (lifecycle::is_present(facets)) {
@@ -137,6 +154,20 @@ facet_grid <- function(rows = NULL, cols = NULL, scales = "fixed",
137154
y = any(space %in% c("free_y", "free"))
138155
)
139156

157+
draw_axes <- arg_match0(axes, c("margins", "all_x", "all_y", "all"))
158+
draw_axes <- list(
159+
x = any(draw_axes %in% c("all_x", "all")),
160+
y = any(draw_axes %in% c("all_y", "all"))
161+
)
162+
163+
# Omitting labels is special-cased internally, so even when no internal axes
164+
# are to be drawn, register as labelled.
165+
axis_labels <- arg_match0(axis.labels, c("margins", "all_x", "all_y", "all"))
166+
axis_labels <- list(
167+
x = !draw_axes$x || any(axis_labels %in% c("all_x", "all")),
168+
y = !draw_axes$y || any(axis_labels %in% c("all_y", "all"))
169+
)
170+
140171
if (!is.null(switch)) {
141172
arg_match0(switch, c("both", "x", "y"))
142173
}
@@ -150,7 +181,8 @@ facet_grid <- function(rows = NULL, cols = NULL, scales = "fixed",
150181
shrink = shrink,
151182
params = list(rows = facets_list$rows, cols = facets_list$cols, margins = margins,
152183
free = free, space_free = space_free, labeller = labeller,
153-
as.table = as.table, switch = switch, drop = drop)
184+
as.table = as.table, switch = switch, drop = drop,
185+
draw_axes = draw_axes, axis_labels = axis_labels)
154186
)
155187
}
156188

@@ -306,8 +338,22 @@ FacetGrid <- ggproto("FacetGrid", Facet,
306338
cli::cli_abort("{.fn {snake_class(coord)}} doesn't support free scales.")
307339
}
308340

309-
cols <- which(layout$ROW == 1)
310-
rows <- which(layout$COL == 1)
341+
if (!params$axis_labels$x) {
342+
cols <- seq_len(nrow(layout))
343+
x_axis_order <- as.integer(layout$PANEL[order(layout$ROW, layout$COL)])
344+
} else {
345+
cols <- which(layout$ROW == 1)
346+
x_axis_order <- layout$COL
347+
}
348+
if (!params$axis_labels$y) {
349+
rows <- seq_len(nrow(layout))
350+
y_axis_order <- as.integer(layout$PANEL[order(layout$ROW, layout$COL)])
351+
} else {
352+
rows <- which(layout$COL == 1)
353+
y_axis_order <- layout$ROW
354+
}
355+
356+
ranges <- censor_labels(ranges, layout, params$axis_labels)
311357
axes <- render_axes(ranges[cols], ranges[rows], coord, theme, transpose = TRUE)
312358

313359
col_vars <- unique0(layout[names(params$cols)])
@@ -334,7 +380,8 @@ FacetGrid <- ggproto("FacetGrid", Facet,
334380
}
335381
ncol <- max(layout$COL)
336382
nrow <- max(layout$ROW)
337-
panel_table <- matrix(panels, nrow = nrow, ncol = ncol, byrow = TRUE)
383+
mtx <- function(x) matrix(x, nrow = nrow, ncol = ncol, byrow = TRUE)
384+
panel_table <- mtx(panels)
338385

339386
# @kohske
340387
# Now size of each panel is calculated using PANEL$ranges, which is given by
@@ -358,7 +405,7 @@ FacetGrid <- ggproto("FacetGrid", Facet,
358405
}
359406

360407
panel_table <- gtable_matrix("layout", panel_table,
361-
panel_widths, panel_heights, respect = respect, clip = coord$clip, z = matrix(1, ncol = ncol, nrow = nrow))
408+
panel_widths, panel_heights, respect = respect, clip = coord$clip, z = mtx(1))
362409
panel_table$layout$name <- paste0('panel-', rep(seq_len(nrow), ncol), '-', rep(seq_len(ncol), each = nrow))
363410

364411
panel_table <- gtable_add_col_space(panel_table,
@@ -367,17 +414,27 @@ FacetGrid <- ggproto("FacetGrid", Facet,
367414
theme$panel.spacing.y %||% theme$panel.spacing)
368415

369416
# Add axes
370-
panel_table <- gtable_add_rows(panel_table, max_height(axes$x$top), 0)
371-
panel_table <- gtable_add_rows(panel_table, max_height(axes$x$bottom), -1)
372-
panel_table <- gtable_add_cols(panel_table, max_width(axes$y$left), 0)
373-
panel_table <- gtable_add_cols(panel_table, max_width(axes$y$right), -1)
374-
panel_pos_col <- panel_cols(panel_table)
375-
panel_pos_rows <- panel_rows(panel_table)
417+
if (params$draw_axes$x) {
418+
axes$x <- lapply(axes$x, function(x) mtx(x[x_axis_order]))
419+
panel_table <- weave_axes(panel_table, axes$x)$panels
420+
} else {
421+
panel_table <- gtable_add_rows(panel_table, max_height(axes$x$top), 0)
422+
panel_table <- gtable_add_rows(panel_table, max_height(axes$x$bottom), -1)
423+
panel_pos_col <- panel_cols(panel_table)
424+
panel_table <- gtable_add_grob(panel_table, axes$x$top, 1, panel_pos_col$l, clip = "off", name = paste0("axis-t-", seq_along(axes$x$top)), z = 3)
425+
panel_table <- gtable_add_grob(panel_table, axes$x$bottom, -1, panel_pos_col$l, clip = "off", name = paste0("axis-b-", seq_along(axes$x$bottom)), z = 3)
426+
}
376427

377-
panel_table <- gtable_add_grob(panel_table, axes$x$top, 1, panel_pos_col$l, clip = "off", name = paste0("axis-t-", seq_along(axes$x$top)), z = 3)
378-
panel_table <- gtable_add_grob(panel_table, axes$x$bottom, -1, panel_pos_col$l, clip = "off", name = paste0("axis-b-", seq_along(axes$x$bottom)), z = 3)
379-
panel_table <- gtable_add_grob(panel_table, axes$y$left, panel_pos_rows$t, 1, clip = "off", name = paste0("axis-l-", seq_along(axes$y$left)), z = 3)
380-
panel_table <- gtable_add_grob(panel_table, axes$y$right, panel_pos_rows$t, -1, clip = "off", name = paste0("axis-r-", seq_along(axes$y$right)), z= 3)
428+
if (params$draw_axes$y) {
429+
axes$y <- lapply(axes$y, function(y) mtx(y[y_axis_order]))
430+
panel_table <- weave_axes(panel_table, axes$y)$panels
431+
} else {
432+
panel_table <- gtable_add_cols(panel_table, max_width(axes$y$left), 0)
433+
panel_table <- gtable_add_cols(panel_table, max_width(axes$y$right), -1)
434+
panel_pos_rows <- panel_rows(panel_table)
435+
panel_table <- gtable_add_grob(panel_table, axes$y$left, panel_pos_rows$t, 1, clip = "off", name = paste0("axis-l-", seq_along(axes$y$left)), z = 3)
436+
panel_table <- gtable_add_grob(panel_table, axes$y$right, panel_pos_rows$t, -1, clip = "off", name = paste0("axis-r-", seq_along(axes$y$right)), z= 3)
437+
}
381438

382439
# Add strips
383440
switch_x <- !is.null(params$switch) && params$switch %in% c("both", "x")

0 commit comments

Comments
 (0)