391 lines
16 KiB
Plaintext
391 lines
16 KiB
Plaintext
% File src/library/grid/vignettes/frame.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}
|
|
%\VignetteIndexEntry{Frames and packing grobs}
|
|
%\VignettePackage{grid}
|
|
\newcommand{\code}[1]{\texttt{#1}}
|
|
\newcommand{\pkg}[1]{{\normalfont\fontseries{b}\selectfont #1}}
|
|
\newcommand{\grid}{\pkg{grid}}
|
|
\newcommand{\R}{{\sffamily R}}
|
|
\newcommand{\I}[1]{#1}
|
|
\setlength{\parindent}{0in}
|
|
\setlength{\parskip}{.1in}
|
|
\setlength{\textwidth}{140mm}
|
|
\setlength{\oddsidemargin}{10mm}
|
|
|
|
\title{A GUI-Builder Approach to \grid{} Graphics}
|
|
\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)
|
|
@
|
|
Grid contains a lot of support for locating, sizing, and arranging
|
|
graphical components on a device and with respect to each other.
|
|
However, most of this support relies on \emph{either} the parent
|
|
object dictating both location and size (layouts) \emph{or} the child
|
|
dictating both location and size.
|
|
|
|
Some sorts of arrangements are more conveniently handled by having the
|
|
parent dictate the location, but letting the child dictate the size.
|
|
This is the situation for GUI-builders (software which forms
|
|
arrangements of GUI components or widgets). The approach taken by
|
|
(many ?) GUI-builders is to allow the user to create a parent
|
|
\emph{frame} and then \emph{pack} widgets into this frame. The frame
|
|
locates and arranges the children with the help of hints such as
|
|
``place this widget at the bottom of the frame'', and the children
|
|
dictate what size they would like to be.
|
|
|
|
This document describes a first attempt at such an interface for
|
|
arranging Grid graphical objects.
|
|
|
|
\section*{The \code{"frame"} grob}
|
|
You can create a \code{"frame"} graphical object using the function
|
|
\code{frameGrob()}. You must assign the result to a variable in order
|
|
to pack grobs into it.
|
|
|
|
<<results=hide>>=
|
|
gf <- frameGrob()
|
|
@
|
|
\section*{The \code{packGrob()} function}
|
|
Having created a frame, you can then pack other graphical objects into
|
|
it using the \code{packGrob()} function. This function has a complex
|
|
interface which allows for a variety of methods of packing graphical
|
|
objects. The required arguments are:
|
|
\begin{description}
|
|
\item[\code{frame}] is a \code{"frame"} object created by \code{grid.frame}.
|
|
\item[\code{grob}] is the grob to pack into the frame.
|
|
\end{description}
|
|
The remaining arguments specify where the grob is located in the frame
|
|
and possibly how much space the grob should occupy. The frame is effectively
|
|
just a layout; you can add grobs to existing rows and columns
|
|
or you can append the grob in a new row and/or column. If the
|
|
grob is added to an existing row then the height of
|
|
that row becomes the maximum of the new height and
|
|
the previous height. If the row is new then it just
|
|
gets the specified height. Similar rules apply for column widths.
|
|
\begin{description}
|
|
\item[\code{col}] is the column to put the grob in. This can be 1 greater than
|
|
the existing number of columns (in which case a new column is added).
|
|
\item[\code{row}] is like \code{col} but for rows.
|
|
\item[\code{col.after}] specifies that the grob should be put in a
|
|
new column inserted between \code{col.after} and \code{col.after + 1}.
|
|
\item[\code{col.before}] specifies that the grob should be put in a
|
|
new column inserted between \code{col.before} and \code{col.before + 1}.
|
|
\item[\code{row.after} and \code{row.before}] do what you would expect.
|
|
\item[\code{side}] specifies which side to append the new grob to.
|
|
The valid values are \code{"left"}, \code{"right"}, \code{"bottom"},
|
|
and \code{"top"}.
|
|
\item[\code{width}] is the width of the row that the grob is being packed
|
|
into. If this is not given then the grob supplies the width.
|
|
\item[\code{height}] is like \code{width} but for rows.
|
|
\end{description}
|
|
It is possible to modify this default behaviour. For example, it is possible
|
|
to add a grob to a row and force that row to have the specified height
|
|
by setting \code{force.height=TRUE} (and similarly for column widths).
|
|
It is also possible to pack a graphical object into several rows or
|
|
columns at once (although you cannot simultaneously affect the heights or
|
|
widths of those rows and columns).
|
|
|
|
The result of this function is the modified frame, so you must
|
|
assign the result to a variable.
|
|
|
|
<<results=hide>>=
|
|
gf <- packGrob(gf, textGrob("Hello frame!"))
|
|
@
|
|
\section*{\code{"grobwidth"} and \code{"grobheight"} units}
|
|
|
|
A \code{"frame"} object allows a grob to specify its size by making
|
|
use of \code{"grobwidth"} and \code{"grobheight"} units.
|
|
These units may, of course, be used outside of frames too so
|
|
their use is described here.
|
|
|
|
Consider a simple example where I want to draw a rectangle around
|
|
a piece of text. I can get the size of the piece of text from the
|
|
\code{"text"} grob as follows:
|
|
|
|
<<frame1, include=FALSE, width=4, height=2, fig=TRUE, results=hide>>=
|
|
st <- grid.text("some text")
|
|
grid.rect(width = unit(1, "grobwidth", st),
|
|
height = unit(1, "grobheight", st))
|
|
@
|
|
\begin{center}
|
|
\includegraphics[height=1in, width=2in]{frame-frame1}
|
|
\end{center}
|
|
@
|
|
You could do the same thing with simple \code{"strwidth"}
|
|
and \code{"strheight"} units, but \code{"grobwidth"} and \code{"grobheight"}
|
|
give you a lot more power. The biggest gain is that you
|
|
can get the size of other objects besides pieces of text (more on that
|
|
soon). Another thing you can do is provide a ``reference'' to a grob
|
|
rather than the grob itself; you do this by giving the name of a grob.
|
|
What this does is make the unit ``dynamic'' so that changes in the
|
|
grob affect the unit. The following is a dynamic version of the
|
|
previous example.
|
|
|
|
<<frame1, include=FALSE, width=4, height=2, fig=TRUE, results=hide>>=
|
|
grid.text("some text", name = "st")
|
|
grid.rect(width = unit(1, "grobwidth", "st"),
|
|
height = unit(1, "grobheight", "st"))
|
|
@
|
|
Now watch what happens if I modify the text grob named \code{"st"}:
|
|
|
|
<<results=hide, eval=FALSE>>=
|
|
grid.edit("st", gp = gpar(fontsize = 20))
|
|
|
|
<<frame2, echo=FALSE, include=FALSE, width=4, height=2, fig=TRUE, results=hide>>=
|
|
my.text <- textGrob("some text")
|
|
my.text <- editGrob(my.text, gp = gpar(fontsize = 20))
|
|
my.rect <- rectGrob(width = unit(1, "grobwidth", my.text),
|
|
height = unit(1, "grobheight", my.text))
|
|
grid.draw(my.text)
|
|
grid.draw(my.rect)
|
|
@
|
|
\begin{center}
|
|
\includegraphics[height=1in, width=2in]{frame-frame2}
|
|
\end{center}
|
|
@
|
|
Similarly, I can change the text itself:
|
|
|
|
<<results=hide, eval=FALSE>>=
|
|
grid.edit("st", label="some different text")
|
|
|
|
<<frame3, echo=FALSE, include=FALSE, width=4, height=2, fig=TRUE, results=hide>>=
|
|
my.text <- textGrob("some text")
|
|
my.text <- editGrob(my.text, gp = gpar(fontsize = 20))
|
|
my.text <- editGrob(my.text, label = "some different text")
|
|
my.rect <- rectGrob(width = unit(1, "grobwidth", my.text),
|
|
height = unit(1, "grobheight", my.text))
|
|
grid.draw(my.text)
|
|
grid.draw(my.rect)
|
|
@
|
|
\begin{center}
|
|
\includegraphics[height=1in, width=2in]{frame-frame3}
|
|
\end{center}
|
|
@
|
|
\section*{The \code{widthDetails} and \code{heightDetails} generic functions}
|
|
|
|
The calculation of \code{"grobwidth"} and \code{"grobheight"} units
|
|
is a bit complicated, but fortunately most of it is automated.
|
|
The simple part is that a grob provides a normal \code{"unit"} object
|
|
to express its width or height. The complication comes because that
|
|
\code{"unit"} object has to be evaluated in the correct context; in
|
|
particular, if the grob has a non-\code{NULL} \code{vp} argument then
|
|
those viewports have to be pushed so that the size of the grob is
|
|
the size it would be when it is drawn. This is achieved by calling the
|
|
\code{preDrawDetails()} function for the grob and in most cases what
|
|
happens by default will be correct. The thing to avoid is having
|
|
any viewport operations in a \code{drawDetails()} method for your
|
|
grob; they should go in a \code{preDrawDetails()} method.
|
|
|
|
All that needs to be written (usually) are the functions that
|
|
provide the \code{"unit"} objects. These functions need to be
|
|
\code{widthDetails} and \code{heightDetails} methods.
|
|
|
|
The default methods return \code{unit(1, "null")}
|
|
so your grob will be of this size unless you write your own methods.
|
|
|
|
The classic example methods are those for \code{"text"} grobs; these
|
|
return \code{unit(1, "mystrwidth", <text grob label>)} and
|
|
\code{unit(1, "mystrheight", <text grob label>)} respectively.
|
|
|
|
The other very important examples of these methods are those for
|
|
\code{"frame"} grobs. These return the \code{sum} of the
|
|
widths (heights) of the columns (rows) of the layout that has been
|
|
built up by packing grobs into the frame. This means that
|
|
when a \code{"frame"} grob is packed within another \code{"frame"} grob
|
|
the parent automatically leaves enough room for the child.
|
|
|
|
Another useful pair of examples are those for \code{"rect"} grobs.
|
|
These methods make use of the \code{absolute.size} function.
|
|
When a grob is asked to specify its size, it makes sense to respond
|
|
with the \I{grob}'s width and height if the grob has an ``absolute'' size
|
|
(e.g., \code{"inches"}, \code{"cm"}, \code{"lines"}, etc;
|
|
i.e., the grob knows exactly how big itself is).
|
|
On the other hand, it does not make sense to respond with the \I{grob}'s
|
|
width and height if the grob has a ``relative'' size (e.g., \code{"npc"} or
|
|
\code{"native"}; i.e.,
|
|
the grob needs to know about it's parent's size before it can figure
|
|
out its own). The \code{absolute.size} function leaves absolute
|
|
units alone, but converts relative units to \code{"null"} units
|
|
(i.e., the child says to the parent, ``you decide how big I should be''), so
|
|
you can return something like \code{absolute.size(width(<grob>))} in order
|
|
to always give a sensible answer.
|
|
|
|
@
|
|
\section*{Examples}
|
|
The original motivating example for this GUI-builder approach was
|
|
to be able to produce a quite general-purpose legend grob.
|
|
|
|
A legend consists of data symbols and associated textual descriptions.
|
|
In order to be quite general, it would be nice to allow, for example,
|
|
multiple lines of text per data symbol. Rather than having to look at
|
|
the text supplied for the legend in order to determine the arrangement
|
|
of the legend, it would be nice to be able to simply specify the
|
|
composition of the legend and let it figure out the arrangement for
|
|
us. The code below defines just such a legend grob, using the new
|
|
\code{"frame"} grob and \code{packGrob()} function. Some points to
|
|
note are:
|
|
|
|
\begin{itemize}
|
|
\item The use of \code{border}s to create space around the legend
|
|
components.
|
|
\item The size of the data symbol component is specified, but the
|
|
size of the text components are taken from the \code{"text"} grobs.
|
|
|
|
\item The heights of the rows in the legend will be the maximum of
|
|
$\code{vgap} + \code{unit(1, "lines")}$ and $\code{vgap} +
|
|
\code{unit(1, "grobheight", <text grob>)}$.
|
|
|
|
\item We have two functions, one for generating a grob and one
|
|
for producing output.
|
|
\end{itemize}
|
|
|
|
<<>>=
|
|
legendGrob <- function(pch, labels, frame = TRUE,
|
|
hgap = unit(1, "lines"), vgap = unit(1, "lines"),
|
|
default.units = "lines",
|
|
vp = NULL) {
|
|
nkeys <- length(labels)
|
|
gf <- frameGrob(vp = vp)
|
|
for (i in 1:nkeys) {
|
|
if (i == 1) {
|
|
symbol.border <- unit.c(vgap, hgap, vgap, hgap)
|
|
text.border <- unit.c(vgap, unit(0, "npc"), vgap,
|
|
hgap)
|
|
} else {
|
|
symbol.border <- unit.c(vgap, hgap, unit(0, "npc"), hgap)
|
|
text.border <- unit.c(vgap, unit(0, "npc"), unit(0, "npc"), hgap)
|
|
}
|
|
gf <- packGrob(gf, pointsGrob(0.5, 0.5, pch = pch[i]),
|
|
col = 1, row = i, border = symbol.border,
|
|
width = unit(1, "lines"),
|
|
height = unit(1, "lines"), force.width = TRUE)
|
|
gf <- packGrob(gf, textGrob(labels[i], x = 0, y = 0.5,
|
|
just = c("left", "centre")),
|
|
col = 2, row = i, border = text.border)
|
|
}
|
|
gf
|
|
}
|
|
|
|
grid.legend <- function(pch, labels, frame = TRUE,
|
|
hgap = unit(1, "lines"), vgap = unit(1, "lines"),
|
|
default.units = "lines", draw = TRUE,
|
|
vp = NULL) {
|
|
gf <- legendGrob(pch, labels, frame, hgap, vgap, default.units, vp)
|
|
if (draw) grid.draw(gf)
|
|
gf
|
|
}
|
|
@
|
|
The next piece of code shows the \code{grid.legend()} function being
|
|
used procedurally; the output is shown below the code.
|
|
|
|
<<legend, include=FALSE, width=4, height=2, fig=TRUE, results=hide>>=
|
|
grid.legend(1:3, c("one line", "two\nlines", "three\nlines\nof text"))
|
|
|
|
@
|
|
\begin{center}
|
|
\includegraphics[height=2in, width=4in]{frame-legend}
|
|
\end{center}
|
|
|
|
@
|
|
The legend example might not seem too difficult to do by hand rather
|
|
than using frames and packing, but the next example shows how
|
|
useful it can be.
|
|
|
|
Suppose you want to arrange a legend next to a plot. This requires
|
|
leaving enough space for the legend and then filling the remaining
|
|
space with the plot. This requires figuring out how much space the
|
|
legend needs, and that is a task that is neither trivial nor
|
|
easy to cater for in the general case. Ideally, we want to know
|
|
as little as possible about the legend.
|
|
|
|
With the GUI-builder
|
|
approach this becomes extremely simple. The code below
|
|
shows how the construction of such a scene might be performed; the
|
|
output from the code is again shown below.
|
|
|
|
The following points are noteworthy:
|
|
|
|
\begin{itemize}
|
|
\item We don't need to know anything about how the legend was
|
|
constructed; it could be any sort of grob.
|
|
\item We specify the height of the legend to be \code{unit(1, "null")}
|
|
so that it will occupy the full height of the plot. If we
|
|
did not do this then the plot would be forced to be the height of
|
|
the legend (because of the way that \code{"null"} units interact
|
|
with other units).
|
|
\item The width of the legend is calculated from the contents of the
|
|
legend because the legend is a \code{"frame"} grob.
|
|
\item The dimensions of the ``plot'' default to \code{unit(1, "null")}
|
|
because \code{"collection"} grobs have no width or height methods, which
|
|
means that the plot fills up whatever space remains once the
|
|
legend has been accommodated.
|
|
\end{itemize}
|
|
|
|
<<plot, echo=FALSE, include=FALSE, fig=TRUE, results=hide>>=
|
|
top.vp <- viewport(width = 0.8, height = 0.8)
|
|
pushViewport(top.vp)
|
|
x <- runif(10)
|
|
y1 <- runif(10)
|
|
y2 <- runif(10)
|
|
pch <- 1:3
|
|
labels <- c("Girls", "Boys", "Other")
|
|
gf <- frameGrob()
|
|
plt <- gTree(children = gList(rectGrob(),
|
|
pointsGrob(x, y1, pch = 1),
|
|
pointsGrob(x, y2, pch = 2),
|
|
xaxisGrob(),
|
|
yaxisGrob()))
|
|
gf <- packGrob(gf, plt)
|
|
gf <- packGrob(gf, legendGrob(pch, labels),
|
|
height = unit(1, "null"), side = "right")
|
|
grid.rect(gp = gpar(col = "grey"))
|
|
grid.draw(gf)
|
|
popViewport()
|
|
grid.rect(gp = gpar(lty = "dashed"), width = .99, height = .99)
|
|
@
|
|
\begin{center}
|
|
\includegraphics{frame-plot}
|
|
\end{center}
|
|
@
|
|
\section*{Notes}
|
|
\begin{enumerate}
|
|
\item There are \code{grid.frame()} and \code{grid.pack()} equivalents
|
|
for these functions, but these are really only useful to see the
|
|
changes in the frame as each packing operation takes place.
|
|
|
|
\item This frame-and-packing stuff is easier to use, \emph{but}
|
|
(in almost all cases) it is less efficient than specifying the
|
|
arrangement by hand. There is consequently a penalty to pay in
|
|
terms of memory (inconsequential I think) and in terms of speed
|
|
(noticeably slower).
|
|
|
|
Perhaps one sensible use of these functions is to
|
|
build an image interactively using the simple arguments,
|
|
which will be slow, then attempt to speed up the drawing
|
|
by exploring some of the more advanced arguments.
|
|
|
|
One way to speed things up a bit is to specify the layout when
|
|
the frame is initially created and then use the \code{placeGrob()} function
|
|
to put grobs into existing rows and columns.
|
|
|
|
The speed penalty in the cases I have seen are mostly due to the time
|
|
taken to generate the (sometimes very) complicated unit objects that
|
|
express the heights and widths of the rows and columns of the frame
|
|
layout. Future effort will be put into speeding up the creation of
|
|
unit objects.
|
|
\end{enumerate}
|
|
\end{document}
|
|
|
|
|