Donut chart
Compare the parts of a total in a ring chart, with the total in the center and each part available from the keyboard.
Where the money went
September, household spending
Loading the chart…
<script setup lang="ts">import { onBeforeUnmount, ref, watch } from 'vue'import type { DonutPart } from 'grani'import { UiChartCard, UiDonutChart, UiRadioGroup, UiRadio, useUiT } from 'grani' const { n } = useUiT() const eur = (v: number) => n(v, { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 }) const SPENDING: DonutPart[] = [ { key: 'rent', label: 'Rent', value: 1250 }, { key: 'groceries', label: 'Groceries', value: 610 }, { key: 'transport', label: 'Transport', value: 240 }, { key: 'eating', label: 'Eating out', value: 185 }, { key: 'subscriptions', label: 'Subscriptions', value: 64 },] const state = ref('loading') // Retry loads for a second and a half and comes back with the chart; a state picked meanwhile stands.let timer: ReturnType<typeof setTimeout> | undefined function retry() { state.value = 'loading' clearTimeout(timer) timer = setTimeout(() => (state.value = 'loaded'), 1500)} watch(state, (s) => { if (s !== 'loading') clearTimeout(timer)}) onBeforeUnmount(() => clearTimeout(timer))</script> <template> <div class="flex w-full flex-col items-center gap-4"> <UiChartCard title="Where the money went" description="September, household spending" :loading="state === 'loading' || state === 'refreshing'" :empty="state === 'loading' || state === 'empty' || state === 'failed'" :error="state === 'failed' ? 'The server did not answer. Try again.' : null" bleed="sides" class="w-full max-w-md" @retry="retry" > <UiDonutChart :parts="SPENDING" :format="eur" total-label="Spent" ariaLabel="Spending by category, September" /> <template #footer>Rent takes half, as it does every month.</template> </UiChartCard> <UiRadioGroup v-model="state" variant="segmented" aria-label="State of the card"> <UiRadio value="loading">Loading</UiRadio> <UiRadio value="loaded">Loaded</UiRadio> <UiRadio value="refreshing">Updating</UiRadio> <UiRadio value="empty">No data</UiRadio> <UiRadio value="failed">Failed</UiRadio> </UiRadioGroup> </div></template>
Also called pie chart, ring chart, share chart.
Usage
<UiDonutChart :parts="parts" :aria-label="ariaLabel" /> With GraniResolver in the Vite config, a template needs no import: set it up once.
API
| Name · type | Default | Description |
|---|---|---|
| Data | ||
| parts*DonutPart[] | The parts, in the order they go round the ring and take the chart colors: the caller sorts. Past five, the fifth is "Other", the sum of the rest. | |
| Behavior | ||
| totalLabelstring= "" | "" | What the total in the middle counts: "Visitors"; "Total" when unset. |
| formatChartFormat= "number" | "number" | How values read in the middle, the legend and the table; money writes the currency code after the digits, 1,234.00 USD. |
| legendboolean= true | true | The legend beside the ring, each part with its value and share. |
| Look | ||
| heightnumber= 160 | 160 | The diameter of the ring in pixels. |
| Form and access | ||
| ariaLabel*string | What the chart shows, for a screen reader: "Spending by category, September". | |
Anything else lands on the root element: native attributes, aria-*, data-testid.
When to use another component
| When you need | Take |
|---|---|
| The parts period by period | Bar chart with stack |
| One share of a whole | Progress |
| A flow over time | Area chart |
| The card with its title and data states | Chart card around it |