6 min read

Week02 - Pan and Zoom Almost Any R Graph

Week02 - Pan and Zoom Almost Any R Graph

htmlwidgets News This Week

@yutannihiliation has been prolific this week with a couple new widget seeds and contributions to this widget metricsgraphics announced last week.

@dcenergy

@armish

@lmullen

Not launched last week, but a big miss by me is this htmlwidget for mapping from @lmullen. It was one of the first htmlwidgets built after the Dec. 17 release announcement, and offers some nice functionality and interesting examples.

@rich-iannone

Also, be sure to check out the new graphviz functionality added to last week’s widget of the week DiagrammeR.

This Week’s Widget - svgPanZoom

I wanted to head in a slightly different direction with this week’s widget. Instead of wrapping Javascript charting libraries as most htmlwidgets to date have done, I wanted to let R still do the plotting, since so many of us know and love base graphics, ggplot2, and lattice. I hope nearly every R user can easily incorporate this little widget into their R graphical analysis and publishing workflow.

What Does it Do?

With some help from SVGAnnotation or gridSVG, svgPanZoom will add pan and zoom functionality to nearly all R graphics using svg-pan-zoom.js, a tiny and dependency-free Javascript library. Maybe a little graphic drawn from last week’s DiagrammeR can help explain the process.

library(DiagrammeR)

