I’m unable to figure out how to evenly space each slice on this donut chart.
import { storyblokEditable } from "@storyblok/react";
import { type BlokType } from "../../../types/storyblok";
interface AnimatedPieBlokProps {
blok: BlokType;
}
function AnimatedPieBlok({ blok }: AnimatedPieBlokProps) {
const { pieSlices } = blok;
const totalSlices = pieSlices.length;
const calculatedSlices = pieSlices.map((slice: BlokType) => ({
...slice,
value: 360 / totalSlices
}));
const total = calculatedSlices.reduce((sum: number, item: BlokType) => sum + item.value, 0);
const spacing = 1; // Set the spacing between slices
const sliceAngle = (360 - (spacing * totalSlices)) / totalSlices;
let cumulativeValue = 0;
return (
<div {...storyblokEditable(blok)} key={blok._uid} className="flex flex-row justify-center">
<svg width="450" height="450" viewBox="0 0 200 200" className="flex-1">
{calculatedSlices.map((item: BlokType, index: number) => {
const words = item.title.split(' ');
const firstWord = words[0];
const secondWord = words[1];
const startAngle = cumulativeValue * (Math.PI / 180);
const endAngle = (cumulativeValue + sliceAngle) * (Math.PI / 180);
// Adjust the angles to create spacing
const adjustedStartAngle = startAngle + (spacing / 100); // Adjust for spacing
const adjustedEndAngle = endAngle - (spacing / 100); // Adjust for spacing
const x1 = 100 + 100 * Math.cos(startAngle);
const y1 = 100 + 100 * Math.sin(startAngle);
const x2 = 100 + 100 * Math.cos(endAngle);
const y2 = 100 + 100 * Math.sin(endAngle);
// Calculate the midpoint for the label and image
const midAngle = (adjustedStartAngle + adjustedEndAngle) / 2;
const labelX = 100 + 65 * Math.cos(midAngle); // Adjust the radius for label position
const labelY = 100 + 65 * Math.sin(midAngle); // Adjust the radius for label position
const imageX = 100 + 40 * Math.cos(midAngle); // Adjust the radius for image position
const imageY = 100 + 40 * Math.sin(midAngle); // Adjust the radius for image position
cumulativeValue += sliceAngle + spacing;
return (
<g
key={index}
className="transition-transform duration-300 ease-in-out hover:scale-105 relative hover:z-10"
>
<path
d={`M 100,100 L ${x1},${y1} A 100,100 0 ${item.value / total > 0.5 ? 1 : 0} 1 ${x2},${y2} Z`}
fill={item.backgroundColor}
/>
<image
href={item.image.filename} // Use the imageUrl from the dataset
x={imageX - 15} // Center the image (adjust as needed)
y={imageY - 15} // Center the image (adjust as needed)
width="30" // Set the width of the image
height="30" // Set the height of the image
/>
<text
x={labelX}
y={labelY}
fill="white"
fontSize="7px"
textAnchor="middle"
alignmentBaseline="middle"
>
<tspan>{firstWord}</tspan>
{secondWord ? <tspan x={labelX} dy="1.2em">{secondWord}</tspan> : null}
</text>
</g>
);
})}
<circle cx="100" cy="100" r="30" fill="white" />
<circle cx="100" cy="100" r="29" fill="none" stroke="#140965" strokeWidth="3" />
</svg>
</div>
);
}
export default AnimatedPieBlok;
I’ve tried using other AI tools along with other stackoverflow posts to try and resolve the issue but haven’t been able to get it to work properly.
2
Answers
Key is to use
pathLength
, and then you do not need anyMath
Wrapped in a native JavaScript Web Component (JSWC) for ease of use
with shadowDOM so
<style>
is scoped and does not leak out to the rest of the DOM page:Mix of CSS properties and attributes just to show what is possible, you could do everything with
<path>
attributesUse https://yqnn.github.io/svg-path-editor if you need another d-path
You can use the stroke-dasharray combined with pathLength to create slices, and then move the slices a bit (for the two examples, two different values:
translate(.5 0)
andtranslate(1 0)
). This will make the gap between the slices even. To make the inner and outer edge of the slices look nice, mask off all the slices with a mask that has circles in white and black.My examples are static — you can probably figure out the dynamic version yourself.