Search code examples
javascripthtmlchartsgoogle-visualizationgoogle-developer-tools

Google Charts not Working When Data Table is Global


I'm working on a small HTML application for my website that does some simulations and plots it to a graph (using Google Charts). All of the data will originate in the JavaScript code on the page (i.e. I'm not trying to pull in data from a database or anything like that). For this reason, I would like to have access to the data table from other functions so the data can be updated when a new simulation is run.

What I'm running into is that if I build a data table (and data view) inside of the drawChart() function, everything works fine. See this jsfiddle or the following code:

//Google charts stuff
google.charts.load('current', { 'packages': ['line', 'corechart'] });
google.charts.setOnLoadCallback(drawChart);

function drawChart() {
            var forceChartDiv = document.getElementById('force_chart_div');

            var sim_data = new google.visualization.DataTable();
            sim_data.addColumn('number', 'Elapsed Time (sec)');
            sim_data.addColumn('number', "Total Force");
            sim_data.addColumn('number', "M1 Force(Each)");

            sim_data.addRows([
                [0.0, -.5, 5.7],
                [0.1, .4, 8.7],
                [0.2, .5, 12]
            ]);

            var forceDataView = new google.visualization.DataView(sim_data);
            forceDataView.setColumns([0, 1, 2]);

            var forceChartOptions = {
                chart: {title: 'Simulation Results: Force'},
                width: 900,
                height: 500,
                series: {
                    // Gives each series an axis name that matches the Y-axis below.
                    0: { axis: 'Total' },
                    1: { axis: 'Individual' }
                },
                axes: {
                    // Adds labels to each axis; they don't have to match the axis names.
                    y: {
                        Total: { label: 'Total Force (Newtons)'},
                        Individual: { label: 'Per-Motor Force (Newtons)'}
                    }
                }
            };

            var forceChart = new google.charts.Line(forceChartDiv);   
            forceChart.draw(forceDataView, google.charts.Line.convertOptions(forceChartOptions));
        }

But if I move the code for the creation of the data table and data view outside of the function scope, it doesn't work. See this jsfiddle or the following code:

var sim_data;
var forceDataView;

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

sim_data = new google.visualization.DataTable();
sim_data.addColumn('number', 'Elapsed Time (sec)');
sim_data.addColumn('number', "Total Force");
sim_data.addColumn('number', "M1 Force(Each)");

sim_data.addRows([
                [0.0, -0.5, 5.7],
                [0.1, 0.4, 8.7],
                [0.2, 0.5, 12]
                    ]);

forceDataView = new google.visualization.DataView(sim_data);
forceDataView.setColumns([0, 1, 2]);

google.charts.setOnLoadCallback(drawChart);

function drawChart() {
            var forceChartDiv = document.getElementById('force_chart_div');

            var forceChartOptions = {
                chart: {title: 'Simulation Results: Force'},
                width: 900,
                height: 500,
                series: {
                    // Gives each series an axis name that matches the Y-axis below.
                    0: { axis: 'Total' },
                    1: { axis: 'Individual' }
                },
                axes: {
                    // Adds labels to each axis; they don't have to match the axis names.
                    y: {
                        Total: { label: 'Total Force (Newtons)'},
                        Individual: { label: 'Per-Motor Force (Newtons)'}
                    }
                }
            };

            var forceChart = new google.charts.Line(forceChartDiv);   
            forceChart.draw(forceDataView, google.charts.Line.convertOptions(forceChartOptions));
        }

Both of these examples use the following HTML:

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

I thought it might have something to do with the execution order of the callback function. But putting it in different spots in the code doesn't seem to change anything. In my full project, I went so far as to add a button that called the drawChart() function just to check, but that didn't help either.

Depending on where I put the callback function call, I'll get a red "Data Table is not Defined" alert showing up where the chart is supposed to be on the webpage. That pretty much tells me what I already suspected, but I don't know how to fix it. Any help would be appreciated. I'm a huge JS noob, by the way, so go easy on me.


Solution

  • your instinct was correct, you must wait on the callback to finish,
    before using the google.visualization or google.charts namespaces.

    it has to do more with timing, than placement of the code.

    instead of using the callback statement, we can use the promise that the load statement returns.

    as in the following snippet...

    var sim_data;
    var forceDataView;
    
    //Google charts stuff
    google.charts.load('current', {
        packages: ['line', 'corechart']
    }).then(function () {
        sim_data = new google.visualization.DataTable();
        sim_data.addColumn('number', 'Elapsed Time (sec)');
        sim_data.addColumn('number', "Total Force");
        sim_data.addColumn('number', "M1 Force(Each)");
    
        sim_data.addRows([
                        [0.0, -0.5, 5.7],
                        [0.1, 0.4, 8.7],
                        [0.2, 0.5, 12]
                            ]);
    
        forceDataView = new google.visualization.DataView(sim_data);
        forceDataView.setColumns([0, 1, 2]);
    });
    
    function drawChart() {
        var forceChartDiv = document.getElementById('force_chart_div');
    
        var forceChartOptions = {
            chart: {title: 'Simulation Results: Force'},
            width: 900,
            height: 500,
            series: {
                // Gives each series an axis name that matches the Y-axis below.
                0: { axis: 'Total' },
                1: { axis: 'Individual' }
            },
            axes: {
                // Adds labels to each axis; they don't have to match the axis names.
                y: {
                    Total: { label: 'Total Force (Newtons)'},
                    Individual: { label: 'Per-Motor Force (Newtons)'}
                }
            }
        };
    
        var forceChart = new google.charts.Line(forceChartDiv);   
        forceChart.draw(forceDataView, google.charts.Line.convertOptions(forceChartOptions));
    }