GrChartLine
Берут, когда как величина менялась.
Обзор
Когда брать
- как величина менялась — выручка по месяцам, нагрузка по часам, остаток по дням: линия показывает ход, а не отдельные замеры;
- несколько рядов рядом — легенда переключает серии, тултип показывает все значения в одной абсциссе;
- в ряду есть пропуски —
nullрвёт линию, а не подставляет ноль;gapsрисует через провал перемычку, когда важнее читаемость тренда; - точек тысячи —
decimate: 'auto'(по умолчанию) сокращает рисунок до двух вершин на пиксель, а курсор, клавиатура и скрытая таблица продолжают знать полный ряд. Разбор —../model.md, раздел «Прореживание — проекция, а не данные».
Когда взять другое
| Нужно | Берите |
|---|---|
| Показать целое и вклад частей в него | GrChartArea |
| Сравнить величины по категориям | GrChartBar |
| Показать, из чего состоит одно целое | GrChartPie |
| Сравнить форму профиля по нескольким осям | GrChartRadar |
| Тренд в ячейке таблицы, без осей и рамы | GrSparkline |
| Одно число крупно, с динамикой | GrStatistic |
Примеры 4 у каждого — живое демо и его код
Месяц задержек и час, который его сломал
720 точек на серию на шкале времени: рисунок прорежен до ширины, а курсор и скрытая таблица знают каждый час.
v-model:x-window— видимое окно: его ставят пресеты,zoom="brush"даёт читателю протянуть своё, а ось переходит с дней на часы, когда окно становится коротким. SLO — линияreferences, а не серия.Checkout API · latencyLast 30 days · drag over the chart to zoom inДанные графика X p95 p50 7 сент. 2026 г., 00:00 153 58 7 сент. 2026 г., 07:00 131 66 7 сент. 2026 г., 13:00 232 80 7 сент. 2026 г., 22:00 147 66 8 сент. 2026 г., 06:00 146 66 8 сент. 2026 г., 11:00 228 80 8 сент. 2026 г., 17:00 229 81 8 сент. 2026 г., 19:00 169 77 9 сент. 2026 г., 06:00 138 64 9 сент. 2026 г., 11:00 218 78 9 сент. 2026 г., 14:00 254 82 9 сент. 2026 г., 22:00 145 64 10 сент. 2026 г., 07:00 152 59 10 сент. 2026 г., 13:00 239 91 10 сент. 2026 г., 15:00 244 84 10 сент. 2026 г., 21:00 165 57 11 сент. 2026 г., 07:00 139 68 11 сент. 2026 г., 12:00 239 79 11 сент. 2026 г., 17:00 234 81 11 сент. 2026 г., 21:00 138 62 12 сент. 2026 г., 07:00 128 66 12 сент. 2026 г., 10:00 176 75 12 сент. 2026 г., 14:00 206 70 12 сент. 2026 г., 22:00 127 58 13 сент. 2026 г., 06:00 128 59 13 сент. 2026 г., 13:00 207 70 13 сент. 2026 г., 15:00 165 76 14 сент. 2026 г., 00:00 139 63 14 сент. 2026 г., 06:00 135 61 14 сент. 2026 г., 13:00 249 80 14 сент. 2026 г., 19:00 199 66 14 сент. 2026 г., 22:00 128 66 15 сент. 2026 г., 07:00 134 61 15 сент. 2026 г., 13:00 249 86 15 сент. 2026 г., 16:00 244 80 16 сент. 2026 г., 00:00 133 61 16 сент. 2026 г., 07:00 137 64 16 сент. 2026 г., 12:00 243 84 16 сент. 2026 г., 21:00 138 68 16 сент. 2026 г., 23:00 172 60 17 сент. 2026 г., 05:00 133 65 17 сент. 2026 г., 11:00 231 80 17 сент. 2026 г., 21:00 148 56 18 сент. 2026 г., 02:00 138 68 18 сент. 2026 г., 08:00 151 67 18 сент. 2026 г., 14:00 253 88 18 сент. 2026 г., 21:00 150 58 19 сент. 2026 г., 01:00 170 64 19 сент. 2026 г., 05:00 136 68 19 сент. 2026 г., 11:00 200 73 19 сент. 2026 г., 19:00 154 73 20 сент. 2026 г., 04:00 129 65 20 сент. 2026 г., 05:00 164 62 20 сент. 2026 г., 14:00 204 72 20 сент. 2026 г., 22:00 137 67 21 сент. 2026 г., 04:00 128 64 21 сент. 2026 г., 10:00 218 77 21 сент. 2026 г., 16:00 240 84 21 сент. 2026 г., 22:00 128 60 22 сент. 2026 г., 00:00 164 63 22 сент. 2026 г., 06:00 150 67 22 сент. 2026 г., 13:00 250 81 22 сент. 2026 г., 21:00 148 66 23 сент. 2026 г., 00:00 138 69 23 сент. 2026 г., 07:00 135 68 23 сент. 2026 г., 15:00 252 88 23 сент. 2026 г., 21:00 142 63 24 сент. 2026 г., 04:00 135 61 24 сент. 2026 г., 07:00 629 177 24 сент. 2026 г., 12:00 239 77 24 сент. 2026 г., 21:00 128 59 25 сент. 2026 г., 05:00 129 59 25 сент. 2026 г., 12:00 238 79 25 сент. 2026 г., 16:00 248 87 25 сент. 2026 г., 23:00 133 67 26 сент. 2026 г., 04:00 169 59 26 сент. 2026 г., 07:00 135 68 26 сент. 2026 г., 13:00 209 71 26 сент. 2026 г., 21:00 139 64 27 сент. 2026 г., 06:00 137 62 27 сент. 2026 г., 12:00 202 67 27 сент. 2026 г., 14:00 171 71 27 сент. 2026 г., 22:00 135 62 28 сент. 2026 г., 07:00 135 61 28 сент. 2026 г., 13:00 240 91 28 сент. 2026 г., 15:00 247 80 28 сент. 2026 г., 20:00 148 66 29 сент. 2026 г., 07:00 148 67 29 сент. 2026 г., 11:00 231 77 29 сент. 2026 г., 14:00 257 89 29 сент. 2026 г., 22:00 139 67 30 сент. 2026 г., 07:00 145 65 30 сент. 2026 г., 11:00 230 76 30 сент. 2026 г., 15:00 247 81 30 сент. 2026 г., 22:00 141 68 1 окт. 2026 г., 06:00 134 66 1 окт. 2026 г., 13:00 235 81 1 окт. 2026 г., 20:00 149 72 1 окт. 2026 г., 21:00 172 64 2 окт. 2026 г., 07:00 135 62 2 окт. 2026 г., 11:00 230 81 2 окт. 2026 г., 15:00 250 82 2 окт. 2026 г., 21:00 128 65 3 окт. 2026 г., 08:00 180 66 3 окт. 2026 г., 10:00 156 72 3 окт. 2026 г., 17:00 189 70 3 окт. 2026 г., 21:00 141 56 4 окт. 2026 г., 07:00 133 66 4 окт. 2026 г., 10:00 192 69 4 окт. 2026 г., 19:00 177 65 4 окт. 2026 г., 22:00 129 60 5 окт. 2026 г., 06:00 140 64 5 окт. 2026 г., 14:00 246 81 5 окт. 2026 г., 21:00 130 61 6 окт. 2026 г., 02:00 171 64 6 окт. 2026 г., 07:00 143 56 6 окт. 2026 г., 15:00 253 88 6 окт. 2026 г., 18:00 187 81 6 окт. 2026 г., 22:00 168 57 6 окт. 2026 г., 23:00 146 57 SLO 300 ms: 300 Показано 120 из 720 точек — те же, что нарисованы. Стрелками доступны все. Датчик, который замолчал
nullрвёт линию, а не роняет её в ноль: замолчавший датчик не намерил 0 °C.gaps="dashed"перекидывает через провал перемычку, чтобы тренд читался дальше, а пунктир говорит, что это не замер. Допустимый коридор — опорная полоса, аcurve="smooth"не выносит кривую за показания.Fridge 3 · temperatureNo readings 03:00–05:00 — logger offline Данные графика X Fridge 3 6 окт. 2026 г., 00:00 4,6 6 окт. 2026 г., 00:30 4,8 6 окт. 2026 г., 01:00 5,1 6 окт. 2026 г., 01:30 5,0 6 окт. 2026 г., 02:00 4,9 6 окт. 2026 г., 02:30 5,3 6 окт. 2026 г., 03:00 нет значения 6 окт. 2026 г., 03:30 нет значения 6 окт. 2026 г., 04:00 нет значения 6 окт. 2026 г., 04:30 нет значения 6 окт. 2026 г., 05:00 7,6 6 окт. 2026 г., 05:30 6,9 6 окт. 2026 г., 06:00 6,1 6 окт. 2026 г., 06:30 5,4 6 окт. 2026 г., 07:00 5,0 6 окт. 2026 г., 07:30 4,8 6 окт. 2026 г., 08:00 4,7 6 окт. 2026 г., 08:30 4,9 6 окт. 2026 г., 09:00 5,2 6 окт. 2026 г., 09:30 5,1 6 окт. 2026 г., 10:00 4,8 6 окт. 2026 г., 10:30 4,6 6 окт. 2026 г., 11:00 4,5 6 окт. 2026 г., 11:30 4,7 6 окт. 2026 г., 12:00 4,8 2–8 °C: от 2,0 до 8,0 Места, которые меняются в день, а не плавно
curve="step"рисует величину, которая скачет и держится, — наклонная линия выдумала бы 18,5 места в марте.show-points="always"отмечает выставленные месяцы, аpoint-clickоткрывает строку счёта за месяц.Northwind Traders · seats billedTeam plan, €12 a seat · click a month Данные графика X Seats 1 нояб. 2025 г., 00:00 12 1 дек. 2025 г., 00:00 12 1 янв. 2026 г., 00:00 15 1 февр. 2026 г., 00:00 15 1 мар. 2026 г., 00:00 15 1 апр. 2026 г., 00:00 22 1 мая 2026 г., 00:00 22 1 июн. 2026 г., 00:00 22 1 июл. 2026 г., 00:00 20 1 авг. 2026 г., 00:00 28 1 сент. 2026 г., 00:00 28 1 окт. 2026 г., 00:00 34 Click a month to see its invoice line.
Две единицы — две шкалы
dual-axisдаёт конверсии свою шкалу справа — на левой она легла бы плашмя под 1 700 регистраций. У каждой оси свой формат делений, опора поxотмечает смену цен, аv-model:hidden-seriesдержит состояние легенды у страницы: график только сообщает намерение.Growth after the price changeMore people sign up since W32, fewer of them pay Данные графика X Sign-ups (левая ось) Paid within 14 days (правая ось) W28 820 6,1 W29 860 6,0 W30 910 6,3 W31 870 6,4 W32 1 040 5,8 W33 1 180 5,2 W34 1 210 5,0 W35 1 160 5,1 W36 1 390 4,6 W37 1 520 4,3 W38 1 480 4,4 W39 1 610 4,1 W40 1 700 3,9 New prices: W32
API порождается из пакета · разойтись не может
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
xScale | GrChartScaleKind | undefined | undefined | Тип оси X. Не задан — выводится из данных. |
height | number | undefined | undefined | Высота холста в пикселях; по умолчанию 256. Данные, загрузка и пустое состояние занимают её одинаково. |
width | number | undefined | 640 | Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется. |
yDomain | readonly [number | null, number | null] | undefined | undefined | Границы оси значений `[min, max]`; `null` на месте границы — она берётся по данным. |
includeZero | boolean | undefined | false | Дотянуть ось значений до нуля; по умолчанию нет — ось облегает данные. |
references | readonly GrChartReference[] | undefined | undefined | Опорные линии и полосы: порог, план, коридор допустимого. Не серия и нигде ею не считается: в легенду не попадает, индекс палитры не тратит, в стек не входит и в скрытую таблицу уезжает примечанием, а не строкой данных. |
includeReferencesInDomain | boolean | undefined | false | Включить опоры в домен оси. По умолчанию нет, и это осознанно: порог `1.0` при данных около `0.03` растянул бы ось так, что сами данные схлопнулись бы в линию. |
dualAxis | boolean | undefined | false | Показывать вторую ось значений справа. Без неё серии с `axis: 'right'` попадают на левую. Включение осознанное: две оси позволяют подогнать любые два ряда под видимую корреляцию, и это должно быть решением автора графика, а не побочным эффектом поля в данных. |
yDomainRight | readonly [number | null, number | null] | undefined | undefined | Границы правой оси значений при `dualAxis`; `null` на месте границы — она берётся по данным. |
yTickFormatRight | ((value: number) => string) | undefined | undefined | Подпись деления правой оси. Не задана — число по локали. |
valueFormatRight | GrChartNumberFormat | undefined | undefined | Формат значений серий правой оси в тултипе, скрытой таблице и объявлениях. |
xTickCount | number | undefined | undefined | Желаемое число делений оси X; по умолчанию 6. У категорий — потолок числа подписей (не задан — 12). Подписи, которые не помещаются по ширине, прореживаются. |
yTickCount | number | undefined | 5 | Желаемое число делений оси значений; по умолчанию 5. Шаг остаётся «круглым», поэтому делений бывает на одно меньше. |
xTickFormat | ChartTickFormat | undefined | undefined | Подпись деления оси X: `(value, kind) => string`. Не задана — категория, дата или число по локали. |
yTickFormat | ((value: number) => string) | undefined | undefined | Подпись деления оси значений, например `value => value + '%'`. Место под ось считается по отформатированной подписи. |
valueFormat | GrChartNumberFormat | undefined | undefined | Формат значений в тултипе, скрытой таблице и объявлениях: знаки после запятой и разделители. |
curve | GrChartCurve | undefined | undefined | Форма линии между точками: `linear` (по умолчанию), `smooth` или `step`. Сглаживание не выдумывает значений между замерами. |
showPoints | "auto" | "never" | "always" | undefined | "auto" | Маркеры точек: `auto` (по умолчанию) — только на нечастом ряде, `always` или `never`. |
showGrid | "y" | "both" | "x" | "none" | undefined | undefined | Линии сетки: `y` (по умолчанию) — по делениям значений, `x`, `both` или `none`. |
showLegend | boolean | "auto" | undefined | undefined | Легенда серий: `auto` (по умолчанию) — когда серий больше одной; `true` и `false` — всегда и никогда. |
legendPosition | "bottom" | "top" | undefined | undefined | Где стоит легенда: `bottom` (по умолчанию) или `top`. |
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 | Описание для скринридера — вывод, ради которого график показан; дополняет сводку, а не заменяет её. |
decimate | "auto" | "never" | "always" | undefined | undefined | Когда прореживать ряд для рисунка (LTTB). `'auto'` включается, только когда точек больше бюджета: выше двух вершин на пиксель SVG всё равно не покажет того, что выбросил бы алгоритм, а платит за это длиной строки `d`. Прорежённые точки идут **только** в рисунок — курсор, клавиатура, тултип и скрытая таблица знают полный ряд. |
maxPoints | number | undefined | undefined | Бюджет точек на серию. Не задан — считается от ширины области построения. |
canvasThreshold | number | undefined | GR_CHART_CANVAS_THRESHOLD | Порог, выше которого маркеры не рисуются даже при `showPoints: 'always'`. На `'auto'` при дефолтах не влияет: тот отсекает марки раньше, на `AUTO_MARKERS_LIMIT`. Влиять начинает, если задать порог ниже него. |
zoom | GrChartZoom | undefined | undefined | Какими жестами пользователь меняет видимое окно. По умолчанию — никакими. Управляется только указателем; клавиатурного пути к окну у графика нет. Поэтому окно и выведено наружу как `v-model:xWindow` — приложение обязано дать к нему свой доступный путь: кнопку сброса, поля диапазона. |
xWindowv-model | readonly [GrChartXValue, GrChartXValue] | null | undefined | undefined | Видимое окно по абсциссе — `v-model:xWindow`. `null` — весь ряд. Окно **выбирает данные**, а не обрезает рисунок: по нему считаются позиции, курсор, клавиатура, скрытая таблица и размах оси значений. Прореживание — противоположный случай, оно рисунку невидимо и полный ряд не трогает. Отсюда же следствие: `activeIndex` адресует текущее окно. |
dataTableMaxRows | number | "auto" | undefined | undefined | Потолок строк скрытой таблицы данных. `'auto'` (по умолчанию) — столько же, сколько вершин в рисунке: выше потолка таблица печатает те же точки, что нарисованы, и говорит об этом пометкой. Число задаёт свой потолок, `Infinity` снимает его совсем, `dataTable: 'off'` убирает таблицу целиком — что из этого нужно, решает приложение. |
gaps | "hidden" | "shadow" | "dashed" | undefined | undefined | Чем закрыть разрыв ряда. `hidden` — ничем: линия рвётся, и это честнее всего. `shadow` и `dashed` рисуют перемычку, заметно отличную от линии, — она показывает, куда ряд ушёл за время без замеров, но собой данными не притворяется. |
seriesобязательный | readonly number[] | readonly GrChartSeries[] | — | Серии либо голый ряд чисел — тогда `x` становится порядковым номером. |
Слоты
| Slot | Type | Описание |
|---|---|---|
tooltip | { active: GrChartActivePoint; formatValue: (value: number | null) => string; } | Своё содержимое панели тултипа вместо стандартного; данные активной позиции — в параметрах слота. |
legend | { series: readonly NormalizedSeries[]; toggle: (id: string) => void; } | Своя легенда вместо стандартной. У пустого графика не рисуется. |
empty | any | Своя заглушка пустого графика вместо «Нет данных»; занимает ту же `height`. |
header | any | Заголовок над графиком, внутри его корня — выше легенды, стоящей сверху. |
События
| 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 | Сигнатура | Описание |
|---|---|---|
element | HTMLElement | null | Корневой элемент — для замеров и скролла в потребителе. |
Типы
Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.
import type { GrChartLineConfigurableProps, GrChartSize, GrChartLineProps } from '@feugene/granularity-charts/components/GrChartLine'Файлdist/types/components/GrChartLine/config.d.ts
GrEmptyState и GrSkeleton объявлены зависимостями потому, что рама их
**рендерит**: без объявления они не попадут в замыкание селекции, и
потребитель получит пустое состояние без стилей и скелет без фона.
group помечает, что рама у графиков общая: шаблоны живут в
src/components/GrChartFrame/shared/ и уезжают в общий чанк, который входит
в файлы каждого дотянувшегося компонента. Ребром графа он не становится —
иначе вынесенный хелпер превращал бы соседей в зависимости друг друга, — а
классы из него сборка извлекает по графу бандла, не требуя перечисления в
safelist.
Ту же группу получают остальные графики: рама у них общая, ради этого
механизм и существует.
const grChartLineConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrChartLine">;Файлdist/types/components/GrChartLine/defaults.d.ts
Пропы GrChartLine, настраиваемые глобально через
<GrConfigProvider :component-defaults="{ GrChartLine: { … } }">.
Только оформление: данные и границы осей задаются на месте.
interface GrChartLineConfigurableProps {
/**
* Когда прореживать ряд для рисунка. Политика рендера — приложение с плотными
* рядами задаёт её один раз на всё поддерево.
*/
decimate: 'auto' | 'always' | 'never';
/** Бюджет точек на серию. */
maxPoints: number;
/**
* Какими жестами меняется видимое окно.
*
* Политика приложения, а не свойство отдельного графика: включать
* приближение на одной панели и не включать на соседней — это разные правила
* поведения у одинаковых с виду картинок.
*/
zoom: GrChartZoom;
/**
* Потолок строк скрытой таблицы данных.
*
* Политика приложения: держать ли полный ряд в дереве доступности, усечь ли
* его до нарисованного или убрать таблицу вовсе — решение уровня продукта, а
* не отдельного графика.
*/
dataTableMaxRows: number | 'auto';
size: GrChartSize;
height: number;
curve: GrChartCurve;
/** Чем закрыть разрыв ряда: ничем, тенью или штрихом. */
gaps: 'hidden' | 'shadow' | 'dashed';
showGrid: 'both' | 'x' | 'y' | 'none';
showLegend: boolean | 'auto';
legendPosition: 'top' | 'bottom';
tooltip: boolean;
dataTable: 'hidden' | 'visible' | 'off';
}Файл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/GrChartLine/safelist.d.ts
const grChartLineSafelist: string[];Файлdist/types/components/GrChartLine/GrChartLine.vue.d.ts
Линейный график: ряд во времени, по числовой оси или по категориям. Рисует только марки — оси, сетку, легенду, тултип, состояния, клавиатуру и скрытую таблицу данных берёт на себя общая рама.
interface GrChartLineProps {
/** Серии либо голый ряд чисел — тогда `x` становится порядковым номером. */
series: readonly GrChartSeries[] | readonly number[];
/** Тип оси X. Не задан — выводится из данных. */
xScale?: GrChartScaleKind;
/**
* Высота холста в пикселях; по умолчанию 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;interface GrChartLineEmits {
/**
* Легенда просит скрыть или показать серию — новый список 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 |
Полный контракт пакета: docs/keyboard.md ↗
Использование из прозы библиотеки
Сглаживание не изобретает значений
curve: 'smooth' — монотонная кубика: она не выбрасывает кривую за диапазон
соседних значений. Обычный кубический сплайн на резком перепаде даёт «горб»
ниже минимума или выше максимума пары — то есть рисует значение, которого в
данных нет. step уместен там, где величина и правда менялась ступенями:
тариф, версия, статус.
Перемычка gaps всегда прямая, даже при smooth. Кривая через пропуск имела бы
форму, то есть показывала бы ход значения там, где его не измеряли.
Марки появляются сами
showPoints: 'auto' рисует марки, пока ряд короткий (до шестидесяти точек). На
длинном ряду они сливаются в сплошную полосу и мешают читать линию, поэтому
пропадают. 'always' на пятидесяти тысячах точек — осознанный выбор
потребителя, и стоит он ровно столько, сколько стоит.
Выше порога рисует холст
Тело графика умеет рисоваться двумя способами. Ниже порога это SVG, выше —
<canvas>; выбирает сам компонент, пропа «каким рендерером» нет.
Порог считается в нарисованных вершинах, а не в точках — canvasThreshold,
по умолчанию 24 000. Разница существенная: прореживание режет каждый ряд
до предела экрана (около двух вершин на пиксель), поэтому один ряд в сто тысяч
точек рисуется как 2400 вершин и стоит миллисекунды, а двадцать рядов по 2400 —
те же 48 000 точек — стоят шестнадцати, то есть целого кадра.
Числа замера при ширине 1200px, 2400 вершин на ряд:
| Рядов | SVG | Холст |
|---|---|---|
| 1 | 1,1 мс | 0,4 мс |
| 8 | 6,7 мс | 0,6 мс |
| 20 | 16,3 мс | 1,7 мс |
canvasThreshold: 0 выключает холст совсем — на случай, когда рисунок обязан
остаться векторным: печать, экспорт SVG, свой CSS поверх марок.
Доступность от смены рендерера не меняется вовсе. Курсор, клавиатура,
тултип и скрытая таблица работают с оверлеем и с полными рядами, а не с марками:
холст для них не существует, он aria-hidden и не ловит указатель. Это и было
условием, при котором второй рендерер вообще допустили.
Что холст рисует иначе. Сетка переезжает в него же: холст лежит под <svg>,
чтобы оси и активная точка остались сверху, — а сетка обязана остаться под
рядами. У площади градиентная заливка становится сплошной: url(#…) холст не
понимает, а на двадцати площадях градиент всё равно читается как каша.
Курсор и клавиатура работают с одним состоянием
activeIndex — v-model: им синхронизируют пару графиков, чтобы курсор в
одном подсвечивал ту же абсциссу в другом. hiddenSeries — тоже v-model, но
компонент его не применяет сам: легенда излучает намерение, состояние
принадлежит потребителю.
tooltip: false прячет только панель. Курсор под указателем и с
клавиатуры, активные марки, update:activeIndex и pointHover остаются: у
второго графика пары панель часто лишняя, а курсор нужен — он и показывает ту
же абсциссу.
interactive: false превращает график в картинку: role="img" с именем, без
фокуса, тултипа и клавиатуры. Это режим для печати и для плитки, по которой
кликают целиком.
Скрытая таблица — не запасной вариант
dataTable: 'hidden' (по умолчанию) держит полные данные таблицей в дереве
доступности, но не на экране. Скринридер читает значения, а не «график».
'visible' показывает её же под холстом, 'off' убирает совсем — и это
осознанный отказ от доступности данных, а не оптимизация.
Приближение по абсциссе
zoom включает окно: 'brush' — протяжка по холсту, 'wheel' — колесо,
'both' — оба. По умолчанию выключено.
<GrChartLine 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', то есть бюджет рисунка:
<GrChartLine :series="series" /> <!-- auto: как нарисовано -->
<GrChartLine :series="series" :data-table-max-rows="200" /> <!-- свой потолок -->
<GrChartLine :series="series" :data-table-max-rows="Infinity" /> <!-- всегда полная -->
<GrChartLine :series="series" data-table="off" /> <!-- таблицы нет вовсе -->Выше потолка таблица печатает те же точки, что нарисованы, и говорит об этом пометкой в подвале. Поточечная
полнота при этом не теряется: стрелки обходят весь ряд и проговаривают каждую точку. Таблица отвечает за обзор,
клавиатура — за точное значение; разбор — ../model.md.
'auto' — это «столько строк, сколько можно прочитать». Бюджет рисунка берётся, когда он есть; при
decimate: 'never' его нет, и остаётся фиксированный потолок с равномерной выборкой.
Что из этого нужно, решает приложение: держать десять тысяч строк в дереве доступности — его право, но и цена перестроения такой таблицы на нём.
Порог рисуется опорой, а не серией
План, норма и граница допустимого — это проп references, а не ряд из константы: серия-константа попала бы в
легенду, растянула бы ось и уехала бы в таблицу как данные. Разбор — ../model.md, раздел
«Опора — не серия».
Две оси — осознанное решение
Ряды разного порядка (деньги и штуки) читаются на одном графике только с двумя
осями, а две оси позволяют подогнать любую пару под видимую корреляцию. Поэтому
axis: 'right' у серии не работает, пока не включён dualAxis. Инварианты —
../model.md, раздел «Вторая ось значений».
Пропы этого примера