Fix connectSeparatedPoints, add test
[dygraphs.git] / dygraph.js
index 3502c31..b52a689 100644 (file)
@@ -1509,17 +1509,18 @@ Dygraph.prototype.eventToDomCoords = function(event) {
  * @private
  */
 Dygraph.prototype.findClosestRow = function(domX) {
-  var minDistX = null;
+  var minDistX = Infinity;
   var idx = -1;
   var points = this.layout_.points;
   var l = points.length;
   for (var i = 0; i < l; i++) {
     var point = points[i];
-    if (point === null) continue;
+    if (!Dygraph.isValidPoint(point, true)) continue;
     var dist = Math.abs(point.canvasx - domX);
-    if (minDistX !== null && dist >= minDistX) continue;
-    minDistX = dist;
-    idx = i;
+    if (dist < minDistX) {
+      minDistX = dist;
+      idx = i;
+    }
   }
   return this.idxToRow_(idx);
 };
@@ -1537,7 +1538,7 @@ Dygraph.prototype.findClosestRow = function(domX) {
  * @private
  */
 Dygraph.prototype.findClosestPoint = function(domX, domY) {
-  var minDist = null;
+  var minDist = Infinity;
   var idx = -1;
   var points = this.layout_.points;
   var dist, dx, dy, point, closestPoint, closestSeries;
@@ -1546,20 +1547,21 @@ Dygraph.prototype.findClosestPoint = function(domX, domY) {
     var len = this.layout_.setPointsLengths[setIdx];
     for (var i = 0; i < len; ++i) {
       var point = points[first + i];
-      if (point === null) continue;
+      if (!Dygraph.isValidPoint(point)) continue;
       dx = point.canvasx - domX;
       dy = point.canvasy - domY;
       dist = dx * dx + dy * dy;
-      if (minDist !== null && dist >= minDist) continue;
-      minDist = dist;
-      closestPoint = point;
-      closestSeries = setIdx;
-      idx = i;
+      if (dist < minDist) {
+        minDist = dist;
+        closestPoint = point;
+        closestSeries = setIdx;
+        idx = i;
+      }
     }
   }
   var name = this.layout_.setNames[closestSeries];
   return {
-    row: idx,
+    row: idx + this.getLeftBoundary_(),
     seriesName: name,
     point: closestPoint
   };
@@ -1579,35 +1581,43 @@ Dygraph.prototype.findClosestPoint = function(domX, domY) {
  */
 Dygraph.prototype.findStackedPoint = function(domX, domY) {
   var row = this.findClosestRow(domX);
+  var boundary = this.getLeftBoundary_();
+  var rowIdx = row - boundary;
   var points = this.layout_.points;
   var closestPoint, closestSeries;
   for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
     var first = this.layout_.setPointsOffsets[setIdx];
     var len = this.layout_.setPointsLengths[setIdx];
-    if (row >= len) continue;
-    var p1 = points[first + row];
+    if (rowIdx >= len) continue;
+    var p1 = points[first + rowIdx];
+    if (!Dygraph.isValidPoint(p1)) continue;
     var py = p1.canvasy;
-    if (domX > p1.canvasx && row + 1 < len) {
+    if (domX > p1.canvasx && rowIdx + 1 < len) {
       // interpolate series Y value using next point
-      var p2 = points[first + row + 1];
-      var dx = p2.canvasx - p1.canvasx;
-      if (dx > 0) {
-        var r = (domX - p1.canvasx) / dx;
-        py += r * (p2.canvasy - p1.canvasy);
+      var p2 = points[first + rowIdx + 1];
+      if (Dygraph.isValidPoint(p2)) {
+        var dx = p2.canvasx - p1.canvasx;
+        if (dx > 0) {
+          var r = (domX - p1.canvasx) / dx;
+          py += r * (p2.canvasy - p1.canvasy);
+        }
       }
-    } else if (domX < p1.canvasx && row > 0) {
+    } else if (domX < p1.canvasx && rowIdx > 0) {
       // interpolate series Y value using previous point
-      var p0 = points[first + row - 1];
-      var dx = p1.canvasx - p0.canvasx;
-      if (dx > 0) {
-        var r = (p1.canvasx - domX) / dx;
-        py += r * (p0.canvasy - p1.canvasy);
+      var p0 = points[first + rowIdx - 1];
+      if (Dygraph.isValidPoint(p0)) {
+        var dx = p1.canvasx - p0.canvasx;
+        if (dx > 0) {
+          var r = (p1.canvasx - domX) / dx;
+          py += r * (p0.canvasy - p1.canvasy);
+        }
       }
     }
     // Stop if the point (domX, py) is above this series' upper edge
-    if (setIdx > 0 && py >= domY) break;
-    closestPoint = p1;
-    closestSeries = setIdx;
+    if (setIdx == 0 || py < domY) {
+      closestPoint = p1;
+      closestSeries = setIdx;
+    }
   }
   var name = this.layout_.setNames[closestSeries];
   return {
@@ -1655,6 +1665,18 @@ Dygraph.prototype.mouseMove_ = function(event) {
 };
 
 /**
+ * Fetch left offset from first defined boundaryIds record (see bug #236).
+ */
+Dygraph.prototype.getLeftBoundary_ = function() {
+  for (var i = 0; i < this.boundaryIds_.length; i++) {
+    if (this.boundaryIds_[i] !== undefined) {
+      return this.boundaryIds_[i][0];
+    }
+  }
+  return 0;
+};
+
+/**
  * Transforms layout_.points index into data row number.
  * @param int layout_.points index
  * @return int row number, or -1 if none could be found.
@@ -1663,19 +1685,11 @@ Dygraph.prototype.mouseMove_ = function(event) {
 Dygraph.prototype.idxToRow_ = function(idx) {
   if (idx < 0) return -1;
 
-  // make sure that you get the boundaryIds record which is also defined (see bug #236)
-  var boundaryIdx = -1;
-  for (var i = 0; i < this.boundaryIds_.length; i++) {
-    if (this.boundaryIds_[i] !== undefined) {
-      boundaryIdx = i;
-      break;
-    }
-  }
-  if (boundaryIdx < 0) return -1;
+  var boundary = this.getLeftBoundary_();
   for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
     var set = this.layout_.datasets[setIdx];
     if (idx < set.length) {
-      return this.boundaryIds_[boundaryIdx][0] + idx;
+      return boundary + idx;
     }
     idx -= set.length;
   }
@@ -1848,10 +1862,8 @@ Dygraph.prototype.setLegendHTML_ = function(x, sel_points) {
 Dygraph.prototype.animateSelection_ = function(direction) {
   var totalSteps = 10;
   var millis = 30;
-  if (this.fadeLevel === undefined) {
-    this.fadeLevel = 0;
-    this.animateId = 0;
-  }
+  if (this.fadeLevel === undefined) this.fadeLevel = 0;
+  if (this.animateId === undefined) this.animateId = 0;
   var start = this.fadeLevel;
   var steps = direction < 0 ? start : totalSteps - start;
   if (steps <= 0) {
@@ -1971,12 +1983,7 @@ Dygraph.prototype.setSelection = function(row, opt_seriesName) {
   var pos = 0;
 
   if (row !== false) {
-    for (var i = 0; i < this.boundaryIds_.length; i++) {
-      if (this.boundaryIds_[i] !== undefined) {
-        row -= this.boundaryIds_[i][0];
-        break;
-      }
-    }
+    row -= this.getLeftBoundary_();
   }
 
   var changed = false;
@@ -1992,7 +1999,7 @@ Dygraph.prototype.setSelection = function(row, opt_seriesName) {
           point = this.layout_.unstackPointAtIndex(pos+row);
         }
 
-        this.selPoints_.push(point);
+        if (!(point.yval === null)) this.selPoints_.push(point);
       }
       pos += set.length;
     }
@@ -2064,7 +2071,7 @@ Dygraph.prototype.getSelection = function() {
 
   for (var row=0; row<this.layout_.points.length; row++ ) {
     if (this.layout_.points[row].x == this.selPoints_[0].x) {
-      return row + this.boundaryIds_[0][0];
+      return row + this.getLeftBoundary_();
     }
   }
   return -1;
@@ -2190,9 +2197,8 @@ Dygraph.prototype.predraw_ = function() {
   // rolling averages.
   this.rolledSeries_ = [null];  // x-axis is the first series and it's special
   for (var i = 1; i < this.numColumns(); i++) {
-    var connectSeparatedPoints = this.attr_('connectSeparatedPoints', i);
-    var logScale = this.attr_('logscale', i);
-    var series = this.extractSeries_(this.rawData_, i, logScale, connectSeparatedPoints);
+    var logScale = this.attr_('logscale', i); // TODO(klausw): this looks wrong
+    var series = this.extractSeries_(this.rawData_, i, logScale);
     series = this.rollingAverage(series, this.rollPeriod_);
     this.rolledSeries_.push(series);
   }
@@ -2289,6 +2295,11 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
         }
 
         actual_y = series[j][1];
+        if (actual_y === null) {
+          series[j] = [x, null];
+          continue;
+        }
+
         cumulative_y[x] += actual_y;
 
         series[j] = [x, cumulative_y[x]];
@@ -2675,24 +2686,19 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) {
  * 
  * @private
  */
-Dygraph.prototype.extractSeries_ = function(rawData, i, logScale, connectSeparatedPoints) {
+Dygraph.prototype.extractSeries_ = function(rawData, i, logScale) {
   var series = [];
   for (var j = 0; j < rawData.length; j++) {
     var x = rawData[j][0];
     var point = rawData[j][i];
     if (logScale) {
       // On the log scale, points less than zero do not exist.
-      // This will create a gap in the chart. Note that this ignores
-      // connectSeparatedPoints.
+      // This will create a gap in the chart.
       if (point <= 0) {
         point = null;
       }
-      series.push([x, point]);
-    } else {
-      if (point !== null || !connectSeparatedPoints) {
-        series.push([x, point]);
-      }
     }
+    series.push([x, point]);
   }
   return series;
 };