John Au-Yeung I'm a web developer interested in JavaScript stuff.

Using D3.js v6 with React

6 min read 1736

Using D3.js v6 With React

React is the most popular frontend web framework in the world, and D3.js is among the most popular JavaScript libraries to manipulate graphics on-screen.

Version 6 is the latest release of D3.js, and in this article, we’ll look at how to use D3.js v6 in our React apps.

Getting started

First, we’ll create a React project using create-react-app like so:

npx create-react-app d3-app
cd d3-app
npm start

Then we install the D3.js package:

npm i d3

Now we can add D3 to our React app to add some graphics.

Using D3.js v6 with our React apps

We can use D3 in our app by putting the D3 code in the useEffect Hook callback. This is because we’re selecting an element with the DOM and then changing it with D3.

For example, we can write:

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

export default function App() {
  useEffect(() => {
    d3.select(".target").style("stroke-width", 5);
  }, []);
  return (
    <div className="App">
      <svg>
        <circle
          class="target"
          style={{ fill: "green" }}
          stroke="black"
          cx={50}
          cy={50}
          r={40}
        ></circle>
      </svg>
    </div>
  );
}

We get the SVG with the target class and then change the stroke-width after selecting it.

We can also put the D3 code in a function and call it when we’d like to use it. For example, we can write:

import React from "react";
import * as d3 from "d3";

export default function App() {
  const changeStroke = () => {
    d3.select(".target").style("stroke-width", 5);
  };

  return (
    <div className="App">
      <button onClick={changeStroke}>change stroke</button>
      <svg>
        <circle
          class="target"
          style={{ fill: "green" }}
          stroke="black"
          cx={50}
          cy={50}
          r={40}
        ></circle>
      </svg>
    </div>
  );
}

We set the D3 code to change the circle’s stroke in the changeStroke function, and we call it on a button click.

We made a custom demo for .
No really. Click here to check it out.

We can also add everything into another svg element. For example, we can add three circles by writing the following code:

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

export default function App() {
  useEffect(() => {
    const svg = d3.select("#area");
    svg
      .append("circle")
      .attr("cx", 50)
      .attr("cy", 50)
      .attr("r", 40)
      .style("fill", "blue");
    svg
      .append("circle")
      .attr("cx", 140)
      .attr("cy", 70)
      .attr("r", 40)
      .style("fill", "red");
    svg
      .append("circle")
      .attr("cx", 300)
      .attr("cy", 100)
      .attr("r", 40)
      .style("fill", "green");
  }, []);

  return (
    <div className="App">
      <svg id="area" height={200} width={450}></svg>
    </div>
  );
}

Let’s break it down:

  • The append method with the 'circle' argument adds the circle
  • The attr method calls add the attributes for the circle
  • cx is the x-coordinate of the circle’s center
  • cy is the y-coordinate of the circle’s center
  • r is the radius
  • style contains the properties and values that we want to put into the circle’s style attribute

Scaling graphics

We can scale graphics with D3.js in our React app by using the scaleLinear method. For example, we can write the following code to add an x-axis to our graph:

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

export default function App() {
  useEffect(() => {
    const margin = { top: 10, right: 40, bottom: 30, left: 30 },
      width = 450 - margin.left - margin.right,
      height = 400 - margin.top - margin.bottom;

    const svg = d3
      .select("#area")
      .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})`);

    const x = d3.scaleLinear().domain([0, 100]).range([0, width]);
    svg
      .append("g")
      .attr("transform", `translate(0, ${height})`)
      .call(d3.axisBottom(x));

    const y = d3.scaleLinear().domain([0, 100]).range([height, 0]);
    svg.append("g").call(d3.axisLeft(y));
  }, []);

  return (
    <div className="App">
      <svg id="area" height={400} width={500}></svg>
    </div>
  );
}

We simply call d3.axisBottom with our x function to add the x-axis. It’ll add the values between the min and max values we passed into the domain function.

Adding a y-axis to our graph

It should go without saying that we can add a y-axis to a graph with D3.

In the code above, you’ll notice we added the margin object to let us set the margins for the graph more easily. Then we append the svh object to the body of the page with the following code:

const svg = d3
  .select("#area")
  .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})`);

We set the width and height and then translate the object into the desired position we want.

Next, we add the x-axis and scale by writing:

const x = d3.scaleLinear().domain([0, 100]).range([0, width]);
svg
  .append("g")
  .attr("transform", `translate(0, ${height})`)
  .call(d3.axisBottom(x));

We call the domain and range as we did before, and we call the d3.axisBottom method to add the x-axis.

Then, to add the y-axis, we write:

const y = d3.scaleLinear().domain([0, 100]).range([height, 0]);
svg.append("g").call(d3.axisLeft(y));

We called domain and range to create the min and max values for the y-axis, then we just call d3.axisLeft to add the y-axis.

Creating a scatterplot

To add dots/points to the graph to create a scatter plot, we can add circles as we did before. For example, we can write the following to create a full scatterplot graph:

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

const DATA = [
  { x: 10, y: 20 },
  { x: 20, y: 50 },
  { x: 80, y: 90 }
];

