Разделители тысяч и дробной части

По умолчанию библиотека Chart.js выводит числовые значения без дополнительного форматирования. Для небольших значений этого достаточно, однако при работе с финансовыми данными, аналитикой, статистикой или научными вычислениями возникает необходимость:

  • разделять тысячи;
  • управлять количеством знаков после запятой;
  • использовать локальный формат чисел;
  • отображать валюты;
  • заменять десятичную точку на запятую.

Основная настройка форматирования выполняется через callback-функции.


Разделители тысяч

Проблема отображения больших чисел

Без форматирования значения выглядят тяжело для восприятия:

1500000
25000000
987654321

Гораздо удобнее отображать их так:

1 500 000
25 000 000
987 654 321

или:

1,500,000
25,000,000
987,654,321

Использование toLocaleString()

Наиболее удобный способ форматирования — встроенный метод JavaScript toLocaleString().

Пример

const value = 1500000;

console.log(value.toLocaleString());

Результат зависит от локали системы:

1 500 000

Форматирование подписей оси Y

Для изменения отображения чисел на шкале используется callback в ticks.

Пример

const config = {
    type: 'bar',

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

    options: {
        scales: {
            y: {
                ticks: {
                    callback: function(value) {
                        return value.toLocaleString();
                    }
                }
            }
        }
    }
};

Результат форматирования

Было:

100000
200000
300000

Стало:

100 000
200 000
300 000

Локализация чисел

Метод toLocaleString() поддерживает локали.

Русский формат

value.toLocaleString('ru-RU');

Результат:

1 500 000,5

Американский формат

value.toLocaleString('en-US');

Результат:

1,500,000.5

Немецкий формат

value.toLocaleString('de-DE');

Результат:

1.500.000,5

Форматирование tooltip

Tooltip часто требует отдельной настройки, поскольку данные в подсказках отображаются независимо от осей.

Пример

const config = {
    type: 'line',

    data: {
        labels: ['Пн', 'Вт', 'Ср'],
        datasets: [{
            label: 'Доход',
            data: [1250000, 980000, 2100000]
        }]
    },

    options: {
        plugins: {
            tooltip: {
                callbacks: {
                    label: function(context) {
                        let value = context.parsed.y;

                        return 'Доход: ' +
                            value.toLocaleString('ru-RU');
                    }
                }
            }
        }
    }
};

Добавление валюты

Рубли

return value.toLocaleString('ru-RU') + ' ₽';

Результат:

1 500 000 ₽

Автоматическое форматирование валюты

JavaScript поддерживает специальный режим currency.

Пример

value.toLocaleString('ru-RU', {
    style: 'currency',
    currency: 'RUB'
});

Результат:

1 500 000,00 ₽

Форматирование долларов

value.toLocaleString('en-US', {
    style: 'currency',
    currency: 'USD'
});

Результат:

$1,500,000.00

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

value.toLocaleString('de-DE', {
    style: 'currency',
    currency: 'EUR'
});

Результат:

1.500.000,00 €

Управление дробной частью

Фиксированное количество знаков

value.toLocaleString('ru-RU', {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2
});

Результат:

1 500,00

Округление чисел

До одного знака после запятой

value.toLocaleString('ru-RU', {
    maximumFractionDigits: 1
});

Удаление дробной части

value.toLocaleString('ru-RU', {
    maximumFractionDigits: 0
});

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

Пример

const value = 0.25;

console.log(
    value.toLocaleString('ru-RU', {
        style: 'percent'
    })
);

Результат:

25 %

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

Для сложных сценариев лучше использовать Intl.NumberFormat.

Пример

const formatter = new Intl.NumberFormat('ru-RU', {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2
});

console.log(formatter.format(1234567.89));

Результат:

1 234 567,89

Преимущества Intl.NumberFormat

Повторное использование

Форматтер создаётся один раз:

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

После чего применяется многократно:

formatter.format(value);

Это эффективнее, чем постоянный вызов toLocaleString().


Использование форматтера в Chart.js

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

const formatter = new Intl.NumberFormat('ru-RU', {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2
});

