Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Google Charts not working within jsdom #1463

Closed
bjm88 opened this issue Apr 26, 2016 · 5 comments
Closed

Google Charts not working within jsdom #1463

bjm88 opened this issue Apr 26, 2016 · 5 comments
Labels

Comments

@bjm88
Copy link

bjm88 commented Apr 26, 2016

Hi,
I read online many examples of using jsdom to do server side charting. I can't seem to get it to work with Google Charts though. I'm using the latest 8.4.0 build and I've tried a few variations like auto loading the full google visualization api or using the recommended load base api script first then visualization. Both had issues, code is below, commented parts is for other way.

Trying to load it sync all in script tag using googles autoload way gives this error:
var data = new google.visualization.DataTable();
TypeError: google.visualization.DataTable is not a function

If I change it to use the google.load after jsdom gives done event it just exists and I get no details
google.load("visualization", "1", {packages:["corechart"]});
google.setOnLoadCallback(function(){
console.log("Google onload callback called");
window.docharting();
});

full code:

var jsdom = require("jsdom");

//TODO newscript way to load, can update someday
//http://www.gstatic.com/charts/loader.js
//google.charts.load('current', {'packages':['corechart']});
//google.charts.setOnLoadCallback(docharting);
jsdom.env({
  html: '<div id="map" style="width: 900px; height: 500px;"></div>',
  scripts: ["http://www.google.com/jsapi?autoload={'modules':[{'name':'visualization','version':'1','packages':['corechart']}]}"],
  FetchExternalResources :["script", "link", "img"],
  ProcessExternalResources: ["script"],
  done: function (err, window) {
    if(err){
        console.log("err: " + err);
        return;
    }else{
        console.log("Initiating charting...");

        var google = window.google;
        console.log("Google visualization: " + google.visualization);
        window.docharting = function(err, obj){
            console.log("err " + JSON.stringify(err) + ", obj: " + obj);
            console.log("Starting chart drawing...");
                    // Create the data table.
            var data = new google.visualization.DataTable();
            data.addColumn('string', 'Topping');
            data.addColumn('number', 'Slices');
            data.addRows([
              ['Mushrooms', 3],
              ['Onions', 1],
              ['Olives', 1],
              ['Zucchini', 1],
              ['Pepperoni', 2]
            ]);

            // Set chart options
            var options = {'title':'How Much Pizza I Ate Last Night',
                           'width':400,
                           'height':300};

            d = window.document;
            elm = d.getElementById('map');

            console.log("Got map container " + el + ", starting chart rendering...");

            // Instantiate and draw our chart, passing in some options.
            var chart = new google.visualization.PieChart(elm);

            google.visualization.events.addListener(chart, 'ready', function () {
                console.log("Chart ready, capturing image uri");
              imageURI = chart.getImageURI();
              console.log(imageURI);
            });

            console.log("Data ready, starting drawing...");
            chart.draw(data, options);
            console.log("Done drawing");
        }

        if(google){

            //google.load("visualization", "1", {packages:["corechart"]});
            //google.setOnLoadCallback(function(){
            //  console.log("Google onload callback called");
            //  window.docharting();
            //});
            window.docharting();
        }else{
            console.log("Google apis did not initialize properly");
        }
    }
  }
});
@Sebmaster
Copy link
Member

Sebmaster commented Apr 26, 2016

  1. The library uses document.write, so you have to embed it directly into the html you feed to jsdom.
  2. FetchExternalResouces and ProcessExternalResources need to be nested in a features object.
  3. You might need to set a timeout to do the actual drawing so the charting lib has some time to load. I didn't test without it.
var jsdom = require("jsdom");

