Графики · Line Chart

Line Chart

Многорядные линии для непрерывных временных рядов – изменение во времени, индексированные сравнения, прогнозы. Канон FT / Джона Бёрн-Мёрдока: прямые подписи у концов линий вместо легенды, один акцентный ряд в цвете акцента, остальные приглушены до серого (никакой радуги), бледная сетка (намеренное и верное отступление от правила «без сетки» для столбцов), честные разрывы там, где данных нет, и шкала Y, которая показывает изменение – а не величину от нуля.

Studio · Код, график, настройки

Трёхпанельный верстак. Меняйте любую настройку справа – многорядный график в центре обновляется вживую, а код слева перегенерируется, готовый к вставке в ваш проект.

Код
import { LineChart } from "@/components/charts/line-chart";

const data = [
  { name: "Выручка", data: [120, 138, 131, 159, 172, 188], color: "#F54900" },
  { name: "Расходы", data: [98, 104, 109, 112, 118, 121], color: "#71717A" }
];
const labels = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN"];

export function Example() {
  return (
    <LineChart
      data={data}
      labels={labels}
      height={240}
      emphasisSeries="Выручка"
      header={{ title: "Выручка и расходы", subtitle: "Последние 6 месяцев" }}
      source="Brock Analytics, 2026"
      onPointClick={(selection) => {
        console.log("clicked", selection.series.name, selection.point);
      }}
      onPointHover={(selection) => {
        // selection is null on mouse leave
        setHover(selection);
      }}
      onPointFocus={(selection) => {
        // Fires on keyboard navigation
        announce(`${selection.series.name}: ${selection.point.y}`);
      }}
      exportable
      exportFileName="revenue-vs-costs"
    />
  );
}
График
Выручка и расходы
Последние 6 месяцев
Source: Brock Analytics, 2026
Line chart with 2 series. Выручка: high 188 at JUN, low 120 at JAN; Расходы: high 121 at JUN, low 98 at JAN. Source: Brock Analytics, 2026.
Data table.
XВыручкаРасходы
JAN12098
FEB138104
MAR131109
APR159112
MAY172118
JUN188121

Установка

npx shadcn@latest add https://brockui.com/r/line-chart

Использование

import { LineChart } from "@/components/charts/line-chart";

const data = [
  { name: "Revenue", data: [120, 138, 131, 159, 172, 188] },
  { name: "Costs", data: [98, 104, 109, 112, 118, 121] },
];
const labels = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN"];

export function Example() {
  return (
    <LineChart
      data={data}
      labels={labels}
      height={240}
      emphasisSeries="Revenue"
      source="Brock Analytics, 2026"
    />
  );
}

// Opinionated defaults — but every sub-component is yours to replace via the
// `slots` prop. The crosshair tooltip lists every series at the hovered x.
export function WithCustomTooltip() {
  return (
    <LineChart
      data={data}
      labels={labels}
      slots={{
        tooltip: ({ xLabel, points }) => (
          <div className="border-2 border-brock-accent p-2">
            <div className="font-mono text-[10px] uppercase">{xLabel}</div>
            {points.map((p) => (
              <div key={p.series} className="flex gap-2">
                <span style={{ color: p.color }}>{p.series}</span>
                <span className="font-mono tabular-nums">{p.formatted}</span>
              </div>
            ))}
          </div>
        ),
      }}
    />
  );
}

Пропы

