X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph.js;h=2966316ef101ebb965c2bd6e554f8578d8e4d72e;hb=bfb3e0a44ba7eb76704389cd1515db9995944d41;hp=a9b1cf90800849fd7deb7347db15c349353d28b4;hpb=103fd879738e386ff2f65401700b7bcdb4b317a6;p=dygraphs.git diff --git a/dygraph.js b/dygraph.js index a9b1cf9..2966316 100644 --- a/dygraph.js +++ b/dygraph.js @@ -46,7 +46,7 @@ // For "production" code, this gets set to false by uglifyjs. if (typeof(DEBUG) === 'undefined') DEBUG=true; -/*jshint globalstrict: true */ +var Dygraph = (function() { /*global DygraphLayout:false, DygraphCanvasRenderer:false, DygraphOptions:false, G_vmlCanvasManager:false,ActiveXObject:false */ "use strict"; @@ -83,7 +83,7 @@ var Dygraph = function(div, data, opts, opt_fourth_param) { }; Dygraph.NAME = "Dygraph"; -Dygraph.VERSION = "1.0.1"; +Dygraph.VERSION = "1.1.0"; Dygraph.__repr__ = function() { return "[" + Dygraph.NAME + " " + Dygraph.VERSION + "]"; }; @@ -117,10 +117,8 @@ Dygraph.KMG2_SMALL_LABELS = [ 'm', 'u', 'n', 'p', 'f', 'a', 'z', 'y' ]; * and maxNumberWidth options. * @param {number} x The number to be formatted * @param {Dygraph} opts An options view - * @param {string} name The name of the point's data series - * @param {Dygraph} g The dygraph object */ -Dygraph.numberValueFormatter = function(x, opts, pt, g) { +Dygraph.numberValueFormatter = function(x, opts) { var sigFigs = opts('sigFigs'); if (sigFigs !== null) { @@ -191,8 +189,8 @@ Dygraph.numberValueFormatter = function(x, opts, pt, g) { * variant for use as an axisLabelFormatter. * @private */ -Dygraph.numberAxisLabelFormatter = function(x, granularity, opts, g) { - return Dygraph.numberValueFormatter(x, opts, g); +Dygraph.numberAxisLabelFormatter = function(x, granularity, opts) { + return Dygraph.numberValueFormatter(x, opts); }; /** @@ -228,12 +226,12 @@ Dygraph.dateAxisLabelFormatter = function(date, granularity, opts) { if (granularity >= Dygraph.DECADAL) { return '' + year; } else if (granularity >= Dygraph.MONTHLY) { - return Dygraph.SHORT_MONTH_NAMES_[month] + ' ' + year; + return Dygraph.SHORT_MONTH_NAMES_[month] + ' ' + year; } else { var frac = hours * 3600 + mins * 60 + secs + 1e-3 * millis; if (frac === 0 || granularity >= Dygraph.DAILY) { - // e.g. '21Jan' (%d%b) - return Dygraph.zeropad(day) + Dygraph.SHORT_MONTH_NAMES_[month]; + // e.g. '21 Jan' (%d%b) + return Dygraph.zeropad(day) + ' ' + Dygraph.SHORT_MONTH_NAMES_[month]; } else { return Dygraph.hmsString_(hours, mins, secs); } @@ -292,8 +290,6 @@ Dygraph.DEFAULT_ATTRS = { axisTickSize: 3, axisLabelFontSize: 14, - xAxisLabelWidth: 50, - yAxisLabelWidth: 50, rightGap: 5, showRoller: false, @@ -314,9 +310,7 @@ Dygraph.DEFAULT_ATTRS = { stackedGraphNaNFill: 'all', hideOverlayOnMouseOut: true, - // TODO(danvk): support 'onmouseover' and 'never', and remove synonyms. - legend: 'onmouseover', // the only relevant value at the moment is 'always'. - + legend: 'onmouseover', stepPlot: false, avoidMinZero: false, xRangePad: 0, @@ -328,16 +322,11 @@ Dygraph.DEFAULT_ATTRS = { xLabelHeight: 18, yLabelWidth: 18, - drawXAxis: true, - drawYAxis: true, axisLineColor: "black", axisLineWidth: 0.3, gridLineWidth: 0.3, axisLabelColor: "black", - axisLabelFont: "Arial", // TODO(danvk): is this implemented? axisLabelWidth: 50, - drawYGrid: true, - drawXGrid: true, gridLineColor: "rgb(128,128,128)", interactionModel: null, // will be set to Dygraph.Interaction.defaultModel @@ -363,7 +352,8 @@ Dygraph.DEFAULT_ATTRS = { // per-axis options axes: { x: { - pixelsPerLabel: 60, + pixelsPerLabel: 70, + axisLabelWidth: 60, axisLabelFormatter: Dygraph.dateAxisLabelFormatter, valueFormatter: Dygraph.dateValueFormatter, drawGrid: true, @@ -372,6 +362,7 @@ Dygraph.DEFAULT_ATTRS = { ticker: null // will be set in dygraph-tickers.js }, y: { + axisLabelWidth: 50, pixelsPerLabel: 30, valueFormatter: Dygraph.numberValueFormatter, axisLabelFormatter: Dygraph.numberAxisLabelFormatter, @@ -381,10 +372,11 @@ Dygraph.DEFAULT_ATTRS = { ticker: null // will be set in dygraph-tickers.js }, y2: { + axisLabelWidth: 50, pixelsPerLabel: 30, valueFormatter: Dygraph.numberValueFormatter, axisLabelFormatter: Dygraph.numberAxisLabelFormatter, - drawAxis: false, + drawAxis: true, // only applies when there are two axes of data. drawGrid: false, independentTicks: false, ticker: null // will be set in dygraph-tickers.js @@ -427,21 +419,10 @@ Dygraph.prototype.__old_init__ = function(div, file, labels, attrs) { * @private */ Dygraph.prototype.__init__ = function(div, file, attrs) { - // Hack for IE: if we're using excanvas and the document hasn't finished - // loading yet (and hence may not have initialized whatever it needs to - // initialize), then keep calling this routine periodically until it has. - if (/MSIE/.test(navigator.userAgent) && !window.opera && - typeof(G_vmlCanvasManager) != 'undefined' && - document.readyState != 'complete') { - var self = this; - setTimeout(function() { self.__init__(div, file, attrs); }, 100); - return; - } - // Support two-argument constructor if (attrs === null || attrs === undefined) { attrs = {}; } - attrs = Dygraph.mapLegacyOptions_(attrs); + attrs = Dygraph.copyUserAttrs_(attrs); if (typeof(div) == 'string') { div = document.getElementById(div); @@ -452,8 +433,6 @@ Dygraph.prototype.__init__ = function(div, file, attrs) { return; } - this.isUsingExcanvas_ = typeof(G_vmlCanvasManager) != 'undefined'; - // Copy the important bits into the object // TODO(danvk): most of these should just stay in the attrs_ dictionary. this.maindiv_ = div; @@ -536,8 +515,16 @@ Dygraph.prototype.__init__ = function(div, file, attrs) { this.plugins_ = []; var plugins = Dygraph.PLUGINS.concat(this.getOption('plugins')); for (var i = 0; i < plugins.length; i++) { - var Plugin = plugins[i]; - var pluginInstance = new Plugin(); + // the plugins option may contain either plugin classes or instances. + // Plugin instances contain an activate method. + var Plugin = plugins[i]; // either a constructor or an instance. + var pluginInstance; + if (typeof(Plugin.activate) !== 'undefined') { + pluginInstance = Plugin; + } else { + pluginInstance = new Plugin(); + } + var pluginDict = { plugin: pluginInstance, events: {}, @@ -547,6 +534,7 @@ Dygraph.prototype.__init__ = function(div, file, attrs) { var handlers = pluginInstance.activate(this); for (var eventName in handlers) { + if (!handlers.hasOwnProperty(eventName)) continue; // TODO(danvk): validate eventName. pluginDict.events[eventName] = handlers[eventName]; } @@ -578,12 +566,12 @@ Dygraph.prototype.__init__ = function(div, file, attrs) { /** * Triggers a cascade of events to the various plugins which are interested in them. - * Returns true if the "default behavior" should be performed, i.e. if none of - * the event listeners called event.preventDefault(). + * Returns true if the "default behavior" should be prevented, i.e. if one + * of the event listeners called event.preventDefault(). * @private */ Dygraph.prototype.cascadeEvents_ = function(name, extra_props) { - if (!(name in this.eventListeners_)) return true; + if (!(name in this.eventListeners_)) return false; // QUESTION: can we use objects & prototypes to speed this up? var e = { @@ -1071,7 +1059,7 @@ Dygraph.prototype.toPercentXCoord = function(x) { var xRange = this.xAxisRange(); var pct; var logscale = this.attributes_.getForAxis("logscale", 'x') ; - if (logscale == true) { // logscale can be null so we test for true explicitly. + if (logscale === true) { // logscale can be null so we test for true explicitly. var logr0 = Dygraph.log10(xRange[0]); var logr1 = Dygraph.log10(xRange[1]); pct = (Dygraph.log10(x) - logr0) / (logr1 - logr0); @@ -1222,6 +1210,12 @@ Dygraph.prototype.destroy = function() { this.canvas_ctx_.restore(); this.hidden_ctx_.restore(); + // Destroy any plugins, in the reverse order that they were registered. + for (var i = this.plugins_.length - 1; i >= 0; i--) { + var p = this.plugins_.pop(); + if (p.plugin.destroy) p.plugin.destroy(); + } + var removeRecursive = function(node) { while (node.hasChildNodes()) { removeRecursive(node.firstChild); @@ -1236,7 +1230,7 @@ Dygraph.prototype.destroy = function() { Dygraph.removeEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler_); // remove window handlers - Dygraph.removeEvent(window,'resize',this.resizeHandler_); + Dygraph.removeEvent(window,'resize', this.resizeHandler_); this.resizeHandler_ = null; removeRecursive(this.maindiv_); @@ -1283,18 +1277,7 @@ Dygraph.prototype.createPlotKitCanvas_ = function(canvas) { * @private */ Dygraph.prototype.createMouseEventElement_ = function() { - if (this.isUsingExcanvas_) { - var elem = document.createElement("div"); - elem.style.position = 'absolute'; - elem.style.backgroundColor = 'white'; - elem.style.filter = 'alpha(opacity=0)'; - elem.style.width = this.width_ + "px"; - elem.style.height = this.height_ + "px"; - this.graphDiv.appendChild(elem); - return elem; - } else { - return this.canvas_; - } + return this.canvas_; }; /** @@ -1475,6 +1458,26 @@ Dygraph.prototype.createDragInterface_ = function() { contextB.dragStartY = Dygraph.dragGetY_(event, contextB); contextB.cancelNextDblclick = false; contextB.tarp.cover(); + }, + destroy: function() { + var context = this; + if (context.isZooming || context.isPanning) { + context.isZooming = false; + context.dragStartX = null; + context.dragStartY = null; + } + + if (context.isPanning) { + context.isPanning = false; + context.draggingDate = null; + context.dateRange = null; + for (var i = 0; i < self.axes_.length; i++) { + delete self.axes_[i].draggingValue; + delete self.axes_[i].dragValueRange; + } + } + + context.tarp.uncover(); } }; @@ -1498,27 +1501,13 @@ Dygraph.prototype.createDragInterface_ = function() { // If the user releases the mouse button during a drag, but not over the // canvas, then it doesn't count as a zooming action. - var mouseUpHandler = function(event) { - if (context.isZooming || context.isPanning) { - context.isZooming = false; - context.dragStartX = null; - context.dragStartY = null; - } - - if (context.isPanning) { - context.isPanning = false; - context.draggingDate = null; - context.dateRange = null; - for (var i = 0; i < self.axes_.length; i++) { - delete self.axes_[i].draggingValue; - delete self.axes_[i].dragValueRange; - } - } - - context.tarp.uncover(); - }; + if (!interactionModel.willDestroyContextMyself) { + var mouseUpHandler = function(event) { + context.destroy(); + }; - this.addAndTrackEvent(document, 'mouseup', mouseUpHandler); + this.addAndTrackEvent(document, 'mouseup', mouseUpHandler); + } }; /** @@ -1571,10 +1560,6 @@ Dygraph.prototype.drawZoomRect_ = function(direction, startX, endX, startY, this.layout_.getPlotArea().w, Math.abs(endY - startY)); } } - - if (this.isUsingExcanvas_) { - this.currentZoomRectArgs_ = [direction, startX, endX, startY, endY, 0, 0, 0]; - } }; /** @@ -1583,7 +1568,7 @@ Dygraph.prototype.drawZoomRect_ = function(direction, startX, endX, startY, */ Dygraph.prototype.clearZoomRect_ = function() { this.currentZoomRectArgs_ = null; - this.canvas_ctx_.clearRect(0, 0, this.canvas_.width, this.canvas_.height); + this.canvas_ctx_.clearRect(0, 0, this.width_, this.height_); }; /** @@ -1624,7 +1609,7 @@ Dygraph.prototype.doZoomXDates_ = function(minDate, maxDate) { var that = this; this.doAnimatedZoom(old_window, new_window, null, null, function() { if (that.getFunctionOption("zoomCallback")) { - that.getFunctionOption("zoomCallback")( + that.getFunctionOption("zoomCallback").call(that, minDate, maxDate, that.yAxisRanges()); } }); @@ -1657,7 +1642,7 @@ Dygraph.prototype.doZoomY_ = function(lowY, highY) { this.doAnimatedZoom(null, null, oldValueRanges, newValueRanges, function() { if (that.getFunctionOption("zoomCallback")) { var xRange = that.xAxisRange(); - that.getFunctionOption("zoomCallback")( + that.getFunctionOption("zoomCallback").call(that, xRange[0], xRange[1], that.yAxisRanges()); } }); @@ -1712,7 +1697,7 @@ Dygraph.prototype.resetZoom = function() { } this.drawGraph_(); if (this.getFunctionOption("zoomCallback")) { - this.getFunctionOption("zoomCallback")( + this.getFunctionOption("zoomCallback").call(this, minDate, maxDate, this.yAxisRanges()); } return; @@ -1755,7 +1740,7 @@ Dygraph.prototype.resetZoom = function() { } } if (that.getFunctionOption("zoomCallback")) { - that.getFunctionOption("zoomCallback")( + that.getFunctionOption("zoomCallback").call(that, minDate, maxDate, that.yAxisRanges()); } }); @@ -1992,7 +1977,7 @@ Dygraph.prototype.mouseMove_ = function(event) { var callback = this.getFunctionOption("highlightCallback"); if (callback && selectionChanged) { - callback(event, + callback.call(this, event, this.lastx_, this.selPoints_, this.lastRow_, @@ -2101,10 +2086,6 @@ Dygraph.prototype.updateSelection_ = function(opt_animFraction) { 2 * maxCircleSize + 2, this.height_); } - if (this.isUsingExcanvas_ && this.currentZoomRectArgs_) { - Dygraph.prototype.drawZoomRect_.apply(this, this.currentZoomRectArgs_); - } - if (this.selPoints_.length > 0) { // Draw colored circles over the center of each selected point var canvasx = this.selPoints_[0].canvasx; @@ -2122,7 +2103,7 @@ Dygraph.prototype.updateSelection_ = function(opt_animFraction) { ctx.lineWidth = this.getNumericOption('strokeWidth', pt.name); ctx.strokeStyle = color; ctx.fillStyle = color; - callback(this, pt.name, ctx, canvasx, pt.canvasy, + callback.call(this, this, pt.name, ctx, canvasx, pt.canvasy, color, circleSize, pt.idx); } ctx.restore(); @@ -2136,7 +2117,7 @@ Dygraph.prototype.updateSelection_ = function(opt_animFraction) { * legend. The selection can be cleared using clearSelection() and queried * using getSelection(). * @param {number} row Row number that should be highlighted (i.e. appear with - * hover dots on the chart). Set to false to clear any selection. + * hover dots on the chart). * @param {seriesName} optional series name to highlight that series with the * the highlightSeriesOpts setting. * @param { locked } optional If true, keep seriesName selected when mousing @@ -2205,10 +2186,10 @@ Dygraph.prototype.setSelection = function(row, opt_seriesName, opt_locked) { */ Dygraph.prototype.mouseOut_ = function(event) { if (this.getFunctionOption("unhighlightCallback")) { - this.getFunctionOption("unhighlightCallback")(event); + this.getFunctionOption("unhighlightCallback").call(this, event); } - if (this.getFunctionOption("hideOverlayOnMouseOut") && !this.lockedSet_) { + if (this.getBooleanOption("hideOverlayOnMouseOut") && !this.lockedSet_) { this.clearSelection(); } }; @@ -2278,6 +2259,7 @@ Dygraph.prototype.isSeriesLocked = function() { */ Dygraph.prototype.loadedEvent_ = function(data) { this.rawData_ = this.parseCSV_(data); + this.cascadeDataDidUpdateEvent_(); this.predraw_(); }; @@ -2298,7 +2280,7 @@ Dygraph.prototype.addXTicks_ = function() { var xTicks = xAxisOptionsView('ticker')( range[0], range[1], - this.width_, // TODO(danvk): should be area.width + this.plotter_.area.w, // TODO(danvk): should be area.width xAxisOptionsView, this); // var msg = 'ticker(' + range[0] + ', ' + range[1] + ', ' + this.width_ + ', ' + this.attr_('pixelsPerXLabel') + ') -> ' + JSON.stringify(xTicks); @@ -2349,12 +2331,6 @@ Dygraph.prototype.predraw_ = function() { // TODO(danvk): move more computations out of drawGraph_ and into here. this.computeYAxes_(); - // Create a new plotter. - if (this.plotter_) { - this.cascadeEvents_('clearChart'); - this.plotter_.clear(); - } - if (!this.is_initial_draw_) { this.canvas_ctx_.restore(); this.hidden_ctx_.restore(); @@ -2363,6 +2339,7 @@ Dygraph.prototype.predraw_ = function() { this.canvas_ctx_.save(); this.hidden_ctx_.save(); + // Create a new plotter. this.plotter_ = new DygraphCanvasRenderer(this, this.hidden_, this.hidden_ctx_, @@ -2694,7 +2671,7 @@ Dygraph.prototype.renderGraph_ = function(is_initial_draw) { if (this.getFunctionOption('underlayCallback')) { // NOTE: we pass the dygraph object to this callback twice to avoid breaking // users who expect a deprecated form of this callback. - this.getFunctionOption('underlayCallback')( + this.getFunctionOption('underlayCallback').call(this, this.hidden_ctx_, this.layout_.getPlotArea(), this, this); } @@ -2709,8 +2686,7 @@ Dygraph.prototype.renderGraph_ = function(is_initial_draw) { // TODO(danvk): is this a performance bottleneck when panning? // The interaction canvas should already be empty in that situation. - this.canvas_.getContext('2d').clearRect(0, 0, this.canvas_.width, - this.canvas_.height); + this.canvas_.getContext('2d').clearRect(0, 0, this.width_, this.height_); if (this.getFunctionOption("drawCallback") !== null) { this.getFunctionOption("drawCallback")(this, is_initial_draw); @@ -2958,7 +2934,7 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) { var ticker = opts('ticker'); axis.ticks = ticker(axis.computedValueRange[0], axis.computedValueRange[1], - this.height_, // TODO(danvk): should be area.height + this.plotter_.area.h, opts, this); // Define the first independent axis as primary axis. @@ -2989,7 +2965,7 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) { axis.ticks = ticker(axis.computedValueRange[0], axis.computedValueRange[1], - this.height_, // TODO(danvk): should be area.height + this.plotter_.area.h, opts, this, tick_values); @@ -3393,6 +3369,17 @@ Dygraph.prototype.parseDataTable_ = function(data) { }; /** + * Signals to plugins that the chart data has updated. + * This happens after the data has updated but before the chart has redrawn. + */ +Dygraph.prototype.cascadeDataDidUpdateEvent_ = function() { + // TODO(danvk): there are some issues checking xAxisRange() and using + // toDomCoords from handlers of this event. The visible range should be set + // when the chart is drawn, not derived from the data. + this.cascadeEvents_('dataDidUpdate', {}); +}; + +/** * Get the CSV data. If it's in a function, call that function. If it's in a * file, do an XMLHttpRequest to get it. * @private @@ -3407,11 +3394,13 @@ Dygraph.prototype.start_ = function() { if (Dygraph.isArrayLike(data)) { this.rawData_ = this.parseArray_(data); + this.cascadeDataDidUpdateEvent_(); this.predraw_(); } else if (typeof data == 'object' && typeof data.getColumnRange == 'function') { // must be a DataTable from gviz. this.parseDataTable_(data); + this.cascadeDataDidUpdateEvent_(); this.predraw_(); } else if (typeof data == 'string') { // Heuristic: a newline means it's CSV data. Otherwise it's an URL. @@ -3468,9 +3457,9 @@ Dygraph.prototype.start_ = function() { Dygraph.prototype.updateOptions = function(input_attrs, block_redraw) { if (typeof(block_redraw) == 'undefined') block_redraw = false; - // mapLegacyOptions_ drops the "file" parameter as a convenience to us. + // copyUserAttrs_ drops the "file" parameter as a convenience to us. var file = input_attrs.file; - var attrs = Dygraph.mapLegacyOptions_(input_attrs); + var attrs = Dygraph.copyUserAttrs_(input_attrs); // TODO(danvk): this is a mess. Move these options into attr_. if ('rollPeriod' in attrs) { @@ -3501,6 +3490,10 @@ Dygraph.prototype.updateOptions = function(input_attrs, block_redraw) { this.attributes_.reparseSeries(); if (file) { + // This event indicates that the data is about to change, but hasn't yet. + // TODO(danvk): support cancelation of the update via this event. + this.cascadeEvents_('dataWillUpdate', {}); + this.file_ = file; if (!block_redraw) this.start_(); } else { @@ -3515,47 +3508,15 @@ Dygraph.prototype.updateOptions = function(input_attrs, block_redraw) { }; /** - * Returns a copy of the options with deprecated names converted into current - * names. Also drops the (potentially-large) 'file' attribute. If the caller is - * interested in that, they should save a copy before calling this. - * @private + * Make a copy of input attributes, removing file as a convenience. */ -Dygraph.mapLegacyOptions_ = function(attrs) { +Dygraph.copyUserAttrs_ = function(attrs) { var my_attrs = {}; for (var k in attrs) { + if (!attrs.hasOwnProperty(k)) continue; if (k == 'file') continue; if (attrs.hasOwnProperty(k)) my_attrs[k] = attrs[k]; } - - var set = function(axis, opt, value) { - if (!my_attrs.axes) my_attrs.axes = {}; - if (!my_attrs.axes[axis]) my_attrs.axes[axis] = {}; - my_attrs.axes[axis][opt] = value; - }; - var map = function(opt, axis, new_opt) { - if (typeof(attrs[opt]) != 'undefined') { - console.warn("Option " + opt + " is deprecated. Use the " + - new_opt + " option for the " + axis + " axis instead. " + - "(e.g. { axes : { " + axis + " : { " + new_opt + " : ... } } } " + - "(see http://dygraphs.com/per-axis.html for more information."); - set(axis, new_opt, attrs[opt]); - delete my_attrs[opt]; - } - }; - - // This maps, e.g., xValueFormater -> axes: { x: { valueFormatter: ... } } - map('xValueFormatter', 'x', 'valueFormatter'); - map('pixelsPerXLabel', 'x', 'pixelsPerLabel'); - map('xAxisLabelFormatter', 'x', 'axisLabelFormatter'); - map('xTicker', 'x', 'ticker'); - map('yValueFormatter', 'y', 'valueFormatter'); - map('pixelsPerYLabel', 'y', 'pixelsPerLabel'); - map('yAxisLabelFormatter', 'y', 'axisLabelFormatter'); - map('yTicker', 'y', 'ticker'); - map('drawXGrid', 'x', 'drawGrid'); - map('drawXAxis', 'x', 'drawAxis'); - map('drawYGrid', 'y', 'drawGrid'); - map('drawYAxis', 'y', 'drawAxis'); return my_attrs; }; @@ -3722,7 +3683,7 @@ Dygraph.prototype.ready = function(callback) { if (this.is_initial_draw_) { this.readyFns_.push(callback); } else { - callback(this); + callback.call(this, this); } }; @@ -3767,3 +3728,7 @@ Dygraph.addAnnotationRule = function() { console.warn("Unable to add default annotation CSS rule; display may be off."); }; + +return Dygraph; + +})();