+

Post-Processing grid Graphics

Paul Murrell

University of Auckland
Department of Statistics
This document is licensed under a Creative Commons Attribution 3.0 New Zealand License .

Abstract

Statistical plots drawn with the ggplot2 package generate numerous grid grobs and viewports which are labelled and organised into a coherent hierarchy. This report describes an example that shows how to manipulate the grobs and viewports in a ggplot2 plot using tools from the grid package, export the result to SVG using gridSVG, then manipulate the result further using XML tools, to produce an interactive ggplot2 graphic for the web.


Introduction
Hiearchies in grid scenes
Post-processing grid scenes
Hierarchies in gridSVG translations
Post-processing gridSVG scenes
Generating static plots
Manipulating the plots with grid and gridSVG
Manipulating the SVG with XML tools
Javascript event handlers
Summary
Version information
Bibliography

Introduction

The grid package for R provides low-level tools for drawing graphical scenes. Higher-level packages, such as lattice and ggplot2 make use of grid to draw sophisticated statistical plots.

A sophisticated statistical plot contains many low-level components, but grid provides tools that allow the low-level components to be organised so that there is a sensible structure to the components of a plot. The grid package also provides functions to access and modify the components of a scene, which allows for very low level customisation of a plot.

The gridSVG package exports a grid scene to SVG while retaining the organisation of the grid grobs and viewports in the scene. This allows the powerful set of XML tools to be brought to bear on a grid scene, which creates even greater possibilities for customising the original scene.

This report describes an example of a complex customisation of a grid scene by exporting to SVG and then post-processing using a combination of grid and XML tools. Figure 1, “An interactive ggplot2 plot” shows the image that we are aiming for. This consists of two ggplot2 plots, one (larger one) above the other. The bottom plot shows a complete time series and the top plot shows a zoomed view of a subset, or "window", of the time series. The bottom plot has a semitransparent rectangle, or "thumb", that shows which subset of the time series is currently visible in the top plot. The image is interactive because the thumb in the bottom plot can be dragged with the mouse to change the subset that is visible in the top plot.

Figure 1. An interactive ggplot2 plot. The bottom plot shows a complete time series. The top plot shows a limited window from the time series. The blue "thumb" on the bottom plot represents the window that is currently being viewed; this thumb may be dragged with the mouse to change the window.


The remainder of this introduction gives a brief outline of how to impose a structure and organisation onto a grid scene, how to modify the scene using grid , how to export the scene to SVG using gridSVG, and how to make further modifications using XML tools. This is followed by an in-depth description of how the plot in Figure 1, “An interactive ggplot2 plot” was generated.

Hiearchies in grid scenes

The basic building blocks of a grid scene are graphical objects (grobs), which describe shapes to draw, and viewports, which define subregions of the page in which to draw. These building blocks can be arranged in hierarchies to create trees of graphical objects (gTrees) and trees of viewports (vpTrees). For example, an axis on a plot could be a single parent gTree that contains several child grobs to represent the axis major line, the tick marks, and the tick labels for the axis (see Figure 2, “A grob hierarchy”).

Figure 2. A grid scene consisting of a simple axis and a graph showing how the axis could be represented by a hierarchy of graphical objects: a single parent gTree with several child grobs.


As an example of a hierarchy of viewports, consider a Trellis-like arrangement of multiple plots on a page. An overall parent viewport could be used to define the region on the page that will contain the set of plots and then several child viewports can be used to define the different subregions for the individual plots within the parent viewport (see Figure 3, “A viewport hierarchy”).

Figure 3. A grid scene consisting of multiple viewports that define regions for drawing a Trellis-like plot: a single parent viewport to hold the collection of plots with several children viewports inside.


Taken together, the vpTrees and gTrees that make up a scene can be used to represent the scene with a complex, but rational and predictable hierarchical structure. Figure 4, “A grid scene hierarchy” shows the full tree of grobs and viewports that were created to produce the diagram in Figure 3, “A viewport hierarchy”; there is a parent viewport with a bounding rectangle and text label drawn within it, then four child viewports, each with a rectangle and text label drawn inside. The gridDebug package provides functions for drawing diagrams like the ones in Figure 3, “A viewport hierarchy”, Figure 2, “A grob hierarchy”, and Figure 4, “A grid scene hierarchy”.

