The R Project SVN R

Rev

Rev 49817 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

\documentclass[a4paper]{article}
%\VignetteIndexEntry{Working with grid grobs}
%\VignettePackage{grid}
\newcommand{\code}[1]{\texttt{#1}}
\newcommand{\pkg}[1]{{\normalfont\fontseries{b}\selectfont #1}}
\newcommand{\grid}{\pkg{grid}}
\newcommand{\grob}{\code{grob}}
\newcommand{\gTree}{\code{gTree}}
\newcommand{\gPath}{\code{gPath}}
\newcommand{\lattice}{\pkg{lattice}}
\newcommand{\R}{{\sffamily R}}
\setlength{\parindent}{0in}
\setlength{\parskip}{.1in}
\setlength{\textwidth}{140mm}
\setlength{\oddsidemargin}{10mm}

\title{Modifying \grid{} \code{grob}s}
\author{Paul Murrell}

\begin{document}
\maketitle

<<echo=FALSE, results=hide>>=
library(grDevices)
library(grid)
ps.options(pointsize=12)
options(width=60)

@
There is a distinction between \grob{}s which are just stored
in user-level \R{} objects and \grob{}s which represent
drawn output (i.e., \grob{}s on the \grid{} display list).
There is a naming convention that \code{grid.*()}  functions are
(mainly) used for their side-effect of producing output or
modifying existing output (they create/affect \grob{}s on the display list).
Functions of the form \code{*Grob()} are used for their return value;
the \grob{} that they create/modify.
For example, the following creates a \grob{} and then modifies it, but
performs absolutely no drawing;  this is purely manipulating a
description of a graphical object.

<<>>=
gl <- linesGrob()
gl <- editGrob(gl, gp=gpar(col="green"))

@
The next example produces output.  A \grob{} is returned,
but that \grob{} is just a description of the output that was drawn
and has no direct link to the output.  It is possible to access the
grob representing the output by
using the \grob{}'s \code{name}.  In order to access a \grob{}
which represents drawn output (i.e., a \grob{} on the display list),
you must specify a \gPath{}.  The \gPath{} should be created using the
\code{gPath()} function for writing scripts, but in interactive use, it
is possible to specify the \gPath{} directly as a string.  The code below
shows both approaches.

<<results=hide>>=
grid.newpage()
grid.lines(name="lines")
grid.edit(gPath("lines"), gp=gpar(col="pink"))
grid.edit("lines", gp=gpar(col="red"))

@
Complex graphical objects are provided by the \gTree{} class.
A \gTree{} is a \grob{} which may have other \grob{}s as children.
The \code{xaxis} and \code{yaxis} \grob{}s provided by \grid{} are
examples of \gTree{}s;  the children of an axis include
a lines \grob{} for the tick-marks
and a text \grob{} for the tick-mark labels.
The function \code{childNames()} can be used to list the
names of the children of a \gTree{}.
When dealing with these hierarchical
objects, more complex \gPath{}s can be used to access children
of a \gTree{}.  In the following example, an x-axis is drawn, then
the xaxis itself is edited to modify the locations of the tick-marks, then
the xaxis's text child is edited to modify
the location of the labels on the tick-marks.

<<results=hide>>=
grid.newpage()
pushViewport(viewport(w=.5, h=.5))
grid.rect(gp=gpar(col="grey"))
grid.xaxis(name="myxaxis")
grid.edit("myxaxis", at=1:4/5)
grid.edit(gPath("myxaxis", "labels"), y=unit(-1, "lines"))

@
This next example extends the idea a step further to edit
the child of a child of a \gTree{}.  It also shows the use of
the \gTree{} function to construct a simple \gTree{}
(this is just creating an instance of the \gTree{} class -- it is
also possible to extend the \gTree{} class in order to provide
specialised behaviour for drawing and other things;  more on this
later).  Finally, the example demonstrates how \gPath{}s of depth
greater than 1 can
be specified directly as a string.

<<results=hide>>=
grid.newpage()
pushViewport(viewport(w=.5, h=.5))
myplot <- gTree(name="myplot",
                children=gList(
                  rectGrob(name="box", gp=gpar(col="grey")),
                  xaxisGrob(name="xaxis")))
grid.draw(myplot)
grid.edit("myplot::xaxis", at=1:10/11)
grid.edit("myplot::xaxis::labels", label=round(1:10/11, 2))
grid.edit("myplot::xaxis::labels", y=unit(-1, "lines"))

@
\code{"grobwidth"} units require a \grob{} to give the width of.
There are two ways to specify the \grob{}.
The following example shows the most obvious method of
simply supplying a \grob{}.  Notice that if you modify
\code{gt} it will have no effect on the width of the drawn rectangle.

<<results=hide>>=
grid.newpage()
gt <- grid.text("Hi there")
grid.rect(width=unit(1, "grobwidth", gt))

@
In order to allow a \code{"grobwidth"} unit to track changes
in the \grob{},
it is possible to specify a \gPath{} rather than a \grob{}
as the data for a \code{"grobwidth"} unit.  The following example
modifies the previous example to use a \gPath{}.  Now, the width
of the rectangle changes when the width of the underlying \grob{}
changes.

<<results=hide>>=
grid.newpage()
gt <- grid.text("Hi there", name="sometext")
grid.rect(width=unit(1, "grobwidth", "sometext"))
grid.edit("sometext", label="Something different")

@
One issue in the evaluation of \code{"grobwidth"} units involves
establishing the correct ``context'' for a \grob{} when determining its width
(if a \grob{} has a viewport in its \code{vp} slot then that viewport gets
pushed before the \grob{} is drawn;  that viewport should also be pushed
when determining the width
of the \grob{}).
To achieve this there are
\code{preDrawDetails()},  \code{drawDetails()}, and \code{postDrawDetails()}
generic functions.
(suggestions for better names welcome!).  The idea is that
pushing and popping of viewports should occur in the \code{pre} and
\code{post} generics, and any actual drawing happens in the main
\code{drawDetails()} generic.  This allows the code that calculates a
\grob{} width to call the \code{preDrawDetails()} in order to establish
the context in which the \grob{} would be drawn before calculating its
width.  The following example shows a test case;  a \grob{} is created
(extending to a new class to allow specific methods to be written),
and methods are provided which establish a particular context
for drawing the \grob{}.  These methods are used both in the drawing
of the \grob{} and in the calculation of the \grob{}'s width (when
drawing a bounding rectangle).

<<results=hide>>=
grid.newpage()
mygrob <- grob(name="mygrob", cl="mygrob")
preDrawDetails.mygrob <- function(x) {
  pushViewport(viewport(gp=gpar(fontsize=20)))
}
drawDetails.mygrob <- function(x, recording=TRUE) {
  grid.draw(textGrob("hi there"), recording=FALSE)
}
postDrawDetails.mygrob <- function(x) {
  popViewport()
}
widthDetails.mygrob <- function(x) {
  unit(1, "strwidth", "hi there")
}
grid.draw(mygrob)
grid.rect(width=unit(1, "grobwidth", mygrob))

@
This next example shows a slightly different test case where the
standard \code{preDrawDetails()} and \code{postDrawDetails()} methods
are used, but the \grob{} does have a \code{vp} slot so these
methods do something.  Another interesting feature of this example
is the slightly more complex \gTree{} that is created.  The
\gTree{} has a \code{childrenvp} specified.  When the \gTree{} is drawn,
this viewport
is pushed and then ``up''ed before the children of the \gTree{} are drawn.
This means that the children of the \gTree{} can specify a \code{vpPath}
to the viewport they should be in.  This allows the parent
\gTree{} to create a suite of viewports and then children
of the \gTree{} select which one they want -- this can be more
efficient than having each child push and pop the viewports
 it needs, especially if several children are drawn within the same
viewport.  Another, more realistic example of this is given later.

<<results=hide>>=
grid.newpage()
mygtree <- gTree(name="mygrob",
                 childrenvp=viewport(name="labelvp", gp=gpar(fontsize=20)),
                 children=gList(textGrob("hi there", name="label",
                   vp="labelvp")),
                 cl="mygtree")
widthDetails.mygtree <- function(x) {
  unit(1, "grobwidth", getGrob(x, "label"))
}
grid.draw(mygtree)
grid.rect(width=unit(1, "grobwidth", mygtree))

@
Constructing a description of a \code{frame} \grob{} must be
done via \code{packGrob()} and \code{placeGrob()}.  The following example
shows the construction of a simple frame consisting of two
equal-size columns.

<<results=hide>>=
grid.newpage()
fg <- frameGrob(layout=grid.layout(1, 2))
fg <- placeGrob(fg, textGrob("Hi there"), col=1)
fg <- placeGrob(fg, rectGrob(), col=2)
grid.draw(fg)

@
This next example constructs a slightly fancier frame using
packing.

<<results=hide>>=
grid.newpage()
pushViewport(viewport(layout=grid.layout(2, 2)))
drawIt <- function(row, col) {
  pushViewport(viewport(layout.pos.col=col, layout.pos.row=row))
  grid.rect(gp=gpar(col="grey"))
  grid.draw(fg)
  upViewport()
}
fg <- frameGrob()
fg <- packGrob(fg, textGrob("Hi there"))
fg <- placeGrob(fg, rectGrob())
drawIt(1, 1)
fg <- packGrob(fg, textGrob("Hello again"), side="right")
drawIt(1, 2)
fg <- packGrob(fg, rectGrob(), side="right", width=unit(1, "null"))
drawIt(2, 2)

@
In order to allow frames to update when the objects packed within them
are modified, there is a \code{dynamic} argument to
\code{packGrob()} (and \code{grid.pack()}).  The following
extends the previous example to show how this might be used.
Another feature of this example is the demonstration of
``non-strict'' searching that occurs in the \code{grid.edit()}
call;  the \grob{} called \code{"midtext"} is not at the top-level,
but is still found.  Something like
\code{grid.get("midtext", strict=TRUE)} would fail.

<<results=hide>>=
grid.newpage()
fg <- frameGrob()
fg <- packGrob(fg, textGrob("Hi there"))
fg <- placeGrob(fg, rectGrob())
fg <- packGrob(fg, textGrob("Hello again", name="midtext"),
               side="right", dynamic=TRUE)
fg <- packGrob(fg, rectGrob(), side="right", width=unit(1, "null"))
grid.draw(fg)
grid.edit("midtext", label="something much longer")

@
There have been a few examples already which have involved creating a \gTree{}.
This next example explicitly demonstrates this technique.  A \gTree{}
is created with two important components.  The \code{childrenvp} is
a \code{vpTree} consisting of a \code{"plotRegion"} viewport to provide
margins around a plot and a \code{"dataRegion"} viewport to provide
x- and y-scales.  The \code{"dataRegion"} gets pushed within the
\code{"plotRegion"} and both are pushed and then ``up''ed before the
children are drawn.  The \code{children} of the \gTree{} are
an \code{xaxis} and a \code{yaxis} both drawn within the \code{"dataRegion"},
and a \code{rect} drawn around the border of the \code{"plotRegion"}.
A further feature of this example is the use of the \code{addGrob()}
and \code{removeGrob()} functions to modify the \gTree{}.  The first
modification involves adding a new child to the \gTree{} which is a
set of points drawn within the \code{"dataRegion"}.  The second
modification involves adding another set of points with a different
symbol (NOTE that this second set of points is given a name so that
it is easy to identify this set amongst the children of the \gTree{}).
The final modification is to remove the second set of points from the
\gTree{}.

<<results=hide>>=
grid.newpage()
pushViewport(viewport(layout=grid.layout(2, 2)))
drawIt <- function(row, col) {
  pushViewport(viewport(layout.pos.col=col, layout.pos.row=row))
  grid.rect(gp=gpar(col="grey"))
  grid.draw(gplot)
  upViewport()
}
gplot <- gTree(x=NULL, y=NULL,
               childrenvp=vpTree(
                 plotViewport(c(5, 4, 4, 2), name="plotRegion"),
                 vpList(viewport(name="dataRegion"))),
               children=gList(
                 xaxisGrob(vp="plotRegion::dataRegion"),
                 yaxisGrob(vp="plotRegion::dataRegion"),
                 rectGrob(vp="plotRegion")))
drawIt(1, 1)
gplot <- addGrob(gplot, pointsGrob(vp="plotRegion::dataRegion"))
drawIt(1, 2)
gplot <- addGrob(gplot, pointsGrob(name="data1", pch=2,
                                   vp="plotRegion::dataRegion"))
drawIt(2, 1)
gplot <- removeGrob(gplot, "data1")
drawIt(2, 2)

@
This next example provides a simple
demonstration of saving and loading \grid{} \grob{}s.  It is also a nice
demonstration that \grob{}s copy like normal
\R{} objects.

<<results=hide>>=
gplot <- gTree(x=NULL, y=NULL,
               childrenvp=vpTree(
                 plotViewport(c(5, 4, 4, 2), name="plotRegion"),
                   vpList(viewport(name="dataRegion"))),
               children=gList(
                 xaxisGrob(vp="plotRegion::dataRegion"),
                 yaxisGrob(vp="plotRegion::dataRegion"),
                 rectGrob(vp="plotRegion")))
save(gplot, file="gplot1")
gplot <- addGrob(gplot, pointsGrob(vp="plotRegion::dataRegion"))
save(gplot, file="gplot2")
grid.newpage()
pushViewport(viewport(layout=grid.layout(1, 2)))
pushViewport(viewport(layout.pos.col=1))
load("gplot1")
grid.draw(gplot)
popViewport()
pushViewport(viewport(layout.pos.col=2))
load("gplot2")
grid.draw(gplot)
popViewport()

@
This next example just demonstrates that it is possible to use a
\gPath{} to access the children of a \gTree{} when editing.
This is the \code{editGrob()} equivalent of an earlier example
that used \code{grid.edit()}.  One
useful application of this API is the ability to modify the
appearance of quite precise elements of a large, complex graphical
object by editing the \code{gp} slot of a child (of a child ...) of
a \gTree{}.

<<results=hide>>=
myplot <- gTree(name="myplot",
                children=gList(
                  rectGrob(name="box", gp=gpar(col="grey")),
                  xaxisGrob(name="xaxis")))
myplot <- editGrob(myplot, gPath="xaxis", at=1:10/11)
myplot <- editGrob(myplot, gPath="xaxis::labels", label=round(1:10/11, 2))
myplot <- editGrob(myplot, gPath="xaxis::labels", y=unit(-1, "lines"))
grid.newpage()
pushViewport(viewport(w=.5, h=.5))
grid.draw(myplot)

@
The following example demonstrates
the use of the \code{getGrob()} and \code{grid.get()} (along with
\gPath{}s) to access \grob{}s.

<<results=hide>>=
myplot <- gTree(name="myplot",
                children=gList(rectGrob(name="box", gp=gpar(col="grey")),
                  xaxisGrob(name="xaxis")))
getGrob(myplot, "xaxis")
myplot <- editGrob(myplot, gPath="xaxis", at=1:10/11)
getGrob(myplot, "xaxis::labels")
grid.newpage()
pushViewport(viewport(w=.5, h=.5))
grid.draw(myplot)
grid.get("myplot")
grid.get("myplot::xaxis")
grid.get("myplot::xaxis::labels")

@
There is also an API for (re)setting children of a \gTree{}
or any drawn \grob{}.  This is not intended for general user use,
but provides a simple way for developers to perform modifications
to the structure of a \gTree{} by doing something like ...

\begin{verbatim}
grob <- getGrob(<spec>)
<modify grob>
setGrob(<spec>, grob)
\end{verbatim}

This approach is used in the implementation of packing and placing grobs.
The following example shows some simple usage of the
\code{setGrob()} and \code{grid.set()} functions to replace
children of a \gTree{} with different \grob{}s.  NOTE that currently
such replacement can only occur if the name of the new \grob{}
is the same as the name of the old \grob{}.

<<results=hide>>=
myplot <- gTree(name="myplot",
                children=gList(rectGrob(name="box", gp=gpar(col="grey")),
                  xaxisGrob(name="xaxis")))
myplot <- setGrob(myplot, "xaxis", rectGrob(name="xaxis"))
grid.newpage()
pushViewport(viewport(w=.5, h=.5))
grid.draw(myplot)
grid.set("myplot::xaxis", xaxisGrob(name="xaxis", at=1:3/4))
grid.set("myplot::xaxis::labels",
         textGrob(name="labels", x=unit(1:3/4, "native"),
                  y=unit(-1, "lines"), label=letters[1:3]))
myplot <- setGrob(grid.get("myplot"), "xaxis::labels",
                  circleGrob(name="labels"))
grid.newpage()
pushViewport(viewport(w=.5, h=.5))
grid.draw(myplot)

@
This next example just shows more complex use of the add/remove
facilities for modifying \grob{}s.  Again, \code{addGrob()} and
\code{removeGrob()} are for constructing descriptions of graphical
objects and \code{grid.add()} and \code{grid.remove()} are for
modifying drawn output.  Of particular note are the last two
lines involving \code{grid.remove()}.  The first point is that
there are multiple \grob{}s on  the display list with the same
name.  The example only affects the first one it finds;  this could
easily be extended to affect the display list ``globally'' (for
children of \gTree{}s, there cannot be multiple children with the
same name so the issue does not arise).  The last line is interesting
because it actually erases the \grob{} named \code{"plot1"} from
the display list altogether (well, the first instance on the display list of
a \grob{} called \code{"plot1"} anyway).

<<results=hide>>=
drawIt <- function(row, col) {
  pushViewport(viewport(layout.pos.col=col, layout.pos.row=row))
  grid.rect(gp=gpar(col="grey"))
  grid.draw(gplot)
  upViewport()
}
gplot <- gTree(name="plot1",
               childrenvp=vpTree(
                 plotViewport(c(5, 4, 4, 2), name="plotRegion"),
                 vpList(viewport(name="dataRegion"))),
               children=gList(
                 xaxisGrob(name="xaxis", vp="plotRegion::dataRegion"),
                 yaxisGrob(name="yaxis", vp="plotRegion::dataRegion"),
                 rectGrob(name="box", vp="plotRegion")))
grid.newpage()
pushViewport(viewport(layout=grid.layout(2, 2)))
drawIt(1, 1)
grid.add("plot1", pointsGrob(0.5, 0.5, name="data1",
                             vp="plotRegion::dataRegion"))
grid.add("plot1::xaxis", textGrob("X Axis", y=unit(-2, "lines"), name="xlab"))
grid.edit("plot1::xaxis::xlab", y=unit(-3, "lines"))
gplot <- grid.get("plot1")
gplot <- addGrob(gplot, gPath="yaxis",
                 textGrob("Y Axis", x=unit(-3, "lines"), rot=90, name="ylab"))
drawIt(1, 2)
gplot <- removeGrob(gplot, "xaxis::xlab")
drawIt(2, 1)
grid.remove("plot1::data1")
grid.remove("plot1")

@
The next example is just a \code{grid.place()} and \code{grid.pack()}
equivalent of an earlier example involving
\code{placeGrob()} and \code{packGrob()}.  The interesting feature is
that each action is reflected in the output as it occurs.

<<results=hide>>=
grid.newpage()
grid.frame(name="myframe", layout=grid.layout(1, 2))
grid.place("myframe", textGrob("Hi there"), col=1)
grid.place("myframe", rectGrob(), col=2)
grid.newpage()
grid.frame(name="frame2")
grid.pack("frame2", textGrob("Hi there"))
grid.place("frame2", rectGrob())
grid.pack("frame2", textGrob("Hello again"), side="right")
grid.pack("frame2", rectGrob(), side="right", width=unit(1, "null"))

@
% Start a new page
% Not echoed, not evaluated
% ONLY here for checkVignettes so that all output doesn't
% end up on one enormous page

<<eval=FALSE, echo=FALSE>>=
grid.newpage()

@
\end{document}