338 lines
11 KiB
Plaintext
338 lines
11 KiB
Plaintext
|
% 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}
|
||
|
|