+

Advanced SVG Graphics from R

Paul Murrell

University of Auckland
Department of Statistics

Simon Potter

University of Auckland
Department of Statistics
This document is licensed under a Creative Commons Attribution 3.0 New Zealand License .

Abstract

The gridSVG package has recently provided an interface for some more advanced SVG graphics features: gradient fills and pattern fills, clipping paths, masks, and filters. This report describes a simple test case for some of these advanced graphics features and then explores some ideas for making use of these features in Statistical Graphics.


A Source Image
Fill Gradients
Clipping Paths
Filters
Working off-screen
Gradient Fills in Statistical Graphics
Pattern Fills in Statistical Graphics
Clipping Paths in Statistical Graphics
Filters in Statistical Graphics
Masks in Statistical Graphics
Summary
A. Software used in this report
Bibliography

A Source Image

For the simple test case, we will start with an image of a leaf that was generated by OpenClipArt user Aungkarn Sugcharoun (see Figure 1, “A leaf”).

Figure 1. A leaf image from the OpenClipArt library.


The original SVG image was converted to a PostScript format using Inkscape so that it could be imported into R using the grImport package.

+
library(grImport)
PostScriptTrace("fall12.ps", "fall12.xml")
leaf <- readPicture("fall12.xml")

We define several functions (code not shown) that allow us to generate grobs based on the original leaf image and draw the leaf border and the leaf veins separately. Examples of the use of these functions are shown below and the output is shown in Figure 2, “Components of a leaf”

+
pushViewport(viewport(x=0, width=.5, just="left"))
drawLeaf()
popViewport()
pushViewport(viewport(x=.5, width=.5, just="left"))
drawVeins()
popViewport()

Figure 2. The leaf image from the OpenClipArt library broken into two separate shapes: the leaf border and the leaf veins.


Fill Gradients

We will now begin to work with the gridSVG package to create a new image.

+
library(gridSVG)

One new feature is the ability to define gradient fills, either linear gradients or radial gradients. The code below defines a simple linear gradient that smoothly transitions from black at the bottom-left corner, through red, to yellow at the top-right corner (see Figure 3, “A linear gradient”).

+
fill <- linearGradient(c("black", "red", "yellow"))

Figure 3. A linear gradient.


We will now use this linear gradient to fill the leaf shape. First, we draw the leaf outline and then we apply the fill gradient using the grid.gradientFill() function (see Figure 4, “A linear gradient fill”). In this case, we draw a grob first and give it a name, then use that name to apply the gradient fill to the appropriate grob. There is also a gradientFillGrob() function to apply the fill to a grob off-screen instead.

+
drawLeaf(name="leaf")
grid.gradientFill("leaf", fill)

Figure 4. A linear gradient fill.


Note that this operation does not draw a gradient fill on a normal R graphics device. The fill is only visible once the image has been exported to SVG via the grid.export(), or by using the special gridsvg() device. The resulting SVG image is then viewed in a browser or some other application that can render SVG.

The next bit of code demonstrates the creation and use of a radial gradient (see Figure 5, “A radial gradient fill”). In this gradient, the colours are semitransparent and transition smoothly from red at the centre to yellow 70% of the way to the edge (and then remain at yellow the rest of the way to the edge).

+
fill2 <- radialGradient(c(rgb(1,0,0,.2), rgb(1,1,0,.4)),
                        stops=c(0, .7))
+
drawLeaf(name="leaf-2")
grid.gradientFill("leaf-2", fill2)

Figure 5. A radial gradient fill.


Clipping Paths

Another new feature in gridSVG is the ability to create and use non-rectangular clipping regions. The following code generates a standard grid polygon grob, uses it to define a clipping path, and then clips the radial-gradient-filled leaf using the grid.clipPath() function and the clipping path (see Figure 6, “A non-rectangular clipping region.”).

