Динамическая вертикальная ось для размещения линий тренда на линейной диаграмме Google

Я новичок в программировании, но за последние несколько месяцев мне удалось нащупать свой путь, создав веб-сайт, который использует линейную диаграмму Google и встроенную линейную линию тренда для отображения исторического среднего уровня моря и скорости повышения среднего уровня моря для в различных местах Новой Зеландии и Тихого океана. В каждом месте есть своя собственная линейная диаграмма Google с линейной линией тренда, показывающая скорость изменения среднего уровня моря за выбранный пользователем период. Теперь я хочу расширить функциональность каждой линейной диаграммы Google таким образом, чтобы как линейная, так и полиномиальная линия тренда продолжалась до 2120 года (в настоящее время они отображаются только до 2018 года), даже если доступные данные, на основе которых они рассчитываются, используют наблюдаемые данные вверх до 2018 года. Это позволит пользователю спрогнозировать высоту уровня моря до 2020 года. Я понимаю, что это объяснение может сбивать с толку, поэтому, пожалуйста, посетите мой веб-сайт www.sealevel.nz, чтобы увидеть существующие диаграммы, которые, я надеюсь, помогут помощь в понимании моей проблемы.

Ниже приведен код расширенной версии диаграммы, которая показывает как линейную, так и полиномиальную линию тренда второй степени с осью x линейной диаграммы Google, которая теперь показывает 2120 год. Моя проблема в том, что мне нужно, чтобы ось y динамически настраивалась на показать всю полноту обеих линий тренда независимо от того, какой период времени выбирает пользователь. Например, если вы выберете 1971 и 2018 годы на ползунке диапазона дат, то обе линии тренда обрежутся в 2017 году (линейный) и 2031 год (полиномиальный) соответственно. Мне нужно видеть как линии тренда, так и их значения до 2120 года.

Прошу прощения за мои навыки программирования-новичка. Мой код:

<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script src="https://unpkg.com/mathjs/dist/math.min.js"></script>
<script type="text/javascript">

google.load('visualization', 'current', {'packages':['controls','corechart']});
google.setOnLoadCallback(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 into data 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': {
      'filterColumnLabel': 'Year',
        'ui': {
       cssClass: 'filter-date',
          'format': { pattern: '0000' },
      'labelStacking': 'vertical',
      'allowTyping': false,
      'allowMultiple': false    
      }
    },
  });
  var MSLChart = new google.visualization.ChartWrapper({
    'chartType': 'LineChart',
    'containerId': 'chart_div',
    'options': {  
    'fontSize': '14', 
    'title': 'Timbucktoo Annual Mean Sea Level Summary',
        hAxis: {title: 'Year', format:'0000', maxValue: 2120},
        vAxis: {title: 'Height above Chart Datum (mm)', format:'0000'},
        '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]}
  });

  var dashboard = new google.visualization.Dashboard(document.getElementById('dashboard_div')).
    bind(YearPicker, MSLChart).

  draw(data)
 </script>

person Bryan Ward    schedule 04.11.2019    source источник


Ответы (1)


во-первых, я не уверен, почему на графике нарисована линия тренда, которая не видна
что немного усложняет задачу, потому что сначала нам нужно нарисовать график,
чтобы найти минимальное и максимальное значения оси Y.

но есть методы диаграммы, которые мы можем использовать для определения максимального ценность.

Во-первых, мы получаем интерфейс макета диаграммы.

var chartLayout = MSLChart.getChart().getChartLayoutInterface();

поскольку мы используем ChartWrapper, нам нужно получить диаграмму из оболочки (MSLChart.getChart()).

Затем мы используем метод getBoundingBox, чтобы найти минимальное и максимальное значения каждой строки.

var yAxisCoords = {min: null, max: null};
var lineIndex = 0;
var boundsLine = chartLayout.getBoundingBox('line#' + lineIndex);
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);