ИмяТипПо умолчаниюОписание
datanumber[] | LineChartDataPoint[] | LineChartSeries[]Данные линий. Три формы: number[] (один ряд, в паре с labels/x), LineChartDataPoint[] (один ряд, объектная форма) или LineChartSeries[] (несколько рядов с общей осью X). Значение null (в массивах чисел или как y: null) рисует РАЗРЫВ – линия прерывается, а не интерполируется через пропуск
labels(string | number)[]undefinedПодписи/позиции оси X, используются когда data – один ряд из чисел или точек без собственного x. Строки → шкала point; числа → linear/time
x(string | number)[]undefinedПсевдоним для labels. Читается естественнее для непрерывной оси X
heightnumber200Высота графика в пикселях (область графика + ось Y)
trendnumberundefinedДесятичный индикатор тренда: 0.184 → ↗ +18.4%. Акцент при росте, приглушённый при падении. Рендерится справа сверху над графиком
referenceLine{ value: number | { stat: 'mean' | 'median' }, label? }undefinedПунктирная ГОРИЗОНТАЛЬНАЯ референсная линия – фиксированное значение («Цель») или статистика по АКЦЕНТНОМУ (или первому) ряду ({ stat: 'mean' } / { stat: 'median' }). Статистики подписываются Mean/Median автоматически. Участвует в шкале Y, чтобы линия оставалась видимой
sourcestringundefinedСтрока атрибуции под графиком (паттерн FT/Bloomberg)
accentstringvar(--brock-accent)Переопределение акцентного цвета (любой CSS-цвет или var). По умолчанию – --brock-accent. Красит акцентный ряд и положительный тренд
lineWidthnumber1.75Толщина линии в пикселях
curve'linear' | 'monotone''linear'Интерполяция линии. 'linear' – прямая ломаная; 'monotone' сглаживает без выброса – безопасное сглаживание, которое не выдумывает пик между двумя точками
markers'auto' | 'always' | 'none''auto'Политика маркеров (точек). 'auto' показывает точки только для разрежённых рядов (≤ ~20 точек) или одиночной точки; 'always' / 'none' задают поведение принудительно
xScale'linear' | 'time' | 'point'inferredШкала оси X. По умолчанию определяется: 'point' для строковых x, иначе 'linear'. 'time' детерминированно парсит таймстемпы / ISO-даты (никогда Date.now())
yScale'linear' | 'log''linear'Шкала оси Y. 'linear' (дефолт) или 'log' (по основанию 10, деления 1·2·5). Неположительные значения отбрасываются на лог-шкале с предупреждением в dev – у них нет позиции на логарифме
gridlinesbooleantrueБледная горизонтальная сетка. По умолчанию true – намеренное и верное отступление от правила «без сетки» для столбцов: Тафти/FT допускают слабую сетку для линий и временных рядов
legend'none' | 'direct' | 'top''direct' (multi)Где находится легенда рядов. 'direct' (дефолт для нескольких рядов) подписывает каждую линию у правого конца её цветом – подпись FT, заменяет легенду. 'top' – строка чипов над графиком; 'none' скрывает
directLabelsbooleanlegend === 'direct'Прямые подписи у концов линий вкл/выкл. По умолчанию true для нескольких рядов (следует за legend === 'direct'). Пересекающиеся подписи раздвигаются проходом против коллизий по вертикали
directLabelValuesbooleanfalseДобавлять последнее значение каждого ряда к его прямой подписи
emphasisSeriesstringundefinedВыделить один ряд по key или name: он берёт акцентный цвет на полную, остальные приглушаются до серого. Per-series emphasis: true тоже работает. Если ничего не задано и ряд один – он выделяется
lastValueDotbooleanfalseРисовать акцентную точку у последней определённой точки каждого ряда – маркер FT «где закончилось»
yBaselineZerobooleanfalseВключить нулевую базу в шкалу Y. По умолчанию шкала Y – «красивый» авто-диапазон, НЕ привязанный к нулю: линия показывает изменение, а не величину от нуля (дефолт FT). Включайте для долей/процентов
descriptionstringauto-generatedДоступное описание для скринридеров (figcaption + sr-таблица). По умолчанию – авто-сводка по Эми Сезал: 'Line chart with N series. SERIES: high … at …, low … at …'
yAxisFormat(v: number) => stringtoLocaleStringСвой форматтер подписей делений оси Y. Сильнее numberFormat
formatValue(v: number) => stringtoLocaleStringСвой форматтер значений тултипа / прямых подписей / референсной линии. Сильнее numberFormat
classNamestringundefinedПроброс className на внешнюю обёртку figure
header{ title?, subtitle? }undefinedБлок заголовка и подзаголовка над графиком
xAxis{ title?, hideTicks?, ticks?, format? }undefinedНастройка оси X: title (под делениями), hideTicks, примерное число делений, свой форматтер подписей (получает разрешённый числовой x)
yAxis{ title?, min?, max?, hideTicks?, ticks? }undefinedНастройка оси Y. В отличие от столбчатого графика, шкала Y линии по умолчанию – красивый авто-диапазон (НЕ от нуля): история – это изменение. Задать min / max, скрыть деления, число делений, повёрнутый заголовок
numberFormat{ prefix?, suffix?, decimals?, locale?, notation?, style?, currency? }undefinedФорматирование чисел для делений оси Y, значений тултипа, прямых подписей и референсной линии. Поддерживает локаль BCP-47, notation Intl.NumberFormat ('compact' → 1,2 тыс.), style ('currency' / 'percent') и валюту ISO 4217. Явные formatValue/yAxisFormat сильнее
animation{ enabled?, duration? }{ enabled: true, duration: 600 }Анимация прорисовки/появления линий при монтировании. Автоматически отключается при prefers-reduced-motion
eventsLineChartEvent[]undefinedВертикальные маркеры событий – тонкие пунктирные линии в позициях x с опциональными повёрнутыми подписями. Выноска FT «деплой / шок / анонс». Воспроизводится в SVG/PNG-экспорте
bandsLineChartBand[]undefinedЗатенённые зоны по диапазону x – вертикальные зоны низкой непрозрачности (паттерн FT с затенением рецессий). За линиями; воспроизводится в экспорте
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'Базовое имя файла выгрузки. Строка – фиксированное, функция – по формату. Расширение (.png/.svg/.csv) добавляется автоматически
onExport(format, artifact) => voidundefinedСрабатывает после завершения экспорта. Получает формат ('png'|'svg'|'csv'|'copy') и артефакт (Blob для png/copy, строка для svg/csv). Для аналитики и своих share-сценариев
refRef<LineChartHandle>Императивный API: { exportSVG, exportPNG, exportCSV, copyImage, focusPoint, getSelection }. Экспорт работает даже в loading/error/empty. focusPoint(target) принимает индекс точки (с ограничением) на акцентном ряду ИЛИ стабильный key (неизвестный key → -1). getSelection() возвращает { series, point, index, key } или null
onPointClick(selection, event) => voidundefinedСрабатывает на клик, тап или Enter/Space на сфокусированной точке. Получает полный selection (ряд + точка + индекс + key) и исходное событие
onPointHover(selection | null) => voidundefinedСрабатывает при наведении на ближайший x (selection = null при уходе). Удобно для синхронизации своих легенд и панелей деталей с наведённым x
onPointFocus(selection) => voidundefinedСрабатывает при смене клавиатурного фокуса между точками (Arrow / Home / End по x, Up / Down – смена ряда). Отслеживает позицию roving tabindex
slotsLineChartSlots{}Словарь headless-слотов. Каждый слот заменяет дефолтный саб-компонент: tooltip (перекрестье, несколько рядов), empty (нет данных), loading (скелетон), error (терминальная), toolbar (чипы экспорта), caption (под источником), watermark (оверлей фигуры). Каждый слот получает типизированные пропы. Слоты сильнее шорткатов loadingFallback / errorFallback
captionstringundefinedКороткая редакционная подпись-примечание – курсив с левой границей, под строкой источника. Паттерн полей FT/писем Stripe. slots.caption сильнее
watermarkstringundefinedДиагональная вотермарка – едва заметный пиксельный текст (DRAFT / CONFIDENTIAL) поверх графика. Маркер жизненного цикла документа, не брендинг (для атрибуции есть source). Намеренно ПЕЧАТАЕТСЯ. slots.watermark сильнее
chartTypestring'line'Машиночитаемый идентификатор, проставляется на фигуре как data-chart-type. Входит в toJSON(). AI/LLM-инструменты и аналитика используют его, чтобы понимать тип графика
dataDescriptionstringundefinedОписание данных на естественном языке («Выручка и расходы, последние 6 месяцев»). Проставляется как data-description. Для AI-промптов и редакционной провенанс. Не путать с `description` (авто-метка для скринридера)
data-testidstringundefinedQA-селектор, пробрасывается на фигуру. Стабилен при рефакторинге классов – конвенция Testing Library / Playwright

