diff --git a/DESCRIPTION b/DESCRIPTION index 93c968a..3fa0379 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -17,7 +17,7 @@ Depends: R (>= 3.3.0) License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.2 Collate: 'utils.R' 'helpers.R' diff --git a/NAMESPACE b/NAMESPACE index 6f80cd9..7c8936c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,6 +2,7 @@ S3method(plot,ggtrace_highjacked) S3method(print,ggtrace_highjacked) +export(.layer_is) export(capture_env) export(capture_fn) export(clear_global_ggtrace) @@ -47,6 +48,7 @@ export(layer_after_scale) export(layer_after_stat) export(layer_before_geom) export(layer_before_stat) +export(layer_is) export(set_global_ggtrace) export(set_last_ggtrace) export(with_ggtrace) diff --git a/R/sublayer-data.R b/R/sublayer-data.R index 4e70d3d..836e76a 100644 --- a/R/sublayer-data.R +++ b/R/sublayer-data.R @@ -1,7 +1,9 @@ #' Inspect snapshots of sub-layer data #' -#' `layer_before_stat()`, `layer_after_stat()`, `layer_before_geom()`, and `layer_after_scale()` -#' are helper functions that return a snapshot of a layer's data in the internals. +#' `layer_before_stat()`, `layer_after_stat()`, `layer_before_geom()`, and +#' `layer_after_scale()` are convenience functions that return a snapshot of +#' a layer's data in the internals. `layer_is()` is a helper function used by +#' these. #' #' @name sublayer-data #' @@ -83,13 +85,13 @@ sublayer_data <- function(x, cond = 1L, } inspect_expr <- rlang::call2( - paste0("ggtrace_inspect_", step[1]), + paste0("inspect_", step[1]), x_expr, rlang::parse_expr(step[2]), .ns = "ggtrace" ) - if (cond != 1L) inspect_expr$cond <- cond + inspect_expr[[4]] <- rlang::expr(layer_is(!!cond)) if (!isFALSE(error)) inspect_expr$error <- error if (step[1] == "args") inspect_expr <- call("$", inspect_expr, quote(data)) @@ -98,8 +100,10 @@ sublayer_data <- function(x, cond = 1L, if (!verbose) options(rethrow_error) inspect_expr_fmt <- rlang::expr_deparse(inspect_expr, width = Inf) - inspect_expr_fmt <- gsub("^ggtrace::", "", inspect_expr_fmt) - if (verbose) cli::cli_alert_success("Executed {.code {inspect_expr_fmt}}") + if ("package:ggtrace" %in% search()) { + inspect_expr_fmt <- gsub("^ggtrace::", "", inspect_expr_fmt) + } + if (verbose) cli::cli_alert_success("Ran {.code {inspect_expr_fmt}}") if (rlang::is_installed("tibble")) { out <- asNamespace("tibble")$as_tibble(out) @@ -137,3 +141,53 @@ layer_after_scale <- function(plot, i = 1L, ..., error = FALSE, verbose = TRUE) ..., error = error, verbose = verbose) } +#' @rdname sublayer-data +#' @param expr An expression to evaluate for each call to the method, which +#' exposes information about the current layer that the method is being +#' called for. In technical terms, `layer_is()` subsets calls to the method +#' that are downstream of the `by_layer()` function in the ggplot internals. +#' It exposes some context-dependent variables, including: +#' * `i`: Scalar integer representing the nth layer +#' * `layers`: A list whose contents are equivalent to `$layers` +#' @export +layer_is <- function(expr) { + x <- rlang::enexpr(expr) + rlang::expr(.layer_is(!!x)) +} + +#' @rdname sublayer-data +#' @export +.layer_is <- function(expr) { + + x <- rlang::enexpr(expr) + + invalid_trace_msg <- function(x) { + sprintf("Invalid context: must be called from {.fn %s}.", x) + } + if (!any(sapply(sys.calls(), rlang::is_call, "ggplot_build"))) { + cli::cli_abort(invalid_trace_msg("ggplot_build")) + } + by_layer_idx <- which(sapply(sys.calls(), rlang::is_call, "by_layer"))[1] + if (is.na(by_layer_idx)) { + return(FALSE) + } + by_layer_env <- rlang::env_clone(sys.frames()[[by_layer_idx]]) + + if (is.numeric(x)) { + n_layers <- length(by_layer_env$layers) + if (x > n_layers) { + cli::cli_abort( + "[ggtrace] Plot has {n_layers} layer{?s} - {x} is invalid.", + call = rlang::call2("layer_is", x) + ) + } + x <- rlang::call2("==", quote(i), as.integer(x)) + } + + keep <- c("i", "layers") + drop <- setdiff(rlang::env_names(by_layer_env), keep) + rlang::env_unbind(by_layer_env, nms = drop) + + isTRUE(rlang::eval_bare(x, by_layer_env)) + +} diff --git a/man/sublayer-data.Rd b/man/sublayer-data.Rd index d0f86b2..a372948 100644 --- a/man/sublayer-data.Rd +++ b/man/sublayer-data.Rd @@ -6,6 +6,8 @@ \alias{layer_after_stat} \alias{layer_before_geom} \alias{layer_after_scale} +\alias{layer_is} +\alias{.layer_is} \title{Inspect snapshots of sub-layer data} \usage{ layer_before_stat(plot, i = 1L, ..., error = FALSE, verbose = TRUE) @@ -15,6 +17,10 @@ layer_after_stat(plot, i = 1L, ..., error = FALSE, verbose = TRUE) layer_before_geom(plot, i = 1L, ..., error = FALSE, verbose = TRUE) layer_after_scale(plot, i = 1L, ..., error = FALSE, verbose = TRUE) + +layer_is(expr) + +.layer_is(expr) } \arguments{ \item{plot}{A ggplot object. If missing, defaults to \code{ggplot2::last_plot()}.} @@ -26,13 +32,25 @@ layer_after_scale(plot, i = 1L, ..., error = FALSE, verbose = TRUE) \item{error}{If \code{TRUE}, returns the layer data early if available before the point of error.} \item{verbose}{If \code{TRUE}, prints the corresponding ggtrace code and re-prints the error if it exists.} + +\item{expr}{An expression to evaluate for each call to the method, which +exposes information about the current layer that the method is being +called for. In technical terms, \code{layer_is()} subsets calls to the method +that are downstream of the \code{by_layer()} function in the ggplot internals. +It exposes some context-dependent variables, including: +\itemize{ +\item \code{i}: Scalar integer representing the nth layer +\item \code{layers}: A list whose contents are equivalent to \verb{$layers} +}} } \value{ A dataframe } \description{ -\code{layer_before_stat()}, \code{layer_after_stat()}, \code{layer_before_geom()}, and \code{layer_after_scale()} -are helper functions that return a snapshot of a layer's data in the internals. +\code{layer_before_stat()}, \code{layer_after_stat()}, \code{layer_before_geom()}, and +\code{layer_after_scale()} are convenience functions that return a snapshot of +a layer's data in the internals. \code{layer_is()} is a helper function used by +these. } \examples{ library(ggplot2)