X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph-canvas.js;h=e661d7ea81b163c55c5fbe5c76919bd4b083307d;hb=354e15abd4ca9d73768dfe07c567737d835fcaa2;hp=5b074968b576d9db6ec4beef690ef72904948237;hpb=3df0ccf0a09d28577e3a695302f1deb926ae8a83;p=dygraphs.git diff --git a/dygraph-canvas.js b/dygraph-canvas.js index 5b07496..e661d7e 100644 --- a/dygraph-canvas.js +++ b/dygraph-canvas.js @@ -17,7 +17,7 @@ DygraphLayout = function(dygraph, options) { this.dygraph_ = dygraph; this.options = {}; // TODO(danvk): remove, use attr_ instead. - MochiKit.Base.update(this.options, options ? options : {}); + Dygraph.update(this.options, options ? options : {}); this.datasets = new Array(); }; @@ -37,13 +37,19 @@ DygraphLayout.prototype.evaluate = function() { DygraphLayout.prototype._evaluateLimits = function() { this.minxval = this.maxxval = null; - for (var name in this.datasets) { - var series = this.datasets[name]; - var x1 = series[0][0]; - if (!this.minxval || x1 < this.minxval) this.minxval = x1; - - var x2 = series[series.length - 1][0]; - if (!this.maxxval || x2 > this.maxxval) this.maxxval = x2; + if (this.options.dateWindow) { + this.minxval = this.options.dateWindow[0]; + this.maxxval = this.options.dateWindow[1]; + } else { + for (var name in this.datasets) { + if (!this.datasets.hasOwnProperty(name)) continue; + var series = this.datasets[name]; + var x1 = series[0][0]; + if (!this.minxval || x1 < this.minxval) this.minxval = x1; + + var x2 = series[series.length - 1][0]; + if (!this.maxxval || x2 > this.maxxval) this.maxxval = x2; + } } this.xrange = this.maxxval - this.minxval; this.xscale = (this.xrange != 0 ? 1/this.xrange : 1.0); @@ -58,10 +64,13 @@ DygraphLayout.prototype._evaluateLineCharts = function() { // add all the rects this.points = new Array(); for (var setName in this.datasets) { + if (!this.datasets.hasOwnProperty(setName)) continue; + var dataset = this.datasets[setName]; for (var j = 0; j < dataset.length; j++) { var item = dataset[j]; var point = { + // TODO(danvk): here x: ((parseFloat(item[0]) - this.minxval) * this.xscale), y: 1.0 - ((parseFloat(item[1]) - this.minyval) * this.yscale), xval: parseFloat(item[0]), @@ -76,9 +85,7 @@ DygraphLayout.prototype._evaluateLineCharts = function() { if (point.y >= 1.0) { point.y = 1.0; } - if ((point.x >= 0.0) && (point.x <= 1.0)) { - this.points.push(point); - } + this.points.push(point); } } }; @@ -117,6 +124,7 @@ DygraphLayout.prototype.evaluateWithError = function() { // Copy over the error terms var i = 0; // index in this.points for (var setName in this.datasets) { + if (!this.datasets.hasOwnProperty(setName)) continue; var j = 0; var dataset = this.datasets[setName]; for (var j = 0; j < dataset.length; j++, i++) { @@ -146,7 +154,7 @@ DygraphLayout.prototype.removeAllDatasets = function() { * @param {Object} new_options an associative array of new properties */ DygraphLayout.prototype.updateOptions = function(new_options) { - MochiKit.Base.update(this.options, new_options ? new_options : {}); + Dygraph.update(this.options, new_options ? new_options : {}); }; // Subclass PlotKit.CanvasRenderer to add: @@ -165,37 +173,28 @@ DygraphCanvasRenderer = function(dygraph, element, layout, options) { // default options this.options = { - "strokeWidth": 0.5, - "drawXAxis": true, - "drawYAxis": true, - "axisLineColor": Color.blackColor(), - "axisLineWidth": 0.5, - "axisTickSize": 3, - "axisLabelColor": Color.blackColor(), - "axisLabelFont": "Arial", - "axisLabelFontSize": 9, - "axisLabelWidth": 50, - "drawYGrid": true, - "drawXGrid": true, - "gridLineColor": MochiKit.Color.Color.grayColor() + "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 }; - MochiKit.Base.update(this.options, options); + Dygraph.update(this.options, options); this.layout = layout; - this.element = MochiKit.DOM.getElement(element); + this.element = element; this.container = this.element.parentNode; - // Stuff relating to Canvas on IE support - this.isIE = (/MSIE/.test(navigator.userAgent) && !window.opera); - - if (this.isIE && !isNil(G_vmlCanvasManager)) { - this.IEDelay = 0.5; - this.maxTries = 5; - this.renderDelay = null; - this.clearDelay = null; - this.element = G_vmlCanvasManager.initElement(this.element); - } - this.height = this.element.height; this.width = this.element.width; @@ -215,8 +214,8 @@ DygraphCanvasRenderer = function(dygraph, element, layout, options) { this.area.h = this.height - this.options.axisLabelFontSize - 2 * this.options.axisTickSize; - MochiKit.DOM.updateNodeAttributes(this.container, - {"style":{ "position": "relative", "width": this.width + "px"}}); + this.container.style.position = "relative"; + this.container.style.width = this.width + "px"; }; DygraphCanvasRenderer.prototype.clear = function() { @@ -230,6 +229,7 @@ DygraphCanvasRenderer.prototype.clear = function() { var context = this.element.getContext("2d"); } 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; @@ -239,8 +239,14 @@ DygraphCanvasRenderer.prototype.clear = function() { var context = this.element.getContext("2d"); context.clearRect(0, 0, this.width, this.height); - MochiKit.Iter.forEach(this.xlabels, MochiKit.DOM.removeElement); - MochiKit.Iter.forEach(this.ylabels, MochiKit.DOM.removeElement); + for (var i = 0; i < this.xlabels.length; i++) { + var el = this.xlabels[i]; + el.parentNode.removeChild(el); + } + for (var i = 0; i < this.ylabels.length; i++) { + var el = this.ylabels[i]; + el.parentNode.removeChild(el); + } this.xlabels = new Array(); this.ylabels = new Array(); }; @@ -249,10 +255,10 @@ DygraphCanvasRenderer.prototype.clear = function() { DygraphCanvasRenderer.isSupported = function(canvasName) { var canvas = null; try { - if (MochiKit.Base.isUndefinedOrNull(canvasName)) - canvas = MochiKit.DOM.CANVAS({}); + if (typeof(canvasName) == 'undefined' || canvasName == null) + canvas = document.createElement("canvas"); else - canvas = MochiKit.DOM.getElement(canvasName); + canvas = canvasName; var context = canvas.getContext("2d"); } catch (e) { @@ -271,10 +277,15 @@ DygraphCanvasRenderer.isSupported = function(canvasName) { DygraphCanvasRenderer.prototype.render = function() { // Draw the new X/Y grid var ctx = this.element.getContext("2d"); + + if (this.options.underlayCallback) { + this.options.underlayCallback(ctx, this.area, this.layout, this.dygraph_); + } + if (this.options.drawYGrid) { var ticks = this.layout.yticks; ctx.save(); - ctx.strokeStyle = this.options.gridLineColor.toRGBString(); + ctx.strokeStyle = this.options.gridLineColor; ctx.lineWidth = this.options.axisLineWidth; for (var i = 0; i < ticks.length; i++) { var x = this.area.x; @@ -290,7 +301,7 @@ DygraphCanvasRenderer.prototype.render = function() { if (this.options.drawXGrid) { var ticks = this.layout.xticks; ctx.save(); - ctx.strokeStyle = this.options.gridLineColor.toRGBString(); + ctx.strokeStyle = this.options.gridLineColor; ctx.lineWidth = this.options.axisLineWidth; for (var i=0; i 0) { + for (var i = 0; i < this.layout.yticks.length; i++) { + var tick = this.layout.yticks[i]; if (typeof(tick) == "function") return; var x = this.area.x; var y = this.area.y + tick[0] * this.area.h; @@ -344,7 +363,7 @@ DygraphCanvasRenderer.prototype._renderAxis = function() { context.closePath(); context.stroke(); - var label = DIV(labelStyle, tick[1]); + var label = makeDiv(tick[1]); var top = (y - this.options.axisLabelFontSize / 2); if (top < 0) top = 0; @@ -356,11 +375,9 @@ DygraphCanvasRenderer.prototype._renderAxis = function() { label.style.left = "0px"; label.style.textAlign = "right"; label.style.width = this.options.yAxisLabelWidth + "px"; - MochiKit.DOM.appendChildNodes(this.container, label); + this.container.appendChild(label); this.ylabels.push(label); - }; - - MochiKit.Iter.forEach(this.layout.yticks, bind(drawTick, this)); + } // The lowest tick on the y-axis often overlaps with the leftmost // tick on the x-axis. Shift the bottom tick up a little bit to @@ -383,7 +400,8 @@ DygraphCanvasRenderer.prototype._renderAxis = function() { if (this.options.drawXAxis) { if (this.layout.xticks) { - var drawTick = function(tick) { + for (var i = 0; i < this.layout.xticks.length; i++) { + var tick = this.layout.xticks[i]; if (typeof(dataset) == "function") return; var x = this.area.x + tick[0] * this.area.w; @@ -394,7 +412,7 @@ DygraphCanvasRenderer.prototype._renderAxis = function() { context.closePath(); context.stroke(); - var label = DIV(labelStyle, tick[1]); + var label = makeDiv(tick[1]); label.style.textAlign = "center"; label.style.bottom = "0px"; @@ -410,11 +428,9 @@ DygraphCanvasRenderer.prototype._renderAxis = function() { label.style.left = left + "px"; label.style.width = this.options.xAxisLabelWidth + "px"; - MochiKit.DOM.appendChildNodes(this.container, label); + this.container.appendChild(label); this.xlabels.push(label); - }; - - MochiKit.Iter.forEach(this.layout.xticks, bind(drawTick, this)); + } } context.beginPath(); @@ -435,120 +451,184 @@ 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 fillAlpha = this.options.fillAlpha; var errorBars = this.layout.options.errorBars; + var fillGraph = this.layout.options.fillGraph; + var stackedGraph = this.layout.options.stackedGraph; + + var setNames = []; + for (var name in this.layout.datasets) { + if (this.layout.datasets.hasOwnProperty(name)) { + setNames.push(name); + } + } var setCount = setNames.length; - var bind = MochiKit.Base.bind; - var partial = MochiKit.Base.partial; - //Update Points - var updatePoint = function(point) { + this.colors = {} + for (var i = 0; i < setCount; i++) { + this.colors[setNames[i]] = colorScheme[i % colorCount]; + } + + // Update Points + // TODO(danvk): here + for (var i = 0; i < this.layout.points.length; i++) { + var point = this.layout.points[i]; point.canvasx = this.area.w * point.x + this.area.x; point.canvasy = this.area.h * point.y + this.area.y; } - MochiKit.Iter.forEach(this.layout.points, updatePoint, this); // create paths var isOK = function(x) { return x && !isNaN(x); }; - var makePath = function(ctx) { + + var ctx = context; + if (errorBars) { + if (fillGraph) { + this.dygraph_.warn("Can't use fillGraph option with error bars"); + } + for (var i = 0; i < setCount; i++) { var setName = setNames[i]; - var color = colorScheme[i%colorCount]; - var strokeX = this.options.strokeColorTransform; + var color = this.colors[setName]; // setup graphics context - context.save(); - context.strokeStyle = color.toRGBString(); - context.lineWidth = this.options.strokeWidth; - var point = this.layout.points[0]; - var pointSize = this.dygraph_.attr_("pointSize"); - var prevX = null, prevY = null; - var drawPoints = this.dygraph_.attr_("drawPoints"); - var points = this.layout.points; - for (var j = 0; j < points.length; j++) { - var point = points[j]; + ctx.save(); + var prevX = NaN; + var prevYs = [-1, -1]; + var yscale = this.layout.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(); + for (var j = 0; j < this.layout.points.length; j++) { + var point = this.layout.points[j]; if (point.name == setName) { - if (!isOK(point.canvasy)) { - // this will make us move to the next point, not draw a line to it. - prevX = prevY = null; - } else { - // 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 || - !isOK(points[j+1].canvasy))); - - if (!prevX) { - prevX = point.canvasx; - prevY = point.canvasy; - } else { - ctx.beginPath(); - ctx.moveTo(prevX, prevY); - prevX = point.canvasx; - prevY = point.canvasy; - ctx.lineTo(prevX, prevY); - ctx.stroke(); - } - - if (drawPoints || isIsolated) { - ctx.beginPath(); - ctx.fillStyle = color.toRGBString(); - ctx.arc(point.canvasx, point.canvasy, pointSize, 0, 360, false); - ctx.fill(); - } + if (!isOK(point.y)) { + prevX = NaN; + continue; } + // TODO(danvk): here + 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 (!isNaN(prevX)) { + 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(); } - }; + } else if (fillGraph) { + var axisY = 1.0 + this.layout.minyval * this.layout.yscale; + if (axisY < 0.0) axisY = 0.0; + else if (axisY > 1.0) axisY = 1.0; + axisY = this.area.h * axisY + this.area.y; - var makeErrorBars = function(ctx) { - for (var i = 0; i < setCount; i++) { + var baseline = [] // for stacked graphs: baseline for filling + + // process sets in reverse order (needed for stacked graphs) + for (var i = setCount - 1; i >= 0; i--) { var setName = setNames[i]; - var color = colorScheme[i % colorCount]; - var strokeX = this.options.strokeColorTransform; + var color = this.colors[setName]; // setup graphics context - context.save(); - context.strokeStyle = color.toRGBString(); - context.lineWidth = this.options.strokeWidth; - var prevX = -1; + ctx.save(); + var prevX = NaN; var prevYs = [-1, -1]; - var count = 0; var yscale = this.layout.yscale; - var errorTrapezoid = function(ctx_,point) { - count++; + // 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(); + for (var j = 0; j < this.layout.points.length; j++) { + var point = this.layout.points[j]; if (point.name == setName) { - if (!point.y || isNaN(point.y)) { - prevX = -1; - return; + if (!isOK(point.y)) { + prevX = NaN; + continue; } - 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(); + var newYs; + if (stackedGraph) { + lastY = baseline[point.canvasx]; + if (lastY === undefined) lastY = axisY; + baseline[point.canvasx] = point.canvasy; + newYs = [ point.canvasy, lastY ]; + } else { + newYs = [ point.canvasy, axisY ]; } - prevYs[0] = newYs[0]; - prevYs[1] = newYs[1]; + if (!isNaN(prevX)) { + 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; } - }; - // 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(); } - }; + } + + for (var i = 0; i < setCount; i++) { + var setName = setNames[i]; + var color = this.colors[setName]; + + // setup graphics context + context.save(); + var point = this.layout.points[0]; + var pointSize = this.dygraph_.attr_("pointSize"); + var prevX = null, prevY = null; + var drawPoints = this.dygraph_.attr_("drawPoints"); + var points = this.layout.points; + for (var j = 0; j < points.length; j++) { + var point = points[j]; + if (point.name == setName) { + if (!isOK(point.canvasy)) { + // this will make us move to the next point, not draw a line to it. + prevX = prevY = null; + } else { + // 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 || + !isOK(points[j+1].canvasy))); + + if (!prevX) { + prevX = point.canvasx; + prevY = point.canvasy; + } else { + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.lineWidth = this.options.strokeWidth; + ctx.moveTo(prevX, prevY); + prevX = point.canvasx; + prevY = point.canvasy; + ctx.lineTo(prevX, prevY); + ctx.stroke(); + } + + if (drawPoints || isIsolated) { + ctx.beginPath(); + ctx.fillStyle = color; + ctx.arc(point.canvasx, point.canvasy, pointSize, + 0, 2 * Math.PI, false); + ctx.fill(); + } + } + } + } + } - if (errorBars) - bind(makeErrorBars, this)(context); - bind(makePath, this)(context); context.restore(); };