В Chart.js любой график рендерится внутри
<canvas>, что позволяет получать итоговое изображение
без серверной обработки. Основной механизм экспорта основан на API
HTMLCanvasElement и методах, встроенных в саму библиотеку.
Ключевые подходы к получению изображения:
toBase64Image() — быстрое получение dataURLtoBlob() — формирование бинарного файлаcanvas.toDataURL()toBase64ImageChart.js предоставляет встроенный метод, позволяющий получить изображение графика в формате Base64.
const canvas = document.getElementById('chart');
const chart = new Chart(canvas, {
type: 'bar',
data: {
labels: ['A', 'B', 'C'],
datasets: [{
label: 'Данные',
data: [10, 20, 30]
}]
}
});
const imageBase64 = chart.toBase64Image();
Результат представляет собой строку:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
Наиболее распространённый сценарий — подстановка в ссылку загрузки:
const link = document.createElement('a');
link.href = chart.toBase64Image();
link.download = 'chart.png';
link.click();
toBlobМетод toBlob позволяет получить изображение в виде
бинарного объекта, что удобнее для работы с файлами и большими
графиками.
const canvas = document.getElementById('chart');
const chart = new Chart(canvas, {
type: 'line',
data: {
labels: ['Янв', 'Фев', 'Мар'],
datasets: [{
label: 'Продажи',
data: [15, 25, 18]
}]
}
});
chart.canvas.toBlob(function(blob) {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'chart.png';
link.click();
URL.revokeObjectURL(url);
}, 'image/png');
Особенность подхода заключается в том, что данные не кодируются в base64, что снижает нагрузку на память.
canvas.toDataURLChart.js фактически опирается на стандартный метод canvas:
const canvas = chart.canvas;
const image = canvas.toDataURL('image/png');
Далее изображение обрабатывается так же, как и в предыдущих случаях:
const link = document.createElement('a');
link.href = image;
link.download = 'chart.png';
link.click();
При стандартном экспорте изображение может получаться размытым на
дисплеях с высокой плотностью пикселей. Причина заключается в
devicePixelRatio.
Увеличение качества достигается через временное масштабирование:
const originalRatio = window.devicePixelRatio;
const canvas = chart.canvas;
const context = canvas.getContext('2d');
canvas.width = canvas.width * 2;
canvas.height = canvas.height * 2;
context.scale(2, 2);
chart.update();
Более корректный способ — пересоздание графика с увеличенным
devicePixelRatio:
new Chart(canvas, {
type: 'bar',
data: data,
options: {
devicePixelRatio: 2
}
});
PNG поддерживает прозрачность, и при экспорте фон может оказаться прозрачным. Для обеспечения стабильного внешнего вида задаётся принудительный фон.
const plugin = {
id: 'custom_canvas_background',
beforeDraw: (chart) => {
const ctx = chart.canvas.getContext('2d');
ctx.save();
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, chart.width, chart.height);
ctx.restore();
}
};
Использование:
new Chart(canvas, {
type: 'line',
data: data,
plugins: [plugin]
});
При повторяющемся использовании логика выносится в универсальную функцию:
function downloadChartImage(chart, filename = 'chart.png') {
const link = document.createElement('a');
link.href = chart.toBase64Image();
link.download = filename;
link.click();
}
Вариант с Blob:
function downloadChartImageBlob(chart, filename = 'chart.png') {
chart.canvas.toBlob((blob) => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
}, 'image/png');
}
При использовании изображений внутри графика (например,
pointStyle: image) может возникнуть ограничение
безопасности браузера.
Если изображение загружено с другого домена без корректных CORS-заголовков, canvas становится «tainted», и экспорт блокируется.
Решение заключается в:
img.crossOrigin = 'anonymous' перед
загрузкойChart.js по умолчанию завершает анимацию перед созданием изображения. Для фиксации состояния без ожидания используется отключение анимации:
new Chart(canvas, {
type: 'bar',
data: data,
options: {
animation: false
}
});
При динамическом изменении данных важно дождаться завершения перерисовки:
chart.data.datasets[0].data = [5, 10, 15];
chart.update();
setTimeout(() => {
const img = chart.toBase64Image();
}, 0);
Более стабильный вариант:
chart.update('none');
const img = chart.toBase64Image();
При наличии нескольких экземпляров Chart.js логика применяется индивидуально к каждому объекту:
charts.forEach((chart, index) => {
const link = document.createElement('a');
link.href = chart.toBase64Image();
link.download = `chart_${index}.png`;
link.click();
});
Основные технические ограничения: