【问题标题】:Set Maximum Value for Lower Range of Google Number Range Filter为谷歌数字范围过滤器的下限设置最大值
【发布时间】:2019-11-14 02:57:05
【问题描述】:

我有一个交互式谷歌折线图,它显示用户选择的两个不同年份之间的历史海平面数据。该图表还显示所选期间的线性和多项式趋势线。可用的历史数据范围为 1904 年至 2018 年(含)。但是,用户可以选择从 1904 到 2120(含)的任何开始年份和结束年份。如果选择 2018 年之后的结束年份,图表会显示截至 2018 年的可用历史数据,然后将两条趋势线延伸以显示截至用户所选年份的预测海平面。

这似乎可以正常工作,直到两个选定的年份都超过 2018 年,即 2020 年和 2056 年抛出错误(“无法读取 null 的属性'top'”),因为它无法从包含没有观察到的数据。目前,我正在使用一个错误处理程序来解决这个问题,该处理程序在发生这种情况时会启动并显示一条警告消息,告知用户他们不能选择大于 2018 年的开始年份。然后页面重新加载,数字范围过滤器默认返回分别到 1904 年和 2018 年的开始和结束年份,这并不理想。我想做的是限制用户选择不超过 2018 年的开始年份,但数字范围过滤器控件中似乎没有选项/设置来执行此操作。有什么想法吗?

我的代码:

<html>

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

google.charts.load('current', {
  packages: ['controls']
}).then(initialize);

function initialize() {
  var query = new google.visualization.Query('https://docs.google.com/spreadsheets/d/1vn1iuhsG33XzFrC4QwkTdUnxOGdcPQOj-cuaEZeX-eA/edit#gid=0');
  query.send(drawDashboard);
}

function drawDashboard(response) {

  var data = response.getDataTable();
  //Asign units of 'mm' to data.
  var formatMS = new google.visualization.NumberFormat({
    pattern: '# mm'
  });

  // format data into mm.
  for (var colIndex = 1; colIndex < data.getNumberOfColumns(); colIndex++) {
    formatMS.format(data, colIndex);
  }
  var YearPicker = new google.visualization.ControlWrapper({
    controlType: 'NumberRangeFilter',
    containerId: 'filter_div',
    options: {
      maxValue:2120,
      filterColumnLabel: 'Year',
      ui: {
        cssClass: 'filter-date',
        format: {pattern: '0000'},
        labelStacking: 'vertical',
        allowTyping: false,
        allowMultiple: false
      }
    },
    "state": {"lowValue": 1904, "highValue": 2018},
  });

  google.visualization.events.addListener(YearPicker, 'statechange', filterChange);

  var MSLChart = new google.visualization.ChartWrapper({
    chartType: 'LineChart',
    containerId: 'chart_div',
    dataTable: data,
    options: {
      fontSize: '14',
      title: 'Timbucktoo Annual Mean Sea Level Summary',
      hAxis: {title: 'Year', format: '0000'},
      vAxis: {title: 'Height above Chart Datum (mm)', format:'###0'},
      height: 600,
      chartArea: {height: '81%', width: '85%', left: 100},
      legend: {position: 'in', alignment: 'end', textStyle: {fontSize: 13}},
      colors: ['blue'],
      trendlines: {
        0: {
          type: 'polynomial',
          degree: 2,
          color: 'green',
          visibleInLegend: true,
        },
        1: {
          type: 'linear',
          color: 'black',
          visibleInLegend: true,
        },
      },
      series: {
        0: { visibleInLegend: true },
        1: { visibleInLegend: false },
      },
    },
    view: {columns: [0,1,2]}
  });

  google.visualization.events.addOneTimeListener(MSLChart, 'ready', filterChange);

  function filterChange() {
    // get chart layout
    var chartLayout = MSLChart.getChart().getChartLayoutInterface();

    // get y-axis bounds
    var yAxisCoords = {min: null, max: null};
    var lineIndex = 0;
    var boundsLine = chartLayout.getBoundingBox('line#' + lineIndex);
   try {
    do {
      yAxisCoords.max = yAxisCoords.max || boundsLine.top;
      yAxisCoords.max = Math.min(yAxisCoords.max, boundsLine.top);
      yAxisCoords.min = yAxisCoords.min || (boundsLine.top + boundsLine.height);
      yAxisCoords.min = Math.max(yAxisCoords.min, (boundsLine.top + boundsLine.height));
      lineIndex++;
      boundsLine = chartLayout.getBoundingBox('line#' + lineIndex);
    } while (boundsLine !== null);
     }
     catch (error) {alert("Please choose a start year less than or equal to 2018");
     window.location.reload(false);
     exit;
     }
    var state = YearPicker.getState();
      var EndYear = state.highValue;
    // re-draw chart
    MSLChart.setOption('vAxis.viewWindow.max', chartLayout.getVAxisValue(yAxisCoords.max));
    MSLChart.setOption('vAxis.viewWindow.min', chartLayout.getVAxisValue(yAxisCoords.min));
    MSLChart.setOption('hAxis.viewWindow.max', EndYear);
    MSLChart.draw();
    google.visualization.events.addOneTimeListener(MSLChart.getChart(), 'ready', filterChange);
    }

  var dashboard = new google.visualization.Dashboard(
    document.getElementById('dashboard_div')
  ).bind(YearPicker, MSLChart).draw(data);
}
</script>
  <div id="dashboard_div">
    <div id="chart_div"></div>
    <div id="filter_div"></div>
  </div>
