Update documentation to include step plots.
[dygraphs.git] / dygraph.js
index a9b14ab..ee5306c 100644 (file)
@@ -123,7 +123,9 @@ Dygraph.DEFAULT_ATTRS = {
   connectSeparatedPoints: false,
 
   stackedGraph: false,
-  hideOverlayOnMouseOut: true
+  hideOverlayOnMouseOut: true,
+
+  stepPlot: false
 };
 
 // Various logging levels.
@@ -186,11 +188,17 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
   // The div might have been specified as percent of the current window size,
   // convert that to an appropriate number of pixels.
   if (div.style.width.indexOf("%") == div.style.width.length - 1) {
-    // Minus ten pixels  keeps scrollbars from showing up for a 100% width div.
-    this.width_ = (this.width_ * self.innerWidth / 100) - 10;
+    this.width_ = div.offsetWidth;
   }
   if (div.style.height.indexOf("%") == div.style.height.length - 1) {
-    this.height_ = (this.height_ * self.innerHeight / 100) - 10;
+    this.height_ = div.offsetHeight;
+  }
+
+  if (this.width_ == 0) {
+    this.error("dygraph has zero width. Please specify a width in pixels.");
+  }
+  if (this.height_ == 0) {
+    this.error("dygraph has zero height. Please specify a height in pixels.");
   }
 
   // TODO(danvk): set fillGraph to be part of attrs_ here, not user_attrs_.
