Saving loon plots

R.W. Oldford

April 20, 2020


Interactive analysis and static plots

When carrying out an interactive analysis, you might want to save some pictures to include in any later report. You might also want to save the state of some interactive plot so that you can pause your analysis, quit R, and later return to your analysis where you left off.

To illustrate how to do this, consider the classic data set iris. Here are three different linked plots that might be displayed.

library(loon)
# First, make sure you always assign a loon plot to a variable
# at the same time that you create it.  This will give you access
# to it later.  
# (NB: If you haven't assigned it, you can later using l_getFromPath(). )
# 
#  A histogram, the name for the linkingGroup is arbitrary
h <- l_hist(iris$Sepal.Length, color = "grey", xlabel = "Sepal length",
            linkingGroup = "flowers")
#
# A scatterplot
p <- l_plot(iris$Petal.Width, iris$Petal.Length,  
            color = "grey", size = 10,
            xlabel = "Petal Width", ylabel = "Petal Length",
            linkingGroup = "flowers")
#
# A serial axes plot using the first 4 columns of iris (i.e. no Species info).
sa <- l_serialaxes(iris[, 1:4],   
            color = "grey", linkingGroup = "flowers")
#
# A static version of the plot can be easily produced.
plot(h)

That plot will simply just appear (as it is) in the processed RMarkdown file.

If you have the grid and gridExtra packages loaded, you could arrange to see several such plots in one place.

library(grid); library(gridExtra)
# First get the data stuctures corresponding to the static snapshots
# of the current plots.  Because we want to arrange them, we don't
# draw them at first.
plot_h <- plot(h, draw = FALSE)
plot_p <- plot(p, draw = FALSE)
plot_sa <- plot(sa, draw = FALSE)
# Now we draw them
grid.arrange(plot_h, plot_p, plot_sa, nrow = 1)

When interaction is only programmatic

This works fine when what you want to display has been entirely determined by programming done inside the RMarkdown code. For example, if the colours had been changed programmatically, the linked plots would change and plotting them again would reflect that change.

# Change the colour by changing it *programmatically* on the plot
# 
h["color"] <- iris$Species  # use species to determine the colours
plot_h <- plot(h, draw = FALSE)
plot_p <- plot(p, draw = FALSE)
plot_sa <- plot(sa, draw = FALSE)
# And draw them again
grid.arrange(plot_h, plot_p, plot_sa, nrow = 1)

There are a couple of things to note here.

First, the plots had to be redrawn because they are static snapshots reflecting what the interactive plot looked like at the time the snapshot was taken using plot().

Second, all linked plots changed when the colour was only directly changed on the histogram.

Finally, there are numerous states that can be programmatically accessed and set on an interactive plot. These states can be found by calling names() on the plot in question.

Knowing the state names provides the user a programmatic means to access and/or to set their values. The values can be queried at any time, whether they were set programmatically or interactively. For example,

# Accessing values
head(sa["color"])  # will show the current colours of sa
# Note that these colours are expressed as 12 hexadecimal digits 
# This is what is used in TCL.  To turn them into R's 6 digit hexadecimal 
# (to use in other R static plots, for example) just use hex12tohex6()
hex12tohex6(sa["color"][1:3])
# Setting values ... make only specied iris versicolor active
sa["active"] <- iris$Species == "versicolor"
plot(sa)
# Setting values ... make everything active
sa["active"] <- TRUE
plot(sa)

A problem with doing things interactively is that any direct manipulation of the plots using the inspector or brushing, panning, zooming, etc. is not being recorded.

If you want to have the values of states (which were set interactively) to appear later in a report, these values will need to be recorded somewhere first.

It will be necessary to save the state somehow, either as a snapshot or as data in a separate file to be recovered later (saving plot states like colours, selections, etc.).

In either case, information must be stored so that an RMarkdown file can read it back in and adjust the plots programmatically.

Saving shapshots for later display.

When you are doing things interactively, you may just want to save a current version of the plot, effectively a snapshot, to a file. Then, after you have completed your analysis you can read those back in.

You would save the current picture in a variety of ways.

Export from RStudio

Simply plot(p) and select the Export button above the static plot. Choose a file name and location for your plot.

Because plot() actually turns the loon plot into a grid graphic, the picture saves will be close but not necessarily exactly as it appears on screen. Typical differences include font sizes and names.

Use l_export()

This is a loon function to export a loon plot to a file. On the plus side, what you see is what you get. The loon plot saved to the file will be the same as it appears on the display.

Trouble is, depending on your OS, you might not be able to save the picture as any type of image. For example, pdf images will be available to nearly all OSes, png will not. You do need to specify the file suffix:

l_export(p, filename = "myplot_via_l_export.pdf", height = 500, width = 600)

Use R’s png() device

In R, there are a variety of graphics “devices”. These identify where R will draw a static plot. The call dev.cur() will tell you what the current device is.

One such device is png() which allows R plots to be drawn as a png graphic. It would work like this

# turn the png graphics device on with name of the file
png(file = "myplot_via_R.png", width = 600, height = 500)
# Draw the static plot
plot(p)
# Turn the device off/close the file
dev.off()

