X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph-canvas.js;h=2824003ffd8ebfdad5810a0c3fbaeea35eb9d76d;hb=7b00a3cd1eedf449f0d08f63efc544ffb566ed08;hp=2fc9eaedf5a0e8469f76928c9e80bc7aba493457;hpb=9a7309104212501d777d2b47b2f20f98833b9241;p=dygraphs.git diff --git a/dygraph-canvas.js b/dygraph-canvas.js index 2fc9eae..2824003 100644 --- a/dygraph-canvas.js +++ b/dygraph-canvas.js @@ -1,236 +1,803 @@ -// Copyright 2006 Dan Vanderkam (danvdk@gmail.com) -// All Rights Reserved. +/** + * @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 + */ + +/*jshint globalstrict: true */ +/*global Dygraph:false,RGBColorParser:false */ +"use strict"; + /** - * @fileoverview Subclasses various parts of PlotKit to meet the additional - * needs of Dygraph: grid overlays and error bars + * @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 {Canvas} 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; -// Subclass PlotKit.Layout to add: -// 1. Sigma/errorBars properties -// 2. Copy error terms for PlotKit.CanvasRenderer._renderLineChart + this.layout = layout; + this.element = element; + this.elementContext = elementContext; + this.container = this.element.parentNode; + + this.height = this.element.height; + this.width = this.element.width; + + // --- check whether everything is ok before we return + if (!this.isIE && !(DygraphCanvasRenderer.isSupported(this.element))) + throw "Canvas is not supported."; + + // internal state + this.area = layout.getPlotArea(); + this.container.style.position = "relative"; + this.container.style.width = this.width + "px"; + + // Set up a clipping area for the canvas (and the interaction canvas). + // This ensures that we don't overdraw. + if (this.dygraph_.isUsingExcanvas_) { + this._createIEClipArea(); + } else { + // 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(); + } + } +}; /** - * Creates a new DygraphLayout object. Options are the same as those allowed - * by the PlotKit.Layout constructor. - * @param {Object} options Options for PlotKit.Layout - * @return {Object} The DygraphLayout object + * This just forwards to dygraph.attr_. + * TODO(danvk): remove this? + * @private */ -DygraphLayout = function(options) { - PlotKit.Layout.call(this, "line", options); +DygraphCanvasRenderer.prototype.attr_ = function(name, opt_seriesName) { + return this.dygraph_.attr_(name, opt_seriesName); }; -DygraphLayout.prototype = new PlotKit.Layout(); /** - * Behaves the same way as PlotKit.Layout, but also copies the errors + * Clears out all chart content and DOM elements. + * This is called immediately before render() on every frame, including + * during zooms and pans. * @private */ -DygraphLayout.prototype.evaluateWithError = function() { - this.evaluate(); - if (!this.options.errorBars) return; - - // Copy over the error terms - var i = 0; // index in this.points - for (var setName in this.datasets) { - var j = 0; - var dataset = this.datasets[setName]; - if (PlotKit.Base.isFuncLike(dataset)) continue; - for (var j = 0; j < dataset.length; j++, i++) { - var item = dataset[j]; - var xv = parseFloat(item[0]); - var yv = parseFloat(item[1]); - - if (xv == this.points[i].xval && - yv == this.points[i].yval) { - this.points[i].errorMinus = parseFloat(item[2]); - this.points[i].errorPlus = parseFloat(item[3]); +DygraphCanvasRenderer.prototype.clear = function() { + var context; + if (this.isIE) { + // VML takes a while to start up, so we just poll every this.IEDelay + try { + if (this.clearDelay) { + this.clearDelay.cancel(); + this.clearDelay = null; } + context = this.elementContext; + } + catch (e) { + // TODO(danvk): this is broken, since MochiKit.Async is gone. + // this.clearDelay = MochiKit.Async.wait(this.IEDelay); + // this.clearDelay.addCallback(bind(this.clear, this)); + return; } } + + context = this.elementContext; + context.clearRect(0, 0, this.width, this.height); +}; + +/** + * Checks whether the browser supports the <canvas> tag. + * @private + */ +DygraphCanvasRenderer.isSupported = function(canvasName) { + var canvas = null; + try { + if (typeof(canvasName) == 'undefined' || canvasName === null) { + canvas = document.createElement("canvas"); + } else { + canvas = canvasName; + } + canvas.getContext("2d"); + } + catch (e) { + var ie = navigator.appVersion.match(/MSIE (\d\.\d)/); + var opera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1); + if ((!ie) || (ie[1] < 6) || (opera)) + return false; + return true; + } + return true; }; /** - * Convenience function to remove all the data sets from a graph + * 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 */ -DygraphLayout.prototype.removeAllDatasets = function() { - delete this.datasets; - this.datasets = new Array(); +DygraphCanvasRenderer.prototype.render = function() { + // attaches point.canvas{x,y} + this._updatePoints(); + + // actually draws the chart. + this._renderLineChart(); +}; + +DygraphCanvasRenderer.prototype._createIEClipArea = function() { + var className = 'dygraph-clip-div'; + var graphDiv = this.dygraph_.graphDiv; + + // Remove old clip divs. + for (var i = graphDiv.childNodes.length-1; i >= 0; i--) { + if (graphDiv.childNodes[i].className == className) { + graphDiv.removeChild(graphDiv.childNodes[i]); + } + } + + // Determine background color to give clip divs. + var backgroundColor = document.bgColor; + var element = this.dygraph_.graphDiv; + while (element != document) { + var bgcolor = element.currentStyle.backgroundColor; + if (bgcolor && bgcolor != 'transparent') { + backgroundColor = bgcolor; + break; + } + element = element.parentNode; + } + + function createClipDiv(area) { + if (area.w === 0 || area.h === 0) { + return; + } + var elem = document.createElement('div'); + elem.className = className; + elem.style.backgroundColor = backgroundColor; + elem.style.position = 'absolute'; + elem.style.left = area.x + 'px'; + elem.style.top = area.y + 'px'; + elem.style.width = area.w + 'px'; + elem.style.height = area.h + 'px'; + graphDiv.appendChild(elem); + } + + var plotArea = this.area; + // Left side + createClipDiv({ + x:0, y:0, + w:plotArea.x, + h:this.height + }); + + // Top + createClipDiv({ + x: plotArea.x, y: 0, + w: this.width - plotArea.x, + h: plotArea.y + }); + + // Right side + createClipDiv({ + x: plotArea.x + plotArea.w, y: 0, + w: this.width-plotArea.x - plotArea.w, + h: this.height + }); + + // Bottom + createClipDiv({ + x: plotArea.x, + y: plotArea.y + plotArea.h, + w: this.width - plotArea.x, + h: this.height - plotArea.h - plotArea.y + }); }; + /** - * Change the values of various layout options - * @param {Object} new_options an associative array of new properties + * 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. */ -DygraphLayout.prototype.updateOptions = function(new_options) { - MochiKit.Base.update(this.options, new_options ? new_options : {}); +DygraphCanvasRenderer._getIteratorPredicate = function(connectSeparatedPoints) { + return connectSeparatedPoints ? + DygraphCanvasRenderer._predicateThatSkipsEmptyPoints : + null; }; -// Subclass PlotKit.CanvasRenderer to add: -// 1. X/Y grid overlay -// 2. Ability to draw error bars (if required) +DygraphCanvasRenderer._predicateThatSkipsEmptyPoints = + function(array, idx) { + return array[idx].yval !== null; +}; /** - * Sets some PlotKit.CanvasRenderer options - * @param {Object} element The canvas to attach to - * @param {Layout} layout The DygraphLayout object for this graph. - * @param {Object} options Options to pass on to CanvasRenderer + * 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 = function(element, layout, options) { - PlotKit.CanvasRenderer.call(this, element, layout, options); - this.options.shouldFill = false; - this.options.shouldStroke = true; - this.options.drawYGrid = true; - this.options.drawXGrid = true; - this.options.gridLineColor = MochiKit.Color.Color.grayColor(); - MochiKit.Base.update(this.options, options); +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.getOption("stepPlot", e.setName); + + if (!Dygraph.isArrayLike(strokePattern)) { + strokePattern = null; + } - // TODO(danvk) This shouldn't be necessary: effects should be overlaid - this.options.drawBackground = false; + var drawGapPoints = g.getOption('drawGapEdgePoints', e.setName); + + var points = e.points; + var iter = Dygraph.createIterator(points, 0, points.length, + DygraphCanvasRenderer._getIteratorPredicate( + g.getOption("connectSeparatedPoints"))); // TODO(danvk): per-series? + + 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(); }; -DygraphCanvasRenderer.prototype = new PlotKit.CanvasRenderer(); /** - * Draw an X/Y grid on top of the existing plot + * 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.prototype.render = function() { - // Draw the new X/Y grid - var ctx = this.element.getContext("2d"); - if (this.options.drawYGrid) { - var ticks = this.layout.yticks; - ctx.save(); - ctx.strokeStyle = this.options.gridLineColor.toRGBString(); - ctx.lineWidth = this.options.axisLineWidth; - for (var i = 0; i < ticks.length; i++) { - var x = this.area.x; - var y = this.area.y + ticks[i][0] * this.area.h; - ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(x + this.area.w, y); - ctx.closePath(); - ctx.stroke(); +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]; } + + 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]); + } + prevCanvasX = point.canvasx; + prevCanvasY = point.canvasy; + } + first = false; } + ctx.stroke(); + return pointsOnLine; +}; - if (this.options.drawXGrid) { - var ticks = this.layout.xticks; +/** + * 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(); - ctx.strokeStyle = this.options.gridLineColor.toRGBString(); - ctx.lineWidth = this.options.axisLineWidth; - for (var i=0; i plotter fn. + for (i = 0; i < setNames.length; i++) { + setName = setNames[i]; + var setPlotter = this.attr_("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(); + } + } }; /** - * Overrides the CanvasRenderer method to draw error bars + * Standard plotters. These may be used by clients via Dygraph.Plotters. + * See comments there for more details. */ -DygraphCanvasRenderer.prototype._renderLineChart = function() { - var context = this.element.getContext("2d"); - var colorCount = this.options.colorScheme.length; - var colorScheme = this.options.colorScheme; - var setNames = MochiKit.Base.keys(this.layout.datasets); - var errorBars = this.layout.options.errorBars; - var setCount = setNames.length; - var bind = MochiKit.Base.bind; - var partial = MochiKit.Base.partial; +DygraphCanvasRenderer._Plotters = { + linePlotter: function(e) { + DygraphCanvasRenderer._linePlotter(e); + }, - //Update Points - var updatePoint = function(point) { - point.canvasx = this.area.w * point.x + this.area.x; - point.canvasy = this.area.h * point.y + this.area.y; + fillPlotter: function(e) { + DygraphCanvasRenderer._fillPlotter(e); + }, + + errorPlotter: function(e) { + DygraphCanvasRenderer._errorPlotter(e); } - MochiKit.Iter.forEach(this.layout.points, updatePoint, this); +}; - // create paths - var makePath = function(ctx) { - for (var i = 0; i < setCount; i++) { - var setName = setNames[i]; - var color = colorScheme[i%colorCount]; - var strokeX = this.options.strokeColorTransform; +/** + * 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; - // setup graphics context - context.save(); - context.strokeStyle = color.toRGBString(); - context.lineWidth = this.options.strokeWidth; - ctx.beginPath(); - var point = this.layout.points[0]; - var first_point = true; - var addPoint = function(ctx_, point) { - if (point.name == setName) { - if (isNaN(point.canvasy)) { - // this will make us move to the next point, not draw a line to it. - first_point = true; + // 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.getOption("strokeBorderWidth", setName); + var drawPointCallback = g.getOption("drawPointCallback", setName) || + Dygraph.Circles.DEFAULT; + var strokePattern = g.getOption("strokePattern", setName); + var drawPoints = g.getOption("drawPoints", setName); + var pointSize = g.getOption("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.getOption("errorBars") || g.getOption("customBars"); + if (!errorBars) return; + + var fillGraph = g.getOption("fillGraph", setName); + if (fillGraph) { + g.warn("Can't use fillGraph option with error bars"); + } + + var ctx = e.drawingContext; + var color = e.color; + var fillAlpha = g.getOption('fillAlpha', setName); + var stepPlot = g.getOption("stepPlot", setName); + var points = e.points; + + var iter = Dygraph.createIterator(points, 0, points.length, + DygraphCanvasRenderer._getIteratorPredicate( + g.getOption("connectSeparatedPoints"))); + + 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 = new RGBColorParser(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; + } + + if (stepPlot) { + newYs = [ point.y_bottom, point.y_top ]; + prevY = point.y; + } else { + newYs = [ point.y_bottom, point.y_top ]; + } + 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(); +}; + +/** + * 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.getOption("fillGraph", setNames[i])) return true; + } + return false; + })(); + + if (!anySeriesFilled) return; + + var ctx = e.drawingContext; + var area = e.plotArea; + var sets = e.allSeriesPoints; + var setCount = sets.length; + + var fillAlpha = g.getOption('fillAlpha'); + var stackedGraph = g.getOption("stackedGraph"); + var colors = g.getColors(); + + var baseline = {}; // for stacked graphs: baseline for filling + var currBaseline; + var prevStepPlot; // for different line drawing modes (line/step) per series + + // process sets in reverse order (needed for stacked graphs) + for (var setIdx = setCount - 1; setIdx >= 0; setIdx--) { + var setName = setNames[setIdx]; + if (!g.getOption('fillGraph', setName)) continue; + + var stepPlot = g.getOption('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.getOption("connectSeparatedPoints"))); + + // 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 = new RGBColorParser(color); + var err_color = + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')'; + ctx.fillStyle = err_color; + ctx.beginPath(); + var last_x, is_first = true; + while (iter.hasNext) { + var point = iter.next(); + if (!Dygraph.isOK(point.y)) { + prevX = NaN; + 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 { - if (first_point) { - ctx_.moveTo(point.canvasx, point.canvasy); - first_point = false; - } else { - ctx_.lineTo(point.canvasx, point.canvasy); - } + lastY = currBaseline; } } - }; - MochiKit.Iter.forEach(this.layout.points, partial(addPoint, ctx), this); - ctx.stroke(); - } - }; + newYs = [ point.canvasy, lastY ]; - var makeErrorBars = function(ctx) { - for (var i = 0; i < setCount; i++) { - var setName = setNames[i]; - var color = colorScheme[i % colorCount]; - var strokeX = this.options.strokeColorTransform; - - // setup graphics context - context.save(); - context.strokeStyle = color.toRGBString(); - context.lineWidth = this.options.strokeWidth; - var prevX = -1; - var prevYs = [-1, -1]; - var count = 0; - var yscale = this.layout.yscale; - var errorTrapezoid = function(ctx_,point) { - count++; - if (point.name == setName) { - var newYs = [ point.y - point.errorPlus * yscale, - point.y + point.errorMinus * yscale ]; - newYs[0] = this.area.h * newYs[0] + this.area.y; - newYs[1] = this.area.h * newYs[1] + this.area.y; - if (prevX >= 0) { - ctx_.moveTo(prevX, prevYs[0]); - ctx_.lineTo(point.canvasx, newYs[0]); - ctx_.lineTo(point.canvasx, newYs[1]); - ctx_.lineTo(prevX, prevYs[1]); - ctx_.closePath(); + 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] ]; } - prevYs[0] = newYs[0]; - prevYs[1] = newYs[1]; - prevX = point.canvasx; + } else { + baseline[point.canvasx] = point.canvasy; } - }; - // should be same color as the lines - var err_color = color.colorWithAlpha(0.15); - ctx.fillStyle = err_color.toRGBString(); - ctx.beginPath(); - MochiKit.Iter.forEach(this.layout.points, partial(errorTrapezoid, ctx), this); - ctx.fill(); - } - }; - if (errorBars) - bind(makeErrorBars, this)(context); - bind(makePath, this)(context); - context.restore(); + } else { + newYs = [ point.canvasy, axisY ]; + } + if (!isNaN(prevX)) { + ctx.moveTo(prevX, prevYs[0]); + + // Move to top fill point + if (stepPlot) { + ctx.lineTo(point.canvasx, prevYs[0]); + } else { + ctx.lineTo(point.canvasx, newYs[0]); + } + // Move to bottom fill point + if (prevStepPlot && currBaseline) { + // Draw to the bottom of the baseline + ctx.lineTo(point.canvasx, currBaseline[1]); + } else { + ctx.lineTo(point.canvasx, newYs[1]); + } + + ctx.lineTo(prevX, prevYs[1]); + ctx.closePath(); + } + prevYs = newYs; + prevX = point.canvasx; + } + prevStepPlot = stepPlot; + ctx.fill(); + } };