Переписать асинхронные графы JS синхронно? - PullRequest
0 голосов
/ 24 марта 2020

Я пытаюсь использовать графы, чтобы создать многоканальный / многоканальный просмотрщик сигналов, для просмотра, например, цифровых сигналов c. Пример, приведенный здесь, отображает что-то вроде этого:

dygraphs-example

Очевидно, мне нужно синхронизировать две дорожки (т.е. когда я увеличиваю одну дорожку, другие должны соответственно увеличиваться) - в противном случае, это не очень полезно.

Я подготовил суть с обсуждаемым здесь кодом: https://gist.github.com/sdaau/2d21ff7d88f03118bd5b31fac66d2588; и должно быть возможно отобразить пример через bl.ocks.org (см. также здесь ), или если вы запускаете файлы. html локально в вашем браузере (я тестировал с Firefox 74)

Проблема заключается в следующем:

  • Когда данные .csv предоставляются в виде кода в коде JavaScript, синхронные вызовы работают - и проблем нет, синхронизация работает ( см .: test_01_dygraphs_sync_csv. html
  • Когда данные .csv предоставляются через удаленные URL-адреса, синхронные вызовы не работают - и синхронизация не работает (см .: test_02_dygraphs_sync_csv. html)
  • Когда данные .csv предоставляются через удаленные URL-адреса и код перезаписывается асинхронно - синхронизация выполняется (см .: test_03_dygraphs_async_csv. html)

Причина этого объясняется в Есть ли событие загрузки Dygraph? :

Если вы передаете данные CSV или массив в конструктор Dygraphs , он будет вызываться синхронно. Если вы передадите URL, он будет называться асинхронно.

Однако проблема в том, что мне нужно использовать URL-адреса .csv, но я всегда логически думаю об этом с точки зрения синхронных вызовов; Например:

load_g1();
load_g2();
sync_g1g2();

... который, как показано, нарушает синхронизацию осей; и тип асинхронного вызова, который на самом деле работает с синхронизацией:

function load_g1() {
  ...
  g1.ready(function() {
    function load_g2() {
      ...
      g2.ready(function() {
        sync_g1g2();
      });
    }
    load_g2();
  });
}

... то есть «ад обратного вызова», действительно затрудняет мне управление кодом.

Так Я видел такие вопросы как:

... и, видимо, способ сделать это - использовать Обещания - к сожалению, я не все, кто разбирается в JavaScript, и я не могу сказать, если или как это можно применить к этому коду dygraphs.

Итак, подведем итог: есть ли способ переписать вызовы асинхронной загрузки кода .csv с помощью dygraphs, чтобы в конечном итоге я мог написать что-то вроде этих команд в последовательности (в Initialize () в примере)? :

load_g1();
load_g2();
sync_g1g2();

Для справки, я вставлю код test_03_dygraphs_async_csv.html ниже:

<!DOCTYPE html>
    <link rel="stylesheet" href="http://dygraphs.com/2.1.0/dygraph.css">
<title>Test 03: dygraphs asynchronous (URL .csv -> synchronize() OK)</title>
<style>
#graphdiv1, #graphdiv2 {
  display: inline-block;
  vertical-align: top;
}
#legend1, #legend2 {
  display: inline-block;
  vertical-align: top;
}
</style>

<h2>Test 03: dygraphs asynchronous (URL .csv -> synchronize() OK)</h2>

<hr/>

<div id="legend1" style="height:40px;">.</div>

<div id="graphdiv1"
  style="width:98%; height:200px;"></div>

<div id="legend2" style="height:40px;">.</div>

<div id="graphdiv2"
  style="width:98%; height:200px;"></div>

