React D3 Donut Chart
Tags: react, d3, chart
Published: 2021-06-20 11:59:29

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'.

```javascript
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 

```javascript
import * as d3 from 'd3';
```

Create D3 selectors with React refs 

```javascript
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
```javascript
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  
```javascript
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.

```javascript
    // 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.
```javascript
// 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`

```javascript
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 

```javascript
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;


```

### Result

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vy0i2voa4xfjvlzo04ap.png)