GrChartArea

  • charts
  • Графики
  • API · 42 props
  • ⌨ клавиши

Берут, когда целое и вклад частей в него во времени.

import { GrChartArea } from '@feugene/granularity-charts/components/GrChartArea'

Обзор

Когда брать

  • целое и вклад частей в него во времени — выручка по каналам, трафик по источникам, время по стадиям: stacked кладёт каждую серию на сумму предыдущих;
  • один ряд, у которого важен объём — заливка до нуля читается как «сколько всего», а не «на каком уровне»;
  • доли, меняющиеся во времени — stacked: '100%' нормирует каждую позицию к единице, и видно перераспределение, а не рост;
  • ряды, которые обязаны просвечивать — при наложении без стека fill: 'auto' даёт градиент, и нижний ряд остаётся виден.

Когда взять другое

НужноБерите
Показать ход значения, объём не важенGrChartLine
Сравнить величины по категориямGrChartBar
Показать состав целого на один моментGrChartPie
Сравнить форму профиля по нескольким осямGrChartRadar
Тренд в ячейке таблицыGrSparkline

Категорий больше семи-восьми — стек перестаёт читаться: полосы становятся тоньше подписи, и сравнить их глазом уже нельзя. Это признак того, что нужен GrChartBar с группировкой либо меньше серий.

