GrChartBullet
Machine-translated from the Russian original, not yet reviewed. Read the original
When to take it
- a metric with thresholds that are already in the data —
warningandcriticalarrive from the backend and are usually drawn nowhere: “bad” is visible, “how bad” is not; - the plan and the fact side by side — the notch of the target and the bar of the value are read at a glance, without subtracting in one’s head;
- several metrics in a row — a bullet takes up a line, and a dozen metrics stand as a list comparable vertically;
- room is expensive — a card, a table row, the header of a dashboard: the same meaning as a dial in a fifth of the area;
- the quantity may be missing —
value: nulldraws the target and the ranges without a bar rather than a zero.
When to take something else
| Need | Take |
|---|---|
| Compare the quantities of several categories with one another | GrChartBar |
| Show the course of a quantity over time | GrChartLine |
| Show the share of completion of a single task | GrProgressCircle |
| Show one number large, with a trend | GrStatistic |
There is deliberately no dial in the package
A gauge spends a lot of room on little data and reads poorly quantitatively: from a needle one takes “roughly in the middle” rather than a number. A bullet solves the same task with a bar, and when there are several such metrics it is also comparable vertically, which dials cannot do at all.
Three weights that do not argue
The ranges are the background, the value a narrow bar on top, the target a notch across. The
thickness of the value bar is set by the --gr-chart-bullet-value-width token rather than by the
geometry: the bar is drawn with a <line>, and the theme changes its weight without touching the
track.
Colours of your own for the ranges are set by rangeColors — one per band, from “good” to “bad”.
There is always one band more than there are boundaries, even if a boundary went off the scale: such
a boundary is clamped rather than thrown away, otherwise the colours would move onto the
neighbouring bands.
`value: null` is not a zero
If there is no quantity, the bar is not drawn, the notch of the target remains and there is a dash in the table. For a cost that is the literal case: no write-offs means no cost, and a zero would lie here.
Together with the value the meter role disappears as well: it requires aria-valuenow, and without
a quantity there is none — a role left in place would give a violation of serious level.
A value beyond the scale is not truncated silently
The bar runs into the edge and gets an overflow marker, and the real quantity goes into the tooltip, into the hidden table and into the announcement. Truncating it quietly would mean showing a different number.
The role is `meter` rather than `progressbar`
progressbar is about the completion of a task that comes to an end; here there is a quantity that
simply is. slider fits even less: the value is not editable. Besides
aria-valuenow/valuemin/valuemax the overlay carries an aria-valuetext with a human wording
(“0.031 of 0.05, target 0.04”) — one number without units and without the target would say less than
a sighted person sees. The ranges are announced in the description of the chart: the coloured zones
would otherwise exist only for the sighted.
Limits
There are no several values in one track: a bullet compares one quantity with one target. The component does not draw a needle or the “gathering” of the value with an animation either.
Install
npm i @feugene/granularity-chartsImport
import { GrChartBullet } from '@feugene/granularity-charts/components/GrChartBullet'API
The API for this component has not been generated yet: the showcase generator only covers the core so far. Until it does, the reference lives in the package documentation.
Examples 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>