+
pg <- polygonGrob(c(.7, 0, 0, 1, 1),
                  c(0, .7, 1, 1, 0))
cp <- clipPath(pg)
+
drawLeaf(name="leaf-2")
grid.gradientFill("leaf-2", fill2)
grid.clipPath("leaf-2", cp)

Figure 6. A non-rectangular clipping region. On the left is a diagram showing the shape of the region that is being used to clip the leaf and on the right is the result of clipping the leaf using that region.


This clipped radial-gradient leaf can then be drawn over the top of the linear-gradient leaf to produce a more subtle autumnal effect (see Figure 7, “Two leaves overlaid.”).

Figure 7. Two leaves overlaid: the first is filled with a linear gradient and the second is filled with a semitransparent radial gradient and then clipped.


Filters

A third new feature in gridSVG is the ability to apply filter operations, such as blurs and compositing operations, to components of an image.

The following code generates a simple Gaussian blur filter and then applies it to a drawing of the "veins" of the leaf (see Figure 8, “A filter operation.”).

+
blur <- filterEffect(feGaussianBlur(sd=1))
+
drawVeins("veins")
grid.filter("veins", blur)

Figure 8. A filter operation On the left is a diagram showing the veins of the leaf and on the right is the result of applying a Gaussian blur to the drawing.


These blurred veins can be drawn on top of the combined linear and radial gradient image (see Figure 9, “Veins overlaid on leaf”).

Figure 9. Blurred veins over a semitransparent radial gradient leaf over a linear gradient leaf.


The code below demonstrates a more complex filter. This time, the starting point is the leaf outline, filled with a linear gradient. The opaque area of this image is blurred and then offset to the right and down by a small amount to create a "drop shadow" (see Figure 10, “A complex filter operation.”).

+
blurAlpha <- feGaussianBlur(input="SourceAlpha",
                            sd=5, result="blur")
offset <- feOffset(input="blur",
                   unit(3, "mm"), unit(-3, "mm"))
drop <- filterEffect(list(blurAlpha, offset))
+
drawLeaf("leaf-shadow")
grid.gradientFill("leaf-shadow", fill)
grid.filter("leaf-shadow", drop)

Figure 10. A complex filter operation starting with a filled leaf, using the opaque region of the leaf, blurring that region, and then offsetting the blur.


This drop shadow can be drawn first, with the gradient fills and blurred veins on top (see Figure 11, “A fake 3D leaf”).

Figure 11. Blurred veins over a semitransparent radial gradient leaf over a linear gradient leaf over a drop shadow.


Working off-screen

As a final demonstration, the following code shows that there are off-screen variations of all of the functions used above. So it is possible to create grobs and gTrees and apply clipping paths, gradient fills, and filters to them before drawing.

The code below also demonstrates how these SVG features can be applied to whole collections of grobs at once. The combination of linear-gradient leaf plus clipped radial-gradient leaf plus blurred veins are all further modified using an "emboss" filter (which contributes slightly to the fake 3D effect; see Figure 12, “An autumnal leaf”).

+
leaf1 <- gradientFillGrob(leafGrob("leaf-1"), fill)
leaf2 <- clipPathGrob(gradientFillGrob(leafGrob("leaf-2"), fill2), cp)
veins <- filterGrob(veinsGrob("veins"), blur)
leafTreeChildren <- gList(leaf1, leaf2, veins)
emboss <- filterEffect(list(feConvolveMatrix(kernelMatrix=
                                             rbind(c(1,0,0),
                                                   c(0,1,0),
                                                   c(0,0,-1)))))
leafTree <- filterGrob(gTree(children=leafTreeChildren), emboss)

Figure 12. Blurred veins over a semitransparent radial gradient leaf over a linear gradient leaf, all embossed, over a drop shadow.


Gradient Fills in Statistical Graphics