<script type="text/javascript" src="http://dygraphs.com/2.1.0/dygraph.js"></script>
<script type="text/javascript" src="http://dygraphs.com/2.1.0/extras/synchronizer.js"></script>
<script type="text/javascript">
// http://dygraphs.com/tests/plotters.html
// Darken a color
function darkenColor(colorStr) {
  // Defined in dygraph-utils.js
  var color = Dygraph.toRGB_(colorStr);
  color.r = Math.floor((255 + color.r) / 2);
  color.g = Math.floor((255 + color.g) / 2);
  color.b = Math.floor((255 + color.b) / 2);
  return 'rgb(' + color.r + ',' + color.g + ',' + color.b + ')';
}

// This function draws bars for a single series.
function barChartPlotter(e) {
  var ctx = e.drawingContext;
  var points = e.points;
  var y_bottom = e.dygraph.toDomYCoord(0);

  ctx.fillStyle = darkenColor(e.color);

  //// Find the minimum separation between x-values. .. fixed
  var bar_width = Math.floor(2.0);

  // Do the actual plotting.
  for (var i = 0; i < points.length; i++) {
    var p = points[i];
    var center_x = p.canvasx;

    ctx.fillRect(center_x - bar_width / 2, p.canvasy,
        bar_width, y_bottom - p.canvasy);

    ctx.strokeRect(center_x - bar_width / 2, p.canvasy,
        bar_width, y_bottom - p.canvasy);
  }
}


function legendFormatter(data) {
  if (data.x == null) {
    // This happens when there's no selection and {legend: 'always'} is set.
    return '<br>' + data.series.map(function(series) { return series.dashHTML + ' ' + series.labelHTML }).join('<br>');
  }

  var html = this.getLabels()[0] + ': ' + data.xHTML;
  data.series.forEach(function(series) {
    if (!series.isVisible) return;
    var labeledData = series.labelHTML + ': ' + series.yHTML;
    if (series.isHighlighted) {
      labeledData = '<b>' + labeledData + '</b>';
    }
    html += '<br>' + series.dashHTML + ' ' + labeledData;
  });
  return html;
}

var g1, g2;

function load_g1() {
  g1 = new Dygraph(
    document.getElementById("graphdiv1"),
    //"x,val1\n" +
    //"0,0\n" +
    //"18790378,1\n" +
    //"19111992,0\n" +
    //"20107172,1\n" +
    //"21101338,0\n" +
    //"183224018,0\n",
    "https://gist.githubusercontent.com/sdaau/2d21ff7d88f03118bd5b31fac66d2588/raw/val1_data.csv",
    {           // options
      animatedZooms: true,
      stepPlot: true,
      axes: {
        x: {
          drawGrid: false
        },
      },
      includeZero: true,
      legend: 'always',
      labelsKMB: true,
      labelsDiv: document.getElementById('legend1'),
      legendFormatter: legendFormatter,
    }
  );
  // NOTE: SO:26316435 "If you pass CSV data or an array to the Dygraphs constructor, it will be called synchronously. If you pass a URL, it will be called asynchronously."
  g1.ready(function() {
    load_g2();
  });
}

function load_g2() {
  g2 = new Dygraph(
    document.getElementById("graphdiv2"),
    //"x,val2\n" +
    //"0,0\n" +
    //"18790378,0\n" +
    //"19111992,10\n" +
    //"20107172,40\n" +
    //"21101338,30\n" +
    //"22095808,20\n" +
    //"23091420,50\n" +
    //"24085288,10\n" +
    //"25080336,50\n" +
    //"26075516,40\n" +
    //"27069272,20\n",
    "https://gist.githubusercontent.com/sdaau/2d21ff7d88f03118bd5b31fac66d2588/raw/val2_data.csv",
    {           // options
      //title: 'val2', // no need for title (y axis label) here, if using fixed ("always") legend as separate div - shown there.
      animatedZooms: true,
      plotter: barChartPlotter,
      axes: {
        x: {
          drawGrid: false
        },
      },
      includeZero: true,
      legend: 'always', // needs to be always, if we want the legend fixed, that is, not reparented to canvas, as it is for follow, which might fail with bad values
      labelsKMB: true, // seemingly only for y values, not x?
      labelsDiv: document.getElementById('legend2'),
      legendFormatter: legendFormatter,
    }
  );
  // NOTE: SO:26316435 "If you pass CSV data or an array to the Dygraphs constructor, it will be called synchronously. If you pass a URL, it will be called asynchronously."
  g2.ready(function() {
    sync_g1g2();
  });
}

