DEV Community

Cover image for Creating Data Visualizations with D3 and ReactJS
Simon Pfeiffer for Codesphere Inc.

Posted on

17 4

Creating Data Visualizations with D3 and ReactJS

Alt Text

D3 is a lot more than just a graphing library, it's a toolset for efficiently editing the DOM and creating visualizations based on data.

If all you want is to quickly insert a generic bar graph into your app, then D3 is probably not for you, but if you want the ability to customize your visualizations from the ground up, then D3 is the industry standard.

In this tutorial, we are going to create three different simple visualizations with D3 in React. 

Now while these things can be done without D3 and aren't particularly flashy, they highlight some of the basics of D3 very well. If you want to see some cool demos to get a sense of just how powerful D3 is, then check out some of the demos on the D3.js home page.

https://d3js.org/


To see the three visualizations that we are going to make, you can open up this project in Codesphere, a free development environment with instant deployment features. Just click the link, sign in, and run:
npm ci && npm start

http://codesphere.com/#https://github.com/LiorB-D/d3tutorial


Setting up React

Let's start off with a blank Create-React-App.
If you are new to React, this can be made with:

npx create-react-app my-app

We are also going to need to install D3 onto our app, with:

npm install d3

Don’t forget to import D3 in all the files you are using it in!

import * as d3 from 'd3'

For now, we are going to render an empty div and create a useEffect hook, in which we will later insert all of our D3 code:

import './App.css';
import * as d3 from 'd3' // Import D3
import {useEffect} from 'react'
function App() {
useEffect(() => {
/*
This is going to run once when the App is first rendered.
All of our D3 code, which will edit the elements on our DOM, will be put here.
If you wanted to run this code in response to a certain action, such as a buttonPress event,
you can put in the appropriate function.
*/
}, [])
return (
<div className = "App"> // Render an empty Div for now.
</div>
);
}
export default App;
view raw App.js hosted with ❤ by GitHub

Creating Labels Dynamically

The first thing we are going to do is use D3 to dynamically insert p tags based on data. While this of course is not too difficult to do with vanilla React, I would recommend for anyone learning D3 to get comfortable editing the DOM in this way.

import './App.css';
import * as d3 from 'd3'
import {useEffect, useState} from 'react'
function App() {
useEffect(() => {
// Create a dataset of pets and the amount of people that own them
let dataSet = [
{subject: "Dogs", count: 150},
{subject: "Fish", count: 75},
{subject: "Cats", count: 135},
{subject: "Bunnies", count: 240},
]
// Generate a p tag for each element in the dataSet with the text: Subject: Count
d3.select('#pgraphs').selectAll('p').data(dataSet).enter().append('p').text(dt => dt.subject + ": " + dt.count)
}, [])
return (
<div className = "App">
<div id="pgraphs"></div> // Create a div to house our p tags
</div>
);
}
export default App;
view raw App.js hosted with ❤ by GitHub

Let’s go through each part of that D3 line on line 17 and breakdown what it does:

  1. d3.select(“#pgraphs”) selects the div with the id “pgraphs”
  2. .selectAll(‘p’) tells d3 that we want to look at the p tags within that div. Since there are currently no p tags, we will later need to create them.
  3. .data(dataSet) binds that dataSet array to these p tags
  4. .enter().append(‘p’) adds all missing p tags within the div such that there is one p tag for each element in the bound dataset
  5. .text(dt => dt.subject + “: ” + dt.count) sets the text of each of these p tags based on an arrow function we are defining within the text() function. Here, we want to take each corresponding element in the dataSet array and create a string based on the subject and count

Creating An Animated Html-Based Bar Graph

Next, we are going to create a custom Bar Graph based on this data by creating a div for each element and dynamically setting the height.

In our App.css, we are going to add two styles: One for the div containing the bar graph, and one for each individual bar.

#BarChart {
display: flex;
flex-direction: row; /* Arrange the Bars Horizontally using FlexBox */
align-items: flex-end; /* Have the bars start at the bottom and go up */
justify-content: center; /* Center the bars in the middle of the screen */
margin: 50px;
}
.bar {
background-color: #3aa70f; /* Have each bar be a nice green */
width: 10px; /* The animation will start with each bar being 10px wide, and then expand outwards */
}
view raw App.css hosted with ❤ by GitHub

Now in our useEffect we are going to have D3 do the following animation.

  1. Set each bar to have the same height (Which will be equal to the highest count value)
  2. Wait 300 milliseconds
  3. Set each bar's height to correspond with a count value.
  4. Transition the bars into having a margin and a larger width.
import './App.css';
import * as d3 from 'd3'
import {useEffect, useState} from 'react'
function App() {
useEffect(() => {
// Create a dataset of pets and the amount of people that own them
let dataSet = [
{subject: "Dogs", count: 150},
{subject: "Fish", count: 75},
{subject: "Cats", count: 135},
{subject: "Bunnies", count: 240},
]
// Generate a p tag for each element in the dataSet with the text: Subject: Count
d3.select('#pgraphs').selectAll('p').data(dataSet).enter().append('p').text(dt => dt.subject + ": " + dt.count)
// Bar Chart:
const getMax = () => { // Calculate the maximum value in the DataSet
let max = 0
dataSet.forEach((dt) => {
if(dt.count > max) {max = dt.count}
})
return max
}
// Create each of the bars and then set them all to have the same height(Which is the max value)
d3.select('#BarChart').selectAll('div').data(dataSet)
.enter().append('div').classed('bar', true).style('height', `${getMax()}px`)
//Transition the bars into having a height based on their corresponding count value
d3.select('#BarChart').selectAll('.bar')
.transition().duration(1000).style('height', bar => `${bar.count}px`)
.style('width', '80px').style('margin-right', '10px').delay(300) // Fix their width and margin
}, [])
return (
<div className = "App">
<div id="pgraphs"></div> // Create a div to house our p tags
<div id="BarChart"></div> // Create a div to house our BarChart
</div>
);
}
export default App;
view raw App.js hosted with ❤ by GitHub

