Anyone who’s spoken with me in the last six months has found out that D3.js is my new favorite obsession. I recently got the opportunity to use D3.js to create a customized data visualization for the current client. This client handles large, complicated, and unique data sets. Therefore, there are many occasions where standard data visualizations won’t suffice. There were several unique challenges when creating this data visualization.
Challenge #1: React + D3.js
The first challenge is that the client is using React. React and D3 both want to manipulate the DOM, so there have to be very clear boundaries on when React stops and D3 takes over. For instance, if you wrap a <rect> for a bar chart with a <Tooltip>, both React and D3 want to control it and it ends up crashing the site once the mouse enters the <rect>.
The React portion creates the main SVG, then maps the data to create the rectangles, lines, icon groups, or whatever else your data visualization needs. D3 can create all of the rectangle elements, but with the challenge of multiple data values as presented below, D3 would have to map through the array multiple times. That’s not a problem if you have a small data set, but when you have large data sets, multiple passes through the data array can cause a lag. Place the following code in the return statement in React:
<svg ref={svgRef}>
{data.map((d, index) => (
<>
<line key={d.line} />
<g class=”icons” key={d.icon}></g>
</>
))}
</svg>
This creates the main SVG, along with empty elements for the line and icons. The rest of the magic is done using D3. This particular data visualization has filters that change the parameters of the data. In order for the visualization to change when the filters change, and without refreshing the page, D3 needs to be called with the React hook useEffect(). This hook replaces both componentDidMount() and componentDidUpdate(). Call this hook inside the function, before the return statement.
useEffect(() => {
draw();
}, [props.data]);
All of the D3 happens inside the draw const. This keeps D3 and React in their separate corners, not fighting with each other to control the DOM. With each d3.select function, it’s important to first select svgRef.current. This will only select the most current data selection if you have filters on your timeline that change the data. When selecting an element using D3, you start with the outer element, then you can keep selecting until you get the inner element you want to add to. You can then add classes, attributes, etc, using the format of the example below. In this example, x1, y1 are the starting point for the line, x2, y2 are the ending points for the line. I’m using a simple conditional statement to check if the data item is the last item in the data array. This will prevent the x2 and y2 values from being null, which will return an unknown error. The xScale and yScale references will be covered in detail in a later blog post.
const draw = () => {
d3.select(svgRef.current)
.select(“line”)
.data(data)
.attr(“x1”, (d, index) => index < data.length – 1 ? xScale(data[index].date): “0”)
.attr(“y1”, (d, index) => index < data.lenth – 1 ? yScale(data[index].line)
.attr(“x2”, (d, index) => index < data.length – 1 ? xScale(data[index + 1].date): “0”)
.attr(“y2”, (d, index) => index < data.lenth – 1 ? yScale(data[index + 1].line)
.attr(“stroke-width”, “2”)
.style(“stroke”, “blue”)
}
The code given here will give you a simple line graph, with D3 handling all of the drawing, and React providing a container for the D3 drawing. Subsequent blog articles will cover transitions (animations), working with more complicated data sets, handling icons, and x and y scales.
READ MORE: The Fundamental Differences of GraphQL and REST API's, Smart Technology in Our Daily Lives, Creating a Custom Material UI Theme Part 1: Customizing a Color Scheme, UX Measures and Methods to Evaluate Video Game Interfaces
Comments
Add Comment