Figure 4. A diagram of the combined hierarchy of vpTrees and gTrees within a grid scene that consists of a parent viewport and four child viewports, with a rectangle and text grob drawn within all of the viewports.


Post-processing grid scenes

The grid package maintains a record of the grob and viewport hierarchies in the current scene and provides functions to access and modify components of the scene.

For example, the grid.ls() function can be used to display a list of the grobs in a scene; the code below shows what we would get after drawing the scene in Figure 2, “A grob hierarchy”, which consists of just a parent axis grob with major line, tick mark, and tick label child grobs.

+
grid.ls(fullNames=TRUE)
xaxis[axis]
  lines[major]
  segments[ticks]
  text[labels]

We can use the function grid.edit() to modify grobs and the function grid.remove() to remove grobs from the scene. The following code changes the tick label text to grey and removes the axis major line (see Figure 5, “A modified grid scene”). There is also the grid.get() function to access a grob and grid.set() to replace a grob.

+
grid.edit("labels", gp=gpar(col="grey"))
grid.remove("major")

Figure 5. The simple scene from Figure 2, “A grob hierarchy” with the tick labels changed to grey and the axis major line removed.


It is also possible to access (but not modify) the viewports in a grid scene. To demonstrate this, the following code draws a lattice plot, which creates a number of grid viewports in order to draw the plot. The downViewport() function is used to revisit one of the viewports that lattice generated and a semitransparent green overlay is drawn to show that we are drawing within one of the viewports that was used to draw the original plot (see Figure 6, “Revisiting a grid viewport”).

+
library(lattice)
barchart(yield ~ variety | site, data = barley,
         groups = year, layout = c(1,6), stack = TRUE,
         auto.key = list(space = "right"),
         ylab = "Barley Yield (bushels/acre)",
         scales = list(x = list(rot = 45)))
downViewport("plot_01.panel.1.3.vp")
grid.rect(gp=gpar(col=NA, fill=rgb(0,1,0,.5)))

Figure 6. A lattice plot with a semitransparent green rectangle drawn within one of the original grid viewports that was used to draw the original plot.


Hierarchies in gridSVG translations

The gridSVG package provides functions to translate a grid scene to an SVG format. This differs from the SVG format that is generated by the core svg() function because the SVG generated by gridSVG retains the hierarchies of viewports and grobs from the grid scene. This is demonstrated in Figure 7, “A gridSVG scene hierarchy”, which shows a fragment of the SVG code that is generated by gridSVG from the simple scene in Figure 2, “A grob hierarchy”. The grob hierarchy of a single parent axis that contains separate children for the axis major line and the axis ticks is reflected in the nesting of elements in the SVG code.

Figure 7. A fragment of the SVG code that is generated by the gridSVG package from the grid scene in Figure 2, “A grob hierarchy”. The grob hierarchy from the grid scene is reflected in the nesting of elements in the SVG code produced by gridSVG.

<g id="axis">
  <g id="major">
    <polyline fill="none" id="major.1" points="54,108 162,108"/>
  </g>
  <g id="ticks">
    <polyline fill="none" id="ticks.1" points="54,108 54,100.8"/>
    <polyline fill="none" id="ticks.2" points="81,108 81,100.8"/>
    <polyline fill="none" id="ticks.3" points="108,108 108,100.8"/>
    <polyline fill="none" id="ticks.4" points="135,108 135,100.8"/>
    <polyline fill="none" id="ticks.5" points="162,108 162,100.8"/>
  </g>
</g>

Post-processing gridSVG scenes

SVG is a dialect of XML, the eXtensible Markup Language; an SVG document is a special case of an XML document. There are a number of powerful tools for working with XML documents, including XPath for specifying subsets of an XML document and XSLT for transforming XML documents to other formats.

With a grid scene that has been exported to SVG via gridSVG, we have an SVG document with a sensible structure and powerful XML tools to access and manipulate the scene at least as much as we are able to do within R itself using grid functions. The code below shows some example expressions for selecting subsets of the SVG elements in Figure 7, “A gridSVG scene hierarchy”. The first example selects the SVG <g> element with an id value of "major".

[1] "//svg:g[@id = 'major']"
<g id="major">
  <polyline fill="none" id="major.1" points="126,252 378,252"/>
</g> 

The next example shows how to select just the second <polyline> element from within the <g> element with an id value of "ticks".

[1] "//svg:g[@id = 'ticks']/svg:polyline[2]"
<polyline fill="none" id="ticks.2" points="189,252 189,244.8"/> 

The final example shows how to extract all <polyline> elements, no matter which <g> element they reside within.

[1] "//svg:polyline"
[[1]]
<polyline fill="none" id="major.1" points="126,252 378,252"/> 

[[2]]
<polyline fill="none" id="ticks.1" points="126,252 126,244.8"/> 

[[3]]
<polyline fill="none" id="ticks.2" points="189,252 189,244.8"/> 

[[4]]
<polyline fill="none" id="ticks.3" points="252,252 252,244.8"/> 

[[5]]
<polyline fill="none" id="ticks.4" points="315,252 315,244.8"/> 

[[6]]
<polyline fill="none" id="ticks.5" points="378,252 378,244.8"/> 

The remainder of this report describes how Figure 1, “An interactive ggplot2 plot” was generated by taking advantage of the ability to manipulate a grid scene that was originally drawn in R to end up with a dynamic and interactive plot in an SVG format for viewing on the web.

Generating static plots

The first step towards Figure 1, “An interactive ggplot2 plot” requires generating the ggplot2 plots as standard static plots in R. The time series shown in these plots is based on the Nile data set that comes with R.

+
df <- data.frame(time=as.numeric(time(Nile)), 
                 flow=as.numeric(Nile))

The top plot is a ggplot2 line plot of the Nile data, with time on the x-axis and volume of water flow on the y-axis.

+
library(ggplot2)
+
thePlot <- ggplot(df, aes(x=time, y=flow)) + geom_line()

The bottom plot is essentially the same plot, just with the y-axis labelling adjusted to avoid having too many labels, and the axis labels "turned off". The x-axis label is left out altogether, but the y-axis label is drawn white (to match the background) so that the width of the plot region in the bottom plot is exactly the same as the width of the plot region in the top plot.

+
botPlot <- thePlot +
           scale_y_continuous(breaks=c(600, 1200)) +
           theme(plot.background=element_rect(colour="transparent"),
                 axis.title.y=element_text(colour="white", size=6),
                 axis.title.x=element_blank())

The two plots are drawn together on the same page by creating two grid viewports, one large one that consumes most of the top of the page and a smaller viewport at the bottom, and then drawing each plot in a separate viewport. This operation is captured within a function (below) because we will need to draw this scene more than once.

+
library(grid)
+
doplot <- function() {
    pushViewport(viewport(y=1, height=.8, just="top", 
                          name="topvp"))
    print(thePlot, newpage=FALSE)
    upViewport()
    pushViewport(viewport(y=0, height=.2, just="bottom", 
                          name="bottomvp"))
    print(botPlot, newpage=FALSE)
    upViewport()
}

Figure 8, “A static ggplot2 plot” shows the static image that is produced by doplot().

Figure 8. A static image composed of two ggplot2 plots. Both plots show the same complete series, just with different aspect ratios (and different axis labelling).


Manipulating the plots with grid and gridSVG

The final interactive plot in Figure 1, “An interactive ggplot2 plot” is based on a combination of two static scenes, one "normal" width version and one "wide" version. This section describes the construction of those two versions of the scene.

The normal scene has a known width and the wider scene has a width scale times as wide.

+
sceneWidth <- 7
scale <- 4

The content of the normal scene is produced using the doplot() function shown above.

+
doplot()

We now want to calculate some of the physical dimensions of this scene so that we can match the content of the normal scene and the wider scene. In particular, we want to determine how wide the margins are around the top plot in this normal scene (so that we can generate exactly the same margins around the wider scene). To do this calculation, we want to revisit the viewport for the top plot region in the normal scene and determine its width. In recent versions of ggplot2, the grobs and viewports for this scene are not visible by default, so we need to "force" them to gain access (see the section called “Version information” for more information on software versions relevant to this report).

