3

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
});
WhiteHat
  • 59,912
  • 7
  • 51
  • 133
stevendesu
  • 15,753
  • 22
  • 105
  • 182

1 Answers1

3

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>
WhiteHat
  • 59,912
  • 7
  • 51
  • 133
  • Unfortunately this solves one problem but creates another, but I guess I'll find a new solution to the other problem :) The reason I have the other months in the first place is because elsewhere on the dashboard I show a line graph of the historic values. Upon filtering for a given name (which only shows a single row in the table for the "current" income) I want the line graph to **also** be filtered to show the historic values for that name. So effectively the table needs to show a subview of the view created by the filter. – stevendesu Sep 14 '17 at 14:10
  • use a dashboard for everything but the table, then use second option above to draw table... – WhiteHat Sep 14 '17 at 14:12