Let’s go through these new D3 functions that we just used:

  • .classed(‘bar’, true) gives all the selected elements the CSS class “bar”
  • .style(style, value) gives all the selected elements a given CSS style with a given value
  • .transition() tells d3 to transition the element into the changes that will be made
  • .duration(ms) dictates the duration of the transition in milliseconds
  • .delay(ms) delays all the previous changes by a certain amount of milliseconds

If all is working well, the animation should look like this:

Alt Text


Creating An SVG-based Line Graph

While in the previous two examples we used HTML elements, if you want much more versatility you are going to want to use D3 to manipulate SVG elements.

For our line graph, we are going to create X and Y axes as well as a nice animation. For this example, we are also going to generate a random dataSet so that we have more points to work with.

First, let’s add the following style to our App.css

#LineChart {
overflow: visible /* Just in case the axis render outside of our div, let's make sure it still displays */
}
view raw App.css hosted with ❤ by GitHub

We are then going to use D3 to do the following:

  1. Create D3 scales, which will allow us to easily map our data values to pixel values in our SVG.
  2. Define a path with scaled x and y coordinates
  3. Create x and y-axis based on our scales
  4. Graph a straight horizontal line at y = 0 in the #LineChart SVG
  5. Transition that line into having the correct y values based on our data
  6. Append our axis to our SVG
import './App.css';
import * as d3 from 'd3'
import {useEffect, useState} from 'react'
function App() {
useEffect(() => {
// Create a dataset of pets and the amount of people that own them
let dataSet = [
{subject: "Dogs", count: 150},
{subject: "Fish", count: 75},
{subject: "Cats", count: 135},
{subject: "Bunnies", count: 240},
]
// Generate a p tag for each element in the dataSet with the text: Subject: Count
d3.select('#pgraphs').selectAll('p').data(dataSet).enter().append('p').text(dt => dt.subject + ": " + dt.count)
// Bar Chart:
const getMax = () => { // Calculate the maximum value in the DataSet
let max = 0
dataSet.forEach((dt) => {
if(dt.count > max) {max = dt.count}
})
return max
}
// Create each of the bars and then set them all to have the same height(Which is the max value)
d3.select('#BarChart').selectAll('div').data(dataSet)
.enter().append('div').classed('bar', true).style('height', `${getMax()}px`)
//Transition the bars into having a height based on their corresponding count value
d3.select('#BarChart').selectAll('.bar')
.transition().duration(1000).style('height', bar => `${bar.count}px`)
.style('width', '80px').style('margin-right', '10px').delay(300) // Fix their width and margin
// Generate random data for our line where x is [0,15) and y is between 0 and 100
let lineData = []
for(let i = 0; i < 15; i++) {
lineData.push({x: i + 1, y: Math.round(Math.random() * 100)})
}
// Create our scales to map our data values(domain) to coordinate values(range)
let xScale = d3.scaleLinear().domain([0,15]).range([0, 300])
let yScale = d3.scaleLinear().domain([0,100]).range([300, 0]) // Since the SVG y starts at the top, we are inverting the 0 and 300.
// Generate a path with D3 based on the scaled data values
let line = d3.line()
.x(dt => xScale(dt.x))
.y(dt => yScale(dt.y))
// Generate the x and y Axis based on these scales
let xAxis = d3.axisBottom(xScale)
let yAxis = d3.axisLeft(yScale)
// Create the horizontal base line
d3.select('#LineChart').selectAll('path').datum(lineData) // Bind our data to the path element
.attr('d', d3.line().x(dt => xScale(dt.x)) // Set the path to our line function, but where x is the corresponding x
.y(yScale(0))).attr("stroke", "blue").attr('fill', 'none') // Set the y to always be 0 and set stroke and fill color
d3.select('#LineChart').selectAll('path').transition().duration(1000) // Transition the line over 1 sec
.attr('d', line) // Set the path to our line variable (Which corresponds the actual path of the data)
// Append the Axis to our LineChart svg
d3.select('#LineChart').append("g")
.attr("transform", "translate(0, " + 300 + ")").call(xAxis)
d3.select('#LineChart').append("g")
.attr("transform", "translate(0, 0)").call(yAxis)
}, [])
return (
<div className = "App">
<div id="pgraphs"></div> // Create a div to house our p tags
<div id="BarChart"></div> // Create a div to house our BarChart
<svg id="LineChart" width = {350} height = {350}><path/></svg> // Create an SVG and path for our LineChart
</div>
);
}
export default App;
view raw App.js hosted with ❤ by GitHub

Note that with functions like d3.line(), d3.axisBottom() and d3.scaleLinear(), D3 is giving us tools for processes that are possible in vanilla javascript. Even though D3 is built to allow you to make your visualizations from the ground up, it offers a host of different functions like this.

If all is working well, you should see the following Line Chart:

Alt Text

Conclusion

Again, this is only the bare bones of what is possible with D3. D3’s versatility and in-house toolset mean that the only limit to the beautiful visualizations you create is your imagination!

Thanks for reading!

Top comments (0)