šŸŒ® Getting started with React + D3.js

Camelia D. Brumar
8 min readJan 11, 2023

Tacos. Two fun facts that I like about tacos are that they can be filled with anything (really!) and that there is a day celebrating them (October 4th in the US)!

(A little well-deserved sidebar here. My love for tacos is also shared with the creators of the Exploding Kittens card game when they created all kinds of taco cat artifacts such as the TACOCAT card, TACOCAT plushie, and TACOCAT Spelled Backwards boardgame. And no, I am not a board games geek *wink wink*.)

Sidebar over, why am I even mentioning tacos? Well, my analogy here can feel a bit forced, but I do believe that combining React with D3.js resembles the process of putting together a taco. React is like the white corn tortilla that makes up the delicious taco shell (mmhm!) and d3 is like the filling of the taco. React holds the visuals, d3 fills the react components with delicious visualizations.

I chose to learn how to work with these two tools based on the workflow other colleagues in my lab have and standard practices in the industry. I didnā€™t find many practical examples out there online that would combine already existing react and d3 code and make it work, so I decided to create a series of short posts to share my learning journey with React and D3, with the hope that is helpful to anyone else struggling to make these two libraries work together (in harmony and with no errors).

Prerequisites

A beginner's understanding of React and D3 will be necessary to follow along with this tutorial. I will, in the future compile a list of valuable resources that I always find myself returning to when learning React and D3. The visualization below is what you will see in your browser at the end of this tutorial:

Now then, letā€™s get cookinā€™!

Ingredients

This is a simple recipe that only requires three ingredients:

  1. React.js, or just the ability to use create-react-app in your terminal. For that, youā€™ll need to install Node.js and npm (which comes with the installation of Node.js). This can be done by following these instructions up to step 4 (included) for mac users or following the steps in the prerequisites on this other website for Windows users.
  2. D3.js: for now you need to know that this is the visualization library that we will use, but will add it to the project in the directions section below.
  3. D3 example to embed in a react component: I chose a simple bar chart to get started: https://d3-graph-gallery.com/graph/barplot_basic.html

Directions

  1. Create a react app following the official react documentation: https://reactjs.org/docs/create-a-new-react-app.html
    That is, using the terminal, navigate to the directory where youā€™d like to store the the project folder. Then, run these commands:
yarn create react-app react-d3-test
cd react-d3-test

You could also use npx instead, but then I suggest being consistent with the commands when installing libraries in your react app and using the same package (npm or yarn):

npx react-app react-d3-test
cd react-d3-test

This creates a single-page react app called react-d3-test and navigates inside the project folder.

2. Once inside the project folder, itā€™s time to install the d3 dependency to use this library in our project. Execute the following command

yarn add d3
# OR USING NPM:
# npm install d3

Weā€™re all set to start adding the chart as a react component.

3. Index.js will look like this out of the box:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

For our purposes, and due to the annoying double rendering problem with the StrictMode in React, I decided to remove the StrictMode tags since it will make our lives easier while developing this simple example. Thus, the code here will look like this:

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

4. Moving on to the App.js component, this is how it looks like initially without any modifications:

import logo from './logo.svg';
import './App.css';

function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}

export default App;

Which is the started code, primarily useless, thus, weā€™ll modify it to contain a title and the Barchart component that we want to import.

import './App.css';
import Barchart from './components/Barchart';

function App() {
return (
<div className="App">
<h1>Hello React + D3 world!</h1>
<Barchart/>
</div>
);
}

export default App;

5. Now, the most exciting part, letā€™s build the Barchart component. Under src, create a directory called ā€˜componentsā€™, and inside it, create a file named ā€˜Barchart.jsxā€™. Here we first import the needed dependencies (d3 , the useEffect hook, and the useRef hook):

import * as d3 from "d3";
import { useEffect, useRef } from "react";

Then, we create an empty Barchart functional component and export it:

const Barchart = () => {};

export default Barchart;

6. We would like our component to fetch the data only once when the component is mounted and then use it to create the visualization. Hence, the useEffect hook comes in handy. We add all the code in the d3 example inside of the body of the useEffect hook as indicated below:

const Barchart = () => {
// set the dimensions and margins of the graph
var margin = { top: 30, right: 30, bottom: 70, left: 60 },
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;

// append the svg object to the body of the page
var svg = d3
.select("#my_dataviz")
.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 + ")");

