JavaScript
Article

Building Animated Components, or How React Makes D3 Better

By Swizec Teller

This article was peer reviewed by Mark Brown and Jack Franklin. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

D3 is great. As the jQuery of the web data visualization world, it can do everything you can think of.

Many of the best data visualizations you’ve seen online use D3. It is a great library, and with the recent v4 update, it became more robust than ever.

Add React, and you can make D3 even better.

Just like jQuery, D3 is powerful but low level. The bigger your visualization, the harder your code becomes to work with, the more time you spend fixing bugs and pulling your hair out.

React can fix that.

You can read my book React+d3js ES6 for a deep insight, or keep reading for an overview of how to best integrate React and D3. In a practical example, we’ll see how to build declarative transition-based animations.

A version of this article also exists as a D3 meetup talk on YouTube.

Is React Worth It?

OK, React is big. It adds a ton of code to your payload, and it increases your dependency footprint. It’s yet another library that you have to keep updated.

If you want to use it effectively, you’ll need a build step. Something to turn JSX code into pure JavaScript.

Setting up Webpack and Babel is easy these days: just run create-react-app. It gives you JSX compilation, modern JavaScript features, linting, hot loading, and code minification for production builds. It’s great.

Despite the size and tooling complexity, React is worth it, especially if you’re serious about your visualization. If you’re building a one-off that you’ll never have to maintain, debug, or expand, stick to pure D3. If you’re building something real, I encourage you to add React to the mix.

To me, the main benefit is that React ~~forces~~ strongly encourages you to componentize your code. The other benefits are either symptoms of componentization, or made possible by it.

The main benefits of using React with your D3 code are:

  • componentization
  • easier testing & debugging
  • smart DOM redraws
  • hot loading

Componentization encourages you to build your code as a series of logical units – components. With JSX, you can use them like they were HTML elements: <Histogram />, <Piechart />, <MyFancyThingThatIMade />. We’ll dive deeper into that in the next section.

Building your visualization as a series of components makes it easier to test and debug. You can focus on logical units one at a time. If a component works here, it will work over there as well. If it passes tests and looks nice, it will pass tests and look nice no matter how often you render it, no matter where you put it, and no matter who calls it. 🙌

React understands the structure of your code, so it knows how to redraw only the components that have changes. There’s no more hard work in deciding what to re-render and what to leave alone. Just change and forget. React can figure it out on its own. And yes, if you look at a profiling tool, you’ll see that only the parts with changes are re-rendered.

alphabet-redraws

Using create-react-app to configure your tooling, React can utilize hot loading. Let’s say you’re building a visualization of 30,000 datapoints. With pure D3, you have to refresh the page for every code change. Load the dataset, parse the dataset, render the dataset, click around to reach the state you’re testing … yawn.

With React -> no reload, no waiting. Just immediate changes on the page. When I first saw it in action, it felt like eating ice cream while the crescendo of 1812 Overture plays in the background. Mind = blown.

Benefits of Componentization

Components this, components that. Blah blah blah. Why should you care? Your dataviz code already works. You build it, you ship it, you make people happy.

But does the code make you happy? With components, it can. Components make your life easier because they make your code:

  • declarative
  • reusable
  • understandable
  • organized

It’s okay if that sounds like buzzword soup. Let me show you.

For instance, declarative code is the kind of code where you say what you want, not how you want it. Ever written HTML or CSS? You know how to write declarative code! Congratz!

React uses JSX to make your JavaScript look like HTML. But don’t worry, it all compiles to pure JavaScript behind the scenes.

Try to guess what this code does:

render() {
  // ...
  return (
      <g transform={translate}>
          <Histogram data={this.props.data}
                     value={(d) => d.base_salary}
                     x={0}
                     y={0}
                     width={400}
                     height={200}
                     title="All" />
          <Histogram data={engineerData}
                     value={(d) => d.base_salary}
                     x={450}
                     y={0}
                     width={400}
                     height={200}
                     title="Engineer" />
          <Histogram data={programmerData}
                     value={(d) => d.base_salary}
                     x={0}
                     y={220}
                     width={400}
                     height={200}
                     title="Programmer"/>
          <Histogram data={developerData}
                     value={(d) => d.base_salary}
                     x={450}
                     y={220}
                     width={400}
                     height={200}
                     title="Developer" />
      </g>
  )
}

If you guessed “Renders four histograms”, you were right. Hooray.

After you create a Histogram component, you can use it like it was a normal piece of HTML. A histogram shows up anywhere you put <Histogram /> with the right parameters.

In this case, the parameters are x and y coordinates, width and height sizing, the title, some data, and a value accessor. They can be anything your component needs.

Parameters look like HTML attributes, but can take any JavaScript object, even functions. It’s like HTML on steroids.

With some boilerplate and the right dataset, that code above gives you a picture like this. A comparison of salary distributions for different types of people who write software.

4 histograms of salary distributions

Look at the code again. Notice how reusable components are? It’s like <Histogram /> was a function that created a histogram. Behind the scenes it does compile into a function call – (new Histogram()).render(), or something similar. Histogram becomes a class, and you call an instance’s render function every time you use <Histogram />.

