X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;ds=sidebyside;f=dygraph-canvas.js;h=04280c4946a81efa05e04dc8327683daea52d2ee;hb=1b89e01f33c071af04e0586163fa3c09ac115b09;hp=ab66ef35025011f108fb00477835f185b88dd30c;hpb=b2c9222a1acdb3c5af252937176fe2e9f9ea7bb1;p=dygraphs.git diff --git a/dygraph-canvas.js b/dygraph-canvas.js index ab66ef3..04280c4 100644 --- a/dygraph-canvas.js +++ b/dygraph-canvas.js @@ -2,32 +2,22 @@ // All Rights Reserved. /** - * @fileoverview Based on PlotKit, but modified to meet the needs of dygraphs. + * @fileoverview Based on PlotKit.CanvasRenderer, but modified to meet the + * needs of dygraphs. + * * In particular, support for: * - grid overlays * - error bars * - dygraphs attribute system - * - * High level overview of classes: - * - * - DygraphLayout - * This contains all the data to be charted. - * It uses data coordinates, but also records the chart range (in data - * coordinates) and hence is able to calculate percentage positions ('In - * this view, Point A lies 25% down the x-axis.') - * Two things that it does not do are: - * 1. Record pixel coordinates for anything. - * 2. (oddly) determine anything about the layout of chart elements. - * The naming is a vestige of Dygraph's original PlotKit roots. - * - * - DygraphCanvasRenderer - * This class determines the charting area (in pixel coordinates), maps the - * percentage coordinates in the DygraphLayout to pixels and draws them. - * It's also responsible for creating chart DOM elements, i.e. annotations, - * tick mark labels, the title and the x/y-axis labels. */ /** + * This class determines the charting area (in pixel coordinates), maps the + * percentage coordinates in the DygraphLayout to pixels and draws them. + * It's also responsible for creating chart DOM elements, i.e. annotations, + * tick mark labels, the title and the x/y-axis labels. + * This class is based on PlotKit.CanvasRenderer. + * * Creates a new DygraphLayout object. * @return {Object} The DygraphLayout object */ @@ -70,7 +60,7 @@ DygraphLayout.prototype.setAnnotations = function(ann) { return; } Dygraph.update(a, ann[i]); - if (!a.xval) a.xval = parse(a.x); + if (!a.xval) a.xval = parse(a.x, this.dygraph_); this.annotations.push(a); } }; @@ -278,43 +268,19 @@ DygraphLayout.prototype.unstackPointAtIndex = function(idx) { return unstackedPoint; } -// Subclass PlotKit.CanvasRenderer to add: -// 1. X/Y grid overlay -// 2. Ability to draw error bars (if required) - /** - * Sets some PlotKit.CanvasRenderer options + * The DygraphCanvasRenderer class does the actual rendering of the chart onto + * a canvas. It's based on PlotKit.CanvasRenderer. +>>>>>>> master * @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. - * @param {Object} options Options to pass on to CanvasRenderer + * @constructor */ -DygraphCanvasRenderer = function(dygraph, element, elementContext, layout, - options) { - // TODO(danvk): remove options, just use dygraph.attr_. +DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) { this.dygraph_ = dygraph; - // default options - this.options = { - "strokeWidth": 0.5, - "drawXAxis": true, - "drawYAxis": true, - "axisLineColor": "black", - "axisLineWidth": 0.5, - "axisTickSize": 3, - "axisLabelColor": "black", - "axisLabelFont": "Arial", - "axisLabelFontSize": 9, - "axisLabelWidth": 50, - "drawYGrid": true, - "drawXGrid": true, - "gridLineColor": "rgb(128,128,128)", - "fillAlpha": 0.15, - "underlayCallback": null - }; - Dygraph.update(this.options, options); - this.layout = layout; this.element = element; this.elementContext = elementContext; @@ -333,20 +299,54 @@ DygraphCanvasRenderer = function(dygraph, element, elementContext, layout, this.annotations = new Array(); this.chartLabels = {}; - // TODO(danvk): consider all axes in this computation. - this.area = { + this.area = this.computeArea_(); + 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. + 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(); +}; + +DygraphCanvasRenderer.prototype.attr_ = function(x) { + return this.dygraph_.attr_(x); +}; + +// Compute the box which the chart should be drawn in. This is the canvas's +// box, less space needed for axis and chart labels. +// TODO(danvk): this belongs in DygraphLayout. +DygraphCanvasRenderer.prototype.computeArea_ = function() { + var area = { // TODO(danvk): per-axis setting. - x: this.options.yAxisLabelWidth + 2 * this.options.axisTickSize, + x: 0, y: 0 }; - this.area.w = this.width - this.area.x - this.options.rightGap; - this.area.h = this.height - this.options.axisLabelFontSize - - 2 * this.options.axisTickSize; + if (this.attr_('drawYAxis')) { + area.x = this.attr_('yAxisLabelWidth') + 2 * this.attr_('axisTickSize'); + } + + area.w = this.width - area.x - this.attr_('rightGap'); + area.h = this.height; + if (this.attr_('drawXAxis')) { + if (this.attr_('xAxisHeight')) { + area.h -= this.attr_('xAxisHeight'); + } else { + area.h -= this.attr_('axisLabelFontSize') + 2 * this.attr_('axisTickSize'); + } + } // Shrink the drawing area to accomodate additional y-axes. if (this.dygraph_.numAxes() == 2) { // TODO(danvk): per-axis setting. - this.area.w -= (this.options.yAxisLabelWidth + 2 * this.options.axisTickSize); + area.w -= (this.attr_('yAxisLabelWidth') + 2 * this.attr_('axisTickSize')); } else if (this.dygraph_.numAxes() > 2) { this.dygraph_.error("Only two y-axes are supported at this time. (Trying " + "to use " + this.dygraph_.numAxes() + ")"); @@ -354,11 +354,11 @@ DygraphCanvasRenderer = function(dygraph, element, elementContext, layout, // Add space for chart labels: title, xlabel and ylabel. if (this.attr_('title')) { - this.area.h -= this.attr_('titleHeight'); - this.area.y += this.attr_('titleHeight'); + area.h -= this.attr_('titleHeight'); + area.y += this.attr_('titleHeight'); } if (this.attr_('xlabel')) { - this.area.h -= this.attr_('xLabelHeight'); + area.h -= this.attr_('xLabelHeight'); } if (this.attr_('ylabel')) { // It would make sense to shift the chart here to make room for the y-axis @@ -367,24 +367,7 @@ DygraphCanvasRenderer = function(dygraph, element, elementContext, layout, // doesn't, the yAxisLabelWidth option can be increased. } - 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. - 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(); -}; - -DygraphCanvasRenderer.prototype.attr_ = function(x) { - return this.dygraph_.attr_(x); + return area; }; DygraphCanvasRenderer.prototype.clear = function() { @@ -452,6 +435,14 @@ DygraphCanvasRenderer.isSupported = function(canvasName) { }; /** + * @param { [String] } colors Array of color strings. Should have one entry for + * each series to be rendered. + */ +DygraphCanvasRenderer.prototype.setColors = function(colors) { + this.colorScheme_ = colors; +}; + +/** * Draw an X/Y grid on top of the existing plot */ DygraphCanvasRenderer.prototype.render = function() { @@ -461,17 +452,17 @@ DygraphCanvasRenderer.prototype.render = function() { function halfUp(x){return Math.round(x)+0.5}; function halfDown(y){return Math.round(y)-0.5}; - if (this.options.underlayCallback) { + if (this.attr_('underlayCallback')) { // NOTE: we pass the dygraph object to this callback twice to avoid breaking // users who expect a deprecated form of this callback. - this.options.underlayCallback(ctx, this.area, this.dygraph_, this.dygraph_); + this.attr_('underlayCallback')(ctx, this.area, this.dygraph_, this.dygraph_); } - if (this.options.drawYGrid) { + if (this.attr_('drawYGrid')) { var ticks = this.layout.yticks; ctx.save(); - ctx.strokeStyle = this.options.gridLineColor; - ctx.lineWidth = this.options.axisLineWidth; + ctx.strokeStyle = this.attr_('gridLineColor'); + ctx.lineWidth = this.attr_('gridLineWidth'); for (var i = 0; i < ticks.length; i++) { // TODO(danvk): allow secondary axes to draw a grid, too. if (ticks[i][0] != 0) continue; @@ -485,11 +476,11 @@ DygraphCanvasRenderer.prototype.render = function() { } } - if (this.options.drawXGrid) { + if (this.attr_('drawXGrid')) { var ticks = this.layout.xticks; ctx.save(); - ctx.strokeStyle = this.options.gridLineColor; - ctx.lineWidth = this.options.axisLineWidth; + ctx.strokeStyle = this.attr_('gridLineColor'); + ctx.lineWidth = this.attr_('gridLineWidth'); for (var i=0; i 0) { for (var i = 0; i < this.layout.yticks.length; i++) { var tick = this.layout.yticks[i]; @@ -557,28 +552,28 @@ DygraphCanvasRenderer.prototype._renderAxis = function() { var y = this.area.y + tick[1] * this.area.h; context.beginPath(); context.moveTo(halfUp(x), halfDown(y)); - context.lineTo(halfUp(x - sgn * this.options.axisTickSize), halfDown(y)); + context.lineTo(halfUp(x - sgn * this.attr_('axisTickSize')), halfDown(y)); context.closePath(); context.stroke(); - var label = makeDiv(tick[2]); - var top = (y - this.options.axisLabelFontSize / 2); + var label = makeDiv(tick[2], 'y'); + var top = (y - this.attr_('axisLabelFontSize') / 2); if (top < 0) top = 0; - if (top + this.options.axisLabelFontSize + 3 > this.height) { + if (top + this.attr_('axisLabelFontSize') + 3 > this.height) { label.style.bottom = "0px"; } else { label.style.top = top + "px"; } if (tick[0] == 0) { - label.style.left = (this.area.x - this.options.yAxisLabelWidth - this.options.axisTickSize) + "px"; + label.style.left = (this.area.x - this.attr_('yAxisLabelWidth') - this.attr_('axisTickSize')) + "px"; label.style.textAlign = "right"; } else if (tick[0] == 1) { label.style.left = (this.area.x + this.area.w + - this.options.axisTickSize) + "px"; + this.attr_('axisTickSize')) + "px"; label.style.textAlign = "left"; } - label.style.width = this.options.yAxisLabelWidth + "px"; + label.style.width = this.attr_('yAxisLabelWidth') + "px"; this.container.appendChild(label); this.ylabels.push(label); } @@ -587,7 +582,7 @@ DygraphCanvasRenderer.prototype._renderAxis = function() { // tick on the x-axis. Shift the bottom tick up a little bit to // compensate if necessary. var bottomTick = this.ylabels[0]; - var fontSize = this.options.axisLabelFontSize; + var fontSize = this.attr_('axisLabelFontSize'); var bottom = parseInt(bottomTick.style.top) + fontSize; if (bottom > this.height - fontSize) { bottomTick.style.top = (parseInt(bottomTick.style.top) - @@ -612,7 +607,7 @@ DygraphCanvasRenderer.prototype._renderAxis = function() { } } - if (this.options.drawXAxis) { + if (this.attr_('drawXAxis')) { if (this.layout.xticks) { for (var i = 0; i < this.layout.xticks.length; i++) { var tick = this.layout.xticks[i]; @@ -622,17 +617,17 @@ DygraphCanvasRenderer.prototype._renderAxis = function() { var y = this.area.y + this.area.h; context.beginPath(); context.moveTo(halfUp(x), halfDown(y)); - context.lineTo(halfUp(x), halfDown(y + this.options.axisTickSize)); + context.lineTo(halfUp(x), halfDown(y + this.attr_('axisTickSize'))); context.closePath(); context.stroke(); - var label = makeDiv(tick[1]); + var label = makeDiv(tick[1], 'x'); label.style.textAlign = "center"; - label.style.top = (y + this.options.axisTickSize) + 'px'; + label.style.top = (y + this.attr_('axisTickSize')) + 'px'; - var left = (x - this.options.axisLabelWidth/2); - if (left + this.options.axisLabelWidth > this.width) { - left = this.width - this.options.xAxisLabelWidth; + var left = (x - this.attr_('axisLabelWidth')/2); + if (left + this.attr_('axisLabelWidth') > this.width) { + left = this.width - this.attr_('xAxisLabelWidth'); label.style.textAlign = "right"; } if (left < 0) { @@ -641,7 +636,7 @@ DygraphCanvasRenderer.prototype._renderAxis = function() { } label.style.left = left + "px"; - label.style.width = this.options.xAxisLabelWidth + "px"; + label.style.width = this.attr_('xAxisLabelWidth') + "px"; this.container.appendChild(label); this.xlabels.push(label); } @@ -756,7 +751,7 @@ DygraphCanvasRenderer.prototype._renderChartLabels = function() { DygraphCanvasRenderer.prototype._renderAnnotations = function() { var annotationStyle = { "position": "absolute", - "fontSize": this.options.axisLabelFontSize + "px", + "fontSize": this.attr_('axisLabelFontSize') + "px", "zIndex": 10, "overflow": "hidden" }; @@ -857,10 +852,8 @@ DygraphCanvasRenderer.prototype._renderAnnotations = function() { DygraphCanvasRenderer.prototype._renderLineChart = function() { // TODO(danvk): use this.attr_ for many of these. var context = this.elementContext; - var colorCount = this.options.colorScheme.length; - var colorScheme = this.options.colorScheme; - var fillAlpha = this.options.fillAlpha; - var errorBars = this.attr_("errorBars"); + var fillAlpha = this.attr_('fillAlpha'); + var errorBars = this.attr_("errorBars") || this.attr_("customBars"); var fillGraph = this.attr_("fillGraph"); var stackedGraph = this.attr_("stackedGraph"); var stepPlot = this.attr_("stepPlot"); @@ -873,9 +866,10 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { } var setCount = setNames.length; + // TODO(danvk): Move this mapping into Dygraph and get it out of here. this.colors = {} for (var i = 0; i < setCount; i++) { - this.colors[setNames[i]] = colorScheme[i % colorCount]; + this.colors[setNames[i]] = this.colorScheme_[i % this.colorScheme_.length]; } // Update Points @@ -1009,6 +1003,10 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { } } + var isNullOrNaN = function(x) { + return (x === null || isNaN(x)); + }; + for (var i = 0; i < setCount; i++) { var setName = setNames[i]; var color = this.colors[setName]; @@ -1024,12 +1022,12 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { for (var j = 0; j < points.length; j++) { var point = points[j]; if (point.name == setName) { - if (!Dygraph.isOK(point.canvasy)) { + if (isNullOrNaN(point.canvasy)) { if (stepPlot && prevX != null) { // Draw a horizontal line to the start of the missing data ctx.beginPath(); ctx.strokeStyle = color; - ctx.lineWidth = this.options.strokeWidth; + ctx.lineWidth = this.attr_('strokeWidth'); ctx.moveTo(prevX, prevY); ctx.lineTo(point.canvasx, prevY); ctx.stroke(); @@ -1040,9 +1038,9 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { // A point is "isolated" if it is non-null but both the previous // and next points are null. var isIsolated = (!prevX && (j == points.length - 1 || - !Dygraph.isOK(points[j+1].canvasy))); + isNullOrNaN(points[j+1].canvasy))); - if (!prevX) { + if (prevX === null) { prevX = point.canvasx; prevY = point.canvasy; } else {