1 /**
  2  * @license
  3  * Copyright 2006 Dan Vanderkam (danvdk@gmail.com)
  4  * MIT-licenced: https://opensource.org/licenses/MIT
  5  */
  6 
  7 /**
  8  * @fileoverview Based on PlotKit.CanvasRenderer, but modified to meet the
  9  * needs of dygraphs.
 10  *
 11  * In particular, support for:
 12  * - grid overlays
 13  * - high/low bands
 14  * - dygraphs attribute system
 15  */
 16 
 17 /**
 18  * The DygraphCanvasRenderer class does the actual rendering of the chart onto
 19  * a canvas. It's based on PlotKit.CanvasRenderer.
 20  * @param {Object} element The canvas to attach to
 21  * @param {Object} elementContext The 2d context of the canvas (injected so it
 22  * can be mocked for testing.)
 23  * @param {Layout} layout The DygraphLayout object for this graph.
 24  * @constructor
 25  */
 26 
 27 /*global Dygraph:false */
 28 "use strict";
 29 
 30 import * as utils from './dygraph-utils';
 31 import Dygraph from './dygraph';
 32 
 33 /**
 34  * @constructor
 35  *
 36  * This gets called when there are "new points" to chart. This is generally the
 37  * case when the underlying data being charted has changed. It is _not_ called
 38  * in the common case that the user has zoomed or is panning the view.
 39  *
 40  * The chart canvas has already been created by the Dygraph object. The
 41  * renderer simply gets a drawing context.
 42  *
 43  * @param {Dygraph} dygraph The chart to which this renderer belongs.
 44  * @param {HTMLCanvasElement} element The <canvas> DOM element on which to draw.
 45  * @param {CanvasRenderingContext2D} elementContext The drawing context.
 46  * @param {DygraphLayout} layout The chart's DygraphLayout object.
 47  *
 48  * TODO(danvk): remove the elementContext property.
 49  */
 50 var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) {
 51   this.dygraph_ = dygraph;
 52 
 53   this.layout = layout;
 54   this.element = element;
 55   this.elementContext = elementContext;
 56 
 57   this.height = dygraph.height_;
 58   this.width = dygraph.width_;
 59 
 60   // --- check whether everything is ok before we return
 61   if (!utils.isCanvasSupported(this.element)) {
 62     throw "Canvas is not supported.";
 63   }
 64 
 65   // internal state
 66   this.area = layout.getPlotArea();
 67 
 68   // Set up a clipping area for the canvas (and the interaction canvas).
 69   // This ensures that we don't overdraw.
 70   var ctx = this.dygraph_.canvas_ctx_;
 71   ctx.beginPath();
 72   ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h);
 73   ctx.clip();
 74 
 75   ctx = this.dygraph_.hidden_ctx_;
 76   ctx.beginPath();
 77   ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h);
 78   ctx.clip();
 79 };
 80 
 81 /**
 82  * Clears out all chart content and DOM elements.
 83  * This is called immediately before render() on every frame, including
 84  * during zooms and pans.
 85  * @private
 86  */
 87 DygraphCanvasRenderer.prototype.clear = function() {
 88   this.elementContext.clearRect(0, 0, this.width, this.height);
 89 };
 90 
 91 /**
 92  * This method is responsible for drawing everything on the chart, including
 93  * lines, high/low bands, fills and axes.
 94  * It is called immediately after clear() on every frame, including during pans
 95  * and zooms.
 96  * @private
 97  */
 98 DygraphCanvasRenderer.prototype.render = function() {
 99   // attaches point.canvas{x,y}
100   this._updatePoints();
101 
102   // actually draws the chart.
103   this._renderLineChart();
104 };
105 
106 /**
107  * Returns a predicate to be used with an iterator, which will
108  * iterate over points appropriately, depending on whether
109  * connectSeparatedPoints is true. When it's false, the predicate will
110  * skip over points with missing yVals.
111  */
112 DygraphCanvasRenderer._getIteratorPredicate = function(connectSeparatedPoints) {
113   return connectSeparatedPoints ?
114       DygraphCanvasRenderer._predicateThatSkipsEmptyPoints :
115       null;
116 };
117 
118 DygraphCanvasRenderer._predicateThatSkipsEmptyPoints =
119     function(array, idx) {
120   return array[idx].yval !== null;
121 };
122 
123 /**
124  * Draws a line with the styles passed in and calls all the drawPointCallbacks.
125  * @param {Object} e The dictionary passed to the plotter function.
126  * @private
127  */
128 DygraphCanvasRenderer._drawStyledLine = function(e,
129     color, strokeWidth, strokePattern, drawPoints,
130     drawPointCallback, pointSize) {
131   var g = e.dygraph;
132   // TODO(konigsberg): Compute attributes outside this method call.
133   var stepPlot = g.getBooleanOption("stepPlot", e.setName);
134 
135   if (!utils.isArrayLike(strokePattern)) {
136     strokePattern = null;
137   }
138 
139   var drawGapPoints = g.getBooleanOption('drawGapEdgePoints', e.setName);
140 
141   var points = e.points;
142   var setName = e.setName;
143   var iter = utils.createIterator(points, 0, points.length,
144       DygraphCanvasRenderer._getIteratorPredicate(
145           g.getBooleanOption("connectSeparatedPoints", setName)));
146 
147   var stroking = strokePattern && (strokePattern.length >= 2);
148 
149   var ctx = e.drawingContext;
150   ctx.save();
151   if (stroking) {
152     if (ctx.setLineDash) ctx.setLineDash(strokePattern);
153   }
154 
155   var pointsOnLine = DygraphCanvasRenderer._drawSeries(
156       e, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color);
157   DygraphCanvasRenderer._drawPointsOnLine(
158       e, pointsOnLine, drawPointCallback, color, pointSize);
159 
160   if (stroking) {
161     if (ctx.setLineDash) ctx.setLineDash([]);
162   }
163 
164   ctx.restore();
165 };
166 
167 /**
168  * This does the actual drawing of lines on the canvas, for just one series.
169  * Returns a list of [canvasx, canvasy] pairs for points for which a
170  * drawPointCallback should be fired.  These include isolated points, or all
171  * points if drawPoints=true.
172  * @param {Object} e The dictionary passed to the plotter function.
173  * @private
174  */
175 DygraphCanvasRenderer._drawSeries = function(e,
176     iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color) {
177 
178   var prevCanvasX = null;
179   var prevCanvasY = null;
180   var nextCanvasY = null;
181   var isIsolated; // true if this point is isolated (no line segments)
182   var point; // the point being processed in the while loop
183   var pointsOnLine = []; // Array of [canvasx, canvasy] pairs.
184   var first = true; // the first cycle through the while loop
185 
186   var ctx = e.drawingContext;
187   ctx.beginPath();
188   ctx.strokeStyle = color;
189   ctx.lineWidth = strokeWidth;
190 
191   // NOTE: we break the iterator's encapsulation here for about a 25% speedup.
192   var arr = iter.array_;
193   var limit = iter.end_;
194   var predicate = iter.predicate_;
195 
196   for (var i = iter.start_; i < limit; i++) {
197     point = arr[i];
198     if (predicate) {
199       while (i < limit && !predicate(arr, i)) {
200         i++;
201       }
202       if (i == limit) break;
203       point = arr[i];
204     }
205 
206     // FIXME: The 'canvasy != canvasy' test here catches NaN values but the test
207     // doesn't catch Infinity values. Could change this to
208     // !isFinite(point.canvasy), but I assume it avoids isNaN for performance?
209     if (point.canvasy === null || point.canvasy != point.canvasy) {
210       if (stepPlot && prevCanvasX !== null) {
211         // Draw a horizontal line to the start of the missing data
212         ctx.moveTo(prevCanvasX, prevCanvasY);
213         ctx.lineTo(point.canvasx, prevCanvasY);
214       }
215       prevCanvasX = prevCanvasY = null;
216     } else {
217       isIsolated = false;
218       if (drawGapPoints || prevCanvasX === null) {
219         iter.nextIdx_ = i;
220         iter.next();
221         nextCanvasY = iter.hasNext ? iter.peek.canvasy : null;
222 
223         var isNextCanvasYNullOrNaN = nextCanvasY === null ||
224             nextCanvasY != nextCanvasY;
225         isIsolated = (prevCanvasX === null && isNextCanvasYNullOrNaN);
226         if (drawGapPoints) {
227           // Also consider a point to be "isolated" if it's adjacent to a
228           // null point, excluding the graph edges.
229           if ((!first && prevCanvasX === null) ||
230               (iter.hasNext && isNextCanvasYNullOrNaN)) {
231             isIsolated = true;
232           }
233         }
234       }
235 
236       if (prevCanvasX !== null) {
237         if (strokeWidth) {
238           if (stepPlot) {
239             ctx.moveTo(prevCanvasX, prevCanvasY);
240             ctx.lineTo(point.canvasx, prevCanvasY);
241           }
242 
243           ctx.lineTo(point.canvasx, point.canvasy);
244         }
245       } else {
246         ctx.moveTo(point.canvasx, point.canvasy);
247       }
248       if (drawPoints || isIsolated) {
249         pointsOnLine.push([point.canvasx, point.canvasy, point.idx]);
250       }
251       prevCanvasX = point.canvasx;
252       prevCanvasY = point.canvasy;
253     }
254     first = false;
255   }
256   ctx.stroke();
257   return pointsOnLine;
258 };
259 
260 /**
261  * This fires the drawPointCallback functions, which draw dots on the points by
262  * default. This gets used when the "drawPoints" option is set, or when there
263  * are isolated points.
264  * @param {Object} e The dictionary passed to the plotter function.
265  * @private
266  */
267 DygraphCanvasRenderer._drawPointsOnLine = function(
268     e, pointsOnLine, drawPointCallback, color, pointSize) {
269   var ctx = e.drawingContext;
270   for (var idx = 0; idx < pointsOnLine.length; idx++) {
271     var cb = pointsOnLine[idx];
272     ctx.save();
273     drawPointCallback.call(e.dygraph,
274         e.dygraph, e.setName, ctx, cb[0], cb[1], color, pointSize, cb[2]);
275     ctx.restore();
276   }
277 };
278 
279 /**
280  * Attaches canvas coordinates to the points array.
281  * @private
282  */
283 DygraphCanvasRenderer.prototype._updatePoints = function() {
284   // Update Points
285   // TODO(danvk): here
286   //
287   // TODO(bhs): this loop is a hot-spot for high-point-count charts. These
288   // transformations can be pushed into the canvas via linear transformation
289   // matrices.
290   // NOTE(danvk): this is trickier than it sounds at first. The transformation
291   // needs to be done before the .moveTo() and .lineTo() calls, but must be
292   // undone before the .stroke() call to ensure that the stroke width is
293   // unaffected.  An alternative is to reduce the stroke width in the
294   // transformed coordinate space, but you can't specify different values for
295   // each dimension (as you can with .scale()). The speedup here is ~12%.
296   var sets = this.layout.points;
297   for (var i = sets.length; i--;) {
298     var points = sets[i];
299     for (var j = points.length; j--;) {
300       var point = points[j];
301       point.canvasx = this.area.w * point.x + this.area.x;
302       point.canvasy = this.area.h * point.y + this.area.y;
303     }
304   }
305 };
306 
307 /**
308  * Add canvas Actually draw the lines chart, including high/low bands.
309  *
310  * This function can only be called if DygraphLayout's points array has been
311  * updated with canvas{x,y} attributes, i.e. by
312  * DygraphCanvasRenderer._updatePoints.
313  *
314  * @param {string=} opt_seriesName when specified, only that series will
315  *     be drawn. (This is used for expedited redrawing with highlightSeriesOpts)
316  * @param {CanvasRenderingContext2D} opt_ctx when specified, the drawing
317  *     context.  However, lines are typically drawn on the object's
318  *     elementContext.
319  * @private
320  */
321 DygraphCanvasRenderer.prototype._renderLineChart = function(opt_seriesName, opt_ctx) {
322   var ctx = opt_ctx || this.elementContext;
323   var i;
324 
325   var sets = this.layout.points;
326   var setNames = this.layout.setNames;
327   var setName;
328 
329   this.colors = this.dygraph_.colorsMap_;
330 
331   // Determine which series have specialized plotters.
332   var plotter_attr = this.dygraph_.getOption("plotter");
333   var plotters = plotter_attr;
334   if (!utils.isArrayLike(plotters)) {
335     plotters = [plotters];
336   }
337 
338   var setPlotters = {};  // series name -> plotter fn.
339   for (i = 0; i < setNames.length; i++) {
340     setName = setNames[i];
341     var setPlotter = this.dygraph_.getOption("plotter", setName);
342     if (setPlotter == plotter_attr) continue;  // not specialized.
343 
344     setPlotters[setName] = setPlotter;
345   }
346 
347   for (i = 0; i < plotters.length; i++) {
348     var plotter = plotters[i];
349     var is_last = (i == plotters.length - 1);
350 
351     for (var j = 0; j < sets.length; j++) {
352       setName = setNames[j];
353       if (opt_seriesName && setName != opt_seriesName) continue;
354 
355       var points = sets[j];
356 
357       // Only throw in the specialized plotters on the last iteration.
358       var p = plotter;
359       if (setName in setPlotters) {
360         if (is_last) {
361           p = setPlotters[setName];
362         } else {
363           // Don't use the standard plotters in this case.
364           continue;
365         }
366       }
367 
368       var color = this.colors[setName];
369       var strokeWidth = this.dygraph_.getOption("strokeWidth", setName);
370 
371       ctx.save();
372       ctx.strokeStyle = color;
373       ctx.lineWidth = strokeWidth;
374       p({
375         points: points,
376         setName: setName,
377         drawingContext: ctx,
378         color: color,
379         strokeWidth: strokeWidth,
380         dygraph: this.dygraph_,
381         axis: this.dygraph_.axisPropertiesForSeries(setName),
382         plotArea: this.area,
383         seriesIndex: j,
384         seriesCount: sets.length,
385         singleSeriesName: opt_seriesName,
386         allSeriesPoints: sets
387       });
388       ctx.restore();
389     }
390   }
391 };
392 
393 /**
394  * Standard plotters. These may be used by clients via Dygraph.Plotters.
395  * See comments there for more details.
396  */
397 DygraphCanvasRenderer._Plotters = {
398   linePlotter: function(e) {
399     DygraphCanvasRenderer._linePlotter(e);
400   },
401 
402   fillPlotter: function(e) {
403     DygraphCanvasRenderer._fillPlotter(e);
404   },
405 
406   errorPlotter: function(e) {
407     DygraphCanvasRenderer._errorPlotter(e);
408   }
409 };
410 
411 /**
412  * Plotter which draws the central lines for a series.
413  * @private
414  */
415 DygraphCanvasRenderer._linePlotter = function(e) {
416   var g = e.dygraph;
417   var setName = e.setName;
418   var strokeWidth = e.strokeWidth;
419 
420   // TODO(danvk): Check if there's any performance impact of just calling
421   // getOption() inside of _drawStyledLine. Passing in so many parameters makes
422   // this code a bit nasty.
423   var borderWidth = g.getNumericOption("strokeBorderWidth", setName);
424   var drawPointCallback = g.getOption("drawPointCallback", setName) ||
425       utils.Circles.DEFAULT;
426   var strokePattern = g.getOption("strokePattern", setName);
427   var drawPoints = g.getBooleanOption("drawPoints", setName);
428   var pointSize = g.getNumericOption("pointSize", setName);
429 
430   if (borderWidth && strokeWidth) {
431     DygraphCanvasRenderer._drawStyledLine(e,
432         g.getOption("strokeBorderColor", setName),
433         strokeWidth + 2 * borderWidth,
434         strokePattern,
435         drawPoints,
436         drawPointCallback,
437         pointSize
438         );
439   }
440 
441   DygraphCanvasRenderer._drawStyledLine(e,
442       e.color,
443       strokeWidth,
444       strokePattern,
445       drawPoints,
446       drawPointCallback,
447       pointSize
448   );
449 };
450 
451 /**
452  * Draws the shaded high/low bands (confidence intervals) for each series.
453  * This happens before the center lines are drawn, since the center lines
454  * need to be drawn on top of the high/low bands for all series.
455  * @private
456  */
457 DygraphCanvasRenderer._errorPlotter = function(e) {
458   var g = e.dygraph;
459   var setName = e.setName;
460   var errorBars = g.getBooleanOption("errorBars") ||
461       g.getBooleanOption("customBars");
462   if (!errorBars) return;
463 
464   var fillGraph = g.getBooleanOption("fillGraph", setName);
465   if (fillGraph) {
466     console.warn("Can't use fillGraph option with customBars or errorBars option");
467   }
468 
469   var ctx = e.drawingContext;
470   var color = e.color;
471   var fillAlpha = g.getNumericOption('fillAlpha', setName);
472   var stepPlot = g.getBooleanOption("stepPlot", setName);
473   var points = e.points;
474 
475   var iter = utils.createIterator(points, 0, points.length,
476       DygraphCanvasRenderer._getIteratorPredicate(
477           g.getBooleanOption("connectSeparatedPoints", setName)));
478 
479   var newYs;
480 
481   // setup graphics context
482   var prevX = NaN;
483   var prevY = NaN;
484   var prevYs = [-1, -1];
485   // should be same color as the lines but only 15% opaque.
486   var rgb = utils.toRGB_(color);
487   var err_color =
488       'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')';
489   ctx.fillStyle = err_color;
490   ctx.beginPath();
491 
492   var isNullUndefinedOrNaN = function(x) {
493     return (x === null ||
494             x === undefined ||
495             isNaN(x));
496   };
497 
498   while (iter.hasNext) {
499     var point = iter.next();
500     if ((!stepPlot && isNullUndefinedOrNaN(point.y)) ||
501         (stepPlot && !isNaN(prevY) && isNullUndefinedOrNaN(prevY))) {
502       prevX = NaN;
503       continue;
504     }
505 
506     newYs = [ point.y_bottom, point.y_top ];
507     if (stepPlot) {
508       prevY = point.y;
509     }
510 
511     // The documentation specifically disallows nulls inside the point arrays,
512     // but in case it happens we should do something sensible.
513     if (isNaN(newYs[0])) newYs[0] = point.y;
514     if (isNaN(newYs[1])) newYs[1] = point.y;
515 
516     newYs[0] = e.plotArea.h * newYs[0] + e.plotArea.y;
517     newYs[1] = e.plotArea.h * newYs[1] + e.plotArea.y;
518     if (!isNaN(prevX)) {
519       if (stepPlot) {
520         ctx.moveTo(prevX, prevYs[0]);
521         ctx.lineTo(point.canvasx, prevYs[0]);
522         ctx.lineTo(point.canvasx, prevYs[1]);
523       } else {
524         ctx.moveTo(prevX, prevYs[0]);
525         ctx.lineTo(point.canvasx, newYs[0]);
526         ctx.lineTo(point.canvasx, newYs[1]);
527       }
528       ctx.lineTo(prevX, prevYs[1]);
529       ctx.closePath();
530     }
531     prevYs = newYs;
532     prevX = point.canvasx;
533   }
534   ctx.fill();
535 };
536 
537 /**
538  * Proxy for CanvasRenderingContext2D which drops moveTo/lineTo calls which are
539  * superfluous. It accumulates all movements which haven't changed the x-value
540  * and only applies the two with the most extreme y-values.
541  *
542  * Calls to lineTo/moveTo must have non-decreasing x-values.
543  */
544 DygraphCanvasRenderer._fastCanvasProxy = function(context) {
545   var pendingActions = [];  // array of [type, x, y] tuples
546   var lastRoundedX = null;
547   var lastFlushedX = null;
548 
549   var LINE_TO = 1,
550       MOVE_TO = 2;
551 
552   var actionCount = 0;  // number of moveTos and lineTos passed to context.
553 
554   // Drop superfluous motions
555   // Assumes all pendingActions have the same (rounded) x-value.
556   var compressActions = function(opt_losslessOnly) {
557     if (pendingActions.length <= 1) return;
558 
559     // Lossless compression: drop inconsequential moveTos.
560     for (var i = pendingActions.length - 1; i > 0; i--) {
561       var action = pendingActions[i];
562       if (action[0] == MOVE_TO) {
563         var prevAction = pendingActions[i - 1];
564         if (prevAction[1] == action[1] && prevAction[2] == action[2]) {
565           pendingActions.splice(i, 1);
566         }
567       }
568     }
569 
570     // Lossless compression: ... drop consecutive moveTos ...
571     for (var i = 0; i < pendingActions.length - 1; /* incremented internally */) {
572       var action = pendingActions[i];
573       if (action[0] == MOVE_TO && pendingActions[i + 1][0] == MOVE_TO) {
574         pendingActions.splice(i, 1);
575       } else {
576         i++;
577       }
578     }
579 
580     // Lossy compression: ... drop all but the extreme y-values ...
581     if (pendingActions.length > 2 && !opt_losslessOnly) {
582       // keep an initial moveTo, but drop all others.
583       var startIdx = 0;
584       if (pendingActions[0][0] == MOVE_TO) startIdx++;
585       var minIdx = null, maxIdx = null;
586       for (var i = startIdx; i < pendingActions.length; i++) {
587         var action = pendingActions[i];
588         if (action[0] != LINE_TO) continue;
589         if (minIdx === null && maxIdx === null) {
590           minIdx = i;
591           maxIdx = i;
592         } else {
593           var y = action[2];
594           if (y < pendingActions[minIdx][2]) {
595             minIdx = i;
596           } else if (y > pendingActions[maxIdx][2]) {
597             maxIdx = i;
598           }
599         }
600       }
601       var minAction = pendingActions[minIdx],
602           maxAction = pendingActions[maxIdx];
603       pendingActions.splice(startIdx, pendingActions.length - startIdx);
604       if (minIdx < maxIdx) {
605         pendingActions.push(minAction);
606         pendingActions.push(maxAction);
607       } else if (minIdx > maxIdx) {
608         pendingActions.push(maxAction);
609         pendingActions.push(minAction);
610       } else {
611         pendingActions.push(minAction);
612       }
613     }
614   };
615 
616   var flushActions = function(opt_noLossyCompression) {
617     compressActions(opt_noLossyCompression);
618     for (var i = 0, len = pendingActions.length; i < len; i++) {
619       var action = pendingActions[i];
620       if (action[0] == LINE_TO) {
621         context.lineTo(action[1], action[2]);
622       } else if (action[0] == MOVE_TO) {
623         context.moveTo(action[1], action[2]);
624       }
625     }
626     if (pendingActions.length) {
627       lastFlushedX = pendingActions[pendingActions.length - 1][1];
628     }
629     actionCount += pendingActions.length;
630     pendingActions = [];
631   };
632 
633   var addAction = function(action, x, y) {
634     var rx = Math.round(x);
635     if (lastRoundedX === null || rx != lastRoundedX) {
636       // if there are large gaps on the x-axis, it's essential to keep the
637       // first and last point as well.
638       var hasGapOnLeft = (lastRoundedX - lastFlushedX > 1),
639           hasGapOnRight = (rx - lastRoundedX > 1),
640           hasGap = hasGapOnLeft || hasGapOnRight;
641       flushActions(hasGap);
642       lastRoundedX = rx;
643     }
644     pendingActions.push([action, x, y]);
645   };
646 
647   return {
648     moveTo: function(x, y) {
649       addAction(MOVE_TO, x, y);
650     },
651     lineTo: function(x, y) {
652       addAction(LINE_TO, x, y);
653     },
654 
655     // for major operations like stroke/fill, we skip compression to ensure
656     // that there are no artifacts at the right edge.
657     stroke:    function() { flushActions(true); context.stroke(); },
658     fill:      function() { flushActions(true); context.fill(); },
659     beginPath: function() { flushActions(true); context.beginPath(); },
660     closePath: function() { flushActions(true); context.closePath(); },
661 
662     _count: function() { return actionCount; }
663   };
664 };
665 
666 /**
667  * Draws the shaded regions when "fillGraph" is set.
668  * Not to be confused with high/low bands (historically misnamed errorBars).
669  *
670  * For stacked charts, it's more convenient to handle all the series
671  * simultaneously. So this plotter plots all the points on the first series
672  * it's asked to draw, then ignores all the other series.
673  *
674  * @private
675  */
676 DygraphCanvasRenderer._fillPlotter = function(e) {
677   // Skip if we're drawing a single series for interactive highlight overlay.
678   if (e.singleSeriesName) return;
679 
680   // We'll handle all the series at once, not one-by-one.
681   if (e.seriesIndex !== 0) return;
682 
683   var g = e.dygraph;
684   var setNames = g.getLabels().slice(1);  // remove x-axis
685 
686   // getLabels() includes names for invisible series, which are not included in
687   // allSeriesPoints. We remove those to make the two match.
688   // TODO(danvk): provide a simpler way to get this information.
689   for (var i = setNames.length; i >= 0; i--) {
690     if (!g.visibility()[i]) setNames.splice(i, 1);
691   }
692 
693   var anySeriesFilled = (function() {
694     for (var i = 0; i < setNames.length; i++) {
695       if (g.getBooleanOption("fillGraph", setNames[i])) return true;
696     }
697     return false;
698   })();
699 
700   if (!anySeriesFilled) return;
701 
702   var area = e.plotArea;
703   var sets = e.allSeriesPoints;
704   var setCount = sets.length;
705 
706   var stackedGraph = g.getBooleanOption("stackedGraph");
707   var colors = g.getColors();
708 
709   // For stacked graphs, track the baseline for filling.
710   //
711   // The filled areas below graph lines are trapezoids with two
712   // vertical edges. The top edge is the line segment being drawn, and
713   // the baseline is the bottom edge. Each baseline corresponds to the
714   // top line segment from the previous stacked line. In the case of
715   // step plots, the trapezoids are rectangles.
716   var baseline = {};
717   var currBaseline;
718   var prevStepPlot;  // for different line drawing modes (line/step) per series
719 
720   // Helper function to trace a line back along the baseline.
721   var traceBackPath = function(ctx, baselineX, baselineY, pathBack) {
722     ctx.lineTo(baselineX, baselineY);
723     if (stackedGraph) {
724       for (var i = pathBack.length - 1; i >= 0; i--) {
725         var pt = pathBack[i];
726         ctx.lineTo(pt[0], pt[1]);
727       }
728     }
729   };
730 
731   // process sets in reverse order (needed for stacked graphs)
732   for (var setIdx = setCount - 1; setIdx >= 0; setIdx--) {
733     var ctx = e.drawingContext;
734     var setName = setNames[setIdx];
735     if (!g.getBooleanOption('fillGraph', setName)) continue;
736 
737     var fillAlpha = g.getNumericOption('fillAlpha', setName);
738     var stepPlot = g.getBooleanOption('stepPlot', setName);
739     var color = colors[setIdx];
740     var axis = g.axisPropertiesForSeries(setName);
741     var axisY = 1.0 + axis.minyval * axis.yscale;
742     if (axisY < 0.0) axisY = 0.0;
743     else if (axisY > 1.0) axisY = 1.0;
744     axisY = area.h * axisY + area.y;
745 
746     var points = sets[setIdx];
747     var iter = utils.createIterator(points, 0, points.length,
748         DygraphCanvasRenderer._getIteratorPredicate(
749             g.getBooleanOption("connectSeparatedPoints", setName)));
750 
751     // setup graphics context
752     var prevX = NaN;
753     var prevYs = [-1, -1];
754     var newYs;
755     // should be same color as the lines but only 15% opaque.
756     var rgb = utils.toRGB_(color);
757     var err_color =
758         'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')';
759     ctx.fillStyle = err_color;
760     ctx.beginPath();
761     var last_x, is_first = true;
762 
763     // If the point density is high enough, dropping segments on their way to
764     // the canvas justifies the overhead of doing so.
765     if (points.length > 2 * g.width_ || Dygraph.FORCE_FAST_PROXY) {
766       ctx = DygraphCanvasRenderer._fastCanvasProxy(ctx);
767     }
768 
769     // For filled charts, we draw points from left to right, then back along
770     // the x-axis to complete a shape for filling.
771     // For stacked plots, this "back path" is a more complex shape. This array
772     // stores the [x, y] values needed to trace that shape.
773     var pathBack = [];
774 
775     // TODO(danvk): there are a lot of options at play in this loop.
776     //     The logic would be much clearer if some (e.g. stackGraph and
777     //     stepPlot) were split off into separate sub-plotters.
778     var point;
779     while (iter.hasNext) {
780       point = iter.next();
781       if (!utils.isOK(point.y) && !stepPlot) {
782         traceBackPath(ctx, prevX, prevYs[1], pathBack);
783         pathBack = [];
784         prevX = NaN;
785         if (point.y_stacked !== null && !isNaN(point.y_stacked)) {
786           baseline[point.canvasx] = area.h * point.y_stacked + area.y;
787         }
788         continue;
789       }
790       if (stackedGraph) {
791         if (!is_first && last_x == point.xval) {
792           continue;
793         } else {
794           is_first = false;
795           last_x = point.xval;
796         }
797 
798         currBaseline = baseline[point.canvasx];
799         var lastY;
800         if (currBaseline === undefined) {
801           lastY = axisY;
802         } else {
803           if(prevStepPlot) {
804             lastY = currBaseline[0];
805           } else {
806             lastY = currBaseline;
807           }
808         }
809         newYs = [ point.canvasy, lastY ];
810 
811         if (stepPlot) {
812           // Step plots must keep track of the top and bottom of
813           // the baseline at each point.
814           if (prevYs[0] === -1) {
815             baseline[point.canvasx] = [ point.canvasy, axisY ];
816           } else {
817             baseline[point.canvasx] = [ point.canvasy, prevYs[0] ];
818           }
819         } else {
820           baseline[point.canvasx] = point.canvasy;
821         }
822 
823       } else {
824         if (isNaN(point.canvasy) && stepPlot) {
825           newYs = [ area.y + area.h, axisY ];
826         } else {
827           newYs = [ point.canvasy, axisY ];
828         }
829       }
830       if (!isNaN(prevX)) {
831         // Move to top fill point
832         if (stepPlot) {
833           ctx.lineTo(point.canvasx, prevYs[0]);
834           ctx.lineTo(point.canvasx, newYs[0]);
835         } else {
836           ctx.lineTo(point.canvasx, newYs[0]);
837         }
838 
839         // Record the baseline for the reverse path.
840         if (stackedGraph) {
841           pathBack.push([prevX, prevYs[1]]);
842           if (prevStepPlot && currBaseline) {
843             // Draw to the bottom of the baseline
844             pathBack.push([point.canvasx, currBaseline[1]]);
845           } else {
846             pathBack.push([point.canvasx, newYs[1]]);
847           }
848         }
849       } else {
850         ctx.moveTo(point.canvasx, newYs[1]);
851         ctx.lineTo(point.canvasx, newYs[0]);
852       }
853       prevYs = newYs;
854       prevX = point.canvasx;
855     }
856     prevStepPlot = stepPlot;
857     if (newYs && point) {
858       traceBackPath(ctx, point.canvasx, newYs[1], pathBack);
859       pathBack = [];
860     }
861     ctx.fill();
862   }
863 };
864 
865 export default DygraphCanvasRenderer;
866