Eksport wykresów d3.js do pliku PDF

Prezentowałem sposób na eksport wykresów zapisanych w formacie SVG do pliku PNG, często jednak oprócz eksportu do samego obrazka trzeba także zapewnić możliwośc pobrania pliku PDF i o tym dzisiaj będzie ten post.

Około 70% procesu eksportu do PDF załatwione już zostało przy okazji eksportu do PNG dlatego też nie będę tutaj opisywał aspektów, które zostały poruszone w poprzednim poście, więc aby rozwiać wątpliwości zachęcam się z nim zapoznać.

jsPDF

Z pomocą przychodzi biblioteka jsPDF, dzięki której można tworzyć dokumenty PDF po stronie przeglądarki internetowej. Poprzednio generowany obrazek z bazowego SVG będzie świetną podstawą bo wystarczy tylko go „przechwycić” i dodać do dokumentu PDF.

Flow

1. Pobranie elementu SVG
var baseSVG = document.getElementById('chart').getElementsByTagName('svg')[0];
2. Przygotowanie obiektu canvas z rozmiarami na podstawie elementu SVG.
function prepareCanvas(baseSVG, scale) {
  var svgDimensions = baseSVG.getBoundingClientRect();
  var canvas = document.createElement('canvas');
  canvas.width = svgDimensions.width * scale;
  canvas.height = svgDimensions.height * scale;
  return canvas;
}

Polecam te rozmiary przeskalować conajmniej 4-krotnie aby zwiększyć jakość wyeksportowanego wykresu w PDF. Niestety konwertując SVG do obrazka staje się on grafiką rastrową więc aby zapewnić dobrą jakość trzeba odpowiedni powiększyć canvas (jednak nie warto przesadzić, myślę, że 8-krotne powiększenie to maksymalna wartość).

3. Dodanie białego tła do nowego elementu canvas :exclamation:
function addBackgroundToCanvas(canvas) {
  var ctx = canvas.getContext('2d');
  ctx.fillStyle = 'white';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = 'black';
}

To ważny punkt, którego nie można pominąć. Domyślnie canva jest przeźroczysta, ale po eksporcie do PDF i tak jest białe tło. Aby znacząco przyspieszyć proces eksportu warto ustawić tło elementu canvas. Napotkałem taki problem, że dodawanie obrazka do dokumentu trwało kilka sekund, a przy okazji blokowało całą przeglądarkę co jest niedopuszczalne. Powodem było, że konwertowanie obrazka z przeźroczystym tłem używało nieoptymalnego algorytmu biblioteki jsPDF.

4. Przygotowanie danych obrazka w formacie base64
function prepareImageData(baseSVG) {
  var xml = new XMLSerializer().serializeToString(baseSVG);
  var svg64 = btoa(unescape(encodeURIComponent(xml)));
  var b64Start = 'data:image/svg+xml;base64,';
  return b64Start + svg64;
}
5. Dodanie obrazka do elementu canvas
function drawOnCanvas(canvas, imageData, exportProcessFn) {
  var img = new Image();
  var ctx = canvas.getContext('2d');
  img.src = imageData;
  img.onload = function () {
     ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
     exportProcessFn();
  };
}
6. Uworzenie obiektu jsPDF
var doc = new jsPDF({
  orientation: 'landscape',
  unit: 'mm',
  format: 'a4'
});

Biblioteka jsPDF posiada wiele opcji, które można zobaczyć w dokumentacji. Na nasze potrzeby deklarujemy tylko orientację kartki, jednostkę i format.

7. Dodanie obrazka do dokumentu z elementu canvas
var imgDimensions = getImageDimensions(canvas);
doc.addImage(
  canvas.toDataURL('image/jpeg'),
  'JPEG',
  imgDimensions.x,
  imgDimensions.y,
  imgDimensions.width,
  imgDimensions.height,
  '',
  'FAST'
);

Do obliczenia wymiarów obrazka napisałem funkcję, która może pomóc zmapować rozmiar obrazka zawarty w pikselach na milimetry

function getImageDimensions(canvas) {
  var margin = 4;
  var pixelToMillimeter = 0.264583333;
  return {
       x: margin,
       y: margin,
       width: canvas.width * pixelToMillimeter,
       height: canvas.height * pixelToMillimeter
  };
}

W przykładzie na samym dole ta funkcja jest trochę bardziej rozbudowana bo zawiera jeszcze zabezpieczenie jeśli szerokość eksportowanego obrazka przekracza szerokość kartki.

8. jsPDF posiada wbudowaną metodę, która wyzwala pobieranie, wystarczy tylko wywołać…
doc.save('chart.pdf');

Działający przykład

Nie wszystkie elementy, które znajdują się w powyższym kodzie zostały opisane ze względu na to, że dziejszy post jest rozwinięciem eksportu wykresów do obrazków, o których można było przeczytać poprzednio. Zachęcam zapoznać się z tamtym materiałem przede wszystkim jeśli potrzebujesz zapewnić wsparcie dla przeglądarek Microsoftu :smiley:

NodeStart - Twórz back-end w JavaScript / TypeScript
Programista skupiony głównie wokół technologii webowych, ale nie przywiązujący się do konkretnych języków i narzędzi. Skoncentrowany na ciągłym rozwoju, zwolennik ruchu Software Crafmanship. Na codzień pracując w DAZN ma okazję rozwijać interesujący projekt do streamingu wydarzeń sportowych. Prywatnie fan sportu, a szczególnie piłki nożnej. Po godzinach próbuje również swoich sił w piwowarstwie domowym.
PODZIEL SIĘ