diff --git a/R/aes-evaluation.R b/R/aes-evaluation.R index 30d0b3f501..e29d0c5d25 100644 --- a/R/aes-evaluation.R +++ b/R/aes-evaluation.R @@ -32,7 +32,7 @@ #' Below follows an overview of the three stages of evaluation and how aesthetic #' evaluation can be controlled. #' -#' ## Stage 1: direct input +#' ## Stage 1: direct input at the start #' The default is to map at the beginning, using the layer data provided by #' the user. If you want to map directly from the layer data you should not do #' anything special. This is the only stage where the original layer data can @@ -87,9 +87,11 @@ #' ``` #' #' ## Complex staging -#' If you want to map the same aesthetic multiple times, e.g. map `x` to a -#' data column for the stat, but remap it for the geom, you can use the -#' `stage()` function to collect multiple mappings. +#' Sometimes, you may want to map the same aesthetic multiple times, e.g. map +#' `x` to a data column at the start for the layer stat, but remap it later to +#' a variable from the stat transformation for the layer geom. The `stage()` +#' function allows you to control multiple mappings for the same aesthetic +#' across all three stages of evaluation. #' #' ```r #' # Use stage to modify the scaled fill @@ -97,7 +99,7 @@ #' geom_boxplot(aes(fill = stage(class, after_scale = alpha(fill, 0.4)))) #' #' # Using data for computing summary, but placing label elsewhere. -#' # Also, we're making our own computed variable to use for the label. +#' # Also, we're making our own computed variables to use for the label. #' ggplot(mpg, aes(class, displ)) + #' geom_violin() + #' stat_summary( @@ -110,6 +112,11 @@ #' ) #' ``` #' +#' Conceptually, `aes(x)` is equivalent to `aes(stage(start = x))`, and +#' `aes(after_stat(count))` is equivalent to `aes(stage(after_stat = count))`, +#' and so on. `stage()` is most useful when at least two of its arguments are +#' specified. +#' #' ## Theme access #' The `from_theme()` function can be used to acces the [`element_geom()`] #' fields of the `theme(geom)` argument. Using `aes(colour = from_theme(ink))` @@ -332,7 +339,7 @@ strip_stage <- function(expr) { } else if (is_call(uq_expr, "stage")) { uq_expr <- call_match(uq_expr, stage) # Prefer stat mapping if present, otherwise original mapping (fallback to - # scale mapping) but there should always be two arguments to stage() + # scale mapping) uq_expr$after_stat %||% uq_expr$start %||% uq_expr$after_scale } else { expr diff --git a/man/aes_eval.Rd b/man/aes_eval.Rd index 11b8d2f1bd..aaf4c55277 100644 --- a/man/aes_eval.Rd +++ b/man/aes_eval.Rd @@ -49,7 +49,7 @@ should be evaluated. \section{Staging}{ Below follows an overview of the three stages of evaluation and how aesthetic evaluation can be controlled. -\subsection{Stage 1: direct input}{ +\subsection{Stage 1: direct input at the start}{ The default is to map at the beginning, using the layer data provided by the user. If you want to map directly from the layer data you should not do @@ -108,16 +108,18 @@ ggplot(mpg, aes(cty, colour = factor(cyl))) + \subsection{Complex staging}{ -If you want to map the same aesthetic multiple times, e.g. map \code{x} to a -data column for the stat, but remap it for the geom, you can use the -\code{stage()} function to collect multiple mappings. +Sometimes, you may want to map the same aesthetic multiple times, e.g. map +\code{x} to a data column at the start for the layer stat, but remap it later to +a variable from the stat transformation for the layer geom. The \code{stage()} +function allows you to control multiple mappings for the same aesthetic +across all three stages of evaluation. \if{html}{\out{
}}\preformatted{# Use stage to modify the scaled fill ggplot(mpg, aes(class, hwy)) + geom_boxplot(aes(fill = stage(class, after_scale = alpha(fill, 0.4)))) # Using data for computing summary, but placing label elsewhere. -# Also, we're making our own computed variable to use for the label. +# Also, we're making our own computed variables to use for the label. ggplot(mpg, aes(class, displ)) + geom_violin() + stat_summary( @@ -129,6 +131,11 @@ ggplot(mpg, aes(class, displ)) + fun.data = ~ round(data.frame(mean = mean(.x), sd = sd(.x)), 2) ) }\if{html}{\out{
}} + +Conceptually, \code{aes(x)} is equivalent to \code{aes(stage(start = x))}, and +\code{aes(after_stat(count))} is equivalent to \code{aes(stage(after_stat = count))}, +and so on. \code{stage()} is most useful when at least two of its arguments are +specified. } \subsection{Theme access}{ diff --git a/tests/testthat/test-aes-calculated.R b/tests/testthat/test-aes-calculated.R index 3ac8e06dbe..9d5c49c68e 100644 --- a/tests/testthat/test-aes-calculated.R +++ b/tests/testthat/test-aes-calculated.R @@ -124,3 +124,27 @@ test_that("functions can be masked", { expect_equal(evaled, list(x = 10, y = 30)) }) + +test_that("stage allows aesthetics that are only mapped to start", { + + df <- data.frame(x = 1:2) + + start_unnamed <- aes(stage(x)) + expect_equal( + eval_aesthetics(start_unnamed, data = df), + list(x = 1:2) + ) + + start_named <- aes(stage(start = x)) + expect_equal( + eval_aesthetics(start_named, data = df), + list(x = 1:2) + ) + + start_nulls <- aes(stage(start = x, after_stat = NULL, after_scale = NULL)) + expect_equal( + eval_aesthetics(start_nulls, data = df), + list(x = 1:2) + ) + +})