Motivation

Where VIT Shines

  • Easy to use via a GUI
  • Animations work pretty well
  • Heavy use of R's facilities for both statistics and graphics
  • Programmed entirely in R
  • Cross Platform (works on at least Windows, OSX and Linux)

How VIT Creates Animations

VIT Frame

VIT Weaknesses

  • Repeated drawing is slow
  • No access to time. Animations work on a per-frame basis.
  • Animation sequences are very difficult to manage
  • The GUI and animations are described procedurally, rather than declaratively
  • Difficult to distribute — requires R and RGtk2 (along with a few other large packages)

How about gridSVG?

Using gridSVG to manage the task of drawing has several advantages:

  • Declarative animation that can track time (e.g. "from A to B in 3s")
  • Interactivity (via hyperlinking and JavaScript)
  • Vector graphics
  • Viewable by any modern web browser (even IE!)

Intro to gridSVG

gridSVG uses the grid graphics engine to produce SVG that retains the names of grid objects.

library(gridSVG)
grid.rect(name = "myrect")
grid.circle(name = "mycircle")
grid.ls()
myrect
mycircle
Simple gridSVG example

gridSVG Output

grid.export("example.svg")
<svg>
  <g transform="translate(0, 192) scale(1, -1)">
    <g id="gridSVG">
      <g id="myrect.1">
        <rect id="myrect.1.1" x="0" y="0" width="192" height="192"/>
      </g>
      <g id="mycircle.1">
        <circle id="mycircle.1.1" cx="96" cy="96" r="96"/>
      </g>
    </g>
  </g>
</svg>

Why not gridSVG?

  • Slow to generate graphics
  • Would require a web page to be loaded once an SVG was created
  • Each animation would require a new SVG file
  • Too much work!

Why not gridSVG?

We can load a web page with SVG content, but lose the ability to communicate with R.

gridSVG can only encode information in a plot once

R as a Web Server

  • How about using R as a web server? (not new: Rook, Rserve, shiny, rApache, ...)
  • In theory we can generate plots in response to user requests
  • We could then provide a web-based interface to applications
  • A 'shiny' example demonstrates this...
  • This also gives us access to powerful JavaScript libraries

JavaScript Examples with D3

A New Approach

  • How about using R a web server to generate gridSVG plots?
  • We also want parts of a plot to be updated too

The key idea:

  • R is awesome for statistics (and statistical graphics!)
  • JavaScript libraries are powerful for assisting with interactivity and animation
  • Use gridSVG to generate plots (and pieces of plots) for JavaScript libraries (like D3) to play with

Naming

The problem:

  • SVG allows us to name each piece of a plot (key reason for using gridSVG)
  • SVG is an XML grammar
  • XML requires unique names for each component
  • grid is more relaxed with naming, so no simple mapping is possible

Naming

Consider the following code:

library(grid)
pushViewport(viewport(name = "a"))
grid.rect(name = "a")
grid.circle(name = "a")
grid.draw(gTree(name = "a",
  children = gList(
    circleGrob(name = "a"),
    rectGrob(name = "a"))))
popViewport()

If we want to retain access to these names, and also produce valid SVG, we need a naming scheme!

Naming

The basic idea is to keep a counter for each time gridSVG meets a grob or viewport.

The resulting SVG ID is the name suffixed with the counter (separated by a .).

pushViewport(viewport(name = "a"))

a.1

grid.rect(name = "a")

a.2

And so on...

Naming

It is a bit more complicated than this.

We also have a way of controlling what the IDs look like (arguments to grid.export()):

  • prefix — Allows every generated ID to be prefixed with a given string
  • usePaths — Determine whether path information is retained in names
  • uniqueNames — Allow a direct mapping from grid names to SVG IDs

There are also further ways of controlling output: addClasses, annotate, and setSVGoptions()

Name Mapping

  • We know that gridSVG maps grid names to SVG IDs
  • It is difficult to work out what the exact ID is in advance

gridSVG now exports name mapping information so we can find out what the IDs are:

gridSVGMappings() and getSVGMappings()

Name Mapping

pushViewport(viewport(name = "a"))
grid.rect(name = "a")

What did these names turn into?

getSVGMappings("a", type = "vp")
[1] "a.1"
getSVGMappings("a", type = "grob")
[1] "a.2"

Name Mapping

We can also find out how to select the content using XPath or CSS Selectors.

getSVGMappings("a", type = "vp", result = "xpath")
[1] "//*[@id='a.1']"
getSVGMappings("a", type = "vp", result = "selector")
[1] "#a\\.1"

Node-based SVG

Problem: We want to generate an image in-memory, but couldn't because gridSVG would always save to a file

