Morphing Pie Chart
Conic-gradient pie chart whose slices glide from one data set to the next; the legend recomputes the percentages on every change.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Data has 28 effects, including 1 free. Explore the category →
3 usage examples



How it works
The .mpie-pie disc is a six-colour conic-gradient whose boundaries are variables: --mpie-s1 to --mpie-s6, the running total of the slices. Declared with @property as <percentage>, they can be animated: when they change, the borders between slices rotate smoothly (1.2 s) instead of jumping.
The data is the legend itself: each <li class="mpie-slice" data-value="…"> is one slice (2 to 6). The script divides each value by the total, sets the cumulative boundaries on the disc and writes the rounded percentage next to the label; rounding is distributed so the displayed total is always 100%. Values can be raw counts (plots, points, kWh).
To switch to another data set, change the data-value attributes: one MutationObserver per instance re-runs the calculation. The .mpie-caption text under the chart ("State A") is free text; update it at the same time to name the new state.
The first time the chart scrolls into view (150 ms later), every boundary starts at 0% and glides to its value together: the disc, empty at first (--mpie-track colour), fills clockwise, with all slices growing at once and already in their final proportions.
Accessibility
- The legend carries the data: it is a real
<ul>named byaria-label("Breakdown"), each item reads "label, percentage". The disc isaria-hiddenas it adds nothing more for a screen reader. - Colour and text: each legend dot uses its slice colour; the written percentage avoids having to compare close hues (pink and red, for example).
- Reduced motion: under
prefers-reduced-motion: reduce, the slice transition is turned off, the disc goes straight to the new state and is full on first display. - Silent changes: the legend is not an
aria-liveregion. When users trigger the change themselves (a "Next season" button), focus stays on their button and they can reread the list.
Browser compatibility
conic-gradient, @property (for the glide), aspect-ratio, MutationObserver, IntersectionObserver. Measured in Chromium 151, Firefox 153 and WebKit 26.5: 150 ms after a change the first boundary sits between the old and new value in all three engines; two independent pies, 390 px, white background, no JS error.
Without @property the pie is correct but switches states without animation. Without JavaScript the disc stays empty and the legend shows the labels without percentages.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0246-root">
<div class="mpie-wrap">
<div class="mpie-pie" aria-hidden="true"></div>
<ul class="mpie-legend" aria-label="Breakdown">
<li class="mpie-slice" data-value="30">Part A</li>
<li class="mpie-slice" data-value="25">Part B</li>
<li class="mpie-slice" data-value="25">Part C</li>
<li class="mpie-slice" data-value="20">Part D</li>
</ul>
</div>
<p class="mpie-caption">State A</p>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
data-value on each .mpie-slice (HTML) |
30, 25, 25, 20 | Slice value (any unit, the script computes the percentages). Changing it makes the pie glide. |
| Number of slices (HTML) | 4 | From 2 to 6 .mpie-slice items; any beyond six are ignored. |
.mpie-caption (HTML) |
"State A" | Free text under the chart, to update along with the data; remove it if not needed. |
--mpie-c1 to --mpie-c6 (CSS) |
#8b5cf6, #06b6d4, #f43f5e, #f59e0b, #10b981, #ec4899 | Slice and legend dot colours, in list order. |
--mpie-track (CSS) |
rgba(255,255,255,.08) | Colour of the empty disc, visible before it fills. |
--mpie-size (CSS) |
150px | Disc diameter; it never exceeds the container width. |
--mpie-speed (CSS) |
1.2s | Duration of the glide between two states. |
--mpie-text, --mpie-accent (CSS) |
70% white, #a78bfa | Legend and state text colours. |
--mpie-bg, --mpie-min-height (CSS) |
#0a0a0f, 300px | Root background and minimum height. |
FAQ
root.querySelectorAll('.mpie-slice').forEach((li, i) => li.setAttribute('data-value', state[i])), then change the .mpie-caption text. Hook that function to your own button, a timer or a tab..mpie-pie: mask-image: radial-gradient(closest-side, transparent 55%, #000 56%) (plus its -webkit-mask-image form). The centre becomes transparent and your page background shows through.