Callback-функции: title, label, footer, afterBody

Система всплывающих подсказок в Chart.js построена вокруг набора callback-функций. Они позволяют полностью переопределять содержимое tooltip: заголовок, строки данных, подписи, нижний колонтитул, дополнительные блоки текста и даже форматирование отдельных элементов.

Основные callback-функции находятся внутри:

options: {
    plugins: {
        tooltip: {
            callbacks: {
                title() {},
                label() {},
                footer() {},
                afterBody() {}
            }
        }
    }
}

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


Общая структура tooltip callbacks

Пример минимальной конфигурации:

const config = {
    type: 'line',
    data,
    options: {
        plugins: {
            tooltip: {
                callbacks: {
                    title(items) {
                        return 'Заголовок';
                    },

                    label(context) {
                        return 'Значение';
                    },

                    afterBody(items) {
                        return 'Дополнительный текст';
                    },

                    footer(items) {
                        return 'Подвал tooltip';
                    }
                }
            }
        }
    }
};

Объект context

Большинство callback-функций получают объект context.

Структура объекта:

{
    chart,
    dataset,
    datasetIndex,
    dataIndex,
    parsed,
    raw,
    formattedValue,
    label
}

Ключевые свойства:

Свойство Описание
chart Экземпляр графика
dataset Текущий dataset
datasetIndex Индекс набора данных
dataIndex Индекс элемента
parsed Преобразованные числовые данные
raw Исходное значение
formattedValue Отформатированное значение
label Подпись текущей точки

Callback title

Функция title() отвечает за верхнюю часть tooltip.

Сигнатура

title(items)

Параметр items — массив элементов tooltip.


Простой title

tooltip: {
    callbacks: {
        title(items) {
            return items[0].label;
        }
    }
}

Результат:

Март

Формирование динамического заголовка

tooltip: {
    callbacks: {
        title(items) {
            const item = items[0];

            return `Продажи за ${item.label}`;
        }
    }
}

Результат:

Продажи за Март

Использование parsed в title

tooltip: {
    callbacks: {
        title(items) {
            const value = items[0].parsed.y;

            return `Сумма: ${value}`;
        }
    }
}

Для line chart:

parsed.y

Для bar chart:

parsed.y

Для horizontal bar:

parsed.x

Многострочный title

Tooltip поддерживает массив строк.

tooltip: {
    callbacks: {
        title(items) {
            return [
                'Финансовый отчёт',
                `Период: ${items[0].label}`
            ];
        }
    }
}

Результат:

Финансовый отчёт
Период: Апрель

Условный title

tooltip: {
    callbacks: {
        title(items) {
            const value = items[0].parsed.y;

            if (value > 1000) {
                return 'Высокие продажи';
            }

            return 'Обычные продажи';
        }
    }
}

Callback label

label() — наиболее используемая callback-функция tooltip.

Она отвечает за основное содержимое строк данных.

Сигнатура

label(context)

Базовый label

tooltip: {
    callbacks: {
        label(context) {
            return context.formattedValue;
        }
    }
}

Добавление названия dataset

tooltip: {
    callbacks: {
        label(context) {
            return `${context.dataset.label}: ${context.formattedValue}`;
        }
    }
}

Результат:

Доход: 4500

Форматирование валюты

tooltip: {
    callbacks: {
        label(context) {
            const value = context.parsed.y;

            return `₸ ${value.toLocaleString()}`;
        }
    }
}

Форматирование процентов

tooltip: {
    callbacks: {
        label(context) {
            return `${context.parsed.y}%`;
        }
    }
}

Использование Intl.NumberFormat

Наиболее правильный способ форматирования чисел:

const formatter = new Intl.NumberFormat('ru-RU', {
    style: 'currency',
    currency: 'KZT'
});

tooltip: {
    callbacks: {
        label(context) {
            return formatter.format(context.parsed.y);
        }
    }
}

