I am trying to use dygraphs, to make a sort-of multichannel/multitrack waveform viewer, to browse e.g. digital logic signals. The example here, renders something like this:
Obviously, I'd need the two tracks synchronized (i.e. when I zoom in one track, the other should correspondingly zoom in) - otherwise, there is not much use of this.
I have prepared a gist with the code discussed here: https://gist.github.com/sdaau/2d21ff7d88f03118bd5b31fac66d2588 ; and it should be possible to render the example via bl.ocks.org (see also here), or if you run the .html files locally in your browser (I've tested with Firefox 74)
The problem is:
- When the .csv data is provided inline in the JavaScript code, synchronous calls work, - and there is no problem, synchronization works (see: test_01_dygraphs_sync_csv.html
- When the .csv data is provided via remote URLs, synchronous calls do not work - and synchronization does not work (see: test_02_dygraphs_sync_csv.html)
- When the .csv data is provided via remote URLs, and the code is rewritten asynchronously - synchronization then works (see: test_03_dygraphs_async_csv.html)
The reason for this is explained in Is there an Dygraph has loaded event? :
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.
However, the problem is, I need to use .csv URLs, but I logically always think in terms of synchronous calls with this; e.g.:
load_g1();
load_g2();
sync_g1g2();
... which, as shown, breaks axes synchronization; and the kind of asynchronous calling which actually works with synchronization:
function load_g1() {
...
g1.ready(function() {
function load_g2() {
...
g2.ready(function() {
sync_g1g2();
});
}
load_g2();
});
}
... that is, "callback hell", really makes it difficult for me to manage the code.
So, I've seen questions like:
- Rewriting JS app from Synchronous calls to Asynchronous
- How to rewrite this asynchronous method to return the value synchronously?
... and apparently the way to do this is to use Promises - unfortunately, I'm not all that versed in JavaScript, and I cannot say if or how can this be applied to this dygraphs code.
So, to summarize: is there a way to rewrite the calls to asynchronous loading of .csv code with dygraphs, so that ultimately I can write something like these commands, in a sequence (in Initialize() in the example)? :
load_g1();
load_g2();
sync_g1g2();
For reference, I'll paste the code of test_03_dygraphs_async_csv.html
below:
<!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>