UNPKG

dygraphs

Version:

dygraphs is a fast, flexible open source JavaScript charting library.

1,303 lines (1,219 loc) 424 kB
"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 &lt;canvas&gt; 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