Simplest Way to Build Responsive D3 Chart in React

Akarshan Bansal
Frontend Weekly
Published in
6 min readJul 1, 2019

It’s a good time to get into React. I, myself, have been part of the angular community since as long as I can remember. But you cannot ignore the benefits that react provides whether it’s the high-performance virtual-DOM or the ability to create isolated visual components with great speed and efficiency.

There has always been a great demand for graphics and data-visualizations in the JavaScript community and D3 is the top library to solve these requirements.

How does D3 work?

D3 is a powerful, open-source javascript library to build data-visualizations and graphs. It utilizes the HTML DOM elements and appends to them an SVG element which effectively becomes the container for the visualizations. At the same time, CSS is used to add fills, animations, transitions and all the things that make the visualizations look pretty.

Set up your React app.

I’m not going to go in detail to create a react app since there are plenty of great resources out there. There are plenty of good starter-projects on GitHub that can help you to set up the project. My recommendation would be to use create-react-app which is the official starting kit provided by Facebook. If you’re new to react, it would be wise to use this as it would set up your entire build environment and webpack config which you would have had to do separately otherwise.

The steps to use create-react-app are pretty straightforward. You can check them out here.

https://facebook.github.io/create-react-app/docs/getting-started.

Install D3

To install D3, run the following command inside your app folder where you have your package.json file.

npm install --save d3

Please note that we will be using d3 version 4 in this tutorial.

Build your parent component

Let’s create a very simple React component that has a div container in which we will append our chart.

App.js

import React from "react";
import './App.scss';
class App extends React.Component {render() {
return (
<div className="mainPage">
</div>
)
}
}
export default App;

App.scss

.mainPage {
background-color: white;
height: 50vh;
width: 100%;
text-align: center;
}

Build your chart component

Let’s build the chart first and we will worry about the responsiveness later.

Chart.js

class RowChart extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [{skill: "CSS", value: 80},{skill: "HTML", value: 70},{skill: "JS", value: 85},{skill: "ANGULAR", value: 90},{skill: "REACT", value: 75},{skill: "D3", value: 70},{skill: "NODE JS", value: 65},{ skill: "JAVA", value: 65},{skill: "UI DESIGN", value: 70},{skill: "XD", value: 65}],
yAxisAttribute: "skill",
xAxisAttribute: "value",
width: 1000,
height: 400,
}
this.chartRef = React.createRef();
}
}

Here we’re building a component called Chart which has a div container with class “rowChart”. We have set a few properties on the state itself which are the width, height, data, xAxisAttribute (column for x-axis from data json) and yAxisAttribute (column for y-axis from data json).

Note that we have created a ref using React.createRef called this.chartRef which gives us access to the respective DOM element.

Add the drawChart method

drawChart() {let margin = {top: 20, right: 30, bottom: 40, left: 90},
width = this.state.width - margin.left - margin.right,
height = this.state.height - margin.top - margin.bottom;
// append the svg object to the body of the pagelet svg = d3.select(".rowChart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Add X axislet x = d3.scaleLinear()
.domain([0, 100])
.range([ 0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.attr('class','axis x')
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");
// Add Y axislet y = d3.scaleBand()
.range([ 0, height ])
.domain(this.state.data.map((d) => d[this.state.yAxisAttribute]))
.padding(.1);
svg.append("g")
.attr('class','axis y')
.call(d3.axisLeft(y))
.selectAll("text")
.attr("dy", null)
// Add Bars
svg.selectAll("myRect")
.data(this.state.data)
.enter()
.append("rect")
.on('mouseover', function(){
d3.select(this).style('opacity', 0.5)
})
.on('mouseout', function(){
d3.select(this).style('opacity', 1)
})
.attr("x", x(0) )
.attr("y", (d) => y(d[this.state.yAxisAttribute]))
.attr("width", 0)
.attr("height", y.bandwidth() -10 )
.attr("fill", "#DF337D")
.transition(d3.transition().duration(1000))
.attr("width", (d) => x(d[this.state.xAxisAttribute]))
}

The drawChart method has the following flow

  1. Appending the svg element to the div element with the .rowChart class
  2. Adding the x and y axis
  3. Appending the bar rectangles.

Use the component lifecycle method componentDidMount

Call the drawChart method inside componentDidMount method provided by react. The componentDidMount method is called once the DOM has been mounted in the browser.

componentDidMount() {
this.drawChart();
}

Don’t forget to bind the drawChart method in your constructor

this.drawChart = this.drawChart.bind(this);

Your chart will probably look something like this right now

D3 chart in full-screen size

Adding responsiveness

SVG elements are by default fixed in size. They don’t resize like other Html DOM elements. One of the ways which I use is to resize the charts based on the size of the parent containers that they are in.

Let’s see how we can do that.

  1. Set initial width and height as 0 in the constructor.
this.state = {
data: [{skill: "CSS", value: 80},{skill: "HTML", value: 70},{skill: "JS", value: 85},{skill: "ANGULAR", value: 90},{skill: "REACT", value: 75},{skill: "D3", value: 70},{skill: "NODE JS", value: 65},{ skill: "JAVA", value: 65},{skill: "UI DESIGN", value: 70},{skill: "XD", value: 65}],
yAxisAttribute: "skill",
xAxisAttribute: "value",
width: 0,
height: 0,
}

2. Create methods getWidth and getHeight to retrieve the width and height from the parent DOM element.

Here we can use the ref that we create earlier called chartRef and get the width and height of the parent element of the chart

getWidth(){
return this.chartRef.current.parentElement.offsetWidth;
}
getHeight(){
return this.chartRef.current.parentElement.offsetHeight;
}

3. Update componentDidMount method

Here we can put the drawChart method inside the setState callback method. Since setState is asynchronous we need to put the drawChart method inside the callback

componentDidMount() {
let width = this.getWidth()
let height = this.getHeight();
this.setState({width: width, height: height}, ()=> {
this.drawChart();
});
}

4. Add resizing event listener and redraw function

Now to update the chart on resize, we can add an event listener on the window object and call the redraw function inside it.

The redraw function will redraw the chart with the updated width and height of the resized screen

We can use a combination of setTimeout and clearTimeout to debounce the updating of the chart on resize.

componentDidMount() {
let width = this.getWidth()
let height = this.getHeight();
this.setState({width: width, height: height}, ()=> {
this.drawChart();
});
let resizedFn;
window.addEventListener("resize", () => {
clearTimeout(resizedFn);
resizedFn = setTimeout(() => {
this.redrawChart();
}, 200)
});
}
redrawChart() {
let width = this.setWidth()
this.setState({width: width});
d3.select(".rowChart svg").remove();
this.drawChart = this.drawChart.bind(this);
this.drawChart();
}

Add the Chart component inside your App Component

import React from "react";
import './App.scss';
import RowChart from "./row-chart";
class App extends React.Component {render() {
return (
<div className="mainPage">
<RowChart />
</div>
)
}
}
export default App;

Now your chart will be responsive for all different screen sizes.

D3 Chart in smaller screen size

This is the way that I like to build responsive charts in D3 with React for my own work. It can be intimidating for people like me with background originating in other frameworks like Angular or Vue to integrate D3 with React, but I hope this tutorial will help to make it easy for you.

Thanks for reading guys. Please share your questions and suggestions in the comments below. Cheers !!

--

--

Akarshan Bansal
Frontend Weekly

Front end developer with Accenture Products. Passionate about JS, music and traveling.