Примеры 5 у каждого — живое демо и его код

  • Выручка по каналам: верхний край — сумма

    stacked кладёт каждый канал на сумму предыдущих, и верхний край читается как итог. План — линия references, а не серия, и include-references-in-domain оставляет её на экране. Канал, скрытый в легенде (v-model:hidden-series), выпадает и из стека, и из числа над графиком. Новый период показывает loading, пустой — слот empty, а слот tooltip называет деньги в евро.

    Revenue in September€399k+4,7%
    Данные графика
    XDirect salesPartnersMarketplace
    Apr17110846
    May17511252
    Jun18211555
    Jul19012157
    Aug19612461
    Sep20412966
    Plan €420k: 420
  • Доли трафика, а не его объём

    stacked="100%" отвечает на «что из чего»: рост из рисунка уходит намеренно, а тултип по-прежнему называет абсолютные сессии. Слот legend получает серии и toggle, поэтому своя легенда с последней долей скрывает серии так же, как встроенная; legend-toggle сообщает об этом. curve="smooth", show-grid="y" и legend-position="top" держат ленты спокойными.

    Where visitors come fromShare of sessions by source, last 12 weeks
    Данные графика
    XSearchDirectSocialNewsletter
    W295,2003,1001,200900
    W305,3503,1501,260880
    W315,2803,2201,380910
    W325,4003,2601,520940
    W335,3203,3001,690920
    W345,1803,3501,840950
    W355,0503,4202,010930
    W364,9803,4802,190960
    W374,9003,5502,350980
    W384,8203,6002,520970
    W394,7603,6802,680990
    W404,7003,7402,8501,000

  • Сутки нагрузки сервера с приближением

    1 440 точек на оси time: decimate прореживает нарисованный путь до max-points, а курсор, клавиатура и скрытая таблица — с потолком data-table-max-rows — знают каждую минуту. zoom="both" принимает протяжку и колесо, а v-model:x-window позволяет «Whole day» сбросить окно. Зона перегрузки — опорная полоса; point-click прикалывает момент к инциденту.

    api-eu-1 · CPU5 October, UTC · drag over the chart or scroll to zoom in
    Данные графика
    XCPU
    5 окт. 2026 г., 00:0012
    5 окт. 2026 г., 00:1420,5
    5 окт. 2026 г., 00:369,7
    5 окт. 2026 г., 00:3918,7
    5 окт. 2026 г., 00:589
    5 окт. 2026 г., 01:0118,1
    5 окт. 2026 г., 01:237,6
    5 окт. 2026 г., 01:2616,7
    5 окт. 2026 г., 01:486,5
    5 окт. 2026 г., 01:5115,6
    5 окт. 2026 г., 02:135,6
    5 окт. 2026 г., 02:1614,7
    5 окт. 2026 г., 02:355,9
    5 окт. 2026 г., 02:4114,2
    5 окт. 2026 г., 03:005,5
    5 окт. 2026 г., 03:0314,7
    5 окт. 2026 г., 03:255,5
    5 окт. 2026 г., 03:2814,7
    5 окт. 2026 г., 03:505,7
    5 окт. 2026 г., 03:5314,9
    5 окт. 2026 г., 04:126,9
    5 окт. 2026 г., 04:1815,4
    5 окт. 2026 г., 04:377,6
    5 окт. 2026 г., 04:4316,2
    5 окт. 2026 г., 05:028,6
    5 окт. 2026 г., 05:0518
    5 окт. 2026 г., 05:279,9
    5 окт. 2026 г., 05:3019,2
    5 окт. 2026 г., 05:4912
    5 окт. 2026 г., 05:5520,7
    5 окт. 2026 г., 06:1413,6
    5 окт. 2026 г., 06:2022,4
    5 окт. 2026 г., 06:3915,5
    5 окт. 2026 г., 06:4524,3
    5 окт. 2026 г., 07:0417,5
    5 окт. 2026 г., 07:0726,9
    5 окт. 2026 г., 07:2620,2
    5 окт. 2026 г., 07:3229,1
    5 окт. 2026 г., 07:5122,4
    5 окт. 2026 г., 07:5731,4
    5 окт. 2026 г., 08:1624,7
    5 окт. 2026 г., 08:2233,7
    5 окт. 2026 г., 08:4127,1
    5 окт. 2026 г., 08:4736,1
    5 окт. 2026 г., 09:0330
    5 окт. 2026 г., 09:0939
    5 окт. 2026 г., 09:2832,4
    5 окт. 2026 г., 09:3441,4
    5 окт. 2026 г., 09:5334,8
    5 окт. 2026 г., 09:5943,8
    5 окт. 2026 г., 10:1837,1
    5 окт. 2026 г., 10:2446,1
    5 окт. 2026 г., 10:4039,9
    5 окт. 2026 г., 10:4648,8
    5 окт. 2026 г., 11:0542
    5 окт. 2026 г., 11:1150,8
    5 окт. 2026 г., 11:3043,9
    5 окт. 2026 г., 11:3652,8
    5 окт. 2026 г., 11:5545,7
    5 окт. 2026 г., 12:0154,5
    5 окт. 2026 г., 12:1747,9
    5 окт. 2026 г., 12:2656
    5 окт. 2026 г., 12:4249,3
    5 окт. 2026 г., 12:4858
    5 окт. 2026 г., 13:0750,5
    5 окт. 2026 г., 13:1359,1
    5 окт. 2026 г., 13:3251,4
    5 окт. 2026 г., 13:4854,2
    5 окт. 2026 г., 14:0091
    5 окт. 2026 г., 14:1298
    5 окт. 2026 г., 14:2287
    5 окт. 2026 г., 14:3053,6
    5 окт. 2026 г., 14:3961,2
    5 окт. 2026 г., 14:5553,6
    5 окт. 2026 г., 15:0461,1
    5 окт. 2026 г., 15:2053,3
    5 окт. 2026 г., 15:3462,6
    5 окт. 2026 г., 15:4552,7
    5 окт. 2026 г., 15:5161
    5 окт. 2026 г., 16:1051,9
    5 окт. 2026 г., 16:1660
    5 окт. 2026 г., 16:3251,7
    5 окт. 2026 г., 16:4158,9
    5 окт. 2026 г., 16:5750,4
    5 окт. 2026 г., 17:1159,1
    5 окт. 2026 г., 17:2248,7
    5 окт. 2026 г., 17:3657,3
    5 окт. 2026 г., 17:4746,9
    5 окт. 2026 г., 17:5354,8
    5 окт. 2026 г., 18:0945,9
    5 окт. 2026 г., 18:1852,7
    5 окт. 2026 г., 18:3443,7
    5 окт. 2026 г., 18:4852
    5 окт. 2026 г., 18:5941,3
    5 окт. 2026 г., 19:1349,5
    5 окт. 2026 г., 19:2438,8
    5 окт. 2026 г., 19:3046,5
    5 окт. 2026 г., 19:4637,2
    5 окт. 2026 г., 19:5543,9
    5 окт. 2026 г., 20:1134,5
    5 окт. 2026 г., 20:2542,5
    5 окт. 2026 г., 20:3631,7
    5 окт. 2026 г., 20:5039,7
    5 окт. 2026 г., 21:0128,9
    5 окт. 2026 г., 21:1536,9
    5 окт. 2026 г., 21:2327,2
    5 окт. 2026 г., 21:3233,8
    5 окт. 2026 г., 21:4824,4
    5 окт. 2026 г., 22:0232,5
    5 окт. 2026 г., 22:1321,7
    5 окт. 2026 г., 22:2729,8
    5 окт. 2026 г., 22:3819
    5 окт. 2026 г., 22:5227,2
    5 окт. 2026 г., 23:0017,6
    5 окт. 2026 г., 23:1724,8
    5 окт. 2026 г., 23:2515,2
    5 окт. 2026 г., 23:3923,6
    5 окт. 2026 г., 23:5013
    5 окт. 2026 г., 23:5813,5
    5 окт. 2026 г., 23:5919,8
    Overload: от 80 до 100
    Показано 120 из 1440 точек — те же, что нарисованы. Стрелками доступны все.

    Click a point to add it to the incident.

  • Два графика с одним курсором

    v-model:active-index связывает пару: наведение на любой график двигает курсор в обоих, тултипы называют одну и ту же минуту, а строка над ними сводит её в одну линию. Запросы в минуту и миллисекунды расходятся на два порядка — dual-axis с y-domain-right, y-tick-format-right и value-format-right здесь осознанный выбор, а не умолчание. size="sm" держит пару компактной.

    Checkout API · last hour13:59 · 1,232 requests · p95 140 ms · 1 errors
    Данные графика
    XRequests / min (левая ось)p95 latency, ms (правая ось)
    13:001 100165
    13:011 176167
    13:021 160169
    13:031 235170
    13:041 217170
    13:051 199170
    13:061 271169
    13:071 250167
    13:081 320165
    13:091 295163
    13:101 269160
    13:111 333156
    13:121 303152
    13:131 363148
    13:141 328144
    13:151 291140
    13:161 344136
    13:171 303132
    13:181 352127
    13:191 306124
    13:201 259120
    13:211 302117
    13:221 252115
    13:231 200113
    13:241 238111
    13:251 184110
    13:261 221110
    13:271 165110
    13:281 109111
    13:291 146113
    13:301 090115
    13:311 126117
    13:321 072120
    13:331 018124
    13:341 057128
    13:351 006132
    13:361 048136
    13:371 000140
    13:38953144
    13:391 001149
    13:40958153
    13:411 010156
    13:42972320
    13:43936323
    13:44995325
    13:45963327
    13:46934329
    13:47999330
    13:48974170
    13:491 042170
    13:501 020169
    13:511 000167
    13:521 073165
    13:531 055163
    13:541 130160
    13:551 113156
    13:561 097152
    13:571 173148
    13:581 157144
    13:591 232140
    Данные графика
    XErrors / min
    13:000
    13:013
    13:022
    13:031
    13:040
    13:053
    13:062
    13:071
    13:080
    13:093
    13:102
    13:111
    13:120
    13:133
    13:142
    13:151
    13:160
    13:173
    13:182
    13:191
    13:200
    13:213
    13:222
    13:231
    13:240
    13:253
    13:262
    13:271
    13:280
    13:293
    13:302
    13:311
    13:320
    13:333
    13:342
    13:351
    13:360
    13:373
    13:382
    13:391
    13:400
    13:413
    13:4218
    13:4323
    13:4421
    13:4519
    13:4624
    13:4722
    13:480
    13:493
    13:502
    13:511
    13:520
    13:533
    13:542
    13:551
    13:560
    13:573
    13:582
    13:591

