GrChartBar

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

Берут, когда сколько у каждой категории.

import { GrChartBar } from '@feugene/granularity-charts/components/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
    Данные графика
    XPlanFact
    North120132
    South9588
    East8084
    West11097
    Central7076
    NorthPlan €120kFact €132k+10,0%
  • Из чего состоит каждая неделя

    stacked делает высоту столбца итогом недели, а сегменты — каналами: переход с почты на чат виден без второго графика. Другая команда загружается заново, и loading держит рамку вместо старого рисунка; show-grid="y" оставляет только те линии, по которым читают.

    Support tickets per weekChat grows while email and phone shrink
    Данные графика
    XEmailChatPhone
    W3321214064
    W3419815658
    W3523017161
    W3624516855
    W3722119052
    W3820521449
    W3919023644
    W4018425141
  • Длинные названия ложатся набок

    orientation="horizontal" даёт «Delivered after the promised date» целую строку вместо наклонного обрезанного хвоста и держит рейтинг сверху вниз. В рейтинге важен каждый столбец, поэтому dim-inactive выключен; одной серии легенда не нужна, а слот tooltip добавляет долю от всех возвратов.

    Refund reasons, September188 refunds, most frequent first
    Данные графика
    XRefunds
    Ordered by mistake64
    Arrived damaged in transit41
    Customer changed their mind37
    Colour differs from the photo22
    Missing parts or screws15
    Delivered after the promised date9
  • Деньги и штуки на двух осях

    Тысячи евро и тысячи заказов не делят шкалу. dual-axis с y-domain-right и y-tick-format-right уводит заказы направо; это осознанно, потому что двумя осями можно подогнать любую пару под корреляцию, — оба домена начинаются с нуля. Только вертикаль: у горизонтали нет места под вторую ось значений.

    Revenue and ordersOrders grow faster than revenue: the average basket is getting smaller
    Данные графика
    XRevenue, € thousands (левая ось)Orders (правая ось)
    Apr3121,840
    May3381,910
    Jun3511,875
    Jul3722,050
    Aug3862,190
    Sep3992,240

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

Props

PropTypeпо умолчаниюОписание
xScaleGrChartScaleKind | undefinedundefinedТип оси X. Не задан — выводится из данных; для столбцов это обычно категории.
stackedboolean | "100%" | undefinedfalse`true` — стопка, `'100%'` — стопка с нормировкой столбца к единице.
heightnumber | undefinedundefinedВысота холста в пикселях; по умолчанию 256. Данные, загрузка и пустое состояние занимают её одинаково.
widthnumber | undefined640Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется.
yDomainreadonly [number | null, number | null] | undefinedundefinedГраницы оси значений `[min, max]`; `null` на месте границы — она берётся по данным.
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). Подписи, которые не помещаются по ширине, прореживаются.
yTickCountnumber | undefined5Желаемое число делений оси значений; по умолчанию 5. Шаг остаётся «круглым», поэтому делений бывает на одно меньше.
xTickFormatChartTickFormat | undefinedundefinedПодпись деления оси X: `(value, kind) => string`. Не задана — категория, дата или число по локали.

Слоты

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—
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 { GrChartBarConfigurableProps, GrChartSize, GrChartBarProps } from '@feugene/granularity-charts/components/GrChartBar'

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

grChartBarConfigconst

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

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

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

GrChartBarConfigurablePropsinterface

Пропы 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

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/GrChartBar/safelist.d.ts

grChartBarSafelistconst
const grChartBarSafelist: string[];

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

GrChartBarPropsinterface

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

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;
GrChartBarEmitsinterface
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

Серия читается той же парой стрелок; активная категория подсвечивается приглушением соседей

КлавишаДействие
—категория

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

Горизонталь: полосы вбок, имена осей по данным
vue
<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, раздел «Вторая ось значений».