A topics that pops up frequently in the Google Charts forums is about serializing the contents of an interactive chart - which is normally rendered as an svg object - as a plain old image. The current Google Charts API do not offer this feature (at the time of this writing) but a solution working on all modern browsers can be quickly written. This article demonstrates how.
You can try the code discussed above at this sample page, and access the example source code at this GitHub gist.
Note that this article discusses how to serialize charts to images in a completely client-side fashion. If you are willing to do server-side image generation, there are plenty of alternatives (such as getting screenshots of headless webkit processes).
Also note that there are domains in which Google Charts can be converted to images, for example when you are printing Google spreadsheets containing charts, exporting charts contained in Google spreadsheets (they offer a ‘Publish as image’ option) or generating charts using Google Apps Script Chart services. Depending on your case, you might not need the solution described here, but you may have to resort to it if you are using Google Charts directly via their public APIs.
The main trick to make image serialization work is to first convert the SVG chart into a canvas element, extract its contents as base64 encoded image using canvas.toDataURL, then use this payload as the src attribute for an img tag generated on the fly.
Caveats
There are various downsides that make this technique suboptimal (but hey, better than having nothing, right?):
- It works only for SVG charts. You won’t be able to save an AnnotatedTimeline, or other flash-based charts, to image.
- It requires the browser to support svg, canvas and data URLs. This cuts out Internet Explorer 8 and lower. For these cases, there are not that many alternatives, aside from installing Chrome frame or relying on ActiveX or Silverlight embeds to take screenshots of the relevant portion of the page (whose discussion is out of scope here).
- Some browsers impose limitations on data urls sizes (for example, Opera 11) which may limit the size/complexity of the chart you can serialize
- Serializing complex charts (for example, Geocharts) takes a noticeable amount of time (depending on the browser and speed of the machine you are using) and may freeze the browser for a few seconds. Most of the basic charts are serialized almost instantaneously though.
So this solution will work on Chrome, Safari, Firefox (tested on 3.6+), Internet Explorer 9+ and, partially, in Opera.
How it works
The serialization code works as follows. First, you extract the svg code for the chart you want to serialize (assuming chartContainer points to the html element containing the chart):
var chartArea = chartContainer.getElementsByTagName('iframe')[0].
contentDocument.getElementById('chartArea');
var svg = chartArea.innerHTML;
Then you create a canvas element and position it offscreen for the user not to see it:
var canvas = doc.createElement('canvas');
canvas.setAttribute('width', chartArea.offsetWidth);
canvas.setAttribute('height', chartArea.offsetHeight);
canvas.setAttribute(
'style',
'position: absolute; ' +
'top: ' + (-chartArea.offsetHeight * 2) + 'px;' +
'left: ' + (-chartArea.offsetWidth * 2) + 'px;');
doc.body.appendChild(canvas);
Using the canvg svg-to-canvas converter, you transform the svg instructions into a canvas rendering. You then extract the image data (as a base64 encoded string) and assign it to a local variable:
canvg(canvas, svg);
var imgData = canvas.toDataURL("image/png");
You can then use the image data to populate an img tag elsewhere in the page, or redirect the user to download the generated image:
// Populate img tag
var img = document.createElement('img');
img.src = imgData;
document.getElementById('imageContainer').appendChild(img);
// Download image
window.location = imgData.replace("image/png", "image/octet-stream");
Note how we force a different mime-type on the image data for the download case, to force the browser to trigger a download rather than displaying the image in the browser window.
Forcing the image download does not work in all browsers and it doesn’t behave that well: the downloaded image doesn’t have an assigned filename and every browser will use its own defaults in assigning it, often resulting in a confusing name for the user. For example, Chrome saves the image with the filename of download, with no extension, making it difficult for the user to understand that the file is indeed a PNG image.
It’s probably better to present the image in a separate window or page location, and ask the user to save it from there.
Example code
You can try the code discussed above at this sample page, and access the example source code at this GitHub gist.
Additional resources
- canvg library: http://code.google.com/p/canvg/
- more ways to save canvas to images: http://www.nihilogic.dk/labs/canvas2image/
Have fun.

