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.
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”).
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”
Figure 2. The leaf image from the OpenClipArt library broken into two separate shapes: the leaf border and the leaf veins.
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”).
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.
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).
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)
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.
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))
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”).
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))
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.
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.
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.
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)
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.
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”).
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.
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.
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 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).
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.
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
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