затем мы используем метод getVAxisValue, чтобы определить, каким должно быть каждое значение оси Y,
устанавливаем viewWindow на оси Y и заново рисуем диаграмму.

MSLChart.setOption('vAxis.viewWindow.max', chartLayout.getVAxisValue(yAxisCoords.max));
MSLChart.setOption('vAxis.viewWindow.min', chartLayout.getVAxisValue(yAxisCoords.min));
MSLChart.draw();

мы делаем все это в функции.
мы используем одноразовое 'ready' событие в оболочке диаграммы для первого вычисления.
затем снова на диаграмме.

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);
  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);

  // re-draw chart
  MSLChart.setOption('vAxis.viewWindow.max', chartLayout.getVAxisValue(yAxisCoords.max));
  MSLChart.setOption('vAxis.viewWindow.min', chartLayout.getVAxisValue(yAxisCoords.min));
  MSLChart.draw();
  google.visualization.events.addOneTimeListener(MSLChart.getChart(), 'ready', filterChange);
}

см. следующий рабочий фрагмент ...
(при запуске фрагмента щелкните "всю страницу" вверху справа)

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 into data 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: {
      filterColumnLabel: 'Year',
      ui: {
        cssClass: 'filter-date',
        format: {pattern: '0000'},
        labelStacking: 'vertical',
        allowTyping: false,
        allowMultiple: false
      }
    },
  });

  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', maxValue: 2120},
      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);
    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);

    // re-draw chart
    MSLChart.setOption('vAxis.viewWindow.max', chartLayout.getVAxisValue(yAxisCoords.max));
    MSLChart.setOption('vAxis.viewWindow.min', chartLayout.getVAxisValue(yAxisCoords.min));
    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>


примечание: похоже, вы используете старую инструкцию load для загрузки диаграммы Google.
см. фрагмент выше для обновления ...

