Skip to content

Add record functionality for recording + replaying plots as objects - #686

Merged
grantmcdermott merged 19 commits into
mainfrom
recordPlot
Sep 11, 2026
Merged

Add record functionality for recording + replaying plots as objects#686
grantmcdermott merged 19 commits into
mainfrom
recordPlot

Conversation

@grantmcdermott

@grantmcdermott grantmcdermott commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Fixes #121

Updated description

The PR adds a new top-level tinyplot(..., record = <logical>) argument, which in turn enables (modified) grDevices::recordPlot functionality for recording and replaying tinyplots as objects. User can also enable globally via the sister tpar(record = TRUE) option.

Apart from switching to an explicit opt-in, the main changes from the first iteration of this PR are related to safer handling of replayed (in turn, via better integration with tinyplot internals). This includes automatic displaylist landing for external devices, as well as smart theme and call restoration. Some of this is enabled by the new recordedtinyplot object class, which is really just a thin+tinyplot-friendly wrapper around recordedplot.

P.S. I'm currently marking this feature as "Experimental. But I've test it pretty thoroughly locally and everything seems to be working well. There's minimal overhead and the explicit opt-in requirement also gives us an off-ramp in case we notice any (unfixable) unintended consequences down the road.

MWEs

Basic use via top-level record argument:

pkgload::load_all("~/Documents/Projects/tinyplot/")
#> ℹ Loading tinyplot
p = plt(
  bill_len ~ bill_dep | species, data = penguins, theme = "clean",
  record = TRUE  # <-- key arg
)

We can then carry on with our session, including constructing other plots, before replaying our saved object (and add layers to it, if desired).

# <other jobs, plots, etc.>

p                     # replay our saved plot
plt_add(type = "lm")  # adding to it works as expected

record is requires explicit op-in, but users can also set globally via tpar. One cool feature is that you can record a plot written to an external device (PDF, PNG, etc.) and then recall it later to your interactive viewer.

tpar(record = TRUE). # enable recording by default

p2 = plt(
  body_mass ~ bill_len | species, penguins,
  yaxl = ',',
  main = 'A waddle of penguins',
  sub = 'Species stick together',
  theme = 'web',
  legend = list('top!', title = FALSE),
  file = "penguins.pdf"
)
unlink("penguins.pdf")
p2
plt_add(type = "ellipse", fill = 0.1)

Aside: with global recording turned on (tpar(record = TRUE)), all of the regular object-based idioms and workflows apply. For example, you can draw a plot without explicit assignment and then retrieve it immediately after with .Last.value.

plt(Nile)

n = .Last.value
str(n)
#>  <tinyplot recorded plot>
#>   call: plt(Nile)
#>   display list entries: 7
#>   size: 93.4 Kb

Don't forge to disable automatic recording. The overhead is minimal, but it can start to eat into memory if you start recording+assigning lots of detailed plots with many elements.

tpar(record = FALSE)
Original (outdated) description

By adding grDevices::recordPlot() to the very end of the main tinyplot() function, we gain the ability to record (assign) plots as objects and then replay them later.

pkgload::load_all("~/Documents/Projects/tinyplot/")
#> ℹ Loading tinyplot

a = plt(Sepal.Length ~ Petal.Length | Species, data = iris)

plt(1) # some other random plot

a  # replay the saved plot

Here's a little plt_add gotcha, though...

plt_add(type = "lm") # gotcha: the last plot "call" was plt(1)!

But note that nothing stops us from layering immediately... and we can record and replay from a later layer too. Here, demonstrating with a theme to drive home the point.

plt(Sepal.Length ~ Petal.Length | Species, data = iris, theme = "socviz")
b = plt_add(type = "lm")

plt(1) # random placeholder again

b # replay the saved, layered plot object

Note: Due to the "canvas" nature of base graphics, we can't suppress the initial display. Unless, that is, we do something tricksy like write to a dummy device / file on disk and then recall later.

png("dummyplot.png")
dev.control("enable") # default for png is "inhibit" -> disallows recording
p = plt(Nile, type = "hist")
dev.off()
#> png 
#>   2
p # replay directly on our main, interactie device

Created on 2026-08-20 with reprex v2.1.1

Discussion: To be clear, I'm not convinced that we want to add this functionality. But some quick testing, at least, suggests that it doesn't add any real overhead. Apart from the plt_add() gotcha that I highlight above, I also haven't hit any unintended side effects. Still, I'd like others to kick the tyres too, before we consider whether merging is a good idea.

@b-rodrigues

Copy link
Copy Markdown

Can confirm it works :)
image

see: https://github.com/b-rodrigues/test-tinyplot/tree/master

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Replay state remains incomplete, direct replayPlot() bypasses context restoration, and pairs-style data frames do not return the documented object.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds opt-in recording and replay of tinyplot graphics while preserving state needed for subsequent layers.

Changes:

  • Adds record argument and tpar() setting.
  • Introduces recordedtinyplot objects with print/structure methods.
  • Adds documentation, website navigation, and tests.
File summaries
File Description
R/record.R Implements recorded objects and state restoration.
R/setup_device.R Enables display lists for managed file devices.
R/tinyplot.R Adds recording to the main plotting pipeline.
R/tpar.R Adds global recording configuration.
R/zzz.R Registers the new internal variable.
R/tinyplot.ts.R Inherits updated return documentation.
R/tinyplot.matrix.R Inherits updated return documentation.
R/tinyplot.data.frame.R Inherits updated return documentation.
R/tinyplot_add.R Documents recorded layer returns.
inst/tinytest/test-record.R Tests recording, replay, and layering.
NAMESPACE Registers methods and grDevices imports.
NEWS.md Announces experimental recording support.
vignettes/introduction.qmd Demonstrates saving and replaying plots.
man/recordedtinyplot.Rd Documents recorded plot objects.
man/tinyplot.Rd Documents the argument and return value.
man/tpar.Rd Documents global recording configuration.
man/tinyplot.ts.Rd Updates generated return documentation.
man/tinyplot.matrix.Rd Updates generated return documentation.
man/tinyplot.data.frame.Rd Updates generated return documentation.
man/tinyplot_add.Rd Updates generated return documentation.
altdoc/quarto_website.yml Adds the new reference page.
altdoc/pkgdown.yml Updates the site-build timestamp.
Review details

Files not reviewed (7)

  • man/recordedtinyplot.Rd: Generated file
  • man/tinyplot.Rd: Generated file
  • man/tinyplot.data.frame.Rd: Generated file
  • man/tinyplot.matrix.Rd: Generated file
  • man/tinyplot.ts.Rd: Generated file
  • man/tinyplot_add.Rd: Generated file
  • man/tpar.Rd: Generated file
  • Files reviewed: 15/22 changed files
  • Comments generated: 3
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread R/record.R
Comment thread R/record.R Outdated
Comment thread R/tinyplot.data.frame.R Outdated
@grantmcdermott grantmcdermott changed the title Incorporate recordPlot functionality for recording and replaying plots as objects Add record functionality for recording + replaying plots as objects Sep 11, 2026
@grantmcdermott
grantmcdermott merged commit 08af70f into main Sep 11, 2026
3 checks passed
@grantmcdermott
grantmcdermott deleted the recordPlot branch September 11, 2026 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wishlist: Will tinyplot ever support returning a plot object as ggplot2 does?

3 participants