If you’re planning to build a React app with more than a few simple data visualizations, you’ll probably want to pick an approach that:
- is well-documented, proven, and under active development
- has a relatively expressive interface– meaning it lets you write custom data graphics with just enough verbosity to express what you want
- plays well with React– that is, it doesn’t need to directly mutate the DOM
- gives you plenty of freedom to customize it to suit your app’s needs and visual style
Given the above criteria, these are what I’d consider to be the best available options (not in any particular order):
Note: there are a handful of projects like react-chartjs
, and others which don’t seem to be as actively-maintained, that provide high-level React chart components. If these suit your app, then of course you could just use them!
Comparing the Options
First of all, I’d consider D3
to be the standard library for data visualization on the web. It’s the most-starred result for “data visualization” on Github, leading by 50k stars. To see what it’s capable of, just check out Mike Bostock’s bl.ocks
It seems like Vega and D3 have comparable expressiveness (it is, after all, a wrapper of D3). The awesome thing about Vega is that it lets you write much of what you could write in D3 in a declarative syntax.
The caveat is that the conventional way to use Vega seems to be to let it mutate the DOM. This is similar to the convention for using D3, but soon we’ll see how to use D3’s libraries to scale your data, map it to SVG path data, and then return an element tree, just like you would in any ordinary React
component. I’m not sure how you’d do this with Vega.
Victory and Recharts
Both Victory and Recharts expose high-level chart components, as well as some lower level chart “parts” like axes, tooltips, etc. While these certainly play well with React, for the sake of customization and expressiveness I prefer D3’s “theory of graphics”
approach. I think there’s just more you can do when you’re in control of scaling data and rendering vs. being at the mercy of higher level component interfaces.
It could be that I’m just influenced by the huge number of examples of amazing graphics made using D3, and it may be possible to create graphics that are just as great using Victory and Recharts. Unfortunately though, if I got stuck, I don’t think I’d have as many resources to get help as I would using D3.
Using D3’s libraries to do everything but rendering DOM nodes
D3 is comprised of 30 libraries. I’ve scanned through the sources of each of them to figure out which are in some way dependent on the DOM, and which are totally independent of it.
Most (22/30) of the D3 APIs don’t have any functions that come in direct contact with the DOM!
The above APIs can be used to transform your data to a form that can be mapped to the React elements you’ll use to construct your graphic.
Before we get to an example, we’ll list the (8/30) D3 APIs that do access or mutate the DOM. Some of these can be used with React, with some caveats (e.g., work around a function or two, or carefully use refs to select nodes, and allow D3 to mutate them).
Example: Writing a composite spark line / scatter plot component
For the example, let’s make a simple time series visualization that‘s a composite of a spark line and scatter plot with cartesian axes and some styling.
Let’s make this with React and D3!
This example chart that I built for one of our apps at Analytical Flavor Systems plots a product’s perceived quality (0–7) for the past 14 days. Here’s the data:
Designing the Component Interface
TimeSeriesSparkLineScatterPlot component should be reusable, so we should design it to take an array of any type of object. To support this, we’ll give it the props
selectY. Since it renders an SVG, we should also give it dimension props
height. Its interface now looks something like:
Writing the Component
Let’s get right to it… we’ll start by writing a component that takes the props from the interface we came up with.
Looks good! Now let’s generate an SVG path for the spark line and then render a group and path for the spark line.
That’s a start :)
That’s a bit ugly, but it looks accurate. We’ll wait until the end to add styles. Let’s add some axes. Now, remember that
d3-axis is in the “does mutate the DOM” list… Let’s look at how we can use it in our component:
Caveat: if our data updates, our axes won’t re-render, since we’re using D3 to render them directly into their group nodes when they mount. A workaround might be to add a
**key** to each axis group that changes when the data is updated. This would cause their nodes to unmount, and new nodes to mount, and then the axes would be rendered again. Maybe this is a bad idea... I’d appreciate feedback!
Okay, so it looks like our y axis is rendering, but its contents are outside the bounds of our SVG. Also, our x axis is at the top?!
says “regardless of orientation, axes are always rendered at the origin.”
The D3 convention for this is to manually add a margin around the content and to translate our y axis down to the bottom.
Adding the margins isn’t really interesting enough to walk through. I wrote a component that handles adding a margin around the content of an SVG, called
Great! Let’s add a margin around our content and move our x axis to the bottom of the content:
Awesome, now let’s add the scatter plot. We’ll just get scaled points for our data, and we’ll render a circle at each point:
We get this:
That was easy!
Most of the D3 libraries don’t depend on the DOM. This means you can use them with React, React Native, or something totally different.
I hope this entry was enjoyable and informational. If this kind of thing is up your alley, we’re growing our team at Analytical Flavor Systems
, and we’d be happy to have a conversation ;)
We’re still in the early stages of exploring what’s possible with D3 and React. I’m very interested in hearing the experiences of others who have done data visualizations in React– feel free to leave a comment, link to a related post you’ve written, or ping me @danscan on Twitter