Контроллеры в Chart.js представляют собой ключевой уровень архитектуры, отвечающий за создание и управление поведением конкретных типов диаграмм. Именно контроллер определяет, как интерпретируются данные, какие элементы рендерятся на холсте и каким образом взаимодействуют dataset’ы с визуальными элементами графика.
Встроенная система контроллеров спроектирована так, чтобы её можно было расширять. Наследование от существующих контроллеров позволяет создавать новые типы графиков без необходимости писать всю логику рендеринга с нуля. Вместо этого используется переопределение только тех частей поведения, которые требуют модификации.
Каждый контроллер в Chart.js связывает три уровня:
Контроллер отвечает за:
Ключевая идея: контроллер — это слой между сырыми данными и отрисованной визуализацией.
Наследование целесообразно в ситуациях, когда:
Типичный пример — расширение BarController для создания stacked-like кастомной логики или добавление визуальных эффектов к LineController.
В современных версиях Chart.js контроллеры реализованы как классы:
Все они наследуются от базового Controller.
Базовый класс содержит общую логику:
Наследование реализуется через стандартный механизм ES6:
import { BarController } from 'chart.js';
class CustomBarController extends BarController {
// переопределения
}
При этом важно понимать, что контроллер не просто расширяет поведение, а участвует в жизненном цикле графика через заранее определённые методы.
При создании собственного контроллера обычно переопределяются следующие методы:
initialize()Используется для первичной настройки контроллера.
initialize() {
super.initialize();
this.customState = {};
}
Здесь важно вызывать super.initialize(), чтобы не
нарушить внутреннюю инициализацию Chart.js.
update(mode)Основной метод обновления данных и элементов.
update(mode) {
const meta = this.getMeta();
const data = this._data;
// кастомная обработка данных
super.update(mode);
}
Этот метод часто используется для изменения логики распределения элементов.
parse()Отвечает за преобразование сырых данных в внутренний формат.
parse(start, count) {
const parsed = super.parse(start, count);
return parsed.map(value => ({
...value,
customFlag: true
}));
}
draw()Используется для добавления кастомной отрисовки поверх стандартных элементов.
draw() {
super.draw();
const ctx = this.chart.ctx;
ctx.save();
ctx.fillStyle = 'rgba(0,0,0,0.1)';
ctx.fillRect(0, 0, this.chart.width, this.chart.height);
ctx.restore();
}
Частый сценарий — создание нового типа диаграммы на основе BarController.
import { BarController } from 'chart.js';
class HighlightBarController extends BarController {
draw() {
super.draw();
const meta = this.getMeta();
const ctx = this.chart.ctx;
meta.data.forEach(bar => {
if (bar.parsed.y > 50) {
ctx.save();
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.strokeRect(bar.x - bar.width / 2, bar.y, bar.width, this.chart.chartArea.bottom - bar.y);
ctx.restore();
}
});
}
}
Здесь базовая логика отрисовки столбцов сохраняется, а поверх добавляется дополнительное визуальное выделение.
После создания контроллера его необходимо зарегистрировать:
import { Chart } from 'chart.js';
Chart.register(HighlightBarController);
Далее можно использовать новый тип графика через конфигурацию:
const config = {
type: 'highlightBar',
data: {
labels: ['A', 'B', 'C'],
datasets: [{
data: [10, 60, 30]
}]
}
};
Имя типа графика обычно совпадает с именем контроллера в регистрационной системе.
Контроллер работает с meta-объектом, который хранит состояние элементов:
Наследование позволяет расширять meta:
getMeta() {
const meta = super.getMeta();
meta.customProperty = 'extended';
return meta;
}
Контроллер управляет созданием элементов через фабрики:
Пример переопределения:
createElements() {
const elements = super.createElements();
return elements.map(el => {
el.custom = true;
return el;
});
}
Эта техника позволяет внедрять дополнительные свойства в визуальные элементы без изменения ядра библиотеки.
Контроллер тесно связан с системой анимации Chart.js.
При наследовании важно учитывать:
Пример контроля анимации:
update(mode) {
this.options.animation = {
duration: 800,
easing: 'easeOutQuart'
};
super.update(mode);
}
Хотя один график обычно использует один контроллер, архитектура позволяет комбинировать поведение через:
В таких случаях наследование становится инструментом композиции поведения.
Несмотря на гибкость подхода, существуют ограничения:
При разработке собственного контроллера логика обычно строится по слоям:
Такой подход снижает вероятность конфликтов с внутренней архитектурой Chart.js и сохраняет совместимость с обновлениями библиотеки.