7
votes

I am trying to generate multiple charts in a PDF using Google Charts. For the PDFs, I am using CakePDF with the Wkhtmltopdf engine. I appear to be having a problem with actually loading the Google Chart code into the PDF though. This is my current code for my Javascript.

<script type="text/javascript" src="https://www.google.com/jsapi">
</script>
<script type="text/javascript">
google.load('visualization', '1.0', {'packages':['corechart']});
//setTimeout(function() {google.load('visualization', '1.0', {'packages':['corechart']});}, 100);
google.setOnLoadCallback(drawChart);
function drawChart(doIt,taken, total, element) 
{
    if (typeof doIt === 'boolean')
    {
        var data = new google.visualization.DataTable();
        data.addColumn('string', 'Type');
        data.addColumn('number', 'Courses');
        data.addRows([
          ['Taken', taken],
          ['Remaining', total - taken]
        ]);
        var options = {
                       'width':40,
                       'height':40};
        var chart = new google.visualization.PieChart(document.getElementById(element));
        chart.draw(data, options);
    }
 }
</script>

The problem is that when I do the google.load for the visualization package, it causes Wkhtmltopdf to return with an error saying that the engine returned no data. I found a problem that I thought was similar at Why does google.load cause my page to go blank? so I tried to do setTimeout(function() {google.load('visualization', '1.0', {'packages':['corechart']});}, 100); The problem with this solution was that if the delay is too low, the page will return with no errors, but it will be completely blank; however, if I set the delay too high, the page will just not load the package and the Javascript will break at

var data = new google.visualization.DataTable();

when I call the function. Additionally, that poster stated problem with document.write(), but I have no issues if I add document.write() lines before or after I add the content to the page. I would appreciate it if anyone knew how to get Google Charts to work with Wkhtmltopdf and could help me.


Ok, In an attempt to give the API more time to load, I moved where I call the function to the end of the PHP. Now, it just sets an array of elements that need graphs drawn in them and the proper values, then it calls the function with the value from the arrays. I am not sure what the problem is now though because it is breaking on chart.draw(data, options); now. It appears to be getting the correct values and element passed to it though.

This will seem really roundabout since it is. For whatever reason, Wkhtmltopdf cannot read anything I put in a javascript tag. I have tried everything I can to get it to read it, but it just will not haha. The CakePDF plugin can read Javascript though, so my JS code is in the default PDF layout. Then in the actual view that is rendered by WkhtmltoPdf, I create an array of elements and values. I then do this (after many different possible solutions, this is the only way I was able to call the JS function)

for ($i = 0; $i < sizeof($grade_array); $i++)
{
    $element = $grade_array[$i][2];
    echo '<script type="text/javascript">drawChart(true, '.$this->Js->value($grade_array[$i][0]).', '.$this->Js->value($grade_array[$i][1]).','.json_encode($element).');</script>';
}

It appears to pass all of the correct data. Upon calling the function, I have debug lines printing the parameters, and all parameters are correctly printed. I also noticed that if I do document.write('test') in the same place as the chart.draw(), it will write 'test' without any error. For some reason, if I do chart.draw(), it just says Wkhtmltopdf didn't return any data.

8
Given the code you posted, there is no way for the if (typeof doIt === 'boolean') to return true, because the callback from the google.load call has no parameters (doIt will always be null). You must be calling the drawChart function somewhere else if you are getting an error on the line var data = new google.visualization.DataTable();. Is this the whole of your code or is there more to it?asgallant
I'm sorry about that. I call the function itself later where I pass it the necessary parameters. I know that it is breaking at that line because I actually have a debugging line to print a message after each line so I know where it breaks; I just removed those to make the code easier for someone to read. I am certain that the function is being called with the correct parameters though.Isaac Senior
You could be getting errors if the function is being called before the API is finished loading - the purpose of the callback is to ensure that everything is loaded first. Try arranging your code so that the trigger for the drawChart call happens inside the callback from the google loader (which need not be the drawChart function itself).asgallant
I am not sure exactly how to do that. The function is called from within the content of the page. There is a PHP for loop where I get the proper information and then pass it to the Javascript function. Is there a way I can just delay the page from rendering the PHP until the API is loaded?Isaac Senior
What triggers the function call? A document ready event?asgallant

8 Answers

8
votes

