From: David Eberlein Date: Thu, 2 May 2013 08:50:24 +0000 (+0200) Subject: Merge commit '9f890c23ad80924d0a30f3a14f8680b7c2d6318e' into fix-for-issue-451 X-Git-Tag: v1.0.0~31^2~2 X-Git-Url: https://adrianiainlam.tk/git/?a=commitdiff_plain;h=3fb516b5e8b861f15754428c6071688ab121bb6c;hp=9f890c23ad80924d0a30f3a14f8680b7c2d6318e;p=dygraphs.git Merge commit '9f890c23ad80924d0a30f3a14f8680b7c2d6318e' into fix-for-issue-451 Conflicts: dygraph-layout.js --- diff --git a/auto_tests/misc/local.html b/auto_tests/misc/local.html index 3841372..fb99a26 100644 --- a/auto_tests/misc/local.html +++ b/auto_tests/misc/local.html @@ -23,6 +23,7 @@ + diff --git a/auto_tests/tests/connect_separated_points.js b/auto_tests/tests/connect_separated_points.js new file mode 100644 index 0000000..4bfe09c --- /dev/null +++ b/auto_tests/tests/connect_separated_points.js @@ -0,0 +1,353 @@ +/** + * @fileoverview Test cases for the option "connectSeparatedPoints" especially for the scenario where not every series has a value for each timestamp. + * + * @author julian.eichstaedt@ch.sauter-bc.com (Fr. Sauter AG) + */ +var ConnectSeparatedPointsTestCase = TestCase("connect-separated-points"); + +ConnectSeparatedPointsTestCase.prototype.setUp = function() { + document.body.innerHTML = "
"; +}; + +ConnectSeparatedPointsTestCase.origFunc = Dygraph.getContext; + +ConnectSeparatedPointsTestCase.prototype.setUp = function() { + document.body.innerHTML = "
"; + Dygraph.getContext = function(canvas) { + return new Proxy(ConnectSeparatedPointsTestCase.origFunc(canvas)); + }; +}; + +ConnectSeparatedPointsTestCase.prototype.tearDown = function() { + Dygraph.getContext = ConnectSeparatedPointsTestCase.origFunc; +}; + +ConnectSeparatedPointsTestCase.prototype.testEdgePointsSimple = function() { + var opts = { + width: 480, + height: 320, + labels: ["x", "series1", "series2", "additionalSeries"], + connectSeparatedPoints: true, + dateWindow: [2.5,7.5] + }; + + var data = [ + [0,-1,0,null], + [1,null,2,null], + [2,null,4,null], + [3,0.5,0,null], + [4,1,-1,5], + [5,2,-2,6], + [6,2.5,-2.5,7], + [7,3,-3,null], + [8,4,null,null], + [9,4,-10,null] + ]; + + var graph = document.getElementById("graph"); + var g = new Dygraph(graph, data, opts); + + htx = g.hidden_ctx_; + + var attrs = {}; + + // Test if series1 is drawn correctly. + // ------------------------------------ + + // The first point of the first series + var x1 = data[0][0]; + var y1 = data[0][1]; + var xy1 = g.toDomCoords(x1, y1); + + // The next valid point of this series + var x2 = data[3][0]; + var y2 = data[3][1]; + var xy2 = g.toDomCoords(x2, y2); + + // Check if both points are connected at the left edge of the canvas and if the option "connectSeparatedPoints" works properly + // even if the point is outside the visible range and only one series has a valid value for this point. + CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); + + // Test if series2 is drawn correctly. + // ------------------------------------ + + // The last point of the second series. + var x2 = data[9][0]; + var y2 = data[9][2]; + var xy2 = g.toDomCoords(x2, y2); + + // The previous valid point of this series + var x1 = data[7][0]; + var y1 = data[7][2]; + var xy1 = g.toDomCoords(x1, y1); + + // Check if both points are connected at the right edge of the canvas and if the option "connectSeparatedPoints" works properly + // even if the point is outside the visible range and only one series has a valid value for this point. + CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); +}; + +ConnectSeparatedPointsTestCase.prototype.testEdgePointsCustomBars = function() { + var opts = { + width: 480, + height: 320, + labels: ["x", "series1", "series2", "additionalSeries"], + connectSeparatedPoints: true, + dateWindow: [2.5,7.5], + customBars: true + }; + + var data = [ + [0,[4,5,6], [1,2,3], [null, null, null]], + [1,[null,null,null], [2,3,4], [null, null, null]], + [2,[null,null,null], [3,4,5], [null, null, null]], + [3,[0,1,2], [2,3,4], [null, null, null]], + [4,[1,2,3], [2,3,4], [4, 5, 6]], + [5,[1,2,3], [3,4,5], [4, 5, 6]], + [6,[0,1,2], [4,5,6], [5, 6, 7]], + [7,[0,1,2], [4,5,6], [null, null, null]], + [8,[2,3,4], [null,null,null], [null, null, null]], + [9,[0,1,2], [2,4,9], [null, null, null]] + + ]; + + var graph = document.getElementById("graph"); + var g = new Dygraph(graph, data, opts); + + htx = g.hidden_ctx_; + + var attrs = {}; + + + // Test if values of the series1 are drawn correctly. + // ------------------------------------ + + // The first point of the first series + var x1 = data[0][0]; + var y1 = data[0][1][1]; + var xy1 = g.toDomCoords(x1, y1); + + // The next valid point of this series + var x2 = data[3][0]; + var y2 = data[3][1][1]; + var xy2 = g.toDomCoords(x2, y2); + + // Check if both points are connected at the left edge of the canvas and if the option "connectSeparatedPoints" works properly + // even if the point is outside the visible range and only one series has a valid value for this point. + CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); + + // Test if the custom bars of the series1 are drawn correctly + // -------------------------------------------- + + // The first min-point of this series + x1 = data[0][0]; + y1 = data[0][1][0]; + xy1 = g.toDomCoords(x1, y1); + + // The next valid min-point of the second series. + x2 = data[3][0]; + y2 = data[3][1][0]; + xy2 = g.toDomCoords(x2, y2); + + // Check if both points are connected at the left edge of the canvas and if the option "connectSeparatedPoints" works properly + // even if the point is outside the visible range and only one series has a valid value for this point. + CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); + + // The first max-point of this series + x1 = data[0][0]; + y1 = data[0][1][2]; + xy1 = g.toDomCoords(x1, y1); + + // The next valid max-point of the second series. + x2 = data[3][0]; + y2 = data[3][1][2]; + xy2 = g.toDomCoords(x2, y2); + + // Check if both points are connected at the left edge of the canvas and if the option "connectSeparatedPoints" works properly + // even if the point is outside the visible range and only one series has a valid value for this point. + CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); + + // Test if values of the series2 are drawn correctly. + // ------------------------------------ + + // The last point of the second series. + var x2 = data[9][0]; + var y2 = data[9][2][1]; + var xy2 = g.toDomCoords(x2, y2); + + // The previous valid point of this series + var x1 = data[7][0]; + var y1 = data[7][2][1]; + var xy1 = g.toDomCoords(x1, y1); + + // Check if both points are connected at the right edge of the canvas and if the option "connectSeparatedPoints" works properly + // even if the point is outside the visible range and only one series has a valid value for this point. + CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); + + // Test if the custom bars of the series2 are drawn correctly + // -------------------------------------------- + + // The last min-point of the second series. + x2 = data[9][0]; + y2 = data[9][2][0]; + xy2 = g.toDomCoords(x2, y2); + + // The previous valid min-point of this series + x1 = data[7][0]; + y1 = data[7][2][0]; + xy1 = g.toDomCoords(x1, y1); + + // Check if both points are connected at the right edge of the canvas and if the option "connectSeparatedPoints" works properly + // even if the point is outside the visible range and only one series has a valid value for this point. + CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); + + // The last max-point of the second series. + x2 = data[9][0]; + y2 = data[9][2][2]; + xy2 = g.toDomCoords(x2, y2); + + // The previous valid max-point of this series + x1 = data[7][0]; + y1 = data[7][2][2]; + xy1 = g.toDomCoords(x1, y1); + + // Check if both points are connected at the right edge of the canvas and if the option "connectSeparatedPoints" works properly + // even if the point is outside the visible range and only one series has a valid value for this point. + CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); +}; + +ConnectSeparatedPointsTestCase.prototype.testEdgePointsErrorBars = function() { + var opts = { + width: 480, + height: 320, + labels: ["x", "series1", "series2", "seriesTestHelper"], + connectSeparatedPoints: true, + dateWindow: [2,7.5], + errorBars: true + + }; + + var data = [ + [0,[5,1], [2,1], [null,null]], + [1,[null,null], [3,1], [null,null]], + [2,[null,null], [4,1], [null,null]], + [3,[1,1], [3,1], [null,null]], + [4,[2,1], [3,1], [5,1]], + [5,[2,1], [4,1], [5,1]], + [6,[1,1], [5,1], [6,1]], + [7,[1,1], [5,1], [null,null]], + [8,[3,1], [null,null], [null,null]], + [9,[1,1], [4,1], [null,null]] + + ]; + + var graph = document.getElementById("graph"); + var g = new Dygraph(graph, data, opts); + + htx = g.hidden_ctx_; + + var attrs = {}; + + + // Test if values of the series1 are drawn correctly. + // ------------------------------------ + + // The first point of the first series + var x1 = data[0][0]; + var y1 = data[0][1][0]; + var xy1 = g.toDomCoords(x1, y1); + + // The next valid point of this series + var x2 = data[3][0]; + var y2 = data[3][1][0]; + var xy2 = g.toDomCoords(x2, y2); + + // Check if both points are connected at the left edge of the canvas and if the option "connectSeparatedPoints" works properly + // even if the point is outside the visible range and only one series has a valid value for this point. + CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); + + // Test if the upper error bars of series1 are drawn correctly + // -------------------------------------------- + + // The first upper error-point of this series + x1 = data[0][0]; + var y1error = y1 + (data[0][1][1]*2); + xy1 = g.toDomCoords(x1, y1error); + + // The next valid upper error-point of the second series. + x2 = data[3][0]; + var y2error = y2 + (data[3][1][1]*2); + xy2 = g.toDomCoords(x2, y2error); + + // Check if both points are connected at the left edge of the canvas and if the option "connectSeparatedPoints" works properly + // even if the point is outside the visible range and only one series has a valid value for this point. + CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); + + // Test if the lower error bars of series1 are drawn correctly + // -------------------------------------------- + + // The first lower error-point of this series + x1 = data[0][0]; + y1error = y1 - (data[0][1][1]*2); + xy1 = g.toDomCoords(x1, y1error); + + //The next valid lower error-point of the second series. + x2 = data[3][0]; + y2error = y2 - (data[3][1][1]*2); + xy2 = g.toDomCoords(x2, y2error); + + // Check if both points are connected at the left edge of the canvas and if the option "connectSeparatedPoints" works properly + // even if the point is outside the visible range and only one series has a valid value for this point. + CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); + + + // Test if values of the series2 are drawn correctly. + // ------------------------------------ + + // The last point of this series + x2 = data[9][0]; + y2 = data[9][2][0]; + xy2 = g.toDomCoords(x2, y2); + + // The previous valid point of the first series + x1 = data[7][0]; + y1 = data[7][2][0]; + xy1 = g.toDomCoords(x1, y1); + + // Check if both points are connected at the right edge of the canvas and if the option "connectSeparatedPoints" works properly + // even if the point is outside the visible range and only one series has a valid value for this point. + CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); + + // Test if the upper error bars of series2 are drawn correctly + // -------------------------------------------- + + // The last upper error-point of the second series. + x2 = data[9][0]; + var y2error = y2 + (data[9][2][1]*2); + xy2 = g.toDomCoords(x2, y2error); + + // The previous valid upper error-point of this series + x1 = data[7][0]; + var y1error = y1 + (data[7][2][1]*2); + xy1 = g.toDomCoords(x1, y1error); + + // Check if both points are connected at the right edge of the canvas and if the option "connectSeparatedPoints" works properly + // even if the point is outside the visible range and only one series has a valid value for this point. + CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); + + // Test if the lower error bars of series1 are drawn correctly + // -------------------------------------------- + + // The last lower error-point of the second series. + x2 = data[9][0]; + y2error = y2 - (data[9][2][1]*2); + xy2 = g.toDomCoords(x2, y2error); + + // The previous valid lower error-point of this series + x1 = data[7][0]; + y1error = y1 - (data[7][2][1]*2); + xy1 = g.toDomCoords(x1, y1error); + + // Check if both points are connected at the right edge of the canvas and if the option "connectSeparatedPoints" works properly + // even if the point is outside the visible range and only one series has a valid value for this point. + CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs); +}; diff --git a/auto_tests/tests/step_plot_per_series.js b/auto_tests/tests/step_plot_per_series.js index bf25948..d1bf323 100644 --- a/auto_tests/tests/step_plot_per_series.js +++ b/auto_tests/tests/step_plot_per_series.js @@ -3,7 +3,7 @@ * * @author julian.eichstaedt@ch.sauter-bc.com (Fr. Sauter AG) */ -var StepTestCase = TestCase("step_plot_per_series"); +var StepTestCase = TestCase("step-plot-per-series"); StepTestCase.prototype.setUp = function() { document.body.innerHTML = "
"; diff --git a/dygraph-layout.js b/dygraph-layout.js index dd449e4..9e042f2 100644 --- a/dygraph-layout.js +++ b/dygraph-layout.js @@ -52,12 +52,6 @@ DygraphLayout.prototype.addDataset = function(setname, set_xy) { this.setNames.push(setname); }; -/** - * Returns the box which the chart should be drawn in. This is the canvas's - * box, less space needed for the axis and chart labels. - * - * @return {{x: number, y: number, w: number, h: number}} - */ DygraphLayout.prototype.getPlotArea = function() { return this.area_; }; @@ -217,10 +211,7 @@ DygraphLayout.prototype._evaluateLineCharts = function() { // on chrome+linux, they are 6 times more expensive than iterating through the // points and drawing the lines. The brunt of the cost comes from allocating // the |point| structures. - var boundaryIdStart = 0; - if (this.dygraph_.boundaryIds_.length > 0) { - boundaryIdStart = this.dygraph_.boundaryIds_[this.dygraph_.boundaryIds_.length-1][0]; - } + var boundaryIdStart = this.dygraph_.getLeftBoundary_(); for (var setIdx = 0; setIdx < this.datasets.length; setIdx++) { var dataset = this.datasets[setIdx]; var setName = this.setNames[setIdx]; diff --git a/dygraph.js b/dygraph.js index c09f9dd..7ad3cdb 100644 --- a/dygraph.js +++ b/dygraph.js @@ -2296,7 +2296,9 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) { // Prune down to the desired range, if necessary (for zooming) // Because there can be lines going to points outside of the visible area, // we actually prune to visible points, plus one on either side. - var bars = this.attr_("errorBars") || this.attr_("customBars"); + var errorBars = this.attr_("errorBars"); + var customBars = this.attr_("customBars"); + var bars = errorBars || customBars; if (dateWindow) { var low = dateWindow[0]; var high = dateWindow[1]; @@ -2313,13 +2315,50 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) { } } if (firstIdx === null) firstIdx = 0; - if (firstIdx > 0) firstIdx--; + var correctedFirstIdx = firstIdx; + + var isInvalidValue = true; + while(isInvalidValue && correctedFirstIdx > 0){ + correctedFirstIdx--; + + if(bars){ + if(customBars){ + isInvalidValue = series[correctedFirstIdx][1][1] === null; + } else if(errorBars){ + isInvalidValue = series[correctedFirstIdx][1][0] === null; + } + } else{ + isInvalidValue = series[correctedFirstIdx][1] === null; + } + } + if (lastIdx === null) lastIdx = series.length - 1; - if (lastIdx < series.length - 1) lastIdx++; - boundaryIds[i-1] = [firstIdx, lastIdx]; + var correctedLastIdx = lastIdx; + isInvalidValue = true; + while(isInvalidValue && correctedLastIdx < series.length - 1){ + correctedLastIdx++; + + if(bars){ + if(customBars){ + isInvalidValue = series[correctedLastIdx][1][1] === null; + } else if(errorBars){ + isInvalidValue = series[correctedLastIdx][1][0] === null; + } + } else{ + isInvalidValue = series[correctedLastIdx][1] === null; + } + } + + boundaryIds[i-1] = [(firstIdx > 0) ? firstIdx - 1 : firstIdx, (lastIdx < series.length - 1) ? lastIdx + 1 : lastIdx]; + + if(correctedFirstIdx!==firstIdx) + pruned.push(series[correctedFirstIdx]); for (k = firstIdx; k <= lastIdx; k++) { - pruned.push(series[k]); + pruned.push(series[k]); } + if(correctedLastIdx !== lastIdx) + pruned.push(series[correctedLastIdx]); + series = pruned; } else { boundaryIds[i-1] = [0, series.length-1]; diff --git a/tests/independent-series.html b/tests/independent-series.html index eb9a604..6665e73 100644 --- a/tests/independent-series.html +++ b/tests/independent-series.html @@ -101,6 +101,107 @@ ); +

