% !TeX root = RJwrapper.tex \title{The \pkg{gridGraphics} Package} \author{by Paul Murrell} \setlength\fboxsep{0pt} \maketitle \abstract{The \pkg{gridGraphics} package provides a function, \code{grid.echo()}, that can be used to convert a plot drawn with the \pkg{graphics} package to a visually identical plot drawn using \pkg{grid}. This conversion provides access to a variety of \pkg{grid} tools for making customisations and additions to the plot that are not possible with the \pkg{graphics} package. } \begin{center} {\it Updated \Sexpr{Sys.Date()}} \end{center} \section{Introduction} The core graphics system in R is divided into two main branches, one based on the \pkg{graphics} package and one based on the \pkg{grid} package, with many other packages building on top of one or other of these graphics systems (see Figure \ref{fig:system-graph}). The \pkg{graphics} package is older and provides an emulation of the original GRZ graphics system from S \citep{grz}. The newer \pkg{grid} package, although its performance is actually slower, provides greater flexibility and additional features compared to the \pkg{graphics} package. In particular, a plot drawn with \pkg{grid} can be manipulated and edited in many more ways than a plot drawn with the \pkg{graphics} package. This article describes a new package, called \pkg{gridGraphics}, that allows a plot drawn with \pkg{graphics} to be converted into an identical plot drawn with \pkg{grid}, thereby allowing the plot to be manipulated using all of the tools available in \pkg{grid}. <>= library(gridGraphviz) nodes <- c("grDevices", "grid", "graphics", "lattice", "ggplot2", "othergrid", "plotrix", "maps", "othergraphics", "SVG", "PNG", "PDF") nn <- length(nodes) labels <- nodes labels[grepl("other", nodes)] <- "..." names(labels) <- nodes fills <- rep("white", nn) fills[nodes %in% c("graphics", "grid")] <- "grey" names(fills) <- nodes edgeList <- list(grDevices=list(edges=c("PDF", "PNG", "SVG")), graphics=list(edges="grDevices"), grid=list(edges="grDevices"), plotrix=list(edges="graphics"), maps=list(edges="graphics"), othergraphics=list(edges="graphics"), lattice=list(edges="grid"), ggplot2=list(edges="grid"), othergrid=list(edges="grid"), PDF=list(), PNG=list(), SVG=list()) gnel <- new("graphNEL", nodes=nodes, edgeL=edgeList, edgemode="directed") rag <- agopenTrue(gnel, "system-graph", attrs=list(node=list(shape="ellipse")), nodeAttrs=list(label=labels, fillcolor=fills)) for (i in seq(along = AgEdge(rag))) { AgEdge(rag)[[i]]@arrowhead <- "normal" } pdf("murrell-system-graph.pdf", width=graphWidth(rag), height=graphHeight(rag)) grid.graph(rag) dev.off() @ \begin{figure}[h] \centering \includegraphics[width=.6\textwidth]{murrell-system-graph} \caption{The structure of the core graphics system in R. The \CRANpkg{lattice} package \citep{lattice}, the \CRANpkg{ggplot2} package \citep{ggplot} and many others are built on top of the \pkg{grid} package; the \CRANpkg{plotrix} package \citep{plotrix}, the \CRANpkg{maps} package \citep{maps} and \emph{many} others are built on top of the \pkg{graphics} package.} \label{fig:system-graph} \end{figure} \section{The \code{grid.echo()} function} The \pkg{gridGraphics} package provides a single main function called \code{grid.echo()}. By default, this function takes whatever has been drawn by the \pkg{graphics} package on the current graphics device and redraws it using \pkg{grid}. The following code provides a simple demonstration. We first draw a scatterplot using \code{plot()} from the \pkg{graphics} package, then we call \code{grid.echo()} to replicate the plot with \pkg{grid}. Figure \ref{fig:echo} shows that the original plot and the replicated plot are identical. <>= plot(mpg ~ disp, mtcars, pch=16) @ <<>>= library(gridGraphics) @ <>= grid.echo() @ <>= f <- function() { <> } grid.echo(f) @ \begin{figure}[h] \centering \fcolorbox{lightgray}{white}{\includegraphics[width=.46\textwidth]{murrell-plot}} \fcolorbox{lightgray}{white}{\includegraphics[width=.46\textwidth]{murrell-echo}} \caption{On the left, a scatterplot drawn with \pkg{graphics} package and, on the right, the result of \code{grid.echo()}, which produces the same scatterplot using the \pkg{grid} package.} \label{fig:echo} \end{figure} The following sections will attempt to demonstrate why, although the plots appear identical to the eye, there are important advantages that arise from using \pkg{grid} to do the drawing. \section{Manipulating grobs} One advantage of drawing the plot with \pkg{grid} is that there is an object, a \pkg{grid} \dfn{grob}, recorded for each separate component of the plot that we have drawn. We can see that list of grobs with a call to the \code{grid.ls()} function, as shown below. There is a grob called \code{graphics-plot-1-points-1} that represents the data symbols in the plot, there is a grob called \code{graphics-plot-1-xlab-1} that represents the x-axis label, and so on. <>= grid.ls() @ <>= <> <> grid.ls() @ The \pkg{grid} package provides several functions to manipulate these grobs. For example, the code below uses the \code{grid.edit()} function to rotate the tick labels on the x-axis of the plot to 45 degrees (and turns them red so that the change is easy to spot; see Figure \ref{fig:editplot}). This is a simple example of a customisation that is impossible or very difficult in the \pkg{graphics} package, but is quite straightforward once the plot has been converted to \pkg{grid}. <>= grid.edit("graphics-plot-1-bottom-axis-labels-1", rot=45, gp=gpar(col="red")) @ <>= pdf("murrell-editplot-%d.pdf", width=5, height=5, onefile=FALSE) dev.control("enable") <> <> <> dev.off() @ \begin{figure}[h] \centering \fcolorbox{lightgray}{white}{\includegraphics[width=.46\textwidth]{murrell-editplot-3}} \caption{A scatterplot that was drawn using \code{plot()} from \pkg{graphics}, then redrawn using \pkg{grid} via the \code{grid.echo()} function, then edited using \code{grid.edit()}.} \label{fig:editplot} \end{figure} To provide a more sophisticated example, consider the conditioning plot produced by the following code with a call to the \code{coplot()} function from the \pkg{graphics} package (see the left-hand plot in Figure \ref{fig:coplot}). <>= coplot(lat ~ long | depth, quakes, pch=16, cex=.5, given.values=rbind(c(0, 400), c(300, 700))) @ This is an example of a much more complex plot with many different components. Functions that produce this sort of complex plot can struggle to provide arguments to fine tune all possible elements of the plot. For example, suppose that we want to modify the ``conditioning panel'' at the top of the plot so that the background is a solid colour and the bars are filled in white. This specific task is probably not a common one for most people, but the point of this example is to represent a class of problems where a small detail within a complex plot needs to be modified. The \code{coplot()} function does have an argument \code{bar.bg} to control the fill colour for the bars, as demonstrated in the code below (see the right-hand plot in Figure \ref{fig:coplot}). However, there is no argument that allows us to control the background colour for the panel behind the bars. <>= coplot(lat ~ long | depth, quakes, pch=16, cex=.5, given.values=rbind(c(0, 400), c(300, 700)), bar.bg=c(num="white")) @ \begin{figure}[h] \centering \fcolorbox{lightgray}{white}{\includegraphics[width=.46\textwidth]{murrell-coplot0}} \fcolorbox{lightgray}{white}{\includegraphics[width=.46\textwidth]{murrell-coplot}} \caption{A conditioning plot produced by the \code{coplot()} function from the \pkg{graphics} package. The right-hand version of the plot demonstrates the use of the \code{bar.bg} argument to customise the fill colour of the bars in the conditioning panel at the top of the plot.} \label{fig:coplot} \end{figure} If we replicate this plot using \pkg{grid}, we have more tools available to be able to manipulate the plot. The \code{grid.echo()} function can replicate this plot and gives an identical result to that shown in Figure \ref{fig:coplot}. The call to \code{grid.echo()} shown below also demonstrates the use of the \code{prefix} argument, which can be used to control the naming of the grobs that \code{grid.echo()} draws. The grobs created by this call to \code{grid.echo()} all have names that start with \code{"cp"} rather than the default \code{"graphics"} prefix. <>= grid.echo(prefix="cp") @ Once this conversion has taken place, we now have \pkg{grid} grobs that represent all components of the plot. In particular, there is a grob called \code{"cp-plot-4-box-1"} that draws the border around the conditioning panel. We can edit that grob to give it a fill colour (see the left-hand plot in Figure \ref{fig:coplot-edit}). <>= grid.edit("cp-plot-4-box-1", gp=gpar(fill="red")) @ <>= pdf("murrell-coplot-edit-%d.pdf", onefile=FALSE) dev.control("enable") <> <> <> dev.off() @ This provides another demonstration that converting a plot to \pkg{grid} provides access to all of the components of the plot, which allows fine control over details of the plot that cannot be controlled via the arguments to the original high-level function that created the plot. In this particular example, we have also created a new problem, because the border on the conditioning plot is drawn \emph{after} the bars, so the bars are now obscured. Fortunately, we can fix this as well with further tools that \pkg{grid} provides for manipulating grobs. In the following code, we call the \code{grid.grab()} function to create a single grob (a \dfn{gTree}) that contains all of the other grobs on the page. We then call the \code{grid.reorder()} function to change the order of the grobs within the gTree. The code specifies that the border grob will be drawn first (behind all other components in the plot). Finally, we redraw the reordered plot with the \code{grid.draw()} function to get the final result that we were after (see the right-hand plot in Figure \ref{fig:coplot-edit}). <>= gt <- grid.grab() gt <- reorderGrob(gt, "cp-plot-4-box-1") grid.newpage() grid.draw(gt) @ <>= pdf("murrell-coplot-reorder-%d.pdf", onefile=FALSE) dev.control("enable") <> <> <> <> dev.off() @ \begin{figure}[h] \centering \fcolorbox{lightgray}{white}{\includegraphics[width=.46\textwidth]{murrell-coplot-edit-3}} \fcolorbox{lightgray}{white}{\includegraphics[width=.46\textwidth]{murrell-coplot-reorder-5}} \caption{On the left, the plot from Figure \ref{fig:coplot} after conversion with \code{grid.echo()}, followed by editing to fill the rectangle that draws a border around the top conditioning panel (in red so that the change is visible). On the right, the edited plot has been reordered so that the border around the conditioning panel is drawn first (behind everything else).} \label{fig:coplot-edit} \end{figure} This access to individual components of a plot and the ability to manipulate those components is one benefit of converting a \pkg{graphics} plot to \pkg{grid}. \section{Making use of viewports} Another advantage of using \pkg{grid} is that we can make use of \dfn{viewports}. Viewports are similar to the different plotting regions that the \pkg{graphics} package uses for drawing (see Figure \ref{fig:regions}), but in a \pkg{grid} plot there can be an unlimited number of viewports and all viewports are accessible at any time. In the \pkg{graphics} package there is only the current plot region, figure margins, and outer margins to work with. <>= par(oma=rep(3, 4)) plot.new() # set clipping to figure region par(xpd=TRUE) # deliberately draw a stupidly large rectangle rect(-1, -1, 2, 2, col="gray80") box("figure", lty="dashed") # set clipping back to plot region par(xpd=FALSE) # deliberately draw a stupidly large rectangle rect(-1, -1, 2, 2, col="white") box("plot") text(.5, .5, "Plot Region") for (i in 1:4) { mtext(paste("Figure margin", i), side=i, line=1) mtext(paste("Outer margin", i), side=i, line=1, outer=TRUE) } @ \begin{figure}[h] \centering \fcolorbox{lightgray}{white}{\includegraphics[width=.46\textwidth]{murrell-regions}} \caption{The plotting regions involved in the drawing of a \pkg{graphics} plot.} \label{fig:regions} \end{figure} Figure \ref{fig:viewports} shows a diagram of the hierarchy of viewports that \code{grid.echo()} created when we replicated the simple scatterplot in Figure \ref{fig:echo}. This shows that \pkg{gridGraphics} produces quite a lot of viewports (even for a simple plot), but there is a coherent structure to the viewports, so the complexity can be navigated without too much difficulty. In general, the names of the viewports reflect the plot regions that they mimic in the original plot. At the top of the hierarchy of viewports is a viewport called \code{ROOT}, which represents the entire page (this viewport is always present). Below that is a viewport called \code{graphics-root} and that represents the area of the page that \code{grid.echo()} has drawn into (by default, also the whole page). The next viewport down is called \code{graphics-inner} and this represents the region that is the whole page minus the outer margins. Below that are two viewports, \code{graphics-figure-1} and \code{graphics-figure-1-clip}, both of which correspond to the figure region (the grey area in Figure \ref{fig:regions}). There are two viewports because one has clipping turned on, so that drawing within that region cannot extend beyond the boundaries of the region, and one has clipping turned off. Below each of the figure region viewports are one or more viewports representing the plot region, called either \code{graphics-plot-1} or \code{graphics-plot-1-clip}. Again, the difference between these two plot viewports is whether clipping is on or off. By having the \code{graphics-plot-1} viewport beneath both figure viewports, we can represent all possible values of the \code{par("xpd")} settings: clipping to the figure region, or clipping to the plot region, or no clipping at all. The bottom layer of viewports represent the plot region again, but this time with a viewport that has scales to represent the plot axes. The reason for this additional layer is so that we can reproduce plots that make use of more than one set of axes (e.g., two different y-axis scales). <>= # Function to prettify the output of current.vpTree() formatVPTree <- function(x, indent=0) { end <- regexpr("[)]+,?", x) sibling <- regexpr(", ", x) child <- regexpr("[(]", x) if ((end < child || child < 0) && (end < sibling || sibling < 0)) { lastchar <- end + attr(end, "match.length") cat(paste0(paste(rep(" ", indent), collapse=""), substr(x, 1, end - 1), "\n")) if (lastchar < nchar(x)) { formatVPTree(substring(x, lastchar + 1), indent - attr(end, "match.length") + 1) } } if (child > 0 && (sibling < 0 || child < sibling)) { cat(paste0(paste(rep(" ", indent), collapse=""), substr(x, 1, child - 3), "\n")) formatVPTree(substring(x, child + 1), indent + 1) } if (sibling > 0 && sibling < end && (child < 0 || sibling < child)) { cat(paste0(paste(rep(" ", indent), collapse=""), substr(x, 1, sibling - 1), "\n")) formatVPTree(substring(x, sibling + 2), indent) } } <> <> formatVPTree(current.vpTree()) @ <>= library(gridDebug) pdf("murrell-vptree-%d.pdf", width=17, height=7, onefile=FALSE) <> <> rag <- gridTree(viewports=TRUE, grobs=FALSE, grid=TRUE, vpNodeAttrs=list(shape="box", fontsize=20), split=FALSE) for (i in seq(along = AgEdge(rag))) { AgEdge(rag)[[i]]@arrowhead <- "normal" } grid.graph(rag) dev.off() @ \begin{widefigure}[h] \centering \includegraphics[width=\textwidth]{murrell-vptree-3} \caption{A diagram of the viewports that were created by the \code{grid.echo()} function when it drew the plot in Figure \ref{fig:echo}.} \label{fig:viewports} \end{widefigure} The upside to having so many viewports is that the \pkg{grid} package provides functions to navigate between viewports. So we can have a lot of viewports on the page at once, but switch between them if we want to add drawing within different viewports. As an example, the following code uses the \code{downViewport()} function to revisit the plot region viewport that was created by \code{grid.echo()} and draws a red rectangle around the border (see Figure \ref{fig:box}). The \code{upViewport()} function is then used to take us back to the whole page (the \code{ROOT} viewport). <>= downViewport("graphics-plot-1") grid.rect(gp=gpar(col="red", lwd=3)) upViewport(0) @ <>= pdf("murrell-box-%d.pdf", width=5, height=5, onefile=FALSE) <> <> <> dev.off() @ \begin{figure}[h] \centering \fcolorbox{lightgray}{white}{\includegraphics[width=.46\textwidth]{murrell-box-2}} \caption{The echoed plot from Figure \ref{fig:echo} with a rectangle added by revisiting the viewport that corresponds to the plot region.} \label{fig:box} \end{figure} Once again, a more sophisticated demonstration can be provided if we consider the more complex conditioning plot from Figure \ref{fig:coplot}. Another limitation of the \code{coplot()} function, because it is based on the \pkg{graphics} package, is that there is no way to add further drawing to the conditioning panel at the top of the plot. This plot has several different panels so the replication created by \code{grid.echo()} generates many different viewports, including viewports used to draw the conditioning panel at the top of the plot. With \pkg{grid}, all of these viewports can be revisited after the plot has been drawn. In the following code, we revisit the viewport used to draw the conditioning panel and draw some grid lines in it. Those new lines will be drawn on top of everything else, so additional manipulations, similar to the reordering performed for Figure \ref{fig:coplot-edit}, can also be carried out to push the segments behind everything else (code not shown). The result is shown in Figure \ref{fig:coplot-grid}. <>= downViewport("cp-window-4-1") v <- unit(seq(0, 700, 100), "native") grid.segments(v, 0, v, 1, gp=gpar(col="red"), name="grid") upViewport(0) @ <>= gt <- grid.grab() gt <- reorderGrob(gt, "grid") grid.newpage() grid.draw(gt) @ <>= pdf("murrell-coplot-vp-reorder-%d.pdf", onefile=FALSE) dev.control("enable") <> <> <> <> dev.off() @ \begin{figure}[h] \centering \fcolorbox{lightgray}{white}{\includegraphics[width=.46\textwidth]{murrell-coplot-vp-reorder-4}} \caption{The conditioning plot from Figure \ref{fig:coplot} with a reference grid added (in red so that the change is visible) to the conditioning panel at the top of the plot.} \label{fig:coplot-grid} \end{figure} Another limitation of the original \code{coplot()} function is that it insists on occupying the entire page. Another advantage of working with \pkg{grid} grobs and viewports is that they can be nested within each other to any level. This means that once the output from \code{coplot()} has been replicated as \pkg{grid} output, it can be drawn within a \pkg{grid} viewport and combined on a page with other plots. The following code creates a \pkg{grid} viewport occupying the bottom 70\% of the page and then replicates the conditioning plot only using that part of the page. This code demonstrates another way to call the \code{grid.echo()} function. Rather than calling the \pkg{graphics} function \code{coplot()} to draw a plot and then calling \code{grid.echo()} to replicate it, we can define a function (with no arguments) that draws the plot and then provide that function as the first argument to \code{grid.echo()}. This way the plot is only drawn once, using \pkg{grid}. We also specify \code{newpage=FALSE} in the call to \code{grid.echo()} so that it just draws in the current viewport rather than starting a new page. <<>>= cpfun <- function() { coplot(lat ~ long | depth, quakes, pch=16, cex=.5, given.values=rbind(c(0, 400), c(300, 700))) } @ <>= grid.newpage() pushViewport(viewport(y=0, height=.7, just="bottom")) grid.echo(cpfun, newpage=FALSE, prefix="cp") upViewport() @ The next piece of code draws a \pkg{ggplot2} histogram in the top third of the page, so not only do we have a conditioning plot combined with another plot on the same page (something that was not at all possible with the original \pkg{graphics}-based conditioning plot), but we have a mixture of \pkg{graphics}-based and \pkg{grid}-based output on the same page (see Figure \ref{fig:coplot-combined}). <<>>= library(ggplot2) @ <>= pushViewport(viewport(y=1, height=.33, just="top")) gg <- ggplot(quakes) + geom_bar(aes(x=depth)) + theme(axis.title.x = element_blank()) print(gg, newpage=FALSE) upViewport() @ <>= <> <> grid.rect(y=unit(1, "pt"), width=unit(1, "npc") - unit(2, "pt"), height=unit(.7, "npc") - unit(2, "pt"), just="bottom", gp=gpar(col="red", lty="dashed")) dev.off() @ \begin{figure}[h] \centering \fcolorbox{lightgray}{white}{\includegraphics[width=.46\textwidth]{murrell-coplot-combined}} \caption{The conditioning plot from Figure \ref{fig:coplot} combined with a \pkg{ggplot2} histogram on the same page. A dashed red box has been drawn around the region that is occupied by the conditioning plot, to emphasise the fact that the conditioning plot does not occupy the entire page.} \label{fig:coplot-combined} \end{figure} This sort of result---\pkg{grid}-based plots combined with \pkg{graphics}-based plots on the same page---can also be achieved using the \pkg{gridBase} package \citep{gridBase}. However, \pkg{gridBase} only allows plots from the two packages to coexist side-by-side on the same page; it does not provide any of the benefits of \pkg{grid} for \pkg{graphics}-based plots. \section{Exporting to SVG} Another benefit that we get from converting a \pkg{graphics} plot to \pkg{grid} is that the converted plot can then be exported to SVG via the \CRANpkg{gridSVG} package \citep{murrell-potter:2014}. This means that we gain the potential to add hyperlinks to the plot, animate components of the plot, add advanced SVG features to the plot, and add interactivity (possibly via JavaScript code). As a simple example, the following code draws the scatterplot from Figure \ref{fig:echo} with \code{plot()} from the \pkg{graphics} package, converts the plot to \pkg{grid} with \code{grid.echo()}, and then adds tooltips to each data symbol and exports the plot to SVG with the functions \code{grid.garnish()} and \code{grid.export()} from the \pkg{gridSVG} package. The SVG plot, as viewed in a browser, is shown in Figure \ref{fig:echo-svg}. <<>>= library(gridSVG) @ <>= <> grid.echo() @ <>= grid.garnish("graphics-plot-1-points-1", group=FALSE, title=rownames(mtcars)) grid.export("murrell-echo.svg") @ <>= pdf("murrell-echo-svg.pdf", onefile=FALSE) dev.control("enable") <> <> <> dev.off() @ % sleep 3; shutter -p paul-shutter -a -e -o murrell-echo-cap-svg.png \begin{figure}[h] \centering \includegraphics[width=.6\textwidth]{murrell-echo-cap-svg.png} \caption{The scatterplot plot from Figure \ref{fig:echo}, after conversion to \pkg{grid} using \code{grid.echo()}, exported to SVG, with tooltips added, using functions from the \pkg{gridSVG} package. The tooltips in this example may only work with Firefox.} \label{fig:echo-svg} \end{figure} A \pkg{graphics} plot that is converted by \pkg{gridGraphics} and then exported by \pkg{gridSVG} also has potential benefits for accessibility because, for example, text labels are exported as \code{} elements, which can be recognised by screen reading software. This is not the case with the standard \code{svg()} graphics device, which exports text as \code{} elements. \section{Naming schemes} Something that has been ignored in the examples so far is the fact that all manipulations of \pkg{grid} grobs, as well as navigation between \pkg{grid} viewports, rely on being able to identify, by name, which grob we want to manipulate or which viewport we want to visit. This section describes the naming scheme that the \pkg{gridGraphics} package uses to assign names to the grobs and viewports that it creates. The names of grobs have the following basic pattern: \begin{verbatim} -plot--