diff --git a/R/aes-evaluation.R b/R/aes-evaluation.R index 380cce10a8..720bb0d60b 100644 --- a/R/aes-evaluation.R +++ b/R/aes-evaluation.R @@ -204,6 +204,12 @@ stage_scaled <- function(start = NULL, after_stat = NULL, after_scale = NULL) { after_scale } +#' @rdname aes_eval +#' @export +ignore <- function(x) { + x +} + # Regex to determine if an identifier refers to a calculated aesthetic match_calculated_aes <- "^\\.\\.([a-zA-Z._]+)\\.\\.$" @@ -221,6 +227,10 @@ is_scaled_aes <- function(aesthetics) { is_staged_aes <- function(aesthetics) { vapply(aesthetics, is_staged, logical(1), USE.NAMES = FALSE) } +is_ignored_aes <- function(aesthetics) { + vapply(aesthetics, is_ignored, logical(1), USE.NAMES = FALSE) +} + is_calculated <- function(x, warn = FALSE) { if (is_call(get_expr(x), "after_stat")) { return(TRUE) @@ -263,6 +273,9 @@ is_scaled <- function(x) { is_staged <- function(x) { is_call(get_expr(x), "stage") } +is_ignored <- function(x) { + is_call(get_expr(x), "ignore") +} # Strip dots from expressions strip_dots <- function(expr, env, strip_pronoun = FALSE) { diff --git a/R/annotation.R b/R/annotation.R index ca1530462b..8d0f3aa6d1 100644 --- a/R/annotation.R +++ b/R/annotation.R @@ -70,8 +70,17 @@ annotate <- function(geom, x = NULL, y = NULL, xmin = NULL, xmax = NULL, details <- paste0(names(aesthetics)[bad], " (", lengths[bad], ")") cli::cli_abort("Unequal parameter lengths: {details}") } - data <- data_frame0(!!!position, .size = n) + + # Re-inject potential `ignore()` expressions + mapping <- aes_all(names(data)) + call <- call_match(fn = annotate) + aesthetics <- intersect(names(call), names(mapping)) + for (aes in aesthetics[is_ignored_aes(call[aesthetics])]) { + expr <- quo_get_expr(mapping[[aes]]) + mapping[[aes]] <- quo(ignore(!!expr)) + } + layer( geom = geom, params = list( @@ -81,7 +90,7 @@ annotate <- function(geom, x = NULL, y = NULL, xmin = NULL, xmax = NULL, stat = StatIdentity, position = PositionIdentity, data = data, - mapping = aes_all(names(data)), + mapping = mapping, inherit.aes = FALSE, show.legend = FALSE ) diff --git a/R/layer.R b/R/layer.R index 98e89540cd..38adf11d7b 100644 --- a/R/layer.R +++ b/R/layer.R @@ -344,7 +344,8 @@ Layer <- ggproto("Layer", NULL, aesthetics <- defaults(aesthetics, self$stat$default_aes) aesthetics <- compact(aesthetics) - new <- strip_dots(aesthetics[is_calculated_aes(aesthetics) | is_staged_aes(aesthetics)]) + new <- aesthetics[!is_ignored_aes(aesthetics)] + new <- strip_dots(new[is_calculated_aes(new) | is_staged_aes(new)]) if (length(new) == 0) return(data) # data needs to be non-scaled @@ -428,7 +429,38 @@ Layer <- ggproto("Layer", NULL, } data <- self$geom$handle_na(data, self$computed_geom_params) + + ignored <- self$ignored_aesthetics() + if (any(ignored %in% c(ggplot_global$x_aes, ggplot_global$y_aes))) { + # We temporarily redefine x/y aesthetics to have coords skip + # transformation of ignored aesthetics + local_bindings( + x_aes = setdiff(ggplot_global$x_aes, ignored), + y_aes = setdiff(ggplot_global$y_aes, ignored), + .env = ggplot_global + ) + } + self$geom$draw_layer(data, self$computed_geom_params, layout, layout$coord) + }, + + ignored_aesthetics = function(self) { + aesthetics <- self$computed_mapping + names(aesthetics)[is_ignored_aes(aesthetics)] + }, + + apply_ignore = function(self, data) { + ignored <- self$ignored_aesthetics() + ignored <- names(data) %in% ignored + names(data)[ignored] <- paste0(".ignored_", names(data)[ignored]) + data + }, + + undo_ignore = function(self, data) { + ignored <- self$ignored_aesthetics() + ignored <- names(data) %in% paste0(".ignored_", ignored) + names(data)[ignored] <- gsub("^\\.ignored_", "", names(data)[ignored]) + data } ) diff --git a/R/plot-build.R b/R/plot-build.R index 2c1695e350..d23045c3c1 100644 --- a/R/plot-build.R +++ b/R/plot-build.R @@ -51,6 +51,7 @@ ggplot_build.ggplot <- function(plot) { # Compute aesthetics to produce data with generalised variable names data <- by_layer(function(l, d) l$compute_aesthetics(d, plot), layers, data, "computing aesthetics") + data <- by_layer(function(l, d) l$apply_ignore(d), layers, data, "ignoring aesthetics") # Transform all scales data <- lapply(data, scales$transform_df) @@ -62,6 +63,7 @@ ggplot_build.ggplot <- function(plot) { layout$train_position(data, scale_x(), scale_y()) data <- layout$map_position(data) + data <- by_layer(function(l, d) l$undo_ignore(d), layers, data, "unignoring aesthetics") # Apply and map statistics data <- by_layer(function(l, d) l$compute_statistic(d, layout), layers, data, "computing stat") @@ -79,6 +81,7 @@ ggplot_build.ggplot <- function(plot) { # Reset position scales, then re-train and map. This ensures that facets # have control over the range of a plot: is it generated from what is # displayed, or does it include the range of underlying data + data <- by_layer(function(l, d) l$apply_ignore(d), layers, data, "ignoring aesthetics") layout$reset_scales() layout$train_position(data, scale_x(), scale_y()) layout$setup_panel_params() @@ -90,6 +93,7 @@ ggplot_build.ggplot <- function(plot) { lapply(data, npscales$train_df) data <- lapply(data, npscales$map_df) } + data <- by_layer(function(l, d) l$undo_ignore(d), layers, data, "unignoring aesthetics") # Fill in defaults etc. data <- by_layer(function(l, d) l$compute_geom_2(d), layers, data, "setting up geom aesthetics") diff --git a/R/position-.R b/R/position-.R index e9ea2ddf6f..2cf27dc940 100644 --- a/R/position-.R +++ b/R/position-.R @@ -78,13 +78,14 @@ transform_position <- function(df, trans_x = NULL, trans_y = NULL, ...) { # Treat df as list during transformation for faster set/get oldclass <- class(df) df <- unclass(df) - scales <- aes_to_scale(names(df)) if (!is.null(trans_x)) { - df[scales == "x"] <- lapply(df[scales == "x"], trans_x, ...) + is_x <- names(df) %in% ggplot_global$x_aes + df[is_x] <- lapply(df[is_x], trans_x, ...) } if (!is.null(trans_y)) { - df[scales == "y"] <- lapply(df[scales == "y"], trans_y, ...) + is_y <- names(df) %in% ggplot_global$y_aes + df[is_y] <- lapply(df[is_y], trans_y, ...) } class(df) <- oldclass diff --git a/R/scales-.R b/R/scales-.R index 73c490c8a2..6a912654eb 100644 --- a/R/scales-.R +++ b/R/scales-.R @@ -145,6 +145,7 @@ ScalesList <- ggproto("ScalesList", NULL, if (is.null(aesthetics)) { return() } + aesthetics <- aesthetics[!is_ignored_aes(aesthetics)] names(aesthetics) <- unlist(lapply(names(aesthetics), aes_to_scale)) new_aesthetics <- setdiff(names(aesthetics), self$input())