【问题标题】:Rewrite asynchronous dygraphs JS in synchronous-like way?以类似同步的方式重写异步dygraphs JS?
【发布时间】:2020-07-04 23:51:40
【问题描述】:

我正在尝试使用 dygraphs 来制作一种多通道/多轨波形查看器,以浏览例如数字逻辑信号。此处的示例呈现如下内容:

显然,我需要同步两个轨道(即,当我放大一个轨道时,另一个应该相应地放大) - 否则,没有太多用处。

我已经用这里讨论的代码准备了一个要点:https://gist.github.com/sdaau/2d21ff7d88f03118bd5b31fac66d2588;并且应该可以通过 bl.ocks.org 呈现示例(另请参阅here),或者如果您在浏览器中本地运行 .html 文件(我已经使用 Firefox 74 进行了测试)

问题是:

原因在Is there an Dygraph has loaded event? 中有解释:

如果您将 CSV 数据或数组传递给 Dygraphs 构造函数,它将被同步调用。如果你传递一个 URL,它将被异步调用。

但是,问题是,我需要使用 .csv URL,但从逻辑上讲,我总是考虑与 this 进行同步调用;例如:

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

... 如图所示,这会破坏轴同步;以及实际上与同步一起使用的异步调用:

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

……也就是“回调地狱”,真的让我很难管理代码。

所以,我见过这样的问题:

...显然这样做的方法是使用 Promises - 不幸的是,我并不精通 JavaScript,我不能说是否或如何将其应用于此 dygraphs 代码。

所以,总结一下:有没有办法用 dygraphs 重写对异步加载 .csv 代码的调用,以便最终我可以按顺序编写类似这些命令的内容(在示例中的 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>

【问题讨论】:

    标签: javascript asynchronous dygraphs


    【解决方案1】:

    您基本上需要返回一个新的 Promise,然后在完成后调用 resolve 来代替下一个操作,例如,您的 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 的窍门,你就再也回不去了!如果这没有意义,请给我大喊。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-27
      • 2019-05-26
      相关资源
      最近更新 更多