Bar Chart
Горизонтальные полосы для ранжированных категорий – форма рейтинга. Длинные подписи категорий читаются горизонтально (ради этого bar chart и существует), высота выводится из данных, отрицательные значения растут влево от всегда видимой нулевой базы. Та же дисциплина Тафти, что и в Column Chart: один акцент, без сетки, прямые подписи значений по умолчанию.
Studio · Код, график, настройки
Трёхпанельный верстак. Меняйте любую настройку справа – график в центре обновляется вживую, а код слева перегенерируется, готовый к вставке в ваш проект.
import { BarChart } from "@/components/charts/bar-chart";
const data = [
{ label: "ПРЯМОЙ", value: 4120 },
{ label: "ПОИСК", value: 3870 },
{ label: "СОЦСЕТИ", value: 1290 },
{ label: "EMAIL", value: 940 },
{ label: "РЕФЕРАЛЫ", value: 620 },
{ label: "ПАРТНЁРЫ", value: 480 }
];
export function Example() {
return (
<BarChart
data={data}
header={{ title: "Трафик по каналам", subtitle: "Последние 30 дней" }}
source="Brock Analytics, 2026"
onBarClick={(point, index) => {
console.log("clicked", index, point);
}}
onBarHover={(point, index) => {
// point is null on mouse leave
setHoverIndex(index);
}}
onBarFocus={(point, index) => {
// Fires on keyboard navigation
announce(`Bar ${index + 1}: ${point.value}`);
}}
exportable
exportFileName="traffic-by-channel"
/>
);
}Установка
npx shadcn@latest add https://brockui.com/r/bar-chartИспользование
import { BarChart } from "@/components/charts/bar-chart";
const data = [
{ label: "DIRECT", value: 4120 },
{ label: "SEARCH", value: 3870 },
{ label: "SOCIAL", value: 1290 },
{ label: "EMAIL", value: 940 },
];
export function Example() {
return (
<BarChart
data={data}
sort="desc"
source="Brock Analytics, 2026"
/>
);
}
// Opinionated defaults — but every sub-component is yours to replace via the
// `slots` prop. The data is passed in; you control the rendering.
export function WithCustomTooltip() {
return (
<BarChart
data={data}
slots={{
tooltip: ({ label, value }) => (
<div className="border-2 border-brock-accent p-2">
<span className="font-mono tabular-nums">{value}</span> · {label}
</div>
),
}}
/>
);
}Пропы
| Имя | Тип | По умолчанию | Описание |
|---|---|---|---|
| data | number[] | DataPoint[] | — | Значения полос. Две формы: number[] (с пропом labels) или { key?, label?, value, meta?, pattern?, color?, highlight?, note? }[] (объектная форма). key – стабильный адрес для focusBar (по умолчанию label); meta – ваши данные, возвращаются нетронутыми в каждом коллбэке; отрицательные растут ВЛЕВО от нулевой базы. Синтетическая полоса «Other» несёт isOther + items[] (только на выходе) |
| labels | string[] | undefined | Подписи категорий (левая колонка + тултип). Используются только при data: number[] |
| barThickness | number | 24 | Высота каждой полосы в пикселях (по умолчанию 24 – минимум pointer-target по WCAG 2.5.8). Вместе с gap ВЫВОДИТ высоту графика: пропа height нет, данные решают сами |
| gap | number | 8 | Вертикальный зазор между полосами в пикселях (по умолчанию 8) |
| maxHeight | number | undefined | Потолок высоты области полос. Когда выведенная высота превышает его И scroll='auto', строки скроллятся вертикально в фокусируемом с клавиатуры контейнере. Без scroll='auto' – документированный no-op: каждая категория остаётся видимой |
| labelWidth | number | 96 | Ширина левой колонки подписей категорий в пикселях (по умолчанию 96). Подписи обрезаются многоточием – полный текст в тултипе и sr-таблице; в узких контейнерах (≤420px) колонка сжимается, ≤240px – скрывается (только CSS, канон §11) |
| sort | 'none' | 'asc' | 'desc' | 'none' | Сортировка полос по значению (стабильная). 'none' сохраняет исходный порядок; desc/asc превращают график в рейтинг – естественный режим bar chart |
| topN | number | { n, label?, pinned?, distinct? } | undefined | Оставить N крупнейших полос, свернув хвост в агрегат «Other» (сумма). По умолчанию: закреплён последним независимо от sort, приглушённая заливка --brock-other. Коллбэки получают isOther + items[]. Число = все дефолты |
| referenceLine | { value: number | { stat: 'mean' | 'median' }, label? } | undefined | Пунктирная ВЕРТИКАЛЬНАЯ референсная линия – фиксированный порог («План») или статистика по ИСХОДНЫМ данным (sort/topN не должны двигать статистику). Статистики подписываются Mean/Median автоматически. Участвует в шкале с обеих сторон – отрицательные и нулевые референсы остаются видимыми |
| source | string | undefined | Строка атрибуции под графиком (паттерн FT) |
| accent | string | var(--brock-accent) | Переопределение цвета заливки (любой CSS-цвет или var). По умолчанию – оранжевый Brock |
| barRadius | number | 0 | Радиус внешних углов в px. Углы у базовой линии остаются плоскими; позитивные полосы скругляют ПРАВЫЕ углы, негативные – ЛЕВЫЕ |
| header | { title?, subtitle? } | undefined | Блок заголовка и подзаголовка над графиком |
| xAxis | { title?, max?, hideTicks? } | undefined | Настройка оси X (ЗНАЧЕНИЙ). min намеренно отсутствует – база bar chart всегда ноль (обрезанные полосы лгут). max только расширяет шкалу: значения ниже максимума данных игнорируются с предупреждением в dev |
| yAxis | { title?, hideLabels? } | undefined | Настройка оси Y (КАТЕГОРИЙ) – заголовок, скрытие подписей |
| numberFormat | { prefix?, suffix?, decimals?, locale?, notation?, style?, currency? } | undefined | Форматирование чисел для делений оси X, тултипа и подписей значений. Поддерживает локаль BCP-47, notation Intl.NumberFormat ('compact' → 1,2 тыс.), style ('currency' / 'percent') и валюту ISO 4217. Явные formatValue/xAxisFormat сильнее |
| dataLabels | { show?: boolean | 'auto', format? } | { show: 'auto' } | Прямые подписи значений у ВНЕШНЕГО конца полосы (Hack mono; справа у позитивных, слева у негативных; глубокие полосы переносят подпись внутрь цветом фона). 'auto' (дефолт) показывает подписи И скрывает деления оси X при ≤ 8 полосах. Явный xAxis.hideTicks сильнее. format(value, datum) переопределяет numberFormat |
| pattern | 'solid' | 'hatched' | 'solid' | Заливка всех полос по умолчанию. Per-bar pattern на точке данных сильнее. Штриховка кодирует «оценка/в работе» без второго цвета (Тафти) |
| patternStyle | 'diagonal' | 'diagonal-reverse' | 'dots' | 'vertical' | 'horizontal' | 'diagonal' | Вид штриховки (на уровне графика). Per-bar pattern решает, штрихуется ли полоса; этот проп – как выглядит штриховка. 'dots' – для печати |
| scroll | 'none' | 'auto' | 'none' | Поведение при превышении maxHeight. 'none' (дефолт) – график высотой с данные, каждая категория видна. 'auto' – строки скроллятся вертикально в фокусируемом контейнере |
| animation | { enabled?, duration? } | { enabled: true, duration: 400 } | Каскадный рост полос от базовой линии при монтировании (scaleX; у негативных зеркальный origin). Автоматически отключается при prefers-reduced-motion |
| loading | boolean | false | Состояние загрузки. Без данных → полный скелетон (сплошные строки-плейсхолдеры + доступная подпись загрузки, ARIA role=status). С данными → полупрозрачный оверлей со спиннером для фонового обновления. Уважает prefers-reduced-motion |
| error | Error | string | null | null | Терминальная ошибка. Замещает график даже при наличии данных. Принимает Error, строку или null. ARIA role=alert |
| onRetry | () => void | undefined | Коллбэк кнопки повтора в дефолтном состоянии ошибки. Кнопка рендерится только когда проп передан |
| loadingLabel | string | 'Loading…' | Доступная подпись состояния загрузки (скелетона), используется как его ARIA-метка. Переопределите для локализации |
| errorLabel | string | 'Error' | Подпись над сообщением об ошибке и ARIA-метка. Переопределите для локализации |
| retryLabel | string | 'Retry' | Надпись на кнопке повтора. Переопределите для локализации |
| loadingFallback | ReactNode | undefined | Полная замена дефолтного скелетона и оверлея. Для брендированного состояния загрузки |
| errorFallback | ReactNode | (error: Error) => ReactNode | undefined | Полная замена дефолтного UI ошибки. React-нода или функция, получающая нормализованный Error |
| exportable | boolean | { png?, svg?, csv?, copy? } | false | Показать тулбар экспорта (справа сверху). true – все 4 действия; объектная форма – выборочно. Императивные методы ref работают в любом случае |
| exportFileName | string | (format) => string | 'chart' | Базовое имя файла выгрузки. Строка – фиксированное, функция – по формату. Расширение добавляется автоматически |
| onExport | (format, artifact) => void | undefined | Срабатывает после завершения экспорта. Получает формат ('png'|'svg'|'csv'|'copy') и артефакт (Blob или строку) |
| ref | Ref<BarChartHandle> | — | Императивный API: { exportSVG, exportPNG, exportCSV, copyImage, focusBar, getSelection }. Экспорт работает даже в loading/error/empty. focusBar(target) принимает экранный индекс ИЛИ стабильный key (неизвестный key → -1). getSelection() возвращает { index, key, point } или null |
| onBarClick | (point, index, event) => void | undefined | Срабатывает на клик, тап или Enter/Space на сфокусированной полосе. При переданном пропе полосы получают cursor-pointer |
| onBarHover | (point | null, index | null) => void | undefined | Срабатывает при наведении (point + index) и при уходе из области полос (null, null) |
| onBarFocus | (point, index) => void | undefined | Срабатывает при смене клавиатурного фокуса между полосами (стрелки, Home/End, Tab, программный focusBar()) |
| slots | BarChartSlots | {} | Словарь headless-слотов: tooltip, empty, loading, error, toolbar, caption, watermark – каждый получает типизированные пропы. Слоты сильнее шорткатов loadingFallback / errorFallback |
| caption | string | undefined | Короткая редакционная подпись-примечание – курсив с боковой границей, под строкой источника. slots.caption сильнее |
| watermark | string | undefined | Диагональная вотермарка – едва заметный пиксельный текст поверх графика. Маркер жизненного цикла документа (DRAFT, CONFIDENTIAL) – не брендинг. Намеренно ПЕЧАТАЕТСЯ. slots.watermark сильнее |
| chartType | string | 'bar' | Машиночитаемый идентификатор на фигуре как data-chart-type (по умолчанию 'bar'). Входит в toJSON() |
| dataDescription | string | undefined | Описание данных на естественном языке. Проставляется как data-description. Для AI-промптов и провенанс |
| data-testid | string | undefined | QA-селектор, пробрасывается на фигуру. Стабилен при рефакторинге классов |
| description | string | auto-generated | Доступное описание для скринридеров (figcaption + подпись таблицы). По умолчанию: 'Bar chart with N data points. Highest: …; lowest: …' |
| formatValue | (value, datum?) => string | toLocaleString() | Свой форматтер значений для тултипов, подписей и sr-таблицы. Вторым аргументом получает датум (key, meta, isOther…). Сильнее numberFormat |
| xAxisFormat | (v: number) => string | toLocaleString | Функция форматирования подписей делений оси X (значений). Сильнее numberFormat |
| className | string | undefined | Дополнительные классы на элементе figure |
Доступность
Построен по WCAG 2.2 AA. Клавиатурная навигация, поддержка скринридеров, уважает prefers-reduced-motion. Тот же базис, что у Column Chart: sr-таблица с объявлением трансформаций, forced-colors, авто-описание с максимумом/минимумом.
Честное ограничение: автогенерируемые ARIA-описания компонента – на английском. Если ваш продукт для русскоязычных пользователей, передайте собственный description и локализуйте loadingLabel/errorLabel/retryLabel – locale-pack компонента в планах.
| Tab | Фокус внутрь графика (одна точка останова) |
| ↑ ↓ ← → | Навигация между полосами (roving tabindex; вертикаль – основная ось) |
| Home | К первой полосе |
| End | К последней полосе |
Дизайн-ходы
- 1. Высота выводится из данных: N полос ×
barThickness+ зазоры. Пропа height нет – рейтинг никогда не обрезает свои категории. - 2. Подписи категорий в колонке фиксированной ширины (
labelWidth) с явной лестницей обрезки: многоточие → полный текст в тултипе и sr-таблице → сжатие через container queries. Длинные подписи – причина существования bar chart. - 3. Один
--brock-accentпо обе стороны нуля; негативные растут влево от вертикальной базы 1px. Без сетки (data-ink, Тафти). - 4. Прямые подписи значений у внешнего конца по умолчанию (
dataLabels: "auto") – ось X скрывается, когда каждое значение напечатано; глубокие полосы переносят подпись внутрь цветом фона. - 5. Вертикальная референсная линия (фиксированная или mean/median по исходным данным) с плашкой сверху.
- 6. Осознанные вырезы v1 по канону §13: без bands / trend / hatch-шорткатов (временна́я семантика Column) и без свободных аннотаций – per-datum
noteпокрывает выноски рейтинга.
Когда использовать
Ранжированные категориальные сравнения – трафик по каналам, выручка по регионам, вклад по продуктам (отрицательные значения полноправны). Берите Bar вместо Column всегда, когда подписи категорий – слова, а не временные метки: горизонтальные подписи читаются при любой длине. sort + topN превращают сырые данные в честный рейтинг с явным «Other».
Когда не использовать
Для временных интервалов – Column Chart (время читается слева направо). Для непрерывных трендов – Line Chart. Для крошечных встроенных графиков – Sparkline. Для состава целого – Stacked Bar Chart (скоро).
Источники
- · Financial Times Visual Journalism – ранжированные полосы с разреженными осями и прямыми подписями
- · Datawrapper – bars вместо columns всегда, когда подписи – слова
- · Эдвард Тафти, The Visual Display of Quantitative Information – дисциплина data-ink