// For "production" code, this gets set to false by uglifyjs.
if (typeof(DEBUG) === 'undefined') DEBUG=true;
+var Dygraph = (function() {
/*jshint globalstrict: true */
/*global DygraphLayout:false, DygraphCanvasRenderer:false, DygraphOptions:false, G_vmlCanvasManager:false,ActiveXObject:false */
"use strict";
axisLineWidth: 0.3,
gridLineWidth: 0.3,
axisLabelColor: "black",
- axisLabelFont: "Arial", // TODO(danvk): is this implemented?
axisLabelWidth: 50,
drawYGrid: true,
drawXGrid: true,
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: {},
/**
* 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 = {
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);
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_);
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();
}
};
// 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);
+ }
};
/**
*/
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_);
};
/**
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());
}
});
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());
}
});
}
this.drawGraph_();
if (this.getFunctionOption("zoomCallback")) {
- this.getFunctionOption("zoomCallback")(
+ this.getFunctionOption("zoomCallback").call(this,
minDate, maxDate, this.yAxisRanges());
}
return;
}
}
if (that.getFunctionOption("zoomCallback")) {
- that.getFunctionOption("zoomCallback")(
+ that.getFunctionOption("zoomCallback").call(that,
minDate, maxDate, that.yAxisRanges());
}
});
var callback = this.getFunctionOption("highlightCallback");
if (callback && selectionChanged) {
- callback(event,
+ callback.call(this, event,
this.lastx_,
this.selPoints_,
this.lastRow_,
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();
*/
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();
}
};
*/
Dygraph.prototype.loadedEvent_ = function(data) {
this.rawData_ = this.parseCSV_(data);
+ this.cascadeDataDidUpdateEvent_();
this.predraw_();
};
// 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();
this.canvas_ctx_.save();
this.hidden_ctx_.save();
+ // Create a new plotter.
this.plotter_ = new DygraphCanvasRenderer(this,
this.hidden_,
this.hidden_ctx_,
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);
}
// 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);
};
/**
+ * 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
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.
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 {
if (this.is_initial_draw_) {
this.readyFns_.push(callback);
} else {
- callback(this);
+ callback.call(this, this);
}
};
console.warn("Unable to add default annotation CSS rule; display may be off.");
};
+
+return Dygraph;
+
+})();