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’s 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 and 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.
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.
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.
Declarativeness 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
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 renderLetter
componentsLetter
, 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 Stack Overflow.
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.
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!
Frequently Asked Questions about React and D3
How does React improve D3’s performance?
React enhances D3’s performance by providing a virtual DOM. This feature allows React to only update the parts of the DOM that have changed, rather than the entire DOM. This results in a significant performance boost, especially when dealing with large datasets. React also provides a more structured way to build and manage components, which can make your D3 code cleaner and easier to maintain.
Can I use D3 with React for real-time data visualization?
Yes, you can use D3 with React for real-time data visualization. React’s efficient update mechanism combined with D3’s powerful visualization capabilities make it an excellent choice for real-time data visualization. You can use D3 to generate the visual elements and React to render and update them efficiently.
How do I integrate D3 with React?
Integrating D3 with React involves using D3 for calculations and React for rendering. First, you create a React component. Inside this component, you use D3 to calculate the properties of the visual elements. Then, you use these properties to render the elements using React. This approach allows you to leverage the strengths of both libraries.
What are the benefits of using D3 with React?
Using D3 with React offers several benefits. React’s virtual DOM provides efficient updates, which is especially beneficial when dealing with large datasets or real-time data. D3, on the other hand, offers powerful data visualization capabilities. By using them together, you can create efficient, interactive, and visually appealing data visualizations.
Can I use D3 transitions with React?
Yes, you can use D3 transitions with React, but it requires a different approach than using D3 alone. React’s virtual DOM can interfere with D3’s transitions. To overcome this, you can use D3 to calculate the transition states and React to apply these states over time. This way, you can create smooth animations that leverage the strengths of both libraries.
How do I handle events in D3 with React?
Handling events in D3 with React involves using React’s event handling system. You can define event handlers in your React components and pass them to the D3 elements as props. This way, you can handle user interactions using React’s synthetic event system, which provides a consistent interface across different browsers.
Can I use D3’s data binding with React?
While D3’s data binding is powerful, it doesn’t work well with React’s virtual DOM. Instead, you can use React’s state and props to manage your data. You can then use D3 to calculate the properties of the visual elements based on this data. This approach allows you to leverage the strengths of both libraries while avoiding conflicts.
How do I update a D3 visualization in React?
Updating a D3 visualization in React involves updating the state or props of your React component. When the state or props change, React will re-render the component. You can use D3 in the render method to calculate the new properties of the visual elements based on the updated data.
Can I use D3’s scales with React?
Yes, you can use D3’s scales with React. You can use D3 to calculate the scales based on your data and then use these scales to render your visual elements in React. This approach allows you to leverage D3’s powerful scale functions while benefiting from React’s efficient rendering.
How do I debug a D3 visualization in React?
Debugging a D3 visualization in React involves using the developer tools provided by your browser. You can inspect the React components and their state and props, as well as the D3 visual elements. You can also use console.log statements to print out values at different points in your code. This can help you identify where things are going wrong.