Salesforce Lightning: How to generate PDF with charts and images
Generating PDFs in Salesforce with charts, images and textual content is one of the hardest tasks to achieve. I am sure you searched a lot for native solution but at this point we don’t have any native solution.
Requirement
We had a requirement to generate PDF with charts, images, grid and textual content.
Data source for the charts and grids are from external API and PDF page size may vary from 25 to 50 pages.
Considering the requirement to generate PDF with a large number of pages, we initially searched for a server side apex solution, but it was clear to us after a lot of research that generating PDF with charts is not possible with Apex unless we use third party solutions (mostly paid).
After a lot of research we decided to settle with a client side solution using JS libraries.
Architecture
VF Page
<apex:page controller="PDFGenCtrl" action="{!loadPageData}"><!-- Chartjs lib -->
<apex:includeScript value="{!URLFOR($Resource.chartjs)}"/><!-- TODO - HTML --><!-- TODO - JavaScript --><!-- dom-to-image js lib -->
<apex:includeScript value="{!URLFOR($Resource.domtoimage)}"/>
<!-- dom-to-image jspdf -->
<apex:includeScript value="{!URLFOR($Resource.jspdf)}"/></apex:page>
This is a simple implementation of the above architecture. On page load we are calling “loadPageData” action from “PDFGenCtrl” apex class.
We included 3 required Open Source library
Chartjs — JavaScript library to render charts
Dom-to-Image — To convert rendered html / DOM node into image
jsPDF — To create PDF and attach contents(image, text) into it.
Finally we have placeholders to include custom HTML & JavaScript.
HTML
<div class="container"><button onclick="downloadPDF()" style="float:right;">
Download PDF
</button><!-- page 1 -->
<div class="page">
<canvas id="myChart"></canvas>
</div><!-- page 2 -->
<div class="page">
<img alt="img" src="/resource/image_content"
id="p2_img" width="300"/>
</div></div>
There are 3 parts in the above html.
“Download PDF” button — To generate PDF and download.
There are two divs with “page” CSS class, these two divs will convert into two pages of the pdf with chart and image.
JavaScript
<script>
// Read Page data
var pageData = '{!JSENCODE(pageData)}';
try{
pageData = JSON.parse(pageData);
var ctx = document.getElementById('myChart');
// Render chart in page 1
var myChart = new Chart(ctx, {
type: 'doughnut',
data: pageData.p1_data
});
// update image in page 2
var p2_img = document.getElementById('p2_img');
p2_img.src = pageData.p2_data;
}
catch(e){
}
function downloadPDF(){
// Select all div with 'page' class
var pages = document.getElementsByClassName('page');
var pageCount = 0;
var scale = 1;
//Create PDF
var pdf = new jsPDF('l','mm',[263,172]);
// Loop selected divs / pages
Array.from(pages).forEach((page, i) => {
if(i >0 && i< pages.length){
pdf.addPage();
}
// Convert each div.page node to image
domtoimage.toPng(page, {
width: page.offsetWidth*scale,
height: page.offsetHeight*scale,
style: {
transform: "scale("+ scale + ")",
transformOrigin: "top left",
width: page.offsetWidth + "px",
height: page.offsetHeight + "px"
}
}).then((blob) => {
pdf.setPage(i+1);
// Attach the converted blob / image into created pdf
pdf.addImage(blob, 'PNG', 0, 0, undefined, undefined, i, 'FAST');
pageCount++;
if(pageCount == pages.length){
//Download PDF
pdf.save("PDF_Report.pdf");
}
});
});
}
</script>
On page load, after apex action call we are parsing “pageData” to render a chart in first page and set image src in second page.
“downloadPDF” js function uses “dom-to-image” and “jsPDF” to generate PDF.
Apex
public class PDFGenCtrl { public String pageData {get;set;}
public PageReference loadPageData(){
HttpRequest req = new HttpRequest();
HttpResponse res = new HttpResponse();
Http http = new Http();
req.setEndpoint(‘http://demo3599847.mockable.io/pdf-data');
req.setMethod(‘GET’);
req.setHeader(‘Content-Type’, ‘application/json’);
try {
res = http.send(req);
}
catch(System.CalloutException e) {
System.debug(‘Callout error: ‘+ e);
}
pageData = res.getBody();
return null;
}}
This is a simple Apex class to load page data from REST API and set the response to “pageData” property which can be used in the VF page.
Complete source code is available in github — https://github.com/csrviswa/Salesforce-PDF-Generation