@@ -387,20 +395,24 @@ Dygraph.prototype.createInterface_ = function() {
   this.canvas_.height = this.height_;
   this.canvas_.style.width = this.width_ + "px";    // for IE
   this.canvas_.style.height = this.height_ + "px";  // for IE
-  this.graphDiv.appendChild(this.canvas_);
 
   // ... and for static parts of the chart.
   this.hidden_ = this.createPlotKitCanvas_(this.canvas_);
 
+  // The interactive parts of the graph are drawn on top of the chart.
+  this.graphDiv.appendChild(this.hidden_);
+  this.graphDiv.appendChild(this.canvas_);
+  this.mouseEventElement_ = this.canvas_;
+
   // Make sure we don't overdraw.
   Dygraph.clipCanvas_(this.hidden_, this.clippingArea_);
   Dygraph.clipCanvas_(this.canvas_, this.clippingArea_);
 
   var dygraph = this;
-  Dygraph.addEvent(this.hidden_, 'mousemove', function(e) {
+  Dygraph.addEvent(this.mouseEventElement_, 'mousemove', function(e) {
     dygraph.mouseMove_(e);
   });
-  Dygraph.addEvent(this.hidden_, 'mouseout', function(e) {
+  Dygraph.addEvent(this.mouseEventElement_, 'mouseout', function(e) {
     dygraph.mouseOut_(e);
   });
 
@@ -476,7 +488,6 @@ Dygraph.prototype.createPlotKitCanvas_ = function(canvas) {
   h.height = this.height_;
   h.style.width = this.width_ + "px";    // for IE
   h.style.height = this.height_ + "px";  // for IE
-  this.graphDiv.appendChild(h);
   return h;
 };
 
@@ -707,7 +718,7 @@ Dygraph.prototype.createDragInterface_ = function() {
   var getY = function(e) { return Dygraph.pageX(e) - py };
 
   // Draw zoom rectangles when the mouse is down and the user moves around
-  Dygraph.addEvent(this.hidden_, 'mousemove', function(event) {
+  Dygraph.addEvent(this.mouseEventElement_, 'mousemove', function(event) {
     if (isZooming) {
       dragEndX = getX(event);
       dragEndY = getY(event);
@@ -729,7 +740,7 @@ Dygraph.prototype.createDragInterface_ = function() {
   });
 
   // Track the beginning of drag events
-  Dygraph.addEvent(this.hidden_, 'mousedown', function(event) {
+  Dygraph.addEvent(this.mouseEventElement_, 'mousedown', function(event) {
     px = Dygraph.findPosX(self.canvas_);
     py = Dygraph.findPosY(self.canvas_);
     dragStartX = getX(event);
@@ -763,7 +774,7 @@ Dygraph.prototype.createDragInterface_ = function() {
   });
 
   // Temporarily cancel the dragging event when the mouse leaves the graph
-  Dygraph.addEvent(this.hidden_, 'mouseout', function(event) {
+  Dygraph.addEvent(this.mouseEventElement_, 'mouseout', function(event) {
     if (isZooming) {
       dragEndX = null;
       dragEndY = null;
@@ -772,7 +783,7 @@ Dygraph.prototype.createDragInterface_ = function() {
 
   // If the mouse is released on the canvas during a drag event, then it's a
   // zoom. Only do the zoom if it's over a large enough area (>= 10 pixels)
-  Dygraph.addEvent(this.hidden_, 'mouseup', function(event) {
+  Dygraph.addEvent(this.mouseEventElement_, 'mouseup', function(event) {
     if (isZooming) {
       isZooming = false;
       dragEndX = getX(event);
@@ -808,7 +819,7 @@ Dygraph.prototype.createDragInterface_ = function() {
   });
 
   // Double-clicking zooms back out
-  Dygraph.addEvent(this.hidden_, 'dblclick', function(event) {
+  Dygraph.addEvent(this.mouseEventElement_, 'dblclick', function(event) {
     if (self.dateWindow_ == null) return;
     self.dateWindow_ = null;
     self.drawGraph_(self.rawData_);
@@ -879,7 +890,7 @@ Dygraph.prototype.doZoom_ = function(lowX, highX) {
  * @private
  */
 Dygraph.prototype.mouseMove_ = function(event) {
-  var canvasx = Dygraph.pageX(event) - Dygraph.findPosX(this.hidden_);
+  var canvasx = Dygraph.pageX(event) - Dygraph.findPosX(this.mouseEventElement_);
   var points = this.layout_.points;
 
   var lastx = -1;
@@ -902,9 +913,7 @@ Dygraph.prototype.mouseMove_ = function(event) {
 
   // Extract the points we've selected
   this.selPoints_ = [];
-  var cumulative_sum = 0;  // used only if we have a stackedGraph.
   var l = points.length;
-  var isStacked = this.attr_("stackedGraph");
   if (!this.attr_("stackedGraph")) {
     for (var i = 0; i < l; i++) {
       if (points[i].xval == lastx) {
@@ -912,11 +921,11 @@ Dygraph.prototype.mouseMove_ = function(event) {
       }
     }
   } else {
-    // Stacked points need to be examined in reverse order.
+    // Need to 'unstack' points starting from the bottom
+    var cumulative_sum = 0;
     for (var i = l - 1; i >= 0; i--) {
       if (points[i].xval == lastx) {
-        // Clone the point, since we need to 'unstack' it below.
-        var p = {};
+        var p = {};  // Clone the point since we modify it
         for (var k in points[i]) {
           p[k] = points[i][k];
         }
@@ -925,6 +934,7 @@ Dygraph.prototype.mouseMove_ = function(event) {
         this.selPoints_.push(p);
       }
     }
+    this.selPoints_.reverse();
   }
 
   if (this.attr_("highlightCallback")) {
@@ -1513,17 +1523,19 @@ Dygraph.prototype.drawGraph_ = function(data) {
 
   var connectSeparatedPoints = this.attr_('connectSeparatedPoints');
 
-  // For stacked series.
-  var cumulative_y = [];
-  var stacked_datasets = [];
+  // Loop over the fields (series).  Go from the last to the first,
+  // because if they're stacked that's how we accumulate the values.
+
+  var cumulative_y = [];  // For stacked series.
+  var datasets = [];
 
-  // Loop over all fields in the dataset
-  for (var i = 1; i < data[0].length; i++) {
+  // Loop over all fields and create datasets
+  for (var i = data[0].length - 1; i >= 1; i--) {
     if (!this.visibility()[i - 1]) continue;
 
     var series = [];
     for (var j = 0; j < data.length; j++) {
-      if (data[j][i] !== undefined || !connectSeparatedPoints) {
+      if (data[j][i] != null || !connectSeparatedPoints) {
         var date = data[j][0];
         series.push([date, data[j][i]]);
       }
@@ -1569,38 +1581,35 @@ Dygraph.prototype.drawGraph_ = function(data) {
     if (maxY === null || thisMaxY > maxY) maxY = thisMaxY;
 
     if (bars) {
-      var vals = [];
-      for (var j=0; j<series.length; j++)
-        vals[j] = [series[j][0],
-                   series[j][1][0], series[j][1][1], series[j][1][2]];
-      this.layout_.addDataset(this.attr_("labels")[i], vals);
+      for (var j=0; j<series.length; j++) {
+        val = [series[j][0], series[j][1][0], series[j][1][1], series[j][1][2]];
+        series[j] = val;
+      }
     } else if (this.attr_("stackedGraph")) {
-      var vals = [];
       var l = series.length;
       var actual_y;
       for (var j = 0; j < l; j++) {
-        if (cumulative_y[series[j][0]] === undefined)
-          cumulative_y[series[j][0]] = 0;
+        // If one data set has a NaN, let all subsequent stacked
+        // sets inherit the NaN -- only start at 0 for the first set.
+        var x = series[j][0];
+        if (cumulative_y[x] === undefined)
+          cumulative_y[x] = 0;
 
         actual_y = series[j][1];
-        cumulative_y[series[j][0]] += actual_y;
+        cumulative_y[x] += actual_y;
 
-        vals[j] = [series[j][0], cumulative_y[series[j][0]]]
+        series[j] = [x, cumulative_y[x]]
 
-        if (!maxY || cumulative_y[series[j][0]] > maxY)
-          maxY = cumulative_y[series[j][0]];
+        if (!maxY || cumulative_y[x] > maxY)
+          maxY = cumulative_y[x];
       }
-      stacked_datasets.push([this.attr_("labels")[i], vals]);
-      //this.layout_.addDataset(this.attr_("labels")[i], vals);
-    } else {
-      this.layout_.addDataset(this.attr_("labels")[i], series);
     }
+
+    datasets[i] = series;
   }
 
-  if (stacked_datasets.length > 0) {
-    for (var i = (stacked_datasets.length - 1); i >= 0; i--) {
-      this.layout_.addDataset(stacked_datasets[i][0], stacked_datasets[i][1]);
-    }
+  for (var i = 1; i < datasets.length; i++) {
+    this.layout_.addDataset(this.attr_("labels")[i], datasets[i]);
   }
 
   // Use some heuristics to come up with a good maxY value, unless it's been
@@ -2213,6 +2222,11 @@ Dygraph.prototype.updateOptions = function(attrs) {
  * @param {Number} height Height (in pixels)
  */
 Dygraph.prototype.resize = function(width, height) {
+  if (this.resize_lock) {
+    return;
+  }
+  this.resize_lock = true;
+
   if ((width === null) != (height === null)) {
     this.warn("Dygraph.resize() should be called with zero parameters or " +
               "two non-NULL parameters. Pretending it was zero.");
@@ -2235,6 +2249,8 @@ Dygraph.prototype.resize = function(width, height) {
 
   this.createInterface_();
   this.drawGraph_(this.rawData_);
+
+  this.resize_lock = false;
 };
 
 /**