X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph-canvas.js;h=d6570d12ea1d7e034659be8b0a55fb7d757b0aef;hb=cb00f745bf09d30e1d985bab23cbc8a284064d98;hp=2874990041c65bf93d36ccc9343dd4c002768402;hpb=f8e02b3c7ea1ccbf567bbeda1ceaa959b769dd43;p=dygraphs.git diff --git a/dygraph-canvas.js b/dygraph-canvas.js index 2874990..d6570d1 100644 --- a/dygraph-canvas.js +++ b/dygraph-canvas.js @@ -29,15 +29,18 @@ /** * Creates a new DygraphLayout object. - * @param {Object} options Options for PlotKit.Layout * @return {Object} The DygraphLayout object */ -DygraphLayout = function(dygraph, options) { +DygraphLayout = function(dygraph) { this.dygraph_ = dygraph; - this.options = {}; // TODO(danvk): remove, use attr_ instead. - Dygraph.update(this.options, options ? options : {}); this.datasets = new Array(); this.annotations = new Array(); + this.yAxes_ = null; + + // TODO(danvk): it's odd that xTicks_ and yTicks_ are inputs, but xticks and + // yticks are outputs. Clean this up. + this.xTicks_ = null; + this.yTicks_ = null; }; DygraphLayout.prototype.attr_ = function(name) { @@ -67,11 +70,24 @@ 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); } }; +DygraphLayout.prototype.setXTicks = function(xTicks) { + this.xTicks_ = xTicks; +}; + +// TODO(danvk): add this to the Dygraph object's API or move it into Layout. +DygraphLayout.prototype.setYAxes = function (yAxes) { + this.yAxes_ = yAxes; +}; + +DygraphLayout.prototype.setDateWindow = function(dateWindow) { + this.dateWindow_ = dateWindow; +}; + DygraphLayout.prototype.evaluate = function() { this._evaluateLimits(); this._evaluateLineCharts(); @@ -81,9 +97,9 @@ DygraphLayout.prototype.evaluate = function() { DygraphLayout.prototype._evaluateLimits = function() { this.minxval = this.maxxval = null; - if (this.options.dateWindow) { - this.minxval = this.options.dateWindow[0]; - this.maxxval = this.options.dateWindow[1]; + if (this.dateWindow_) { + this.minxval = this.dateWindow_[0]; + this.maxxval = this.dateWindow_[1]; } else { for (var name in this.datasets) { if (!this.datasets.hasOwnProperty(name)) continue; @@ -100,8 +116,8 @@ DygraphLayout.prototype._evaluateLimits = function() { this.xrange = this.maxxval - this.minxval; this.xscale = (this.xrange != 0 ? 1/this.xrange : 1.0); - for (var i = 0; i < this.options.yAxes.length; i++) { - var axis = this.options.yAxes[i]; + for (var i = 0; i < this.yAxes_.length; i++) { + var axis = this.yAxes_[i]; axis.minyval = axis.computedValueRange[0]; axis.maxyval = axis.computedValueRange[1]; axis.yrange = axis.maxyval - axis.minyval; @@ -126,7 +142,7 @@ DygraphLayout.prototype._evaluateLineCharts = function() { if (!this.datasets.hasOwnProperty(setName)) continue; var dataset = this.datasets[setName]; - var axis = this.options.yAxes[this.options.seriesToAxisMap[setName]]; + var axis = this.dygraph_.axisPropertiesForSeries(setName); for (var j = 0; j < dataset.length; j++) { var item = dataset[j]; @@ -153,8 +169,8 @@ DygraphLayout.prototype._evaluateLineCharts = function() { DygraphLayout.prototype._evaluateLineTicks = function() { this.xticks = new Array(); - for (var i = 0; i < this.options.xTicks.length; i++) { - var tick = this.options.xTicks[i]; + for (var i = 0; i < this.xTicks_.length; i++) { + var tick = this.xTicks_[i]; var label = tick.label; var pos = this.xscale * (tick.v - this.minxval); if ((pos >= 0.0) && (pos <= 1.0)) { @@ -163,8 +179,8 @@ DygraphLayout.prototype._evaluateLineTicks = function() { } this.yticks = new Array(); - for (var i = 0; i < this.options.yAxes.length; i++ ) { - var axis = this.options.yAxes[i]; + for (var i = 0; i < this.yAxes_.length; i++ ) { + var axis = this.yAxes_[i]; for (var j = 0; j < axis.ticks.length; j++) { var tick = axis.ticks[j]; var label = tick.label; @@ -183,7 +199,7 @@ DygraphLayout.prototype._evaluateLineTicks = function() { */ DygraphLayout.prototype.evaluateWithError = function() { this.evaluate(); - if (!this.options.errorBars) return; + if (!(this.attr_('errorBars') || this.attr_('customBars'))) return; // Copy over the error terms var i = 0; // index in this.points @@ -234,14 +250,6 @@ DygraphLayout.prototype.removeAllDatasets = function() { }; /** - * Change the values of various layout options - * @param {Object} new_options an associative array of new properties - */ -DygraphLayout.prototype.updateOptions = function(new_options) { - Dygraph.update(this.options, new_options ? new_options : {}); -}; - -/** * Return a copy of the point at the indicated index, with its yval unstacked. * @param int index of point in layout_.points */ @@ -270,43 +278,17 @@ 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. * @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 */ -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; @@ -325,20 +307,50 @@ 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')) { + 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() + ")"); @@ -346,11 +358,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 @@ -359,24 +371,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() { @@ -444,6 +439,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() { @@ -453,17 +456,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; @@ -477,11 +480,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]; @@ -549,28 +555,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); } @@ -579,7 +585,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) - @@ -604,7 +610,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]; @@ -614,17 +620,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) { @@ -633,7 +639,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); } @@ -748,7 +754,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" }; @@ -849,13 +855,11 @@ 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.layout.options.errorBars; + var fillAlpha = this.attr_('fillAlpha'); + var errorBars = this.attr_("errorBars") || this.attr_("customBars"); var fillGraph = this.attr_("fillGraph"); - var stackedGraph = this.layout.options.stackedGraph; - var stepPlot = this.layout.options.stepPlot; + var stackedGraph = this.attr_("stackedGraph"); + var stepPlot = this.attr_("stepPlot"); var setNames = []; for (var name in this.layout.datasets) { @@ -865,9 +869,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 @@ -887,8 +892,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { for (var i = 0; i < setCount; i++) { var setName = setNames[i]; - var axis = this.layout.options.yAxes[ - this.layout.options.seriesToAxisMap[setName]]; + var axis = this.dygraph_.axisPropertiesForSeries(setName); var color = this.colors[setName]; // setup graphics context @@ -950,8 +954,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { for (var i = setCount - 1; i >= 0; i--) { var setName = setNames[i]; var color = this.colors[setName]; - var axis = this.layout.options.yAxes[ - this.layout.options.seriesToAxisMap[setName]]; + var axis = this.dygraph_.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; @@ -1023,7 +1026,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { // 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(); @@ -1036,7 +1039,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { var isIsolated = (!prevX && (j == points.length - 1 || !Dygraph.isOK(points[j+1].canvasy))); - if (!prevX) { + if (prevX === null) { prevX = point.canvasx; prevY = point.canvasy; } else {