Search code examples
javascriptreactjsd3.jsdonut-chartreact-google-charts

Customizing Donut chart


I am trying to build a donut chart

I want a design similar the following image where the values are shown in between the coloured pie

import DonutChart from 'react-d3-donut';

    let data = [{
      count: 20    ,  
      color: "red"  ,
      name: 'My name' 
      },{
        count:30,
        color:"green",
        name:"yeys"
      },{
        count:20,
        color:"orange",
        name:"yeys"
      }]
      <DonutChart
innerRadius={90}
outerRadius={100}
transition={true}
svgClass="example1"
pieClass="pie1"
displayTooltip={true}
strokeWidth={3}
data={data} />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

are there any other libraries that helps me in building this design


Solution

  • Here is a simple donut chart with D3:

    const data = [
        {value: 20, text: 'First', color: 'red'},
        {value: 30, text: 'Second', color: 'green'},
        {value: 60, text: 'Third', color: 'blue'},
    ];
    
    const svg = d3.select('svg');
    const width = parseInt(svg.attr('width'));
    const height = parseInt(svg.attr('height'));
    
    const margin = 10;
    const arcWidth = 30;
    const radius = Math.min(width/2 - margin, height/2 - margin) - arcWidth / 2;
    const center = {x: width / 2, y: height / 2};
    
    let anglePos = 0;
    const angleOffset = 0.025;
    
    const sum = data.reduce((s, {value}) => s + value, 0);
    data.forEach(({value, text, color}, index) => {
      const angle = Math.PI * 2 * value / sum;
      const start = {
        x: center.x + radius * Math.sin(anglePos + angleOffset),
        y: center.y + radius * -Math.cos(anglePos + angleOffset),
      };
      anglePos += angle;
      const end = {
        x: center.x + radius * Math.sin(anglePos - angleOffset),
        y: center.y + radius * -Math.cos(anglePos -angleOffset),
      };
      const flags = value / sum >= 0.5 ? '1 1 1' : '0 0 1';
      const pathId = `my-pie-chart-path-${index}`;
      svg.append('path')
        .attr('id', pathId)
        .attr('d', `M ${start.x},${start.y} A ${radius},${radius} ${flags} ${end.x},${end.y}`)
        .style('stroke', color)
        .style('fill', 'none')
        .style('stroke-width', arcWidth);
        
       svg.append('text')
        .attr('dy', 6)
        .attr('text-anchor', 'middle')
        .append('textPath')
        .attr('startOffset', angle * radius / 2)
        .attr('href', `#${pathId}`)
        .text(text)
    });
    text {
      font-family: Calibri;
      font-size: 20px;
      font-weight: bold;
      fill: white;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
    
    <svg width="200" height="200"></svg>

    An example with text backgrounds:

    const data = [
        {value: 20, text: 'First', color: 'red'},
        {value: 30, text: 'Second', color: 'green'},
        {value: 60, text: 'Third', color: 'blue'},
    ];
    
    const svg = d3.select('svg');
    const width = parseInt(svg.attr('width'));
    const height = parseInt(svg.attr('height'));
    
    const margin = 10;
    const arcWidth = 30;
    const radius = Math.min(width/2 - margin, height/2 - margin) - arcWidth / 2;
    const center = {x: width / 2, y: height / 2};
    
    let anglePos = 0;
    const angleOffset = 0.025;
    
    const sum = data.reduce((s, {value}) => s + value, 0);
    data.forEach(({value, text, color}, index) => {
        const angle = Math.PI * 2 * value / sum;
      const start = {
        x: center.x + radius * Math.sin(anglePos + angleOffset),
        y: center.y + radius * -Math.cos(anglePos + angleOffset),
      };
      anglePos += angle;
      const end = {
        x: center.x + radius * Math.sin(anglePos - angleOffset),
        y: center.y + radius * -Math.cos(anglePos -angleOffset),
      };
      const flags = value / sum >= 0.5 ? '1 1 1' : '0 0 1';
      const pathId = `my-pie-chart-path-${index}`;
      svg.append('path')
        .attr('id', pathId)
        .attr('d', `M ${start.x},${start.y} A ${radius},${radius} ${flags} ${end.x},${end.y}`)
        //.attr('stroke-linecap', 'round')
        .style('stroke', color)
        .style('fill', 'none')
        .style('stroke-width', arcWidth);
        
      const textBg = svg.append('path');
         
        const textElement = svg.append('text')
        .text(text)
        .attr('dy', 6)
        .attr('text-anchor', 'middle');
        
      const textBox = textElement.node().getBBox();
      textElement.text(null);
      const bgAngle = Math.abs(textBox.x) / radius;
      const midAngle = anglePos - angle / 2;
      const endBgAngle = midAngle + bgAngle;
      const startBg = {
        x: center.x + radius * Math.sin(midAngle - bgAngle),
        y: center.y + radius * -Math.cos(midAngle - bgAngle),
      };
      const endBg = {
        x: center.x + radius * Math.sin(midAngle + bgAngle),
        y: center.y + radius * -Math.cos(midAngle + bgAngle),
      };
    textBg.attr('d', `M ${startBg.x},${startBg.y} A ${radius},${radius} 0 0 1 ${endBg.x},${endBg.y}`)
        .style('stroke', 'white')
        .style('fill', 'none')
        .attr('stroke-linecap', 'round')
        .style('stroke-width', arcWidth * 0.6);
      
      textElement.append('textPath')
        .text(text)
        .attr('startOffset', angle * radius / 2)
        .attr('href', `#${pathId}`)
    });
    text {
      font-family: Calibri;
      font-size: 16px;
      fill: black;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
    
    <svg width="200" height="200"></svg>