diff --git a/DESCRIPTION b/DESCRIPTION index bcbfff5607..82e1a1ed9a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -221,6 +221,7 @@ Collate: 'stat-summary.r' 'stat-unique.r' 'stat-ydensity.r' + 'subplot.R' 'summarise-plot.R' 'summary.r' 'theme-elements.r' diff --git a/NAMESPACE b/NAMESPACE index e14b3233d1..2e81a80aa1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -537,6 +537,8 @@ export(stat_summary_bin) export(stat_summary_hex) export(stat_unique) export(stat_ydensity) +export(subplot_grid) +export(subplot_wrap) export(summarise_coord) export(summarise_layers) export(summarise_layout) diff --git a/NEWS.md b/NEWS.md index 85bb67443f..9a282c7464 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # ggplot2 3.0.0.9000 +* Add `subplot_wrap()` and `subplot_grid()` which make it easier to use + faceting as a way to create subplots of different variables (@jiho, #2786). + * `stat_contour()`, `stat_density2d()`, `stat_bin2d()`, `stat_binhex()` now calculate normalized statistics including `nlevel`, `ndensity`, and `ncount`. Also, `stat_density()` now includes the calculated statistic diff --git a/R/subplot.R b/R/subplot.R new file mode 100644 index 0000000000..7a1d96d673 --- /dev/null +++ b/R/subplot.R @@ -0,0 +1,111 @@ +#' Create a ribbon or grid of subplots +#' +#' `subplot_wrap` and `subplot_grid` are variations of [facet_wrap()] and +#' [facet_grid()] that are more appropriate when the facets represent +#' different variables rather than different subsets of the same variable. +#' +#' @inheritParams facet_wrap +#' @inheritParams facet_grid +#' @param axis Axis to which the variables of the subplots are mapped. +#' @param scales Either "auto", in which case only the scale of the axis +#' designated by `axis` is free, or "free" in which case both scales are +#' free. +#' @param ... Passed to [facet_wrap()] or [facet_grid()]. +#' @rdname subplot +#' @export +#' @examples +#' # Stack different variables in the same column +#' # NB: see tidyr::gather for a more practical alternative +#' cars <- stack(mtcars, select=c(disp, drat, qsec)) +#' cars$mpg <- mtcars$mpg +#' cars$cyl <- mtcars$cyl +#' head(cars) +#' +#' # inspect the distribution of all stacked variables at once +#' p <- ggplot(cars) + geom_histogram(aes(values), bins=10) +#' p + facet_wrap(~ind, scales="free_x") +#' p + subplot_wrap(~ind) +#' +#' # examine the relationship of all variables with mpg +#' p <- ggplot(cars) + geom_point(aes(values, mpg)) +#' p + facet_wrap(~ind, scales="free_x") +#' p + subplot_wrap(~ind) +#' # or in the other direction +#' p <- ggplot(cars) + geom_point(aes(mpg, values)) +#' p + facet_wrap(~ind, scales="free_y") +#' p + subplot_wrap(~ind, axis="y") +#' +#' # do the same but with an additional conditional variable, hence +#' # generating a grid of subplots +#' p <- ggplot(cars) + geom_point(aes(values, mpg)) +#' p + facet_grid(cyl~ind, scales="free_x") +#' p + subplot_grid(cyl~ind) +#' +#' # the facet labels play the role of the axis title and respect its theme +#' ggplot(cars) + geom_point(aes(values, mpg)) + subplot_wrap(~ind) +#' theme_update(axis.title=element_text(colour="red")) +#' ggplot(cars) + geom_point(aes(values, mpg)) + subplot_wrap(~ind) +#' # but only when the plot if created *after* global theme updates +#' p <- ggplot(cars) + geom_point(aes(values, mpg)) + subplot_wrap(~ind) +#' theme_update(axis.title=element_text(colour="blue")) +#' p +#' p + theme(axis.title=element_text(colour="green")) +#' # here, in both cases, the facet labels stay blue, unfortunately +subplot_wrap <- function(facets, axis="x", scales="auto", ...) { + axis <- match.arg(axis, c("x", "y")) + list( + facet_wrap(facets=facets, + scales=subplot_scales(scales, axis), + strip.position=ifelse(axis=="x", "bottom", "left"), ...), + subplot_theme(axis) + ) +} + +#' @rdname subplot +#' @export +subplot_grid <- function(rows=NULL, cols=NULL, axis="x", scales="auto", ...) { + axis <- match.arg(axis, c("x", "y")) + list( + facet_grid(rows=rows, cols=cols, + scales=subplot_scales(scales, axis), switch=axis, ...), + subplot_theme(axis) + ) +} + +subplot_scales <- function(scales, axis) { + scales <- match.arg(scales, c("auto", "free")) + # with facets representing different variables, the scale of the axis + # mapped to the variables should be free + if (scales == "auto") { + scales <- paste0("free_", axis) + } + return(scales) +} + +subplot_theme <- function(axis) { + if (axis == "x") { + th <- theme( + # give the strip text the appearance of the x axis title + strip.background.x=element_blank(), + strip.text.x=calc_element("axis.title.x", theme_get()), + # TODO this gets the theme elements at the time of plot creation, rather than rendering, which means only global theme changes before the creation of the plot are respected. Ideally strip.text.* should inherit whatever is in axis.title.* at the time of rendering. Not sure if/how this is possible + # remove the x axis title + axis.title.x=element_blank() + ) + } else { + # do the same but for the y axis + th <- theme( + strip.background.y=element_blank(), + strip.text.y=calc_element("axis.title.y", theme_get()), + axis.title.y=element_blank() + ) + # and rotate the text the correct way + th$strip.text.y$angle <- -90 + } + # put the facet strips where the axis titles would be + th + theme( + strip.placement="outside", + strip.switch.pad.wrap=unit(0, "npc"), + strip.switch.pad.grid=unit(0, "npc") + ) +} diff --git a/man/subplot.Rd b/man/subplot.Rd new file mode 100644 index 0000000000..ee5b8b229f --- /dev/null +++ b/man/subplot.Rd @@ -0,0 +1,97 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/subplot.R +\name{subplot_wrap} +\alias{subplot_wrap} +\alias{subplot_grid} +\title{Create a ribbon or grid of subplots} +\usage{ +subplot_wrap(facets, axis = "x", scales = "auto", ...) + +subplot_grid(rows = NULL, cols = NULL, axis = "x", scales = "auto", + ...) +} +\arguments{ +\item{facets}{A set of variables or expressions quoted by \code{\link[=vars]{vars()}} +and defining faceting groups on the rows or columns dimension. +The variables can be named (the names are passed to \code{labeller}). + +For compatibility with the classic interface, can also be a +formula or character vector. Use either a one sided formula, `~a +\itemize{ +\item b\code{, or a character vector,}c("a", "b")`. +}} + +\item{axis}{Axis to which the variables of the subplots are mapped.} + +\item{scales}{Either "auto", in which case only the scale of the axis +designated by \code{axis} is free, or "free" in which case both scales are +free.} + +\item{...}{Passed to \code{\link[=facet_wrap]{facet_wrap()}} or \code{\link[=facet_grid]{facet_grid()}}.} + +\item{rows}{A set of variables or expressions quoted by +\code{\link[=vars]{vars()}} and defining faceting groups on the rows or columns +dimension. The variables can be named (the names are passed to +\code{labeller}). + +For compatibility with the classic interface, \code{rows} can also be +a formula with the rows (of the tabular display) on the LHS and +the columns (of the tabular display) on the RHS; the dot in the +formula is used to indicate there should be no faceting on this +dimension (either row or column).} + +\item{cols}{A set of variables or expressions quoted by +\code{\link[=vars]{vars()}} and defining faceting groups on the rows or columns +dimension. The variables can be named (the names are passed to +\code{labeller}). + +For compatibility with the classic interface, \code{rows} can also be +a formula with the rows (of the tabular display) on the LHS and +the columns (of the tabular display) on the RHS; the dot in the +formula is used to indicate there should be no faceting on this +dimension (either row or column).} +} +\description{ +\code{subplot_wrap} and \code{subplot_grid} are variations of \code{\link[=facet_wrap]{facet_wrap()}} and +\code{\link[=facet_grid]{facet_grid()}} that are more appropriate when the facets represent +different variables rather than different subsets of the same variable. +} +\examples{ +# Stack different variables in the same column +# NB: see tidyr::gather for a more practical alternative +cars <- stack(mtcars, select=c(disp, drat, qsec)) +cars$mpg <- mtcars$mpg +cars$cyl <- mtcars$cyl +head(cars) + +# inspect the distribution of all stacked variables at once +p <- ggplot(cars) + geom_histogram(aes(values), bins=10) +p + facet_wrap(~ind, scales="free_x") +p + subplot_wrap(~ind) + +# examine the relationship of all variables with mpg +p <- ggplot(cars) + geom_point(aes(values, mpg)) +p + facet_wrap(~ind, scales="free_x") +p + subplot_wrap(~ind) +# or in the other direction +p <- ggplot(cars) + geom_point(aes(mpg, values)) +p + facet_wrap(~ind, scales="free_y") +p + subplot_wrap(~ind, axis="y") + +# do the same but with an additional conditional variable, hence +# generating a grid of subplots +p <- ggplot(cars) + geom_point(aes(values, mpg)) +p + facet_grid(cyl~ind, scales="free_x") +p + subplot_grid(cyl~ind) + +# the facet labels play the role of the axis title and respect its theme +ggplot(cars) + geom_point(aes(values, mpg)) + subplot_wrap(~ind) +theme_update(axis.title=element_text(colour="red")) +ggplot(cars) + geom_point(aes(values, mpg)) + subplot_wrap(~ind) +# but only when the plot if created *after* global theme updates +p <- ggplot(cars) + geom_point(aes(values, mpg)) + subplot_wrap(~ind) +theme_update(axis.title=element_text(colour="blue")) +p +p + theme(axis.title=element_text(colour="green")) +# here, in both cases, the facet labels stay blue, unfortunately +}