Синхронизация графиков в Chart.js предполагает согласованное поведение нескольких экземпляров диаграмм, при котором взаимодействие с одним графиком приводит к изменениям состояния других. Подобный подход используется для анализа временных рядов, сравнительных визуализаций и дашбордов, где требуется единый контекст отображения данных.
Библиотека Chart.js изначально не предоставляет встроенной глобальной синхронизации между графиками, однако её архитектура на основе событий, плагинов и программного управления состоянием позволяет реализовать синхронное поведение через пользовательские механизмы.
Каждый график Chart.js представляет собой независимый экземпляр
класса Chart, содержащий:
data)options)Синхронизация достигается за счёт:
mousemove,
mouseout, click)setActiveElements,
update, resetZoomОдним из наиболее частых сценариев является синхронизация tooltip между несколькими графиками по оси X.
Основная идея заключается в вычислении индекса активного элемента и применении его ко всем связанным графикам.
function syncTooltips(sourceChart, charts, event) {
const points = sourceChart.getElementsAtEventForMode(
event,
'index',
{ intersect: false },
false
);
if (!points.length) return;
const index = points[0].index;
charts.forEach(chart => {
const activeElements = chart.data.datasets.map((_, datasetIndex) => ({
datasetIndex,
index
}));
chart.setActiveElements(activeElements);
chart.tooltip.setActiveElements(activeElements, {
x: event.x,
y: event.y
});
chart.update('none');
});
}
'none') для
минимизации задержекПри использовании chartjs-plugin-zoom появляется
возможность синхронизировать масштаб и прокрутку.
Один график выступает источником состояния масштаба, остальные получают его через:
chart.scaleschart.getZoomedScaleBoundszoom и panfunction syncZoom(sourceChart, charts) {
const xScale = sourceChart.scales.x;
const min = xScale.min;
const max = xScale.max;
charts.forEach(chart => {
if (chart === sourceChart) return;
chart.options.scales.x.min = min;
chart.options.scales.x.max = max;
chart.update('none');
});
}
x)При синхронизации нескольких графиков возникает риск бесконечного цикла обновлений. Решение основано на временном отключении обработчиков.
let isSyncing = false;
function safeSync(callback) {
if (isSyncing) return;
isSyncing = true;
callback();
isSyncing = false;
}
Использование:
chart.canvas.addEventListener('mousemove', (event) => {
safeSync(() => syncTooltips(chart, charts, event));
});
Crosshair позволяет отображать вертикальную или горизонтальную линию на всех графиках одновременно.
Реализация обычно выполняется через пользовательский плагин.
const crosshairPlugin = {
id: 'crosshairSync',
afterEvent(chart, args) {
const event = args.event;
if (!event || event.type !== 'mousemove') return;
syncCrosshair(chart, event);
}
};
function syncCrosshair(sourceChart, event) {
const x = event.x;
window.syncedCharts.forEach(chart => {
chart.crosshairX = x;
chart.draw();
});
}
При большом количестве графиков локальная синхронизация становится сложной. Используется централизованный event bus.
const bus = {
events: {},
on(event, handler) {
if (!this.events[event]) this.events[event] = [];
this.events[event].push(handler);
},
emit(event, payload) {
(this.events[event] || []).forEach(h => h(payload));
}
};
bus.on('tooltip-sync', ({ chart, event }) => {
syncTooltips(chart, window.charts, event);
});
bus.on('zoom-sync', ({ chart }) => {
syncZoom(chart, window.charts);
});
chart.canvas.addEventListener('mousemove', (event) => {
bus.emit('tooltip-sync', { chart, event });
});
Chart.js хранит активные элементы в массиве
activeElements. Их синхронизация позволяет выделять
одинаковые точки на нескольких графиках.
function syncActiveElements(sourceChart, charts, event) {
const elements = sourceChart.getElementsAtEventForMode(
event,
'nearest',
{ intersect: true },
false
);
charts.forEach(chart => {
chart.setActiveElements(elements);
chart.update('none');
});
}
Особенность заключается в том, что активные элементы должны соответствовать одинаковой структуре datasets.
При работе с временными рядами часто используется тип оси
time. Синхронизация требует унификации:
function syncTimeScale(sourceChart, charts) {
const scale = sourceChart.scales.x;
charts.forEach(chart => {
chart.options.scales.x.min = scale.min;
chart.options.scales.x.max = scale.max;
chart.update('none');
});
}
При большом количестве графиков критично учитывать:
update)requestAnimationFramefunction throttle(fn, delay) {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall < delay) return;
lastCall = now;
fn(...args);
};
}
Для упрощения синхронизации используется контейнер:
const chartGroup = {
charts: [],
add(chart) {
this.charts.push(chart);
},
syncTooltip(source, event) {
syncTooltips(source, this.charts, event);
},
syncZoom(source) {
syncZoom(source, this.charts);
}
};
Такой подход уменьшает связанность и упрощает расширение системы.
Если графики получают данные из разных источников, синхронизация требует учета:
updatedataReadyPromise.all(dataPromises).then(() => {
charts.forEach(chart => chart.update());
syncZoom(referenceChart, charts);
});
Chart.js plugin API позволяет встроить синхронизацию на уровне жизненного цикла:
beforeInitafterEventafterUpdateafterRenderconst syncPlugin = {
id: 'syncPlugin',
afterEvent(chart, args) {
const event = args.event;
if (!event) return;
syncTooltips(chart, window.charts, event);
},
afterUpdate(chart) {
syncZoom(chart, window.charts);
}
};
При увеличении количества графиков применяется многослойная модель:
Такое разделение позволяет: