Tuesday, July 26, 2016

Ooms Magical Polyglot World

crossposted from BuildingWidgets

Jeroen Ooms (@opencpu) provides R users a magical polyglot world of R, JavaScript, C, and C++. This is my attempt to both thank him and highlight some of all that he has done. Much of my new R depends on his work.

Ooms' Packages

metacran provides a list of all Jeroen's CRAN packages. Now, I wonder if any of his packages are in the Top Downloads.

jsonlite

Let's leverage the helpful meta again from metacran and very quickly get some assistance from hint-hint jsonlite.

library(jsonlite)
library(formattable)
library(tibble)
library(magrittr)

fromJSON("http://cranlogs.r-pkg.org/top/last-month/9") %>%
  {as_tibble(rank=rownames(.$downloads),.$downloads)} %>%
  rownames_to_column(var = "rank") %>%
  format_table(
    formatters = list(
      area(row=which(.$package=="jsonlite")) ~ formatter("span", style="background-color:#D4F; width:100%")
    )
  )
rank package downloads
1 Rcpp 236316
2 plyr 208609
3 ggplot2 201959
4 stringi 188252
5 jsonlite 175853
6 digest 174714
7 stringr 173835
8 magrittr 166437
9 scales 156694
`jsonlite` is an ultra-fast reliable tool to convert and create `json` in `R`. It's fast because like much Jeroen's work, he leverages `C`/`C++` libraries. `shiny` and `htmlwidgets` both depend on `jsonlite`.

V8

V8 gives R its own embedded JavaScript engine to leverage functionality in JavaScript that might not exist in R. For example, the WebCola constraint-based layout engine offers valuable technology not available within R. Let's partially recreate the smallgroups example all in R. You might notice that the previously mentioned jsonlite is essential to this workflow.

library(V8)
library(jsonlite)
library(scales)

ctx = new_context(global="window")

ctx$source("https://cdn.rawgit.com/tgdwyer/WebCola/master/WebCola/cola.min.js")

## [1] "true"

### small grouped example
group_json <- fromJSON(
  system.file(
    "htmlwidgets/lib/WebCola/examples/graphdata/smallgrouped.json",
    package = "colaR"
  )
)

# need to get forEach polyfill
ctx$source(
  "https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.1.10/es5-shim.min.js"
)

# code to recreate small group example
js_group <- '
// console.assert does not exists
console = {}
console.assert = function(){};

var width = 960,
  height = 500

graph = {
"nodes":[
  {"name":"a","width":60,"height":40},
  {"name":"b","width":60,"height":40},
  {"name":"c","width":60,"height":40},
  {"name":"d","width":60,"height":40},
  {"name":"e","width":60,"height":40},
  {"name":"f","width":60,"height":40},
  {"name":"g","width":60,"height":40}
],
"links":[
  {"source":1,"target":2},
  {"source":2,"target":3},
  {"source":3,"target":4},
  {"source":0,"target":1},
  {"source":2,"target":0},
  {"source":3,"target":5},
  {"source":0,"target":5}
],
"groups":[
  {"leaves":[0], "groups":[1]},
  {"leaves":[1,2]},
  {"leaves":[3,4]}
  ]
}

var g_cola = new cola.Layout()
  .linkDistance(100)
  .avoidOverlaps(true)
  .handleDisconnected(false)
  .size([width, height]);

g_cola
  .nodes(graph.nodes)
  .links(graph.links)
  .groups(graph.groups)
  .start()
'

# run the small group JS code in V8
ctx$eval(js_group)

## [1] "[object Object]"

Now, WebCola has done the hard work and laid out our nodes and links, so let's get their positions.

nodes <- ctx$get('
  graph.nodes.map(function(d){
    return {name: d.name, x: d.x, y: d.y, height: d.height, width: d.width};
  })
')

links <- ctx$get('
  graph.links.map(function(d){
    return {x1: d.source.x, y1: d.source.y, x2: d.target.x, y2: d.target.y}
  })
')

Some great examples of packages employing V8 are geojsonio, lawn, DiagrammeRsvg, rmapshaper, and daff.

rjade

We got layout coordinates above. Let's use another one of Jeroen's packages rjade that provides jade (now called pug) templates through V8. rjade will let us build a SVG graph with our layout.

library(rjade)
library(htmltools)

svg <- jade_compile(
'
doctype xml
svg(version="1.1",xmlns="http://www.w3.org/2000/svg",xmlns:xlink="http://www.w3.org/1999/xlink",width="960px",height="500px")
  each l in lines
    line(style={fill:none, stroke:"lightgray"})&attributes({"x1": l.x1, "x2": l.x2, "y1": l.y1, "y2": l.y2})
  each val in rects
    g
      rect(style={fill: fillColor})&attributes({"x": val.x - val.width/2, "y": val.y - val.height/2, "height": val.height - 6, "width": val.width - 6, rx: 5, ry: 5})
      text&attributes({"x": val.x, "y": val.y, "dy": ".2em", "text-anchor":"middle"})= val.name
'
      ,pretty=T
)(rects = nodes, lines = links, fillColor = "lightgray")

HTML(svg)
a b c d e f g

rsvg

If we are not in the browser though with inline SVG support, we very likely will want a static image format such as png or jpeg. Of course, Jeroen has that covered also with the crazy-speedy rsvg. Jeroen offers base64, but in this case we will use base64enc, since it allows raw.

library(rsvg)
library(base64enc)

graph_png <- rsvg_png(charToRaw(svg))

tags$img(src=dataURI(graph_png), mime="image/png")

magick

Jeroen's newest package magick is in my mind the coolest. magick gives us all the power of ImageMagick as easy R functions, and is pure wizardry. I am still shocked that it compiled first try with absolutely no problems.

library(magick)

graph_img <- image_read(graph_png)
wizard_img <- image_read("http://www.imagemagick.org/image/wizard.png")

images <- image_annotate(
  image_append(
    c(
      image_scale(image_crop(wizard_img, "600x600+100+100"), "100"),
      image_crop(graph_img, "400x400+200+0")
    )
  ),
  "Ooms is a Wizard!",
  size = 20,
  color = "blue",
  location = "+100+200"
)

tags$img(src=dataURI(image_write(images)), mime="image/png")

commonmark

I should note that this document was assembled in rmarkdown. RStudio gives us lots of tools for working with rmarkdown, but Jeroen gives us a powerful tool commonmark. Let's use it to give our readers other options for output.

library(commonmark)

rmarkdown::render("Readme.Rmd", "Readme.md", output_format="md_document")

tex <- markdown_latex(readLines("Readme.md"))
cat(tex, file="Readme.tex")

This would convert markdown to LaTeX. As a test, I used commonmark to make the html for this post.

Conclusion and Thanks

There are of course more packages, but I'll stop here. Jeroen Ooms truly is a wizard, and the R community is extraordinarily blessed to have him. Thanks so much Jeroen.

For even more wizardry, be sure to check out opencpu from Jeroen, which makes R available as a web service.