factor out drawFillBars_ and clean up _renderLineChart
[dygraphs.git] / dygraph-canvas.js
index d100269..be76320 100644 (file)
@@ -145,14 +145,6 @@ DygraphCanvasRenderer.isSupported = function(canvasName) {
 };
 
 /**
- * @param { [String] } colors Array of color strings. Should have one entry for
- * each series to be rendered.
- */
-DygraphCanvasRenderer.prototype.setColors = function(colors) {
-  this.colorScheme_ = colors;
-};
-
-/**
  * This method is responsible for drawing everything on the chart, including
  * lines, error bars, fills and axes.
  * It is called immediately after clear() on every frame, including during pans
@@ -160,56 +152,6 @@ DygraphCanvasRenderer.prototype.setColors = function(colors) {
  * @private
  */
 DygraphCanvasRenderer.prototype.render = function() {
-  // Draw the new X/Y grid. Lines appear crisper when pixels are rounded to
-  // half-integers. This prevents them from drawing in two rows/cols.
-  var ctx = this.elementContext;
-  function halfUp(x)  { return Math.round(x) + 0.5; }
-  function halfDown(y){ return Math.round(y) - 0.5; }
-
-  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')(ctx, this.area, this.dygraph_, this.dygraph_);
-  }
-
-  var x, y, i, ticks;
-  if (this.attr_('drawYGrid')) {
-    ticks = this.layout.yticks;
-    ctx.save();
-    ctx.strokeStyle = this.attr_('gridLineColor');
-    ctx.lineWidth = this.attr_('gridLineWidth');
-    for (i = 0; i < ticks.length; i++) {
-      // TODO(danvk): allow secondary axes to draw a grid, too.
-      if (ticks[i][0] !== 0) continue;
-      x = halfUp(this.area.x);
-      y = halfDown(this.area.y + ticks[i][1] * this.area.h);
-      ctx.beginPath();
-      ctx.moveTo(x, y);
-      ctx.lineTo(x + this.area.w, y);
-      ctx.closePath();
-      ctx.stroke();
-    }
-    ctx.restore();
-  }
-
-  if (this.attr_('drawXGrid')) {
-    ticks = this.layout.xticks;
-    ctx.save();
-    ctx.strokeStyle = this.attr_('gridLineColor');
-    ctx.lineWidth = this.attr_('gridLineWidth');
-    for (i=0; i<ticks.length; i++) {
-      x = halfUp(this.area.x + ticks[i][0] * this.area.w);
-      y = halfDown(this.area.y + this.area.h);
-      ctx.beginPath();
-      ctx.moveTo(x, y);
-      ctx.lineTo(x, this.area.y);
-      ctx.closePath();
-      ctx.stroke();
-    }
-    ctx.restore();
-  }
-
-  // Do the ordinary rendering, as before
   this._renderLineChart();
 };
 
