D3.js (Data-Driven Documents) is a powerful JavaScript library used for creating dynamic and interactive data visualizations on the web. With D3.js, creating a donut chart is straightforward and customizable. In this blog post, we will guide you through the process of building a responsive donut chart using d3.js in React.js, step-by-step.
Prerequisites
Before we get started, ensure that you have the following prerequisites:
Basic knowledge of HTML, CSS, and JavaScript
Node.js and npm installed on your system
A text editor like Visual Studio Code
Getting started
Let's create a new React.js project by running the following command in your terminal:
npx create-react-app responsive-donut-chart-using-d3js-reactjs
This command creates a new React.js project with the name responsive-donut-chart-using-d3js-reactjs
. Navigate to the project directory by running the following command:
cd responsive-donut-chart-using-d3js-reactjs
Open the project directory in your text editor. Next, install the d3
library by running the following command:
npm install d3
This command installs the latest version of the d3
library in your project.
Creating the donut chart component
Create a new file named DonutChart.tsx
in the src/features
directory of your project. This file will contain the code for the donut chart component. Here's the code for the component:
import * as d3 from 'd3';
import { FC, useEffect, useRef } from 'react';
import Spinner from 'components/Spinner';
import { useResizeObserver } from 'hooks/useResizeObserver';
const DonutChart: FC<IChart> = ({ data, svgWrapperRef }) => {
const dimensions: any = useResizeObserver(svgWrapperRef);
const svgRef = useRef<SVGSVGElement>(null);
useEffect(() => {
if (!svgRef?.current || !dimensions) return;
const innerWidth = dimensions?.width;
const innerHeight = dimensions?.height;
const radius = Math.min(innerWidth, innerHeight) / 2;
const svg = d3.select(svgRef?.current);
svg.selectAll('*').remove();
const pieGenerator = d3
.pie<IData>()
.value(({ value }) => value)
.sort(null);
const arcGenerator: any = d3
.arc<d3.PieArcDatum<IData>>()
.innerRadius(radius * 0.35)
.outerRadius(radius * 0.65);
const arcGeneratorForLabel = d3
.arc<d3.PieArcDatum<IData>>()
.innerRadius(radius)
.outerRadius(radius * 0.85);
const slices = pieGenerator([...data]);
const g = svg
.attr('width', innerWidth)
.attr('height', innerHeight)
.append('g')
.attr('transform', `translate(${innerWidth / 2}, ${innerHeight / 2})`);
g.selectAll('path')
.data(slices)
.enter()
.append('path')
.attr('fill', (d, i) => d?.data?.fillColor)
.attr('d', arcGenerator);
g.selectAll('polyline')
.data(slices)
.enter()
.append('polyline')
.style('fill', 'none')
.style('stroke', 'steelblue')
.attr('points', (d) => {
const pos = arcGeneratorForLabel.centroid(d);
pos[0] = radius * 0.65 * (midAngle(d) < Math.PI ? 1 : -1);
return [arcGenerator.centroid(d), arcGeneratorForLabel.centroid(d), pos];
});
g.selectAll('text')
.data(slices)
.enter()
.append('text')
.transition()
.duration(500)
.attr('dy', '.35em')
.text((d) => d?.data?.label)
.attr('transform', (d) => {
const pos = arcGeneratorForLabel.centroid(d);
pos[0] = radius * 0.95 * (midAngle(d) < Math.PI ? 1 : -1);
return `translate(${pos})`;
})
.style('font-size', '12px')
.style('fill', (d) => d?.data?.fillColor);
}, [data, dimensions]);
const midAngle = (d: any) => d.startAngle + (d.endAngle - d.startAngle) / 2;
if (!dimensions) {
return (
<div className="flex w-full justify-center items-center py-2">
<Spinner className="text-gray-300 h-8 w-8" />
</div>
);
}
return (
<div className="d3js">
<svg ref={svgRef} />
</div>
);
};
interface IData {
label: string;
value: number;
fillColor: string;
}
interface IChart {
data: IData[];
svgWrapperRef: any;
}
export default DonutChart;
The above code is a functional component in React that renders a donut chart using D3.js library. The component takes in data
as props and the svgWrapperRef
, which is a reference to the wrapper element that contains the SVG element. The component makes use of the useResizeObserver
hook to observe the dimensions of the SVG element's wrapper and sets the dimensions to the dimensions of the SVG element.
Inside the useEffect
hook, the component generates a pie chart using D3.js library with the provided data. It creates an arc generator
, slices the data into pie slices, and appends them as SVG path elements. It also adds text and polylines to label the slices.
The midAngle
function is a helper function used to calculate the mid-angle of the slice, which is used to position the label of the slice.
The component conditionally renders a Spinner
if the dimensions of the SVG element are not yet available. Overall, the code creates a responsive donut chart using D3.js in React.js.
Let’s import the DonutChart
component in the app.tsx
or other components and pass the props. Here's the code for the app.tsx
import { useEffect, useRef, useState } from 'react';
import DonutChart from './DonutChart';
import { randomNumber } from 'utils/helpers';
const Feature = () => {
const [data, setData] = useState<any[]>([]);
const svgLargeRef: any = useRef<SVGSVGElement>();
const svgMediumRef: any = useRef<SVGSVGElement>();
const svgSmallRef: any = useRef<SVGSVGElement>();
useEffect(() => {
const makeRandomData = () => {
setData([]);
['JS', 'Python', '.Net', 'Java', 'GoLang', 'UI/UX']?.map((label) => {
setData((prevData) => [...prevData, { label, value: randomNumber(10, 99), fillColor: `#${randomNumber()}` }]);
});
setTimeout(() => {
makeRandomData();
}, 1000 * 10);
};
makeRandomData();
}, []);
return (
<div className="relative px-5 py-2">
<div className="flex justify-center">
<div className="w-[90vw] h-full">
<h2 className="flex justify-between items-center font-semibold text-black mb-2">D3.js Chart</h2>
<div className="flex w-full h-[75vh] justify-center items-start border border-dashed border-black text-white p-4 space-x-2">
<div ref={svgLargeRef} className="w-4/6 h-72 border border-gray-300 p-1">
<DonutChart data={data} svgWrapperRef={svgLargeRef} />
</div>
<div ref={svgMediumRef} className="w-2/6 h-72 border border-gray-300 p-1">
<DonutChart data={data} svgWrapperRef={svgMediumRef} />
</div>
</div>
</div>
</div>
</div>
);
};
export default Feature;
This is a React functional component that renders a D3.js donut chart. It imports the useEffect
, useRef
, and useState
hooks from React, the DonutChart
component from the local ./DonutChart
file, and the randomNumber
function from a utils/helpers
module.
The component defines three SVG references using the useRef
hook, one for each of the three donut charts that are being rendered. It also initializes the data state as an empty array using the useState
hook.
The useEffect
hook is used to generate random data for the donut charts and update the data state every 10 seconds. The makeRandomData
function generates new data using the randomNumber
function and updates the data state. It is called initially when the component mounts, and then repeatedly every 10 seconds using the setTimeout
function.
The return statement renders the donut charts using the DonutChart
component, passing in the data state and the corresponding SVG reference for each chart. The component also renders some HTML elements with classes and styles.
Summary
This blog post explains how to use the D3.js library to build a responsive and interactive donut chart in a React application. The author walks through the steps required to create a data visualization component, and explains key concepts such as creating and updating SVG elements, data binding, and color schemes. The final result is a highly customizable and visually appealing donut chart that can be integrated into any React project.
I hope you found this tutorial helpful! If you have any questions or feedback, feel free to let me know.