Declarative D3 Examples in React

Rowan 🤖
Technical Credit 💸
3 min readMar 24, 2018

D3.js is a very popular library for creating visualisations on the web. The main problem with using it inside a React app is that D3 uses a jQuery like API for creating the SVG visualisations. For example, you quite often start your D3 visualisation like this:

var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

Which creates the below in the DOM as a child of <body>:

<svg width="960" height="500"><g transform="translate(480,250)"/></svg>

Aside from the chained methods call making the code look very verbose (and intimidating to those not familiar with D3), it also means that D3 is directly manipulating the DOM. This DOM manipulation is something that we can work with, but it means that we are not taking full advantage of the Virtual DOM and the expressiveness of JSX.

Thankfully in a lot of situations it is very easy to convert D3 API calls into their JSX equivalents. This idea isn’t new, and I would recommend reading this detailed post on it (especially noting the list of D3 DOM manipulating APIs), so this post will focus on converting two real examples from the D3 gallery into declarative React. These conversions will try to follow the original as closely as possible in terms of order and variable names, but there are a few differences that are worth mentioning:

  • The original examples use the ES5 API which (for me at least) can be a bit tricky to find the location of their ES module equivalent e.g. ES5 usesd3.layout.pie() , but when using modules, pie needs be imported from d3-shape — there is no d3-layout module.
  • The original examples load data from the server e.g. d3.csv("data.csv"), but to keep things simple, the converted code just parses a string variable containing the same data e.g. csvParse(dataCsv).
  • ES2015+ features are used where possible e.g. arrow functions.

Example 1: Donut Chart

The first example is a pretty basic donut chart. You can see the original ES5 version here and the converted code below.

As you can see, we still leverage D3 to do all the heavy lifting e.g. calculate the actual path descriptions, but we use JSX to declare the elements of our SVG. Hopefully you will agree that seeing how the actual SVG will be structured makes it a lot easier to develop, debug and maintain.

Example 2: Bar Chart

The second example is a bar chart that is once again quite simple. The original ES5 version can be seen here and the converted below.

This conversion looks like it contains a lot more code, but if you ignore the Prettier formatting and remove the long comment in the render method explaining a bug from the original version, then the conversion is actually quite succinct.

A point of contention in the above conversion is the use of the helper functions axisBottom and axisLeft. These functions use direct DOM manipulation to create our x and y axes, so they work against our goal of using JSX for everything. However, this seems like a reasonable compromise for the following reasons:

  • We use them to manipulate an empty node <g ref={node=>select(node).call(axisLeft(y).ticks(10,'%'))}/>, so React will be happy enough with it.
  • We don’t need to chain a whole lot of methods together, so the code is still quite readable.
  • These functions are provided for convenience and it’s not that uncommon to ignore them and write your own axis code.
  • The actual source code of axis.js is not too complicated, and it wouldn’t be too difficult to convert it into a React component (assuming that someone hasn’t already done this).

Next Steps

You can find the full source code in my Github repository. I will be updating this repository with more examples as I need them (happy to accept PRs as well). You can also see working demos on Github.

Animations using the D3 transition APIs still prove to be a challenge in React since they rely heavily on DOM manipulation. I hope to find an elegant solution to this and will write about it as soon as I do. Update: See the follow-up post about animations.

--

--