Потоковое обновление данных в Chart.js основано на непрерывном изменении структуры datasets с последующим контролируемым перерисовыванием графика. В отличие от статической визуализации, где данные задаются единожды при инициализации, потоковый режим предполагает постоянное добавление новых точек, удаление устаревших значений и синхронизацию отображения с источником событий в реальном времени.
В основе механизма лежит разделение данных и рендера. Chart.js хранит
состояние графика в объекте chart.data, а визуализация
формируется через вызов chart.update().
Ключевая особенность потоковой модели заключается в том, что:
Типичная структура данных для потокового режима:
const data = {
datasets: [{
label: 'Сенсор',
data: []
}]
};
Основной операцией становится добавление элемента в массив
data:
chart.data.datasets[0].data.push({
x: Date.now(),
y: Math.random() * 100
});
chart.update('none');
Режим 'none' отключает анимацию, что критично для
высокой частоты обновлений.
При высоких нагрузках важно ограничивать размер массива:
const dataset = chart.data.datasets[0];
dataset.data.push({ x: Date.now(), y: value });
if (dataset.data.length > 200) {
dataset.data.shift();
}
chart.update('none');
Такой подход предотвращает деградацию производительности при длительной работе.
Для потоковых данных наиболее естественно использовать временную ось:
const config = {
type: 'line',
data,
options: {
scales: {
x: {
type: 'time',
time: {
unit: 'second'
}
}
}
}
};
Временная шкала обеспечивает:
WebSocket обеспечивает двусторонний канал передачи данных:
const socket = new WebSocket('wss://example.com/data');
socket.onmess age = (event) => {
const parsed = JSON.parse(event.data);
chart.data.datasets[0].data.push({
x: parsed.timestamp,
y: parsed.value
});
chart.update('none');
};
Особенность подхода заключается в минимальной задержке между генерацией и отображением данных.
SSE используется для одностороннего потока:
const source = new EventSource('/stream');
source.onmess age = (event) => {
const dataPoint = JSON.parse(event.data);
chart.data.datasets[0].data.push(dataPoint);
chart.update('none');
};
SSE проще в реализации, но ограничен HTTP-моделью.
Используется при тестировании:
setInterval(() => {
chart.data.datasets[0].data.push({
x: Date.now(),
y: Math.sin(Date.now() / 1000)
});
chart.update('none');
}, 500);
Частота вызова chart.update() является критическим
фактором производительности.
Чрезмерное обновление приводит к:
Оптимальная стратегия — буферизация:
const buffer = [];
function pushData(point) {
buffer.push(point);
}
setInterval(() => {
if (buffer.length === 0) return;
chart.data.datasets[0].data.push(...buffer);
buffer.length = 0;
chart.update('none');
}, 200);
Для визуально плавных графиков применяется синхронизация с рендерным циклом браузера:
function renderLoop() {
if (buffer.length > 0) {
chart.data.datasets[0].data.push(...buffer);
buffer.length = 0;
chart.update('none');
}
requestAnimationFrame(renderLoop);
}
requestAnimationFrame(renderLoop);
Такой подход согласует обновления с частотой экрана.
Долгоживущие графики требуют стратегии удаления старых точек:
const MAX_POINTS = 300;
const dataset = chart.data.datasets[0];
dataset.data.push(newPoint);
while (dataset.data.length > MAX_POINTS) {
dataset.data.shift();
}
const now = Date.now();
const windowSize = 60000;
dataset.data = dataset.data.filter(p => now - p.x < windowSize);
Второй вариант предпочтителен для временных рядов.
При работе с несколькими источниками структура расширяется:
chart.data.datasets[0].data.push({ x, y: sensorA });
chart.data.datasets[1].data.push({ x, y: sensorB });
chart.update('none');
Важно синхронизировать временные метки, иначе визуальное сравнение теряет смысл.
Автоматическое масштабирование осей при потоковой передаче может вызывать визуальные скачки.
Для стабилизации применяются ограничения:
options: {
scales: {
y: {
min: 0,
max: 100
}
}
}
Или динамическое вычисление:
const values = dataset.data.map(p => p.y);
chart.options.scales.y.min = Math.min(...values);
chart.options.scales.y.max = Math.max(...values);
При высокочастотных потоках критичны следующие техники:
chart.update('none');
Использование примитивных объектов вместо сложных:
// быстрее
{ x: 123, y: 45 }
// медленнее
{ x: new Date(), y: computeValue(), meta: {...} }
Избегание полного пересчёта datasets:
chart.data.datasets[0].data[chart.data.datasets[0].data.length - 1] = newPoint;
chart.update('none');
При превышении допустимой частоты обновлений наблюдаются:
update();Решение заключается в принудительной дедупликации обновлений:
let scheduled = false;
function scheduleUpdate() {
if (scheduled) return;
scheduled = true;
requestAnimationFrame(() => {
chart.update('none');
scheduled = false;
});
}
При остановке потоков необходимо освобождать ресурсы:
socket.close();
chart.destroy();
Некорректное завершение приводит к утечкам памяти из-за сохранённых ссылок на DOM и обработчики событий.