Экспорт в PDF через jsPDF

Экспорт графиков из Chart.js в PDF основан на преобразовании canvas-элемента в изображение с последующим встраиванием этого изображения в PDF-документ. Библиотека jsPDF выступает генератором PDF, принимая растровые данные (PNG, JPEG) и размещая их на страницах документа.

В основе процесса лежит цепочка:

  • отрисовка графика Chart.js на <canvas>
  • получение изображения canvas в формате base64
  • вставка изображения в PDF через jsPDF
  • формирование структуры документа (страницы, размеры, отступы)

Chart.js не выполняет экспорт самостоятельно, он лишь предоставляет готовый canvas.


Подготовка окружения Chart.js и jsPDF

Для работы требуется наличие двух ключевых компонентов:

  • Chart.js — рендеринг графиков
  • jsPDF — создание PDF-документов

Типовая установка через npm:

npm install chart.js jspdf

Подключение в модуле:

import { Chart } from 'chart.js';
import { jsPDF } from 'jspdf';

Если используется CDN:

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jspdf"></script>

Получение изображения canvas из Chart.js

Chart.js предоставляет встроенный механизм экспорта изображения через метод toBase64Image().

Пример получения PNG-строки:

const canvas = document.getElementById('myChart');
const base64Image = canvas.toDataURL('image/png');

Альтернативный способ через экземпляр графика:

const image = myChart.toBase64Image();

Оба варианта возвращают строку вида:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...

Эта строка напрямую используется в jsPDF.


Базовый экспорт одного графика в PDF

Минимальный сценарий заключается в создании PDF и вставке изображения:

import { jsPDF } from 'jspdf';

const pdf = new jsPDF();

const canvas = document.getElementById('myChart');
const image = canvas.toDataURL('image/png');

pdf.addImage(image, 'PNG', 10, 10, 190, 100);
pdf.save('chart.pdf');

Параметры addImage:

  • image — base64 строка
  • 'PNG' — формат
  • 10, 10 — координаты на странице
  • 190, 100 — ширина и высота

Масштабирование и корректное размещение графика

PDF имеет фиксированный формат страницы, поэтому важно учитывать пропорции canvas.

Стандартный лист A4 в jsPDF:

const pdf = new jsPDF('p', 'mm', 'a4');

Размеры страницы:

  • ширина: ~210 мм
  • высота: ~297 мм

Пример масштабирования с сохранением пропорций:

const pdf = new jsPDF('p', 'mm', 'a4');

const img = myChart.toBase64Image();

const pageWidth = pdf.internal.pageSize.getWidth();
const pageHeight = pdf.internal.pageSize.getHeight();

const imgProps = pdf.getImageProperties(img);

const ratio = imgProps.height / imgProps.width;

const width = pageWidth - 20;
const height = width * ratio;

pdf.addImage(img, 'PNG', 10, 10, width, height);

Ключевая логика:

  • ширина подгоняется под страницу
  • высота вычисляется через коэффициент пропорций

Экспорт нескольких графиков на одной странице

При размещении нескольких диаграмм используется фиксированная сетка координат.

const pdf = new jsPDF();

const charts = [chart1, chart2, chart3];

let y = 10;

charts.forEach((chart) => {
  const img = chart.toBase64Image();

  pdf.addImage(img, 'PNG', 10, y, 180, 60);

  y += 70;
});

Если координаты выходят за пределы страницы, требуется переход:

if (y > 250) {
  pdf.addPage();
  y = 10;
}

Работа с несколькими страницами PDF

Каждый график может занимать отдельную страницу:

const pdf = new jsPDF();

charts.forEach((chart, index) => {
  if (index !== 0) {
    pdf.addPage();
  }

  const img = chart.toBase64Image();
  pdf.addImage(img, 'PNG', 10, 10, 190, 120);
});

pdf.save('charts.pdf');

Такой подход используется при отчётной генерации.


Использование html2canvas как альтернативы

В некоторых случаях график находится не напрямую в Chart.js, а в сложной DOM-структуре. Тогда применяется библиотека html2canvas:

import html2canvas from 'html2canvas';
import { jsPDF } from 'jspdf';

html2canvas(document.querySelector('#chartContainer')).then((canvas) => {
  const img = canvas.toDataURL('image/png');

  const pdf = new jsPDF();
  pdf.addImage(img, 'PNG', 10, 10, 190, 100);
  pdf.save('report.pdf');
});

Особенности:

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

Повышение качества изображения при экспорте

Canvas по умолчанию может быть недостаточно чётким при масштабировании. Для улучшения качества используется увеличение разрешения:

html2canvas(element, {
  scale: 2
});

Для Chart.js также применяется увеличение размера canvas:

const chart = new Chart(ctx, {
  options: {
    responsive: false,
    maintainAspectRatio: false
  }
});

Дополнительно можно задавать размеры canvas вручную:

canvas.width = 1200;
canvas.height = 600;

Работа с ориентацией страницы

jsPDF поддерживает портретную и альбомную ориентацию:

const pdf = new jsPDF('landscape', 'mm', 'a4');

Альбомная ориентация применяется для широких графиков:

  • линейные временные ряды
  • сравнительные столбчатые диаграммы
  • тепловые карты

Автоматическая подгонка графика под PDF

Универсальный подход включает вычисление доступной области:

function addChart(pdf, chart, x, y, maxWidth) {
  const img = chart.toBase64Image();
  const props = pdf.getImageProperties(img);

  const ratio = props.height / props.width;

  const width = maxWidth;
  const height = width * ratio;

  pdf.addImage(img, 'PNG', x, y, width, height);

  return height;
}

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

const pdf = new jsPDF();

let y = 10;

y += addChart(pdf, chart1, 10, y, 180);
y += 10;
y += addChart(pdf, chart2, 10, y, 180);

Добавление текста и подписей к графикам

PDF редко содержит только изображения. Часто добавляются подписи:

pdf.setFontSize(12);
pdf.text('Анализ продаж за период', 10, 10);

pdf.addImage(img, 'PNG', 10, 20, 180, 100);

Дополнительно:

pdf.setFontSize(10);
pdf.text('Источник: внутренние данные системы', 10, 280);

Частые проблемы при экспорте

Размытое изображение

Причина: низкое разрешение canvas Решение: увеличение scale или размера canvas


Обрезка графика

Причина: неверные размеры addImage Решение: вычисление пропорций через getImageProperties


Пустое изображение

Причина: экспорт до завершения рендера Chart.js Решение: ожидание события animation complete

options: {
  animation: {
    onComplete: function () {
      exportPDF();
    }
  }
}

Смещение элементов

Причина: несоответствие DPI canvas и PDF Решение: фиксированное масштабирование и отказ от responsive режима


Генерация отчётных документов с графиками

При построении систем аналитики используется комбинированный подход:

  • таблицы данных
  • текстовые блоки
  • графики Chart.js
  • экспорт через jsPDF

Структура отчёта:

  • титульная страница
  • аналитические секции
  • графические страницы
  • итоговые выводы в текстовом виде

Каждая секция формируется как отдельная страница PDF, а графики вставляются как изображения canvas без потери логики визуализации.