In this section, and those that follow, we will propose some ideas for possible uses of advanced SVG graphics features in a statistical graphics context. In several cases, there are clearly useful applications, but in some cases the demonstrations are highly experimental and not fully developed. We do not claim to have discovered an uncontroversial application for all of the new SVG features, but prefer to emphasise that access to these features enables us to explore new ways to present data.

This section gives an example of a possible use of gradient fills in a statistical plot. The idea here is to use a gradient fill to represent variation, by replacing error bars on points in a scatterplot with gradient fills. This counts as one of the not uncontroversial applications.

The starting point is a lattice plot with a custom panel function that draws invisible rectangles around each data point (in addition to the normal data symbols).

+
library(lattice)
+
xyplot(y ~ x, ylim=extendrange(c(y+delta, y-delta)), 
       pch=21, col="white", bg="black",
       panel=function(x, y, ...) {
           lrect(x-barwidth, y-delta, x+barwidth, y+delta,
                 border="transparent",
                 identifier="bar")
           panel.xyplot(x, y, ...)
       })

We now define a linear gradient, register it under the name "lg", and then apply the gradient to each of the individual rectangles in the plot. The result is shown in Figure 13, “A plot with a gradient”.

+
lg <- linearGradient(c("white", "black", "white"),
                     x0=.5, y0=0, x1=.5, y1=1)
registerGradientFill("lg", lg)
grid.gradientFill("bar.rect", label=rep("lg", 10), 
                  grep=TRUE, group=FALSE)

There are two extra details shown in this example. First, there is a call to the registerGradientFill() function. This registration step allows us to associate a specific label, "lg" in this case, with the gradient fill. This registration has been implicit in previous examples, with a label automatically generated by the system. The second detail is that the call to grid.gradientFill() uses the registered label, rather than the gradient fill object itself, to associate the gradient with a grid grob. The label is repeated 10 times because there are ten separate points around which we are drawing a linear gradient.

Figure 13. A lattice plot with a linear gradient applied to rectangles around each data symbol to represent uncertainty in the y-values.


Pattern Fills in Statistical Graphics

This section describes a use for pattern fills in a statistical plot. The starting point is a lattice barchart with a grouping variable (so that there are bars that need to be identified with a particular group; see Figure 14, “A barchart”).

+
plotdat <- aggregate(hp ~ gear + vs, data = mtcars, mean)
plotdat$gear <- factor(plotdat$gear)
plotdat$vs <- factor(plotdat$vs)
barchart(hp ~ gear, groups = vs, data = plotdat,
         xlab = "Number of Gears", ylab = "Mean Horsepower",
         auto.key = list(space = "right"), horizontal = FALSE)

Figure 14. A lattice barchart with a grouping variable.


The following code defines two patterns for the two groups of bars; one consisting of angled lines and one of blurred circles.

+
fills <- c("#CCFFFF", "#FFCCFF")
val0pat <- pattern(gTree(children=gList(
                             rectGrob(gp=gpar(col=NA, fill=fills[1])),
                             linesGrob(gp=gpar(col="black")))),
                   width = unit(2, "mm"), height = unit(2, "mm"),
                   dev.width = 1, dev.height = 1)
val1pat <- pattern(gTree(children=gList(
                             rectGrob(gp=gpar(col=NA, fill=fills[2])),
                             filterGrob(circleGrob(r=.4, gp=gpar(lwd=5)), 
                                        filterEffect(feGaussianBlur(sd=10))))),
                   width = unit(2, "mm"), height = unit(2, "mm"),
                   dev.width = 1, dev.height = 1)

These patterns can then be applied to the bars in the plot and the legend to produce Figure 15, “A barchart with pattern fills”. This sort of pattern fill is generally frowned upon because of the visual artifacts that it can produce, so the use of pattern fills is no longer common. However, this facility does make it possible to easily experiment with pattern fills to explore new uses; in this case, a very light fill is used in an attempt to create more of a texture that does not distract from the clear colour grouping when viewed in colour, but does assist with distinguishing between the bars if the plot is printed in grey scale.

Figure 15. A lattice barchart with a grouping variable and pattern fills.


Clipping Paths in Statistical Graphics

This section demonstrates an example of using clipping paths in a statistical plot (of sorts). The idea here is that we have geospatial data (the two-dimensional density of earthquakes) that we want to represent using filled contours (see Figure 16, “Filled contours”).

Figure 16. Filled contours representing the density of earthquake events.


These data relate to the geographic region of New Zealand, so we can provide a context by drawing a map outline for New Zealand over the filled contours (see Figure 17, “Filled contours with map”).

Figure 17. Filled contours representing the density of earthquake events, with a map of New Zealand overlaid.


If we want to only display the density of earthquakes over land (in New Zealand), we can apply a clipping path based on the outline of New Zealand to the filled contours (see Figure 18, “Filled contours clipped to a map”).

+
# Draw the contours
grid.polygon(polyxs, polyys, id.lengths = eachlevel,
             name = "contours", default.units = "native",
             gp = gpar(fill = fillColours,
                       col = adjustcolor(fillColours,
                                         1, 0.9, 0.9, 0.9)))
# Create a path of the NZ islands
nzpath <- pathGrob(pathxs, pathys, id.lengths = groupns,
                   default.units = "native")
# Draw the path and also clip the contours to it
grid.draw(nzpath)
grid.clipPath("contours", clipPath(nzpath))

Figure 18. Filled contours representing the density of earthquake events, clipped to a map of New Zealand.


Filters in Statistical Graphics

This section demonstrates an example of the use of filters in a statistical plot. The starting point is a multipanel lattice plot.

+
trellis.par.set(superpose.symbol=list(pch=21, fill=rgb(1,1,1,.5)),
                background=list(col="grey90"),
                panel.background=list(col="grey90"))
xyplot(Sepal.Length + Sepal.Width ~ Petal.Length + Petal.Width | Species,
       data = iris, scales = "free", layout = c(2, 2),
       auto.key = list(x = .55, y = .7, corner = c(0, 0)))

Now we define a simple blur filter and apply the filter to an empty piece of text (just so that the filter is exported as part of the SVG output).

+
blur <- filterEffect(feGaussianBlur(sd=2))
registerFilter("point-blur", blur)
grid.draw(filterGrob(textGrob(""), label="point-blur"))

Finally, we garnish the point symbols in the plot legend so that they call a javascript highlight() function on a mouse click.

+
legendSymbols <- grid.get("key.points", grep=TRUE, global=TRUE)
for (i in legendSymbols) {
    name <- i$name
    grid.garnish(name, 
                 onclick=paste("highlight(", 
                               substr(name, nchar(name), nchar(name)),
                               ")", sep=""))
}
grid.script(file="highlight-blur.js")

When the user clicks on one of the data symbols in the plot legend, the javascript code applies the blur filter to the corresponding data symbols in the plot panels, thereby highlighting one of the data series (see Figure 19, “A plot with a filter”).

Figure 19. A lattice plot with a blur filter that can be applied via mouse click. Clicking on one of the data symbols in the legend applies a blur filter to points in each panel to highlight the appropriate data series.


Masks in Statistical Graphics

Another new feature of gridSVG, that was not demonstrated in the initial leaf example, is the concept of a mask. This is an object that can be used to control what portion of an object is drawn by controlling the transparency of the rendering of the object. It is similar to the idea of clipping, but allows for rendering some parts of an object semitransparently rather than just "on" or "off"; it can also be easier to construct a mask rather than a clipping path in some circumstances.

In this section, we demonstrate a use for masks in a statistical graphics setting. The starting point for this example is a lattice plot with a reference grid and an internal legend (see Figure 20, “A lattice plot”). The important feature of this plot is that the reference grid is visible behind the legend.

Figure 20. A lattice plot with a reference grid and an internal legend; the reference grid is visible in the background of the legend.


