GrChartBar
Берут, когда сколько у каждой категории.
Обзор
Когда брать
- сколько у каждой категории — заказы по регионам, ошибки по сервисам, выручка по товарам: столбцы сравниваются глазом точнее любого другого типа;
- две-три метрики на категорию рядом — серии встают группой,
groupPaddingразводит группы; - состав каждой категории —
stackedскладывает серии в один столбец; - доли внутри категории —
stacked: '100%'нормирует столбец к единице, и сравниваются пропорции, а не величины; - длинные названия категорий —
orientation: 'horizontal'кладёт полосы вбок, и подпись читается строкой, а не наклонным хвостом; - категорий немного — вертикаль держит полтора-два десятка, дальше подписи оси налезают друг на друга; горизонталь считает не ширину, а высоту, и спокойно берёт три-четыре десятка строк.
Когда взять другое
| Нужно | Берите |
|---|---|
| Показать ход значения во времени | GrChartLine |
| Показать целое и вклад частей во времени | GrChartArea |
| Показать состав одного целого | GrChartPie |
| Сравнить форму профиля по нескольким осям | GrChartRadar |
| Сравнить строки таблицы между собой | GrDataTable + GrSparkline |
Категорий сотни — это не график, а список: место кончается раньше, чем у читателя терпение (у вертикали — ширина, у горизонтали — высота страницы). Такие данные показывают таблицей с сортировкой, а верхушку — столбцами.
Примеры 4 у каждого — живое демо и его код
План и факт рядом
Группа столбцов отвечает «сколько у каждого»: оба начинаются с нуля, и разница читается по высоте, а стопка дала бы план плюс факт — число без смысла.
group-paddingразводит регионы,bar-radiusскругляет только дальний конец, аpoint-clickоткрывает регион в панели под графиком.Q3 sales: plan against factThousands of euros by region · click a region Данные графика X Plan Fact North 120 132 South 95 88 East 80 84 West 110 97 Central 70 76 NorthPlan €120kFact €132k+10,0%Из чего состоит каждая неделя
stackedделает высоту столбца итогом недели, а сегменты — каналами: переход с почты на чат виден без второго графика. Другая команда загружается заново, иloadingдержит рамку вместо старого рисунка;show-grid="y"оставляет только те линии, по которым читают.Support tickets per weekChat grows while email and phone shrinkДанные графика X Email Chat Phone W33 212 140 64 W34 198 156 58 W35 230 171 61 W36 245 168 55 W37 221 190 52 W38 205 214 49 W39 190 236 44 W40 184 251 41 Длинные названия ложатся набок
orientation="horizontal"даёт «Delivered after the promised date» целую строку вместо наклонного обрезанного хвоста и держит рейтинг сверху вниз. В рейтинге важен каждый столбец, поэтомуdim-inactiveвыключен; одной серии легенда не нужна, а слотtooltipдобавляет долю от всех возвратов.Refund reasons, September188 refunds, most frequent first Данные графика X Refunds Ordered by mistake 64 Arrived damaged in transit 41 Customer changed their mind 37 Colour differs from the photo 22 Missing parts or screws 15 Delivered after the promised date 9 Деньги и штуки на двух осях
Тысячи евро и тысячи заказов не делят шкалу.
dual-axisсy-domain-rightиy-tick-format-rightуводит заказы направо; это осознанно, потому что двумя осями можно подогнать любую пару под корреляцию, — оба домена начинаются с нуля. Только вертикаль: у горизонтали нет места под вторую ось значений.Revenue and ordersOrders grow faster than revenue: the average basket is getting smaller Данные графика X Revenue, € thousands (левая ось) Orders (правая ось) Apr 312 1,840 May 338 1,910 Jun 351 1,875 Jul 372 2,050 Aug 386 2,190 Sep 399 2,240
API порождается из пакета · разойтись не может
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
xScale | GrChartScaleKind | undefined | undefined | Тип оси X. Не задан — выводится из данных; для столбцов это обычно категории. |
stacked | boolean | "100%" | undefined | false | `true` — стопка, `'100%'` — стопка с нормировкой столбца к единице. |
height | number | undefined | undefined | Высота холста в пикселях; по умолчанию 256. Данные, загрузка и пустое состояние занимают её одинаково. |
width | number | undefined | 640 | Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется. |
yDomain | readonly [number | null, number | null] | undefined | undefined | Границы оси значений `[min, max]`; `null` на месте границы — она берётся по данным. |
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 | Формат значений в тултипе, скрытой таблице и объявлениях: знаки после запятой и разделители. |
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 | Описание для скринридера — вывод, ради которого график показан; дополняет сводку, а не заменяет её. |
dataTableMaxRows | number | "auto" | undefined | undefined | Потолок строк скрытой таблицы данных. `'auto'` (по умолчанию) — столько строк, сколько можно прочитать; выше потолка таблица идёт равномерной выборкой и говорит об этом пометкой, а поточечная полнота остаётся за клавиатурой. Число задаёт свой потолок, `Infinity` снимает его совсем, `dataTable: 'off'` убирает таблицу целиком — что из этого нужно, решает приложение. |
orientation | ChartOrientation | undefined | undefined | Раскладка столбцов. `'horizontal'` — категории идут сверху вниз, значения вправо. Берут её ради подписей: длинное название категории в вертикальной раскладке либо наезжает на соседнее, либо встаёт наискось. Оси и после поворота называются **по данным**: `yDomain`, `yTickFormat` и `showGrid: 'y'` относятся к оси значений, где бы она ни лежала. Алиасов нет намеренно — два пропа с одним смыслом хуже одного неточного имени. |
groupPadding | number | undefined | undefined | Доля ширины слота, уходящая в зазор между сериями внутри категории. |
barRadius | number | undefined | undefined | Скругление дальнего от базовой линии конца полосы, пиксели. |
dimInactive | boolean | undefined | undefined | Гасить полосы неактивных категорий при наведении. `false` — активная категория ничем не выделяется, о ней говорит только тултип. Это осмысленно там, где график стоит рядом с легендой или таблицей и лишнее движение цвета мешает читать соседей. |
seriesобязательный | readonly number[] | readonly GrChartSeries[] | — | Серии либо голый ряд чисел — тогда категорией становится порядковый номер. |
Слоты
| 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 | — |
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 { GrChartBarConfigurableProps, GrChartSize, GrChartBarProps } from '@feugene/granularity-charts/components/GrChartBar'Файлdist/types/components/GrChartBar/config.d.ts
group тот же, что у остальных графиков: рама у них общая, и её чанк тоже.
Ребром графа компонентов общий чанк не становится — иначе вынесенный хелпер
превращал бы соседей в зависимости друг друга; в файлы каждого дотянувшегося
компонента он входит, и классы рамы сборка извлекает оттуда сама.
GrEmptyState и GrSkeleton объявлены зависимостями потому, что рама их
**рендерит**: без объявления они не попадут в замыкание селекции, и
приложение не подмешает их CSS.
const grChartBarConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrChartBar">;Файлdist/types/components/GrChartBar/defaults.d.ts
Пропы GrChartBar, настраиваемые глобально через
<GrConfigProvider :component-defaults="{ GrChartBar: { … } }">.
Только оформление: данные, режим стека и границы осей задаются на месте.
interface GrChartBarConfigurableProps {
size: GrChartSize;
/**
* Раскладка столбцов. Настраивается глобально, потому что решение сквозное:
* приложение с длинными названиями категорий выбирает горизонталь один раз.
*/
orientation: ChartOrientation;
height: number;
/** Скругление дальнего конца полосы в пикселях: радиус идёт в геометрию пути, а не в CSS. */
barRadius: number;
groupPadding: number;
/** Гасить полосы неактивных категорий при наведении. */
dimInactive: boolean;
showGrid: 'both' | 'x' | 'y' | 'none';
showLegend: boolean | 'auto';
legendPosition: 'top' | 'bottom';
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/GrChartBar/safelist.d.ts
const grChartBarSafelist: string[];Файлdist/types/components/GrChartBar/GrChartBar.vue.d.ts
Столбцы: величины по категориям — рядом, стопкой или долями до ста процентов. Ось значений у столбцов **всегда** начинается от нуля: высота полосы это и есть величина, и обрезанная снизу ось врёт о ней в разы. У линии обрезать можно — она показывает изменение, а не величину.
interface GrChartBarProps {
/** Серии либо голый ряд чисел — тогда категорией становится порядковый номер. */
series: readonly GrChartSeries[] | readonly number[];
/** Тип оси X. Не задан — выводится из данных; для столбцов это обычно категории. */
xScale?: GrChartScaleKind;
/**
* Раскладка столбцов.
*
* `'horizontal'` — категории идут сверху вниз, значения вправо. Берут её ради
* подписей: длинное название категории в вертикальной раскладке либо наезжает
* на соседнее, либо встаёт наискось.
*
* Оси и после поворота называются **по данным**: `yDomain`, `yTickFormat` и
* `showGrid: 'y'` относятся к оси значений, где бы она ни лежала. Алиасов нет
* намеренно — два пропа с одним смыслом хуже одного неточного имени.
*/
orientation?: ChartOrientation;
/** `true` — стопка, `'100%'` — стопка с нормировкой столбца к единице. */
stacked?: boolean | '100%';
/** Доля ширины слота, уходящая в зазор между сериями внутри категории. */
groupPadding?: number;
/** Скругление дальнего от базовой линии конца полосы, пиксели. */
barRadius?: number;
/**
* Гасить полосы неактивных категорий при наведении.
*
* `false` — активная категория ничем не выделяется, о ней говорит только
* тултип. Это осмысленно там, где график стоит рядом с легендой или таблицей
* и лишнее движение цвета мешает читать соседей.
*/
dimInactive?: boolean;
/**
* Высота холста в пикселях; по умолчанию 256. Данные, загрузка и пустое состояние занимают её
* одинаково.
*/
height?: number;
/** Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется. */
width?: number;
/** Границы оси значений `[min, max]`; `null` на месте границы — она берётся по данным. */
yDomain?: readonly [number | null, number | null];
/**
* Опорные линии и полосы: порог, план, коридор допустимого.
*
* Не серия и нигде ею не считается: в легенду не попадает, индекс палитры не
* тратит, в стек не входит и в скрытую таблицу уезжает примечанием, а не
* строкой данных.
*/
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 GrChartBarEmits {
/**
* Легенда просит скрыть или показать серию — новый список id; применяет его потребитель
* (`v-model:hiddenSeries`).
*/
(e: 'update:hiddenSeries', value: string[]): void;
/** Курсор сдвинулся: индекс позиции или `null` — для `v-model:activeIndex`. */
(e: 'update:activeIndex', value: number | null): void;
/** Щелчок или Enter по активной позиции — со значениями всех видимых серий в ней. */
(e: 'pointClick', value: GrChartActivePoint): void;
/** Курсор пришёл на позицию или ушёл с графика (`null`). */
(e: 'pointHover', value: GrChartActivePoint | null): void;
/** Нажат пункт легенды: какая серия и в какое состояние просится. */
(e: 'legendToggle', value: {
seriesId: string;
hidden: boolean;
}): void;
}Доступность
Паттерн APG
Серия читается той же парой стрелок; активная категория подсвечивается приглушением соседей
| Клавиша | Действие |
|---|---|
| — | категория |
Полный контракт пакета: docs/keyboard.md ↗
Использование из прозы библиотеки
Горизонталь: полосы вбок, имена осей по данным
<GrChartBar :series="series" orientation="horizontal" aria-label="Выручка по товарам" />Меняется только раскладка: стек, группа, '100%', опоры, легенда, тултип и
скрытая таблица работают ровно так же.
Оси называются по данным, а не по экрану. yDomain, yTickFormat,
yTickCount — всегда ось значений, где бы она ни лежала; xTickFormat — всегда
подписи категорий. При showGrid: 'y' линии значений на горизонтали идут
вертикально: сетка следует за своей осью, а не за направлением на экране.
Альтернатива — завести парные horizontalTickFormat и прочее — дала бы два
пропа с одним смыслом и вопрос «а какой из них сейчас главный» на каждой
странице. Одно неточное имя дешевле двух точных.
Клавиатура следует за глазами: ↓/↑ идут по категориям сверху вниз,
←/→ переключают читаемую серию внутри категории.
Имя категории читается строкой. Колонка подписей растёт по самому длинному
отформатированному имени — до 40% ширины графика, но не уже 96px. Многоточие
появляется только за этим потолком, а полное имя остаётся в <title> подписи,
в тултипе и в скрытой таблице.
Чего горизонталь не умеет
dualAxis при orientation: 'horizontal' выключен: вторая ось значений стала
бы верхней, а верхнего поля у раскладки нет. Проп не падает — он просто не
действует, все серии садятся на одну ось, и в dev выводится предупреждение.
Нужны две шкалы — берите вертикаль.
Ноль на оси не отключается
includeZero у столбцов нечего выключать: полоса рисуется от базовой линии, и
если база не ноль, длина полосы перестаёт быть величиной. У линии выбор есть —
там читают ход, а не длину.
Приглушение вместо скрытия
dimInactive гасит неактивные серии под курсором вместо того, чтобы убирать
их. Убранная серия сдвинула бы соседей и изменила ширину полос — рисунок
«поехал» бы от одного движения мыши.
Порог рисуется опорой, а не серией
План, норма и граница допустимого — это проп references, а не ряд из константы: серия-константа попала бы в
легенду, растянула бы ось и уехала бы в таблицу как данные. Разбор — ../model.md, раздел
«Опора — не серия».
Стопка и группа — разные вопросы
Группа отвечает «сколько у каждого», стопка — «из чего состоит каждый». Смешивать их в одном графике нельзя: читатель не знает, сравнивать ему высоты столбцов или высоты сегментов. Нужны оба ответа — это два графика.
Две оси — осознанное решение
Ряды разного порядка (деньги и штуки) читаются на одном графике только с двумя
осями, а две оси позволяют подогнать любую пару под видимую корреляцию. Поэтому
axis: 'right' у серии не работает, пока не включён dualAxis. Инварианты —
../model.md, раздел «Вторая ось значений».
Пропы этого примера