Отображение нескольких осей в одном tooltip

В многосерийных графиках 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 при нескольких осях

По умолчанию tooltip в Chart.js отображает все элементы, находящиеся под курсором, независимо от их оси. Однако значения из разных шкал могут восприниматься неоднозначно без явного указания контекста.

При использовании нескольких осей возникает несколько типичных задач:

  • группировка данных по точке по оси X
  • сохранение принадлежности значения к конкретной оси
  • визуальное разделение значений внутри tooltip
  • предотвращение путаницы между разными шкалами

Настройка режима взаимодействия

Ключевым параметром, влияющим на tooltip, является interaction.

options: {
  interaction: {
    mode: 'index',
    intersect: false
  }
}

Режим index обеспечивает группировку всех значений с одинаковым индексом по оси X. Это позволяет отображать данные разных осей в одном tooltip-блоке.

Параметр intersect: false расширяет область захвата, делая tooltip менее чувствительным к точному наведению на точку.

Формирование единого tooltip для разных осей

Для объединения значений разных осей важно не только включить режим index, но и корректно настроить отображение строк через callbacks.

Использование label callback

options: {
  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 семантически однозначным при любых масштабах данных.

Сортировка и группировка элементов 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 ограничен по структуре. При необходимости полного контроля над отображением используется внешний 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
  • выводить кастомные подписи шкал
  • форматировать значения независимо от Chart.js

Форматирование значений с учётом масштаба осей

При наличии разных шкал значения часто требуют форматирования. Например, проценты и абсолютные величины не должны отображаться одинаково.

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 при анимации

При анимации данных tooltip может обновляться асинхронно. Важно учитывать, что порядок datasets и их привязка к осям должна оставаться стабильной.

Рекомендуется:

  • фиксировать yAxisID для каждого dataset
  • избегать динамического изменения структуры scales без пересоздания графика
  • синхронизировать обновление данных через chart.update()

Особенности поведения при стэкинге и множественных шкалах

При использовании stacked: true для одной оси и независимой шкалы для другой tooltip может отображать значения, которые визуально не сопоставимы.

scales: {
  y: {
    stacked: true
  },
  y1: {
    stacked: false
  }
}

В таком случае tooltip остаётся единым, но логика интерпретации значений должна учитывать различие режимов.

Контроль отображения через dataIndex и datasetIndex

Каждый элемент tooltip содержит:

  • datasetIndex
  • dataIndex
  • dataset
  • parsed

Эти параметры позволяют точно управлять выводом:

callbacks: {
  label: function(context) {
    const { datasetIndex, dataIndex } = context;

    return `D${datasetIndex}[${dataIndex}]: ${context.raw}`;
  }
}

Такой подход используется при сложной агрегации данных из разных осей в один визуальный блок.

Синхронизация tooltip между несколькими графиками

При наличии нескольких графиков с общими осами X tooltip может быть синхронизирован через external controller или через события mousemove. В таких сценариях важно сохранять соответствие index, иначе значения разных осей могут рассинхронизироваться.

Объединение значений разных осей в структурированный tooltip

При необходимости единообразного отображения всех осей применяется ручная группировка:

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-панелей.

Итоговая модель поведения tooltip при нескольких осях

При корректной конфигурации Chart.js tooltip становится агрегатором данных, объединяющим:

  • значения разных шкал
  • наборы данных с различными единицами измерения
  • логически связанные точки по оси X

Ключевыми механизмами выступают interaction.mode, yAxisID, callbacks tooltip и, при необходимости, внешний renderer, обеспечивающий полную свободу компоновки информации.