Skip to contents

The following 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 subsequently (3) closing the device:

temp <- airquality$Temp                         # Fetch some data

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

Note that this has some unfortunate constraints:

  • Rendering information must be specified before the plot is created
    • File format (png(), pdf(), svg(), …)
    • Filepath (i.e.: file="my_plot1.png")
    • Dimensions (i.e.: width=600, height=400)
  • There is no way (without re-running the plotting code) to render the plot…
    • …in multiple formats.
    • …in multiple dimensions.
  • No easy way to access the plotting data without writing to a file first.
  • Closing the device with dev.off() must be called every time.

unigd solves these issues by employing a different graphics device architecture.

Plot rendering with unigd

Let’s see how the same render can be created using unigd:

library(unigd)
temp <- airquality$Temp                              # Fetch some data

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

Notice how rendering is an explicit instruction after plotting when using unigd. This way we can also render the same plot to multiple formats and/or dimensions:

# ...
hist(temp, col="darkblue")
ugd_save(file="my_plot1.png", width=600, height=400) # Render 600*400 PNG file
ugd_save(file="my_plot2.pdf", width=300, height=300) # Render 300*300 PDF file
# ...

Starting and closing a device can be cumbersome, especially if the plotting code aborts after an error and leaves the device open. For this reason unigd comes with a set of functions called ugd_*_inline:

library(unigd)
temp <- airquality$Temp # Fetch some data

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

Plotting this way keeps you from having to create and close a device manually. Depending on your personal preference this may also be considered as more ‘readable’ code.

You can obtain the full list of included renderers with ugd_renderers(). (It’s growing with every unigd update!)

The next section will illustrate how to access the render data directly without having to create a file.

In-memory render access

For some applications, you might want to access the rendered data directly. Example use-cases for this might be report generation, web services or interactive applications. While you can most likely think of workarounds for this issue, this unigd feature will certainly lower code complexity and increase performance.

Rendering in-memory is done by simply calling 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) # Print the SVG as a string

Of course there is also a inline function for this:

temp <- airquality$Temp

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

cat(my_svg) # Print the SVG as a string

More unigd features

unigd offers a number of features which go beyond the base R graphics devices.

Zoom

All rendering function in unigd offer a zoom parameter. This parameter can be used to increase (or decrease) the size of objects inside a plot (independently of plot dimensions). For example zoom=2 will increase the size of all objects to 200%, zoom=0.5 will decrease 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)

# (Output directly in this RMarkdown document)
knitr::raw_html(paste0(sprintf("<img src=\"%s\" />", c(my_svg_1_0, my_svg_1_5, my_svg_0_5))))

Paging (by index)

The page parameter lets you select which plot should from the history should be rendered. By default this is set to 0 which will use the last created plot. Set this to any number ≥ 1 to select a plot by it’s index (oldest first). Use numbers ≤ 0 to select plots 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
ugd_save(file="plot.png", page = -1) # Plot #9

dev.off() 

Note that plots can be deleted from the history the same way:

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

Instead of keeping track of the plot index, which might change when plots are added and removed, static plot IDs can be obtained.

Plot IDs

If you want to render a plot at a later point without having to keep track of its index, you can obtain its ID at any point after it’s creation.

The following example extensively demonstrates how this can be used:

ugd()

plot(rnorm(50))           # A

first_plot_id <- ugd_id() # Get last ID (A at this point)

hist(rnorm(50))           # B

plot(sin((1:100)/3))      # C

other_id <- ugd_id(-1)    # Get the second-to-last ID (B at this point)

hist(runif(100))          # D

ugd_remove(3)             # Remove 3rd plot (C)

first_again <- ugd_id(1)  # Get the first ID (A)

ugd_save(file="plot_1.png", page = first_plot_id)
ugd_save(file="plot_2.png", page = other_id)
ugd_save(file="plot_3.png", page = first_again)

dev.off() 

Note that a typical use-case would be much simpler, and just be getting the last ID after each plot by calling ugd_id() subsequently.

(Special) renderers

unigd also ships with a number of ‘special’ renderers. This guide will not go into too much detail about this topic but here are some noteworthy mentions:

  • "strings"-renderer
    • All text elements inside a plot
    • Linebreak separated plain text format
    • Could be used to e.g. ‘search’ through plots
  • "meta"-renderer
    • Meta information about the plot
    • Guaranteed to have a render time of O(1) regardless of number of objects
    • Includes complexity (number of draw calls and clipping planes)
    • JSON format
  • "json"-renderer
    • Contains all information unigd has about one plot
    • JSON format

Performance considerations

While unigd aims to provide the best performance in any case, there are some considerations you can make when optimizing graphics rendering.

At this point it should be mentioned that for most user applications readability should be prioritized over performance and, unless graphics rendering is bottlenecking your R script, you can most likely ignore this section in good conscience.

When optimizing rendering code, it is fundamental to understand in what cases unigd needs to call into the R graphics engine to let a plot be re-drawn:

Rendering is done after drawing. The last drawn dimensions of a plot are cached. We can derive a few simple rules from this:

  • Rendering the same plot in different formats: Fast.
  • Rendering the same plot in different dimensions: Slow(er).

This means ordering the rendering calls will result in faster execution:

# SLOWER:
ugd_save(file="my_plot1.png", width=600, height=400)
ugd_save(file="my_plot2.pdf", width=300, height=300) # re-draw 1
ugd_save(file="my_plot3.pdf", width=600, height=400) # re-draw 2

# FASTER:
ugd_save(file="my_plot1.png", width=600, height=400)
ugd_save(file="my_plot3.pdf", width=600, height=400)
ugd_save(file="my_plot2.pdf", width=300, height=300) # re-draw 1

And, while unigd gives you the choice of specifying your render dimension after plotting, you can hint them at device creation time to achieve the best performance:

# SLOWER:
ugd() # default dimensions: 720 * 576
# ...
ugd_save(file="my_plot1.png", width=300, height=300) # re-draw

# FASTER:
ugd(width=300, height=300)
# ...
ugd_save(file="my_plot1.png", width=300, height=300)

If the dimensions are omitted when calling rendering functions, the last known dimensions will be used and rendering is guaranteed to be fast:

ugd_save(file="my_plot1.png")

Any use of ugd_*_inline functions is also guaranteed to be fast.

Note that width and height also interact with the zoom parameter. (i.e.: Cached width = width / zoom).