D3 has long been the de facto tool for building web-based data visualizations. Its paradigm of binding data to DOM elements and working with selections is extremely powerful. The library allows you to build practically any type of visualization; your imagination becomes the limiting factor.

The past couple of years have seen the tremendous rise in popularity of React, a JavaScript library for building user interfaces. Its declarative way of building reusable, composable components encourages a clean and functional way of building anything from simple widgets to complex single-page apps.

At Lucify, we build complex visualizations. Both D3 and React are fantastic tools that help us in our work. Combining the two, however, isn’t completely straightforward. The challenge lies in the overlapping responsibility of the two libraries: DOM manipulation. If both libraries manage the same parts of the DOM, things can get ugly.

In this article, I will go over different ways of combining React with D3 along with some best practices we’ve picked up in our work. I’m going to assume that you have basic knowledge of React and D3. The examples use TypeScript, another technology that we’ve been extremely happy with.

Who owns the DOM?

Deciding on who owns which parts of the DOM is the key decision you need to make when combining React with D3. Things will break if both libraries handle the same elements. In order to be fast, React maintains a virtual DOM — an internal representation of the DOM. If D3 modifies parts the DOM that React is responsible for, the shadow DOM will become out of sync, which results in undefined behavior. On the flipside, if React touches elements that D3 is handling, these elements will be missing properties that D3 depends on.

The decision about which library to hand DOM ownership to isn’t binary. You can let React handle certain elements while D3 takes care of others. Or you can let D3 handle all visualization-related elements. Or you can use D3 to only help with calculations and not touch the DOM at all. The decision depends heavily on what you’re trying to build and personal preference. Let’s look at the strengths and weaknesses of each approach.

D3 owns the DOM

One of the overwhelming reasons to choose D3 for DOM manipulation is the wealth of ready-made visualizations that are available online. It’s very easy to add an SVG element to your React component, hand the element over to D3, and let it take care of the rest. This lets you get started very quickly. When using this approach, from React’s point of view, the SVG element becomes a black box that is handled by D3.

The D3 libraries that help with certain DOM-related tasks are another reason to choose D3. These include common visualization features like axes, transitions, brushing, and zooming. The implementations are very flexible, allowing you to customize how they work to fit your needs.

This flexibility and power come at a cost. Even though individual D3 libraries have relatively simple APIs, understanding how to combine them in the “right” way when building complex visualizations is not a trivial task. Mike Bostock, the creator of D3, has shared hundreds of fantastic examples that are a great resource to learn from, but fully understanding how they work takes time. The code is elegant and succinct, but can have a steep learning curve — you need to be intimately familiar with the APIs of many different D3 libraries and language features like closures.

React owns the DOM

On the other end of the D3 vs. React spectrum, we have the approach in which React owns the visualization DOM. D3 is only used to help with layouts or scales, for example. In practice, this means that your code shouldn’t be importing d3-selection .

A major benefit of this approach is that you only have one mental model to keep in your head while building the DOM. There’s a real cognitive cost associated with jumping between React’s and D3’s ways of thinking, and not having switch back and forth is a definite plus. Being able to reason about your whole app in a similar fashion — going over the JSX in each component’s render() method — simplifies your cognitive burden.

Even though React and D3 are both declarative frameworks — you describe what you want, not how you want it — React somehow feels more declarative. It’s all too easy to start making imperative code using D3. When it comes to manipulating the DOM, React’s render() method makes it difficult to write imperative code. The component lifecycle methods are also a standardized, easy to understand way of handling the different stages of a component’s lifecycle.

React’s component-based approach to building an app has many benefits: components help you isolate concerns, make your code reusable, and let you easily compose your app from smaller parts. Thinking of your components as functions that map the passed in props to DOM elements is a good way of thinking about the structure of your app.

Being such a popular framework, many developers have worked on polishing React’s developer experience. Tools such as the React and Redux dev tools are immensely useful for developers. Hot module replacement is another such feature. Once you get used to having your components hot-swap themselves on each code change, having to reload the whole page and resetting the visualization to its initial state feels extremely cumbersome.

One of the downsides of using React is that you will have to manually implement some things — axes, transitions, brushing — that come out of the box with D3. Even though ready-made React components exist for these tasks, wiring them up to work smoothly with each other is not as simple as with D3.

A hybrid approach

As said, deciding between React and D3 doesn’t need to be an either/or decision. A hybrid approach can work well, where you use the strengths of each framework to manage the DOM. You can use D3 to handle certain parts of the visualization — axes and transitions, for example — while using React for others.

For an example of such an approach, see the example application later on in this post.

Transitions

In interactive visualizations, transitions play a crucial role. They greatly help users understand changes across time and result in a more aesthetically pleasing end result.

Transitions generally come in two flavors: CSS transitions and manual interpolations. Both can be done with React and D3.

Using CSS transitions

