Обзор архитектуры и принципов работы

Chart.js построен как модульная библиотека визуализации данных, ориентированная на работу через HTML5 Canvas. В основе лежит принцип разделения ответственности между слоями: подготовка данных, математическое преобразование, система отрисовки и управление состоянием графика. Такая структура позволяет изолировать логику вычислений от рендеринга и обеспечивает предсказуемое поведение при изменении данных или конфигурации.

Ключевая особенность архитектуры — наличие централизованного объекта графика, который управляет всеми компонентами: наборами данных, осями, элементами графика и плагинами. Этот объект выступает точкой координации, через которую проходят все изменения состояния.


Центральный объект Chart

Каждый график в Chart.js создаётся как экземпляр класса Chart. Он содержит:

  • конфигурацию (тип графика, данные, опции)
  • состояние элементов визуализации
  • зарегистрированные плагины
  • внутренний кэш вычислений

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

Важный архитектурный принцип — иммутабельность входной конфигурации. Библиотека не изменяет исходный объект конфигурации напрямую, а строит внутреннее состояние на его основе.


Модель данных

Данные в Chart.js структурированы вокруг двух основных сущностей:

  • datasets — наборы значений
  • labels — метки категорий или точек

Каждый dataset содержит:

  • массив данных
  • стиль отображения
  • метаинформацию для рендера

Данные не используются напрямую в отрисовке. Они проходят через слой адаптации, который преобразует их в внутренние элементы графика (points, bars, arcs и т.д.).


Система элементов (Elements)

Отрисовка графика основана на наборе примитивов:

  • PointElement
  • LineElement
  • BarElement
  • ArcElement

Каждый элемент отвечает за:

  • хранение координат
  • визуальные параметры (цвет, толщина, радиус)
  • логику отрисовки в Canvas
  • обработку анимаций

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


Контроллеры типов графиков

Архитектура Chart.js разделяет логику разных типов графиков через контроллеры (Controllers).

Примеры:

  • LineController
  • BarController
  • DoughnutController

Контроллер выполняет роль посредника между данными и элементами. Он:

  • преобразует dataset в элементы
  • управляет их жизненным циклом
  • обновляет позиции при изменении данных
  • синхронизирует элементы с осями

Контроллеры являются ключевым механизмом расширяемости. Добавление нового типа графика сводится к созданию нового контроллера и набора элементов.


Система масштабов (Scales)

Оси координат реализованы через систему масштабов:

  • linear
  • category
  • time
  • logarithmic

Каждый scale отвечает за:

  • преобразование данных в координаты canvas
  • вычисление диапазона значений
  • генерацию тик-марок (ticks)
  • форматирование отображаемых значений

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


Layout engine и геометрия

Chart.js использует внутренний механизм компоновки для расчёта размеров элементов:

  • области графика
  • отступов
  • легенды
  • заголовков
  • осей

Каждый компонент сообщает свои требования к пространству. Далее выполняется распределение доступной области с учётом приоритетов и ограничений.

Геометрическая модель строится вокруг canvas-координат, где все элементы переводятся в абсолютные пиксели перед отрисовкой.


Слой рендеринга

Основной механизм вывода — HTML5 Canvas API. Chart.js не использует DOM для построения графиков, что обеспечивает:

  • высокую производительность
  • минимальные накладные расходы
  • предсказуемую отрисовку

Рендеринг проходит в несколько стадий:

  1. очистка canvas
  2. вычисление изменений состояния
  3. отрисовка элементов по слоям
  4. применение анимации

Элементы отрисовываются последовательно, часто с разделением по z-index логике: фоновые слои, данные, интерактивные элементы.


Анимационная подсистема

Анимации основаны на интерполяции состояний между предыдущим и новым значением.

Каждый элемент хранит:

  • предыдущее состояние
  • целевое состояние
  • прогресс анимации

Анимационный движок управляет временем и функцией easing. При обновлении данных система не пересоздаёт график, а плавно изменяет параметры существующих элементов.

Это позволяет:

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

Плагинная архитектура

Chart.js построен как расширяемая система через плагины.

Плагины подключаются к жизненному циклу графика через хуки:

  • beforeInit
  • afterUpdate
  • beforeDraw
  • afterDraw
  • resize
  • destroy

Плагин может:

  • модифицировать данные
  • изменять поведение рендера
  • добавлять новые визуальные элементы
  • перехватывать события

Такой механизм позволяет расширять функциональность без изменения ядра библиотеки.


Событийная модель

Интерактивность реализована через систему событий Canvas:

  • движение мыши
  • клики
  • hover
  • touch-события

Chart.js преобразует координаты курсора в контекст данных графика. Далее происходит hit-testing — определение, над каким элементом находится указатель.

Результатом является набор активных элементов, которые могут подсвечиваться или использоваться для всплывающих подсказок.


Обновление состояния и реактивность

Обновление графика происходит через метод update(), который инициирует полный цикл перерасчёта:

  • анализ изменений конфигурации
  • пересчёт данных
  • обновление scales
  • реконструкция элементов при необходимости
  • перерисовка canvas

Система оптимизирует обновления, избегая полного пересоздания графика, если изменения локальны (например, обновление данных одного dataset).


Иерархия внутренних компонентов

Внутренняя структура Chart.js можно представить как иерархию:

  • Chart (корневой объект)

    • Config (конфигурация)
    • Data (datasets и labels)
    • Controllers (логика типов графиков)
    • Scales (оси и преобразования)
    • Elements (визуальные примитивы)
    • Plugins (расширения)
    • Animator (анимации)
    • Layout Engine (геометрия)

Эта иерархия обеспечивает строгую модульность и разделение ответственности между слоями.


Принципы проектирования

Архитектура Chart.js основана на нескольких устойчивых принципах:

Разделение данных и представления Данные не зависят от рендера и наоборот.

Композиция вместо наследования Поведение формируется через комбинацию контроллеров, элементов и плагинов.

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

Минимизация прямых зависимостей Компоненты взаимодействуют через абстракции и контекст графика.

Детерминированный рендеринг При одинаковом входном состоянии результат отрисовки воспроизводим.


Поток выполнения при отрисовке

Типичный цикл построения графика включает последовательность:

  • инициализация конфигурации
  • создание контроллеров
  • подготовка datasets
  • вычисление scales
  • построение layout
  • генерация элементов
  • вычисление позиций
  • рендеринг на canvas
  • запуск анимации (если требуется)

Каждый этап строго отделён, что упрощает диагностику и расширение функциональности.


Оптимизационные механизмы

Для обеспечения производительности применяются:

  • кэширование вычисленных scale-значений
  • повторное использование элементов
  • минимизация перерасчётов layout
  • частичная перерисовка canvas
  • batching обновлений

Эти механизмы позволяют Chart.js эффективно работать даже с большими наборами данных и высокой частотой обновлений.