GrChartBullet
Берут, когда метрика с порогами, которые уже есть в данных.
Когда брать
- метрика с порогами, которые уже есть в данных —
warningиcriticalприходят с бэкенда и обычно нигде не нарисованы: видно «плохо», не видно «насколько»; - план и факт рядом — засечка цели и полоса значения читаются одним взглядом, без вычитания в уме;
- несколько метрик подряд — bullet занимает строку, и десяток метрик встаёт списком, сравнимым по вертикали;
- место дорого — карточка, строка таблицы, шапка дашборда: тот же смысл, что у циферблата, при впятеро меньшей площади;
- величина может отсутствовать —
value: nullрисует цель и диапазоны без полосы, а не ноль.
Когда взять другое
| Нужно | Берите |
|---|---|
| Сравнить величины нескольких категорий между собой | GrChartBar |
| Показать ход величины во времени | GrChartLine |
| Показать долю выполнения одной задачи | GrProgressCircle |
| Показать одно число крупно, с динамикой | GrStatistic |
Циферблата в пакете нет намеренно
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 сравнивает одну величину с одной целью. Стрелку и «набор» значения анимацией компонент тоже не рисует.
Установка
npm i @feugene/granularity-chartsИмпорт
import { GrChartBullet } from '@feugene/granularity-charts/components/GrChartBullet'API
API этого компонента ещё не посчитан: генератор витрины пока обходит только ядро. Пока его нет, справочник — в документации пакета.
Примеры 2
Basic
<script setup lang="ts">
/**
* Bullet берут вместо числа с бейджем: бейдж говорит «плохо», bullet — насколько
* плохо и далеко ли до следующей границы.
*
* Несколько метрик подряд сравниваются по вертикали — циферблаты так не умеют.
*/
const metrics = [
{ label: 'Себестоимость кредита, $', value: 0.031, target: 0.04, ranges: [0.03, 0.04], max: 0.05 },
{ label: 'Конверсия в оплату, %', value: 12.4, target: 15, ranges: [8, 15], max: 20 },
{ label: 'Время ответа поддержки, ч', value: 6.2, target: 4, ranges: [4, 8], max: 12 },
]
const rangeColors = ['var(--gr-success)', 'var(--gr-warning)', 'var(--gr-danger)']
</script>
<template>
<div class="grid gap-4">
<div v-for="metric in metrics" :key="metric.label" class="grid gap-1">
<span class="text-[length:var(--gr-control-text-sm)] text-[var(--gr-muted-fg)]">
{{ metric.label }}
</span>
<GrChartBullet
:value="metric.value"
:target="metric.target"
:ranges="metric.ranges"
:max="metric.max"
:range-colors="rangeColors"
:label="metric.label"
:height="44"
/>
</div>
<p class="text-[length:var(--gr-control-text-sm)] text-[var(--gr-muted-fg)]">
Три разных визуальных веса, чтобы они не спорили: диапазоны — фон, значение — узкая полоса
поверх, цель — засечка поперёк. Роль оверлея здесь <code>meter</code>, и
<code>aria-valuetext</code> читается как «0,031 из 0,05, цель 0,04» — одно число без единиц
и без цели сказало бы меньше, чем видит зрячий.
</p>
</div>
</template>States
<script setup lang="ts">
import { ref } from 'vue'
/**
* Два крайних случая, о которых обычно забывают: величины нет вовсе и величина
* вышла за шкалу.
*
* Ни то, ни другое нельзя показать нулём или обрезанной полосой — оба варианта
* нарисовали бы число, которого в данных нет.
*/
const state = ref<'normal' | 'missing' | 'overflow'>('missing')
const value = { normal: 0.031, missing: null, overflow: 0.12 }
const hint = {
normal: 'Обычный случай: полоса внутри шкалы, цель рядом.',
missing: 'Нет managed-списаний — нет и себестоимости. Полосы нет, цель на месте, в таблице прочерк. Роль `meter` при этом снимается: без `aria-valuenow` она невалидна.',
overflow: 'Значение за верхом шкалы: полоса упирается в край и получает маркер переполнения, а настоящая величина уходит в тултип, таблицу и объявление.',
}
</script>
<template>
<div class="grid gap-3">
<GrSegmented
v-model="state"
size="sm"
:options="[
{ value: 'normal', label: 'В норме' },
{ value: 'missing', label: 'Нет значения' },
{ value: 'overflow', label: 'За шкалой' },
]"
aria-label="Состояние метрики"
/>
<GrChartBullet
:value="value[state]"
:target="0.04"
:ranges="[0.03, 0.04]"
:max="0.05"
label="Себестоимость кредита"
:height="48"
data-table="visible"
/>
<p class="text-[length:var(--gr-control-text-sm)] text-[var(--gr-muted-fg)]">
{{ hint[state] }}
</p>
</div>
</template>