Chernoff Faces with D3 and React.js

chernoff-faces-demo

TL;DR: here's the demo!

Chernoff faces[1] are a useful way to visualize multivariate data in a way that leverages the great facial recognition ability of our brains. There's a great paper[2] about it too! Here's an example that highlights their utility:

Imagine visually comparing election results as they're rolling in on election day. You want to see how things like voter turnout, Democrat-to-Republican ratio, votes counted, etc compare across a few select states. There are a number of separate categories (states) and lots of variables for each one, each with their own range of possible values.

Very quickly, things gets complicated and hard to follow.

One possible solution is to leverage our adeptness at facial recognition by representing a subset of those voting variables as facial features on a set of faces, where each face represents a state. So, if your're looking at Ohio, Kentucky, and Florida, you'll see three faces, and each face's brow, facial shape, mouth shape (eg: a smile or a frown), and even hair style or hat shape will let you easily notice minute changes as they happen.

It will take a bit of time to internalize the facial features, but you only need to learn a handful of facial-feature-to-variable mappings and you're good to go.

So, these faces would be great to use.

I went looking for tools to programmatically generate Chernoff faces.

First, there's an R package[3] for this, so you R folk can go do what you do.

But, there's also a D3 plugin[4] for this. However, as a D3 noob, I couldn't make sense of this plugin without digging into it quite a bit.

I set out to make it a bit simpler and also to experiment with javascript tooling, D3, SVG's fascinating language for doing vector graphics with markup, and React.js and the whole stateless approach to UI design.

Here's the Github project. Also, here's the demo!

The demo is very simple; you see one Chernoff face and you can play with various facial features' values live and see how different values affect the face.

Implementation Details

Internally, I have a few React components driving the thing. Those are fairly standard, but I want to call attention to the state-less way I wrapped up the D3 plugin. Here's the code, from this file:

// Let's define a stateless wrapper around this plugin now....

var d3ChernoffChart = {};

d3ChernoffChart.create = function(el, props, state) {  
  var svg = d3.select(el)
    // make sure an svg tag from the svg namespace exists
    // (https://github.com/mbostock/d3/wiki/Selections#append)
    .append('svg:svg')
    // set viewport size
    .attr('width', props.width).attr('height', props.height);

  this.update(el, state);
};

d3ChernoffChart.update = function(el, state) {  
  this._clearFace();
  this._drawFace(el, state.data);
};

d3ChernoffChart.destroy = function(el, props, state) {  
  this._clearFace();
};

d3ChernoffChart._drawFace = function(el, data) {  
  var face = d3.select('svg')
    // I expect a 'g' tag with class 'chernoff'
    .selectAll('g.chernoff')

  face.data(data)
    .enter()
    .append('svg:g')
    .attr('class', 'chernoff')
    // this is a series of D3 chained calls that invoke the
    // rendering of various facial features one by one, using `data`
    .call(chernoffRenderer);
};

d3ChernoffChart._clearFace = function() {  
  d3.select('svg').selectAll('g.chernoff').remove();
};

This was rather simple and I took some inspiration from this post. Made things up the foodchain in React component land a lot easier.

Overall, I loved this approach because D3 looked like it was bundling up state and logic too much. As a beginner, it's nice to have layers of interfaces easily visible. I had to make my own such interfaces to fully understand the overall problem.

The Development Experience

It was a lot of fun! I actually kept a development log too, which was a nice way to explore various options while not being afraid to strip away code entirely. I went through multiple rounds of massive project-wide changes.

I learned a lot about javascript tooling and bundled up a bunch of random projects as Bower components... only to end up switching back to npm based code along with browserify.

Oh, browserify absolutely rocks. Excellent way to make full use of npm packages. However, I do find bower very easy to use now, even if I have to bundle up things myself. I might try webpack, but browserify is so mindblowingly easy config-free... it'll be hard. Of course, that javascript file is kinda big...

And I like React.js so far. One particularly awesome feature: prop types! They throw nice errors and help you validate that you're receiving the right kind of data in your component. Super useful when you're developing a bunch of new components!


  1. Chernoff Faces -- Wikipedia Page
  2. Chernoff, Herman, "The Use of Faces to Represent Points in k-Dimensional Space Graphically", Journal of the American Statistical Association. June 1973, Vol. 68., No. 342, pp. 361-368.
  3. Chernoff Faces -- R Package
  4. Chernoff Faces -- D3 Plugin