API порождается из пакета · разойтись не может

Props

PropTypeпо умолчаниюОписание
xScaleGrChartScaleKind | undefinedundefinedТип оси X. Не задан — выводится из данных.
stackedboolean | "100%" | undefinedfalseСкладывать серии: каждая полоса ложится на сумму предыдущих. `'100%'` вдобавок нормирует каждую позицию к единице — тогда лента показывает, как менялось **распределение**, а не величины. Собственные значения точек при этом не трогаются: тултип и таблица говорят «сорок», а не «треть».
fill"auto" | "gradient" | "solid" | undefinedundefinedЗаливка. `auto` — градиент у наложения, плотная у стека.
heightnumber | undefinedundefinedВысота холста в пикселях; по умолчанию 256. Данные, загрузка и пустое состояние занимают её одинаково.
widthnumber | undefined640Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется.
yDomainreadonly [number | null, number | null] | undefinedundefinedГраницы оси значений `[min, max]`; `null` на месте границы — она берётся по данным.
includeZeroboolean | undefinedfalseДотянуть ось значений до нуля; по умолчанию нет — ось облегает данные.
referencesreadonly GrChartReference[] | undefinedundefinedОпорные линии и полосы: порог, план, коридор допустимого. Не серия и нигде ею не считается: в легенду не попадает, индекс палитры не тратит, в стек не входит и в скрытую таблицу уезжает примечанием, а не строкой данных.
includeReferencesInDomainboolean | undefinedfalseВключить опоры в домен оси. По умолчанию нет, и это осознанно: порог `1.0` при данных около `0.03` растянул бы ось так, что сами данные схлопнулись бы в линию.
dualAxisboolean | undefinedfalseПоказывать вторую ось значений справа. Без неё серии с `axis: 'right'` попадают на левую. Включение осознанное: две оси позволяют подогнать любые два ряда под видимую корреляцию, и это должно быть решением автора графика, а не побочным эффектом поля в данных.
yDomainRightreadonly [number | null, number | null] | undefinedundefinedГраницы правой оси значений при `dualAxis`; `null` на месте границы — она берётся по данным.
yTickFormatRight((value: number) => string) | undefinedundefinedПодпись деления правой оси. Не задана — число по локали.
valueFormatRightGrChartNumberFormat | undefinedundefinedФормат значений серий правой оси в тултипе, скрытой таблице и объявлениях.
xTickCountnumber | undefinedundefinedЖелаемое число делений оси X; по умолчанию 6. У категорий — потолок числа подписей (не задан — 12). Подписи, которые не помещаются по ширине, прореживаются.