const config = {
    type: 'line',

    data: {
        labels: ['Январь', 'Февраль', 'Март'],
        datasets: [{
            label: 'Прибыль',
            data: [1250000.5, 2250000.75, 3100000.9]
        }]
    },

    options: {
        scales: {
            y: {
                ticks: {
                    callback: function(value) {
                        return formatter.format(value);
                    }
                }
            }
        },

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

Сокращённое отображение больших чисел

Иногда крупные значения удобнее показывать в сокращённом виде.

Пример

function formatNumber(value) {

    if (value >= 1000000) {
        return (value / 1000000).toFixed(1) + 'M';
    }

    if (value >= 1000) {
        return (value / 1000).toFixed(1) + 'K';
    }

    return value;
}

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

ticks: {
    callback: function(value) {
        return formatNumber(value);
    }
}

Результат

1500      → 1.5K
2500000   → 2.5M

Русские сокращения

Пример

function formatNumber(value) {

    if (value >= 1000000) {
        return (value / 1000000).toFixed(1) + ' млн';
    }

    if (value >= 1000) {
        return (value / 1000).toFixed(1) + ' тыс';
    }

    return value;
}

Форматирование подписей данных

При использовании плагина chartjs-plugin-datalabels форматирование выполняется через formatter.

Пример

plugins: {
    datalabels: {
        formatter: function(value) {
            return value.toLocaleString('ru-RU');
        }
    }
}

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

Для легенд чаще форматируются не числа, а подписи, однако возможно динамическое создание текста.

Пример

label: 'Продажи: ' +
    total.toLocaleString('ru-RU')

Форматирование временных рядов

В финансовых графиках удобно комбинировать:

  • разделители тысяч;
  • валюту;
  • фиксированную точность.

Пример

const moneyFormatter =
    new Intl.NumberFormat('ru-RU', {

        style: 'currency',
        currency: 'RUB',

        minimumFractionDigits: 2,
        maximumFractionDigits: 2
    });

Использование форматтера для tooltip

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

Форматирование отрицательных чисел

Пример

const formatter = new Intl.NumberFormat('ru-RU', {
    minimumFractionDigits: 2
});

console.log(formatter.format(-1500000.5));

Результат:

-1 500 000,50

Кастомное форматирование

Иногда требуется полностью собственный формат.

Пример

function customFormat(value) {

    return value
        .toFixed(2)
        .replace('.', ',')
        .replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
}

Результат

1234567.89

преобразуется в:

1 234 567,89

Использование регулярных выражений

Ключевая часть форматирования:

/\B(?=(\d{3})+(?!\d))/g

Разбирается следующим образом:

Выражение Назначение
\B позиция внутри числа
(?=...) проверка вперёд
\d{3} группа из трёх цифр
(?!\d) отсутствие следующей цифры

Форматирование нулевых значений

Пример

callback: function(value) {

    if (value === 0) {
        return '0';
    }

    return value.toLocaleString('ru-RU');
}

Обработка null и undefined

Безопасное форматирование

function safeFormat(value) {

    if (value == null) {
        return '-';
    }

    return value.toLocaleString('ru-RU');
}

Универсальный formatter для Chart.js

Готовая функция

function createFormatter(locale = 'ru-RU') {

    const formatter =
        new Intl.NumberFormat(locale, {

            minimumFractionDigits: 0,
            maximumFractionDigits: 2
        });

    return function(value) {
        return formatter.format(value);
    };
}

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

const format = createFormatter();

ticks: {
    callback: function(value) {
        return format(value);
    }
}

Форматирование чисел в Chart.js 4

В Chart.js 4 callback-функции работают аналогично предыдущим версиям.

Основные точки форматирования

Область Настройка
Оси ticks.callback
Tooltip tooltip.callbacks.label
DataLabels datalabels.formatter
Подписи пользовательские функции
Легенда генерация текста

Частые ошибки

Использование строк вместо чисел

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

data: ['1000', '2000']

Правильно:

data: [1000, 2000]

Отсутствие проверки null

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

value.toLocaleString()

Правильно:

value?.toLocaleString?.()

Лишнее создание форматтера

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

callback: function(value) {

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

    return formatter.format(value);
}

Правильно:

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

Практический пример

const formatter =
    new Intl.NumberFormat('ru-RU', {

        style: 'currency',
        currency: 'RUB',

        minimumFractionDigits: 2,
        maximumFractionDigits: 2
    });

const config = {

    type: 'bar',

    data: {

        labels: [
            'Январь',
            'Февраль',
            'Март'
        ],

        datasets: [{
            label: 'Выручка',

            data: [
                1250000.25,
                2200000.5,
                3100000.75
            ]
        }]
    },

    options: {

        scales: {

            y: {
                ticks: {

                    callback: function(value) {
                        return formatter.format(value);
                    }
                }
            }
        },

        plugins: {

            tooltip: {

                callbacks: {

                    label: function(context) {

                        return 'Выручка: ' +
                            formatter.format(
                                context.parsed.y
                            );
                    }
                }
            }
        }
    }
};