D3.js is one of the best JavaScript library for data visualizations,
let's combine D3 and React create a simple Donut Chart.
I am not going to explain how we can create react app 😉
We can start from d3 onboarding.
Add D3 on you project
npm i --save d3
Create DonutChart component
After installing D3 we will create a new react component called DonutChart
. here I am giving file name 'DonutChart.js'.
import React , { Component} from 'react';
class DonutChart extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
}
render() {
return <>
<div></div>
</>
}
}
export default DonutChart;
Use D3 in React component
We will import D3 in to our component
import * as d3 from 'd3';
Create D3 selectors with React refs
class DonutChart extends Component {
constructor(props) {
super(props);
this.chRef = React.createRef();
}
componentDidMount() {
const divEl = d3.select(this.chRef.current)
}
render() {
return <>
<div ref={this.chRef}></div>
</>
}
}
Donut Painting
Create a colors Array for the donut partition painting
const colors = [ '#8ce8ad', '#57e188', '#34c768', '#2db757', '#27acaa', '#42c9c2', '#60e6e1', '#93f0e6', '#87d3f2', '#4ebeeb', '#35a4e8', '#188ce5', '#542ea5', '#724bc3', '#9c82d4', '#c981b2', '#b14891', '#ff6d00', '#ff810a', '#ff9831', '#ffb46a', '#ff9a91', '#ff736a', '#f95d54', '#ff4136', '#c4c4cd' ];
Or else we can create random color generator. in this option you need to also consider text colors for readability and better user experiences
const colors = ()=> { '#' + Math.floor(Math.random()*16777215).toString(16)}
Draw donut chart
let's deep in to the show 🏊🏻, We will create a drawChart()
method for portability.
// DrawChart
drawChart(){
const {data } = this.props;
const svgContainer = d3.select(this.chRef.current).node();
const width = svgContainer.getBoundingClientRect().width;
const height = width;
const margin = 15;
let radius = Math.min(width, height) / 2 - margin;
// Create SVG
const svg = d3.select(this.chRef.current)
.append('svg')
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox', '0 0 ' + width + ' ' + width )
//.attr('preserveAspectRatio','xMinYMin')
.append("g")
.attr("transform", "translate(" + Math.min(width,height) / 2 + "," + Math.min(width,height) / 2 + ")");
let pie = d3.pie()
.value( d => d.value )
let data_ready = pie(data)
// Donut partition
svg
.selectAll('whatever')
.data(data_ready)
.enter()
.append('path')
.attr('d', d3.arc()
.innerRadius(radius/ 1.75) // This is the size of the donut hole
.outerRadius(radius)
)
.attr('fill', (d) => colors[d.index] )
.attr("stroke", "#fff")
.style("stroke-width", "2")
.style("opacity", "0.8")
}
Show partition name and value
We can add labels also for identifying the donut partition.
// legend Position
let legendPosition = d3.arc().innerRadius(radius/1.75).outerRadius(radius);
// Legend group and legend name
svg
.selectAll('mySlices')
.data(data_ready)
.enter()
.append('g')
.attr("transform", d => `translate(${legendPosition.centroid(d)})`)
.attr("class", 'legend-g')
.style("user-select", "none")
.append('text')
.text(d => d.data.name)
.style("text-anchor", "middle")
.style("font-weight", 700)
.style("fill", '#222')
.style("font-size", 14);
//Label for value
svg
.selectAll('.legend-g')
.append('text')
.text((d) => { return d.data.value })
.style("fill", '#444')
.style("font-size", 12)
.style("text-anchor", "middle")
.attr("y", 16);
View DonutChart.js
let's see full picture of DonutChart.js
import React , { Component} from 'react';
import * as d3 from 'd3';
const colors = [ '#8ce8ad', '#57e188', '#34c768', '#2db757', '#27acaa', '#42c9c2', '#60e6e1', '#93f0e6', '#87d3f2', '#4ebeeb', '#35a4e8', '#188ce5', '#542ea5', '#724bc3', '#9c82d4', '#c981b2', '#b14891', '#ff6d00', '#ff810a', '#ff9831', '#ffb46a', '#ff9a91', '#ff736a', '#f95d54', '#ff4136', '#c4c4cd' ];
class DonutChart extends Component {
constructor(props) {
super(props);
this.chRef = React.createRef();
}
// Chart load after component Mount
componentDidMount() {
this.drawChart()
}
// DrawChart
drawChart(){
const {data } = this.props;
const svgContainer = d3.select(this.chRef.current).node();
const width = svgContainer.getBoundingClientRect().width;
const height = width;
const margin = 15;
let radius = Math.min(width, height) / 2 - margin;
// legend Position
let legendPosition = d3.arc().innerRadius(radius/1.75).outerRadius(radius);
// Create SVG
const svg = d3.select(this.chRef.current)
.append('svg')
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox', '0 0 ' + width + ' ' + width )
//.attr('preserveAspectRatio','xMinYMin')
.append("g")
.attr("transform", "translate(" + Math.min(width,height) / 2 + "," + Math.min(width,height) / 2 + ")");
let pie = d3.pie()
.value( d => d.value )
let data_ready = pie(data)
// Donut partition
svg
.selectAll('whatever')
.data(data_ready)
.enter()
.append('path')
.attr('d', d3.arc()
.innerRadius(radius/ 1.75) // This is the size of the donut hole
.outerRadius(radius)
)
.attr('fill', (d) => colors[d.index] )
.attr("stroke", "#fff")
.style("stroke-width", "2")
.style("opacity", "0.8")
// Legend group and legend name
svg
.selectAll('mySlices')
.data(data_ready)
.enter()
.append('g')
.attr("transform", d => `translate(${legendPosition.centroid(d)})`)
.attr("class", 'legend-g')
.style("user-select", "none")
.append('text')
.text(d => d.data.name)
.style("text-anchor", "middle")
.style("font-weight", 700)
.style("fill", '#222')
.style("font-size", 14);
//Label for value
svg
.selectAll('.legend-g')
.append('text')
.text((d)=>{ return d.data.value})
.style("fill", '#444')
.style("font-size", 12)
.style("text-anchor", "middle")
.attr("y", 16 );
}
render() {
return <>
<div ref={this.chRef}></div> </>
}
}
export default DonutChart;
Import and Use
Let's Import and use DonutChart
in application
import './App.css';
import DonutChart from './charts/DonutChart.js';
const donutData = [
{name: "<5", value: 19},
{name: "5-9", value: 20},
{name: "10-14", value: 19},
{name: "15-19", value: 24},
{name: "20-24", value: 22},
{name: "25-29", value: 29},
{name: "30-34", value: 22},
{name: "35-39", value: 18},
{name: "40-44", value: 23},
{name: "45-49", value: 19},
{name: "50-54", value: 16},
{name: "55-59", value: 19},
{name: "60-64", value: 28},
{name: "65-69", value: 17},
{name: "70-74", value: 20},
{name: "75-79", value: 17},
{name: "80-84", value: 18},
{name: "≥85", value: 21}
]
function App() {
return (
<div className="App">
<DonutChart data={donutData} />
</div>
);
}
export default App;
Top comments (2)
Apologies;
couldn't resist doing it with a 999 (Gzip) Bytes W3C standard Web Component
and only HTML:
Great 👏