Merge commit '9f890c23ad80924d0a30f3a14f8680b7c2d6318e' into fix-for-issue-451
authorDavid Eberlein <david.eberlein@ch.sauter-bc.com>
Thu, 2 May 2013 08:50:24 +0000 (10:50 +0200)
committerDavid Eberlein <david.eberlein@ch.sauter-bc.com>
Thu, 2 May 2013 08:50:24 +0000 (10:50 +0200)
Conflicts:
dygraph-layout.js

auto_tests/misc/local.html
auto_tests/tests/connect_separated_points.js [new file with mode: 0644]
auto_tests/tests/step_plot_per_series.js
dygraph-layout.js
dygraph.js
tests/independent-series.html

index 3841372..fb99a26 100644 (file)
@@ -23,6 +23,7 @@
   <script type="text/javascript" src="../tests/axis_labels.js"></script>
   <script type="text/javascript" src="../tests/axis_labels-deprecated.js"></script>
   <script type="text/javascript" src="../tests/callback.js"></script>
+  <script type="text/javascript" src="../tests/connect_separated_points.js"></script>
   <script type="text/javascript" src="../tests/css.js"></script>
   <script type="text/javascript" src="../tests/custom_bars.js"></script>
   <script type="text/javascript" src="../tests/date_formats.js"></script>
diff --git a/auto_tests/tests/connect_separated_points.js b/auto_tests/tests/connect_separated_points.js
new file mode 100644 (file)
index 0000000..4bfe09c
--- /dev/null
@@ -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 = "<div id='graph'></div>";
+};
+
+ConnectSeparatedPointsTestCase.origFunc = Dygraph.getContext;
+
+ConnectSeparatedPointsTestCase.prototype.setUp = function() {
+  document.body.innerHTML = "<div id='graph'></div>";
+  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);
+};
index bf25948..d1bf323 100644 (file)
@@ -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 = "<div id='graph'></div>";
index dd449e4..9e042f2 100644 (file)
@@ -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];
index c09f9dd..7ad3cdb 100644 (file)
@@ -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];
index eb9a604..6665e73 100644 (file)
     );
     </script>
 
+    <h3>Behavior at the edges of the panel for independent series</h3>
+    <p> 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. </p>
+              
+    <table><tr>
+    <td valign=top>
+    <table>
+      <table class=thinborder>
+        <tr><th>x</th><th>A</th></tr>
+        <tr><td>0</td><td>2</td></tr>
+        <tr><td>1</td><td>3</td></tr>
+        <tr><td>2</td><td>3</td></tr>
+        <tr><td>4</td><td>4</td></tr>
+        <tr><td>5</td><td>3</td></tr>
+        <tr><td>6</td><td>3</td></tr>
+        <tr><td>7</td><td>3</td></tr>
+        <tr><td>8</td><td>4</td></tr>
+      </table>
+    </td>
+    <td valign=top style="padding-left:25px;">
+      <table class=thinborder>
+        <tr><th>x</th><th>B</th></tr>
+        <tr><td>0</td><td>1</td></tr>
+        <tr><td>1</td><td>2</td></tr>
+        <tr><td>2</td><td>1</td></tr>
+        <tr><td>8</td><td>2</td></tr>
+      </table>
+    </td>
+    </tr></table>
+
+    <div id="graph2" style="float: right; margin-right: 50px; width: 400px; height: 300px;"></div>
+    <p>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).</p> 
+    <p>Use double click to unzoom and see the currently invisible points</p> 
+    
+    <script type="text/javascript">
+    g2 = new Dygraph(
+      document.getElementById("graph2"),
+"x,A,B  \n" +
+"0,2,1   \n" +
+"1,3,2   \n" +
+"2,3,1   \n" +
+"3,,   \n" +
+"4,4,   \n" +
+"5,3,   \n" +
+"6,3,   \n" +
+"7,3,   \n" +
+"8,4,2   \n"
+
+      , {
+        connectSeparatedPoints: true,
+        drawPoints: true,
+        pointSize: 3,
+        highlightCircleSize: 5,
+        dateWindow: [0,3]
+      }
+    );
+    </script>
+    
+    <table><tr>
+    <td valign=top>
+    Index
+    <pre>&nbsp;
+0
+1
+2
+3
+4
+5
+6
+7
+8</pre>
+    </td>
+    <td valign=top>
+    (CSV)
+    <pre>x,A,B
+0,2,1
+1,3,2
+2,3,1
+3,,
+4,4,
+5,3,
+6,3,
+7,3,
+8,4,2</pre>
+    </td>
+    <td valign=top style="padding-left: 25px;">
+    (native)
+    <pre>[ 
+  [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] ]</pre>
+    </td>
+    </tr></table>
+    
     <h3>Encoding a gap</h3>
     <p>There's one extra wrinkle. What if one of the series has a missing
     value, i.e. what if your series are something like </p>
     </td>
     </tr></table>
 
-    <div id="graph2" style="float: right; margin-right: 50px; width: 400px; height: 300px;"></div>
+    <div id="graph3" style="float: right; margin-right: 50px; width: 400px; height: 300px;"></div>
     <p>The gap would normally be encoded as a null, or missing value. But when you use <code>connectSeparatedPoints</code>, that has a special meaning. Instead, you have to use <code>NaN</code>. This is a bit of a hack, but it gets the job done.</p> 
 
     <script type="text/javascript">
     g2 = new Dygraph(
-      document.getElementById("graph2"),
+      document.getElementById("graph3"),
 "x,A,B  \n" +
 "1,,3   \n" +
 "2,2,   \n" +