GrChartPie
Overview
When to take it
- what one whole consists of — the structure of expenses, the shares of channels, the distribution of statuses: a circle shows a part of a whole and nothing more;
- there are few shares — three to six; beyond that the segments become thinner than their labels, and they can no longer be compared by eye;
- a total is needed in the centre —
variant: 'donut'frees the middle for the sum or a key number (totalLabel, the#centerslot); - the small stuff has to be taken off the picture —
labelMinSharetakes the labels of shares below the threshold away, leaving them in the legend and in the data table.
When to take something else
| Need | Take |
|---|---|
| Compare quantities with one another | GrChartBar |
| Show how the composition changed over time | GrChartArea with a stack |
| Show the share of a single metric | GrProgressCircle |
| Compare the shape of a profile across several axes | GrChartRadar |
People cannot compare shares between two circles: an angle reads worse than a length, and two circles are of different sizes on top of that. Two compositions side by side are columns normalised to a hundred per cent.
Examples 4 each with a live demo and its code
What the storage is spent on
The bar answers «how full», the donut answers «with what». The chart’s own legend is off, because the list on the right is the legend:
v-model:active-indexties a slice and its row, so hovering either lights both.total-labelsigns the sum in the hole.Storage412 GB of 500 GBChart data Category Value Share Attachments 186 45% Backups 124 30% Exports 58 14% Audit log 44 11% - AttachmentsFiles on invoices and receipts186 GB
- BackupsNightly, kept for 30 days124 GB
- ExportsCSV and PDF reports58 GB
- Audit logKept for 2 years44 GB
Statuses in the colours of their meaning
A slice
coloroverrides the palette with a theme role: green, amber and red say «on time, late, failed» before the legend is read.labels="share"writes the percentages, and the#centerslot puts what matters into the hole — «92% on time» instead of the sum — at thecx,cyand font sizes it is given.Deliveries, last week92% arrived on time - On time1,842 · 92%
- Late131 · 7%
- Failed attempt27 · 1%
Chart data Category Value Share On time 1,842 92% Late 131 7% Failed attempt 27 1% A slice that filters the queue
Three parts is where a pie still reads.
labels="value"writes the counts — «17 by phone» is an answer, «18%» is a hint — andslice-clicknarrows the list of tickets under the chart to that channel.Open tickets by channel - Email48 · 50%
- Chat31 · 32%
- Phone17 · 18%
Chart data Category Value Share Email 48 50% Chat 31 32% Phone 17 18% - T-2291Invoice shows the old VAT number
- T-2294Cannot add a second admin
- T-2297Card declined on renewal
A long tail folded into «Other»
Ten categories would be slivers nobody compares, so the page folds the small ones into «Other» and puts it last — the chart never sorts or groups, because only the page knows where «Other» belongs.
label-min-sharekeeps thin slices unlabelled on the picture, and thetooltipslot opens «Other» up.Expenses, September€278.0k in total - Salaries182 · 65%
- Cloud41 · 15%
- Office23 · 8%
- Marketing19 · 7%
- Other13 · 5%
Chart data Category Value Share Salaries 182 65% Cloud 41 15% Office 23 8% Marketing 19 7% Other 13 5%
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
height | number | undefined | undefined | The canvas height in pixels; 256 by default. The data, the loading state and the empty state all take up the same height. |
width | number | undefined | 640 | Declared width: the first render starts from it, after that the width is measured. |
valueFormat | GrChartNumberFormat | undefined | undefined | The value format in the tooltip, the hidden table and announcements: decimal places and separators. |
showLegend | boolean | undefined | undefined | A legend of the slices with values and percentages; `true` by default. |
legendPosition | "bottom" | "top" | undefined | undefined | Where the legend sits: `bottom` (the default) or `top`. |
tooltip | boolean | undefined | undefined | The panel with the values under the cursor; `true` by default. `false` hides only the panel: the cursor, the active marks, `update:activeIndex` and `pointHover` keep working while the chart is interactive. |
activeIndexv-model | number | null | undefined | undefined | The cursor — `v-model:activeIndex`. |
loading | boolean | undefined | false | Loading: a skeleton of the same height stands in place of the plot area, and the space for the axes is already reserved. |
empty | boolean | undefined | undefined | The empty state, set explicitly. Unset — the chart is empty when there is nothing to draw. |
emptyText | string | undefined | undefined | The placeholder text of an empty chart. Unset — "No data" from the package translations. |
dataTable | "hidden" | "visible" | "off" | undefined | undefined | The data table: `hidden` (the default) — for the screen reader only, `visible` — under the chart, `off` — no table. |
interactive | boolean | undefined | true | `false` — the chart becomes a picture: no focus, no tooltip, no keyboard. |
size | "md" | "xs" | "sm" | "lg" | undefined | undefined | The type size of the slice labels and the legend on the core control scale; by default — the size from `GrConfigProvider`. |
locale | string | undefined | undefined | The BCP-47 locale for numbers and dates. Unset — the language of the application translations, otherwise `en`. |
ariaLabel | string | undefined | undefined | The accessible name of the chart. Unset — a summary the component assembles: the chart type and the amount of data. |
ariaDescription | string | undefined | undefined | A description for the screen reader — the conclusion the chart is shown for; it adds to the summary rather than replacing it. |
dataTableMaxRows | number | "auto" | undefined | undefined | The row cap of the hidden data table. `'auto'` (the default) — as many rows as can be read; above the cap the table takes an even sample and says so with a note, while point-by-point completeness is left to the keyboard. A number sets its own cap, `Infinity` lifts it entirely, `dataTable: 'off'` removes the table altogether — which of these is needed is up to the application. |
labels | "none" | "value" | "share" | undefined | undefined | Labels outside the ring. `share` — percentages, `value` — values. |
variant | "pie" | "donut" | undefined | undefined | The shape: `pie` (the default) — a circle, `donut` — a ring with the total in the middle (the `#center` slot). |
donutRatio | number | undefined | undefined | The radius of the donut hole as a share of the outer radius. |
startAngle | number | undefined | 0 | The angle of the first slice in degrees from twelve o'clock, clockwise. |
labelMinShare | number | undefined | LABEL_MIN_SHARE | The smallest share that gets a label: at three percent the label would overlap its neighbour. |
totalLabel | string | undefined | undefined | The caption under the total in the middle of the donut. |
datarequired | readonly (number | null)[] | readonly GrChartPieSlice[] | — | Slices or a bare array of numbers — then the ordinal index becomes the label. |
Slots
| Slot | Type | Description |
|---|---|---|
center |
| The middle of the donut. The content is SVG in canvas coordinates: position it from `cx`/`cy` and keep it within `innerRadius`. |
tooltip |
| Custom content for the tooltip panel instead of the standard one; the data of the active position comes in the slot props. |
legend | { slices: readonly PieEntry[]; formatValue: (value: number | null) => string; } | A custom legend instead of the standard one. It is not drawn for an empty chart. |
empty | any | A custom placeholder for an empty chart instead of "No data"; it takes up the same `height`. |
header | any | A header above the chart, inside its root — above a legend placed on top. |
Events
| Event | Signature | Description |
|---|---|---|
update:activeIndex | (event: "update:activeIndex", value: number | null): void | — |
sliceClick | (event: "sliceClick", value: GrChartPieActiveSlice): void | — |
sliceHover | (event: "sliceHover", value: GrChartPieActiveSlice | null): void | — |
Methods
| Methods | Signature | Description |
|---|---|---|
element | HTMLElement | null | The root element — for measuring and scrolling in the consumer. |
Types
Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.
import type { GrChartPieConfigurableProps, GrChartSize, GrChartPieTexture } from '@feugene/granularity-charts/components/GrChartPie'Filedist/types/components/GrChartPie/config.d.ts
const grChartPieConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrChartPie">;Filedist/types/components/GrChartPie/defaults.d.ts
interface GrChartPieConfigurableProps {
size: GrChartSize;
height: number;
variant: 'pie' | 'donut';
donutRatio: number;
labels: 'none' | 'share' | 'value';
showLegend: boolean;
legendPosition: 'top' | 'bottom';
tooltip: boolean;
dataTable: 'hidden' | 'visible' | 'off';
dataTableMaxRows: number | 'auto';
}Filedist/types/components/GrChartFrame/chartFrameStyles.d.ts
type GrChartSize = GrComponentSize;Filedist/types/components/GrChartPie/grChartPieStyles.d.ts
const GR_CHART_PIE_TEXTURES: readonly ["solid", "hatch", "hatch-reverse", "grid"];type GrChartPieTexture = typeof GR_CHART_PIE_TEXTURES[number];function pieTextureFor(index: number): GrChartPieTexture;function pieTexturePaths(texture: GrChartPieTexture, tile?: number): string[];Filedist/types/components/GrChartPie/safelist.d.ts
const grChartPieSafelist: string[];Filedist/types/components/GrChartPie/GrChartPie.vue.d.ts
interface GrChartPieSlice {
label: string;
value: number | null;
color?: string;
meta?: unknown;
}interface GrChartPieActiveSlice {
index: number;
label: string;
value: number | null;
share: number | null;
color: string;
}interface GrChartPieProps {
data: readonly GrChartPieSlice[] | readonly (number | null)[];
variant?: 'pie' | 'donut';
donutRatio?: number;
height?: number;
width?: number;
startAngle?: number;
labels?: 'none' | 'share' | 'value';
labelMinShare?: number;
showLegend?: boolean;
legendPosition?: 'top' | 'bottom';
tooltip?: boolean;
activeIndex?: number | null;
loading?: boolean;
empty?: boolean;
emptyText?: string;
dataTable?: 'hidden' | 'visible' | 'off';
interactive?: boolean;
size?: GrChartSize;
valueFormat?: GrChartNumberFormat;
locale?: string;
totalLabel?: string;
ariaLabel?: string;
ariaDescription?: string;
dataTableMaxRows?: number | 'auto';
}interface GrChartPieEmits {
(e: 'update:activeIndex', value: number | null): void;
(e: 'sliceClick', value: GrChartPieActiveSlice): void;
(e: 'sliceHover', value: GrChartPieActiveSlice | null): void;
}Accessibility
APG pattern
Смены серии нет — серия одна. Активная доля вырастает наружу
Full contract of the package: docs/keyboard.md ↗
Usage from the library prose
A custom number in the middle of the donut
By default the hole holds the total — the sum of the shares by valueFormat and the totalLabel
caption. The #center slot replaces it with its own content. That content is drawn inside the
canvas, so it is SVG rather than HTML, and it is placed from the coordinates the slot passes:
cx/cy — the centre of the donut, innerRadius — the radius of the hole, valueFont/labelFont
— the default font sizes for the content, chosen from the size of the hole.
<GrChartPie :data="deliveries" variant="donut">
<template #center="{ cx, cy, valueFont, labelFont }">
<text :x="cx" :y="cy - labelFont / 2" text-anchor="middle" dominant-baseline="middle" :font-size="valueFont" font-weight="600">
92%
</text>
<text :x="cx" :y="cy + valueFont * 0.55" text-anchor="middle" dominant-baseline="middle" :font-size="labelFont">
on time
</text>
</template>
</GrChartPie>Content wider than the diameter of the hole (2 × innerRadius) will spill onto the ring. The total
reaches the slot too — total as a number and formattedTotal as a string by valueFormat.
Props in this example