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).
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;
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:
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
And for the other tip adding A 1 1 0 1 0 5.7971 58.4322
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:
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>