// Parse the Data
d3.csv(
"https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/7_OneCatOneNum_header.csv",
function (data) {
// X axis
var x = d3
.scaleBand()
.range([0, width])
.domain(
data.map(function (d) {
return d.Country;
})
)
.padding(0.2);
svg
.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");

// Add Y axis
var y = d3.scaleLinear().domain([0, 13000]).range([height, 0]);
svg.append("g").call(d3.axisLeft(y));

// Bars
svg
.selectAll("mybar")
.data(data)
.enter()
.append("rect")
.attr("x", function (d) {
return x(d.Country);
})
.attr("y", function (d) {
return y(d.Value);
})
.attr("width", x.bandwidth())
.attr("height", function (d) {
return height - y(d.Value);
})
.attr("fill", "#69b3a2");
}
);
};

export default Barchart;

There are few changes that we will make to this d3 example in order to make it work with React. First, when we select the svg, there is no svg in our index.html with the id ā€œmy_datavizā€. A way to solve this problem, is to use the useRef hook. This is what the React documentation says about it:

Essentially, useRef is like a ā€œboxā€ that can hold a mutable value in its .current property. You might be familiar with refs primarily as a way to access the DOM. If you pass a ref object to React with <div ref={myRef} />, React will set its .current property to the corresponding DOM node whenever that node changes.

Thus, before the useEffect hook, create a ref and pass it to the d3.select method as follows:

const Barchart = () => {
const ref = useRef();

useEffect(() => {

...

const svg = d3
.select(ref.current)
.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})`);

...

}, []);
};

Another place where we have to add the reference is as the ref property of the svg that our component will return:

const Barchart = () => {
const ref = useRef();

useEffect(() => {

...

}, []);

return <svg width={460} height={400} id="barchart" ref={ref} />;
};

The full Barchart.jsx code, with all the sauces and toppings, looks like this:

import * as d3 from "d3";
import { useEffect, useRef } from "react";

const Barchart = () => {
const ref = useRef();

useEffect(() => {
// set the dimensions and margins of the graph
const margin = { top: 30, right: 30, bottom: 70, left: 60 },
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;

// append the svg object to the body of the page
const svg = d3
.select(ref.current)
.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})`);

// Parse the Data
d3.csv(
"https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/7_OneCatOneNum_header.csv"
).then(function (data) {
// X axis
const x = d3
.scaleBand()
.range([0, width])
.domain(data.map((d) => d.Country))
.padding(0.2);
svg
.append("g")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");

// Add Y axis
const y = d3.scaleLinear().domain([0, 13000]).range([height, 0]);
svg.append("g").call(d3.axisLeft(y));

// Bars
svg
.selectAll("mybar")
.data(data)
.join("rect")
.attr("x", (d) => x(d.Country))
.attr("y", (d) => y(d.Value))
.attr("width", x.bandwidth())
.attr("height", (d) => height - y(d.Value))
.attr("fill", "#5f0f40");
});
}, []);

return <svg width={460} height={400} id="barchart" ref={ref} />;
};

export default Barchart;

*Note that for the final result, I just changed the color of the bars to make it more aesthetically pleasing (a tiny, tiny bit).

7. Now you have to heat that tortilla! Run the following command in the terminal to run your application locally in your browser.

yarn start
# OR USING NPM
# npm start

And boom! An appetizing d3 bar chart served on an exquisite React tortilla right in front of your eyes.

Some final thoughts on the sauceā€¦

Tacos go well with Creamy Chipotle Ranch Dressing or some Salsa Verde (you can thank me later).

Thanks for reading this post, Iā€™m looking forward to writing about my future findings on React + D3, and please leave comments on what you liked about it (Iā€™m sure the tacos were delicious, but feedback on the code is welcomed, too!) and what could be improved.

References

TACOCAT image: https://www.stylinonline.com/exploding-kittens-tacocat-plush/?gclid=Cj0KCQiAtvSdBhD0ARIsAPf8oNl-U1IX_Rdko_2Fijq3rHq1UTpB_i8nSYLsqtFjXTsnS7hv-vd3LpUaAvpPEALw_wcB
React documentation on create-react-app: https://reactjs.org/docs/create-a-new-react-app.html
How to add d3 to your project: https://www.freecodecamp.org/news/how-to-get-started-with-d3-and-react-c7da74a5bd9f/
Tacos pic: https://www.feastingathome.com/grilled-steak-tacos-with-cilantro-chimichurri-sauce/

--

--

Camelia D. Brumar

Ph.D. Candidate in Computer Science at Tufts University working with Dr. Remco Chang in the Visual Analytics Lab ([v]alt).