Search code examples
javascriptnode.jschartschart.jspuppeteer

How to create a bar chart image on nodejs without a browser?


Have a single nodejs project using a PDF generator, and need paste a bar chart into it, but the project runs on a desktopless server.

Have any library or method to create a bar chart image on nodejs on the runtime without a web browser for paste it into a pdf file?

I use puppeteer for create the PDF but the Chart.js library can not render the canvas, by example:

await page.setContent(`<!DOCTYPE html>
<html><body>
<canvas id="chart"></canvas>
<script>
    const ctx = document.getElementById('chart');

    new Chart(ctx, {
        type: 'bar',
        data: {
            labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
            datasets: [{
                label: '# of Votes',
                data: [12, 19, 3, 5, 2, 3],
                borderWidth: 1
            }]
        },
        options: {
            scales: { y: { beginAtZero: true } }
        }
    });
</script>
</body></html>`,
{ waitUntil: 'domcontentloaded' });

await page.addScriptTag({ url: 'https://cdn.jsdelivr.net/npm/chart.js' })

The results is a blank page. That's why I'm looking for a better option to generate a simple image and paste it directly.

I also tried:

await page.setContent(`<!DOCTYPE html>
<html><body>
<canvas id="chart"></canvas>
</body></html>`,
{ waitUntil: [ 'load', 'networkidle0' ] });

await page.addScriptTag({ url: 'https://cdn.jsdelivr.net/npm/chart.js' });
await page.addScriptTag({ content: `
    const ctx = document.getElementById('chart');

    new Chart(ctx, {
        type: 'bar',
        data: {
            labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
            datasets: [{
                label: '# of Votes',
                data: [12, 19, 3, 5, 2, 3],
                borderWidth: 1
            }]
        },
        options: {
            scales: { y: { beginAtZero: true } }
        }
    });
});

Solution

  • You're adding the script after running the chart.js code that depends on it. Try adding the script first:

    const puppeteer = require("puppeteer"); // ^22.6.0
    
    const html = `<!DOCTYPE html>
    <html><body>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <canvas id="chart"></canvas>
    <script>
        const ctx = document.getElementById('chart');
        new Chart(ctx, {
            type: 'bar',
            data: {
                labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
                datasets: [{
                    label: '# of Votes',
                    data: [12, 19, 3, 5, 2, 3],
                    borderWidth: 1
                }]
            },
            options: {
                scales: { y: { beginAtZero: true } },
                animation: { duration: 0 },
            }
        });
    </script>
    </body></html>`;
    
    let browser;
    (async () => {
      browser = await puppeteer.launch();
      const [page] = await browser.pages();
      await page.setContent(html);
      await page.pdf({path: "test.pdf"});
    })()
      .catch(err => console.error(err))
      .finally(() => browser?.close());
    

    Note that I'm also disabling animations so the screenshot captures the final chart rather than an in-progress rendering of it. If you still see missing pieces, you can wait until requests arrive or use a broader (less optimal) approach like setContent(html, {waitUntil: "networkidle0"}).

    When things seem to be "not showing up" (or whatever) in web projects, be sure to look at the developer tools to see if there are JS errors. Your original code should give Uncaught ReferenceError: Chart is not defined which is a pretty clear description of the issue.

    Contrary to your original post title, we are using a browser here, it's just running headlessly. It's useful to open it headfully for temporary debugging with puppeteer.launch({headless: false}).

    See also: