Skip to contents

This guide walks through the basic features of unigd and compares them with the plot rendering methods in base R.

Plot rendering in base R

Rendering a plot in base R is done by (1) starting a graphics device, (2) calling some plot functions and (3) closing the device:

temp <- airquality$Temp

png(file="my_plot.png", width=600, height=400) # (1) Start the 'png' device
hist(temp, col="darkblue")                     # (2) Plot a histogram
dev.off()                                      # (3) Close the device

This has some unfortunate constraints:

  • Rendering information must be specified before the plot is created: file format, filepath, and dimensions.
  • There is no way to render the same plot in multiple formats or dimensions without re-running the plotting code.
  • No easy way to access the rendered data without writing to a file first.
  • dev.off() must be called every time, even if the plotting code errors.

unigd solves these issues with a different graphics device architecture.

Plot rendering with unigd

The same render using unigd:

library(unigd)
temp <- airquality$Temp

ugd()                                                # (1) Start the 'ugd' device
hist(temp, col="darkblue")                           # (2) Plot a histogram
ugd_save(file="my_plot.png", width=600, height=400)  # Render 600x400 PNG
dev.off()                                            # (3) Close the device

Rendering is an explicit step after plotting. This means you can render the same plot to multiple formats and dimensions:

# ...
hist(temp, col="darkblue")
ugd_save(file="my_plot.png", width=600, height=400) # 600x400 PNG
ugd_save(file="my_plot.pdf", width=300, height=300) # 300x300 PDF
# ...

Starting and closing a device can be cumbersome, especially if the plotting code errors and leaves the device open. For this reason unigd provides ugd_*_inline functions that handle device lifecycle automatically:

library(unigd)
temp <- airquality$Temp

ugd_save_inline({
  hist(temp, col="darkblue")
}, file="my_plot.png", width=600, height=400)

You can get the full list of included renderers with ugd_renderers().

In-memory render access

For applications like report generation, web services, or interactive tools, you may want to access the rendered data directly instead of writing to a file. Use ugd_render() instead of ugd_save():

temp <- airquality$Temp

ugd()
hist(temp, col="darkblue")
my_svg <- ugd_render(as="svg")
dev.off()

cat(my_svg)

There is also an inline variant:

temp <- airquality$Temp

my_svg <- ugd_render_inline({
  hist(temp, col="darkblue")
}, as="svg")

cat(my_svg)

More features

Zoom

All rendering functions offer a zoom parameter that scales the size of objects inside a plot independently of the plot dimensions. For example, zoom=2 doubles all objects to 200%, zoom=0.5 halves them to 50%.

my_svg_1_0 <- ugd_render_inline({
  hist(temp, col="darkblue", main = "Zoom 1.0")
}, as="png-base64", width=300, height=300, zoom=1.0)

my_svg_1_5 <- ugd_render_inline({
  hist(temp, col="darkblue", main = "Zoom 1.5")
}, as="png-base64", width=300, height=300, zoom=1.5)

my_svg_0_5 <- ugd_render_inline({
  hist(temp, col="darkblue", main = "Zoom 0.5")
}, as="png-base64", width=300, height=300, zoom=0.5)

alts <- c("Histogram at zoom 1.0", "Histogram at zoom 1.5", "Histogram at zoom 0.5")
knitr::raw_html(paste0(sprintf("<img src=\"%s\" alt=\"%s\" />",
  c(my_svg_1_0, my_svg_1_5, my_svg_0_5), alts)))
Histogram at zoom 1.0Histogram at zoom 1.5Histogram at zoom 0.5

Paging (by index)

The page parameter selects which plot from the history to render. By default it is 0, which uses the most recently created plot. Values ≥ 1 select by index (oldest first), values ≤ 0 select newest-first:

ugd()
for (i in 1:10) {
  plot(1, main=paste0("Plot #", i))
}

ugd_save(file="plot.png", page = 3)  # Plot #3
ugd_save(file="plot.png")            # Plot #10 (latest)
ugd_save(file="plot.png", page = -1) # Plot #9

dev.off()

Plots can also be removed from history:

ugd_remove()          # Remove last
ugd_remove(page = -1) # Remove second-to-last
ugd_clear()           # Remove all

Plot IDs

Plot indices shift when plots are added or removed. If you need to refer to a specific plot later, grab its ID with ugd_id():

ugd()

plot(rnorm(50))           # A
id_a <- ugd_id()          # Get last ID (A)

hist(rnorm(50))           # B
plot(sin((1:100)/3))      # C
id_b <- ugd_id(-1)        # Second-to-last (B)

hist(runif(100))          # D
ugd_remove(3)             # Remove C

ugd_save(file="plot_a.png", page = id_a)
ugd_save(file="plot_b.png", page = id_b)

dev.off()

In practice this is usually simpler: just call ugd_id() after each plot to capture its ID.

Special renderers

unigd ships with a few special renderers beyond image formats:

  • "strings": All text elements inside a plot, one per line. Useful for searching through plots.
  • "meta": Plot metadata in JSON format. Includes complexity (number of draw calls and clipping planes). Guaranteed O(1) render time regardless of plot complexity.
  • "json": All information unigd has about a plot, in JSON format.

Performance considerations

For most applications, readability should be prioritized over performance. Unless graphics rendering is bottlenecking your R script, you can safely skip this section.

The key thing to understand is when unigd needs to ask the R graphics engine to redraw a plot. Rendering happens after drawing, and the last drawn dimensions are cached. Two rules follow from this:

  • Rendering the same plot in different formats: fast (no redraw needed).
  • Rendering the same plot in different dimensions: slower (triggers a redraw).

Grouping renders by dimension avoids unnecessary redraws:

# Two redraws (dimensions keep changing):
ugd_save(file="my_plot.png", width=600, height=400)
ugd_save(file="my_plot.pdf", width=300, height=300) # redraw
ugd_save(file="my_plot.svg",  width=600, height=400) # redraw

# One redraw (same dimensions grouped together):
ugd_save(file="my_plot.png", width=600, height=400)
ugd_save(file="my_plot.svg",  width=600, height=400)
ugd_save(file="my_plot.pdf", width=300, height=300) # redraw

You can also avoid the initial redraw by setting the target dimensions at device creation time:

ugd(width=300, height=300)
# ...
ugd_save(file="my_plot.png", width=300, height=300) # no redraw needed

If you omit the dimensions when calling rendering functions, the last known dimensions are used and no redraw occurs:

ugd_save(file="my_plot.png")

All ugd_*_inline functions also avoid unnecessary redraws.

Note that zoom interacts with dimensions: the cached width is width / zoom.