React components should follow the principles of good functional programming. No side effects, statelessness, idempotency, comparability. Unless you really, really want to break the rules.

Unlike JavaScript functions where following these principles requires deliberate effort, React makes it hard not to code that way. That’s a win when you work in a team.

Declerativeness and reusability make your code understandable by default. If you’ve ever used HTML, you can read what that code does. You might not understand the details, but if you know some HTML and JavaScript, you know how to read JSX.

Complex components are made out of simpler components, which are made out of even simpler components, which are eventually made out of pure HTML elements. This keeps your code organized.

When you come back in six months, you can look at your code and think, “Ah yes, four histograms. To tweak this, I should open the Histogram component and poke around”

React takes the principles I’ve always loved about fancy-pants functional programming and makes them practical. I love that.

Let me show you an example – an animated alphabet.

A Practical Example

Animated alphabet

We’re going to build an animated alphabet. Not because it’s the simplest example of using React and D3 together, but because it looks cool. When I show this at live talks, people always oooh and aaah, especially when I show proof that only the DOM elements with changes get redrawn.

This is a shortened version of a more in-depth article on React and D3 and transitions that I posted on my blog a few months ago. We’re going to gloss over some details in this version to keep it short. You can dive into the full codebase in the github repository.

The code is based on React 15 and D3 4.0.0. Some of the syntax I use, like class properties, is not in stable ES6 yet, but should work if you use create-react-app for your tooling setup.

————

To make an animated alphabet, we need two components:

  • Alphabet, which creates random lists of letters every 1.5 seconds, then maps through them to render Letter components
  • Letter, which renders an SVG text element, and takes care of its own enter/update/exit transitions

We’re going to use React to render SVG elements, and we’ll use D3 for transitions, intervals, and some maths.

The Alphabet Component

The Alphabet component holds the current list of letters in state and renders a collection of Letter components in a loop.

We start with a skeleton like this:

// src/components/Alphabet/index.jsx
import React, { Component } from 'react';
import ReactTransitionGroup from 'react-addons-transition-group';
import * as d3 from 'd3';

require('./style.css');

import Letter from './Letter';

class Alphabet extends Component {
    static letters = "abcdefghijklmnopqrstuvwxyz".split('');
    state = {alphabet: []}

    componentWillMount() {
        // starts an interval to update alphabet
    }

    render() {
        // spits out svg elements
    }
}

export default Alphabet;

We import our dependencies, add some styling, and define the Alphabet component. It holds a list of available letters in a static letters property and an empty alphabet in component state. We’ll need a componentWillMount and a render method as well.

The best place to create a new alphabet every 1.5 seconds is in componentWillMount:

// src/components/Alphabet/index.jsx
    componentWillMount() {
        d3.interval(() => this.setState({
           alphabet: d3.shuffle(Alphabet.letters)
                       .slice(0, Math.floor(Math.random() * Alphabet.letters.length))
                       .sort()
        }), 1500);
    }

