Move code into src/
[dygraphs.git] / dygraph-canvas.js
diff --git a/dygraph-canvas.js b/dygraph-canvas.js
deleted file mode 100644 (file)
index a688c43..0000000
+++ /dev/null
@@ -1,862 +0,0 @@
-/**
- * @license
- * Copyright 2006 Dan Vanderkam (danvdk@gmail.com)
- * MIT-licensed (http://opensource.org/licenses/MIT)
- */
-
-/**
- * @fileoverview Based on PlotKit.CanvasRenderer, but modified to meet the
- * needs of dygraphs.
- *
- * In particular, support for:
- * - grid overlays
- * - error bars
- * - dygraphs attribute system
- */
-
-/**
- * The DygraphCanvasRenderer class does the actual rendering of the chart onto
- * a canvas. It's based on PlotKit.CanvasRenderer.
- * @param {Object} element The canvas to attach to
- * @param {Object} elementContext The 2d context of the canvas (injected so it
- * can be mocked for testing.)
- * @param {Layout} layout The DygraphLayout object for this graph.
- * @constructor
- */
-
-var DygraphCanvasRenderer = (function() {
-/*global Dygraph:false */
-"use strict";
-
-
-/**
- * @constructor
- *
- * This gets called when there are "new points" to chart. This is generally the
- * case when the underlying data being charted has changed. It is _not_ called
- * in the common case that the user has zoomed or is panning the view.
- *
- * The chart canvas has already been created by the Dygraph object. The
- * renderer simply gets a drawing context.
- *
- * @param {Dygraph} dygraph The chart to which this renderer belongs.
- * @param {HTMLCanvasElement} element The <canvas> DOM element on which to draw.
- * @param {CanvasRenderingContext2D} elementContext The drawing context.
- * @param {DygraphLayout} layout The chart's DygraphLayout object.
- *
- * TODO(danvk): remove the elementContext property.
- */
-var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) {
-  this.dygraph_ = dygraph;
-
-  this.layout = layout;
-  this.element = element;
-  this.elementContext = elementContext;
-
-  this.height = dygraph.height_;
-  this.width = dygraph.width_;
-
-  // --- check whether everything is ok before we return
-  if (!Dygraph.isCanvasSupported(this.element)) {
-    throw "Canvas is not supported.";
-  }
-
-  // internal state
-  this.area = layout.getPlotArea();
-
-  // Set up a clipping area for the canvas (and the interaction canvas).
-  // This ensures that we don't overdraw.
-  // on Android 3 and 4, setting a clipping area on a canvas prevents it from
-  // displaying anything.
-  if (!Dygraph.isAndroid()) {
-    var ctx = this.dygraph_.canvas_ctx_;
-    ctx.beginPath();
-    ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h);
-    ctx.clip();
-
-    ctx = this.dygraph_.hidden_ctx_;
-    ctx.beginPath();
-    ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h);
-    ctx.clip();
-  }
-};
-
-/**
- * Clears out all chart content and DOM elements.
- * This is called immediately before render() on every frame, including
- * during zooms and pans.
- * @private
- */
-DygraphCanvasRenderer.prototype.clear = function() {
-  this.elementContext.clearRect(0, 0, this.width, this.height);
-};
-
-/**
- * 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
- * and zooms.
- * @private
- */
-DygraphCanvasRenderer.prototype.render = function() {
-  // attaches point.canvas{x,y}
-  this._updatePoints();
-
-  // actually draws the chart.
-  this._renderLineChart();
-};
-
-/**
- * Returns a predicate to be used with an iterator, which will
- * iterate over points appropriately, depending on whether
- * connectSeparatedPoints is true. When it's false, the predicate will
- * skip over points with missing yVals.
- */
-DygraphCanvasRenderer._getIteratorPredicate = function(connectSeparatedPoints) {
-  return connectSeparatedPoints ?
-      DygraphCanvasRenderer._predicateThatSkipsEmptyPoints :
-      null;
-};
-
-DygraphCanvasRenderer._predicateThatSkipsEmptyPoints =
-    function(array, idx) {
-  return array[idx].yval !== null;
-};
-
-/**
- * Draws a line with the styles passed in and calls all the drawPointCallbacks.
- * @param {Object} e The dictionary passed to the plotter function.
- * @private
- */
-DygraphCanvasRenderer._drawStyledLine = function(e,
-    color, strokeWidth, strokePattern, drawPoints,
-    drawPointCallback, pointSize) {
-  var g = e.dygraph;
-  // TODO(konigsberg): Compute attributes outside this method call.
-  var stepPlot = g.getBooleanOption("stepPlot", e.setName);
-
-  if (!Dygraph.isArrayLike(strokePattern)) {
-    strokePattern = null;
-  }
-
-  var drawGapPoints = g.getBooleanOption('drawGapEdgePoints', e.setName);
-
-  var points = e.points;
-  var setName = e.setName;
-  var iter = Dygraph.createIterator(points, 0, points.length,
-      DygraphCanvasRenderer._getIteratorPredicate(
-          g.getBooleanOption("connectSeparatedPoints", setName)));
-
-  var stroking = strokePattern && (strokePattern.length >= 2);
-
-  var ctx = e.drawingContext;
-  ctx.save();
-  if (stroking) {
-    ctx.installPattern(strokePattern);
-  }
-
-  var pointsOnLine = DygraphCanvasRenderer._drawSeries(
-      e, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color);
-  DygraphCanvasRenderer._drawPointsOnLine(
-      e, pointsOnLine, drawPointCallback, color, pointSize);
-
-  if (stroking) {
-    ctx.uninstallPattern();
-  }
-
-  ctx.restore();
-};
-
-/**
- * This does the actual drawing of lines on the canvas, for just one series.
- * Returns a list of [canvasx, canvasy] pairs for points for which a
- * drawPointCallback should be fired.  These include isolated points, or all
- * points if drawPoints=true.
- * @param {Object} e The dictionary passed to the plotter function.
- * @private
- */
-DygraphCanvasRenderer._drawSeries = function(e,
-    iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color) {
-
-  var prevCanvasX = null;
-  var prevCanvasY = null;
-  var nextCanvasY = null;
-  var isIsolated; // true if this point is isolated (no line segments)
-  var point; // the point being processed in the while loop
-  var pointsOnLine = []; // Array of [canvasx, canvasy] pairs.
-  var first = true; // the first cycle through the while loop
-
-  var ctx = e.drawingContext;
-  ctx.beginPath();
-  ctx.strokeStyle = color;
-  ctx.lineWidth = strokeWidth;
-
-  // NOTE: we break the iterator's encapsulation here for about a 25% speedup.
-  var arr = iter.array_;
-  var limit = iter.end_;
-  var predicate = iter.predicate_;
-
-  for (var i = iter.start_; i < limit; i++) {
-    point = arr[i];
-    if (predicate) {
-      while (i < limit && !predicate(arr, i)) {
-        i++;
-      }
-      if (i == limit) break;
-      point = arr[i];
-    }
-
-    // FIXME: The 'canvasy != canvasy' test here catches NaN values but the test
-    // doesn't catch Infinity values. Could change this to
-    // !isFinite(point.canvasy), but I assume it avoids isNaN for performance?
-    if (point.canvasy === null || point.canvasy != point.canvasy) {
-      if (stepPlot && prevCanvasX !== null) {
-        // Draw a horizontal line to the start of the missing data
-        ctx.moveTo(prevCanvasX, prevCanvasY);
-        ctx.lineTo(point.canvasx, prevCanvasY);
-      }
-      prevCanvasX = prevCanvasY = null;
-    } else {
-      isIsolated = false;
-      if (drawGapPoints || !prevCanvasX) {
-        iter.nextIdx_ = i;
-        iter.next();
-        nextCanvasY = iter.hasNext ? iter.peek.canvasy : null;
-
-        var isNextCanvasYNullOrNaN = nextCanvasY === null ||
-            nextCanvasY != nextCanvasY;
-        isIsolated = (!prevCanvasX && isNextCanvasYNullOrNaN);
-        if (drawGapPoints) {
-          // 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)) {
-            isIsolated = true;
-          }
-        }
-      }
-
-      if (prevCanvasX !== null) {
-        if (strokeWidth) {
-          if (stepPlot) {
-            ctx.moveTo(prevCanvasX, prevCanvasY);
-            ctx.lineTo(point.canvasx, prevCanvasY);
-          }
-
-          ctx.lineTo(point.canvasx, point.canvasy);
-        }
-      } else {
-        ctx.moveTo(point.canvasx, point.canvasy);
-      }
-      if (drawPoints || isIsolated) {
-        pointsOnLine.push([point.canvasx, point.canvasy, point.idx]);
-      }
-      prevCanvasX = point.canvasx;
-      prevCanvasY = point.canvasy;
-    }
-    first = false;
-  }
-  ctx.stroke();
-  return pointsOnLine;
-};
-
-/**
- * This fires the drawPointCallback functions, which draw dots on the points by
- * default. This gets used when the "drawPoints" option is set, or when there
- * are isolated points.
- * @param {Object} e The dictionary passed to the plotter function.
- * @private
- */
-DygraphCanvasRenderer._drawPointsOnLine = function(
-    e, pointsOnLine, drawPointCallback, color, pointSize) {
-  var ctx = e.drawingContext;
-  for (var idx = 0; idx < pointsOnLine.length; idx++) {
-    var cb = pointsOnLine[idx];
-    ctx.save();
-    drawPointCallback.call(e.dygraph,
-        e.dygraph, e.setName, ctx, cb[0], cb[1], color, pointSize, cb[2]);
-    ctx.restore();
-  }
-};
-
-/**
- * Attaches canvas coordinates to the points array.
- * @private
- */
-DygraphCanvasRenderer.prototype._updatePoints = function() {
-  // Update Points
-  // TODO(danvk): here
-  //
-  // 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.
-  // NOTE(danvk): this is trickier than it sounds at first. The transformation
-  // needs to be done before the .moveTo() and .lineTo() calls, but must be
-  // undone before the .stroke() call to ensure that the stroke width is
-  // 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 sets = this.layout.points;
-  for (var 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;
-    }
-  }
-};
-
-/**
- * Add canvas Actually draw the lines chart, including error bars.
- *
- * This function can only be called if DygraphLayout's points array has been
- * updated with canvas{x,y} attributes, i.e. by
- * DygraphCanvasRenderer._updatePoints.
- *
- * @param {string=} opt_seriesName when specified, only that series will
- *     be drawn. (This is used for expedited redrawing with highlightSeriesOpts)
- * @param {CanvasRenderingContext2D} opt_ctx when specified, the drawing
- *     context.  However, lines are typically drawn on the object's
- *     elementContext.
- * @private
- */
-DygraphCanvasRenderer.prototype._renderLineChart = function(opt_seriesName, opt_ctx) {
-  var ctx = opt_ctx || this.elementContext;
-  var i;
-
-  var sets = this.layout.points;
-  var setNames = this.layout.setNames;
-  var setName;
-
-  this.colors = this.dygraph_.colorsMap_;
-
-  // Determine which series have specialized plotters.
-  var plotter_attr = this.dygraph_.getOption("plotter");
-  var plotters = plotter_attr;
-  if (!Dygraph.isArrayLike(plotters)) {
-    plotters = [plotters];
-  }
-
-  var setPlotters = {};  // series name -> plotter fn.
-  for (i = 0; i < setNames.length; i++) {
-    setName = setNames[i];
-    var setPlotter = this.dygraph_.getOption("plotter", setName);
-    if (setPlotter == plotter_attr) continue;  // not specialized.
-
-    setPlotters[setName] = setPlotter;
-  }
-
-  for (i = 0; i < plotters.length; i++) {
-    var plotter = plotters[i];
-    var is_last = (i == plotters.length - 1);
-
-    for (var j = 0; j < sets.length; j++) {
-      setName = setNames[j];
-      if (opt_seriesName && setName != opt_seriesName) continue;
-
-      var points = sets[j];
-
-      // Only throw in the specialized plotters on the last iteration.
-      var p = plotter;
-      if (setName in setPlotters) {
-        if (is_last) {
-          p = setPlotters[setName];
-        } else {
-          // Don't use the standard plotters in this case.
-          continue;
-        }
-      }
-
-      var color = this.colors[setName];
-      var strokeWidth = this.dygraph_.getOption("strokeWidth", setName);
-
-      ctx.save();
-      ctx.strokeStyle = color;
-      ctx.lineWidth = strokeWidth;
-      p({
-        points: points,
-        setName: setName,
-        drawingContext: ctx,
-        color: color,
-        strokeWidth: strokeWidth,
-        dygraph: this.dygraph_,
-        axis: this.dygraph_.axisPropertiesForSeries(setName),
-        plotArea: this.area,
-        seriesIndex: j,
-        seriesCount: sets.length,
-        singleSeriesName: opt_seriesName,
-        allSeriesPoints: sets
-      });
-      ctx.restore();
-    }
-  }
-};
-
-/**
- * Standard plotters. These may be used by clients via Dygraph.Plotters.
- * See comments there for more details.
- */
-DygraphCanvasRenderer._Plotters = {
-  linePlotter: function(e) {
-    DygraphCanvasRenderer._linePlotter(e);
-  },
-
-  fillPlotter: function(e) {
-    DygraphCanvasRenderer._fillPlotter(e);
-  },
-
-  errorPlotter: function(e) {
-    DygraphCanvasRenderer._errorPlotter(e);
-  }
-};
-
-/**
- * Plotter which draws the central lines for a series.
- * @private
- */
-DygraphCanvasRenderer._linePlotter = function(e) {
-  var g = e.dygraph;
-  var setName = e.setName;
-  var strokeWidth = e.strokeWidth;
-
-  // TODO(danvk): Check if there's any performance impact of just calling
-  // getOption() inside of _drawStyledLine. Passing in so many parameters makes
-  // this code a bit nasty.
-  var borderWidth = g.getNumericOption("strokeBorderWidth", setName);
-  var drawPointCallback = g.getOption("drawPointCallback", setName) ||
-      Dygraph.Circles.DEFAULT;
-  var strokePattern = g.getOption("strokePattern", setName);
-  var drawPoints = g.getBooleanOption("drawPoints", setName);
-  var pointSize = g.getNumericOption("pointSize", setName);
-
-  if (borderWidth && strokeWidth) {
-    DygraphCanvasRenderer._drawStyledLine(e,
-        g.getOption("strokeBorderColor", setName),
-        strokeWidth + 2 * borderWidth,
-        strokePattern,
-        drawPoints,
-        drawPointCallback,
-        pointSize
-        );
-  }
-
-  DygraphCanvasRenderer._drawStyledLine(e,
-      e.color,
-      strokeWidth,
-      strokePattern,
-      drawPoints,
-      drawPointCallback,
-      pointSize
-  );
-};
-
-/**
- * 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._errorPlotter = function(e) {
-  var g = e.dygraph;
-  var setName = e.setName;
-  var errorBars = g.getBooleanOption("errorBars") ||
-      g.getBooleanOption("customBars");
-  if (!errorBars) return;
-
-  var fillGraph = g.getBooleanOption("fillGraph", setName);
-  if (fillGraph) {
-    console.warn("Can't use fillGraph option with error bars");
-  }
-
-  var ctx = e.drawingContext;
-  var color = e.color;
-  var fillAlpha = g.getNumericOption('fillAlpha', setName);
-  var stepPlot = g.getBooleanOption("stepPlot", setName);
-  var points = e.points;
-
-  var iter = Dygraph.createIterator(points, 0, points.length,
-      DygraphCanvasRenderer._getIteratorPredicate(
-          g.getBooleanOption("connectSeparatedPoints", setName)));
-
-  var newYs;
-
-  // setup graphics context
-  var prevX = NaN;
-  var prevY = NaN;
-  var prevYs = [-1, -1];
-  // should be same color as the lines but only 15% opaque.
-  var rgb = Dygraph.toRGB_(color);
-  var err_color =
-      'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')';
-  ctx.fillStyle = err_color;
-  ctx.beginPath();
-
-  var isNullUndefinedOrNaN = function(x) {
-    return (x === null ||
-            x === undefined ||
-            isNaN(x));
-  };
-
-  while (iter.hasNext) {
-    var point = iter.next();
-    if ((!stepPlot && isNullUndefinedOrNaN(point.y)) ||
-        (stepPlot && !isNaN(prevY) && isNullUndefinedOrNaN(prevY))) {
-      prevX = NaN;
-      continue;
-    }
-
-    newYs = [ point.y_bottom, point.y_top ];
-    if (stepPlot) {
-      prevY = point.y;
-    }
-
-    // The documentation specifically disallows nulls inside the point arrays,
-    // but in case it happens we should do something sensible.
-    if (isNaN(newYs[0])) newYs[0] = point.y;
-    if (isNaN(newYs[1])) newYs[1] = point.y;
-
-    newYs[0] = e.plotArea.h * newYs[0] + e.plotArea.y;
-    newYs[1] = e.plotArea.h * newYs[1] + e.plotArea.y;
-    if (!isNaN(prevX)) {
-      if (stepPlot) {
-        ctx.moveTo(prevX, prevYs[0]);
-        ctx.lineTo(point.canvasx, prevYs[0]);
-        ctx.lineTo(point.canvasx, prevYs[1]);
-      } else {
-        ctx.moveTo(prevX, prevYs[0]);
-        ctx.lineTo(point.canvasx, newYs[0]);
-        ctx.lineTo(point.canvasx, newYs[1]);
-      }
-      ctx.lineTo(prevX, prevYs[1]);
-      ctx.closePath();
-    }
-    prevYs = newYs;
-    prevX = point.canvasx;
-  }
-  ctx.fill();
-};
-
-
-/**
- * Proxy for CanvasRenderingContext2D which drops moveTo/lineTo calls which are
- * superfluous. It accumulates all movements which haven't changed the x-value
- * and only applies the two with the most extreme y-values.
- * 
- * Calls to lineTo/moveTo must have non-decreasing x-values.
- */
-DygraphCanvasRenderer._fastCanvasProxy = function(context) {
-  var pendingActions = [];  // array of [type, x, y] tuples
-  var lastRoundedX = null;
-
-  var LINE_TO = 1,
-      MOVE_TO = 2;
-
-  var actionCount = 0;  // number of moveTos and lineTos passed to context.
-
-  // Drop superfluous motions
-  // Assumes all pendingActions have the same (rounded) x-value.
-  var compressActions = function(opt_losslessOnly) {
-    if (pendingActions.length <= 1) return;
-
-    // Lossless compression: drop inconsequential moveTos.
-    for (var i = pendingActions.length - 1; i > 0; i--) {
-      var action = pendingActions[i];
-      if (action[0] == MOVE_TO) {
-        var prevAction = pendingActions[i - 1];
-        if (prevAction[1] == action[1] && prevAction[2] == action[2]) {
-          pendingActions.splice(i, 1);
-        }
-      }
-    }
-
-    // Lossless compression: ... drop consecutive moveTos ...
-    for (var i = 0; i < pendingActions.length - 1; /* incremented internally */) {
-      var action = pendingActions[i];
-      if (action[0] == MOVE_TO && pendingActions[i + 1][0] == MOVE_TO) {
-        pendingActions.splice(i, 1);
-      } else {
-        i++;
-      }
-    }
-
-    // Lossy compression: ... drop all but the extreme y-values ...
-    if (pendingActions.length > 2 && !opt_losslessOnly) {
-      // keep an initial moveTo, but drop all others.
-      var startIdx = 0;
-      if (pendingActions[0][0] == MOVE_TO) startIdx++;
-      var minIdx = null, maxIdx = null;
-      for (var i = startIdx; i < pendingActions.length; i++) {
-        var action = pendingActions[i];
-        if (action[0] != LINE_TO) continue;
-        if (minIdx === null && maxIdx === null) {
-          minIdx = i;
-          maxIdx = i;
-        } else {
-          var y = action[2];
-          if (y < pendingActions[minIdx][2]) {
-            minIdx = i;
-          } else if (y > pendingActions[maxIdx][2]) {
-            maxIdx = i;
-          }
-        }
-      }
-      var minAction = pendingActions[minIdx],
-          maxAction = pendingActions[maxIdx];
-      pendingActions.splice(startIdx, pendingActions.length - startIdx);
-      if (minIdx < maxIdx) {
-        pendingActions.push(minAction);
-        pendingActions.push(maxAction);
-      } else if (minIdx > maxIdx) {
-        pendingActions.push(maxAction);
-        pendingActions.push(minAction);
-      } else {
-        pendingActions.push(minAction);
-      }
-    }
-  };
-
-  var flushActions = function(opt_noLossyCompression) {
-    compressActions(opt_noLossyCompression);
-    for (var i = 0, len = pendingActions.length; i < len; i++) {
-      var action = pendingActions[i];
-      if (action[0] == LINE_TO) {
-        context.lineTo(action[1], action[2]);
-      } else if (action[0] == MOVE_TO) {
-        context.moveTo(action[1], action[2]);
-      }
-    }
-    actionCount += pendingActions.length;
-    pendingActions = [];
-  };
-
-  var addAction = function(action, x, y) {
-    var rx = Math.round(x);
-    if (lastRoundedX === null || rx != lastRoundedX) {
-      flushActions();
-      lastRoundedX = rx;
-    }
-    pendingActions.push([action, x, y]);
-  };
-
-  return {
-    moveTo: function(x, y) {
-      addAction(MOVE_TO, x, y);
-    },
-    lineTo: function(x, y) {
-      addAction(LINE_TO, x, y);
-    },
-
-    // for major operations like stroke/fill, we skip compression to ensure
-    // that there are no artifacts at the right edge.
-    stroke:    function() { flushActions(true); context.stroke(); },
-    fill:      function() { flushActions(true); context.fill(); },
-    beginPath: function() { flushActions(true); context.beginPath(); },
-    closePath: function() { flushActions(true); context.closePath(); },
-
-    _count: function() { return actionCount; }
-  };
-};
-
-/**
- * Draws the shaded regions when "fillGraph" is set. Not to be confused with
- * error bars.
- *
- * For stacked charts, it's more convenient to handle all the series
- * simultaneously. So this plotter plots all the points on the first series
- * it's asked to draw, then ignores all the other series.
- *
- * @private
- */
-DygraphCanvasRenderer._fillPlotter = function(e) {
-  // Skip if we're drawing a single series for interactive highlight overlay.
-  if (e.singleSeriesName) return;
-
-  // We'll handle all the series at once, not one-by-one.
-  if (e.seriesIndex !== 0) return;
-
-  var g = e.dygraph;
-  var setNames = g.getLabels().slice(1);  // remove x-axis
-
-  // getLabels() includes names for invisible series, which are not included in
-  // allSeriesPoints. We remove those to make the two match.
-  // TODO(danvk): provide a simpler way to get this information.
-  for (var i = setNames.length; i >= 0; i--) {
-    if (!g.visibility()[i]) setNames.splice(i, 1);
-  }
-
-  var anySeriesFilled = (function() {
-    for (var i = 0; i < setNames.length; i++) {
-      if (g.getBooleanOption("fillGraph", setNames[i])) return true;
-    }
-    return false;
-  })();
-
-  if (!anySeriesFilled) return;
-
-  var area = e.plotArea;
-  var sets = e.allSeriesPoints;
-  var setCount = sets.length;
-
-  var fillAlpha = g.getNumericOption('fillAlpha');
-  var stackedGraph = g.getBooleanOption("stackedGraph");
-  var colors = g.getColors();
-
-  // For stacked graphs, track the baseline for filling.
-  //
-  // The filled areas below graph lines are trapezoids with two
-  // vertical edges. The top edge is the line segment being drawn, and
-  // the baseline is the bottom edge. Each baseline corresponds to the
-  // top line segment from the previous stacked line. In the case of
-  // step plots, the trapezoids are rectangles.
-  var baseline = {};
-  var currBaseline;
-  var prevStepPlot;  // for different line drawing modes (line/step) per series
-
-  // Helper function to trace a line back along the baseline.
-  var traceBackPath = function(ctx, baselineX, baselineY, pathBack) {
-    ctx.lineTo(baselineX, baselineY);
-    if (stackedGraph) {
-      for (var i = pathBack.length - 1; i >= 0; i--) {
-        var pt = pathBack[i];
-        ctx.lineTo(pt[0], pt[1]);
-      }
-    }
-  };
-
-  // process sets in reverse order (needed for stacked graphs)
-  for (var setIdx = setCount - 1; setIdx >= 0; setIdx--) {
-    var ctx = e.drawingContext;
-    var setName = setNames[setIdx];
-    if (!g.getBooleanOption('fillGraph', setName)) continue;
-
-    var stepPlot = g.getBooleanOption('stepPlot', setName);
-    var color = colors[setIdx];
-    var axis = g.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 = area.h * axisY + area.y;
-
-    var points = sets[setIdx];
-    var iter = Dygraph.createIterator(points, 0, points.length,
-        DygraphCanvasRenderer._getIteratorPredicate(
-            g.getBooleanOption("connectSeparatedPoints", setName)));
-
-    // setup graphics context
-    var prevX = NaN;
-    var prevYs = [-1, -1];
-    var newYs;
-    // should be same color as the lines but only 15% opaque.
-    var rgb = Dygraph.toRGB_(color);
-    var err_color =
-        'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')';
-    ctx.fillStyle = err_color;
-    ctx.beginPath();
-    var last_x, is_first = true;
-
-    // If the point density is high enough, dropping segments on their way to
-    // the canvas justifies the overhead of doing so.
-    if (points.length > 2 * g.width_) {
-      ctx = DygraphCanvasRenderer._fastCanvasProxy(ctx);
-    }
-
-    // For filled charts, we draw points from left to right, then back along
-    // the x-axis to complete a shape for filling.
-    // For stacked plots, this "back path" is a more complex shape. This array
-    // stores the [x, y] values needed to trace that shape.
-    var pathBack = [];
-
-    // TODO(danvk): there are a lot of options at play in this loop.
-    //     The logic would be much clearer if some (e.g. stackGraph and
-    //     stepPlot) were split off into separate sub-plotters.
-    var point;
-    while (iter.hasNext) {
-      point = iter.next();
-      if (!Dygraph.isOK(point.y) && !stepPlot) {
-        traceBackPath(ctx, prevX, prevYs[1], pathBack);
-        pathBack = [];
-        prevX = NaN;
-        if (point.y_stacked !== null && !isNaN(point.y_stacked)) {
-          baseline[point.canvasx] = area.h * point.y_stacked + area.y;
-        }
-        continue;
-      }
-      if (stackedGraph) {
-        if (!is_first && last_x == point.xval) {
-          continue;
-        } else {
-          is_first = false;
-          last_x = point.xval;
-        }
-
-        currBaseline = baseline[point.canvasx];
-        var lastY;
-        if (currBaseline === undefined) {
-          lastY = axisY;
-        } else {
-          if(prevStepPlot) {
-            lastY = currBaseline[0];
-          } else {
-            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 {
-          baseline[point.canvasx] = point.canvasy;
-        }
-
-      } else {
-        if (isNaN(point.canvasy) && stepPlot) {
-          newYs = [ area.y + area.h, axisY ];
-        } else {
-          newYs = [ point.canvasy, axisY ];
-        }
-      }
-      if (!isNaN(prevX)) {
-        // Move to top fill point
-        if (stepPlot) {
-          ctx.lineTo(point.canvasx, prevYs[0]);
-          ctx.lineTo(point.canvasx, newYs[0]);
-        } else {
-          ctx.lineTo(point.canvasx, newYs[0]);
-        }
-
-        // Record the baseline for the reverse path.
-        if (stackedGraph) {
-          pathBack.push([prevX, prevYs[1]]);
-          if (prevStepPlot && currBaseline) {
-            // Draw to the bottom of the baseline
-            pathBack.push([point.canvasx, currBaseline[1]]);
-          } else {
-            pathBack.push([point.canvasx, newYs[1]]);
-          }
-        }
-      } else {
-        ctx.moveTo(point.canvasx, newYs[1]);
-        ctx.lineTo(point.canvasx, newYs[0]);
-      }
-      prevYs = newYs;
-      prevX = point.canvasx;
-    }
-    prevStepPlot = stepPlot;
-    if (newYs && point) {
-      traceBackPath(ctx, point.canvasx, newYs[1], pathBack);
-      pathBack = [];
-    }
-    ctx.fill();
-  }
-};
-
-return DygraphCanvasRenderer;
-
-})();