The goal in this example is to remove the reference grid from the background of the legend, but to do so not by drawing an opaque background on the legend, but by masking the reference grid so that it is not drawn where the legend is drawn.

This requires creating a mask where the legend is being drawn (see Figure 21, “A legend mask”) and then applying that mask to the reference grid (see Figure 22, “A masked reference grid”). Whereever the mask is white, the reference grid is drawn, but where the mask is black, the reference grid is not drawn.

Figure 21. A mask based on the location of the legend in the lattice plot.


Figure 22. The mask applied to the reference grid in the lattice plot (to create a hole in the reference grid where the legend will be drawn).


Now the legend is drawn as normal, appearing in the hole in the reference grid (see Figure 23, “A masked reference grid”).

Figure 23. A lattice plot with a reference grid and an internal legend where the reference grid has been masked to create a hole where the legend is drawn (so that the reference grid is not visible in the background of the legend).


Summary

Version 1.2 of the gridSVG package provides an R interface for more advanced SVG features: gradient fills, pattern fills, clipping paths, masks, and filters. This has two significant benefits: first, it provides a convenient high-level interface for generating complex SVG images from data; second, it makes advanced graphics techniques available for constructing statistical plots. This report demonstrates some possible applications for statistical plots, but the longer term potential gain comes from the ability to experiment with advanced graphics in the visualisation of data. In other words, we may discover new ways to present data because we have access to new presentation tools.

A. Software used in this report

Most of the code in this report is R code that uses packages that are available from CRAN (see version information below). The exception is version 1.2-0 of the gridSVG package, which was only available from R-Forge at the time of writing. This should appear on CRAN in the near future.

+
sessionInfo()
R Under development (unstable) (2013-06-11 r62941)
Platform: x86_64-unknown-linux-gnu (64-bit)

locale:
 [1] LC_CTYPE=C                 LC_NUMERIC=C              
 [3] LC_TIME=en_NZ.UTF-8        LC_COLLATE=en_NZ.UTF-8    
 [5] LC_MONETARY=en_NZ.UTF-8    LC_MESSAGES=en_NZ.UTF-8   
 [7] LC_PAPER=C                 LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_NZ.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] grid      stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
[1] MASS_7.3-26     maps_2.3-2      lattice_0.20-15 gridSVG_1.2-0  
[5] RJSONIO_1.0-1   grImport_0.9-0  XML_3.97-0      XDynDocs_0.3-1 
[9] Sxslt_0.91-1   

loaded via a namespace (and not attached):
[1] codetools_0.2-8 tools_3.1.0    

Bibliography

[R] R Development Core Team. “ R : A Language and Environment for Statistical Computing ”. 2011. ISBN 3-900051-07-0.

[gridsvg] Paul Murrell and Simon Potter. “ gridSVG : Export grid graphics as SVG ”. 2012. R package version 1.2-0.

[grimport] Paul Murrell. “ Importing Vector Graphics : The grImport Package for R ”. 2009. Journal of Statistical Software.

[lattice] Deepayan Sarkar. “ Lattice : Multivariate Data Visualization with R ”. 2008. ISBN 978-0-387-75968-5.

[SVG] Erik Dahlstrom, Patrick Dengler, Anthony Grasso, Chris Lilley, Cameron McCormack, Doug Schepers, Jonathon Watt, Jon Ferraiolo, Jun Fujisawa, and Dean Jackson. “ Scalable Vector Graphics (SVG) 1.1 ”. 2011. http://www.w3.org/TR/2011/REC-SVG11-20110816/.


dynDoc("leaf.xml", "HTML", force = TRUE, xslParams = c(html.stylesheet = "http://stattech.wordpress.fos.auckland.ac.nz/wp-content/themes/twentyeleven/style.css customStyle.css", 
    base.dir = "HTML", generate.toc = "article toc"))
Wed Jul 10 11:47:55 2013