Merge remote-tracking branch 'official/master' into per-axis-grid
authorDavid Eberlein <david.eberlein@ch.sauter-bc.com>
Wed, 8 May 2013 06:42:37 +0000 (08:42 +0200)
committerDavid Eberlein <david.eberlein@ch.sauter-bc.com>
Wed, 8 May 2013 06:42:37 +0000 (08:42 +0200)
Conflicts:
tests/two-axes.html

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
plugins/axes.js
tests/independent-series.html

index de1d483..1604e8e 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..4b600f7 100644 (file)
@@ -217,10 +217,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 82725d5..5e4ba62 100644 (file)
@@ -2284,6 +2284,17 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
   var datasets = [];
   var extremes = {};  // series name -> [low, high]
   var i, j, k;
+  var errorBars = this.attr_("errorBars");
+  var customBars = this.attr_("customBars");
+  var bars = errorBars || customBars;
+  var isValueNull = function(sample) {
+    if (!bars) {
+      return sample[1] === null;
+    } else {
+      return customBars ? sample[1][1] === null : 
+        errorBars ? sample[1][0] === null : false;
+    }
+  };
 
   // Loop over the fields (series).  Go from the last to the first,
   // because if they're stacked that's how we accumulate the values.
@@ -2302,11 +2313,11 @@ 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");
     if (dateWindow) {
       var low = dateWindow[0];
       var high = dateWindow[1];
       var pruned = [];
+
       // TODO(danvk): do binary search instead of linear search.
       // TODO(danvk): pass firstIdx and lastIdx directly to the renderer.
       var firstIdx = null, lastIdx = null;
@@ -2318,14 +2329,36 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
           lastIdx = k;
         }
       }
+
       if (firstIdx === null) firstIdx = 0;
-      if (firstIdx > 0) firstIdx--;
+      var correctedFirstIdx = firstIdx;
+      var isInvalidValue = true;
+      while (isInvalidValue && correctedFirstIdx > 0) {
+        correctedFirstIdx--;
+        isInvalidValue = isValueNull(series[correctedFirstIdx]);
+      }
+
       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++;
+        isInvalidValue = isValueNull(series[correctedLastIdx]);
+      }
+
+      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]);
       }
+      if (correctedLastIdx !== lastIdx) {
+        pruned.push(series[correctedLastIdx]);
+      }
+
       series = pruned;
     } else {
       boundaryIds[i-1] = [0, series.length-1];
index f7865c8..cacda86 100644 (file)
@@ -71,9 +71,12 @@ axes.prototype.layout = function(e) {
   }
 
   if (g.numAxes() == 2) {
-    // TODO(danvk): per-axis setting.
-    var w = g.getOption('yAxisLabelWidth') + 2 * g.getOption('axisTickSize');
-    e.reserveSpaceRight(w);
+    // TODO(danvk): introduce a 'drawAxis' per-axis property.
+    if (g.getOption('drawYAxis')) {
+      // TODO(danvk): per-axis setting.
+      var w = g.getOption('yAxisLabelWidth') + 2 * g.getOption('axisTickSize');
+      e.reserveSpaceRight(w);
+    }
   } else if (g.numAxes() > 2) {
     g.error("Only two y-axes are supported at this time. (Trying " +
             "to use " + g.numAxes() + ")");
index eb9a604..bddcc6b 100644 (file)
     </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" +
     </td>
     </tr></table>
 
+    <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>
+
   </body>
 </html>