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"
/>
);
}| X | Выручка | Расходы |
|---|---|---|
| JAN | 120 | 98 |
| FEB | 138 | 104 |
| MAR | 131 | 109 |
| APR | 159 | 112 |
| MAY | 172 | 118 |
| JUN | 188 | 121 |
Установка
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>
),
}}
/>
);
}Пропы
| Имя | Тип | По умолчанию | Описание |
|---|---|---|---|
| data | number[] | 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 |
| height | number | 200 | Высота графика в пикселях (область графика + ось Y) |
| trend | number | undefined | Десятичный индикатор тренда: 0.184 → ↗ +18.4%. Акцент при росте, приглушённый при падении. Рендерится справа сверху над графиком |
| referenceLine | { value: number | { stat: 'mean' | 'median' }, label? } | undefined | Пунктирная ГОРИЗОНТАЛЬНАЯ референсная линия – фиксированное значение («Цель») или статистика по АКЦЕНТНОМУ (или первому) ряду ({ stat: 'mean' } / { stat: 'median' }). Статистики подписываются Mean/Median автоматически. Участвует в шкале Y, чтобы линия оставалась видимой |
| source | string | undefined | Строка атрибуции под графиком (паттерн FT/Bloomberg) |
| accent | string | var(--brock-accent) | Переопределение акцентного цвета (любой CSS-цвет или var). По умолчанию – --brock-accent. Красит акцентный ряд и положительный тренд |
| lineWidth | number | 1.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 – у них нет позиции на логарифме |
| gridlines | boolean | true | Бледная горизонтальная сетка. По умолчанию true – намеренное и верное отступление от правила «без сетки» для столбцов: Тафти/FT допускают слабую сетку для линий и временных рядов |
| legend | 'none' | 'direct' | 'top' | 'direct' (multi) | Где находится легенда рядов. 'direct' (дефолт для нескольких рядов) подписывает каждую линию у правого конца её цветом – подпись FT, заменяет легенду. 'top' – строка чипов над графиком; 'none' скрывает |
| directLabels | boolean | legend === 'direct' | Прямые подписи у концов линий вкл/выкл. По умолчанию true для нескольких рядов (следует за legend === 'direct'). Пересекающиеся подписи раздвигаются проходом против коллизий по вертикали |
| directLabelValues | boolean | false | Добавлять последнее значение каждого ряда к его прямой подписи |
| emphasisSeries | string | undefined | Выделить один ряд по key или name: он берёт акцентный цвет на полную, остальные приглушаются до серого. Per-series emphasis: true тоже работает. Если ничего не задано и ряд один – он выделяется |
| lastValueDot | boolean | false | Рисовать акцентную точку у последней определённой точки каждого ряда – маркер FT «где закончилось» |
| yBaselineZero | boolean | false | Включить нулевую базу в шкалу Y. По умолчанию шкала Y – «красивый» авто-диапазон, НЕ привязанный к нулю: линия показывает изменение, а не величину от нуля (дефолт FT). Включайте для долей/процентов |
| description | string | auto-generated | Доступное описание для скринридеров (figcaption + sr-таблица). По умолчанию – авто-сводка по Эми Сезал: 'Line chart with N series. SERIES: high … at …, low … at …' |
| yAxisFormat | (v: number) => string | toLocaleString | Свой форматтер подписей делений оси Y. Сильнее numberFormat |
| formatValue | (v: number) => string | toLocaleString | Свой форматтер значений тултипа / прямых подписей / референсной линии. Сильнее numberFormat |
| className | string | undefined | Проброс 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 |
| events | LineChartEvent[] | undefined | Вертикальные маркеры событий – тонкие пунктирные линии в позициях x с опциональными повёрнутыми подписями. Выноска FT «деплой / шок / анонс». Воспроизводится в SVG/PNG-экспорте |
| bands | LineChartBand[] | undefined | Затенённые зоны по диапазону x – вертикальные зоны низкой непрозрачности (паттерн FT с затенением рецессий). За линиями; воспроизводится в экспорте |
| 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' | Базовое имя файла выгрузки. Строка – фиксированное, функция – по формату. Расширение (.png/.svg/.csv) добавляется автоматически |
| onExport | (format, artifact) => void | undefined | Срабатывает после завершения экспорта. Получает формат ('png'|'svg'|'csv'|'copy') и артефакт (Blob для png/copy, строка для svg/csv). Для аналитики и своих share-сценариев |
| ref | Ref<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) => void | undefined | Срабатывает на клик, тап или Enter/Space на сфокусированной точке. Получает полный selection (ряд + точка + индекс + key) и исходное событие |
| onPointHover | (selection | null) => void | undefined | Срабатывает при наведении на ближайший x (selection = null при уходе). Удобно для синхронизации своих легенд и панелей деталей с наведённым x |
| onPointFocus | (selection) => void | undefined | Срабатывает при смене клавиатурного фокуса между точками (Arrow / Home / End по x, Up / Down – смена ряда). Отслеживает позицию roving tabindex |
| slots | LineChartSlots | {} | Словарь headless-слотов. Каждый слот заменяет дефолтный саб-компонент: tooltip (перекрестье, несколько рядов), empty (нет данных), loading (скелетон), error (терминальная), toolbar (чипы экспорта), caption (под источником), watermark (оверлей фигуры). Каждый слот получает типизированные пропы. Слоты сильнее шорткатов loadingFallback / errorFallback |
| caption | string | undefined | Короткая редакционная подпись-примечание – курсив с левой границей, под строкой источника. Паттерн полей FT/писем Stripe. slots.caption сильнее |
| watermark | string | undefined | Диагональная вотермарка – едва заметный пиксельный текст (DRAFT / CONFIDENTIAL) поверх графика. Маркер жизненного цикла документа, не брендинг (для атрибуции есть source). Намеренно ПЕЧАТАЕТСЯ. slots.watermark сильнее |
| chartType | string | 'line' | Машиночитаемый идентификатор, проставляется на фигуре как data-chart-type. Входит в toJSON(). AI/LLM-инструменты и аналитика используют его, чтобы понимать тип графика |
| dataDescription | string | undefined | Описание данных на естественном языке («Выручка и расходы, последние 6 месяцев»). Проставляется как data-description. Для AI-промптов и редакционной провенанс. Не путать с `description` (авто-метка для скринридера) |
| data-testid | string | undefined | QA-селектор, пробрасывается на фигуру. Стабилен при рефакторинге классов – конвенция 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. Прямые подписи у концов линий заменяют легенду (подпись FT), в цвете каждой линии, с защитой от коллизий по вертикали.
- 2. Акцент, не радуга: один ряд в
--brock-accentна полную, остальные приглушены до сдержанной серой шкалы. Никакой разноцветной «лапши». - 3. Бледная горизонтальная сетка – намеренное и верное отступление от правила «без сетки» для столбцов. Тафти / FT допускают её для чтения временных рядов.
- 4. Честные разрывы: точка
y: nullРАЗРЫВАЕТ линию на под-пути – пропуск никогда не интерполируется молча. - 5. Шкала Y показывает изменение, а не величину – «красивый» авто-диапазон, по умолчанию НЕ от нуля (
yBaselineZeroвключает ноль для долей/процентов). - 6. Перекрестье + тултип на несколько рядов привязывается к ближайшему x и перечисляет значение каждого ряда там, по убыванию.
- 7. Редакционные слои: вертикальные маркеры
events, затенённыеbands(затенение рецессий),dashed-ряд для прогнозов – всё воспроизводится в SVG-экспорте. - 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, спарклайны, малые множества