export default function App() {
  useEffect(() => {
    const margin = { top: 10, right: 40, bottom: 30, left: 30 },
      width = 450 - margin.left - margin.right,
      height = 400 - margin.top - margin.bottom;

    const svg = d3
      .select("#area")
      .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})`);

    const x = d3.scaleLinear().domain([0, 100]).range([0, width]);
    svg
      .append("g")
      .attr("transform", `translate(0, ${height})`)
      .call(d3.axisBottom(x));

    const y = d3.scaleLinear().domain([0, 100]).range([height, 0]);
    svg.append("g").call(d3.axisLeft(y));

    svg
      .selectAll("whatever")
      .data(DATA)
      .enter()
      .append("circle")
      .attr("cx", (d) => x(d.x))
      .attr("cy", (d) => y(d.y))
      .attr("r", 7);
  }, []);

  return (
    <div className="App">
      <svg id="area" height={400} width={500}></svg>
    </div>
  );
}

Let’s break it down a bit. You’ll notice we added this code in the useEffect callback to render the points on the scatterplot:

svg
  .selectAll("whatever")
  .data(DATA)
  .enter()
  .append("circle")
  .attr("cx", (d) => x(d.x))
  .attr("cy", (d) => y(d.y))
  .attr("r", 7);

We read the data with the data method. The DATA array is:

const DATA = [
  { x: 10, y: 20 },
  { x: 20, y: 50 },
  { x: 80, y: 90 }
];

Then, to get the x- and y-coordinates relative to the domain, we use the x and y methods with the dots. The values are between 0 and 100, and they’ll automatically be scaled proportionally to the width and height values.

So if x is 10, then on the screen, cx would be 10 * (450 - margin.left - margin.right); if y is 20, then on the screen, cy would be 20 * (400 - margin.top - margin.bottom).

Creating graphs from data

One of the most common use cases for D3 is to create graphs from data. To do so, we can read the data from a CSV.

For example, we can read data from the data.csv file in the src folder:

date,close
01-May-20,58.13
30-Apr-20,53.98
27-Apr-20,67.00
26-Apr-20,89.70
25-Apr-20,99.00
24-Apr-20,130.28
23-Apr-20,166.70
20-Apr-20,234.98
19-Apr-20,345.44
18-Apr-20,443.34
17-Apr-20,543.70
16-Apr-20,580.13
13-Apr-20,605.23
12-Apr-20,622.77
11-Apr-20,626.20
10-Apr-20,628.44
09-Apr-20,636.23
05-Apr-20,633.68
04-Apr-20,624.31
03-Apr-20,629.32
02-Apr-20,618.63

In App.js, we write:

import React, { useEffect } from "react";
import * as d3 from "d3";
import "d3-time-format";
const parseTime = d3.timeParse("%d-%b-%y");

const createGraph = async () => {
  const margin = { top: 20, right: 20, bottom: 50, left: 70 },
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;
  const x = d3.scaleTime().range([0, width]);
  const y = d3.scaleLinear().range([height, 0]);

  const valueLine = d3.line()
    .x((d) => { return x(d.date); })
    .y((d) => { return y(d.close); });

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

  let data = await d3.csv(require("./data.csv"))

  data.forEach((d) => {
    d.date = parseTime(d.date);
    d.close = +d.close;
  });

  data = data.sort((a, b) => +a.date - +b.date)

  x.domain(d3.extent(data, (d) => { return d.date; }));
  y.domain([0, d3.max(data, (d) => { return d.close; })]);

  svg.append("path")
    .data([data])
    .attr("class", "line")
    .attr("d", valueLine);

  svg.append("g")
    .attr("transform", `translate(0, ${height})`)
    .call(d3.axisBottom(x));

  svg.append("g")
    .call(d3.axisLeft(y));
}

export default function App() {
  useEffect(() => {
    createGraph();
  }, []);

  return (
    <div>
      <style>{
        `
          .line {
            fill: none;
            stroke: steelblue;
            stroke-width: 2px;
          }
      `}
      </style>
    </div>
  );
}

We move the graph creation code to the createGraph function. We’ve also added the d3-time-format library by running npm i d3-time-format. Then we can import it like we do on this file.

We created the axes in a similar way to the previous examples. We create the x and y functions like so, so that we can create the axes with them later:

const x = d3.scaleTime().range([0, width]);
const y = d3.scaleLinear().range([height, 0]);

The container for the graph is created with:

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

Then, we read the CSV data with:

let data = await d3.csv(require("./data.csv"))

We parse the time and change the close property into numbers with:

data.forEach((d) => {
  d.date = parseTime(d.date);
  d.close = +d.close;
});

Then we sort the data by date like so:

data = data.sort((a, b) => +a.date - +b.date)

We add the line with the following code:

svg.append("path")
  .data([data])
  .attr("class", "line")
  .attr("d", valueLine);

And we add the x- and y-axes, respectively:

svg.append("g")
  .attr("transform", `translate(0, ${height})`)
  .call(d3.axisBottom(x));
svg.append("g")
  .call(d3.axisLeft(y));

Finally, in the App component, we write:

<style>{
  `
    .line {
      fill: none;
      stroke: steelblue;
      stroke-width: 2px;
    }
`}

With this, we’ll see a line instead of a filled shape.

Conclusion

As you’ve just seen, we can easily create graphics with D3.js. It works well with React apps and can be integrated with no extra work.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

John Au-Yeung I'm a web developer interested in JavaScript stuff.

Leave a Reply