@@ -397,7 +339,7 @@ DygraphCanvasRenderer.prototype._drawSeries = function(
 
   strategy.init();
 
-  while(iter.hasNext()) {
+  while(iter.hasNext) {
     point = iter.next();
     if (point.canvasy === null || point.canvasy != point.canvasy) {
       if (stepPlot && prevCanvasX !== null) {
@@ -408,7 +350,7 @@ DygraphCanvasRenderer.prototype._drawSeries = function(
       }
       prevCanvasX = prevCanvasY = null;
     } else {
-      nextCanvasY = iter.hasNext() ? iter.peek().canvasy : null;
+      nextCanvasY = iter.hasNext ? iter.peek.canvasy : null;
       // TODO: we calculate isNullOrNaN for this point, and the next, and then, when
       // we iterate, test for isNullOrNaN again. Why bother?
       var isNextCanvasYNullOrNaN = nextCanvasY === null || nextCanvasY != nextCanvasY;
@@ -417,7 +359,7 @@ DygraphCanvasRenderer.prototype._drawSeries = function(
         // Also consider a point to be "isolated" if it's adjacent to a
         // null point, excluding the graph edges.
         if ((!first && !prevCanvasX) ||
-            (iter.hasNext() && isNextCanvasYNullOrNaN)) {
+            (iter.hasNext && isNextCanvasYNullOrNaN)) {
           isIsolated = true;
         }
       }
@@ -477,20 +419,13 @@ DygraphCanvasRenderer.prototype._drawLine = function(ctx, i) {
 
 /**
  * Actually draw the lines chart, including error bars.
- * TODO(danvk): split this into several smaller functions.
  * @private
  */
 DygraphCanvasRenderer.prototype._renderLineChart = function() {
-  // TODO(danvk): use this.attr_ for many of these.
   var ctx = this.elementContext;
-  var fillAlpha = this.attr_('fillAlpha');
   var errorBars = this.attr_("errorBars") || this.attr_("customBars");
   var fillGraph = this.attr_("fillGraph");
-  var stackedGraph = this.attr_("stackedGraph");
-  var stepPlot = this.attr_("stepPlot");
-  var points = this.layout.points;
-  var pointsLength = points.length;
-  var point, i, prevX, prevY, prevYs, color, setName, newYs, err_color, rgb, yscale, axis;
+  var i;
 
   var setNames = this.layout.setNames;
   var setCount = setNames.length;
@@ -503,177 +438,219 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
   // TODO(bhs): this loop is a hot-spot for high-point-count charts. These
   // transformations can be pushed into the canvas via linear transformation
   // matrices.
-  for (i = pointsLength; i--;) {
-    point = points[i];
+  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;
   }
 
-  // create paths
+  // Draw any "fills", i.e. error bars or the filled area under a series.
+  // These must all be drawn before any lines, so that the main lines of a
+  // series are drawn on top.
   if (errorBars) {
-    ctx.save();
     if (fillGraph) {
       this.dygraph_.warn("Can't use fillGraph option with error bars");
     }
 
-    for (i = 0; i < setCount; i++) {
-      setName = setNames[i];
-      axis = this.dygraph_.axisPropertiesForSeries(setName);
-      color = this.colors[setName];
-
-      var firstIndexInSet = this.layout.setPointsOffsets[i];
-      var setLength = this.layout.setPointsLengths[i];
-
-      var iter = Dygraph.createIterator(points, firstIndexInSet, setLength,
-          DygraphCanvasRenderer._getIteratorPredicate(this.attr_("connectSeparatedPoints")));
-
-      // setup graphics context
-      prevX = NaN;
-      prevY = NaN;
-      prevYs = [-1, -1];
-      yscale = axis.yscale;
-      // should be same color as the lines but only 15% opaque.
-      rgb = new RGBColor(color);
-      err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' +
-                            fillAlpha + ')';
-      ctx.fillStyle = err_color;
-      ctx.beginPath();
-      while (iter.hasNext()) {
-        point = iter.next();
-        if (point.name == setName) { // TODO(klausw): this is always true
-          if (!Dygraph.isOK(point.y)) {
-            prevX = NaN;
-            continue;
-          }
+    ctx.save();
+    this.drawErrorBars_(points);
+    ctx.restore();
+  } else if (fillGraph) {
+    ctx.save();
+    this.drawFillBars_(points);
+    ctx.restore();
+  }
+
+  // Drawing the lines.
+  for (i = 0; i < setCount; i += 1) {
+    this._drawLine(ctx, i);
+  }
+};
 
-          // TODO(danvk): here
+/**
+ * Draws the shaded error bars/confidence intervals for each series.
+ * This happens before the center lines are drawn, since the center lines
+ * need to be drawn on top of the error bars for all series.
+ *
+ * @private
+ */
+DygraphCanvasRenderer.prototype.drawErrorBars_ = function(points) {
+  var ctx = this.elementContext;
+  var setNames = this.layout.setNames;
+  var setCount = setNames.length;
+  var fillAlpha = this.attr_('fillAlpha');
+  var stepPlot = this.attr_('stepPlot');
+
+  var newYs;
+
+  for (var i = 0; i < setCount; i++) {
+    var setName = setNames[i];
+    var axis = this.dygraph_.axisPropertiesForSeries(setName);
+    var color = this.colors[setName];
+
+    var firstIndexInSet = this.layout.setPointsOffsets[i];
+    var setLength = this.layout.setPointsLengths[i];
+
+    var iter = Dygraph.createIterator(points, firstIndexInSet, setLength,
+        DygraphCanvasRenderer._getIteratorPredicate(
+            this.attr_("connectSeparatedPoints")));
+
+    // setup graphics context
+    var prevX = NaN;
+    var prevY = NaN;
+    var prevYs = [-1, -1];
+    var yscale = axis.yscale;
+    // should be same color as the lines but only 15% opaque.
+    var rgb = new RGBColor(color);
+    var err_color =
+        'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')';
+    ctx.fillStyle = err_color;
+    ctx.beginPath();
+    while (iter.hasNext) {
+      var point = iter.next();
+      if (point.name == setName) { // TODO(klausw): this is always true
+        if (!Dygraph.isOK(point.y)) {
+          prevX = NaN;
+          continue;
+        }
+
+        // TODO(danvk): here
+        if (stepPlot) {
+          newYs = [ point.y_bottom, point.y_top ];
+          prevY = point.y;
+        } else {
+          newYs = [ point.y_bottom, point.y_top ];
+        }
+        newYs[0] = this.area.h * newYs[0] + this.area.y;
+        newYs[1] = this.area.h * newYs[1] + this.area.y;
+        if (!isNaN(prevX)) {
           if (stepPlot) {
-            newYs = [ point.y_bottom, point.y_top ];
-            prevY = point.y;
+            ctx.moveTo(prevX, newYs[0]);
           } else {
-            newYs = [ point.y_bottom, point.y_top ];
+            ctx.moveTo(prevX, prevYs[0]);
           }
-          newYs[0] = this.area.h * newYs[0] + this.area.y;
-          newYs[1] = this.area.h * newYs[1] + this.area.y;
-          if (!isNaN(prevX)) {
-            if (stepPlot) {
-              ctx.moveTo(prevX, newYs[0]);
-            } else {
-              ctx.moveTo(prevX, prevYs[0]);
-            }
-            ctx.lineTo(point.canvasx, newYs[0]);
-            ctx.lineTo(point.canvasx, newYs[1]);
-            if (stepPlot) {
-              ctx.lineTo(prevX, newYs[1]);
-            } else {
-              ctx.lineTo(prevX, prevYs[1]);
-            }
-            ctx.closePath();
+          ctx.lineTo(point.canvasx, newYs[0]);
+          ctx.lineTo(point.canvasx, newYs[1]);
+          if (stepPlot) {
+            ctx.lineTo(prevX, newYs[1]);
+          } else {
+            ctx.lineTo(prevX, prevYs[1]);
           }
-          prevYs = newYs;
-          prevX = point.canvasx;
+          ctx.closePath();
         }
+        prevYs = newYs;
+        prevX = point.canvasx;
       }
-      ctx.fill();
     }
-    ctx.restore();
-  } else if (fillGraph) {
-    ctx.save();
-    var baseline = {};  // for stacked graphs: baseline for filling
-    var currBaseline;
-
-    // process sets in reverse order (needed for stacked graphs)
-    for (i = setCount - 1; i >= 0; i--) {
-      setName = setNames[i];
-      color = this.colors[setName];
-      axis = this.dygraph_.axisPropertiesForSeries(setName);
-      var axisY = 1.0 + axis.minyval * axis.yscale;
-      if (axisY < 0.0) axisY = 0.0;
-      else if (axisY > 1.0) axisY = 1.0;
-      axisY = this.area.h * axisY + this.area.y;
-      var firstIndexInSet = this.layout.setPointsOffsets[i];
-      var setLength = this.layout.setPointsLengths[i];
-
-      var iter = Dygraph.createIterator(points, firstIndexInSet, setLength,
-          DygraphCanvasRenderer._getIteratorPredicate(this.attr_("connectSeparatedPoints")));
-
-      // setup graphics context
-      prevX = NaN;
-      prevYs = [-1, -1];
-      yscale = axis.yscale;
-      // should be same color as the lines but only 15% opaque.
-      rgb = new RGBColor(color);
-      err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' +
-                            fillAlpha + ')';
-      ctx.fillStyle = err_color;
-      ctx.beginPath();
-      while(iter.hasNext()) {
-        point = iter.next();
-        if (point.name == setName) { // TODO(klausw): this is always true
-          if (!Dygraph.isOK(point.y)) {
-            prevX = NaN;
-            continue;
-          }
-          if (stackedGraph) {
-            currBaseline = baseline[point.canvasx];
-            var lastY;
-            if (currBaseline === undefined) {
-              lastY = axisY;
-            } else {
-              if(stepPlot) {
-                lastY = currBaseline[0];
-              } else {
-                lastY = currBaseline;
-              }
-            }
-            newYs = [ point.canvasy, lastY ];
+    ctx.fill();
+  }
+};
 
+/**
+ * Draws the shaded regions when "fillGraph" is set. Not to be confused with
+ * error bars.
+ *
+ * @private
+ */
+DygraphCanvasRenderer.prototype.drawFillBars_ = function(points) {
+  var ctx = this.elementContext;
+  var setNames = this.layout.setNames;
+  var setCount = setNames.length;
+  var fillAlpha = this.attr_('fillAlpha');
+  var stepPlot = this.attr_('stepPlot');
+  var stackedGraph = this.attr_("stackedGraph");
+
+  var baseline = {};  // for stacked graphs: baseline for filling
+  var currBaseline;
+
+  // process sets in reverse order (needed for stacked graphs)
+  for (var i = setCount - 1; i >= 0; i--) {
+    var setName = setNames[i];
+    var color = this.colors[setName];
+    var axis = this.dygraph_.axisPropertiesForSeries(setName);
+    var axisY = 1.0 + axis.minyval * axis.yscale;
+    if (axisY < 0.0) axisY = 0.0;
+    else if (axisY > 1.0) axisY = 1.0;
+    axisY = this.area.h * axisY + this.area.y;
+    var firstIndexInSet = this.layout.setPointsOffsets[i];
+    var setLength = this.layout.setPointsLengths[i];
+
+    var iter = Dygraph.createIterator(points, firstIndexInSet, setLength,
+        DygraphCanvasRenderer._getIteratorPredicate(
+            this.attr_("connectSeparatedPoints")));
+
+    // setup graphics context
+    var prevX = NaN;
+    var prevYs = [-1, -1];
+    var newYs;
+    var yscale = axis.yscale;
+    // should be same color as the lines but only 15% opaque.
+    var rgb = new RGBColor(color);
+    var err_color =
+        'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')';
+    ctx.fillStyle = err_color;
+    ctx.beginPath();
+    while(iter.hasNext) {
+      var point = iter.next();
+      if (point.name == setName) { // TODO(klausw): this is always true
+        if (!Dygraph.isOK(point.y)) {
+          prevX = NaN;
+          continue;
+        }
+        if (stackedGraph) {
+          currBaseline = baseline[point.canvasx];
+          var lastY;
+          if (currBaseline === undefined) {
+            lastY = axisY;
+          } else {
             if(stepPlot) {
-              // Step plots must keep track of the top and bottom of
-              // the baseline at each point.
-              if(prevYs[0] === -1) {
-                baseline[point.canvasx] = [ point.canvasy, axisY ];
-              } else {
-                baseline[point.canvasx] = [ point.canvasy, prevYs[0] ];
-              }
+              lastY = currBaseline[0];
             } else {
-              baseline[point.canvasx] = point.canvasy;
+              lastY = currBaseline;
             }
+          }
+          newYs = [ point.canvasy, lastY ];
 
+          if(stepPlot) {
+            // Step plots must keep track of the top and bottom of
+            // the baseline at each point.
+            if(prevYs[0] === -1) {
+              baseline[point.canvasx] = [ point.canvasy, axisY ];
+            } else {
+              baseline[point.canvasx] = [ point.canvasy, prevYs[0] ];
+            }
           } else {
-            newYs = [ point.canvasy, axisY ];
+            baseline[point.canvasx] = point.canvasy;
           }
-          if (!isNaN(prevX)) {
-            ctx.moveTo(prevX, prevYs[0]);
 
-            if (stepPlot) {
-              ctx.lineTo(point.canvasx, prevYs[0]);
-              if(currBaseline) {
-                // Draw to the bottom of the baseline
-                ctx.lineTo(point.canvasx, currBaseline[1]);
-              } else {
-                ctx.lineTo(point.canvasx, newYs[1]);
-              }
+        } else {
+          newYs = [ point.canvasy, axisY ];
+        }
+        if (!isNaN(prevX)) {
+          ctx.moveTo(prevX, prevYs[0]);
+
+          if (stepPlot) {
+            ctx.lineTo(point.canvasx, prevYs[0]);
+            if(currBaseline) {
+              // Draw to the bottom of the baseline
+              ctx.lineTo(point.canvasx, currBaseline[1]);
             } else {
-              ctx.lineTo(point.canvasx, newYs[0]);
               ctx.lineTo(point.canvasx, newYs[1]);
             }
-
-            ctx.lineTo(prevX, prevYs[1]);
-            ctx.closePath();
+          } else {
+            ctx.lineTo(point.canvasx, newYs[0]);
+            ctx.lineTo(point.canvasx, newYs[1]);
           }
-          prevYs = newYs;
-          prevX = point.canvasx;
+
+          ctx.lineTo(prevX, prevYs[1]);
+          ctx.closePath();
         }
+        prevYs = newYs;
+        prevX = point.canvasx;
       }
-      ctx.fill();
     }
-    ctx.restore();
-  }
-
-  // Drawing the lines.
-  for (i = 0; i < setCount; i += 1) {
-    this._drawLine(ctx, i);
+    ctx.fill();
   }
 };