49 Comments to this page
Dear Riccardo:
I love your solution. Thanks for the well written article and examples. Is there any *easy* way to change the the filename from "file.part" to "file.png"?
Thanks for the example, it was exactly what I needed!
To Fred: all I did was take the base64 png data, throw it into a hidden form, submit the form to my backend script (so I could post a massive string), then output that decoded png data with the "Content-Disposition: attachment;" header. No page reload necessary and you still get the "download" prompt with a clean labelled png file.
For PHP backend:
echo base64_decode(substr($_POST['data'], 22)); // data from canvas.toDataURL("image/png");
header('Content-Type: image/png');
header('Content-Disposition: attachment;filename="chart.png"');
header('Cache-Control: max-age=0');
exit;
There is a bug:
If on the chart there are numbers on axis and the is a space separate tausands, it will throw an an error (and in firefox the converson stop). So the correct getImageData function is:
function getImgData(chartContainer) {
var chartArea = chartContainer.getElementsByTagName('iframe')[0].
contentDocument.getElementById('chartArea');
//************ADD this line:**************
var svg = chartArea.innerHTML.replace(/ /g," ");
var doc = chartContainer.ownerDocument;
var canvas = doc.createElement('canvas');
canvas.setAttribute('width', chartArea.offsetWidth);
canvas.setAttribute('height', chartArea.offsetHeight);
canvas.setAttribute(
'style',
'position: absolute; ' +
'top: ' + (-chartArea.offsetHeight * 2) + 'px;' +
'left: ' + (-chartArea.offsetWidth * 2) + 'px;');
doc.body.appendChild(canvas);
canvg(canvas, svg);
var imgData = canvas.toDataURL("image/png");
canvas.parentNode.removeChild(canvas);
return imgData;
}
I just came here to say "battle horse, hoooooooooooo!"
This doesn't work on Google Chrome!
This does not work on IE too.
This solution is perfect to tie in with the Google Charts. I really appreciate you taking the time to post this info and the tutorial. Thanks!
This solution works perfectly on my project right now, since i'm doing a print functionality for google charts together with ABC PDF.
The issue here is that this doesn't work in IE8 but works in IE9, any possible workaround on this? The issue seems to be at HTML5's canvas.
I've tried reading about its counterpart for previous version which is excanvas, But haven't had any luck yet on how it is to be implemented.
Anyone?
gymson, this solution is built around the idea of svg -> canvas, so no it's impossible to work in IE8. In my application I just prompt users (when the canvas fails to create) to upgrade to IE9 or install a modern browser like Firefox or Chrome.
Hey hey!
I used the Github source code and prettty nice move. It works smoothly on Chrome, Firefox and Opera. However with our dear IE (even IE9) it's not possible to directly save the file.
Thx for your article, cheers.
Hi,
I tried it in Visualforce Page(Salesforce.com) and it is not working for Chrome, IE. It works in for Firefox. Any Idea on how do I solve it?
Hi everyone... just thanks to Ricardo, is a pretty good solution...for me is working in Fx, Cr,IE9..
this is my solution back end for java, with struts2 action...
Just for to send it, first i take off the "data:image/png;base64" from the string, and handle it like a base64 string..
<code>
public String uploadImageBase64() throws IOException {
BASE64Decoder decoder = new BASE64Decoder();
byte[] decodedBytes = decoder.decodeBuffer(imageBase64);
System.out.println("Decoded upload data : " + decodedBytes.length);
String uploadFile = "diagrama.jpg";
File file = new File(uploadFile);
if (file.exists()) {
file.delete();
}
System.out.println("outF.canWrite() = " + file.canWrite());
System.out.println("File save path : " + uploadFile);
FileOutputStream fOut = new FileOutputStream(getUserUploadDir()+"/"+file);
fOut.write(decodedBytes);
fOut.close();
jsonResponse.setSuccess(true);
return "json";
}
</code>
thanks for your tips :) ...
HI Riccardo
This is a great solution....but i wanna ask you i have two chart on same page
so i am not abto download both the chart together on single click...but i can do that with individual buttons...
so can give me any solution for this...
thanks for your help..
Thanks for this - it seems to work well with line charts, and it's much easier to tell users "Please use Google Chrome" than it is to teach them how to take screenshots.
I was even able to make MVC.NET echo back the image as a downloadable file. (Yes, it's very silly to have the client render the image, post it back to the server, and then serve it as a downloadable file, but it works.)
In case anyone else needs to do this, instead of adding an <img> element to the page, submit the imgData object and a filename to a controller. The controller itself is actually very simple:
public ActionResult RedownloadImage(string data, string filename)
{
return File(Convert.FromBase64String(data.Substring(data.IndexOf(",")+1)), "image/png", filename);
}
The drawback, of course, is the time spent uploading and then downloading the image. But it works, and it works far more simply than trying to get the same report to display with the same features in SSRS.
Google charts was updated today and it is now rendered without iframes. Any ideas on how to adapt the code?
Fix that google charts changes (no iFrame anymore)
var svg = $(chartContainer).find('svg').parent().html();
var doc = chartContainer.ownerDocument;
var canvas = doc.createElement('canvas');
canvas.setAttribute('style', 'position: absolute; ' + '');
doc.body.appendChild(canvas);
canvg(canvas, svg);
var imgData = canvas.toDataURL("image/png");
canvas.parentNode.removeChild(canvas);
return imgData;
hello
I am French, sorry for my english
Congratulations and thank you for your contribution
http://www.battlehorse.net/attach/topics/charts/google_charts_to_image.html
I used it and it worked very well for converting graphic images.
But now it works more on my side but also on your link.
With firebug we found a problem:
erreur function getImgData :
TypeError: chartContainer.getElementsByTagName("iframe")[0] is undefined
var chartArea = chartContainer.getElementsByTagName('iframe')[0].
Do you have a hypothesis? I am looking for my side if you have an
idea, do not hesitate.
thank you,
cordially
Thanks Thomas, works fine now!
Thanks Thomas, works fine now!
Change the code but did not work ...
appears "chartContainer.find" is undefined.
any ideas?
Thanks.
Hi ALL,
I am getting this exception.
$ is not defined
Can anyone let me know where I am going wrong?
Thanks.
@vivek $ is alias for jQuery , Did you remember to include jQuery on your HTML ??
Hi there,
I got the following error:
Uncaught TypeError: Cannot read property 'contentDocument' of undefined
(line 7)
This is in all browsers. Anyone an idea about this?
Thanks
Dirk
Thanks Syzer, works fine now!
Thanks Syzer,
I was referencing different jquery file. Now its working.
Thanks a Lot!
Since the release of version 1.32 of Google Visualization API (24 sept 2012) they don't use the iframe anymore and the function getImgData(chartContainer) doesn't work anymore (https://gist.github.com/1333906).
To make the function work again I replaced the first 2 lines of the function with:
---
var chartArea = chartContainer.children[0];
var svg = chartArea.innerHTML.substring(chartArea.innerHTML.indexOf("<svg"), chartArea.innerHTML.indexOf("</svg>") + 6);
---
And it works again. :)
Since the release of version 1.32 of Google Visualization API (24 sept 2012) they don't use the iframe anymore and the function getImgData(chartContainer) doesn't work anymore (https://gist.github.com/1333906).
To make the function work again I replaced the first 2 lines of the function with:
---
var chartArea = chartContainer.children[0];
var svg = chartArea.innerHTML.substring(chartArea.innerHTML.indexOf("<svg"), chartArea.innerHTML.indexOf("</svg>") + 6);
---
And it works again. :)
A quick Question :
When I am using Firefox to save the Image. It's giving me the file with extension as .part. Is there any way to save it as .png or any other?
Thanks!
Vivek
Hi Riccado,
This script just stopped working today. I think Google changed their code to generate the chart?
Javascript is showing error at the following line.
var chartArea = chartContainer.getElementsByTagName('iframe')[0].contentDocument.getElementById('chartArea');
Hey Riccado,
Sytse's solution fixed this issue. You both are awesome.
Sytse, thanks so much! Saved my behind with your solution.
good on you mate, great solution both the original and the one for dealing with a lack of IE.
Demo is not working.
TypeError: chartContainer.getElementsByTagName("iframe")[0] is undefined
what is the solution for this error???
Since the last API update this is not working anymore as it does not use iFrames.
What do you guys think of saving the image into a MySQL database. I've seen this done before in the past, people saving images into databases. This would allow for quick and easy recall if you are planning to include the images into a PDF through a PDF library like TCPDF.
Great bit of code!
To fix for the new charts implementation, just do this:
var chartArea = chartContainer.getElementsByTagName('div')[1];
on line 7
var chartArea = chartContainer.getElementsByTagName('div')[1];
fix line 7 but not work IE 8, IE 9
AAAAAAAAAAAAAAAAAAA......
Very interesting!
I´m currently searching for a way users can possibly share google charts from my wordpress site.
Another approach *COULD* be to let users make a snapshot and then move ahead with that image.. don`t know whats easier to accomplish.
In case of wordpress.. i guess one could find a snapshot plugin (or modify one) for having the chart area preselected (means can only make pic of that area.. not whole webpage)and maybe integrate a share button or whatever the intention is.
Fantastic tool. Though I got it working in Chrome and Firefox, IE 9 just isn't cooperating. Any ideas on why IE 9 isn't supported?
Hi to all!
Recently i discovered an alternative solution for save google charts as image ,due to a project that i developed for my job.
I tested without problem a javascript library that it's free of charge and help me a lot to save the google charts as image.It's supports all the browsers except IE <9, but also there is an another version that supports all the browsers including IE<9.
If you interested in look at http://www.chartstoimage.eu.
Hi
i implement this code but i got the JavaScript error like
Type error s is null on js file line no
if (typeof(s.documentElement) != 'undefined') {
any one help me to fix this error.
thanks
Hi Jeny,
If you prefer you can use the free grChartImg javascript library that it's an easy and an reliable way to do a lot of things supported by all browsers.
Just check the documentation at www.chartstoimage.eu
I used
var chartArea = chartContainer.getElementsByTagName('svg')[0].parentNode;
to fix the no-iframes issue. But, I have issues rendering line and area graphs, the vAxis covers the left hand side of the rendered data. Can't really use this in a product till I can figure out how to avoid this.
nverba, I'm running into the exact same issue. My charts render just fine in the browser, but when I convert them to .png's (I'm using the same trick you are to handle lack of iframes) the y- and x-axis labels are shifted horizontally. So the y-axis labels are on the graph itself and the x-axis labels are off-center. I haven't found a fix yet, and in all my searching you're the only person I've seen having the same issue.
It seems there was a bug after some refactoring of the text. This has been reported in the issues tracker https://code.google.com/p/canvg/issues/detail?id=202&q=text-anchor
For now it seems the advice is to use the older revision https://code.google.com/p/canvg/source/detail?r=152
I'm happy to say it's all working fine for me now. :)
MANY MANY thanks nverba! I had a working solution on saving these charts and it just got messed up, the same as you wrote, from one day to another. I was checking for the error for hours now, luckily I found your comment. Many thanks, working perfectly with including:
https://canvg.googlecode.com/svn-history/r152/trunk/canvg.js
You can find a complete working example for this tutorial, updated to include the fixes from the comments above in this answer on StackOverflow: http://stackoverflow.com/a/16137983/571733
1) Set the chartArea variable to work with Google Visualization API version 1.32 (Sept 24, 2012) and later
2) Use canvg.js r157 as a workaround for a regression identified by @nverba
This is a fantastic tool utility. It works in Firefox and Chrome, but not IE (any versions). Here's the error:
SCRIPT438: Object doesn't support property or method 'getContext'
This is a fantastic tool utility. It works in Firefox and Chrome, but not IE (any versions). Here's the error:
SCRIPT438: Object doesn't support property or method 'getContext'