person WhiteHat    schedule 04.11.2019
comment
Большое спасибо @WhiteHat. Я потрясен тем, что вы придумали. Если вы когда-нибудь будете в Новой Зеландии, я буду более чем счастлив купить вам пива или двух. У меня есть пара вопросов в отдельных комментариях: - person Bryan Ward; 06.11.2019
comment
1. Тенденция действительно становится отрицательной для определенных пар лет. Например, в 1932 и 1990 годах полиномиальная линия тренда становится отрицательной, в то время как линейная линия тренда остается положительной. Поэтому, пытаясь вычислить минимальное значение оси Y для такого сценария, я возвратил свойства left, width и height для обеих линий тренда, а затем использовал метод getVAxisValue для возврата соответствующего значения оси Y, как вы указали, но мне кажется, что я получаю только бессмысленные значения. - person Bryan Ward; 06.11.2019
comment
Например: var minTrend = Math.min(trendLine0.left, trendLine1.left); var yCoord = chartLayout.getVAxisValue(minTrend); console.log(yCoord); Или var minTrend = Math.min(trendLine0.width, trendLine1.width); var yCoord = chartLayout.getVAxisValue(minTrend); console.log(yCoord); Или var minTrend = Math.min(trendLine0.height, trendLine1.height); var yCoord = chartLayout.getVAxisValue(minTrend); console.log(yCoord); - person Bryan Ward; 06.11.2019
comment
Кажется, что все вышеперечисленное возвращает значение, которое не является минимальным значением оси y. Так есть ли другое свойство, кроме «left», «width», «top» и «height» объекта линии тренда, которое будет возвращать минимальное значение? - person Bryan Ward; 06.11.2019
comment
2. На что ссылаются начальные значения trendLine0.top, trendLine1.top до того, как они будут преобразованы в соответствующие значения оси Y с помощью метода getVAxisValue? - person Bryan Ward; 06.11.2019
comment
Без проблем. Не торопись. Я никуда не тороплюсь. - person Bryan Ward; 07.11.2019
comment
изменил ответ выше, обратите внимание: я заметил, что когда значение оси min упало ниже 1000, опция minValue проигнорирует наше значение и упадет до нуля. используя вместо этого viewWindow, проблема исправлена ​​... - person WhiteHat; 07.11.2019
comment
1. Почему yAxisCoords.min = boundsLine.top + boundsLine.height; (когда lineIndex = 0) вместо этого не равно boundsLine.top - boundsLine.height;? - person Bryan Ward; 11.11.2019
comment
2. Не могли бы вы напомнить мне еще раз, почему yAxisCoordsMax равно минимуму yAxisCoords.max и boundsLine.top, а не максимуму из этих двух значений? Я помню, как вы упоминали в своем предыдущем ответе, что это было связано с тем, что наибольшее значение было отрицательным, если оно было слишком большим для отображения на существующей оси Y визуализированной диаграммы. - person Bryan Ward; 11.11.2019
comment
3. Я хочу внести изменения в диаграмму и numberRangeFilter таким образом, чтобы по умолчанию использовались годы, для которых доступны наблюдаемые данные (синяя линия) при первой загрузке диаграммы, то есть в данном случае 1904 и 2018. После загрузки затем пользователь может выбрать год, после которого будут доступны данные наблюдений до 2120 года, и увидеть обе линии тренда до года, который они выбрали, чтобы получить прогноз уровня моря в какой-то момент в будущем. - person Bryan Ward; 11.11.2019
comment
Для этого я вставил 'maxValue': 2120, в параметры и "state": {"lowValue": 1904, "highValue": 2018}, в параметры конфигурации numberRangeFilter ControlWrapper и удалил maxValue: 2120 из параметров в ChartWrapper. Однако, когда я затем перезагружаю диаграмму и выбираю, например, 1904 и 2120 годы, диаграмму не удается перерисовать. Фактически, его нельзя перерисовать ни за один год, выбранный после 2018 года? - person Bryan Ward; 11.11.2019
comment
4. Чтобы показать две линии тренда для одной и той же серии данных, мне пришлось создать две серии данных, содержащих идентичные данные, а затем показать линейную линию тренда для одной серии и полиномиальную линию тренда для другой. Похоже, вы не можете показать две разные линии тренда для одного и того же ряда данных. Это правильно? - person Bryan Ward; 11.11.2019
comment
Хорошо, поэтому я добавил следующее к функции filterChange(), чтобы она правильно перерисовывала ось x, когда выбран год после 2018, который, кажется, работает: - person Bryan Ward; 12.11.2019
comment
Однако он по-прежнему отсекает одну или обе линии тренда при выборе определенных лет. Итак, я знаю, что мне нужно запускать функцию filterChange всякий раз, когда YearPicker numberRangeFilter меняет состояние. Однако я безуспешно пытался вставить google.visualization.events.addOneTimeListener(YearPicker, 'statechange', filterChange); в разные места, чтобы вызвать функцию filterChange. Я также попытался изменить значение function() в вышеупомянутом слушателе в моем предыдущем комментарии на filterChange, также безуспешно. - person Bryan Ward; 12.11.2019
comment
может быть проще задать другой вопрос, у меня проблемы с отслеживанием всех комментариев. Попробую ответить на несколько. 1) для оси Y min нам нужно найти самую нижнюю точку линии, поэтому мы начинаем с верхней и добавляем высоту. 2) думайте о верхнем левом углу области диаграммы как о позиции 0, 0. и он увеличивается влево и вниз. если линия находится над видимой областью диаграммы, тогда координата y будет отрицательным значением. что касается 3) другой вопрос будет лучше - давайте назовем его готовым - person WhiteHat; 13.11.2019
comment
Нет проблем @WhiteHat. Очень признателен за вашу помощь. Только что опубликовали вышеупомянутый еще один вопрос под названием «Установить максимальное значение для нижнего диапазона фильтра диапазона номеров Google». - person Bryan Ward; 14.11.2019
comment
ура, я посмотрю. есть ли шанс отметить это как принятый? видите галочку возле кнопок голосования ... - person WhiteHat; 14.11.2019