Кастомные шкалы (Scale)

Шкалы в Chart.js представляют собой отдельный слой системы визуализации, отвечающий за преобразование входных данных в координаты на графике. Любая диаграмма, независимо от типа, опирается на минимум одну шкалу, которая определяет, как значения интерпретируются и отображаются на осях.

Внутренняя архитектура Chart.js построена вокруг модульной системы, где шкалы являются независимыми компонентами. Каждая шкала:

  • получает набор данных от датасетов;
  • преобразует значения в координатную систему;
  • вычисляет диапазон отображения;
  • генерирует метки (ticks);
  • управляет сеткой и визуальными элементами оси.

Связь между датасетами и шкалами задаётся через свойства xAxisID и yAxisID. Это позволяет одному графику использовать несколько независимых осей, включая пользовательские реализации.

Шкала описывается как класс, наследующий базовый Scale, который предоставляет общий контракт поведения: обработку данных, построение диапазона и отрисовку.

Базовые типы шкал и их поведение

Chart.js предоставляет несколько встроенных типов шкал, каждый из которых решает определённый класс задач.

Категориальная шкала (category scale) Используется для дискретных значений. Преобразует строки в равномерно распределённые позиции.

Особенности:

  • не выполняет числовую интерполяцию;
  • порядок определяется входным массивом;
  • полезна для временных меток без числового анализа.

Линейная шкала (linear scale) Работает с числовыми значениями и обеспечивает равномерное распределение.

Характеристики:

  • поддержка min/max;
  • автоматический подбор шагов;
  • линейное преобразование значений.

Логарифмическая шкала (logarithmic scale) Применяется для экспоненциальных данных.

Особенности:

  • невозможность обработки нулевых и отрицательных значений;
  • визуализация больших диапазонов.

Временная шкала (time scale) Использует адаптеры даты и времени.

Особенности:

  • поддержка различных форматов дат;
  • автоматическое масштабирование по временным интервалам;
  • зависимость от подключаемого date-adapter.

Радиальная шкала (radial scale) Используется в radar и polarArea диаграммах.

Особенности:

  • преобразование значений в угловую систему координат;
  • центрирование вокруг центральной точки.

Концепция регистрации пользовательских шкал

Механизм расширения Chart.js построен на регистрации классов шкал в глобальном реестре. Каждая новая шкала должна быть зарегистрирована перед использованием.

Регистрация выполняется через систему регистрации компонентов:

import { Chart } from 'chart.js';

class CustomScale extends Chart.Scale {
  constructor(cfg) {
    super(cfg);
  }

  parse(raw, index) {
    return Number(raw);
  }

  determineDataLimits() {
    const { min, max } = this.getMinMax(true);
    this.min = min;
    this.max = max;
  }

  buildTicks() {
    const ticks = [];
    for (let i = this.min; i <= this.max; i += 10) {
      ticks.push({ value: i });
    }
    return ticks;
  }

  getPixelForValue(value) {
    const range = this.max - this.min;
    const percent = (value - this.min) / range;
    return this.left + percent * this.width;
  }

  getValueForPixel(pixel) {
    const percent = (pixel - this.left) / this.width;
    return this.min + percent * (this.max - this.min);
  }
}

Chart.register(CustomScale);

После регистрации шкала становится доступной через конфигурацию type.

Жизненный цикл шкалы

Каждая шкала проходит несколько этапов обработки данных:

1. Инициализация Создание экземпляра и привязка к конфигурации графика.

2. Парсинг данных Метод parse() преобразует входные значения датасетов в внутренний формат.

3. Определение диапазона Метод determineDataLimits() вычисляет min/max.

4. Построение тиков Метод buildTicks() формирует набор меток.

5. Расчёт координат Методы getPixelForValue() и getValueForPixel() выполняют двунаправленное преобразование.

6. Отрисовка Включает построение оси, сетки и подписей.

Взаимодействие шкал с датасетами

Каждый датасет может явно указывать, к какой шкале он привязан:

datasets: [
  {
    label: 'Продажи',
    data: [10, 20, 30],
    yAxisID: 'y'
  }
]

Если yAxisID не задан, используется шкала по умолчанию.

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

Кастомизация генерации тиков

Одним из ключевых механизмов расширения является контроль над построением меток оси.

