Search code examples
javascriptchartsgoogle-visualization

How to maintain consistent colors using a Google charts (visualisation) filter with a pie chart?


I would like a dashboard with a pie chart like this example but keep the colors consistent. E.g. in the example, the color of Margreth changes from pink to orange if you set the filter from None to Female.

The { role: 'style' } option is unfortunately not available for pie charts. Instead, a list of colors has to be provided in the options. When using the filter, the first available colors from the list are used. So unlike this (unfortenately) accepted answer, it does not help to provide a list of colors with a size equal to the total unfiltered dataset.

Instead, I think a function needs to be defined to dynamically create a list of colors that aligns with the filtered data, like:

function getColors () {
        // build colors array
        var colors = [];
        for (var i = 0; i < data.getNumberOfRows(); i++) {
            colors.push(data.getValue(i, 2)); // color is in the 3nd column of the datatable
        }
        return colors; }

'options':{
          colors: getColors()  },

FYI this jsfiddle.

However, if I do that, getColors returns the colors for the complete dataset.

=> how do I?:

  1. Only return the filtered rows and therefore only the list of colors to be visualised? I noticed a method "getFilteredRows" but that seems more like a way to build your own filter, I don't understand.
  2. Make sure that the list of colors is updated and applied on each change of the filter? The fiddle above does it on a button click. I do not know how to do it using the google charts filter. I tried addListener (see below) with values like filter, select or dashboard ready. But none of those work.

FYI my code for this dashboard:

HTML:

<div id = "dashboard_TK_stemmen">
<div id="filter_TK_stemmen"></div>
<p id = "pie_TK_stemmen"></p>
</div>

JS:

google.charts.setOnLoadCallback(Dashboard);
function Dashboard()  {
      var data = google.visualization.arrayToDataTable([ ['Partij', 'Aantal', { role: 'style' }, 'Datum'], ['PvdA', 20105, '#E30613', '20 maart 2019'], ['VVD', 9919, '#F47621', '20 maart 2019'], ['CDA', 6480, '#007B5F', '20 maart 2019'], ['PvdA', 13303, '#E30613', '18 maart 2015'], ['VVD', 8096, '#F47621', '18 maart 2015'], ['CDA', 5896, '#007B5F', '18 maart 2015'] ]);
      var dashboard = new google.visualization.Dashboard(document.getElementById('dashboard_TK_stemmen'));
      var filter = new google.visualization.ControlWrapper({
        'controlType': 'CategoryFilter',
        'containerId': 'filter_TK_stemmen',
        'options': {
        'filterColumnLabel': 'Datum',
        'ui': {'caption': 'Kies een verkiezingsuitslag', 'label': 'Uitslag van: ', 'allowNone': false, 'allowMultiple': false,
         'sortValues':false,'allowTyping':false} } });
     function getColors () {
        // build colors array
        var colors = [];
        for (var i = 0; i < data.getNumberOfRows(); i++) {
            colors.push(data.getValue(i, 2));
        }
        return colors; }
      var pie = new google.visualization.ChartWrapper({
        'chartType': 'PieChart',
        'containerId': 'pie_TK_stemmen',
        'options':{
          pieHole: 0.3,
          chartArea: {bottom: 0, width: '100%', height: '85%'},
          hAxis: {textPosition: 'in'}, vAxis: {textPosition: 'in'},
          legend: {position: 'top',  maxLines: 3},
          colors: getColors()  },
          'view': {'columns': [0, 1]}
        });
  dashboard.bind(filter, pie);
  dashboard.draw(data); 
  google.visualization.events.addListener(pie, 'ready', function() {
  pie.setOption('colors', getColors());
  });     
      }; 

Solution

  • we can draw the filter and pie chart independently from one another,
    rather than using a dashboard.

    we use the 'ready' and 'statechange' events on the filter to know when to draw the pie chart.

    when either of these events are fired, we get the selected value from the filter.

    var filterValue = filter.getState().selectedValues[0];
    

    since you have the following options set on the filter,
    we can be sure there is always only one value in the selectedValues array...

        allowNone: false,
        allowMultiple: false,
    

    using the value from the filter, we can find the rows that meet the filter using getFilteredRows

    var visibleRows = data.getFilteredRows([{
      column: 3,
      value: filterValue
    }]);
    

    we can then use the visible rows to build a data view over the original data table.

    var dataView = new google.visualization.DataView(data);
    dataView.setRows(visibleRows);
    

    then we can pull the colors from the style column in the data view.

    var colors = [];
    for (var i = 0; i < dataView.getNumberOfRows(); i++) {
      colors.push(dataView.getValue(i, 2));
    }
    

    and apply the colors and data view to the pie chart and draw it...

    pie.setDataTable(dataView);
    pie.setOption('colors', colors);
    pie.draw();
    

    see following working snippet...

    it appears you had the same set of colors for each filter value,
    so I've changed the second set of colors for example purposes...

    google.charts.load('current', {
      packages: ['controls', 'corechart']
    }).then(Dashboard);
    
    function Dashboard()  {
      // build data table
      var data = google.visualization.arrayToDataTable([
        ['Partij', 'Aantal', { role: 'style' }, 'Datum'],
        ['PvdA', 20105, '#E30613', '20 maart 2019'],
        ['VVD', 9919, '#F47621', '20 maart 2019'],
        ['CDA', 6480, '#007B5F', '20 maart 2019'],
        ['PvdA', 13303, 'cyan', '18 maart 2015'],
        ['VVD', 8096, 'magenta', '18 maart 2015'],
        ['CDA', 5896, 'yellow', '18 maart 2015']
      ]);
    
      // build filter
      var filter = new google.visualization.ControlWrapper({
        controlType: 'CategoryFilter',
        containerId: 'filter_TK_stemmen',
        dataTable: data,  // <-- assign data table to filter
        options: {
          filterColumnLabel: 'Datum',
          ui: {
            caption: 'Kies een verkiezingsuitslag',
            label: 'Uitslag van: ',
            allowNone: false,
            allowMultiple: false,
            sortValues: false,
            allowTyping: false
          }
        }
      });
    
      // build pie chart
      var pie = new google.visualization.ChartWrapper({
        chartType: 'PieChart',
        containerId: 'pie_TK_stemmen',
        options: {
          pieHole: 0.3,
          chartArea: {bottom: 0, width: '100%', height: '85%'},
          legend: {position: 'top',  maxLines: 3},
        },
        view: {
          columns: [0, 1]
        }
      });
    
      // assign filter event listeners
      google.visualization.events.addListener(filter, 'statechange', drawPie);
      google.visualization.events.addListener(filter, 'ready', drawPie);
    
      // draw filter
      filter.draw();
    
      // draw pie chart, called from filter events
      function drawPie() {
        // get selected filter value
        var filterValue = filter.getState().selectedValues[0];
    
        // get visible rows for filter value
        var visibleRows = data.getFilteredRows([{
          column: 3,
          value: filterValue
        }]);
    
        // build data view over original data table
        var dataView = new google.visualization.DataView(data);
        dataView.setRows(visibleRows);
    
        // get colors from style column in data view
        var colors = [];
        for (var i = 0; i < dataView.getNumberOfRows(); i++) {
          colors.push(dataView.getValue(i, 2));
        }
    
        // assign data view, colors, and draw pie chart
        pie.setDataTable(dataView);
        pie.setOption('colors', colors);
        pie.draw();
      }
    }
    <script src="https://www.gstatic.com/charts/loader.js"></script>
    <div id = "dashboard_TK_stemmen">
      <div id="filter_TK_stemmen"></div>
      <p id = "pie_TK_stemmen"></p>
    </div>