first stab at keying DygraphLayout.points on setIdx
authorDan Vanderkam <dan@dygraphs.com>
Fri, 20 Jul 2012 17:58:53 +0000 (13:58 -0400)
committerDan Vanderkam <dan@dygraphs.com>
Fri, 20 Jul 2012 17:58:53 +0000 (13:58 -0400)
dygraph-canvas.js
dygraph-layout.js
dygraph.js

index 5b7c973..9263250 100644 (file)
@@ -247,19 +247,17 @@ DygraphCanvasRenderer._predicateThatSkipsEmptyPoints =
  * @private
  */
 DygraphCanvasRenderer.prototype._drawStyledLine = function(
-    ctx, i, setName, color, strokeWidth, strokePattern, drawPoints,
+    ctx, setIdx, setName, color, strokeWidth, strokePattern, drawPoints,
     drawPointCallback, pointSize) {
   // TODO(konigsberg): Compute attributes outside this method call.
   var stepPlot = this.attr_("stepPlot");
-  var firstIndexInSet = this.layout.setPointsOffsets[i];
-  var setLength = this.layout.setPointsLengths[i];
-  var points = this.layout.points;
   if (!Dygraph.isArrayLike(strokePattern)) {
     strokePattern = null;
   }
   var drawGapPoints = this.dygraph_.attr_('drawGapEdgePoints', setName);
 
-  var iter = Dygraph.createIterator(points, firstIndexInSet, setLength,
+  var points = this.layout.points[setIdx];
+  var iter = Dygraph.createIterator(points, 0, points.length,
       DygraphCanvasRenderer._getIteratorPredicate(
           this.attr_("connectSeparatedPoints")));
 
@@ -428,11 +426,14 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
   // unaffected.  An alternative is to reduce the stroke width in the
   // transformed coordinate space, but you can't specify different values for
   // each dimension (as you can with .scale()). The speedup here is ~12%.
-  var points = this.layout.points;
-  for (i = points.length; i--;) {
-    var point = points[i];
-    point.canvasx = this.area.w * point.x + this.area.x;
-    point.canvasy = this.area.h * point.y + this.area.y;
+  var sets = this.layout.points;
+  for (i = sets.length; i--;) {
+    var points = sets[i];
+    for (var j = points.length; j--;) {
+      var point = points[j];
+      point.canvasx = this.area.w * point.x + this.area.x;
+      point.canvasy = this.area.h * point.y + this.area.y;
+    }
   }
 
   // Draw any "fills", i.e. error bars or the filled area under a series.
index a5e6908..e25afba 100644 (file)
@@ -35,6 +35,7 @@ var DygraphLayout = function(dygraph) {
   this.setNames = [];
   this.annotations = [];
   this.yAxes_ = null;
+  this.points = null;
 
   // TODO(danvk): it's odd that xTicks_ and yTicks_ are inputs, but xticks and
   // yticks are outputs. Clean this up.
@@ -218,35 +219,24 @@ DygraphLayout._calcYNormal = function(axis, value) {
 };
 
 DygraphLayout.prototype._evaluateLineCharts = function() {
-  // An array to keep track of how many points will be drawn for each set.
-  // This will allow for the canvas renderer to not have to check every point
-  // for every data set since the points are added in order of the sets in
-  // datasets.
-  this.setPointsLengths = [];
-  this.setPointsOffsets = [];
-
   var connectSeparated = this.attr_('connectSeparatedPoints');
+
+  // series index -> point index in series -> |point| structure
+  this.points = new Array(this.datasets.length);
+
   // TODO(bhs): these loops are a hot-spot for high-point-count charts. In fact,
   // 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 i = 0;
-  var setIdx;
-
-  // Preallocating the size of points reduces reallocations, and therefore,
-  // calls to collect garbage.
-  var totalPoints = 0;
-  for (setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
-    totalPoints += this.datasets[setIdx].length;
-  }
-  this.points = new Array(totalPoints);
-
-  for (setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
-    this.setPointsOffsets.push(i);
+  for (var setIdx = 0; setIdx < this.datasets.length; setIdx++) {
     var dataset = this.datasets[setIdx];
     var setName = this.setNames[setIdx];
     var axis = this.dygraph_.axisPropertiesForSeries(setName);
 
+    // Preallocating the size of points reduces reallocations, and therefore,
+    // calls to collect garbage.
+    var seriesPoints = new Array(dataset.length);
+
     for (var j = 0; j < dataset.length; j++) {
       var item = dataset[j];
       var xValue = DygraphLayout.parseFloat_(item[0]);
@@ -257,20 +247,21 @@ DygraphLayout.prototype._evaluateLineCharts = function() {
       // Range from 0-1 where 0 represents top and 1 represents bottom
       var yNormal = DygraphLayout._calcYNormal(axis, yValue);
 
+      // TODO(danvk): drop the point in this case, don't null it.
+      // The nulls create complexity in DygraphCanvasRenderer._drawSeries.
       if (connectSeparated && item[1] === null) {
         yValue = null;
       }
-      this.points[i] = {
-        // TODO(danvk): here
+      seriesPoints[j] = {
         x: xNormal,
         y: yNormal,
         xval: xValue,
         yval: yValue,
-        name: setName
+        name: setName  // TODO(danvk): is this really necessary?
       };
-      i++;
     }
-    this.setPointsLengths.push(i - this.setPointsOffsets[setIdx]);
+
+    this.points[setIdx] = seriesPoints;
   }
 };
 
@@ -326,6 +317,7 @@ DygraphLayout.prototype.evaluateWithError = function() {
   // Copy over the error terms
   var i = 0;  // index in this.points
   for (var setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
+    var points = this.points[setIdx];
     var j = 0;
     var dataset = this.datasets[setIdx];
     var setName = this.setNames[setIdx];
@@ -335,15 +327,15 @@ DygraphLayout.prototype.evaluateWithError = function() {
       var xv = DygraphLayout.parseFloat_(item[0]);
       var yv = DygraphLayout.parseFloat_(item[1]);
 
-      if (xv == this.points[i].xval &&
-          yv == this.points[i].yval) {
+      if (xv == points[j].xval &&
+          yv == points[j].yval) {
         var errorMinus = DygraphLayout.parseFloat_(item[2]);
         var errorPlus = DygraphLayout.parseFloat_(item[3]);
 
         var yv_minus = yv - errorMinus;
         var yv_plus = yv + errorPlus;
-        this.points[i].y_top = DygraphLayout._calcYNormal(axis, yv_minus);
-        this.points[i].y_bottom = DygraphLayout._calcYNormal(axis, yv_plus);
+        points[j].y_top = DygraphLayout._calcYNormal(axis, yv_minus);
+        points[j].y_bottom = DygraphLayout._calcYNormal(axis, yv_plus);
       }
     }
   }
@@ -367,12 +359,15 @@ DygraphLayout.prototype._evaluateAnnotations = function() {
   }
 
   // TODO(antrob): loop through annotations not points.
-  for (i = 0; i < this.points.length; i++) {
-    var p = this.points[i];
-    var k = p.xval + "," + p.name;
-    if (k in annotations) {
-      p.annotation = annotations[k];
-      this.annotated_points.push(p);
+  for (var setIdx = 0; setIdx < this.points.length; setIdx++) {
+    var points = this.points[setIdx];
+    for (i = 0; i < points.length; i++) {
+      var p = points[i];
+      var k = p.xval + "," + p.name;
+      if (k in annotations) {
+        p.annotation = annotations[k];
+        this.annotated_points.push(p);
+      }
     }
   }
 };
@@ -395,8 +390,8 @@ DygraphLayout.prototype.removeAllDatasets = function() {
  * Return a copy of the point at the indicated index, with its yval unstacked.
  * @param int index of point in layout_.points
  */
-DygraphLayout.prototype.unstackPointAtIndex = function(idx) {
-  var point = this.points[idx];
+DygraphLayout.prototype.unstackPointAtIndex = function(setIdx, row) {
+  var point = this.points[setIdx][row];
   // If the point is missing, no unstacking is necessary
   if (!point.yval) {
     return point;
@@ -414,9 +409,10 @@ DygraphLayout.prototype.unstackPointAtIndex = function(idx) {
 
   // The unstacked yval is equal to the current yval minus the yval of the
   // next point at the same xval.
-  for (var i = idx+1; i < this.points.length; i++) {
-    if ((this.points[i].xval == point.xval) && this.points[i].yval) {
-      unstackedPoint.yval -= this.points[i].yval;
+  var points = this.points[setIdx];
+  for (var i = row + 1; i < points.length; i++) {
+    if ((points[i].xval == point.xval) && points[i].yval) {
+      unstackedPoint.yval -= points[i].yval;
       break;
     }
   }
index 5e10d06..f97494a 100644 (file)
@@ -1597,19 +1597,25 @@ Dygraph.prototype.eventToDomCoords = function(event) {
  */
 Dygraph.prototype.findClosestRow = function(domX) {
   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 (!Dygraph.isValidPoint(point, true)) continue;
-    var dist = Math.abs(point.canvasx - domX);
-    if (dist < minDistX) {
-      minDistX = dist;
-      idx = i;
+  var pointIdx = -1, setIdx = -1;
+  var sets = this.layout_.points;
+  for (var i = 0; i < sets.length; i++) {
+    var points = sets[i];
+    var len = points.length;
+    for (var j = 0; j < len; j++) {
+      var point = points[j];
+      if (!Dygraph.isValidPoint(point, true)) continue;
+      var dist = Math.abs(point.canvasx - domX);
+      if (dist < minDistX) {
+        minDistX = dist;
+        setIdx = i;
+        pointIdx = j;
+      }
     }
   }
-  return this.idxToRow_(idx);
+
+  // TODO(danvk): remove this function; it's trivial and has only one use.
+  return this.idxToRow_(setIdx, pointIdx);
 };
 
 /**
@@ -1627,13 +1633,11 @@ Dygraph.prototype.findClosestRow = function(domX) {
 Dygraph.prototype.findClosestPoint = function(domX, domY) {
   var minDist = Infinity;
   var idx = -1;
-  var points = this.layout_.points;
   var dist, dx, dy, point, closestPoint, closestSeries;
   for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
-    var first = this.layout_.setPointsOffsets[setIdx];
-    var len = this.layout_.setPointsLengths[setIdx];
-    for (var i = 0; i < len; ++i) {
-      var point = points[first + i];
+    var points = this.layout_.points[setIdx];
+    for (var i = 0; i < points.length; ++i) {
+      var point = points[i];
       if (!Dygraph.isValidPoint(point)) continue;
       dx = point.canvasx - domX;
       dy = point.canvasy - domY;
@@ -1670,18 +1674,17 @@ 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 sets = 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 (rowIdx >= len) continue;
-    var p1 = points[first + rowIdx];
+    var points = this.layout_.points[setIdx];
+    if (rowIdx >= points.length) continue;
+    var p1 = points[rowIdx];
     if (!Dygraph.isValidPoint(p1)) continue;
     var py = p1.canvasy;
-    if (domX > p1.canvasx && rowIdx + 1 < len) {
+    if (domX > p1.canvasx && rowIdx + 1 < points.length) {
       // interpolate series Y value using next point
-      var p2 = points[first + rowIdx + 1];
+      var p2 = points[rowIdx + 1];
       if (Dygraph.isValidPoint(p2)) {
         var dx = p2.canvasx - p1.canvasx;
         if (dx > 0) {
@@ -1691,7 +1694,7 @@ Dygraph.prototype.findStackedPoint = function(domX, domY) {
       }
     } else if (domX < p1.canvasx && rowIdx > 0) {
       // interpolate series Y value using previous point
-      var p0 = points[first + rowIdx - 1];
+      var p0 = points[rowIdx - 1];
       if (Dygraph.isValidPoint(p0)) {
         var dx = p1.canvasx - p0.canvasx;
         if (dx > 0) {
@@ -1724,7 +1727,7 @@ Dygraph.prototype.findStackedPoint = function(domX, domY) {
 Dygraph.prototype.mouseMove_ = function(event) {
   // This prevents JS errors when mousing over the canvas before data loads.
   var points = this.layout_.points;
-  if (points === undefined) return;
+  if (points === undefined || points === null) return;
 
   var canvasCoords = this.eventToDomCoords(event);
   var canvasx = canvasCoords[0];
@@ -1770,18 +1773,19 @@ Dygraph.prototype.getLeftBoundary_ = function() {
  * @return int row number, or -1 if none could be found.
  * @private
  */
-Dygraph.prototype.idxToRow_ = function(idx) {
-  if (idx < 0) return -1;
+Dygraph.prototype.idxToRow_ = function(setIdx, rowIdx) {
+  if (rowIdx < 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 boundary + idx;
-    }
-    idx -= set.length;
-  }
-  return -1;
+  return boundary + rowIdx;
+  // for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
+  //   var set = this.layout_.datasets[setIdx];
+  //   if (idx < set.length) {
+  //     return boundary + idx;
+  //   }
+  //   idx -= set.length;
+  // }
+  // return -1;
 };
 
 Dygraph.prototype.animateSelection_ = function(direction) {
@@ -1906,7 +1910,6 @@ Dygraph.prototype.updateSelection_ = function(opt_animFraction) {
 Dygraph.prototype.setSelection = function(row, opt_seriesName) {
   // Extract the points we've selected
   this.selPoints_ = [];
-  var pos = 0;
 
   if (row !== false) {
     row -= this.getLeftBoundary_();
@@ -1919,15 +1922,14 @@ Dygraph.prototype.setSelection = function(row, opt_seriesName) {
     for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
       var set = this.layout_.datasets[setIdx];
       if (row < set.length) {
-        var point = this.layout_.points[pos+row];
+        var point = this.layout_.points[setIdx][row];
 
         if (this.attr_("stackedGraph")) {
-          point = this.layout_.unstackPointAtIndex(pos+row);
+          point = this.layout_.unstackPointAtIndex(setIdx, row);
         }
 
         if (!(point.yval === null)) this.selPoints_.push(point);
       }
-      pos += set.length;
     }
   } else {
     if (this.lastRow_ >= 0) changed = true;
@@ -1996,9 +1998,12 @@ Dygraph.prototype.getSelection = function() {
     return -1;
   }
 
-  for (var row=0; row<this.layout_.points.length; row++ ) {
-    if (this.layout_.points[row].x == this.selPoints_[0].x) {
-      return row + this.getLeftBoundary_();
+  for (var setIdx = 0; setIdx < this.layout_.points.length; setIdx++) {
+    var points = this.layout_.points[setIdx];
+    for (var row = 0; row < points.length; row++) {
+      if (points[row].x == this.selPoints_[0].x) {
+        return row + this.getLeftBoundary_();
+      }
     }
   }
   return -1;