We use d3.interval( //.., 1500) to call a function every 1.5 seconds. On each period, we shuffle the available letters, slice out a random amount, sort them, and update component state with setState().

This ensures our alphabet is both random and in alphabetical order. setState() triggers a re-render.

Our declarative magic starts in the render method.

// src/components/Alphabet/index.jsx
    render() {
        let transform = `translate(${this.props.x}, ${this.props.y})`;

        return (
            <g transform={transform}>
                <ReactTransitionGroup component="g">
                    {this.state.alphabet.map((d, i) => (
                        <Letter d={d} i={i} key={`letter-${d}`} />
                     ))}
                </ReactTransitionGroup>
            </g>
        );
    }

We use an SVG transformation to move our alphabet into the specified (x, y) position, then define a ReactTransitionGroup and map through this.state.alphabet to render a bunch of Letter components with wanton disregard.

Each Letter gets its current text, d, and index, i. The key attribute helps React recognize which component is which. Using ReactTransitionGroup gives us special component lifecycle methods that help with smooth transitions.

ReactTransitionGroup

In addition to the normal lifecycle hooks that tell us when a component mounts, updates, and unmounts, ReactTransitionGroup gives us access to componentWillEnter, componentWillLeave, and a few others. Notice something familiar?

componentWillEnter is the same as D3’s .enter(), componentWillLeave is the same as D3’s .exit(), and componentWillUpdate is the same as D3’s .update().

“The same” is a strong concept – they’re analogous. D3’s hooks operate on entire selections – groups of components – while React’s hooks operate on each component individually. In D3, an overlord is dictating what happens; in React, each component knows what to do.

That makes React code easier to understand. I think. ¯_(ツ)_/¯

ReactTransitionGroup gives us even more hooks, but these three are all we need. It’s nice that in both componentWillEnter and componentWillLeave, we can use a callback to explicitly say “The transition is done. React, back to you”.

My thanks to Michelle Tilley for writing about ReactTransitionGroup on StackOverflow.

The Letter Component

Now we’re ready for the cool stuff – a component that can transition itself into and out of a visualization declaratively.

The basic skeleton for our Letter component looks like this:

// src/components/Alphabet/Letter.jsx

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import * as d3 from 'd3';

class Letter extends Component {
    state = {
        y: -60,
        x: 0,
        className: 'enter',
        fillOpacity: 1e-6
    }
    transition = d3.transition()
                   .duration(750)
                   .ease(d3.easeCubicInOut);

    componentWillEnter(callback) {
        // start enter transition, then callback()
    }

    componentWillLeave(callback) {
        // start exit transition, then callback()
    }

    componentWillReceiveProps(nextProps) {
        if (this.props.i != nextProps.i) {
           // start update transition
        }
    }

    render() {
       // spit out a <text> element
    }
};

export default Letter;

We start with some dependencies and define a Letter component with a default state and a default transition. In most cases, you’d want to avoid using state for coordinates and other transient properties. That’s what props are for. With transitions we use state because it helps us keep React’s reality in sync with D3’s reality.

That said, those magic default values could be default props. That would make our Alphabet more flexible.

componentWillEnter

We put the enter transition in componentWillEnter.

// src/components/Alphabet/Letter.jsx
    componentWillEnter(callback) {
        let node = d3.select(ReactDOM.findDOMNode(this));

        this.setState({x: this.props.i*32});

        node.transition(this.transition)
            .attr('y', 0)
            .style('fill-opacity', 1)
            .on('end', () => {
                this.setState({y: 0, fillOpacity: 1});
                callback()
            });
    }

We use reactDOM.findDOMNode() to get our DOM node and use d3.select() to turn it into a d3 selection. Now anything D3 can do, our component can do. Yessss! 🙌

Then we update this.state.x using the current index and letter width. The width is a value that we Just Know™. Putting x in state helps us avoid jumpiness: The i prop changes on each update, but we want to delay when the Letter moves.

When a Letter first renders, it’s invisible and 60 pixels above the baseline. To animate it moving down and becoming visible, we use a D3 transition.

We use node.transition(this.transition) to start a new transition with default settings from earlier. Any .attr and .style changes that we make happen over time directly on the DOM element itself.

This confuses React because it assumes it’s the lord and master of the DOM. So we have to sync React’s reality with actual reality using a callback: .on('end', ...). We use setState() to update component state, and trigger the main callback. React now knows this letter is done appearing.

componentWillLeave

The exit transition goes in componentWillLeave(). Same concept as above, just in reverse.

// src/components/Alphabet/
    componentWillLeave(callback) {
        let node = d3.select(ReactDOM.findDOMNode(this));

        this.setState({className: 'exit'});

        node.transition(this.transition)
            .attr('y', 60)
            .style('fill-opacity', 1e-6)
            .on('end', () => {
                callback()
            });
    }

This time, we update state to change the className instead of x. That’s because x doesn’t change.

The exit transition itself is an inverse of the enter transition: letter moves down and becomes invisible. After the transition, we tell React it’s okay to remove the component.

componentWillReceiveProps

The update transition goes into componentWillReceiveProps().

// src/components/Alphabet/Letter.jsx
    componentWillReceiveProps(nextProps) {
        if (this.props.i != nextProps.i) {
            let node = d3.select(ReactDOM.findDOMNode(this));

            this.setState({className: 'update'});

            node.transition(this.transition)
                .attr('x', nextProps.i*32)
                .on('end', () => this.setState({x: nextProps.i*32}));
        }
    }

You know the pattern by now, don’t you? Update state, do transition, sync state with reality after transition.

In this case, we change the className, then move the letter into its new horizontal position.

render

After all that transition magic, you might be thinking “Holy cow, how do I render this!?”. I don’t blame ya!

But we did all the hard work. Rendering is straightforward:

// src/components/Alphabet/Letter.jsx
    render() {
        return (
            <text dy=".35em"
                  y={this.state.y}
                  x={this.state.x}
                  className={this.state.className}
                  style={{fillOpacity: this.state.fillOpacity}}>
                {this.props.d}
            </text>
        );
    }

We return an SVG <text> element rendered at an (x, y) position with a className and a fillOpacity. It shows a single letter given by the d prop.

As mentioned: using state for x, y, className, and fillOpacity is wrong in theory. You’d normally use props for that. But state is the simplest way I can think of to communicate between the render and lifecycle methods.

You Know the Basics!

Boom. That’s it. You know how to build an animated declarative visualization. That’s pretty cool if you ask me.

This is what it looks like in action:

Such nice transitions, and all you had to do was loop through an array and render some <Letter> components. How cool is that? 😉

In Conclusion

You now understand React well enough to make technical decisions. You can look at project and decide: “Yes, this is more than a throwaway toy. Components and debuggability will help me.”

For extra fun, you also know how to use React and D3 together to build declarative animations. A feat most difficult in the olden days.

To learn more about properly integrating React and D3 check out my book, React+d3js ES6.

  • http://mentedigital.github.io José Cage

    Awesome stuff… (y)

    • http://swizec.com Swizec

      o/

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in JavaScript, once a week, for free.