Слоты

SlotTypeОписание
tooltip{ active: GrChartActivePoint; formatValue: (value: number | null) => string; }Своё содержимое панели тултипа вместо стандартного; данные активной позиции — в параметрах слота.
legend{ series: readonly NormalizedSeries[]; toggle: (id: string) => void; }Своя легенда вместо стандартной. У пустого графика не рисуется.
emptyanyСвоя заглушка пустого графика вместо «Нет данных»; занимает ту же `height`.
headeranyЗаголовок над графиком, внутри его корня — выше легенды, стоящей сверху.

События

EventСигнатураОписание
update:hiddenSeries(event: "update:hiddenSeries", value: string[]): void—
update:activeIndex(event: "update:activeIndex", value: number | null): void—
update:xWindow(event: "update:xWindow", value: GrChartXWindow | null): void—
pointClick(event: "pointClick", value: GrChartActivePoint): void—
pointHover(event: "pointHover", value: GrChartActivePoint | null): void—
legendToggle(event: "legendToggle", value: { seriesId: string; hidden: boolean; }): void—

Методы

MethodsСигнатураОписание
elementHTMLElement | nullКорневой элемент — для замеров и скролла в потребителе.

Типы

Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.

Импорт
import type { GrChartAreaConfigurableProps, GrChartSize, GrChartAreaProps } from '@feugene/granularity-charts/components/GrChartArea'

Файлdist/types/components/GrChartArea/config.d.ts

grChartAreaConfigconst

group тот же, что у остальных графиков: рама у них общая, и её чанк тоже. Ребром графа компонентов общий чанк не становится — иначе вынесенный хелпер превращал бы соседей в зависимости друг друга; в файлы каждого дотянувшегося компонента он входит, и классы рамы сборка извлекает оттуда сама. GrEmptyState и GrSkeleton объявлены зависимостями потому, что рама их **рендерит**: без объявления они не попадут в замыкание селекции, и приложение не подмешает их CSS.

const grChartAreaConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrChartArea">;

Файлdist/types/components/GrChartArea/defaults.d.ts

GrChartAreaConfigurablePropsinterface

Пропы GrChartArea, настраиваемые глобально через <GrConfigProvider :component-defaults="{ GrChartArea: { … } }">. Только оформление: данные, стек и границы осей задаются на месте.

