The Combination of React and D3
A match made in heaven
Table of Contents:
Intro
This is a series of articles about combining
React and D3
. During all series we will build an application using best of both technologies. Combining them could be tough at some points and to achieve best result you need to balance it. So here we are to make some balance magic.
Attention — Some parts may feel opinionated: yes they are!
Problem
React is just great for rendering Views. D3 in its place is the richest framework for building data-driven applications. Historically D3 provided kind of imperative way to manipulate DOM with the direct access to it. You may see a lot of examples on https://bl.ocks.org/ which are great but don’t really connect with React way of manipulating the view. How can we combine them elegantly?
React — View, D3 — math
In Captify, we know how important is to show data to users. Insights could drive your choices and help you better understand your business. We are using React and D3 to make this happen.
First of all, we defined what was really important to us.
Our main goals to achieve were:
- a consistent way of managing View
- manipulate data with tools available in D3.
- the best available developer experience
- detailed control of elements
After multiple proofs of concept we outlined three popular opinions on the problem:
- React — View, D3 — math
- React — SVG as root, D3 — vanilla in lifecycles with real DOM
- React — render fake DOM, D3 — vanilla using fake DOM
Mentioned approaches nicely described by @tibotiber in the article.
With a little inspiration by @sxywu, we decided to stick with React — View, D3 — math. This approach gave us everything we were looking for. Ability to manage View all with React. Data manipulations with D3 functions. SVG will be rendered as usual components which give us detailed control on each element and reactful developer experience.
Why not react-d3 lib X?
There are plenty of available solutions for building charts using react and d3 with already existing components. The great list is provided here. They definitely can help you in building demos or applications that don’t require big flexibility of style or rendering.
But if you have a unique style requirement or a set of components wouldn’t be similar to default solutions — you might need something built by yourself.
Where the code comes?
Here.
Let’s build a simple static timeline chart. Something like the example below but without axises and grid behind the chart.
Jumping ahead you can find all related code here.
Or a quick demo deployed here.
After bootstrap, we have brand new CRA powered app with d3
and dayjs
. For development purposes, we added the full d3
library, but actually, we don’t need all of that. If you need some exact functions of d3
it would be much better to install only them like d3-scale
. In case you don’t know how much your project will grow just make sure you have tree shaking enabled.
Timeline
Our chart would consist of two basic parts D3 calculations
and Component
.
Here we defined aTimeLine
component with littleutil
components.
D3
d3.js
will provide us any calculations we want.
In our case they are 5 functions:
yScale + xScale — functions to define boundary sizes of our dataset, this should be recalculated each time data changes.
path + areaInside — functions to shape our data into visual formation. path
for line and areaInside
for the same line but with inside part for nice fill styles.
The interface is a function meant to be used before render. We could use it either as HOC or hook. For our chart, we are using a hook approach. That’s why we returning the calculated data and functions in an array.
Export default vs named
. Named exports give us the ability to carefully unit test exact functions, default export gives us good usage of combination — just what we need in a single chart.
Parameter passing
. You may notice the different style of passing arguments, for this I have only 1 recommendation put scales as separate arguments and everything else could go as the last parameter in a gathered options object.
defaultMapper
is a helpful thing. Sometimes you want to use the same scales but for different shape of data, in that case, you could just pass different mapper and reuse old scaling function.
Component
Props. We have only 1 property viewBox
which describes what ratio and initial size we are looking for.
Hooks. Data is any dataset we want to represent. Dimension is different from property viewBox. Dimension declares the real size of SVG where render will be. viewBox will be scaled to dimension.
For render purposes, we are wrapping d3 calculations into useMemo
. Hook will save us time on recalculating d3 functions when viewBox and data not changing.
Render — SVG with all children one by one. Defs for styling in SVG. Paths to draw line and area. Circles to draw actual points.
That’s it. We just built a custom chart with React elements and D3 calculations. Let’s see how it looks like in the end.
What’s next
We covered basic integration without mentioning real-world problems like tooltips, axis, maps or animations. These problems are really interesting to implement and worth to have separate stories for them.
Just a chart lib:
- https://www.smashingmagazine.com/2018/02/react-d3-ecosystem/
D3 first:
- https://blog.logrocket.com/data-visualization-in-react-using-react-d3-c35835af16d0
- https://medium.com/@Elijah_Meeks/interactive-applications-with-react-d3-f76f7b3ebc71
Faux first:
- https://blog.sicara.com/a-starting-point-on-using-d3-with-react-869fdf3dfaf
- https://medium.com/@tibotiber/react-d3-js-balancing-performance-developer-experience-4da35f912484