Несколько строк в label

tooltip: {
    callbacks: {
        label(context) {
            return [
                `Категория: ${context.label}`,
                `Значение: ${context.parsed.y}`,
                `Dataset: ${context.dataset.label}`
            ];
        }
    }
}

Использование raw

raw содержит исходные данные до обработки.

data: [
    {
        x: 'Январь',
        y: 120,
        manager: 'Алексей'
    }
]
tooltip: {
    callbacks: {
        label(context) {
            return [
                `Продажи: ${context.raw.y}`,
                `Менеджер: ${context.raw.manager}`
            ];
        }
    }
}

Работа с custom-полями

data: [
    {
        x: 'Q1',
        y: 1500,
        region: 'Центральный',
        employees: 24
    }
]
tooltip: {
    callbacks: {
        label(context) {
            const raw = context.raw;

            return [
                `Регион: ${raw.region}`,
                `Сотрудников: ${raw.employees}`
            ];
        }
    }
}

Полное переопределение label

tooltip: {
    callbacks: {
        label(context) {
            return `● ${context.dataset.label} → ${context.parsed.y}`;
        }
    }
}

Удаление label

Если вернуть null или пустую строку, строка скрывается.

tooltip: {
    callbacks: {
        label() {
            return '';
        }
    }
}

Callback afterBody

Функция afterBody() добавляет содержимое после основного блока tooltip.

Сигнатура

afterBody(items)

Простой afterBody

tooltip: {
    callbacks: {
        afterBody(items) {
            return 'Дополнительная информация';
        }
    }
}

Вычисление суммы

tooltip: {
    callbacks: {
        afterBody(items) {
            let total = 0;

            items.forEach(item => {
                total += item.parsed.y;
            });

            return `Сумма: ${total}`;
        }
    }
}

Вычисление среднего значения

tooltip: {
    callbacks: {
        afterBody(items) {
            const total = items.reduce((sum, item) => {
                return sum + item.parsed.y;
            }, 0);

            const average = total / items.length;

            return `Среднее: ${average.toFixed(2)}`;
        }
    }
}

Многострочный afterBody

tooltip: {
    callbacks: {
        afterBody(items) {
            return [
                'Дополнительная статистика',
                `Количество элементов: ${items.length}`
            ];
        }
    }
}

Использование afterBody для аналитики

tooltip: {
    callbacks: {
        afterBody(items) {
            const max = Math.max(
                ...items.map(item => item.parsed.y)
            );

            return `Максимум: ${max}`;
        }
    }
}

footer() формирует нижнюю часть tooltip.

Сигнатура

footer(items)

tooltip: {
    callbacks: {
        footer(items) {
            return 'Конец отчёта';
        }
    }
}

tooltip: {
    callbacks: {
        footer(items) {
            const total = items.reduce((sum, item) => {
                return sum + item.parsed.y;
            }, 0);

            return `Итого: ${total}`;
        }
    }
}

tooltip: {
    callbacks: {
        footer(items) {
            const total = items.reduce((sum, item) => {
                return sum + item.parsed.y;
            }, 0);

            return `Общий стек: ${total}`;
        }
    }
}

tooltip: {
    callbacks: {
        footer(items) {
            const total = items.reduce((sum, item) => {
                return sum + item.parsed.y;
            }, 0);

            return `Общий доход: ₸ ${total.toLocaleString()}`;
        }
    }
}

tooltip: {
    callbacks: {
        footer(items) {
            return [
                'Финансовая аналитика',
                'Все значения указаны в тенге'
            ];
        }
    }
}

Комбинирование callback-функций

Полноценный пример:

