fix plotter demo
[dygraphs.git] / dygraph.js
index abd2973..2fd2c05 100644 (file)
@@ -1063,8 +1063,10 @@ Dygraph.prototype.createMouseEventElement_ = function() {
  * @private
  */
 Dygraph.prototype.setColors_ = function() {
-  var num = this.attr_("labels").length - 1;
+  var labels = this.getLabels();
+  var num = labels.length - 1;
   this.colors_ = [];
+  this.colorsMap_ = {};
   var colors = this.attr_('colors');
   var i;
   if (!colors) {
@@ -1076,17 +1078,18 @@ Dygraph.prototype.setColors_ = function() {
       // alternate colors for high contrast.
       var idx = i % 2 ? Math.ceil(i / 2) : (half + i / 2);
       var hue = (1.0 * idx/ (1 + num));
-      this.colors_.push(Dygraph.hsvToRGB(hue, sat, val));
+      var colorStr = Dygraph.hsvToRGB(hue, sat, val);
+      this.colors_.push(colorStr);
+      this.colorsMap_[labels[i]] = colorStr;
     }
   } else {
     for (i = 0; i < num; i++) {
       if (!this.visibility()[i]) continue;
       var colorStr = colors[i % colors.length];
       this.colors_.push(colorStr);
+      this.colorsMap_[labels[1 + i]] = colorStr;
     }
   }
-
-  this.plotter_.setColors(this.colors_);
 };
 
 /**
@@ -1123,7 +1126,7 @@ Dygraph.prototype.getPropertiesForSeries = function(series_name) {
     name: series_name,
     column: idx,
     visible: this.visibility()[idx - 1],
-    color: this.plotter_.colors[series_name],
+    color: this.colorsMap_[series_name],
     axis: 1 + this.seriesToAxisMap_[series_name]
   };
 };
@@ -1594,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);
 };
 
 /**
@@ -1624,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;
@@ -1667,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) {
@@ -1688,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) {
@@ -1721,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];
@@ -1767,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) {
@@ -1903,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_();
@@ -1916,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;
@@ -1993,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;
@@ -2102,7 +2110,10 @@ Dygraph.prototype.predraw_ = function() {
   this.computeYAxes_();
 
   // Create a new plotter.
-  if (this.plotter_) this.plotter_.clear();
+  if (this.plotter_) {
+    this.cascadeEvents_('clearChart');
+    this.plotter_.clear();
+  }
   this.plotter_ = new DygraphCanvasRenderer(this,
                                             this.hidden_,
                                             this.hidden_ctx_,
@@ -2161,7 +2172,9 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
   for (i = num_series; i >= 1; i--) {
     if (!this.visibility()[i - 1]) continue;
 
-    // TODO(danvk): is this copy really necessary?
+    // Note: this copy _is_ necessary at the moment.
+    // If you remove it, it breaks zooming with error bars on.
+    // TODO(danvk): investigate further & write a test for this.
     var series = [];
     for (j = 0; j < rolledSeries[i].length; j++) {
       series.push(rolledSeries[i][j]);
@@ -2330,8 +2343,26 @@ Dygraph.prototype.drawGraph_ = function() {
  * @private
  */
 Dygraph.prototype.renderGraph_ = function(is_initial_draw) {
+  this.cascadeEvents_('clearChart');
   this.plotter_.clear();
+
+  if (this.attr_('underlayCallback')) {
+    // NOTE: we pass the dygraph object to this callback twice to avoid breaking
+    // users who expect a deprecated form of this callback.
+    this.attr_('underlayCallback')(
+        this.hidden_ctx_, this.layout_.getPlotArea(), this, this);
+  }
+
+  var e = {
+    canvas: this.hidden_,
+    drawingContext: this.hidden_ctx_,
+  };
+  this.cascadeEvents_('willDrawChart', e);
   this.plotter_.render();
+  this.cascadeEvents_('didDrawChart', e);
+
+  // TODO(danvk): is this a performance bottleneck when panning?
+  // The interaction canvas should already be empty in that situation.
   this.canvas_.getContext('2d').clearRect(0, 0, this.canvas_.width,
                                           this.canvas_.height);
 
@@ -2340,8 +2371,6 @@ Dygraph.prototype.renderGraph_ = function(is_initial_draw) {
   if (this.rangeSelector_) {
     this.rangeSelector_.renderInteractiveLayer();
   }
-
-  this.cascadeEvents_('drawChart');
   if (this.attr_("drawCallback") !== null) {
     this.attr_("drawCallback")(this, is_initial_draw);
   }
@@ -2612,10 +2641,12 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) {
  *
  * This is where undesirable points (i.e. negative values on log scales and
  * missing values through which we wish to connect lines) are dropped.
+ * TODO(danvk): the "missing values" bit above doesn't seem right.
  * 
  * @private
  */
 Dygraph.prototype.extractSeries_ = function(rawData, i, logScale) {
+  // TODO(danvk): pre-allocate series here.
   var series = [];
   for (var j = 0; j < rawData.length; j++) {
     var x = rawData[j][0];
@@ -3012,6 +3043,13 @@ Dygraph.prototype.parseArray_ = function(data) {
     for (i = 1; i < data[0].length; i++) {
       this.attrs_.labels.push("Y" + i);
     }
+  } else {
+    var num_labels = this.attr_("labels");
+    if (num_labels.length != data[0].length) {
+      this.error("Mismatch between number of labels (" + num_labels +
+          ") and number of columns in array (" + data[0].length + ")");
+      return null;
+    }
   }
 
   if (Dygraph.isDateLike(data[0][0])) {
@@ -3488,6 +3526,7 @@ Dygraph.prototype.datasetIndexFromSetName_ = function(name) {
  * called once -- all calls after the first will return immediately.
  */
 Dygraph.addAnnotationRule = function() {
+  // TODO(danvk): move this function into plugins/annotations.js?
   if (Dygraph.addedAnnotationCSS) return;
 
   var rule = "border: 1px solid black; " +