//TODO newscript way to load, can update someday
//http://www.gstatic.com/charts/loader.js
//google.charts.load('current', {'packages':['corechart']});
//google.charts.setOnLoadCallback(docharting);
jsdom.env({
  html: `<div id="map" style="width: 900px; height: 500px;"></div><script src="http://www.google.com/jsapi?autoload={'modules':[{'name':'visualization','version':'1','packages':['corechart']}]}"></script>`,
  features: {
  FetchExternalResources :["script", "link", "img"],
  ProcessExternalResources: ["script"],
  },virtualConsole: jsdom.createVirtualConsole().sendTo(console),
  done: function (err, window) {
console.log(window.document.body.innerHTML)
    if(err){
        console.log("err: " + err);
        return;
    }else{
        console.log("Initiating charting...");

        var google = window.google;
        console.log("Google visualization: " + google.visualization);
        window.docharting = function(err, obj){
            console.log("err " + JSON.stringify(err) + ", obj: " + obj);
            console.log("Starting chart drawing...");
                    // Create the data table.
            console.log(google.visualization)
            var data = new google.visualization.DataTable();
            data.addColumn('string', 'Topping');
            data.addColumn('number', 'Slices');
            data.addRows([
              ['Mushrooms', 3],
              ['Onions', 1],
              ['Olives', 1],
              ['Zucchini', 1],
              ['Pepperoni', 2]
            ]);

            // Set chart options
            var options = {'title':'How Much Pizza I Ate Last Night',
                           'width':400,
                           'height':300};

            d = window.document;
            elm = d.getElementById('map');

            console.log("Got map container " + el + ", starting chart rendering...");

            // Instantiate and draw our chart, passing in some options.
            var chart = new google.visualization.PieChart(elm);

            google.visualization.events.addListener(chart, 'ready', function () {
                console.log("Chart ready, capturing image uri");
              imageURI = chart.getImageURI();
              console.log(imageURI);
            });

            console.log("Data ready, starting drawing...");
            chart.draw(data, options);
            console.log("Done drawing");
        }

        if(google){

            //google.load("visualization", "1", {packages:["corechart"]});
            //google.setOnLoadCallback(function(){
            //  console.log("Google onload callback called");
            //  window.docharting();
            //});
            setTimeout(window.docharting, 4000);
        }else{
            console.log("Google apis did not initialize properly");
        }
    }
  }
});

@bjm88
Copy link
Author

bjm88 commented Apr 27, 2016

Thank you Sebmaster, this was very helpful, though I'm still not able to get end to end solution to work. The goal is using node and jsdom to use google charts API server side to generate chart images for emails and PDFs... To that end I took your changes and enhanced slightly...

  1. Added a setInterval poller rather than just waiting 4 seconds for init, this works and I see it inits in usually 100-200 milliseconds
  2. I added Q library for promise and tried to call draw and then have a promise that resolves when chart is done drawing, however this
    google.visualization.events.addListener(chart, 'ready', function () {
    is never called..it seems the node process just exists immediately after calling chart.draw(data, options);

Any ideas on how to test if chart actually working or why chart ready event not firing properly, and also why the program isn't just hanging waiting for my promise to resolve like I'd expect...

Thank you!

var jsdom = require("jsdom");
var Q = require("q");

//TODO newscript way to load, can update someday
//http://www.gstatic.com/charts/loader.js
//google.charts.load('current', {'packages':['corechart']});
//google.charts.setOnLoadCallback(docharting);
jsdom.env({
  html: `<div id="map" style="width: 900px; height: 500px;"></div><script src="http://www.google.com/jsapi?autoload={'modules':[{'name':'visualization','version':'1','packages':['corechart']}]}"></script>`,
  features: {
  FetchExternalResources :["script", "link", "img"],
  ProcessExternalResources: ["script"],
  },virtualConsole: jsdom.createVirtualConsole().sendTo(console),
  done: function (err, window) {
    console.log("DOM ready, starting...");
    if(err){
        console.log("err: " + err);
        return;
    }else{
        var google = window.google;
        window.docharting = function(){
            console.log("Starting chart building...");

            var data = new google.visualization.DataTable();
            data.addColumn('string', 'Topping');
            data.addColumn('number', 'Slices');
            data.addRows([
              ['Mushrooms', 3],
              ['Onions', 1],
              ['Olives', 1],
              ['Zucchini', 1],
              ['Pepperoni', 2]
            ]);

            var options = {'title':'How Much Pizza I Ate Last Night',
                           'width':400,
                           'height':300};

            d = window.document;
            elm = d.getElementById('map');
            var chart = new google.visualization.PieChart(elm);
            var deferred = Q.defer();

            google.visualization.events.addListener(chart, 'ready', function () {
              console.log("Chart ready, capturing image uri");
              var imageURI = chart.getImageURI();
              console.log(imageURI);
              deferred.resolve(imageURI);
            });

            console.log("Data ready, starting drawing...");

            window.chartDrawWrapper = function(){
                chart.draw(data, options);
                return deferred.promise;
            }
            window.chartDrawWrapper().then(function(imageURI){
                console.log("Got imageURI with lenght " + imageURI.length);
            }).catch(function (error) {
                console.log("errors=" + error);
            });;
        }

        window.isFunction = function(functionToCheck) {
         var getType = {};
         return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
        }

        window.checkchartinit = function(){
            if(google && google.visualization && google.visualization.PieChart && window.isFunction(google.visualization.PieChart)){
                console.log("Chart API init successful");
                clearInterval(window.chartinitpoller);
                window.docharting();
            }else{
                console.log("Chart API not init yet");
            }
        }

        window.chartinitpoller = setInterval(window.checkchartinit, 100);
    }
  }
});

@amory
Copy link

amory commented May 22, 2016

I had a similar problem that I solved by using the new Google library loader, hacking up a quick patch for issue #1495 and building node with full internationalization support. Here's how I did it.

The first problem is how the Google library loader chains resource loading. It creates an element with a load event listener to load a subsequent resource. It chains all resources to be loaded this way and then inserts the first element (a css link) into the DOM. The problem is it sets the href for the link after it's attached to the DOM. This is broken by #1495.

After resolving that issue with a quick hack, the code to create a chart is pretty straightforward:

var jsdom = require('jsdom');

jsdom.env({
  html: '<html><head></head><body><div id="chart_div"></div></body></html>',
  scripts: ["https://www.gstatic.com/charts/loader.js"],
  features: {
    FetchExternalResources: ["script", "link"],
    ProcessExternalResources: ["script"],
  },
  done: function(err, window) {
    var document = window.document;
    var google = window.google;

    // Load the Visualization API and the corechart package.
    google.charts.load('current', {'packages':['corechart']});

    // Set a callback to run when the Google Visualization API is loaded.
    google.charts.setOnLoadCallback(drawChart);

    // Callback that creates and populates a data table,
    // instantiates the pie chart, passes in the data and
    // draws it.
    function drawChart() {

      // Create the data table.
      var data = new google.visualization.DataTable();
      data.addColumn('string', 'Topping');
      data.addColumn('number', 'Slices');
      data.addRows([
        ['Mushrooms', 3],
        ['Onions', 1],
        ['Olives', 1],
        ['Zucchini', 1],
        ['Pepperoni', 2]
      ]);

      // Set chart options
      var options = {'title':'How Much Pizza I Ate Last Night',
                     'width':400,
                     'height':300};

      // Instantiate and draw our chart, passing in some options.
      var chart = new google.visualization.PieChart(document.getElementById('chart_div'));

      google.visualization.events.addListener(chart, 'ready', function () {
        // Do something with the chart here
        console.log('chart ready');
      });

      chart.draw(data, options);
    }
  }
});

The other issue I ran into was Node by default is not built with full internationalization support. If you get this fatal error:

#
# Fatal error in , line 0
# Failed to create ICU break iterator, are ICU data files missing?
#

Then building node as described here should solve it.

@zallek
Copy link

zallek commented Jun 20, 2016

I have made a repo for generating google chart images on node if you are interested in.
https://github.com/zallek/node-googlecharts

@katylava
Copy link

katylava commented Jul 1, 2016

@Am0ry What was your quick hack to resolve #1495?

@domenic domenic closed this as completed Jul 2, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants