From 38e3d209effd6f9a3e49d993719808006d9d2ada Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Tue, 31 Jul 2012 16:03:49 -0400 Subject: [PATCH] Add 'plotter' option, which allows custom drawing. This can be used to create bar charts, mixed bar/line charts, box and whisker charts, chandle charts, or whatever you can dream up. Squashed commit of the following: commit 0d5ce69dda5f02db7a4576b220f8cb7bc45cd197 Author: Dan Vanderkam Date: Tue Jul 31 12:38:43 2012 -0400 bug fix commit 15e29ef96fc41e73512edf7c404462cfa6f7ed3c Author: Dan Vanderkam Date: Tue Jul 31 12:37:21 2012 -0400 kberg comments commit 0a61073b21bb92002f9808356be02e015b584450 Author: Dan Vanderkam Date: Tue Jul 31 11:04:49 2012 -0400 copy over more stuff in push-to-web commit 3b9f9d1b6ca68a61d593ff909f2cdab2827b3410 Merge: 7f808fd 85e975f Author: Dan Vanderkam Date: Tue Jul 31 10:49:06 2012 -0400 Merge branch 'master' into plotter-option commit 7f808fd2944d486d567a07768c1849aad1cb2768 Author: Dan Vanderkam Date: Tue Jul 31 10:28:19 2012 -0400 mixed error/line commit 5b5d1c37504749fd35ab1d862bcfc871a99ba422 Author: Dan Vanderkam Date: Mon Jul 30 21:28:04 2012 -0400 tweaks to plotters demo commit 371163baa1086f99e126a5896acb09a535c019c2 Author: Dan Vanderkam Date: Mon Jul 30 21:27:50 2012 -0400 Dygraph.Plotters commit 6446f7c315a7f58e9daa39a2c1cbd2c877ae7870 Author: Dan Vanderkam Date: Mon Jul 30 21:08:49 2012 -0400 a few more plotter examples commit 7867dbdf2b4733de76d3ed8d999336ba58ccba86 Author: Dan Vanderkam Date: Mon Jul 30 18:55:02 2012 -0400 cleanup & comment commit d8768ade1b85c81af5df52b7425f870f420babfc Author: Dan Vanderkam Date: Sun Jul 29 20:08:01 2012 -0400 fix highlightSeriesOpts issue commit bfbf2452e7f34088de46242c5bf54e3904c6a49d Author: Dan Vanderkam Date: Sun Jul 29 19:47:20 2012 -0400 more cleanup commit f8fdb6b618443b2a74ce5861f2aa0ce1206320e3 Author: Dan Vanderkam Date: Sun Jul 29 19:43:43 2012 -0400 reduce number of params to various line drawing function commit 2d101ef9d34a9a52a7051cc2698f12f435854a04 Author: Dan Vanderkam Date: Sun Jul 29 19:25:15 2012 -0400 only use fillPlotter when necessary commit 0a7c6dca3e21261874b6c88c02c626e9c5f5cad9 Author: Dan Vanderkam Date: Sun Jul 29 19:22:32 2012 -0400 fix issue with fillPlotter & visibility commit fb86aa8b43bd02f594ea3c05840d0d40d965b1be Author: Dan Vanderkam Date: Sat Jul 28 19:10:24 2012 -0400 restructure, comments commit c77fe2f37e60d394d1cffaf1126955d014b44838 Author: Dan Vanderkam Date: Sat Jul 28 17:52:52 2012 -0400 port over fillGraph plotter commit 04b5eb3b52be9f2c12f959378f8ae35c22008ded Author: Dan Vanderkam Date: Sat Jul 28 17:44:58 2012 -0400 add candle chart demo commit 27737ae38eac6b6096687d8960f8b34fd4ec8dfa Merge: b81d3fc 6f8f19e Author: Dan Vanderkam Date: Wed Jul 25 17:49:37 2012 -0400 Merge branch 'master' into plotter-option commit b81d3fcbceaa115d16710a328747b8a7b2af9788 Author: Dan Vanderkam Date: Wed Jul 25 17:47:27 2012 -0400 pass all series plus seriesIndex to plotters commit 727229cbe532d2088f59d886ab368df4d499d5d8 Merge: fd784bf 16879a6 Author: Dan Vanderkam Date: Wed Jul 25 17:43:51 2012 -0400 merge upstream changes commit fd784bf9c0d18173238c921a4fde8fc35946b9c0 Author: Dan Vanderkam Date: Tue Jul 24 19:50:02 2012 -0400 drawXgrid commit b638898ad332edc71ed26528604eab94bcc791be Author: Dan Vanderkam Date: Sat Jul 21 20:29:51 2012 -0400 Bar chart plotter demo commit 0eeb143cb02d29e38bda87a273be737c7623686e Author: Dan Vanderkam Date: Sat Jul 21 20:06:51 2012 -0400 add line & error plotters; highlightSeriesOpts is broken --- dygraph-canvas.js | 434 ++++++++++++++++++++++++++++--------------- dygraph-options-reference.js | 6 + dygraph.js | 26 ++- push-to-web.sh | 4 +- tests/fillGraph.html | 3 - tests/plotters.html | 326 ++++++++++++++++++++++++++++++++ 6 files changed, 645 insertions(+), 154 deletions(-) create mode 100644 tests/plotters.html diff --git a/dygraph-canvas.js b/dygraph-canvas.js index 825a4c2..5286ad5 100644 --- a/dygraph-canvas.js +++ b/dygraph-canvas.js @@ -87,8 +87,13 @@ var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) { } }; -DygraphCanvasRenderer.prototype.attr_ = function(x) { - return this.dygraph_.attr_(x); +/** + * 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); }; /** @@ -152,6 +157,10 @@ DygraphCanvasRenderer.isSupported = function(canvasName) { * @private */ DygraphCanvasRenderer.prototype.render = function() { + // attaches point.canvas{x,y} + this._updatePoints(); + + // actually draws the chart. this._renderLineChart(); }; @@ -243,32 +252,39 @@ DygraphCanvasRenderer._predicateThatSkipsEmptyPoints = }; /** + * 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.prototype._drawStyledLine = function( - ctx, setIdx, setName, color, strokeWidth, strokePattern, drawPoints, +DygraphCanvasRenderer._drawStyledLine = function(e, + color, strokeWidth, strokePattern, drawPoints, drawPointCallback, pointSize) { + var g = e.dygraph; // TODO(konigsberg): Compute attributes outside this method call. - var stepPlot = this.attr_("stepPlot"); + var stepPlot = g.getOption("stepPlot"); // TODO(danvk): per-series if (!Dygraph.isArrayLike(strokePattern)) { strokePattern = null; } - var drawGapPoints = this.dygraph_.attr_('drawGapEdgePoints', setName); - var points = this.layout.points[setIdx]; + var drawGapPoints = g.getOption('drawGapEdgePoints', e.setName); + + var points = e.points; var iter = Dygraph.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate( - this.attr_("connectSeparatedPoints"))); + 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 = this._drawSeries(ctx, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color); - this._drawPointsOnLine(ctx, pointsOnLine, drawPointCallback, setName, color, pointSize); + var pointsOnLine = DygraphCanvasRenderer._drawSeries( + e, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color); + DygraphCanvasRenderer._drawPointsOnLine( + e, pointsOnLine, drawPointCallback, color, pointSize); if (stroking) { ctx.uninstallPattern(); @@ -277,19 +293,16 @@ DygraphCanvasRenderer.prototype._drawStyledLine = function( ctx.restore(); }; -DygraphCanvasRenderer.prototype._drawPointsOnLine = function(ctx, pointsOnLine, drawPointCallback, setName, color, pointSize) { - for (var idx = 0; idx < pointsOnLine.length; idx++) { - var cb = pointsOnLine[idx]; - ctx.save(); - drawPointCallback( - this.dygraph_, setName, ctx, cb[0], cb[1], color, pointSize); - ctx.restore(); - } -} - -DygraphCanvasRenderer.prototype._drawSeries = function( - ctx, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, - stepPlot, color) { +/** + * 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; @@ -299,6 +312,7 @@ DygraphCanvasRenderer.prototype._drawSeries = function( 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; @@ -370,49 +384,30 @@ DygraphCanvasRenderer.prototype._drawSeries = function( return pointsOnLine; }; -DygraphCanvasRenderer.prototype._drawLine = function(ctx, i) { - var setNames = this.layout.setNames; - var setName = setNames[i]; - - var strokeWidth = this.dygraph_.attr_("strokeWidth", setName); - var borderWidth = this.dygraph_.attr_("strokeBorderWidth", setName); - var drawPointCallback = this.dygraph_.attr_("drawPointCallback", setName) || - Dygraph.Circles.DEFAULT; - - if (borderWidth && strokeWidth) { - this._drawStyledLine(ctx, i, setName, - this.dygraph_.attr_("strokeBorderColor", setName), - strokeWidth + 2 * borderWidth, - this.dygraph_.attr_("strokePattern", setName), - this.dygraph_.attr_("drawPoints", setName), - drawPointCallback, - this.dygraph_.attr_("pointSize", setName)); +/** + * 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( + e.dygraph, e.setName, ctx, cb[0], cb[1], color, pointSize); + ctx.restore(); } - - this._drawStyledLine(ctx, i, setName, - this.colors[setName], - strokeWidth, - this.dygraph_.attr_("strokePattern", setName), - this.dygraph_.attr_("drawPoints", setName), - drawPointCallback, - this.dygraph_.attr_("pointSize", setName)); -}; +} /** - * Actually draw the lines chart, including error bars. + * Attaches canvas coordinates to the points array. * @private */ -DygraphCanvasRenderer.prototype._renderLineChart = function() { - var ctx = this.elementContext; - var errorBars = this.attr_("errorBars") || this.attr_("customBars"); - var fillGraph = this.attr_("fillGraph"); - var i; - - var setNames = this.layout.setNames; - var setCount = setNames.length; - - this.colors = this.dygraph_.colorsMap_; - +DygraphCanvasRenderer.prototype._updatePoints = function() { // Update Points // TODO(danvk): here // @@ -426,7 +421,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { // 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 (i = sets.length; i--;) { + for (var i = sets.length; i--;) { var points = sets[i]; for (var j = points.length; j--;) { var point = points[j]; @@ -434,117 +429,262 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { point.canvasy = this.area.h * point.y + this.area.y; } } +}; - // Draw any "fills", i.e. error bars or the filled area under a series. - // These must all be drawn before any lines, so that the main lines of a - // series are drawn on top. - if (errorBars) { - if (fillGraph) { - this.dygraph_.warn("Can't use fillGraph option with error bars"); - } +/** + * 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. + * @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; - ctx.save(); - this.drawErrorBars_(points); - ctx.restore(); - } else if (fillGraph) { - ctx.save(); - this.drawFillBars_(points); - ctx.restore(); + var sets = this.layout.points; + var setNames = this.layout.setNames; + var setCount = setNames.length; + + this.colors = this.dygraph_.colorsMap_; + + // Determine which series have specialized plotters. + var plotter_attr = this.attr_("plotter"); + var plotters = plotter_attr; + if (!Dygraph.isArrayLike(plotters)) { + plotters = [plotters]; + } + + var setPlotters = {}; // series name -> plotter fn. + for (i = 0; i < setNames.length; i++) { + var 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++) { + var 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, + 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); + }, - // Drawing the lines. - for (i = 0; i < setCount; i += 1) { - this._drawLine(ctx, i); + 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.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.prototype.drawErrorBars_ = function(points) { - var ctx = this.elementContext; - var setNames = this.layout.setNames; - var setCount = setNames.length; - var fillAlpha = this.attr_('fillAlpha'); - var stepPlot = this.attr_('stepPlot'); +DygraphCanvasRenderer._errorPlotter = function(e) { + var g = e.dygraph; + var errorBars = g.getOption("errorBars") || g.getOption("customBars"); + if (!errorBars) return; + + var fillGraph = g.getOption("fillGraph"); + if (fillGraph) { + g.warn("Can't use fillGraph option with error bars"); + } - var newYs; + var setName = e.setName; + 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 points = e.points; - for (var setIdx = 0; setIdx < setCount; setIdx++) { - var setName = setNames[setIdx]; - var axis = this.dygraph_.axisPropertiesForSeries(setName); - var color = this.colors[setName]; + var iter = Dygraph.createIterator(points, 0, points.length, + DygraphCanvasRenderer._getIteratorPredicate( + g.getOption("connectSeparatedPoints"))); - var points = this.layout.points[setIdx]; - var iter = Dygraph.createIterator(points, 0, points.length, - DygraphCanvasRenderer._getIteratorPredicate( - this.attr_("connectSeparatedPoints"))); + var newYs; - // setup graphics context - 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 RGBColor(color); - var err_color = - 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')'; - ctx.fillStyle = err_color; - ctx.beginPath(); - while (iter.hasNext) { - var point = iter.next(); - if (!Dygraph.isOK(point.y)) { - prevX = NaN; - continue; - } + // setup graphics context + 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 RGBColor(color); + var err_color = + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')'; + ctx.fillStyle = err_color; + ctx.beginPath(); + while (iter.hasNext) { + var point = iter.next(); + if (!Dygraph.isOK(point.y)) { + 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) { - newYs = [ point.y_bottom, point.y_top ]; - prevY = point.y; + ctx.moveTo(prevX, newYs[0]); } else { - newYs = [ point.y_bottom, point.y_top ]; + ctx.moveTo(prevX, prevYs[0]); } - newYs[0] = this.area.h * newYs[0] + this.area.y; - newYs[1] = this.area.h * newYs[1] + this.area.y; - if (!isNaN(prevX)) { - if (stepPlot) { - ctx.moveTo(prevX, newYs[0]); - } else { - ctx.moveTo(prevX, prevYs[0]); - } - ctx.lineTo(point.canvasx, newYs[0]); - ctx.lineTo(point.canvasx, newYs[1]); - if (stepPlot) { - ctx.lineTo(prevX, newYs[1]); - } else { - ctx.lineTo(prevX, prevYs[1]); - } - ctx.closePath(); + ctx.lineTo(point.canvasx, newYs[0]); + ctx.lineTo(point.canvasx, newYs[1]); + if (stepPlot) { + ctx.lineTo(prevX, newYs[1]); + } else { + ctx.lineTo(prevX, prevYs[1]); } - prevYs = newYs; - prevX = point.canvasx; + ctx.closePath(); } - ctx.fill(); + 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.prototype.drawFillBars_ = function(points) { - var ctx = this.elementContext; - var setNames = this.layout.setNames; - var setCount = setNames.length; - var fillAlpha = this.attr_('fillAlpha'); - var stepPlot = this.attr_('stepPlot'); - var stackedGraph = this.attr_("stackedGraph"); +DygraphCanvasRenderer._fillPlotter = function(e) { + var g = e.dygraph; + if (!g.getOption("fillGraph")) return; + + // We'll handle all the series at once, not one-by-one. + if (e.seriesIndex !== 0) return; + + var ctx = e.drawingContext; + var area = e.plotArea; + var sets = e.allSeriesPoints; + var setCount = sets.length; + + 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 fillAlpha = g.getOption('fillAlpha'); + var stepPlot = g.getOption('stepPlot'); + var stackedGraph = g.getOption("stackedGraph"); + var colors = g.getColors(); var baseline = {}; // for stacked graphs: baseline for filling var currBaseline; @@ -552,17 +692,17 @@ DygraphCanvasRenderer.prototype.drawFillBars_ = function(points) { // process sets in reverse order (needed for stacked graphs) for (var setIdx = setCount - 1; setIdx >= 0; setIdx--) { var setName = setNames[setIdx]; - var color = this.colors[setName]; - var axis = this.dygraph_.axisPropertiesForSeries(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 = this.area.h * axisY + this.area.y; + axisY = area.h * axisY + area.y; - var points = this.layout.points[setIdx]; + var points = sets[setIdx]; var iter = Dygraph.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate( - this.attr_("connectSeparatedPoints"))); + g.getOption("connectSeparatedPoints"))); // setup graphics context var prevX = NaN; diff --git a/dygraph-options-reference.js b/dygraph-options-reference.js index 04f13d7..e39c073 100644 --- a/dygraph-options-reference.js +++ b/dygraph-options-reference.js @@ -753,6 +753,12 @@ Dygraph.OPTIONS_REFERENCE = // "labels": ["Interactive Elements"], "type": "boolean", "description": "Set this option to animate the transition between zoom windows. Applies to programmatic and interactive zooms. Note that if you also set a drawCallback, it will be called several times on each zoom. If you set a zoomCallback, it will only be called after the animation is complete." + }, + "plotter": { + "default": "[DygraphCanvasRenderer.Plotters.fillPlotter, DygraphCanvasRenderer.Plotters.errorPlotter, DygraphCanvasRenderer.Plotters.linePlotter]", + "labels": ["Data Line display"], + "type": "array or function", + "description": "A function (or array of functions) which plot each data series on the chart. TODO(danvk): more details! May be set per-series." } } ; // diff --git a/dygraph.js b/dygraph.js index ba6889e..bb74cdc 100644 --- a/dygraph.js +++ b/dygraph.js @@ -180,6 +180,18 @@ Dygraph.dateAxisFormatter = function(date, granularity) { } }; +/** + * Standard plotters. These may be used by clients. + * Available plotters are: + * - Dygraph.Plotters.linePlotter: draws central lines (most common) + * - Dygraph.Plotters.errorPlotter: draws error bars + * - Dygraph.Plotters.fillPlotter: draws fills under lines (used with fillGraph) + * + * By default, the plotter is [fillPlotter, errorPlotter, linePlotter]. + * This causes all the lines to be drawn over all the fills/error bars. + */ +Dygraph.Plotters = DygraphCanvasRenderer._Plotters; + // Default attribute values. Dygraph.DEFAULT_ATTRS = { @@ -261,6 +273,14 @@ Dygraph.DEFAULT_ATTRS = { rangeSelectorPlotStrokeColor: "#808FAB", rangeSelectorPlotFillColor: "#A7B1C4", + // The ordering here ensures that central lines always appear above any + // fill bars/error bars. + plotter: [ + Dygraph.Plotters.fillPlotter, + Dygraph.Plotters.errorPlotter, + Dygraph.Plotters.linePlotter + ], + // per-axis options axes: { x: { @@ -1852,8 +1872,10 @@ Dygraph.prototype.updateSelection_ = function(opt_animFraction) { ctx.fillStyle = 'rgba(255,255,255,' + alpha + ')'; ctx.fillRect(0, 0, this.width_, this.height_); } - var setIdx = this.datasetIndexFromSetName_(this.highlightSet_); - this.plotter_._drawLine(ctx, setIdx); + + // Redraw only the highlighted series in the interactive canvas (not the + // static plot canvas, which is where series are usually drawn). + this.plotter_._renderLineChart(this.highlightSet_, ctx); } else if (this.previousVerticalX_ >= 0) { // Determine the maximum highlight circle size. var maxCircleSize = 0; diff --git a/push-to-web.sh b/push-to-web.sh index bd51622..00448c3 100755 --- a/push-to-web.sh +++ b/push-to-web.sh @@ -24,9 +24,9 @@ if [ -s docs/options.html ] ; then find . -path ./.git -prune -o -print | xargs chmod a+rX # Copy everything to the site. - rsync -avzr gallery common tests jsdoc experimental plugins $site \ + rsync -avzr gallery strftime rgbcolor common tests jsdoc experimental plugins $site \ && \ - rsync -avzr dygraph*.js gadget.xml excanvas.js thumbnail.png screenshot.png docs/* $site/ + rsync -avzr dashed-canvas.js stacktrace.js dygraph*.js gadget.xml excanvas.js thumbnail.png screenshot.png docs/* $site/ else echo "generate-documentation.py failed" fi diff --git a/tests/fillGraph.html b/tests/fillGraph.html index 90adb82..b6c501d 100644 --- a/tests/fillGraph.html +++ b/tests/fillGraph.html @@ -20,9 +20,6 @@

Filled, negatives:

-

Filled, error bars:

-
- + + + + + +

This page demonstrates how to build custom plotters with dygraphs. + The plotter option + allows you to write your own drawing logic. This can be used to achieve + powerful customization. View source to see how the examples work.

+ +

Bar Chart

+

Here a specialized plotter is used to draw + a bar plot rather than a line plot:

+
+ +

Candle Chart

+

Here a specialized plotter is used to + combined four series into a unified "Candle" plot:

+
+ +

Bar & Line Chart

+

The plotter + option may be set on a per-series basis to create mixed charts:

+
+ +

Multi-column Bar Chart

+
+ +

Mixed Error Bars and Lines

+

You can tweak the standard plotters list to achieve effects which would + be difficult otherwise, e.g. drawing series with only confidence intervals + and showing error bars only for some series.

+
+ + + + -- 2.7.4