buildTicks() {
  const step = this.options.stepSize || 5;
  const ticks = [];

  for (let v = this.min; v <= this.max; v += step) {
    ticks.push({
      value: v,
      label: `~${v}`
    });
  }

  return ticks;
}

Дополнительно может использоваться метод formatTicks, позволяющий изменить отображаемый текст без изменения значений.

Форматирование и адаптация значений

Chart.js разделяет внутренние значения и отображаемые подписи. Это позволяет сохранять точность данных при гибком UI-форматировании.

Ключевые методы:

  • valueToLabelConversion
  • formatValue
  • afterTickToLabelConversion

Пример форматирования:

options: {
  scales: {
    y: {
      ticks: {
        callback: (value) => `${value} ₽`
      }
    }
  }
}

В кастомной шкале аналогичное поведение реализуется через переопределение метода:

formatValue(value) {
  return `${value.toFixed(2)} units`;
}

Геометрия преобразований координат

Любая шкала обязана реализовать преобразование между значением и пикселем.

Линейное преобразование выражается через нормализацию:

percent = (value - min) / (max - min)
pixel = start + percent * length

Обратное преобразование:

value = min + (pixel - start) / length * (max - min)

Эти формулы являются основой большинства кастомных реализаций шкал.

Несколько шкал на одной оси

Chart.js поддерживает конфигурации, где несколько шкал работают параллельно:

scales: {
  y: {
    type: 'linear',
    position: 'left'
  },
  y2: {
    type: 'linear',
    position: 'right'
  }
}

Это позволяет разделять данные по масштабу без искажения визуального восприятия.

Кастомные шкалы могут комбинировать разные стратегии отображения, например, логарифмическое преобразование с линейной отрисовкой.

Интеграция с системой плагинов

Шкалы тесно связаны с системой плагинов Chart.js. Плагины могут:

  • модифицировать ticks перед отрисовкой;
  • изменять диапазоны min/max;
  • вмешиваться в процесс парсинга;
  • переопределять поведение координат.

Пример перехвата тиков:

const plugin = {
  beforeBuildTicks(scale) {
    if (scale.id === 'y') {
      scale.options.ticks.stepSize = 2;
    }
  }
};

Сложные кастомные сценарии

Кастомные шкалы применяются в случаях, когда стандартные типы не покрывают задачу:

  • нестандартные единицы измерения;
  • дискретные бизнес-метрики;
  • комбинированные оси (например, индекс + процент);
  • шкалы с нелинейной сегментацией;
  • визуализация категорий с порядковым смыслом.

Пример нелинейной шкалы с сегментацией:

getPixelForValue(value) {
  if (value < 50) {
    return this.left + (value / 50) * (this.width * 0.3);
  }
  return this.left + this.width * 0.3 + ((value - 50) / 50) * (this.width * 0.7);
}

Управление диапазонами и автонастройка

Автоматическое определение границ реализуется через анализ датасетов.

Типовой алгоритм:

  • проход по всем значениям;
  • вычисление min/max;
  • применение padding;
  • округление до “красивых” чисел.
determineDataLimits() {
  const values = this.getMatchingVisibleMetas()
    .flatMap(meta => meta.data);

  this.min = Math.min(...values);
  this.max = Math.max(...values);

  const padding = (this.max - this.min) * 0.1;

  this.min -= padding;
  this.max += padding;
}

Поведение шкал при обновлении данных

Шкалы пересчитываются при изменении датасетов. При этом учитываются режимы обновления:

  • update(): полное пересчитывание;
  • resize(): изменение геометрии без пересчёта данных;
  • reset(): сброс анимационного состояния.

В сложных сценариях кастомные шкалы могут кэшировать промежуточные вычисления для повышения производительности.

Расширение прототипа шкалы

Базовый класс Scale предоставляет набор методов, которые могут быть переопределены:

  • init()
  • parse()
  • buildTicks()
  • getPixelForValue()
  • getValueForPixel()
  • draw()

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

Внутренние зависимости шкал

Шкалы взаимодействуют с несколькими системами:

  • контроллеры элементов (bar, line, point);
  • layout engine (распределение пространства);
  • animation engine (интерполяции значений);
  • tooltip и legend системы.

Любое изменение шкалы отражается на всех слоях визуализации, поскольку координатная система является центральным элементом рендеринга.