Search code examples
javascriptchartsgoogle-visualizationtimelinegantt-chart

Google Timeline Visualization doesn't change series row height on slider interaction


So I've got a timeline with data in it that can be concurrent...

enter image description here

When I move the ChartRangeSlider to a different timeframe, some of the timeline bars will either disappear or show because there is nothing happening in the timeframe that is active.

These is how the timeline and the range slider are set up. I don't have any event listeners running...

       // Configure range slider
        var timelineRangeSlider = new google.visualization.ControlWrapper({
            'controlType': 'ChartRangeFilter',
            'containerId': 'timeline-filter',
            'state': 
            {
                'range': 
                {
                    'start': currentTime,
                    'end':   fourWeek                   
                }
            },
            'options': 
            {
                'filterColumnIndex': 4,
                'ui': 
                {
                    'chartType': 'ScatterChart',
                    'chartOptions': 
                    {
                        'width': '100%',
                        'height': '50',
                        'chartArea': 
                        {
                            'width': '80%', // make sure this is the same for the chart and control so the axes align right
                            'height': '80%'
                        },
                        'hAxis': 
                        {
                            'baselineColor': 'none'
                        }
                    },
                    'chartView': 
                    {
                        'columns': [4,6]
                    }
                }
            }
        });

        // Configure timeline
        var timeline = new google.visualization.ChartWrapper({
            'chartType': 'Timeline',
            'containerId': 'timeline-chart',
            'options': 
            {
                'timeline': 
                {
                    'showBarLabels': false
                },
                'width': '100%',
                'height': '325',
                'tooltip': 
                {
                    'isHtml': true
                },
                'chartArea': 
                {
                    'width': '80%', // make sure this is the same for the chart and control so the axes align right
                    'height': '80%'
                }, 
            },
            'view': 
            {
                'columns': [0,1,2,3,4,5]
            }           
        });

How can I stop this from happening, and have each of the four separate rows (one for each series) have a static height that won't change when I interact with the range slider?


Solution

  • to display the same number of rows, regardless of the filter settings,
    replace the rows removed by the filter with "blank" rows,
    doing so will require some manipulation

    if you're using a dashboard to bind the chart and filter,
    it will probably be easier to draw each independently
    listen for the 'statechange' event on the filter,
    to know when to re-draw the chart

    use a data view to exclude the rows hidden by the filter,
    add blank rows in their place
    use the colors option on the timeline to set blank rows to 'transparent'
    also use a blank tooltip for these rows

    see following working snippet, for an example of how this could be accomplished...

    google.charts.load('current', {
      packages: ['controls', 'timeline']
    }).then(function () {
      var dataTable = google.visualization.arrayToDataTable([
        ['Row Label', 'Bar Label', {role: 'tooltip', type: 'string', p: {html: true}}, 'Start', 'End', 'Scatter', 'Data / Blank'],
        ['A', 'Series 0', null, new Date(2018, 1, 1), new Date(2018, 1, 28), 1, 'data'],
        ['B', 'Series 1', null, new Date(2018, 4, 1), new Date(2018, 4, 31), 1, 'data'],
        ['C', 'Series 2', null, new Date(2018, 7, 1), new Date(2018, 7, 31), 1, 'data'],
        ['D', 'Series 3', null, new Date(2018, 10, 1), new Date(2018, 10, 30), 1, 'data']
      ]);
    
      var blankTooltip = '<div class="hidden"></div>';
      var colorPallette = ['cyan', 'magenta', 'lime', 'yellow'];
      var dateRange = {
        start: dataTable.getColumnRange(3),
        end: dataTable.getColumnRange(4)
      };
    
      // Configure range slider
      var timelineRangeSlider = new google.visualization.ControlWrapper({
        controlType: 'ChartRangeFilter',
        containerId: 'timeline-filter',
    
        dataTable: dataTable,
    
        state: {
          range: {
            start: dateRange.start.min,
            end: dateRange.end.max
          }
        },
        options: {
          filterColumnIndex: 3,
          ui: {
            chartType: 'ScatterChart',
            chartOptions: {
              width: '100%',
              height: '50',
              chartArea: {
                width: '80%',
                height: '80%'
              },
              hAxis: {
                baselineColor: 'none'
              }
            },
            chartView: {
              columns: [3,5]
            }
          }
        }
      });
    
      google.visualization.events.addListener(timelineRangeSlider, 'statechange', function (props) {
        // filter state
        var state = timelineRangeSlider.getState();
    
        // wait until statechange has finished
        if (!props.inProgress) {
          // delete previously added blank rows
          var blankRows = dataTable.getFilteredRows([{
            column: 6,
            value: 'blank'
          }]);
          var i = blankRows.length;
          while (i--) {
            dataTable.removeRow(blankRows[i]);
          }
    
          // add blank rows for non-visible rows
          var blankRows = [];
          var timelineColors = [];
          var visibleRows = dataTable.getFilteredRows([{
            column: 3,
            minValue: state.range.start
          }, {
            column: 4,
            maxValue: state.range.end
          }]);
          for (var i = 0; i < dataTable.getNumberOfRows(); i++) {
            if (visibleRows.indexOf(i) === -1) {
              blankRows.push([
                dataTable.getValue(i, 0),
                dataTable.getValue(i, 1),
                blankTooltip,
                state.range.start,
                state.range.start,
                1,
                'blank'
              ]);
              timelineColors.push('transparent');
            } else {
              timelineColors.push(colorPallette[i]);
            }
          }
    
          // build timeline view rows
          var lastRowIndex = dataTable.addRows(blankRows);
          var i = blankRows.length;
          while (i--) {
            visibleRows.push((lastRowIndex - i));
          }
    
          // re-config timeline
          var timelineView = new google.visualization.DataView(dataTable);
          timelineView.setRows(visibleRows);
          timelineView = timelineView.toDataTable();
          timelineView.sort([{column: 0}]);
          timeline.setDataTable(timelineView);
          timeline.setOption('colors', timelineColors);
          timeline.draw();
        }
      });
    
      timelineRangeSlider.draw();
    
      // Configure timeline
      var timeline = new google.visualization.ChartWrapper({
        chartType: 'Timeline',
        containerId: 'timeline-chart',
    
        dataTable: dataTable,
    
        options: {
          colors: colorPallette,
    
          timeline: {
            showBarLabels: false
          },
          width: '100%',
          height: '325',
          tooltip: {
            isHtml: true
          },
          chartArea: {
            width: '80%',
            height: '80%'
          }
        },
        view: {
          columns: [0,1,2,3,4]
        }
      });
      timeline.draw();
    });
    .hidden {
      display: none;
      visibility: hidden;
    }
    <script src="https://www.gstatic.com/charts/loader.js"></script>
    <div id="dashboard_div">
      <div id="timeline-filter"></div>
      <div id="timeline-chart"></div>
    </div>