function sync_g1g2() {
  g1.updateOptions({
    dateWindow: g2.xAxisExtremes() // ok, works
  });

  var sync = Dygraph.synchronize(g1, g2, { // options
    zoom: true,
    selection: true,
    range: false, // if you wish to only sync the x-axis.
  });
  // charts are now synchronized
}

function Initialize(evt) {
  load_g1();
}

Initialize();
</script>

1 Ответ

1 голос
/ 28 марта 2020

В основном вам нужно вернуть новое Обещание, а затем вызвать решение, когда вы закончите вместо следующего действия, например, ваша функция load_g1 будет выглядеть так (я удалил некоторые комментарии ради пример)

function load_g1() {
  return new Promise(function(resolve, reject) {
    g1 = new Dygraph(
      document.getElementById('graphdiv1'),

      'https://gist.githubusercontent.com/sdaau/2d21ff7d88f03118bd5b31fac66d2588/raw/val1_data.csv',
      {
        // options
        animatedZooms: true,
        stepPlot: true,
        axes: {
          x: {
            drawGrid: false,
          },
        },
        includeZero: true,
        legend: 'always',
        labelsKMB: true,
        labelsDiv: document.getElementById('legend1'),
        legendFormatter,
      },
    );
    // NOTE: SO:26316435 "If you pass CSV data or an array to the Dygraphs constructor, it will be called synchronously. If you pass a URL, it will be called asynchronously."
    g1.ready(function() {
      resolve();
    });
  });
}

Ваша load_g2 функция будет выглядеть следующим образом

function load_g2() {
  return new Promise(function(resolve, reject) {
    g2 = new Dygraph(
      document.getElementById('graphdiv2'),
      'https://gist.githubusercontent.com/sdaau/2d21ff7d88f03118bd5b31fac66d2588/raw/val2_data.csv',
      {
        // options
        // title: 'val2', // no need for title (y axis label) here, if using fixed ("always") legend as separate div - shown there.
        animatedZooms: true,
        plotter: barChartPlotter,
        axes: {
          x: {
            drawGrid: false,
          },
        },
        includeZero: true,
        legend: 'always', // needs to be always, if we want the legend fixed, that is, not reparented to canvas, as it is for follow, which might fail with bad values
        labelsKMB: true, // seemingly only for y values, not x?
        labelsDiv: document.getElementById('legend2'),
        legendFormatter,
      },
    );
    // NOTE: SO:26316435 "If you pass CSV data or an array to the Dygraphs constructor, it will be called synchronously. If you pass a URL, it will be called asynchronously."
    g2.ready(function() {
      resolve();
    });
  });
}

и ваш sync_g1g2 будет выглядеть так

function sync_g1g2() {
  return new Promise(function(resolve, reject) {
    g1.updateOptions({
      dateWindow: g2.xAxisExtremes(), // ok, works
    });

    const sync = Dygraph.synchronize(g1, g2, {
      // options
      zoom: true,
      selection: true,
      range: false, // if you wish to only sync the x-axis.
    });
    // charts are now synchronized
    resolve();
  });
}

Посмотрите, как я звоню resolve вместо следующего действия, это означает, что мы можем изменить вашу Initialize функцию на следующую

async function Initialize(evt) {
  await load_g1();
  await load_g2();
  await sync_g1g2();
}

Определив функцию Initialize как async, мы можем использовать выражение await. await будет ждать выполнения обещания, прежде чем перейти к следующей строке.

Стоит также отметить, что в этом примере мы не используем функцию reject, но это стоит рассмотреть для будущих случаев использования.

Как только вы освоите Promises ты никогда не go вернешься! Дайте мне крик, если это не имеет смысла.

...