Haifeng Zhang
Haifeng Zhang

Reputation: 31895

Canvas charts is blurry when download as PDF

2nd Update:

Thanks again for everyone who commented/answered my question

Problem solved: I am not able to make canvas image looks better on the generated PDF and the due date of the project is closing, finally, I decided to move the "PDF Report" feature from Front-End(Angular) to Back-End(python), I have posted an answer(not really an answer for my own question, but more like a solution)

Update after a couple of days search:

I have replaced Chart.js with Echarts but the blurry issue still exists. I did a lot of search on jsPDF and Canvas blurry online, it seems like the canvas settings need to be customized, unfortunately, I have no clue how to achieve it.

How looks in broswer: enter image description here

When download as PDF: enter image description here

Original Question:

I have an Angular project which is required to download PDF for graph data. I choose Chart.js and jspdf which works well. When I print 2 charts in one page, the resolution is okay but when I try to print 3 charts on the same page, the texts are blurry. I have read this similar question but not figured out how to make resolution better when having multiple charts.

HTML Code:

  <div class="col-md-4">
    <div class="card card-chart">
      <div class="card-header">
        <h5 class="card-title">Cervical Lateral Flexion</h5>
        <!--<p class="card-category">Line Chart</p>-->
      </div>
      <div class="card-body">
        <canvas id="cervicalLateralFlexion"></canvas>
      </div>
    </div>
  </div>

Typescript Code:

  public static buildChart(doc, selector, title, yHeight) {
    let height = yHeight;
    const canvas = document.querySelector(selector) as HTMLCanvasElement;
    doc.setFontSize(h5);
    doc.text(leftMargin, yHeight, title);
    const canvasImage = canvas.toDataURL('image/png');
    height += margin;
    doc.addImage(canvasImage, 'PNG', leftMargin, height);
  }

Can anyone help? How to solve this issue? Thanks in advance! Image 1 is how it looks on web page and image 2 is PDF file.

enter image description here

enter image description here

Upvotes: 9

Views: 4399

Answers (4)

Neeraj Sewani
Neeraj Sewani

Reputation: 4287

If you are using puppeteer for taking the screenshot in image or pdf format then you need to scale the image using deviceScaleFactor option, by default it is set to 1. Its min value is 1 and the max is 5. By setting it, both the image as well as pdf would be scaled up in quality. Check out this issue for more details on this. Attaching the code below:

const page = await browser.newPage();
await page.setViewport({ width: 800, height: 800, deviceScaleFactor: 3 });

P.S. Setting width and height to viewport wouldn't have any effect on the size of image/pdf.

Upvotes: 1

Haifeng Zhang
Haifeng Zhang

Reputation: 31895

Update on May 10th, 2020 I have recorded a couple of videos on how to build PDF report using Weasyprint and Plotly, you can find details here: https://www.youtube.com/watch?v=hjWTCYPy7oU&t=1s

Update on Feb 3rd, 2020:

I have found that Plotly is really amazing when export static images, highly recommended. See images I have built and you can compare it with my original question post images: enter image description here A couple of features:

  • Images can be saved as SVG or PNG. No worry about the blurry issue anymore!
  • it is easy to customize the theme/style
  • You can add any shape on the graph, see the blue vertical line, you can draw triangle, rectangle(see the filled gray area) and any other shapes you want to.
  • It supports multiple languages: JS, Python, and R.

Original Post in 2019:

Thanks all for whom answered my question, I really appreciate your help I spent more than 2 weeks on jsPDF + Chart.js but still not figuring out how to print PDF with canvas images properly.

This project is new, the languages/tools/dependencies are not restricted. To make the project done on time I switch the PDF report from Front-End(Angular) to Back-End(Python). Now it took me 2 days to complete the PDF.

sources I used to generate PDF report and Charts:

Many thanks to Weasyprint for making generating PDF documents with HTML and CSS productive! Here's the PDF I have been generated:

enter image description here

enter image description here

Upvotes: 1

dean grande
dean grande

Reputation: 744

This is a workaround, not a direct solution to the jsPDF issues described. I was faced with the exact same issue: jsPDF, blurriness for charts and angular.

Hopefully, I'm not straying too far off topic but I thought this would be valuable for some.

The workaround: node + puppeteer

puppeteer is a headless chrome browser that works under any environment (even linux terminal) and can render a web page into an image or PDF.

    import * as puppeteer from "puppeteer";

    const browser = await puppeteer.launch({});
    const page = await browser.newPage();

    await page.goto("https://google.com", { waitUntil: 'networkidle2' });
    await page.setViewport({ width: 1250, height: 3950 });
    await page.screenshot({ path: 'hourly_report.png' });
    await browser.close();

Upvotes: 2

kiranvj
kiranvj

Reputation: 34117

You need to set the height and width in addImage

doc.addImage(canvasImage, 'PNG', leftMargin, height, "IMAGE-WIDTH-HERE", "IMAGE-HEIGHT-HERE");

If you don't know the image dimensions try something like this.

function getImageDimensions(file) {
  return new Promise (function (resolved, rejected) {
    var i = new Image()
    i.onload = function(){
      resolved({w: i.width, h: i.height})
    };
    i.src = file
  })
}

public static buildChart(doc, selector, title, yHeight) {
    let height = yHeight;
    const canvas = document.querySelector(selector) as HTMLCanvasElement;
    doc.setFontSize(h5);
    doc.text(leftMargin, yHeight, title);

    const canvasImage = canvas.toDataURL('image/png, 1.0');

    let dimensions = await getImageDimensions(canvasImage);

    height += margin;
    doc.addImage(canvasImage, 'PNG', leftMargin, height, dimensions.w, dimensions.h);
  }

getImageDimensions function copied from here, this function uses Promises since we need synchronous behavior in your scenario.

Upvotes: 2

Related Questions