GrChartBullet

  • charts
  • Графики
  • API · 23 props

Берут, когда метрика с порогами, которые уже есть в данных.

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

Обзор

Когда брать

  • метрика с порогами, которые уже есть в данных — warning и critical приходят с бэкенда и обычно нигде не нарисованы: видно «плохо», не видно «насколько»;
  • план и факт рядом — засечка цели и полоса значения читаются одним взглядом, без вычитания в уме;
  • несколько метрик подряд — bullet занимает строку, и десяток метрик встаёт списком, сравнимым по вертикали;
  • место дорого — карточка, строка таблицы, шапка дашборда: тот же смысл, что у циферблата, при впятеро меньшей площади;
  • величина может отсутствовать — value: null рисует цель и диапазоны без полосы, а не ноль.

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

НужноБерите
Сравнить величины нескольких категорий между собойGrChartBar
Показать ход величины во времениGrChartLine
Показать долю выполнения одной задачиGrProgressCircle
Показать одно число крупно, с динамикойGrStatistic

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

  • Цели задержки, сервис на строку

    ranges красят дорожку полосами — хорошо, терпимо, плохо, — а target рисует поперёк цель: «182» само по себе мало что говорит, «182 из 200, в хорошей полосе» — всё. Bullet занимает строку, и таблица сервисов встаёт там, где стоял бы один циферблат. Сервис без трафика получает value: null: засечка и полосы остаются, полосы значения нет — нет данных не значит ноль. value-click открывает сервис.

    Latency objectives · p95, last hourThe mark is the objective; the bands are good, tolerable and bad
    Checkout API
    Данные графика
    ПоказательЗначениеЦель
    Checkout API182200
    Диапазоны: от 0 до 200; от 200 до 300; от 300 до 400
    182 ms
    Search
    Данные графика
    ПоказательЗначениеЦель
    Search96150
    Диапазоны: от 0 до 150; от 150 до 250; от 250 до 300
    96 ms
    Invoices
    Данные графика
    ПоказательЗначениеЦель
    Invoices341250
    Диапазоны: от 0 до 250; от 250 до 350; от 350 до 450
    341 ms
    Exports
    Данные графика
    ПоказательЗначениеЦель
    Exportsнет значения900
    Диапазоны: от 0 до 900; от 900 до 1 500; от 1 500 до 2 000
    no traffic

    Click a bar to open the service.

  • План продаж, где больше — лучше

    Полосы по умолчанию идут от «хорошо» к «плохо» с ростом величины — верно для задержки, неверно для плана. range-colors красит их полоса за полосой: красный до 70 %, янтарный до плана, зелёный выше. color задаёт тон самой полосы значения, а size="xs" укладывает четверых в маленькую карточку.

    Quota attainment · OctoberTarget is 100 % of the personal quota
    Grace Wu114%
    Данные графика
    ПоказательЗначениеЦель
    Grace Wu1,141,00
    Диапазоны: от 0,00 до 0,70; от 0,70 до 1,00; от 1,00 до 1,30
    Omar Haddad96%
    Данные графика
    ПоказательЗначениеЦель
    Omar Haddad0,961,00
    Диапазоны: от 0,00 до 0,70; от 0,70 до 1,00; от 1,00 до 1,30
    Ines Novak81%
    Данные графика
    ПоказательЗначениеЦель
    Ines Novak0,811,00
    Диапазоны: от 0,00 до 0,70; от 0,70 до 1,00; от 1,00 до 1,30
    Linus Ray58%
    Данные графика
    ПоказательЗначениеЦель
    Linus Ray0,581,00
    Диапазоны: от 0,00 до 0,70; от 0,70 до 1,00; от 1,00 до 1,30
  • Бюджет, который уже превышен

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

    Budgets after nine monthsThe mark is the share of the year gone — 75 % of the budget
    Engineering€1420k of €2000k
    Данные графика
    ПоказательЗначениеЦель
    Engineering1 4201 500
    Диапазоны: от 0 до 1 500; от 1 500 до 1 800; от 1 800 до 2 000
    Marketing€980k of €800k
    Данные графика
    ПоказательЗначениеЦель
    Marketing980600
    Диапазоны: от 0 до 600; от 600 до 720; от 720 до 800
    Sales€610k of €900k
    Данные графика
    ПоказательЗначениеЦель
    Sales610675
    Диапазоны: от 0 до 675; от 675 до 810; от 810 до 900
  • Плитки складов снизу вверх

    У узкой плитки дашборда есть высота, а ширины нет: orientation="vertical" сохраняет ту же величину, цель и полосы, только читаются они снизу вверх. label называет метрику для тултипа и таблицы данных, а подпись на плитке пишет страница — она знает, где плитка стоит.

    Warehouse capacityPallet places in use · target 80 %
    Данные графика
    ПоказательЗначениеЦель
    Hamburg812800
    Диапазоны: от 0 до 850; от 850 до 950; от 950 до 1 000
    Hamburg81% full812 of 1000
    Данные графика
    ПоказательЗначениеЦель
    Bremen640720
    Диапазоны: от 0 до 765; от 765 до 855; от 855 до 900
    Bremen71% full640 of 900
    Данные графика
    ПоказательЗначениеЦель
    Rotterdam1 170960
    Диапазоны: от 0 до 1 020; от 1 020 до 1 140; от 1 140 до 1 200
    Rotterdam98% full1170 of 1200

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

