UNPKG

@amcharts/amcharts4

Version:
1,323 lines (1,322 loc) 67.2 kB
import { __extends } from "tslib"; /** * ============================================================================ * IMPORTS * ============================================================================ * @hidden */ import { Container } from "./Container"; import { List, ListDisposer } from "./utils/List"; import { OrderedListTemplate } from "./utils/SortedList"; import { Dictionary } from "./utils/Dictionary"; import { Disposer, MultiDisposer } from "./utils/Disposer"; import { DataSource } from "./data/DataSource"; import { Responsive } from "./utils/Responsive"; import { system } from "./System"; import { DataItem } from "./DataItem"; import { registry } from "./Registry"; import * as $math from "./utils/Math"; import * as $array from "./utils/Array"; import * as $ease from "./utils/Ease"; import * as $utils from "./utils/Utils"; import * as $iter from "./utils/Iterator"; import * as $object from "./utils/Object"; import * as $type from "./utils/Type"; /** * ============================================================================ * MAIN CLASS * ============================================================================ * @hidden */ /** * A Component represents an independent functional element or control, that * can have it's own behavior, children, data, etc. * * A few examples of a Component: [[Legend]], [[Series]], [[Scrollbar]]. * * @see {@link IComponentEvents} for a list of available events * @see {@link IComponentAdapters} for a list of available Adapters * @important */ var Component = /** @class */ (function (_super) { __extends(Component, _super); /** * Constructor */ function Component() { var _this = // Init _super.call(this) || this; /** * Holds data field names. * * Data fields define connection beween [[DataItem]] and actual properties * in raw data. */ _this.dataFields = {}; /** * A list of [[DataSource]] definitions of external data source. * * @ignore Exclude from docs */ _this._dataSources = {}; /** * This is used when only new data is invalidated (if added using `addData` * method). * * @ignore Exclude from docs */ _this._parseDataFrom = 0; /** * Holds the disposers for the dataItems and dataUsers * * @ignore Exclude from docs */ _this._dataDisposers = []; /** * Currently selected "data set". * * If it's set to `""`, main data set (unaggregated data) is used. */ _this._currentDataSetId = ""; /** * [_start description] * * @ignore Exclude from docs */ _this._start = 0; /** * [_end description] * * @ignore Exclude from docs */ _this._end = 1; /** * If set to `true`, changing data range in element will not trigger * `daterangechanged` event. */ _this.skipRangeEvent = false; /** * Whenever selected scope changes (chart is zoomed or panned), for example * by interaction from a Scrollbar, or API, a chart needs to reposition * its contents. * * `rangeChangeDuration` influences how this is performed. * * If set to zero (0), the change will happen instantenously. * * If set to non-zero value, the chart will gradually animate into new * position for the set amount of milliseconds. * * @default 0 * @see {@link https://www.amcharts.com/docs/v4/concepts/animations/} for more info about animations */ _this.rangeChangeDuration = 0; /** * An easing function to use for range change animation. * * @see {@link Ease} * @see {@link https://www.amcharts.com/docs/v4/concepts/animations/} for more info about animations */ _this.rangeChangeEasing = $ease.cubicOut; /** * A duration (ms) of each data parsing step. A Component parses its data in * chunks in order to avoid completely freezing the machine when large data * sets are used. This setting will control how many milliseconds should pass * when parsing data until parser stops for a brief moment to let other * processes catch up. */ _this.parsingStepDuration = 50; /** * [dataInvalid description] * * @ignore Exclude from docs * @todo Description */ _this.dataInvalid = false; /** * * @ignore Exclude from docs */ _this.rawDataInvalid = false; /** * [dataRangeInvalid description] * * @ignore Exclude from docs * @todo Description */ _this.dataRangeInvalid = false; /** * [dataItemsInvalid description] * * @ignore Exclude from docs * @todo Description */ _this.dataItemsInvalid = false; /** * If set to a non-zero number the element will "animate" data values of its * children. * * This will happen on first load and whenever data values change. * * Enabling interpolation will mean that elements will transit smoothly into * new values rather than updating instantly. * * @default 0 * @see {@link https://www.amcharts.com/docs/v4/concepts/animations/} for more info about animations */ _this.interpolationDuration = 0; /** * An easing function to use for interpolating values when transiting from * one source value to another. * * @default cubicOut * @see {@link https://www.amcharts.com/docs/v4/concepts/animations/} for more info about animations * @see {@link Ease} */ _this.interpolationEasing = $ease.cubicOut; /** * Indicates whether transition between data item's values should start and * play out all at once, or with a small delay (as defined by * `sequencedInterpolationDelay`) for each subsequent data item. * * @default true * @see {@link https://www.amcharts.com/docs/v4/concepts/animations/} for more info about animations */ _this.sequencedInterpolation = true; /** * A delay (ms) to wait between animating each subsequent data item's * interpolation animation. * * Relative only if `sequencedInterpolation = true`. * * @default 0 * @see {@link https://www.amcharts.com/docs/v4/concepts/animations/} for more info about animations */ _this.sequencedInterpolationDelay = 0; /** * A progress (0-1) for the data validation process. * * @ignore Exclude from docs */ _this.dataValidationProgress = 0; _this._addAllDataItems = true; _this._usesData = true; _this.className = "Component"; _this.minZoomCount = 1; _this.maxZoomCount = 0; _this._dataItems = new OrderedListTemplate(_this.createDataItem()); _this._dataItems.events.on("inserted", _this.handleDataItemAdded, _this, false); _this._dataItems.events.on("removed", _this.handleDataItemRemoved, _this, false); _this._disposers.push(new ListDisposer(_this._dataItems)); _this._disposers.push(_this._dataItems.template); _this.invalidateData(); // TODO what about remove ? _this.dataUsers.events.on("inserted", _this.handleDataUserAdded, _this, false); // Set up disposers _this._disposers.push(new MultiDisposer(_this._dataDisposers)); _this._start = 0; _this._end = 1; _this.maxZoomDeclination = 1; // Apply theme _this.applyTheme(); return _this; } /** * Returns a new/empty DataItem of the type appropriate for this object. * * @see {@link DataItem} * @return Data Item */ Component.prototype.createDataItem = function () { return new DataItem(); }; /** * [handleDataUserAdded description] * * @ignore Exclude from docs * @todo Description * @param event Event object */ Component.prototype.handleDataUserAdded = function (event) { var dataUser = event.newValue; dataUser.dataProvider = this; }; /** * [handleDataItemValueChange description] * * @ignore Exclude from docs * @todo Description */ Component.prototype.handleDataItemValueChange = function (dataItem, name) { if (!this.dataItemsInvalid) { this.invalidateDataItems(); } }; /** * [handleDataItemWorkingValueChange description] * * @ignore Exclude from docs */ Component.prototype.handleDataItemWorkingValueChange = function (dataItem, name) { }; /** * [handleDataItemWorkingLocationChange description] * * @ignore Exclude from docs */ Component.prototype.handleDataItemWorkingLocationChange = function (dataItem, name) { }; /** * [handleDataItemCalculatedValueChange description] * * @ignore Exclude from docs */ Component.prototype.handleDataItemCalculatedValueChange = function (dataItem, name) { }; /** * [handleDataItemPropertyChange description] * * @ignore Exclude from docs */ Component.prototype.handleDataItemPropertyChange = function (dataItem, name) { }; /** * Populates a [[DataItem]] width data from data source. * * Loops through all the fields and if such a field is found in raw data * object, a corresponding value on passed in `dataItem` is set. * * @ignore Exclude from docs * @param item */ Component.prototype.processDataItem = function (dataItem, dataContext) { var _this = this; if (dataItem) { if (!dataContext) { dataContext = {}; } // store reference to original data item dataItem.dataContext = dataContext; var hasSomeValues_1 = false; $object.each(this.dataFields, function (key, fieldValue) { var fieldName = key; var value = dataContext[fieldValue]; // Apply adapters to a retrieved value if (_this._adapterO) { if (_this._adapterO.isEnabled("dataContextValue")) { value = _this._adapterO.apply("dataContextValue", { field: fieldName, value: value, dataItem: dataItem }).value; } } if ($type.hasValue(value)) { hasSomeValues_1 = true; if (dataItem.hasChildren[fieldName]) { var template = _this.createDataItem(); template.copyFrom(_this.mainDataSet.template); var children = new OrderedListTemplate(template); children.events.on("inserted", _this.handleDataItemAdded, _this, false); children.events.on("removed", _this.handleDataItemRemoved, _this, false); _this._dataDisposers.push(new ListDisposer(children)); var count = value.length; for (var i = 0; i < count; i++) { var rawDataItem = value[i]; var childDataItem = children.create(); childDataItem.parent = dataItem; _this.processDataItem(childDataItem, rawDataItem); } var anyDataItem = dataItem; anyDataItem[fieldName] = children; } else { // data is converted to numbers/dates in each dataItem dataItem[fieldName] = value; } } }); $object.each(this.propertyFields, function (key, fieldValue) { var f = key; var value = dataContext[fieldValue]; if ($type.hasValue(value)) { hasSomeValues_1 = true; dataItem.setProperty(f, value); } }); // @todo we might need some flag which would tell whether we should create empty data items or not. if (!this._addAllDataItems && !hasSomeValues_1) { this.mainDataSet.remove(dataItem); } } }; /** * * When validating raw data, instead of processing data item, we update it * * @ignore Exclude from docs * @param item */ Component.prototype.updateDataItem = function (dataItem) { var _this = this; if (dataItem) { var dataContext_1 = dataItem.dataContext; $object.each(this.dataFields, function (key, fieldValue) { var fieldName = key; var value = dataContext_1[fieldValue]; // Apply adapters to a retrieved value if (_this._adapterO) { value = _this._adapterO.apply("dataContextValue", { field: fieldName, value: value, dataItem: dataItem }).value; } if ($type.hasValue(value)) { if (dataItem.hasChildren[fieldName]) { var anyDataItem = dataItem; var children = (anyDataItem[fieldName]); children.each(function (child) { _this.updateDataItem(child); }); } else { // data is converted to numbers/dates in each dataItem dataItem[fieldName] = value; } } }); $object.each(this.propertyFields, function (key, fieldValue) { var f = key; var value = dataContext_1[fieldValue]; if ($type.hasValue(value)) { dataItem.setProperty(f, value); } }); } }; /** * [validateDataElements description] * * @ignore Exclude from docs * @todo Description */ Component.prototype.validateDataElements = function () { var count = this.endIndex; for (var i = this.startIndex; i < count; i++) { var dataItem = this.dataItems.getIndex(i); // TODO is this correct if (dataItem) { this.validateDataElement(dataItem); } } }; /** * Validates this element and its related elements. * * @ignore Exclude from docs */ Component.prototype.validate = function () { this.validateDataElements(); _super.prototype.validate.call(this); }; /** * [validateDataElement description] * * @ignore Exclude from docs * @param dataItem [description] */ Component.prototype.validateDataElement = function (dataItem) { }; /** * Adds one or several (array) of data items to the existing data. * * @param rawDataItem One or many raw data item objects */ Component.prototype.addData = function (rawDataItem, removeCount, skipRaw) { var _this = this; // need to check if data is invalid, as addData might be called multiple times if (!this.dataInvalid && this.inited) { this._parseDataFrom = this.data.length; // save length of parsed data } if (!skipRaw) { if (rawDataItem instanceof Array) { // can't use concat because new array is returned $array.each(rawDataItem, function (dataItem) { _this.data.push(dataItem); }); } else { this.data.push(rawDataItem); // add to raw data array } } if (this.inited) { this.removeData(removeCount, skipRaw); } else { if ($type.isNumber(removeCount)) { while (removeCount > 0) { this.data.shift(); removeCount--; } } } this.invalidateData(); }; /** * Removes elements from the beginning of data * * @param count number of elements to remove */ Component.prototype.removeData = function (count, skipRaw) { if ($type.isNumber(count) && count > 0) { while (count > 0) { var dataItem = this.mainDataSet.getIndex(0); if (dataItem) { this.mainDataSet.remove(dataItem); } this.dataUsers.each(function (dataUser) { if (!dataUser.data || dataUser.data.length == 0) { var dataItem_1 = dataUser.mainDataSet.getIndex(0); if (dataItem_1) { dataUser.mainDataSet.remove(dataItem_1); } } }); if (!skipRaw) { this.data.shift(); } if (this._parseDataFrom > 0) { this._parseDataFrom--; } count--; } // changed from invalidateData since 4.7.19 to solve #51551 this.invalidateDataItems(); } }; /** * Triggers a data (re)parsing. * * @ignore Exclude from docs */ Component.prototype.invalidateData = function () { if (this.disabled || this.isTemplate) { return; } //if(!this.dataInvalid){ registry.addToInvalidComponents(this); system.requestFrame(); this.dataInvalid = true; $iter.each(this.dataUsers.iterator(), function (x) { x.invalidateDataItems(); }); //} }; /** * [invalidateDataUsers description] * * @ignore Exclude from docs * @todo Description */ Component.prototype.invalidateDataUsers = function () { $iter.each(this.dataUsers.iterator(), function (x) { x.invalidate(); }); }; /** * Invalidates data values. When data array is not changed, but values within * it changes, we invalidate data so that component would process changes. * * @ignore Exclude from docs */ Component.prototype.invalidateDataItems = function () { if (this.disabled || this.isTemplate) { return; } //if(!this.dataItemsInvalid){ $array.move(registry.invalidDataItems, this); system.requestFrame(); this.dataItemsInvalid = true; $iter.each(this.dataUsers.iterator(), function (x) { x.invalidateDataItems(); }); //} }; /** * Invalidates data range. This is done when data which must be shown * changes (chart is zoomed for example). * * @ignore Exclude from docs */ Component.prototype.invalidateDataRange = function () { if (this.disabled || this.isTemplate) { return; } //if(!this.dataRangeInvalid){ this.dataRangeInvalid = true; $array.move(registry.invalidDataRange, this); system.requestFrame(); //} }; /** * Processes data range. * * @todo Description * @ignore Exclude from docs */ Component.prototype.validateDataRange = function () { $array.remove(registry.invalidDataRange, this); this.dataRangeInvalid = false; if (this.startIndex != this._prevStartIndex || this.endIndex != this._prevEndIndex) { this.rangeChangeUpdate(); this.appendDataItems(); this.invalidate(); this.dispatchImmediately("datarangechanged"); } }; /** * [sliceData description] * * @todo Description * @ignore Exclude from docs */ Component.prototype.sliceData = function () { this._workingStartIndex = this.startIndex; this._workingEndIndex = this.endIndex; }; /** * [rangeChangeUpdate description] * * @todo Description * @ignore Exclude from docs */ Component.prototype.rangeChangeUpdate = function () { this.sliceData(); this._prevStartIndex = this.startIndex; this._prevEndIndex = this.endIndex; }; /** * [appendDataItems description] * * @todo Description * @ignore Exclude from docs */ Component.prototype.appendDataItems = function () { // TODO use an iterator instead var count = this.endIndex; for (var i = this.startIndex; i < count; i++) { // data item var dataItem = this.dataItems.getIndex(i); if (dataItem) { dataItem.__disabled = false; } } for (var i = 0; i < this.startIndex; i++) { var dataItem = this.dataItems.getIndex(i); if (dataItem) { dataItem.__disabled = true; } } for (var i = this.endIndex; i < this.dataItems.length; i++) { var dataItem = this.dataItems.getIndex(i); if (dataItem) { dataItem.__disabled = true; } } }; /** * If you want to have a smooth transition from one data values to another, you change your raw data and then you must call this method. * then instead of redrawing everything, the chart will check raw data and smoothly transit from previous to new data */ Component.prototype.invalidateRawData = function () { if (this.disabled || this.isTemplate) { return; } //if(!this.rawDataInvalid){ $array.move(registry.invalidRawDatas, this); system.requestFrame(); this.rawDataInvalid = true; $iter.each(this.dataUsers.iterator(), function (x) { x.invalidateRawData(); }); //} }; /** * @ignore */ Component.prototype.validateRawData = function () { var _this = this; $array.remove(registry.invalidRawDatas, this); $iter.each(this.mainDataSet.iterator(), function (dataItem) { if (dataItem) { _this.updateDataItem(dataItem); } }); }; /** * Destroys this object and all related data. */ Component.prototype.dispose = function () { var _this = this; this.mainDataSet.template.clones.clear(); $object.each(this._dataSources, function (key, source) { _this.removeDispose(source); }); this.disposeData(); _super.prototype.dispose.call(this); }; /** * @ignore */ Component.prototype.disposeData = function () { this.mainDataSet.template.clones.clear(); $array.each(this._dataDisposers, function (x) { x.dispose(); }); // and for all components $iter.each(this.dataUsers.iterator(), function (dataUser) { dataUser.disposeData(); }); this._dataDisposers.length = 0; this._startIndex = undefined; this._endIndex = undefined; // dispose old this.mainDataSet.clear(); this.mainDataSet.template.clones.clear(); if (this._dataSets) { this._dataSets.clear(); } }; Component.prototype.getDataItem = function (dataContext) { return this.mainDataSet.create(); }; /** * Validates (processes) data. * * @ignore Exclude from docs */ Component.prototype.validateData = function () { this.dispatchImmediately("beforedatavalidated"); this.dataInvalid = false; registry.removeFromInvalidComponents(this); if (this.__disabled) { return; } this.dataValidationProgress = 0; // need this to slice new data this._prevStartIndex = undefined; this._prevEndIndex = undefined; // todo: this needs some overthinking, maybe some extra settings like zoomOotonDataupdate like in v3 or so. some charts like pie chart probably should act like this always this._startIndex = undefined; this._endIndex = undefined; if (this.dataFields.data && this.dataItem) { var dataContext = this.dataItem.dataContext; this._data = dataContext[this.dataFields.data]; } // data items array is reset only if all data is validated, if _parseDataFrom is not 0, we append new data only // check heatmap demo if uncommented // fixed both issues by adding && this.data.length > 0 // check adding series example if changed if (this._parseDataFrom === 0 && this.data.length > 0) { this.disposeData(); } if (this.data.length > 0) { var preloader = this.preloader; // and for all components $iter.each(this.dataUsers.iterator(), function (dataUser) { // todo: this needs some overthinking, maybe some extra settings like zoomOUtonDataupdate like in v3 or so. some charts like pie chart probably should act like this always dataUser._startIndex = undefined; dataUser._endIndex = undefined; }); var counter = 0; var startTime = Date.now(); // parse data var i = this._parseDataFrom; var n = this.data.length; var _loop_1 = function () { var rawDataItem = this_1.data[i]; if (this_1._usesData) { var dataItem = this_1.getDataItem(rawDataItem); this_1.processDataItem(dataItem, rawDataItem); } this_1.dataUsers.each(function (dataUser) { if (dataUser.data.length == 0) { // checking if data is not set directly var dataUserDataItem = dataUser.getDataItem(rawDataItem); dataUser.processDataItem(dataUserDataItem, rawDataItem); } }); counter++; // show preloader if this takes too many time if (counter == 100) { // no need to check it on each data item counter = 0; var elapsed = Date.now() - startTime; if (elapsed > this_1.parsingStepDuration) { if (i < this_1.data.length - 10) { this_1._parseDataFrom = i + 1; // update preloader if (preloader) { if (i / this_1.data.length > 0.5 && !preloader.visible) { // do not start showing } else { preloader.progress = i / this_1.data.length; } } this_1.dataValidationProgress = i / this_1.data.length; i = this_1.data.length; // stops cycle this_1.invalidateData(); return { value: void 0 }; } } } }; var this_1 = this; for (i; i < n; i++) { var state_1 = _loop_1(); if (typeof state_1 === "object") return state_1.value; } if (preloader) { preloader.progress = 1; } this.dataUsers.each(function (dataUser) { if (dataUser.hidden || (dataUser.appeared && !dataUser.visible && dataUser.stacked)) { dataUser.hide(0); } }); } this.dataValidationProgress = 1; this._parseDataFrom = 0; // reset this index, it is set to dataItems.length if addData() method was used. this.invalidateDataItems(); if (!this._internalDefaultsApplied) { this.applyInternalDefaults(); } this.dispatch("datavalidated"); // can't zoom chart if dispatched immediately }; /** * Validates (processes) data items. * * @ignore Exclude from docs */ Component.prototype.validateDataItems = function () { $array.remove(registry.invalidDataItems, this); this.dataItemsInvalid = false; this.invalidateDataRange(); this.invalidate(); this.dispatch("dataitemsvalidated"); }; Object.defineProperty(Component.prototype, "data", { /** * Returns element's source (raw) data. * * @return Data */ get: function () { if (!this._data) { this._data = []; } if (!this._adapterO) { return this._data; } else { return this._adapterO.apply("data", this._data); } }, /** * Sets source (raw) data for the element. The "data" is always an `Array` * of objects. * * IMPORTANT: The order of data items in `data` array is important as it * might affect chart look and behavior. [More details](https://www.amcharts.com/docs/v4/concepts/data/#Order_of_data_items). * * @param value Data */ set: function (value) { this.setData(value); }, enumerable: true, configurable: true }); Component.prototype.setData = function (value) { // array might be the same, but there might be items added // todo: check if array changed, toString maybe? if (!this.isDisposed()) { this._parseDataFrom = 0; this.disposeData(); this._data = value; if (value && value.length > 0) { this.invalidateData(); } else { this.dispatchImmediately("beforedatavalidated"); this.dispatch("datavalidated"); } } }; /** * Returns (creates if necessary) a [[DataSource]] bound to any specific * property. * * For example if I want to bind `data` to an external JSON file, I'd create * a DataSource for it. * * @param property Property to bind external data to * @return A DataSource for property */ Component.prototype.getDataSource = function (property) { var _this = this; if (!$type.hasValue(this._dataSources[property])) { this._dataSources[property] = new DataSource(); this._dataSources[property].component = this; this.setDataSourceEvents(this._dataSources[property], property); this._dataSources[property].adapter.add("dateFields", function (val) { return _this.dataSourceDateFields(val); }); this._dataSources[property].adapter.add("numberFields", function (val) { return _this.dataSourceNumberFields(val); }); this.events.on("inited", function () { _this.loadData(property); }, this, false); } return this._dataSources[property]; }; Object.defineProperty(Component.prototype, "dataSource", { /** * @return Data source */ get: function () { if (!this._dataSources["data"]) { this.getDataSource("data"); } return this._dataSources["data"]; }, /** *A [[DataSource]] to be used for loading Component's data. * * @see {@link https://www.amcharts.com/docs/v4/concepts/loading-external-data/} for more on loading external data * @param value Data source */ set: function (value) { var _this = this; if (this._dataSources["data"]) { this.removeDispose(this._dataSources["data"]); } this._dataSources["data"] = value; this._dataSources["data"].component = this; this.events.on("inited", function () { _this.loadData("data"); }, this, false); this.setDataSourceEvents(value, "data"); }, enumerable: true, configurable: true }); /** * Initiates loading of the external data via [[DataSource]]. * * @ignore Exclude from docs */ Component.prototype.loadData = function (property) { this._dataSources[property].load(); }; /** * This function is called by the [[DataSource]]'s `dateFields` adapater * so that particular chart types can popuplate this setting with their * own type-specific data fields so they are parsed properly. * * @ignore Exclude from docs * @param value Array of date fields * @return Array of date fields populated with chart's date fields */ Component.prototype.dataSourceDateFields = function (value) { return value; }; /** * This function is called by the [[DataSource]]'s `numberFields` adapater * so that particular chart types can popuplate this setting with their * own type-specific data fields so they are parsed properly. * * @ignore Exclude from docs * @param value Array of number fields * @return Array of number fields populated with chart's number fields */ Component.prototype.dataSourceNumberFields = function (value) { return value; }; /** * * @ignore Exclude from docs * @todo Description * @param list [description] * @param dataFields [description] * @param targetList [description] * @return [description] */ Component.prototype.populateDataSourceFields = function (list, dataFields, targetList) { $array.each(targetList, function (value) { if (dataFields[value] && $array.indexOf(list, dataFields[value]) === -1) { list.push(dataFields[value]); } }); return list; }; /** * Sets events on a [[DataSource]]. * * @ignore Exclude from docs */ Component.prototype.setDataSourceEvents = function (ds, property) { var _this = this; ds.events.on("started", function (ev) { var preloader = _this.preloader; if (preloader) { preloader.progress = 0; //preloader.label.text = this.language.translate("Loading"); } }, undefined, false); ds.events.on("loadstarted", function (ev) { var preloader = _this.preloader; if (preloader) { preloader.progress = 0.25; } }, undefined, false); ds.events.on("loadended", function (ev) { var preloader = _this.preloader; if (preloader) { preloader.progress = 0.5; } }, undefined, false); ds.events.on("parseended", function (ev) { var preloader = _this.preloader; if (preloader) { preloader.progress = 0.75; } }, undefined, false); ds.events.on("ended", function (ev) { var preloader = _this.preloader; if (preloader) { preloader.progress = 1; } }, undefined, false); ds.events.on("error", function (ev) { var preloader = _this.preloader; if (preloader) { preloader.progress = 1; } _this.openModal(ev.message); }, undefined, false); if (property) { ds.events.on("done", function (ev) { var preloader = _this.preloader; if (preloader) { preloader.progress = 1; } if (property == "data" && !$type.isArray(ev.data)) { ev.data = [ev.data]; } if (ds.incremental && property == "data" && _this.data.length) { _this.addData(ev.data, ds.keepCount ? ev.data.length : 0); } else if (ds.updateCurrentData && property == "data" && _this.data.length) { // cycle through existing data items $array.each(_this.data, function (item, index) { if ($type.hasValue(ev.data[index])) { $object.each(item, function (key, val) { if ($type.hasValue(ev.data[index][key])) { item[key] = ev.data[index][key]; } }); } }); _this.invalidateRawData(); } else { _this[property] = ev.data; } }); } }; Object.defineProperty(Component.prototype, "responsive", { /** * @return Responsive rules handler */ get: function () { if (!this._responsive) { this._responsive = new Responsive(); this._responsive.component = this; } return this._responsive; }, /** * A [[Responsive]] instance to be used when applying conditional * property values. * * NOTE: Responsive features are currently in development and may not work * as expected, if at all. * * @param value Responsive rules handler */ set: function (value) { this._responsive = value; this._responsive.component = this; }, enumerable: true, configurable: true }); /** * Sets current zoom. * * The range uses relative values from 0 to 1, with 0 marking beginning and 1 * marking end of the available data range. * * This method will not have any effect when called on a chart object. * Since the chart can have a number of axes and series, each with its own * data, the meaning of "range" is very ambiguous. * * To zoom the chart use `zoom*` methods on its respective axes. * * @param range Range * @param skipRangeEvent Should rangechanged event not be triggered? * @param instantly Do not animate? * @return Actual modidied range (taking `maxZoomFactor` into account) */ Component.prototype.zoom = function (range, skipRangeEvent, instantly, declination) { var _this = this; if (skipRangeEvent === void 0) { skipRangeEvent = false; } if (instantly === void 0) { instantly = false; } var start = range.start; var end = range.end; var priority = range.priority; if (range.start == range.end) { range.start = range.start - 0.5 / this.maxZoomFactor; range.end = range.end + 0.5 / this.maxZoomFactor; } if (priority == "end" && end == 1 && start != 0) { if (start < this.start) { priority = "start"; } } if (priority == "start" && start == 0) { if (end > this.end) { priority = "end"; } } if (!$type.isNumber(declination)) { declination = this.maxZoomDeclination; } if (!$type.isNumber(start) || !$type.isNumber(end)) { return { start: this.start, end: this.end }; } if (this._finalStart != start || this._finalEnd != end) { var maxZoomFactor = this.maxZoomFactor / this.minZoomCount; var minZoomFactor = this.maxZoomFactor / this.maxZoomCount; // most likely we are dragging left scrollbar grip here, so we tend to modify end if (priority == "start") { if (this.maxZoomCount > 0) { // add to the end if (1 / (end - start) < minZoomFactor) { end = start + 1 / minZoomFactor; } } // add to the end if (1 / (end - start) > maxZoomFactor) { end = start + 1 / maxZoomFactor; } //unless end is > 0 if (end > 1 && end - start < 1 / maxZoomFactor) { //end = 1; start = end - 1 / maxZoomFactor; } } // most likely we are dragging right, so we modify left else { if (this.maxZoomCount > 0) { // add to the end if (1 / (end - start) < minZoomFactor) { start = end - 1 / minZoomFactor; } } // remove from start if (1 / (end - start) > maxZoomFactor) { if (start <= 0) { end = start + 1 / maxZoomFactor; } else { start = end - 1 / maxZoomFactor; } } if (start < 0 && end - start < 1 / maxZoomFactor) { //start = 0; end = start + 1 / maxZoomFactor; } } if (start < -declination) { start = -declination; } if (1 / (end - start) > maxZoomFactor) { end = start + 1 / maxZoomFactor; } if (end > 1 + declination) { end = 1 + declination; } if (1 / (end - start) > maxZoomFactor) { start = end - 1 / maxZoomFactor; } this._finalEnd = end; this._finalStart = start; this.skipRangeEvent = skipRangeEvent; this.dispatchImmediately("rangechangestarted"); if (this.rangeChangeDuration > 0 && !instantly) { // todo: maybe move this to Animation var rangeChangeAnimation = this.rangeChangeAnimation; if (rangeChangeAnimation && rangeChangeAnimation.progress < 1) { var options = rangeChangeAnimation.animationOptions; if (options.length > 1) { if (options[0].to == start && options[1].to == end) { return { start: start, end: end }; } else { if (!rangeChangeAnimation.isDisposed()) { rangeChangeAnimation.stop(); } } } } if (this.rangeChangeAnimation) { this.rangeChangeAnimation.kill(); } rangeChangeAnimation = this.animate([{ property: "start", to: start }, { property: "end", to: end }], this.rangeChangeDuration, this.rangeChangeEasing); this.rangeChangeAnimation = rangeChangeAnimation; if (rangeChangeAnimation && !rangeChangeAnimation.isFinished()) { rangeChangeAnimation.events.on("animationended", function () { _this.dispatchImmediately("rangechangeended"); }); } else { this.dispatchImmediately("rangechangeended"); } } else { this.start = start; this.end = end; this.dispatch("rangechangeended"); } } return { start: start, end: end }; }; /** * Zooms to specific data items using their index in data. * * This method will not have any effect when called on a chart object. * Since the chart can have a number of axes and series, each with its own * data, the meaning of "index" is very ambiguous. * * To zoom the chart use `zoom*` methods on its respective axes. * * @param startIndex Index of the starting data item * @param endIndex Index of the ending data item * @param skipRangeEvent Should rangechanged event not be triggered? * @param instantly Do not animate? */ Component.prototype.zoomToIndexes = function (startIndex, endIndex, skipRangeEvent, instantly) { if (!$type.isNumber(startIndex) || !$type.isNumber(endIndex)) { return; } var start = startIndex / this.dataItems.length; var end = endIndex / this.dataItems.length; this.zoom({ start: start, end: end }, skipRangeEvent, instantly); }; Object.defineProperty(Component.prototype, "zoomFactor", { /** * A current zoom factor (0-1). 1 meaning fully zoomed out. (showing all of * the available data) * * @return Zoom factor */ get: function () { return $math.fitToRange(1 / (this.end - this.start), 1, this.maxZoomFactor); }, enumerable: true, configurable: true }); Object.defineProperty(Component.prototype, "maxZoomFactor", { /** * @return Maximum zoomFactor */ get: function () { return this.getPropertyValue("maxZoomFactor"); }, /** * Max available `zoomFactor`. * * The element will not allow zoom to occur beyond this factor. * * [[DateAxis]] and [[CategoryAxis]] calculate this atutomatically so that * category axis could be zoomed to one category and date axis allows to be * zoomed up to one base interval. * * In case you want to restrict category or date axis to be zoomed to more * than one category or more than one base interval, use `minZoomCount` * property (set it to `> 1`). * * Default value of [[ValueAxis]]'s `maxZoomFactor` is `1000`. * * Feel free to modify it to allow bigger zoom or to restrict zooming. * * @param value Maximum zoomFactor */ set: function (value) { if (this.setPropertyValue("maxZoomFactor", value)) { if (value == 1) { this.maxZoomDeclination = 0; } this.invalidateDataRange(); } }, enumerable: true, configurable: true }); Object.defineProperty(Component.prototype, "maxZoomDeclination", { /** * @ignore * @return Maximum zoom declination */ get: function () { return this.getPropertyValue("maxZoomDeclination"); }, /** * Max zoom declination. * * @ignore * @default 1 * @param value Maximum zoom declination */ set: function (value) { if (this.setPropertyValue("maxZoomDeclination", value)) { this.invalidateDataRange(); } }, enumerable: true, configurable: true }); Object.defineProperty(Component.prototype, "startIndex", { /** * Current starting index. * * @return Start index */ get: function () { if (!$type.isNumber(this._startIndex)) { this._startIndex = 0; } return this._startIndex; }, /** * Sets current starting index. * * @ignore Exclude from docs * @param value Start index */