+
grid.force()

We can now visit the top plot region, calculate its width, and then calculate the margin widths (based on the known overall width). The name of the viewport we need can be obtained by inspection of grid.ls() output or using showViewport() (see Figure 9, “A viewport diagram”).

This is an example of the usefulness of being able to explore the grid drawing context (viewports) that are generated by ggplot2.

+
downViewport("panel.3-4-3-4")
plotRegionWidth <- convertWidth(unit(1, "npc"), 
                                "inches", valueOnly=TRUE)
marginWidth <- sceneWidth - plotRegionWidth
upViewport(0)

Figure 9. A diagram of some of the viewports involved in drawing the scene in Figure 8, “A static ggplot2 plot”, as produced by showViewport().


The next step is to add a "thumb" to the bottom of the two plots in the normal scene. The steps involved are: navigate to the plot region viewport in the bottom plot; create a semitransparent blue rectangle (the width is based on the scale we are using); add an onmousedown event handler to the rectangle so that we can do something when the user clicks on this rectangle; and add the rectangle to the scene (see Figure 10, “Adding the thumb”). For now, this thumb is just a static rectangle, but in the interactive version, the user will be able to click on this thumb and slide it to change the time series window that is visible in the top scene.

This is an example of the uesfulness of being able to manipulate and modify the grobs within a grid scene.

+
downViewport("bottomvp::layout::panel.3-4-3-4")
rg <- rectGrob(x=0, width=1/scale, just="left",
               gp=gpar(col=rgb(0,0,1,.5), fill=rgb(0,0,1,.2)),
               name="thumb")
thumb <- garnishGrob(rg,
                     onmousedown="thumbDown(evt)")
grid.draw(thumb)
upViewport(0)

Figure 10. The scene in Figure 8, “A static ggplot2 plot” with a (static) rectangular "thumb" added to the bottom plot.


A final step for now is to add javascript code to the scene. the section called “Javascript event handlers” will discuss the javascript code itself; for now we are just making sure that the SVG file that we produce will contain the javascript code. We can now generate an SVG version of the normal scene using gridToSVG().

+
grid.script(filename="slider.js")
gridToSVG("normalplot.svg")

The wider scene is very straightforward by comparison. It is just the output from doplot() drawn on a wider page (then exported to SVG; see Figure 11, “A wide scene”).

+
pdf("wideplot.pdf", width=plotRegionWidth*scale + marginWidth)
doplot()
gridToSVG("wideplot.svg")
dev.off()

Figure 11. A wide version of the scene in Figure 8, “A static ggplot2 plot”.


Manipulating the SVG with XML tools

The two static scenes that we have generated so far will provide the raw materials for constructing an interactive scene. The interactive scene will be constructed by extracting pieces from the static scenes and combining them together.

The first step is just to read the two static SVG files into R.

+
normalSVG <- xmlParse("normalplot.svg")
wideSVG <- xmlParse("wideplot.svg")

One major piece of reconstruction involves replacing the top plot contents in the normal scene with the top plot contents from the wide scene. The following code identifies the top plot within the normal scene by specifying the <g> element with the relevant id attribute. This id value corresponds to the name of the grob used in the drawing of the original scene.

+
normalPlotSVG <- 
    getNodeSet(normalSVG,
               "//svg:g[@id='topvp::layout::panel.3-4-3-4.1']",
               c(svg="http://www.w3.org/2000/svg"))[[1]]

The top plot in the wide scene is accessed with similar code. The main difference is that we identify the <g> element that is the child of the <g> element with the relevant id. What we are doing is identifying a parent element in the normal scene for which we can provide a new child element from the wide scene.

+
widePlotSVG <- 
    getNodeSet(wideSVG,
               "//svg:g[@id='topvp::layout::panel.3-4-3-4.1']/svg:g[@id='panel.3-4-3-4']",
               c(svg="http://www.w3.org/2000/svg"))[[1]]

Now we remove the <g> element child of the top plot in the normal scene and add a new child, which is the top plot from the wide scene. This is an example of the power of XML tools for manipulating SVG content. It also relies on the fact the the SVG content has retained the coherent structure from the original grid scene.