Props

PropTypeпо умолчаниюОписание
heightnumber | undefinedundefinedВысота холста в пикселях; по умолчанию 48. Данные, загрузка и пустое состояние занимают её одинаково.
widthnumber | undefined640Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется.
valueFormatGrChartNumberFormat | undefinedundefinedФормат значений в тултипе, скрытой таблице и объявлениях: знаки после запятой и разделители.
tooltipboolean | undefinedundefinedПанель со значениями под курсором; по умолчанию `true`. `false` прячет только панель: курсор, активные марки, `update:activeIndex` и `pointHover` работают, пока график интерактивен.
loadingboolean | undefinedfalseЗагрузка: на месте области построения — скелет той же высоты, место под оси уже зарезервировано.
emptyboolean | undefinedundefinedПустое состояние явно. Не задано — график пуст, когда рисовать нечего.
emptyTextstring | undefinedundefinedТекст заглушки пустого графика. Не задан — «Нет данных» из переводов пакета.
dataTable"hidden" | "visible" | "off" | undefinedundefinedТаблица данных: `hidden` (по умолчанию) — только для скринридера, `visible` — под графиком, `off` — без неё.
interactiveboolean | undefinedtrue`false` — график становится картинкой: без фокуса, тултипа и клавиатуры.
size"md" | "xs" | "sm" | "lg" | undefinedundefinedКегль подписей по контрольной шкале ядра; по умолчанию — размер из `GrConfigProvider`.
localestring | undefinedundefinedBCP-47 локаль чисел и дат. Не задана — язык переводов приложения, иначе `en`.
ariaLabelstring | undefinedundefinedДоступное имя графика. Не задано — сводка из `label`, величины и цели.
ariaDescriptionstring | undefinedundefinedОписание для скринридера — вывод, ради которого график показан; дополняет сводку, а не заменяет её.
dataTableMaxRowsnumber | "auto" | undefinedundefinedПотолок строк скрытой таблицы данных. `'auto'` (по умолчанию) — столько строк, сколько можно прочитать; выше потолка таблица идёт равномерной выборкой и говорит об этом пометкой, а поточечная полнота остаётся за клавиатурой. Число задаёт свой потолок, `Infinity` снимает его совсем, `dataTable: 'off'` убирает таблицу целиком — что из этого нужно, решает приложение.

Слоты

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

События

EventСигнатураОписание
valueClick(event: "valueClick", value: number | null): void—

Методы

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

Типы

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

Импорт
import type { GrChartBulletConfigurableProps, GrChartSize, GrChartBulletProps } from '@feugene/granularity-charts/components/GrChartBullet'

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

grChartBulletConfigconst

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

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

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

GrChartBulletConfigurablePropsinterface

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

