Adopting D3 in React apps
In this article I’ll try to cover the basics of adopting D3.js with a React project. The first question you need to answer is..
Can you just use SVG?
It is very easy to be mesmerized by the magic created by the visualizations showcased by D3 site and code sandboxes. However, if all you need are standard charts, I recommend adopting a charting library instead. And for heaven’s sake don’t create another charting library from scratch. These have been solved a hundred times already; pick one that closely matches your need and go with it. In our team at HPE, we use re-charts for most of our line/bar/time-series charts.
SVG is a first class citizen just as any HTML markup. Your React views can render it just as well. The problem is many UI engineers are not quite familiar with SVG and do not take time to master it. In fact, D3 uses SVG underneath. If all you need are simple SVG visualizations with out any complex math or animations involved, go ahead and create them as React SVG components.
Here is a Circle example using D3.
export const Circles = ({ data, diameter, fill }) => {
const svg = React.useRef();React.useEffect(() => {
d3.select(svg.current)
.selectAll("circle")
.data(data)
.enter()
.append("g")
.attr("transform", `translate(${diameter / 2})`)
.append("circle")
.attr("cx", (i) => i * diameter)
.attr("cy", diameter)
.attr("r", diameter / 2)
.attr("fill", "#F06E65");
}, []);return (
<div>
<h3>Circles</h3>
<svg
ref={svg}
id="svg"
width="100%"
height="300"
></svg>
</div>
);
};
Here is the exact component using just SVG
export const SVGCircles = ({ data, diameter, fill }: ICircles) => {
const toDisc = (i) => (
<circle r={diameter / 2} cx={i * diameter} cy={diameter} fill={fill} />
);return (
<div>
<h3>SVG Circles</h3>
<svg id="svg" width="100%" height="300">
<g transform={`translate(${diameter / 2})`}>
{data.map(toDisc)}
</g>
</svg>
</div>
);
};
You can see that you really do not need D3 here. React and SVG is plenty enough. Check the sandbox below to see how the component is implemented using D3 and just SVG.
https://codesandbox.io/s/d3-vs-svg-bdvxb?file=/src/SVGCircles.tsx
Using D3 is an overkill where the SVG markup is very easy to reason about. React is really good at handling DOM, SVG excels in drawing. In custom, static image scenarios such as these, you can keep it simple. If you are worried about having to write SVG, remember this:
You cannot use D3 effectively without mastering SVG
React and D3 — playing nice
D3 is a visualization toolbox that arms you with graphical, mathematical and physical modeling abstractions. D3 mostly does not contain out of the box chart widgets but do provide useful components such as axes. It is up to you to build them using the different pieces provided within its modules.
D3 contains several modules, some of which manipulates the DOM. The conflict happens when React and D3 tries to mutate the DOM at the same time. Thus a clear separation of responsibilities is due. Ideally, you want to do this:
- React creates a DOM element as the D3 playground. This is passed on as a ref.
const svg = React.useRef();
.
.
.
return (
<div>
<h3>SVG Circles</h3>
<svg id="svg" width="100%" height="300">
<g transform={`translate(${diameter / 2})`}>
{data.map(toDisc)}
</g>
</svg>
</div>
);
};
- It then passes on the ref to D3 like so:
React.useEffect(() => {
d3.select(svg.current)
.selectAll("circle")
.
.
.
.
}, []);
- React will manage the state that is required for the visualization to work. All the intermediate calculations will be computed by D3 within a useEffect hook. Anytime, the effect runs, D3 will re-render the DOM.
Sandbox: https://codesandbox.io/s/d3-vs-svg-bdvxb?file=/src/Circles.tsx