+
removeChildren(normalPlotSVG, "g")
addChildren(normalPlotSVG, widePlotSVG)

A very similar series of steps is followed to replace the original x-axis in the top plot of the normal scene with a wider x-axis from the top plot of the wide scene.

+
normalAxisSVG <-
    getNodeSet(normalSVG,
               "//svg:g[contains(@id, 'topvp::layout::axis-b.4-4-4-4')]",
               c(svg="http://www.w3.org/2000/svg"))[[1]]
wideAxisSVG <-
    getNodeSet(wideSVG,
               "//svg:g[contains(@id, 'topvp::layout::axis-b.4-4-4-4')]/child::*",
               c(svg="http://www.w3.org/2000/svg"))[[1]]
removeChildren(normalAxisSVG, "g")
addChildren(normalAxisSVG, wideAxisSVG)

At this point, the new scene consists of the bottom plot, the y-axis and x-axis label from the top plot of the normal scene, plus the contents and x-axis from the top plot of the wide scene, which extend far to the right of the scene (see Figure 12, “A mixture of scenes”).

Figure 12. A combination of the bottom plot and y-axis from the normal scene and the top plot content and x-axis from the wide scene.


The final step is to add some more event handling to the scene so that movement of the mouse (with the mouse button over the thumb and the mouse button down) will result in movement of the thumb and changes to the time series window that is shown in the top plot.

We do this by setting onmouseup and onmousemove attributes on the root <svg> element of the scene. The final step is to save the interactive scene to a file and this produces the final plot shown in Figure 1, “An interactive ggplot2 plot” (reproduced in Figure 13, “An interactive ggplot2 plot” for convenience).

+
addAttributes(getNodeSet(normalSVG, "/svg:svg",
                         c(svg="http://www.w3.org/2000/svg"))[[1]],
              onmouseup="mUp(evt)",
              onmousemove=paste("mMove(evt, ", scale, ")", sep=""))

saveXML(normalSVG, file="sliderplot.svg")

Figure 13. An interactive ggplot2 plot. This is a copy of Figure 1, “An interactive ggplot2 plot” produced here for convenience.


The scene in Figure 13, “An interactive ggplot2 plot” is not quite exactly the same as the one in Figure 1, “An interactive ggplot2 plot” because the axis ticks are not clipped to the width of the top plot. This clipping can be set up using the same ideas of extracting information from the static scenes and using it to modify the interactive scene (see the section called “Version information” for a link to the complete R code).

Javascript event handlers

The R code described so far has set up the static components of the final scene and the starting positions for the dynamic components of the final scene. For the scene to change in response to user input, we rely on javascript code.

This code consists of three main parts: what to do when the user clicks the mouse button down (within the thumb); what to do when the user lifts the mouse button up again; and what to do when the user moves the mouse (with the mouse button down).

The first two parts are simple: we just keep a global variable indicating whether the mouse is down (within the thumb) or not plus a record of the location where the mouse was first clicked down.

function thumbDown(evt) {
    movingThumb = true;
    startx = getMouseX(evt);
}

function mUp(evt) {
    if (movingThumb) {
        movingThumb = false;
        xoffset = savedoffset;
    }
}

The hard work is done when the mouse moves, but this is still not particularly complex. The basic idea is to determine how far the mouse has moved, calculate a transformation to apply, and then apply that transformation to the thumb. Something similar must be done for the top plot contents to change the time series window that is visible, with the difference being just one of scale and direction (when the thumb slides to the right, the contents of the top plot slide, faster, to the left).

function mMove(evt, scale) {
    if (movingThumb) {
        var newX = getMouseX(evt);
        var target = document.getElementById("thumb");
        var xshift = newX - startx;
        var thumbx = xoffset + xshift;
        if (moveOK(thumbx)) {
            /* Move the thumb */
            var transMatrix = [1,0,0,1,0,0];
            transMatrix[4] = thumbx;
            transform = "matrix(" +  transMatrix.join(' ') + ")";
            target.setAttributeNS(null, "transform", transform);
            /* Move the top plot (the gTree NOT the viewport) */
            var plot = document.getElementById("panel.3-4-3-4");
            transMatrix = [1,0,0,1,0,0];
            transMatrix[4] = -1*scale*thumbx;
            transform = "matrix(" +  transMatrix.join(' ') + ")";
            plot.setAttributeNS(null, "transform", transform);
            /* Move the top axis (the gTree NOT the viewport) */
            var axis = document.getElementById("axis-b.4-4-4-4");
            axis.setAttributeNS(null, "transform", transform);
            savedoffset = thumbx;
        }
    }
}

