Search code examples
javascriptchartsgoogle-visualizationdashboard

Google Visualization Dashboard with filter and DataView


I have a DataTable that looks something like this:

+-------+---------+--------+
| month |  name   | income |
+-------+---------+--------+
| Jan   | Alice   | $5,000 |
| Feb   | Alice   | $3,000 |
| Mar   | Alice   | $4,500 |
| Jan   | Bob     | $2,750 |
| Feb   | Bob     | $8,000 |
| Mar   | Bob     | $1,000 |
| Jan   | Charlie | $3,500 |
| Feb   | Charlie | $4,100 |
| Mar   | Charlie | $3,900 |
  ...       ...       ...

I wish to display a Google Visualization with one ChartWrapper and one ControlWrapper.

The ChartWrapper will display a Table, but I only want this Table to show the most recent month. To do this I'm using .setView() on the ChartWrapper.

The ControlWrapper will wrap a CategoryFilter on the name column.

The issue I'm running into is that when I try to select a name, it throws an error: Invalid row index ... Should be in range [0-...]

I believe I'm getting this issue because setView accepts a static array of rows to display, but if the table is filtered then the rows to display will be different. I'm calling setView like so:

var recentRows = dataTable.getFilteredRows([
    {
        "column": 2,
        "minValue": dataTable.getColumnRange(2).max
    }
]);
chartWrapper.setView({
    "rows": recentRows
});

Solution

  • when using the Dashboard control,
    the ChartWrapper and ControlWrapper need to be in sync and use the same data set

    as you've found, setting the view property only on the chart throws this off

    to correct, use a DataView to draw the dashboard,
    as seen in the following working snippet...

    google.charts.load('current', {
      callback: drawChart,
      packages: ['controls']
    });
    
    function drawChart() {
      var data = google.visualization.arrayToDataTable([
        ['month', 'name', 'income'],
        [0, 'Alice', 5000],
        [1, 'Alice', 3000],
        [2, 'Alice', 4500],
        [0, 'Bob', 2750],
        [1, 'Bob', 8000],
        [2, 'Bob', 1000],
        [0, 'Charlie', 3500],
        [1, 'Charlie', 4100],
        [2, 'Charlie', 3900]
      ]);
    
      var control = new google.visualization.ControlWrapper({
        controlType: 'CategoryFilter',
        containerId: 'control',
        options: {
          filterColumnLabel: 'name',
          ui: {
            allowTyping: false,
            allowMultiple: true
          }
        }
      });
    
      var chart = new google.visualization.ChartWrapper({
        chartType: 'Table',
        containerId: 'chart'
      });
    
      var view = new google.visualization.DataView(data);
      view.setRows(data.getFilteredRows([{
        column: 0,
        minValue: data.getColumnRange(0).max
      }]));
    
      var dashboard = new google.visualization.Dashboard(
        document.getElementById('dashboard')
      );
      dashboard.bind(control, chart);
      dashboard.draw(view);
    }
    <script src="https://www.gstatic.com/charts/loader.js"></script>
    <div id="control"></div>
    <div id="chart"></div>


    another option would be not to use a dashboard
    draw each wrapper independently,
    then listen for the statechange event on the control,
    and draw the table accordingly

    see following working snippet...

    google.charts.load('current', {
      callback: drawChart,
      packages: ['controls']
    });
    
    function drawChart() {
      var data = google.visualization.arrayToDataTable([
        ['month', 'name', 'income'],
        [0, 'Alice', 5000],
        [1, 'Alice', 3000],
        [2, 'Alice', 4500],
        [0, 'Bob', 2750],
        [1, 'Bob', 8000],
        [2, 'Bob', 1000],
        [0, 'Charlie', 3500],
        [1, 'Charlie', 4100],
        [2, 'Charlie', 3900]
      ]);
    
      var control = new google.visualization.ControlWrapper({
        controlType: 'CategoryFilter',
        containerId: 'control',
        dataTable: data,
        options: {
          filterColumnLabel: 'name',
          ui: {
            allowTyping: false,
            allowMultiple: true
          }
        }
      });
      control.draw();
    
      var chart = new google.visualization.ChartWrapper({
        chartType: 'Table',
        containerId: 'chart',
        dataTable: data
      });
    
      google.visualization.events.addListener(control, 'ready', drawTable);
      google.visualization.events.addListener(control, 'statechange', drawTable);
      drawTable();
    
      function drawTable() {
        var filters = [{
          column: 0,
          minValue: data.getColumnRange(0).max
        }];
    
        var selectedNames = control.getState().selectedValues;
        if (selectedNames.length > 0) {
          filters.push({
            column: 1,
            test: function (value, row, column, table) {
              return (selectedNames.indexOf(table.getValue(row, column)) > -1);
            }
          });
        }
    
        chart.setView({
          rows: data.getFilteredRows(filters)
        });
        chart.draw();
      }
    }
    <script src="https://www.gstatic.com/charts/loader.js"></script>
    <div id="control"></div>
    <div id="chart"></div>