Segmented Donut Chart
- Skia
- Reanimated
- Gesture Handler
- Haptics
A ring of five rounded slices that sweep in one after another, pop outward when tapped and roll the number in the middle to the chosen value. Every slice is its own Path, restroked from a derived value each frame, so the animation never waits on a React render.
Gaps in pixels
The gap control is in pixels, so the chart converts it to an angle at the live radius, gap / radius, and a resize keeps the spacing the same. layoutSegments starts each slice half a gap in and paints its share minus one gap, which keeps every slice centered in its slot. The gap is capped so that all of them together take at most 90% of the ring.
const maxGap = (TWO_PI * 0.9) / n;const gap = gapRad < 0 ? 0 : gapRad > maxGap ? maxGap : gapRad;// …const painted = Math.max(0, share - gap);const start = cursor + gap / 2;One clock for five slices
Replay fires its haptic on the JavaScript thread, then hops to the UI thread only to stamp a start time; firing the haptic from inside that hop intermittently aborted the worklets runtime. Every slice reads the same clock, waits 0.13 s per index and sweeps for 0.55 s with an ease-out. Before the first replay the reveal is pinned at 1, so the chart loads fully drawn instead of as an empty ring.
const DRAW_SECS = 0.55;const STAGGER_SECS = 0.13;// …const reveal = fireSignal === 0 ? 1 : easings.easeOut( segmentReveal(index, seconds.get(), DRAW_SECS, STAGGER_SECS), );Pop, glow and the counter
A tap is tested against the slices exactly as drawn: it is moved back into each slice's own space and checked against the band and, with rounded caps, the two end disks. The chosen slice slides out 7% of the radius, moves to the last draw slot so it sits above the ring, and gains a glow blurred by 6 * pop. The center number rolls to its value over 420 ms on a cubic curve, because a spring overshoots and counts back.
if (hasSelection) order.push(selected);// …const scale = 1 + 0.02 * pop;const offset = geometry.radius * geometry.popDistanceFraction * pop;// …strokeWidth: (geometry.stroke * (1 + 0.12 * pop)) / scale,opacity: dim,glow: (6 * pop) / scale,Tuning
- 4
- 0.6
- On
<AnimatedDonutChart params={{ gap: 4, innerRadius: 0.6, rounded: true }} fireSignal={0} />