interface GrChartAreaConfigurableProps {
    /**
     * Когда прореживать ряд для рисунка. Политика рендера — приложение с плотными
     * рядами задаёт её один раз на всё поддерево.
     */
    decimate: 'auto' | 'always' | 'never';
    /** Бюджет точек на серию. */
    maxPoints: number;
    /**
     * Какими жестами меняется видимое окно.
     *
     * Политика приложения, а не свойство отдельного графика: включать
     * приближение на одной панели и не включать на соседней — это разные правила
     * поведения у одинаковых с виду картинок.
     */
    zoom: GrChartZoom;
    /**
     * Потолок строк скрытой таблицы данных.
     *
     * Политика приложения: держать ли полный ряд в дереве доступности, усечь ли
     * его до нарисованного или убрать таблицу вовсе — решение уровня продукта, а
     * не отдельного графика.
     */
    dataTableMaxRows: number | 'auto';
    size: GrChartSize;
    height: number;
    curve: GrChartCurve;
    fill: 'auto' | 'gradient' | 'solid';
    showGrid: 'both' | 'x' | 'y' | 'none';
    showLegend: boolean | 'auto';
    legendPosition: 'top' | 'bottom';
    tooltip: boolean;
    dataTable: 'hidden' | 'visible' | 'off';
}

Файлdist/types/components/GrChartFrame/chartFrameStyles.d.ts

GrChartSizetype

Классы общей рамы графика. Рама — не компонент: у директории нет ни index.ts, ни config.ts, поэтому в реестры она не попадает (гейт frameOwnership.test.ts). Владельцем токенов --gr-chart-frame-* она при этом остаётся: гейт покомпонентных токенов ищет реестры по файлу tokens.json в любой директории Gr…, а не по составу компонента. Пиксельных литералов и утилит uno-шкалы (text-sm, rounded-md) здесь нет: темой они не настраиваются, а выглядят правильно.

type GrChartSize = GrComponentSize;

Файлdist/types/components/GrChartArea/safelist.d.ts

grChartAreaSafelistconst
const grChartAreaSafelist: string[];

Файлdist/types/components/GrChartArea/GrChartArea.vue.d.ts

GrChartAreaPropsinterface

Площадь: тот же ряд, что у линии, но с заливкой до базовой линии — и складываемый в стек. Отдельным компонентом, а не пропом линии: взявший линию не должен получать в бандл арифметику стека и разметку градиентов, которых он не просил.