Solution: Use the wonderful XML package.

This also gives us many other benefits!

Node-based SVG

Benefits of XML

  • Easier and more reliable image construction
  • Able to generate in-memory
  • Can select pieces of the plot using XPath
  • And add or remove to an image at any point (not just appending)

Node-based SVG

An Example

library(ggplot2)
qplot(mpg, wt, data=mtcars, colour=cyl)

We'll try to remove the legend without using any plotting commands.

Node-based SVG

A ggplot2 qplot

Node-based SVG

svgdoc <- grid.export("")$svg
legendNode <- 
    getNodeSet(svgdoc,
               "//svg:g[@id='layout::guide-box.3-5-3-5.1']",
                c(svg="http://www.w3.org/2000/svg"))[[1]]
removeChildren(xmlParent(legendNode), legendNode)
saveXML(svgdoc, file = "ggplot-xml-fig2.svg")

Node-based SVG

A ggplot2 qplot with its legend removed

Node-based SVG

A ggplot2 qplot with only its legend

Coordinate System

  • grid viewports have a scale, location and dimension
  • gridSVG lost the scale and translated all locations and dimensions to pixels
  • No way of accessing any locations once we exported to SVG

We need a way of retaining viewport coordinate system information

Coordinate System

We export a viewport's coordinate system to JSON, a structured data format.

pushViewport(viewport(x = unit(0.5, "npc"),
                      y = unit(0, "npc"),
                      width = unit(3, "inches"),
                      height = unit(7, "cm"),
                      xscale = c(0, 20),
                      yscale = c(0, 20),
                      name = "example"))
popViewport()
grid.export("coords-example.svg", exportCoords = "file")

Coordinate System

{
    "example.1": {
        "x": 191.83,
        "y": -132.44,
        "width": 288.34,
        "height": 264.88,
        "xscale": [0, 20],
        "yscale": [0, 20],
        "inch":  96.11 
    } 
}

Coordinate System

So how do we use this?

First, read in the data using gridSVGCoords()

Then use one of the following functions:

  • viewportConvertX()
  • viewportConvertY()
  • viewportConvertWidth()
  • viewportConvertHeight()

We can also recreate a viewport using viewportCreate()

Using Coordinate System Information

We can convert grid units to SVG pixels

viewportConvertX("example.1", 3, "native")
[1] 110.45
viewportConvertY("example.1", 14, "native");
[1] 283.1

We can also go from SVG pixels to grid units

viewportConvertX("example.1", 110.45, "svg", "native")
[1] 3
viewportConvertY("example.1", 283.1, "svg" "native");
[1] 14

Example

Summary so far

What we now have in gridSVG:

  • Valid SVG names, including a convenient way of identifying graphical components (via name mapping information)
  • Node-based images, enabling us to build images in-memory and manipulate images easily
  • Coordinate system retention, so we know where to put new content

With this information at hand, we can re-build the LOESS smoother example using gridSVG

Content Selection

The XPath query used to select a legend from a ggplot2 plot:

//svg:g[@id='layout::guide-box.3-5-3-5.1']

A bit verbose, fortunately there is an alternative query language — CSS selectors.

CSS selectors are often used in JavaScript libraries

$(".header-nav") // jQuery
d3.selectAll(".header-nav") // D3

Comparing XPath and CSS Selectors

CSS Selector XPath Expression
#test *[@id = 'test']
.test *[@class and contains(concat(' ', normalize-space(@class), ' '), ' test ')]
body p body/descendant-or-self::*/p

CSS Selectors offer a subset of the functionality of XPath, but are far easier to write

selectr

  • One problem: the XML package doesn't let us use CSS selectors!
  • What if we could translate CSS selectors to XPath?
  • A Python package, cssselect can do this!
  • selectr is a port of cssselect to R

selectr Examples

library(selectr)
css_to_xpath("#test")
[1] "descendant-or-self::*[@id = 'test']"
css_to_xpath(".test")
[1] "descendant-or-self::*[@class and contains(concat(' ', normalize-space(@class), ' '), ' test ')]"
css_to_xpath("body p")
[1] "descendant-or-self::body/descendant-or-self::*/p"

selectr Examples

Let's use this on a “real” document? Let's use the Department's Technical Report Blog.

library(XML)
page <- htmlParse("http://stattech.wordpress.fos.auckland.ac.nz/")
# What is the page title?
querySelector(page, "title")
<title>Stat Tech | Statistics Technical Blog</title>

selectr Examples

