Title: | Programmatically explore, debug, and manipulate ggplot internals |
---|---|
Description: | Programmatically explore, debug, and manipulate ggplot internals. Package ggtrace offers a low-level interface that extends base R capabilities of trace, as well as a family of workflow functions that make interactions with ggplot internals more accessible. |
Authors: | June Choe [aut, cre] |
Maintainer: | June Choe <[email protected]> |
License: | MIT + file LICENSE |
Version: | 0.7.3 |
Built: | 2024-12-17 05:58:33 UTC |
Source: | https://github.com/yjunechoe/ggtrace |
Get information about ggproto methods
get_method(method, inherit = FALSE) get_method_inheritance(obj, trim_overriden = TRUE) ggbody(method, inherit = FALSE, as.list = TRUE) ggformals(method, inherit = FALSE)
get_method(method, inherit = FALSE) get_method_inheritance(obj, trim_overriden = TRUE) ggbody(method, inherit = FALSE, as.list = TRUE) ggformals(method, inherit = FALSE)
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
inherit |
Whether the method should be searched from its closest parent. Defaults to |
obj |
A ggproto object |
trim_overriden |
Whether |
as.list |
Whether |
get_method()
returns the method.
get_method_inheritance()
lists available methods from self and parent ggprotos.
ggbody()
returns the body of the method.
ggformals()
returns the formals of the method.
A list
If a method is being traced via ggtrace()
or ggedit()
, get_method()
will return the current modified state
of the method. As of v0.3.5, calling get_method()
on a method that has a trace on it will return a warning
to emphasize this fact.
When using inherit = TRUE
, make sure that all ggproto objects from class(ggproto)
are available (by loading
the packages where they are defined, for example). Under the hood, get_method()
loops through the parents
to search for the method, so it needs to be able to evaluate each element of class(ggproto)
as an object.
get_method()
calls get("method", ggproto)
under the hood.
The get("method", ggproto)
syntax is the long form of ggproto$method
which retrieves
the actual function body. This is a subtle but important difference for inspecting ggproto methods.
For example, this works: debugonce(get("compute_group", StatCount))
But this fails to insert a break point: debugonce(StatCount$compute_group)
get_method()
was designed so that you do not have to worry about this distinction.
library(ggplot2) # Uninformative StatCount$compute_group formals(StatCount$compute_group) body(StatCount$compute_group) # Errors # get(StatCount$compute_group) # Informative get_method(StatCount$compute_group) ggformals(StatCount$compute_group) # formals(get_method(StatCount$compute_group)) ggbody(StatCount$compute_group) # body(get_method(StatCount$compute_group)) # Works for ggproto in extension packages ggbody(ggforce::StatDelaunaySegment$compute_group) library(ggforce) ggbody(StatBezier$compute_panel) # `inherit = TRUE` will return the method from the closest parent ## ERRORS: ## get_method(StatBoxplot$compute_panel) ## ggbody(StatBoxplot$compute_panel) ## ggformals(StatBoxplot$compute_panel) ggbody(StatBoxplot$compute_panel, inherit = TRUE) ggbody(Stat$compute_panel) # Navigating complex inheritance class(GeomArcBar) invisible(ggbody(GeomArcBar$default_aes, inherit = TRUE)) # self invisible(ggbody(GeomArcBar$draw_panel, inherit = TRUE)) # parent invisible(ggbody(GeomArcBar$draw_key, inherit = TRUE)) # grandparent invisible(ggbody(GeomArcBar$draw_group, inherit = TRUE)) # top-level # Getting information about method inheritance all at once # - default `trim_overriden = TRUE` hides redundant methods defined in parent get_method_inheritance(GeomArcBar, trim_overriden = TRUE) # Works for custom ggproto # - Example from {ggplot2} "Extending ggplot2" vignette StatDensityCommon <- ggproto("StatDensityCommon", Stat, required_aes = "x", setup_params = function(data, params) { if (!is.null(params$bandwidth)) return(params) xs <- split(data$x, data$group) bws <- vapply(xs, bw.nrd0, numeric(1)) bw <- mean(bws) message("Picking bandwidth of ", signif(bw, 3)) params$bandwidth <- bw params }, compute_group = function(data, scales, bandwidth = 1) { d <- density(data$x, bw = bandwidth) data.frame(x = d$x, y = d$y) } ) as.list(body(get("compute_group", StatDensityCommon))) ggbody(StatDensityCommon$compute_group) # As of v.0.4.0, ggbody works for functions as well ggbody(sample) ggtrace(sample, 1) invisible(ggbody(sample)) is_traced(sample) gguntrace(sample)
library(ggplot2) # Uninformative StatCount$compute_group formals(StatCount$compute_group) body(StatCount$compute_group) # Errors # get(StatCount$compute_group) # Informative get_method(StatCount$compute_group) ggformals(StatCount$compute_group) # formals(get_method(StatCount$compute_group)) ggbody(StatCount$compute_group) # body(get_method(StatCount$compute_group)) # Works for ggproto in extension packages ggbody(ggforce::StatDelaunaySegment$compute_group) library(ggforce) ggbody(StatBezier$compute_panel) # `inherit = TRUE` will return the method from the closest parent ## ERRORS: ## get_method(StatBoxplot$compute_panel) ## ggbody(StatBoxplot$compute_panel) ## ggformals(StatBoxplot$compute_panel) ggbody(StatBoxplot$compute_panel, inherit = TRUE) ggbody(Stat$compute_panel) # Navigating complex inheritance class(GeomArcBar) invisible(ggbody(GeomArcBar$default_aes, inherit = TRUE)) # self invisible(ggbody(GeomArcBar$draw_panel, inherit = TRUE)) # parent invisible(ggbody(GeomArcBar$draw_key, inherit = TRUE)) # grandparent invisible(ggbody(GeomArcBar$draw_group, inherit = TRUE)) # top-level # Getting information about method inheritance all at once # - default `trim_overriden = TRUE` hides redundant methods defined in parent get_method_inheritance(GeomArcBar, trim_overriden = TRUE) # Works for custom ggproto # - Example from {ggplot2} "Extending ggplot2" vignette StatDensityCommon <- ggproto("StatDensityCommon", Stat, required_aes = "x", setup_params = function(data, params) { if (!is.null(params$bandwidth)) return(params) xs <- split(data$x, data$group) bws <- vapply(xs, bw.nrd0, numeric(1)) bw <- mean(bws) message("Picking bandwidth of ", signif(bw, 3)) params$bandwidth <- bw params }, compute_group = function(data, scales, bandwidth = 1) { d <- density(data$x, bw = bandwidth) data.frame(x = d$x, y = d$y) } ) as.list(body(get("compute_group", StatDensityCommon))) ggbody(StatDensityCommon$compute_group) # As of v.0.4.0, ggbody works for functions as well ggbody(sample) ggtrace(sample, 1) invisible(ggbody(sample)) is_traced(sample) gguntrace(sample)
Debug a ggproto method
ggdebug(method, ...) ggdebugonce(method, ...) ggundebug(method, ...)
ggdebug(method, ...) ggdebugonce(method, ...) ggundebug(method, ...)
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
... |
Ignored. Designed for the ease of calling this function by modifying the call to
an earlier |
Interactively edit a masking copy of the source code
ggedit(method, remove_trace = FALSE, ...)
ggedit(method, remove_trace = FALSE, ...)
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
remove_trace |
Whether to edit from a clean slate. Defaults to |
... |
Unused, for extensibility. |
Like base::trace()
, the edit is in effect until gguntrace()
is called.
Changes with ggedit()
are cumulative, so ggedit()
will inform you via a warning
if you're making an edit on top of an existing edit. Call gguntrace()
on the object
first if you'd like to edit the method's original unaltered source code.
Only works in interactive contexts.
Calling ggtrace()
on an method that that has changes from ggedit()
will remove the
changes from ggedit()
. It is possible to combine both features, but disabled in
the package to keep the API consistent. It is against the philosophy of {ggtrace}
to
mix programmatic and interactive workflows.
## Not run: jitter_plot <- ggplot(diamonds[1:1000,], aes(cut, depth)) + geom_point(position = position_jitter(width = 0.2, seed = 2021)) # Interactively modify the method's source code in text editor ggedit(PositionJitter$compute_layer) # Check the edited code ggbody(PositionJitter$compute_layer) # Execute method with edit jitter_plot # Untrace gguntrace(PositionJitter$compute_layer) # Edit is removed in the next call jitter_plot ## End(Not run)
## Not run: jitter_plot <- ggplot(diamonds[1:1000,], aes(cut, depth)) + geom_point(position = position_jitter(width = 0.2, seed = 2021)) # Interactively modify the method's source code in text editor ggedit(PositionJitter$compute_layer) # Check the edited code ggbody(PositionJitter$compute_layer) # Execute method with edit jitter_plot # Untrace gguntrace(PositionJitter$compute_layer) # Edit is removed in the next call jitter_plot ## End(Not run)
Insert traces for delayed evaluation
ggtrace( method, trace_steps, trace_exprs, once = TRUE, use_names = TRUE, ..., print_output = TRUE, verbose = FALSE )
ggtrace( method, trace_steps, trace_exprs, once = TRUE, use_names = TRUE, ..., print_output = TRUE, verbose = FALSE )
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
trace_steps |
A sorted numeric vector of positions in the method's body to trace. Negative indices
reference steps from the last, where |
trace_exprs |
A list of expressions to evaluate at each position specified
in To simply run a step and return its output, you can use the |
once |
Whether to |
use_names |
Whether the trace dump should use the names from |
... |
Unused, for extensibility. |
print_output |
Whether to |
verbose |
Whether logs should be printed when trace is triggered. Encompasses |
ggtrace()
is a wrapper around base::trace()
which is called on the ggproto method.
It calls base::untrace()
on itself on exit by default, to make its effect ephemeral like base::debugonce()
.
A major feature is the ability to pass multiple positions and expressions to trace_steps
and trace_exprs
to
inspect, capture, and modify the run time environment of ggproto methods. It is recommended to consult the output
of ggbody()
when deciding which expressions to evaluate at which steps.
The output of the expressions passed to trace_exprs
is printed while tracing takes place. The
list of outputs from ggtrace()
("trace dumps") can be returned for further inspection with
last_ggtrace()
or global_ggtrace()
.
Broadly, there are four flavors of working with the {ggtrace}
package, listed in the order of increasing complexity:
Inspect: The canonical use of ggtrace()
to make queries, where expressions are passed in and
their evaluated output are returned, potentially for further inspection.
Capture: The strategy of returning the method's runtime environment for more complex explorations outside of the debugging context.
A method's environment contextualizes the self
object in addition to making all inherited params and local variables available.
A reference to the method's runtime environment can be returned with environment()
, as in trace_exprs = quote(environment())
.
Note that environments are mutable, meaning that environment()
returned from the first and last steps will reference
the same environment. To get a snapshot of the environment at a particular step, you can return a deep copy with
rlang::env_clone(environment())
.
Inject: The strategy of modifying the behavior of a method as it runs by passing in expressions that make assignments.
For example, trace_steps = c(1, 10)
with trace_exprs = rlang::exprs(a <- 5, a)
will first assign a new variable a
at step 1, and return its value 5
at step 10. This can also be used to modify important variables like
quote(data$x <- data$x * 10)
. If you would like to inject an object from the global environment, you can make use of the
!!
(bang-bang) operator from {rlang}
, like so: rlang::expr(data <- !!modified_data)
.
Note that the execution environment is created anew each time the method is ran, so modifying the environment from its previous execution will not affect future calls to the method.
If you would like to capture the modified plot output and assign it to a variable, you can do so with
ggplotGrob()
. You can then render the modified plot with print()
.
Edit: It is also possible to make any arbitrary modifications to the method's source code, which stays in effect
until the method is untraced. While this is also handled with base::trace()
, this workflow is fundamentally
interactive. Therefore, it has been refactored as its own function ggedit()
. See ?ggedit
for more details.
If you wrap a ggplot in invisible()
to silence ggtrace()
, the plot will not build, which also means that
the tracing is not triggered. This is because the print/plot method of ggplot is what triggers the evaluation
of the plot code. It is recommended to allow ggtrace()
to print information, but if you'd really like to silence
it, you can do so by wrapping the plot in a function that forces its evaluation first, like ggplotGrob
,
as in invisible(ggplotGrob(<plot>))
.
If for any reason ggtrace(once = TRUE)
fails to untrace itself on exit, you may accidentally trigger
the trace again. To check if a method is being traced, call is_traced()
. You can also always call
gguntrace()
since unlike base::untrace()
, it will not error if a trace doesn't exist on the method.
Instead, gguntrace()
will do nothing in that case and simply inform you that there is no trace to remove.
Because base::trace()
wraps the method body in a special environment, it is not possible to inspect the
method/function which called it, even with something like rlang::caller_env()
. You will traverse through
a few wrapping environments created by base::trace()
which eventually ends up looping around.
Various information is sent to the console whenever a trace is triggered. You can control what gets displayed with print_output
and
verbose
, which are both TRUE
by default. print_output
simply calls print()
on the evaluated expressions, and turning this
off may be desirable if expressions in trace_exprs
evaluates to a long dataframe or vector. verbose
controls all
information printed to the console including those by print()
, and setting verbose = FALSE
will mean that only
message()
s will be displayed. Lastly, you can suppress message()
from ggtrace()
with options(ggtrace.suppressMessages = TRUE)
,
though suppressing messages is generally not recommended.
gguntrace()
, is_traced()
, last_ggtrace()
, global_ggtrace()
# One example of an Inspect workflow ---- library(ggplot2) jitter_plot <- ggplot(diamonds[1:1000,], aes(cut, depth)) + geom_point(position = position_jitter(width = 0.2, seed = 2021)) jitter_plot ggbody(PositionJitter$compute_layer) ## Step 1 ==== ## Inspect what `data` look like at the start of the function ggtrace(PositionJitter$compute_layer, trace_steps = 1, trace_exprs = quote(head(data))) jitter_plot ## Step 2 ==== ## What does `data` look like at the end of the method? Unfortunately, `trace()` only lets us enter ## at the beginning of a step, so we can't inspect what happens after the last step is evaluated. To ## address this, `ggtrace()` offers a `~step` keyword which gets substituted for the current line. ## We also set `print_output = FALSE` to disable printing of the output ggtrace( PositionJitter$compute_layer, trace_steps = 14, trace_exprs = quote(~step), # This is the default if `trace_exprs` is not provided print_output = FALSE ) # We wrap the plot in `ggplotGrob()` and `invisible()` to force # its evaluation while suppressing its rendering invisible(ggplotGrob(jitter_plot)) # The output of the evaluated expressions can be inspected with `last_ggtrace()` head(last_ggtrace()[[1]]) ## Step 3 ==== ## If we want both to be returned at the same time for an easier comparison, we can pass in a ## (named) list of expressions. ggtrace( PositionJitter$compute_layer, trace_steps = c(1, 14), trace_exprs = rlang::exprs( before_jitter = data, after_jitter = ~step ), verbose = FALSE ) invisible(ggplotGrob(jitter_plot)) ## Step 4 ==== ## The output of the evaluated expressions can be inspected with `last_ggtrace()` jitter_tracedump <- last_ggtrace() lapply(jitter_tracedump, head, 3) jitter_distances <- jitter_tracedump[["before_jitter"]]$x - jitter_tracedump[["after_jitter"]]$x range(jitter_distances) jitter_plot$layers[[1]]$position$width
# One example of an Inspect workflow ---- library(ggplot2) jitter_plot <- ggplot(diamonds[1:1000,], aes(cut, depth)) + geom_point(position = position_jitter(width = 0.2, seed = 2021)) jitter_plot ggbody(PositionJitter$compute_layer) ## Step 1 ==== ## Inspect what `data` look like at the start of the function ggtrace(PositionJitter$compute_layer, trace_steps = 1, trace_exprs = quote(head(data))) jitter_plot ## Step 2 ==== ## What does `data` look like at the end of the method? Unfortunately, `trace()` only lets us enter ## at the beginning of a step, so we can't inspect what happens after the last step is evaluated. To ## address this, `ggtrace()` offers a `~step` keyword which gets substituted for the current line. ## We also set `print_output = FALSE` to disable printing of the output ggtrace( PositionJitter$compute_layer, trace_steps = 14, trace_exprs = quote(~step), # This is the default if `trace_exprs` is not provided print_output = FALSE ) # We wrap the plot in `ggplotGrob()` and `invisible()` to force # its evaluation while suppressing its rendering invisible(ggplotGrob(jitter_plot)) # The output of the evaluated expressions can be inspected with `last_ggtrace()` head(last_ggtrace()[[1]]) ## Step 3 ==== ## If we want both to be returned at the same time for an easier comparison, we can pass in a ## (named) list of expressions. ggtrace( PositionJitter$compute_layer, trace_steps = c(1, 14), trace_exprs = rlang::exprs( before_jitter = data, after_jitter = ~step ), verbose = FALSE ) invisible(ggplotGrob(jitter_plot)) ## Step 4 ==== ## The output of the evaluated expressions can be inspected with `last_ggtrace()` jitter_tracedump <- last_ggtrace() lapply(jitter_tracedump, head, 3) jitter_distances <- jitter_tracedump[["before_jitter"]]$x - jitter_tracedump[["after_jitter"]]$x range(jitter_distances) jitter_plot$layers[[1]]$position$width
Capture a snapshot of a method's execution environment
ggtrace_capture_env(x, method, cond = 1L, at = -1L, ...) capture_env(x, method, cond = 1L, at = -1L, ...)
ggtrace_capture_env(x, method, cond = 1L, at = -1L, ...) capture_env(x, method, cond = 1L, at = -1L, ...)
x |
A ggplot object |
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
cond |
When the method environment should be captured. Defaults to |
at |
Which step of the method body the environment should be captured.
See |
... |
Unused. |
An environment
When quoted expressions are passed to the cond
or value
argument of
workflow functions they are evaluated in a special environment which
we call the "tracing context".
The tracing context is "data-masked" (see rlang::eval_tidy()
), and exposes
an internal variable called ._counter_
which increments every time a
function/method has been called by the ggplot object supplied to the x
argument of workflow functions. For example, cond = quote(._counter_ == 1L)
is evaluated as TRUE
when the method is called for the first time. The
cond
argument also supports numeric shorthands like cond = 1L
which evaluates to
quote(._counter_ == 1L)
, and this is the default value of cond
for
all workflow functions that only return one value (e.g., ggtrace_capture_fn()
).
It is recommended to consult the output of ggtrace_inspect_n()
and
ggtrace_inspect_which()
to construct expressions that condition on ._counter_
.
For highjack functions like ggtrace_highjack_return()
, the value about to
be returned by the function/method can be accessed with returnValue()
in the
value
argument. By default, value
is set to quote(returnValue())
which
simply evaluates to the return value, but directly computing on returnValue()
to
derive a different return value for the function/method is also possible.
library(ggplot2) # Example from https://ggplot2.tidyverse.org/reference/aes_eval.html after_scale_plot <- ggplot(mpg, aes(class, hwy)) + geom_boxplot(aes(colour = class, fill = after_scale(alpha(colour, 0.4)))) after_scale_plot # `after_scale()` is resolved by `Geom$use_defaults` (at Step 6) before_applying <- ggtrace_capture_env( x = after_scale_plot, method = Geom$use_defaults, at = 1 # To be more specific, do `at = 6` ) after_applying <- ggtrace_capture_env( x = after_scale_plot, method = Geom$use_defaults, at = -1 # To be more specific, do `at = 7` ) colnames(before_applying$data) colnames(after_applying$data) library(dplyr) before_applying$data %>% select(any_of(c("colour", "fill"))) after_applying$data %>% select(any_of(c("colour", "fill"))) identical( before_applying$data %>% select(any_of(c("colour", "fill"))) %>% mutate(fill = alpha(colour, 0.4)), #< after_scale() logic here after_applying$data %>% select(any_of(c("colour", "fill"))) ) # Using the captured environment for further evaluation ggbody(Geom$draw_panel) by_group_drawing_code <- rlang::call_args(ggbody(Geom$draw_panel)[[3]])[[2]] by_group_drawing_code draw_panel_env <- ggtrace_capture_env( x = after_scale_plot, method = Geom$draw_panel ) draw_panel_env boxes <- eval(by_group_drawing_code, draw_panel_env) library(grid) grid.newpage() grid.draw(editGrob(boxes[[1]], vp = viewport()))
library(ggplot2) # Example from https://ggplot2.tidyverse.org/reference/aes_eval.html after_scale_plot <- ggplot(mpg, aes(class, hwy)) + geom_boxplot(aes(colour = class, fill = after_scale(alpha(colour, 0.4)))) after_scale_plot # `after_scale()` is resolved by `Geom$use_defaults` (at Step 6) before_applying <- ggtrace_capture_env( x = after_scale_plot, method = Geom$use_defaults, at = 1 # To be more specific, do `at = 6` ) after_applying <- ggtrace_capture_env( x = after_scale_plot, method = Geom$use_defaults, at = -1 # To be more specific, do `at = 7` ) colnames(before_applying$data) colnames(after_applying$data) library(dplyr) before_applying$data %>% select(any_of(c("colour", "fill"))) after_applying$data %>% select(any_of(c("colour", "fill"))) identical( before_applying$data %>% select(any_of(c("colour", "fill"))) %>% mutate(fill = alpha(colour, 0.4)), #< after_scale() logic here after_applying$data %>% select(any_of(c("colour", "fill"))) ) # Using the captured environment for further evaluation ggbody(Geom$draw_panel) by_group_drawing_code <- rlang::call_args(ggbody(Geom$draw_panel)[[3]])[[2]] by_group_drawing_code draw_panel_env <- ggtrace_capture_env( x = after_scale_plot, method = Geom$draw_panel ) draw_panel_env boxes <- eval(by_group_drawing_code, draw_panel_env) library(grid) grid.newpage() grid.draw(editGrob(boxes[[1]], vp = viewport()))
Returns a ggproto method as a function with arguments pre-filled to their values when it was first called
ggtrace_capture_fn(x, method, cond = 1L, ...) capture_fn(x, method, cond = 1L, ...)
ggtrace_capture_fn(x, method, cond = 1L, ...) capture_fn(x, method, cond = 1L, ...)
x |
A ggplot object |
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
cond |
When the method function should be captured. Defaults to |
... |
Unused. |
A function
When quoted expressions are passed to the cond
or value
argument of
workflow functions they are evaluated in a special environment which
we call the "tracing context".
The tracing context is "data-masked" (see rlang::eval_tidy()
), and exposes
an internal variable called ._counter_
which increments every time a
function/method has been called by the ggplot object supplied to the x
argument of workflow functions. For example, cond = quote(._counter_ == 1L)
is evaluated as TRUE
when the method is called for the first time. The
cond
argument also supports numeric shorthands like cond = 1L
which evaluates to
quote(._counter_ == 1L)
, and this is the default value of cond
for
all workflow functions that only return one value (e.g., ggtrace_capture_fn()
).
It is recommended to consult the output of ggtrace_inspect_n()
and
ggtrace_inspect_which()
to construct expressions that condition on ._counter_
.
For highjack functions like ggtrace_highjack_return()
, the value about to
be returned by the function/method can be accessed with returnValue()
in the
value
argument. By default, value
is set to quote(returnValue())
which
simply evaluates to the return value, but directly computing on returnValue()
to
derive a different return value for the function/method is also possible.
For functions and methods that take ...
, arguments passed to ...
are captured and
promoted to function arguments. The captured values are available for inspection via formals()
.
library(ggplot2) set.seed(47) df <- as.data.frame(matrix(sample(5, 1000, TRUE), ncol = 2)) table(df) base <- ggplot(df, aes(x = V1, y = V2)) p1 <- base + stat_summary(orientation = "x") p1 p1_compute_panel <- ggtrace_capture_fn(p1, method = StatSummary$compute_panel) # `p1_compute_panel` is a copy of the ggproto method body(p1_compute_panel) ggbody(StatSummary$compute_panel, as.list = FALSE) # Its arguments are pre-filled (captured at runtime) sapply(formals(p1_compute_panel), class) # Runs as it should p1_compute_panel() # You can inspect changes to its behavior outisde of ggplot # For example, see what happens when aes is flipped via `orientation = "y"` p1_compute_panel(flipped_aes = TRUE) # We confirm this output to be true when `orientation = "y"` in `stat_summary()` p2 <- base + stat_summary(orientation = "y") p2_compute_panel <- ggtrace_capture_fn(p2, method = StatSummary$compute_panel) identical(p1_compute_panel(flipped_aes = TRUE), p2_compute_panel()) # You can interactively explore with `debugonce(p2_compute_panel)` # Note that the captured method looks slightly different if the method takes `...` p3 <- base + stat_smooth() + geom_jitter() p3 p3_compute_panel <- ggtrace_capture_fn(p3, method = Stat$compute_panel) # For one, the body is different - it's a "wrapper" around the captured method body(p3_compute_panel) # The captured method is stored in the `"inner"` attribute attr(p3_compute_panel, "inner") # Captured argument defaults are again available for inspection via `formals()` # Note that arguments passed to the `...` are promoted to function arguments names(ggformals(Stat$compute_panel)) names(formals(p3_compute_panel)) # It works the same otherwise - plus you get the benefit of autocomplete head(p3_compute_panel()) head(p3_compute_panel(level = .99)[, c("ymin", "ymax")]) head(p3_compute_panel(flipped_aes = TRUE)) # Interactively explore with `debugonce(attr(p3_compute_panel, "inner"))`
library(ggplot2) set.seed(47) df <- as.data.frame(matrix(sample(5, 1000, TRUE), ncol = 2)) table(df) base <- ggplot(df, aes(x = V1, y = V2)) p1 <- base + stat_summary(orientation = "x") p1 p1_compute_panel <- ggtrace_capture_fn(p1, method = StatSummary$compute_panel) # `p1_compute_panel` is a copy of the ggproto method body(p1_compute_panel) ggbody(StatSummary$compute_panel, as.list = FALSE) # Its arguments are pre-filled (captured at runtime) sapply(formals(p1_compute_panel), class) # Runs as it should p1_compute_panel() # You can inspect changes to its behavior outisde of ggplot # For example, see what happens when aes is flipped via `orientation = "y"` p1_compute_panel(flipped_aes = TRUE) # We confirm this output to be true when `orientation = "y"` in `stat_summary()` p2 <- base + stat_summary(orientation = "y") p2_compute_panel <- ggtrace_capture_fn(p2, method = StatSummary$compute_panel) identical(p1_compute_panel(flipped_aes = TRUE), p2_compute_panel()) # You can interactively explore with `debugonce(p2_compute_panel)` # Note that the captured method looks slightly different if the method takes `...` p3 <- base + stat_smooth() + geom_jitter() p3 p3_compute_panel <- ggtrace_capture_fn(p3, method = Stat$compute_panel) # For one, the body is different - it's a "wrapper" around the captured method body(p3_compute_panel) # The captured method is stored in the `"inner"` attribute attr(p3_compute_panel, "inner") # Captured argument defaults are again available for inspection via `formals()` # Note that arguments passed to the `...` are promoted to function arguments names(ggformals(Stat$compute_panel)) names(formals(p3_compute_panel)) # It works the same otherwise - plus you get the benefit of autocomplete head(p3_compute_panel()) head(p3_compute_panel(level = .99)[, c("ymin", "ymax")]) head(p3_compute_panel(flipped_aes = TRUE)) # Interactively explore with `debugonce(attr(p3_compute_panel, "inner"))`
Highjack a method's execution and modify its argument values
ggtrace_highjack_args(x, method, cond = 1L, values, ..., draw = TRUE) highjack_args(x, method, cond = 1L, values, ..., draw = TRUE)
ggtrace_highjack_args(x, method, cond = 1L, values, ..., draw = TRUE) highjack_args(x, method, cond = 1L, values, ..., draw = TRUE)
x |
A ggplot object |
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
cond |
When the return value should be replaced. Defaults to |
values |
A named list of variable-value pairings. When values are expressions, they are evaluated in the formals. |
... |
Unused. |
draw |
Whether to draw the modified graphical output from evaluating |
A gtable object with class <ggtrace_highjacked>
When quoted expressions are passed to the cond
or value
argument of
workflow functions they are evaluated in a special environment which
we call the "tracing context".
The tracing context is "data-masked" (see rlang::eval_tidy()
), and exposes
an internal variable called ._counter_
which increments every time a
function/method has been called by the ggplot object supplied to the x
argument of workflow functions. For example, cond = quote(._counter_ == 1L)
is evaluated as TRUE
when the method is called for the first time. The
cond
argument also supports numeric shorthands like cond = 1L
which evaluates to
quote(._counter_ == 1L)
, and this is the default value of cond
for
all workflow functions that only return one value (e.g., ggtrace_capture_fn()
).
It is recommended to consult the output of ggtrace_inspect_n()
and
ggtrace_inspect_which()
to construct expressions that condition on ._counter_
.
For highjack functions like ggtrace_highjack_return()
, the value about to
be returned by the function/method can be accessed with returnValue()
in the
value
argument. By default, value
is set to quote(returnValue())
which
simply evaluates to the return value, but directly computing on returnValue()
to
derive a different return value for the function/method is also possible.
set.seed(1116) library(ggplot2) library(dplyr) p <- ggplot(mtcars, aes(mpg, hp, color = factor(cyl))) + geom_point() + geom_smooth(method = "lm") p # Fit predictions from loess regression just for second group ggtrace_highjack_args( x = p, method = StatSmooth$compute_group, cond = quote(data$group[1] == 2), values = list(method = "loess") ) # If value is an expression, it's evaluated in the Tracing Context ggtrace_highjack_args( x = p, method = StatSmooth$compute_group, values = rlang::exprs( # Every time the method is called, call it with a bigger CI level = ._counter_ * 0.3, # Fit models to just a random sample of the data data = data %>% slice_sample(prop = .8) ) )
set.seed(1116) library(ggplot2) library(dplyr) p <- ggplot(mtcars, aes(mpg, hp, color = factor(cyl))) + geom_point() + geom_smooth(method = "lm") p # Fit predictions from loess regression just for second group ggtrace_highjack_args( x = p, method = StatSmooth$compute_group, cond = quote(data$group[1] == 2), values = list(method = "loess") ) # If value is an expression, it's evaluated in the Tracing Context ggtrace_highjack_args( x = p, method = StatSmooth$compute_group, values = rlang::exprs( # Every time the method is called, call it with a bigger CI level = ._counter_ * 0.3, # Fit models to just a random sample of the data data = data %>% slice_sample(prop = .8) ) )
Highjack a method's execution and make it return a user-supplied value
ggtrace_highjack_return( x, method, cond = 1L, value = quote(returnValue()), ..., draw = TRUE ) highjack_return( x, method, cond = 1L, value = quote(returnValue()), ..., draw = TRUE )
ggtrace_highjack_return( x, method, cond = 1L, value = quote(returnValue()), ..., draw = TRUE ) highjack_return( x, method, cond = 1L, value = quote(returnValue()), ..., draw = TRUE )
x |
A ggplot object |
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
cond |
When the return value should be replaced. Defaults to |
value |
What the method should return instead. Defaults to |
... |
Unused. |
draw |
Whether to draw the modified graphical output from evaluating |
A gtable object with class <ggtrace_highjacked>
When quoted expressions are passed to the cond
or value
argument of
workflow functions they are evaluated in a special environment which
we call the "tracing context".
The tracing context is "data-masked" (see rlang::eval_tidy()
), and exposes
an internal variable called ._counter_
which increments every time a
function/method has been called by the ggplot object supplied to the x
argument of workflow functions. For example, cond = quote(._counter_ == 1L)
is evaluated as TRUE
when the method is called for the first time. The
cond
argument also supports numeric shorthands like cond = 1L
which evaluates to
quote(._counter_ == 1L)
, and this is the default value of cond
for
all workflow functions that only return one value (e.g., ggtrace_capture_fn()
).
It is recommended to consult the output of ggtrace_inspect_n()
and
ggtrace_inspect_which()
to construct expressions that condition on ._counter_
.
For highjack functions like ggtrace_highjack_return()
, the value about to
be returned by the function/method can be accessed with returnValue()
in the
value
argument. By default, value
is set to quote(returnValue())
which
simply evaluates to the return value, but directly computing on returnValue()
to
derive a different return value for the function/method is also possible.
set.seed(1116) library(ggplot2) library(dplyr) p1 <- ggplot(diamonds, aes(cut)) + geom_bar(aes(fill = cut)) + facet_wrap(~ clarity) p1 # Highjack `Stat$compute_panel` at the first panel # to return higher values for `count` ggtrace_highjack_return( x = p1, method = Stat$compute_panel, value = quote({ returnValue() %>% mutate(count = count * 100) }) ) # Highjack `Stat$compute_panel` at the fourth panel # to shuffle bars in the x-axis ggtrace_highjack_return( x = p1, method = Stat$compute_panel, cond = quote(data$PANEL[1] == 4), value = quote({ returnValue() %>% mutate(x = sample(x)) }) ) # Bars get a black outline and get darker from left-to-right, but only for second panel ggtrace_highjack_return( x = p1, method = GeomBar$draw_panel, cond = quote(data$PANEL[1] == 2), value = quote({ editGrob(returnValue(), gp = gpar( col = "black", alpha = seq(0.2, 1, length.out = nrow(data) ))) }) )
set.seed(1116) library(ggplot2) library(dplyr) p1 <- ggplot(diamonds, aes(cut)) + geom_bar(aes(fill = cut)) + facet_wrap(~ clarity) p1 # Highjack `Stat$compute_panel` at the first panel # to return higher values for `count` ggtrace_highjack_return( x = p1, method = Stat$compute_panel, value = quote({ returnValue() %>% mutate(count = count * 100) }) ) # Highjack `Stat$compute_panel` at the fourth panel # to shuffle bars in the x-axis ggtrace_highjack_return( x = p1, method = Stat$compute_panel, cond = quote(data$PANEL[1] == 4), value = quote({ returnValue() %>% mutate(x = sample(x)) }) ) # Bars get a black outline and get darker from left-to-right, but only for second panel ggtrace_highjack_return( x = p1, method = GeomBar$draw_panel, cond = quote(data$PANEL[1] == 2), value = quote({ editGrob(returnValue(), gp = gpar( col = "black", alpha = seq(0.2, 1, length.out = nrow(data) ))) }) )
Inspect the arguments passed into a method
ggtrace_inspect_args( x, method, cond = 1L, hoist_dots = TRUE, ..., error = FALSE ) inspect_args(x, method, cond = 1L, hoist_dots = TRUE, ..., error = FALSE)
ggtrace_inspect_args( x, method, cond = 1L, hoist_dots = TRUE, ..., error = FALSE ) inspect_args(x, method, cond = 1L, hoist_dots = TRUE, ..., error = FALSE)
x |
A ggplot object |
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
cond |
When the arguments should be inspected. Defaults to |
hoist_dots |
Whether treat arguments passed to |
... |
Unused. |
error |
If |
A list of argument-value pairs from the method
when it is called.
When quoted expressions are passed to the cond
or value
argument of
workflow functions they are evaluated in a special environment which
we call the "tracing context".
The tracing context is "data-masked" (see rlang::eval_tidy()
), and exposes
an internal variable called ._counter_
which increments every time a
function/method has been called by the ggplot object supplied to the x
argument of workflow functions. For example, cond = quote(._counter_ == 1L)
is evaluated as TRUE
when the method is called for the first time. The
cond
argument also supports numeric shorthands like cond = 1L
which evaluates to
quote(._counter_ == 1L)
, and this is the default value of cond
for
all workflow functions that only return one value (e.g., ggtrace_capture_fn()
).
It is recommended to consult the output of ggtrace_inspect_n()
and
ggtrace_inspect_which()
to construct expressions that condition on ._counter_
.
For highjack functions like ggtrace_highjack_return()
, the value about to
be returned by the function/method can be accessed with returnValue()
in the
value
argument. By default, value
is set to quote(returnValue())
which
simply evaluates to the return value, but directly computing on returnValue()
to
derive a different return value for the function/method is also possible.
library(ggplot2) p1 <- ggplot(diamonds, aes(cut)) + geom_bar(aes(fill = cut)) + facet_wrap(~ clarity) p1 # Argument value of `Stat$compute_panel` for the first panel compute_panel_args_1 <- ggtrace_inspect_args(x = p1, method = Stat$compute_panel) names(ggformals(Stat$compute_panel)) names(compute_panel_args_1) table(compute_panel_args_1$data$fill) # `hoist_dots` preserves information about which arguments were passed to `...` with_dots <- ggtrace_inspect_args(p1, Stat$compute_panel, hoist_dots = FALSE) names(with_dots) with_dots$`...`
library(ggplot2) p1 <- ggplot(diamonds, aes(cut)) + geom_bar(aes(fill = cut)) + facet_wrap(~ clarity) p1 # Argument value of `Stat$compute_panel` for the first panel compute_panel_args_1 <- ggtrace_inspect_args(x = p1, method = Stat$compute_panel) names(ggformals(Stat$compute_panel)) names(compute_panel_args_1) table(compute_panel_args_1$data$fill) # `hoist_dots` preserves information about which arguments were passed to `...` with_dots <- ggtrace_inspect_args(p1, Stat$compute_panel, hoist_dots = FALSE) names(with_dots) with_dots$`...`
Inspect how many times a method was called
ggtrace_inspect_n(x, method, ..., error = FALSE) inspect_n(x, method, ..., error = FALSE)
ggtrace_inspect_n(x, method, ..., error = FALSE) inspect_n(x, method, ..., error = FALSE)
x |
A ggplot object |
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
... |
Unused. |
error |
If |
The number of times method
was called in the evaluation of x
library(ggplot2) p1 <- ggplot(diamonds, aes(cut)) + geom_bar(aes(fill = cut)) + facet_wrap(~ clarity) p1 # 1 call to Stat$compute_layer ggtrace_inspect_n(p1, Stat$compute_layer) # 8 calls to Stat$compute_panel ggtrace_inspect_n(p1, Stat$compute_panel) # Note that there are 0 calls to Stat$compute_group ... ggtrace_inspect_n(p1, Stat$compute_group) # because StatCount has its own "compute_group" method defined ggtrace_inspect_n(p1, StatCount$compute_group) # How about if we add a second layer that uses StatCount? p2 <- p1 + geom_text( aes(label = after_stat(count)), stat = StatCount, position = position_nudge(y = 500) ) p2 # Now there are double the calls to Stat/StatCount methods ggtrace_inspect_n(p2, Stat$compute_layer) ggtrace_inspect_n(p2, Stat$compute_panel) ggtrace_inspect_n(p2, StatCount$compute_group) # But separate calls to each layer's respective Geoms ggtrace_inspect_n(p2, GeomBar$draw_panel) ggtrace_inspect_n(p2, GeomText$draw_panel)
library(ggplot2) p1 <- ggplot(diamonds, aes(cut)) + geom_bar(aes(fill = cut)) + facet_wrap(~ clarity) p1 # 1 call to Stat$compute_layer ggtrace_inspect_n(p1, Stat$compute_layer) # 8 calls to Stat$compute_panel ggtrace_inspect_n(p1, Stat$compute_panel) # Note that there are 0 calls to Stat$compute_group ... ggtrace_inspect_n(p1, Stat$compute_group) # because StatCount has its own "compute_group" method defined ggtrace_inspect_n(p1, StatCount$compute_group) # How about if we add a second layer that uses StatCount? p2 <- p1 + geom_text( aes(label = after_stat(count)), stat = StatCount, position = position_nudge(y = 500) ) p2 # Now there are double the calls to Stat/StatCount methods ggtrace_inspect_n(p2, Stat$compute_layer) ggtrace_inspect_n(p2, Stat$compute_panel) ggtrace_inspect_n(p2, StatCount$compute_group) # But separate calls to each layer's respective Geoms ggtrace_inspect_n(p2, GeomBar$draw_panel) ggtrace_inspect_n(p2, GeomText$draw_panel)
Get information about a ggproto method on error
ggtrace_inspect_on_error(x, method, ...) inspect_on_error(x, method, ...)
ggtrace_inspect_on_error(x, method, ...) inspect_on_error(x, method, ...)
x |
A ggplot object |
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
... |
Unused. |
A list of three elements: counter
, args
, and env
.
library(ggplot2) erroring_barplot <- ggplot(mtcars, aes(mpg, hp)) + stat_summary() + geom_bar() ggtrace_inspect_on_error(erroring_barplot, StatCount$setup_params) ggtrace_inspect_on_error(erroring_barplot, ggplot2:::Layer$compute_statistic)
library(ggplot2) erroring_barplot <- ggplot(mtcars, aes(mpg, hp)) + stat_summary() + geom_bar() ggtrace_inspect_on_error(erroring_barplot, StatCount$setup_params) ggtrace_inspect_on_error(erroring_barplot, ggplot2:::Layer$compute_statistic)
Inspect the return value of a method
ggtrace_inspect_return(x, method, cond = 1L, ..., error = FALSE) inspect_return(x, method, cond = 1L, ..., error = FALSE)
ggtrace_inspect_return(x, method, cond = 1L, ..., error = FALSE) inspect_return(x, method, cond = 1L, ..., error = FALSE)
x |
A ggplot object |
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
cond |
When the return value should be inspected. Defaults to |
... |
Unused. |
error |
If |
The return value from method
when it is called.
When quoted expressions are passed to the cond
or value
argument of
workflow functions they are evaluated in a special environment which
we call the "tracing context".
The tracing context is "data-masked" (see rlang::eval_tidy()
), and exposes
an internal variable called ._counter_
which increments every time a
function/method has been called by the ggplot object supplied to the x
argument of workflow functions. For example, cond = quote(._counter_ == 1L)
is evaluated as TRUE
when the method is called for the first time. The
cond
argument also supports numeric shorthands like cond = 1L
which evaluates to
quote(._counter_ == 1L)
, and this is the default value of cond
for
all workflow functions that only return one value (e.g., ggtrace_capture_fn()
).
It is recommended to consult the output of ggtrace_inspect_n()
and
ggtrace_inspect_which()
to construct expressions that condition on ._counter_
.
For highjack functions like ggtrace_highjack_return()
, the value about to
be returned by the function/method can be accessed with returnValue()
in the
value
argument. By default, value
is set to quote(returnValue())
which
simply evaluates to the return value, but directly computing on returnValue()
to
derive a different return value for the function/method is also possible.
library(ggplot2) p1 <- ggplot(diamonds, aes(cut)) + geom_bar(aes(fill = cut)) + facet_wrap(~ clarity) p1 # Return value of `Stat$compute_panel` for the first panel ggtrace_inspect_return(x = p1, method = Stat$compute_panel) # Return value for 4th panel ggtrace_inspect_return(x = p1, method = Stat$compute_panel, cond = 4L) # Return value for 4th panel, 2nd group (bar) ggtrace_inspect_return( x = p1, method = StatCount$compute_group, cond = quote(data$PANEL[1] == 4 && data$group[1] == 2) )
library(ggplot2) p1 <- ggplot(diamonds, aes(cut)) + geom_bar(aes(fill = cut)) + facet_wrap(~ clarity) p1 # Return value of `Stat$compute_panel` for the first panel ggtrace_inspect_return(x = p1, method = Stat$compute_panel) # Return value for 4th panel ggtrace_inspect_return(x = p1, method = Stat$compute_panel, cond = 4L) # Return value for 4th panel, 2nd group (bar) ggtrace_inspect_return( x = p1, method = StatCount$compute_group, cond = quote(data$PANEL[1] == 4 && data$group[1] == 2) )
Inspect the value of variables from a method
ggtrace_inspect_vars( x, method, cond = 1L, at = "all", vars, by_var = TRUE, ..., error = FALSE ) inspect_vars( x, method, cond = 1L, at = "all", vars, by_var = TRUE, ..., error = FALSE )
ggtrace_inspect_vars( x, method, cond = 1L, at = "all", vars, by_var = TRUE, ..., error = FALSE ) inspect_vars( x, method, cond = 1L, at = "all", vars, by_var = TRUE, ..., error = FALSE )
x |
A ggplot object |
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
cond |
When the return value should be inspected. Defaults to |
at |
Which steps in the method body the values of |
vars |
A character vector of variable names |
by_var |
Boolean that controls the format of the output:
|
... |
Unused. |
error |
If |
A list of values of vars
at each step at
. Simplifies if vars
and/or at
is length-1.
When quoted expressions are passed to the cond
or value
argument of
workflow functions they are evaluated in a special environment which
we call the "tracing context".
The tracing context is "data-masked" (see rlang::eval_tidy()
), and exposes
an internal variable called ._counter_
which increments every time a
function/method has been called by the ggplot object supplied to the x
argument of workflow functions. For example, cond = quote(._counter_ == 1L)
is evaluated as TRUE
when the method is called for the first time. The
cond
argument also supports numeric shorthands like cond = 1L
which evaluates to
quote(._counter_ == 1L)
, and this is the default value of cond
for
all workflow functions that only return one value (e.g., ggtrace_capture_fn()
).
It is recommended to consult the output of ggtrace_inspect_n()
and
ggtrace_inspect_which()
to construct expressions that condition on ._counter_
.
For highjack functions like ggtrace_highjack_return()
, the value about to
be returned by the function/method can be accessed with returnValue()
in the
value
argument. By default, value
is set to quote(returnValue())
which
simply evaluates to the return value, but directly computing on returnValue()
to
derive a different return value for the function/method is also possible.
library(ggplot2) p1 <- ggplot(mtcars[1:10,], aes(mpg, hp)) + geom_smooth() p1 # The `data` variable is bound to two unique values in `compute_group` method: ggtrace_inspect_vars(p1, StatSmooth$compute_group, vars = "data") # Note that elements of this list capture the method's state upon entering a step, # so "Step1" and "Step5" should be interpreted as the value of `data` at the start # the method's execution (before "Step1") and its value as a result of running Step4 # (before "Step5"). Indeed, we see that the `weight` column is defined in Step4, so # the data is flagged as changed at the start of Step5 ggbody(StatSmooth$compute_group)[[4]] # Comparing the "Steps" themselves can be useful p2 <- p1 + scale_x_continuous(trans = "log") + scale_y_continuous(trans = "log") p2 # Comparing the original plot to one with log-transformed scales reveals a change # in data detected at the beginning of Step 14 names(ggtrace_inspect_vars(p1, ggplot2:::ggplot_build.ggplot, vars = "data")) names(ggtrace_inspect_vars(p2, ggplot2:::ggplot_build.ggplot, vars = "data")) # We can pinpoint the calculation of scale transformations to Step 13: ggbody(ggplot2:::ggplot_build.ggplot)[[13]] # With `by_vars = FALSE`, elements of the returned list are steps instead of values. # Note that this does not drop unchanged values: ggtrace_inspect_vars(p1, StatSmooth$compute_group, vars = "data", at = 1:6, by_var = FALSE)
library(ggplot2) p1 <- ggplot(mtcars[1:10,], aes(mpg, hp)) + geom_smooth() p1 # The `data` variable is bound to two unique values in `compute_group` method: ggtrace_inspect_vars(p1, StatSmooth$compute_group, vars = "data") # Note that elements of this list capture the method's state upon entering a step, # so "Step1" and "Step5" should be interpreted as the value of `data` at the start # the method's execution (before "Step1") and its value as a result of running Step4 # (before "Step5"). Indeed, we see that the `weight` column is defined in Step4, so # the data is flagged as changed at the start of Step5 ggbody(StatSmooth$compute_group)[[4]] # Comparing the "Steps" themselves can be useful p2 <- p1 + scale_x_continuous(trans = "log") + scale_y_continuous(trans = "log") p2 # Comparing the original plot to one with log-transformed scales reveals a change # in data detected at the beginning of Step 14 names(ggtrace_inspect_vars(p1, ggplot2:::ggplot_build.ggplot, vars = "data")) names(ggtrace_inspect_vars(p2, ggplot2:::ggplot_build.ggplot, vars = "data")) # We can pinpoint the calculation of scale transformations to Step 13: ggbody(ggplot2:::ggplot_build.ggplot)[[13]] # With `by_vars = FALSE`, elements of the returned list are steps instead of values. # Note that this does not drop unchanged values: ggtrace_inspect_vars(p1, StatSmooth$compute_group, vars = "data", at = 1:6, by_var = FALSE)
Inspect which calls to a ggproto method met a particular condition
ggtrace_inspect_which(x, method, cond, ..., error = FALSE) inspect_which(x, method, cond, ..., error = FALSE)
ggtrace_inspect_which(x, method, cond, ..., error = FALSE) inspect_which(x, method, cond, ..., error = FALSE)
x |
A ggplot object |
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
cond |
Expression evaluating to a logical inside |
... |
Unused. |
error |
If |
The values of the tracing context variable ._counter_
when cond
is evaluated as TRUE
.
When quoted expressions are passed to the cond
or value
argument of
workflow functions they are evaluated in a special environment which
we call the "tracing context".
The tracing context is "data-masked" (see rlang::eval_tidy()
), and exposes
an internal variable called ._counter_
which increments every time a
function/method has been called by the ggplot object supplied to the x
argument of workflow functions. For example, cond = quote(._counter_ == 1L)
is evaluated as TRUE
when the method is called for the first time. The
cond
argument also supports numeric shorthands like cond = 1L
which evaluates to
quote(._counter_ == 1L)
, and this is the default value of cond
for
all workflow functions that only return one value (e.g., ggtrace_capture_fn()
).
It is recommended to consult the output of ggtrace_inspect_n()
and
ggtrace_inspect_which()
to construct expressions that condition on ._counter_
.
For highjack functions like ggtrace_highjack_return()
, the value about to
be returned by the function/method can be accessed with returnValue()
in the
value
argument. By default, value
is set to quote(returnValue())
which
simply evaluates to the return value, but directly computing on returnValue()
to
derive a different return value for the function/method is also possible.
library(ggplot2) p1 <- ggplot(diamonds, aes(cut)) + geom_bar(aes(fill = cut)) + facet_wrap(~ clarity) p1 # Values of `._counter_` when `compute_group` is called for groups in the second panel: ggtrace_inspect_which(p1, StatCount$compute_group, quote(data$PANEL[1] == 2)) # How about if we add a second layer that uses StatCount? p2 <- p1 + geom_text( aes(label = after_stat(count)), stat = StatCount, position = position_nudge(y = 500) ) p2 ggtrace_inspect_which(p2, StatCount$compute_group, quote(data$PANEL[1] == 2)) # Behaves like `base::which()` and returns `integer(0)` when no matches are found ggtrace_inspect_which(p2, StatBoxplot$compute_group, quote(data$PANEL[1] == 2))
library(ggplot2) p1 <- ggplot(diamonds, aes(cut)) + geom_bar(aes(fill = cut)) + facet_wrap(~ clarity) p1 # Values of `._counter_` when `compute_group` is called for groups in the second panel: ggtrace_inspect_which(p1, StatCount$compute_group, quote(data$PANEL[1] == 2)) # How about if we add a second layer that uses StatCount? p2 <- p1 + geom_text( aes(label = after_stat(count)), stat = StatCount, position = position_nudge(y = 500) ) p2 ggtrace_inspect_which(p2, StatCount$compute_group, quote(data$PANEL[1] == 2)) # Behaves like `base::which()` and returns `integer(0)` when no matches are found ggtrace_inspect_which(p2, StatBoxplot$compute_group, quote(data$PANEL[1] == 2))
Used for explicitly calling untrace()
on a ggproto object.
gguntrace(method, ...)
gguntrace(method, ...)
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
... |
Ignored. Designed for the ease of calling this function by modifying the call to
an earlier |
Unlike base::untrace()
, there is no adverse side effect to repeatedly calling gguntrace()
on a ggproto method. gguntrace()
will only throw an error if the method cannot be found.
If the method is valid, gguntrace()
will do one of two things:
Inform that it has successfully removed the trace (after untracing)
Inform that the there isn't an existing trace (after doing nothing)
library(ggplot2) gguntrace(Stat$compute_layer) is_traced(Stat$compute_layer) ggtrace(Stat$compute_layer, 1) is_traced(Stat$compute_layer) gguntrace(Stat$compute_layer) is_traced(Stat$compute_layer) gguntrace(Stat$compute_layer)
library(ggplot2) gguntrace(Stat$compute_layer) is_traced(Stat$compute_layer) ggtrace(Stat$compute_layer, 1) is_traced(Stat$compute_layer) gguntrace(Stat$compute_layer) is_traced(Stat$compute_layer) gguntrace(Stat$compute_layer)
Check if a method is being traced
is_traced(method)
is_traced(method)
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
logical
library(ggplot2) gguntrace(Stat$compute_layer) is_traced(Stat$compute_layer) ggtrace(Stat$compute_layer, 1) is_traced(Stat$compute_layer) gguntrace(Stat$compute_layer) is_traced(Stat$compute_layer) gguntrace(Stat$compute_layer)
library(ggplot2) gguntrace(Stat$compute_layer) is_traced(Stat$compute_layer) ggtrace(Stat$compute_layer, 1) is_traced(Stat$compute_layer) gguntrace(Stat$compute_layer) is_traced(Stat$compute_layer) gguntrace(Stat$compute_layer)
last_layer_errorcontext()
returns the error context at the level of the Layer
ggproto.
last_sublayer_errorcontext()
(EXPERIMENTAL) returns the error context at the sub-Layer
level (e.g., Stat
or Geom
).
last_layer_errorcontext(reprint_error = FALSE, ggtrace_notes = TRUE) last_sublayer_errorcontext(reprint_error = FALSE, ggtrace_notes = TRUE)
last_layer_errorcontext(reprint_error = FALSE, ggtrace_notes = TRUE) last_sublayer_errorcontext(reprint_error = FALSE, ggtrace_notes = TRUE)
reprint_error |
Re-prints the original error message to the console. Defaults to |
ggtrace_notes |
Prints the |
An dynamically constructed and evaluated call to ggtrace_inspect_args()
.
Prioritizes showing the state of layer data whenever possible (by extracting the data
argument).
These functions can only retrieve information from errors propagating from
Layer
ggproto methods. In non-technical terms, they only work for errors with a
"Error occured in the Nth layer" message (as of {ggplot2}
>= 3.4.0).
The scope of last_sublayer_errorcontext()
is narrower, since not all Layer
methods call
a sub-Layer method. This function is intended for developers - in most cases users can
get all the information necessary to debug layer code from last_layer_errorcontext()
(there are only so many ways to break a ggplot from user-facing code).
## Not run: library(ggplot2) erroring_barplot1 <- ggplot(mtcars, aes(mpg, hp)) + stat_summary(fun.data = "mean_se") + geom_bar() # Render to trigger error erroring_barplot1 # Both return the same snapshot of layer data # but at different levels of specificity last_layer_errorcontext() last_sublayer_errorcontext() erroring_barplot2 <- ggplot(mtcars, aes(mpg, hp)) + stat_summary() + geom_bar(aes(y = c(1, 2))) erroring_barplot2 # This works: last_layer_errorcontext() # This doesn't: there's no sub-layer ggproto involved in this error last_sublayer_errorcontext() library(ggforce) erroring_sina <- ggplot(mtcars, aes(mpg)) + geom_bar() + geom_sina() erroring_barplot1 # The two return different snapshots of layer data here - # see `ggplot2:::Layer$compute_statistic` for why. last_layer_errorcontext() last_sublayer_errorcontext() ## End(Not run)
## Not run: library(ggplot2) erroring_barplot1 <- ggplot(mtcars, aes(mpg, hp)) + stat_summary(fun.data = "mean_se") + geom_bar() # Render to trigger error erroring_barplot1 # Both return the same snapshot of layer data # but at different levels of specificity last_layer_errorcontext() last_sublayer_errorcontext() erroring_barplot2 <- ggplot(mtcars, aes(mpg, hp)) + stat_summary() + geom_bar(aes(y = c(1, 2))) erroring_barplot2 # This works: last_layer_errorcontext() # This doesn't: there's no sub-layer ggproto involved in this error last_sublayer_errorcontext() library(ggforce) erroring_sina <- ggplot(mtcars, aes(mpg)) + geom_bar() + geom_sina() erroring_barplot1 # The two return different snapshots of layer data here - # see `ggplot2:::Layer$compute_statistic` for why. last_layer_errorcontext() last_sublayer_errorcontext() ## End(Not run)
with_ggtrace()
provides a functional interface to ggtrace()
. It takes a ggplot object
and parameters passed to ggtrace()
and returns the immediate tracedump and/or graphical
output without side effects.
with_ggtrace(x, method, ..., out = c("tracedump", "gtable", "both"))
with_ggtrace(x, method, ..., out = c("tracedump", "gtable", "both"))
x |
A ggplot object whose evaluation triggers the trace as specified by the |
method |
A function or a ggproto method. The ggproto method may be specified using any of the following forms:
|
... |
Arguments passed on to
|
out |
Whether the function should return the output of triggered traces ("tracedump"), or the resulting graphical object from evaluating the ggplot ("gtable"), or "both", which returns the tracedump but also renders the resulting plot as a side effect. Partial matching is supported, so these options could also be specified as "t", "g", or "b". Defaults to "tracedump". |
A list or gtable object of class <ggtrace_highjacked>
To trigger evaluation of x
, the function ggeval_silent(x)
is called internally.
library(ggplot2) # Long-form `ggtrace()` method: boxplot_plot <- ggplot(diamonds[1:500,], aes(cut, depth)) + geom_boxplot() ggtrace( method = StatBoxplot$compute_group, trace_steps = -1, trace_exprs = quote(~step) ) boxplot_plot first_tracedump <- last_ggtrace() # Short-form functional `with_ggtrace()` method: second_tracedump <- with_ggtrace( x = boxplot_plot, method = StatBoxplot$compute_group, trace_steps = -1, trace_exprs = quote(~step) ) identical(first_tracedump, second_tracedump) # An example with `out = "gtable"` (or `"g"`) grid_plot <- ggplot(mtcars, aes(mpg, hp)) + geom_point() + facet_grid(am ~ cyl) grid_plot outline <- grid::rectGrob( x = 0.5, y = 0.5, width = 1, height = 1, gp = grid::gpar(col = "red", lwd = 5, fill = NA) ) with_ggtrace( x = grid_plot, method = Layout$render, trace_steps = 5, trace_exprs = rlang::expr({ panels[c(3, 5)] <- lapply(panels[c(3, 5)], function(panel) { gTree(children = gList(panel, !!outline)) }) }), out = "gtable" # or "g" ) # With `once = FALSE` for persistent tracing (still cleaned up after) lm_plot <- ggplot(mpg, aes(displ, hwy, color = drv)) + geom_point() + geom_smooth(method = "lm") lm_plot with_ggtrace( x = lm_plot, method = StatSmooth$compute_group, trace_steps = c(1, 11), trace_exprs = list( group = quote(data$group[1]), coef = quote(model$coef) ) ) with_ggtrace( x = lm_plot, method = StatSmooth$compute_group, trace_steps = 1, trace_exprs = quote(method <- c("loess", "lm", "loess")[data$group[1]]), out = "g" # or "gtable" )
library(ggplot2) # Long-form `ggtrace()` method: boxplot_plot <- ggplot(diamonds[1:500,], aes(cut, depth)) + geom_boxplot() ggtrace( method = StatBoxplot$compute_group, trace_steps = -1, trace_exprs = quote(~step) ) boxplot_plot first_tracedump <- last_ggtrace() # Short-form functional `with_ggtrace()` method: second_tracedump <- with_ggtrace( x = boxplot_plot, method = StatBoxplot$compute_group, trace_steps = -1, trace_exprs = quote(~step) ) identical(first_tracedump, second_tracedump) # An example with `out = "gtable"` (or `"g"`) grid_plot <- ggplot(mtcars, aes(mpg, hp)) + geom_point() + facet_grid(am ~ cyl) grid_plot outline <- grid::rectGrob( x = 0.5, y = 0.5, width = 1, height = 1, gp = grid::gpar(col = "red", lwd = 5, fill = NA) ) with_ggtrace( x = grid_plot, method = Layout$render, trace_steps = 5, trace_exprs = rlang::expr({ panels[c(3, 5)] <- lapply(panels[c(3, 5)], function(panel) { gTree(children = gList(panel, !!outline)) }) }), out = "gtable" # or "g" ) # With `once = FALSE` for persistent tracing (still cleaned up after) lm_plot <- ggplot(mpg, aes(displ, hwy, color = drv)) + geom_point() + geom_smooth(method = "lm") lm_plot with_ggtrace( x = lm_plot, method = StatSmooth$compute_group, trace_steps = c(1, 11), trace_exprs = list( group = quote(data$group[1]), coef = quote(model$coef) ) ) with_ggtrace( x = lm_plot, method = StatSmooth$compute_group, trace_steps = 1, trace_exprs = quote(method <- c("loess", "lm", "loess")[data$group[1]]), out = "g" # or "gtable" )