- ds
- Data display
- Donut and pie
Donut and pie
Parts of a whole, with the total in the middle.
A part of a whole, when there are few parts and the whole matters. The middle holds the total, which is the main reason to pick a donut over a bar. Past five segments, or when the parts are close in size, use horizontal bars: angles are hard to compare.
- Compute$8,42068%
- Storage$1,44012%
- Network$1,21010%
- Managed DB$9608%
- Other$3103%
Show code
<div class="donut-row">
<div class="donut" role="img" aria-label="Cloud spend for September, $12.3k: compute 68%, storage 12%, network 10%, managed databases 8%, other 3%">
<svg viewBox="0 0 42 42" aria-hidden="true"><circle class="track" cx="21" cy="21" r="15.915"/><circle class="s1" cx="21" cy="21" r="15.915" pathLength="100" stroke-dasharray="67.63 32.37" stroke-dashoffset="0.00"><title>Compute: 68.2%</title></circle><circle class="s2" cx="21" cy="21" r="15.915" pathLength="100" stroke-dasharray="11.07 88.93" stroke-dashoffset="-68.23"><title>Storage: 11.7%</title></circle><circle class="s3" cx="21" cy="21" r="15.915" pathLength="100" stroke-dasharray="9.21 90.79" stroke-dashoffset="-79.90"><title>Network: 9.8%</title></circle><circle class="s4" cx="21" cy="21" r="15.915" pathLength="100" stroke-dasharray="7.18 92.82" stroke-dashoffset="-89.71"><title>Managed DB: 7.8%</title></circle><circle class="s6" cx="21" cy="21" r="15.915" pathLength="100" stroke-dasharray="1.91 98.09" stroke-dashoffset="-97.49"><title>Other: 2.5%</title></circle></svg>
<div class="donut-c"><b>$12.3k</b><span>September</span></div>
</div>
<ul class="ch-legend col">
<li class="s1"><span>Compute</span><b>$8,420</b><small>68%</small></li>
<li class="s2"><span>Storage</span><b>$1,440</b><small>12%</small></li>
<li class="s3"><span>Network</span><b>$1,210</b><small>10%</small></li>
<li class="s4"><span>Managed DB</span><b>$960</b><small>8%</small></li>
<li class="s6"><span>Other</span><b>$310</b><small>3%</small></li>
</ul>
</div>Segments are circles with stroke-dasharray on a circumference of 100, so the dash is the share in percent and the offset is the running total. A 0.6 gap separates them. Hovering one thickens it and dims the rest; the <title> in each gives the exact share.
Status split and sizes
Code
<div class="donut thin" style="--size:7rem" role="img" aria-label="Responses by status: 97.6% 2xx, 2.1% 4xx, 0.3% 5xx">
<svg viewBox="0 0 42 42" aria-hidden="true"><circle class="track" cx="21" cy="21" r="15.915"/><circle class="c-ok" cx="21" cy="21" r="15.915" pathLength="100" stroke-dasharray="97.00 3.00" stroke-dashoffset="0.00"><title>2xx: 97.6%</title></circle><circle class="c-warn" cx="21" cy="21" r="15.915" pathLength="100" stroke-dasharray="1.50 98.50" stroke-dashoffset="-97.60"><title>4xx: 2.1%</title></circle><circle class="c-down" cx="21" cy="21" r="15.915" pathLength="100" stroke-dasharray="0.10 100.30" stroke-dashoffset="-99.70"><title>5xx: 0.3%</title></circle></svg>
<div class="donut-c"><b>97.6%</b><span>2xx</span></div>
</div>
<div class="donut" style="--size:5.5rem" role="img" aria-label="Storage quota 71% used">
<svg viewBox="0 0 42 42" aria-hidden="true"><circle class="track" cx="21" cy="21" r="15.915"/><circle class="s1" cx="21" cy="21" r="15.915" pathLength="100" stroke-dasharray="70.40 29.60" stroke-dashoffset="0.00"><title>Used: 71.0%</title></circle><circle class="rest" cx="21" cy="21" r="15.915" pathLength="100" stroke-dasharray="28.40 71.60" stroke-dashoffset="-71.00"><title>Free: 29.0%</title></circle></svg>
<div class="donut-c"><b>71%</b><span>quota</span></div>
</div>
<div class="donut pie" style="--size:4rem" role="img" aria-label="Spend split, pie">
<svg viewBox="0 0 42 42" aria-hidden="true"><circle class="track" cx="21" cy="21" r="15.915"/><circle class="s1" cx="21" cy="21" r="15.915" pathLength="100" stroke-dasharray="69.39 30.61" stroke-dashoffset="0.00"><title>Compute: 70.0%</title></circle><circle class="s2" cx="21" cy="21" r="15.915" pathLength="100" stroke-dasharray="11.37 88.63" stroke-dashoffset="-69.99"><title>Storage: 12.0%</title></circle><circle class="s3" cx="21" cy="21" r="15.915" pathLength="100" stroke-dasharray="9.46 90.54" stroke-dashoffset="-81.96"><title>Network: 10.1%</title></circle><circle class="s4" cx="21" cy="21" r="15.915" pathLength="100" stroke-dasharray="7.38 92.62" stroke-dashoffset="-92.02"><title>Managed DB: 8.0%</title></circle></svg>
</div>Status codes take status colours, because there the colour is the meaning. One value against a capacity is better as a ring; here the free share is a .rest segment with no stroke, so the track shows through. A pie (.pie) suits a 4rem thumbnail with no label; at larger sizes the hole is more useful than the extra ink.
| Class | On | What it does |
|---|---|---|
.donut | div | The square frame. --size (9rem), --sw the ring width (5 of 42). Modifiers: thin, pie. |
circle | svg | r="15.915" pathLength="100" on a 42 viewBox. stroke-dasharray="share 100-share", stroke-dashoffset="-start". Colour from a series or status class. .track for the empty ring, .rest for a remainder that shows the track. |
.donut-c | div | The centre: a value in b, a label in span. |
.donut-row | div | Donut and a .ch-legend.col beside it, wrapping on narrow screens. small in a legend item is the share. |
donut() | src/lib/viz.ts | Builds the SVG from name and value pairs. |
- The total in the middle.
- Largest segment first, clockwise from twelve o'clock, "other" last in the neutral.
- Values and shares in the legend.
- More than five segments.
- Two donuts side by side to compare: use bars.
- Labels around the ring with leader lines.
Accessibility
role="img"on the frame, with every share in the label. The legend repeats the numbers as text.