motion-pie
Pie and donut charts for parts of a whole. The pie sweeps in when it scrolls into view, the slices spring to new sizes when the data changes, and the hovered slice springs outward. The legend shows each share.
Preview
Hover a slice, or press New data.
<motion-pie
values="42, 28, 18, 12"
labels="Direct, Search, Social, Email"
label="Traffic sources"
></motion-pie>
<motion-pie
donut
values="1450, 620, 380, 240"
labels="Rent, Food, Transport, Fun"
label="Monthly budget"
format="currency:EUR"
></motion-pie>
<button id="new-data">New data</button>Set values from JavaScript to change the data. The slices spring to their new sizes:
const pie = document.querySelector('motion-pie')
const datasets = ['42, 28, 18, 12', '25, 40, 20, 15', '30, 15, 35, 20']
let step = 0
document.querySelector('#new-data').addEventListener('click', () => {
step = (step + 1) % datasets.length
pie.values = datasets[step]
})Quick start
Save this as index.html and open it in a browser. No install, no build step.
<!doctype html>
<html>
<head>
<script type="module" src="https://cdn.jsdelivr.net/npm/motion-components@1/dist/motion-pie.js/+esm"></script>
</head>
<body>
<motion-pie
values="42, 28, 18, 12"
labels="Direct, Search, Social, Email"
label="Traffic sources"
></motion-pie>
</body>
</html>In a project with a bundler, install the package and import the component once:
npm install motion-componentsimport 'motion-components/motion-pie'When to use a pie
A pie shows how a whole splits into a few parts. Use it for six slices or fewer. When there are more, the extra slices are combined into one slice called "Other". To compare values that are close together, use a bar chart: lengths are easier to compare than angles.
Donut
Add donut to cut out the center. The center shows the total, with total-label as its caption. When you hover a slice, the
center shows that slice instead of a tooltip.
<motion-pie
donut
values="1450, 620, 380, 240"
labels="Rent, Food, Transport, Fun"
total-label="Per month"
format="currency:EUR"
></motion-pie>Give the chart its data
1. Numbers in attributes
One number per slice in values, and one name per slice in labels.
<motion-pie values="42, 28, 18, 12" labels="Direct, Search, Social, Email"></motion-pie>2. A table
Wrap a <table> with the labels in the first column and the values in the second. Screen readers still read the table, and the pie updates when a cell changes. Each value is the first number in its cell, and a cell's data-value overrides its text; see how cells are read.
<motion-pie label="Traffic sources">
<table>
<tr><th>Source</th><th>Visits</th></tr>
<tr><td>Direct</td><td>4200</td></tr>
<tr><td>Search</td><td>2800</td></tr>
<tr><td>Social</td><td>1800</td></tr>
<tr><td>Email</td><td>1200</td></tr>
</table>
</motion-pie>3. From JavaScript
document.querySelector('motion-pie').data = {
labels: ['Direct', 'Search', 'Social', 'Email'],
values: [4200, 2800, 1800, 1200],
}More patterns, such as a period switcher, a chart from a form and data from a JSON file, are on the Chart Recipes page.
Frameworks
Write the data into attributes, or wrap a table.
<script type="module" src="https://cdn.jsdelivr.net/npm/motion-components@1/dist/motion-pie.js/+esm"></script>
<motion-pie values="42, 28, 18, 12" labels="Direct, Search, Social, Email"></motion-pie>React 19 sets data as a property, so pass the object from state. The slices spring to every new value.
import 'motion-components/motion-pie'
export function BudgetChart({ categories, amounts }) {
return (
<motion-pie
donut
format="currency:EUR"
data={{ labels: categories, values: amounts }}
></motion-pie>
)
}Tell Vue that motion-* tags are custom elements (see Getting started), then bind :data.
<script setup>
import 'motion-components/motion-pie'
const props = defineProps(['categories', 'amounts'])
</script>
<template>
<motion-pie
donut
format="currency:EUR"
:data="{ labels: props.categories, values: props.amounts }"
></motion-pie>
</template>Colors and size
Slices use the same color-blind-safe palette as motion-chart, in light and dark mode. Override it with --mc-chart-1 to
--mc-chart-6.
<motion-pie
values="60, 40"
labels="Done, Open"
style="--mc-chart-1: #16a34a; --mc-chart-2: #d4d4d8; --mc-chart-height: 12rem"
></motion-pie>Properties
| Attribute | Property | Type | Default | Description |
|---|---|---|---|---|
| | | string | '' | Comma-separated numbers, one per slice |
| | | string | '' | Comma-separated slice labels |
| | | object | null | Property only: |
| | | boolean | false | Cuts out the center and shows the total there |
| | | string | 'Total' | Caption under the total in a donut |
| | | string | '' | |
| | | string | '' | Accessible name of the chart |
| | | string | 'view' | When the sweep-in runs: |
| | | number | 0.8 | Spring duration in seconds |
| | | number | 0.25 | Spring bounciness when the values change |
| | | boolean | true | Set |
| | | boolean | true | Set |
CSS custom properties
| Property | Type | Default | Description |
|---|---|---|---|
| | length | 16rem | Height of the chart, including the legend |
| | color | blue, orange, aqua, yellow, magenta, green | Slice colors, in order |
| | color | Canvas | Background behind the chart. Used for the gap between slices and the tooltip |
JavaScript API
replay() sweeps the pie in again. It also has play(), pause(), finish(), cancel(),
playState and the motion-start and motion-finish events. See the JavaScript API.
Accessibility
Screen readers get the data as a table with each value and share. Give the chart a label. Press Tab to focus it, then the
arrow keys to move between slices, Home and End for the first and last, and Esc to clear. Each slice is announced
with its value and share. With prefers-reduced-motion, the pie shows at once and changes without animation.