Skip to content

The simple version

One wedge per row, and the share is the angle it turns through. The same structure with almost no styling: a good place to start your own.

How it’s built

A pie is a 100% stacked bar bent into a circle, so rhp does most of the work.

The spans. shares turns the hours into percentages and stackUp turns those into a from and a to:

const stack = createMemo(() => stackUp(shares(hours())));

Part one runs 0 to 37, part two 37 to 61, and so on to 100.

One box for all of them. The Plot has overlap, so every slat sits in the same box instead of a band of its own. Flat, that is a stacked bar. Round, it is a pie. Each slat’s Bar spans from to to, which rhp writes as --rhp-lo and --rhp-hi, and its Label sits at the middle of that span, which rhp writes as --rhp-p.

The wedge is the one thing rhp cannot draw, because an outline is a path, which is why rhp’s own Area and Line are paths too. The Bar keeps the numbers and gives up its box, and an <svg> inside it draws the wedge in a 100 by 100 box around the middle. The simple version is three commands: out to where the span starts, round the rim, close.

`M0,0L${xy(a, R)}A${R},${R} 0 ${b - a > 0.5 ? 1 : 0} 1 ${xy(b, R)}Z`

A gap has to be a distance, not an angle. Pad the angles and the gap is wide at the rim and closes to nothing in the middle, where every wedge meets. So the gallery version pushes each straight edge sideways instead of turning it, and Math.asin(off / r) gives the turn where a pushed edge crosses the circle of radius r. The edge stays parallel to the true one, so the paper between two wedges is one width the whole way out.

A stroke rounds the corners, not the path:

.slice path { fill: var(--rhp-color); stroke: var(--rhp-color); stroke-width: 3.8; stroke-linejoin: round; }

One color for both makes one solid shape, and stroke-linejoin: round rounds every corner of it. It also grows the wedge by half its width, so the path is drawn that much smaller and the edges pushed that much further apart. And each wedge starts at a small inner radius rather than a tip, so all four of its corners sit on a circle and one stroke can round them all.

The path is never transitioned. CSS would carry every point along a straight line, and a point crossing a circle in a straight line cuts the corner, so the rim would flatten on the way. animated moves the two numbers instead, and the wedge is drawn again from them each frame:

const span = animated(() => [d.from, d.to], SWEEP);

The label moves on the same two numbers, as the Multiple example’s labels do. Its --turn is their middle, and its CSS turns it out to that angle, so it stays on its wedge the whole way:

<Label at={(d.from + d.to) / 2} class="tag" style={{ "--turn": (span()[0] + span()[1]) / 200 }}>

Flat, none of this runs. The <svg> is hidden and rhp draws the bar itself, with --rhp-gap between segments and --rhp-radius on their corners. The same two numbers, spent on a length instead of an angle.

The gallery version also has a wedge that slides out on hover, in the direction CSS works out from sin() and cos() of the angle halfway along its span. Under overlap every slat fills the plot, so the slat is given pointer-events: none and the painted path and its label are given it back. keyboard on the Plot adds tab and the arrow keys.