В библиотеке Chart.js легенда графика формируется автоматически на
основе наборов данных (datasets). Каждый элемент легенды
представляет отдельную серию данных и содержит:
Массив объектов легенды называется legendItems. Он
используется внутренним механизмом Chart.js для генерации визуальных
элементов легенды и может быть полностью переопределён.
Стандартная генерация легенды выполняется через:
options: {
plugins: {
legend: {
labels: {
generateLabels(chart) {
// Возврат массива legendItems
}
}
}
}
}
Метод generateLabels() позволяет вручную создавать
элементы легенды, управляя их содержимым и поведением.
Каждый элемент массива представляет объект со специальными свойствами.
Пример:
{
text: 'Продажи',
fillStyle: '#36A2EB',
strokeStyle: '#1E88E5',
lineWidth: 2,
hidden: false,
datasetIndex: 0
}
Основные свойства:
| Свойство | Назначение |
|---|---|
text |
Текст легенды |
fillStyle |
Цвет заливки маркера |
strokeStyle |
Цвет рамки |
lineWidth |
Толщина линии |
hidden |
Скрыт ли dataset |
datasetIndex |
Индекс набора данных |
lineCap |
Стиль окончания линии |
lineDash |
Пунктир |
lineJoin |
Соединение линий |
borderRadius |
Скругление |
fontColor |
Цвет текста |
pointStyle |
Тип маркера |
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Январь', 'Февраль', 'Март'],
datasets: [
{
label: 'Доход',
data: [10, 20, 30],
backgroundColor: '#4CAF50'
},
{
label: 'Расход',
data: [15, 12, 18],
backgroundColor: '#F44336'
}
]
},
options: {
plugins: {
legend: {
labels: {
generateLabels(chart) {
const datasets = chart.data.datasets;
return datasets.map((dataset, index) => ({
text: dataset.label,
fillStyle: dataset.backgroundColor,
hidden: !chart.isDatasetVisible(index),
datasetIndex: index
}));
}
}
}
}
}
});
В данном примере:
datasets;Во многих случаях требуется не полная замена легенды, а модификация стандартной логики.
Для этого используется встроенный генератор:
Chart.defaults.plugins.legend.labels.generateLabels(chart)
Пример:
generateLabels(chart) {
const original =
Chart.defaults.plugins.legend.labels.generateLabels(chart);
return original.map(item => {
item.text = 'Серия: ' + item.text;
return item;
});
}
Такой подход позволяет:
Часто легенда должна содержать дополнительную информацию.
Например:
Пример:
generateLabels(chart) {
return chart.data.datasets.map((dataset, index) => {
const total = dataset.data.reduce((a, b) => a + b, 0);
return {
text: `${dataset.label} (${total})`,
fillStyle: dataset.backgroundColor,
hidden: !chart.isDatasetVisible(index),
datasetIndex: index
};
});
}
Результат:
Доход (60)
Расход (45)
generateLabels(chart) {
const grandTotal = chart.data.datasets
.flatMap(ds => ds.data)
.reduce((a, b) => a + b, 0);
return chart.data.datasets.map((dataset, index) => {
const total = dataset.data.reduce((a, b) => a + b, 0);
const percent =
((total / grandTotal) * 100).toFixed(1);
return {
text: `${dataset.label} — ${percent}%`,
fillStyle: dataset.backgroundColor,
datasetIndex: index
};
});
}
generateLabels(chart) {
return chart.data.datasets.map((dataset, index) => ({
text: dataset.label,
fillStyle:
dataset.data[0] > 20
? '#4CAF50'
: '#F44336',
datasetIndex: index
}));
}
generateLabels(chart) {
const ctx = chart.ctx;
const gradient = ctx.createLinearGradient(0, 0, 100, 0);
gradient.addColorStop(0, '#2196F3');
gradient.addColorStop(1, '#9C27B0');
return chart.data.datasets.map((dataset, index) => ({
text: dataset.label,
fillStyle: gradient,
datasetIndex: index
}));
}
Chart.js поддерживает различные формы маркеров.
Пример:
labels: {
usePointStyle: true,
generateLabels(chart) {
return chart.data.datasets.map((dataset, index) => ({
text: dataset.label,
fillStyle: dataset.backgroundColor,
pointStyle: 'triangle',
datasetIndex: index
}));
}
}
Доступные значения:
| Значение | Форма |
|---|---|
circle |
Круг |
triangle |
Треугольник |
rect |
Прямоугольник |
rectRounded |
Скруглённый |
rectRot |
Повернутый квадрат |
cross |
Крест |
crossRot |
Поворотный крест |
star |
Звезда |
line |
Линия |
dash |
Пунктир |
Иногда часть datasets не должна отображаться.
Пример:
generateLabels(chart) {
return chart.data.datasets
.filter(dataset => !dataset.hiddenInLegend)
.map((dataset, index) => ({
text: dataset.label,
fillStyle: dataset.backgroundColor,
datasetIndex: index
}));
}
Dataset:
{
label: 'Служебные данные',
hiddenInLegend: true
}
generateLabels(chart) {
return chart.data.datasets
.map((dataset, index) => {
const total =
dataset.data.reduce((a, b) => a + b, 0);
return {
text: dataset.label,
fillStyle: dataset.backgroundColor,
datasetIndex: index,
total
};
})
.sort((a, b) => b.total - a.total);
}
.sort((a, b) => a.text.localeCompare(b.text))
Объекты legendItems могут содержать собственные поля.
generateLabels(chart) {
return chart.data.datasets.map((dataset, index) => ({
text: dataset.label,
fillStyle: dataset.backgroundColor,
datasetIndex: index,
customData: {
total: dataset.data.reduce((a, b) => a + b, 0),
category: dataset.category
}
}));
}
Такие свойства удобно использовать в:
Для собственной HTML-легенды встроенную необходимо отключить.
plugins: {
legend: {
display: false
}
}
После этого legendItems можно использовать вручную.
const items =
chart.options.plugins.legend.labels.generateLabels(chart);
const legendContainer =
document.getElementById('legend');
items.forEach(item => {
const div = document.createElement('div');
div.innerHTML = `
<span style="
display:inline-block;
width:12px;
height:12px;
background:${item.fillStyle};
margin-right:8px;
"></span>
${item.text}
`;
legendContainer.appendChild(div);
});
div.addEventListener('click', () => {
chart.toggleDataVisibility(item.datasetIndex);
chart.update();
});
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: ['A', 'B', 'C'],
datasets: [
{
label: 'Продажи',
data: [10, 20, 30],
borderColor: '#2196F3'
},
{
label: 'Прибыль',
data: [5, 15, 25],
borderColor: '#F44336'
}
]
},
options: {
plugins: {
legend: {
display: false
}
}
}
});
const legend =
document.getElementById('legend');
const items =
Chart.defaults.plugins.legend.labels.generateLabels(chart);
items.forEach(item => {
const button =
document.createElement('button');
button.textContent = item.text;
button.style.border =
`2px solid ${item.strokeStyle}`;
button.addEventListener('click', () => {
chart.setDatasetVisibility(
item.datasetIndex,
!chart.isDatasetVisible(item.datasetIndex)
);
chart.update();
});
legend.appendChild(button);
});
У круговых диаграмм legendItems работают иначе.
Вместо datasets легенда обычно создаётся по отдельным сегментам.
Пример:
generateLabels(chart) {
const data = chart.data;
return data.labels.map((label, index) => ({
text: label,
fillStyle:
data.datasets[0].backgroundColor[index],
hidden: !chart.getDataVisibility(index),
index
}));
}
div.addEventListener('click', () => {
chart.toggleDataVisibility(item.index);
chart.update();
});
legendItems особенно полезны при создании пользовательских плагинов.
Пример:
const customLegendPlugin = {
id: 'customLegendPlugin',
afterUpdate(chart) {
const items =
chart.options.plugins.legend.labels
.generateLabels(chart);
console.log(items);
}
};
Регистрация:
Chart.register(customLegendPlugin);
generateLabels(chart) {
return chart.data.datasets.map((dataset, index) => ({
text: '⬤ ' + dataset.label,
fillStyle: dataset.backgroundColor,
datasetIndex: index
}));
}
generateLabels(chart) {
return chart.data.datasets.map((dataset, index) => {
const visible =
chart.isDatasetVisible(index);
return {
text: dataset.label,
fillStyle:
visible
? dataset.backgroundColor
: '#BDBDBD',
fontColor:
visible
? '#000'
: '#999',
hidden: !visible,
datasetIndex: index
};
});
}
generateLabels(chart) {
return chart.data.datasets.map((dataset, index) => ({
text: dataset.label,
strokeStyle: dataset.borderColor,
lineDash: [5, 5],
lineWidth: 3,
fillStyle: 'transparent',
datasetIndex: index
}));
}
borderRadius: 10
lineWidth: 4
fillStyle: 'rgba(33,150,243,0.3)'
При работе с десятками и сотнями элементов рекомендуется:
generateLabels;Плохой подход:
generateLabels(chart) {
return expensiveCalculation();
}
Лучший вариант:
const cache = [];
generateLabels(chart) {
if (cache.length) {
return cache;
}
cache.push(
...chart.data.datasets.map((dataset, index) => ({
text: dataset.label,
datasetIndex: index
}))
);
return cache;
}
Без datasetIndex переключение видимости перестаёт
работать.
Неправильно:
{
text: dataset.label
}
Правильно:
{
text: dataset.label,
datasetIndex: index
}
Неправильно:
return {};
Правильно:
return [];
Chart.js не рендерит HTML внутри canvas-легенды.
Неправильно:
text: '<b>Продажи</b>'
Наиболее масштабируемая структура:
datasets
↓
generateLabels()
↓
legendItems
↓
HTML renderer
↓
обработчики событий
↓
chart.update()
Такой подход обеспечивает: