Visualization is for Sharing: Using React for Portable Data Visualization

Nate Hunzaker, Former Development Director

Article Category: #Code

Posted on

Data visualization has become a more frequent element of our work at Viget. Be it simple pie charts or beautiful maps displaying jersey sales, visually representing data in a compelling way is a great device for storytelling and provides rich fuel for social sharing.

Yet making these illustrations fast, shareable, and accessible has its challenges. Although browsers are powerful, tightly coupling the rendering process to them greatly complicates the generation of images that can be shared or saved for presentations.

On The Jersey Report, Chris Jones (the original wizard) came up with a really great way to solve this problem using PhantomJS:

Use PhantomJS to render browser content

(Many thanks to Owen Shifflett for this gorgeous graphic)

The diagram above highlights a fundamental challenge: the DOM. In this system, PhantomJS produces an actual DOM and is responsible for rasterizing content into a PNG.

Moving beyond the DOM

Here at Viget, we've become huge fans of React. It gives us greater flexibility by working with a virtual representation of the DOM. When the DOM simply becomes an idea, instead of a requirement, rendering becomes just another implementation detail. This is particularly useful when rendering content server-side, loosely diagrammed below:

Now let's get started.

Building Portable Data Visualization

All of the code for this post is available on Github. We'll make a simple radar chart:

For this example I am using PathsJS, which makes it easy to calculate the paths for an SVG graphic without worrying about actual DOM manipulation (handled by React). These same techniques are equally as effective with D3, although we like to perform custom builds to cut out what isn't in use.

The core rendering of the chart is quite simple. Let's start by building the container for the chart:

// I am using CommonJS for these examples.
// This makes it much easier to include code in both
// the browser and in NodeJS.
//
// To allow the browser understand CommonJS, I am
// using Webpack, see the config here:
// https://github.com/nhunzaker/an-isomorphic-chart/blob/master/webpack.config.js
var React = require('React');
var Radar = require('./radar');
var Chart = React.createClass({
  getDefaultProps: function() {
    return {
      data : [],
      height : 250,
      width : 400
    }
  },
  render: function() {
    var x = this.props.width / 2;
    var y = this.props.height / 2;
    var radius = Math.min(x, y);
    // For those new to this syntax (HTML in JS? Crazy huh?!), checkout
    // Facebook's post about it:
    // http://facebook.github.io/react/docs/jsx-in-depth.html
    return (
      <svg height={ this.props.height } width={ this.props.width }>
        <title>Monster Stats</title>
        <Radar data={ this.props.data } x={ x } y={ y } r={ radius } />
      </svg>
    );
  }
});

module.exports = Chart;

And thus, a radar chart was born. The last step is to throw it in the browser:

var React = require('react');
var Chart = require('./chart');
var data = require('../data/monsters.json');

React.render(<Chart data={ data } />, document.getElementById('chart'));

This gets us as far as the browser, but what about server side rendering? Easy:

// For full server config (templating, etc...) see 
// https://github.com/nhunzaker/an-isomorphic-chart/blob/master/server/index.js
// Teach node how to parse JSX
require('node-jsx').install();

var app      = require('express')();
var monsters = require('../data/monsters.json');
var React    = require('react');
var Chart    = require('../src/chart.jsx');

function buildChart() {
  return React.createElement(Chart, { data: monsters });
}

app.get('/', function(req, res) {
  // Render a stringified version of the React output, which leaves hooks
  // that help React to "awaken" on page load
  res.render('index', {
    chart: React.renderToString(buildChart())
  });
});

app.get('/chart.svg', function(req, res) {
  // Rendering to static markup cuts React specific hooks
  var payload = React.renderToStaticMarkup(buildChart());
  res.set({
    'Content-Type': 'text/svg+xml'
  });
  res.end(payload);
});

app.listen(process.env.PORT, function() {
  console.log("This example is running on port %s", process.env.PORT);
});

The server above specifies two routes. The first serves a page containing the chart, the second returns an SVG image of the chart itself.

You can see the final result of this work on the Github repo's example page:

Very cool! Not only do we get a fast server side response, but we can even export it to Adobe Illustrator! Even more — it works before JavaScript evaluates or even if it is disabled!

With the visualization coming from the server ahead of time, it matters less how slow a user's connection is (within reason). Additionally, it gives the user immediate feedback while the reasonably small JavaScript payload downloads in the background.

Wrapping up

In this example I used NodeJS to handle server-side rendering, however you could also create a simple script with the same core rendering methods and access it in any language (we've done a similar technique within a Rails app).

This is a very crude example, however the fundamental power of this technique hinges on React's ability to render outside of the DOM. With a surge of new ideas in the community, and extremely powerful visualization libraries at our disposal, it is a very exciting time to be in the field of front-end development. I look forward to seeing fast, portable data visualization become a greater part of the front-end developer toolkit.

Related Articles