Легенда в Chart.js представляет собой интерактивный элемент интерфейса, отображающий список наборов данных графика. По умолчанию легенда умеет:
Стандартное поведение особенно полезно при работе с графиками, содержащими большое количество наборов данных.
Базовая конфигурация легенды располагается в разделе
plugins.legend.
const config = {
type: 'line',
data,
options: {
plugins: {
legend: {
display: true
}
}
}
};
По умолчанию Chart.js автоматически скрывает или показывает dataset при клике по элементу легенды.
const config = {
type: 'bar',
data: {
labels: ['Январь', 'Февраль', 'Март'],
datasets: [
{
label: 'Продажи',
data: [10, 20, 30],
backgroundColor: 'royalblue'
},
{
label: 'Расходы',
data: [5, 15, 25],
backgroundColor: 'tomato'
}
]
}
};
При клике по пункту «Продажи» соответствующий dataset скрывается. Повторный клик возвращает его обратно.
onClickДля переопределения стандартного поведения используется обработчик
onClick.
options: {
plugins: {
legend: {
onClick(event, legendItem, legend) {
}
}
}
}
| Аргумент | Описание |
|---|---|
event |
Объект события браузера |
legendItem |
Информация о нажатом элементе |
legend |
Экземпляр легенды |
Каждый элемент легенды связан с dataset через свойство
datasetIndex.
onClick(event, legendItem, legend) {
const index = legendItem.datasetIndex;
console.log(index);
}
Значение индекса соответствует позиции dataset внутри массива
datasets.
datasets: [
{ label: 'A' }, // index = 0
{ label: 'B' }, // index = 1
{ label: 'C' } // index = 2
]
Экземпляр графика доступен через legend.chart.
onClick(event, legendItem, legend) {
const chart = legend.chart;
console.log(chart);
}
Через объект графика можно:
Chart.js предоставляет методы hide() и
show().
onClick(event, legendItem, legend) {
const chart = legend.chart;
const index = legendItem.datasetIndex;
if (chart.isDatasetVisible(index)) {
chart.hide(index);
} else {
chart.show(index);
}
}
Метод:
chart.isDatasetVisible(index)
возвращает true или false.
При указании собственного onClick встроенная логика
больше не используется.
Стандартный механизм можно восстановить вручную.
const defaultLegendClickHandler =
Chart.defaults.plugins.legend.onClick;
const config = {
type: 'line',
data,
options: {
plugins: {
legend: {
onClick(event, legendItem, legend) {
console.log('Клик по легенде');
defaultLegendClickHandler(
event,
legendItem,
legend
);
}
}
}
}
};
Подобная схема позволяет расширять стандартное поведение без его потери.
Обработчик может менять стили datasets.
onClick(event, legendItem, legend) {
const chart = legend.chart;
const dataset =
chart.data.datasets[legendItem.datasetIndex];
dataset.backgroundColor = 'rgba(65, 105, 225, 0.2)';
chart.update();
}
onClick(event, legendItem, legend) {
const chart = legend.chart;
const dataset =
chart.data.datasets[legendItem.datasetIndex];
dataset.borderColor = 'red';
chart.update();
}
onClick(event, legendItem, legend) {
const chart = legend.chart;
const dataset =
chart.data.datasets[legendItem.datasetIndex];
dataset.borderWidth =
dataset.borderWidth === 2 ? 6 : 2;
chart.update();
}
Обработчик способен изменять сразу несколько наборов данных.
onClick(event, legendItem, legend) {
const chart = legend.chart;
const selectedIndex = legendItem.datasetIndex;
chart.data.datasets.forEach((dataset, index) => {
if (index === selectedIndex) {
chart.show(index);
} else {
chart.hide(index);
}
});
}
Подобный режим напоминает фильтрацию данных.
Иногда требуется поведение, аналогичное radio-button.
onClick(event, legendItem, legend) {
const chart = legend.chart;
const clickedIndex = legendItem.datasetIndex;
const visibleCount =
chart.data.datasets.filter((_, i) =>
chart.isDatasetVisible(i)
).length;
chart.data.datasets.forEach((_, i) => {
if (i === clickedIndex) {
if (visibleCount === 1 &&
chart.isDatasetVisible(i)) {
chart.show(i);
} else {
chart.show(i);
}
} else {
chart.hide(i);
}
});
}
При сложных визуализациях datasets могут объединяться в логические группы.
const groups = {
sales: [0, 1],
expenses: [2, 3]
};
onClick(event, legendItem, legend) {
const chart = legend.chart;
const groupName =
legendItem.text.toLowerCase();
const indexes = groups[groupName];
indexes.forEach(index => {
if (chart.isDatasetVisible(index)) {
chart.hide(index);
} else {
chart.show(index);
}
});
}
Обработчик способен полностью менять содержимое datasets.
onClick(event, legendItem, legend) {
const chart = legend.chart;
chart.data.datasets[0].data = [
100,
200,
300
];
chart.update();
}
onClick(event, legendItem, legend) {
const chart = legend.chart;
chart.data.datasets.push({
label: 'Новый dataset',
data: [5, 15, 25],
backgroundColor: 'green'
});
chart.update();
}
onClick(event, legendItem, legend) {
const chart = legend.chart;
chart.data.datasets.splice(
legendItem.datasetIndex,
1
);
chart.update();
}
Часто требуется хранить дополнительные флаги.
const state = {
selectedDataset: null
};
const config = {
options: {
plugins: {
legend: {
onClick(event, legendItem, legend) {
state.selectedDataset =
legendItem.datasetIndex;
console.log(state);
}
}
}
}
};
После изменения графика обычно вызывается:
chart.update();
Метод запускает повторную отрисовку с анимацией.
chart.update('none');
onHoverЛегенда поддерживает реакцию на наведение.
legend: {
onHover(event, legendItem, legend) {
event.native.target.style.cursor =
'pointer';
}
}
onLeavelegend: {
onLeave(event, legendItem, legend) {
event.native.target.style.cursor =
'default';
}
}
Популярный интерфейсный приём — выделение выбранной серии.
onClick(event, legendItem, legend) {
const chart = legend.chart;
const selected = legendItem.datasetIndex;
chart.data.datasets.forEach((dataset, index) => {
if (index === selected) {
dataset.borderWidth = 5;
} else {
dataset.borderWidth = 1;
}
});
chart.update();
}
generateLabelsChart.js позволяет полностью изменить содержимое легенды.
legend: {
labels: {
generateLabels(chart) {
return chart.data.datasets.map(
(dataset, index) => ({
text: dataset.label,
fillStyle: dataset.backgroundColor,
hidden: !chart.isDatasetVisible(index),
datasetIndex: index
})
);
}
}
}
После этого onClick продолжает работать с
пользовательскими элементами.
Canvas-легенда имеет ограничения. Для полной свободы часто создаётся HTML-версия.
<div id="legend-container"></div>
<canvas id="myChart"></canvas>
const legendContainer =
document.getElementById('legend-container');
chart.data.datasets.forEach((dataset, index) => {
const button =
document.createElement('button');
button.textContent = dataset.label;
button.addEventListener('click', () => {
if (chart.isDatasetVisible(index)) {
chart.hide(index);
} else {
chart.show(index);
}
});
legendContainer.appendChild(button);
});
HTML-подход позволяет:
Chart.js не предоставляет встроенный dblclick, однако
обработку можно реализовать вручную.
let lastClick = 0;
onClick(event, legendItem, legend) {
const currentTime = Date.now();
if (currentTime - lastClick < 300) {
console.log('Двойной клик');
}
lastClick = currentTime;
}
При интеграции с React, Vue или Angular обработчик легенды часто обновляет состояние приложения.
onClick(event, legendItem, legend) {
appState.activeDataset =
legendItem.datasetIndex;
renderUI();
}
Частые вызовы chart.update() могут снижать
производительность при больших объёмах данных.
Оптимизации:
update();chart.update()Изменения datasets не отображаются без обновления графика.
dataset.borderColor = 'red';
// Нет update()
datasetIndexconst dataset =
chart.data.datasets[legendItem.datasetIndex];
При удалении datasets индексы могут изменяться.
Если вызывается собственная логика и стандартный обработчик одновременно, возможно двойное переключение видимости.
defaultLegendClickHandler(...);
// Дополнительный hide/show
Для крупных проектов обработчики рекомендуется выносить в отдельные функции.
function handleLegendClick(
event,
legendItem,
legend
) {
const chart = legend.chart;
toggleDataset(
chart,
legendItem.datasetIndex
);
}
class LegendController {
constructor(chart) {
this.chart = chart;
}
toggle(index) {
if (this.chart.isDatasetVisible(index)) {
this.chart.hide(index);
} else {
this.chart.show(index);
}
}
}
Плагины Chart.js способны перехватывать события легенды.
const plugin = {
id: 'legendPlugin',
beforeEvent(chart, args) {
console.log(args.event);
}
};
Подключение:
new Chart(ctx, {
type: 'line',
data,
plugins: [plugin]
});
const config = {
type: 'line',
data: {
labels: ['A', 'B', 'C'],
datasets: [
{
label: 'Продажи',
data: [10, 20, 30],
borderColor: 'blue'
},
{
label: 'Расходы',
data: [5, 15, 25],
borderColor: 'red'
}
]
},
options: {
plugins: {
legend: {
onClick(event, legendItem, legend) {
const chart = legend.chart;
const index =
legendItem.datasetIndex;
chart.data.datasets.forEach(
(dataset, i) => {
dataset.borderWidth =
i === index ? 5 : 1;
}
);
if (
chart.isDatasetVisible(index)
) {
chart.hide(index);
} else {
chart.show(index);
}
chart.update();
},
onHover(event) {
event.native.target.style.cursor =
'pointer';
},
onLeave(event) {
event.native.target.style.cursor =
'default';
}
}
}
}
};