DonutChart
donutchart-1779379671725.tsx
'use client'
import { useState } from 'react'
const SEGMENTS = [
{ label: 'Buttons', value: 12, color: '#C9A84C' },
{ label: 'Forms', value: 10, color: '#6366f1' },
{ label: 'Navigation', value: 8, color: '#22c55e' },
{ label: 'Dashboard', value: 7, color: '#f59e0b' },
{ label: 'Charts', value: 6, color: '#ec4899' },
{ label: 'Other', value: 77, color: '#334155' },
]
export default function DonutChart() {
const [hovered, setHovered] = useState<number | null>(null)
const total = SEGMENTS.reduce((a, s) => a + s.value, 0)
const CX = 100, CY = 100, R = 70, r = 42
let angle = -90
const paths = SEGMENTS.map((seg, i) => {
const pct = seg.value / total
const startAngle = angle
angle += pct * 360
const endAngle = angle
const a1 = (startAngle * Math.PI) / 180
const a2 = (endAngle * Math.PI) / 180
const x1 = CX + R * Math.cos(a1), y1 = CY + R * Math.sin(a1)
const x2 = CX + R * Math.cos(a2), y2 = CY + R * Math.sin(a2)
const ix1 = CX + r * Math.cos(a1), iy1 = CY + r * Math.sin(a1)
const ix2 = CX + r * Math.cos(a2), iy2 = CY + r * Math.sin(a2)
const large = pct > 0.5 ? 1 : 0
return {
d: `M ${x1} ${y1} A ${R} ${R} 0 ${large} 1 ${x2} ${y2} L ${ix2} ${iy2} A ${r} ${r} 0 ${large} 0 ${ix1} ${iy1} Z`,
mid: startAngle + pct * 180,
...seg, i,
}
})
const hov = hovered !== null ? SEGMENTS[hovered] : null
return (
<div style={{ background: '#0D0D0D', border: '1px solid rgba(255,255,255,0.06)', borderRadius: 16, padding: 24, display: 'flex', gap: 24, flexWrap: 'wrap' }}>
<div style={{ position: 'relative', width: 200, flexShrink: 0 }}>
<svg viewBox="0 0 200 200" width={200} height={200}>
{paths.map((p, i) => {
const scale = hovered === i ? 1.04 : 1
return (
<path key={i} d={p.d} fill={p.color} opacity={hovered === null || hovered === i ? 1 : 0.4}
style={{ transformOrigin: '100px 100px', transform: `scale(${scale})`, transition: 'all 0.15s', cursor: 'pointer' }}
onMouseEnter={() => setHovered(i)} onMouseLeave={() => setHovered(null)} />
)
})}
{/* Center */}
<text x={CX} y={CY - 6} textAnchor="middle" fontSize={hov ? 22 : 26} fontWeight={800} fill="#F5F5F0">
{hov ? hov.value : total}
</text>
<text x={CX} y={CY + 14} textAnchor="middle" fontSize={10} fill="rgba(255,255,255,0.4)">
{hov ? hov.label : 'components'}
</text>
{hov && (
<text x={CX} y={CY + 28} textAnchor="middle" fontSize={10} fill={hov.color}>
{((hov.value / total) * 100).toFixed(1)}%
</text>
)}
</svg>
</div>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 8 }}>
<div style={{ color: '#F5F5F0', fontSize: 14, fontWeight: 700, marginBottom: 4 }}>By Category</div>
{SEGMENTS.map((seg, i) => (
<div key={i} onMouseEnter={() => setHovered(i)} onMouseLeave={() => setHovered(null)}
style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', opacity: hovered === null || hovered === i ? 1 : 0.4, transition: 'opacity 0.15s' }}>
<div style={{ width: 10, height: 10, borderRadius: 2, background: seg.color, flexShrink: 0 }} />
<div style={{ flex: 1, color: 'rgba(255,255,255,0.7)', fontSize: 12 }}>{seg.label}</div>
<div style={{ color: '#F5F5F0', fontSize: 12, fontWeight: 600 }}>{seg.value}</div>
<div style={{ color: seg.color, fontSize: 11 }}>{((seg.value / total) * 100).toFixed(0)}%</div>
</div>
))}
</div>
</div>
)
}Component info
CategoryData Visualization
Frameworkreact
TierFREE
Views0
Copies0
About
Interactive SVG donut chart with hover segments, center stats, legend, and animated draw