Introduction
There are sites that descibe how to save the charts produced by Google APIs as images. Most of those written before 2019 no longer work. This is because they used the older Image Charts API which was depreciated in 2012 and finally stopped working on March 18, 2019 when it joined the list of discontinued Google projects.
This page was written to explore some of the ways to save a chart as an image and uses the first example from the Pie Chart documentation as a start.
Print Screen (PrtScr)
Saving the broswer screen is the easiest way for anyone to get a copy of a chart. The resulting image can then be manipulated in any image editor.
The Chart
The normal chart that is produced:
A normal Google Chart API chart
The image below was produced using the code from Printing PNG Charts documentation. Of course, the image loses all interactivity that the original chart is capable of.
In the documentation, the png image is produced in the original div that holds the chart as soon as it has finished being processed, here it is shown in a different div. simply by specifying the ID of the div it is to be displayed in. The code used is from the documentation and is:
// Wait for the chart to finish drawing before calling the getImageURI() method.
google.visualization.events.addListener(chart, 'ready', function () {
chart_div.innerHTML = '<img src="' + chart.getImageURI() + '">';
});
An image of a Google Chart API chart
Linking to the Chart Image
Now there is a bit of a problem in the example code given in the Printing PNG Charts documentation. The documentation also gives an example of the format of the data returned by getImageURI() which is:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAADICAYAAADGFbfiA...
The documentation says that to produce a link to the image of the chart to use a div along with:
document.getElementById('png').outerHTML = '<a href="' + chart.getImageURI() + '">Printable version</a>';
This gives:
The link above does work not as expected. Simply clicking on the link does nothing in Chrome or Edge (both version 108), and Opera (version 93). It does work in Firefox (version 108). Right clicking on the link brings up a menu in all the browsers and all the choices in that work as expected and the images display in a new tab.
Typical browser menu when right-clciking on a link. This is Edge version 108.
As the link opens in a new tab, I thought I would try doing that in the link by using target="_blank" in it:
The link above works, or rather does not work, in exactly in the same way as the previous link.
The Problem: Why are the Links not Working?
In developer mode in the browsers, I clicked on the above links. In all of them aside from Firefox where the links behave as expected a message appears saying:
Not allowed to navigate top frame to data
I looked up this message and found the article Deprecations and removals in Chrome 60 written by Joe Medley in June 2017, in Chrome Developers. Of this behaviour the article says:
Remove content-initiated top frame navigations to data URLs
Because of their unfamiliarity to non-technical browser users, we're increasingly seeing the data: scheme being used in spoofing and phishing attacks. To prevent this, we're blocking web pages from loading data: URLs in the top frame. This applies to <a> tags, window.open, window.location and similar mechanisms. The data: scheme will still work for resources loaded by a page.
This feature was deprecated in Chrome 58 and is now removed.
Chrome-type browsers use Blink as the rendering engine and an earlier set of postings in the Blink Development group in February 2017, explains the situation a little more:
In practice, these will be blocked:
- Navigations when the user clicks on links in the form of <A HREF="data:…">
- window.open("data:…")
- window.location = "data:…"
- Meta redirects
The following will still be allowed:
- User navigating to the URL by typing or pasting it in the omnibox
- Downloads from these protocols:
- Via non-browser-handled MIME types
- Via <A download>
- Via “Save link as”
I thought it was interesting that both the a href and window.location type links were blocked from using data URIs. If both are blocked, how do browsers manage to open the data in a new tab from the right-click menu? Just as interesting is that not blocked in the browser were <a download...> links.
This change by the browsers affected more than the Google Charts API. Several other APIs and libraries, such as the ones for dynamically producing PDFs, which depend on opening data: URIs were affected.
Some Solutions
Using the above information I added a new <div> tag to the HTML and in the chart code added:
document.getElementById('downimg').innerHTML = '<a download="google-chart-image" href="' + chart.getImageURI() + '">Download Chart Image</a>';
Which gives:
The above link works properly in the browsers I tested it on. Right clicking on the link allows full use of the menu, including opening the image in a new tab rather than having it download.
I still like the idea of giving the user a choice of opening the image in a new tab and letting them decide whether to download it or not using the right-click menu. Most people seem to know they can do this with images and other files such as PDFs.
Although most browsers do not allow you to open the data: URL as as a URL directly, what can be done is create a new iframe or page and then write the data: URL to that.
For the first method I added the following to the Google Chart API code:
document.getElementById('iframeimg-1').innerHTML = '<button id="' + chart.getImageURI() + '" onClick="frame1(this.id);">Chart Image IFrame Link 1</button>';
And the JavaScript function for the onClick event is:
function frame1(URI){
var newtab = window.open();
newtab.document.write('<iframe src="' + URI + '" width="100%"" height="99%"" border="2px"></iframe>');
}
This gives:
The second method is very similar to the first, but I do not know which is more correct. I added the following to the Google Chart API code:
document.getElementById('iframeimg-2').innerHTML = '<button id="' + chart.getImageURI() + '" onClick="frame2(this.id);">Chart Image IFrame Link 2</button>';
And the JavaScript function for the onClick event is:
function frame2(URI){
var iframe = "<iframe width='100%' height='99%' border='2px' src='" + URI + "'></iframe>"
var newtab = window.open();
newtab.document.open();
newtab.document.write(iframe);
newtab.document.close();
}
This gives:
A third method involves opening a new tab and writing the chart.getImageURI() directly into it. I added the following to the Google Chart API code:
document.getElementById('imgwrite').innerHTML = '<button id="' + chart.getImageURI() + '" onClick="imgwrite(this.id);">Image Write Link 1</button>';
And the JavaScript function for the onClick event is:
function imgwrite(URI){
var win = window.open('about:blank');
var img = new Image();
img.src = URI;
setTimeout(function(){
win.document.write(img.outerHTML);
}, 0);
}
This gives:
The Code
The code for the above chart and links is:
<script type="text/javascript">
google.charts.load('current', {'packages':['corechart']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Task', 'Hours per Day'],
['Work', 11],
['Eat', 2],
['Commute', 2],
['Watch TV', 2],
['Sleep', 7]
]);
var options = {
title: 'Daily Activities',
height: '300'
};
var chart = new google.visualization.PieChart(document.getElementById('chartdiv'));
google.visualization.events.addListener(chart, 'ready', function () {
// Put the image of the chart in a div with the ID of chartimg
chartimg.innerHTML = '<img src="' + chart.getImageURI() + '">';
document.getElementById('png').innerHTML = '<a href="' + chart.getImageURI() + '">Printable version</a>';
document.getElementById('png-new-tab').innerHTML = '<a href="' + chart.getImageURI() + '" target="_blank"' + '>Printable version in New Tab</a>';
document.getElementById('down-img').innerHTML = '<a download="google-chart-image" href="' + chart.getImageURI() + '">Download Chart Image</a>';
document.getElementById('iframeimg-1').innerHTML = '<button id="' + chart.getImageURI() + '" onClick="frame1(this.id);">Chart Image IFrame Link 1</button>';
document.getElementById('iframeimg-2').innerHTML = '<button id="' + chart.getImageURI() + '" onClick="frame2(this.id);">Chart Image IFrame Link 2</button>';
document.getElementById('imgwrite').innerHTML = '<button id="' + chart.getImageURI() + '" onClick="imgwrite(this.id);">Image Write Link 1</button>';
});
chart.draw(data, options);
}
function frame1(URI){
var newtab = window.open();
newtab.document.write('<iframe src="' + URI + '" width="100%"" height="99%"" border="2px"></iframe>');
}
function frame2(URI){
var iframe = "<iframe width='100%' height='99%' border='2px' src='" + URI + "'></iframe>"
var newtab = window.open();
newtab.document.open();
newtab.document.write(iframe);
newtab.document.close();
}
function imgwrite(URI){
var win = window.open('about:blank');
var img = new Image();
img.src = URI;
setTimeout(function(){
win.document.write(img.outerHTML);
}, 0);
}
</script>
Sources and Resources
How to open the newly created image in a new tab?
HTML5 Canvas to Data URL Tutorial
JavaScript : Not allowed to navigate top frame to data URL (Chrome)
JsPDF - Not allowed to navigate top frame to data URL
Not allowed to navigate top frame to data URL: data:image/png
What does the "Not allowed to navigate top frame to data URL:" JavaScript exception means in Google Chrome
Save chat transcript does not work in Chrome and iOS 13 and below
This page created December 21, 2022; last modified March 30, 2023