blob: bbbca668eb9b6eac55158ca3fd76c71202aaec78 [file] [log] [blame]
% 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
<<echo=FALSE, results=hide>>=
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.
<<echo=FALSE, results=hide>>=
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.
<<vpstackguts>>=
vp <- viewport(width = 0.5, height = 0.5)
grid.rect(vp = vpStack(vp, vp))
<<echo=FALSE, fig=TRUE>>=
grid.rect(gp = gpar(col = "grey"))
<<vpstackguts>>
@
\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}.
<<echo=FALSE, results=hide>>=
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.
<<eval=FALSE>>=
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"))))
<<echo=FALSE, fig=TRUE>>=
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.
<<viewports>>=
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.}.
<<grid, echo=FALSE, eval=FALSE>>=
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{}
<<plot, eval=FALSE>>=
seekViewport("plot")
grid.points(x, y)
grid.xaxis()
grid.yaxis()
grid.rect()
@
\ldots{} the x-axis label is drawn in margin 1 \ldots{}
<<margin1, eval=FALSE>>=
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).
<<margin2, eval=FALSE>>=
seekViewport("margin2")
grid.text("Random Y", x = unit(1, "lines"), rot = 90)
<<echo=FALSE, results=hide, fig=TRUE>>=
pushViewport(viewport(w = 0.9, h = 0.9))
<<viewports>>
<<grid>>
<<plot>>
<<margin1>>
<<margin2>>
@
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.
<<annguts, eval=FALSE>>=
seekViewport("margin3")
grid.text("The user adds a title!", gp = gpar(fontsize = 20))
<<echo=FALSE, results=hide, fig=TRUE>>=
pushViewport(viewport(w = 0.9, h = 0.9))
<<viewports>>
<<grid>>
<<plot>>
<<margin1>>
<<margin2>>
<<annguts>>
popViewport(0)
@
\end{document}