# What are all of the links to technical reports?
querySelectorAll(page, "h1.entry-title > a")
[[1]]
<a href="http://stattech.wordpress.fos.auckland.ac.nz/2013-5-open-data-in-new-zealand/" title="Permalink to 2013-5 Open Data in New Zealand" rel="bookmark">2013-5 Open Data in New Zealand</a> 

[[2]]
<a href="http://stattech.wordpress.fos.auckland.ac.nz/2013-4-generating-structured-and-labelled-svg/" title="Permalink to 2013-4 Generating Structured and Labelled SVG" rel="bookmark">2013-4 Generating Structured and Labelled SVG</a> 

[[3]]
<a href="http://stattech.wordpress.fos.auckland.ac.nz/2013-3-generating-unique-names-in-gridsvg/" title="Permalink to 2013-3 Generating unique names in gridSVG" rel="bookmark">2013-3 Generating unique names in gridSVG</a> 
...

selectr

Similarly we can query namespaced content like SVG images with querySelectorNS() and querySelectorAllNS():

svgdoc <- xmlParse(system.file("tests/svg-mathml.svg",
                               package = "selectr"))
# Search for <script> elements in the SVG namespace
querySelectorNS(svgdoc, "svg|script",
                c(svg = "http://www.w3.org/2000/svg"))
<script type="application/ecmascript" xlink:href="test2.svg.coords.js"/> 

selectr

querySelectorAllNS(svgdoc, "svg|script",
                   c(svg = "http://www.w3.org/2000/svg"))
[[1]]
<script type="application/ecmascript" xlink:href="test2.svg.coords.js"/> 

[[2]]
<script type="application/ecmascript" xlink:href="test2.svg.convert.js"/> 

selectr Summary

Perform a translation from CSS Selectors to XPath

  • css_to_xpath()

Search for content matching a selector

  • querySelector() — first match
  • querySelectorAll() — all matches

Search for namespaced content (e.g. XHTML, SVG, MathML)

  • querySelectorNS()
  • querySelectorAllNS()

Another gridSVG Example

Animation Sequencing

  • Sequencing animations is difficult to do by hand
  • We want to ensure that animation B follows animation A
  • Doing this by changing starting times for animations does not scale
  • VIT has many distinct animations that could benefit from sequencing (if it had access to time)

Animation Sequencing

To solve these problems, the 'animaker' package was created.

  • It controls the sequencing of animations
  • Does not determine what the animations do
  • Manages only start times and durations

Animation Sequencing

An atomic animation is the simplest of all animations

It has three parameters:

  • start — the starting time
  • durn — the duration
  • label — a description for the animation
atomic(start = 3, durn = 1, label = "example")

Animation Sequencing

Atomic animations can be combined in two ways:

  • In series: a 'sequence' animation
  • In parallel: a 'track' animation

We can also make sequences of tracks and tracks of sequences.

Animation Sequencing

A sequence

a <- atomic(start = 0, durn = 3, label = "a")
b <- atomic(start = 1, durn = 2, label = "b")
c <- atomic(start = 0, durn = 1, label = "c")
exampleSeq <- vec(a, b, c, label = "sequence")
plot(exampleSeq)

Animation Sequencing

An animation sequence constructed by animaker

Animation Sequencing

A track

d <- atomic(start = 0, durn = 10, label = "d")
exampleTrack <- trac(exampleSeq, d, label = "track")
plot(exampleTrack)

Animation Sequencing

An animation track constructed by animaker

Animation Sequencing

The whole point is to produce a timing scheme:

timing(exampleTrack)
  label start durn vec      vecNum trac  tracNum
1 a     0      3   sequence 1      track 1      
2 b     4      2   sequence 2      track 1      
3 c     6      1   sequence 3      track 1      
4 d     0     10                   track 2 

Animation Sequencing

These animation sequences are only descriptions, we need to apply them

The TimingManager JS library performs this task by matching JavaScript functions with atomic animations (omitted)

We now have a way of managing animation sequences. Let's see what we can do with this!

Sampling Variation Example

Thank You!


Any Questions?

Extras

Non-rectangular Clipping Paths

grid.clipPath()

Non-rectangular clipping paths with gridSVG

Extras

Opacity Masks

grid.mask()

An image alongside a mask definition

Extras

Opacity Masks

grid.mask()

An image with a mask applied

Extras

Patterns

grid.patternFill()

A barchart with a pattern definition alongside

Extras

Patterns

grid.patternFill()

A barchart with a pattern applied

Extras

Gradients

grid.gradientFill()

Linear and radial gradients applied to grobs

Extras

Filter Effects

grid.filter()

The process of applying a drop-shadow filter effect

Extras

Filter Effects

grid.filter()

A filter effect applied to a grob