GrChartPie

  • charts
  • charts
  • API · 24 props
  • ⌨ keys
import { GrChartPie } from '@feugene/granularity-charts/components/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 #center slot);
  • the small stuff has to be taken off the picture — labelMinShare takes the labels of shares below the threshold away, leaving them in the legend and in the data table.

When to take something else

NeedTake
Compare quantities with one anotherGrChartBar
Show how the composition changed over timeGrChartArea with a stack
Show the share of a single metricGrProgressCircle
Compare the shape of a profile across several axesGrChartRadar

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-index ties a slice and its row, so hovering either lights both. total-label signs the sum in the hole.

    Storage412 GB of 500 GB
    Chart data
    CategoryValueShare
    Attachments18645%
    Backups12430%
    Exports5814%
    Audit log4411%
    • 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 color overrides 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 #center slot puts what matters into the hole — «92% on time» instead of the sum — at the cx, cy and font sizes it is given.

    Deliveries, last week92% arrived on time
    • On time1,842 · 92%
    • Late131 · 7%
    • Failed attempt27 · 1%
    Chart data
    CategoryValueShare
    On time1,84292%
    Late1317%
    Failed attempt271%
  • 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 — and slice-click narrows the list of tickets under the chart to that channel.

    Open tickets by channel
    • Email48 · 50%
    • Chat31 · 32%
    • Phone17 · 18%
    Chart data
    CategoryValueShare
    Email4850%
    Chat3132%
    Phone1718%
    • 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-share keeps thin slices unlabelled on the picture, and the tooltip slot opens «Other» up.

    Expenses, September€278.0k in total
    • Salaries182 · 65%
    • Cloud41 · 15%
    • Office23 · 8%
    • Marketing19 · 7%
    • Other13 · 5%
    Chart data
    CategoryValueShare
    Salaries18265%
    Cloud4115%
    Office238%
    Marketing197%
    Other135%

API generated from the package · cannot drift

Props

PropTypedefaultDescription
heightnumber | undefinedundefinedThe canvas height in pixels; 256 by default. The data, the loading state and the empty state all take up the same height.
widthnumber | undefined640Declared width: the first render starts from it, after that the width is measured.
valueFormatGrChartNumberFormat | undefinedundefinedThe value format in the tooltip, the hidden table and announcements: decimal places and separators.
showLegendboolean | undefinedundefinedA legend of the slices with values and percentages; `true` by default.
legendPosition"bottom" | "top" | undefinedundefinedWhere the legend sits: `bottom` (the default) or `top`.
tooltipboolean | undefinedundefinedThe 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-modelnumber | null | undefinedundefinedThe cursor — `v-model:activeIndex`.
loadingboolean | undefinedfalseLoading: a skeleton of the same height stands in place of the plot area, and the space for the axes is already reserved.
emptyboolean | undefinedundefinedThe empty state, set explicitly. Unset — the chart is empty when there is nothing to draw.
emptyTextstring | undefinedundefinedThe placeholder text of an empty chart. Unset — "No data" from the package translations.
dataTable"hidden" | "visible" | "off" | undefinedundefinedThe data table: `hidden` (the default) — for the screen reader only, `visible` — under the chart, `off` — no table.
interactiveboolean | undefinedtrue`false` — the chart becomes a picture: no focus, no tooltip, no keyboard.
size"md" | "xs" | "sm" | "lg" | undefinedundefinedThe type size of the slice labels and the legend on the core control scale; by default — the size from `GrConfigProvider`.
localestring | undefinedundefinedThe BCP-47 locale for numbers and dates. Unset — the language of the application translations, otherwise `en`.

Slots

SlotTypeDescription
center
{ total: number; formattedTotal: string; cx: number; cy: num{ total: number; formattedTotal: string; cx: number; cy: number; innerRadius: number; valueFont: number; labelFont: number; }
The middle of the donut. The content is SVG in canvas coordinates: position it from `cx`/`cy` and keep it within `innerRadius`.
tooltip
{ active: GrChartActivePoint; slice: GrChartPieActiveSlice |{ active: GrChartActivePoint; slice: GrChartPieActiveSlice | null; formatValue: (value: number | null) => string; }
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.
emptyanyA custom placeholder for an empty chart instead of "No data"; it takes up the same `height`.
headeranyA header above the chart, inside its root — above a legend placed on top.

Events

EventSignatureDescription
update:activeIndex(event: "update:activeIndex", value: number | null): void—
sliceClick(event: "sliceClick", value: GrChartPieActiveSlice): void—
sliceHover(event: "sliceHover", value: GrChartPieActiveSlice | null): void—

Methods

MethodsSignatureDescription
elementHTMLElement | nullThe 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
import type { GrChartPieConfigurableProps, GrChartSize, GrChartPieTexture } from '@feugene/granularity-charts/components/GrChartPie'

Filedist/types/components/GrChartPie/config.d.ts

grChartPieConfigconst
const grChartPieConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrChartPie">;

Filedist/types/components/GrChartPie/defaults.d.ts

GrChartPieConfigurablePropsinterface
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

GrChartSizetype
type GrChartSize = GrComponentSize;

Filedist/types/components/GrChartPie/grChartPieStyles.d.ts

GR_CHART_PIE_TEXTURESconst
const GR_CHART_PIE_TEXTURES: readonly ["solid", "hatch", "hatch-reverse", "grid"];
GrChartPieTexturetype
type GrChartPieTexture = typeof GR_CHART_PIE_TEXTURES[number];
pieTextureForfunction
function pieTextureFor(index: number): GrChartPieTexture;
pieTexturePathsfunction
function pieTexturePaths(texture: GrChartPieTexture, tile?: number): string[];

Filedist/types/components/GrChartPie/safelist.d.ts

grChartPieSafelistconst
const grChartPieSafelist: string[];

Filedist/types/components/GrChartPie/GrChartPie.vue.d.ts

GrChartPieSliceinterface
interface GrChartPieSlice {
    label: string;
    value: number | null;
    color?: string;
    meta?: unknown;
}
GrChartPieActiveSliceinterface
interface GrChartPieActiveSlice {
    index: number;
    label: string;
    value: number | null;
    share: number | null;
    color: string;
}
GrChartPiePropsinterface
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';
}
GrChartPieEmitsinterface
interface GrChartPieEmits {
    (e: 'update:activeIndex', value: number | null): void;
    (e: 'sliceClick', value: GrChartPieActiveSlice): void;
    (e: 'sliceHover', value: GrChartPieActiveSlice | null): void;
}

Accessibility

APG pattern

Смены серии нет — серия одна. Активная доля вырастает наружу

Usage from the library prose

There are no negative shares

Negative values and gaps are discarded rather than taken by absolute value: “minus three” of a part of a circle does not exist, and turning it into a plus silently would mean drawing data that is not there. What was discarded is visible by the sourceIndex of a share — it stays the index of the input.

The order of the shares is as they arrived

The circle sorts nothing and groups nothing. The tail is gathered into “Other” by the consumer, because only they know that “Other” has to stand last rather than end up in the middle by size.

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.

vue
<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.