Abstract
We describe an approach to creating interactive and animated graphical displays using R's graphics engine and Scalable Vector Graphics, an XML vocabulary for describing two-dimensional graphical displays. We use the svg() graphics device in R and then post-process the resulting XML documents. The post-processing identifies the elements in the SVG that correspond to the different components of the graphical display, e.g. points, axes, labels, lines. One can then annotate these elements to add interactivity and animation effects. One can also use JavaScript to provide dynamic interactive effects to the plot, enabling rich user interactions and compelling visualizations. The resulting SVG documents can be embedded within HTML documents and can involve JavaScript code that integrates the SVG and HTML objects. The functionality is provided via the SVGAnnotation package and makes static plots generated via R graphics functions available as stand-alone, interactive and animated plots for the Web and other venues.
The way we view graphical representations of data has significantly changed in recent years. For example, viewers expect to interact with a graphic on the Web by clicking on it to get more information, to produce a different view, or control an animation. These capabilities are available in Scalable Vector Graphics (SVG) [1, W3 Schools SVG Tutorial], and in this article, we describe an approach within R [2] to creating stand-alone interactive graphics and animations using SVG. SVG offers interactivity and animation through a rich set of elements that allow the graphics objects to be grouped, styled, transformed, composed with other rendered objects, clipped, masked, and filtered. SVG may not be the most ideal approach to interactive graphics, but it has advantages that come from its simplicity and increasing use on the Web and publishing generally. The approach presented here uses R's high-level plotting functions, R's SVG graphics device engine and the third-party cairo rendering engine [18] to create high-quality plots in SVG. Our tools then allow users to post-process the SVG documents/plots to provide the additional annotations to make the document interactive, e.g. users can add hyperlinks, tool tips, linked plots, controls (sliders and buttons), and animation to these displays to create potentially rich, compelling graphical displays that can be viewed outside of the R environment.
The SVGAnnotation package [17] contains a suite of tools to facilitate the programmer in this process. The package uses the XML [ Open XML] parsing and writing facilities in the XML package [16] to provide “high-level” facilities for augmenting the SVG output from the standard plotting functions in R, including lattice [20] and the traditional grz-model plotting functions in R. In addition to providing useful higher-levels facilities for modifying the more common R plots, the SVGAnnotation package also provides many utilities to handle other types of plots, e.g. maps and graphs. Of course, it is possible to generate interactive graphics from “scratch” by taking charge of all drawing, e.g. determining the coordinate system and drawing the axes, tick marks, labels, etc. However for complex data plots, we are much better off using this post-processing approach and leveraging R's excellent existing graphics facilities.
We do not want to suggest that the approach presented here is the definitive or dominant solution to the quest for general interactive graphics for R or statistics generally. Our focus is on making graphical displays created in R available in new ways to different audiences outside of R and within modern multi-media environments. This approach works well in many cases, and the XML tools in XML make it an alternative that we believe is worth exploring. While the popularity or uptake of this approach may be debatable due to prospects of SVG or implementations of the complete SVG specification in widely used browsers, what we have implemented can be readily adapated to other formats such as Flash or the JavaScript canvas. Hence this paper also provides ideas for future work with other similar graphical formats such as Flash and Flex MXML [28] and the JavaScript canvas. The essential idea is to post-process regular R graphics output and identify and combine the low-level graphical objects (e.g. lines and text) into higher-level components within the plot (e.g. axes and labels). Given this association, we can then annotate components to create interactive, dynamic and animated plots in various formats.
The remainder of the paper is organized as follows. We start by explaining reasons why SVG is a valuable format. We then give a brief introduction to several examples that we use throughout the document. These illustrate different features of SVG and how they might be used for displaying statistical results. We then introduce the common elements of SVG and examine the typical SVG document produced when plotting in R. From there we move on to explore general facilities that one may use to add to an SVG plot generated within R. The examples serve as the context for the discussion of these functions and the general approach. The facilities include adding tool tips and hyperlinks to elements of a display and more advanced features that deal with non-standard R displays and animation. We also show how one can add GUI components directly in SVG and alternatively use HTML forms with SVG. Additionally, we illustrate aspects of integrating JavaScript and SVG. We conclude the paper by discussing different directions we are exploring in this general area of stand-alone, interactive, animated graphics that can be displayed on the Web.
We discuss several technologies in this paper which may be new to readers. We provide an introduction to XML and also to JavaScript in two appendices. It may be useful for some readers to review these before reading the rest of this paper.
We also note that this paper can be viewed as an HTML document (at http://www.omegahat.org/SVGAnnotation/JSSPaper.html) and the SVG displays within it are "live", i.e. interactive and/or animated. That is a better medium for understanding the material than the printed, static medium.
Scalable Vector Graphics (SVG) is an XML format for describing two dimensional (2-D) graphical displays that also supports interactivity, animation, and filters for special effects on the different elements within the display. SVG is a vector-based system that describes an image as a series of geometric shapes. Compare this to a raster representation that uses a rectangular array of pixels (picture elements) to represent what appears at each location in the display. An SVG document includes the commands to draw shapes at specific sets of coordinates. These shapes are infinitely scalable because they are vector descriptions, i.e. the viewer can adjust and change the display (for example, to zoom in and refocus) and maintain a clear picture.
SVG is a graphics format similar to PNG, JPEG, and PDF. Many commonly used Web browsers directly support SVG (Firefox, Opera, Safari) and there is a plug-in for Internet Explorer. For example, Firefox can act as a rendering engine that interprets the vector description and draws the objects in the display within the page on the screen. There are also other non-Web-browser viewers for SVG such as Inkscape (http://www.inkscape.org/) and Squiggle (based on Apache's Batik) (http://xmlgraphics.apache.org/batik/tools/browser.html). In addition, SVG can, as we will see, be included in HTML documents, like JPEG and PNG, or in-lined within HTML content. However, quite differently from other image formats, SVG graphics, and their sub-elements, remain interactive when displayed within an HTML document and can participate as components in rich applications that interact with other graphics and HTML components. SVG graphics can also be included in PDF documents using an XML-based page description language named Formatting Objects (FO) [29], which is used for high-quality typesetting of XML documents. In summary, SVG is a viable alternative to PNG and JPEG formats, and it is also capable of fine-grained scaling and allows for interactivity and animation of individual objects in the display.
The size of SVG files can be both significantly smaller or larger than the corresponding raster displays, e.g. PNG and JPEG. This is very similar to PDF and Postscript formats. For simple displays with few elements, an SVG document will have entries for just those elements and so be quite small. Bitmap formats however will have the same size regardless of the content as it is the dimensions of the canvas that determines the content of the file. As a result, for complex displays with many elements, an SVG file may be much larger than a bitmap format. Furthermore, SVG is an XML vocabulary and so suffers from the verbosity of that format. However, SVG is also a regular text format and so can be greatly compressed using standard compression algorithms (e.g. GNU zip). Of course, we cannot simply compare the size of compressed SVG files to uncompressed bitmap formats as we should compare sizes when both formats are compressed. JPEG files, for example, are already compressed and so direct comparisons are meaningful. Most importantly, we should compare the size of files that are directly usable. SVG viewer applications (e.g. Web browsers, Inkscape) can directly read compressed SVG (.svgz) files, but they typically do not read compressed bitmap formats. As a result, the regular SVG files can be significantly smaller than comparable bitmap graphics and so faster to download and utilizing less bandwidth. Of course, the receiving application has to dynamically un-compress the content.
As just mentioned, an SVG file is a plain-text document. Since it is a grammar of XML, it is also highly structured. Being plain text means that it is relatively easy to create, view and edit using a text editor, and the highly structured nature of SVG makes it easy to modify programmatically. These features are essential to the approach described here: R is used to create the initial plot, we programmatically identify the elements in the SVG document that correspond to the components of the plot, and then augment the SVG document with additional information to create the interactivity and/or animation.
As a grammar of XML, SVG separates content and structure from presentation, e.g. the physical appearance of components in the presentation can be modified with a Cascading StyleSheet [7] without the need of changing or re-plotting the graphic. For example, someone who does not know R or even have the original data can change the contents of the CSS file that the SVG document uses, or substitute a different CSS file in order to, for example, change the color of points in the data region or the size of the line strokes for the axes labels. An additional feature of SVG is that elements of a plot can be grouped together and operated on as a single object. This makes it relatively easy to reuse visual objects and also to apply operations to them both programmatically and interactively. This is quite different from the style of programming available in traditional (grz) R graphics which draws on the canvas and does not work with graphical objects.
The SVG vocabulary provides support for adding interaction and animation declarations that are used when the SVG is displayed. However, one of the powerful aspects of SVG is that we can also combine SVG with JavaScript (also known as ECMAScript) [3]. This allows us to provide interaction and animation programmatically during the rendering of the SVG rather than declaratively through SVG elements. Both approaches work well in different circumstances. Several JavaScript functions are provided in the SVGAnnotation package to handle common cases of interaction and animation. For more specialized graphical displays, the creator of the annotated SVG document may need to write JavaScript code too and so work in a second language. For some users, this will be problematic, but the package does provide some facilities to aid in this step, e.g. making objects in R available to the JavaScript code. While switching between languages can be difficult, we should recognize that the plots are being displayed in a very different medium and use a language (JavaScript) that is very widely used for Web content.
The interactivity and animation described here are very different from what is available in more commonly used statistical graphics systems such as GGobi [GGobi], iPlots [34], or Mondrian [30]. Each of these provide an interactive environment for the purpose of exploratory visualization and are intended primarily for use in the data analysis phase. While one could in theory build similar systems in SVG, this would be quite unnatural. Instead, SVG is mostly used for creating both simple and very rich interactive presentation graphics that typically come at the end of the data analysis stage. Rather than providing general interactive features for data exploration, with SVG graphics, we create application-specific interactivity and often include graphical interfaces for that display, and even combine different media.
To get a sense of the possibilities for interactivity with SVG, we provide a relatively comprehensive set of examples that we have created using the facilities in SVGAnnotation. We begin with very simple examples that use high-level facilities in the package and proceed to more complex cases built using the utility functions also in the package. Some of these more advanced displays require a deeper understanding of how particular plots created in R are represented in SVG and an ability to write JavaScript.
The examples are intended to introduce the reader to the capabilities of SVG. They are also arranged to gradually move from high-level facilities to more technical details, i.e. they increase in complexity. The aim is to provide readers with sufficient concrete examples and accompanying code to develop SVG-based interactive displays themselves. When readers have worked through these examples, they will hopefully have the facilities to create SVG and their own customized displays.
Tool tips and hyperlinks are very simple, but effective, forms of interactivity. With hyperlinks, the user interacts with a plot by clicking on an active region, say an axis label or a point, and in response, the browser opens the referenced Web page. An example of this is shown in the scatter plot in Figure 1, “Hyperlinks on titles”. The mouse is over the title of the map, and the status bar at the bottom of the screen shows the URL of the USGS map of the South Pacific that will be loaded with a mouse click.
This scatter plot was produced by a call to the plot() function in R. After the plot was created, a hyperlink was added to the title of the plot using the addLink() function in SVGAnnotation. When viewed in a browser, a mouse click on the title will lead to the browser opening up the url shown in the status bar of the screen shot. (This plot is adapted from [35].)
With tool tips, the user interacts with a plot by pausing the pointer over a portion of the plot, say an axis label or a point. This mouse-over action causes a small window to pop up with additional information. Figure 2, “ Tool tips on a hexbin plot ” shows an example where the mouse has been placed on an hexagonal bin in the plot and a tool tip consequently appeared to provide a count of the number of observations in that bin. Note that these forms of interactivity do not require R or JavaScript within the browser, but merely any SVG-compliant browser.
The hexagonal-bin plot shown here was generated by a call to the hexbin() function. After the plot was created, the SVG file was then post-processed to add a tool tip to each of the shaded hexagonal regions so that when the mouse hovers over a hexagon, the number of observations in that bin appears in the tool tip.
It is also possible to link observations across sub-plots. For example, Figure 3, “Linked scatter plots” shows the mouse over a point in one of the plots. This mouse-over action causes that point and the corresponding point in the second plot to be colored red. When the mouse moves off the point, both points return to their original color(s). Another example found later in this paper demonstrates how to link across conditional plots, where the mouse-over action on a plot legend causes the corresponding groups of points to be highlighted in each of the panels (Figure 11, “ Linking a legend to a group of points in a conditional plot ”). This linking of points across plots uses reasonably generic JavaScript that is provided in the SVGAnnotation package.
The points in the two scatter plots shown here are linked. When the mouse covers a point in one plot, that point and the point in the other plot that corresponds to the same observation change color. When the mouse moves off the point, then the linked points return to their original color(s). The linkPlots() function in SVGAnnotation takes care of embedding the necessary annotations and JavaScript in the SVG file to change the color of the points.
SVG provides basic animation capabilities that can be used to animate R graphics. For example, it is possible to create animations similar in spirit to the well-known GapMinder animation (http://gapminder.org/world), where points move across a canvas. See Figure 4, “Scatter plot animation”. These simple animations are possible by simply adding SVG elements/commands to the SVG content generated by R. In other words, no JavaScript is needed to create the animation effects.
This screen shot shows the scatter plot at the end of the animation. Each circle represents one country. Time is represented through the movement of the points from one decade to the next. The location of the circle corresponds to the pair (income, life expectancy) and the area of the circle is proportional to the size of the population. The animate() function in SVGAnnotation provides the basic functionality for this scatter plot animation. (Reloading the page will restart the animation).
The Carto:Net project [CartoNet] has made available a Graphical User Interface (GUI) library for SVG. For convenience, this library is distributed as part of the SVGAnnotation package. It can be used to add controls such as sliders, radio boxes, and choice menus to an SVG plot. These controls provide an interface for the viewer to have more complex interactions with the graphic. For example, in Figure 5, “A slider for choosing parameter values” the slider specifies the bandwidth used to smooth the data in the scatter plot on the left. When the viewer moves the slider, the corresponding smoothed curve is displayed in the left-hand plot, and the right-hand plot is updated to display the residuals from the newly selected fit. The interactivity of the GUI controls are provided via JavaScript functionality in Carto:Net. Additional JavaScript is required to perform the plot-specific functionality, i.e. to display the correct curve and residuals in the figure. We should note that R is not involved when the plot is being viewed and the different curves are being displayed. This is done entirely via SVG and JavaScript with fitted values precomputed within R and serialized to the JavaScript code.
This pair of plots were made in R. Hidden within the plot on the left are curves from the fit of a few hundred different values for the smoothing parameter, and hidden in the right-hand plot are the residuals for each of those fits. The slider displayed across the bottom of the image is provided by Carto:Net [CartoNet]. It is added to the SVG display using addSlider() in SVGAnnotation. When the viewer changes the value of the slider, the corresponding curve and residuals are displayed and the previously displayed curve and residuals are hidden. JavaScript added to the SVG document responds to the change in the slider and takes care of hiding and showing the various parts of the plots.
An alternative to SVG GUI controls is to embed the SVG graphic in an (X)HTML page and use controls provided by an HTML form to control the plot. Again, JavaScript is required to respond to the user input via the HTML controls. The basic set of HTML UI controls is quite limited, so the author must either use JavaScript to provide the slider (e.g. using the YUI toolkit from Yahoo http://developer.yahoo.com/yui/) or change the interface to use one of the simpler controls.
A complication from embedding an SVG document within an (X)HTML document stems from the JavaScript code within the HTML code being separate from the code and objects within the SVG document. In this case, a simple extra step is necessary to access the SVG elements from the JavaScript code within the HTML document. An example is shown in Figure 6, “ SVG embedded within HTML links regions in a map to HTML tables. ”. The map of the United States presidential election results is embedded in an HTML <div> tag. When the viewer clicks on a state in the map, JavaScript located in the HTML document pulls up the state's summary information and dynamically places it in a table in the <div> above the map.
This screen shot shows a canonical red-blue map of the results of US presidential election embedded within a <div> tag in an HTML page. Interactivity within the map is controlled via JavaScript located in the HTML page. When the viewer clicks on a state, the table displaying the summary of votes in the state is rendered within the light-grey <div> element above the map. Also, the individual county results are available below the map (not shown in this screen shot).
These six figures show the variety of interactivity possible with SVG. In the following sections we introduce the functionality available within SVGAnnotation to create these and other interactive graphics. The examples are chosen to demonstrate our philosophy behind creating interactivity with R and SVG. The package offers the R programmer a new mode of displaying R graphics in an interactive, dynamic manner on the Web.
Having seen the different capabilities of SVG, we now turn our attention to how a user can create these displays in R. In this section, we introduce several high-level functions from the SVGAnnotation package that make it easy to add interactivity to SVG plots. We briefly describe the main functions in Table 1, “ High-level functions for adding interactivity to SVG plots ” and explain the basic approach we take to create these graphical displays. In addition, we demonstrate how to use some of these functions with a few examples.
In order to produce SVG graphics in R (using the built-in graphics
device), libcairo [18] must be installed when R is built.
You can determine whether an R installation supports SVG with the
expression capabilities()["cairo"]. If this yields
TRUE, then the libcairo-based SVG support is present. Assuming
this is the case, we create/open an SVG graphics device with a call to
the svg()
function and then issue R plotting commands
to the SVG device as with any other graphics devices. We must
remember to close the device with a call to dev.off()
when the commands to generate the display are complete. For example,
svg("foo.svg")
plot(density(rnorm(100)), type = "l")
abline(v = 0, lty = 2, col = "red")
curve(dnorm(x), -3, 3, add = TRUE, col = "blue")
dev.off()
creates the file named foo.svg that contains the
SVG that renders a smoothed density of 100 random normal observations.
The SVGAnnotation package provides a convenience layer to this process. The svgPlot() function opens the device, evaluates the code to create the plot(s), and then closes the device, all in a single function call. For example,
svgPlot({
plot(density(rnorm(100)), type = "l")
abline(v = 0, lty = 2, col = "red")
curve(dnorm(x), -3, 3, add = TRUE, col = "blue")
},
"foo.svg")
performs the same function calls as in the above example. We recommend using svgPlot() when all the commands to generate the display are available as a single unit because svgPlot() also inserts the R code that generated the plot as meta-data into the SVG file and provides provenance and reflection information. This can be convenient for the programmer post-processing the resulting SVG. Even more important is the additional information it adds for lattice plots such as the number of panels, strips, conditioning variables, and levels, and details about the legend. These allow us to more easily and reliably identify the different SVG elements corresponding to the components of the R plot(s) we want to annotate.
An additional reason for using the svgPlot() function is that it can hide the use of a file. As shown below, often we want the SVG document generated by R's graphics commands as an XML tree and not written to a file. svgPlot() will return this tree if no file name is specified by the caller, e.g.
doc = svgPlot({
plot(density(rnorm(100)), type = "l")
abline(v = 0, lty = 2, col = "red")
curve(dnorm(x), -3, 3, add = TRUE, col = "blue")
})
| Function | Brief Description |
|---|---|
| svgPlot | Opens the SVG device, evaluates the commands to generate the plot, and closes the device. It adds the R plotting commands to the SVG document and either returns the parsed XML document or writes it to a file. |
| addToolTips | Associates text with elements of the SVG document so that the text can be viewed in a tool tip when the mouse is placed over that object/element in the display. The default action is to add tool tips to points in a scatter plot. |
| addLink | Associates target URLs with any SVG element so that a mouse
click on an axis label, title, point or some other SVG element displays the
specified URL in the viewer's Web browser. Set the
addArea parameter to TRUE if you want the link to be
associated with a bounding rectangle around the SVG element. |
| linkPlots | Implements a simple linking mechanism of points within an R plot consisting of multiple sub-panels/plots. When the mouse is moved over a point in a sub-plot, the color of all corresponding points in other plots/panels also changes. The sub-plots can be created by, for example, arranging plots via the mfrow parameter in par() , pairs() , or splom() in lattice. |
| getAxesLabelNodes | Examines the SVG document and returns the SVG elements/nodes which represent the text of the main title and the X and Y axes for each sub-plot within the R graphic. |
| addCSS | Associates material from one or more Cascading Style Sheets with the specified SVG document. The CSS content can be identified as a link to the CSS file (via an "href"erence to the CSS file or URL), or alternatively it can be directly embedded into the SVG as in-line CSS material, making the resulting SVG "stand-alone". The SVGAnnotation package contains a default CSS file which handles visibility/transparency and colors for components that are commonly added to SVG displays via the SVGAnnotation functions. |
The higher-level facilities in SVGAnnotation make it quite easy to add simple forms of interactivity to an SVG display. To add interactivity, we follow a three-step process.
Plotting Stage: In this stage, we create the base SVG document. That is, we create an SVG graphics device and plot to it using R plotting tools. (Again, we recommend using svgPlot() because it adds the plotting commands to the SVG document.)
Annotation Stage: Here, the SVG file created in the Plotting Stage is modified. That is, SVG content (and possibly JavaScript code) is added to the document to make the display or sub-plot interactive or animated. Depending on the type of plot to be annotated and the kind of annotations desired, higher-level functions described in this section may be able to handle all aspects of these annotations. Some cases, however, may require intermediate functions to, for example, add tool tips to a non-standard plot, e.g. hexbin (see the section called “Tools for annotating SVG elements ”). Also, the annotation stage may possibly require working directly with the SVG document to, for example, add GUI controls to the plot (the section called “Creating GUIs”). All of these approaches use the XML package to parse and manipulate the SVG document from within R. We'll see examples of how this is used, both internally in the high-level functions and also when directly manipulating the SVG tree for more specialized annotations.
When the annotation is completed, we save the SVG document to a file.
Viewing Stage: Now the enhanced SVG document is loaded into an SVG viewer, such as Opera, Firefox, Safari, or Squiggle and the reader views and interacts with the image. In this stage, R is not available. The interactivity is generated by SVG elements themselves and/or JavaScript that has been added in the annotation stage.
In this section we provide simple examples of how to use the high-level functions to add tool tips (Example 1, “Adding Tool Tips to Points and Labels” and hyperlinks (Example 2, “Adding Hyperlinks to a Plot Title”) to plots. The R user does not need to understand SVG to add these simple features to plots.
Example 1. Adding Tool Tips to Points and Labels
In this example, we demonstrate how to add tool tips to the SVG plot shown in Figure 7, “Tool tips for points in a scatter plot”. The figure shows one of the effects that we are attempting to create: when the mouse is over a point then a tool tip with additional information about that point appears.
This screen shot shows a scatter plot where each point has a tool tip on it. The tool tips were added using the addToolTips() function in SVGAnnotation. When viewed in a browser, as the mouse passes over the point, information about the values of each variable for that observation is provided in a pop-up window. (Another screen shot of this plot is shown in Figure 1, “Hyperlinks on titles”.)
The first step is to make the plot using the SVG graphics device.
depth.col = gray.colors(100)[cut(quakes$depth, 100, label=FALSE)]
depth.ord = rev(order(quakes$depth))
doc = svgPlot(
plot(lat ~ long, data = quakes[depth.ord, ],
pch = 19, col = depth.col[depth.ord],
xlab = "Longitude", ylab="Latitude",
main = "Fiji Region Earthquakes") )
As noted earlier, the function svgPlot() is a wrapper for R's own svg() function. We did not specify a value for the file parameter for svgPlot() and as a result, the function returns the parsed XML/SVG tree, which we can then post-process and enhance. (This code is adapted from [35].)
The default operation for addToolTips() is to add tool tips on each of the points in a scatter plot. We simply pass the SVG document to addToolTips() along with the text to be displayed. For example, using the row names from the data frame is as simple as
addToolTips(doc, rownames(quakes[depth.ord, ]), addArea = 2)
If we wanted the tool tip to provide information about the value of each of the variables for that observation, we could do this with
addToolTips(doc, apply(quakes[depth.ord, ], 1,
function(x)
paste(names(quakes), x, sep = " = ",
collapse = ", ")), addArea = 2)
We can also use addToolTips() to provide tool tips on the axes labels. This time, since it is not the default operation, we need to pass the particular axes label nodes to be annotated, rather than the entire SVG document. We find these axes label nodes using another high-level function in SVGAnnotation, getAxesLabelNodes() . This function locates the nodes in the SVG document that correspond to the title and axes labels:
ax = getAxesLabelNodes(doc)
The first element returned is the title. We discard the title node, and call addToolTips() to place tool tips on just the axis nodes:
addToolTips(ax[-1],
c("Degrees east of the prime meridean", "Degrees south of the equator"),
addArea = 2)
The addToolTips() function will operate on any SVG element passed to it via the function's first argument.
Now that the SVG has been annotated, we save the modified document:
saveXML(doc, "quakes_tips.svg")
This document can now be opened in an SVG viewer (e.g. a Web browser) and the viewer can interact with it by mousing over the points and axes labels to read the tool tips that we have provided.
Example 2. Adding Hyperlinks to a Plot Title
We sometimes want to allow the viewer of a graphical display to click on a phrase or an individual point and jump to a different view or a Web page. SVG supports hyperlinks on elements so we can readily add this feature to R plots.
We continue with the plot that we annotated in Example 1, “Adding Tool Tips to Points and Labels” and add a hyper-link to the title. We add a feature to the plot that will allow viewers to click on the phrase “Fiji Region Earthquakes"” and have their Web browser display the USGS web page containing a map of recent earthquakes in the South Pacific. We have already seen that the title of the plot is in the first element of the return value from the call to getAxesLabelNodes() , which was saved in the R variable ax (see note below). We simply associate the target URL with this element via a call to addLink() as follows:
addAxesLinks(ax[[1]],
"http://earthquake.usgs.gov/eqcenter/recenteqsww/Maps/
region/S_Pacific.php")
Now, when the viewer clicks on the rectangular bounding box that encloses the title, the browser will open the USGS website.
| Note | |
|---|---|
The parsed SVG document is a C-level structure, and the return value from xmlParse() is a reference to this structure. Further, the functions, such as getAxesLabelNodes() , return pointers to the nodes in this C-level structure. The consequence of this is that when we assign the return value from say getAxesLabelNodes() to an R object, we are not getting a new copy of the nodes. Hence, any modification to the returned value modifies the original parsed document. For example, in Example 2, “Adding Hyperlinks to a Plot Title” an assignment to the R variable ax modifies the axes label nodes in the C-level structure. This is an important distinction from the usual way R handles assignments. The call to saveXML() will save the modified, parsed document as an XML file. |
| Note | |
|---|---|
Currently, not all common plots in R are handled at a high-level as described in this section. Some may require low-level manipulation of the XML in the same manner we have illustrated and implemented within the examples in the next sections of this paper and in the SVGAnnotation package. |
In this section we provide a brief overview of the commonly used XML elements/nodes in SVG and the basics of the drawing model. For more detailed information on SVG, readers should consult [1], and readers unfamiliar with XML should read the section called “Appendix: Basics of XML” or [5]. We also explore the basic layout of an SVG document created by the SVG device in R. In particular, we examine the SVG document produced from a simple call to plot() with two numeric vectors. If we wish to annotate other types of plots, i.e. one that is not covered by the high-level functions in SVGAnnotation, such as a ternary plot, then we will need to understand the structure of documents produced by the SVG device.
We include here a sample SVG file that will make our description of the SVG elements more concrete. This document was created manually (not with R) and is rendered in Figure 8, “Sample SVG”.
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns = "http://www.w3.org/2000/svg"
xmlns:xlink = "http://www.w3.org/1999/xlink"
width = "300pt" height = "300pt"
viewBox = "0 0 300 300" version = "1.1">
<defs>
<g id="circles">
<circle id = "greencirc" cx = "15" cy="15" r = "15"
fill = "lightgreen"/>
<circle id = "pinkcirc" cx= "50" cy="50" r = "15" fill = "pink"/>
</g>
<style type="text/css">
< ![CDATA[
.recs {fill: rgb(50%,50%,50%); fill-opacity: 1; stroke: red;}
]] >
</style>
</defs>
<g id = "main">
<rect x = "10" y = "20" width = "50" height = "100"
class = "recs"/>
<use x = "100" y = "100" xlink:href = "#circles"/>
<use x = "200" y = "50" xlink:href = "#circles" />
<image xlink:href = "examples/pointer.jpg"
x = "70" y = "50" width = "10" height = "10"/>
<path style = "fill: rgb(100, 149, 237);
fill-opacity: 0.5;stroke-width: 0.75;
stroke-linecap: round; stroke-linejoin: round;
stroke: rgb(0%,0%,0%); stroke-opacity: 1;"
d = "M 102.8 174.6 L 102.8 174.6
L 106.1 178.0 L 100.9 189.3 L 102.8 191.2 L 102.1 195.1
L 102.1 209.9 L 53.9 209.9 L 51.5 210.0 L 50.0 206.0
L 49.3 202.9 L 52.0 191.5 L 52.7 185.0 L 52.9 184.1
L 53.4 179.0 L 53.3 173.0 L 54.5 173.1 L 55.1 173.1
L 56.0 172.8 L 60.5 174.0 L 61.4 177.2 L 63.5 178.4
L 67.7 177.5 L 72.5 177.7 L 88.5 174.6 L 102.8 174.6
Z"
id = "oregon"/>
<text x = "110" y = "200" fill = "navy" font-size = "15">
Oregon
</text>
</g>
</svg>
Figure 8. Sample SVG
This simple SVG image is composed of a gray rectangle with a red border, two pairs of green and pink circles, a jpeg image of a pointer, a path that draws the perimeter of Oregon and fills the resulting polygon with blue, and the text “Oregon”.
The image makes use of the most commonly used tags in SVG, which we describe below.
SVG documents begin with the root tag <svg>. Possible elements it can have are: a <title> tag that contains the text to be displayed in the title bar of the SVG viewer; a <desc> tag, which holds a description of the document; and other tags for grouping and drawing elements.
Instructions to draw basic shapes are provided via the <line>, <rect>, <circle>, <ellipse>, and <polygon> tags. In our sample document,
<rect x = "10" y = "20" width = "50" height = "100" class = "recs" />
is an instruction to draw a rectangle with upper left corner at (10, 20) and a width of 50 and height of 100. (The class attribute contains style information about the color of the interior of the rectangle and its border.) These shape elements are specific families of shapes that can also be rendered with the more general <path> element.
The <path> tag provides the information needed to draw a “curve”. It contains instructions for the placement of a “pen” on a “canvas” and the movement of the pen from one point to the next in a “connect-the-dot” manner. The instructions for drawing the path are specified via a character string that is provided in the d attribute of <path>. For example, the path for the Oregon border in Figure 8, “Sample SVG” is as follows
d = "M 102.8 174.6 L 102.8 174.6
L 106.1 178.0 L 100.9 189.3 L 102.8 191.2 L 102.1 195.1
L 102.1 209.9 L 53.9 209.9 L 51.5 210.0 L 50.0 206.0
L 49.3 202.9 L 52.0 191.5 L 52.7 185.0 L 52.9 184.1
L 53.4 179.0 L 53.3 173.0 L 54.5 173.1 L 55.1 173.1
L 56.0 172.8 L 60.5 174.0 L 61.4 177.2 L 63.5 178.4
L 67.7 177.5 L 72.5 177.7 L 88.5 174.6 L 102.8 174.6
Z"
The drawing of the Oregon border begins by picking up the pen and placing it at the starting position (102.8, 174.6). This position is given either as an absolute position, i.e. “M x,y”, or a relative position, i.e. “m x,y”. Note that the capitalization of the letter M determines whether the position is relative (m) or absolute (M). Either a comma or blank space can be used to separate the x and y coordinates. From the starting point, the pen draws line segments from one point to the next. The line segment may be a straight line (L or l) or elliptical lines determined by quadratic (Q or q) or cubic (C or c) Bezier curves. The Z command closes a path by drawing a line segment from the pen's current position back to the starting point. These paths provide very succinct notation for drawing curves.
The libcairo rendering engine (and hence the svg() device in R) uses <path> for rendering all shapes including characters and text. One benefit to this approach of drawing text by explicitly using <path> commands to draw each letter is that there is no reliance on fonts when the SVG document is viewed. It also means that scaling the SVG preserves the shape of the letters with very high accuracy. However, when you add text and shapes to an SVG document, you may want to use the simpler higher-level short-cut tags, e.g. <text> and <ellipse>.
Elements can be grouped using the <g> tag. This is helpful when for example, you want to treat the collection of objects as a single object in order to transform it as a unit, or place the same appearance characteristics, or style, on a collection of elements. The style placed on <g> will apply to all of its sub-elements. Grouped elements can also be defined and then inserted multiple times in the document (see the description of the <defs> element below). It is also possible to nest other <svg> elements within a <g> element. This allows us to create compositions reusing previously and separately created displays.
The <defs> node is a container for SVG elements that are defined and given a label, but not immediately put in the SVG display. These definitions can be augmented, displayed and reused within the SVG display through a reference to the element's unique id. The <defs> element acts as a dictionary of template elements.
For example, in the sample SVG code we defined a pair of circles, one green and the other pink. These are grouped together into a single unit and identified by the id of “circles” as shown here:
<g id="circles">
<circle id = "greencirc" cx = "15" cy="15" r = "15"
fill = "lightgreen"/>
<circle id = "pinkcirc" cx= "50" cy="50" r = "15" fill = "pink"/>
</g>
This pair of circles is defined in the <defs> element, and rendered via the <use> tag. The pair of circles are rendered twice, at two different locations on the canvas as follows,
<use x = "100" y = "100" xlink:href = "#circles"/> <use x = "200" y = "50" xlink:href = "#circles" />
As with HTML, we specify the reference to an internal element (or "anchor") by prefixing the name/id of the desired element with a '#', i.e. "#circles". This suggests that we can link to elements in other files, and indeed we can. In fact, we have the full power of another XML technology named XLink [6].
Many times, we want to create style definitions to be used by multiple SVG elements. The style of an element can be specified in four ways:
In-line styles One approach is to place the style information directly in the element (e.g. <circle>, <path>) by setting the value of a style attribute. For example, the style of the Oregon polygon,
<path style = "fill: rgb(100, 149, 237);
fill-opacity: 0.5;stroke-width: 0.75;
stroke-linecap: round; stroke-linejoin: round;
stroke: rgb(0%,0%,0%); stroke-opacity: 1;"
...
/>
provides the color (cornflower blue) and opacity for filling the polygon, the color of border (black), and details about the border, such as the thickness of the line. With this approach, the style attribute value holds a string of Cascading Style Sheet (CSS) properties.
Internal Stylesheet The style information can be placed in a stylesheet that is stored within the file in the <defs> node of the document. As an example, the rectangle in our Figure 8, “Sample SVG” uses the "recs" class within the internal stylesheet. This connection is specified via the class attribute on the <rect> element:
<rect x = "10" y = "20" width = "50" height = "100" class = "recs" />
The CSS style sheet and its classes are found within the <defs> portion of the file, in the <style>:
<style type="text/css">
< ![CDATA[
.recs {fill: rgb(50%,50%,50%); fill-opacity: 1; stroke: red;}
] ]>
</style>
For more information about Cascading StyleSheets see [7]
External Stylesheet The stylesheets may also be located in an external file. It can be included via the xml-stylesheet processing instruction such as
<?xml-stylesheet type="text/css" href="../RSVGPlot.css" ?>
Presentation Attributes An alternative to using a stylesheet, whether in-line, internal, or external, is to provide individual presentation attributes directly in the SVG element. As an example, the pink circle's color in Figure 8, “Sample SVG” is specified through a fill attribute in the <circle> tag as follows,
<circle id = "pinkcirc" cx= "50" cy="50" r = "15" fill = "pink"/>
Note that, colors in SVG can be represented by text name, e.g. "cornflowerblue", the red-green-blue triple rgb(100, 149, 237), or the hexadecimal representation of the triple, e.g. “#6495ED”.
The presentation attributes are very straightforward and easy to use. We can just add a simple attribute, e.g. fill, on an element and avoid the extra layer of indirectness. This approach allows us to easily modify the presentation of an element in response to a user action. However, the downside of this approach is that presentation is mixed with content. For this reason, the in-line, internal and external cascading style sheets are preferable to presentation attributes. These various approaches can be mixed where style information can be provided from a combination of in-line, internal, and external stylesheets, as well as presentation attributes.
We next examine a typical document produced from the SVG graphics device in R. The SVG produced in R is highly structured, and we use this structure to locate particular elements and enhance them with additional attributes, parent them with new elements, and insert sibling elements in order to create various forms of interactivity and animation. We examine the SVG generated for the following call to plot() that was used to make the scatter plot in Example 1, “Adding Tool Tips to Points and Labels”.
doc = svgPlot(
plot(lat ~ long, data = quakes[depth.ord, ],
pch = 19, col = depth.col[depth.ord],
xlab = "Longitude", ylab="Latitude",
main = "Fiji Region Earthquakes") )
We can explore the contents of the XML/SVG document programmatically, using the tools available in the XML package. The tree in Figure 9, “SVG Document Tree” provides a picture of the hierarchy of SVG nodes.
Figure 9. SVG Document Tree
This tree provides a visual representation of the organization and structure of the SVG document produced by the call to plot() in Example 1, “Adding Tool Tips to Points and Labels”. SVG elements are shown as nodes in the tree. The style-sheet and the <display> element on the left of the tree are added by svgPlot() . The <CDATA> child of the <display> element contains the R code passed in the call to svgPlot() . The nodes with dotted lines are those that have been added to the SVG document by the addLink() and addToolTips() functions. For readability, not all nodes are displayed, and in some cases the number of nodes is provided to make it clear to which part of the plot these elements correspond. For example, the “1000 children” refers to the elements that plot the points in the scatter plot in Figure 7, “Tool tips for points in a scatter plot”; they correspond to the 1000 observations in the quakes data frame.
The XML package provides several tools that aid us in examining the structure and content of an SVG document. We demonstrate some of these as we explore the resulting SVG document. We begin by retrieving the top node and assigning it to root,
root = xmlRoot(doc)
We can use other functions such as xmlName() , xmlSize() , and xmlValue() to query the name, number of children, and the text content of an element, respectively. With them, we determine that the root has three children, the first contains the R code for the scatter plot, and the following two are the <defs> and the main <g> tag that contains the plotting elements.
xmlSize(root)
[1] 3
xmlApply(root, xmlName)
$display [1] "display" $defs [1] "defs" $g [1] "g"
or more simply
names(root)
[1] "display" "defs" "g"
xmlValue(root[[2]])[1] "plot(lat ~ long, data = quakes[depth.ord, ], pch = 19, col = depth.col[depth.ord], \n xlab = \"Longitude\", ylab = \"Latitude\", main = \"Fiji Region Earthquakes\")"
To examine the children of <defs> and <g>, we can use the xmlChildren() function and explore the elements of these lists as regular XML nodes. Alternatively, we can use the function getNodeSet() which extracts elements from the XML tree, doc, and is a very general mechanism for querying the entire tree or sub-trees. We provide an XPath expression to getNodeSet() that specifies how to locate these nodes. XPath [6, 27] is an extraction tool for locating content in an XML document. It uses the hierarchy of a well-formed XML document to specify the desired elements to extract. XPath is not an XML vocabulary; it has a syntax that is similar to the way files are located in a hierarchy of directories in a computer file system, but it is much more flexible and general. Rather than locating a single node in the tree, XPath extracts node-sets, which are collections of nodes that meet the criteria in the XPath expression. The node-set may be empty when no nodes satisfy the XPath expression. Likewise, when multiple nodes match the expression, then a collection of nodes make up the node-set. For example,
"/x:svg/x:g/*
locates all grandchildren of the root <svg> that have a <g> parent. Note that we must specify the name space for the <svg> tag. Since the SVG elements use the default name space in this document, getNodeSet() allows us to use any name space abbreviation without defining it or matching it to the name space prefix in the document. In this case we simply chose "x". We use getNodeSet() to extract these elements, and then request their names via a call to sapply() ,
kids = getNodeSet(doc, "/x:svg/x:g/*", "x") sapply(kids, xmlName)
[1] "rect" "g" "g" "g"
For more details on how to use XPath to retrieve nodes from an XML document see [6]. The return value from getNodeSet() is a list, and we access the first element by the standard indexing methods for a list:
kids[[1]]
<rect x="0" y="0" width="504" height="504" style="fill: rgb(100%,100%,100%); fill-opacity: 1; stroke: none;"/>
We can also use any one of the following approaches,
root[[3]][[1]] root[["g"]][["rect"]] getNodeSet(doc, "/x:svg/x:g/x:rect", "x")
The first two return an object of class XMLInternalNode, whereas the call to getNodeSet() returns a list where each element is an XMLInternalNode.
Fortunately, R's plotting functions are very regular or predictable and so we can determine which nodes corresponds to which graphics objects quite easily. The approach we use here capitalizes on understanding the default operation of the plotting functions. To see how, notice that the first <g> sibling of <rect> has 1000 children.
sapply(kids, xmlSize)
[1] 0 1000 25 3
This number exactly matches the number of points plotted.
dim(quakes)
[1] 1000 5
The element with 25 children corresponds to the axes of the data region of the plot and their tick marks. The element with just 3 children contains the title and the two axes labels.
The high-level functions described in the section called “Simple Annotations” make use of these default locations in the SVG output for the most common plots. If you need to annotate a more specialized plot, then you may need to use the other functions in SVGAnnotation, or directly handle the XML nodes yourself with the functionality available in the XML package.
Next, let's examine the first of these 1000 nodes. We see that it is a <path> element.
<path style="fill-rule: nonzero;
fill: rgb(90.196078%,90.196078%,90.196078%);
fill-opacity: 1;stroke-width: 0.75; stroke-linecap: round;
stroke-linejoin: round; stroke: rgb(90.196078%,90.196078%,90.196078%);
stroke-opacity: 1;stroke-miterlimit: 10; "
d="M 337.144531 191.292969 C 337.144531 194.894531
331.746094 194.894531 ... " />
Although the symbol used to represent a point is a circle, the SVG graphics device in R (via libcairo) does not use the <circle> element to draw the circle. Instead, the <path> node provides instructions for drawing the circle using Bezier curves to connect the points supplied in the d attribute. (The letter C in this attribute value denotes that the points are to be connected by a cubic Bezier). svgPlot() has added the type attribute to the path so that the element can be more easily identified as instructions to draw a point in a plot. This makes it easier for the programmer to extract and annotate elements. Finally, the style information includes instructions to fill the resulting “circle” a particular shade of grey.
The coordinates used to draw the path are specified in the coordinate system of the SVG canvas, not the coordinate system of the data. The SVG coordinate system places the smallest values of x and y at the upper left corner of the canvas and the maximum values for x and y at the lower right corner, i.e. y increases as you move down and x increases as you move right. One implication of this is that we cannot use the X and Y coordinates from our data to directly identify SVG elements in the document; they will need to be converted into this alternative coordinate system. In addition, the SVG coordinate system supports many units, but the device in R uses points (abbreviated as 'pt' or 'pts'). A point is approximately 1/72 of an inch. The size of the canvas is provided via the width and height attributes on the <svg> root node of the document.
The libcairo engine used by R generates all shapes exclusively with the <path> tag. This holds true as well for the text in axes and plot labels; that is, the cairo rendering engine in R creates the text by explicitly drawing the letters via SVG paths. The reason for this is that the resulting letters scale extremely well and do not rely on special fonts which may not be available at the time of rendering. More specifically, the path for the glyphs that correspond to the text are created and placed in a <defs> element, and a <use> element brings in the glyph at the proper location in the plot. This representation introduces some difficulties for us because the text for legends and axes labels do not appear as plain text in the SVG document and so are not easily located for post-processing. The placement of the glyphs in the <defs> means that there is one additional level of indirection that needs to be handled when annotating text.
To make this concrete, let's consider our scatter plot example again. The last child of the main graphing node contains the information for drawing the title and axes labels. It has three children, one each for the title, y axis, and x axis, respectively. We identify the x axis with the following XPath expression,
getNodeSet(doc, "/x:svg/x:g/x:g[3]/*[last()]", "x")
[[1]] <g style="fill: rgb(0%,0%,0%); fill-opacity: 1;" type="axis-label"> <use xlink:href="#glyph1-7" x="14.398438" y="266.152344"/> <use xlink:href="#glyph1-8" x="14.398438" y="259.478516"/> <use xlink:href="#glyph1-9" x="14.398438" y="252.804688"/> <use xlink:href="#glyph1-10" x="14.398438" y="249.470703"/> <use xlink:href="#glyph1-9" x="14.398438" y="246.804688"/> <use xlink:href="#glyph1-11" x="14.398438" y="243.470703"/> <use xlink:href="#glyph1-12" x="14.398438" y="236.796875"/> <use xlink:href="#glyph1-13" x="14.398438" y="230.123047"/> </g> attr(,"class") [1] "XMLNodeSet"
This XPath expression starts at the root node, proceeds down one step
to the root's <g> child, then down another level in
the tree to select the third <g> element, and
finally, one more level to the last child of the third
<g>. Notice that we use the XPath predicate
[3] to select the third <g> and
the XPath function last() to get the last child
element.
This particular <g> element contains the instructions for drawing the axes label "Latitude". It contains references to eight glyphs created in the <defs> node of the document and referred to via the href attribute, i.e. one glyph for each letter. The letter 'a' appears in the document as
<symbol overflow="visible" id="glyph1-8"> <path style="stroke: none;" d="M -1.253906 -1.1875 C -1.023438 -1.1875 -0.84375 -1.269531 -0.710938 -1.4375 C -0.578125 -1.605469 -0.515625 -1.800781 -0.515625 -2.03125 C -0.515625 -2.308594 -0.578125 -2.578125 -0.707031 -2.839844 ... C -2.492188 -1.042969 -2.636719 -1.398438 -2.695312 -1.839844 Z M -4.820312 -2.449219 "/> </symbol>
Finally, after we add tool tips to each of the points,
addToolTips(doc, apply(quakes[depth.ord, ], 1, function(x)
paste(names(quakes), x, sep = " = ", collapse = ", ")),
addArea = 2)
the <path> element becomes:
<path style="fill-rule: nonzero;
fill: rgb(90.196078%,90.196078%,90.196078%);
fill-opacity: 1;stroke-width: 0.75; stroke-linecap: round;
stroke-linejoin: round; stroke: rgb(90.196078%,90.196078%,90.196078%);
stroke-opacity: 1;stroke-miterlimit: 10; "
d="M 337.144531 191.292969 C 337.144531 194.894531
331.746094 194.894531 ... "
type="plot-point">
<title>lat = -20.32, long = 180.88, depth = 680, mag = 4.2,
stations = 22
</title>
</path>
The additional child element <title> was added to <path> by the addToolTips() and contains the information that will be displayed when the mouse hovers the point. This additional element is denoted by dashed lines in Figure 9, “SVG Document Tree”.
The SVGAnnotation package provides facilities for identifying elements of the SVG document that correspond to particular parts of plots. These assist developers in creating new high-level functions for annotating the output from “non-standard” plotting functions in R. We demonstrate how one might add hypertext references to polygons in a map (Example 3, “Adding Hyperlinks to Polygons”), and explore how to add tool tips to the hexagonal regions in a hexbin plot (Example 4, “Interactive Hexagonal Bin Plots”) used for displaying a scatter plot with many observations that would ordinarily overlap [21]. These examples demonstrate the use of some of the functions that are briefly described in Table 2, “ Intermediate-level functions for working with SVG elements ”.
In the section called “The SVG Grammar”, we determined the structure of an SVG document generated via libcairo from a call to the plot() function in R. To do this, we used information about the data being plotted. For example, knowing the number of observations helped us find the SVG nodes corresponding to the points. We matched the number of rows in the data frame against the number of elements in various parts of the SVG document. In this section, we continue with this approach of discovering which elements in the generated SVG correspond to particular components of a map and a hexbin plot. In addition to examining the R variable that is being plotted, we also examine the graphics object returned from the call to the R plotting function in order to uncover the structure in the corresponding SVG image. In the following example, we match the length of the vector of polygon names returned from a call to map() against the number of elements in the SVG rendition of the map to find the elements corresponding to the conceptual regions of the map, i.e. the polygons. This is a deterministic process that need only be done once because the SVG output for a particular type of plot has a very regular structure. Although regular, the structure is not a formal one that allows us to immediately identify the SVG elements corresponding to R components. The SVGAnnotation package does most of this automatically. We are describing it here to illustrate the underlying mechanism and approach so it is clearly understood and can be adapted for new types of plots.
| Function | Brief Description |
|---|---|
| getPlotRegionNodes() , getStripNodes() | Retrieves the SVG elements which house the contents of the data regions of the sub-plots within the display. This works for traditional and lattice plots, and also for histograms and bar plots. getStripNodes() finds the “strips” above each panel in a lattice plot associated with the different conditioning variables that group the observations across the panels. |
| getPlotPoints() | This function finds the nodes that correspond to the points plotted in a data region. These may be in multiple plots or panels. |
| getBoundingBox() | Computes the coordinates of the rectangle enclosing, or associated with, an SVG object such as an axis label or any text path. |
| getViewBox() | Returns a 2-by-2 matrix that gives the (x, y) coordinates of the top left and bottom right corners of the viewing area/rectangle for the entire display, with columns ordered as x and y. |
| enlargeSVGViewBox() | Changes the dimensions of the viewing box so that extra elements can be added to the document and displayed within its viewing area, e.g. a slider below a plot or check boxes alongside a plot. |
| getStyle() , setStyle() , modifyStyle() | Query, set, and reorganize the value of a style attribute of an SVG node. We use these functions to determine and set the vector of CSS-based style settings. |
| addECMAScripts() and addCSS() | These add JavaScript/ECMAScript and CSS code to an SVG document. They either directly insert the content or put a reference (href attribute) to the file/URL in the document. |
Example 3. Adding Hyperlinks to Polygons
In this example, we create a popular map of the USA where we color the states red or blue according to whether McCain or Obama received the majority of votes cast in the 2008 presidential election (see Figure 10, “Hyperlinks on regions of a map.”. These data were “scraped” from the New York Times Web Site http://elections.nytimes.com/2008/results/president/map.html.
The map in the screen shot shown here was produced by the map() function in the maps package [19]. The SVG file was then post-processed to add hyperlinks to the state regions using the addLink() function in SVGAnnotation. When the viewer clicks on a state, the browser links to the corresponding state's Web page of election results on the New York Times site, e.g. http://elections.nytimes.com/2008/results/states/california. We might also add a hyperlink for the title of the R plot, "Election 2008". We could similarly add links on axes labels on regular plots.
We use the maps package to draw the map.
doc = svgPlot({
map('state', fill = TRUE, col = stateColors)
title("Election 2008")
})
We need to determine where the polygon regions for the states are located in the SVG document so that we can add hyperlinks to them. While getPlotPoints() will find the SVG polygon elements for us, we will explore how we can find them manually. We use an XPath expression, to find all the <path> nodes in the document. As explained in the section called “The SVG display for an R plot”, the paths will be located within a <g> element two layers beneath the root node. The following XPath expression locates any <path> that has two consecutive <g> ancestors within the hierarchy from the node to the root.
polygonsPaths = getNodeSet(doc, "/svg:svg/svg:g/svg:g//svg:path", "svg")
We found 63 <path> elements,
length(polygonPaths)
[1] 63
which is the same number of polygons that map() uses in drawing the state map for the United States:
mapStateNames = map("state", namesonly = TRUE, plot = FALSE)
length(mapStateNames)
[1] 63
Note that we made this comparison by examining the value returned by the call to map() . The order here corresponds to the order in which the polygons were plotted in R.
Now that we have found the state polygons in the SVG file, we are ready to add links to them. As in Example 2, “Adding Hyperlinks to a Plot Title”, we use the function addLink() , passing it the XML elements corresponding to the polygons. We create the vector of target URLs on the New York Times web site as follows, stripping the names returned by map() to get just the state part and appending these to the relevant base URL and replacing any blank in a state name with a hyphen:
mapStateNames = gsub(":.*$", "", mapStateNames)
mapStateNames = gsub("[[:blank:]]", "-", mapStateNames)
urls = paste("http://elections.nytimes.com/2008/results/states/",
mapStateNames, ".html", sep = "")
Then we add the links:
addLink(polygonPaths, urls, css = character())
When the saved document is viewed in a Web browser, or a dedicated SVG viewer such as batik [23], a mouse click on a state will display the state's page on the New York Times Website in the browser.
We should note that, if we had not filled the polygons with color, then the interiors would not be explicitly drawn and therefore, only the boundaries (i.e. the paths) would be active. This means that the interiors of the states would not respond to a mouse click. Also, the map function draws (and returns) a different number of polygons if we fill the polygons or not. As a result, the computations would be very slightly different.
Although we have completed the task of adding links to the polygon paths, let's see how we can use the function getPlotPoints() , one of the intermediate-level facilities in the package for handling SVG nodes, to find the polygons:
ptz = getPlotPoints(doc) length(ptz)
[1] 63
all(sapply(ptz, xmlName) == "path")
[1] TRUE
We see that getPlotPoints() retrieves as many “points” as there are polygons. We can infer that this function does indeed locate the regions in an SVG map. Indeed, these high-level functions in SVGAnnotation are intelligent enough to handle many diverse types of plots and should be used in preference to low-level processing of nodes with getNodeSet() when possible.
We next look at another slightly non-standard plot where we examine the R graphics object returned from the call to hexbin() to infer the structure of the SVG document for a hexbin plot. Specifically, we determine the location of the elements in the SVG file that correspond to the hexagonal bins in the display.
Example 4. Interactive Hexagonal Bin Plots
The goal of this example is to add tool tips to a hexagonal bin plot, where a mouse over a shaded hexagonal region displays information about that bin, e.g. the number of points included in the bin (see Figure 2, “ Tool tips on a hexbin plot ”).
We begin by making the hexagonal bin plot and saving the return value from the call to hexbin() , which is an S4 object.
library(hexbin) hbin = hexbin(Occupancy, Flow)
The count slot in the hbin object is of interest to us because it contains a vector of cell counts for all of the bins, and its length tells us how many hexagons are in the plot.
length(hbin@count)
[1] 281
The length of hbin tells us that there are 281 bins in the plot. We match this against the number of children in the plotting region found by getPlotRegionNodes() to verify that these correspond to the bins:
doc = svgPlot(plot(hbin, main = "Loop Detector on I-5")) reg = getPlotRegionNodes(doc) sapply(reg, xmlSize)
[1] 281
Now that we have found the elements in the SVG document that correspond to the hexagonal bins, we can easily add tool tips to them as follows:
hexbins = xmlChildren(reg[[1]])
sapply(seq(along = hexbins),
function(i) {
addToolTips(hexbins[[i]],
paste("Count: ", hbin@count[i]), addArea = 2)
})
Additional annotations that might be included in the tool tip are the xcm and ycm slots in hbin, which hold the x and y coordinates (in data rather than SVG coordinates) for the center of mass of the cell.
So far in this paper, we have added SVG in the post-processing stage to create interactive effects with tooltips and hyperlinks. Here, with the help of JavaScript, we can create plots with more complex interactivity. JavaScript can be added to an SVG display so that we can programmatically change the value of an attribute of an element while viewing it. (For a brief introduction to JavaScript see the section called “Appendix: Basics of JavaScript”). In particular, we show how to change the attribute of an element in response to a mouse event, e.g. we can change an element's color, whether it is visible or hidden, its location, or its size. A mouse action can initiate the changing of SVG “on the fly”, or dynamically, to allow a rich set of user interactions with the plot.
In our first example we create a display that uses color to link points across multiple plots, i.e. when the mouse moves over a point in one plot, then the color changes for that point and the points for the corresponding observations in the other plots. This functionality is provided via the high-level function linkPlots() . In addition to providing an example of how to use the high-level functionality, we demonstrate the lower-level details of how to post-process the SVG document to add both annotations and JavaScript in order to enable this sort of interactivity. An important part of the post-processing stage involves adding unique identifiers to all the nodes that the JavaScript code might access and modify. These identifiers allow JavaScript to access the nodes by name rather than contextual position. The post-processing also frequently includes adding additional attributes to elements to store original values of attributes that may be dynamically modified so that we can revert these temporary changes. We use this approach in our example that demonstrates how to create a display that links observations across panels in a lattice plot.
Example 5. Point-wise Linking across Plots
In this example, we show how to use the high-level linkPlots() function to link points across plots (see Figure 3, “Linked scatter plots”). We start by creating the collection of plots. We might use a simple call to the pairs() function to create a draftsman's display of pairwise-scatter plots. Alternatively, one can create the plots with individual R commands and arrange them in arbitrary layouts with par() or layout() . We'll use the latter approach and create just two plots:
doc = svgPlot({par(mfrow = c(1,2))
plot(Murder ~ UrbanPop, USArrests, main="", cex = 1.4)
plot(Rape ~ UrbanPop, USArrests, main = "", cex = 1.4)
},
width = 14, height = 7)
The approach generalizes trivially to more than 2 plots.
The high-level function linkPlots() does the hard work for us:
linkPlots(doc) saveXML(doc, "USArrests_linked.svg")
We can then view this and mouse over points in either plot to see the linking.
In the remainder of this example, we explain how the
linkPlots()
function modifies the SVG to perform the
linking action. The key idea is that the SVG is annotated to add a
unique identifier to each SVG element that represents a point in a
plot. This identifier includes information as to which plot region and
observation the point belongs. The linkPlots()
function retrieves the points in each of the plot regions by using
getPlotRegionNodes()
. Whether we use
pairs()
or par(mfrow = ...), the
points within each plotting region appear in a regular order; that is,
the first element in each plotting region corresponds to the first row
in the data frame, the second element to the second row, and so
on. Thus elements across plots can be matched by simply using the
order in which they appear in their respective plotting regions, assuming
that they come from the same data frame or have a correspondence by
row. The identifiers added to the points are of the form
plotI-J where I is the plot index and
J is the observation index within the plot. This unique
identifier is added as an id attribute on each of
the SVG elements. This identifier makes it easy for the JavaScript
function to find the elements to be linked.
In addition, the linkPlots() function in SVGAnnotation adds onmouseover and onmouseout attributes to each point element in the SVG document. The value of each of these attributes is a JavaScript function call to perform the linking and unlinking action, e.g. to change the color of the linked points. Below is the augmented SVG node that corresponds to the 10th point in the first plot. This is the point colored red in the left-hand plot in Figure 3, “Linked scatter plots”.
<path style="stroke-width:0.75; ... " d="M 260.417969 72.800781 C 260.417969 77.839844 252.855469 ..." id="plot1-10" onmouseover="color_point(evt, 10 , 2 , 'red' )" onmouseout="reset_color(evt, 10 , 2 )" fill="none" originalFill="none"/>
In addition to annotating the point elements in the document with all
of these attributes (id,
onmouseover, onmouseout, and
originalFill), the linkPlots()
function adds the JavaScript code for the two functions
color_point and reset_color to
the SVG document. The code appears in a <script> node
within the SVG document. Note that these JavaScript functions are
relatively general and available in the package for making customized
plots. (For more information about JavaScript, see the section called “Appendix: Basics of JavaScript”.)
The principles we established for annotating the SVG to link points across plots carry over to more general settings. The basic ideas used in linkPlots() are to post-process the document so that elements in the SVG are easily identified and changed at the time of viewing and to create JavaScript functions that are not dependent on a particular set of data. This approach can be summarized by the following basic actions performed in the annotation stage:
Within R, add unique identifiers to the relevant graphics elements in the SVG document so they can
be retrieved easily in the viewing stage (via the JavaScript function
getElementById).
Create special attributes to store the default values for settings that are subject to change, e.g. original-style. These attribute names are made up by the developer, and should not conflict with SVG attribute names.
For the "action"/interactive elements, set up event attributes on the elements. The attribute values are JavaScript function calls. Our philosophy is to pass all element-specific information needed to respond to a request/mouse-event in the call. This way, the JavaScript functions can be used in other situations, e.g. with other data and other types of plots and not rely on auxiliary global variables.
Embed in the document the JavaScript functions that modify and reset the element attributes.
In this section, we demonstrate how to use the basic approach just outlined to incorporate JavaScript into SVG. We create a customized event handler that extends the notion of linked plots to lattice plots that are linked via an interactive legend.
Example 6. Linking across lattice plots
In this example, we follow the steps listed above to customize a JavaScript event handler. In particular, we show how you might extend the linking type of interactivity in Example 5, “Point-wise Linking across Plots” to lattice plots. We add a legend to a lattice plot that responds to mouse-over events by highlighting the observations in each panel of the plot that belong to the group specified in the legend. The elements of the legend correspond to levels of a variable (gear) that is not displayed in the lattice plot, i.e. not by color or glyph/plotting character within the panels.
The scatter plots show the relationship between horsepower and miles per gallon for automatic and manual transmission cars. The points within each panel are colored based on the number of cylinders the engine has (4 cyan, 6 pink, and 8 green). The legend above the plots lists the possible number of gears, 3, 4, and 5. The mouse in this figure hovers over the 4-gear group, making the points belonging to that group highlighted. This interactivity is accomplished by changing the style attribute of the points via JavaScript. When the mouse moves off the legend, the original styles for these points are restored.
We begin by making the plot. (The cars data frame is a copy of mtcars where the cyl and am variables have been turned into factors with more meaningful labels). We use simpleKey() to add a legend to the lattice plot:
groups = sort(unique(mtcars$gear))
labels = paste(groups, "gears", sep = " ")
doc = svgPlot(xyplot(mpg ~ disp| am, groups = cyl, data = cars,
key = simpleKey(text = labels,
columns= length(labels),
points= FALSE)))
There are 2 plotting regions in the SVG document, one for each panel. Each plotting-region element contains its points as child elements and we locate the entire collection of the corresponding SVG elements with
panels = getPlotRegionNodes(doc) points = unlist(lapply(panels, xmlChildren), recursive = FALSE)
Now that we have the point elements, we can proceed with the first
step in creating our own event handler, and augment them with unique
identifiers. We create an identifier based on the point's index
within its gear-group and panel, e.g. id="2-4-1"
is the identifier for the first observation in the four-gear group in
the second panel (i.e. the manual transmission cars). We use the
addAttributes()
in the XML package to
add the id attribute as follows
ids = by(cars, list(cars$gear, cars$am),
function(x) paste(as.integer(x$am), x$gear,
1:nrow(x), sep = "-"))
uids = unlist(ids)
mapply(function(node, id)
addAttributes(node, id = id),
points, uids)
Note that we figured out the ordering of the points in the SVG document by experimenting with the SVG for xyplot() . We programmatically changed the color of a point in the SVG, and viewed the modified document to determine that the points for each group appear sequentially within the document.
After exploring the document more, we know that all of the elements of the plot's legend are sibling nodes of the plotting regions. They are the last items to be drawn and so are the last children of the top-level <g> element in the SVG document. A simple XPath expression can be used to locate them:
nodes = getNodeSet(doc, "/x:svg/x:g/x:g", "x") nodes = nodes[-(1:(length(nodes) - length(unique(cars$gear))))]
Alternatively, we could use getLatticeLegendNodes() which does this more robustly.
Now we are ready for the third step in the process: augmenting the
legend labels with JavaScript calls for mouse events. We have skipped
the second step, that of preserving the default style values, because
we will take care of it at viewing time with our JavaScript function
highlight. The highlight
function will highlight or un-highlight the relevant points and it
will save and restore the default styles of the points. It is called
with 3 arguments: the first argument indicates the gear-group; the
second argument is an array giving the number of elements within that
group in each panel; and, the third argument indicates whether the
event is a mouse over (true) or a mouse out (false). So we want to
generate JavaScript code such as highlight(4, [4, 8],
true). Notice that we represent arrays in JavaScript using
square bracket delimiters (e.g. [1, 2]). We
construct these simple arrays with direct calls to
paste()
. For more complex R objects, we would be
advised to use the RJSONIO package to serialize R
objects to JavaScript object notation (JSON).
To generate the values for these arrays, we need to know how many points are in each group within each panel. We get a frequency table with this information via
counts = table(cars$am, cars$gear) counts
3 4 5
automatic 15 4 0
manual 0 8 5
These counts for the number of cars for each gear-transmission combination will be used to construct the code for the mouse event. We do this as follows:
sapply(seq(along = 1:ncol(counts)),
function(i) {
cts = paste("[", paste(counts[,i], collapse = ", "), "]",
sep = "")
addAttributes(nodes[[i]],
onmouseover = paste("highlight(", groups[i], ",",
cts, ", true)"),
onmouseout = paste("highlight(", groups[i], ",",
cts, ", false)")
)
})
For the fourth and last step, we add to the SVG document the
JavaScript function definition for highlight which is
located in a separate file. The addECMAScripts()
function takes care of this for us:
addECMAScripts(doc, "multiLegend.js") saveXML(doc, "mt_lattice.svg")
We now examine the JavaScript function highlight to
see how it handles the interactivity. Recall that it is called with
the index of the group to be highlighted/un-highlighted and the array
giving the counts of the number of points in that group within each
panel. So the function, shown below, iterates over each panel and
each point within the desired group in that panel and constructs the
corresponding id value for the affected point. It
then retrieves the SVG object with that identifier using the
getElementById Javascript method. This method is
very convenient because it retrieves a node in the SVG document by
specifying the value of its id attribute. So our
highlight function is defined as
function highlight(group, pointCounts, status)
{
var el;
var i, numPanels = pointCounts.length;
for(panel = 1; panel <= numPanels; panel++) {
for(i = 1; i <= pointCounts[panel-1]; i++) {
var id = panel + "-"+ group + "-" + i;
el = document.getElementById(id);
if(el == null) {
alert("can't find element " + id)
return(1);
}
highlightPoint(el, status);
}
}
}
Once an element is retrieved, another JavaScript function -
highlightPoint - is called to change the appearance
of that point.
The highlightPoint function modifies the value of
the style attribute for an element. In the
previous examples, we have handled a style change by adding a specific
presentation attribute to the element, e.g. adding a
fill attribute to change the fill color. Here we
use regular expressions (in Perl format) to process the value of
the style attribute. We break the character string into
individual components, change the fill component, and create the new
in-line style string. At the same time, we make sure to save the
original style information in order to restore it as needed. It is
saved in an attribute we made for this purpose, dubbed
original-style. The
highlightPoint function is defined as follows.
function highlightPoint(el, status)
{
var old = el.getAttribute('original-style');
if(status && old == null)
el.setAttribute('original-style', el.getAttribute('style'));
if(status) {
var cur = el.getAttribute('style');
var tmp = cur.replace(/fill: [^;]+/, "fill: black");
var tmp = tmp.replace(/stroke-width: [^;]+/, "stroke-width: 2");
el.setAttribute('style', tmp);
}
else
el.setAttribute('style', old);
}
In this section, we use a different approach to creating interactivity with JavaScript. In the section called “Mouse events and JavaScript” we used JavaScript to simply alter the attributes of existing elements. That is, all of the computations on the data were done in advance, in R, in either the plotting stage or the annotation stage, and the JavaScript functions simply changed and reset attributes. Here, in the annotation stage, we place R objects in the SVG document as JavaScript variables, and in the viewing stage, we use JavaScript and these variables to create new shapes in the display. We provide two examples. In the first, we draw line segments on a scatter plot to connect a point to its nearest neighbors. The information as to which points are nearest others is calculated in R and placed in the SVG document as JavaScript variables. Now drawing is done in two places, in R via the plotting routines, and in JavaScript at the time the document is viewed (and R is no longer available). If we had made this plot using the earlier approach, we would have drawn all possible line segments in R, hidden them within the plot, and the JavaScript would respond to mouse-over and out events by modifying element attributes in order to show and hide the line segments. But in the approach here, we draw new lines as they are needed and then discard them.
Example 7. Interactive Nearest Neighbors
In this example, we use JavaScript to dynamically (i.e. at viewing time) augment a scatter plot so that it displays the four nearest-neighbors to a point. That is, when the mouse moves over a point, new line segments are drawn from that point to its four nearest neighbors (see Figure 12, “ Interactive display of a point's 4 nearest neighbors. ”), and when the mouse moves off the point, the lines are removed. To do this, we use JavaScript to lookup the elements in the SVG display that correspond to the nearest points, and for each of these neighbors, we add new <line> elements from the selected point to the neighbors. We are not adding XML content to the original SVG document, but rather adding line objects to the JavaScript rendering of the SVG display.
This scatter plot can show the four nearest neighbors for any point. When the mouse moves over a point, a JavaScript function is called to determine the coordinates of the 4 nearest neighbors and draw line segments from the active point to its neighbors. The SVG document contains JavaScript variables that hold the indices of the neighbors for each point, in order from closest to farthest. The nearest neighbors are defined by Euclidean distance in the mpg and wt dimensions. Notice that the scales on the two axes are not the same and so the aspect ratio for the plot is not 1. This can yield a somewhat misleading display of the nearest neighbors.
We begin by creating the basic scatter plot:
doc = svgPlot(plot(mpg ~ wt, mtcars,
main = "Motor Trend Car Road Tests",
pch=19, col= c("green", "blue")[(am+1)]))
Then in the annotation stage, we add unique identifiers to the points in the plot. We add an id attribute to each point that simply has the index of the observation in the data frame. Since JavaScript uses 0-based indexing, we find it easier to start the indexing at 0, e.g.
ptz = getPlotPoints(doc, simplify = FALSE)[[1]]
sapply(seq(along = ptz),
function(i)
addAttributes(ptz[[i]], id = i - 1)))
We also add calls to the JavaScript functions
(showNeighbors and hideLines) to
handle the mouse-over and mouse-out events on each point in the plot:
sapply(seq(along = ptz),
function(i) {
addAttributes(ptz[[i]],
onmouseover = "showNeighbors(evt, k, neighbors)",
onmouseout = "hideLines(evt)")
})
Also in the annotation stage, we calculate the distances between points, and we use these distances to identify the nearest neighbors as follows:
DD = as.matrix(dist(mtcars[, c("mpg", "wt")]))
D = t(apply(DD, 1, order)) - 1
Notice that we use the order of the observation in the data frame so that it will match the identifier that we added to the point in the plot.
We make this information about a point's neighbors available to the
JavaScript code as a two-dimensional JavaScript array, serialized from
R. The following call to addECMAScripts()
illustrates
how we serialize the R matrix D to JavaScript as the
variable neighbors, in addition to adding the
JavaScript code from the file knn.js:
dimnames(D) = list(NULL, NULL) addECMAScripts(doc, "knn.js", TRUE, neighbors = D)
The addECMAScripts()
function has a ... argument
that accepts R objects in the form name = value.
These objects are added to the SVG document as JavaScript variables
with the argument name used as the name for the JavaScript
variable. The TRUE value for the third argument indicates we want
the contents of the JavaScript file to be copied into the SVG rather
than supplied via a link. We choose to copy the contents to make the
resulting SVG file independent of other auxiliary files. The
JavaScript functions showNeighbors and
hideLines are in the file knn.js.
The showNeighbors function has three
arguments: the event object, the number of neighbors of interest, and
a two-dimensional array identifying the nearest neighbors for all
points. The array is a JavaScript variable that contains the contents
of the R matrix D. Notice that the data are separated
from the function and explicitly passed as arguments in the function
call so that the function can be used with other data in other
contexts. With its helper functions, showNeighbors
retrieves the index of each of the k neighboring
points from the neighbors array; extracts the
corresponding JavaScript SVG object and gets its coordinates in the SVG
canvas; and then creates a new line segment between the neighboring point
and the active point. The line segments run from the center of the
active point's symbol to the center of each of the neighbors. The
function puts these line segments inside a new group element
(corresponding to a <g> node), which facilitates
removing the set of lines when the mouse moves off the point.
We provide here the complete code for one of the helper functions,
addLines. We include it to show how JavaScript
methods are used to construct new elements in the display:
function addLines(obj, neighbors, numNeighbors)
{
var x, y, x1, y1;
var tmp = obj.getBBox();
x = tmp.x + tmp.width/2;
y = tmp.y + tmp.height/2;
lineGroup = document.createElementNS(svgNS, "g");
obj.parentNode.appendChild(lineGroup);
var ids = obj.getAttribute('id') + ": ";
for(var i = 1; i <= numNeighbors ; i++) {
var target;
target = document.getElementById(neighbors[i]);
ids = ids + " " + neighbors[i];
tmp = target.getBBox();
x1 = tmp.x + tmp.width/2;
y1 = tmp.y + tmp.height/2;
var line = document.createElementNS(svgNS, "line");
line.setAttribute('x1', x);
line.setAttribute('y1', y);
line.setAttribute('x2', x1);
line.setAttribute('y2', y1);
line.setAttribute('style', "fill: red; stroke: red;");
line.setAttribute('class', "neighborLine");
lineGroup.appendChild(line);
}
window.status = ids;
}
Note that although libcairo uses only <path> elements
to draw objects, we can use higher level elements such as
<line> elements in our JavaScript drawing. Also note
that when we create the new nodes, we must include the name space or
otherwise the node is not recognized as SVG. The line segments are
put into a group that is stored as a global variable in JavaScript.
This makes it easy to to remove the lines in one operation when the
mouse moves off the point. This is illustrated by the definition of
the hideLines function which simply checks the
value of the variable lineGroup and removes its
children if it is an existing node:
function hideLines()
{
if(typeof lineGroup != "undefined") {
lineGroup.parentNode.removeChild(lineGroup);
lineGroup = document.createElementNS(svgNS, "g");
}
}
This example has demonstrated how to use JavaScript to dynamically manipulate an SVG display using data created in R when the plot was originally created. Again, in the viewing stage, when the JavaScript is running, the original data in R are not available. The matrix of ordered neighbors for each point that was computed in R is available within the JavaScript code. If the script needs any of the additional data, then they must be placed in the document in the annotation stage.
An alternative approach to the above example is to add, in the annotation stage, all of the nearest neighbor lines for all of the points. We would set the visibility style attribute to "hidden" so these line segments would not be displayed when the document is loaded by the viewer. Then, a point's onmouseover function call would identify the appropriate line segments emanating from the point to its nearest neighbors and change their visibility attribute to "visible". Similarly, the onmouseout event would change them back to “hidden”. This alternative approach is similar in spirit to those examples in the section called “Mouse events and JavaScript”, i.e. JavaScript functions do little other than change attribute values on nodes in the document. There are run-time benefits because we don't have to create the lines each time the user moves over a point. However, there are many more lines in the display and the loading of the SVG file may be slower. This trade-off involves where we do the computations. The approach that leaves the line creation to JavaScript also generalizes to allow dynamic specification of the number of nearest neighbors, e.g. with a slider or HTML spin box.
Example 8. Highlighting Nodes and Edges in a Graph
In this example, we explore how to annotate an SVG document created via Rgraphviz [15] to make the graph interactive. Specifically, when the mouse moves over a node in the graph, the node and its connections to other nodes in the graph are highlighted. In addition, the other connections between nodes in the graph recede in appearance (see Figure 13, “ Interactive nodes in a graph ”).
This graph is made using the Rgraphviz package. The graph has been annotated to make it interactive, where a mouse-over event on a node brings that node, its edges, and connecting nodes to the forefront, and the remaining nodes and edges recede into the background. This interactivity relies on JavaScript. The mouse-over event triggers a call to a JavaScript function that finds the edges and nodes that need to be modified and changes their color by modifying the corresponding SVG elements. The information identifying the connections between nodes is supplied via JavaScript variables whose values were computed in R and stored in the SVG document as JavaScript variables.
This particular graph is not of great interest, being a simple random graph. The ideas apply to more interesting data such as package dependencies, call graphs illustrating which functions call which other functions. Creating aesthetically pleasing displays from this data is a separate task from making them interactive. The same principles and steps apply however.
We make a simple graph with 10 nodes using the graph package.
library(graph) library(Rgraphviz) set.seed(123) V = letters[1:10] M = 1:4 g1 = randomGraph(V, M, 0.8)
We use a circular layout of the nodes, using the “twopi” type of layout, and examine the resulting SVG content. We do this with
layout2pi = agopen(g1, layoutType = "twopi", name = "bob") doc = svgPlot(plot(layout2pi))
top = xmlRoot(doc)[["g"]][["g"]]
We have 65 nodes here; 10 are <g> nodes and the remainder are <path> elements as we can see from
table(names(top))g path 10 55
In this layout, the first 10 SVG elements in the graph specify the text labels for the graph's nodes. The remaining 55 elements correspond to the 10 circles, one for each node, and the 45 edges corresponding to the 10 choose 2 undirected connections.
The layout2pi variable actually contains all the information about the positions of the nodes and edges. renderGraph() uses this to draw the graph using R's graphics primitives (circle, line, text, etc.). So rather than using R's graphics and post-processing the resulting SVG document, we might consider creating the SVG directly from the layout information returned by agopen() . We wouldn't have to identify the node and edge elements in the SVG tree as we would explicitly create them. We could give them meaningful id attributes to make annotating the SVG content easier. This is a reasonable approach but it requires that we permit the R user to specify the color, font family and size, line types and widths, etc. for the nodes and edges. Furthermore, the R user would not be able to place additional elements on the plot with regular R graphics functions. So it is best to work with the SVG document that R creates.
We can however make use of the Ragraph object (returned by agopen() ) to identify the elements in the SVG. For example, we can look at the edges in the AgEdge slot of the layout. This is a list with 45 elements. The order of the elements in the top-level SVG <g> element corresponds to the order of the edges in this list.
Now that we have some understanding of the structure of the SVG
document and how to identify the nodes and edges, we can proceed with
our goal of making the display interactive. We add code to the
document that allows the user to mouse over/click on a node and
highlight the associated edges. The action is done via JavaScript.
We start by identifying each node by a number, its index in the list
of nodes. To highlight the node and edge, we call the function
highlightEdges, passing it that index. The
highlightEdges function determines the ids of the
edges associated with that node and uses these to retrieve the corresponding element
in the display. It then sets their color attribute to, e.g., red. The
function also changes the color of the other edges and nodes to make
them less visible. When the mouse leaves the node, we set the color
of the edges back to their original colors.
The first step is to add an id attribute on each of the SVG elements corresponding to the nodes and edges. The function addGraphIds() does this for us, e.g.,
ids = addGraphIds(doc, layout2pi)
This uses the node labels as the node ids. The edge ids are of the form, "edge:sourceId-destinationId", e.g., "edge:a-b", identifying the two end points with their ids and indicating the direction of the edge.
To implement highlightEdges, we need information
for each node giving the ids of the associated edges. It is natural
to represent this in R as a list with each element being a character
vector giving the edge identifiers corresponding to the node. This is
relatively straightforward to create in R using
getEdgeInfo()
(in SVGAnnotation).
However, we need it in JavaScript.
The addECMAScripts()
function will take care of serializing
this R object in a suitable
form so that it can be used directly in JavaScript code.
It uses the toJSON()
function in the RJSONIO
package to do this. The following illustrates
the form in which it will appear in the JavaScript code:
cat(toJSON(getEdgeInfo(rGraph)))[ ["edge:a-c","edge:a-d","edge:a-e","edge:a-g","edge:a-h","edge:a-i", "edge:a-j","edge:a-b","edge:a-f"], ["edge:b-a","edge:b-c","edge:b-d","edge:b-e","edge:b-f","edge:b-g", "edge:b-h","edge:b-i","edge:b-j"], ... ["edge:j-a","edge:j-c","edge:j-d","edge:j-e","edge:j-g","edge:j-h", "edge:j-i","edge:j-b","edge:j-f"] ]
Now that we have arranged for the node-edge information to be
available to the highlightEdges function, the JavaScript
code to manipulate this is, at its very basic, like the following:
function highlightEdges(evt, row, color)
{
var labels = edgeTable[row];
if(undefined(color))
color = "black";
/* highlight the node. */
evt.target.setAttribute('style', 'fill: ' + "red");
/* highlight this node's edges */
for(var i = 0; i < labels.length; i++) {
var el = document.getElementById(labels[i]);
setEdgeStyle(el, "stroke: " + color + add);
}
/* hide the other edges */
labels = edgeDiff[row];
var stroke = "lightgray";
for(var i = 0 ; i < labels.length; i++) {
var el = document.getElementById(labels[i]);
setEdgeStyle(el, 'stroke: ' + stroke + add);
}
}
We have omitted some of the details related to creating
the style string in setEdgeStyle that are similar to the
JavaScript regular expression processing in a previous example. The full function is available from the package's
Web site and in the package itself.
Given this JavaScript function, we are ready to put all the pieces together to annotate the SVG document. The steps are: i) put mouse event handlers on the SVG elements corresponding to the nodes in the graph, ii) compute the information about the edges for each node, iii) add the code and this edge information data to the SVG document. We do this as
ids = addGraphIds(doc, layout2pi)
els = getNodeElements(doc) # get SVG elements for graph nodes
sapply(seq(along = els),
function(i)
addAttributes(els[[i]],
onmouseover = paste("highlightEdges(evt, ", i - 1, ", 'red');"),
onmouseout = paste("highlightEdges(evt, ", i - 1, ");")))
# Setup "hiding" the other edges when we highlight these ones.
info = getEdgeInfo(rGraph) # from the graph package.
otherEdges = lapply(info,
function(x)
setdiff(ids$edgeIds, x))
addECMAScripts(doc, "highlightEdges.js", TRUE,
edgeTable = info, edgeDiff = otherEdges)
saveXML(doc, docName(doc))
Note we pass the edge information to JavaScript
via addECMAScripts()
and give create JavaScript
variables named edgeTable and edgeDiff.
SVG supports two types of animation: declarative, which solely uses SVG facilities; and scripted, which relies on JavaScript. In this section, the focus is on the declarative approach, however, we present examples of both. The SVG animation facilities make it quite easy to produce animations similar to GapMinder, by simply annotating a scatter plot display with animation elements.
The basic concept of a “pure” SVG animation is that animation elements provide directions on how to move an object, or group of objects, and how to change the shape or appearance/style of an object. These animation elements are added as children of the object that is being animated. The animation tag names are <animate>, <animateMotion>, <animateTransform>, <animateColor>, and <set>. These tags act on a particular attribute of the parent object. For example, the <animateTransform> tag can be used to change the width attribute, causing the object to grow or shrink, or it can change the visibility attribute in order to make an object become hidden or visible. As another example, with <animateMotion>, we can specify a new location for the object, which causes the object to move to a new position on the display.
To get an idea as to how this works, the following SVG animation takes two seconds to move a circle from its original location at the time of loading to a new location and simultaneously shrink the circle from a radius of 5.4 to 3.
<circle x="95.1" y="369.8" r="5.4"
style="fill-rule: nonzero;
fill: rgb(100%,0%,0%); ...">
<animateMotion id="move1" from="95.1, 369.8" to="66.7, 412.7"
fill="freeze" dur="2s" begin="0s"/>
<animateTransform attributeName="transform" type="scale"
fill="freeze" to="3" dur="2s" begin="0s"/>
</circle>
This mini-animation is the building-block for the scatter-plot animation shown in Figure 4, “Scatter plot animation”. There, each point represents a country, and the x and y locations correspond to the country's average life expectancy and income for a particular decade. As the points move, they grow or shrink according to the change in population size from one decade to the next. The animation is described in more detail in Example 9, “Animating Scatter Plots through snapshots in time”.
Notice that the movement of the circle and the changing of its size
are coordinated by specifying that both animations are to begin at the
same time (begin is at 0 seconds, i.e. at load
time) and take the same amount of time (dur is 2
seconds). With the <animateMotion>, it is also
possible to specify the path the circle would take in traveling from
one location to the other. Any animation element requires a
dur attribute to specify its duration. The value
can be provided in seconds, e.g. dur="10s", or
minutes, e.g. dur="2min". It is also possible to
use clock values, e.g. dur="2:10" for 2 minutes
and 10 seconds. The SVG clock starts ticking when the document has
completed loading.
In addition to specifying the duration of an animation, it is also possible to specify when the animation is to begin or end. The values of these attributes can be absolute values, as with dur, or they can be relative to the start or end of another animation. For example, if we change the begin value in our <animateTransform> to "move1.end+3s", then the circle would start shrinking 3 seconds after it finished moving to its new location, i.e. 3 seconds after the animation corresponding to the animation element with an id attribute value of "move1" ends. This can be useful when synchronizing parts of an animation.
The <set> element behaves somewhat differently in that it is used to change attribute values such as fill. For example, the color of the circle could be changed when it reaches its new location by embedding the following element as a child of the <circle> as follows:
<set attributeName="fill" fill = "freeze"
attributeType = "CSS" to = "#FFDB00FF"
begin = "move1.end"/>
The attributeName specifies which attribute on the parent node is to be “animated”. The to attribute indicates the new color of the circle. The change of color begins when the circle has finished moving. The fill attribute on the <set> tag is not to be confused with the fill attribute of the circle. The <set> tag's fill attribute refers to what is to happen at the end of the animation; the value of "freeze" indicates that the final value should be kept. Without it, the attribute being animated would return to its original value, i.e. the color would change back to the color before the animation started.
One additional point to note is that the attributeType attribute in the <set> element is used to indicate where the attribute being changed can be found. That is, a value of "CSS" means that the fill color is located in the style attribute of <circle>. If the circle had specified the value of its fill color in a presentation attribute, then we would have given the attributeType value as "XML".
The animate() function in the SVGAnnotation package takes a scatter plot and moves the points around the canvas through a sequence of steps. In each step, a point moves continuously from one position to another; then in the next step, the point moves from there to yet another location. We implement it by adding <animateMotion> elements to each of the SVG elements corresponding to points in the plot. Each “point” in the SVG document will have as many <animateMotion> elements as there are time steps. In addition, if we want the size of the points to represent the value of an additional variable, which also changes in time, then we can specify the radii of the circles in each stage. The animate() function will add <animateTransform> elements to the display to change the circle sizes, and if the point is to change color, <set> elements are also added. These various options are specified in the function call.
The animate() function takes as an argument the SVG display of the starting scatter plot. It could in fact create the plot itself based on the data for the first time step, but the caller will most likely want to create the plot in advance in order to customize its appearance (e.g., title, axes labels, lines, etc.). A second argument provides the data to be animated. The data are provided as a data frame giving the locations of the points in the scatter plot for the different steps. It is structured as a column of x values and a column of y values for each step stacked on top of each other. In addition, the which parameter to animate() indicates to which step the points belong. We can provide radii for the circles via the radii parameter, if we want to change the size of the circles, and colors for each stage can be specified via the colors parameter. We demonstrate some of these features and some implementation issues in the next example, Example 9, “Animating Scatter Plots through snapshots in time”.
Example 9. Animating Scatter Plots through snapshots in time
In this example, we display 11 decades of United Nations data via a scatter plot animation shown in Figure 4, “Scatter plot animation”. The following is a snippet of the data we use,
yr country longevity income population
1 1900 Argentina 39.700 4708.2323 6584000
2 1900 Australia 63.220 6740.9394 4278000
3 1900 Austria 42.000 5163.9268 6550000
4 1900 Bangladesh 22.000 794.8383 31786000
5 1900 Belgium 51.110 4746.9662 7478000
6 1900 Brazil 32.900 705.4758 21754000
7 1900 Bulgaria 43.300 1578.9537 4000000
...
390 2000 Netherlands 80.000 35653.3938 16491461
391 2000 Norway 80.550 48264.6727 4610820
392 2000 Peru 69.906 6875.5102 28302603
393 2000 Philippines 70.303 3030.8802 89468677
394 2000 Portugal 78.920 20149.0828 10605870
395 2000 United Kingdom 78.471 32334.5334 60609153
396 2000 United States 77.890 42445.7000 298444215
We want to plot the variable income along the X axis and longevity along Y; we use yr to indicate the time/step; and population determines the radius of the circle. These data have many missing values, and most of the work for us is in cleaning up that data and formatting it for animate() .
Once the data are prepared, we plot the points for the first decade, which creates the starting frame of the animation. We control much of the layout, placing the tick marks, grid lines, and labels ourselves, as shown here
doc = svgPlot( {
plot(longevity ~ income,
subset(gapM, yr == 1900 & country %in% ctry),
pch = 21, col = colsB, bg = colsI,
xlab = "Income", ylab = "Life Expectancy",
axes = FALSE,
xlim = c(-400, 50000), ylim = c(20, 85) )
box()
y.at = seq(20, 85, by = 5)
x.at = c(200, 400, 1000, 2000, 4000, 10000, 20000, 40000)
axis(1, at = x.at, labels = formatC(x.at, big.mark = ",",
format = "d") )
axis(2, at = y.at, labels = formatC(y.at) )
abline(h = y.at, col="gray", lty = 3)
abline(v = x.at, col="gray", lty = 3)
legend(35000, 40, longCont, col = colB, fill = colB,
bty = "n", cex = 0.75 )
})
Here gapM holds the properly formatted data frame, and ctry the vector of countries to be included in the animation. We would also like to provide tooltips for each of the points that display the particular country's name. We annotate the plot using addToolTips() as follows
addToolTips(doc, as.character(gapM$country[gapM$yr == 1900 & gapM$country %in% ctry]))
The initial view or starting point of the animation is now in the variable doc. We provide this to animate() , along with the subsequent slices of the data for each decade. We specify the duration of the animation via the interval parameters ( the total duration of the animation can also be specified via dur), and the radii of the circle at each time period (e.g. decade) is provided in radii. The call to animate() is
animate(doc,
data = gapM[gapM$country %in% ctry, c("income", "longevity")],
which = gapM$yr[gapM$country %in% ctry],
dropFirst = TRUE,
labels = seq(1900, length = 11, by = 10),
begin = 0, interval = 3,
radii = radL[ctry])
The animate() function also needs to know the range of the horizontal and vertical data used to create the initial plot. If this is not the range of the entire data, the caller needs to specify these explicitly. Furthermore, background labels and point colors can be changed at each time period; these are specified via labels and colors, respectively.
animate() handles the complication that arises from the coordinate system of the data being different from the SVG coordinate system. The data values must be transformed into the SVG coordinate system before we add an <animateMotion> node to move the points to new locations. Below is a snippet of the code in the animate() function that does this.
rect = getBoundingBox(plotRegion)
transformX = function(vals, xlim, rect) {
(vals - xlim[1]) *
(rect[2, 1]- rect[1, 1])/(xlim[2] - xlim[1]) +
rect[1, 1]
}
The getBoundingBox() function returns the limits of the plotting region in the SVG coordinate system, and these are used to transform the data values (in vals). If the data are to be plotted on a log scale, then the data must be transformed as well. That is, the parameter log in plot() will create a mismatch between the scale of the data and the plotting region. Instead, the caller will need to take logs of the data and handle the specification of the tick mark labels if they are to appear in unlogged units.
A second complication arises due to the SVG element for drawing the plotting symbol being a <path> rather than a <circle>. The animate() function examines the path to determine whether or not the plotting symbol is a circle. If the radii parameter is provided and the plotting symbol is a circle, then the <path> is replaced by a circle so that it can grow and shrink as it moves through the stages of the animation. Currently, all other symbols will move around the canvas and the radii will be ignored, but a scaling transformation is possible.
In this section, we examine how we can animate plots using JavaScript, explicitly via programmatic changes to the display at viewing time. There are some kinds of animations that are difficult to achieve with the declarative approach of the SVG model; for example, to change a path dynamically (i.e. at run/viewing-time) requires scripting. We provide a simple example to illustrate a few of the ideas in the JavaScript approach.
The setInterval function is key to using JavaScript
for animation. This function sets a timer to go off at repeated
intervals. When it does, previously specified JavaScript code is
evaluated. For example, in the code
animationHandle = setInterval("animationStep(polyIds, stateResultsByYear,
yearLabels, polyStateNames)", Interval);
the setInterval function is called with the values
"animationStep(polyIds, stateResultsByYear, yearLabels,
polyStateNames)" and Interval. This means that every
Interval milliseconds, the specified call to
animationStep will be made.
The return value from the setInterval function is a
reference to the timer just established. We assign this to the global
variable animationHandle. To stop the animation, we
can call the JavaScript function clearInterval with
animationHandle as its argument and the timer will be
removed.
Previously we encountered the use of scripts to interact with an SVG
graphic in the section called “Mouse events and JavaScript”. There, a command was
called as the result of a mouse event, such as a mouse-over and
mouse-out. In addition, there are the click, mouse-down, and mouse-up
events associated with the mouse button. Another event that can be
used to trigger a function call is “load”, which occurs
when the SVG document has been loaded and rendered in the viewer.
Each of these events has a corresponding attribute,
e.g. onmouseover, onclick,
and onload, and the value of the attribute is one
or more JavaScript statements to be executed when the event occurs
(see Example 7, “Interactive Nearest Neighbors” and Example 5, “Point-wise Linking across Plots”). Any of these events can also be used
to initiate an animation. Below, we place the
setInterval within an init
function, and have it called at the time the SVG document is loaded,
i.e. the top of the document appears as
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="504pt" height="504pt" viewBox="0 0 504 504"
version="1.1" onload="init(evt)">
The key attribute is onload. The corresponding JavaScript code is
var animationHandle;
function init(evt) {
...
animationHandle= setInterval("animationStep(polyIds,
stateResultsByYear, yearLabels, polyStateNames)", Interval);
}
The following example demonstrates how to use the timer to create an animation.
Example 10. Animation with JavaScript & SVG
We will animate a political map of the states within the USA. We color each state based on whether the majority voted for a Republican (red) or a Democratic (blue) presidential candidate. For any given year, this gives us a visualization of the geographical voting patterns. We have this information for each presidential election from 1900 onwards. (This information can be obtained from http://electionatlas.org.) We can use these data to animate the map over time and illustrate how some states change and others remain reasonably constant over time.
The data are in stateResultsByYear, a named list with an element for each of the presidential elections. Each such element is a character vector giving the colors for each of the states. The names on this character vector identify each state. The names of the stateResultsByYear list identify the election year, e.g. 1900, 1904, ..., 2008.
We start by using R to plot the map for the election in 1900. Also, we want our animation to start when the viewer clicks on a label titled "Start" so we add this text to the lower left corner of the plot.
library(maps)
doc = svgPlot({
m <- map('state', fill = TRUE, col = stateResultsByYear[[1]])
title(names(stateResultsByYear)[1])
text(m$range[1] + 3, m$range[3] + 1, "Start", col = "green")
})
At regular intervals, e.g. 1 second, we will update the title to the next election year and then change the color of the polygon(s) for each state. The title element is reasonably easy to find using getAxesLabelNodes() :
labels = getAxesLabelNodes(doc)
However, we are going to change the nature of this title node to be a regular text element rather than a path element. The reason for this is that it is significantly simpler to modify the value of the text node than that of a path which represents text:
title = asTextNode(labels[[1]][[1]], "1900")
We put an id attribute on the new title element, which is the first element of the labels list, so that we can retrieve it with JavaScript code at viewing time.
xmlAttrs(title) = c(id = "title")
We add an id attribute to the
“Start” label, but also make it respond to
onclick events. This causes the state of the
animation to change from stopped to running, running to paused, or
from paused to running, and to also change the text of this label
appropriately. We do this with the JavaScript function
toggleAnimation and add the call to the label with
start = asTextNode(labels[[2]][[1]], "Start") xmlAttrs(start) = c(id = "Start", onclick = "toggleAnimation(evt)")
We'll also add a tooltip to this label which will both provide information to the viewer but also make the entire label (and not just the path) active. So we add this and the CSS file which takes care of the appearance of the underlying rectangle:
addToolTips(labels[[2]], "Start or pause the animation") addCSS(doc)
The next step is to add an identifier to each polygon on the map. From Example 3, “Adding Hyperlinks to Polygons” we found that the map contains 63 polygons, as some states such as Washington are drawn using multiple polygons. Each polygon has a unique name, which we add as ids to the elements:
pts = getPlotPoints(doc)
mapply(function(node, id)
xmlAttrs(node) = c(id = id),
pts, m$names)
We also put a tooltip on each polygon to identify the state.
addToolTips(pts, m$names)
Finally, we add the necessary variables to the JavaScript code in the SVG
document along with the JavaScript code for handling the animation.
We will assume the JavaScript code is in a file
named animateElectionMap.js. So we add the code
and variables to the SVG document using
addECMAScripts(doc, "animateElectionMap.js",
stateResultsByYear = stateResultsByYear,
yearLabels = names(stateResultsByYear),
polyIds = m$names,
polyStateNames = polyStateNames,
insertJS = FALSE)
Note that we are adding 4 variables: stateResultsByYear, which holds the colors for the states; yearLabels, the years of the elections; polyIds, the unique names for the polygons; and polyStateNames, the state names for each of the polygons (so Washington appears multiple times, once for each polygon).
The JavaScript code will update the value of fill for each polygon. It is easiest to set this as an individual attribute rather than modify the style attribute by replacing the "style: value;" sub-string. To this end, we will change the way the SVG style attributes are stored for each of the polygon nodes. We use convertCSSStylesToSVG() for this:
convertCSSStylesToSVG(pts)
That is all we need to do to our SVG document at this point so we can save it to a file:
saveXML(doc, "exJSAnimateElectionMap.svg")
All that remains in our example is to write the relevant JavaScript
code. We need the toggleAnimation function and the
code that does the actual changing of the color of each state/polygon.
This code also checks to see if the animation is complete and, if so,
changes the label from “Pause” to “Start”.
We start by defining some global variables for controlling the animation. These are the identifier for the interval, the current year being displayed and the amount of time between updates in the animation:
var animationHandle = null; var currentYear = 0; var Interval = 600;
Next we define the function that updates the display for the next
year. This increments the value of currentYear and
checks to see if the animation is done. If it is, we reset the
variables and the value displayed in the “Start” node.
If not, we update the display with the values for this election year
by calling displayYear. The entire
animationStep appears here.
function animationStep(ids, colors, yearLabels, stateNames)
{
currentYear++;
if(currentYear >= yearLabels.length) {
var el = document.getElementById("Start");
setTextValue(el, "Start");
clearInterval(animationHandle);
animationHandle = null;
return;
}
displayYear(currentYear, ids, colors, yearLabels, stateNames);
}
The function displayYear updates the colors of the
polygons and and changes the year displayed in the title node. It is
defined as,
function displayYear(year, ids, colors, yearLabels, stateNames)
{
for(var i = 0; i < ids.length; i++) {
var el = document.getElementById(ids[i]);
/* Lookup the year by name. Then within the year, look up
the state, using the actual name not the polygon id. */
var col = colors[yearLabels[year]][ stateNames[ i ] ] ;
if(el && col)
el.setAttribute('style', 'fill: ' + col);
}
var title = document.getElementById('title');
setTextValue(title, yearLabels[year]);
}
The next function we need is the one that starts and pauses the
animation. This examines the value of the animation handle and if this
is non-null, terminates the animation. Otherwise, it starts the
animation and shows the first year. It also updates the value
displayed on the “Start” button. This function,
toggleAnimation, is shown below.
function toggleAnimation(evt)
{
var label;
if(animationHandle) {
clearInterval(animationHandle);
animationHandle = null;
label = currentYear > 0 ? "Restart" : "Start";
} else {
animationHandle= setInterval("animationStep(polyIds,
stateResultsByYear, yearLabels, polyStateNames)", Interval);
currentYear = 0;
displayYear(currentYear, polyIds, stateResultsByYear,
yearLabels, polyStateNames);
label = "Pause";
}
var start = document.getElementById('Start');
setTextValue(start, label);
}
The utility function which changes the value/label of a <text> node is given by
function setTextValue(node, val)
{
node.firstChild.data = val;
}
In Example 10, “Animation with JavaScript & SVG”, we added a colored rectangle with the text “Start” to act as a button, where clicking on this region in the plot started an animation. In this section, we extend this idea and demonstrate how to provide interactivity through user controls, such as buttons, check boxes, and sliders that are drawn on the display using SVG & JavaScript rather than R. The Carto:Net community [CartoNet] has developed an open source library of SVG graphical user interface (GUI) controls to facilitate interactivity in SVG documents. The library consists of JavaScript functions to build the controls as SVG elements and respond to user interaction. This library is available from http://carto.net/ and is also distributed with the SVGAnnotation package, for convenience. It includes controls such as a check box, radio button, button, slider, text box, and selection menu that are rendered using native SVG elements and are quite different from their JavaScript equivalents.
By using these SVG GUI components, we can create applications that have rich user interfaces. An alternative approach is to embed the SVG graphic in an HTML document and control it through buttons and boxes in an HTML form (we show how to do this in the section called “Interfacing with (X)HTML”). With SVG, there is the potential to design unique, sophisticated GUI elements, such as sliders, dial knobs, and color choosers. Another potentially useful feature of SVG-based GUI components is that they can be rendered with SVG filters and transformations, e.g. at an angle or different filters (e.g. Gaussian blurring), or even animated. The main disadvantage is complexity. SVG GUI elements are somewhat more complex to use in programs than the “built-in” HTML form elements. Fortunately, the Carto:Net library offers a variety of GUI elements that can be easily embedded in the document.
In this section, we provide examples of how to use two of these GUI controls, a slider and check box. The first example adds a slider to a display in order to give the viewer control of a smoothing parameter for a fitted curve. The second example uses check boxes to add and remove groups of data (time series) from a plot.
Although the GUI control is being drawn with JavaScript commands, the basic approach to annotating the SVG remains essentially the same. The annotation stage now typically requires the following additional tasks:
Enlarge the viewing area so that there is room for the GUI control (i.e. change the viewBox attribute on the root element).
Add an initialization script to the document in order to create the GUI object when the document is loaded (i.e. add a call to a JavaScript initialization function via the onload attribute on the <svg> element). This script needs to call the JavaScript function provided by Carto:Net that creates the GUI object.
Populate the <defs> node with elements that contain the generic drawing instructions for the control that are provided by Carto:Net. And, locate the GUI in the display by adding an empty <g> tag that Carto:Net scripts use to draw the graphical representation of the control.
Add the Carto:Net scripts to the document along with the application specific JavaScript functions that connect GUI events to changes in the display, e.g. a change in the location of the slider's thumb calls a function that reveals a new curve on a plot and hides from view the old curve.
In this section, we consider an example of how to embed a Carto:Net slider in an SVG document. The SVGAnnotation package provides a generic addSlider() function to add a slider to an SVG document. The addSlider() function takes care of setting up the SVG content according to the Carto:Net requirements. It enlarges the “viewBox” to make room for the slider, adds the initialization script to the <onload> attribute of the root node, adds the required <g> parent node for the slider, and inserts the required JavaScript. In addition, it places the slider thumb drawing instructions in the <defs> portion of the document. The declaration of the function is shown below.
addSlider =
function(doc, onload, javascript,
id = "slider", svg = NULL,
defaultJavascripts = c("mapApp.js",
"helper_functions.js", "slider.js"),
side = "bottom", ...)
The onload argument gives the JavaScript code that will be invoked when the document is loaded. Also, javascript and defaultJavascripts take the file names for the JavaScript code (file names or text content) that contain, respectively, application specific functions and the required Carto:Net code. Finally, any JavaScript variables needed in the document are supplied via the ... argument.
The JavaScript slider can be queried at various times to find the location of the
slider thumb, to set its value, to move the slider thumb, and to
remove the slider from the display. This functionality is provided
via the JavaScript slider object's methods getValue,
setValue, moveTo, and
removeSlider, respectively, invoked as, e.g.,
slider.getValue(). The next example, demonstrates
how to use the Carto:Net slider.
Example 11. Controlling smoothness with a slider
In this example, we build a slider to interactively control the bandwidth of a kernel smoother (see Figure 5, “A slider for choosing parameter values”). When the viewer moves the slider thumb to a new position, the corresponding fitted density curve is overlaid on the scatter plot on the left-hand side of the display, and a scatter plot of the residuals is rendered in the plot on the right-hand side.
Similar to the approach of many of the previous examples, the computations for fitting the curve to the data are performed in R in advance for every possible selectable value of the smoothing parameter. The resulting curves and residuals are plotted in the SVG document. Then in the annotation stage, the SVG elements corresponding to each curve are given unique ids so that they can be identified. In this case, we use the identifier, “curve-lambda-N”, where N is the parameter value associated with the curve. These nodes are also annotated with a visibility attribute so the curves can be exposed or hidden according to the value of the smoothing parameter selected by the viewer via the slider. The residuals are similarly annotated. To do this, we group together the residuals from the fit for a particular smoothing parameter, and place them within a <g> node that has a unique id, namely “residual-group-N”. We also add a visibility attribute of "hidden" to each <g>.
The final annotation task is to add the slider to the document. A call
to addSlider()
does this for us. The value of the
onload argument is a call to the
init function that creates the slider. When the document is
loaded, this function will be called with the number of smoothing
parameter values, which is also the number of fitted curves, and the
number of positions on the slider. This extra information is used to
coordinate the slider value with the curve and residual points to be
displayed. We add this onload attribute and the
supporting JavaScript code to the SVG document with
svg = xmlRoot(doc)
addSlider(doc, svg = svg,
onload = sprintf("init(evt, %d);", max(lambdas)),
javascript = "linkedSmoother.js", id = "slider-lambda")
saveXML(doc, docName(doc))
The id parameter identifies the id of the SVG element in the SVG document that corresponds to the slider, i.e. the identifier on the <g> element that is added to support the slider.
The init function simply instantiates the
slider. It is defined as
var cur_lambda = 2;
function init(evt, maxLambda) {
var sliderStyles = {"stroke" : "blue", "stroke-width" : 3};
var invisSliderWidth = 15;
new slider("lambda", "slider-lambda",
100, 510, 2, 475, 510, maxLambda, 2,
sliderStyles, invisSliderWidth, "sliderSymbol",
setLambda, false);
}
A call to init creates the slider via the JavaScript
constructor function,
var slider = new slider(id, parentNode, x1, y1, value1, x2, y2, value2,
startVal, sliderStyles, invisSliderWidth,
sliderSymb, functionToCall, mouseMoveBool);
In our example, the id of “lambda”
specifies a unique identifier for the slider that is used internally within Carto:Net; the
parentNode, “slider-lambda”, gives
the id for the <g> element that will contain the
slider; the triple x1, y1,
value1, which we have set to (100, 510, 2),
provides the (x,y) coordinates for the location of the
start of the slider and the starting value of the slider; similarly,
x2, y2,
value2 provide this information for the end of
the slider; and startVal provides the initial
value of the slider. We further customize the slider by specifying
presentation attributes via CSS. In our example, we set the
sliderStyles so the slider will be drawn with a
thick blue line. Finally, the events on the slider are handled by the
JavaScript callback function, setLambda, which we
provide via the functionToCall argument, and the
final argument, mouseMoveBool is
“false”, indicating not to trigger updates as the mouse
drags the slider, i.e. only call setLambda when the
mouse is released and the action of dragging the slider thumb has
stopped.
The setLambda function has a helper function
setVisibility. Both are shown below. They are similar in
spirit to JavaScript functions we have used in other examples in that
they simply reset the visibility attribute for the appropriate group
of points and curve (e.g. Example 6, “Linking across lattice plots”). They are
defined as
function setLambda(evType, group,val)
{
if(Math.floor(val) == cur_lambda) return(0);
setVisibility(cur_lambda, 'hidden');
cur_lambda = Math.floor(val);
setVisibility(cur_lambda, 'visible');
}
and
function setVisibility(lambda, state)
{
var el;
lambda = Math.floor(lambda);
el = document.getElementById("curve-lambda-" + lambda);
el.setAttribute('visibility', state);
el = document.getElementById("residual-group-" + lambda);
if(el) {
el.setAttribute('visibility', state);
return(0);
}
}We have put this code in the linkedSmoother.js file and so have already included it in the SVG document.
We can use a similar approach to create interactivity with other SVG GUI controls offered in Carto:Net. We provide one additional example involving check boxes, for users unfamiliar with event handling. The check boxes in Carto:Net support toggle events, i.e. when a check box is checked or unchecked then a user-supplied function is called to perform some action. For example, in Example 12, “Showing and hiding currency exchange series with check boxes”, several time series are overlaid on a plot and made visible or invisible according to whether or not a corresponding check box is checked or not. That is, when a check box is toggled, this event then triggers a function call to change the visibility attribute of the associated times series.
The SVG document needs to be modified in the same way we did for the slider so as to include the check box GUI controls. In particular, the document should have an empty group <g> that will hold the elements for the check boxes and their labels. This needs to be added with a unique id to the appropriate place in the document; the checkBox JavaScript object needs to be initialized via JavaScript in the onload attribute on the root element; and the JavaScript code from the files helper_functions.js, timer.js and check box_and_radiobutton.js must be included, either by reference or by content.
The radioShowHide() function in R sets up a group of check boxes for toggling on and off curves in a plot. Once the initial SVG plot has been made, radioShowHide() post-processes the SVG in the annotation stage. This function handles all of the setup. Similar to the slider, radioShowHide() expands the viewing area to make room for drawing the check boxes; sets up the drawing areas for the check boxes; adds to the document the initialization script that creates the JavaScript check box objects when the document is loaded in the browser; and includes the JavaScript functions provided by Carto:Net that respond to the viewer actions and the application specific JavaScript functions that change the graphical display in response to viewers actions.
The function signature/declaration for the radioShowHide() function provides the information needed to perform these tasks:
radioShowHide =
function(doc, insertScripts = TRUE, within = FALSE, group = FALSE,
labels = paste("Series", seq(length = numSeries)),
extraWidth = 15 * (max(nchar(labels)) + 1),
save = !is(doc, "XMLInternalDocument"),
id.prefix = "series",
checkboxCallback = if(is.na(within)) "togglePanel"
else "toggle",
jscripts = c("helper_functions.js", "timer.js",
"checkbox_and_radiobutton.js",
"hideSVGElements.js"),
numPanels = 1)
Briefly, doc specifies the SVG document to be
annotated; within indicates whether a regular plot
(FALSE) or matplot (TRUE) call was used to create the plot,
which helps in finding the series; group allow the
series to be matched up across lattice panels so that a series can be
toggled on and off in all panels; labels contains
the text labels for the check boxes; and
checkboxCallback holds the JavaScript for the call
back function.
Example 12. Showing and hiding currency exchange series with check boxes
In this example, we create an interactive time series plot, where several times series curves are super-imposed on the same plot and check boxes along side the plot are used to toggle the display of the series on and off (see Figure 14, “Togglable Exchange Rate Display”). The check boxes are ordered according to the median value of the time series to make it easier to visually connect the check box with its time series. The text labels could also have their colors coordinated with the series to make this connection clearer. We create the SVG plot with the command
svgPlot({
matplot(eu$Date, as.data.frame(lapply(eu[,-1], log)),
type = "l", xlab = "Year", ylab = "log(exchange rate)",
main = "European Exchange Rate", xaxt = "n")
startYr = min(eu$Date)
endYr = max(eu$Date)
axis.POSIXct(1, at=seq(startYr, endYr, by="year"), format ="%y")
abline(h = 1, col = "gray")
}, "euSeries.svg")
Note that in this case we are explicitly writing the SVG to a file rather than returning the SVG tree.
This screen shot shows a time-series plot (via matplot() ) of exchange rates against the Euro for 24 different currencies. The SVG is post processed to add check boxes to the right of the plot. These check boxes are interactive and allow the viewer to toggle on/off the display of the corresponding currency's time series within the plot. The SVG GUI controls shown here are provided by Carto:Net.
A simple call to radioShowHide() will do all the post-processing of the SVG document created in the matplot() call:
doc = radioShowHide("euSeries.svg", within = TRUE,
labels = currency.names[ match(names(eu)[-1],
names(currency.names))])
We can also extend this functionality to plots with multiple panels. For example, we might draw the daily time series for the different currencies for each year in a separate panel by conditioning on year. Then toggling the currency check box would show/hide the corresponding curves in each of the panels. Again, the reader should view the example by visiting the package's Web site and viewing it there.
The interactivity for the examples considered so far have been entirely contained within the SVG document. That is, the SVG or JavaScript code that responds to user interaction has been located in the SVG file. In this section, we demonstrate that it is also possible to control SVG using JavaScript that is placed within an HTML document, where the SVG display is also embedded in the HTML.
There are several benefits to this approach. For example, HTML forms can be used to control the interactivity, and these controls can be easily visually arranged using layout facilities in HTML, e.g. lists, <table>s or <div>s rather than in the SVG display. Other advantages are that embedding the SVG document in HTML allows us to control the width and height of the area in which it is displayed; multiple SVG documents can be embedded in one HTML document; and SVG documents can be linked together via JavaScript in the HTML document. There can be separate JavaScript code in the different components, i.e. in the HTML document and each SVG document. This increased locality and flexibility can also be slightly more complicated.
In this section, we provide two examples of this approach. The first revisits a previous example and creates interactivity through HTML forms rather than with mouseover calls to JavaScript functions. It provides an important comparison. In the second example, we move the process of annotating the SVG entirely into the viewing stage, specifically at the point where the document is loaded into the browser. As such, JavaScript, rather than R, is used to augment the SVG elements with the necessary annotations for interactivity, e.g. adding onmouseclick attributes to plot elements.
HTML forms [4] provide built-in controls, such as a choice menu, check box, radio button, textbox, and button that receive input from the user. The controls are not as rich as those available in Carto:Net, e.g. a slider is only available via JavaScript rather than a built-in HTML element, however they are simple to deploy in an HTML page and make available to readers.
With forms, we can mix controls in HTML with a plot in SVG. We embed the SVG display in an HTML document using the HTML element <object> such as
<object id="PlotID" data="plot.svg"
type="image/svg+xml" width="960" height="800"/>
which specifies the name of the SVG file via the data attribute, its MIME type, and dimensions. When we want to operate on pieces of the SVG document (e.g. when the viewer clicks on an HTML button), we use JavaScript to retrieve the resulting SVG object. Once we have the SVG object, we can operate on it with JavaScript, just as we have when we placed the scripts in the SVG document. The JavaScript code below shows how to retrieve the the embedded object and treat it as an SVG document.
doc = document.getElementById('PlotID');
doc = doc.getSVGDocument();
Note that we use 0 as the index because JavaScript uses 0-based indexing (see the section called “Appendix: Basics of JavaScript”).
We can then write JavaScript callback functions that respond to viewer input via the controls in an HTML form where these functions interact with an SVG document embedded in the HTML document. For example, when a viewer selects an option from a choice menu, a JavaScript function can be called to modify an SVG display in the document. We illustrate this with the next example.
Example 13. Using forms to highlight points in a lattice plot.
This example re-implements Example 6, “Linking across lattice plots”. In that example, we have a legend on a lattice plot. The legend displays the levels of a categorical variable which is not contained in the display but which is part of the data set being displayed, i.e. an additional variable. When the viewer moused over an entry in the legend, the observations corresponding to this level of the variable were highlighted in the different panels of the lattice plot. In this version of the example, we remove the legend from the plot, and instead control the highlighting action through a choice menu in an HTML form (see Figure 15, “ Linking an HTML select menu to points in a conditional plot ”). That is, the SVG is embedded in an HTML document and this document also contains an HTML form with a choice menu.
This screen shot of an HTML page shows an SVG lattice plot that is controlled by a choice menu in an HTML form. It offers the same functionality as the SVG document in Figure 11, “ Linking a legend to a group of points in a conditional plot ”. However, when the viewer changes the choice in the HTML form, a JavaScript function in the HTML document is called to respond to this event.
The HTML document rendered in Figure 15, “ Linking an HTML select menu to points in a conditional plot ” contains the SVG plot by including it using the following <object> tag:
<object id="latticePlot" data="mt_lattice_Choice.svg"
type="text/svg+xml" width="960" height="768" />
The SVG document is the same as the one created in the earlier example with two important differences. First, it no longer contains any JavaScript code. Also, the legend has been removed from the lattice plot as the HTML form is replacing the mouse-over capability. However, all of the plotting elements still have their appropriate identifier attributes (i.e. their id indicates to which group they belong).
Along with the SVG display of the scatter plot, the HTML document also contains a <form> with a <select> menu that has four options as shown below.
<form> Choose number of gears: <select name="gear" onchange="showChoice(this)"> <option value="0" default="true">Select</option> <option value="1">three</option> <option value="2">four</option> <option value="3">five</option> </select> </form>
Notice that when the selection is changed, the
showChoice function is called.
The showChoice function is passed the JavaScript
object corresponding to the choice menu and it uses this to query the
value the viewer has selected. The showChoice
function then calls highlight with the information
that it needs to highlight the new group of points.
showChoice is
var oldgroup = 0;
var group = 0;
var e;
function showChoice(obj)
{
e = document.getElementById('latticePlot');
e = e.getSVGDocument();
group = Math.floor(obj.value);
if (group > 0) {
highlight(group, pointCounts[(group - 1)], true);
}
if (oldgroup > 0) {
highlight(oldgroup, pointCounts[(oldgroup - 1) ], false);
}
oldgroup = group;
}
The highlight function called from
showChoice and its helper function
highlightPoint are identical to the functions by
the same name that were placed in the <script> tag
within the SVG document in Example 6, “Linking across lattice plots”. They
extract the elements in the SVG document corresponding to the points
that belong to the selected group, and change their style attributes.
These function rely on the plotting elements having unique
ids, that indicate the group of points to which
they belong. The embedded SVG document is in the global variable
e, from which highlight obtains
the panel as follows:
function highlight(group, pointCts, status)
{
var el;
var i, numPanels = pointCts.length;
for(panel = 1; panel <= numPanels; panel++) {
for(i = 1; i <= pointCts[panel-1]; i++) {
var id = panel + "-"+ group + "-" + i;
el = e.getElementById(id);
if(el == null) {
alert("can't find element " + id)
return(1);
}
highlightPoint(el, status);
}
}
}
highlight and highlightPoint now
appear in the <head> of the HTML document rather
than in the SVG document. We can programmatically add it to the HTML
document in much the same way we did for the SVG document using
addECMAScripts()
.
Note that the SVG document created for this example had no JavaScript in it, and no calls to JavaScript functions. The interactive functionality does rely on the plot elements in the SVG having unique identifiers. In general, provided the plot elements have appropriate ids, “plain” SVG can be made interactive via JavaScript embedded in HTML rather than in the SVG. The advantage of this approach is that plots made for some general purpose can be made to have interactive capabilities.
There is yet another approach to providing interactivity for SVG documents that are embedded within HTML. The idea is that we use JavaScript code in the HTML document to programmatically modify elements in the SVG document at the time it is loaded in the browser. This approach can be useful when the plots have been produced in SVG but the creator has no motivation to add any interactivity. If the JavaScript code can identify the appropriate elements in the SVG document, either by contextual knowledge or using known id attribute values, it can add onmouseover, onmouseout and onclick event attributes to these elements and so dynamically (i.e. at viewing time) add the interactivity.
As an example of this approach, we consider the case where we place mouse capabilities on a map so that a mouse click triggers the display of an HTML table.
Example 14. Communication between SVG and an HTML page
In this example, we create an HTML page that displays the canonical red-blue map of the United States, where the states are colored red or blue according to whether the majority of votes in the 2008 presidential election were Republican or Democrat, respectively. The viewer interacts with the map by clicking on a state in the map, and as a result, summary statistics for the selected state are loaded into the web page as well as county-level data.
We layout the HTML page using nested <div>s as follows
<body>
<center>
<h3>Presidential Election 2008</h3>
<p>
The map is interactive in that you can click on
anywhere within a state to see more detailed
results about the voting in that state.
</p>
</center>
<div id="main">
<div id="summaryMap">
<div id="stateSummary"></div>
<div id="map">
<object id="svgMap" data="stateMap.svg"
type="image/svg+xml" width="700" height="700">
</object>
</div>
</div>
<div id="countySummary">
<a id="toggleCounty"
onclick="toggleCountyView()">+ Click to see county results</a>
<div id="countySummaryContent" class="hidden">
Click on a state to see more detailed information about the
election results for that state.
</div>
</div>
</div>
Each of these <div>s has an associated style which we place in the <head>.
The SVG map is embedded in the <div> that has an id of “map”. The plot was created with R using the maps package. We add id attributes to the polygon elements that correspond to the polygon name used in map() . (See Example 3, “Adding Hyperlinks to Polygons” for an example of how to do this.) Although we place unique identifiers on each polygon, we do not add any JavaScript interactivity in the annotation stage of the plot creation. Recall that the first stage is when we create the graphical display in R; the second stage, the annotation stage, is when we use R to modify the resulting SVG document to add tags, attributes, and JavaScript to support interactivity; and the third stage is the viewing stage, when we are in the browser and R is no longer available, and JavaScript functions respond to events. Instead, in this example, we use JavaScript located in the HTML page to annotate the SVG and add onclick attributes to each of the polygon elements at the time the page is loaded into the browser. We do this with the following JavaScript code:
<script type="text/javascript">
var doc;
doc = document.getElementById('svgMap');
doc = doc.getSVGDocument();
for(var i = 0; i < polyIds.length ; i++) {
var el = doc.getElementById(polyIds[i]);
el.onclick = "parent.show('" + polyStates[i] + "')";
}
</script>
The mouse click will occur on an element in the SVG document, and
since the JavaScript to handle this event is in the HTML document, not
the SVG, we need to call the show method of the parent doucument
using parent.show. Notice that the mouse click function
call passes the state name, not the polygon name. The JavaScript
variables polyIds and polyStates
contain the unique names of the polygons in the map and the names of
the states that the polygons belong to, respectively. (Recall that
there are 63 polygons in the map because some states are drawn with
multiple polygons, e.g. Manhattan in New York state).
The show function appears in a
<script> tag in the <head> of the
HTML file. We also place the JavaScript variables
polyIds and polyStates there.
show is defined as
<script type="text/javascript">
function show(stateName)
{
var val;
var div;
val = stateSummaryTables[stateName];
div = document.getElementById("stateSummary");
div.innerHTML = val;
val = countyTables[stateName];
if(val) {
div = document.getElementById("countySummaryContent");
div.innerHTML = val;
}
}
</script>
When show is called, it retrieves a reference to the
embedded element in the HTML document named “stateSummary”, which is where it will
place the table of statistics for the selected state. We modify the contents of this
<div> element, adding the new HTML table. In
addition, the county level information is retrieved and placed in the
<div> called “countySummaryContent”.
Finally, additional JavaScript variables are loaded into the document
with
<script type="text/javascript" src="stateHTMLTables.js"></script>
These variables were created in R and contain the HTML table content for the states and counties information.
The main difference between the approach presented here and that found in the earlier examples is that some of the post-plot annotations have been lifted into the viewing stage. One result of this approach is that the code that responds to user interaction will be more indirect as it is no longer within the SVG document. Also, the post-processing occurs outside of the R environment, at the time the document is loaded. Thus we can take SVG documents not made for interactivity and modify them with JavaScript. A down side to this approach is that the interactivity may require access from the browser to the data and statistical routines used to generate the plot.
The SVGAnnotation package allows R users to leverage the sophisticated and flexible static graphics frameworks in R to create displays of data and models and then display them in new and interesting ways in new emerging media. With SVGAnnotation, we can add interactivity and animation to the displays. The mechanism relies on post-processing the output from the R graphics engine and associating elements within the SVG output with the high-level components of the display. This is entirely deterministic based on the nature of the R plot, but is slightly different for each type of plot since there is no simple format or model for expressing all plots. The purpose of SVGAnnotation is to find the SVG elements corresponding to the R elements and allow the R user to easily augment these.
The package provides high-level facilities for “standard” plots in which we can readily identify the R elements. It also allows an R programmer to use intermediate-level facilities to operate on axes, legends, etc. There are also low-level facilities for identifying the shape of visual elements, e.g. polygon, line, vertical line, text. The functions in the package can also add high-level type identifiers to nodes in the SVG document such as identifying data points, axes, labels. These facilities allow us to handle cases from regular R graphics, lattice[20], ggplot [14] and grid [13]. Leveraging the XML facilities in R or higher-level functions offers capabilities for creating rich new plots in R. Most importantly, the approach and the concept it implements is readily extended to other contexts. The abstract idea and approach is the important contribution; the implementation is a detail.
Our approach in the SVGAnnotation package is to use the high-quality rendering engine provided by libcairo from within R. There are two other SVG graphics devices available for use within R. These are the RSvgDevice [31] and the derived RSVGTipsDevice [32] R packages. The former generates an SVG document that contains SVG elements corresponding to the graphical primitives the R graphics engine emits, e.g. lines, rectangles, circles, text. However, the libcairo system is more widely used and more robust. It also deals with text in more advanced and sophisticated ways. This is the primary reason we use the libcairo approach even though the format of the SVG documents that RSvgDevice produces is simpler and more direct.
The RSVGTipsDevice package builds on the code from RSvgDevice. It allows R programmers to add SVG annotations and does so when the SVG is being created, rather via post-processing additions. R users can set text for tooltips and URLs for hyperlinks that are applied to the next graphical primitive that the R graphics engine emits. This works well when the R programmer is creating all graphical elements in a display directly in their own code. However, it does not work when calling existing plotting functions that create many graphical elements. The computational model does not provide the appropriate resolution for associating an annotation with a particular element, but just the next one that will be created. As a result, we cannot annotate just the vertical axis label when calling a function that creates an entire plot.
Our post-processing approach is not limited to using libcairo. We could also use RSVGTipsDevice to generate the SVG content and then programmatically manipulate that. The documents generated by the two different devices will be somewhat different (e.g. the use of groups of characters for strings, in-line CSS styles versus separate classes, SVG primitives for circles rather than paths). However, because the elements of the graphical display were generated by the R graphics engine with the same R expressions, the order of the primitive elements and the general structure will be very similar.
The package gridSVG [12] is an earlier and quite different approach to taking advantage of SVG. As the name suggests, it is focused on R's grid graphics system.For this reason, it cannot work with displays created with the traditional and original graphics model (“grz”) but does handle all of the plot types in lattice. gridSVG takes advantage of the structured self-describing information contained in grid's graphical objects. As one creates the grid display, the information about the locations and shapes are stored as objects in R. These can then be translated to SVG without using the R graphics device system. New graphics primitives have been added to the grid system to add hyperlinks, animations, etc. corresponding to the facilities provided in SVG.
The approach provided by gridSVG is quite rich. It removes the need to post-process the graphics that we presented here. Instead, one proactively and explicitly states graphical concepts. One limitation that is similar to that of RSVGTipsDevice is that we still run into problems with interleaving SVG annotations. While a user can issue grid commands that add SVG facilities to the output, higher-level functions that create plots produce entire sequences/hierarchies of grid objects in a single operation. If these do not add the desired annotations, we have to post-process the grid hierarchy to add them. This is a very similar post-processing that we are doing, however it is done on R objects.
Of course, gridSVG is restricted to the grid graphics system. Our approach however works with the output of any R graphical output, although it needs to be “trained” to understand the content. The combination of the two approaches: gridSVG and SVGAnnotation appear to give a great deal of flexibility that allows both pre-processing and post-processing.
The animation package [33] provides functionality to create movies (e.g. animated GIF files or MPEG movies) entirely within R. The idea is that one draws a sequence of plots in R. These are combined and then displayed at regular intervals as frames of the movie to give the appearance of motion. R users can draw each frame using R code and without regard for particular graphics device or formats. This simplifies the process for many. The approach does, however, involve redrawing each frame rather than animating individual objects. It also provides no interactive facilities. It involves a different programming model where we redraw entire displays rather than working on individual graphical elements. In different circumstances, each model has advantages. So while animated displays are common to both the animation and SVGAnnotation packages, the goals and infrastructure are very different.
The motivation behind the SVGAnnotation package is the ability to exploit and harness new media such as interactive Web browsers. The aim is to enable statisticians to be able to readily create new graphical displays of data and models using existing tools that can be displayed in rich, interactive and animated venues such as Web browsers, Google Earth, Google Maps, GIS applications. The SVGAnnotation package focuses on facilitating R users in leveraging exiting R graphical functionality and then post-processing the output to make it interactive and/or animated. It is a complete computational model in that it enables the author of a display (or a third-party) to modify all elements in that display. This approach can be used for other modern formats.
Another format for graphical displays and applications is Adobe's Flash & Flex (http://www.adobe.com/products/flash). This is an alternative to SVG that is a very widely used framework (the ActionScript programming language, collection of run-time libraries, and compiler suite) for creating interactive, animated general interfaces and displays. The range of applications include rich graphical user interfaces and interactive, animated business charts. Flash and Flex are freely available, but not Open Source. The format and tools are mostly controlled by Adobe. The Adobe chart libraries for Flash are only available in commercial distributions.
There are publicly available Open Source libraries for creating certain types of common plots, e.g flare http://flare.prefuse.org. Alternatively, we can use R to create statistical graphical displays by generating ActionScript code to render the different elements in the display. We have developed a prototype R graphics device (in the FlashMXML package) that creates Flash plots within R. To provide interaction and animation, connect the plot to GUI components, etc., we can post-process that code in much the same way we do with the SVGAnnotation package.
Rather than considering Flash and JavaScript as competitors to SVG, we think that each has its own strengths. SVG is more straightforward and direct than Flash and JavaScript for creating graphical displays. For one, we have an excellent R graphics device that creates the initial SVG content. We can add GUI components, but Flash is better for this as it provides a much richer collection of GUI components such as the data grid. Drawing displays with JavaScript avoids depending on the presence of support for either SVG or Flash and so can be deployed in more environments.
Another approach is to use the HTML5 canvas element that has been recently introduced in several browsers. We can create JavaScript objects for drawing, e.g., circles, lines, text on a canvas. Again, we have developed a prototype R graphics device that generates such code for an R plot. We can also annotate this to add animation and interaction. We also mention the Vector Markup Language (VML) (http://msdn.microsoft.com/en-us/library/bb263898(VS.85).aspx) which is quite similar to SVG. It is used within Microsoft products such as Word, Excel and PowerPoint. However, it is not widely supported by other applications.
A fundamental aspect of what we have described with respect to the SVGAnnotation package is that R is used to create the display but is not available at viewing time when the SVG document is rendered. If R were an extension or plugin for the SVG viewer, the ECMAScript code within an SVG document coud make use of R at run-time. It could invoke R functions and access data to update the display in response to user interaction and animation. Additionally, we could use R code to manipulate the SVG and HTML content and so enable programming in either ECMAScript and R. We are developing such an extension for Firefox which embeds R within a user's browser. This modernizes our previous work in 2000 on the SNetscape package that embedded R and R graphics devices within the Netscape browser. We believe the combination of SVG (or Flash or the HTML5 canvas) with R at viewing-time will prove to be a very rich and flexible environment for creating new types of dynamic, interactive and animated graphics and also allow Google Earth and Maps displays to be combined with R plots and computations.
In conclusion, the increasing importance of Web based presentations is a space where statisticians need to be engaged. To accomplish this, we need more tools for creating these presentations. SVGAnnotation offers one approach; we are working on others. We ask that the reader think of the package as a rich starting point that enables a new mode of displaying R graphics in an interactive, dynamic manner on the Web. It establishes a foundation on which we and others can build even higher-level facilities for annotating SVG content and providing rich plots in sever different media (i.e. SVG, HTML, JavaScript).
[2] R: A Language and Environment for Statistical Computing. 2009. http://www.r-project.org
[8] The XML C parser and toolkit of Gnome. http://www.xmlsoft.org
[9] Carto:Net. http://www.carto.net
[10] GGobi. http://www.ggobi.org
[11] Open XML. The Markup Explained. http://openxmldeveloper.org/attachment/1970.ashx2007.
[18] Cairo. http://www.cairographics.org
[21] The hexbin package. http://www.bioconductor.org/pacakges/release/bioc/html/R/hexbin.html
[22] Opera Software. http://www.opera.com/browser/
[23] Batik: Java SVG Toolkit. http://xmlgraphics.apache.org/batik/
[24] JavaScript Tutorial. http://www.w3schools.com/JS/default.asp
[25] XML Tutorial. http://www.w3schools.com/xml/default.asp
[26] SVG Tutorial. http://www.w3schools.com/svg/default.asp
[27] XPath Tutorial. http://www.w3schools.com/XPath/default.asp
[28] Programming Flex 3: The Comprehensive Guide to Creating Rich Internet Applications with Adobe Flex . O'Reilly. 2008.
[30] Interactive Data Visualization Using Mondrian. 2002. Journal of Statistical Software. 11. http://www.jstatsoft.org/v07/i11/
[31] The RSvgDevice package. 2009-01-04. http://cran.r-project.org/web/packages/RSvgDevice/index.html
[32] The RSVGTipsDevice package. http://cran.r-project.org/web/packages/RSVGTipsDevice/index.html2009-02-19.
[33] The animation package. http://animation.yihui.name2009-07-17.
[34] The iPlots package for R. http://www.rosuda.org/iplots/
[35] Lattice: Multivariate Data Visualization with R. Springer. 2008. http://lmdvr.r-forge.r-project.org/figures/figures.html
The basic unit in an XML document is an element, also known as a node or chunk. An element can contain textual content and/or additional XML elements. By content, we mean the simple text, such as the word “Oregon” that appears in the simple SVG document shown in Figure 8, “Sample SVG”. So we have text and XML elements. An element begins with a start-tag and ends with an end-tag. The start-tag has the format <tagname> and the end-tag uses the same tag name but has an additional forward slash before the tag name, i.e. </tagname>. Those readers who have read or composed HTML (HyperText Markup Language) will recognize this format. For more in-depth information about XML see [5, 25].
SVG, like HTML or WordProcessingML, is an example of a specific grammar of XML, where the focus of SVG is to describe graphical displays. The SVG document rendered in Figure 8, “Sample SVG” includes element/tag names such as <circle> and <rect> for drawing circles and rectangles respectively. Notice that some of the elements in the sample SVG document are nested within other start and end tags. This hierarchical structure gives us great flexibility in describing complex, nested data with arbitrary depth.
For more details about the specific tags and how to create SVG graphics, see the section called “The SVG Grammar”.
In addition to child or sub-elements, an XML element can have attributes associated with it. Attributes are supplied in the element/tag itself as name="value" pairs separated by an equals sign as follows: <tagname attributeName="value">. For example, the <text> tag,
<text x = "110" y = "200" fill = "navy" font-size = "15">
Oregon
</text>
has an attribute, x, which specifies the horizontal position of the text on the SVG canvas. The value for x in this example is "110", indicating position 110 along the x-axis of the canvas (which in our example is 300 by 300 points). The syntax rules for elements and their tags are provided below in the section called “ Well-formed XML”.
We should note one important short cut for start and end tags. If an XML element contains no text content or elements within it, i.e. there are no text or sub-elements contained between the start and end tags of the element, then it can be written as <tagname/> rather than the longer <tagname ></tagname>. For example, notice that the <rect> element:
<rect x = "10" y = "20" width = "50" height = "100"
class = "recs"/>
is empty. All of the relevant information for drawing the rectangle is contained in the attributes, i.e. its width, height, location on the canvas, and color. Hence the end tag is omitted and the start tag also acts as a closing tag.
For XML to be properly processed it must obey some basic syntax rules, and we say the document is well-formed when it satisfies these rules. The following list is a subset of the most important syntax rules. This list covers the vast majority of XML documents. Notice that they are very general, and do not pertain to a specific grammar of XML. XML documents that are not well-formed typically produce fatal errors when processed.
<text> Oregon </Text>is not well-formed because the start-tag begins with a lower-case “t” that does not match the capital “T” in the end-tag.
<defs>
<g id="circles">
<circle id = "pinkcirc" cx= "50" cy="50" r = "15" fill = "pink"/>
</g>
</defs>
Note the use of indentation is optional, but it makes it easier to see
the nesting of elements. (The white space is part of the XML document and is
typically preserved during processing.)
The conceptual model of the document as a tree can be very helpful when processing and navigating an XML document. The SVG shown in the section called “The SVG Grammar” uses indentation to make the nesting of elements clear, and demonstrates that the elements are logically structured into a hierarchical tree. Each element can be thought of as a node in the tree where branches emanate from the node to those elements that it immediately contains. Figure 16, “Sample SVG Document Tree” shows the tree representation of this SVG document.
Figure 16. Sample SVG Document Tree
This tree provides a conceptual model for the organization of the SVG document rendered in Figure 8, “Sample SVG”. Each element is represented by a node in the tree and the branches from one node to another show the connections between the nodes that are one layer apart from each other in the nesting of nodes. For example, there are two <circle> nodes and both are nested directly as children of (i.e. under) the <g> node, and the branch between these nodes indicates this connection. The particular <g> node is a child of the <defs> element. Text content nodes are actually elements but without a tag name and are part of the tree. They are displayed via boxes, e.g. “Oregon” is the sole text node in this tree.
The root of the tree is also referred to as the document node, which in this case is the <svg> element. As mentioned earlier, there is only one root node per document. Notice that the root node of this tree has two children, a <defs> node and a <g> node. The <g> has six children - a <rect> node, two <use> nodes, and one each of <image>, <path> and <text>.
The relative position of these nodes in the tree are described using family tree terminology. For example, the <defs> element is the parent of a <g> node, and <circle> is the child of <g>. The <rect> element is a sibling to <image>, and <g>, <defs>, and <svg> are all ancestors of each of the <circle> nodes. Also, note that <path> is referred to as a “following sibling” to <image> because it comes after (to the right) of <image>.
The character content of an element is placed in a “text” node. That is, text content is also represented as a node in the tree. In our example, the text Oregon is a child node of <text>. The terminal nodes in a tree are those that have no children and are known as leaf nodes. By design, text content will always be in a leaf node.
In addition to elements, XML markup includes the XML declaration, processing instructions, comments, and CDATA section delimiters.
XML declaration. An XML document must start with the XML declaration that identifies it as an XML document and provides the XML version number,
<?xml version="1.0" encoding="UTF-8"?>
The declaration appears outside of the root element.
Processing Instructions. Similar to the XML declaration, processing instructions must begin with <? and end with ?>. Immediately following the <? is the target of the instruction, i.e. the agent/application for which the instruction is intended. The following processing instruction is for an xml-stylesheet, which is a standard name that means the parameters in this processing instruction are intended for an XML viewer, e.g. a Web browser or XML editor, that can apply a style sheet to the document.
<?xml-stylesheet type="text/css" href= '~/Rpackages/SVGAnnotation/CSS/RSVGPlot.css'?>
Notice that the processing instructions for the xml-stylesheet are provided via name-value pairs in a format that imitates the syntax for attributes, e.g. type="text/css". This format is optional, and other applications could expect the processing instructions to be in some other format. Different applications support different processing instructions. Most applications simply ignore any processing instruction whose target they don't recognize. (Technically, the XML declaration is not a processing instruction). The XML-Stylesheet processing instructions are always placed in the document prolog between the XML declaration and the root element start tag. Other processing instructions may also be placed in the prolog or at almost any other location in the XML document, i.e. within sub-nodes.
Comments. A comment must appear between <!-- and -->, and it can contain < and > because all text between these two delimiters is ignored by the XML processor and so is neither rendered nor read. For example,
<!-- This is a comment which is so long that it appears on three lines of the document before it ends with -- followed by >. -->
Comments can appear anywhere after the XML declaration, e.g. they can appear outside the root element of the document.
Entities. Because XML uses the < character to start or end an XML element, it seems impossible to include the literal character < as text within an XML node. We use XML entities for this. An entity is a named symbol within XML that corresponds to some fixed content. We refer to them by prefixing the name of the entity with & and adding the suffix ;. There are numerous built-in entities to which we can refer. For example, the < character can be written as <. Similarly, > is written as > and & as &.
CDATA. Entities are convenient for escaping content from XML processing, especially individual characters. There are occasions, however, where have a significant amount of content that we want to escape from XML processing and to be treated as literal or verbatim content. For example, when we insert the R code used to generate an SVG plot into the SVG document, we want to avoid having to worry about characters an XML processor will interpret, i.e. &, <, >, etc. We do this by enclosing the content between between <![CDATA[ and ] ]> markers. For example,
<![CDATA[
plot(y ~ x, myData[ (myData$var1 < 10 | myData$var1 > 20) &
myData$var2 %in% c("A", "B", "&")])
]]>
This is then treated as a verbatim block of characters and not parsed by the XML processor for XML elements.
We provide here a brief introduction to JavaScript for programmers who are familiar with the S language. JavaScript (the official name is ECMAScript) is an interpreted scripting language that is widely used within HTML documents and also within SVG displays and Flash applications (using a slight variant named ActionScript). JavaScript can be used within HTML to respond to user actions on buttons, menus, checkboxes, etc. in HTML forms, or to dynamically validate the content of a form before submitting it to a remote server. JavaScript can also be used to dynamically and programmatically construct the content of an HTML document. Within SVG, we use JavaScript to respond to user events on elements of the display (e.g. circles, lines, rectangles, ...) and to provide animation. Similar to HTML, JavaScript can be used to dynamically create SVG elements within the display after the initial display.
As with R, one does not need to compile JavaScript code since it is interpreted. However, JavaScript is more like C++, Python and Java in its computational model. Much of the use of the language will focus on classes and instances (objects) of these classes. We frequently invoke an object's methods rather than call top-level functions, although like R, we can have regular functions unattached to objects.
JavaScript is not a vectorized language like R, i.e. it does not operate on vectors element-wise unless explicitly programmed to do so using loop constructs.
JavaScript requires variables to be declared within the scope in which they are used. Unlike C++ or Java, one does not need to declare the type of a variable and a variable can take on values of different types during its lifetime.
To make use of JavaScript within an HTML or SVG document, we must connect that code with the document. We can do this by either inserting the code as content in the document or alternatively adding a reference to the file containing the JavaScript code. That code file may be located locally or on a remote server, subject to certain security restrictions. Both approaches use the <script> node within the HTML or SVG document. We can insert the code content between the start and end tag of the <script> node. Alternatively, we can use an attribute in the <script> element to refer to the JavaScript file. For SVG, we use an attribute named xlink:href; for HTML, we use src. The value in both cases is a local file name or a URL. The following illustrates the mechanism for SVG:
<script xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
type="text/ecmascript"
xlink:href="SVGAnnotation/tests/multiLegend.js"/>
For HTML
<script type="text/javascript" src="alert.js"/>
To in-line the JavaScript content, we use
<script type="text/ecmascript">
var neighbors = [[0, 1, 31, 20, 3, 29], [2, 8, 9, 24]];
var k = 4;
function showNeighbors(evt, k, neighbors)
{
var idx = 1 * evt.target.getAttribute('id');
window.status = "Showing " + idx;
addLines(evt.target, neighbors[idx], k);
}
</script>
Note that we have to specify the type attribute but can use either javascript or ecmascript in most HTML browsers and SVG viewers.
Often, JavaScript code that defines functions that will be used in event handler attributes on HTML or SVG elements are placed in a script element near the top of the HTML or SVG document. For HTML, these function definitions and global variables often appear in the <head> element. JavaScript code can also appear within the <body> of an HTML document and is evaluated when it is processed and so can access previously created elements in the document.
We provide here a brief summary of many of the basic syntax features of the language. For more detailed information about JavaScript see [3, 24]
Executable statements typically end with a semicolon. Although it is not strictly required, it is good practice to use the semicolon in JavaScript.
Like R, curly braces group JavaScript statements together into executable blocks and are used for defining the body of a function or multi-expression if, while or for clause.
Variables are declared within a specific scope via the
var prefix, e.g.
var global = 1;
var debug = false;
function foo(N) {
var i;
for(i = 0; i < N; i++) {
...
}
}
The declaration can assign an initialization value to the variable as
in the first two expressions immediately above. These are global
variables; the variable i is local to the function
calls for foo. Note that the values for boolean
variables are true and false, not TRUE and FALSE as in R.
JavaScript supports arrays, including multi-dimensional arrays which
can be “ragged”, i.e. the sub-arrays can have different
length/dimensions. In the JavaScript code above, the variable
neighbors contains two arrays, one of length 6 and
the other of length 4. JavaScript uses 0-based indexing for arrays.
For example, the two-dimensional array neighbors,
neighbors[0] returns the first element of the
top-level array and this is itself an array of six integer values. The
expression neighbors[1][0] returns the first
element in the second array, and the value is the scalar 2.
Multi-line comments are delimited by /* and */ and single line comments begin with //, e.g.
/* This is a multi-line
comment.
*/
// This is a comment
var debug = false; // controls display of debug info with alert().
The equal sign (=) is the assignment operator. In addition, x += y is equivalent to the assignment x = x + y, and -=, *=, and /= are similarly defined. Also, the operator ++ increments the referenced variable in-place by 1 and -- decrements by 1, i.e. ++x is equivalent to x = x + 1,
Adding two character variables, pastes the two strings together. If a
number and string are “added”, the result will be a
string, e.g. "K is " + k results in the character string "K is 4".
Comparison operators are the same as in R,
e.g.
x == 1, abc < 10,
str != "this is a string". The
logical operators are &&, ||, and
! correspond to and, or, and not, respectively.
The control flow syntax is similar to that in R:
if (x < 2) {
...
} else if( x > 2 && x < 10) {
...
} else
...
JavaScript provides an if-else construct for conditional evaluation. We cannot assign the value of an if-else statement to a variable as we can in R.
x = if(y > 2) 3 else 10;
is a syntax error and will terminate the processing of the JavaScript code. JavaScript does provide the ternary operator for these simple situations: varname = (condition) ? value1 : value2
JavaScript supports for and
do...while loops. In the code below, the JavaScript
within the curly braces will be executed N + 1
times, as i takes on the values 0, 1, ...,
N.
for(var i = 0; i <= N ; i++) {
var target;
target = document.getElementById(neighbors[i]);
ids = ids + " " + neighbors[i];
...
}
Again, note that JavaScript is not vectorized, as R is.
JavaScript code can be used in-line within <script> elements within a document or as the value of attributes of HTML or SVG elements, e.g. event handlers such as onclick, onmouseover, onmouseout. The JavaScript code is one or more JavaScript expressions separated by ';' or new lines.
Typically these expressions call JavaScript functions. This is
especially true of event handler code that invokes a function with
specific arguments to respond to the event in a particular way. For
example, the SVG attribute onmouseover = "showNeighbors(evt,
k, neighbors)" results in a call to the JavaScript function
showNeighbors when the mouse moves over the
corresponding element in the SVG display (see Example 7, “Interactive Nearest Neighbors” for more details). The call passes three
arguments: the event object and the global variables
k and neighbors.
Unlike R, JavaScript functions are not defined and assigned to a variable. Instead, functions are defined using the keyword function as a prefix to the definition as in
function functionName(var1, var2, ..., varN)
{
body-code
}
Like R, the parameters correspond to local variables within each call to the function. Since (non-primitive) variables in JavaScript are passed by reference, changes to these objects are made to the objects in the calling frame and are accessible after the specific function call is completed, i.e. back in the calling frame.
In addition to the syntactic rules for JavaScript above, we spend a
great deal of time when writing JavaScript focused on objects and
their methods. JavaScript provides a large library of classes and we
either explicitly create instances of these via the
new operator, e.g. rx = new
Regexp("^[ACGT]+$") or work with existing objects provided
to us. We operate on these objects via their methods. We invoke
these methods as if they were functions belonging to the object,
e.g. rx.exec("my string") or
node.removeChildren(). Objects also have
properties or fields and we can query and set these, e.g.
button.value = "My Label" and
document.links.
There are many classes and each has many methods. This is what makes JavaScript useful, but also difficult to learn because you need to find the classes and methods of use for your task. There are a few classes and methods, however, that arise very commonly in JavaScript code for SVG and HTML documents. One of this is the Document that provides access to the contents of the document being displayed. Another is the general concept of a document element represented by the Node and Element classes and the more specific SVGElement and HTMLElement classes and their sub-classes (e.g. SVGCircleElement, HTMLHeadingElement). These classes allow us to not only query the contents of the document, but also to programmatically modify this content, adding new elements or changing the characteristics of existing elements.
When JavaScript code is evaluated as part of an HTML or SVG document,
there is an implicit global variable named
document. This is the top-level Document object and
we can refer to it without any declarations or additional code.
Perhaps the most important method this provides for our use is
getElementById which searches the entire
SVG/HTML tree and finds the element which has an id attribute with the
specified value. Another method,
getElementsByTagName, allows us to find
SVG/HTML elements based on the name of the element/tag,
e.g. “h1” or “path”.
Once we have an element in the document, we can use its methods to
operate on it. We can retrieve the values of the element's attributes
via getAttribute, e.g.
circle.getAttribute("r"). Similarly, we can set
the value of an existing or new attribute using
setAttribute. We can query the child nodes of
a Node/Element via its childNodes field, and the
parent node via parentNode field. We can add
nodes via the appendChild and
insertBefore methods, and we can remove nodes
via removeChild.
As with any programming language, JavaScript code we write often contains bugs, especially when we are learning the language. Since the code is being run within a Web browser or specialized SVG viewer, it is being run asynchronously (i.e. when an event occurs such as the document is loaded or the viewer clicks on a shape in the plot) rather than explicitly by our direct commands. As a result, debugging JavaScript can seem somewhat difficult. There are several tools to aid us, however. It is essential to look at the error console within the Web browser, e.g. selected in Firefox via the menu Tools -> Error Console, and in Opera under Tools -> Advanced -> Error Console.
While the “print” approach to debugging is not generally
a good one, it is a common approach in asynchronous JavaScript
programming. The idea is that we display the values of variables or
computations as we evaluate the code and display it for the viewer or
programmer to see in order to understand how the code is behaving. We
can display the message in the viewer's status bar or in a pop-up
window. In addition to helping the programmer debug code, this same
approach can be used to provide information to the viewer. The
alert function is the primary one used to display
information in a pop-up window. We construct the text of the message
to display and pass this as the sole argument in the call to
alert. The viewer must click on the "Okay" button
to dismiss the window and continue with the calculations.
In an HTML browser, we can display text in the status bar by merely
assigning a string containing the desired text to the variable
status. For example, we can display the number of
links in a document with
status = "# of links " + document.links.length
A more sophisticated approach to debugging is to use a browser extension such as Firebug (http://getfirebug.com) for Firefox, or Firebug Lite (http://getfirebug.com/lite.html) for various browsers (including Firefox). Firebug is an extension that the programmer has to install. Firebug lite is an JavaScript file that one can include by reference in an HTML document, e.g.
<script type='text/javascript'
src='http://getfirebug.com/releases/lite/1.2/
firebug-lite-compressed.js'>
</script>
These provide a separate panel within the browser's tab and show many
aspects of the JavaScript code and HTML document. We can write
messages to the console part of this panel via console.log("a
string") and use this to track how code is running.
We have seen how we can display information to the viewer via either a
pop-up window or the status bar. There are also facilities for getting
feedback from the viewer via pop-up windows. The
confirm function displays a message and allows the
viewer to proceed or cancel an operation. For example,
function doConfirm()
{
var con = confirm("Do you really want to do this?");
if (con == true)
{ ... }
else
{ ... }
}
We can also get information via HTML forms and other active elements of the document rather than via separate (pop-up) windows.