GrChartWaterfall

  • charts
  • charts
  • API · 25 props
  • ⌨ keys
import { GrChartWaterfall } from '@feugene/granularity-charts/components/GrChartWaterfall'

Overview

When to take it

  • movement from balance to balance — subscriptions, liabilities, a warehouse: not only every change is visible but also how the end of the month came out of its beginning;
  • checking whether the balance adds up — kind: 'total' steps put the real values at the start and at the end, and a discrepancy with the sum of the movements becomes visible to the eye rather than in one’s head;
  • breaking down the delta between two numbers — “revenue grew by 12 %” and the four reasons for that growth read as one story;
  • increases and decreases mixed together — the colour follows the sign, and the direction of every step is visible before the labels are read;
  • there are few steps — up to fifteen or twenty; beyond that the labels of the axis run into one another, and the bridge is worth folding down to large items.

When to take something else

NeedTake
Compare quantities across categories, with no successionGrChartBar
Show the composition of one whole at one momentGrChartPie
Show the course of a quantity over timeGrChartLine
Show where users are lost between stagesGrChartFunnel
Show one number with a trendGrStatistic

Examples 4 each with a live demo and its code

  • How September's MRR was made

    The opening MRR is a total, drawn from zero; new business, expansion, contraction and churn accumulate on it, green up and red down. show-total closes the bridge with a named column. The tooltip slot gets each step with the running total before and after it, and step-click lists the accounts behind a movement.

    MRR bridge · SeptemberFrom €184.0k to €205.6k · click a step to see its accounts
    Chart data
    StepChangeRunning total beforeRunning total after
    Start of Sep184.00.0184.0
    New22.4184.0206.4
    Expansion9.1206.4215.5
    Reactivation1.1215.5216.6
    Contraction-3.2216.6213.4
    Churn-7.8213.4205.6
    End of Sep205.6205.6205.6
    Click a movement to list the accounts behind it.
  • A stock check that does not add up

    Both ends are real numbers — the opening balance and the physical count — so both are total steps, and when the movements do not add up the last bar stops short of where they end: 32 lamps are missing, and the picture says so. A zero step is drawn as a mark, and orientation="horizontal" gives each label its own line.

    Desk lamp, black · stock check32 units are not accounted for
    Chart data
    StepChangeRunning total beforeRunning total after
    Opening stock1,20001,200
    Received4801,2001,680
    Shipped-9101,680770
    Returns36770806
    Written off-14806792
    Transfers0792792
    Physical count760792760
  • Where the growth came from

    «+€142k» and four reasons for it read as one story. Next to the €1.2m of Q2 a €19k currency effect would be a hairline, and an axis cut above zero would lie about the sizes — so the bridge explains the change itself, from zero, and show-total names where it lands.

    Revenue Q2 → Q3: +€142kVolume did most of it; mix and the dollar took some back
    Chart data
    StepChangeRunning total beforeRunning total after
    Price64064
    Volume11864182
    Mix-21182161
    FX-19161142
    Change142142142
  • An income statement with its subtotals

    Gross and operating profit are lines of the report, not deltas: kind: "total" draws them from zero and restarts the accumulation on them, so the reader never adds columns up to find a number that already has a name. show-connectors joins the movements and stops at every total.

    Income statement · Q3 2026Thousands of eurosNet margin 15.7%
    Chart data
    StepChangeRunning total beforeRunning total after
    Revenue1,24001,240
    Cost of sales-4101,240830
    Gross profit830830830
    Salaries-390830440
    Ads-120440320
    Rent-60320260
    Operating profit260260260
    Tax-65260195
    Net profit195195195

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.
yDomainreadonly [number | null, number | null] | undefinedundefinedThe bounds of the value axis `[min, max]`; `null` in place of a bound — it is taken from the data.
yTickCountnumber | undefined5The desired number of ticks on the value axis; 5 by default. The step stays "round", so there may be one tick fewer.
yTickFormat((value: number) => string) | undefinedundefinedThe tick label of the value axis, for example `value => value + '%'`. The space for the axis is measured from the formatted label.
valueFormatGrChartNumberFormat | undefinedundefinedThe value format in the tooltip, the hidden table and announcements: decimal places and separators.
showGrid"y" | "both" | "x" | "none" | undefinedundefinedWhich grid is needed. The axes are named after the data: in the horizontal layout they swap places by themselves.
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.

Slots

