# Chart.js to PDF: render charts in HTML-to-PDF Chart.js to PDF without blank canvases. Disable animation, use wait_for_expression, get sharp output, plus tips for ECharts and ApexCharts. **Quick answer.** In Chart.js, set `animation: false` and set `window.chartsReady = true` once the charts have drawn. Then send the HTML with `wait_for_expression` so the renderer waits for that flag before printing. ```bash curl -X POST https://html-pdf.com/v1/pdf \ -H "Authorization: Bearer $HTML_PDF_KEY" \ -H "Content-Type: application/json" \ -d "$(jq -n --rawfile html report.html \ '{html: $html, format: "A4", margin: "15mm", wait_for_expression: "window.chartsReady === true"}')" \ -o report.pdf ``` Charts are the most common reason an HTML-to-PDF render comes out wrong. The PDF shows an empty box, a half-drawn bar chart, or a blurry image. The cause is almost always timing. Chart.js draws on a `` with JavaScript, often with an animation, and the browser prints before the drawing is done. This guide fixes that with three changes that apply to Puppeteer, Playwright, Gotenberg or an API. ## Why charts come out blank or half-drawn These are the four usual causes, so you know what to fix. 1. **The script loads after the page is "ready".** The renderer may print as soon as the DOM loads, before Chart.js from a CDN has run. 2. **Animations.** Chart.js animates by default, so the first frames show bars at zero height. 3. **Responsive sizing.** With `responsive: true`, the chart sizes itself to its container, and print layout can resize the canvas after it was drawn. 4. **Resolution.** A canvas is a bitmap. Drawn at screen resolution, it looks soft when zoomed in a PDF viewer. ## A print-ready Chart.js page This page gives you a working template that prints charts fully drawn and sharp. ```html

Monthly revenue

``` Here is what each piece does. - **`animation: false`** removes the in-between frames, so the first render is the final chart. - **`responsive: false`** with `width` and `height` on the canvas gives a fixed size that print layout can't change. 700 px is about 185 mm at 96 dpi, which fits an A4 page with normal margins. - **`devicePixelRatio: 2`** draws the canvas at twice the resolution. Use 2 or 3, since higher values only make the PDF bigger. - **The `afterRender` plugin** sets `window.chartsReady = true` once every chart has drawn. The renderer waits for that flag. - **`break-inside: avoid`** keeps a chart from being split across two pages. See [page breaks](https://html-pdf.com/html-to-pdf-page-breaks). If your charts load data with `fetch()`, create the charts after the data arrives. The plugin still sets the flag at the right time. ## Wait for the flag This section shows how to make your own renderer wait for the flag. **Puppeteer (DIY):** ```js await page.setContent(html, { waitUntil: "networkidle0" }); await page.waitForFunction("window.chartsReady === true", { timeout: 10000 }); await page.pdf({ path: "report.pdf", format: "A4", printBackground: true }); ``` **Gotenberg (DIY):** send the form field `waitForExpression=window.chartsReady === true`. Avoid fixed sleeps when you can. A two-second delay is too long for fast pages and too short on a slow day. Wait for the condition instead, and use a delay only as a fallback. ## Or skip the server You can skip running Chromium yourself. Send the same HTML to html-pdf.com with `wait_for_expression`, and Chromium runs the scripts, waits for your flag, then prints. ```bash curl -X POST https://html-pdf.com/v1/pdf \ -H "Authorization: Bearer $HTML_PDF_KEY" \ -H "Content-Type: application/json" \ -d "$(jq -n --rawfile html report.html \ '{html: $html, format: "A4", margin: "15mm", wait_for_expression: "window.chartsReady === true"}')" \ -o report.pdf ``` In Python: ```python import os, requests r = requests.post("https://html-pdf.com/v1/pdf", headers={"Authorization": f"Bearer {os.environ['HTML_PDF_KEY']}"}, json={ "html": open("report.html").read(), "format": "A4", "margin": "15mm", "wait_for_expression": "window.chartsReady === true", }) r.raise_for_status() open("report.pdf", "wb").write(r.content) ``` Total render time is capped at 30 seconds. If the flag never becomes true, the request fails and you aren't charged. For a dashboard that is already public, send `{"url": "...", "wait_for_expression": "...", "media": "screen"}` instead. This works as long as the page sets the flag itself. ## ECharts and ApexCharts The same pattern works with other chart libraries. - **ECharts:** set `animation: false` in the option and set the flag in the `finished` event with `chart.on("finished", () => { window.chartsReady = true; })`. Initialize with `echarts.init(el, null, { renderer: "svg" })` to get vector output. - **ApexCharts:** set `chart: { animations: { enabled: false } }`. Since `chart.render()` returns a promise, use `chart.render().then(() => { window.chartsReady = true; })`. ## SVG charts don't need waits SVG charts are the simplest case. Charts that arrive as finished SVG markup (for example generated on the server with D3, Vega or matplotlib with `format="svg"`) are just part of the HTML. Nothing has to run, so you don't need `wait_for_expression` at all. SVG also stays sharp at any zoom and keeps text selectable. If you control the pipeline, rendering charts to SVG before calling the PDF API is the most reliable option. ## From an AI agent An AI agent can do all of this for you. With the [PDF MCP server](https://html-pdf.com/pdf-mcp-server), an agent can build a report page with Chart.js and call `html_to_pdf` with `wait_for_expression`. Ask it to *"disable animation and set window.chartsReady when the charts render"*. ## Pricing You pay per page. 1 credit = 1 PDF page, and you get 100 free credits once. After that, packs go from $10 for 1,000 pages down to $0.005 per page. Credits never expire and failed renders are free. ## FAQ ### Why is my Chart.js chart blank in the PDF? The PDF was printed before Chart.js finished drawing. Set `animation: false`, set a flag such as `window.chartsReady = true` after the charts render, and pass `wait_for_expression` so the renderer waits for it. ### How do I make Chart.js charts sharp in a PDF? Set `devicePixelRatio: 2` (or 3) in the chart options so the canvas is drawn at a higher resolution. For fully vector output, render charts as SVG instead. ### Does it work with ECharts and ApexCharts? Yes. Disable animations and set the ready flag in the ECharts `finished` event or after ApexCharts `chart.render()` resolves. ### Am I charged if the chart never finishes rendering? No. If the flag never becomes true within the 30-second limit, the request fails and failed renders cost nothing. [Get a free API key](https://html-pdf.com/login) · [API docs](https://html-pdf.com/docs) · [Puppeteer HTML to PDF](https://html-pdf.com/puppeteer-html-to-pdf) · [Templates](https://html-pdf.com/templates)