Компонент ResponsiveLine из библиотеки @nivo/line
ожидает строго структурированные данные. Базовая модель представляет
собой массив серий, где каждая серия содержит идентификатор и набор
точек:
const data = [
{
id: "temperature",
color: "hsl(210, 70%, 50%)",
data: [
{ x: "10:00", y: 21 },
{ x: "10:05", y: 22 },
{ x: "10:10", y: 23 }
]
}
];
Ключевые особенности формата:
id — уникальный идентификатор линииdata — массив точекx и y — координаты (x может быть строкой,
числом или датой)Для потоковых данных важна неизменяемость структуры: каждая новая порция данных создаёт новый объект массива, а не мутирует старый.
Компонент ResponsiveLine строится вокруг набора
параметров, определяющих визуализацию:
import { ResponsiveLine } from "@nivo/line";
<ResponsiveLine
data={data}
margin={{ top: 50, right: 110, bottom: 50, left: 60 }}
xScale={{ type: "point" }}
yScale={{ type: "linear", min: "auto", max: "auto" }}
axisBottom={{
tickSize: 5,
tickPadding: 5,
tickRotation: 0
}}
axisLeft={{
tickSize: 5,
tickPadding: 5,
tickRotation: 0
}}
enablePoints={true}
pointSize={6}
useMesh={true}
/>
Основные элементы:
При работе с динамикой важно проектировать структуру данных так, чтобы добавление новых точек не требовало перерасчёта всей серии.
Типичная схема хранения:
const [data, setData] = useState([
{
id: "stream",
data: []
}
]);
Добавление новой точки выполняется через создание нового массива:
const addPoint = (point) => {
setData((prev) => {
const series = prev[0];
const updatedSeries = {
...series,
data: [
...series.data,
point
]
};
return [updatedSeries];
});
};
Для получения данных в реальном времени используется WebSocket или аналогичный поток:
useEffect(() => {
const ws = new WebSocket("wss://example-stream.com");
ws.onmess age = (event) => {
const message = JSON.parse(event.data);
addPoint({
x: message.time,
y: message.value
});
};
return () => ws.close();
}, []);
Каждое сообщение преобразуется в точку графика и добавляется в серию.
При длительном стриминге массив данных растёт бесконечно, что приводит к деградации производительности. Используется оконный подход:
const MAX_POINTS = 50;
const addPoint = (point) => {
setData((prev) => {
const series = prev[0];
const sliced = series.data.slice(-MAX_POINTS + 1);
return [
{
...series,
data: [...sliced, point]
}
];
});
};
Этот подход сохраняет только последние значения, формируя «скользящее окно».
При потоковых данных часто используется временная ось:
<ResponsiveLine
data={data}
xScale={{
type: "time",
format: "%H:%M:%S",
precision: "second"
}}
xFormat="time:%H:%M:%S"
/>
Форматирование времени требует согласованности входных данных:
{
x: new Date().toISOString(),
y: 42
}
Nivo поддерживает анимации переходов между состояниями графика:
<ResponsiveLine
data={data}
animate={true}
motionConfig="molasses"
/>
При стриминге важно учитывать:
При потоковой подаче данных ключевым фактором становится контроль перерисовок React-компонента.
Используются подходы:
const addPoint = useCallback((point) => {
setData((prev) => {
const series = prev[0];
return [
{
...series,
data: [...series.data, point]
}
];
});
}, []);
const chartData = useMemo(() => data, [data]);
Обновление данных отделяется от визуального компонента
ResponsiveLine, что снижает нагрузку на рендеринг.
При динамическом обновлении важно поведение tooltip:
<ResponsiveLine
useMesh={true}
enableSlices="x"
crosshairType="cross"
/>
Особенности:
useMesh улучшает точность наведенияenableSlices синхронизирует данные по оси XПри работе с несколькими потоками структура данных расширяется:
const [data, setData] = useState([
{ id: "cpu", data: [] },
{ id: "memory", data: [] }
]);
Обновление конкретной серии:
const updateSeries = (id, point) => {
setData((prev) =>
prev.map((series) => {
if (series.id !== id) return series;
return {
...series,
data: [...series.data, point]
};
})
);
};
При наличии нескольких источников данных важно поддерживать временную согласованность:
xПример буфера:
const bufferRef = useRef([]);
const flushBuffer = () => {
setData((prev) => {
const updated = [...prev];
bufferRef.current.forEach(({ id, point }) => {
const series = updated.find(s => s.id === id);
series.data.push(point);
});
bufferRef.current = [];
return updated;
});
};
При частоте обновления выше 10–20 сообщений в секунду возникают ограничения рендеринга React.
Используются стратегии:
let lastUpdate = 0;
const handleMessage = (point) => {
const now = Date.now();
if (now - lastUpdate > 100) {
lastUpdate = now;
addPoint(point);
}
};
const queueRef = useRef([]);
const tick = () => {
if (queueRef.current.length > 0) {
setData((prev) => {
const series = prev[0];
return [
{
...series,
data: [...series.data, ...queueRef.current]
}
];
});
queueRef.current = [];
}
requestAnimationFrame(tick);
};
useEffect(() => {
requestAnimationFrame(tick);
}, []);
Компонент позволяет детально управлять внешним видом:
<ResponsiveLine
curve="monotoneX"
lineWidth={2}
enablePoints={true}
pointSize={8}
pointColor={{ theme: "background" }}
pointBorderWidth={2}
pointBorderColor={{ from: "serieColor" }}
/>
Для стриминга часто отключают точки:
При очень длинных потоках важно контролировать:
Пример адаптивного масштаба:
yScale={{
type: "linear",
min: "auto",
max: "auto",
stacked: false
}}
При необходимости можно вручную фиксировать диапазон для стабильной визуализации.
Line-компонент в потоковом режиме часто применяется для:
Во всех случаях ключевым становится баланс между:
Типовая архитектура включает три слоя:
Data ingestion layer
Processing layer
Visualization layer
ResponsiveLineРазделение слоёв снижает связанность и упрощает масштабирование потоковой системы.