The purpose of this repository is to establish strategies that can be used while working with D3.js in the context of React.
https://goatstone.com/react-d3/
-
Working with React and D3s' conflicting DOM manipulation.
-
Animation in React, updating graphs with new data.
-
Defining the style of the D3 graph with CSS
-
Providing the user the ability to change the graph type
In the D3 documentation, it lays out clearly how to use the useRef
hook to store the code generated from D3.
The following code summarizes the strategy demonstrated in the D3 documentation. It creates a red square. The D3 select function is passed the instance of the variable created by the React useRef hook.
import React, { useRef, useEffect } from "react";
import { select } from "d3";
const App = () => {
const svgRef = useRef(null);
useEffect(() => {
const svg = select(svgRef.current)
svg.append('rect')
.attr('width', '300')
.attr('height', '300')
.attr('fill', 'red')
}, [])
return (
<svg ref={svgRef} width="300" height="300">
</svg>
)
}
export default App;
Another strategy is to use a React useRef in an SVG element. Get an SVG node from a function that builds the D3 SVG. Set the SVG React useRef variable with appendChild. The following code demonstrates this technique. The example displays a red square.
import React, { useEffect, useRef } from 'react';
import { create } from 'd3';
const redSquare = () => {
const svg = create('svg')
svg.append('g')
.append('rect')
.attr('width', "300")
.attr('height', "300")
.attr('fill', 'red')
return svg.node();
}
const App = () => {
const svgRef = useRef(null);
useEffect(() => {
if (svgRef.current) {
svgRef.current.appendChild(redSquare());
}
}, [])
return (
<svg width="300" height="300">
<g ref={svgRef}></g>
</svg>
);
};
export default App;
Using setInterval is a standard way to create a timer that drives an animation. The timer can be started at the initiation of the page load but a problem arises if the animation needs to be toggled on and off. In a standard web page a variable can be returned from the setInterval call that can be used later with the function clearTimeout
. In React, with each refresh of the page, variables like this are thrown out. The solution is to use a useRef hook to preserve the returned variable that can be later used in a clearTimeout function. Following is the code that demonstrates this.
// Set up the useRef that will be preserved between refreshes
const intervalID = useRef(null);
// Use React state to control the animation on or off state
const [animationOn, setAnimationOn] = useState<boolean>(false);
// Set up a useEffect that takes the animationOn React state
useEffect(() => {
if (!animationOn) {
clearTimeout(intervalID.current);
} else {
intervalID.current = setInterval(() => {
const newData = data.map((d) => {
return d + Math.floor(Math.random() * 3 - 3);
});
setData(newData);
}, 3000);
}
return () => clearTimeout(intervalID.current);
}, [animationOn]);
D3 generates SVG with class names. These class names can be used in CSS definitions to define the styles of the generated SVG. The following CSS can be used to style a D3 axis.
svg .tick {
color: #333;
}
svg .domain {
color: blue;
}
Custom SVG shapes, generated by D3 can be given custom class names. These class names can be used with the CSS key work customcolor
to define the styles of these SVG shapes. Following is an example of using CSS to apply a color to a shape in D3.
svg .background {
color: #eee;
}
const svg = d3.create("svg")
.append("rect")
.attr("width", graphDimensions.width)
.attr("height", graphDimensions.height)
.attr("fill", "currentcolor")
.attr("class", "background");
Seeing the same data with different graph types can give insight into the given data.
Following is a strategy for swapping graph types in the same area. It uses the previously mentioned appendChild method to put the D3 node into the document.
In the user interface, the type of graph can be selected. When chartType
is changed it triggers the useEffect
and an if else
statement sets the new chart node with the correct function.
const svgRef = useRef<any>(null);
const [data, setData] = useState(initData);
const [chartType, setChartType] = useState("histogram");
useEffect(() => {
svgRef.current.innerHTML = "";
let chartNode;
if (chartType === "line") {
chartNode = lineChart(data);
} else {
chartNode = histogram(data);
}
svgRef.current.appendChild(chartNode);
}, [data, chartType]);