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.

example

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 .

Structure

Here we defined a TimeLine component with little util components.

D3

d3.js will provide us any calculations we want.

In our case they are 5 functions:

scales

yScale + xScale — functions to define boundary sizes of our dataset, this should be recalculated each time data changes.

path and area

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.

interface

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

View props

Props. We have only 1 property viewBox which describes what ratio and initial size we are looking for.

hooks

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

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.

chart

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.