X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph-canvas.js;fp=dygraph-canvas.js;h=0000000000000000000000000000000000000000;hb=3123ca57f71d145bb5bcc4a2f754d3dff3225346;hp=a688c43975958639e973715cdcbc5727b18712c9;hpb=26ee953643ccd2d32e38e6b60b20e6a01c1dc9ba;p=dygraphs.git diff --git a/dygraph-canvas.js b/dygraph-canvas.js deleted file mode 100644 index a688c43..0000000 --- a/dygraph-canvas.js +++ /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; - -})();