Building D3-inspired charts with React

From the D3.js homepage:

D3 is not a monolithic framework that seeks to provide every conceivable feature.

D3 focuses on providing tools to construct rich visualisations, rather than an out of the box solution, but D3 provides a lot of tools. The default D3 distribution is about 50KB minified and gzipped, and contains as well as core functionality around DOM manipulation and selection, data manipulation, and graphic generation utilities, various other things such as CSV parsing and an XHR wrapper.

I’ve experimented with building applications using only D3 and no other libraries, and a lot can be done with the support of D3, more easily than with the standard JavaScript browser APIs alone. On the other hand, many developers will find themselves integrating D3 visualisations as a part of a larger application using some other framework. They won’t want yet another XHR wrapper, or parsing of text data. If they are using Underscore they may not want D3’s array utilities. And no doubt most people don’t need to combine force layouts, pie charts and geographic projections on the same page.

More to the point, I don’t think of D3 just as a collection of tools but a way of thinking. Not just “thinking with data-joins”, but the realisation that there is an alternative to just dropping in a standard chart in a hole in a web-page, it’s often simple and more effective to build an appropriate visualisation from the ground up, more tailored to the data and context.

D3: A reaction

In this post I’ll do a couple of things. Firstly I’ll talk about using selected components of D3 rather than the whole library. Secondly I’ll look at an example of constructing a simple chart in a D3 “style” but using a different framework to provide the rendering engine. I’ve chosen to use React in place of D3 to render a chart to the browser DOM (and as we’ll see later, to a string to provide server-side rendering).

Full code for this example can be found here.

D3 in pieces

If you use D3 you probably include a single d3.js script (rather, d3.min.js), but the D3 is arranged modularly. It’s combined by a tool (also by Mike Bostock, creator of D3js) called SMASH. From its README:

SMASH TOGETHER FILES! PROBABLY JAVASCRIPT.

So it’s possible to make custom builds of D3 in the same way for including in your page. You can find out how on the smash wiki.

Alternatively, D3 is now published on NPM as a collection of separate packages providing CommonJS modules. Below I’m going to show the creation of some charts that don’t use D3 for rendering, however as I’m not interested in changing the way their scales work, I’m going to continue to use D3 scales. To do this, I install the module:

npm install --save d3-scale

And then simply require it:

var scale = require('d3-scale');

Of course it’s not that simple, as we’re talking about JavaScript included in a browser. To use a CommonJS module as installed by this node package, you might look to RequireJS, Browserify or Webpack. In the example accompanying this post I’m using Browserify.

Let’s Make a React Chart

I’m going to put together some simple charts using React, somewhat following the examples in the D3 tutorial series Let’s Make a Bar Chart. The first part culminates in an example generating a chart composed of <div> elements from an array of input data:

I want to create this same chart as a React component. Using JSX (via babel) to mix HTML tags in our JavaScript source, this is very straightforward, a simple component with a render function:

This component could be used as <BarChart data={[1, 2, 3]} />. The data is passed in as part of the data property, accessed as this.props.data. The function that maps props.data to the resulting <div> elements corresponds to the data-join in the D3 version. This particular D3 code only has an enter selection, but the react code just specifies the desired virtual DOM elements, which would correspond more to a fuller D3 example including an exit/update selection: if we were to update with different data, this component will update accordingly.

Again notice that we make use of the D3 scale as per the original, but using Underscore for the max function rather than taking on the d3-arrays module.

Similarly, looking at part II of Let’s Make a Bar Chart, we can also create SVG output. The result doesn’t need too much comment, other than to note I’ve made use of the ES6 features of our babel transform (ES6 classes, arrow functions, const, template string):

The output looks like this (in fact this is literally the resulting DOM):

1352

Server-side rendering

So what was the point of all this, other than to make a change of syntax and maybe switch one dependency for another? Well one thing that React does quite well is server-side rendering (or so-called isomorphic JavaScript): pre-rendering the same page server side, and making use of the same rendering logic when making updates on the client side. This might be to improve page load time, showing content without waiting for JS libraries to load, or to mix statically rendered content with dynamically rendered content tailored for the user.

In our case the React charts can be just as well rendered server side, without requiring either a DOM implementation such as jsdom, or a bowser engine like PhantomJS.

We use the same chart code module (thanks to the use of browserify), and render it as part of the HTML source on the server, while rendering to the dom on the client. For this example I make use of a simple template, where the server rendering will replace the reactOutput placeholder and then the component will be mounted client-side on the element with id reactNode:

<div id="reactNode">
    <%- reactOutput %>
</div>

The server code makes use of React.renderToString:

while the client simply renders on the mount node:

Animation

Something which is rather different is animation. In D3 you will generally use transition to animate a selection. For example, to fade bars in we would apply a transition to the enter selection, while to animate the change in value of bars when data is updated, we would apply a transition to the update selection.

For the chart above we would have a few options. The simplest to consider is where we just want to animate the change in a property which can be styled with CSS, and we can use CSS transitions with no code change.

.chart rect {
  fill: steelblue;
  transition: width 1s;
}

For the case of adding elements which we want to be animated, there is a standard React solution in the form of ReactCSSTransitionGroup/ReactTransitionGroup. This sets things up to trigger a CSS transition on the element entering using a pair of CSS classes in the style of ng-animate. Our example above becomes:

Notice we specify the transition name addBar, which will correspond to our CSS, and a component of <g> (because the default <span> will not work so well in our SVG). Then we add the accompanying CSS:

.chart .addBar-enter {
	opacity: 0.01;
}
.chart .addBar-enter.addBar-enter-active {
	opacity: 1;
	transition: opacity .5s ease-in;
}

This is very similar to adding a D3 transition on the enter selection. If we wanted to perform some other enter animation, for example a CSS animation rather than transition, we could use the hooks on ReactTransitionGroup to do so.

JavaScript animations

Unfortunately the original SVG example above had a <text> element with x and y properties, which can not be set via CSS, so we cannot create a transition as above. So on the data value changing, we would want something more like the D3 JavaScript animation used by transition().

There are a few libraries around to help with this, for example react.animate, react-animate, react-transition, react-transition-transformers. But basically, we can create an animation using a timer and interpolating animated values, making use of the React component state to cause the interpolated value to be rendered.

A simple example of this can be found in the example source for this post. You can see this is a little painful to do by hand, but allows for arbitrary complex JS animations - if not using one of the libraries mentioned above, it may make sense to use D3 or one of the available JS easing/tweening libraries.

Conclusion

I think D3 is great, in particular

  • The way of thinking about constructing visualisations
  • The library of existing visualisations to draw on

The implementation and the selection model are good as far as they go but may not be the right fit for all applications, and you might want to consider carefully which parts are the right fit for you.

In this post I looked at some simple examples of building charts using React rather than D3 as rendering engine. This might be attractive in the context of a larger React app, particularly where individual visualisation components are fairly small.

More fundamentally, there are contexts in which the React model makes things easier, in particular specifying the desired DOM output and relying on the virtual-dom diffing to sort out updates, rather than explicitly having to consider enter/exit/update selections. On top of that the use of JSX and interpolation can make things a little easier to read. On the other hand it can be seen that some things fit less well into this model, so it’s important to keep an open mind.

MORE BY NICHOLAS

blog comments powered by Disqus