GrChartFunnel
Overview
When to take it
- a sequence of stages where each is a subset of the previous one — registration, activation, payment: the funnel shows on which transition the most is lost;
- there are three stages or more — for two a pair of numbers is enough, and at three the numbers already stop answering;
- both the absolute quantity and the share matter — the width carries the value, and the label switches between the value and either of the two shares;
- the funnel may “widen” — growth between stages is sometimes legitimate (different cohorts) and sometimes an error in the data: the component draws it honestly and says so in the description.
When to take something else
| Need | Take |
|---|---|
| Show how the end of a period came out of its beginning | GrChartWaterfall |
| Compare the quantities of categories not related by nesting | GrChartBar |
| Show the composition of one whole | GrChartPie |
| Show a single conversion as a number | GrStatistic |
Examples 4 each with a live demo and its code
From pricing page to paying customer
labels="share-first"counts every stage from the first: «4.9% became paying» is the figure a funnel is usually built for. Thetooltipslot gets the stage with both shares — of the start and of the previous step — because a share without its denominator means nothing.stage-clickopens the accounts stuck at a stage.Trial funnel · SeptemberEach stage is a subset of the one before · click a stage Chart data Step Value Share of first Share of previous Visited pricing 12,400 100% — Started a trial 3,180 26% 26% Sent a first invoice 1,420 11% 45% Became paying 612 5% 43% Click a stage to list its accounts.
Where checkout loses the most
labels="share-prev"shows the loss at each step on its own, and the worst transition stands out — here between the address and the payment.shape="bar"drops the narrowing ribbon,gapseparates the steps, andsize="sm"fits a card.Checkout, last 7 daysBiggest loss: Shipping address → Payment, −38% Chart data Step Value Share of first Share of previous Cart 5,240 100% — Shipping address 4,310 82% 82% Payment 2,690 51% 62% Order confirmed 2,470 47% 92% A stage that grows, and a stage at zero
Invitations are counted per invitation, not per account, so the second stage is wider than the first — and the funnel draws it wider instead of straightening it: either the data is wrong or the stages come from different cohorts, and
aria-descriptionsays which. «Nobody upgraded yet» is a result, so a zero stage keeps a sliver and its label.Referral programmeInvitations outnumber the accounts that sent them Chart data Step Value Share of first Share of previous Accounts with referral link 940 100% — Invitations sent 1,360 145% 145% Invitees signed up 410 44% 30% Upgraded to Enterprise 0 0% 0% A sales pipeline, left to right
orientation="horizontal"follows the path of a deal across the board.labels="value"leaves the counts — with four stages the shares are read from the shape — andstage-hoverfeeds the line above the chart.Q4 pipelineHover a stageChart data Step Value Share of first Share of previous Leads 420 100% — Qualified 186 44% 44% Proposal sent 74 18% 40% Won 31 7% 42%
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 goes 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. |
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 | 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 stage names and the outside labels 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 ceiling of the hidden data table. `'auto'` (the default) — as many rows as can be read; above the ceiling the table goes by an even sample and says so with a note, while point-by-point completeness stays with the keyboard. A number sets its own ceiling, `Infinity` removes it altogether, `dataTable: 'off'` removes the table entirely — which of these is needed is up to the application. |
orientation | "vertical" | "horizontal" | undefined | undefined | The direction: `vertical` (the default) — stages top to bottom, names on the left; `horizontal` — left to right, names under the stages. |
labels | "none" | "value" | "share-first" | "share-prev" | undefined | undefined | The number by a stage: the value (the default), or the share of the first stage or of the previous one. If it does not fit inside, it sits next to the stage. `'none'` removes the numbers; the stage names stay. |
shape | "trapezoid" | "bar" | undefined | undefined | A tapering ribbon or rectangles of one width. |
gap | number | undefined | undefined | The gap between steps in pixels. |
stagesrequired | readonly GrChartFunnelStage[] | — | The stages in order: `{ label, value, color? }`. The width of a stage is proportional to its value, not to its position. |
Slots
| Slot | Type | Description |
|---|---|---|
tooltip | { active: GrChartActivePoint; stage: GrChartFunnelActiveStage | null; } | Custom content for the tooltip panel instead of the standard one; the data of the active position comes in the slot props. |
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 | — |
stageClick | (event: "stageClick", value: GrChartFunnelActiveStage): void | — |
stageHover | (event: "stageHover", value: GrChartFunnelActiveStage | null): void | — |
Methods
| Methods | Signature | Description |
|---|---|---|
element | HTMLElement | null | The root element — for measurements 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 { GrChartFunnelConfigurableProps, GrChartSize, GrChartFunnelActiveStage } from '@feugene/granularity-charts/components/GrChartFunnel'Filedist/types/components/GrChartFunnel/config.d.ts
const grChartFunnelConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrChartFunnel">;Filedist/types/components/GrChartFunnel/defaults.d.ts
interface GrChartFunnelConfigurableProps {
size: GrChartSize;
height: number;
orientation: 'vertical' | 'horizontal';
shape: 'trapezoid' | 'bar';
labels: 'value' | 'share-first' | 'share-prev' | 'none';
gap: number;
tooltip: boolean;
dataTable: 'hidden' | 'visible' | 'off';
dataTableMaxRows: number | 'auto';
}Filedist/types/components/GrChartFrame/chartFrameStyles.d.ts
type GrChartSize = GrComponentSize;Filedist/types/components/GrChartFunnel/safelist.d.ts
const grChartFunnelSafelist: string[];Filedist/types/components/GrChartFunnel/GrChartFunnel.vue.d.ts
interface GrChartFunnelActiveStage {
index: number;
label: string;
value: number;
shareFirst: number | null;
sharePrev: number | null;
}interface GrChartFunnelProps {
stages: readonly GrChartFunnelStage[];
labels?: 'value' | 'share-first' | 'share-prev' | 'none';
orientation?: 'vertical' | 'horizontal';
shape?: 'trapezoid' | 'bar';
gap?: number;
height?: number;
width?: number;
valueFormat?: GrChartNumberFormat;
tooltip?: boolean;
activeIndex?: number | null;
loading?: boolean;
empty?: boolean;
emptyText?: string;
dataTable?: 'hidden' | 'visible' | 'off';
interactive?: boolean;
size?: GrChartSize;
locale?: string;
ariaLabel?: string;
ariaDescription?: string;
dataTableMaxRows?: number | 'auto';
}interface GrChartFunnelEmits {
(e: 'update:activeIndex', value: number | null): void;
(e: 'stageClick', value: GrChartFunnelActiveStage): void;
(e: 'stageHover', value: GrChartFunnelActiveStage | null): void;
}Usage from the library prose
A name and a number at every stage
Stage names are always drawn: without them it is impossible to tell which bar means what, and the
tooltip and the hidden table do not replace that. In the vertical layout the names stand in a column
on the left (up to 40% of the chart width, no narrower than 96px), in the horizontal one — in a row
under the stages; anything longer gets an ellipsis and the full name in <title>.
The value label stands inside the stage if it fits, and otherwise beside it: to the right of the
stage’s middle in the vertical layout, above it in the horizontal one. A narrow stage is exactly
where the funnel loses the most, so the label there never disappears. labels: 'none' removes only
the numbers; the names stay.
The width is proportional to the value rather than to the order
The decrease is drawn because it is in the data. A stage that is larger than the previous one is not
straightened out: it is drawn wider and enters ariaDescription as a separate phrase. Quietly
“fixing” such a chart would mean hiding either an error in the data or the fact that the stages were
counted over different cohorts.
A zero stage is visible
“Nobody got here” is a result rather than the absence of a stage. The stage keeps a minimum width and a “0” label beside it, otherwise the funnel would look shorter than it is.
The ribbon and the bars give the same numbers
shape: 'trapezoid' narrows towards the next stage, and shape: 'bar' draws rectangles — the
values, the shares and the content of the table match to the digit in the process. The shape here is
a matter of taste rather than of meaning.
The keyboard walks the stages with both pairs of arrows
The funnel stands as a single column, so ↑↓ mean exactly the same as ←→. A stage is announced as
a whole: the value and both shares.
Limits
The component does not compute the funnel from events — the values are given by the consumer. There is no branching (several paths out of one stage), and no comparison of two funnels side by side either.
Props in this example