Google Charts API

Multiple Charts on a Page

Introduction

There should not be a problem putting multiple charts on a page, either displaying the same data in different charts or loading a new set of data in a new datatable. The Google Charts API documentation for multiple charts is fairly clear but there are a couple of gotchas.

Chart Loader

The chart loader must only be called once on a page. Calling it multiple times will result in problems.

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

Chart Libraries

About half of the charts can be loaded a single library named "corechart"

google.charts.load('current', {'packages':['corechart']});

Charts loaded using corechart are: Area, Bar, Bubble, Candlestick, Column, Combo, Diff, Donut, Histogram, Intervals, Line, Pie, Scatter, Stepped Area, Trendline, and Waterfall

The exceptions are (package to be loaded in brackets): Annotation (annotationchart), Calendar (calendar), Gantt (gantt), Gauge (gauge) , Geo (geochart), Map (map), Org (orgchart), Sankey (sankey), Table (table), Timeline (timeline), Tree Map (treemap), Vega (vegachart), and Word Tree (wordtree)

Where multiple charts need to be loaded a single statement can be used, for example:

google.charts.load('current', {packages: ['corechart', 'table', 'sankey']};

If the chart to be drawn is not one of those included in the the corechart set then it does not need to be included, for example:

google.charts.load('current', {packages: ['timeline']};


Let the User Choose!

There are many ways to visualize data amd the designer must ensure the proper chart type is chosen to display the data. Sometimes it is better to give the user a choice which visualization to use to better understand the data.

The Google Charts API allows the redrawing of the data without importing it again. The following example uses a simple dropdown to allow the user to change the chart type.

Notes

The data comes from a Google Sheet. This is the documentation for the API Column Charts.

This example is not the same as the Controls and Dashboards in the API. Those are designed to filter data, this example changes the chart type drawn.

This chart is based on a column chart and the changes in the sub-type are maded by changing the chart options.

This example uses the onChange event in the dropdown to trigger the redrawing of the chart. Some people may prefer to use an event listener. For more complex projects a single event listener can be used to listen for events anywhere within a element with an ID and distinguish which element inside it triggered the listener. Handling Events for Many Elements by Kirupa explains how to do this.

The code changes between three values for the column chart, false, true and 'percent'. If you just need to toggle between false and true then this simple code works for boolean values:

variable = !variable;

I use namespaces in my JavaScript as per the W3C JavaScript Best Practices. This stops my code interfering with any libraries I may use and vice versa. Because redrawChart needs to accessed by the dropdown, I needed to make a global variable inside the namespace to hold the data table. I also had to return the redrawChart function at the end of the namespace to make that available to the dropdown.

The chart shows the sex of students in a small college for various semesters. By changing the chart type the various relationships and trends of the data can be seen more easily.

The HTML for the dropdown is:


	<select id="chooseChart" onchange="studentSex.redrawChart()">
  	<option value="Column">Column</option>
  	<option value="Stacked">Stacked</option>
  	<option value="Percent">Percent</option>
	</select>
	

The JavaScript for the above chart is:


    <script type="text/javascript">
	var studentSex = (function() {
	// create namespace global variable to hold the data
	var studentData;
    google.charts.load('current', {'packages':['corechart']});
    google.charts.setOnLoadCallback(drawChart);
	
    function drawChart() {
		var queryString = encodeURIComponent('SELECT B,C,D ORDER BY A');
		var query = new google.visualization.Query('https://docs.google.com/spreadsheets/d/1tswaWUAbeBijHq4o505w0h7TmbD-qGhR3jBactcbGq0/gviz/tq?gid=1786091632&headers=1&tq=' + queryString);
      query.send(handleQueryResponse);
    }

    function handleQueryResponse(response) {
      if (response.isError()) {
        alert('Error in query: ' + response.getMessage() + ' ' + response.getDetailedMessage());
        return;
      }
		
    studentData = response.getDataTable();
		
	redrawChart();
	} 
		
	function redrawChart(){
	var chartType = document.getElementById("chooseChart").value;
	var optionVar = false;
	if (chartType == "Stacked") {optionVar = true}
	if (chartType == "Percent") {optionVar = 'percent'}
	var options = {isStacked: optionVar};
	var chart = new google.visualization.ColumnChart(document.getElementById('studentData_div'));
    chart.draw(studentData, options);
	} 
	
	// Make the redrawChart function available outside the studentSex namespace by returning it.
	// This is so the onChange event of the dropdown can access it 
	return{redrawChart};
	})();
	</script>
    

In the following example the same data is used and it is the chart type that is changed.

Notes

Notice that because the code for this example is in a different JavaSCript namepsace than the previous, the same variable and function names can be used and that they do not interfer with each other. This is the whole point of using namespaces. The scope of the functions and variables is confined to the namespace.

There is no easy way for variables to be used in the chart definitions and so the entire line is defined.

If the chartWrapper Class is used then the setChartType or setOptions methods can be used in the options or drawChart constructor to change the chart type and those can accept variables.

There is no reason changing both the chart type and the options for them cannot be used together. Care must be taken that the defaults are defined or odd behaviours may show or the chart may not draw at all.

Both Column and Bar charts use the corechart library so there is no need to load another. Some charts will require the loading of other chart libraries.

The HTML for the dropdown is:


	<select id="chartType" onchange="changeChart.redrawChart()">
  	<option value="Column">Column</option>
  	<option value="Bar">Bar</option>
	</select>
	

The JavaScript for the above chart is:


    <script type="text/javascript">
	// create namespace global variable (changeChart) to hold the data
	var changeChart = (function() {
	var studentData;
    google.charts.load('current', {'packages':['corechart']});
    google.charts.setOnLoadCallback(drawChart);
	
    function drawChart() {
		var queryString = encodeURIComponent('SELECT B,C,D ORDER BY A');
		var query = new google.visualization.Query('https://docs.google.com/spreadsheets/d/1tswaWUAbeBijHq4o505w0h7TmbD-qGhR3jBactcbGq0/gviz/tq?gid=1786091632&headers=1&tq=' + queryString);
      query.send(handleQueryResponse);
    }

    function handleQueryResponse(response) {
      if (response.isError()) {
        alert('Error in query: ' + response.getMessage() + ' ' + response.getDetailedMessage());
        return;
      }	
    studentData = response.getDataTable();
	redrawChart();
	} 
		
	function redrawChart(){
	var chartType = document.getElementById("chartType").value;
	if (chartType == "Column") {chart = new google.visualization.ColumnChart(document.getElementById('changeChart_div'));}
	if (chartType == "Bar") {chart = new google.visualization.BarChart(document.getElementById('changeChart_div'));}
	
    chart.draw(studentData);
	}
	
	// Make the redrawChart function available outside the changeChart namespace by returning it.
	// This is so the onChange event of the dropdown can access it 
	return{redrawChart};
	})();
	</script>
    

 


Multiple Data Tables, Multiple Charts

Executing the code and drawing charts from multiple tables has long been a source of frustration and the API's Help Forum and Stack Overflow has multiple questions about this. Such as Can't find the problem with my multiple charts on same page, Multiple Google Charts, More than two Google Charts on a single page?, Google Charts stops drawing after first chart and How to add two Google charts on the one page?

Those issues were largely fixed in the API and it is possible to create the data tables then draw each one as in the API Draw Multiple Charts documentation. But what happens if you have a lot of charts and prefer to use arrays and loops to draw them?

Apart from errors in the code such as trying to use loader.js twice, the problem is the way that JavaScript behaves. JavaScript is mostly synchronous, that is, it processes one line of code at a time - except for the times it is asynchronous and does multiple things at once!

An example of an asynchronous function is setTimeout. What happens is that the code up to the timer is executed, the timer starts, any remaining code is then executed until the timer reaches its elapsed time then that code executes. Sometimes what needs to happen is that the code execution needs to be paused completely until an event occurs, the screen is redrawn, a file opened and read or some other condition met.

The latter is what Callbacks, Promises, and Async/Await are all about. One of the eaiest tutorials I've found to understand all of this is Callbacks, Promises, and Async by Chris Nwamba on scotch.

drawChart()

The drawChart constructor is barely anything but variables, so that provides a good way to import multiple data tables and draw multiple charts. There is a slight price to pay, as the documentation says "you cannot assign method listeners to catch chart events." If that turns out to be a problem then there's always the chartWrapper Class.

drawChart is very easy to use but consider the following. There are three divs to hold three different charts from three different sets of data. It should first draw a column chart, a bar chart and then a table but you are probably just seeing the table chart.

This is sometimes a problem when drawing multiple charts on a page. What I think is happening is that the FOR loop is being processed so fast that the first and second charts start to be processed but before they can be drawn the third chart code gets processed and takes over the screen drawing.

The JavaScript for the above chart(s) is:


    <script type="text/javascript">
	var multiChart = (function() {
    google.charts.load(['current','table']);
    var myString = []; 
	myString[0] = '{"containerId": "column_div",' + '"dataSourceUrl": "https://docs.google.com/spreadsheets/d/1tswaWUAbeBijHq4o505w0h7TmbD-qGhR3jBactcbGq0/gviz/tq?gid=1786091632&headers=1",' + '"query":"SELECT B,C,D ORDER BY A",' + '"chartType": "ColumnChart"' + '}';
    myString[1] = '{"containerId": "bar_div",' + '"dataSourceUrl": "https://docs.google.com/spreadsheets/d/1tswaWUAbeBijHq4o505w0h7TmbD-qGhR3jBactcbGq0/gviz/tq?gid=0&headers=1",' + '"query":"SELECT A,B ORDER BY A LIMIT 8",' + '"chartType": "BarChart"' + '}';
	myString[2] = '{"containerId": "table_div",' + '"dataSourceUrl": "https://docs.google.com/spreadsheets/d/1tswaWUAbeBijHq4o505w0h7TmbD-qGhR3jBactcbGq0/gviz/tq?gid=234750873&headers=1",' + '"query":"SELECT A,B ORDER BY A",' + '"chartType": "Table"' + '}';
	
	var strLength = myString.length;
	for (i=0; i < strLength; i++){
		var newString = myString[i];
  		function drawVisualization() {
  			google.visualization.drawChart(newString);
  		}
  		google.charts.setOnLoadCallback(drawVisualization);
  	}
	})();
	</script>
    

Solution: A Zero Length Timer

You should now be able to see the column, bar and table charts:


 

 

This is about the quickest and easiest way to draw simple multiple charts on a page and was done by including a setTimeout to queue the various graphs.

The JavaScript for the above chart(s) is:


    <script type="text/javascript">
	var timerChart = (function() {
    google.charts.load(['current','table']);
    var myString = []; 
	myString[0] = '{"containerId": "timerColumn_div",' + '"dataSourceUrl": "https://docs.google.com/spreadsheets/d/1tswaWUAbeBijHq4o505w0h7TmbD-qGhR3jBactcbGq0/gviz/tq?gid=1786091632&headers=1",' + '"query":"SELECT B,C,D ORDER BY A",' + '"chartType": "ColumnChart"' + '}';
    myString[1] = '{"containerId": "timerBar_div",' + '"dataSourceUrl": "https://docs.google.com/spreadsheets/d/1tswaWUAbeBijHq4o505w0h7TmbD-qGhR3jBactcbGq0/gviz/tq?gid=0&headers=1",' + '"query":"SELECT A,B ORDER BY A LIMIT 8",' + '"chartType": "BarChart"' + '}';
	myString[2] = '{"containerId": "timerTable_div",' + '"dataSourceUrl": "https://docs.google.com/spreadsheets/d/1tswaWUAbeBijHq4o505w0h7TmbD-qGhR3jBactcbGq0/gviz/tq?gid=234750873&headers=1",' + '"query":"SELECT A,B ORDER BY A",' + '"chartType": "Table"' + '}';
	
	var strLength = myString.length;
	for (i=0; i < strLength; i++){
		processChart(i);
	}
		
    function processChart(i) { 
  		setTimeout(function() { 
  			function drawVisualization() {
				var newString = myString[i]
  				google.visualization.drawChart(newString);
  			}
  			google.charts.setOnLoadCallback(drawVisualization); 
  		}, 	0); 
	}
		
	})();
	</script>
    

What's happening in the above code is that the chart drawing is added to a timed queue of code to be executed. There is no other synchronous code to be run and the 0 time does not mean "do it now", it means "do it as soon as there's nothing else to be done" which is why using setTimeout does not guarantee accurate timings, but it does mean the code has to finish running before it starts on the next task.

This page created January 12, 2021; last modified January 30, 2021