X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph-canvas.js;h=6bf5fbcb457e187bb089ce0b93f395e1d9d6423f;hb=a22cc80916b6e165451995e1ae3ed4d36dc86eab;hp=fad305447bbdb9c2f0b628394e92816ea8e585dc;hpb=7249a5aa13b2861eb6e79ca39059233fc6ed701d;p=dygraphs.git diff --git a/dygraph-canvas.js b/dygraph-canvas.js index fad3054..6bf5fbc 100644 --- a/dygraph-canvas.js +++ b/dygraph-canvas.js @@ -25,7 +25,7 @@ */ /*jshint globalstrict: true */ -/*global Dygraph:false,RGBColorParser:false */ +/*global Dygraph:false */ "use strict"; @@ -39,8 +39,8 @@ * The chart canvas has already been created by the Dygraph object. The * renderer simply gets a drawing context. * - * @param {Dyraph} dygraph The chart to which this renderer belongs. - * @param {Canvas} element The <canvas> DOM element on which to draw. + * @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. * @@ -58,7 +58,8 @@ var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) { this.width = this.element.width; // --- check whether everything is ok before we return - if (!this.isIE && !(DygraphCanvasRenderer.isSupported(this.element))) + // NOTE(konigsberg): isIE is never defined in this object. Bug of some sort. + if (!this.isIE && !(Dygraph.isCanvasSupported(this.element))) throw "Canvas is not supported."; // internal state @@ -88,15 +89,6 @@ var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) { }; /** - * This just forwards to dygraph.attr_. - * TODO(danvk): remove this? - * @private - */ -DygraphCanvasRenderer.prototype.attr_ = function(name, opt_seriesName) { - return this.dygraph_.attr_(name, opt_seriesName); -}; - -/** * Clears out all chart content and DOM elements. * This is called immediately before render() on every frame, including * during zooms and pans. @@ -126,30 +118,6 @@ DygraphCanvasRenderer.prototype.clear = function() { }; /** - * 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; -}; - -/** * 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 @@ -261,17 +229,19 @@ DygraphCanvasRenderer._drawStyledLine = function(e, drawPointCallback, pointSize) { var g = e.dygraph; // TODO(konigsberg): Compute attributes outside this method call. - var stepPlot = g.getOption("stepPlot"); // TODO(danvk): per-series + var stepPlot = g.getBooleanOption("stepPlot", e.setName); + if (!Dygraph.isArrayLike(strokePattern)) { strokePattern = null; } - var drawGapPoints = g.getOption('drawGapEdgePoints', e.setName); + 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.getOption("connectSeparatedPoints"))); // TODO(danvk): per-series? + g.getBooleanOption("connectSeparatedPoints", setName))); var stroking = strokePattern && (strokePattern.length >= 2); @@ -332,6 +302,9 @@ DygraphCanvasRenderer._drawSeries = function(e, 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 @@ -343,7 +316,7 @@ DygraphCanvasRenderer._drawSeries = function(e, isIsolated = false; if (drawGapPoints || !prevCanvasX) { iter.nextIdx_ = i; - var peek = iter.next(); + iter.next(); nextCanvasY = iter.hasNext ? iter.peek.canvasy : null; var isNextCanvasYNullOrNaN = nextCanvasY === null || @@ -372,7 +345,7 @@ DygraphCanvasRenderer._drawSeries = function(e, ctx.moveTo(point.canvasx, point.canvasy); } if (drawPoints || isIsolated) { - pointsOnLine.push([point.canvasx, point.canvasy]); + pointsOnLine.push([point.canvasx, point.canvasy, point.idx]); } prevCanvasX = point.canvasx; prevCanvasY = point.canvasy; @@ -397,7 +370,7 @@ DygraphCanvasRenderer._drawPointsOnLine = function( var cb = pointsOnLine[idx]; ctx.save(); drawPointCallback( - e.dygraph, e.setName, ctx, cb[0], cb[1], color, pointSize); + e.dygraph, e.setName, ctx, cb[0], cb[1], color, pointSize, cb[2]); ctx.restore(); } }; @@ -432,31 +405,30 @@ DygraphCanvasRenderer.prototype._updatePoints = function() { /** * Add canvas Actually draw the lines chart, including error bars. - * If opt_seriesName is specified, only that series will be drawn. - * (This is used for expedited redrawing with highlightSeriesOpts) - * Lines are typically drawn in the non-interactive dygraph canvas. If opt_ctx - * is specified, they can be drawn elsewhere. * * 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 errorBars = this.attr_("errorBars") || this.attr_("customBars"); - var fillGraph = this.attr_("fillGraph"); var i; var sets = this.layout.points; var setNames = this.layout.setNames; - var setCount = setNames.length; var setName; this.colors = this.dygraph_.colorsMap_; // Determine which series have specialized plotters. - var plotter_attr = this.attr_("plotter"); + var plotter_attr = this.dygraph_.getOption("plotter"); var plotters = plotter_attr; if (!Dygraph.isArrayLike(plotters)) { plotters = [plotters]; @@ -465,7 +437,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function(opt_seriesName, opt_ var setPlotters = {}; // series name -> plotter fn. for (i = 0; i < setNames.length; i++) { setName = setNames[i]; - var setPlotter = this.attr_("plotter", setName); + var setPlotter = this.dygraph_.getOption("plotter", setName); if (setPlotter == plotter_attr) continue; // not specialized. setPlotters[setName] = setPlotter; @@ -509,6 +481,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function(opt_seriesName, opt_ plotArea: this.area, seriesIndex: j, seriesCount: sets.length, + singleSeriesName: opt_seriesName, allSeriesPoints: sets }); ctx.restore(); @@ -546,12 +519,12 @@ DygraphCanvasRenderer._linePlotter = function(e) { // 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 borderWidth = g.getNumericOption("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); + var drawPoints = g.getBooleanOption("drawPoints", setName); + var pointSize = g.getNumericOption("pointSize", setName); if (borderWidth && strokeWidth) { DygraphCanvasRenderer._drawStyledLine(e, @@ -583,24 +556,24 @@ DygraphCanvasRenderer._linePlotter = function(e) { DygraphCanvasRenderer._errorPlotter = function(e) { var g = e.dygraph; var setName = e.setName; - var errorBars = g.getOption("errorBars") || g.getOption("customBars"); + var errorBars = g.getBooleanOption("errorBars") || + g.getBooleanOption("customBars"); if (!errorBars) return; - var fillGraph = g.getOption("fillGraph", setName); + var fillGraph = g.getBooleanOption("fillGraph", setName); if (fillGraph) { - g.warn("Can't use fillGraph option with error bars"); + Dygraph.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'); // TODO(danvk): per-series - var axis = e.axis; + 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.getOption("connectSeparatedPoints"))); + g.getBooleanOption("connectSeparatedPoints", setName))); var newYs; @@ -608,9 +581,8 @@ DygraphCanvasRenderer._errorPlotter = function(e) { var prevX = NaN; var prevY = NaN; var prevYs = [-1, -1]; - var yscale = axis.yscale; // should be same color as the lines but only 15% opaque. - var rgb = new RGBColorParser(color); + var rgb = Dygraph.toRGB_(color); var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')'; ctx.fillStyle = err_color; @@ -668,6 +640,9 @@ DygraphCanvasRenderer._errorPlotter = function(e) { * @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; @@ -683,7 +658,7 @@ DygraphCanvasRenderer._fillPlotter = function(e) { var anySeriesFilled = (function() { for (var i = 0; i < setNames.length; i++) { - if (g.getOption("fillGraph", setNames[i])) return true; + if (g.getBooleanOption("fillGraph", setNames[i])) return true; } return false; })(); @@ -695,19 +670,27 @@ DygraphCanvasRenderer._fillPlotter = function(e) { var sets = e.allSeriesPoints; var setCount = sets.length; - var fillAlpha = g.getOption('fillAlpha'); - var stepPlot = g.getOption('stepPlot'); - var stackedGraph = g.getOption("stackedGraph"); + var fillAlpha = g.getNumericOption('fillAlpha'); + var stackedGraph = g.getBooleanOption("stackedGraph"); var colors = g.getColors(); - var baseline = {}; // for stacked graphs: baseline for filling + // 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 // 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; - + 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; @@ -718,32 +701,42 @@ DygraphCanvasRenderer._fillPlotter = function(e) { var points = sets[setIdx]; var iter = Dygraph.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate( - g.getOption("connectSeparatedPoints"))); + g.getBooleanOption("connectSeparatedPoints", setName))); // setup graphics context var prevX = NaN; var prevYs = [-1, -1]; var newYs; - var yscale = axis.yscale; // should be same color as the lines but only 15% opaque. - var rgb = new RGBColorParser(color); + var rgb = Dygraph.toRGB_(color); var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')'; ctx.fillStyle = err_color; ctx.beginPath(); - while(iter.hasNext) { + var last_x, is_first = true; + while (iter.hasNext) { var point = iter.next(); if (!Dygraph.isOK(point.y)) { 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(stepPlot) { + if(prevStepPlot) { lastY = currBaseline[0]; } else { lastY = currBaseline; @@ -768,17 +761,18 @@ DygraphCanvasRenderer._fillPlotter = function(e) { } if (!isNaN(prevX)) { ctx.moveTo(prevX, prevYs[0]); - + + // Move to top fill point if (stepPlot) { ctx.lineTo(point.canvasx, prevYs[0]); - if(currBaseline) { - // Draw to the bottom of the baseline - ctx.lineTo(point.canvasx, currBaseline[1]); - } else { - ctx.lineTo(point.canvasx, newYs[1]); - } } 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]); } @@ -788,6 +782,7 @@ DygraphCanvasRenderer._fillPlotter = function(e) { prevYs = newYs; prevX = point.canvasx; } + prevStepPlot = stepPlot; ctx.fill(); } };