</html>

【问题讨论】:

    标签: javascript controls google-visualization trendline


    【解决方案1】:

    为了防止错误,在filterChange函数中,
    更改do...while 语句,

    do {
      yAxisCoords.max = yAxisCoords.max || boundsLine.top;
      yAxisCoords.max = Math.min(yAxisCoords.max, boundsLine.top);
      yAxisCoords.min = yAxisCoords.min || (boundsLine.top + boundsLine.height);
      yAxisCoords.min = Math.max(yAxisCoords.min, (boundsLine.top + boundsLine.height));
      lineIndex++;
      boundsLine = chartLayout.getBoundingBox('line#' + lineIndex);
    } while (boundsLine !== null);
    

    只是一个while 声明

    while (boundsLine !== null) {
      yAxisCoords.max = yAxisCoords.max || boundsLine.top;
      yAxisCoords.max = Math.min(yAxisCoords.max, boundsLine.top);
      yAxisCoords.min = yAxisCoords.min || (boundsLine.top + boundsLine.height);
      yAxisCoords.min = Math.max(yAxisCoords.min, (boundsLine.top + boundsLine.height));
      lineIndex++;
      boundsLine = chartLayout.getBoundingBox('line#' + lineIndex);
    };
    

    do...while 假设总是至少画一条线。


    我们无法阻止用户选择超过 2018 年的开始年份,
    但是我们可以在他们这样做时立即重置开始年份。

    google.visualization.events.addListener(YearPicker, 'statechange', function () {
      var state = YearPicker.getState();
      state.lowValue = Math.min(2018, state.lowValue);
      YearPicker.setState({
        lowValue: state.lowValue,
        highValue: state.highValue
      });
      YearPicker.draw();
      filterChange();
    });
    

    请参阅以下工作 sn-p...

    google.charts.load('current', {
      packages: ['controls']
    }).then(initialize);
    
    function initialize() {
      var query = new google.visualization.Query('https://docs.google.com/spreadsheets/d/1vn1iuhsG33XzFrC4QwkTdUnxOGdcPQOj-cuaEZeX-eA/edit#gid=0');
      query.send(drawDashboard);
    }
    
    function drawDashboard(response) {
    
      var data = response.getDataTable();
      //Asign units of 'mm' to data.
      var formatMS = new google.visualization.NumberFormat({
        pattern: '# mm'
      });
    
      // format data into mm.
      for (var colIndex = 1; colIndex < data.getNumberOfColumns(); colIndex++) {
        formatMS.format(data, colIndex);
      }
      var YearPicker = new google.visualization.ControlWrapper({
        controlType: 'NumberRangeFilter',
        containerId: 'filter_div',
        options: {
          maxValue: 2120,
          filterColumnLabel: 'Year',
          ui: {
            cssClass: 'filter-date',
            format: {pattern: '0000'},
            labelStacking: 'vertical',
            allowTyping: false,
            allowMultiple: false
          }
        },
        state: {lowValue: 1904, highValue: 2018},
      });
    
      google.visualization.events.addListener(YearPicker, 'statechange', function () {
        var state = YearPicker.getState();
        state.lowValue = Math.min(2018, state.lowValue);
        YearPicker.setState({
          lowValue: state.lowValue,
          highValue: state.highValue
        });
        YearPicker.draw();
        filterChange();
      });
    
      var MSLChart = new google.visualization.ChartWrapper({
        chartType: 'LineChart',
        containerId: 'chart_div',
        dataTable: data,
        options: {
          fontSize: '14',
          title: 'Timbucktoo Annual Mean Sea Level Summary',
          hAxis: {title: 'Year', format: '0000'},
          vAxis: {title: 'Height above Chart Datum (mm)', format:'###0'},
          height: 600,
          chartArea: {height: '81%', width: '85%', left: 100},
          legend: {position: 'in', alignment: 'end', textStyle: {fontSize: 13}},
          colors: ['blue'],
          trendlines: {
            0: {
              type: 'polynomial',
              degree: 2,
              color: 'green',
              visibleInLegend: true,
            },
            1: {
              type: 'linear',
              color: 'black',
              visibleInLegend: true,
            },
          },
          series: {
            0: { visibleInLegend: true },
            1: { visibleInLegend: false },
          },
        },
        view: {columns: [0,1,2]}
      });
    
      google.visualization.events.addOneTimeListener(MSLChart, 'ready', filterChange);
    
      function filterChange() {
        // get chart layout
        var chartLayout = MSLChart.getChart().getChartLayoutInterface();
    
        // get y-axis bounds
        var yAxisCoords = {min: null, max: null};
        var lineIndex = 0;
        var boundsLine = chartLayout.getBoundingBox('line#' + lineIndex);
    
        while (boundsLine !== null) {
          yAxisCoords.max = yAxisCoords.max || boundsLine.top;
          yAxisCoords.max = Math.min(yAxisCoords.max, boundsLine.top);
          yAxisCoords.min = yAxisCoords.min || (boundsLine.top + boundsLine.height);
          yAxisCoords.min = Math.max(yAxisCoords.min, (boundsLine.top + boundsLine.height));
          lineIndex++;
          boundsLine = chartLayout.getBoundingBox('line#' + lineIndex);
        };
    
        var state = YearPicker.getState();
        var EndYear = state.highValue;
    
        // re-draw chart
        MSLChart.setOption('vAxis.viewWindow.max', chartLayout.getVAxisValue(yAxisCoords.max));
        MSLChart.setOption('vAxis.viewWindow.min', chartLayout.getVAxisValue(yAxisCoords.min));
        MSLChart.setOption('hAxis.viewWindow.max', EndYear);
        MSLChart.draw();
        google.visualization.events.addOneTimeListener(MSLChart.getChart(), 'ready', filterChange);
      }
    
      var dashboard = new google.visualization.Dashboard(
        document.getElementById('dashboard_div')
      ).bind(YearPicker, MSLChart).draw(data);
    }
    <script src="https://www.gstatic.com/charts/loader.js"></script>
    <div id="dashboard_div">
      <div id="chart_div"></div>
      <div id="filter_div"></div>
    </div>

    编辑

    没有用于修改趋势线工具提示的选项,
    但我们可以在'onmouseover' 事件期间手动更改它。

    首先,我们需要使用 html 工具提示,默认为 svg。
    添加此选项...

      tooltip: {
        isHtml: true
      },
    

    然后将'onmouseover' 事件添加到图表中,
    我们可以在包装器的'ready' 事件中执行此操作。

    google.visualization.events.addOneTimeListener(MSLChart, 'ready', function () {
      google.visualization.events.addListener(MSLChart.getChart(), 'onmouseover', function (props) {
        // ensure trendline tooltip
        if ((props.column === 0) && (props.row !== null)) {
          // get year value
          var year = MSLChart.getDataTable().getValue(props.row, 0);
    
          // get tooltip, remove width
          var tooltip = MSLChart.getChart().getContainer().getElementsByTagName('ul');
          tooltip[0].parentNode.style.width = null;
    
          // get tooltip labels
          var tooltipLabels = MSLChart.getChart().getContainer().getElementsByTagName('span');
    
          // set year
          tooltipLabels[0].innerHTML = year;
    
          // remove formula
          tooltipLabels[1].innerHTML = '';
    
          // set height value
          var height = parseFloat(tooltipLabels[2].innerHTML.split(' ')[2].replace(',', '')).toFixed(0);
          tooltipLabels[2].innerHTML = height + ' mm';
        }
      });
    });
    

    请参阅以下工作 sn-p...

    google.charts.load('current', {
      packages: ['controls']
    }).then(initialize);
    
    function initialize() {
      var query = new google.visualization.Query('https://docs.google.com/spreadsheets/d/1vn1iuhsG33XzFrC4QwkTdUnxOGdcPQOj-cuaEZeX-eA/edit#gid=0');
      query.send(drawDashboard);
    }
    
    function drawDashboard(response) {
    
      var data = response.getDataTable();
      //Asign units of 'mm' to data.
      var formatMS = new google.visualization.NumberFormat({
        pattern: '# mm'
      });
    
      // format data into mm.
      for (var colIndex = 1; colIndex < data.getNumberOfColumns(); colIndex++) {
        formatMS.format(data, colIndex);
      }
      var YearPicker = new google.visualization.ControlWrapper({
        controlType: 'NumberRangeFilter',
        containerId: 'filter_div',
        options: {
          maxValue: 2120,
          filterColumnLabel: 'Year',
          ui: {
            cssClass: 'filter-date',
            format: {pattern: '0000'},
            labelStacking: 'vertical',
            allowTyping: false,
            allowMultiple: false
          }
        },
        state: {lowValue: 1904, highValue: 2018},
      });
    
      google.visualization.events.addListener(YearPicker, 'statechange', function () {
        var state = YearPicker.getState();
        state.lowValue = Math.min(2018, state.lowValue);
        YearPicker.setState({
          lowValue: state.lowValue,
          highValue: state.highValue
        });
        YearPicker.draw();
        filterChange();
      });
    
      var MSLChart = new google.visualization.ChartWrapper({
        chartType: 'LineChart',
        containerId: 'chart_div',
        dataTable: data,
        options: {
          fontSize: '14',
          title: 'Timbucktoo Annual Mean Sea Level Summary',
          hAxis: {title: 'Year', format: '0000'},
          vAxis: {title: 'Height above Chart Datum (mm)', format:'###0'},
          height: 600,
          chartArea: {height: '81%', width: '85%', left: 100},
          legend: {position: 'in', alignment: 'end', textStyle: {fontSize: 13}},
          colors: ['blue'],
          tooltip: {
            isHtml: true
          },
          trendlines: {
            0: {
              type: 'polynomial',
              degree: 2,
              color: 'green',
              visibleInLegend: true,
            },
            1: {
              type: 'linear',
              color: 'black',
              visibleInLegend: true,
            },
          },
          series: {
            0: { visibleInLegend: true },
            1: { visibleInLegend: false },
          },
        },
        view: {columns: [0,1,2]}
      });
    
      google.visualization.events.addOneTimeListener(MSLChart, 'ready', filterChange);
    
      function filterChange() {
        // get chart layout
        var chartLayout = MSLChart.getChart().getChartLayoutInterface();
    
        // get y-axis bounds
        var yAxisCoords = {min: null, max: null};
        var lineIndex = 0;
        var boundsLine = chartLayout.getBoundingBox('line#' + lineIndex);
    
        while (boundsLine !== null) {
          yAxisCoords.max = yAxisCoords.max || boundsLine.top;
          yAxisCoords.max = Math.min(yAxisCoords.max, boundsLine.top);
          yAxisCoords.min = yAxisCoords.min || (boundsLine.top + boundsLine.height);
          yAxisCoords.min = Math.max(yAxisCoords.min, (boundsLine.top + boundsLine.height));
          lineIndex++;
          boundsLine = chartLayout.getBoundingBox('line#' + lineIndex);
        };
    
        var state = YearPicker.getState();
        var EndYear = state.highValue;
    
        // re-draw chart
        MSLChart.setOption('vAxis.viewWindow.max', chartLayout.getVAxisValue(yAxisCoords.max));
        MSLChart.setOption('vAxis.viewWindow.min', chartLayout.getVAxisValue(yAxisCoords.min));
        MSLChart.setOption('hAxis.viewWindow.max', EndYear);
        MSLChart.draw();
        google.visualization.events.addOneTimeListener(MSLChart.getChart(), 'ready', filterChange);
      }
    
      google.visualization.events.addOneTimeListener(MSLChart, 'ready', function () {
        google.visualization.events.addListener(MSLChart.getChart(), 'onmouseover', function (props) {
          // ensure trendline tooltip
          if ((props.column === 0) && (props.row !== null)) {
            var year = MSLChart.getDataTable().getValue(props.row, 0);
    
            // get tooltip, remove width
            var tooltip = MSLChart.getChart().getContainer().getElementsByTagName('ul');
            tooltip[0].parentNode.style.width = null;
    
            // get tooltip labels
            var tooltipLabels = MSLChart.getChart().getContainer().getElementsByTagName('span');
    
            // set year
            tooltipLabels[0].innerHTML = year;
    
            // remove formula
            tooltipLabels[1].innerHTML = '';
    
            // set height
            var height = parseFloat(tooltipLabels[2].innerHTML.split(' ')[2].replace(',', '')).toFixed(0);
            tooltipLabels[2].innerHTML = height + ' mm';
          }
        });
      });
    
      var dashboard = new google.visualization.Dashboard(
        document.getElementById('dashboard_div')
      ).bind(YearPicker, MSLChart).draw(data);
    }
    <script src="https://www.gstatic.com/charts/loader.js"></script>
    <div id="dashboard_div">
      <div id="chart_div"></div>
      <div id="filter_div"></div>
    </div>

    【讨论】:

    • 太棒了@WhiteHat。真的很感激。有没有办法在每条趋势线的工具提示中隐藏方程式,而只显示年份和海平面高度?鉴于图例中已经显示了趋势线方程,我觉得它只是使工具提示复杂化,使外行更难理解。此外,是否可以将趋势线工具提示中的年份和高度显示为不带逗号和小数点的整数,就像观察数据的工具提示中显示的那样,即“2029:1962 mm”,而不是“2029.826:1962.449”?
    • 谢谢@Whitehat。只需通读 Google Charts Tooltips 指南即可尝试按照您所做的操作。几个问题: 1. 当您将鼠标悬停在图例上时会产生错误? 2. 当您选择 1904 年和 2018 年以外的开始和结束年份,然后将鼠标悬停在任一趋势线上的任何点上时,工具提示中弹出的年份与工具提示正下方 x 轴上的年份不匹配?
    • 干杯,更改了上面的 EDIT1. 由于鼠标悬停事件在图例上触发,我们需要确保该行不为空 --> if ((props.column === 0) &amp;&amp; (props.row !== null)) { 2. 要获取年份值,我们需要使用来自图表包装器的过滤数据表 --> var year = MSLChart.getDataTable().getValue(props.row, 0);
    • 再次感谢@WhiteHat。工具提示中弹出的年份仍然与工具提示正下方 x 轴上的年份不匹配?例如,如果您选择 1904 和 2120,然后将鼠标悬停在 x 轴上 2120 正上方的任一趋势线上,则工具提示中返回的年份是 2062,而应该是 2120。我自己尝试解决这个问题,但没有得到远。
    • 那太好了,有时我会掉进深坑,找不到回去的路。这应该工作得很好......
    猜你喜欢
    • 1970-01-01
    • 2013-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-21
    • 2016-10-14
    相关资源
    最近更新 更多