How do I load Google Charts in node.js?
Asked Answered
J

2

4

When I attempt to load a Google Chart in node.js, nothing happens.

I tried loading the first example from the line chart docs in both zombie.js and jsdom, but the chart never loads in either case.

The end goal is to retrieve the SVG data of the generated chart for export into an image or PDF. So if an alternate method (server side using node.js or PHP) to achieve this is possible, I'm open to suggestions.

NOTE: I have successfully generated a images of a few charts using gChartPhp, but the requirements of this project state that the embedded version be the interactive version provided by the current API and the exported version be visually IDENTICAL to the embedded one (without being interactive, obviously).

Edit: I tagged PhantomJS, since that is the solution with which I ultimately went.

Sorry for the lack of links, but the spam prevention mechanism will only allow me to post 2.

Jittery answered 29/12, 2012 at 9:9 Comment(0)
J
2

It wasn't the ideal solution, but I found an alternative to node.js for accomplishing the same end goal in PhantomJS. Simply create an HTML file containing the chart (test.html) and like node.js, create a JS file containing your code (test.js). Then run your JS file with PhantomJS.

In your JS file, open your HTML file as a webpage, then render it, either saving the image buffer to a file:

var page = require('webpage').create();
page.open('test.html', function () {
    page.render('test.png');
    phantom.exit();
});

Then run it:

phantomjs test.js

To dynamically create a chart, create the following JS file (test2.js):

var system = require('system');
var page = require('webpage').create();
page.onCallback = function(data)
{
    page.clipRect = data.clipRect;
    page.render('test.png');
    phantom.exit();
};
page.includeJs('http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js', function()
{
    page.includeJs('https://www.google.com/jsapi', function()
    {
        page.evaluate(function(chartType, data_json, options_json)
        {
            var div = $('<div />').attr('id', 'chart').width(900).height(500).appendTo($('body'));
            google.load("visualization", "1",
            {
                packages:[chartType == 'GeoChart' ? 'geochart' : 'corechart'],
                callback: function()
                {
                    data_arr = $.parseJSON(data_json);
                    data = google.visualization.arrayToDataTable(data_arr);
                    options = $.parseJSON(options_json);
                    chart = new google.visualization[chartType]($(div).get(0));
                    google.visualization.events.addListener(chart, 'ready', function()
                    {
                        window.callPhantom(
                        {
                            clipRect: $(div).get(0).getBoundingClientRect()
                        });
                    });
                    chart.draw(data, options);
                }
            });
        }, system.args[1], system.args[2], system.args[3]);
    });
});

Then run it:

phantomjs test2.js LineChart '[["Date","Steve","David","Other"],["Dec 31",8,5,3],["Jan 1",7,10,4],["Jan 2",9,4,3],["Jan 3",7,5,3]]' '{"hAxis.slantedText":true}'

phantomjs test2.js PieChart '[["Employee","Calls"],["Steve",31],["David",24],["Other",13]]' '{"is3D":true}'

phantomjs test2.js GeoChart '[["State","Calls"],["US-CA",7],["US-TX",5],["US-FL",4],["US-NY",8]]' '{"region":"US","resolution":"provinces"}'

To get the image data from an external script, make a copy of test2.js (test3.js) and change

page.render('test.png');

to

console.log(page.renderBase64('png'));

Then call it (from PHP, for example):

<?php

    $data = array(
        array("Employee", "Calls"),
        array("Steve", 31),
        array("David", 24),
        array("Other", 13)
    );
    $options = array(
        "is3D" => true
    );
    $command = "phantomjs test3.js PieChart '" . json_encode($data) . "' '" . json_encode($options) . "'";
    unset($output);
    $result = exec($command, $output);
    $base64_image = implode("\n", $output);
    $image = base64_decode($base64_image);

?>

NOTE: Looking back on this whole process, the problem I was having with node.js was possibly that I didn't setup callbacks or timeouts to wait until the charts were "ready".

Jittery answered 4/1, 2013 at 0:33 Comment(0)
O
5

I'm 8 years late but I've just released an open-source project Google Charts Node that renders chart images in Puppeteer (somewhat of a successor to the original PhantomJS solution).

google-charts-node is available as an NPM library and can be used like so:

const GoogleChartsNode = require('google-charts-node');

function drawChart() {
  // Set up your chart here, just like in the browser
  // ...

  const chart = new google.visualization.BarChart(container);
  chart.draw(data, options);
}

// Render the chart to image
const image = await GoogleChartsNode.render(drawChart, {
  width: 400,
  height: 300,
});