interface GrChartBulletConfigurableProps {
    size: GrChartSize;
    height: number;
    orientation: 'horizontal' | 'vertical';
    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/GrChartBullet/safelist.d.ts

grChartBulletSafelistconst
const grChartBulletSafelist: string[];

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

GrChartBulletPropsinterface

Bullet Стивена Фью: величина, цель и качественные диапазоны в одну строку. Три разных визуальных веса, чтобы они не спорили: диапазоны — фон, значение — узкая полоса поверх, цель — засечка поперёк. Циферблата в пакете нет намеренно: он тратит много места на мало данных и плохо читается количественно. Роль оверлея здесь meter, а не application: это не приложение со своей картой клавиш, а величина на шкале. Роль исчезает вместе со значением — meter без aria-valuenow невалиден.

interface GrChartBulletProps {
    /** Измеряемая величина. `null` — величины нет; это не ноль. */
    value: number | null;
    /** Целевое значение — засечка поперёк дорожки. */
    target?: number;
    /**
     * Границы качественных диапазонов: `[0.9, 1.0]` при `max: 1.2` даёт три
     * полосы — до 0.9, 0.9…1.0 и свыше 1.0. Порядок не важен.
     */
    ranges?: readonly number[];
    /** Верх шкалы. Не задан — максимум из величины, цели и границ с запасом. */
    max?: number;
    /** Низ шкалы; по умолчанию 0. Полоса bullet без нуля искажает длину — величину читают по ней. */
    min?: number;
    /** Цвета полос от «хорошо» к «плохо». Длина на единицу больше `ranges`. */
    rangeColors?: readonly string[];
    /** Тон самой полосы значения. */
    color?: string;
    /** Направление дорожки: `horizontal` (по умолчанию) или `vertical`. */
    orientation?: 'horizontal' | 'vertical';
    /** Имя метрики: заголовок строки в таблице и в тултипе. */
    label?: string;
    /**
     * Высота холста в пикселях; по умолчанию 48. Данные, загрузка и пустое состояние занимают её
     * одинаково.
     */
    height?: number;
    /** Объявленная ширина: от неё идёт первый рендер, дальше ширина замеряется. */
    width?: number;
    /**
     * Формат значений в тултипе, скрытой таблице и объявлениях: знаки после запятой и разделители.
     */
    valueFormat?: GrChartNumberFormat;
    /**
     * Панель со значениями под курсором; по умолчанию `true`. `false` прячет только
     * панель: курсор, активные марки, `update:activeIndex` и `pointHover` работают,
     * пока график интерактивен.
     */
    tooltip?: boolean;
    /**
     * Загрузка: на месте области построения — скелет той же высоты, место под оси уже
     * зарезервировано.
     */
    loading?: boolean;
    /** Пустое состояние явно. Не задано — график пуст, когда рисовать нечего. */
    empty?: boolean;
    /** Текст заглушки пустого графика. Не задан — «Нет данных» из переводов пакета. */
    emptyText?: string;
    /**
     * Таблица данных: `hidden` (по умолчанию) — только для скринридера, `visible` — под графиком,
     * `off` — без неё.
     */
    dataTable?: 'hidden' | 'visible' | 'off';
    /** `false` — график становится картинкой: без фокуса, тултипа и клавиатуры. */
    interactive?: boolean;
    /** Кегль подписей по контрольной шкале ядра; по умолчанию — размер из `GrConfigProvider`. */
    size?: GrChartSize;
    /** BCP-47 локаль чисел и дат. Не задана — язык переводов приложения, иначе `en`. */
    locale?: string;
    /** Доступное имя графика. Не задано — сводка из `label`, величины и цели. */
    ariaLabel?: string;
    /**
     * Описание для скринридера — вывод, ради которого график показан; дополняет сводку, а не заменяет
     * её.
     */
    ariaDescription?: string;
    /**
     * Потолок строк скрытой таблицы данных.
     *
     * `'auto'` (по умолчанию) — столько строк, сколько можно прочитать; выше
     * потолка таблица идёт равномерной выборкой и говорит об этом пометкой, а
     * поточечная полнота остаётся за клавиатурой. Число задаёт свой потолок,
     * `Infinity` снимает его совсем, `dataTable: 'off'` убирает таблицу целиком —
     * что из этого нужно, решает приложение.
     */
    dataTableMaxRows?: number | 'auto';
}
GrChartBulletEmitsinterface
interface GrChartBulletEmits {
    /** Щелчок или Enter по графику; в событии — сама величина. */
    (e: 'valueClick', value: number | null): void;
}

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

Циферблата в пакете нет намеренно

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

Три веса, которые не спорят

Диапазоны — фон, значение — узкая полоса поверх, цель — засечка поперёк. Толщина полосы значения задаётся токеном --gr-chart-bullet-value-width, а не геометрией: полоса рисуется <line>, и тема меняет её вес, не трогая дорожку.

Свои цвета диапазонов задаёт rangeColors — по одному на полосу, от «хорошо» к «плохо». Полос всегда на одну больше, чем границ, даже если граница вышла за шкалу: такая граница зажимается, а не выбрасывается, иначе цвета уехали бы на соседние полосы.

value: null — не ноль

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

Вместе со значением исчезает и роль meter: она требует aria-valuenow, а без величины его нет — оставленная роль дала бы нарушение уровня serious.

Значение за шкалой не обрезается молча

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

Роль — meter, а не progressbar

progressbar — про выполнение задачи, которое заканчивается; здесь величина, которая просто есть. slider не подходит тем более: значение не редактируется. Помимо aria-valuenow/valuemin/valuemax оверлей несёт aria-valuetext с человеческой формулировкой («0,031 из 0,05, цель 0,04») — одно число без единиц и без цели сказало бы меньше, чем видит зрячий. Диапазоны объявляются в описании графика: цветные зоны иначе существуют только для зрячих.

Границы

Нескольких значений в одной дорожке нет: bullet сравнивает одну величину с одной целью. Стрелку и «набор» значения анимацией компонент тоже не рисует.