\title{It's Not What You Draw,\\It's What You Not Draw} \subtitle{Drawing Paths with Holes in R Graphics} \author{by Paul Murrell} \maketitle \abstract{ The \R{} graphics engine has new support for drawing complex paths via the functions \code{polypath()} and \code{grid.path()}. This article explains what is meant by a complex path and demonstrates the usefulness of complex paths in drawing non-trivial shapes, logos, customised data symbols, and maps. } One of the design goals of the \R{} graphics system \citep{R} is to allow fine control over the small details of plots. One way that the \R{} graphics system does this is by providing access to low-level generic graphics facilities, such as the ability to draw basic shapes and the ability to control apparently esoteric, but still useful, features of those shapes, such as the line end style used for drawing lines. In R version 2.12.0, another low-level graphics facility was added to \R{}: the ability to draw \dfn{complex paths} (not just polygons). This article describes this new facility and presents some examples that show how complex paths might be useful. \section{Drawing paths with holes} The concept of a \dfn{path} is similar to the concept of a polygon: a path is defined by a series of $(x, y)$ locations that describe the boundary of the path. For example, the following code defines a set of $(x, y)$ locations that describe a simple triangle. <>= x <- c(.1, .5, .9) y <- c(.1, .8, .1) @ A triangle can be drawn from these locations using either the \code{polypath()} function from the \pkg{graphics} package or, as shown below and in Figure \ref{figure:simplepath}, using the \code{grid.path()} function from the \pkg{grid} package. <<>>= library(grid) @ <>= grid.path(x, y, gp=gpar(fill="grey")) @ \begin{figure} \begin{center} \includegraphics[width=.5\textwidth]{MurrellPaths-simplepath} \end{center} \caption{\label{figure:simplepath}A triangle drawn by the \code{grid.path()} function from a set of three $(x, y)$ locations.} \end{figure} As for any basic shape, it is possible to control the colour and thickness of the path border and the colour used to fill the interior of the path. We can also provide more than one set of $(x, y)$ locations when drawing a path. The following code provides an example, defining a new set of six locations along with an \code{id} vector that can be used to break the locations into two groups of three. <>= x <- c(.1, .5, .9, .1, .2, .3) y <- c(.1, .8, .1, .7, .6, .7) id <- rep(1:2, each=3) @ <<>>= cbind(x, y, id) @ These locations can be used to describe a path that consists of two distinct triangles. The following code draws such a path using \code{grid.path()}. The \code{id} argument is used to identify distinct groups of locations when using \code{grid.path()}.\footnote{When using \code{polypath()}, \code{NA} values must be inserted between distinct groups of $(x, y)$ values.} Figure \ref{figure:mediumpath} shows the result of drawing this path. <>= grid.path(x, y, id=id, gp=gpar(fill="grey")) @ \begin{figure} \begin{center} \includegraphics[width=.5\textwidth]{MurrellPaths-mediumpath} \end{center} \caption{\label{figure:mediumpath}Two triangles drawn by the \code{grid.path()} function from a set of six $(x, y)$ locations broken into two groups of three locations.} \end{figure} This output looks exactly the same as the output we would get from drawing the two groups of locations as polygons, using \code{grid.polygon()} or \code{polygon()}, but conceptually there is a difference because the path treats the two groups of locations as defining a single shape. We can see the difference more clearly if we move the smaller triangle so that it lies within the larger triangle (see Figure \ref{figure:mediumpathinside}). <>= x <- c(.1, .5, .9, .4, .5, .6) y <- c(.1, .8, .1, .5, .4, .5) @ <>= grid.polygon(x, y, id=id, gp=gpar(fill="grey")) @ <>= grid.path(x, y, id=id, gp=gpar(fill="grey")) @ \begin{figure} \begin{center} \includegraphics[width=.49\textwidth]{MurrellPaths-mediumpathinside} \includegraphics[width=.49\textwidth]{MurrellPaths-mediumpathpolygon} \end{center} \caption{\label{figure:mediumpathinside}On the left is a path drawn by the \code{grid.path()} function where the boundary of the path consists of two distinct triangles (one within the other). The result is a single shape with a hole in it. On the right is the result of drawing the two boundaries with the \code{grid.polygon()} function, which treats the boundaries as two separate shapes. In this case, the smaller triangle is drawn (filled in) on top of the larger triangle.} \end{figure} This example demonstrates that the two triangles together define a single shape, which is a triangular region with a triangular hole in it. The interior of the shape---the area that is shaded---does \emph{not} include the region within the smaller triangle. \subsection{Fill rules} There are two ways to determine the interior of a path like this. The default is called the \dfn{non-zero winding rule}. This draws an imaginary straight line and looks at where the straight line intersects the boundary of the shape. A count is made of how many times the boundary is running left-to-right at the intersection and how many times the boundary is running right-to-left; if the two counts are the same then we are outside the shape and if the counts are different, we are inside the shape. To see this more clearly, Figure \ref{figure:mediumpathdirection} uses lines with arrows to show the directions on the boundaries of the path from Figure \ref{figure:mediumpathinside}. <>= bdir <- function(nr, nc) { for (r in 1:nr) { for (c in 1:nc) { i <- (r - 1)*nc + c if (c == nc) j <- i - nc + 1 else j <- i + 1 grid.segments(x[i], y[i], x[j], y[j], arrow=arrow(angle=20, type="closed", length=unit(4, "mm")), gp=gpar(fill="black")) if (c == 1) grid.circle(x[i], y[i], r=unit(1, "mm"), gp=gpar(fill="black")) } } } bdir <- function(nr, nc) { # grid.path(x, y, id=id, gp=gpar(col="grey")) xm <- matrix(x, ncol=nr) ym <- matrix(y, ncol=nr) grid.circle(xm[1, ], ym[1, ], r=unit(1, "mm"), gp=gpar(fill="grey")) xsp <- xsplineGrob(rbind(xm, xm[1, ]), rbind(ym, ym[1, ]), id=rep(1:nr, each=(nc + 1)), shape=-.1) curves <- xsplinePoints(xsp) lapply(curves, function(xy) { grid.lines(xy$x[1:10], xy$y[1:10]) }) grid.circle(xm[1, ], ym[1, ], r=unit(2, "mm"), gp=gpar(col=NA, fill="white")) lapply(curves, function(xy) { grid.lines(xy$x[-(1:9)], xy$y[-(1:9)], arrow=arrow(angle=20, length=unit(3, "mm"), type="closed"), gp=gpar(fill="black")) }) invisible() } <>= bdir(2, 3) @ \begin{figure} \begin{center} \includegraphics[width=.5\textwidth]{MurrellPaths-mediumpathdirection} \end{center} \caption{\label{figure:mediumpathdirection}The direction of the boundaries for the path in Figure \ref{figure:mediumpathinside}.} \end{figure} The outer triangle boundary is clockwise and the inner triangle boundary is anti-clockwise, so, using the non-zero winding rule, the region within the inner triangle is actually outside the path. A straight line from inside the inner triangle to outside the outer triangle intersects two boundaries, one going right-to-left and one going left-to-right. To further demonstrate this rule, the following code defines a more complex path, this time consisting of three triangles: one large clockwise triangle, with two smaller triangles inside, one clockwise and one anti-clockwise. <>= x <- c(.1, .5, .9, .4, .5, .6, .4, .6, .5) y <- c(.1, .8, .1, .5, .4, .5, .3, .3, .2) id <- rep(1:3, each=3) @ Figure \ref{figure:complexpath} shows a diagram of the boundary directions and the result of drawing this path. Because the second smaller triangle is clockwise, the region inside that triangle is still part of the interior of the path, according to the non-zero winding rule. <>= bdir(3, 3) @ <>= grid.path(x, y, id=id, gp=gpar(fill="grey")) @ \begin{figure} \begin{center} \includegraphics[width=.49\textwidth]{MurrellPaths-complexpathdirection} \includegraphics[width=.49\textwidth]{MurrellPaths-complexpath} \end{center} \caption{\label{figure:complexpath}A path where the boundary consists of three triangles (two smaller ones within one larger one). The diagram on the left shows the direction of the boundaries for the path. On the right, the path is drawn by the \code{grid.path()} function, with the interior of the path determined using the non-zero winding rule.} \end{figure} The other rule for determining the interior of a path is called the \dfn{even-odd} rule. This just draws an imaginary straight line through the shape and counts how many times the straight line crosses the boundary of the shape. Each time a boundary is crossed, we toggle between outside and inside the shape. The following code draws the same path as in Figure \ref{figure:complexpath}, but uses the even-odd rule to determine the shape's interior. This time, the result is a larger triangle with \emph{two} smaller triangular holes punched out of it (see Figure \ref{figure:complexpathevenodd}). <>= grid.path(x, y, id=id, rule="evenodd", gp=gpar(fill="grey")) @ \begin{figure} \begin{center} \includegraphics[width=.5\textwidth]{MurrellPaths-complexpathevenodd} \end{center} \caption{\label{figure:complexpathevenodd}The path from Figure \ref{figure:complexpath} drawn using the even-odd fill rule.} \end{figure} The {\sf SVG} language specification contains a nice simple explanation and demonstration of these fill rules; see \url{http://www.w3.org/TR/SVG/painting.html#FillRuleProperty}. \section{Applications} So what can these complex paths be used for? The possibilities are endless, but this section describes a couple of concrete examples. The \R{} code for these examples can be obtained from the online resources for that accompany this article.\footnote{\url{http://www.stat.auckland.ac.nz/~paul/R/Paths/}} A trivial observation is that complex paths allow us to draw complex shapes. The triangle with triangular holes from the previous section is an example of a complex shape; it is not possible to describe this shape as a simple polygon. Another way that paths can be useful for drawing complex shapes is that they allow us to combine several simpler shapes to construct a more complex whole. Figure \ref{figure:target} shows an example of this, where the overall shape shape has a very complex outline, but it can be constructed as a path simply by combining circles and triangles. <>= target <- function(xc, yc, size, gp=gpar()) { n <- 49 t <- seq(0, 2*pi, length=n) x <- cos(t) y <- sin(t) xx <- xc + size*x yy <- yc + size*y scale <- size*.7 xxx <- xc + scale*x yyy <- yc + scale*y trix <- c(xc + size*.25, xc + size*1.1, xc + size*1.1, xc - size*.25, xc - size*1.1, xc - size*1.1, xc, xc + size*.2, xc - size*.2, xc, xc - size*.2, xc + size*.2) triy <- c(yc, yc - size*.2, yc + size*.2, yc, yc + size*.2, yc - size*.2, yc + size*.25, yc + size*1.1, yc + size*1.1, yc - size*.25, yc - size*1.1, yc - size*1.1) grid.path(c(xx, rev(xxx), trix), c(yy, rev(yyy), triy), id=c(rep(1:2, each=n), rep(3, 3), rep(4, 3), rep(5, 3), rep(6, 3)), gp=gp) } @ <>= target(.5, .5, .3) @ <>= target(.5, .5, .3, gp=gpar(lwd=3)) target(.5, .5, .3, gp=gpar(col=NA, fill="grey")) @ \begin{figure} \begin{center} \includegraphics[width=.49\textwidth]{MurrellPaths-target} \includegraphics[width=.49\textwidth]{MurrellPaths-targetfilled} \end{center} \caption{\label{figure:target}A complex shape constructed from simple shapes combined together to make a path.} \end{figure} Figure \ref{figure:highlight} shows how this shape might be used to dramatically highlight a point of interest within a graph (in this case, to bring attention to the data for the \emph{Ferrari Dino} in the \code{mtcars} data set). <>= highlight <- function(name) { target(convertX(unit(mtcars$disp[rownames(mtcars) == name], "native"), "npc", valueOnly=TRUE), convertY(unit(mtcars$mpg[rownames(mtcars) == name], "native"), "npc", valueOnly=TRUE), .07, gp=gpar(col=NA, fill=rgb(1, 0, 0, .5))) } library(lattice) xyplot(mpg ~ disp, mtcars) downViewport("plot_01.panel.1.1.vp") # highlight("Porsche 914-2") highlight("Ferrari Dino") # highlight("Lotus Europa") # highlight("Lincoln Continental") @ \begin{figure} \begin{center} \includegraphics[width=\textwidth]{MurrellPaths-highlight} \end{center} \caption{\label{figure:highlight}A plot with a complex path used to highlight a special point of interest.} \end{figure} Another situation where the ability to draw complex paths can be useful is if we are trying to draw a shape that someone else has created. For example, we might want to draw the logo of a company or an organisation as a label on a plot. Figure \ref{figure:GNUlogo} shows the GNU logo. This image consists of a single complex path, so we must be able to draw such paths in order to render it correctly. <>= library(grImport) @ <>= PostScriptTrace("GNU.ps", "GNU.xml") GNU <- readPicture("GNU.xml") grid.picture(GNU, x=.25, width=.4, use.gc=FALSE) grid.picture(GNU, x=.75, width=.4) @ \begin{figure} \begin{center} \includegraphics[width=\textwidth]{MurrellPaths-GNUlogo} \end{center} \caption{\label{figure:GNUlogo}A complex path that describes the GNU logo.} \end{figure} Figure \ref{figure:GNUplot} shows the GNU logo being used as a background watermark for a \pkg{lattice} barchart \citep{pkg:lattice}. <>= library(lattice) @ <>= cit <- c("1998"=4, "1999"=15, "2000"=17, "2001"=39, "2002"=119, "2003"=276, "2004"=523, "2005"=945, "2006"=1475, "2007"=2015) barchart(~ cit, main = "Number of Citations per Year", xlab = "", panel = function(...) { grid.picture(GNU) grid.rect(gp = gpar(fill = rgb(1, 1, 1, .9))) panel.barchart(...) }) @ \begin{figure} \begin{center} \includegraphics[width=\textwidth]{MurrellPaths-GNUplot} \end{center} \caption{\label{figure:GNUplot}A plot with the GNU logo from Figure \ref{figure:GNUlogo} as a background watermark.} \end{figure} Another way that we might use external complex shapes is as data symbols on a plot. Figure \ref{figure:bus} shows a bus icon. Again, this bus icon is a single path so it must be drawn using \code{grid.path()} or \code{polypath()} in order for it to render correctly. % Bus data from City of Chicago Data Portal % http://data.cityofchicago.org/Transportation/CTA-Ridership-Daily-Boarding-Totals/ <>= load("bus.rda") busdata <- read.csv("CTA_-_Ridership_-_Daily_Boarding_Totals.csv") busdata$date <- as.Date(busdata$service_date, format="%m/%d/%Y") <>= grid.picture(bus, x=.25, width=.4, use.gc=FALSE, gp=gpar(lwd=.5)) grid.picture(bus, x=.75, width=.4) @ \begin{figure} \begin{center} \includegraphics[width=\textwidth]{MurrellPaths-bus} \end{center} \caption{\label{figure:bus}A path that describes a bus icon.} \end{figure} Figure \ref{figure:busplot} shows this bus icon being used as data symbols on a \pkg{lattice} scatterplot of daily bus ridership data. <>= library(lattice) xyplot(bus/1000 ~ date, busdata, subset=1:7, type="l", scales=list(x=list(rot=30))) downViewport("plot_01.panel.1.1.vp") setGeneric("modcolour", function(object, ...) { standardGeneric("modcolour") }) setMethod("modcolour", signature("PictureFill"), function(object, ...) { path <- grobify(object, ...) if (!is.null(path$gp$fill) && !is.na(path$gp$fill) && path$gp$fill == "#000000") path$gp$fill <- trellis.par.get("plot.symbol")$col path }) for (i in 1:7) { grid.picture(bus, unit(busdata$date[i], "native"), unit(busdata$bus[i]/1000, "native"), width=unit(7, "mm"), FUN=modcolour) } @ \begin{figure} \begin{center} \includegraphics[width=\textwidth]{MurrellPaths-busplot} \end{center} \caption{\label{figure:busplot}A plot with the bus icon from Figure \ref{figure:bus} used as a data symbol.} \end{figure} Another general area where complex paths arise is the drawing of maps. The outline of a country's borders or coastline represents a set of $(x, y)$ coordinates, often in a very complicated arrangement. One situation where it can be useful to treat the map outline as a path is the case where a country contains a lake; the lake can be thought of as a hole in the country shape. Things can get quite complicated if the lake then contains an island, and the island has a lake, and so on. If the map is treated as a path to fill then all of these cases are dealt with quite easily. Figure \ref{figure:nz} shows a map of part of the South Island of New Zealand. The lake in the lower right quadrant of this map is Lake Te Anau and at the base of one of the westerly spurs of this lake is an island. This map outline has been drawn as a path with a green fill colour used to indicate land area and an appropriate fill rule ensures that the lake is not filled in, but the island on the lake is. <>= library(maptools) load("full_NZ_shore.RData") polygonsToPath <- function(ps) { # Turn the list of polygons into a single set of x/y x <- do.call("c", sapply(ps, function(p) { p@coords[,1] })) y <- do.call("c", sapply(ps, function(p) { p@coords[,2] })) id.lengths <- sapply(ps, function(p) { nrow(p@coords) }) # Generate vertex set lengths list(x=x, y=y, id.lengths=id.lengths) } path <- polygonsToPath(NZ@polygons[[1]]@Polygons) # xrange <- range(path$x) # yrange <- range(path$y) xrange <- c(167.5, 167.7) yrange <- c(-45.5, -44.5) grid.newpage() grid.rect(gp=gpar(col=NA, fill="azure2")) pushViewport(viewport(layout=grid.layout(widths=diff(xrange), heights=diff(yrange), respect=TRUE)), viewport(layout.pos.col=1, xscale=xrange, yscale=yrange)) grid.path(path$x, path$y, id.lengths=path$id.lengths, default.units="native", gp=gpar(fill=colours()[497])) popViewport(2) grid.rect() @ \begin{figure} \begin{center} \includegraphics[width=.8\textwidth]{MurrellPaths-nz} \end{center} \caption{\label{figure:nz}A map showing an island in a lake on an island.} \end{figure} Although \R{} provides many low-level graphics facilities, such as the ability to draw complex paths, there are still some basic tricks that it does not yet support. One example is the ability to clip output to an arbitrary region on the page (it is currently only possible to clip to rectangular regions with \R{}). Sometimes, a missing feature like this can be worked around by making inventive use of the existing facilities. Figure \ref{figure:mapclip} shows an example of this, where a contour of earthquake events has been overlaid on a map of New Zealand, but the contours are only visible over land areas. <>= library(akima) library(maps) library(gpclib) quakes <- read.csv("quakes-mod.csv") quakes$long <- ifelse(quakes$LONG < 0, 360 + quakes$LONG, quakes$LONG) quakes <- quakes[quakes$LAT < 0 & quakes$long < 190, ] library(MASS) qd <- kde2d(quakes$long, quakes$LAT, n=100) ql <- contourLines(qd$x, qd$y, qd$z, nlevels=10) n <- length(ql) # points(quakes$long, quakes$LAT, pch=".") outline <- map("nz", plot=FALSE) xrange <- range(outline$x, na.rm=TRUE) yrange <- range(outline$y, na.rm=TRUE) xbox <- xrange + c(-2, 2) ybox <- yrange + c(-2, 2) hue <- 240 @ <>= par(mar=rep(2, 4)) # Plot the data map("nz", col="grey", fill=TRUE) @ <>= par(mar=rep(2, 4)) # Plot the data map("nz") mapply(function(c, col) { polygon(c, col=col, border=adjustcolor(col, 1, .9, .9, .9)) }, ql, as.list(hcl(hue, 50, 20 + 60*n:1/(n+1)))) map("nz", add=TRUE) @ <>= par(mar=rep(2, 4)) map("nz", col=NA) polypath(c(outline$x, NA, c(xbox, rev(xbox))), c(outline$y, NA, rep(ybox, each=2)), col="grey", rule="evenodd") box() @ <>= par(mar=rep(0, 4)) # Plot the data map("nz") mapply(function(c, col) { polygon(c, col=col, border=adjustcolor(col, 1, .9, .9, .9)) }, ql, as.list(hcl(hue, 50, 20 + 60*n:1/(n+1)))) # grey(.7*n:1/(n+1) + .2))) polypath(c(outline$x, NA, c(xbox, rev(xbox))), c(outline$y, NA, rep(ybox, each=2)), col="white", rule="evenodd") # points(quakes$long[quakes$MAG > 7], # quakes$LAT[quakes$MAG > 7], # pch=21, bg=hcl(hue - 180, 80, 80)) @ \begin{figure} \begin{center} \includegraphics[width=\textwidth]{MurrellPaths-nzfinal} \end{center} \caption{\label{figure:mapclip}A map with an overlaid contour. A path has been used to obscure the contour where it does not overlap with land.} \end{figure} This result was achieved using complex paths (see Figure \ref{figure:mapclipsetup}). The starting point is the entire contour overlaid on a New Zealand map (left). A path is constructed from the New Zealand coastline (middle), and then a bounding rectangle is added to the path (right). This combined path allows us to fill a region that is everything outside of the New Zealand coastline and that can be drawn on top of the original image to obscure those parts of the contour that are not over land. \begin{figure*} \begin{center} \includegraphics[width=.3\textwidth]{MurrellPaths-nzcontour} \includegraphics[width=.3\textwidth]{MurrellPaths-nzmap} \includegraphics[width=.3\textwidth]{MurrellPaths-nzpathfill} \end{center} \caption{\label{figure:mapclipsetup}A map with a path used to obscure unwanted drawing.} \end{figure*} \section{Caveats} The \code{polypath()} and \code{grid.path()} functions are only supported on the \code{pdf()}, \code{postscript()}, \code{x11(type="cairo")}, \code{windows()}, and \code{quartz()} graphics devices (and associated raster formats). These functions are not supported on \code{x11(type="Xlib")}, \code{xfig()}, or \code{pictex()} and support is not guaranteed on graphics devices provided by extension packages. \section{Summary} There are new functions, \code{polypath()} and \code{grid.path()} for drawing complex paths, including paths with holes, in \R{} graphics output. These functions can be useful for drawing non-trivial shapes, logos, custom data symbols, and maps. \section{Acknowledgements} The following material and data were used in this article: \begin{itemize} \item The GNU logo was created by Aurelio A. Heckert and is available from \url{http://www.gnu.org/graphics/heckert_gnu.html}. \item The bus icon was created by the Geographics Unit, School of Environment, The University of Auckland. \item Both the GNU logo and the bus icon shape information were imported into \R{} and drawn using the \pkg{grImport} package \citep{pkg:grImport}. \item The bus data were obtained from the City of Chicago Data Portal \url{http://data.cityofchicago.org/Transportation/CTA-Ridership-Daily-Boarding-Totals/}. \item The detailed map of the South Island of New Zealand came from the Global Self-consistent, Hierarchical, High-resolution Shoreline Database \citep[version 2.0;][]{gshhs} and was loaded into \R{} using the \pkg{maptools} package \citep{pkg:maptools}. \item The earthquake data came from the GeoNet Project: \url{http://www.geonet.org.nz/} \item The New Zealand coastline information for Figures \ref{figure:mapclip} and \ref{figure:mapclipsetup} came from the \pkg{maps} package \citep{pkg:maps}. \end{itemize} Many thanks also to the anonymous reviewers who suggested several useful improvements to this article. % \bibliography{MurrellPaths} \begin{thebibliography}{6} \providecommand{\natexlab}[1]{#1} \providecommand{\url}[1]{\texttt{#1}} \expandafter\ifx\csname urlstyle\endcsname\relax \providecommand{\doi}[1]{doi: #1}\else \providecommand{\doi}{doi: \begingroup \urlstyle{rm}\Url}\fi \bibitem[Brownrigg and Minka(2011)]{pkg:maps} R.~Brownrigg and T.~P. Minka. \newblock \emph{\pkg{maps}: Draw Geographical Maps}, 2011. \newblock URL \url{http://CRAN.R-project.org/package=maps}. \newblock R package version 2.1-6. \bibitem[Lewin-Koh and Bivand(2011)]{pkg:maptools} N.~J. Lewin-Koh and R.~Bivand. \newblock \emph{\pkg{maptools}: Tools for reading and handling spatial objects}, 2011. \newblock URL \url{http://CRAN.R-project.org/package=maptools}. \newblock R package version 0.8-6. \bibitem[Murrell(2009)]{pkg:grImport} P.~Murrell. \newblock Importing vector graphics: The \pkg{grImport} package for {R}. \newblock \emph{Journal of Statistical Software}, 30\penalty0 (4):\penalty0 1--37, 2009. \newblock URL \url{http://www.jstatsoft.org/v30/i04/}. \bibitem[{R Development Core Team}(2011)]{R} {R Development Core Team}. \newblock \emph{\R{}: A Language and Environment for Statistical Computing}. \newblock R Foundation for Statistical Computing, Vienna, Austria, 2011. \newblock URL \url{http://www.R-project.org/}. \newblock {ISBN} 3-900051-07-0. \bibitem[Sarkar(2008)]{pkg:lattice} D.~Sarkar. \newblock \emph{\pkg{Lattice}: Multivariate Data Visualization with \R{}}. \newblock Springer, New York, 2008. \newblock URL \url{http://lmdvr.r-forge.r-project.org}. \newblock ISBN 978-0-387-75968-5. \bibitem[Wessel and Smith(1996)]{gshhs} P.~Wessel and W.~H.~F. Smith. \newblock A global self-consistent, hierarchical, high-resolution shoreline database. \newblock \emph{Journal of Geophysical Research}, pages 8741--8743, 1996. \end{thebibliography} \address{Paul Murrell\\ Department of Statistics\\ The University of Auckland\\ New Zealand\\ \email{paul@stat.auckland.ac.nz}}