Skip to content

Commit 2c631d5

Browse files
committed
Merge pull request #974 from lionel-/facet-titles
Facet titles
2 parents efd3cbd + 0ad37dd commit 2c631d5

File tree

11 files changed

+353
-37
lines changed

11 files changed

+353
-37
lines changed

NEWS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ ggplot2 1.0.1.9000
3838

3939
* `Geom`, `Stat` and `Position` are now exported, making it easier to create
4040
new geoms, stats and positions in other packages (#989).
41+
* `facet_grid()` and `facet_wrap()` gain a `switch` argument that
42+
allows the facet titles to be displayed near the axes. They then act
43+
as axes subtitles. Can be set to "x", "y" or "both" (the latter only
44+
for grids) to control which label strips are switched. (@lionel-)
45+
46+
* `Geom` is now exported, making it easier to create new geoms in other
47+
packages (#989).
4148

4249
* New `theme_void()`, which is completely empty. Useful for plots with non
4350
standard coordinates or for producing numerical drawings with R. (@jiho, #976)

R/facet-grid-.r

Lines changed: 117 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
#' @param as.table If \code{TRUE}, the default, the facets are laid out like
2828
#' a table with highest values at the bottom-right. If \code{FALSE}, the
2929
#' facets are laid out like a plot with the highest value at the top-right.
30+
#' @param switch By default, the labels are displayed on the top and
31+
#' right of the plot. If \code{"x"}, the top labels will be
32+
#' displayed to the bottom. If \code{"y"}, the right-hand side
33+
#' labels will be displayed to the left. Can also be set to
34+
#' \code{"both"}.
3035
#' @param shrink If \code{TRUE}, will shrink scales to fit output of
3136
#' statistics, not raw data. If \code{FALSE}, will be range of raw data
3237
#' before statistical summary.
@@ -162,8 +167,22 @@
162167
#' mg + facet_grid(vs + am ~ gear, margins = "vs")
163168
#' mg + facet_grid(vs + am ~ gear, margins = "gear")
164169
#' mg + facet_grid(vs + am ~ gear, margins = c("gear", "am"))
170+
#'
171+
#' # The facet strips can be displayed near the axes with switch
172+
#' data <- transform(mtcars,
173+
#' am = factor(am, levels = 0:1, c("Automatic", "Manual")),
174+
#' gear = factor(gear, levels = 3:5, labels = c("Three", "Four", "Five"))
175+
#' )
176+
#' p <- ggplot(data, aes(mpg, disp)) + geom_point()
177+
#' p + facet_grid(am ~ gear, switch = "both") + theme_light()
178+
#'
179+
#' # It may be more aesthetic to use a theme without boxes around
180+
#' # around the strips.
181+
#' p + facet_grid(am ~ gear + vs, switch = "y") + theme_minimal()
182+
#' p + facet_grid(am ~ ., switch = "y") +
183+
#' theme_gray() %+replace% theme(strip.background = element_blank())
165184
#' }
166-
facet_grid <- function(facets, margins = FALSE, scales = "fixed", space = "fixed", shrink = TRUE, labeller = "label_value", as.table = TRUE, drop = TRUE) {
185+
facet_grid <- function(facets, margins = FALSE, scales = "fixed", space = "fixed", shrink = TRUE, labeller = "label_value", as.table = TRUE, switch = NULL, drop = TRUE) {
167186
scales <- match.arg(scales, c("fixed", "free_x", "free_y", "free"))
168187
free <- list(
169188
x = any(scales %in% c("free_x", "free")),
@@ -200,8 +219,8 @@ facet_grid <- function(facets, margins = FALSE, scales = "fixed", space = "fixed
200219

201220
facet(
202221
rows = rows, cols = cols, margins = margins, shrink = shrink,
203-
free = free, space_free = space_free,
204-
labeller = labeller, as.table = as.table, drop = drop,
222+
free = free, space_free = space_free, labeller = labeller,
223+
as.table = as.table, switch = switch, drop = drop,
205224
subclass = "grid"
206225
)
207226
}
@@ -239,17 +258,85 @@ facet_render.grid <- function(facet, panel, coord, theme, geom_grobs) {
239258
strips$r$heights <- panels$heights
240259
strips$t$widths <- panels$widths
241260

261+
# Check if switch is consistent with grid layout
262+
switch_x <- !is.null(facet$switch) && facet$switch %in% c("both", "x")
263+
switch_y <- !is.null(facet$switch) && facet$switch %in% c("both", "y")
264+
if (switch_x && length(strips$t) == 0) {
265+
facet$switch <- if (facet$switch == "both") "y" else NULL
266+
switch_x <- FALSE
267+
warning("Cannot switch x axis strips as they do not exist", call. = FALSE)
268+
}
269+
if (switch_y && length(strips$r) == 0) {
270+
facet$switch <- if (facet$switch == "both") "x" else NULL
271+
switch_y <- FALSE
272+
warning("Cannot switch y axis strips as they do not exist", call. = FALSE)
273+
}
274+
275+
242276
# Combine components into complete plot
243-
top <- strips$t
244-
top <- gtable_add_cols(top, strips$r$widths)
245-
top <- gtable_add_cols(top, axes$l$widths, pos = 0)
277+
if (is.null(facet$switch)) {
278+
top <- strips$t
279+
top <- gtable_add_cols(top, strips$r$widths)
280+
top <- gtable_add_cols(top, axes$l$widths, pos = 0)
246281

247-
center <- cbind(axes$l, panels, strips$r, z = c(2, 1, 3))
248-
bottom <- axes$b
249-
bottom <- gtable_add_cols(bottom, strips$r$widths)
250-
bottom <- gtable_add_cols(bottom, axes$l$widths, pos = 0)
282+
center <- cbind(axes$l, panels, strips$r, z = c(2, 1, 3))
283+
bottom <- axes$b
284+
bottom <- gtable_add_cols(bottom, strips$r$widths)
285+
bottom <- gtable_add_cols(bottom, axes$l$widths, pos = 0)
286+
287+
complete <- rbind(top, center, bottom, z = c(1, 2, 3))
288+
289+
} else {
290+
# Add padding between the switched strips and the axes
291+
padding <- convertUnit(theme$strip.switch.pad.grid, "cm")
292+
293+
if (switch_x) {
294+
t_heights <- c(padding, strips$t$heights)
295+
gt_t <- gtable(widths = strips$t$widths, heights = unit(t_heights, "cm"))
296+
gt_t <- gtable_add_grob(gt_t, strips$t, name = strips$t$name, clip = "off",
297+
t = 1, l = 1, b = -1, r = -1)
298+
}
299+
if (switch_y) {
300+
r_widths <- c(strips$r$widths, padding)
301+
gt_r <- gtable(widths = unit(r_widths, "cm"), heights = strips$r$heights)
302+
gt_r <- gtable_add_grob(gt_r, strips$r, name = strips$r$name, clip = "off",
303+
t = 1, l = 1, b = -1, r = -1)
304+
}
305+
306+
# Combine plot elements according to strip positions
307+
if (switch_x && switch_y) {
308+
center <- cbind(gt_r, axes$l, panels, z = c(3, 2, 1))
309+
310+
bottom <- rbind(axes$b, gt_t)
311+
bottom <- gtable_add_cols(bottom, axes$l$widths, pos = 0)
312+
bottom <- gtable_add_cols(bottom, gt_r$widths, pos = 0)
313+
314+
complete <- rbind(center, bottom, z = c(1, 2))
315+
} else if (switch_x) {
316+
center <- cbind(axes$l, panels, strips$r, z = c(2, 1, 3))
251317

252-
complete <- rbind(top, center, bottom, z = c(1, 2, 3))
318+
bottom <- rbind(axes$b, gt_t)
319+
bottom <- gtable_add_cols(bottom, strips$r$widths)
320+
bottom <- gtable_add_cols(bottom, axes$l$widths, pos = 0)
321+
322+
complete <- rbind(center, bottom, z = c(1, 2))
323+
} else if (switch_y) {
324+
top <- strips$t
325+
top <- gtable_add_cols(top, gt_r$widths, pos = 0)
326+
top <- gtable_add_cols(top, axes$l$widths, pos = 0)
327+
328+
center <- cbind(gt_r, axes$l, panels, z = c(3, 2, 1))
329+
bottom <- axes$b
330+
bottom <- gtable_add_cols(bottom, axes$l$widths, pos = 0)
331+
bottom <- gtable_add_cols(bottom, gt_r$widths, pos = 0)
332+
333+
complete <- rbind(top, center, bottom, z = c(1, 2, 3))
334+
} else {
335+
stop("`switch` must be either NULL, 'both', 'x', or 'y'",
336+
call. = FALSE)
337+
}
338+
}
339+
253340
complete$respect <- panels$respect
254341
complete$name <- "layout"
255342
bottom <- axes$b
@@ -262,13 +349,23 @@ facet_strips.grid <- function(facet, panel, theme) {
262349
col_vars <- unique(panel$layout[names(facet$cols)])
263350
row_vars <- unique(panel$layout[names(facet$rows)])
264351

352+
dir <- list(r = "r", t = "t")
353+
if (!is.null(facet$switch) && facet$switch %in% c("both", "x")) {
354+
dir$t <- "b"
355+
}
356+
if (!is.null(facet$switch) && facet$switch %in% c("both", "y")){
357+
dir$r <- "l"
358+
}
359+
265360
list(
266-
r = build_strip(panel, row_vars, facet$labeller, theme, "r"),
267-
t = build_strip(panel, col_vars, facet$labeller, theme, "t")
361+
r = build_strip(panel, row_vars, facet$labeller,
362+
theme, dir$r, switch = facet$switch),
363+
t = build_strip(panel, col_vars, facet$labeller,
364+
theme, dir$t, switch = facet$switch)
268365
)
269366
}
270367

271-
build_strip <- function(panel, label_df, labeller, theme, side = "right") {
368+
build_strip <- function(panel, label_df, labeller, theme, side = "right", switch = NULL) {
272369
side <- match.arg(side, c("top", "left", "bottom", "right"))
273370
horizontal <- side %in% c("top", "bottom")
274371
labeller <- match.fun(labeller)
@@ -290,17 +387,21 @@ build_strip <- function(panel, label_df, labeller, theme, side = "right") {
290387
labels[, i] <- labeller(names(label_df)[i], label_df[, i])
291388
}
292389

390+
# Display the mirror of the y strip labels if switched
391+
if (!is.null(switch) && switch %in% c("both", "y")) {
392+
theme$strip.text.y$angle <- adjust_angle(theme$strip.text.y$angle)
393+
}
394+
293395
# Render as grobs
294396
grobs <- apply(labels, c(1,2), ggstrip, theme = theme,
295397
horizontal = horizontal)
296398

297399
# Create layout
298400
name <- paste("strip", side, sep = "-")
299401
if (horizontal) {
300-
grobs <- t(grobs)
301-
302402
# Each row is as high as the highest and as a wide as the panel
303403
row_height <- function(row) max(laply(row, height_cm))
404+
grobs <- t(grobs)
304405
heights <- unit(apply(grobs, 1, row_height), "cm")
305406
widths <- unit(rep(1, ncol(grobs)), "null")
306407
} else {

R/facet-labels.r

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ labeller <- function(..., keep.as.numeric=FALSE) {
182182

183183

184184
# Grob for strip labels
185-
ggstrip <- function(text, horizontal=TRUE, theme) {
185+
ggstrip <- function(text, horizontal = TRUE, theme) {
186186
text_theme <- if (horizontal) "strip.text.x" else "strip.text.y"
187187
if (is.list(text)) text <- text[[1]]
188188

@@ -197,3 +197,15 @@ ggstrip <- function(text, horizontal=TRUE, theme) {
197197
height = grobHeight(label) + unit(0.5, "lines")
198198
))
199199
}
200+
201+
202+
# Helper to adjust angle of switched strips
203+
adjust_angle <- function(angle) {
204+
if (is.null(angle)) {
205+
-90
206+
} else if ((angle + 180) > 360) {
207+
angle - 180
208+
} else {
209+
angle + 180
210+
}
211+
}

0 commit comments

Comments
 (0)