GrChartBullet

Пакет: @feugene/granularity-chartsспутникГруппа: Прочее

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

Когда брать

  • метрика с порогами, которые уже есть в данных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

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

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>

Документация компонентаВсе компоненты