const config = {
    type: 'bar',

    data: {
        labels: ['Январь', 'Февраль', 'Март'],
        datasets: [{
            label: 'Продажи',
            data: [1200, 1900, 800]
        }]
    },

    options: {
        plugins: {
            tooltip: {
                callbacks: {

                    title(items) {
                        return `Отчёт: ${items[0].label}`;
                    },

                    label(context) {
                        return `Продажи: ₸ ${context.parsed.y}`;
                    },

                    afterBody(items) {
                        const value = items[0].parsed.y;

                        if (value > 1500) {
                            return 'Пиковое значение';
                        }

                        return 'Стандартный показатель';
                    },

                    footer(items) {
                        return `Dataset: ${items[0].dataset.label}`;
                    }
                }
            }
        }
    }
};

Порядок выполнения callback-функций

Tooltip формируется по этапам:

  1. beforeTitle
  2. title
  3. afterTitle
  4. beforeBody
  5. beforeLabel
  6. label
  7. afterLabel
  8. afterBody
  9. beforeFooter
  10. footer
  11. afterFooter

Возвращаемые значения

Callback может вернуть:

Тип Результат
string Одна строка
string[] Несколько строк
'' Пустая строка
undefined Значение по умолчанию
null Обычно скрывает блок

Производительность callback-функций

Tooltip callbacks вызываются при каждом наведении курсора.

Не рекомендуется:

label(context) {
    fetch('/api/data');
}

Не рекомендуется:

label(context) {
    heavyCalculation();
}

Оптимальный подход:

const formatter = new Intl.NumberFormat('ru-RU');

tooltip: {
    callbacks: {
        label(context) {
            return formatter.format(context.parsed.y);
        }
    }
}

Использование внешних данных

const regions = {
    0: 'Север',
    1: 'Юг',
    2: 'Запад'
};

tooltip: {
    callbacks: {
        label(context) {
            const region = regions[context.dataIndex];

            return `${region}: ${context.parsed.y}`;
        }
    }
}

Callback-функции и разные типы графиков

Line chart

context.parsed.y

Bar chart

context.parsed.y

Horizontal bar

context.parsed.x

Pie chart

context.parsed

Callback в pie chart

tooltip: {
    callbacks: {
        label(context) {
            const value = context.parsed;
            const label = context.label;

            return `${label}: ${value}%`;
        }
    }
}

Использование callback с time scale

tooltip: {
    callbacks: {
        title(items) {
            const date = new Date(items[0].parsed.x);

            return date.toLocaleDateString('ru-RU');
        }
    }
}

Создание сложного аналитического tooltip

tooltip: {
    callbacks: {

        title(items) {
            return `Месяц: ${items[0].label}`;
        },

        label(context) {
            return [
                `Продажи: ${context.parsed.y}`,
                `План: ${context.raw.plan}`,
                `Менеджер: ${context.raw.manager}`
            ];
        },

        afterBody(items) {
            const fact = items[0].parsed.y;
            const plan = items[0].raw.plan;

            const percent = (
                fact / plan * 100
            ).toFixed(1);

            return `Выполнение плана: ${percent}%`;
        },

        footer(items) {
            return 'ERP Analytics System';
        }
    }
}

Типичные ошибки

Ошибка доступа к parsed

Неправильно:

context.parsed.value

Правильно:

context.parsed.y

Ошибка с items[0]

Неправильно:

title(item) {
    return item.label;
}

Правильно:

title(items) {
    return items[0].label;
}

Отсутствие return

Неправильно:

label(context) {
    const value = context.parsed.y;
}

Правильно:

label(context) {
    return context.parsed.y;
}

Практический шаблон tooltip callbacks

plugins: {
    tooltip: {
        callbacks: {

            title(items) {
                return `Период: ${items[0].label}`;
            },

            label(context) {
                return `${context.dataset.label}: ${context.formattedValue}`;
            },

            afterBody(items) {
                const total = items.reduce((sum, item) => {
                    return sum + item.parsed.y;
                }, 0);

                return `Сумма: ${total}`;
            },

            footer(items) {
                return `Элементов: ${items.length}`;
            }
        }
    }
}