Home > Software design >  SVG Donut Chart with space between the path
SVG Donut Chart with space between the path

Time:06-20

Is there a good way change the following example (stackblitz), in order to have some space between the paths and have them rounded (eg. using stroke-linecap: round).

Here is the example: enter image description here

CodePudding user response:

I am not sure who made this and how exactly this works. But changes to donut-chart.pipe.ts can get close to what you want.

const degrees = this.percentToDegrees(donutSlice.percent - 3);
const innerRadius = radius - borderSize   10;

enter image description here

Though you can't get the rounded corners that simply. A SVG path is defined by series of points - you would have to re-calculate those points. SVG stroke could have rounded tips but not an SVG path, nor can SVG path inherit CSS type of properties. Unfortunately seems that the original author did not implement this feature either. So we should do some math to get them rounded.

Basically you could add more Elliptical Arc's to both tips of the svg. Following advice from: enter image description here

So by adding A 1 1 0 0 0 100 50 to the green path we get something like this.

M 100 50 A 50 50 0 1 0 1.5708 62.4345 L 6.4138 61.191 A 45 45 0 1 1 95 50 A 1 1 0 0 0 100 50

enter image description here

And for the other tip adding A 1 1 0 1 0 5.7971 58.4322

enter image description here

With both tips:

M 100 50 A 50 50 0 1 0 0.8856 59.3691 A 1 1 0 1 0 5.7971 58.4322 A 45 45 0 1 1 95 50 A 1 1 0 1 0 100 50

So now we understand what we need to do, we add Arc before L and an Arc to the end, where x, y is the locations of the original path.

commands.push(`A 1 1 0 1 0 ${ this.getCoordFromDegrees(degrees, innerRadius, svgSize) }`);
commands.push(`A 1 1 0 0 0 100 50`);

I have marked the changes with // <-------.

  getSliceCommands(donutSlice: DonutSlice, radius: number, svgSize: number, borderSize: number): string {
    const degrees = this.percentToDegrees(donutSlice.percent - 3); // <-------
    const longPathFlag = degrees > 180 ? 1 : 0;
    const innerRadius = radius - borderSize   10; // <-------

    const commands: string[] = [];
    
    commands.push(`M ${ svgSize / 2   radius } ${ svgSize / 2 }`);
    commands.push(`A ${ radius } ${ radius } 0 ${ longPathFlag } 0 ${ this.getCoordFromDegrees(degrees, radius, svgSize) }`);
    commands.push(`A 1 1 0 1 0 ${ this.getCoordFromDegrees(degrees, innerRadius, svgSize) }`); // <-------
    commands.push(`L ${ this.getCoordFromDegrees(degrees, innerRadius, svgSize) }`);
    commands.push(`A ${ innerRadius } ${ innerRadius } 0 ${ longPathFlag } 1 ${ svgSize / 2   innerRadius } ${ svgSize / 2 }`);
    commands.push(`A 1 1 0 0 0 100 50`); // <-------
    return commands.join(' ');
  }

End result:

enter image description here

Forked: https://stackblitz.com/edit/angular-ivy-foqctq?file=src/app/donut-chart/donut-chart.pipe.ts

CodePudding user response:

Here you have a doughnut chart based on the array of objects. I guess it is just a matter of calculating the length of each slice and add that to the dash array and the dash offset.

The circlecontainer has an event listener that prints out the label below the chart.

const DonutSlice = [{
    id: 1,
    percent: 55,
    color: 'DarkSeaGreen',
    label: 'Slice 1'
  },
  {
    id: 2,
    percent: 30,
    color: 'DarkOrchid',
    label: 'Slice 2'
  },
  {
    id: 3,
    percent: 5,
    color: 'Tomato',
    label: 'Slice 3'
  },
  {
    id: 3,
    percent: 10,
    color: 'DodgerBlue',
    label: 'Slice 4'
  }
];

const circlecontainer = document.getElementById('circlecontainer');
const output = document.getElementById('output');

circlecontainer.innerHTML = DonutSlice.map((item, i, arr) => {
  let offset = 2*i   arr.filter((item, j) => j < i)
                       .reduce((total, item) => total   item.percent, 0);
  return `<circle data-id="${item.id}" stroke="${item.color}"
          cx="20" cy="20" r="19" 
          pathLength="${100   2*arr.length}"
          stroke-dasharray="${item.percent} 100"
          stroke-dashoffset="-${offset}" />`;
}).join('');

circlecontainer.addEventListener('click', e => {
  let slice = e.target.closest('.slice');
  if(slice){
    item = DonutSlice.find(item => item.id == parseInt(slice.dataset.id));
    output.innerHTML = `${item.label} was clicked`;
  }
});
.slice {
  stroke-linecap: round;
  stroke-width: 2;
  fill: none;
  cursor: pointer;
}

p#output {
  text-align: center;
}
<div style="width: 200px">
  <svg xmlns="http//www.w3.org/2000/svg" viewBox="0 0 40 40" id="svg">
    <g id="circlecontainer" transform="rotate(-90 20 20)"></g>
  </svg>
  <p id="output"></p>
</div>

  • Related