3 * Copyright 2006 Dan Vanderkam (danvdk@gmail.com)
4 * MIT-licensed (http://opensource.org/licenses/MIT)
8 * @fileoverview Based on PlotKit.CanvasRenderer, but modified to meet the
11 * In particular, support for:
14 * - dygraphs attribute system
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.
27 /*global Dygraph:false */
30 import * as utils from
'./dygraph-utils';
31 import Dygraph from
'./dygraph';
37 * This gets called when there are "new points" to chart. This is generally the
38 * case when the underlying data being charted has changed. It is _not_ called
39 * in the common case that the user has zoomed or is panning the view.
41 * The chart canvas has already been created by the Dygraph object. The
42 * renderer simply gets a drawing context.
44 * @param {Dygraph} dygraph The chart to which this renderer belongs.
45 * @param {HTMLCanvasElement} element The <canvas> DOM element on which to draw.
46 * @param {CanvasRenderingContext2D} elementContext The drawing context.
47 * @param {DygraphLayout} layout The chart's DygraphLayout object.
49 * TODO(danvk): remove the elementContext property.
51 var DygraphCanvasRenderer
= function(dygraph
, element
, elementContext
, layout
) {
52 this.dygraph_
= dygraph
;
55 this.element
= element
;
56 this.elementContext
= elementContext
;
58 this.height
= dygraph
.height_
;
59 this.width
= dygraph
.width_
;
61 // --- check whether everything is ok before we return
62 if (!utils
.isCanvasSupported(this.element
)) {
63 throw "Canvas is not supported.";
67 this.area
= layout
.getPlotArea();
69 // Set up a clipping area for the canvas (and the interaction canvas).
70 // This ensures that we don't overdraw.
71 // on Android 3 and 4, setting a clipping area on a canvas prevents it from
72 // displaying anything.
73 if (!utils
.isAndroid()) {
74 var ctx
= this.dygraph_
.canvas_ctx_
;
76 ctx
.rect(this.area
.x
, this.area
.y
, this.area
.w
, this.area
.h
);
79 ctx
= this.dygraph_
.hidden_ctx_
;
81 ctx
.rect(this.area
.x
, this.area
.y
, this.area
.w
, this.area
.h
);
87 * Clears out all chart content and DOM elements.
88 * This is called immediately before render() on every frame, including
89 * during zooms and pans.
92 DygraphCanvasRenderer
.prototype.clear
= function() {
93 this.elementContext
.clearRect(0, 0, this.width
, this.height
);
97 * This method is responsible for drawing everything on the chart, including
98 * lines, error bars, fills and axes.
99 * It is called immediately after clear() on every frame, including during pans
103 DygraphCanvasRenderer
.prototype.render
= function() {
104 // attaches point.canvas{x,y}
105 this._updatePoints();
107 // actually draws the chart.
108 this._renderLineChart();
112 * Returns a predicate to be used with an iterator, which will
113 * iterate over points appropriately, depending on whether
114 * connectSeparatedPoints is true. When it's false, the predicate will
115 * skip over points with missing yVals.
117 DygraphCanvasRenderer
._getIteratorPredicate
= function(connectSeparatedPoints
) {
118 return connectSeparatedPoints
?
119 DygraphCanvasRenderer
._predicateThatSkipsEmptyPoints
:
123 DygraphCanvasRenderer
._predicateThatSkipsEmptyPoints
=
124 function(array
, idx
) {
125 return array
[idx
].yval
!== null;
129 * Draws a line with the styles passed in and calls all the drawPointCallbacks.
130 * @param {Object} e The dictionary passed to the plotter function.
133 DygraphCanvasRenderer
._drawStyledLine
= function(e
,
134 color
, strokeWidth
, strokePattern
, drawPoints
,
135 drawPointCallback
, pointSize
) {
137 // TODO(konigsberg): Compute attributes outside this method call.
138 var stepPlot
= g
.getBooleanOption("stepPlot", e
.setName
);
140 if (!utils
.isArrayLike(strokePattern
)) {
141 strokePattern
= null;
144 var drawGapPoints
= g
.getBooleanOption('drawGapEdgePoints', e
.setName
);
146 var points
= e
.points
;
147 var setName
= e
.setName
;
148 var iter
= utils
.createIterator(points
, 0, points
.length
,
149 DygraphCanvasRenderer
._getIteratorPredicate(
150 g
.getBooleanOption("connectSeparatedPoints", setName
)));
152 var stroking
= strokePattern
&& (strokePattern
.length
>= 2);
154 var ctx
= e
.drawingContext
;
157 if (ctx
.setLineDash
) ctx
.setLineDash(strokePattern
);
160 var pointsOnLine
= DygraphCanvasRenderer
._drawSeries(
161 e
, iter
, strokeWidth
, pointSize
, drawPoints
, drawGapPoints
, stepPlot
, color
);
162 DygraphCanvasRenderer
._drawPointsOnLine(
163 e
, pointsOnLine
, drawPointCallback
, color
, pointSize
);
166 if (ctx
.setLineDash
) ctx
.setLineDash([]);
173 * This does the actual drawing of lines on the canvas, for just one series.
174 * Returns a list of [canvasx, canvasy] pairs for points for which a
175 * drawPointCallback should be fired. These include isolated points, or all
176 * points if drawPoints=true.
177 * @param {Object} e The dictionary passed to the plotter function.
180 DygraphCanvasRenderer
._drawSeries
= function(e
,
181 iter
, strokeWidth
, pointSize
, drawPoints
, drawGapPoints
, stepPlot
, color
) {
183 var prevCanvasX
= null;
184 var prevCanvasY
= null;
185 var nextCanvasY
= null;
186 var isIsolated
; // true if this point is isolated (no line segments)
187 var point
; // the point being processed in the while loop
188 var pointsOnLine
= []; // Array of [canvasx, canvasy] pairs.
189 var first
= true; // the first cycle through the while loop
191 var ctx
= e
.drawingContext
;
193 ctx
.strokeStyle
= color
;
194 ctx
.lineWidth
= strokeWidth
;
196 // NOTE: we break the iterator's encapsulation here for about a 25% speedup.
197 var arr
= iter
.array_
;
198 var limit
= iter
.end_
;
199 var predicate
= iter
.predicate_
;
201 for (var i
= iter
.start_
; i
< limit
; i
++) {
204 while (i
< limit
&& !predicate(arr
, i
)) {
207 if (i
== limit
) break;
211 // FIXME: The 'canvasy != canvasy' test here catches NaN values but the test
212 // doesn't catch Infinity values. Could change this to
213 // !isFinite(point.canvasy), but I assume it avoids isNaN for performance?
214 if (point
.canvasy
=== null || point
.canvasy
!= point
.canvasy
) {
215 if (stepPlot
&& prevCanvasX
!== null) {
216 // Draw a horizontal line to the start of the missing data
217 ctx
.moveTo(prevCanvasX
, prevCanvasY
);
218 ctx
.lineTo(point
.canvasx
, prevCanvasY
);
220 prevCanvasX
= prevCanvasY
= null;
223 if (drawGapPoints
|| !prevCanvasX
) {
226 nextCanvasY
= iter
.hasNext
? iter
.peek
.canvasy
: null;
228 var isNextCanvasYNullOrNaN
= nextCanvasY
=== null ||
229 nextCanvasY
!= nextCanvasY
;
230 isIsolated
= (!prevCanvasX
&& isNextCanvasYNullOrNaN
);
232 // Also consider a point to be "isolated" if it's adjacent to a
233 // null point, excluding the graph edges.
234 if ((!first
&& !prevCanvasX
) ||
235 (iter
.hasNext
&& isNextCanvasYNullOrNaN
)) {
241 if (prevCanvasX
!== null) {
244 ctx
.moveTo(prevCanvasX
, prevCanvasY
);
245 ctx
.lineTo(point
.canvasx
, prevCanvasY
);
248 ctx
.lineTo(point
.canvasx
, point
.canvasy
);
251 ctx
.moveTo(point
.canvasx
, point
.canvasy
);
253 if (drawPoints
|| isIsolated
) {
254 pointsOnLine
.push([point
.canvasx
, point
.canvasy
, point
.idx
]);
256 prevCanvasX
= point
.canvasx
;
257 prevCanvasY
= point
.canvasy
;
266 * This fires the drawPointCallback functions, which draw dots on the points by
267 * default. This gets used when the "drawPoints" option is set, or when there
268 * are isolated points.
269 * @param {Object} e The dictionary passed to the plotter function.
272 DygraphCanvasRenderer
._drawPointsOnLine
= function(
273 e
, pointsOnLine
, drawPointCallback
, color
, pointSize
) {
274 var ctx
= e
.drawingContext
;
275 for (var idx
= 0; idx
< pointsOnLine
.length
; idx
++) {
276 var cb
= pointsOnLine
[idx
];
278 drawPointCallback
.call(e
.dygraph
,
279 e
.dygraph
, e
.setName
, ctx
, cb
[0], cb
[1], color
, pointSize
, cb
[2]);
285 * Attaches canvas coordinates to the points array.
288 DygraphCanvasRenderer
.prototype._updatePoints
= function() {
292 // TODO(bhs): this loop is a hot-spot for high-point-count charts. These
293 // transformations can be pushed into the canvas via linear transformation
295 // NOTE(danvk): this is trickier than it sounds at first. The transformation
296 // needs to be done before the .moveTo() and .lineTo() calls, but must be
297 // undone before the .stroke() call to ensure that the stroke width is
298 // unaffected. An alternative is to reduce the stroke width in the
299 // transformed coordinate space, but you can't specify different values for
300 // each dimension (as you can with .scale()). The speedup here is ~12%.
301 var sets
= this.layout
.points
;
302 for (var i
= sets
.length
; i
--;) {
303 var points
= sets
[i
];
304 for (var j
= points
.length
; j
--;) {
305 var point
= points
[j
];
306 point
.canvasx
= this.area
.w
* point
.x
+ this.area
.x
;
307 point
.canvasy
= this.area
.h
* point
.y
+ this.area
.y
;
313 * Add canvas Actually draw the lines chart, including error bars.
315 * This function can only be called if DygraphLayout's points array has been
316 * updated with canvas{x,y} attributes, i.e. by
317 * DygraphCanvasRenderer._updatePoints.
319 * @param {string=} opt_seriesName when specified, only that series will
320 * be drawn. (This is used for expedited redrawing with highlightSeriesOpts)
321 * @param {CanvasRenderingContext2D} opt_ctx when specified, the drawing
322 * context. However, lines are typically drawn on the object's
326 DygraphCanvasRenderer
.prototype._renderLineChart
= function(opt_seriesName
, opt_ctx
) {
327 var ctx
= opt_ctx
|| this.elementContext
;
330 var sets
= this.layout
.points
;
331 var setNames
= this.layout
.setNames
;
334 this.colors
= this.dygraph_
.colorsMap_
;
336 // Determine which series have specialized plotters.
337 var plotter_attr
= this.dygraph_
.getOption("plotter");
338 var plotters
= plotter_attr
;
339 if (!utils
.isArrayLike(plotters
)) {
340 plotters
= [plotters
];
343 var setPlotters
= {}; // series name -> plotter fn.
344 for (i
= 0; i
< setNames
.length
; i
++) {
345 setName
= setNames
[i
];
346 var setPlotter
= this.dygraph_
.getOption("plotter", setName
);
347 if (setPlotter
== plotter_attr
) continue; // not specialized.
349 setPlotters
[setName
] = setPlotter
;
352 for (i
= 0; i
< plotters
.length
; i
++) {
353 var plotter
= plotters
[i
];
354 var is_last
= (i
== plotters
.length
- 1);
356 for (var j
= 0; j
< sets
.length
; j
++) {
357 setName
= setNames
[j
];
358 if (opt_seriesName
&& setName
!= opt_seriesName
) continue;
360 var points
= sets
[j
];
362 // Only throw in the specialized plotters on the last iteration.
364 if (setName
in setPlotters
) {
366 p
= setPlotters
[setName
];
368 // Don't use the standard plotters in this case.
373 var color
= this.colors
[setName
];
374 var strokeWidth
= this.dygraph_
.getOption("strokeWidth", setName
);
377 ctx
.strokeStyle
= color
;
378 ctx
.lineWidth
= strokeWidth
;
384 strokeWidth
: strokeWidth
,
385 dygraph
: this.dygraph_
,
386 axis
: this.dygraph_
.axisPropertiesForSeries(setName
),
389 seriesCount
: sets
.length
,
390 singleSeriesName
: opt_seriesName
,
391 allSeriesPoints
: sets
399 * Standard plotters. These may be used by clients via Dygraph.Plotters.
400 * See comments there for more details.
402 DygraphCanvasRenderer
._Plotters
= {
403 linePlotter
: function(e
) {
404 DygraphCanvasRenderer
._linePlotter(e
);
407 fillPlotter
: function(e
) {
408 DygraphCanvasRenderer
._fillPlotter(e
);
411 errorPlotter
: function(e
) {
412 DygraphCanvasRenderer
._errorPlotter(e
);
417 * Plotter which draws the central lines for a series.
420 DygraphCanvasRenderer
._linePlotter
= function(e
) {
422 var setName
= e
.setName
;
423 var strokeWidth
= e
.strokeWidth
;
425 // TODO(danvk): Check if there's any performance impact of just calling
426 // getOption() inside of _drawStyledLine. Passing in so many parameters makes
427 // this code a bit nasty.
428 var borderWidth
= g
.getNumericOption("strokeBorderWidth", setName
);
429 var drawPointCallback
= g
.getOption("drawPointCallback", setName
) ||
430 utils
.Circles
.DEFAULT
;
431 var strokePattern
= g
.getOption("strokePattern", setName
);
432 var drawPoints
= g
.getBooleanOption("drawPoints", setName
);
433 var pointSize
= g
.getNumericOption("pointSize", setName
);
435 if (borderWidth
&& strokeWidth
) {
436 DygraphCanvasRenderer
._drawStyledLine(e
,
437 g
.getOption("strokeBorderColor", setName
),
438 strokeWidth
+ 2 * borderWidth
,
446 DygraphCanvasRenderer
._drawStyledLine(e
,
457 * Draws the shaded error bars/confidence intervals for each series.
458 * This happens before the center lines are drawn, since the center lines
459 * need to be drawn on top of the error bars for all series.
462 DygraphCanvasRenderer
._errorPlotter
= function(e
) {
464 var setName
= e
.setName
;
465 var errorBars
= g
.getBooleanOption("errorBars") ||
466 g
.getBooleanOption("customBars");
467 if (!errorBars
) return;
469 var fillGraph
= g
.getBooleanOption("fillGraph", setName
);
471 console
.warn("Can't use fillGraph option with error bars");
474 var ctx
= e
.drawingContext
;
476 var fillAlpha
= g
.getNumericOption('fillAlpha', setName
);
477 var stepPlot
= g
.getBooleanOption("stepPlot", setName
);
478 var points
= e
.points
;
480 var iter
= utils
.createIterator(points
, 0, points
.length
,
481 DygraphCanvasRenderer
._getIteratorPredicate(
482 g
.getBooleanOption("connectSeparatedPoints", setName
)));
486 // setup graphics context
489 var prevYs
= [-1, -1];
490 // should be same color as the lines but only 15% opaque.
491 var rgb
= utils
.toRGB_(color
);
493 'rgba(' + rgb
.r
+ ',' + rgb
.g
+ ',' + rgb
.b
+ ',' + fillAlpha
+ ')';
494 ctx
.fillStyle
= err_color
;
497 var isNullUndefinedOrNaN
= function(x
) {
498 return (x
=== null ||
503 while (iter
.hasNext
) {
504 var point
= iter
.next();
505 if ((!stepPlot
&& isNullUndefinedOrNaN(point
.y
)) ||
506 (stepPlot
&& !isNaN(prevY
) && isNullUndefinedOrNaN(prevY
))) {
511 newYs
= [ point
.y_bottom
, point
.y_top
];
516 // The documentation specifically disallows nulls inside the point arrays,
517 // but in case it happens we should do something sensible.
518 if (isNaN(newYs
[0])) newYs
[0] = point
.y
;
519 if (isNaN(newYs
[1])) newYs
[1] = point
.y
;
521 newYs
[0] = e
.plotArea
.h
* newYs
[0] + e
.plotArea
.y
;
522 newYs
[1] = e
.plotArea
.h
* newYs
[1] + e
.plotArea
.y
;
525 ctx
.moveTo(prevX
, prevYs
[0]);
526 ctx
.lineTo(point
.canvasx
, prevYs
[0]);
527 ctx
.lineTo(point
.canvasx
, prevYs
[1]);
529 ctx
.moveTo(prevX
, prevYs
[0]);
530 ctx
.lineTo(point
.canvasx
, newYs
[0]);
531 ctx
.lineTo(point
.canvasx
, newYs
[1]);
533 ctx
.lineTo(prevX
, prevYs
[1]);
537 prevX
= point
.canvasx
;
544 * Proxy for CanvasRenderingContext2D which drops moveTo/lineTo calls which are
545 * superfluous. It accumulates all movements which haven't changed the x-value
546 * and only applies the two with the most extreme y-values.
548 * Calls to lineTo/moveTo must have non-decreasing x-values.
550 DygraphCanvasRenderer
._fastCanvasProxy
= function(context
) {
551 var pendingActions
= []; // array of [type, x, y] tuples
552 var lastRoundedX
= null;
553 var lastFlushedX
= null;
558 var actionCount
= 0; // number of moveTos and lineTos passed to context.
560 // Drop superfluous motions
561 // Assumes all pendingActions have the same (rounded) x-value.
562 var compressActions
= function(opt_losslessOnly
) {
563 if (pendingActions
.length
<= 1) return;
565 // Lossless compression: drop inconsequential moveTos.
566 for (var i
= pendingActions
.length
- 1; i
> 0; i
--) {
567 var action
= pendingActions
[i
];
568 if (action
[0] == MOVE_TO
) {
569 var prevAction
= pendingActions
[i
- 1];
570 if (prevAction
[1] == action
[1] && prevAction
[2] == action
[2]) {
571 pendingActions
.splice(i
, 1);
576 // Lossless compression: ... drop consecutive moveTos ...
577 for (var i
= 0; i
< pendingActions
.length
- 1; /* incremented internally */) {
578 var action
= pendingActions
[i
];
579 if (action
[0] == MOVE_TO
&& pendingActions
[i
+ 1][0] == MOVE_TO
) {
580 pendingActions
.splice(i
, 1);
586 // Lossy compression: ... drop all but the extreme y-values ...
587 if (pendingActions
.length
> 2 && !opt_losslessOnly
) {
588 // keep an initial moveTo, but drop all others.
590 if (pendingActions
[0][0] == MOVE_TO
) startIdx
++;
591 var minIdx
= null, maxIdx
= null;
592 for (var i
= startIdx
; i
< pendingActions
.length
; i
++) {
593 var action
= pendingActions
[i
];
594 if (action
[0] != LINE_TO
) continue;
595 if (minIdx
=== null && maxIdx
=== null) {
600 if (y
< pendingActions
[minIdx
][2]) {
602 } else if (y
> pendingActions
[maxIdx
][2]) {
607 var minAction
= pendingActions
[minIdx
],
608 maxAction
= pendingActions
[maxIdx
];
609 pendingActions
.splice(startIdx
, pendingActions
.length
- startIdx
);
610 if (minIdx
< maxIdx
) {
611 pendingActions
.push(minAction
);
612 pendingActions
.push(maxAction
);
613 } else if (minIdx
> maxIdx
) {
614 pendingActions
.push(maxAction
);
615 pendingActions
.push(minAction
);
617 pendingActions
.push(minAction
);
622 var flushActions
= function(opt_noLossyCompression
) {
623 compressActions(opt_noLossyCompression
);
624 for (var i
= 0, len
= pendingActions
.length
; i
< len
; i
++) {
625 var action
= pendingActions
[i
];
626 if (action
[0] == LINE_TO
) {
627 context
.lineTo(action
[1], action
[2]);
628 } else if (action
[0] == MOVE_TO
) {
629 context
.moveTo(action
[1], action
[2]);
632 if (pendingActions
.length
) {
633 lastFlushedX
= pendingActions
[pendingActions
.length
- 1][1];
635 actionCount
+= pendingActions
.length
;
639 var addAction
= function(action
, x
, y
) {
640 var rx
= Math
.round(x
);
641 if (lastRoundedX
=== null || rx
!= lastRoundedX
) {
642 // if there are large gaps on the x-axis, it's essential to keep the
643 // first and last point as well.
644 var hasGapOnLeft
= (lastRoundedX
- lastFlushedX
> 1),
645 hasGapOnRight
= (rx
- lastRoundedX
> 1),
646 hasGap
= hasGapOnLeft
|| hasGapOnRight
;
647 flushActions(hasGap
);
650 pendingActions
.push([action
, x
, y
]);
654 moveTo
: function(x
, y
) {
655 addAction(MOVE_TO
, x
, y
);
657 lineTo
: function(x
, y
) {
658 addAction(LINE_TO
, x
, y
);
661 // for major operations like stroke/fill
, we skip compression to ensure
662 // that there are no artifacts at the right edge.
663 stroke
: function() { flushActions(true); context
.stroke(); },
664 fill
: function() { flushActions(true); context
.fill(); },
665 beginPath
: function() { flushActions(true); context
.beginPath(); },
666 closePath
: function() { flushActions(true); context
.closePath(); },
668 _count
: function() { return actionCount
; }
673 * Draws the shaded regions when "fillGraph" is set. Not to be confused with
676 * For stacked charts, it's more convenient to handle all the series
677 * simultaneously. So this plotter plots all the points on the first series
678 * it's asked to draw, then ignores all the other series.
682 DygraphCanvasRenderer
._fillPlotter
= function(e
) {
683 // Skip if we're drawing a single series for interactive highlight overlay.
684 if (e
.singleSeriesName
) return;
686 // We'll handle all the series at once, not one-by-one.
687 if (e
.seriesIndex
!== 0) return;
690 var setNames
= g
.getLabels().slice(1); // remove x-axis
692 // getLabels() includes names for invisible series, which are not included in
693 // allSeriesPoints. We remove those to make the two match.
694 // TODO(danvk): provide a simpler way to get this information.
695 for (var i
= setNames
.length
; i
>= 0; i
--) {
696 if (!g
.visibility()[i
]) setNames
.splice(i
, 1);
699 var anySeriesFilled
= (function() {
700 for (var i
= 0; i
< setNames
.length
; i
++) {
701 if (g
.getBooleanOption("fillGraph", setNames
[i
])) return true;
706 if (!anySeriesFilled
) return;
708 var area
= e
.plotArea
;
709 var sets
= e
.allSeriesPoints
;
710 var setCount
= sets
.length
;
712 var stackedGraph
= g
.getBooleanOption("stackedGraph");
713 var colors
= g
.getColors();
715 // For stacked graphs, track the baseline for filling.
717 // The filled areas below graph lines are trapezoids with two
718 // vertical edges. The top edge is the line segment being drawn, and
719 // the baseline is the bottom edge. Each baseline corresponds to the
720 // top line segment from the previous stacked line. In the case of
721 // step plots, the trapezoids are rectangles.
724 var prevStepPlot
; // for different line drawing modes (line/step) per series
726 // Helper function to trace a line back along the baseline.
727 var traceBackPath
= function(ctx
, baselineX
, baselineY
, pathBack
) {
728 ctx
.lineTo(baselineX
, baselineY
);
730 for (var i
= pathBack
.length
- 1; i
>= 0; i
--) {
731 var pt
= pathBack
[i
];
732 ctx
.lineTo(pt
[0], pt
[1]);
737 // process sets in reverse order (needed for stacked graphs)
738 for (var setIdx
= setCount
- 1; setIdx
>= 0; setIdx
--) {
739 var ctx
= e
.drawingContext
;
740 var setName
= setNames
[setIdx
];
741 if (!g
.getBooleanOption('fillGraph', setName
)) continue;
743 var fillAlpha
= g
.getNumericOption('fillAlpha', setName
);
744 var stepPlot
= g
.getBooleanOption('stepPlot', setName
);
745 var color
= colors
[setIdx
];
746 var axis
= g
.axisPropertiesForSeries(setName
);
747 var axisY
= 1.0 + axis
.minyval
* axis
.yscale
;
748 if (axisY
< 0.0) axisY
= 0.0;
749 else if (axisY
> 1.0) axisY
= 1.0;
750 axisY
= area
.h
* axisY
+ area
.y
;
752 var points
= sets
[setIdx
];
753 var iter
= utils
.createIterator(points
, 0, points
.length
,
754 DygraphCanvasRenderer
._getIteratorPredicate(
755 g
.getBooleanOption("connectSeparatedPoints", setName
)));
757 // setup graphics context
759 var prevYs
= [-1, -1];
761 // should be same color as the lines but only 15% opaque.
762 var rgb
= utils
.toRGB_(color
);
764 'rgba(' + rgb
.r
+ ',' + rgb
.g
+ ',' + rgb
.b
+ ',' + fillAlpha
+ ')';
765 ctx
.fillStyle
= err_color
;
767 var last_x
, is_first
= true;
769 // If the point density is high enough, dropping segments on their way to
770 // the canvas justifies the overhead of doing so.
771 if (points
.length
> 2 * g
.width_
|| Dygraph
.FORCE_FAST_PROXY
) {
772 ctx
= DygraphCanvasRenderer
._fastCanvasProxy(ctx
);
775 // For filled charts, we draw points from left to right, then back along
776 // the x-axis to complete a shape for filling.
777 // For stacked plots, this "back path" is a more complex shape. This array
778 // stores the [x, y] values needed to trace that shape.
781 // TODO(danvk): there are a lot of options at play in this loop.
782 // The logic would be much clearer if some (e.g. stackGraph and
783 // stepPlot) were split off into separate sub-plotters.
785 while (iter
.hasNext
) {
787 if (!utils
.isOK(point
.y
) && !stepPlot
) {
788 traceBackPath(ctx
, prevX
, prevYs
[1], pathBack
);
791 if (point
.y_stacked
!== null && !isNaN(point
.y_stacked
)) {
792 baseline
[point
.canvasx
] = area
.h
* point
.y_stacked
+ area
.y
;
797 if (!is_first
&& last_x
== point
.xval
) {
804 currBaseline
= baseline
[point
.canvasx
];
806 if (currBaseline
=== undefined
) {
810 lastY
= currBaseline
[0];
812 lastY
= currBaseline
;
815 newYs
= [ point
.canvasy
, lastY
];
818 // Step plots must keep track of the top and bottom of
819 // the baseline at each point.
820 if (prevYs
[0] === -1) {
821 baseline
[point
.canvasx
] = [ point
.canvasy
, axisY
];
823 baseline
[point
.canvasx
] = [ point
.canvasy
, prevYs
[0] ];
826 baseline
[point
.canvasx
] = point
.canvasy
;
830 if (isNaN(point
.canvasy
) && stepPlot
) {
831 newYs
= [ area
.y
+ area
.h
, axisY
];
833 newYs
= [ point
.canvasy
, axisY
];
837 // Move to top fill point
839 ctx
.lineTo(point
.canvasx
, prevYs
[0]);
840 ctx
.lineTo(point
.canvasx
, newYs
[0]);
842 ctx
.lineTo(point
.canvasx
, newYs
[0]);
845 // Record the baseline for the reverse path.
847 pathBack
.push([prevX
, prevYs
[1]]);
848 if (prevStepPlot
&& currBaseline
) {
849 // Draw to the bottom of the baseline
850 pathBack
.push([point
.canvasx
, currBaseline
[1]]);
852 pathBack
.push([point
.canvasx
, newYs
[1]]);
856 ctx
.moveTo(point
.canvasx
, newYs
[1]);
857 ctx
.lineTo(point
.canvasx
, newYs
[0]);
860 prevX
= point
.canvasx
;
862 prevStepPlot
= stepPlot
;
863 if (newYs
&& point
) {
864 traceBackPath(ctx
, point
.canvasx
, newYs
[1], pathBack
);
871 export default DygraphCanvasRenderer
;