Графики · Bar Chart

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"
    />
  );
}
График
Трафик по каналам
Последние 30 дней
Source: Brock Analytics, 2026
Bar chart with 6 data points. Highest: ПРЯМОЙ (4,120); lowest: ПАРТНЁРЫ (480). Source: Brock Analytics, 2026.
Data table.
LabelValue
ПРЯМОЙ4,120
ПОИСК3,870
СОЦСЕТИ1,290
EMAIL940
РЕФЕРАЛЫ620
ПАРТНЁРЫ480

Установка

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>
        ),
      }}
    />
  );
}

Пропы

ИмяТипПо умолчаниюОписание
datanumber[] | DataPoint[]Значения полос. Две формы: number[] (с пропом labels) или { key?, label?, value, meta?, pattern?, color?, highlight?, note? }[] (объектная форма). key – стабильный адрес для focusBar (по умолчанию label); meta – ваши данные, возвращаются нетронутыми в каждом коллбэке; отрицательные растут ВЛЕВО от нулевой базы. Синтетическая полоса «Other» несёт isOther + items[] (только на выходе)
labelsstring[]undefinedПодписи категорий (левая колонка + тултип). Используются только при data: number[]
barThicknessnumber24Высота каждой полосы в пикселях (по умолчанию 24 – минимум pointer-target по WCAG 2.5.8). Вместе с gap ВЫВОДИТ высоту графика: пропа height нет, данные решают сами
gapnumber8Вертикальный зазор между полосами в пикселях (по умолчанию 8)
maxHeightnumberundefinedПотолок высоты области полос. Когда выведенная высота превышает его И scroll='auto', строки скроллятся вертикально в фокусируемом с клавиатуры контейнере. Без scroll='auto' – документированный no-op: каждая категория остаётся видимой
labelWidthnumber96Ширина левой колонки подписей категорий в пикселях (по умолчанию 96). Подписи обрезаются многоточием – полный текст в тултипе и sr-таблице; в узких контейнерах (≤420px) колонка сжимается, ≤240px – скрывается (только CSS, канон §11)
sort'none' | 'asc' | 'desc''none'Сортировка полос по значению (стабильная). 'none' сохраняет исходный порядок; desc/asc превращают график в рейтинг – естественный режим bar chart
topNnumber | { n, label?, pinned?, distinct? }undefinedОставить N крупнейших полос, свернув хвост в агрегат «Other» (сумма). По умолчанию: закреплён последним независимо от sort, приглушённая заливка --brock-other. Коллбэки получают isOther + items[]. Число = все дефолты
referenceLine{ value: number | { stat: 'mean' | 'median' }, label? }undefinedПунктирная ВЕРТИКАЛЬНАЯ референсная линия – фиксированный порог («План») или статистика по ИСХОДНЫМ данным (sort/topN не должны двигать статистику). Статистики подписываются Mean/Median автоматически. Участвует в шкале с обеих сторон – отрицательные и нулевые референсы остаются видимыми
sourcestringundefinedСтрока атрибуции под графиком (паттерн FT)
accentstringvar(--brock-accent)Переопределение цвета заливки (любой CSS-цвет или var). По умолчанию – оранжевый Brock
barRadiusnumber0Радиус внешних углов в 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
loadingbooleanfalseСостояние загрузки. Без данных → полный скелетон (сплошные строки-плейсхолдеры + доступная подпись загрузки, ARIA role=status). С данными → полупрозрачный оверлей со спиннером для фонового обновления. Уважает prefers-reduced-motion
errorError | string | nullnullТерминальная ошибка. Замещает график даже при наличии данных. Принимает Error, строку или null. ARIA role=alert
onRetry() => voidundefinedКоллбэк кнопки повтора в дефолтном состоянии ошибки. Кнопка рендерится только когда проп передан
loadingLabelstring'Loading…'Доступная подпись состояния загрузки (скелетона), используется как его ARIA-метка. Переопределите для локализации
errorLabelstring'Error'Подпись над сообщением об ошибке и ARIA-метка. Переопределите для локализации
retryLabelstring'Retry'Надпись на кнопке повтора. Переопределите для локализации
loadingFallbackReactNodeundefinedПолная замена дефолтного скелетона и оверлея. Для брендированного состояния загрузки
errorFallbackReactNode | (error: Error) => ReactNodeundefinedПолная замена дефолтного UI ошибки. React-нода или функция, получающая нормализованный Error
exportableboolean | { png?, svg?, csv?, copy? }falseПоказать тулбар экспорта (справа сверху). true – все 4 действия; объектная форма – выборочно. Императивные методы ref работают в любом случае
exportFileNamestring | (format) => string'chart'Базовое имя файла выгрузки. Строка – фиксированное, функция – по формату. Расширение добавляется автоматически
onExport(format, artifact) => voidundefinedСрабатывает после завершения экспорта. Получает формат ('png'|'svg'|'csv'|'copy') и артефакт (Blob или строку)
refRef<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) => voidundefinedСрабатывает на клик, тап или Enter/Space на сфокусированной полосе. При переданном пропе полосы получают cursor-pointer
onBarHover(point | null, index | null) => voidundefinedСрабатывает при наведении (point + index) и при уходе из области полос (null, null)
onBarFocus(point, index) => voidundefinedСрабатывает при смене клавиатурного фокуса между полосами (стрелки, Home/End, Tab, программный focusBar())
slotsBarChartSlots{}Словарь headless-слотов: tooltip, empty, loading, error, toolbar, caption, watermark – каждый получает типизированные пропы. Слоты сильнее шорткатов loadingFallback / errorFallback
captionstringundefinedКороткая редакционная подпись-примечание – курсив с боковой границей, под строкой источника. slots.caption сильнее
watermarkstringundefinedДиагональная вотермарка – едва заметный пиксельный текст поверх графика. Маркер жизненного цикла документа (DRAFT, CONFIDENTIAL) – не брендинг. Намеренно ПЕЧАТАЕТСЯ. slots.watermark сильнее
chartTypestring'bar'Машиночитаемый идентификатор на фигуре как data-chart-type (по умолчанию 'bar'). Входит в toJSON()
dataDescriptionstringundefinedОписание данных на естественном языке. Проставляется как data-description. Для AI-промптов и провенанс
data-testidstringundefinedQA-селектор, пробрасывается на фигуру. Стабилен при рефакторинге классов
descriptionstringauto-generatedДоступное описание для скринридеров (figcaption + подпись таблицы). По умолчанию: 'Bar chart with N data points. Highest: …; lowest: …'
formatValue(value, datum?) => stringtoLocaleString()Свой форматтер значений для тултипов, подписей и sr-таблицы. Вторым аргументом получает датум (key, meta, isOther…). Сильнее numberFormat
xAxisFormat(v: number) => stringtoLocaleStringФункция форматирования подписей делений оси X (значений). Сильнее numberFormat
classNamestringundefinedДополнительные классы на элементе 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. 1. Высота выводится из данных: N полос × barThickness + зазоры. Пропа height нет – рейтинг никогда не обрезает свои категории.
  2. 2. Подписи категорий в колонке фиксированной ширины (labelWidth) с явной лестницей обрезки: многоточие → полный текст в тултипе и sr-таблице → сжатие через container queries. Длинные подписи – причина существования bar chart.
  3. 3. Один --brock-accent по обе стороны нуля; негативные растут влево от вертикальной базы 1px. Без сетки (data-ink, Тафти).
  4. 4. Прямые подписи значений у внешнего конца по умолчанию (dataLabels: "auto") – ось X скрывается, когда каждое значение напечатано; глубокие полосы переносят подпись внутрь цветом фона.
  5. 5. Вертикальная референсная линия (фиксированная или mean/median по исходным данным) с плашкой сверху.
  6. 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