UNPKG

echarts

Version:

Apache ECharts is a powerful, interactive charting and data visualization library for browser

1,295 lines (1,291 loc) 84.4 kB
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /** * AUTO-GENERATED FILE. DO NOT MODIFY. */ import { __extends } from "tslib"; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import * as zrender from 'zrender/lib/zrender.js'; import { assert, each, isFunction, isObject, indexOf, bind, clone, setAsPrimitive, extend, createHashMap, map, defaults, isDom, isArray, noop, isString, retrieve2 } from 'zrender/lib/core/util.js'; import env from 'zrender/lib/core/env.js'; import timsort from 'zrender/lib/core/timsort.js'; import Eventful from 'zrender/lib/core/Eventful.js'; import GlobalModel from '../model/Global.js'; import ExtensionAPI from './ExtensionAPI.js'; import CoordinateSystemManager from './CoordinateSystem.js'; import OptionManager from '../model/OptionManager.js'; import backwardCompat from '../preprocessor/backwardCompat.js'; import dataStack from '../processor/dataStack.js'; import SeriesModel from '../model/Series.js'; import ComponentView from '../view/Component.js'; import ChartView from '../view/Chart.js'; import * as graphic from '../util/graphic.js'; import { getECData } from '../util/innerStore.js'; import { isHighDownDispatcher, HOVER_STATE_EMPHASIS, HOVER_STATE_BLUR, blurSeriesFromHighlightPayload, toggleSelectionFromPayload, updateSeriesElementSelection, getAllSelectedIndices, isSelectChangePayload, isHighDownPayload, HIGHLIGHT_ACTION_TYPE, DOWNPLAY_ACTION_TYPE, SELECT_ACTION_TYPE, UNSELECT_ACTION_TYPE, TOGGLE_SELECT_ACTION_TYPE, savePathStates, enterEmphasis, leaveEmphasis, leaveBlur, enterSelect, leaveSelect, enterBlur, allLeaveBlur, findComponentHighDownDispatchers, blurComponent, handleGlobalMouseOverForHighDown, handleGlobalMouseOutForHighDown } from '../util/states.js'; import * as modelUtil from '../util/model.js'; import { throttle } from '../util/throttle.js'; import { seriesStyleTask, dataStyleTask, dataColorPaletteTask } from '../visual/style.js'; import loadingDefault from '../loading/default.js'; import Scheduler from './Scheduler.js'; import lightTheme from '../theme/light.js'; import darkTheme from '../theme/dark.js'; import { parseClassType } from '../util/clazz.js'; import { ECEventProcessor } from '../util/ECEventProcessor.js'; import { seriesSymbolTask, dataSymbolTask } from '../visual/symbol.js'; import { getVisualFromData, getItemVisualFromData } from '../visual/helper.js'; import { deprecateLog, deprecateReplaceLog, error, warn } from '../util/log.js'; import { handleLegacySelectEvents } from '../legacy/dataSelectAction.js'; import { registerExternalTransform } from '../data/helper/transform.js'; import { createLocaleObject, SYSTEM_LANG } from './locale.js'; import { findEventDispatcher } from '../util/event.js'; import decal from '../visual/decal.js'; import lifecycle from './lifecycle.js'; import { platformApi, setPlatformAPI } from 'zrender/lib/core/platform.js'; import { getImpl } from './impl.js'; export var version = '5.6.0'; export var dependencies = { zrender: '5.6.1' }; var TEST_FRAME_REMAIN_TIME = 1; var PRIORITY_PROCESSOR_SERIES_FILTER = 800; // Some data processors depends on the stack result dimension (to calculate data extent). // So data stack stage should be in front of data processing stage. var PRIORITY_PROCESSOR_DATASTACK = 900; // "Data filter" will block the stream, so it should be // put at the beginning of data processing. var PRIORITY_PROCESSOR_FILTER = 1000; var PRIORITY_PROCESSOR_DEFAULT = 2000; var PRIORITY_PROCESSOR_STATISTIC = 5000; var PRIORITY_VISUAL_LAYOUT = 1000; var PRIORITY_VISUAL_PROGRESSIVE_LAYOUT = 1100; var PRIORITY_VISUAL_GLOBAL = 2000; var PRIORITY_VISUAL_CHART = 3000; var PRIORITY_VISUAL_COMPONENT = 4000; // Visual property in data. Greater than `PRIORITY_VISUAL_COMPONENT` to enable to // overwrite the viusal result of component (like `visualMap`) // using data item specific setting (like itemStyle.xxx on data item) var PRIORITY_VISUAL_CHART_DATA_CUSTOM = 4500; // Greater than `PRIORITY_VISUAL_CHART_DATA_CUSTOM` to enable to layout based on // visual result like `symbolSize`. var PRIORITY_VISUAL_POST_CHART_LAYOUT = 4600; var PRIORITY_VISUAL_BRUSH = 5000; var PRIORITY_VISUAL_ARIA = 6000; var PRIORITY_VISUAL_DECAL = 7000; export var PRIORITY = { PROCESSOR: { FILTER: PRIORITY_PROCESSOR_FILTER, SERIES_FILTER: PRIORITY_PROCESSOR_SERIES_FILTER, STATISTIC: PRIORITY_PROCESSOR_STATISTIC }, VISUAL: { LAYOUT: PRIORITY_VISUAL_LAYOUT, PROGRESSIVE_LAYOUT: PRIORITY_VISUAL_PROGRESSIVE_LAYOUT, GLOBAL: PRIORITY_VISUAL_GLOBAL, CHART: PRIORITY_VISUAL_CHART, POST_CHART_LAYOUT: PRIORITY_VISUAL_POST_CHART_LAYOUT, COMPONENT: PRIORITY_VISUAL_COMPONENT, BRUSH: PRIORITY_VISUAL_BRUSH, CHART_ITEM: PRIORITY_VISUAL_CHART_DATA_CUSTOM, ARIA: PRIORITY_VISUAL_ARIA, DECAL: PRIORITY_VISUAL_DECAL } }; // Main process have three entries: `setOption`, `dispatchAction` and `resize`, // where they must not be invoked nestedly, except the only case: invoke // dispatchAction with updateMethod "none" in main process. // This flag is used to carry out this rule. // All events will be triggered out side main process (i.e. when !this[IN_MAIN_PROCESS]). var IN_MAIN_PROCESS_KEY = '__flagInMainProcess'; var PENDING_UPDATE = '__pendingUpdate'; var STATUS_NEEDS_UPDATE_KEY = '__needsUpdateStatus'; var ACTION_REG = /^[a-zA-Z0-9_]+$/; var CONNECT_STATUS_KEY = '__connectUpdateStatus'; var CONNECT_STATUS_PENDING = 0; var CONNECT_STATUS_UPDATING = 1; var CONNECT_STATUS_UPDATED = 2; ; ; function createRegisterEventWithLowercaseECharts(method) { return function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } if (this.isDisposed()) { disposedWarning(this.id); return; } return toLowercaseNameAndCallEventful(this, method, args); }; } function createRegisterEventWithLowercaseMessageCenter(method) { return function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return toLowercaseNameAndCallEventful(this, method, args); }; } function toLowercaseNameAndCallEventful(host, method, args) { // `args[0]` is event name. Event name is all lowercase. args[0] = args[0] && args[0].toLowerCase(); return Eventful.prototype[method].apply(host, args); } var MessageCenter = /** @class */function (_super) { __extends(MessageCenter, _super); function MessageCenter() { return _super !== null && _super.apply(this, arguments) || this; } return MessageCenter; }(Eventful); var messageCenterProto = MessageCenter.prototype; messageCenterProto.on = createRegisterEventWithLowercaseMessageCenter('on'); messageCenterProto.off = createRegisterEventWithLowercaseMessageCenter('off'); // --------------------------------------- // Internal method names for class ECharts // --------------------------------------- var prepare; var prepareView; var updateDirectly; var updateMethods; var doConvertPixel; var updateStreamModes; var doDispatchAction; var flushPendingActions; var triggerUpdatedEvent; var bindRenderedEvent; var bindMouseEvent; var render; var renderComponents; var renderSeries; var createExtensionAPI; var enableConnect; var markStatusToUpdate; var applyChangedStates; var ECharts = /** @class */function (_super) { __extends(ECharts, _super); function ECharts(dom, // Theme name or themeOption. theme, opts) { var _this = _super.call(this, new ECEventProcessor()) || this; _this._chartsViews = []; _this._chartsMap = {}; _this._componentsViews = []; _this._componentsMap = {}; // Can't dispatch action during rendering procedure _this._pendingActions = []; opts = opts || {}; // Get theme by name if (isString(theme)) { theme = themeStorage[theme]; } _this._dom = dom; var defaultRenderer = 'canvas'; var defaultCoarsePointer = 'auto'; var defaultUseDirtyRect = false; if (process.env.NODE_ENV !== 'production') { var root = /* eslint-disable-next-line */ env.hasGlobalWindow ? window : global; if (root) { defaultRenderer = retrieve2(root.__ECHARTS__DEFAULT__RENDERER__, defaultRenderer); defaultCoarsePointer = retrieve2(root.__ECHARTS__DEFAULT__COARSE_POINTER, defaultCoarsePointer); defaultUseDirtyRect = retrieve2(root.__ECHARTS__DEFAULT__USE_DIRTY_RECT__, defaultUseDirtyRect); } } if (opts.ssr) { zrender.registerSSRDataGetter(function (el) { var ecData = getECData(el); var dataIndex = ecData.dataIndex; if (dataIndex == null) { return; } var hashMap = createHashMap(); hashMap.set('series_index', ecData.seriesIndex); hashMap.set('data_index', dataIndex); ecData.ssrType && hashMap.set('ssr_type', ecData.ssrType); return hashMap; }); } var zr = _this._zr = zrender.init(dom, { renderer: opts.renderer || defaultRenderer, devicePixelRatio: opts.devicePixelRatio, width: opts.width, height: opts.height, ssr: opts.ssr, useDirtyRect: retrieve2(opts.useDirtyRect, defaultUseDirtyRect), useCoarsePointer: retrieve2(opts.useCoarsePointer, defaultCoarsePointer), pointerSize: opts.pointerSize }); _this._ssr = opts.ssr; // Expect 60 fps. _this._throttledZrFlush = throttle(bind(zr.flush, zr), 17); theme = clone(theme); theme && backwardCompat(theme, true); _this._theme = theme; _this._locale = createLocaleObject(opts.locale || SYSTEM_LANG); _this._coordSysMgr = new CoordinateSystemManager(); var api = _this._api = createExtensionAPI(_this); // Sort on demand function prioritySortFunc(a, b) { return a.__prio - b.__prio; } timsort(visualFuncs, prioritySortFunc); timsort(dataProcessorFuncs, prioritySortFunc); _this._scheduler = new Scheduler(_this, api, dataProcessorFuncs, visualFuncs); _this._messageCenter = new MessageCenter(); // Init mouse events _this._initEvents(); // In case some people write `window.onresize = chart.resize` _this.resize = bind(_this.resize, _this); zr.animation.on('frame', _this._onframe, _this); bindRenderedEvent(zr, _this); bindMouseEvent(zr, _this); // ECharts instance can be used as value. setAsPrimitive(_this); return _this; } ECharts.prototype._onframe = function () { if (this._disposed) { return; } applyChangedStates(this); var scheduler = this._scheduler; // Lazy update if (this[PENDING_UPDATE]) { var silent = this[PENDING_UPDATE].silent; this[IN_MAIN_PROCESS_KEY] = true; try { prepare(this); updateMethods.update.call(this, null, this[PENDING_UPDATE].updateParams); } catch (e) { this[IN_MAIN_PROCESS_KEY] = false; this[PENDING_UPDATE] = null; throw e; } // At present, in each frame, zrender performs: // (1) animation step forward. // (2) trigger('frame') (where this `_onframe` is called) // (3) zrender flush (render). // If we do nothing here, since we use `setToFinal: true`, the step (3) above // will render the final state of the elements before the real animation started. this._zr.flush(); this[IN_MAIN_PROCESS_KEY] = false; this[PENDING_UPDATE] = null; flushPendingActions.call(this, silent); triggerUpdatedEvent.call(this, silent); } // Avoid do both lazy update and progress in one frame. else if (scheduler.unfinished) { // Stream progress. var remainTime = TEST_FRAME_REMAIN_TIME; var ecModel = this._model; var api = this._api; scheduler.unfinished = false; do { var startTime = +new Date(); scheduler.performSeriesTasks(ecModel); // Currently dataProcessorFuncs do not check threshold. scheduler.performDataProcessorTasks(ecModel); updateStreamModes(this, ecModel); // Do not update coordinate system here. Because that coord system update in // each frame is not a good user experience. So we follow the rule that // the extent of the coordinate system is determined in the first frame (the // frame is executed immediately after task reset. // this._coordSysMgr.update(ecModel, api); // console.log('--- ec frame visual ---', remainTime); scheduler.performVisualTasks(ecModel); renderSeries(this, this._model, api, 'remain', {}); remainTime -= +new Date() - startTime; } while (remainTime > 0 && scheduler.unfinished); // Call flush explicitly for trigger finished event. if (!scheduler.unfinished) { this._zr.flush(); } // Else, zr flushing be ensue within the same frame, // because zr flushing is after onframe event. } }; ECharts.prototype.getDom = function () { return this._dom; }; ECharts.prototype.getId = function () { return this.id; }; ECharts.prototype.getZr = function () { return this._zr; }; ECharts.prototype.isSSR = function () { return this._ssr; }; /* eslint-disable-next-line */ ECharts.prototype.setOption = function (option, notMerge, lazyUpdate) { if (this[IN_MAIN_PROCESS_KEY]) { if (process.env.NODE_ENV !== 'production') { error('`setOption` should not be called during main process.'); } return; } if (this._disposed) { disposedWarning(this.id); return; } var silent; var replaceMerge; var transitionOpt; if (isObject(notMerge)) { lazyUpdate = notMerge.lazyUpdate; silent = notMerge.silent; replaceMerge = notMerge.replaceMerge; transitionOpt = notMerge.transition; notMerge = notMerge.notMerge; } this[IN_MAIN_PROCESS_KEY] = true; if (!this._model || notMerge) { var optionManager = new OptionManager(this._api); var theme = this._theme; var ecModel = this._model = new GlobalModel(); ecModel.scheduler = this._scheduler; ecModel.ssr = this._ssr; ecModel.init(null, null, null, theme, this._locale, optionManager); } this._model.setOption(option, { replaceMerge: replaceMerge }, optionPreprocessorFuncs); var updateParams = { seriesTransition: transitionOpt, optionChanged: true }; if (lazyUpdate) { this[PENDING_UPDATE] = { silent: silent, updateParams: updateParams }; this[IN_MAIN_PROCESS_KEY] = false; // `setOption(option, {lazyMode: true})` may be called when zrender has been slept. // It should wake it up to make sure zrender start to render at the next frame. this.getZr().wakeUp(); } else { try { prepare(this); updateMethods.update.call(this, null, updateParams); } catch (e) { this[PENDING_UPDATE] = null; this[IN_MAIN_PROCESS_KEY] = false; throw e; } // Ensure zr refresh sychronously, and then pixel in canvas can be // fetched after `setOption`. if (!this._ssr) { // not use flush when using ssr mode. this._zr.flush(); } this[PENDING_UPDATE] = null; this[IN_MAIN_PROCESS_KEY] = false; flushPendingActions.call(this, silent); triggerUpdatedEvent.call(this, silent); } }; /** * @deprecated */ ECharts.prototype.setTheme = function () { deprecateLog('ECharts#setTheme() is DEPRECATED in ECharts 3.0'); }; // We don't want developers to use getModel directly. ECharts.prototype.getModel = function () { return this._model; }; ECharts.prototype.getOption = function () { return this._model && this._model.getOption(); }; ECharts.prototype.getWidth = function () { return this._zr.getWidth(); }; ECharts.prototype.getHeight = function () { return this._zr.getHeight(); }; ECharts.prototype.getDevicePixelRatio = function () { return this._zr.painter.dpr /* eslint-disable-next-line */ || env.hasGlobalWindow && window.devicePixelRatio || 1; }; /** * Get canvas which has all thing rendered * @deprecated Use renderToCanvas instead. */ ECharts.prototype.getRenderedCanvas = function (opts) { if (process.env.NODE_ENV !== 'production') { deprecateReplaceLog('getRenderedCanvas', 'renderToCanvas'); } return this.renderToCanvas(opts); }; ECharts.prototype.renderToCanvas = function (opts) { opts = opts || {}; var painter = this._zr.painter; if (process.env.NODE_ENV !== 'production') { if (painter.type !== 'canvas') { throw new Error('renderToCanvas can only be used in the canvas renderer.'); } } return painter.getRenderedCanvas({ backgroundColor: opts.backgroundColor || this._model.get('backgroundColor'), pixelRatio: opts.pixelRatio || this.getDevicePixelRatio() }); }; ECharts.prototype.renderToSVGString = function (opts) { opts = opts || {}; var painter = this._zr.painter; if (process.env.NODE_ENV !== 'production') { if (painter.type !== 'svg') { throw new Error('renderToSVGString can only be used in the svg renderer.'); } } return painter.renderToString({ useViewBox: opts.useViewBox }); }; /** * Get svg data url */ ECharts.prototype.getSvgDataURL = function () { if (!env.svgSupported) { return; } var zr = this._zr; var list = zr.storage.getDisplayList(); // Stop animations each(list, function (el) { el.stopAnimation(null, true); }); return zr.painter.toDataURL(); }; ECharts.prototype.getDataURL = function (opts) { if (this._disposed) { disposedWarning(this.id); return; } opts = opts || {}; var excludeComponents = opts.excludeComponents; var ecModel = this._model; var excludesComponentViews = []; var self = this; each(excludeComponents, function (componentType) { ecModel.eachComponent({ mainType: componentType }, function (component) { var view = self._componentsMap[component.__viewId]; if (!view.group.ignore) { excludesComponentViews.push(view); view.group.ignore = true; } }); }); var url = this._zr.painter.getType() === 'svg' ? this.getSvgDataURL() : this.renderToCanvas(opts).toDataURL('image/' + (opts && opts.type || 'png')); each(excludesComponentViews, function (view) { view.group.ignore = false; }); return url; }; ECharts.prototype.getConnectedDataURL = function (opts) { if (this._disposed) { disposedWarning(this.id); return; } var isSvg = opts.type === 'svg'; var groupId = this.group; var mathMin = Math.min; var mathMax = Math.max; var MAX_NUMBER = Infinity; if (connectedGroups[groupId]) { var left_1 = MAX_NUMBER; var top_1 = MAX_NUMBER; var right_1 = -MAX_NUMBER; var bottom_1 = -MAX_NUMBER; var canvasList_1 = []; var dpr_1 = opts && opts.pixelRatio || this.getDevicePixelRatio(); each(instances, function (chart, id) { if (chart.group === groupId) { var canvas = isSvg ? chart.getZr().painter.getSvgDom().innerHTML : chart.renderToCanvas(clone(opts)); var boundingRect = chart.getDom().getBoundingClientRect(); left_1 = mathMin(boundingRect.left, left_1); top_1 = mathMin(boundingRect.top, top_1); right_1 = mathMax(boundingRect.right, right_1); bottom_1 = mathMax(boundingRect.bottom, bottom_1); canvasList_1.push({ dom: canvas, left: boundingRect.left, top: boundingRect.top }); } }); left_1 *= dpr_1; top_1 *= dpr_1; right_1 *= dpr_1; bottom_1 *= dpr_1; var width = right_1 - left_1; var height = bottom_1 - top_1; var targetCanvas = platformApi.createCanvas(); var zr_1 = zrender.init(targetCanvas, { renderer: isSvg ? 'svg' : 'canvas' }); zr_1.resize({ width: width, height: height }); if (isSvg) { var content_1 = ''; each(canvasList_1, function (item) { var x = item.left - left_1; var y = item.top - top_1; content_1 += '<g transform="translate(' + x + ',' + y + ')">' + item.dom + '</g>'; }); zr_1.painter.getSvgRoot().innerHTML = content_1; if (opts.connectedBackgroundColor) { zr_1.painter.setBackgroundColor(opts.connectedBackgroundColor); } zr_1.refreshImmediately(); return zr_1.painter.toDataURL(); } else { // Background between the charts if (opts.connectedBackgroundColor) { zr_1.add(new graphic.Rect({ shape: { x: 0, y: 0, width: width, height: height }, style: { fill: opts.connectedBackgroundColor } })); } each(canvasList_1, function (item) { var img = new graphic.Image({ style: { x: item.left * dpr_1 - left_1, y: item.top * dpr_1 - top_1, image: item.dom } }); zr_1.add(img); }); zr_1.refreshImmediately(); return targetCanvas.toDataURL('image/' + (opts && opts.type || 'png')); } } else { return this.getDataURL(opts); } }; ECharts.prototype.convertToPixel = function (finder, value) { return doConvertPixel(this, 'convertToPixel', finder, value); }; ECharts.prototype.convertFromPixel = function (finder, value) { return doConvertPixel(this, 'convertFromPixel', finder, value); }; /** * Is the specified coordinate systems or components contain the given pixel point. * @param {Array|number} value * @return {boolean} result */ ECharts.prototype.containPixel = function (finder, value) { if (this._disposed) { disposedWarning(this.id); return; } var ecModel = this._model; var result; var findResult = modelUtil.parseFinder(ecModel, finder); each(findResult, function (models, key) { key.indexOf('Models') >= 0 && each(models, function (model) { var coordSys = model.coordinateSystem; if (coordSys && coordSys.containPoint) { result = result || !!coordSys.containPoint(value); } else if (key === 'seriesModels') { var view = this._chartsMap[model.__viewId]; if (view && view.containPoint) { result = result || view.containPoint(value, model); } else { if (process.env.NODE_ENV !== 'production') { warn(key + ': ' + (view ? 'The found component do not support containPoint.' : 'No view mapping to the found component.')); } } } else { if (process.env.NODE_ENV !== 'production') { warn(key + ': containPoint is not supported'); } } }, this); }, this); return !!result; }; /** * Get visual from series or data. * @param finder * If string, e.g., 'series', means {seriesIndex: 0}. * If Object, could contain some of these properties below: * { * seriesIndex / seriesId / seriesName, * dataIndex / dataIndexInside * } * If dataIndex is not specified, series visual will be fetched, * but not data item visual. * If all of seriesIndex, seriesId, seriesName are not specified, * visual will be fetched from first series. * @param visualType 'color', 'symbol', 'symbolSize' */ ECharts.prototype.getVisual = function (finder, visualType) { var ecModel = this._model; var parsedFinder = modelUtil.parseFinder(ecModel, finder, { defaultMainType: 'series' }); var seriesModel = parsedFinder.seriesModel; if (process.env.NODE_ENV !== 'production') { if (!seriesModel) { warn('There is no specified series model'); } } var data = seriesModel.getData(); var dataIndexInside = parsedFinder.hasOwnProperty('dataIndexInside') ? parsedFinder.dataIndexInside : parsedFinder.hasOwnProperty('dataIndex') ? data.indexOfRawIndex(parsedFinder.dataIndex) : null; return dataIndexInside != null ? getItemVisualFromData(data, dataIndexInside, visualType) : getVisualFromData(data, visualType); }; /** * Get view of corresponding component model */ ECharts.prototype.getViewOfComponentModel = function (componentModel) { return this._componentsMap[componentModel.__viewId]; }; /** * Get view of corresponding series model */ ECharts.prototype.getViewOfSeriesModel = function (seriesModel) { return this._chartsMap[seriesModel.__viewId]; }; ECharts.prototype._initEvents = function () { var _this = this; each(MOUSE_EVENT_NAMES, function (eveName) { var handler = function (e) { var ecModel = _this.getModel(); var el = e.target; var params; var isGlobalOut = eveName === 'globalout'; // no e.target when 'globalout'. if (isGlobalOut) { params = {}; } else { el && findEventDispatcher(el, function (parent) { var ecData = getECData(parent); if (ecData && ecData.dataIndex != null) { var dataModel = ecData.dataModel || ecModel.getSeriesByIndex(ecData.seriesIndex); params = dataModel && dataModel.getDataParams(ecData.dataIndex, ecData.dataType, el) || {}; return true; } // If element has custom eventData of components else if (ecData.eventData) { params = extend({}, ecData.eventData); return true; } }, true); } // Contract: if params prepared in mouse event, // these properties must be specified: // { // componentType: string (component main type) // componentIndex: number // } // Otherwise event query can not work. if (params) { var componentType = params.componentType; var componentIndex = params.componentIndex; // Special handling for historic reason: when trigger by // markLine/markPoint/markArea, the componentType is // 'markLine'/'markPoint'/'markArea', but we should better // enable them to be queried by seriesIndex, since their // option is set in each series. if (componentType === 'markLine' || componentType === 'markPoint' || componentType === 'markArea') { componentType = 'series'; componentIndex = params.seriesIndex; } var model = componentType && componentIndex != null && ecModel.getComponent(componentType, componentIndex); var view = model && _this[model.mainType === 'series' ? '_chartsMap' : '_componentsMap'][model.__viewId]; if (process.env.NODE_ENV !== 'production') { // `event.componentType` and `event[componentTpype + 'Index']` must not // be missed, otherwise there is no way to distinguish source component. // See `dataFormat.getDataParams`. if (!isGlobalOut && !(model && view)) { warn('model or view can not be found by params'); } } params.event = e; params.type = eveName; _this._$eventProcessor.eventInfo = { targetEl: el, packedEvent: params, model: model, view: view }; _this.trigger(eveName, params); } }; // Consider that some component (like tooltip, brush, ...) // register zr event handler, but user event handler might // do anything, such as call `setOption` or `dispatchAction`, // which probably update any of the content and probably // cause problem if it is called previous other inner handlers. handler.zrEventfulCallAtLast = true; _this._zr.on(eveName, handler, _this); }); each(eventActionMap, function (actionType, eventType) { _this._messageCenter.on(eventType, function (event) { this.trigger(eventType, event); }, _this); }); // Extra events // TODO register? each(['selectchanged'], function (eventType) { _this._messageCenter.on(eventType, function (event) { this.trigger(eventType, event); }, _this); }); handleLegacySelectEvents(this._messageCenter, this, this._api); }; ECharts.prototype.isDisposed = function () { return this._disposed; }; ECharts.prototype.clear = function () { if (this._disposed) { disposedWarning(this.id); return; } this.setOption({ series: [] }, true); }; ECharts.prototype.dispose = function () { if (this._disposed) { disposedWarning(this.id); return; } this._disposed = true; var dom = this.getDom(); if (dom) { modelUtil.setAttribute(this.getDom(), DOM_ATTRIBUTE_KEY, ''); } var chart = this; var api = chart._api; var ecModel = chart._model; each(chart._componentsViews, function (component) { component.dispose(ecModel, api); }); each(chart._chartsViews, function (chart) { chart.dispose(ecModel, api); }); // Dispose after all views disposed chart._zr.dispose(); // Set properties to null. // To reduce the memory cost in case the top code still holds this instance unexpectedly. chart._dom = chart._model = chart._chartsMap = chart._componentsMap = chart._chartsViews = chart._componentsViews = chart._scheduler = chart._api = chart._zr = chart._throttledZrFlush = chart._theme = chart._coordSysMgr = chart._messageCenter = null; delete instances[chart.id]; }; /** * Resize the chart */ ECharts.prototype.resize = function (opts) { if (this[IN_MAIN_PROCESS_KEY]) { if (process.env.NODE_ENV !== 'production') { error('`resize` should not be called during main process.'); } return; } if (this._disposed) { disposedWarning(this.id); return; } this._zr.resize(opts); var ecModel = this._model; // Resize loading effect this._loadingFX && this._loadingFX.resize(); if (!ecModel) { return; } var needPrepare = ecModel.resetOption('media'); var silent = opts && opts.silent; // There is some real cases that: // chart.setOption(option, { lazyUpdate: true }); // chart.resize(); if (this[PENDING_UPDATE]) { if (silent == null) { silent = this[PENDING_UPDATE].silent; } needPrepare = true; this[PENDING_UPDATE] = null; } this[IN_MAIN_PROCESS_KEY] = true; try { needPrepare && prepare(this); updateMethods.update.call(this, { type: 'resize', animation: extend({ // Disable animation duration: 0 }, opts && opts.animation) }); } catch (e) { this[IN_MAIN_PROCESS_KEY] = false; throw e; } this[IN_MAIN_PROCESS_KEY] = false; flushPendingActions.call(this, silent); triggerUpdatedEvent.call(this, silent); }; ECharts.prototype.showLoading = function (name, cfg) { if (this._disposed) { disposedWarning(this.id); return; } if (isObject(name)) { cfg = name; name = ''; } name = name || 'default'; this.hideLoading(); if (!loadingEffects[name]) { if (process.env.NODE_ENV !== 'production') { warn('Loading effects ' + name + ' not exists.'); } return; } var el = loadingEffects[name](this._api, cfg); var zr = this._zr; this._loadingFX = el; zr.add(el); }; /** * Hide loading effect */ ECharts.prototype.hideLoading = function () { if (this._disposed) { disposedWarning(this.id); return; } this._loadingFX && this._zr.remove(this._loadingFX); this._loadingFX = null; }; ECharts.prototype.makeActionFromEvent = function (eventObj) { var payload = extend({}, eventObj); payload.type = eventActionMap[eventObj.type]; return payload; }; /** * @param opt If pass boolean, means opt.silent * @param opt.silent Default `false`. Whether trigger events. * @param opt.flush Default `undefined`. * true: Flush immediately, and then pixel in canvas can be fetched * immediately. Caution: it might affect performance. * false: Not flush. * undefined: Auto decide whether perform flush. */ ECharts.prototype.dispatchAction = function (payload, opt) { if (this._disposed) { disposedWarning(this.id); return; } if (!isObject(opt)) { opt = { silent: !!opt }; } if (!actions[payload.type]) { return; } // Avoid dispatch action before setOption. Especially in `connect`. if (!this._model) { return; } // May dispatchAction in rendering procedure if (this[IN_MAIN_PROCESS_KEY]) { this._pendingActions.push(payload); return; } var silent = opt.silent; doDispatchAction.call(this, payload, silent); var flush = opt.flush; if (flush) { this._zr.flush(); } else if (flush !== false && env.browser.weChat) { // In WeChat embedded browser, `requestAnimationFrame` and `setInterval` // hang when sliding page (on touch event), which cause that zr does not // refresh until user interaction finished, which is not expected. // But `dispatchAction` may be called too frequently when pan on touch // screen, which impacts performance if do not throttle them. this._throttledZrFlush(); } flushPendingActions.call(this, silent); triggerUpdatedEvent.call(this, silent); }; ECharts.prototype.updateLabelLayout = function () { lifecycle.trigger('series:layoutlabels', this._model, this._api, { // Not adding series labels. // TODO updatedSeries: [] }); }; ECharts.prototype.appendData = function (params) { if (this._disposed) { disposedWarning(this.id); return; } var seriesIndex = params.seriesIndex; var ecModel = this.getModel(); var seriesModel = ecModel.getSeriesByIndex(seriesIndex); if (process.env.NODE_ENV !== 'production') { assert(params.data && seriesModel); } seriesModel.appendData(params); // Note: `appendData` does not support that update extent of coordinate // system, util some scenario require that. In the expected usage of // `appendData`, the initial extent of coordinate system should better // be fixed by axis `min`/`max` setting or initial data, otherwise if // the extent changed while `appendData`, the location of the painted // graphic elements have to be changed, which make the usage of // `appendData` meaningless. this._scheduler.unfinished = true; this.getZr().wakeUp(); }; // A work around for no `internal` modifier in ts yet but // need to strictly hide private methods to JS users. ECharts.internalField = function () { prepare = function (ecIns) { var scheduler = ecIns._scheduler; scheduler.restorePipelines(ecIns._model); scheduler.prepareStageTasks(); prepareView(ecIns, true); prepareView(ecIns, false); scheduler.plan(); }; /** * Prepare view instances of charts and components */ prepareView = function (ecIns, isComponent) { var ecModel = ecIns._model; var scheduler = ecIns._scheduler; var viewList = isComponent ? ecIns._componentsViews : ecIns._chartsViews; var viewMap = isComponent ? ecIns._componentsMap : ecIns._chartsMap; var zr = ecIns._zr; var api = ecIns._api; for (var i = 0; i < viewList.length; i++) { viewList[i].__alive = false; } isComponent ? ecModel.eachComponent(function (componentType, model) { componentType !== 'series' && doPrepare(model); }) : ecModel.eachSeries(doPrepare); function doPrepare(model) { // By default view will be reused if possible for the case that `setOption` with "notMerge" // mode and need to enable transition animation. (Usually, when they have the same id, or // especially no id but have the same type & name & index. See the `model.id` generation // rule in `makeIdAndName` and `viewId` generation rule here). // But in `replaceMerge` mode, this feature should be able to disabled when it is clear that // the new model has nothing to do with the old model. var requireNewView = model.__requireNewView; // This command should not work twice. model.__requireNewView = false; // Consider: id same and type changed. var viewId = '_ec_' + model.id + '_' + model.type; var view = !requireNewView && viewMap[viewId]; if (!view) { var classType = parseClassType(model.type); var Clazz = isComponent ? ComponentView.getClass(classType.main, classType.sub) : // FIXME:TS // (ChartView as ChartViewConstructor).getClass('series', classType.sub) // For backward compat, still support a chart type declared as only subType // like "liquidfill", but recommend "series.liquidfill" // But need a base class to make a type series. ChartView.getClass(classType.sub); if (process.env.NODE_ENV !== 'production') { assert(Clazz, classType.sub + ' does not exist.'); } view = new Clazz(); view.init(ecModel, api); viewMap[viewId] = view; viewList.push(view); zr.add(view.group); } model.__viewId = view.__id = viewId; view.__alive = true; view.__model = model; view.group.__ecComponentInfo = { mainType: model.mainType, index: model.componentIndex }; !isComponent && scheduler.prepareView(view, model, ecModel, api); } for (var i = 0; i < viewList.length;) { var view = viewList[i]; if (!view.__alive) { !isComponent && view.renderTask.dispose(); zr.remove(view.group); view.dispose(ecModel, api); viewList.splice(i, 1); if (viewMap[view.__id] === view) { delete viewMap[view.__id]; } view.__id = view.group.__ecComponentInfo = null; } else { i++; } } }; updateDirectly = function (ecIns, method, payload, mainType, subType) { var ecModel = ecIns._model; ecModel.setUpdatePayload(payload); // broadcast if (!mainType) { // FIXME // Chart will not be update directly here, except set dirty. // But there is no such scenario now. each([].concat(ecIns._componentsViews).concat(ecIns._chartsViews), callView); return; } var query = {}; query[mainType + 'Id'] = payload[mainType + 'Id']; query[mainType + 'Index'] = payload[mainType + 'Index']; query[mainType + 'Name'] = payload[mainType + 'Name']; var condition = { mainType: mainType, query: query }; subType && (condition.subType = subType); // subType may be '' by parseClassType; var excludeSeriesId = payload.excludeSeriesId; var excludeSeriesIdMap; if (excludeSeriesId != null) { excludeSeriesIdMap = createHashMap(); each(modelUtil.normalizeToArray(excludeSeriesId), function (id) { var modelId = modelUtil.convertOptionIdName(id, null); if (modelId != null) { excludeSeriesIdMap.set(modelId, true); } }); } // If dispatchAction before setOption, do nothing. ecModel && ecModel.eachComponent(condition, function (model) { var isExcluded = excludeSeriesIdMap && excludeSeriesIdMap.get(model.id) != null; if (isExcluded) { return; } ; if (isHighDownPayload(payload)) { if (model instanceof SeriesModel) { if (payload.type === HIGHLIGHT_ACTION_TYPE && !payload.notBlur && !model.get(['emphasis', 'disabled'])) { blurSeriesFromHighlightPayload(model, payload, ecIns._api); } } else { var _a = findComponentHighDownDispatchers(model.mainType, model.componentIndex, payload.name, ecIns._api), focusSelf = _a.focusSelf, dispatchers = _a.dispatchers; if (payload.type === HIGHLIGHT_ACTION_TYPE && focusSelf && !payload.notBlur) { blurComponent(model.mainType, model.componentIndex, ecIns._api); } // PENDING: // Whether to put this "enter emphasis" code in `ComponentView`, // which will be the same as `ChartView` but might be not necessary // and will be far from this logic. if (dispatchers) { each(dispatchers, function (dispatcher) { payload.type === HIGHLIGHT_ACTION_TYPE ? enterEmphasis(dispatcher) : leaveEmphasis(dispatcher); }); } } } else if (isSelectChangePayload(payload)) { // TODO geo if (model instanceof SeriesModel) { toggleSelectionFromPayload(model, payload, ecIns._api); updateSeriesElementSelection(model); markStatusToUpdate(ecIns); } } }, ecIns); ecModel && ecModel.eachComponent(condition, function (model) { var isExcluded = excludeSeriesIdMap && excludeSeriesIdMap.get(model.id) != null; if (isExcluded) { return; } ; callView(ecIns[mainType === 'series' ? '_chartsMap' : '_componentsMap'][model.__viewId]); }, ecIns); function callView(view) { view && view.__alive && view[method] && view[method](view.__model, ecModel, ecIns._api, payload); } }; updateMethods = { prepareAndUpdate: function (payload) { prepare(this); updateMethods.update.call(this, payload, { // Needs to mark option changed if newOption is given. // It's from MagicType. // TODO If use a separate flag optionChanged in payload? optionChanged: payload.newOption != null }); }, update: function (payload, updateParams) { var ecModel = this._model; var api = this._api; var zr = this._zr; var coordSysMgr = this._coordSysMgr; var scheduler = this._scheduler; // update before setOption if (!ecModel) { return; } ecModel.setUpdatePayload(payload); scheduler.restoreData(ecModel, payload); scheduler.performSeriesTasks(ecModel); // TODO // Save total ecModel here for undo/redo (after restoring data and before processing data). // Undo (restoration of total ecModel) can be carried out in 'action' or outside API call. // Create new coordinate system each update // In LineView may save the old coordinate system and use it to get the original point. coordSysMgr.create(ecModel, api); scheduler.performDataProcessorTasks(ecModel, payload); // Current stream render is not supported in data process. So we can update // stream modes after data processing, where the filtered data is used to // determine whether to use progressive rendering. updateStreamModes(this, ecModel); // We update stream modes before coordinate system updated, then the modes info // can be fetched when coord sys updating (consider the barGrid extent fix). But // the drawback is the full coord info can not be fetched. Fortunately this full // coord is not required in stream mode updater currently. coordSysMgr.update(ecModel, api); clearColorPalette(ecModel); scheduler.performVisualTasks(ecModel, payload); render(this, ecModel, api, payload, updateParams); // Set background var backgroundColor = ecModel.get('backgroundColor') || 'transparent'; var darkMode = ecModel.get('darkMode'); zr.setBackgroundColor(backgroundColor); // Force set dark mode. if (darkMode != null && darkMode !== 'auto') { zr.setDarkMode(darkMode); } lifecycle.trigger('afterupdate', ecModel, api); }, updateTransform: function (payload) { var _this = this; var ecModel = this._model; var api = this._api; // update before setOption if (!ecModel) { return; } ecModel.setUpdatePayload(payload); // ChartView.markUpdateMethod(payload, 'updateTransform'); var componentDirtyList = []; ecModel.eachComponent(function (componentType, componentModel) { if (componentType === 'series') { return; } var componentView = _this.getViewOfComponentModel(componentModel); if (componentView && componentView.__alive) { if (componentView.updateTransform) { var result = componentView.updateTransform(componentModel, ecModel, api, payload); result && result.update && componentDirtyList.push(componentView); } else { componentDirtyList.push(componentView); } } }); var seriesDirtyMap = createHashMap(); ecModel.eachSeries(function (seriesModel) { var chartView = _this._chartsMap[seriesModel.__viewId]; if (chartView.updateTransform) { var result = chartView.updateTransform(seriesModel, ecModel, api, payload); result && result.update && seriesDirtyMap.set(seriesModel.uid, 1); } else { seriesDirtyMap.set(seriesModel.uid, 1); } }); clearColorPalette(ecModel); // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline. // this._scheduler.performVisualTasks(ecModel, payload, 'layout', true); this._scheduler.performVisualTasks(ecModel, payload, { setDirty: true, dirtyMap: seriesDirtyMap }); // Currently, not call render of components. Geo render cost a lot. // renderComponents(ecIns, ecModel, api, payload, componentDirtyList); renderSeries(this, ecModel, api, payload, {}, seriesDirtyMap); lifecycle.trigger('afterupdate', ecModel, api); }, updateView: function (payload) { var ecModel = this._model; // update before setOption if (!ecModel) { return; } ecModel.setUpdatePayload(payload); ChartView.markUpdateMethod(payload, 'updateView'); clearColorPalette(ecModel); // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline. this._scheduler.performVisualTasks(ecModel, payload, { setDirty: true }); render(this, ecModel, this._api, payload, {}); lifecycle.trigger('afterupdate', ecModel, this._api); }, updateVisual: function (payload) { // updateMethods.update.call(this, payload); var _this = this; var ecModel = this._model; // update before setOption if (!ecModel) { return; } ecModel.setUpdatePayload(payload); // clear all visual ecModel.eachSeries(function (seriesModel) { seriesModel.getData().clearAllVisual(); }); // Perform visual ChartView.markUpdateMethod(payload, 'updateVisual'); clearColorPalette(ecModel); // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline. this._scheduler.performVisualTasks(ecModel, payload, { visualType: 'visual', setDirty: true }); ecModel.eachComponent(function (componentType, componentModel) { if (componentType !== 'series') { var componentView = _this.getViewOfComponentModel(componentModel); componentView && componentView.__alive &&