There are some other details involved, such as code to ensure that the thumb cannot slide off the edges of the bottom plot, but the three functions above constitute the main functionality (see the section called “Version information” for a link to the full javascript code).

Summary

This report describes the development of an interactive plot for viewing in a web browser that is constructed by post-processing a scene composed from standard static ggplot2 plots. The construction of the plot depends heavily on several important features of several R packages:

  • a ggplot2 plot is composed of many named grid grobs and viewports that are organised into a coherent hierarchy.
  • grid provides tools for manipulating the hierarchy of grobs and viewports in a scene.
  • The gridSVG package exports a grid scene to SVG with the names and organisation of grobs and viewports intact.
  • The XML package provides tools for manipulating SVG content.

The interactive scene described in this report demonstrates that these tools can be combined to perform quite sophisticated post-processing on a grid scene to produce interesting and useful interactive graphics for the web.

Version information

A complete copy of the R code that produces Figure 1, “An interactive ggplot2 plot” is available here. The javascript code is here.

This code is known to run in R 2.16.0 using a fork of gtable and the R2.16 branch of the gridSVG package on R-Forge.

It is anticipated that the code will work with the main version of gridSVG from around April 2013 (following the public release of R version 2.16.0). At the time of writing, it was unclear whether the gtable fork would be merged into any official release.

Bibliography

[R] R Team and . “ R : A Language and Environment for Statistical Computing ”. 2011. ISBN 3-900051-07-0.

[gridsvg] Paul Murrell and Simon Potter. “ gridSVG : Export grid graphics as SVG ”. 2012. R package version 1.1-0.

[griddebug] Paul Murrell and Velvet Ly.. “ gridDebug : Debugging Grid Graphics ”. 2012. R package version 0.5-0.

[ggplot2] Hadley Wickham. “ ggplot2 : elegant graphics for data analysis ”. 2009.

[XML] Duncan Lang. “ XML : Tools for parsing and generating XML within R and S-Plus. ”. 2012. R package version 3.9-4.

[lattice] Deepayan Sarkar. “ Lattice : Multivariate Data Visualization with R ”. 2008. ISBN 978-0-387-75968-5.

[gtable] Hadley Wickham. “ gtable : Arrange grobs in tables. ”. 2012. R package version 0.1.1.99.

[W3CXML] , Tim Bray, Jean Paoli, C Sperberg-McQueen, Eve Maler, and François Yergeau. “ Extensible Markup Language (XML) 1.0 ”. 2006. http://www.w3.org/TR/2006/REC-xml-20060816/.

[XPath] , James Clark, and Steve DeRose. “ XML Path Language (XPath) 1.0 ”. 1999. http://www.w3.org/TR/1999/REC-xpath-19991116.

[XLST] and James Clark. “ XSL Transformations (XSLT) 1.0 ”. 1999. http://www.w3.org/TR/1999/REC-xslt-19991116.

[SVG] , Erik Dahlstrom, Patrick Dengler, Anthony Grasso, Chris Lilley, Cameron McCormack, Doug Schepers, Jonathon Watt, Jon Ferraiolo, Jun Fujisawa, and Dean Jackson. “ Scalable Vector Graphics (SVG) 1.1 ”. 2011. http://www.w3.org/TR/2011/REC-SVG11-20110816/.


dynDoc("ggplotSlider.xml", "HTML", force = TRUE, xslParams = c(html.stylesheet = "http://stattech.wordpress.fos.auckland.ac.nz/wp-content/themes/twentyeleven/style.css customStyle.css", 
    base.dir = "HTML", generate.toc = "article toc"))
Tue Dec 11 09:33:54 2012