Skip to content

Commit 0d3db5c

Browse files
authored
Trim intermediate plots (#207)
Fixes #206
1 parent ceb17e7 commit 0d3db5c

File tree

7 files changed

+139
-0
lines changed

7 files changed

+139
-0
lines changed

NAMESPACE

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Generated by roxygen2: do not edit by hand
22

3+
S3method("[",evaluate_evaluation)
34
S3method(parse_all,"function")
45
S3method(parse_all,call)
56
S3method(parse_all,character)
@@ -26,6 +27,7 @@ export(parse_all)
2627
export(remove_hooks)
2728
export(replay)
2829
export(set_hooks)
30+
export(trim_intermediate_plots)
2931
export(try_capture_stack)
3032
import(grDevices)
3133
import(graphics)

NEWS.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# evaluate (development version)
22

3+
* New `trim_intermediate_plots()` drops intermediate plots to reveal the complete/final plot (#206).
34
* evaluation "chunks" now provide a function-like scope. This means that `on.exit()` will now run at the end of the evaluate code, rather than immediately and `return()` will cause the evaluation to finish (#201).
45
* The default `value` handler now evaluates print in a child environment of the evaluation environment. This largely makes evaluate easier to test, but should make defining S3 methods for print a little easier (#192).
56
* `parse_all()` adds a `\n` to the end of every line, even the last one if it didn't have one in the input.

R/evaluation.R

+9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ new_evaluation <- function(x) {
33
structure(x, class = c("evaluate_evaluation", "list"))
44
}
55

6+
is_evaluation <- function(x) {
7+
inherits(x, "evaluate_evaluation")
8+
}
9+
10+
#' @export
11+
`[.evaluate_evaluation` <- function(x, i, ...) {
12+
new_evaluation(NextMethod())
13+
}
14+
615
#' @export
716
print.evaluate_evaluation <- function(x, ...) {
817
cat_line("<evaluation>")

R/graphics.R

+55
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ local_plot_hooks <- function(f, frame = parent.frame()) {
99
invisible()
1010
}
1111

12+
# visual changes ---------------------------------------------------------
13+
1214
looks_different <- function(old_dl, new_dl) {
1315
if (identical(old_dl, new_dl)) {
1416
return(FALSE)
@@ -54,3 +56,56 @@ non_visual_calls <- c(
5456
"C_strHeight", "C_strWidth",
5557
"palette", "palette2"
5658
)
59+
60+
# plot trimming ----------------------------------------------------------
61+
62+
#' Trim away intermediate plots
63+
#'
64+
#' Trim off plots that are modified by subsequent lines to only show
65+
#' the "final" plot.
66+
#'
67+
#' @param x An evaluation object produced by [evaluate()].
68+
#' @return A modified evaluation object.
69+
#' @export
70+
#' @examples
71+
#' ev <- evaluate(c(
72+
#' "plot(1:3)",
73+
#' "text(1, 1, 'x')",
74+
#' "text(1, 1, 'y')"
75+
#' ))
76+
#'
77+
#' # All intermediate plots are captured
78+
#' ev
79+
#' # Only the final plot is shown
80+
#' trim_intermediate_plots(ev)
81+
trim_intermediate_plots <- function(x) {
82+
if (!is_evaluation(x)) {
83+
stop("`x` must be an evaluation object.")
84+
}
85+
86+
is_plot <- vapply(x, is.recordedplot, logical(1))
87+
plot_idx <- which(is_plot)
88+
keep <- rep(TRUE, length(plot_idx))
89+
90+
prev_plot <- NULL
91+
for (i in seq2(2, length(plot_idx))) {
92+
cur_plot_dl <- x[[plot_idx[i]]][[1]]
93+
prev_plot_dl <- x[[plot_idx[i - 1]]][[1]]
94+
95+
if (prev_plot_dl %is_prefix_of% cur_plot_dl) {
96+
keep[i - 1] <- FALSE
97+
}
98+
}
99+
100+
idx <- seq_along(x)
101+
idx <- setdiff(idx, plot_idx[!keep])
102+
x[idx]
103+
}
104+
105+
`%is_prefix_of%` <- function(x, y) {
106+
if (length(x) > length(y)) {
107+
return(FALSE)
108+
}
109+
110+
identical(x[], y[seq_along(x)])
111+
}

man/trim_intermediate_plots.Rd

+30
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/_snaps/graphics.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# checks its input
2+
3+
Code
4+
trim_intermediate_plots(1)
5+
Condition
6+
Error in `trim_intermediate_plots()`:
7+
! `x` must be an evaluation object.
8+

tests/testthat/test-graphics.R

+34
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,37 @@ test_that("evaluate ignores plots created in new device", {
222222
})
223223
expect_output_types(ev, c("source", "source", "source", "source", "plot"))
224224
})
225+
226+
227+
# trim_intermediate_plots ------------------------------------------------
228+
229+
test_that("can trim off intermediate plots", {
230+
ev <- evaluate(c(
231+
"plot(1:3)",
232+
"text(1, 1, 'x')",
233+
"text(1, 1, 'y')"
234+
))
235+
ev <- trim_intermediate_plots(ev)
236+
expect_output_types(ev, c("source", "source", "source", "plot"))
237+
238+
ev <- evaluate(c(
239+
"plot(1:3)",
240+
"text(1, 1, 'x')",
241+
"plot(1:3)",
242+
"text(1, 1, 'y')"
243+
))
244+
ev <- trim_intermediate_plots(ev)
245+
expect_output_types(ev, c("source", "source", "plot", "source", "source", "plot"))
246+
})
247+
248+
test_that("works with empty output", {
249+
ev <- trim_intermediate_plots(evaluate(""))
250+
expect_output_types(ev, "source")
251+
252+
ev <- trim_intermediate_plots(new_evaluation(list()))
253+
expect_output_types(ev, character())
254+
})
255+
256+
test_that("checks its input", {
257+
expect_snapshot(trim_intermediate_plots(1), error = TRUE)
258+
})

0 commit comments

Comments
 (0)