The CSS transition property is very powerful. It allows you automatically animate transitions between two states for many different types of properties, and is usually hardware-accelerated.

If you are transitioning between a set of states that you know beforehand, adding transitions can be as simple as adding or removing predefined CSS classes to an element. Doing this with either D3 or React is trivial.

If, on the other hand, you do not know all the possible states beforehand — the color of an area in a map, for example — using CSS classes alone is not enough. You will need to set the values of the style (or other) property based on your data. As long as that element has an appropriate CSS transition property, the change will be gradual. Setting these attributes with React or D3 is also straightforward.

To add transitions for elements that are entering or leaving the DOM, React and D3 have slightly different approaches. D3 has the enter and exit selections that can be styled to your tastes. React doesn’t have such functionality built-in, but you can use react-transition-group to apply styles or classes to elements as they enter or leave the DOM.

When it comes to SVG elements, CSS transitions have some caveats:

Many non-style attributes can’t be transitioned using CSS transitions.

With Safari, CSS transitions for transforms only work if you define the transform with a style attribute instead of the transform attribute.

attribute instead of the attribute. IE doesn’t support setting a transform for SVG elements using style . You will need to set the element’s transform attribute, which doesn’t support transitions.

As soon as you start encountering any of these limitations, it’s probably a good idea to switch to interpolations.

Interpolations

Sometimes CSS transitions aren’t enough. Especially when it comes to SVG, cross-browser compatibility can become an issue. Synchronising transitions across many elements or visualizations may also be difficult with CSS. For these situations, using interpolated transitions — i.e. setting the changing attributes programmatically — is the right choice.

To get transitions with React, you’ll need to resort to third-party libraries. react-motion is one of the most popular ones. It is extremely flexible: it comes with built-in spring-like animations and supports both synchronous and staggered animations. The initial learning curve may be a bit steep, but once mastered, creating complex animations is a breeze. If you’re only interested in animating entering, leaving, and reordered elements, react-flip-move is a great choice — it’s simple and works great.

Compared to React, D3 really shines when it comes to transitions. Managing synchronous, sequential, or staggered animations across multiple components is very easy. D3 also has out-of-the-box support for a host of different interpolators: colors, numbers, transforms, and strings that contain numbers.

If you’re using D3 to control the DOM, you should also use it to handle transitions. When using React, you can still use D3 to set the attribute of the element that changes on transitions — the height of a bar chart, for example — while React sets all other attributes. You can even pass a D3 transition instance as a prop to other React elements in order to synchronize transitions across your app. See the example application below for an example of controlling the heights of a bar chart using D3 transitions of an otherwise React-based app.

D3 utility libraries

D3 comes with many utility libraries that don’t touch the DOM. These libraries are useful for many of the common tasks required for building visualizations. With the release of version 4.0, D3 is now modularized, meaning it’s easier to pick and choose which parts of D3 to use in your project.

Below are a few highlights:

Fetching and parsing data: d3-fetch and d3-dsv help you load your data.

General utilities: d3-array, d3-random and d3-collection provide tools that make working with data easier.

String formatting: d3-format and d3-time-format help you format data into human-readable form.

Scales: d3-scale helps you translate your data into a visual representation and back. d3-scale-chromatic extends it with good choices for ready-made categorical colors.

Colors: d3-color, d3-hsv and d3-hcg make working with colors a lot simpler.

Geography: d3-geo contains utilities related to calculations and projections of geographic data. For even more projections, check out d3-geo-projection.

Layout and shapes: d3-shape, d3-polygon, d3-hierarchy, d3-force, d3-voronoi and d3-quadtree all help you organize data and lay them out in different shapes or layouts.

Visualization types: D3 includes support for laying out certain types of visualizations: d3-sankey, d3-chord, d3-contour and d3-hexbin.

Check out the D3 organization on GitHub for all of the official D3 libraries. Of course, there are many more unofficial open source libraries to choose from.

Working with data

When it comes to data, you will need to think about a few aspects: data preparation, the data file format, data loading, and data storage in your app.

It’s not uncommon for your raw data to be in a format that’s not optimal for your visualization. Transforming the data into a format that’s easier to work with is generally a good idea. We often write TypeScript scripts to perform this transformation.

For the data format, JSON is a good choice, especially for hierarchical data. JavaScript has built-in functions for generating and loading JSON, eschewing the need for third-party parsing libraries. Compared to binary formats or CSV/TSV, JSON does have the downside of producing larger files and may have slower parsing times. However, unless your dataset is very large, this shouldn’t be a problem, especially if your hosting provider compresses your files before sending them.

Loading data

If your dataset is relatively small, your data is stored as JSON, and you use Webpack as your build tool, the simplest way to load data into your app is to simply import the JSON file. Webpack will bundle the data into your code and turn the JSON contents into an array or object. Starting with Webpack 2.0, JSON importing works out of the box — json-loader is no longer needed.

