dygraphs
Version:
dygraphs is a fast, flexible open source JavaScript charting library.
1,303 lines (1,219 loc) • 424 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _dygraphLayout = _interopRequireDefault(require("./dygraph-layout"));
var _dygraphCanvas = _interopRequireDefault(require("./dygraph-canvas"));
var _dygraphOptions = _interopRequireDefault(require("./dygraph-options"));
var _dygraphInteractionModel = _interopRequireDefault(require("./dygraph-interaction-model"));
var DygraphTickers = _interopRequireWildcard(require("./dygraph-tickers"));
var utils = _interopRequireWildcard(require("./dygraph-utils"));
var _dygraphDefaultAttrs = _interopRequireDefault(require("./dygraph-default-attrs"));
var _dygraphOptionsReference = _interopRequireDefault(require("./dygraph-options-reference"));
var _iframeTarp = _interopRequireDefault(require("./iframe-tarp"));
var _default2 = _interopRequireDefault(require("./datahandler/default"));
var _barsError = _interopRequireDefault(require("./datahandler/bars-error"));
var _barsCustom = _interopRequireDefault(require("./datahandler/bars-custom"));
var _defaultFractions = _interopRequireDefault(require("./datahandler/default-fractions"));
var _barsFractions = _interopRequireDefault(require("./datahandler/bars-fractions"));
var _bars = _interopRequireDefault(require("./datahandler/bars"));
var _annotations = _interopRequireDefault(require("./plugins/annotations"));
var _axes = _interopRequireDefault(require("./plugins/axes"));
var _chartLabels = _interopRequireDefault(require("./plugins/chart-labels"));
var _grid = _interopRequireDefault(require("./plugins/grid"));
var _legend = _interopRequireDefault(require("./plugins/legend"));
var _rangeSelector = _interopRequireDefault(require("./plugins/range-selector"));
var _dygraphGviz = _interopRequireDefault(require("./dygraph-gviz"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function _iterableToArrayLimit(arr, i) { var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"]; if (null != _i) { var _s, _e, _x, _r, _arr = [], _n = !0, _d = !1; try { if (_x = (_i = _i.call(arr)).next, 0 === i) { if (Object(_i) !== _i) return; _n = !1; } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0); } catch (err) { _d = !0, _e = err; } finally { try { if (!_n && null != _i["return"] && (_r = _i["return"](), Object(_r) !== _r)) return; } finally { if (_d) throw _e; } } return _arr; } }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
"use strict";
/**
* @class Creates an interactive, zoomable chart.
* @name Dygraph
*
* @constructor
* @param {div | String} div A div or the id of a div into which to construct
* the chart. Must not have any padding.
* @param {String | Function} file A file containing CSV data or a function
* that returns this data. The most basic expected format for each line is
* "YYYY/MM/DD,val1,val2,...". For more information, see
* http://dygraphs.com/data.html.
* @param {Object} attrs Various other attributes, e.g. errorBars determines
* whether the input data contains error ranges. For a complete list of
* options, see http://dygraphs.com/options.html.
*/
var Dygraph = function Dygraph(div, data, opts) {
this.__init__(div, data, opts);
};
Dygraph.NAME = "Dygraph";
Dygraph.VERSION = "2.2.1";
// internal autoloader workaround
var _addrequire = {};
Dygraph._require = function require(what) {
return what in _addrequire ? _addrequire[what] : Dygraph._require._b(what);
};
Dygraph._require._b = null; // set by xfrmmodmap-dy.js
Dygraph._require.add = function add(what, towhat) {
_addrequire[what] = towhat;
};
// Various default values
Dygraph.DEFAULT_ROLL_PERIOD = 1;
Dygraph.DEFAULT_WIDTH = 480;
Dygraph.DEFAULT_HEIGHT = 320;
// For max 60 Hz. animation:
Dygraph.ANIMATION_STEPS = 12;
Dygraph.ANIMATION_DURATION = 200;
/**
* Standard plotters. These may be used by clients.
* Available plotters are:
* - Dygraph.Plotters.linePlotter: draws central lines (most common)
* - Dygraph.Plotters.errorPlotter: draws high/low bands
* - Dygraph.Plotters.fillPlotter: draws fills under lines (used with fillGraph)
*
* By default, the plotter is [fillPlotter, errorPlotter, linePlotter].
* This causes all the lines to be drawn over all the fills/bands.
*/
Dygraph.Plotters = _dygraphCanvas["default"]._Plotters;
// Used for initializing annotation CSS rules only once.
Dygraph.addedAnnotationCSS = false;
/**
* Initializes the Dygraph. This creates a new DIV and constructs the PlotKit
* and context <canvas> inside of it. See the constructor for details.
* on the parameters.
* @param {Element} div the Element to render the graph into.
* @param {string | Function} file Source data
* @param {Object} attrs Miscellaneous other options
* @private
*/
Dygraph.prototype.__init__ = function (div, file, attrs) {
this.is_initial_draw_ = true;
this.readyFns_ = [];
// Support two-argument constructor
if (attrs === null || attrs === undefined) {
attrs = {};
}
attrs = Dygraph.copyUserAttrs_(attrs);
if (typeof div == 'string') {
div = document.getElementById(div);
}
if (!div) {
throw new Error('Constructing dygraph with a non-existent div!');
}
// Copy the important bits into the object
// TODO(danvk): most of these should just stay in the attrs_ dictionary.
this.maindiv_ = div;
this.file_ = file;
this.rollPeriod_ = attrs.rollPeriod || Dygraph.DEFAULT_ROLL_PERIOD;
this.previousVerticalX_ = -1;
this.fractions_ = attrs.fractions || false;
this.dateWindow_ = attrs.dateWindow || null;
this.annotations_ = [];
// Clear the div. This ensure that, if multiple dygraphs are passed the same
// div, then only one will be drawn.
div.innerHTML = "";
var resolved = window.getComputedStyle(div, null);
if (resolved.paddingLeft !== "0px" || resolved.paddingRight !== "0px" || resolved.paddingTop !== "0px" || resolved.paddingBottom !== "0px") console.error('Main div contains padding; graph will misbehave');
// For historical reasons, the 'width' and 'height' options trump all CSS
// rules _except_ for an explicit 'width' or 'height' on the div.
// As an added convenience, if the div has zero height (like <div></div> does
// without any styles), then we use a default height/width.
if (div.style.width === '' && attrs.width) {
div.style.width = attrs.width + "px";
}
if (div.style.height === '' && attrs.height) {
div.style.height = attrs.height + "px";
}
if (div.style.height === '' && div.clientHeight === 0) {
div.style.height = Dygraph.DEFAULT_HEIGHT + "px";
if (div.style.width === '') {
div.style.width = Dygraph.DEFAULT_WIDTH + "px";
}
}
// These will be zero if the dygraph's div is hidden. In that case,
// use the user-specified attributes if present. If not, use zero
// and assume the user will call resize to fix things later.
this.width_ = div.clientWidth || attrs.width || 0;
this.height_ = div.clientHeight || attrs.height || 0;
// TODO(danvk): set fillGraph to be part of attrs_ here, not user_attrs_.
if (attrs.stackedGraph) {
attrs.fillGraph = true;
// TODO(nikhilk): Add any other stackedGraph checks here.
}
// DEPRECATION WARNING: All option processing should be moved from
// attrs_ and user_attrs_ to options_, which holds all this information.
//
// Dygraphs has many options, some of which interact with one another.
// To keep track of everything, we maintain two sets of options:
//
// this.user_attrs_ only options explicitly set by the user.
// this.attrs_ defaults, options derived from user_attrs_, data.
//
// Options are then accessed this.attr_('attr'), which first looks at
// user_attrs_ and then computed attrs_. This way Dygraphs can set intelligent
// defaults without overriding behavior that the user specifically asks for.
this.user_attrs_ = {};
utils.update(this.user_attrs_, attrs);
// This sequence ensures that Dygraph.DEFAULT_ATTRS is never modified.
this.attrs_ = {};
utils.updateDeep(this.attrs_, _dygraphDefaultAttrs["default"]);
this.boundaryIds_ = [];
this.setIndexByName_ = {};
this.datasetIndex_ = [];
this.registeredEvents_ = [];
this.eventListeners_ = {};
this.attributes_ = new _dygraphOptions["default"](this);
// Create the containing DIV and other interactive elements
this.createInterface_();
// Activate plugins.
this.plugins_ = [];
var plugins = Dygraph.PLUGINS.concat(this.getOption('plugins'));
for (var i = 0; i < plugins.length; i++) {
// 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: {},
options: {},
pluginOptions: {}
};
var handlers = pluginInstance.activate(this);
for (var eventName in handlers) {
if (!handlers.hasOwnProperty(eventName)) continue;
// TODO(danvk): validate eventName.
pluginDict.events[eventName] = handlers[eventName];
}
this.plugins_.push(pluginDict);
}
// At this point, plugins can no longer register event handlers.
// Construct a map from event -> ordered list of [callback, plugin].
for (var i = 0; i < this.plugins_.length; i++) {
var plugin_dict = this.plugins_[i];
for (var eventName in plugin_dict.events) {
if (!plugin_dict.events.hasOwnProperty(eventName)) continue;
var callback = plugin_dict.events[eventName];
var pair = [plugin_dict.plugin, callback];
if (!(eventName in this.eventListeners_)) {
this.eventListeners_[eventName] = [pair];
} else {
this.eventListeners_[eventName].push(pair);
}
}
}
this.createDragInterface_();
this.start_();
};
/**
* Triggers a cascade of events to the various plugins which are interested in them.
* 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 false;
// QUESTION: can we use objects & prototypes to speed this up?
var e = {
dygraph: this,
cancelable: false,
defaultPrevented: false,
preventDefault: function preventDefault() {
if (!e.cancelable) throw "Cannot call preventDefault on non-cancelable event.";
e.defaultPrevented = true;
},
propagationStopped: false,
stopPropagation: function stopPropagation() {
e.propagationStopped = true;
}
};
utils.update(e, extra_props);
var callback_plugin_pairs = this.eventListeners_[name];
if (callback_plugin_pairs) {
for (var i = callback_plugin_pairs.length - 1; i >= 0; i--) {
var plugin = callback_plugin_pairs[i][0];
var callback = callback_plugin_pairs[i][1];
callback.call(plugin, e);
if (e.propagationStopped) break;
}
}
return e.defaultPrevented;
};
/**
* Fetch a plugin instance of a particular class. Only for testing.
* @private
* @param {!Class} type The type of the plugin.
* @return {Object} Instance of the plugin, or null if there is none.
*/
Dygraph.prototype.getPluginInstance_ = function (type) {
for (var i = 0; i < this.plugins_.length; i++) {
var p = this.plugins_[i];
if (p.plugin instanceof type) {
return p.plugin;
}
}
return null;
};
/**
* Returns the zoomed status of the chart for one or both axes.
*
* Axis is an optional parameter. Can be set to 'x' or 'y'.
*
* The zoomed status for an axis is set whenever a user zooms using the mouse
* or when the dateWindow or valueRange are updated. Double-clicking or calling
* resetZoom() resets the zoom status for the chart.
*/
Dygraph.prototype.isZoomed = function (axis) {
var isZoomedX = !!this.dateWindow_;
if (axis === 'x') return isZoomedX;
var isZoomedY = this.axes_.map(function (axis) {
return !!axis.valueRange;
}).indexOf(true) >= 0;
if (axis === null || axis === undefined) {
return isZoomedX || isZoomedY;
}
if (axis === 'y') return isZoomedY;
throw new Error("axis parameter is [".concat(axis, "] must be null, 'x' or 'y'."));
};
/**
* Returns information about the Dygraph object, including its containing ID.
*/
Dygraph.prototype.toString = function () {
var maindiv = this.maindiv_;
var id = maindiv && maindiv.id ? maindiv.id : maindiv;
return "[Dygraph " + id + "]";
};
/**
* @private
* Returns the value of an option. This may be set by the user (either in the
* constructor or by calling updateOptions) or by dygraphs, and may be set to a
* per-series value.
* @param {string} name The name of the option, e.g. 'rollPeriod'.
* @param {string} [seriesName] The name of the series to which the option
* will be applied. If no per-series value of this option is available, then
* the global value is returned. This is optional.
* @return {...} The value of the option.
*/
Dygraph.prototype.attr_ = function (name, seriesName) {
if (typeof process !== 'undefined' && process.env.NODE_ENV != 'production') {
// For "production" code, this gets removed by uglifyjs.
if (typeof _dygraphOptionsReference["default"] === 'undefined') {
console.error('Must include options reference JS for testing');
} else if (!_dygraphOptionsReference["default"].hasOwnProperty(name)) {
console.error('Dygraphs is using property ' + name + ', which has no ' + 'entry in the Dygraphs.OPTIONS_REFERENCE listing.');
// Only log this error once.
_dygraphOptionsReference["default"][name] = true;
}
}
return seriesName ? this.attributes_.getForSeries(name, seriesName) : this.attributes_.get(name);
};
/**
* Returns the current value for an option, as set in the constructor or via
* updateOptions. You may pass in an (optional) series name to get per-series
* values for the option.
*
* All values returned by this method should be considered immutable. If you
* modify them, there is no guarantee that the changes will be honored or that
* dygraphs will remain in a consistent state. If you want to modify an option,
* use updateOptions() instead.
*
* @param {string} name The name of the option (e.g. 'strokeWidth')
* @param {string=} opt_seriesName Series name to get per-series values.
* @return {*} The value of the option.
*/
Dygraph.prototype.getOption = function (name, opt_seriesName) {
return this.attr_(name, opt_seriesName);
};
/**
* Like getOption(), but specifically returns a number.
* This is a convenience function for working with the Closure Compiler.
* @param {string} name The name of the option (e.g. 'strokeWidth')
* @param {string=} opt_seriesName Series name to get per-series values.
* @return {number} The value of the option.
* @private
*/
Dygraph.prototype.getNumericOption = function (name, opt_seriesName) {
return (/** @type{number} */this.getOption(name, opt_seriesName)
);
};
/**
* Like getOption(), but specifically returns a string.
* This is a convenience function for working with the Closure Compiler.
* @param {string} name The name of the option (e.g. 'strokeWidth')
* @param {string=} opt_seriesName Series name to get per-series values.
* @return {string} The value of the option.
* @private
*/
Dygraph.prototype.getStringOption = function (name, opt_seriesName) {
return (/** @type{string} */this.getOption(name, opt_seriesName)
);
};
/**
* Like getOption(), but specifically returns a boolean.
* This is a convenience function for working with the Closure Compiler.
* @param {string} name The name of the option (e.g. 'strokeWidth')
* @param {string=} opt_seriesName Series name to get per-series values.
* @return {boolean} The value of the option.
* @private
*/
Dygraph.prototype.getBooleanOption = function (name, opt_seriesName) {
return (/** @type{boolean} */this.getOption(name, opt_seriesName)
);
};
/**
* Like getOption(), but specifically returns a function.
* This is a convenience function for working with the Closure Compiler.
* @param {string} name The name of the option (e.g. 'strokeWidth')
* @param {string=} opt_seriesName Series name to get per-series values.
* @return {function(...)} The value of the option.
* @private
*/
Dygraph.prototype.getFunctionOption = function (name, opt_seriesName) {
return (/** @type{function(...)} */this.getOption(name, opt_seriesName)
);
};
Dygraph.prototype.getOptionForAxis = function (name, axis) {
return this.attributes_.getForAxis(name, axis);
};
/**
* @private
* @param {string} axis The name of the axis (i.e. 'x', 'y' or 'y2')
* @return {...} A function mapping string -> option value
*/
Dygraph.prototype.optionsViewForAxis_ = function (axis) {
var self = this;
return function (opt) {
var axis_opts = self.user_attrs_.axes;
if (axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)) {
return axis_opts[axis][opt];
}
// I don't like that this is in a second spot.
if (axis === 'x' && opt === 'logscale') {
// return the default value.
// TODO(konigsberg): pull the default from a global default.
return false;
}
// user-specified attributes always trump defaults, even if they're less
// specific.
if (typeof self.user_attrs_[opt] != 'undefined') {
return self.user_attrs_[opt];
}
axis_opts = self.attrs_.axes;
if (axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)) {
return axis_opts[axis][opt];
}
// check old-style axis options
// TODO(danvk): add a deprecation warning if either of these match.
if (axis == 'y' && self.axes_[0].hasOwnProperty(opt)) {
return self.axes_[0][opt];
} else if (axis == 'y2' && self.axes_[1].hasOwnProperty(opt)) {
return self.axes_[1][opt];
}
return self.attr_(opt);
};
};
/**
* Returns the current rolling period, as set by the user or an option.
* @return {number} The number of points in the rolling window
*/
Dygraph.prototype.rollPeriod = function () {
return this.rollPeriod_;
};
/**
* Returns the currently-visible x-range. This can be affected by zooming,
* panning or a call to updateOptions.
* Returns a two-element array: [left, right].
* If the Dygraph has dates on the x-axis, these will be millis since epoch.
*/
Dygraph.prototype.xAxisRange = function () {
return this.dateWindow_ ? this.dateWindow_ : this.xAxisExtremes();
};
/**
* Returns the lower- and upper-bound x-axis values of the data set.
*/
Dygraph.prototype.xAxisExtremes = function () {
var pad = this.getNumericOption('xRangePad') / this.plotter_.area.w;
if (this.numRows() === 0) {
return [0 - pad, 1 + pad];
}
var left = this.rawData_[0][0];
var right = this.rawData_[this.rawData_.length - 1][0];
if (pad) {
// Must keep this in sync with dygraph-layout _evaluateLimits()
var range = right - left;
left -= range * pad;
right += range * pad;
}
return [left, right];
};
/**
* Returns the lower- and upper-bound y-axis values for each axis. These are
* the ranges you'll get if you double-click to zoom out or call resetZoom().
* The return value is an array of [low, high] tuples, one for each y-axis.
*/
Dygraph.prototype.yAxisExtremes = function () {
// TODO(danvk): this is pretty inefficient
var packed = this.gatherDatasets_(this.rolledSeries_, null);
var extremes = packed.extremes;
var saveAxes = this.axes_;
this.computeYAxisRanges_(extremes);
var newAxes = this.axes_;
this.axes_ = saveAxes;
return newAxes.map(function (axis) {
return axis.extremeRange;
});
};
/**
* Returns the currently-visible y-range for an axis. This can be affected by
* zooming, panning or a call to updateOptions. Axis indices are zero-based. If
* called with no arguments, returns the range of the first axis.
* Returns a two-element array: [bottom, top].
*/
Dygraph.prototype.yAxisRange = function (idx) {
if (typeof idx == "undefined") idx = 0;
if (idx < 0 || idx >= this.axes_.length) {
return null;
}
var axis = this.axes_[idx];
return [axis.computedValueRange[0], axis.computedValueRange[1]];
};
/**
* Returns the currently-visible y-ranges for each axis. This can be affected by
* zooming, panning, calls to updateOptions, etc.
* Returns an array of [bottom, top] pairs, one for each y-axis.
*/
Dygraph.prototype.yAxisRanges = function () {
var ret = [];
for (var i = 0; i < this.axes_.length; i++) {
ret.push(this.yAxisRange(i));
}
return ret;
};
// TODO(danvk): use these functions throughout dygraphs.
/**
* Convert from data coordinates to canvas/div X/Y coordinates.
* If specified, do this conversion for the coordinate system of a particular
* axis. Uses the first axis by default.
* Returns a two-element array: [X, Y]
*
* Note: use toDomXCoord instead of toDomCoords(x, null) and use toDomYCoord
* instead of toDomCoords(null, y, axis).
*/
Dygraph.prototype.toDomCoords = function (x, y, axis) {
return [this.toDomXCoord(x), this.toDomYCoord(y, axis)];
};
/**
* Convert from data x coordinates to canvas/div X coordinate.
* If specified, do this conversion for the coordinate system of a particular
* axis.
* Returns a single value or null if x is null.
*/
Dygraph.prototype.toDomXCoord = function (x) {
if (x === null) {
return null;
}
var area = this.plotter_.area;
var xRange = this.xAxisRange();
return area.x + (x - xRange[0]) / (xRange[1] - xRange[0]) * area.w;
};
/**
* Convert from data x coordinates to canvas/div Y coordinate and optional
* axis. Uses the first axis by default.
*
* returns a single value or null if y is null.
*/
Dygraph.prototype.toDomYCoord = function (y, axis) {
var pct = this.toPercentYCoord(y, axis);
if (pct === null) {
return null;
}
var area = this.plotter_.area;
return area.y + pct * area.h;
};
/**
* Convert from canvas/div coords to data coordinates.
* If specified, do this conversion for the coordinate system of a particular
* axis. Uses the first axis by default.
* Returns a two-element array: [X, Y].
*
* Note: use toDataXCoord instead of toDataCoords(x, null) and use toDataYCoord
* instead of toDataCoords(null, y, axis).
*/
Dygraph.prototype.toDataCoords = function (x, y, axis) {
return [this.toDataXCoord(x), this.toDataYCoord(y, axis)];
};
/**
* Convert from canvas/div x coordinate to data coordinate.
*
* If x is null, this returns null.
*/
Dygraph.prototype.toDataXCoord = function (x) {
if (x === null) {
return null;
}
var area = this.plotter_.area;
var xRange = this.xAxisRange();
if (!this.attributes_.getForAxis("logscale", 'x')) {
return xRange[0] + (x - area.x) / area.w * (xRange[1] - xRange[0]);
} else {
var pct = (x - area.x) / area.w;
return utils.logRangeFraction(xRange[0], xRange[1], pct);
}
};
/**
* Convert from canvas/div y coord to value.
*
* If y is null, this returns null.
* if axis is null, this uses the first axis.
*/
Dygraph.prototype.toDataYCoord = function (y, axis) {
if (y === null) {
return null;
}
var area = this.plotter_.area;
var yRange = this.yAxisRange(axis);
if (typeof axis == "undefined") axis = 0;
if (!this.attributes_.getForAxis("logscale", axis)) {
return yRange[0] + (area.y + area.h - y) / area.h * (yRange[1] - yRange[0]);
} else {
// Computing the inverse of toDomCoord.
var pct = (y - area.y) / area.h;
// Note reversed yRange, y1 is on top with pct==0.
return utils.logRangeFraction(yRange[1], yRange[0], pct);
}
};
/**
* Converts a y for an axis to a percentage from the top to the
* bottom of the drawing area.
*
* If the coordinate represents a value visible on the canvas, then
* the value will be between 0 and 1, where 0 is the top of the canvas.
* However, this method will return values outside the range, as
* values can fall outside the canvas.
*
* If y is null, this returns null.
* if axis is null, this uses the first axis.
*
* @param {number} y The data y-coordinate.
* @param {number} [axis] The axis number on which the data coordinate lives.
* @return {number} A fraction in [0, 1] where 0 = the top edge.
*/
Dygraph.prototype.toPercentYCoord = function (y, axis) {
if (y === null) {
return null;
}
if (typeof axis == "undefined") axis = 0;
var yRange = this.yAxisRange(axis);
var pct;
var logscale = this.attributes_.getForAxis("logscale", axis);
if (logscale) {
var logr0 = utils.log10(yRange[0]);
var logr1 = utils.log10(yRange[1]);
pct = (logr1 - utils.log10(y)) / (logr1 - logr0);
} else {
// yRange[1] - y is unit distance from the bottom.
// yRange[1] - yRange[0] is the scale of the range.
// (yRange[1] - y) / (yRange[1] - yRange[0]) is the % from the bottom.
pct = (yRange[1] - y) / (yRange[1] - yRange[0]);
}
return pct;
};
/**
* Converts an x value to a percentage from the left to the right of
* the drawing area.
*
* If the coordinate represents a value visible on the canvas, then
* the value will be between 0 and 1, where 0 is the left of the canvas.
* However, this method will return values outside the range, as
* values can fall outside the canvas.
*
* If x is null, this returns null.
* @param {number} x The data x-coordinate.
* @return {number} A fraction in [0, 1] where 0 = the left edge.
*/
Dygraph.prototype.toPercentXCoord = function (x) {
if (x === null) {
return null;
}
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.
var logr0 = utils.log10(xRange[0]);
var logr1 = utils.log10(xRange[1]);
pct = (utils.log10(x) - logr0) / (logr1 - logr0);
} else {
// x - xRange[0] is unit distance from the left.
// xRange[1] - xRange[0] is the scale of the range.
// The full expression below is the % from the left.
pct = (x - xRange[0]) / (xRange[1] - xRange[0]);
}
return pct;
};
/**
* Returns the number of columns (including the independent variable).
* @return {number} The number of columns.
*/
Dygraph.prototype.numColumns = function () {
if (!this.rawData_) return 0;
return this.rawData_[0] ? this.rawData_[0].length : this.attr_("labels").length;
};
/**
* Returns the number of rows (excluding any header/label row).
* @return {number} The number of rows, less any header.
*/
Dygraph.prototype.numRows = function () {
if (!this.rawData_) return 0;
return this.rawData_.length;
};
/**
* Returns the value in the given row and column. If the row and column exceed
* the bounds on the data, returns null. Also returns null if the value is
* missing.
* @param {number} row The row number of the data (0-based). Row 0 is the
* first row of data, not a header row.
* @param {number} col The column number of the data (0-based)
* @return {number} The value in the specified cell or null if the row/col
* were out of range.
*/
Dygraph.prototype.getValue = function (row, col) {
if (row < 0 || row >= this.rawData_.length) return null;
if (col < 0 || col >= this.rawData_[row].length) return null;
return this.rawData_[row][col];
};
/**
* Generates interface elements for the Dygraph: a containing div, a div to
* display the current point, and a textbox to adjust the rolling average
* period. Also creates the Renderer/Layout elements.
* @private
*/
Dygraph.prototype.createInterface_ = function () {
// Create the all-enclosing graph div
var enclosing = this.maindiv_;
this.graphDiv = document.createElement("div");
// TODO(danvk): any other styles that are useful to set here?
this.graphDiv.style.textAlign = 'left'; // This is a CSS "reset"
this.graphDiv.style.position = 'relative';
enclosing.appendChild(this.graphDiv);
// Create the canvas for interactive parts of the chart.
this.canvas_ = utils.createCanvas();
this.canvas_.style.position = "absolute";
this.canvas_.style.top = 0;
this.canvas_.style.left = 0;
// ... and for static parts of the chart.
this.hidden_ = this.createPlotKitCanvas_(this.canvas_);
this.canvas_ctx_ = utils.getContext(this.canvas_);
this.hidden_ctx_ = utils.getContext(this.hidden_);
this.resizeElements_();
// The interactive parts of the graph are drawn on top of the chart.
this.graphDiv.appendChild(this.hidden_);
this.graphDiv.appendChild(this.canvas_);
this.mouseEventElement_ = this.createMouseEventElement_();
// Create the grapher
this.layout_ = new _dygraphLayout["default"](this);
var dygraph = this;
this.mouseMoveHandler_ = function (e) {
dygraph.mouseMove_(e);
};
this.mouseOutHandler_ = function (e) {
// The mouse has left the chart if:
// 1. e.target is inside the chart
// 2. e.relatedTarget is outside the chart
var target = e.target || e.fromElement;
var relatedTarget = e.relatedTarget || e.toElement;
if (utils.isNodeContainedBy(target, dygraph.graphDiv) && !utils.isNodeContainedBy(relatedTarget, dygraph.graphDiv)) {
dygraph.mouseOut_(e);
}
};
this.addAndTrackEvent(window, 'mouseout', this.mouseOutHandler_);
this.addAndTrackEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler_);
// Don't recreate and register the resize handler on subsequent calls.
// This happens when the graph is resized.
if (!this.resizeHandler_) {
this.resizeHandler_ = function (e) {
dygraph.resize();
};
// Update when the window is resized.
// TODO(danvk): drop frames depending on complexity of the chart.
this.addAndTrackEvent(window, 'resize', this.resizeHandler_);
this.resizeObserver_ = null;
var resizeMode = this.getStringOption('resizable');
if (typeof ResizeObserver === 'undefined' && resizeMode !== "no") {
console.error('ResizeObserver unavailable; ignoring resizable property');
resizeMode = "no";
}
if (resizeMode === "horizontal" || resizeMode === "vertical" || resizeMode === "both") {
enclosing.style.resize = resizeMode;
} else if (resizeMode !== "passive") {
resizeMode = "no";
}
if (resizeMode !== "no") {
var maindivOverflow = window.getComputedStyle(enclosing).overflow;
if (window.getComputedStyle(enclosing).overflow === 'visible') enclosing.style.overflow = 'hidden';
this.resizeObserver_ = new ResizeObserver(this.resizeHandler_);
this.resizeObserver_.observe(enclosing);
}
}
};
Dygraph.prototype.resizeElements_ = function () {
this.graphDiv.style.width = this.width_ + "px";
this.graphDiv.style.height = this.height_ + "px";
var pixelRatioOption = this.getNumericOption('pixelRatio');
var canvasScale = pixelRatioOption || utils.getContextPixelRatio(this.canvas_ctx_);
this.canvas_.width = this.width_ * canvasScale;
this.canvas_.height = this.height_ * canvasScale;
this.canvas_.style.width = this.width_ + "px"; // for IE
this.canvas_.style.height = this.height_ + "px"; // for IE
if (canvasScale !== 1) {
this.canvas_ctx_.scale(canvasScale, canvasScale);
}
var hiddenScale = pixelRatioOption || utils.getContextPixelRatio(this.hidden_ctx_);
this.hidden_.width = this.width_ * hiddenScale;
this.hidden_.height = this.height_ * hiddenScale;
this.hidden_.style.width = this.width_ + "px"; // for IE
this.hidden_.style.height = this.height_ + "px"; // for IE
if (hiddenScale !== 1) {
this.hidden_ctx_.scale(hiddenScale, hiddenScale);
}
};
/**
* Detach DOM elements in the dygraph and null out all data references.
* Calling this when you're done with a dygraph can dramatically reduce memory
* usage. See, e.g., the tests/perf.html example.
*/
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 removeRecursive(node) {
while (node.hasChildNodes()) {
removeRecursive(node.firstChild);
node.removeChild(node.firstChild);
}
};
this.removeTrackedEvents_();
// remove mouse event handlers (This may not be necessary anymore)
utils.removeEvent(window, 'mouseout', this.mouseOutHandler_);
utils.removeEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler_);
// dispose of resizing handlers
if (this.resizeObserver_) {
this.resizeObserver_.disconnect();
this.resizeObserver_ = null;
}
utils.removeEvent(window, 'resize', this.resizeHandler_);
this.resizeHandler_ = null;
removeRecursive(this.maindiv_);
var nullOut = function nullOut(obj) {
for (var n in obj) {
if (typeof obj[n] === 'object') {
obj[n] = null;
}
}
};
// These may not all be necessary, but it can't hurt...
nullOut(this.layout_);
nullOut(this.plotter_);
nullOut(this);
};
/**
* Creates the canvas on which the chart will be drawn. Only the Renderer ever
* draws on this particular canvas. All Dygraph work (i.e. drawing hover dots
* or the zoom rectangles) is done on this.canvas_.
* @param {Object} canvas The Dygraph canvas over which to overlay the plot
* @return {Object} The newly-created canvas
* @private
*/
Dygraph.prototype.createPlotKitCanvas_ = function (canvas) {
var h = utils.createCanvas();
h.style.position = "absolute";
// TODO(danvk): h should be offset from canvas. canvas needs to include
// some extra area to make it easier to zoom in on the far left and far
// right. h needs to be precisely the plot area, so that clipping occurs.
h.style.top = canvas.style.top;
h.style.left = canvas.style.left;
h.width = this.width_;
h.height = this.height_;
h.style.width = this.width_ + "px"; // for IE
h.style.height = this.height_ + "px"; // for IE
return h;
};
/**
* Creates an overlay element used to handle mouse events.
* @return {Object} The mouse event element.
* @private
*/
Dygraph.prototype.createMouseEventElement_ = function () {
return this.canvas_;
};
/**
* Generate a set of distinct colors for the data series. This is done with a
* color wheel. Saturation/Value are customizable, and the hue is
* equally-spaced around the color wheel. If a custom set of colors is
* specified, that is used instead.
* @private
*/
Dygraph.prototype.setColors_ = function () {
var labels = this.getLabels();
var num = labels.length - 1;
this.colors_ = [];
this.colorsMap_ = {};
// These are used for when no custom colors are specified.
var sat = this.getNumericOption('colorSaturation') || 1.0;
var val = this.getNumericOption('colorValue') || 0.5;
var half = Math.ceil(num / 2);
var colors = this.getOption('colors');
var visibility = this.visibility();
for (var i = 0; i < num; i++) {
if (!visibility[i]) {
continue;
}
var label = labels[i + 1];
var colorStr = this.attributes_.getForSeries('color', label);
if (!colorStr) {
if (colors) {
colorStr = colors[i % colors.length];
} else {
// alternate colors for high contrast.
var idx = i % 2 ? half + (i + 1) / 2 : Math.ceil((i + 1) / 2);
var hue = 1.0 * idx / (1 + num);
colorStr = utils.hsvToRGB(hue, sat, val);
}
}
this.colors_.push(colorStr);
this.colorsMap_[label] = colorStr;
}
};
/**
* Return the list of colors. This is either the list of colors passed in the
* attributes or the autogenerated list of rgb(r,g,b) strings.
* This does not return colors for invisible series.
* @return {Array.<string>} The list of colors.
*/
Dygraph.prototype.getColors = function () {
return this.colors_;
};
/**
* Returns a few attributes of a series, i.e. its color, its visibility, which
* axis it's assigned to, and its column in the original data.
* Returns null if the series does not exist.
* Otherwise, returns an object with column, visibility, color and axis properties.
* The "axis" property will be set to 1 for y1 and 2 for y2.
* The "column" property can be fed back into getValue(row, column) to get
* values for this series.
*/
Dygraph.prototype.getPropertiesForSeries = function (series_name) {
var idx = -1;
var labels = this.getLabels();
for (var i = 1; i < labels.length; i++) {
if (labels[i] == series_name) {
idx = i;
break;
}
}
if (idx == -1) return null;
return {
name: series_name,
column: idx,
visible: this.visibility()[idx - 1],
color: this.colorsMap_[series_name],
axis: 1 + this.attributes_.axisForSeries(series_name)
};
};
/**
* Create the text box to adjust the averaging period
* @private
*/
Dygraph.prototype.createRollInterface_ = function () {
// Create a roller if one doesn't exist already.
var roller = this.roller_;
if (!roller) {
this.roller_ = roller = document.createElement("input");
roller.type = "text";
roller.style.display = "none";
roller.className = 'dygraph-roller';
this.graphDiv.appendChild(roller);
}
var display = this.getBooleanOption('showRoller') ? 'block' : 'none';
var area = this.getArea();
var textAttr = {
"top": area.y + area.h - 25 + "px",
"left": area.x + 1 + "px",
"display": display
};
roller.size = "2";
roller.value = this.rollPeriod_;
utils.update(roller.style, textAttr);
var that = this;
roller.onchange = function onchange() {
return that.adjustRoll(roller.value);
};
};
/**
* Set up all the mouse handlers needed to capture dragging behavior for zoom
* events.
* @private
*/
Dygraph.prototype.createDragInterface_ = function () {
var context = {
// Tracks whether the mouse is down right now
isZooming: false,
isPanning: false,
// is this drag part of a pan?
is2DPan: false,
// if so, is that pan 1- or 2-dimensional?
dragStartX: null,
// pixel coordinates
dragStartY: null,
// pixel coordinates
dragEndX: null,
// pixel coordinates
dragEndY: null,
// pixel coordinates
dragDirection: null,
prevEndX: null,
// pixel coordinates
prevEndY: null,
// pixel coordinates
prevDragDirection: null,
cancelNextDblclick: false,
// see comment in dygraph-interaction-model.js
// The value on the left side of the graph when a pan operation starts.
initialLeftmostDate: null,
// The number of units each pixel spans. (This won't be valid for log
// scales)
xUnitsPerPixel: null,
// TODO(danvk): update this comment
// The range in second/value units that the viewport encompasses during a
// panning operation.
dateRange: null,
// Top-left corner of the canvas, in DOM coords
// TODO(konigsberg): Rename topLeftCanvasX, topLeftCanvasY.
px: 0,
py: 0,
// Values for use with panEdgeFraction, which limit how far outside the
// graph's data boundaries it can be panned.
boundedDates: null,
// [minDate, maxDate]
boundedValues: null,
// [[minValue, maxValue] ...]
// We cover iframes during mouse interactions. See comments in
// dygraph-utils.js for more info on why this is a good idea.
tarp: new _iframeTarp["default"](),
// contextB is the same thing as this context object but renamed.
initializeMouseDown: function initializeMouseDown(event, g, contextB) {
// prevents mouse drags from selecting page text.
if (event.preventDefault) {
event.preventDefault(); // Firefox, Chrome, etc.
} else {
event.returnValue = false; // IE
event.cancelBubble = true;
}
var canvasPos = utils.findPos(g.canvas_);
contextB.px = canvasPos.x;
contextB.py = canvasPos.y;
contextB.dragStartX = utils.dragGetX_(event, contextB);
contextB.dragStartY = utils.dragGetY_(event, contextB);
contextB.cancelNextDblclick = false;
contextB.tarp.cover();
},
destroy: function destroy() {
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();
}
};
var interactionModel = this.getOption("interactionModel");
// Self is the graph.
var self = this;
// Function that binds the graph and context to the handler.
var bindHandler = function bindHandler(handler) {
return function (event) {
handler(event, self, context);
};
};
for (var eventName in interactionModel) {
if (!interactionModel.hasOwnProperty(eventName)) continue;
this.addAndTrackEvent(this.mouseEventElement_, eventName, bindHandler(interactionModel[eventName]));
}
// If the user releases the mouse button during a drag, but not over the
// canvas, then it doesn't count as a zooming action.
if (!interactionModel.willDestroyContextMyself) {
var mouseUpHandler = function mouseUpHandler(event) {
context.destroy();
};
this.addAndTrackEvent(document, 'mouseup', mouseUpHandler);
}
};
/**
* Draw a gray zoom rectangle over the desired area of the canvas. Also clears
* up any previous zoom rectangles that were drawn. This could be optimized to
* avoid extra redrawing, but it's tricky to avoid interactions with the status
* dots.
*
* @param {number} direction the direction of the zoom rectangle. Acceptable
* values are utils.HORIZONTAL and utils.VERTICAL.
* @param {number} startX The X position where the drag started, in canvas
* coordinates.
* @param {number} endX The current X position of the drag, in canvas coords.
* @param {number} startY The Y position where the drag started, in canvas
* coordinates.
* @param {number} endY The current Y position of the drag, in canvas coords.
* @param {number} prevDirection the value of direction on the previous call to
* this function. Used to avoid excess redrawing
* @param {number} prevEndX The value of endX on the previous call to this
* function. Used to avoid excess redrawing
* @param {number} prevEndY The value of endY on the previous call to this
* function. Used to avoid excess redrawing
* @private
*/
Dygraph.prototype.drawZoomRect_ = function (direction, startX, endX, startY, endY, prevDirection, prevEndX, prevEndY) {
var ctx = this.canvas_ctx_;
// Clean up from the previous rect if necessary
if (prevDirection == utils.HORIZONTAL) {
ctx.clearRect(Math.min(startX, prevEndX), this.layout_.getPlotArea().y, Math.abs(startX - prevEndX), this.layout_.getPlotArea().h);
} else if (prevDirection == utils.VERTICAL) {
ctx.clearRect(this.layout_.getPlotArea().x, Math.min(startY, prevEndY), this.layout_.getPlotArea().w, Math.abs(startY - prevEndY));
}
// Draw a light-grey rectangle to show the new viewing area
if (direction == utils.HORIZONTAL) {
if (endX && startX) {
ctx.fillStyle = "rgba(128,128,128,0.33)";
ctx.fillRect(Math.min(startX, endX), this.layout_.getPlotArea().y, Math.abs(endX - startX), this.layout_.getPlotArea().h);
}
} else if (direction == utils.VERTICAL) {
if (endY && startY) {
ctx.fillStyle = "rgba(128,128,128,0.33)";
ctx.fillRect(this.layout_.getPlotArea().x, Math.min(startY, endY), this.layout_.getPlotArea().w, Math.abs(endY - startY));
}
}
};
/**
* Clear the zoom rectangle (and perform no zoom).
* @private
*/
Dygraph.prototype.clearZoomRect_ = function () {
this.currentZoomRectArgs_ = null;
this.canvas_ctx_.clearRect(0, 0, this.width_, this.height_);
};
/**
* Zoom to something containing [lowX, highX]. These are pixel coordinates in
* the canvas. The exact zoom window may be slightly larger if there are no data
* points near lowX or highX. Don't confuse this function with doZoomXDates,
* which accepts dates that match the raw data. This function redraws the graph.
*
* @param {number} lowX The leftmost pixel value that should be visible.
* @param {number} highX The rightmost pixel value that should be visible.
* @private
*/
Dygraph.prototype.doZoomX_ = function (lowX, highX) {
this.currentZoomRectArgs_ = null;
// Find the earliest and latest dates contained in this canvasx range.
// Convert the call to date ranges of the raw data.
var minDate = this.toDataXCoord(lowX);
var maxDate = this.toDataXCoord(highX);
this.doZoomXDates_(minDate, maxDate);
};
/**
* Zoom to something containing [minDate, maxDate] values. Don't confuse this
* method with doZoomX which accepts pixel coordinates. This function redraws
* the graph.
*
* @param {number} minDate The minimum date that should be visible.
* @param {number} maxDate The maximum date that should be visible.
* @private
*/
Dygraph.prototype.doZoomXDates_ = function (minDate, maxDate) {
// TODO(danvk): when xAxisRange is null (i.e. "fit to data", the animation
// can produce strange effects. Rather than the x-axis transitioning slowly
// between values, it can jerk around.)
var old_window = this.xAxisRange();
var new_window = [minDate, maxDate];
var zoomCallback = this.getFunctionOption('zoomCallback');
var that = this;
this.doAnimatedZoom(old_window, new_window, null, null, function animatedZoomCallback() {
if (zoomCallback) {
zoomCallback.call(that, minDate, maxDate, that.yAxisRanges());
}
});
};
/**
* Zoom to something containing [lowY, highY]. These are pixel coordinates in
* the canvas. This function redraws the graph.
*
* @param {number} lowY The topmost pixel value that should be visible.
* @param {number} highY The lowest pixel value that should be visible.
* @private
*/
Dygraph.prototype.doZoomY_ = function (lowY, highY) {
this.currentZoomRectArgs_ = null;
// Find the highest and lowest values in pixel range for each axis.
// Note that lowY (in pixels) corresponds to the max Value (in data coords).
// This is because pixels increase as you go down on the screen, whereas data
// coordinates increase as you go up the screen.
var oldValueRanges = this.yAxisRanges();
var newValueRanges = [];
for (var i = 0; i < this.axes_.length; i++) {
var hi = this.toDataYCoord(lowY, i);
var low = this.toDataYCoord(highY, i);
newValueRanges.push([low, hi]);
}
var zoomCallback = this.getFunctionOption('zoomCallback');
var that = this;
this.doAnimatedZoom(null, null, oldValueRanges, newValueRanges, function animatedZoomCallback() {
if (zoomCallback) {
var _that$xAxisRange = that.xAxisRange(),
_that$xAxisRange2 = _slicedToArray(_that$xAxisRange, 2),
minX = _that$xAxisRange2[0],
maxX = _that$xAxisRange2[1];
zoomCallback.call(that, minX, maxX, that.yAxisRanges());
}
});
};
/**
* Transition function to use in animations. Returns values between 0.0
* (totally old values) and 1.0 (totally new values) for each frame.
* @private
*/
Dygraph.zoomAnimationFunction = function (frame, numFrames) {
var k = 1.5;
return (1.0 - Math.pow(k, -frame)) / (1.0 - Math.pow(k, -numFrames));
};
/**
* Reset the zoom to the original view coordinates. This is the same as
* double-clicking on the graph.
*/
Dygraph.prototype.resetZoom = function () {
var dirtyX = this.isZoomed('x');
var dirtyY = this.isZoomed('y');
var dirty = dirtyX || dirtyY;
// Clear any selection, since i