% File src/library/grid/vignettes/viewports.Rnw % Part of the R package, https://www.R-project.org % Copyright 2001-13 Paul Murrell and the R Core Team % Distributed under GPL 2 or later \documentclass[a4paper]{article} \usepackage{Rd} % \VignetteIndexEntry{Working with viewports} \newcommand{\grid}{\pkg{grid}} \newcommand{\lattice}{\CRANpkg{lattice}} \setlength{\parindent}{0in} \setlength{\parskip}{.1in} \setlength{\textwidth}{140mm} \setlength{\oddsidemargin}{10mm} \title{Working with \grid{} Viewports} \author{Paul Murrell} \begin{document} \maketitle <>= library(grDevices) library(stats) # for runif() library(grid) ps.options(pointsize = 12) options(width = 60) @ This document describes some features of \grid{} viewports which make it easy to travel back and forth between multiple regions on a device (without having to recreate those regions), and provides a mechanism for a complex plotting function to provide users with access to all of the regions created during plotting. \section*{The viewport tree} \grid{} maintains a tree of pushed viewports on each device. When the \code{upViewport()} function is called it works like \code{popViewport()} except that it does not remove viewports from the viewport tree. For example, the following code pushes a viewport, then navigates back up to the top level viewport and pushes another viewport, without removing the first viewport. <<>>= pushViewport(viewport()) upViewport() pushViewport(viewport()) @ There are now two viewports pushed directly beneath the top-level viewport. This immediately creates an ambiguity; if I navigate back up to the top-level and attempt to revisit one of the viewports, how can I specify which one I want? The answer is that viewports have a \code{name} argument\footnote{The print method for viewports shows the viewport name within square brackets. Try typing \code{current.viewport()}.}. This name, combined with the \code{downViewport()} function, makes it possible to navigate back down to viewports in the viewport tree. Consider the following example, which pushes two viewports called \code{"A"} and \code{"B"}, then navigates to viewport \code{"A"} from the top level. <>= grid.newpage() <<>>= pushViewport(viewport(name = "A")) upViewport() pushViewport(viewport(name = "B")) upViewport() downViewport("A") @ The \code{downViewport()} function searches down the tree from the current position in the tree. The \code{seekViewport()} function is similar, but it always starts searching from the top-level viewport. In the previous example we ended up in viewport \code{"A"}; the following command navigates from \code{"A"} to \code{"B"} in a single step. <<>>= seekViewport("B") @ The function \code{current.vpTree()} provides a (textual) view of the current viewport tree. <<>>= current.vpTree() @ \section*{Viewport stacks, lists, and trees} It is possible to create multiple viewport descriptions \emph{and their relationships}. The functions \code{vpStack()}, \code{vpList()}, and \code{vpTree()} can be used to create a stack, a list, or a tree of viewport descriptions, respectively. It is then possible to push these multiple descriptions at once; viewports in a stack are pushed in series, viewports in a list are pushed in parallel, and for a tree of viewports, the parent is pushed then the children are pushed in parallel. The following simple example demonstrates one usage of this feature; a \grid{} rectangle is drawn \emph{two} viewports below the current level by specifying a stack of viewports in its \code{vp} argument. <>= vp <- viewport(width = 0.5, height = 0.5) grid.rect(vp = vpStack(vp, vp)) <>= grid.rect(gp = gpar(col = "grey")) <> @ \section*{Viewport paths} The previous example demonstrates a subtle feature of \grid{}'s viewport tree. The same viewport, \emph{with the same name}, was pushed twice (in series). This demonstrates that viewport names only have to be unique for viewports which share the same parent. This makes it possible, especially in repetitive plot arrangements, to reuse convenient viewport names. For example, in a \lattice{} style plot, each panel could have a viewport called \code{"strip"} to represent the strip region. This design creates a further ambiguity because there may be more than one viewport with the same name within the viewport tree\footnote{\code{downViewport()} and \code{seekViewport()} will stop at the first match they find (the search is currently depth-first).}. This ambiguity can be resolved by using the \code{vpPath()} function to generate a specification of a stack of viewports that must be matched by name. This path can be passed to either \code{downViewport()} or \code{seekViewport()} as in the following example; notice that we are calling \code{current.vpTree(FALSE)} in order to see the current viewport tree \emph{only from the current viewport down}. <>= grid.newpage() <<>>= pushViewport(viewport(name = "A")) pushViewport(viewport(name = "B")) pushViewport(viewport(name = "A")) @ When we do a seek on just \code{"A"}, we find the first \code{"A"} (just below the top-level viewport). <<>>= seekViewport("A") current.vpTree(FALSE) @ By specifying a \code{vpPath}, we can get the \code{"A"} directly below viewport \code{"B"}. <<>>= seekViewport(vpPath("B", "A")) current.vpTree(FALSE) @ A viewport path is conceptually just a concatenation of several names using a path separator (currently \code{::}). <<>>= vpPath("A", "B") @ For interactive use, it is possible to specify the path as a simple string, but this is not recommended otherwise in case the path separator changes in future versions of \grid{}. As an example, the following two commands are currently equivalent. <>= seekViewport(vpPath("A", "B")) seekViewport("A::B") @ \section*{An example} In this section, we consider a simple example to demonstrate how these new viewport features might be used together. The goal is to produce a simple scatterplot. We will work with some random data. <<>>= x <- runif(10) y <- runif(10) @ We will be establishing some scales appropriate for these data, so we calculate sensible ranges now. <<>>= xscale <- extendrange(x) yscale <- extendrange(y) @ We now produce a set of viewports that will be useful in creating the plot. The first viewport contains a layout to divide the drawing region into several rows and columns. The left and right columns and top and bottom rows provide room for axes and labels, while the central cell provides a region for plotting the data. The diagram below the code shows the layout that we create. <<>>= top.vp <- viewport(layout=grid.layout(3, 3, widths=unit(c(5, 1, 2), c("lines", "null", "lines")), heights=unit(c(5, 1, 4), c("lines", "null", "lines")))) <>= grid.show.layout(viewport.layout(top.vp)) @ Next we create a set of viewports which will occupy different areas within the layout, corresponding to the margins for axes and labels, and the plotting region. <<>>= margin1 <- viewport(layout.pos.col = 2, layout.pos.row = 3, name = "margin1") margin2 <- viewport(layout.pos.col = 1, layout.pos.row = 2, name = "margin2") margin3 <- viewport(layout.pos.col = 2, layout.pos.row = 1, name = "margin3") margin4 <- viewport(layout.pos.col = 3, layout.pos.row = 2, name = "margin4") plot <- viewport(layout.pos.col = 2, layout.pos.row = 2, name = "plot", xscale = xscale, yscale = yscale) @ Notice that we have not pushed any of these viewports yet so no regions exist on the output device. We first of all arrange the viewports into a tree structure, with the \code{top.vp} as the parent node and all of the other viewports as its children. <<>>= splot <- vpTree(top.vp, vpList(margin1, margin2, margin3, margin4, plot)) @ Now we can push this entire tree of viewports in order to create all of the different areas within the drawing region that we need to draw the scatterplot. The result of this push is that we are left in the \code{plot} viewport. <>= pushViewport(splot) @ Now we can navigate to whichever viewport we require and draw the different elements of the plot\footnote{The named viewports that we created are drawn as grey rectangles as a guide.}. <>= labelvp <- function(name) { seekViewport(name) grid.rect(gp = gpar(col = "grey", lwd = 5)) grid.rect(x = 0, y = 1, width = unit(1, "strwidth", name) + unit(2, "mm"), height = unit(1, "lines"), just = c("left", "top"), gp = gpar(fill = "grey", col = NULL)) grid.text(name, x = unit(1, "mm"), y = unit(1, "npc") - unit(1, "mm"), just = c("left", "top"), gp = gpar(col = "white")) } labelvp("plot") labelvp("margin1") labelvp("margin2") labelvp("margin3") labelvp("margin4") @ The data symbols and axes are drawn relative to the plot region \ldots{} <>= seekViewport("plot") grid.points(x, y) grid.xaxis() grid.yaxis() grid.rect() @ \ldots{} the x-axis label is drawn in margin 1 \ldots{} <>= seekViewport("margin1") grid.text("Random X", y = unit(1, "lines")) @ \ldots{} and the y-axis label is drawn in margin 2 (the final output is shown on the next page). <>= seekViewport("margin2") grid.text("Random Y", x = unit(1, "lines"), rot = 90) <>= pushViewport(viewport(w = 0.9, h = 0.9)) <> <> <> <> <> @ As a final step, we navigate back to the top-level viewport (i.e., back to the viewport we started in)\footnote{Here we have used \code{0} to indicate ``navigate to the top-level viewport''. When writing code that could be used by others (i.e., a graphical component that could be embedded within something else), it would be necessary to specify a precise number of viewports to navigate back up. In this case, the number would be {\tt} 2. The \code{downViewport()} function returns the number of viewports it went down, so a general solution is of the form: \code{depth <- downViewport("avp"); upViewport(depth)}.}. <<>>= upViewport(0) @ So far this example has just shown an alternative way of constructing this sort of plot. The output we have generated so far could have been done using \code{pushViewport()} and \code{popViewport()}. The difference is that we still have all of the viewports in the \grid{} viewport tree (and they are addressable by name). This means that a user can seek any of the viewports we used to construct the plot (by name) and add annotations or use \code{grid.locator()} or whatever. For example, a user could use the following commands to add a title. <>= seekViewport("margin3") grid.text("The user adds a title!", gp = gpar(fontsize = 20)) <>= pushViewport(viewport(w = 0.9, h = 0.9)) <> <> <> <> <> <> popViewport(0) @ \end{document}