SlotTypeDescription
tooltip
{ active: GrChartActivePoint; step: GrChartWaterfallActiveSt{ active: GrChartActivePoint; step: GrChartWaterfallActiveStep | 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.
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—
stepClick(event: "stepClick", value: GrChartWaterfallActiveStep): void—
stepHover(event: "stepHover", value: GrChartWaterfallActiveStep | 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 { GrChartWaterfallConfigurableProps, GrChartSize, GrChartWaterfallActiveStep } from '@feugene/granularity-charts/components/GrChartWaterfall'

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

grChartWaterfallConfigconst
const grChartWaterfallConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrChartWaterfall">;

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

GrChartWaterfallConfigurablePropsinterface
interface GrChartWaterfallConfigurableProps {
    size: GrChartSize;
    height: number;
    barRadius: number;
    showConnectors: boolean;
    orientation: 'vertical' | 'horizontal';
    showGrid: 'both' | 'x' | 'y' | 'none';
    tooltip: boolean;
    dataTable: 'hidden' | 'visible' | 'off';
    dataTableMaxRows: number | 'auto';
}

Filedist/types/components/GrChartFrame/chartFrameStyles.d.ts

GrChartSizetype
type GrChartSize = GrComponentSize;

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

grChartWaterfallSafelistconst
const grChartWaterfallSafelist: string[];

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

GrChartWaterfallActiveStepinterface
interface GrChartWaterfallActiveStep {
    index: number;
    label: string;
    value: number;
    before: number;
    after: number;
    kind: 'delta' | 'total';
}
GrChartWaterfallPropsinterface
interface GrChartWaterfallProps {
    steps: readonly GrChartWaterfallStep[];
    baseline?: number;
    showTotal?: boolean | string;
    showConnectors?: boolean;
    barRadius?: number;
    orientation?: 'vertical' | 'horizontal';
    height?: number;
    width?: number;
    yDomain?: readonly [number | null, number | null];
    yTickCount?: number;
    yTickFormat?: (value: number) => string;
    valueFormat?: GrChartNumberFormat;
    showGrid?: 'both' | 'x' | 'y' | 'none';
    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';
}
export interface GrChartWaterfallEmits {
    (e: 'update:activeIndex', value: number | null): void;
    (e: 'stepClick', value: GrChartWaterfallActiveStep): void;
    (e: 'stepHover', value: GrChartWaterfallActiveStep | null): void;
}
type __VLS_Slots = {
    tooltip?: (props: {
        active: GrChartActivePoint;
        step: GrChartWaterfallActiveStep | null;
        formatValue: (value: number | null) => string;
GrChartWaterfallEmitsinterface
interface GrChartWaterfallEmits {
    (e: 'update:activeIndex', value: number | null): void;
    (e: 'stepClick', value: GrChartWaterfallActiveStep): void;
    (e: 'stepHover', value: GrChartWaterfallActiveStep | null): void;
}

Accessibility

APG pattern

При orientation: 'horizontal' шаги идут сверху вниз, и по ним ходят ↓/↑

Usage from the library prose

A total step declares the accumulation rather than adding to it

kind: 'delta' (the default) is a change from the current accumulation, and kind: 'total' an absolute value: the column is drawn from zero, and the accumulation is reset to it. That is what makes it possible to put “At the start” and “At the end” as real numbers from the backend: if the bridge does not add up, the last column will not match the top of the one before it, and the error in the data will be visible.

A connector to such a step is not drawn. It does not continue the accumulation but declares it, and a “from here to here” line would lie about the succession.

The colour follows the sign rather than the index of the series

A bridge is a single series, and the palette of series distinguishes nothing here: what has to be distinguished is an increase (--gr-success) from a decrease (--gr-danger). The total and the declared accumulation are coloured neutrally (--gr-chart-1) — that is not a movement. An explicit color on a step is stronger than any of those rules.

A zero step is drawn as a rule

“There was no movement” is a fact rather than an absence of data. A column of zero height would disappear from the drawing, and the reader would see a story in which such a step does not exist at all. Instead a rule of --gr-chart-waterfall-zero-step thickness stands at the level of the accumulation.

The table carries the accumulation rather than a single delta

The hidden table gives three numbers per step: the change, the accumulation before and the accumulation after. From a single delta the bridge cannot be reconstructed — the height of a column is set precisely by the accumulation, and a reader without sight is obliged to see the same as a sighted one sees from the position of the band.

The category of a step is its index rather than its label

Two “Adjustments” in a row in a bridge are an ordinary matter, and normalisation collapses identical categories inside a series. The index of the step therefore goes inside the frame, and the real labels arrive on the axis separately: otherwise there would be fewer positions than steps, and the cursor and the keyboard would travel over the wrong data.

The value axis follows the accumulation

The domain of the axis is computed from the bases and tops of the bars together with zero, rather than from the values of the steps: a delta of −410 on an accumulation of 1240 does not pull the axis below zero, where not a single bar stands. yDomain takes precedence: if its bottom is above zero, a bar that starts from zero is cut off at the edge of the area — the cut itself says that the axis does not start at zero — and connectors and zero steps outside the domain are not drawn.

Every step is named

Step labels are not thinned out the way bar categories are: a bridge cannot be read without the name of a step. A label that is short of room wraps by words onto two lines, and beyond that ends with an ellipsis, with the full name in <title>; neighbouring labels do not run into each other. If even after wrapping some label is left with fewer than four characters — “Pr…” instead of “Price” on a narrow canvas — the labels are staggered: neighbours go into different rows, and each gets the width of two steps. A truncated label is never shorter than four characters.

In the horizontal layout the names stand in a column on the left, up to 40% of the width, the value axis leaves room for the last tick label, and those of its labels that are short of room are thinned out to every other one — the first and the last stay.

Limits

The bridge does not compute what its steps consist of — they are given by the consumer. It does not group small steps into “Other” either: only the caller knows what counts as small. There are no nested (two-level) bridges.