\documentclass[a4paper]{article} %\VignetteIndexEntry{extensibility} \newcommand{\grid}{{\bf grid}} \newcommand{\gridSVG}{{\sf gridSVG}} \newcommand{\lattice}{{\bf lattice}} \newcommand{\R}{{\sf R}} \newcommand{\js}{{\sf javascript}} \newcommand{\svg}{{\sf SVG}} \newcommand{\code}[1]{{\tt #1}} \setlength{\parindent}{0in} \setlength{\parskip}{.1in} \title{Extending the gridSVG package} \author{Paul Murrell} \begin{document} \maketitle @ \section*{Introduction} It is sometimes useful or necessary for the user/developer to define a new grob (or gTree) class. The \gridSVG{} package only knows about the classes in the \grid{} package (basic graphical primitives, gTrees, plus a few others like frames and cell grobs). This means that \gridSVG{} cannot export, or animate, or garnish new grob classes without additional information. This document describes how to write methods for a new grob class to work with \gridSVG{}. <<>>= library(grid) library(gridSVG) @ The assumption in this document is that a new class has been made because there is a need to have a special \code{drawDetails()} method (because what the class draws has to be determined at drawing time rather than at grob creation time). There should not be any need for the information in this document if you have grobs or gTrees that are entirely defined at time of creation (so do not need any \code{drawDetails()} methods). Those cases should be handled by the \gridSVG{} package already. Users wanting to garnish or animate pieces of your scene should be able to get at whatever components of the scene that they need using gPaths (assuming that you have named all your grobs properly). \subsection*{Simple class example} A simple case is one where the grob being created (at drawing time) is just a simple graphical primitive. For example, the following (contrived) code defines a new class of (text) grob, a \code{timegrob} class, that writes out the time that its text is generated (see Figure \ref{figure:simplegrob}). <<>>= tg <- grob(name="tg", cl="timegrob") @ Because we will be able to reuse it later, we will write a function to create the grob at drawing time. <<>>= timegrob <- function(x) { textGrob(paste("text generated at", Sys.time(), sep="\n"), gp=x$gp, name=x$name) } @ The \code{drawDetails()} method for this new class just calls the \code{timegrob()} function to create a text grob and then draws that grob. <<>>= drawDetails.timegrob <- function(x, ...) { grid.draw(timegrob(x)) } @ Drawing the \code{timegrob} object calls the \code{drawDetails()} method above. <>= grid.draw(tg) @ \begin{figure} \begin{center} \includegraphics[width=2in]{extensibility-simplegrob} \caption{\label{figure:simplegrob}A simple new grob class that draws the time that its text was generated.} \end{center} \end{figure} The important thing about this class is that the grob that is being generated in the \code{drawDetails()} method is just a simple \code{text} grob. \subsection*{Not simple class example} A slightly more complex case is when a new grob class actually generates a gTree in its \code{drawDetails()} method. For example, the following code defines a new class, called \code{boxedtext}, that creates a gTree containing text and a bounding box (at drawing time so that the box can be created from the current size of the text, in case the text has been edited; see Figure \ref{figure:notsimplegrob}). <<>>= bt <- grob(x=unit(.5, "npc"), y=unit(.5, "npc"), label="hi", name="bt", cl="boxedtext") @ Again, it will be useful to have a function that creates the gTree. <<>>= boxedtext <- function(x) { tg <- textGrob(x$label, x$x, x$y, name=paste(x$name, "text", sep=".")) rg <- rectGrob(x$x, x$y, width=grobWidth(tg) + unit(2, "mm"), height=grobHeight(tg) + unit(2, "mm"), name=paste(x$name, "rect", sep=".")) gTree(children=gList(tg, rg), gp=x$gp, name=x$name) } @ The \code{drawDetails()} method is again very simple. <<>>= drawDetails.boxedtext <- function(x, ...) { grid.draw(boxedtext(x)) } @ Drawing the \code{boxedtext} grob call the \code{drawDetails()} method whch creates a gTree, containing a text and bounding rectangle, and draws it. <>= grid.draw(bt) @ \begin{figure} \begin{center} \includegraphics[width=2in]{extensibility-notsimplegrob} \caption{\label{figure:notsimplegrob}A not simple new grob class that draws text with a bounding box.} \end{center} \end{figure} The important thing about this example is that it creates a gTree at drawing time. \section*{Exporting a new grob class to \svg{}} If \gridSVG{} knows nothing about a grob class then it will not export any SVG elements via \code{gridToSVG()}. The technique for telling \gridSVG{} about a new class varies depending on how complex the new class is. \subsection*{Exporting a simple class} The \code{primToDev()} generic function is the function that converts a simple grob into \svg{}. There are \code{primToDev()} methods for all of the standard \grid{} graphical primitives, but for grob classes that \gridSVG{} does not recognise, no \svg{} code will be generated. If the new class only creates a simple grob at drawing time then all that is required is to define a new method for the \code{primToDev()} generic that generates a standard grob and calls \code{primToDev()} on that. For example, the following code creates a method for the simple \code{timegrob} class. This method simply creates the required text grob and then calls \code{primToDev()} on that, which exports the normal \svg{} code for a text grob. <<>>= primToDev.timegrob <- function(x, dev) { primToDev(timegrob(x), dev) } @ The \code{gridToSVG()} function will call this method to produce the appropriate \svg{} code. <<>>= grid.newpage() grid.draw(tg) gridToSVG("simpleclass.svg") @ The \svg{} code is shown below. One important feature is that the \code{id} attributes of the \svg{} elements are sensible. This has happened because the \code{timegrob()} function that we wrote sets the name of the text grob that it creates from the name of the \code{textgrob} object. <>= library(XML) @ <>= simpleclasssvg <- xmlParse("simpleclass.svg") cat(saveXML(simpleclasssvg)) @ Another feature of the \code{timegrob()} function is that it sets the \code{gp} slot of the text grob from the \code{gp} slot of the \code{textgrob} object. This is important to make sure that any graphical parameter settings on the \code{textgrob} are exported properly to \svg{}. \subsection*{Exporting a not simple class} The solution for exporting a new class that creates a gTree at drawing time is very similar to the simple solution. We need to write a \code{primToDev()} method for the new class, the only difference being that this method should create a gTree (rather than just a standard graphical primitive grob). For example, the following code creates a method for the \code{boxedtext} class. This just calls \code{boxedtext()} to create a gTree containing the appropriate text and bounding rectangle and then calls \code{primToDev()} on that gTree. <<>>= primToDev.boxedtext <- function(x, dev) { primToDev(boxedtext(x), dev) } @ The \code{gridToSVG()} function will now generate \svg{} output from a \code{boxedtext} object. <<>>= grid.newpage() grid.draw(bt) gridToSVG("notsimpleclass.svg") @ The \svg{} output is shown below. <>= notsimpleclasssvg <- xmlParse("notsimpleclass.svg") cat(saveXML(notsimpleclasssvg)) @ \section*{Animating a new grob class} In addition to converting a \grid{} scene to \svg{} code, the \gridSVG{} package also provides a way to animate components of a \grid{} scene. The \code{grid.animate()} function allows the user to select a grob by name and provide animated values for features of the grob (e.g., a set of x-values for a circle grob). The \code{grid.animate()} function actually only attaches the animation information to a grob. The real action happens when \code{gridToSVG()} is called and that calls the \code{animate()} generic function. The purpose of that function is to generate \code{} elements in the \svg{} code. As with \code{primToDev()}, there are \code{animate()} methods that generate \code{} elements for all standard graphical primitives, but nothing will happen for a grob class that \gridSVG{} is unaware of. If we want a new (simple) grob class to be able to be animated, we need to write an \code{animate()} method for that grob class. For example, the following code defines an \code{animate()} method for the \code{timegrob} class. This is similar to the \code{primToDev()} method in that it creates a text grob and then calls \code{animate()} on that (to take advantage of the existing \code{animate()} method for text grobs). The only complication is that the animation information on the \code{timegrob} object must be transferred over to the new text grob (this is done in a brute force manner here; perhaps a nicer encapsulation will be provided in the future). <<>>= animate.timegrob <- function(x, ...) { tg <- timegrob(x) tg$animationSets <- x$animationSets tg$groupAnimationSets <- x$groupAnimationSets animate(tg, ...) } @ With this \code{animate()} method in place, the following code draws a \code{timegrob} object and then animates it so that it will move left-to-right across the screen. <<>>= grid.newpage() grid.draw(tg) grid.animate("tg", x=c(.3, .7)) gridToSVG("animsimpleclass.svg") @ The \svg{} code that is generated from this scene is shown below to show that the animation has been recorded in the \svg{} output (it's actually an \code{} element rather than an \code{} element for text grobs). <>= animsimpleclasssvg <- xmlParse("animsimpleclass.svg") cat(saveXML(animsimpleclasssvg)) @ For the case of a new gTree class, things are a little more complicated. Again, we need to write a new \code{animate()} method, but this function can be a bit more complicated because there may be animation information to apply to the gTree as a whole \emph{and} animation information to apply to just the children of the gTree. For example, the code below defines an \code{animate()} method for the \code{boexedtext} class. This creates a gTree containing a text grob and a bounding rect grob. The \code{groupAnimationSets} information is added to the gTree and then \code{animate()} is called on that to output an \code{} element for the entire gTree, \emph{plus} each child of the gTree is extracted, \code{animationSets} information added, and then \code{animate()} called to output \code{} elements for each child of the gTree. <<>>= animate.boxedtext <- function(x, ...) { bt <- boxedtext(x) bt$groupAnimationSets <- x$groupAnimationSets animate(bt, ...) # Animate the children of bt btrect <- getGrob(bt, "bt.rect") btrect$animationSets <- x$animationSets animate(btrect, ...) bttext <- getGrob(bt, "bt.text") bttext$animationSets <- x$animationSets animate(bttext, ...) } @ The following code makes use of this method to animate a \code{boxedtext} grob. The first call to \code{grid.animate()} makes both text and bounding rect move left-to-right across the screen. The second call to \code{grid.animate()} makes the whole gTree disappear after 1 second (once the text and rect have moved to the right of the screen). <<>>= grid.newpage() grid.draw(bt) grid.animate("bt", x=c(.3, .7)) grid.animate("bt", visibility=c("visible", "hidden"), begin=1, duration=0.1, group=TRUE) gridToSVG("animnotsimpleclass.svg") @ The resulting \svg{} code is shown below. <>= animnotsimpleclasssvg <- xmlParse("animnotsimpleclass.svg") cat(saveXML(animnotsimpleclasssvg)) @ \section*{Garnishing a new grob class} The \code{grid.garnish()} function provides a way for users to add interactivity to a \grid{} scene, by specifying (\js{}) event handlers for components of the scene. More generally, the function allows \grid{} grobs to be garnished with arbitrary \svg{} attributes. Like \code{grid.animate()}, all \code{grid.garnish()} does is attach the garnishing information to a grob. The appropriate \svg{} code is only generated when \code{gridToSVG()} is called. The crucial action happens in the generic function \code{garnish()}, which transfers the garnishing information to the \svg{} device that is writing out \svg{} code. As should be familiar by now, there are \code{garnish()} methods for standard \grid{} graphical primitives, but no svg{} attributes will be exported for grob classes that \gridSVG{} does not know about. Fortunately, \code{garnish()} methods are once again pretty easy to write. For example, the following code defines a method for the \code{timegrob} class. This is very much like an \code{animate()} method: a text grob is created, the garnishing information is added to the text grob, then \code{garnish()} is called on the text grob so that the method for text grobs takes care of transferring the \svg{} attributes to the \svg{} device. <<>>= garnish.timegrob <- function(x, ...) { tg <- timegrob(x) tg$attributes <- x$attributes tg$groupAttributes <- x$groupAttributes garnish(tg, ...) } @ The following code shows the new method in action. A \code{timegrob} is drawn, then \code{grid.garnish()} is called to specify that a mouse click on the text should pop an alert dialog. <<>>= grid.newpage() grid.draw(tg) grid.garnish("tg", onmousedown="alert('ouch')") gridToSVG("garnishsimpleclass.svg") @ The resulting \svg{} code is shown below. <>= garnishsimpleclasssvg <- xmlParse("garnishsimpleclass.svg") cat(saveXML(garnishsimpleclasssvg)) @ The method is just as simple for a custom gTree class as well, as shown by the \code{garnish()} method for the \code{boxedtext} class below. <<>>= garnish.boxedtext <- function(x, ...) { bt <- boxedtext(x) bt$attributes <- x$attributes bt$groupAttributes <- x$groupAttributes garnish(bt, ...) } @ The following code shows this method in action, with an additional demonstration of the difference between garnishing individual components of a \grid{} scene and garnishing the overall parent component. A \code{boxedtext} grob is drawn, then the first call to \code{grid.garnish()} ensures that a mouse click on any part of the text or bounding box will produce an alert dialog. The second call to \code{grid.garnish()} sets up a different interaction on just the text so that moving the mouse over the text pops up a different dialog. <<>>= grid.newpage() grid.draw(bt) grid.garnish("bt", onmousedown="alert('ouch')") grid.garnish("bt", onmouseover=c(bt.text="alert('watch it!')"), group=FALSE) gridToSVG("garnishnotsimpleclass.svg") @ The resulting \svg{} code is shown below. <>= garnishnotsimpleclasssvg <- xmlParse("garnishnotsimpleclass.svg") cat(saveXML(garnishnotsimpleclasssvg)) @ \section*{Extending complex classes} A more complex case is one where a new grob class defines its own \code{preDrawDetails()} or \code{postDrawDetials()} methods. If that is the case, then it will be necessary to write a \code{grobToDev()} method for the class, just to get \svg{} exported correctly. See the existing methods for \code{grobToDev()} in the \gridSVG{} source for some examples. \end{document}