interface GrChartAreaProps {
    /** Серии либо голый ряд чисел — тогда `x` становится порядковым номером. */
    series: readonly GrChartSeries[] | readonly number[];
    /** Тип оси X. Не задан — выводится из данных. */
    xScale?: GrChartScaleKind;
    /**
     * Складывать серии: каждая полоса ложится на сумму предыдущих.
     *
     * `'100%'` вдобавок нормирует каждую позицию к единице — тогда лента
     * показывает, как менялось **распределение**, а не величины. Собственные
     * значения точек при этом не трогаются: тултип и таблица говорят «сорок», а
     * не «треть».
     */
    stacked?: boolean | '100%';
    /** Заливка. `auto` — градиент у наложения, плотная у стека. */
    fill?: 'auto' | 'gradient' | 'solid';
    /**
     * Высота холста в пикселях; по умолчанию 256. Данные, загрузка и пустое состояние занимают её
     * одинаково.
     */
    height?: number;
    /** Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется. */
    width?: number;
    /** Границы оси значений `[min, max]`; `null` на месте границы — она берётся по данным. */
    yDomain?: readonly [number | null, number | null];
    /** Дотянуть ось значений до нуля; по умолчанию нет — ось облегает данные. */
    includeZero?: boolean;
    /**
     * Опорные линии и полосы: порог, план, коридор допустимого.
     *
     * Не серия и нигде ею не считается: в легенду не попадает, индекс палитры не
     * тратит, в стек не входит и в скрытую таблицу уезжает примечанием, а не
     * строкой данных.
     */
    references?: readonly GrChartReference[];
    /**
     * Включить опоры в домен оси.
     *
     * По умолчанию нет, и это осознанно: порог `1.0` при данных около `0.03`
     * растянул бы ось так, что сами данные схлопнулись бы в линию.
     */
    includeReferencesInDomain?: boolean;
    /**
     * Показывать вторую ось значений справа.
     *
     * Без неё серии с `axis: 'right'` попадают на левую. Включение осознанное:
     * две оси позволяют подогнать любые два ряда под видимую корреляцию, и это
     * должно быть решением автора графика, а не побочным эффектом поля в данных.
     */
    dualAxis?: boolean;
    /**
     * Границы правой оси значений при `dualAxis`; `null` на месте границы — она берётся по данным.
     */
    yDomainRight?: readonly [number | null, number | null];
    /** Подпись деления правой оси. Не задана — число по локали. */
    yTickFormatRight?: (value: number) => string;
GrChartAreaEmitsinterface
interface GrChartAreaEmits {
    /**
     * Легенда просит скрыть или показать серию — новый список id; применяет его потребитель
     * (`v-model:hiddenSeries`).
     */
    (e: 'update:hiddenSeries', value: string[]): void;
    /** Курсор сдвинулся: индекс позиции или `null` — для `v-model:activeIndex`. */
    (e: 'update:activeIndex', value: number | null): void;
    /** Жест приближения сменил видимое окно — для `v-model:xWindow`. */
    (e: 'update:xWindow', value: GrChartXWindow | null): void;
    /** Щелчок или Enter по активной позиции — со значениями всех видимых серий в ней. */
    (e: 'pointClick', value: GrChartActivePoint): void;
    /** Курсор пришёл на позицию или ушёл с графика (`null`). */
    (e: 'pointHover', value: GrChartActivePoint | null): void;
    /** Нажат пункт легенды: какая серия и в какое состояние просится. */
    (e: 'legendToggle', value: {
        seriesId: string;
        hidden: boolean;
    }): void;
}

Доступность

Паттерн APG

При нескольких сериях ↑/↓ переключают читаемую серию

КлавишаДействие
—точка ряда по оси X

Использование из прозы библиотеки

Выше порога рисует холст

Тело графика умеет рисоваться двумя способами. Ниже порога это SVG, выше — <canvas>; выбирает сам компонент, пропа «каким рендерером» нет.

Порог считается в нарисованных вершинах, а не в точках — canvasThreshold, по умолчанию 24 000. Разница существенная: прореживание режет каждый ряд до предела экрана (около двух вершин на пиксель), поэтому один ряд в сто тысяч точек рисуется как 2400 вершин и стоит миллисекунды, а двадцать рядов по 2400 — те же 48 000 точек — стоят шестнадцати, то есть целого кадра.

Числа замера при ширине 1200px, 2400 вершин на ряд:

РядовSVGХолст
11,1 мс0,4 мс
86,7 мс0,6 мс
2016,3 мс1,7 мс

canvasThreshold: 0 выключает холст совсем — на случай, когда рисунок обязан остаться векторным: печать, экспорт SVG, свой CSS поверх марок.

Доступность от смены рендерера не меняется вовсе. Курсор, клавиатура, тултип и скрытая таблица работают с оверлеем и с полными рядами, а не с марками: холст для них не существует, он aria-hidden и не ловит указатель. Это и было условием, при котором второй рендерер вообще допустили.

Что холст рисует иначе. Сетка переезжает в него же: холст лежит под <svg>, чтобы оси и активная точка остались сверху, — а сетка обязана остаться под рядами. У площади градиентная заливка становится сплошной: url(#…) холст не понимает, а на двадцати площадях градиент всё равно читается как каша.

Заливка выбирается по режиму, а не по вкусу

fill: 'auto' — градиент у наложения и плотная заливка у стека. Причина в разном: наложенным рядам нужно просвечивать друг сквозь друга, а полосы стека стоят встык, и градиент размыл бы границу между двумя соседними — то есть само место, где кончается одна часть и начинается другая.

Значение точки остаётся своим

В стеке на точку приезжают границы полосы отдельными полями, но собственное y не трогается. Тултип, скрытая таблица и живой регион говорят «партнёры — сто девяносто», а не «шестьсот пятьдесят, потому что снизу лежит розница».

Скрытая серия из стека выпадает, лежащие выше опускаются. Ось значений считается по вершинам полос и всегда включает ноль: стек от ненулевой базы врёт про пропорции.

Стек строят по полным рядам. Пропуск — единственное место, где он честен не полностью: сумма в этой позиции занижена. Протянуть последнее известное значение было бы хуже — это нарисовать данные, которых не было.

Сто процентов отвечают на другой вопрос

stacked: true показывает величины и их сумму, stacked: '100%' — только распределение: сумма в каждой позиции равна единице, и лента говорит, как менялись доли, а не сколько всего было. Выбирать надо один вопрос: читатель не знает, сравнивать ему высоты лент или их доли.

Нормируется рисунок, а не данные. Ось переходит в проценты, а тултип, скрытая таблица и живой регион по-прежнему называют абсолютные величины. Позиция с нулевой суммой нормировать не на что — там ноль, а не NaN.

Длинный ряд сокращается в рисунке, а не в данных

decimate: 'auto' (по умолчанию) прореживает вершины пути, когда точек больше, чем экран способен показать. В стопке набор абсцисс общий на все серии — иначе низ верхней полосы интерполировался бы по одним X, а верх нижней по другим, и заливка разошлась бы швами. Курсор, клавиатура, тултип и скрытая таблица знают полный ряд; разбор — ../model.md, раздел «Прореживание — проекция, а не данные».

Приближение по абсциссе

zoom включает окно: 'brush' — протяжка по холсту, 'wheel' — колесо, 'both' — оба. По умолчанию выключено.

vue
<GrChartArea v-model:x-window="window" :series="series" zoom="both" />

<GrButton :disabled="window === null" @click="window = null">
Весь ряд
</GrButton>

Окно выбирает данные, а не обрезает рисунок: по нему считаются позиции, курсор, клавиатура, скрытая таблица и размах оси значений. Практическое следствие — приближение проявляет мелкую структуру, которая на полном ряде ложится сплошной штриховкой: бюджет прореживания считается от ширины области, а точек в окне меньше, и на каждую приходится больше вершин.

Клавиатура работает всегда, когда включён zoom: +/- приближают к активной точке, Shift+стрелки сдвигают окно, 0 возвращает весь ряд. Union пропа перечисляет только жесты указателя — отключаемой клавиатуры у приближения нет by design (../a11y.md, раздел про приближение).

v-model:x-window не обязателен — без привязки график приближается сам. Привязывают его ради другого: синхронной пары графиков, кнопки сброса рядом с холстом, сохранения приближения в адресной строке.

Границы принимаются в том же виде, что абсциссы точек (Date, ISO-строка, число), наружу уходят числами. activeIndex при заданном окне адресует его, а не весь ряд.

Скрытая таблица и её потолок

Полные данные графика строками — то, что читает диктор вместо картинки. По умолчанию dataTable: 'hidden': в дереве доступности, глазами не видна.

Строка на точку читаема, пока строк немного. Поэтому у таблицы есть потолок — dataTableMaxRows, по умолчанию 'auto', то есть бюджет рисунка:

vue
<GrChartArea :series="series" />                              <!-- auto: как нарисовано -->

<GrChartArea :series="series" :data-table-max-rows="200" />   <!-- свой потолок -->

<GrChartArea :series="series" :data-table-max-rows="Infinity" /> <!-- всегда полная -->

<GrChartArea :series="series" data-table="off" />             <!-- таблицы нет вовсе -->

Выше потолка таблица печатает те же точки, что нарисованы, и говорит об этом пометкой в подвале. Поточечная полнота при этом не теряется: стрелки обходят весь ряд и проговаривают каждую точку. Таблица отвечает за обзор, клавиатура — за точное значение; разбор — ../model.md.

'auto' — это «столько строк, сколько можно прочитать». Бюджет рисунка берётся, когда он есть; при decimate: 'never' его нет, и остаётся фиксированный потолок с равномерной выборкой.

Что из этого нужно, решает приложение: держать десять тысяч строк в дереве доступности — его право, но и цена перестроения такой таблицы на нём.

Порог рисуется опорой, а не серией

План, норма и граница допустимого — это проп references, а не ряд из константы: серия-константа попала бы в легенду, растянула бы ось и уехала бы в таблицу как данные. Разбор — ../model.md, раздел «Опора — не серия».

Две оси — осознанное решение

Ряды разного порядка (деньги и штуки) читаются на одном графике только с двумя осями, а две оси позволяют подогнать любую пару под видимую корреляцию. Поэтому axis: 'right' у серии не работает, пока не включён dualAxis. Инварианты — ../model.md, раздел «Вторая ось значений».