Skip to content

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 · typeDefaultDescription
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= truetrueThe legend beside the ring, each part with its value and share.
Look
heightnumber= 160160The diameter of the ring in pixels.
Form and access
ariaLabel*stringWhat 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 needTake
The parts period by periodBar chart with stack
One share of a wholeProgress
A flow over timeArea chart
The card with its title and data statesChart card around it