Now you can save this image buffer as a file or return it as an HTTP response, etc.

It was pretty straightforward to create this. The main caveats were:

  1. Not all charts support getImageURI, so I fall back to puppeteer to take a screenshot when this happens.
  2. It's slow! But if you must use Google Charts as a requirement, you don't really have an alternative. This problem can be mitigated with enough cloud compute resources.

You can view the full source at the Github project, but here's the raw puppeteer flow if you want to do it yourself:

async function render() {
  // Puppeteer setup
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // Add the chart
  await page.setContent(`...Insert your Google Charts code here...`);

  // Use getImageURI if available (not all charts support)
  const imageBase64 = await page.evaluate(() => {
    if (!window.chart || typeof window.chart.getImageURI === 'undefined') {
      return null;
    }
    return window.chart.getImageURI();
  });

  let buf;
  if (imageBase64) {
    buf = Buffer.from(imageBase64.slice('data:image/png;base64,'.length), 'base64');
  } else {
    // getImageURI was not available - take a screenshot using puppeteer
    const elt = await page.$('#chart_div');
    buf = await elt.screenshot();
  }

  await browser.close();
  return buf;
}
Orvalorvan answered 24/6, 2020 at 20:43 Comment(1)
I'm sorry but what is the container that you used here const chart = new google.visualization.BarChart(container);Macrogamete
J
2

It wasn't the ideal solution, but I found an alternative to node.js for accomplishing the same end goal in PhantomJS. Simply create an HTML file containing the chart (test.html) and like node.js, create a JS file containing your code (test.js). Then run your JS file with PhantomJS.

In your JS file, open your HTML file as a webpage, then render it, either saving the image buffer to a file:

var page = require('webpage').create();
page.open('test.html', function () {
    page.render('test.png');
    phantom.exit();
});

Then run it:

phantomjs test.js

To dynamically create a chart, create the following JS file (test2.js):

var system = require('system');
var page = require('webpage').create();
page.onCallback = function(data)
{
    page.clipRect = data.clipRect;
    page.render('test.png');
    phantom.exit();
};
page.includeJs('http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js', function()
{
    page.includeJs('https://www.google.com/jsapi', function()
    {
        page.evaluate(function(chartType, data_json, options_json)
        {
            var div = $('<div />').attr('id', 'chart').width(900).height(500).appendTo($('body'));
            google.load("visualization", "1",
            {
                packages:[chartType == 'GeoChart' ? 'geochart' : 'corechart'],
                callback: function()
                {
                    data_arr = $.parseJSON(data_json);
                    data = google.visualization.arrayToDataTable(data_arr);
                    options = $.parseJSON(options_json);
                    chart = new google.visualization[chartType]($(div).get(0));
                    google.visualization.events.addListener(chart, 'ready', function()
                    {
                        window.callPhantom(
                        {
                            clipRect: $(div).get(0).getBoundingClientRect()
                        });
                    });
                    chart.draw(data, options);
                }
            });
        }, system.args[1], system.args[2], system.args[3]);
    });
});

Then run it:

phantomjs test2.js LineChart '[["Date","Steve","David","Other"],["Dec 31",8,5,3],["Jan 1",7,10,4],["Jan 2",9,4,3],["Jan 3",7,5,3]]' '{"hAxis.slantedText":true}'

phantomjs test2.js PieChart '[["Employee","Calls"],["Steve",31],["David",24],["Other",13]]' '{"is3D":true}'

phantomjs test2.js GeoChart '[["State","Calls"],["US-CA",7],["US-TX",5],["US-FL",4],["US-NY",8]]' '{"region":"US","resolution":"provinces"}'

To get the image data from an external script, make a copy of test2.js (test3.js) and change

page.render('test.png');

to

console.log(page.renderBase64('png'));

Then call it (from PHP, for example):

<?php

    $data = array(
        array("Employee", "Calls"),
        array("Steve", 31),
        array("David", 24),
        array("Other", 13)
    );
    $options = array(
        "is3D" => true
    );
    $command = "phantomjs test3.js PieChart '" . json_encode($data) . "' '" . json_encode($options) . "'";
    unset($output);
    $result = exec($command, $output);
    $base64_image = implode("\n", $output);
    $image = base64_decode($base64_image);

?>

NOTE: Looking back on this whole process, the problem I was having with node.js was possibly that I didn't setup callbacks or timeouts to wait until the charts were "ready".

Jittery answered 4/1, 2013 at 0:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.