Problem: When using wkhtmltopdf / wkhtmltoimage the pdf / image on a html page (with Google Chart), it did not contain the Google chart. The output (pdf / png) was simply blank at the position the Google Chart should show up. The original html page was okay, of course.

Solution: I solved this issue (Google Charts + wkhtmltopdf) by replacing:

<script src="http://www.gstatic.com/charts/loader.js"></script>

by

<script src="http://www.google.com/jsapi"></script>

Somehow the wkhtmltopdf library could not handle a Google Chart html page which contains the first javascript include.

3
votes

I resolved this problem today myself, so here is what worked out for me. For a more detailed walkthrough, you are welcome to read my blog post about the subject.

I only had to make two simple changes to make it work:

  1. Supply the javascript-delay argument, e.g. javascript-delay 1000. This delays the execution of JavaScript code for 1000 milliseconds
  2. Add a callback when loading the Google Visualization API

function init() {
  google.load("visualization", "1.1", {
    packages: ["corechart"],
    callback: 'drawCharts'
  });
}

Then you can simply implement the drawCharts function where you can draw your charts exactly as you normally would.

Make sure your markup includes the following:

<body onload="init()">
    <div id="my-chart"></div>
</body>

Simply draw your chart to the div with the above ID, and you should be good to go.

Note: I am using the newest available binary (0.12.0 as of now). Tested on a 64-bit Ubuntu installation.

2
votes

Instead of using the javascript-delay option, you should rather actually wait for the chart to be rendered by using window-status. window-status makes wkhtmltopdf wait until the window status has the desired value.

Add an event listener to the chart

google.visualization.events.addListener(tableChart, 'ready', myReadyHandler);
function myReadyHandler(){
  window.status = "ready";
}

Run wkhtmltopdf using --window-status ready

2
votes

Encountered this issue recently. I have tried all the workarounds to fix the loading issue - delays, setInterval, setTimeout. Also tried upgrading wkhtml version.

What worked for me is explicitly define the Google Charts version number to use. In my case version 44.

<script>google.load("visualization", "44", {packages:["corechart"]});</script>

Note: using version "1" now means you are using the current version. Per Google document - "All 'jsapi' requests are now being redirected to the new loader. If you were loading version '1' or '1.0', you will now be loading 'current'."

1
votes

I also have problems using

window.onload = function () {
   google.charts.load("current", {packages:["corechart", "timeline", "bar"]});
   google.charts.setOnLoadCallback(drawCharts);
};

drawCharts is never launched by the QT engine, thus no chart is rendered in the PDF (verified also with QTWeb Browser).

I finally solved with a not very elegant way

window.onload = function () {
   google.charts.load("current", {packages:["corechart", "timeline", "bar"]});
   setTimeout(function(){drawCharts();}, 500);
};
0
votes

Google Charts are not good at all. I've serval issues with google charts. I'm using D3 library https://d3js.org v5.9.2.

Everything is ok.

You can find examples how to do it. I used that example https://wizardace.com/d3-piechart/

0
votes

It's working with the following code.

<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>

And adding timeout/wait till its loaded.

 google.charts.load('current', {packages: ['corechart']});
    var interval = setInterval(function() {
      if (
        google.visualization !== undefined
        && google.visualization.DataTable !== undefined
        && google.visualization.PieChart !== undefined
      ){
        clearInterval(interval);
        window.status = 'ready';
        drawCharts();
      }
    }, 100);
0
votes

If you are facing this issue after ~may, july 2020 and your script was working, but not more now, for no reason, you must know why Google has deprecated the version of script in http://www.google.com/jsapi an now they are redirecting the requests for http://www.gstatic.com/charts/loader.js who isn't compatible with wkhtmltopdf.

So, for back to old version you must improve your code like that:

<script type="text/javascript" src="http://www.google.com/jsapi"></script>
        
<script type="text/javascript">
function init() {
    //the latest version who worked for me was the 44. 45 and above not worked anymore.
    google.load("visualization", "44", {packages:["corechart"]});
    var interval = setInterval(function() {
        if ( google.visualization !== undefined && google.visualization.DataTable !== undefined && google.visualization.PieChart !== undefined ){ clearInterval(interval);
        window.status = 'ready';
        drawCharts(); //call here your callback who will render the chart
        }
    }, 100);
}
</script>

And use this option to say to wkhtmltopdf to start render the pdf when window.status is ready:

--window-status ready