blob: e024a2f551e97e87c92d53f097c21dd42f0bde35 [file] [log] [blame]
% File src/library/grid/vignettes/displaylist.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{Display Lists in grid}
% \VignettePackage{grid}
% \VignetteDepends{graphics}
% \VignetteDepends{gridBase}
\newcommand{\grid}{\pkg{grid}}
\newcommand{\gridBase}{\CRANpkg{gridBase}}
\newcommand{\lattice}{\CRANpkg{lattice}}
\setlength{\parindent}{0in}
\setlength{\parskip}{.1in}
\setlength{\textwidth}{140mm}
\setlength{\oddsidemargin}{10mm}
\title{Display Lists in \grid{}}
\author{Paul Murrell}
\begin{document}
\maketitle
<<echo=FALSE, results=hide>>=
library(grDevices)
library(graphics) # for plot()
library(stats) # for runif()
library(grid)
ps.options(pointsize = 12)
options(width = 60)
@
A display list is a record of drawing operations. It is used
to redraw graphics output when a graphics window is resized,
when graphics output is copied from one device to another, and
when graphics output is edited (via \code{grid.edit}).
There are two display lists that can be used when working with
\grid{}. \R{}'s graphics engine maintains a display list and
\grid{} maintains its own display list. The former is maintained at
the C code level and records both base graphics output and
\grid{} graphics output. The latter is maintained at the
R code level and only records \grid{} output.
In standard usage,
the graphics engine's display list is used to redraw when a window
is resized and when copying between devices. \grid{}'s display list
is used for redrawing when editing \grid{} output.
There are two main problems with this standard usage:
\begin{enumerate}
\item The graphics engine display list only records graphics output;
none of the calculations leading up to producing the output are
recorded. This particularly impacts on plots which perform calculations
based on the physical dimensions of the device -- an example is
the legend function which performs calculations in order to
arrange the elements of the legend. The effect can be seen from
any example which uses the legend function. Try running
\code{example(legend)} then resize the device (make it quite
tall and thin or quite wide and fat); the legend will start
to look pretty sick.
{\bf NOTE:} that this is a problem with the graphics engine display
list -- it is not specific to \grid{}. In fact, much of \grid{}'s
behaviour is protected from this problem because things like \grid{}
units are ``declarative'' and will be re-evaluated on each redraw.
However, there are situations where \grid{} output can be afflicted,
in particular, whenever the \code{convertUnit()} function (or one of
its variants) is used (the help file for \code{convertUnit()} gives an
example).
A situation where this problem becomes very relevant for
\grid{} output is when the \gridBase{} package is used.
This is a situation where lots of calculations are performed
in order to align base and grid output, but these calculations
are not recorded on the graphics engine display list, so
if the device is resized the output will become very yukky.
\item \grid{}'s display list does not record base graphics
output\footnote{This is not quite true; it is possible
to include base graphics output on the \grid{} display list
as we will see later.} so if both base and \grid{} output appear
on the same device then the result of editing will not redraw the
base output. The following code provides a simple example:
<<guts1, eval=FALSE>>=
plot(1:10)
par(new = TRUE)
grid.rect(width = 0.5, height = 0.5, gp = gpar(lwd = 3), name = "gr")
<<ex1, echo=FALSE, fig=TRUE, width=4, height=3, include=FALSE>>=
<<guts1>>
grid.rect(width = 0.99, height = 0.99, gp = gpar(lty = "dashed"))
@
\includegraphics[width=4in, height=3in]{displaylist-ex1}
<<ex2, echo=FALSE, fig=TRUE, width=4, height=3, include=FALSE>>=
grid.rect(width = 0.5, height = 0.5, gp = gpar(col = "red", lwd = 3))
grid.rect(width = 0.99, height = 0.99, gp = gpar(lty = "dashed"))
<<eval=FALSE>>=
grid.edit("gr", gp = gpar(col = "red", lwd = 3))
@
\includegraphics[width=4in, height=3in]{displaylist-ex2}
After the \code{grid.edit}, the rectangle has been redrawn, but the
base plot has not.
\end{enumerate}
\section*{Saving calculations on the graphics engine display list\\
and saving base graphics on the \grid{} display list}
Both of the problems described in the previous section can be
avoided by using a \code{drawDetails()} method in \grid{}.
When a \grid{} grob is drawn, the \code{drawDetails} method
for that grob is called; if calculations are put within
a \code{drawDetails} method, then the calculations will be
performed every time the grob is drawn.
This means that it is possible, for example, to use \code{convertUnit()}
and have the result consistent across device resizes or
copies\footnote{In
each of the examples that follow, you should execute the example
code, resize the device to see any inconsistency, then close the
device before trying the next example.}.
This next piece of code is an example where the output becomes
inconsistent when the device is resized. We specify a width for
the rectangle in inches, but convert it (gratuitously) to
NPC coordinates -- when the device is resized, the NPC coordinates
will no longer correspond to 1''.
<<results=hide>>=
grid.rect(width = convertWidth(unit(1, "inches"), "npc"))
@
The next piece of code demonstrates that, if we
place the calculations within a \code{drawDetails} method, then
the output remains consistent across device resizes and
copies.
<<results=hide>>=
drawDetails.myrect <- function(x, recording) {
gr <- rectGrob(width = convertWidth(unit(1, "inches"), "npc"))
grid.draw(gr)
}
grid.draw(grob(cl = "myrect"))
@
The next example shows that a \code{drawDetails()} method can also be
used to save base graphics output on the \grid{} display list. This
example uses \gridBase{} to combine base and \grid{} graphics output.
Here I replicate the last example from the \gridBase{} vignette -- a
set of base pie charts within \grid{} viewports within a base plot.
In this case, I can produce all of the grobs required in the normal
manner -- their locations and sizes are not based on special
calculations\footnote{The example is wrapped inside a check for
whether the \CRANpkg{gridBase} package is installed so that the code
will still ``run'' on systems without \CRANpkg{gridBase}.}.
<<results=hide>>=
x <- c(0.88, 1.00, 0.67, 0.34)
y <- c(0.87, 0.43, 0.04, 0.94)
z <- matrix(runif(4*2), ncol = 2)
maxpiesize <- unit(1, "inches")
totals <- apply(z, 1, sum)
sizemult <- totals/max(totals)
gs <- segmentsGrob(x0 = unit(c(rep(0, 4), x),
rep(c("npc", "native"), each = 4)),
x1 = unit(c(x, x), rep("native", 8)),
y0 = unit(c(y, rep(0, 4)),
rep(c("native", "npc"), each = 4)),
y1 = unit(c(y, y), rep("native", 8)),
gp = gpar(lty = "dashed", col = "grey"))
gr <- rectGrob(gp = gpar(col = "grey", fill = "white", lty = "dashed"))
@
What is important is that I place the calls to the \gridBase{} functions
within the \code{drawDetails} method so that they are performed
every time the grob is drawn {\em and} the calls to the base graphics
functions are in here too so that they are called for every redraw.
<<results=hide>>=
drawDetails.pieplot <- function(x, recording) {
plot(x$x, x$y, xlim = c(-0.2, 1.2), ylim = c(-0.2, 1.2), type = "n")
vps <- baseViewports()
pushViewport(vps$inner, vps$figure, vps$plot, recording = FALSE)
grid.draw(x$gs, recording = FALSE)
for (i in 1:4) {
pushViewport(viewport(x = unit(x$x[i], "native"),
y = unit(x$y[i], "native"),
width = x$sizemult[i]*x$maxpiesize,
height = x$sizemult[i]*x$maxpiesize),
recording = FALSE)
grid.draw(x$gr, recording = FALSE)
par(plt = gridPLT(), new = TRUE)
pie(x$z[i, ], radius = 1, labels = rep("", 2))
popViewport(recording = FALSE)
}
popViewport(3, recording = FALSE)
}
@
The ``pieplot'' is created by assembling the component grobs
into a collective grob of the appropriate class; the \code{drawDetails}
method takes care of actually producing the output.
% produce figure, but don't include
% (to avoid par setting contamination between code segments)
<<results=hide, fig=TRUE, width=6, height=6, include=FALSE>>=
if (suppressWarnings(require("gridBase", quietly = TRUE))) {
grid.draw(grob(x = x, y = y, z = z,
maxpiesize = maxpiesize, sizemult = sizemult,
gs = gs, gr = gr, cl = "pieplot"))
}
@
The output from this example can be resized safely; \grid{} handles
all of the redrawing, and performs all of the actions within the
\code{drawDetails} method for each redraw, including redrawing the
base graphics output!
As a final example, we will harness the \grid{} display list purely to
achieve consistency in base graphics output. The following reproduces
the last example from the \code{legend()} help page, but produces
output which can be resized without the legend going crazy.
% produce figure, but don't include
% (to avoid par setting contamination between code segments)
<<results=hide, fig=TRUE, include=FALSE>>=
drawDetails.mylegend <- function(x, recording) {
x <- 0:64/64
y <- sin(3*pi*x)
plot(x, y, type = "l", col = "blue",
main = "points with bg & legend(*, pt.bg)")
points(x, y, pch = 21, bg = "white")
legend(.4,1, "sin(c x)", pch = 21, pt.bg = "white", lty = 1, col = "blue")
}
grid.draw(grob(cl = "mylegend"))
@
\end{document}