GrChartWaterfall
Берут, когда движение от остатка к остатку.
Обзор
Когда брать
- движение от остатка к остатку — подписки, обязательства, склад: видно не только каждое изменение, но и то, как из начала месяца получился конец;
- проверить, сходится ли баланс — шаги
kind: 'total'ставят реальные значения на начало и на конец, и расхождение с суммой движений становится видно глазом, а не в уме; - разложить дельту между двумя числами — «выручка выросла на 12 %» и четыре причины этого роста читаются как одна история;
- прибавления и убавления вперемешку — цвет идёт по знаку, и направление каждого шага видно до чтения подписей;
- шагов немного — до полутора-двух десятков; дальше подписи оси налезают друг на друга, и мост стоит свернуть до крупных статей.
Когда взять другое
| Нужно | Берите |
|---|---|
| Сравнить величины по категориям, без преемственности | GrChartBar |
| Показать состав одного целого на один момент | GrChartPie |
| Показать ход величины во времени | GrChartLine |
| Показать, где теряются пользователи между ступенями | GrChartFunnel |
| Показать одно число с динамикой | GrStatistic |
Примеры 4 у каждого — живое демо и его код
Из чего сложился MRR сентября
MRR на начало —
total, он рисуется от нуля; новые клиенты, расширения, сокращения и отток накапливаются на нём, вверх зелёным, вниз красным.show-totalзакрывает мост именованным столбцом. Слотtooltipполучает каждый шаг с накоплением до и после, аstep-clickпоказывает аккаунты за движением.MRR bridge · SeptemberFrom €184.0k to €205.6k · click a step to see its accounts Данные графика Шаг Изменение Накоплено до Накоплено после Start of Sep 184,0 0,0 184,0 New 22,4 184,0 206,4 Expansion 9,1 206,4 215,5 Reactivation 1,1 215,5 216,6 Contraction -3,2 216,6 213,4 Churn -7,8 213,4 205,6 End of Sep 205,6 205,6 205,6 Click a movement to list the accounts behind it.Сверка склада, которая не сходится
Оба конца — настоящие числа: остаток на начало и пересчёт, поэтому оба — шаги
total, и если движения не сходятся, последний столбец обрывается раньше, чем они кончаются: 32 лампы пропали, и рисунок это говорит. Нулевой шаг рисуется чертой, аorientation="horizontal"даёт каждой подписи свою строку.Desk lamp, black · stock check32 units are not accounted for Данные графика Шаг Изменение Накоплено до Накоплено после Opening stock 1 200 0 1 200 Received 480 1 200 1 680 Shipped -910 1 680 770 Returns 36 770 806 Written off -14 806 792 Transfers 0 792 792 Physical count 760 792 760 Откуда взялся рост
«+€142k» и четыре его причины читаются одной историей. Рядом с €1,2m второго квартала валютный эффект в €19k был бы волоском, а ось, обрезанная выше нуля, соврала бы о величинах, — поэтому мост объясняет само изменение, от нуля, а
show-totalназывает, куда оно пришло.Revenue Q2 → Q3: +€142kVolume did most of it; mix and the dollar took some back Данные графика Шаг Изменение Накоплено до Накоплено после Price 64 0 64 Volume 118 64 182 Mix -21 182 161 FX -19 161 142 Change 142 142 142 Отчёт о прибылях с промежуточными итогами
Валовая и операционная прибыль — строки отчёта, а не дельты:
kind: "total"рисует их от нуля и начинает накопление заново, так что читателю не приходится складывать столбцы ради числа, у которого уже есть имя.show-connectorsсоединяет движения и обрывается на каждом итоге.Income statement · Q3 2026Thousands of eurosNet margin 15.7%Данные графика Шаг Изменение Накоплено до Накоплено после Revenue 1 240 0 1 240 Cost of sales -410 1 240 830 Gross profit 830 830 830 Salaries -390 830 440 Ads -120 440 320 Rent -60 320 260 Operating profit 260 260 260 Tax -65 260 195 Net profit 195 195 195
API порождается из пакета · разойтись не может
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
height | number | undefined | undefined | Высота холста в пикселях; по умолчанию 256. Данные, загрузка и пустое состояние занимают её одинаково. |
width | number | undefined | 640 | Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется. |
yDomain | readonly [number | null, number | null] | undefined | undefined | Границы оси значений `[min, max]`; `null` на месте границы — она берётся по данным. |
yTickCount | number | undefined | 5 | Желаемое число делений оси значений; по умолчанию 5. Шаг остаётся «круглым», поэтому делений бывает на одно меньше. |
yTickFormat | ((value: number) => string) | undefined | undefined | Подпись деления оси значений, например `value => value + '%'`. Место под ось считается по отформатированной подписи. |
valueFormat | GrChartNumberFormat | undefined | undefined | Формат значений в тултипе, скрытой таблице и объявлениях: знаки после запятой и разделители. |
showGrid | "y" | "both" | "x" | "none" | undefined | undefined | Какая сетка нужна. Оси названы по данным: при горизонтали они меняются местами сами. |
tooltip | boolean | undefined | undefined | Панель со значениями под курсором; по умолчанию `true`. `false` прячет только панель: курсор, активные марки, `update:activeIndex` и `pointHover` работают, пока график интерактивен. |
activeIndexv-model | number | null | undefined | undefined | Курсор — `v-model:activeIndex`. |
loading | boolean | undefined | false | Загрузка: на месте области построения — скелет той же высоты, место под оси уже зарезервировано. |
empty | boolean | undefined | undefined | Пустое состояние явно. Не задано — график пуст, когда рисовать нечего. |
emptyText | string | undefined | undefined | Текст заглушки пустого графика. Не задан — «Нет данных» из переводов пакета. |
dataTable | "hidden" | "visible" | "off" | undefined | undefined | Таблица данных: `hidden` (по умолчанию) — только для скринридера, `visible` — под графиком, `off` — без неё. |
interactive | boolean | undefined | true | `false` — график становится картинкой: без фокуса, тултипа и клавиатуры. |
size | "md" | "xs" | "sm" | "lg" | undefined | undefined | Кегль подписей осей и легенды по контрольной шкале ядра; по умолчанию — размер из `GrConfigProvider`. |
locale | string | undefined | undefined | BCP-47 локаль чисел и дат. Не задана — язык переводов приложения, иначе `en`. |
ariaLabel | string | undefined | undefined | Доступное имя графика. Не задано — сводка, которую собирает компонент: тип графика и объём данных. |
ariaDescription | string | undefined | undefined | Описание для скринридера — вывод, ради которого график показан; дополняет сводку, а не заменяет её. |
dataTableMaxRows | number | "auto" | undefined | undefined | Потолок строк скрытой таблицы данных. `'auto'` (по умолчанию) — столько строк, сколько можно прочитать; выше потолка таблица идёт равномерной выборкой и говорит об этом пометкой, а поточечная полнота остаётся за клавиатурой. Число задаёт свой потолок, `Infinity` снимает его совсем, `dataTable: 'off'` убирает таблицу целиком — что из этого нужно, решает приложение. |
orientation | "vertical" | "horizontal" | undefined | undefined | Направление: `vertical` (по умолчанию) или `horizontal` — длинные имена шагов читаются строкой. |
barRadius | number | undefined | undefined | Скругление столбцов в пикселях. |
baseline | number | undefined | 0 | Начальное накопление. |
showTotal | string | boolean | undefined | false | Дорисовать итоговый столбец справа. Строка задаёт его подпись. |
showConnectors | boolean | undefined | undefined | Соединители между столбцами — показывают, что следующий шаг начинается там, где кончился прошлый; по умолчанию `true`. |
stepsобязательный | readonly GrChartWaterfallStep[] | — | Шаги моста: `{ label, value, kind?, color? }`. `kind: 'total'` — абсолютное значение: столбец от нуля, накопление сбрасывается на него. |
Слоты
| Slot | Type | Описание |
|---|---|---|
tooltip |
| Своё содержимое панели тултипа вместо стандартного; данные активной позиции — в параметрах слота. |
empty | any | Своя заглушка пустого графика вместо «Нет данных»; занимает ту же `height`. |
header | any | Заголовок над графиком, внутри его корня — выше легенды, стоящей сверху. |
События
| Event | Сигнатура | Описание |
|---|---|---|
update:activeIndex | (event: "update:activeIndex", value: number | null): void | — |
stepClick | (event: "stepClick", value: GrChartWaterfallActiveStep): void | — |
stepHover | (event: "stepHover", value: GrChartWaterfallActiveStep | null): void | — |
Методы
| Methods | Сигнатура | Описание |
|---|---|---|
element | HTMLElement | null | Корневой элемент — для замеров и скролла в потребителе. |
Типы
Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.
import type { GrChartWaterfallConfigurableProps, GrChartSize, GrChartWaterfallActiveStep } from '@feugene/granularity-charts/components/GrChartWaterfall'Файлdist/types/components/GrChartWaterfall/config.d.ts
group тот же, что у остальных графиков: рама у них общая, и её чанк тоже.
Ребром графа компонентов общий чанк не становится — иначе вынесенный хелпер
превращал бы соседей в зависимости друг друга; в файлы каждого дотянувшегося
компонента он входит, и классы рамы сборка извлекает оттуда сама.
GrEmptyState и GrSkeleton объявлены зависимостями потому, что рама их
**рендерит**: без объявления они не попадут в замыкание селекции, и
приложение не подмешает их CSS.
const grChartWaterfallConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrChartWaterfall">;Файлdist/types/components/GrChartWaterfall/defaults.d.ts
Пропы GrChartWaterfall, настраиваемые глобально через
<GrConfigProvider :component-defaults="{ GrChartWaterfall: { … } }">.
Только оформление: шаги, начальное накопление и границы оси задаются на месте.
interface GrChartWaterfallConfigurableProps {
size: GrChartSize;
height: number;
/** Скругление дальнего конца столбца в пикселях: радиус идёт в геометрию пути, а не в CSS. */
barRadius: number;
/** Линии от вершины предыдущего столбца к основанию следующего. */
showConnectors: boolean;
orientation: 'vertical' | 'horizontal';
showGrid: 'both' | 'x' | 'y' | 'none';
tooltip: boolean;
dataTable: 'hidden' | 'visible' | 'off';
/**
* Потолок строк скрытой таблицы данных.
*
* Политика приложения: держать ли весь ряд в дереве доступности, усечь ли его
* до читаемого или убрать таблицу вовсе — решение уровня продукта, а не
* отдельного графика.
*/
dataTableMaxRows: number | 'auto';
}Файлdist/types/components/GrChartFrame/chartFrameStyles.d.ts
Классы общей рамы графика.
Рама — не компонент: у директории нет ни index.ts, ни config.ts, поэтому
в реестры она не попадает (гейт frameOwnership.test.ts). Владельцем токенов
--gr-chart-frame-* она при этом остаётся: гейт покомпонентных токенов ищет
реестры по файлу tokens.json в любой директории Gr…, а не по составу
компонента.
Пиксельных литералов и утилит uno-шкалы (text-sm, rounded-md) здесь нет:
темой они не настраиваются, а выглядят правильно.
type GrChartSize = GrComponentSize;Файлdist/types/components/GrChartWaterfall/safelist.d.ts
const grChartWaterfallSafelist: string[];Файлdist/types/components/GrChartWaterfall/GrChartWaterfall.vue.d.ts
Мост: как из начала периода получился конец.
Расходящиеся столбцы отвечают «сколько пришло и сколько ушло»; мост отвечает
«как одно превратилось в другое» — каждый столбец начинается там, где кончился
предыдущий. Вся арифметика в chart/chartWaterfall.ts, здесь только рисунок.
Ось шагов категориальная, но категорией становится **индекс**, а не подпись:
две «Корректировки» подряд — обычное дело для моста, а нормализация
схлопывает одинаковые категории внутри серии, и курсор после этого адресовал
бы чужой шаг. Настоящие подписи приезжают на ось через xTickFormat.
interface GrChartWaterfallActiveStep {
index: number;
label: string;
value: number;
/** Накопление до и после шага. */
before: number;
after: number;
kind: 'delta' | 'total';
}interface GrChartWaterfallProps {
/**
* Шаги моста: `{ label, value, kind?, color? }`. `kind: 'total'` — абсолютное значение: столбец
* от нуля, накопление сбрасывается на него.
*/
steps: readonly GrChartWaterfallStep[];
/** Начальное накопление. */
baseline?: number;
/** Дорисовать итоговый столбец справа. Строка задаёт его подпись. */
showTotal?: boolean | string;
/**
* Соединители между столбцами — показывают, что следующий шаг начинается там, где кончился
* прошлый; по умолчанию `true`.
*/
showConnectors?: boolean;
/** Скругление столбцов в пикселях. */
barRadius?: number;
/**
* Направление: `vertical` (по умолчанию) или `horizontal` — длинные имена шагов читаются строкой.
*/
orientation?: 'vertical' | 'horizontal';
/**
* Высота холста в пикселях; по умолчанию 256. Данные, загрузка и пустое состояние занимают её
* одинаково.
*/
height?: number;
/** Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется. */
width?: number;
/** Границы оси значений `[min, max]`; `null` на месте границы — она берётся по данным. */
yDomain?: readonly [number | null, number | null];
/**
* Желаемое число делений оси значений; по умолчанию 5. Шаг остаётся «круглым», поэтому делений
* бывает на одно меньше.
*/
yTickCount?: number;
/**
* Подпись деления оси значений, например `value => value + '%'`. Место под ось считается по
* отформатированной подписи.
*/
yTickFormat?: (value: number) => string;
/**
* Формат значений в тултипе, скрытой таблице и объявлениях: знаки после запятой и разделители.
*/
valueFormat?: GrChartNumberFormat;
/** Какая сетка нужна. Оси названы по данным: при горизонтали они меняются местами сами. */
showGrid?: 'both' | 'x' | 'y' | 'none';
/**
* Панель со значениями под курсором; по умолчанию `true`. `false` прячет только
* панель: курсор, активные марки, `update:activeIndex` и `pointHover` работают,
* пока график интерактивен.
*/
tooltip?: boolean;
/** Курсор — `v-model:activeIndex`. */
activeIndex?: number | null;
/**
* Загрузка: на месте области построения — скелет той же высоты, место под оси уже
* зарезервировано.
*/
loading?: boolean;
/** Пустое состояние явно. Не задано — график пуст, когда рисовать нечего. */
empty?: boolean;
/** Текст заглушки пустого графика. Не задан — «Нет данных» из переводов пакета. */
emptyText?: string;
/**
* Таблица данных: `hidden` (по умолчанию) — только для скринридера, `visible` — под графиком,
* `off` — без неё.
*/
dataTable?: 'hidden' | 'visible' | 'off';
/** `false` — график становится картинкой: без фокуса, тултипа и клавиатуры. */
interactive?: boolean;
/**
* Кегль подписей осей и легенды по контрольной шкале ядра; по умолчанию — размер из
* `GrConfigProvider`.
*/
size?: GrChartSize;
/** BCP-47 локаль чисел и дат. Не задана — язык переводов приложения, иначе `en`. */
locale?: string;
/**
* Доступное имя графика. Не задано — сводка, которую собирает компонент: тип графика и объём
* данных.
*/
ariaLabel?: string;
/**
* Описание для скринридера — вывод, ради которого график показан; дополняет сводку, а не заменяет
* её.
*/
ariaDescription?: string;
/**
* Потолок строк скрытой таблицы данных.
*
* `'auto'` (по умолчанию) — столько строк, сколько можно прочитать; выше
* потолка таблица идёт равномерной выборкой и говорит об этом пометкой, а
* поточечная полнота остаётся за клавиатурой. Число задаёт свой потолок,
* `Infinity` снимает его совсем, `dataTable: 'off'` убирает таблицу целиком —
* что из этого нужно, решает приложение.
*/
dataTableMaxRows?: number | 'auto';
}
export interface GrChartWaterfallEmits {
/** Курсор сдвинулся: индекс шага или `null` — для `v-model:activeIndex`. */
(e: 'update:activeIndex', value: number | null): void;
/** Щелчок или Enter по шагу — с его приростом и накоплением. */
(e: 'stepClick', value: GrChartWaterfallActiveStep): void;
/** Курсор пришёл на шаг или ушёл с графика (`null`). */
(e: 'stepHover', value: GrChartWaterfallActiveStep | null): void;
}
type __VLS_Slots = {
/**
* Своё содержимое панели тултипа вместо стандартного; данные активной позиции — в параметрах
* слота.
*/
tooltip?: (props: {
active: GrChartActivePoint;
step: GrChartWaterfallActiveStep | null;
formatValue: (value: number | null) => string;interface GrChartWaterfallEmits {
/** Курсор сдвинулся: индекс шага или `null` — для `v-model:activeIndex`. */
(e: 'update:activeIndex', value: number | null): void;
/** Щелчок или Enter по шагу — с его приростом и накоплением. */
(e: 'stepClick', value: GrChartWaterfallActiveStep): void;
/** Курсор пришёл на шаг или ушёл с графика (`null`). */
(e: 'stepHover', value: GrChartWaterfallActiveStep | null): void;
}Доступность
Паттерн APG
При orientation: 'horizontal' шаги идут сверху вниз, и по ним ходят ↓/↑
| Клавиша | Действие |
|---|---|
| — | шаг |
Полный контракт пакета: docs/keyboard.md ↗
Использование из прозы библиотеки
Шаг total объявляет накопление, а не прибавляется к нему
kind: 'delta' (по умолчанию) — изменение от текущего накопления,
kind: 'total' — абсолютное значение: столбец рисуется от нуля, а накопление
сбрасывается на него. Это и позволяет поставить «На начало» и «На конец»
реальными числами с бэкенда: если мост не сходится, последний столбец не
совпадёт с вершиной предпоследнего, и ошибку в данных будет видно.
Соединитель к такому шагу не рисуется. Он не продолжает накопление, а объявляет его, и линия «отсюда сюда» соврала бы о преемственности.
Цвет идёт по знаку, а не по индексу серии
Мост — один ряд, и палитра серий здесь ничего не различает: различать надо
прибавление (--gr-success) и убавление (--gr-danger). Итог и объявленное
накопление красятся нейтрально (--gr-chart-1) — это не движение. Явный color
шага сильнее любого из этих правил.
Нулевой шаг рисуется чертой
«Движения не было» — это факт, а не отсутствие данных. Столбец нулевой высоты
пропал бы с рисунка, и читатель увидел бы историю, в которой такого шага нет
вовсе. Вместо этого на уровне накопления встаёт черта толщиной
--gr-chart-waterfall-zero-step.
Таблица несёт накопление, а не одну дельту
Скрытая таблица даёт три числа на шаг: изменение, накопление до и накопление после. По одной дельте мост не восстановить — высоту столбца задаёт именно накопление, и читающий без зрения обязан видеть то же, что зрячий видит по положению полосы.
Категория шага — его индекс, а не подпись
Две «Корректировки» подряд в мосте — обычное дело, а нормализация схлопывает одинаковые категории внутри серии. Поэтому внутрь рамы уходит индекс шага, а настоящие подписи приезжают на ось отдельно: иначе позиций стало бы меньше, чем шагов, и курсор с клавиатурой поехали бы по чужим данным.
Ось значений — по накоплениям
Домен оси считается по основаниям и вершинам столбцов вместе с нулём, а не по
значениям шагов: дельта −410 на накоплении в 1240 не тянет ось в минус, где не
стоит ни один столбец. yDomain сильнее: если его низ выше нуля, столбец,
идущий от нуля, обрывается на краю области — обрыв и говорит, что ось начинается
не с нуля, — а соединители и нулевые шаги вне домена не рисуются.
Каждый шаг назван
Подписи шагов не прореживаются, как у категорий столбцов: мост без имени шага
не прочитать. Подпись, которой тесно, переносится по словам на две строки, а
дальше кончается многоточием с полным именем в <title>; соседние подписи не
налезают друг на друга. Если и после переноса от какой-то подписи остаётся
меньше четырёх знаков — на узком холсте «Pr…» вместо «Price», — подписи встают
в шахматку: соседние в разные ряды, и каждой достаётся ширина двух шагов.
Усечённая подпись не бывает короче четырёх знаков.
При горизонтали имена стоят колонкой слева, до 40% ширины, ось значений оставляет место под последнюю подпись деления, а её подписи, которым тесно, прореживаются через одну — первая и последняя остаются.
Границы
Мост не считает, из чего состоят его шаги, — их даёт потребитель. Мелкие шаги в «Прочее» он тоже не группирует: только вызывающий знает, что считать мелким. Вложенных (двухуровневых) мостов нет.
Пропы этого примера