Stacked Bars

I recently came across @hrbrmstr’s streamgraph’s package. The package is nice for the manner in which it makes it easy to create streamgraphs but my interest was piqued when I notices that were select boxes. At some point I had tried developing simple HTMLwidget but had gotten gummed up in my understanding of how values were being passed around. Furthermore, HTMLwidgets sanitizes your javascript so there are no global variables left around to inspect and therefore debugging was a pain. So the streamgraphs package is interesting for providing a good best practice on how to pass in data. Heres a few things I learned followed by a little HTMLWidgets package I made that clones Mike Bostock’s stacked bar chart

Heres a few things I picked up from streamgraphs:

Global Variables#

To provide global variables that can be opened in Developer Tools, you need to specify the variables outside of the HTMLwidgets object. data assigned to these variables inside the widget object will be available in the browser.

//https://github.com/hrbrmstr/streamgraph/blob/master/inst/htmlwidgets/streamgraph.js
var dbg, dbg2, dbg3, dbgs, dbgy, dbgx;

HTMLWidgets.widget({
...
})

Long Form and Nesting#

This is really a combination of R and D3 together. In an html widget we’re going to provide the data a widget will use directly instead of the normal call to d2.csv(). How should we provide the data. @hrbrmst follows Hadley’s advice and provides the data in long form. There are some advantages here, namely, that checking for values is simple on the javascript side when all values will br provides as stings and will need to be coerced, for example, in to numbers.

# generate all combinations and provide a default value
# pass data, a 3-column,long df as JSON
data %>%
   left_join(tidyr::expand(., key, date), ., by=c("key", "date")) %>%
   mutate(value=ifelse(is.na(value), 0, value)) %>%
   select(key, value, date) -> data

then you can verify only a single column of values and expand back to wide-form in JS using d3.nest() with a rollup function.

// convert only a single column
data.forEach(function(d) {
   d.colname = d.colname;
  d.value = +d.value;
});

// convert to wide format as if read in by d3.csv()
// Read More here http://jonathansoma.com/tutorials/d3/wide-vs-long-data/
var widedata = d3.nest()
  .key(function(d) { return d["rowname"] }) // sort by key
  .rollup(function(d) {
    return d.reduce(function(prev, curr) {
      prev["rowname"] = curr["rowname"];
      prev[curr["colname"]] = curr["value"];
      return prev;
    }, {});
  })
  .entries(data) 
  .map(function(d) { 
    return d.values;
});

Resizing#

This bit takes care of resizing for you. This may now be standard on HTMLwidgets but I had trouble with this when trying to create maps.

  // initial render
  renderValue: function(el, params, instance) {
    instance.params = params;
    this.drawGraphic(el, params, el.offsetWidth, el.offsetHeight);
  },

  // updated render using instance.params plus the new h/w
  resize: function(el, width, height, instance) {
    if (instance.params)
      this.drawGraphic(el, instance.params, width, height);
    }

Interactivity#

This next bit is perhaps the most useful bit of all. The el in all of htmlwidgets is the element of the DOM that htmlwigdets creates. Perhaps this a “DUH” scenario but once you realize that, you can call and manipulate the DOM with D3/Jquery/etc. Here is the selection of a particular named sub-element created by streamgraph. Most importantly, this allows you to control your widget by the addition os select boxes, etc, opening up the world of truly cloning just about anything on blo.ocks with a little bit of effort

d3.selectAll("#" + el.id + " .layer")

Stacked Bar Charts#

The code for these are online.

library(dplyr)
library(tidyr)
library(stackedbar)

read.csv(system.file("extdata", "statesdata.csv", package = "stackedbar"),
         stringsAsFactors = FALSE) %>%
  tidyr::gather(ages,n,-State) -> dat

stackedbar(dat, "ages", "n", "State", legend=TRUE) %>%
        sg_fill_brewer("PuOr")
stackedbarnormal(dat, "ages", "n", "State", right=150)