React Components for D3 Charts
Tags: Reactjs, D3js

Let’s take a look at how it’s constructed. If you want to play along, you can use the ‘npx create-react-app’ command from your terminal to generate a playground environment. Add d3 to your project folder with ‘npm i d3’ and copy the above into your ‘App.js’ file. Add a new file to the ‘src’ directory named ‘ScatterChart.js’.

Begin by adding some boilerplate code:

import * as d3 from ‘d3’; import React, { useRef, useEffect } from ‘react’; export default function ScatterChart(props) {

const ref = useRef()

const height = 460,

width = 460

return (

<svg

viewBox={“0 0 “ + height + “ “ + width}

ref={ref}

/>

)

}

Here, we’ve defined and exported our functional component, ScatterChart, and defined some initial parameters (height, width and ref). The useRef() React hook provides a reference between our D3 code and the rendered SVG that we return. The snippets that follow will fit in sequence between the defined parameters and the return statement.

We need to grab our svg reference and set some initial parameters. To do that, add a useEffect hook.

useEffect(() => {

const svgElement = d3.select(ref.current)

.append(“g”)

.classed(“svgElement”, true)

.attr(“height”, height)

.attr(“width”, width)

},[])

If our data never changed, the rest of the chart could be drawn from within the same hook. I like to give myself the option to be able to update / filter the dataset without having to rewrite my component, hence I use a second useEffect hook to draw the rest of my chart. I need to grab the element I referenced in the previous hook, then set a few more parameters. Notice that we’re now referencing our props.

useEffect(() => {

const svgElement = d3.select(ref.current).select(“g.svgElement”)

var margin = { top: 10, right: 10, bottom: 10, left: 30 };

var xValMax = d3.max(props.data,function(d){return (d[props.xVal])})*1.1

var yValMax = d3.max(props.data,function(d){return (d[props.yVal])})*1.1

},[props.data])

Any parameters passed on to our component from the parent (in this case “App”, as defined in App.js) can be accessed through “props.parameterName”. This use of props makes React components very handy for storing D3 visuals. Firstly, I can pass in my data. This means that the parent component can fetch, modify and distribute the dataset to multiple child components. Secondly, any properties that I might want to change can be set as variables and altered by passing in parameters from the parent. In this example, i’m only using xVal and yVal (the attributes which will be measured on the x- and y- axes respectively). I could also pass in the text color, circle color, size, etc… We’ll get to that later.

Next, I want to define my axes.

//define x axis

var xScale = d3.scaleLinear()

.domain([0,scatterDomMax])

.range([margin.left, 400]); var xAxis = svgElement.append(“g”)

.call(d3.axisBottom(xScale))

.attr(“class”,”x-axis”)

.attr(“transform”, “translate(0,”+(400)+“)”);

//x label

svgElement.append(“text”)

.attr(“class”,”scatterLabels”)

.style(“text-anchor”, “end”)

.attr(“x”, 405)

.attr(“y”, 395)

.style(“font-size”,”24px”)

.style(“fill”,”black”)

.text(props.xVal); //define y axis

var yScale = d3.scaleLinear()

.domain([0,scatterDomMax])

.range([400,0]) var yAxis = svgElement.append(“g”)

.call(d3.axisLeft(yScale))

.attr(“class”,”y-axis”)

.attr(“transform”,“translate(“+ margin.left +“, 0)”)

//y label

svgElement.append(“text”)

.attr(“class”,”scatterLabels”)

.attr(“transform”, “rotate(-90)”)

.attr(“y”, margin.right + 18)

.attr(“dy”, “1em”)

.style(“text-anchor”, “end”)

.style(“font-size”,”24px”)

.style(“fill”,”black”)

.text(props.yVal);

Finally, we get to the juicy part; the update cycle. Using d3.join() we can deal with changes in our data relatively easily.

svgElement.selectAll(“circle”)

.data(props.data, d => d)

.join(enter => (

enter.append(“circle”)

.attr(“cx”, function (d) { return xScale(d[props.xVal]); })

.attr(“cy”, function (d) { return yScale(d[props.yVal]); })

.attr(“r”, function(d) {return 6})

.attr(“class”,”circles”)

.style(“stroke”, “red”)

.style(“stroke-width”, “2px”)

.style(“fill”, “green”)

.call(enter => (

enter.transition().duration(800)

))

),

update => (

update

.call(update => (

update.transition().duration(800)

))

),

exit => (

exit

.call(exit => {

exit.transition().duration(400)

.style(“opacity”,0)

.remove()

})

)

)

Here, we’re grabbing any circles attached to our svg element (which will be 0 on the first pass) and attaching the data passed down from the parent. The enter function will append a circle to the svg for each point in the dataset, adding a few styles (which can be parameterised) before calling a smooth transition which will play as the webpage loads. If data points are added, they are passed through this same enter function. Updated data points are transitioned via the update function, while data points that no longer exist are passed through the exit function and transitioned out of the visual.

Replacing props with parameters