DiagrammeR("
  graph LR;
    gr[<code>graphics</code>]-->sp[<code>svgPlot</code>]
    la[<code>lattice/trellis</code>]-->sp
    la[<code>lattice/trellis</code>]-->gs[<code>gridSVG</code>]
    gg[<code>ggplot2</code>]-->sp
    gg[<code>ggplot2</code>]-->gs
    sp-->|svg+js|ht[Interactive Graphic]
    gs-->|svg+js|ht
    ht-->Shiny
    ht-->rs[RStudio Viewer]
    ht-->rm[R Markdown]
    ht-->html[standalone HTML]
    ht-->iframe
    classDef default fill:none;
", height = 300, width = 550) %>>%
      htmlwidgets::as.iframe(
        file="diagram.html"
        , libdir = "lib"
        , selfcontained=F
        , width = 650
        , height =400
        , overflow = "hidden"
      )

Examples in R

Let’s start with the base-ics adding svgPanZoom to some plots created by the base graphics in R. We’ll need svgPanZoom and SVGAnnotation for this first set of examples.

#devtools::install_github("timelyportfolio/svgPanZoom")
library(svgPanZoom)
library(SVGAnnotation)

Base Graphics + svgPanZoom

Plots don’t get much more basic than this, but starting simple is the best way to isolate what is happening. When you run the example below, the Pan/zoom-able graph should appear in RStudio Viewer (if using RStudio) or show up in your browser of choice.

# as simple as it gets
svgPanZoom(
  svgPlot(
    plot(1:10)
  )
)

It is hard to tell that our simple little graph now has special powers. Some icons and hints might help. I’m struggling with whether to make this behavior the default. Let me know if you have an opinion.

# as simple as it gets
svgPanZoom(
  svgPlot(
    plot(1:10)
  )
  ,controlIconsEnabled=T
) 

Just to prove that it also works with more complicated plots, let’s do one more example in base graphics. We'll pull this "persian rug art" from?contour`.

svgPanZoom(
  svgPlot(
    {
      x <- y <- seq(-4*pi, 4*pi, len = 27)
      r <- sqrt(outer(x^2, y^2, "+"))
      opar <- par(mfrow = c(2, 2), mar = rep(0, 4))
      dev.new()
      for(f in pi^(0:3))
      contour(
        cos(r^2)*exp(-r/f)
        , drawlabels = FALSE
        , axes = FALSE
        , frame = TRUE, col = 'cadetblue')
    }
  )
)

For one more really nice example, see this beautiful little Kohonen masterpiece from volunteer tester and lover of interactive graphics Alex Bresler.

Grid (lattice | ggplot2) + svgPanZoom

svgPanZoom would be very incomplete if it did not also support the graphics libraries built on top of gridggplot2 and lattice–that spoil us R users. Before we start with the examples, I think I should explain the two choices we have to convert grid graphics to SVG. svgPlot from SVGAnnotation wraps the svg grDevice (probably Cairo) to convert R plots to SVG. Unfortunately, it often results in ugly XML that is difficult to customize or enhance. However, it is much faster than the other choice gridSVG function grid.export(). gridSVG though concentrates on clean, well-structured XML with text elements intact, unique identifiers, helpful meta information, and some helper Javascript. svgPanZoom prefers gridSVG, but if you’re looking for speed choose svgPlot. I’ll demonstrate both with the next example. Let’s go straight into a choropleth map in ggplot2 using Ari Lamstein’s package choroplethr.

library(choroplethr)
library(ggplot2)

# start with svgPlot
#  and use example from ?state_choropleth
data(df_pop_state)
sc <- state_choropleth(
  df_pop_state
  , title="US 2012 State Population Estimates"
  , legend="Population"
)

svgPanZoom(
  svgPlot(
    show(sc)
    # will need to manually specify height/width
    ,height = 9, width = 17
  ), controlIconsEnabled = T
)

Now with gridSVG. You’ll probably notice it takes much longer.

# now with gridSVG
svgPanZoom( sc, controlIconsEnabled = T)

Just in case you are thinking “maps are great, but what about my regular old ggplot2”, let’s do one from ?facet_wrap.

# example from ggplot2 facet_wrap documentation
d <- ggplot(diamonds, aes(carat, price, fill = ..density..)) +
  xlim(0, 2) + stat_binhex(na.rm = TRUE) + theme(aspect.ratio = 1)
# to do with svgPlot 
# svgPlot( { show( d + facet_wrap(~color) ) } )
svgPanZoom(
  (d + facet_wrap(~ color) )
  , controlIconsEnabled = T
)

I still love lattice graphics. Just in case you forgot about some of its functionality, I want to revive this example from ?wireframe.

library(lattice)
m <- wireframe(volcano, shade = TRUE,
           aspect = c(61/87, 0.4),
           light.source = c(10,0,10))

svgPanZoom(
  svgPlot( {show(m)} )
)
# gridSVG will take a little too long, but feel free to try
#   svgPanZoom(m)

Then as with all htmlwidgets, we can pair them with tags from htmltools. How about storytelling with our pan and zoom ?

library(htmltools)

spz = svgPanZoom(
  svgPlot( {show(m)} )
  ,controlIconsEnabled = T
  , height = 500
  , width = 500
  , elementId = "story-volcano"
)

html_print(tagList(
  "my favorite part of the volcano"
  ,tags$button("click here", onclick="zoomFavorite()")
  ,spz
  ,tags$script("
    function zoomFavorite (){
      var z = document.getElementById('story-volcano').zoomWidget;
      // programatically zoom to specified point
      z.zoomAtPoint( 3, {x:200, y:200});
    }
  ")
))

Not Enough Examples

Before the post gets too long and unwieldy, I’ll stop here with the examples. If you still want to see more, see a whole new batch of examples in the Readme.md. So far I have not found a single plot that didn’t work with svgPanZoom. Anybody that reports a failure wins the grand prize of getting mentioned in next week’s blog post.

Some Limitations

I feel like I should mention some of the limitations of svgPanZoom. As mentioned, it is aimed wholly at ease and simplicity with minimal dependencies, so things like an axis that stays put while the graph zooms are not currently possible. Also, not really a fault of svgPanZoom more a weakness of the svg grDevice, the SVGs are not namespaced, so if you have more than one on a page, you’ll notice some funky labels. With gridSVG however, this is not a problem. I’ll try to demonstrate some solutions to this in the documentation or implement into the functionality of svgPanZoom.

I have two killer features that I would like to implement, so stay tuned.

Thanks

Thanks so much for the help from all the help from Twitter testers.