Доступность

Построен по WCAG 2.2 AA. Клавиатурная навигация, поддержка скринридеров, уважает prefers-reduced-motion.

Честное ограничение: автогенерируемые ARIA-описания компонента («Line chart with 2 series. …») – на английском. Если ваш продукт для русскоязычных пользователей, передайте собственный description и локализуйте loadingLabel/errorLabel/retryLabel – locale-pack компонента в планах.

Клавиатура
TabФокус внутрь графика (одна точка останова)
← →Движение по x на акцентном ряду (roving tabindex)
↑ ↓Переключение сфокусированного ряда вверх / вниз
HomeК первой точке
EndК последней точке
Enter / SpaceВызвать onPointClick на сфокусированной точке
Разметка для скринридера
  • · <figure role="figure"> оборачивает график; aria-labelledby указывает на figcaption
  • · Каждая фокусируемая точка – aria-roledescription="data point" с aria-label="SERIES, X: value"
  • · Скрытая <table class="sr-only"> даёт табличную сводку – колонка на ряд, строка на значение x (разрывы – «–»)
  • · Индикатор тренда получает человекочитаемую метку («Trend up 18.4 percent») – стрелки скрыты от AT

Дизайн-ходы

  1. 1. Прямые подписи у концов линий заменяют легенду (подпись FT), в цвете каждой линии, с защитой от коллизий по вертикали.
  2. 2. Акцент, не радуга: один ряд в --brock-accent на полную, остальные приглушены до сдержанной серой шкалы. Никакой разноцветной «лапши».
  3. 3. Бледная горизонтальная сетка – намеренное и верное отступление от правила «без сетки» для столбцов. Тафти / FT допускают её для чтения временных рядов.
  4. 4. Честные разрывы: точка y: null РАЗРЫВАЕТ линию на под-пути – пропуск никогда не интерполируется молча.
  5. 5. Шкала Y показывает изменение, а не величину – «красивый» авто-диапазон, по умолчанию НЕ от нуля (yBaselineZero включает ноль для долей/процентов).
  6. 6. Перекрестье + тултип на несколько рядов привязывается к ближайшему x и перечисляет значение каждого ряда там, по убыванию.
  7. 7. Редакционные слои: вертикальные маркеры events, затенённые bands (затенение рецессий), dashed-ряд для прогнозов – всё воспроизводится в SVG-экспорте.
  8. 8. Понятное пустое состояние (иконка + сообщение No data) и сплошной скелетон загрузки в том же визуальном языке Тафти.

Когда использовать

Line Chart подходит для непрерывных рядов, где ось X упорядочена, а история – это тренд: метрики во времени (выручка, задержка, активные пользователи), индексированные сравнения сущностей (страны, когорты, приведённые к 100) и факт-vs-прогноз (пунктирная линия прогноза). Несколько рядов делят одну ось Y, чтобы читаться друг против друга; акцент держит один в фокусе, остальные дают контекст.

Когда не использовать

Для дискретных упорядоченных интервалов, где каждое значение независимо (дни недели, каналы) – Column Chart. Для рейтингов с длинными подписями – Bar Chart. Для крошечных графиков внутри метрик-карточек и текста – Sparkline. Для состава во времени (части целого) – Stacked Area / Stacked Column (скоро).

Источники

  • · Джон Бёрн-Мёрдок, Financial Times – прямые подписи, акцент вместо радуги, индексированные линии, лог-шкала там, где она говорит правду
  • · Financial Times Visual Journalism – разреженные оси, строка источника, затенение рецессий
  • · Годовые письма Stripe – числа внутри редакционного текста
  • · Эдвард Тафти, The Visual Display of Quantitative Information – дисциплина data-ink, спарклайны, малые множества