X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph-canvas.js;h=5326673b1d466594a7e7bd35b4de2e8edb22e642;hb=beeabac2f71ae45c510b0355d0e76972083a6ca6;hp=4a29848c61708d121162c8c21f5fc24bdecb663e;hpb=0e73b025c40afe6193dfc9ddb6ea6c640156d92e;p=dygraphs.git diff --git a/dygraph-canvas.js b/dygraph-canvas.js index 4a29848..5326673 100644 --- a/dygraph-canvas.js +++ b/dygraph-canvas.js @@ -29,6 +29,23 @@ "use strict"; +/** + * @constructor + * + * This gets called when there are "new points" to chart. This is generally the + * case when the underlying data being charted has changed. It is _not_ called + * in the common case that the user has zoomed or is panning the view. + * + * 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 {CanvasRenderingContext2D} elementContext The drawing context. + * @param {DygraphLayout} layout The chart's DygraphLayout object. + * + * TODO(danvk): remove the elementContext property. + */ var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) { this.dygraph_ = dygraph; @@ -45,11 +62,6 @@ var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) { throw "Canvas is not supported."; // internal state - this.xlabels = []; - this.ylabels = []; - this.annotations = []; - this.chartLabels = {}; - this.area = layout.getPlotArea(); this.container.style.position = "relative"; this.container.style.width = this.width + "px"; @@ -79,6 +91,12 @@ DygraphCanvasRenderer.prototype.attr_ = function(x) { return this.dygraph_.attr_(x); }; +/** + * Clears out all chart content and DOM elements. + * This is called immediately before render() on every frame, including + * during zooms and pans. + * @private + */ DygraphCanvasRenderer.prototype.clear = function() { var context; if (this.isIE) { @@ -100,30 +118,12 @@ DygraphCanvasRenderer.prototype.clear = function() { context = this.elementContext; context.clearRect(0, 0, this.width, this.height); - - function removeArray(ary) { - for (var i = 0; i < ary.length; i++) { - var el = ary[i]; - if (el.parentNode) el.parentNode.removeChild(el); - } - } - - removeArray(this.xlabels); - removeArray(this.ylabels); - removeArray(this.annotations); - - for (var k in this.chartLabels) { - if (!this.chartLabels.hasOwnProperty(k)) continue; - var el = this.chartLabels[k]; - if (el.parentNode) el.parentNode.removeChild(el); - } - this.xlabels = []; - this.ylabels = []; - this.annotations = []; - this.chartLabels = {}; }; - +/** + * Checks whether the browser supports the <canvas> tag. + * @private + */ DygraphCanvasRenderer.isSupported = function(canvasName) { var canvas = null; try { @@ -153,7 +153,11 @@ DygraphCanvasRenderer.prototype.setColors = function(colors) { }; /** - * Draw an X/Y grid on top of the existing plot + * 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 + * and zooms. + * @private */ DygraphCanvasRenderer.prototype.render = function() { // Draw the new X/Y grid. Lines appear crisper when pixels are rounded to @@ -208,9 +212,6 @@ DygraphCanvasRenderer.prototype.render = function() { // Do the ordinary rendering, as before this._renderLineChart(); - this._renderAxis(); - this._renderChartLabels(); - this._renderAnnotations(); }; DygraphCanvasRenderer.prototype._createIEClipArea = function() { @@ -282,400 +283,6 @@ DygraphCanvasRenderer.prototype._createIEClipArea = function() { }); }; -DygraphCanvasRenderer.prototype._renderAxis = function() { - if (!this.attr_('drawXAxis') && !this.attr_('drawYAxis')) return; - - // Round pixels to half-integer boundaries for crisper drawing. - function halfUp(x) { return Math.round(x) + 0.5; } - function halfDown(y){ return Math.round(y) - 0.5; } - - var context = this.elementContext; - - var label, x, y, tick, i; - - var labelStyle = { - position: "absolute", - fontSize: this.attr_('axisLabelFontSize') + "px", - zIndex: 10, - color: this.attr_('axisLabelColor'), - width: this.attr_('axisLabelWidth') + "px", - // height: this.attr_('axisLabelFontSize') + 2 + "px", - lineHeight: "normal", // Something other than "normal" line-height screws up label positioning. - overflow: "hidden" - }; - var makeDiv = function(txt, axis, prec_axis) { - var div = document.createElement("div"); - for (var name in labelStyle) { - if (labelStyle.hasOwnProperty(name)) { - div.style[name] = labelStyle[name]; - } - } - var inner_div = document.createElement("div"); - inner_div.className = 'dygraph-axis-label' + - ' dygraph-axis-label-' + axis + - (prec_axis ? ' dygraph-axis-label-' + prec_axis : ''); - inner_div.innerHTML=txt; - div.appendChild(inner_div); - return div; - }; - - // axis lines - context.save(); - context.strokeStyle = this.attr_('axisLineColor'); - context.lineWidth = this.attr_('axisLineWidth'); - - if (this.attr_('drawYAxis')) { - if (this.layout.yticks && this.layout.yticks.length > 0) { - var num_axes = this.dygraph_.numAxes(); - for (i = 0; i < this.layout.yticks.length; i++) { - tick = this.layout.yticks[i]; - if (typeof(tick) == "function") return; - x = this.area.x; - var sgn = 1; - var prec_axis = 'y1'; - if (tick[0] == 1) { // right-side y-axis - x = this.area.x + this.area.w; - sgn = -1; - prec_axis = 'y2'; - } - y = this.area.y + tick[1] * this.area.h; - - /* Tick marks are currently clipped, so don't bother drawing them. - context.beginPath(); - context.moveTo(halfUp(x), halfDown(y)); - context.lineTo(halfUp(x - sgn * this.attr_('axisTickSize')), halfDown(y)); - context.closePath(); - context.stroke(); - */ - - label = makeDiv(tick[2], 'y', num_axes == 2 ? prec_axis : null); - var top = (y - this.attr_('axisLabelFontSize') / 2); - if (top < 0) top = 0; - - 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.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.attr_('axisTickSize')) + "px"; - label.style.textAlign = "left"; - } - label.style.width = this.attr_('yAxisLabelWidth') + "px"; - this.container.appendChild(label); - this.ylabels.push(label); - } - - // 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 - // compensate if necessary. - var bottomTick = this.ylabels[0]; - var fontSize = this.attr_('axisLabelFontSize'); - var bottom = parseInt(bottomTick.style.top, 10) + fontSize; - if (bottom > this.height - fontSize) { - bottomTick.style.top = (parseInt(bottomTick.style.top, 10) - - fontSize / 2) + "px"; - } - } - - // draw a vertical line on the left to separate the chart from the labels. - var axisX; - if (this.attr_('drawAxesAtZero')) { - var r = this.dygraph_.toPercentXCoord(0); - if (r > 1 || r < 0) r = 0; - axisX = halfUp(this.area.x + r * this.area.w); - } else { - axisX = halfUp(this.area.x); - } - context.beginPath(); - context.moveTo(axisX, halfDown(this.area.y)); - context.lineTo(axisX, halfDown(this.area.y + this.area.h)); - context.closePath(); - context.stroke(); - - // if there's a secondary y-axis, draw a vertical line for that, too. - if (this.dygraph_.numAxes() == 2) { - context.beginPath(); - context.moveTo(halfDown(this.area.x + this.area.w), halfDown(this.area.y)); - context.lineTo(halfDown(this.area.x + this.area.w), halfDown(this.area.y + this.area.h)); - context.closePath(); - context.stroke(); - } - } - - if (this.attr_('drawXAxis')) { - if (this.layout.xticks) { - for (i = 0; i < this.layout.xticks.length; i++) { - tick = this.layout.xticks[i]; - x = this.area.x + tick[0] * this.area.w; - y = this.area.y + this.area.h; - - /* Tick marks are currently clipped, so don't bother drawing them. - context.beginPath(); - context.moveTo(halfUp(x), halfDown(y)); - context.lineTo(halfUp(x), halfDown(y + this.attr_('axisTickSize'))); - context.closePath(); - context.stroke(); - */ - - label = makeDiv(tick[1], 'x'); - label.style.textAlign = "center"; - label.style.top = (y + this.attr_('axisTickSize')) + 'px'; - - 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) { - left = 0; - label.style.textAlign = "left"; - } - - label.style.left = left + "px"; - label.style.width = this.attr_('xAxisLabelWidth') + "px"; - this.container.appendChild(label); - this.xlabels.push(label); - } - } - - context.beginPath(); - var axisY; - if (this.attr_('drawAxesAtZero')) { - var r = this.dygraph_.toPercentYCoord(0, 0); - if (r > 1 || r < 0) r = 1; - axisY = halfDown(this.area.y + r * this.area.h); - } else { - axisY = halfDown(this.area.y + this.area.h); - } - context.moveTo(halfUp(this.area.x), axisY); - context.lineTo(halfUp(this.area.x + this.area.w), axisY); - context.closePath(); - context.stroke(); - } - - context.restore(); -}; - - -DygraphCanvasRenderer.prototype._renderChartLabels = function() { - var div, class_div; - - // Generate divs for the chart title, xlabel and ylabel. - // Space for these divs has already been taken away from the charting area in - // the DygraphCanvasRenderer constructor. - if (this.attr_('title')) { - div = document.createElement("div"); - div.style.position = 'absolute'; - div.style.top = '0px'; - div.style.left = this.area.x + 'px'; - div.style.width = this.area.w + 'px'; - div.style.height = this.attr_('titleHeight') + 'px'; - div.style.textAlign = 'center'; - div.style.fontSize = (this.attr_('titleHeight') - 8) + 'px'; - div.style.fontWeight = 'bold'; - class_div = document.createElement("div"); - class_div.className = 'dygraph-label dygraph-title'; - class_div.innerHTML = this.attr_('title'); - div.appendChild(class_div); - this.container.appendChild(div); - this.chartLabels.title = div; - } - - if (this.attr_('xlabel')) { - div = document.createElement("div"); - div.style.position = 'absolute'; - div.style.bottom = 0; // TODO(danvk): this is lazy. Calculate style.top. - div.style.left = this.area.x + 'px'; - div.style.width = this.area.w + 'px'; - div.style.height = this.attr_('xLabelHeight') + 'px'; - div.style.textAlign = 'center'; - div.style.fontSize = (this.attr_('xLabelHeight') - 2) + 'px'; - - class_div = document.createElement("div"); - class_div.className = 'dygraph-label dygraph-xlabel'; - class_div.innerHTML = this.attr_('xlabel'); - div.appendChild(class_div); - this.container.appendChild(div); - this.chartLabels.xlabel = div; - } - - var that = this; - function createRotatedDiv(axis, classes, html) { - var box = { - left: 0, - top: that.area.y, - width: that.attr_('yLabelWidth'), - height: that.area.h - }; - // TODO(danvk): is this outer div actually necessary? - div = document.createElement("div"); - div.style.position = 'absolute'; - if (axis == 1) { - div.style.left = box.left; - } else { - div.style.right = box.left; - } - div.style.top = box.top + 'px'; - div.style.width = box.width + 'px'; - div.style.height = box.height + 'px'; - div.style.fontSize = (that.attr_('yLabelWidth') - 2) + 'px'; - - var inner_div = document.createElement("div"); - inner_div.style.position = 'absolute'; - inner_div.style.width = box.height + 'px'; - inner_div.style.height = box.width + 'px'; - inner_div.style.top = (box.height / 2 - box.width / 2) + 'px'; - inner_div.style.left = (box.width / 2 - box.height / 2) + 'px'; - inner_div.style.textAlign = 'center'; - - // CSS rotation is an HTML5 feature which is not standardized. Hence every - // browser has its own name for the CSS style. - var val = 'rotate(' + (axis == 1 ? '-' : '') + '90deg)'; - inner_div.style.transform = val; // HTML5 - inner_div.style.WebkitTransform = val; // Safari/Chrome - inner_div.style.MozTransform = val; // Firefox - inner_div.style.OTransform = val; // Opera - inner_div.style.msTransform = val; // IE9 - - if (typeof(document.documentMode) !== 'undefined' && - document.documentMode < 9) { - // We're dealing w/ an old version of IE, so we have to rotate the text - // using a BasicImage transform. This uses a different origin of rotation - // than HTML5 rotation (top left of div vs. its center). - inner_div.style.filter = - 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + - (axis == 1 ? '3' : '1') + ')'; - inner_div.style.left = '0px'; - inner_div.style.top = '0px'; - } - - class_div = document.createElement("div"); - class_div.className = classes; - class_div.innerHTML = html; - - inner_div.appendChild(class_div); - div.appendChild(inner_div); - return div; - } - - var div; - if (this.attr_('ylabel')) { - div = createRotatedDiv(1, 'dygraph-label dygraph-ylabel', - this.attr_('ylabel')); - this.container.appendChild(div); - this.chartLabels.ylabel = div; - } - if (this.attr_('y2label') && this.dygraph_.numAxes() == 2) { - div = createRotatedDiv(2, 'dygraph-label dygraph-y2label', - this.attr_('y2label')); - this.container.appendChild(div); - this.chartLabels.y2label = div; - } -}; - - -DygraphCanvasRenderer.prototype._renderAnnotations = function() { - var annotationStyle = { - "position": "absolute", - "fontSize": this.attr_('axisLabelFontSize') + "px", - "zIndex": 10, - "overflow": "hidden" - }; - - var bindEvt = function(eventName, classEventName, p, self) { - return function(e) { - var a = p.annotation; - if (a.hasOwnProperty(eventName)) { - a[eventName](a, p, self.dygraph_, e); - } else if (self.dygraph_.attr_(classEventName)) { - self.dygraph_.attr_(classEventName)(a, p, self.dygraph_,e ); - } - }; - }; - - // Get a list of point with annotations. - var points = this.layout.annotated_points; - for (var i = 0; i < points.length; i++) { - var p = points[i]; - if (p.canvasx < this.area.x || p.canvasx > this.area.x + this.area.w || - p.canvasy < this.area.y || p.canvasy > this.area.y + this.area.h) { - continue; - } - - var a = p.annotation; - var tick_height = 6; - if (a.hasOwnProperty("tickHeight")) { - tick_height = a.tickHeight; - } - - var div = document.createElement("div"); - for (var name in annotationStyle) { - if (annotationStyle.hasOwnProperty(name)) { - div.style[name] = annotationStyle[name]; - } - } - if (!a.hasOwnProperty('icon')) { - div.className = "dygraphDefaultAnnotation"; - } - if (a.hasOwnProperty('cssClass')) { - div.className += " " + a.cssClass; - } - - var width = a.hasOwnProperty('width') ? a.width : 16; - var height = a.hasOwnProperty('height') ? a.height : 16; - if (a.hasOwnProperty('icon')) { - var img = document.createElement("img"); - img.src = a.icon; - img.width = width; - img.height = height; - div.appendChild(img); - } else if (p.annotation.hasOwnProperty('shortText')) { - div.appendChild(document.createTextNode(p.annotation.shortText)); - } - div.style.left = (p.canvasx - width / 2) + "px"; - if (a.attachAtBottom) { - div.style.top = (this.area.h - height - tick_height) + "px"; - } else { - div.style.top = (p.canvasy - height - tick_height) + "px"; - } - div.style.width = width + "px"; - div.style.height = height + "px"; - div.title = p.annotation.text; - div.style.color = this.colors[p.name]; - div.style.borderColor = this.colors[p.name]; - a.div = div; - - this.dygraph_.addEvent(div, 'click', - bindEvt('clickHandler', 'annotationClickHandler', p, this)); - this.dygraph_.addEvent(div, 'mouseover', - bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p, this)); - this.dygraph_.addEvent(div, 'mouseout', - bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p, this)); - this.dygraph_.addEvent(div, 'dblclick', - bindEvt('dblClickHandler', 'annotationDblClickHandler', p, this)); - - this.container.appendChild(div); - this.annotations.push(div); - - var ctx = this.elementContext; - ctx.strokeStyle = this.colors[p.name]; - ctx.beginPath(); - if (!a.attachAtBottom) { - ctx.moveTo(p.canvasx, p.canvasy); - ctx.lineTo(p.canvasx, p.canvasy - 2 - tick_height); - } else { - ctx.moveTo(p.canvasx, this.area.h); - ctx.lineTo(p.canvasx, this.area.h - 2 - tick_height); - } - ctx.closePath(); - ctx.stroke(); - } -}; /** * Returns a predicate to be used with an iterator, which will @@ -889,11 +496,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() { var setNames = this.layout.setNames; var setCount = setNames.length; - // TODO(danvk): Move this mapping into Dygraph and get it out of here. - this.colors = {}; - for (i = 0; i < setCount; i++) { - this.colors[setNames[i]] = this.colorScheme_[i % this.colorScheme_.length]; - } + this.colors = this.dygraph_.colorsMap_; // Update Points // TODO(danvk): here