51 lines
1.6 KiB
TypeScript
51 lines
1.6 KiB
TypeScript
/** Stacked donut for moderation overview / composition. */
|
|||
|
|
export function Donut({
|
||
|
|
segments,
|
||
|
|
size = 132,
|
||
|
|
thickness = 14,
|
||
|
|
centerLabel,
|
||
|
|
centerSub,
|
||
|
|
}: {
|
||
|
|
segments: { value: number; color: string; label: string }[];
|
||
|
|
size?: number;
|
||
|
|
thickness?: number;
|
||
|
|
centerLabel?: string;
|
||
|
|
centerSub?: string;
|
||
|
|
}) {
|
||
|
|
const total = segments.reduce((s, x) => s + x.value, 0) || 1;
|
||
|
|
const r = size / 2 - thickness / 2;
|
||
|
|
const c = 2 * Math.PI * r;
|
||
|
|
let offset = 0;
|
||
|
|
return (
|
||
|
|
<div className="relative inline-flex items-center justify-center" style={{ width: size, height: size }}>
|
||
|
|
<svg width={size} height={size} className="-rotate-90">
|
||
|
|
<circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke="var(--color-hairline)" strokeWidth={thickness} />
|
||
|
|
{segments.map((s, i) => {
|
||
|
|
const len = (s.value / total) * c;
|
||
|
|
const el = (
|
||
|
|
<circle
|
||
|
|
key={i}
|
||
|
|
cx={size / 2}
|
||
|
|
cy={size / 2}
|
||
|
|
r={r}
|
||
|
|
fill="none"
|
||
|
|
stroke={s.color}
|
||
|
|
strokeWidth={thickness}
|
||
|
|
strokeDasharray={`${len} ${c - len}`}
|
||
|
|
strokeDashoffset={-offset}
|
||
|
|
style={{ transition: "stroke-dashoffset 0.6s ease" }}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
offset += len;
|
||
|
|
return el;
|
||
|
|
})}
|
||
|
|
</svg>
|
||
|
|
{(centerLabel || centerSub) && (
|
||
|
|
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
||
|
|
{centerLabel && <span className="display text-lg">{centerLabel}</span>}
|
||
|
|
{centerSub && <span className="mono text-[0.6rem] text-ink-faint">{centerSub}</span>}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|