Behavior at the edges of the panel for independent series

+

In case only a part of the whole data is visible (e.g. after zooming in) the lines are + drawn to the respective next valid point outside the visible area.

+ + + + +
+ +
+ + + + + + + + + +
xA
02
13
23
44
53
63
73
84
+
+ + + + + + +
xB
01
12
21
82
+
+ +
+

Both graphs have no value at the right edge of the panel (x=3). The lines that are drawn to the right edge are determined by their respective next valid value outside the visible area. + Therefore it is neither necessary that the next valid values are on the same point nor that they have the same index (index 4 for the green line and index 8 for the blue line).

+

Use double click to unzoom and see the currently invisible points

+ + + + + + + +
+ Index +
 
+0
+1
+2
+3
+4
+5
+6
+7
+8
+
+ (CSV) +
x,A,B
+0,2,1
+1,3,2
+2,3,1
+3,,
+4,4,
+5,3,
+6,3,
+7,3,
+8,4,2
+
+ (native) +
[ 
+  [0, 2, 1], 
+  [1, 3, 2],
+  [2, 3, 1],
+  [3, null, null],
+  [4, 4, null],
+  [5, 3, null],
+  [6, 3, null],
+  [7, 3, null],
+  [8, 4, 2] ]
+
+

Encoding a gap

There's one extra wrinkle. What if one of the series has a missing value, i.e. what if your series are something like

@@ -127,12 +228,12 @@ -
+

The gap would normally be encoded as a null, or missing value. But when you use connectSeparatedPoints, that has a special meaning. Instead, you have to use NaN. This is a bit of a hack, but it gets the job done.