Including saved graphics in your report

Having saved various snapshots to various files, you can now include them in your Rmarkdown file using the include_graphics() function from knitr.

Of course, this means that the various files mentioned above were previously saved (not created in the markdown file) and so can now be read into your RMarkdown report as follows:

# The one exported from RStudio
knitr::include_graphics("myplot_via_RStudio.png")
# Followed by the one saved using l_export 
# (note that background grid is missing)
knitr::include_graphics("myplot_via_l_export.pdf")
# And finally, the one saved using R's png device
knitr::include_graphics("myplot_via_R.png")

Alternatively, you could use LaTeX if you want more control. For example, the following LaTeX code would centre the three plots in each column of a tabular lay out:

\begin{center}
  \begin{tabular} {ccc}
    \includegraphics[height = 0.15\textheight]{myplot_via_RStudio.png} & 
    \includegraphics[height = 0.15\textheight]{myplot_via_l_export.pdf} &
    \includegraphics[height = 0.15\textheight]{myplot_via_R.png} 
    \\
    {\scriptsize The one exported from RStudio} & 
    {\scriptsize The one saved using \code{l\_export}} & 
    {\scriptsize The one saved using R's \code{png} device}
  \end{tabular}
\end{center}

Reconstruct the plot from saved states

loon also provides some functionality to save the current states of a loon plot. As with saving snapshots, this would be done at any time during the interactive analysis. Then later, when it is time to write the report, the saved states of the plot could be read back in and the plot reconstructed programmatically to be displayed in the report using plot() as above.

The use model is

  1. Use l_saveStates() to save the current states of the loon plot to a file (repeat as often as desirable, saving to a different file each time).
  2. Later, use l_getStates() to recover the saved states from the named file/
  3. Construct a new loon plot of the desired type.
  4. Copy the saved states to the new plot using l_copyStates() (alternatively, assign selected saved states of the new plot using $).
  5. Produce a static picture of the new loon plot using plot() as above.

Note that only the first step happens in the interactive part of the analysis. Steps 2 through 5 programmatically recover the plot and its picture for incorporation in a report.

Of course in place of step 5, one could use steps 2 through 4 to restart an interactive analysis where one left off.

A simple example

A typical use will be simply recording the plot after some interaction with the display, usually involving linking between two displays, say the scatterplot and histogram above.

We save the states of the plot p in a file as

l_saveStates(p, file = "p_savedStates")

Later, when we construct a new plot (perhaps for a report), the states are recovered from file

p_savedStates <- l_getSavedStates(file = "p_savedStates")

A new plot is constructed and the saved states transferred to it.

new_p <- l_plot(iris$Petal.Width, iris$Petal.Length)
l_copyStates(source = p_savedStates, target = new_p)

and can be programmatically incorporated into a report as

plot(new_p)

Again, alternatively new_p could simply be a reconstruction so as to continue an interactive analysis from that point.

A more complex example

Again, by default, only the following states related to linking are saved

names(p_savedStates)
## [1] "color"        "active"       "selected"     "linkingKey"   "linkingGroup"

[1] "color" "active" "selected" "linkingKey" "linkingGroup"

Sometimes, the interactive analysis will have been more involved. It could, for example, have involved panning and zooming, changing of glyphs, particular choices for labels and titles, and so on.

For example, through direct manipulation of the loon plot and inspector, the scatterplot p might at some point look something like this:

Here, the analyst zoomed in on the species “versicolor”, jittered the locations of the points, and changed some of the glyphs to be large closed triangles. This involves many more states, all of which must be saved to reproduce the plot.

The states to be saved can be named explicitly to be most efficient in storage, providing of course one knows which are the states that need to be saved.

All of the states of a loon plot p are given by names(p). At the risk of saving more information than necessary, we could choose to save all of the states of p, as in

l_saveStates(p, 
             states = names(p), 
             file = "p_focusOnVersicolor")

Now all of the states are available when this file is read back in.

p_focusOnVersicolor <- l_getSavedStates(file = "p_focusOnVersicolor")

When it comes to transferring the states to a new plot there are many choices. We could go with a large collection of display states (more than the default states but still excluding more “basic” states about panning and zooming for example). To see which states, we add the argument returnNames = TRUE:

l_copyStates(source = p_focusOnVersicolor, 
             target = new_p, 
             returnNames = TRUE)

This excluded “basic” states through the argument excludeBasicStates whose default value is TRUE.

More, or different, states could be excluded by using the argument exclude to list them in a vector. Alternatively, the only states to be transferred could be made explicit by naming them in the value of the argument states.

Because the “basic” states were excluded by default, the new plot now looks like

plot(new_p)

As can be seen, the glyph sizes and shapes were transferred but not the zooming or the jittering. To ensure we get an exact copy, we need to include all of the states, that is to exclude none of them.

l_copyStates(source = p_focusOnVersicolor, 
             target = new_p, 
             excludeBasicStates = FALSE)

Now the new_p should look much like the original interactive plot did when it was saved.

plot(new_p)

which it does.