The downside of this approach is that since the data is part of your application’s code, it increases the parsing time and thus loading time of your app. For small datasets, this delay is often negligible, but once your data becomes large, loading the data dynamically using AJAX while showing a loading indicator is a better approach.

To load your data files dynamically, I recommend using Webpack’s file-loader. It will include imported data files in the app bundle and generate the path to the file, which can then be passed to the fetch (or d3-fetch) function. If you’re using Redux, make sure to use something like redux-thunk or redux-saga to help manage asynchronous data loading.

Note that loading data dynamically will increase the complexity of your code: not only do you need to add code that fetches and parses the data, your UI will need to have different states depending on whether the data has been loaded or not.

Storing the data

Once you get the data into your app, you need to decide on how to store it. When only using D3, the choice is obvious: you simply bind it to DOM elements. With React, however, it makes more sense to store the data into a component’s state or into a data store like Redux. This will allow React to automatically update the UI when the data changes and allow you to react to changes by using React’s component lifecycle methods.

So which should you choose: a component’s state or an external data source like Redux? Dan Abramov, the creator of Redux, has some sage advice: you might not need Redux. For many cases, using a component’s local state is more than enough. If you decide to go with this approach and have multiple charts in your app that are linked in some way, you’ll need to store the data in the state of a common ancestor in your React component hierarchy.

At Lucify, our visualizations often show the same data from multiple perspectives, with many interlinked interactions. We’ve found the Redux state tree to be a good place to store both data and interaction selections. Although Redux does increase code architectural complexity, we’ve found the benefits usually outweigh the costs. Not having to pass data and callback functions up and down your component hierarchy tree as props results in cleaner code. By storing all data and selections in the state tree, we can easily modify them with actions and get free memoization by using reselect . Being able to think about your app as a mapping of the Redux state tree into DOM elements feels intuitive.

A note about transforming your data inside your app: you will often need to modify your data in some way before passing it into your visualization component and rendering it. While doing this transformation in a component’s render() function can be fine, keep in mind that there are at least two performance-related costs to this. First of all, whenever the component is re-rendered (i.e. its internal state changes or a parent component is re-rendered), the data transformation is redone as well. For small datasets, this is likely not a problem, but for larger ones, you may be incurring a significant performance penalty.

Secondly, generating the data will always result in a new data object being passed as a prop, which precludes the use of shallow comparisons to check for equality and possibly prevents you from doing easy performance optimizations. A solution to this is to memoize or cache the generated data. This can easily be done with reselect if all your data is in the Redux state tree.

An example visualization app

I put together a very basic example visualization that combines React, Redux, and D3:

The source code is available on GitHub:

Note: for this type of bar chart, this code is way over-engineered — if all you wanted to do is show a single bar chart and single data toggle, there’s no need for Redux. The code is meant to show techniques that can be used to build a more complex application.

The application loads the data inline using Webpack and stores it into the Redux state tree. Redux actions are used to store the selected year into the Redux state tree. We use reselect to create a memoized selector for the bar chart data.

The main App component lays out the application and passes the data into the generic BarChart component. We use react-dimensions to make the chart responsive — the library adds a containerWidth prop to our App component that we use to adjust the width of the bar chart component.

In our BarChart component, we use D3 to handle the axes, bar heights, and transitions related to the Y axis. React is used for everything else. The component stores cached copies of the D3 scale objects which are invalidated when appropriate props change. The axes are redrawn after the chart’s width, height or data change. You need to be careful about doing this inside componentDidUpdate instead of componentWillReceiveProps since you want to use the new prop values.

We use a separate Bar component so that we can easily store a reference to the each bar’s SVG element and update its height using D3 when needed. Each Bar component gets passed a D3 transition object from BarChart so that we can synchronize transitions, both across bars and the chart’s Y axis changes.

A few tips related to TypeScript typings:

Putting it into practice

One option that we haven’t discussed is the use of ready-made React visualization libraries like vx, Victory, react-vis, recharts and data-ui. For building basic visualizations, these libraries can be immensely useful, giving you impressive results in no time.

For the type of work we do at Lucify, however, we have found D3 and React to be the right level of abstraction for us. Whenever we try to use high-level visualization libraries, we quickly run into feature, design, or API limitations. Mike Bostock put it perfectly:

Code is often the best tool we have because it is the most general tool we have; code has almost unlimited expressiveness. Alternatives to code, as well as higher-level programming interfaces and languages, do well in specific domains. But these alternatives must sacrifice generality to offer greater efficiency within their domain.

All of the technologies presented here are being developed at a fast pace. As the tools allow for new ways of doing things, new best practices will be born. Keep in mind that the practices presented in this article might become outdated relatively quickly.

If you know of better ways to combine these technologies or build interactive visualizations, I’d love to hear from you! Feel free to contact me at @vsaarinen or ville.saarinen@lucify.com.

PS. ❤️ Prettier ❤️. Don’t leave home without it!