В многосерийных графиках Chart.js часто используется несколько осей Y, каждая из которых обслуживает собственный набор данных. Такая конфигурация применяется для отображения величин разного масштаба: температуры и давления, доходов и процентов, количества и средних значений. При этом стандартное поведение tooltip может становиться недостаточно информативным, поскольку он по умолчанию группирует данные, не всегда учитывая связь между осями.
В Chart.js каждая ось задаётся через объект scales, а
набор данных связывается с конкретной осью через параметр
yAxisID.
options: {
scales: {
y: {
type: 'linear',
position: 'left'
},
y1: {
type: 'linear',
position: 'right'
}
}
}
data: {
datasets: [
{
label: 'Температура',
data: [12, 19, 3],
yAxisID: 'y'
},
{
label: 'Давление',
data: [101, 98, 105],
yAxisID: 'y1'
}
]
}
Связь через yAxisID является ключевой для корректного
отображения значений в tooltip, особенно когда требуется объединить
данные разных шкал в одном всплывающем окне.
По умолчанию tooltip в Chart.js отображает все элементы, находящиеся под курсором, независимо от их оси. Однако значения из разных шкал могут восприниматься неоднозначно без явного указания контекста.
При использовании нескольких осей возникает несколько типичных задач:
Ключевым параметром, влияющим на tooltip, является
interaction.
options: {
interaction: {
mode: 'index',
intersect: false
}
}
Режим index обеспечивает группировку всех значений с
одинаковым индексом по оси X. Это позволяет отображать данные разных
осей в одном tooltip-блоке.
Параметр intersect: false расширяет область захвата,
делая tooltip менее чувствительным к точному наведению на точку.
Для объединения значений разных осей важно не только включить режим
index, но и корректно настроить отображение строк через
callbacks.
label
callbackoptions: {
plugins: {
tooltip: {
callbacks: {
label: function(context) {
const dataset = context.dataset;
const value = context.raw;
return `${dataset.label}: ${value}`;
}
}
}
}
}
На этом уровне уже сохраняется базовая структура отображения, но отсутствует явная привязка к оси.
При наличии нескольких шкал логично явно указывать, к какой оси относится значение.
callbacks: {
label: function(context) {
const axisId = context.dataset.yAxisID;
const value = context.raw;
let axisLabel = '';
if (axisId === 'y') {
axisLabel = 'левая ось';
} else if (axisId === 'y1') {
axisLabel = 'правая ось';
}
return `${context.dataset.label} (${axisLabel}): ${value}`;
}
}
Такое расширение делает tooltip семантически однозначным при любых масштабах данных.
При нескольких осях важно управлять порядком отображения строк.
Chart.js предоставляет механизм itemSort.
callbacks: {
itemSort: function(a, b) {
return a.dataset.yAxisID.localeCompare(b.dataset.yAxisID);
}
}
Это позволяет группировать значения по оси, например сначала все элементы левой шкалы, затем правой.
Дополнительно применяется filter, если требуется скрыть
отдельные наборы данных:
callbacks: {
filter: function(context) {
return context.dataset.hidden !== true;
}
}
Стандартный tooltip ограничен по структуре. При необходимости полного контроля над отображением используется внешний tooltip.
options: {
plugins: {
tooltip: {
enabled: false,
external: function(context) {
const tooltip = context.tooltip;
if (!tooltip.opacity) {
return;
}
const bodyLines = tooltip.body.map(b => b.lines);
const content = bodyLines.map(lines => {
return `<div>${lines}</div>`;
}).join('');
const el = document.getElementById('custom-tooltip');
el.innerHTML = content;
}
}
}
}
Внешний tooltip позволяет:
yAxisIDПри наличии разных шкал значения часто требуют форматирования. Например, проценты и абсолютные величины не должны отображаться одинаково.
callbacks: {
label: function(context) {
const axis = context.dataset.yAxisID;
const value = context.raw;
if (axis === 'y1') {
return `${context.dataset.label}: ${value}%`;
}
return `${context.dataset.label}: ${value}`;
}
}
Дополнительно возможно применение Intl.NumberFormat:
const formatter = new Intl.NumberFormat('ru-RU');
callbacks: {
label: function(context) {
return `${context.dataset.label}: ${formatter.format(context.raw)}`;
}
}
При анимации данных tooltip может обновляться асинхронно. Важно
учитывать, что порядок datasets и их привязка к осям должна
оставаться стабильной.
Рекомендуется:
yAxisID для каждого datasetchart.update()При использовании stacked: true для одной оси и
независимой шкалы для другой tooltip может отображать значения, которые
визуально не сопоставимы.
scales: {
y: {
stacked: true
},
y1: {
stacked: false
}
}
В таком случае tooltip остаётся единым, но логика интерпретации значений должна учитывать различие режимов.
Каждый элемент tooltip содержит:
datasetIndexdataIndexdatasetparsedЭти параметры позволяют точно управлять выводом:
callbacks: {
label: function(context) {
const { datasetIndex, dataIndex } = context;
return `D${datasetIndex}[${dataIndex}]: ${context.raw}`;
}
}
Такой подход используется при сложной агрегации данных из разных осей в один визуальный блок.
При наличии нескольких графиков с общими осами X tooltip может быть
синхронизирован через external controller или через события
mousemove. В таких сценариях важно сохранять соответствие
index, иначе значения разных осей могут
рассинхронизироваться.
При необходимости единообразного отображения всех осей применяется ручная группировка:
callbacks: {
label: function(context) {
return {
axis: context.dataset.yAxisID,
label: context.dataset.label,
value: context.raw
};
}
}
Далее результат форматируется в afterBody:
callbacks: {
afterBody: function(items) {
const grouped = {};
items.forEach(item => {
const axis = item.dataset.yAxisID;
if (!grouped[axis]) {
grouped[axis] = [];
}
grouped[axis].push(item);
});
return Object.keys(grouped).map(axis => {
return `${axis}: ${grouped[axis].length} значений`;
});
}
}
Такой подход используется для построения сложных аналитических tooltip-панелей.
При корректной конфигурации Chart.js tooltip становится агрегатором данных, объединяющим:
Ключевыми механизмами выступают interaction.mode,
yAxisID, callbacks tooltip и, при необходимости, внешний
renderer, обеспечивающий полную свободу компоновки информации.