// 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";
};
Dygraph.NAME = "Dygraph";
-Dygraph.VERSION = "1.0.1";
+Dygraph.VERSION = "1.1.0";
Dygraph.__repr__ = function() {
return "[" + Dygraph.NAME + " " + Dygraph.VERSION + "]";
};
* 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) {
* 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);
};
/**
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);
}
axisTickSize: 3,
axisLabelFontSize: 14,
- xAxisLabelWidth: 50,
- yAxisLabelWidth: 50,
rightGap: 5,
showRoller: false,
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,
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
// per-axis options
axes: {
x: {
- pixelsPerLabel: 60,
+ pixelsPerLabel: 70,
+ axisLabelWidth: 60,
axisLabelFormatter: Dygraph.dateAxisLabelFormatter,
valueFormatter: Dygraph.dateValueFormatter,
drawGrid: true,
ticker: null // will be set in dygraph-tickers.js
},
y: {
+ axisLabelWidth: 50,
pixelsPerLabel: 30,
valueFormatter: Dygraph.numberValueFormatter,
axisLabelFormatter: Dygraph.numberAxisLabelFormatter,
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
* @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);
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;
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: {},
var handlers = pluginInstance.activate(this);
for (var eventName in handlers) {
+ if (!handlers.hasOwnProperty(eventName)) continue;
// TODO(danvk): validate eventName.
pluginDict.events[eventName] = handlers[eventName];
}
/**
* 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 = {
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);
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_);
* @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_;
};
/**
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);
+ }
};
/**
this.layout_.getPlotArea().w, Math.abs(endY - startY));
}
}
-
- if (this.isUsingExcanvas_) {
- this.currentZoomRectArgs_ = [direction, startX, endX, startY, endY, 0, 0, 0];
- }
};
/**
*/
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_);
};
/**
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;
* 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
*/
Dygraph.prototype.loadedEvent_ = function(data) {
this.rawData_ = this.parseCSV_(data);
+ this.cascadeDataDidUpdateEvent_();
this.predraw_();
};
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);
// 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_,
// 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);
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.
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);
};
/**
+ * 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.
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) {
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 {
};
/**
- * 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;
};
console.warn("Unable to add default annotation CSS rule; display may be off.");
};
+
+return Dygraph;
+
+})();