blob: 6c9721a0dcc1f0fc6852da7acdaa73cfdcf6bf3b [file] [log] [blame]
/*
* 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 { __DEV__ } from './config';
import * as zrender from 'zrender/src/zrender';
import * as zrUtil from 'zrender/src/core/util';
import * as colorTool from 'zrender/src/tool/color';
import env from 'zrender/src/core/env';
import timsort from 'zrender/src/core/timsort';
import Eventful from 'zrender/src/mixin/Eventful';
import GlobalModel from './model/Global';
import ExtensionAPI from './ExtensionAPI';
import CoordinateSystemManager from './CoordinateSystem';
import OptionManager from './model/OptionManager';
import backwardCompat from './preprocessor/backwardCompat';
import dataStack from './processor/dataStack';
import ComponentModel from './model/Component';
import SeriesModel from './model/Series';
import ComponentView from './view/Component';
import ChartView from './view/Chart';
import * as graphic from './util/graphic';
import * as modelUtil from './util/model';
import { throttle } from './util/throttle';
import seriesColor from './visual/seriesColor';
import aria from './visual/aria';
import loadingDefault from './loading/default';
import Scheduler from './stream/Scheduler';
import lightTheme from './theme/light';
import darkTheme from './theme/dark';
import './component/dataset';
import mapDataStorage from './coord/geo/mapDataStorage';
var assert = zrUtil.assert;
var each = zrUtil.each;
var isFunction = zrUtil.isFunction;
var isObject = zrUtil.isObject;
var parseClassType = ComponentModel.parseClassType;
export var version = '4.3.0';
export var dependencies = {
zrender: '4.1.0'
};
var TEST_FRAME_REMAIN_TIME = 1;
var PRIORITY_PROCESSOR_FILTER = 1000;
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_POST_CHART_LAYOUT = 3500;
var PRIORITY_VISUAL_COMPONENT = 4000; // FIXME
// necessary?
var PRIORITY_VISUAL_BRUSH = 5000;
export var PRIORITY = {
PROCESSOR: {
FILTER: PRIORITY_PROCESSOR_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
}
}; // 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 = '__flagInMainProcess';
var OPTION_UPDATED = '__optionUpdated';
var ACTION_REG = /^[a-zA-Z0-9_]+$/;
function createRegisterEventWithLowercaseName(method) {
return function (eventName, handler, context) {
// Event name is all lowercase
eventName = eventName && eventName.toLowerCase();
Eventful.prototype[method].call(this, eventName, handler, context);
};
}
/**
* @module echarts~MessageCenter
*/
function MessageCenter() {
Eventful.call(this);
}
MessageCenter.prototype.on = createRegisterEventWithLowercaseName('on');
MessageCenter.prototype.off = createRegisterEventWithLowercaseName('off');
MessageCenter.prototype.one = createRegisterEventWithLowercaseName('one');
zrUtil.mixin(MessageCenter, Eventful);
/**
* @module echarts~ECharts
*/
function ECharts(dom, theme, opts) {
opts = opts || {}; // Get theme by name
if (typeof theme === 'string') {
theme = themeStorage[theme];
}
/**
* @type {string}
*/
this.id;
/**
* Group id
* @type {string}
*/
this.group;
/**
* @type {HTMLElement}
* @private
*/
this._dom = dom;
var defaultRenderer = 'canvas';
/**
* @type {module:zrender/ZRender}
* @private
*/
var zr = this._zr = zrender.init(dom, {
renderer: opts.renderer || defaultRenderer,
devicePixelRatio: opts.devicePixelRatio,
width: opts.width,
height: opts.height
});
/**
* Expect 60 fps.
* @type {Function}
* @private
*/
this._throttledZrFlush = throttle(zrUtil.bind(zr.flush, zr), 17);
var theme = zrUtil.clone(theme);
theme && backwardCompat(theme, true);
/**
* @type {Object}
* @private
*/
this._theme = theme;
/**
* @type {Array.<module:echarts/view/Chart>}
* @private
*/
this._chartsViews = [];
/**
* @type {Object.<string, module:echarts/view/Chart>}
* @private
*/
this._chartsMap = {};
/**
* @type {Array.<module:echarts/view/Component>}
* @private
*/
this._componentsViews = [];
/**
* @type {Object.<string, module:echarts/view/Component>}
* @private
*/
this._componentsMap = {};
/**
* @type {module:echarts/CoordinateSystem}
* @private
*/
this._coordSysMgr = new CoordinateSystemManager();
/**
* @type {module:echarts/ExtensionAPI}
* @private
*/
var api = this._api = createExtensionAPI(this); // Sort on demand
function prioritySortFunc(a, b) {
return a.__prio - b.__prio;
}
timsort(visualFuncs, prioritySortFunc);
timsort(dataProcessorFuncs, prioritySortFunc);
/**
* @type {module:echarts/stream/Scheduler}
*/
this._scheduler = new Scheduler(this, api, dataProcessorFuncs, visualFuncs);
Eventful.call(this, this._ecEventProcessor = new EventProcessor());
/**
* @type {module:echarts~MessageCenter}
* @private
*/
this._messageCenter = new MessageCenter(); // Init mouse events
this._initEvents(); // In case some people write `window.onresize = chart.resize`
this.resize = zrUtil.bind(this.resize, this); // Can't dispatch action during rendering procedure
this._pendingActions = [];
zr.animation.on('frame', this._onframe, this);
bindRenderedEvent(zr, this); // ECharts instance can be used as value.
zrUtil.setAsPrimitive(this);
}
var echartsProto = ECharts.prototype;
echartsProto._onframe = function () {
if (this._disposed) {
return;
}
var scheduler = this._scheduler; // Lazy update
if (this[OPTION_UPDATED]) {
var silent = this[OPTION_UPDATED].silent;
this[IN_MAIN_PROCESS] = true;
prepare(this);
updateMethods.update.call(this);
this[IN_MAIN_PROCESS] = false;
this[OPTION_UPDATED] = false;
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 determin in the first frame (the
// frame is executed immedietely 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.
}
};
/**
* @return {HTMLElement}
*/
echartsProto.getDom = function () {
return this._dom;
};
/**
* @return {module:zrender~ZRender}
*/
echartsProto.getZr = function () {
return this._zr;
};
/**
* Usage:
* chart.setOption(option, notMerge, lazyUpdate);
* chart.setOption(option, {
* notMerge: ...,
* lazyUpdate: ...,
* silent: ...
* });
*
* @param {Object} option
* @param {Object|boolean} [opts] opts or notMerge.
* @param {boolean} [opts.notMerge=false]
* @param {boolean} [opts.lazyUpdate=false] Useful when setOption frequently.
*/
echartsProto.setOption = function (option, notMerge, lazyUpdate) {
var silent;
if (isObject(notMerge)) {
lazyUpdate = notMerge.lazyUpdate;
silent = notMerge.silent;
notMerge = notMerge.notMerge;
}
this[IN_MAIN_PROCESS] = 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.init(null, null, theme, optionManager);
}
this._model.setOption(option, optionPreprocessorFuncs);
if (lazyUpdate) {
this[OPTION_UPDATED] = {
silent: silent
};
this[IN_MAIN_PROCESS] = false;
} else {
prepare(this);
updateMethods.update.call(this); // Ensure zr refresh sychronously, and then pixel in canvas can be
// fetched after `setOption`.
this._zr.flush();
this[OPTION_UPDATED] = false;
this[IN_MAIN_PROCESS] = false;
flushPendingActions.call(this, silent);
triggerUpdatedEvent.call(this, silent);
}
};
/**
* @DEPRECATED
*/
echartsProto.setTheme = function () {
console.error('ECharts#setTheme() is DEPRECATED in ECharts 3.0');
};
/**
* @return {module:echarts/model/Global}
*/
echartsProto.getModel = function () {
return this._model;
};
/**
* @return {Object}
*/
echartsProto.getOption = function () {
return this._model && this._model.getOption();
};
/**
* @return {number}
*/
echartsProto.getWidth = function () {
return this._zr.getWidth();
};
/**
* @return {number}
*/
echartsProto.getHeight = function () {
return this._zr.getHeight();
};
/**
* @return {number}
*/
echartsProto.getDevicePixelRatio = function () {
return this._zr.painter.dpr || window.devicePixelRatio || 1;
};
/**
* Get canvas which has all thing rendered
* @param {Object} opts
* @param {string} [opts.backgroundColor]
* @return {string}
*/
echartsProto.getRenderedCanvas = function (opts) {
if (!env.canvasSupported) {
return;
}
opts = opts || {};
opts.pixelRatio = opts.pixelRatio || 1;
opts.backgroundColor = opts.backgroundColor || this._model.get('backgroundColor');
var zr = this._zr; // var list = zr.storage.getDisplayList();
// Stop animations
// Never works before in init animation, so remove it.
// zrUtil.each(list, function (el) {
// el.stopAnimation(true);
// });
return zr.painter.getRenderedCanvas(opts);
};
/**
* Get svg data url
* @return {string}
*/
echartsProto.getSvgDataUrl = function () {
if (!env.svgSupported) {
return;
}
var zr = this._zr;
var list = zr.storage.getDisplayList(); // Stop animations
zrUtil.each(list, function (el) {
el.stopAnimation(true);
});
return zr.painter.pathToDataUrl();
};
/**
* @return {string}
* @param {Object} opts
* @param {string} [opts.type='png']
* @param {string} [opts.pixelRatio=1]
* @param {string} [opts.backgroundColor]
* @param {string} [opts.excludeComponents]
*/
echartsProto.getDataURL = function (opts) {
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.getRenderedCanvas(opts).toDataURL('image/' + (opts && opts.type || 'png'));
each(excludesComponentViews, function (view) {
view.group.ignore = false;
});
return url;
};
/**
* @return {string}
* @param {Object} opts
* @param {string} [opts.type='png']
* @param {string} [opts.pixelRatio=1]
* @param {string} [opts.backgroundColor]
*/
echartsProto.getConnectedDataURL = function (opts) {
if (!env.canvasSupported) {
return;
}
var groupId = this.group;
var mathMin = Math.min;
var mathMax = Math.max;
var MAX_NUMBER = Infinity;
if (connectedGroups[groupId]) {
var left = MAX_NUMBER;
var top = MAX_NUMBER;
var right = -MAX_NUMBER;
var bottom = -MAX_NUMBER;
var canvasList = [];
var dpr = opts && opts.pixelRatio || 1;
zrUtil.each(instances, function (chart, id) {
if (chart.group === groupId) {
var canvas = chart.getRenderedCanvas(zrUtil.clone(opts));
var boundingRect = chart.getDom().getBoundingClientRect();
left = mathMin(boundingRect.left, left);
top = mathMin(boundingRect.top, top);
right = mathMax(boundingRect.right, right);
bottom = mathMax(boundingRect.bottom, bottom);
canvasList.push({
dom: canvas,
left: boundingRect.left,
top: boundingRect.top
});
}
});
left *= dpr;
top *= dpr;
right *= dpr;
bottom *= dpr;
var width = right - left;
var height = bottom - top;
var targetCanvas = zrUtil.createCanvas();
targetCanvas.width = width;
targetCanvas.height = height;
var zr = zrender.init(targetCanvas); // Background between the charts
if (opts.connectedBackgroundColor) {
zr.add(new graphic.Rect({
shape: {
x: 0,
y: 0,
width: width,
height: height
},
style: {
fill: opts.connectedBackgroundColor
}
}));
}
each(canvasList, function (item) {
var img = new graphic.Image({
style: {
x: item.left * dpr - left,
y: item.top * dpr - top,
image: item.dom
}
});
zr.add(img);
});
zr.refreshImmediately();
return targetCanvas.toDataURL('image/' + (opts && opts.type || 'png'));
} else {
return this.getDataURL(opts);
}
};
/**
* Convert from logical coordinate system to pixel coordinate system.
* See CoordinateSystem#convertToPixel.
* @param {string|Object} finder
* If string, e.g., 'geo', means {geoIndex: 0}.
* If Object, could contain some of these properties below:
* {
* seriesIndex / seriesId / seriesName,
* geoIndex / geoId, geoName,
* bmapIndex / bmapId / bmapName,
* xAxisIndex / xAxisId / xAxisName,
* yAxisIndex / yAxisId / yAxisName,
* gridIndex / gridId / gridName,
* ... (can be extended)
* }
* @param {Array|number} value
* @return {Array|number} result
*/
echartsProto.convertToPixel = zrUtil.curry(doConvertPixel, 'convertToPixel');
/**
* Convert from pixel coordinate system to logical coordinate system.
* See CoordinateSystem#convertFromPixel.
* @param {string|Object} finder
* If string, e.g., 'geo', means {geoIndex: 0}.
* If Object, could contain some of these properties below:
* {
* seriesIndex / seriesId / seriesName,
* geoIndex / geoId / geoName,
* bmapIndex / bmapId / bmapName,
* xAxisIndex / xAxisId / xAxisName,
* yAxisIndex / yAxisId / yAxisName
* gridIndex / gridId / gridName,
* ... (can be extended)
* }
* @param {Array|number} value
* @return {Array|number} result
*/
echartsProto.convertFromPixel = zrUtil.curry(doConvertPixel, 'convertFromPixel');
function doConvertPixel(methodName, finder, value) {
var ecModel = this._model;
var coordSysList = this._coordSysMgr.getCoordinateSystems();
var result;
finder = modelUtil.parseFinder(ecModel, finder);
for (var i = 0; i < coordSysList.length; i++) {
var coordSys = coordSysList[i];
if (coordSys[methodName] && (result = coordSys[methodName](ecModel, finder, value)) != null) {
return result;
}
}
}
/**
* Is the specified coordinate systems or components contain the given pixel point.
* @param {string|Object} finder
* If string, e.g., 'geo', means {geoIndex: 0}.
* If Object, could contain some of these properties below:
* {
* seriesIndex / seriesId / seriesName,
* geoIndex / geoId / geoName,
* bmapIndex / bmapId / bmapName,
* xAxisIndex / xAxisId / xAxisName,
* yAxisIndex / yAxisId / yAxisName,
* gridIndex / gridId / gridName,
* ... (can be extended)
* }
* @param {Array|number} value
* @return {boolean} result
*/
echartsProto.containPixel = function (finder, value) {
var ecModel = this._model;
var result;
finder = modelUtil.parseFinder(ecModel, finder);
zrUtil.each(finder, function (models, key) {
key.indexOf('Models') >= 0 && zrUtil.each(models, function (model) {
var coordSys = model.coordinateSystem;
if (coordSys && coordSys.containPoint) {
result |= !!coordSys.containPoint(value);
} else if (key === 'seriesModels') {
var view = this._chartsMap[model.__viewId];
if (view && view.containPoint) {
result |= view.containPoint(value, model);
} else {}
} else {}
}, this);
}, this);
return !!result;
};
/**
* Get visual from series or data.
* @param {string|Object} 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 {string} visualType 'color', 'symbol', 'symbolSize'
*/
echartsProto.getVisual = function (finder, visualType) {
var ecModel = this._model;
finder = modelUtil.parseFinder(ecModel, finder, {
defaultMainType: 'series'
});
var seriesModel = finder.seriesModel;
var data = seriesModel.getData();
var dataIndexInside = finder.hasOwnProperty('dataIndexInside') ? finder.dataIndexInside : finder.hasOwnProperty('dataIndex') ? data.indexOfRawIndex(finder.dataIndex) : null;
return dataIndexInside != null ? data.getItemVisual(dataIndexInside, visualType) : data.getVisual(visualType);
};
/**
* Get view of corresponding component model
* @param {module:echarts/model/Component} componentModel
* @return {module:echarts/view/Component}
*/
echartsProto.getViewOfComponentModel = function (componentModel) {
return this._componentsMap[componentModel.__viewId];
};
/**
* Get view of corresponding series model
* @param {module:echarts/model/Series} seriesModel
* @return {module:echarts/view/Chart}
*/
echartsProto.getViewOfSeriesModel = function (seriesModel) {
return this._chartsMap[seriesModel.__viewId];
};
var updateMethods = {
prepareAndUpdate: function (payload) {
prepare(this);
updateMethods.update.call(this, payload);
},
/**
* @param {Object} payload
* @private
*/
update: function (payload) {
// console.profile && console.profile('update');
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;
}
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 orignal 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
// deteming whether 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 requied in stream mode updater currently.
coordSysMgr.update(ecModel, api);
clearColorPalette(ecModel);
scheduler.performVisualTasks(ecModel, payload);
render(this, ecModel, api, payload); // Set background
var backgroundColor = ecModel.get('backgroundColor') || 'transparent'; // In IE8
if (!env.canvasSupported) {
var colorArr = colorTool.parse(backgroundColor);
backgroundColor = colorTool.stringify(colorArr, 'rgb');
if (colorArr[3] === 0) {
backgroundColor = 'transparent';
}
} else {
zr.setBackgroundColor(backgroundColor);
}
performPostUpdateFuncs(ecModel, api); // console.profile && console.profileEnd('update');
},
/**
* @param {Object} payload
* @private
*/
updateTransform: function (payload) {
var ecModel = this._model;
var ecIns = this;
var api = this._api; // update before setOption
if (!ecModel) {
return;
} // ChartView.markUpdateMethod(payload, 'updateTransform');
var componentDirtyList = [];
ecModel.eachComponent(function (componentType, componentModel) {
var componentView = ecIns.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 = zrUtil.createHashMap();
ecModel.eachSeries(function (seriesModel) {
var chartView = ecIns._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(ecIns, ecModel, api, payload, seriesDirtyMap);
performPostUpdateFuncs(ecModel, this._api);
},
/**
* @param {Object} payload
* @private
*/
updateView: function (payload) {
var ecModel = this._model; // update before setOption
if (!ecModel) {
return;
}
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, this._model, this._api, payload);
performPostUpdateFuncs(ecModel, this._api);
},
/**
* @param {Object} payload
* @private
*/
updateVisual: function (payload) {
updateMethods.update.call(this, payload); // var ecModel = this._model;
// // update before setOption
// if (!ecModel) {
// return;
// }
// 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});
// render(this, this._model, this._api, payload);
// performPostUpdateFuncs(ecModel, this._api);
},
/**
* @param {Object} payload
* @private
*/
updateLayout: function (payload) {
updateMethods.update.call(this, payload); // var ecModel = this._model;
// // update before setOption
// if (!ecModel) {
// return;
// }
// ChartView.markUpdateMethod(payload, 'updateLayout');
// // 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});
// render(this, this._model, this._api, payload);
// performPostUpdateFuncs(ecModel, this._api);
}
};
function prepare(ecIns) {
var ecModel = ecIns._model;
var scheduler = ecIns._scheduler;
scheduler.restorePipelines(ecModel);
scheduler.prepareStageTasks();
prepareView(ecIns, 'component', ecModel, scheduler);
prepareView(ecIns, 'chart', ecModel, scheduler);
scheduler.plan();
}
/**
* @private
*/
function updateDirectly(ecIns, method, payload, mainType, subType) {
var ecModel = ecIns._model; // broadcast
if (!mainType) {
// FIXME
// Chart will not be update directly here, except set dirty.
// But there is no such scenario now.
each(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;
if (excludeSeriesId != null) {
excludeSeriesId = zrUtil.createHashMap(modelUtil.normalizeToArray(excludeSeriesId));
} // If dispatchAction before setOption, do nothing.
ecModel && ecModel.eachComponent(condition, function (model) {
if (!excludeSeriesId || excludeSeriesId.get(model.id) == null) {
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);
}
}
/**
* Resize the chart
* @param {Object} opts
* @param {number} [opts.width] Can be 'auto' (the same as null/undefined)
* @param {number} [opts.height] Can be 'auto' (the same as null/undefined)
* @param {boolean} [opts.silent=false]
*/
echartsProto.resize = function (opts) {
this._zr.resize(opts);
var ecModel = this._model; // Resize loading effect
this._loadingFX && this._loadingFX.resize();
if (!ecModel) {
return;
}
var optionChanged = ecModel.resetOption('media');
var silent = opts && opts.silent;
this[IN_MAIN_PROCESS] = true;
optionChanged && prepare(this);
updateMethods.update.call(this);
this[IN_MAIN_PROCESS] = false;
flushPendingActions.call(this, silent);
triggerUpdatedEvent.call(this, silent);
};
function updateStreamModes(ecIns, ecModel) {
var chartsMap = ecIns._chartsMap;
var scheduler = ecIns._scheduler;
ecModel.eachSeries(function (seriesModel) {
scheduler.updateStreamModes(seriesModel, chartsMap[seriesModel.__viewId]);
});
}
/**
* Show loading effect
* @param {string} [name='default']
* @param {Object} [cfg]
*/
echartsProto.showLoading = function (name, cfg) {
if (isObject(name)) {
cfg = name;
name = '';
}
name = name || 'default';
this.hideLoading();
if (!loadingEffects[name]) {
return;
}
var el = loadingEffects[name](this._api, cfg);
var zr = this._zr;
this._loadingFX = el;
zr.add(el);
};
/**
* Hide loading effect
*/
echartsProto.hideLoading = function () {
this._loadingFX && this._zr.remove(this._loadingFX);
this._loadingFX = null;
};
/**
* @param {Object} eventObj
* @return {Object}
*/
echartsProto.makeActionFromEvent = function (eventObj) {
var payload = zrUtil.extend({}, eventObj);
payload.type = eventActionMap[eventObj.type];
return payload;
};
/**
* @pubilc
* @param {Object} payload
* @param {string} [payload.type] Action type
* @param {Object|boolean} [opt] If pass boolean, means opt.silent
* @param {boolean} [opt.silent=false] Whether trigger events.
* @param {boolean} [opt.flush=undefined]
* true: Flush immediately, and then pixel in canvas can be fetched
* immediately. Caution: it might affect performance.
* false: Not not flush.
* undefined: Auto decide whether perform flush.
*/
echartsProto.dispatchAction = function (payload, opt) {
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]) {
this._pendingActions.push(payload);
return;
}
doDispatchAction.call(this, payload, opt.silent);
if (opt.flush) {
this._zr.flush(true);
} else if (opt.flush !== false && env.browser.weChat) {
// In WeChat embeded browser, `requestAnimationFrame` and `setInterval`
// hang when sliding page (on touch event), which cause that zr does not
// refresh util 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, opt.silent);
triggerUpdatedEvent.call(this, opt.silent);
};
function doDispatchAction(payload, silent) {
var payloadType = payload.type;
var escapeConnect = payload.escapeConnect;
var actionWrap = actions[payloadType];
var actionInfo = actionWrap.actionInfo;
var cptType = (actionInfo.update || 'update').split(':');
var updateMethod = cptType.pop();
cptType = cptType[0] != null && parseClassType(cptType[0]);
this[IN_MAIN_PROCESS] = true;
var payloads = [payload];
var batched = false; // Batch action
if (payload.batch) {
batched = true;
payloads = zrUtil.map(payload.batch, function (item) {
item = zrUtil.defaults(zrUtil.extend({}, item), payload);
item.batch = null;
return item;
});
}
var eventObjBatch = [];
var eventObj;
var isHighDown = payloadType === 'highlight' || payloadType === 'downplay';
each(payloads, function (batchItem) {
// Action can specify the event by return it.
eventObj = actionWrap.action(batchItem, this._model, this._api); // Emit event outside
eventObj = eventObj || zrUtil.extend({}, batchItem); // Convert type to eventType
eventObj.type = actionInfo.event || eventObj.type;
eventObjBatch.push(eventObj); // light update does not perform data process, layout and visual.
if (isHighDown) {
// method, payload, mainType, subType
updateDirectly(this, updateMethod, batchItem, 'series');
} else if (cptType) {
updateDirectly(this, updateMethod, batchItem, cptType.main, cptType.sub);
}
}, this);
if (updateMethod !== 'none' && !isHighDown && !cptType) {
// Still dirty
if (this[OPTION_UPDATED]) {
// FIXME Pass payload ?
prepare(this);
updateMethods.update.call(this, payload);
this[OPTION_UPDATED] = false;
} else {
updateMethods[updateMethod].call(this, payload);
}
} // Follow the rule of action batch
if (batched) {
eventObj = {
type: actionInfo.event || payloadType,
escapeConnect: escapeConnect,
batch: eventObjBatch
};
} else {
eventObj = eventObjBatch[0];
}
this[IN_MAIN_PROCESS] = false;
!silent && this._messageCenter.trigger(eventObj.type, eventObj);
}
function flushPendingActions(silent) {
var pendingActions = this._pendingActions;
while (pendingActions.length) {
var payload = pendingActions.shift();
doDispatchAction.call(this, payload, silent);
}
}
function triggerUpdatedEvent(silent) {
!silent && this.trigger('updated');
}
/**
* Event `rendered` is triggered when zr
* rendered. It is useful for realtime
* snapshot (reflect animation).
*
* Event `finished` is triggered when:
* (1) zrender rendering finished.
* (2) initial animation finished.
* (3) progressive rendering finished.
* (4) no pending action.
* (5) no delayed setOption needs to be processed.
*/
function bindRenderedEvent(zr, ecIns) {
zr.on('rendered', function () {
ecIns.trigger('rendered'); // The `finished` event should not be triggered repeatly,
// so it should only be triggered when rendering indeed happend
// in zrender. (Consider the case that dipatchAction is keep
// triggering when mouse move).
if ( // Although zr is dirty if initial animation is not finished
// and this checking is called on frame, we also check
// animation finished for robustness.
zr.animation.isFinished() && !ecIns[OPTION_UPDATED] && !ecIns._scheduler.unfinished && !ecIns._pendingActions.length) {
ecIns.trigger('finished');
}
});
}
/**
* @param {Object} params
* @param {number} params.seriesIndex
* @param {Array|TypedArray} params.data
*/
echartsProto.appendData = function (params) {
var seriesIndex = params.seriesIndex;
var ecModel = this.getModel();
var seriesModel = ecModel.getSeriesByIndex(seriesIndex);
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;
};
/**
* Register event
* @method
*/
echartsProto.on = createRegisterEventWithLowercaseName('on');
echartsProto.off = createRegisterEventWithLowercaseName('off');
echartsProto.one = createRegisterEventWithLowercaseName('one');
/**
* Prepare view instances of charts and components
* @param {module:echarts/model/Global} ecModel
* @private
*/
function prepareView(ecIns, type, ecModel, scheduler) {
var isComponent = type === 'component';
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) {
// Consider: id same and type changed.
var viewId = '_ec_' + model.id + '_' + model.type;
var view = viewMap[viewId];
if (!view) {
var classType = parseClassType(model.type);
var Clazz = isComponent ? ComponentView.getClass(classType.main, classType.sub) : ChartView.getClass(classType.sub);
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);
delete viewMap[view.__id];
view.__id = view.group.__ecComponentInfo = null;
} else {
i++;
}
}
} // /**
// * Encode visual infomation from data after data processing
// *
// * @param {module:echarts/model/Global} ecModel
// * @param {object} layout
// * @param {boolean} [layoutFilter] `true`: only layout,
// * `false`: only not layout,
// * `null`/`undefined`: all.
// * @param {string} taskBaseTag
// * @private
// */
// function startVisualEncoding(ecIns, ecModel, api, payload, layoutFilter) {
// each(visualFuncs, function (visual, index) {
// var isLayout = visual.isLayout;
// if (layoutFilter == null
// || (layoutFilter === false && !isLayout)
// || (layoutFilter === true && isLayout)
// ) {
// visual.func(ecModel, api, payload);
// }
// });
// }
function clearColorPalette(ecModel) {
ecModel.clearColorPalette();
ecModel.eachSeries(function (seriesModel) {
seriesModel.clearColorPalette();
});
}
function render(ecIns, ecModel, api, payload) {
renderComponents(ecIns, ecModel, api, payload);
each(ecIns._chartsViews, function (chart) {
chart.__alive = false;
});
renderSeries(ecIns, ecModel, api, payload); // Remove groups of unrendered charts
each(ecIns._chartsViews, function (chart) {
if (!chart.__alive) {
chart.remove(ecModel, api);
}
});
}
function renderComponents(ecIns, ecModel, api, payload, dirtyList) {
each(dirtyList || ecIns._componentsViews, function (componentView) {
var componentModel = componentView.__model;
componentView.render(componentModel, ecModel, api, payload);
updateZ(componentModel, componentView);
});
}
/**
* Render each chart and component
* @private
*/
function renderSeries(ecIns, ecModel, api, payload, dirtyMap) {
// Render all charts
var scheduler = ecIns._scheduler;
var unfinished;
ecModel.eachSeries(function (seriesModel) {
var chartView = ecIns._chartsMap[seriesModel.__viewId];
chartView.__alive = true;
var renderTask = chartView.renderTask;
scheduler.updatePayload(renderTask, payload);
if (dirtyMap && dirtyMap.get(seriesModel.uid)) {
renderTask.dirty();
}
unfinished |= renderTask.perform(scheduler.getPerformArgs(renderTask));
chartView.group.silent = !!seriesModel.get('silent');
updateZ(seriesModel, chartView);
updateBlend(seriesModel, chartView);
});
scheduler.unfinished |= unfinished; // If use hover layer
updateHoverLayerStatus(ecIns, ecModel); // Add aria
aria(ecIns._zr.dom, ecModel);
}
function performPostUpdateFuncs(ecModel, api) {
each(postUpdateFuncs, function (func) {
func(ecModel, api);
});
}
var MOUSE_EVENT_NAMES = ['click', 'dblclick', 'mouseover', 'mouseout', 'mousemove', 'mousedown', 'mouseup', 'globalout', 'contextmenu'];
/**
* @private
*/
echartsProto._initEvents = function () {
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 if (el && el.dataIndex != null) {
var dataModel = el.dataModel || ecModel.getSeriesByIndex(el.seriesIndex);
params = dataModel && dataModel.getDataParams(el.dataIndex, el.dataType, el) || {};
} // If element has custom eventData of components
else if (el && el.eventData) {
params = zrUtil.extend({}, el.eventData);
} // 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];
params.event = e;
params.type = eveName;
this._ecEventProcessor.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);
}, this);
each(eventActionMap, function (actionType, eventType) {
this._messageCenter.on(eventType, function (event) {
this.trigger(eventType, event);
}, this);
}, this);
};
/**
* @return {boolean}
*/
echartsProto.isDisposed = function () {
return this._disposed;
};
/**
* Clear
*/
echartsProto.clear = function () {
this.setOption({
series: []
}, true);
};
/**
* Dispose instance
*/
echartsProto.dispose = function () {
if (this._disposed) {
return;
}
this._disposed = true;
modelUtil.setAttribute(this.getDom(), DOM_ATTRIBUTE_KEY, '');
var api = this._api;
var ecModel = this._model;
each(this._componentsViews, function (component) {
component.dispose(ecModel, api);
});
each(this._chartsViews, function (chart) {
chart.dispose(ecModel, api);
}); // Dispose after all views disposed
this._zr.dispose();
delete instances[this.id];
};
zrUtil.mixin(ECharts, Eventful);
function updateHoverLayerStatus(ecIns, ecModel) {
var zr = ecIns._zr;
var storage = zr.storage;
var elCount = 0;
storage.traverse(function (el) {
elCount++;
});
if (elCount > ecModel.get('hoverLayerThreshold') && !env.node) {
ecModel.eachSeries(function (seriesModel) {
if (seriesModel.preventUsingHoverLayer) {
return;
}
var chartView = ecIns._chartsMap[seriesModel.__viewId];
if (chartView.__alive) {
chartView.group.traverse(function (el) {
// Don't switch back.
el.useHoverLayer = true;
});
}
});
}
}
/**
* Update chart progressive and blend.
* @param {module:echarts/model/Series|module:echarts/model/Component} model
* @param {module:echarts/view/Component|module:echarts/view/Chart} view
*/
function updateBlend(seriesModel, chartView) {
var blendMode = seriesModel.get('blendMode') || null;
chartView.group.traverse(function (el) {
// FIXME marker and other components
if (!el.isGroup) {
// Only set if blendMode is changed. In case element is incremental and don't wan't to rerender.
if (el.style.blend !== blendMode) {
el.setStyle('blend', blendMode);
}
}
if (el.eachPendingDisplayable) {
el.eachPendingDisplayable(function (displayable) {
displayable.setStyle('blend', blendMode);
});
}
});
}
/**
* @param {module:echarts/model/Series|module:echarts/model/Component} model
* @param {module:echarts/view/Component|module:echarts/view/Chart} view
*/
function updateZ(model, view) {
var z = model.get('z');
var zlevel = model.get('zlevel'); // Set z and zlevel
view.group.traverse(function (el) {
if (el.type !== 'group') {
z != null && (el.z = z);
zlevel != null && (el.zlevel = zlevel);
}
});
}
function createExtensionAPI(ecInstance) {
var coordSysMgr = ecInstance._coordSysMgr;
return zrUtil.extend(new ExtensionAPI(ecInstance), {
// Inject methods
getCoordinateSystems: zrUtil.bind(coordSysMgr.getCoordinateSystems, coordSysMgr),
getComponentByElement: function (el) {
while (el) {
var modelInfo = el.__ecComponentInfo;
if (modelInfo != null) {
return ecInstance._model.getComponent(modelInfo.mainType, modelInfo.index);
}
el = el.parent;
}
}
});
}
/**
* @class
* Usage of query:
* `chart.on('click', query, handler);`
* The `query` can be:
* + The component type query string, only `mainType` or `mainType.subType`,
* like: 'xAxis', 'series', 'xAxis.category' or 'series.line'.
* + The component query object, like:
* `{seriesIndex: 2}`, `{seriesName: 'xx'}`, `{seriesId: 'some'}`,
* `{xAxisIndex: 2}`, `{xAxisName: 'xx'}`, `{xAxisId: 'some'}`.
* + The data query object, like:
* `{dataIndex: 123}`, `{dataType: 'link'}`, `{name: 'some'}`.
* + The other query object (cmponent customized query), like:
* `{element: 'some'}` (only available in custom series).
*
* Caveat: If a prop in the `query` object is `null/undefined`, it is the
* same as there is no such prop in the `query` object.
*/
function EventProcessor() {
// These info required: targetEl, packedEvent, model, view
this.eventInfo;
}
EventProcessor.prototype = {
constructor: EventProcessor,
normalizeQuery: function (query) {
var cptQuery = {};
var dataQuery = {};
var otherQuery = {}; // `query` is `mainType` or `mainType.subType` of component.
if (zrUtil.isString(query)) {
var condCptType = parseClassType(query); // `.main` and `.sub` may be ''.
cptQuery.mainType = condCptType.main || null;
cptQuery.subType = condCptType.sub || null;
} // `query` is an object, convert to {mainType, index, name, id}.
else {
// `xxxIndex`, `xxxName`, `xxxId`, `name`, `dataIndex`, `dataType` is reserved,
// can not be used in `compomentModel.filterForExposedEvent`.
var suffixes = ['Index', 'Name', 'Id'];
var dataKeys = {
name: 1,
dataIndex: 1,
dataType: 1
};
zrUtil.each(query, function (val, key) {
var reserved = false;
for (var i = 0; i < suffixes.length; i++) {
var propSuffix = suffixes[i];
var suffixPos = key.lastIndexOf(propSuffix);
if (suffixPos > 0 && suffixPos === key.length - propSuffix.length) {
var mainType = key.slice(0, suffixPos); // Consider `dataIndex`.
if (mainType !== 'data') {
cptQuery.mainType = mainType;
cptQuery[propSuffix.toLowerCase()] = val;
reserved = true;
}
}
}
if (dataKeys.hasOwnProperty(key)) {
dataQuery[key] = val;
reserved = true;
}
if (!reserved) {
otherQuery[key] = val;
}
});
}
return {
cptQuery: cptQuery,
dataQuery: dataQuery,
otherQuery: otherQuery
};
},
filter: function (eventType, query, args) {
// They should be assigned before each trigger call.
var eventInfo = this.eventInfo;
if (!eventInfo) {
return true;
}
var targetEl = eventInfo.targetEl;
var packedEvent = eventInfo.packedEvent;
var model = eventInfo.model;
var view = eventInfo.view; // For event like 'globalout'.
if (!model || !view) {
return true;
}
var cptQuery = query.cptQuery;
var dataQuery = query.dataQuery;
return check(cptQuery, model, 'mainType') && check(cptQuery, model, 'subType') && check(cptQuery, model, 'index', 'componentIndex') && check(cptQuery, model, 'name') && check(cptQuery, model, 'id') && check(dataQuery, packedEvent, 'name') && check(dataQuery, packedEvent, 'dataIndex') && check(dataQuery, packedEvent, 'dataType') && (!view.filterForExposedEvent || view.filterForExposedEvent(eventType, query.otherQuery, targetEl, packedEvent));
function check(query, host, prop, propOnHost) {
return query[prop] == null || host[propOnHost || prop] === query[prop];
}
},
afterTrigger: function () {
// Make sure the eventInfo wont be used in next trigger.
this.eventInfo = null;
}
};
/**
* @type {Object} key: actionType.
* @inner
*/
var actions = {};
/**
* Map eventType to actionType
* @type {Object}
*/
var eventActionMap = {};
/**
* Data processor functions of each stage
* @type {Array.<Object.<string, Function>>}
* @inner
*/
var dataProcessorFuncs = [];
/**
* @type {Array.<Function>}
* @inner
*/
var optionPreprocessorFuncs = [];
/**
* @type {Array.<Function>}
* @inner
*/
var postUpdateFuncs = [];
/**
* Visual encoding functions of each stage
* @type {Array.<Object.<string, Function>>}
*/
var visualFuncs = [];
/**
* Theme storage
* @type {Object.<key, Object>}
*/
var themeStorage = {};
/**
* Loading effects
*/
var loadingEffects = {};
var instances = {};
var connectedGroups = {};
var idBase = new Date() - 0;
var groupIdBase = new Date() - 0;
var DOM_ATTRIBUTE_KEY = '_echarts_instance_';
function enableConnect(chart) {
var STATUS_PENDING = 0;
var STATUS_UPDATING = 1;
var STATUS_UPDATED = 2;
var STATUS_KEY = '__connectUpdateStatus';
function updateConnectedChartsStatus(charts, status) {
for (var i = 0; i < charts.length; i++) {
var otherChart = charts[i];
otherChart[STATUS_KEY] = status;
}
}
each(eventActionMap, function (actionType, eventType) {
chart._messageCenter.on(eventType, function (event) {
if (connectedGroups[chart.group] && chart[STATUS_KEY] !== STATUS_PENDING) {
if (event && event.escapeConnect) {
return;
}
var action = chart.makeActionFromEvent(event);
var otherCharts = [];
each(instances, function (otherChart) {
if (otherChart !== chart && otherChart.group === chart.group) {
otherCharts.push(otherChart);
}
});
updateConnectedChartsStatus(otherCharts, STATUS_PENDING);
each(otherCharts, function (otherChart) {
if (otherChart[STATUS_KEY] !== STATUS_UPDATING) {
otherChart.dispatchAction(action);
}
});
updateConnectedChartsStatus(otherCharts, STATUS_UPDATED);
}
});
});
}
/**
* @param {HTMLElement} dom
* @param {Object} [theme]
* @param {Object} opts
* @param {number} [opts.devicePixelRatio] Use window.devicePixelRatio by default
* @param {string} [opts.renderer] Can choose 'canvas' or 'svg' to render the chart.
* @param {number} [opts.width] Use clientWidth of the input `dom` by default.
* Can be 'auto' (the same as null/undefined)
* @param {number} [opts.height] Use clientHeight of the input `dom` by default.
* Can be 'auto' (the same as null/undefined)
*/
export function init(dom, theme, opts) {
var existInstance = getInstanceByDom(dom);
if (existInstance) {
return existInstance;
}
var chart = new ECharts(dom, theme, opts);
chart.id = 'ec_' + idBase++;
instances[chart.id] = chart;
modelUtil.setAttribute(dom, DOM_ATTRIBUTE_KEY, chart.id);
enableConnect(chart);
return chart;
}
/**
* @return {string|Array.<module:echarts~ECharts>} groupId
*/
export function connect(groupId) {
// Is array of charts
if (zrUtil.isArray(groupId)) {
var charts = groupId;
groupId = null; // If any chart has group
each(charts, function (chart) {
if (chart.group != null) {
groupId = chart.group;
}
});
groupId = groupId || 'g_' + groupIdBase++;
each(charts, function (chart) {
chart.group = groupId;
});
}
connectedGroups[groupId] = true;
return groupId;
}
/**
* @DEPRECATED
* @return {string} groupId
*/
export function disConnect(groupId) {
connectedGroups[groupId] = false;
}
/**
* @return {string} groupId
*/
export var disconnect = disConnect;
/**
* Dispose a chart instance
* @param {module:echarts~ECharts|HTMLDomElement|string} chart
*/
export function dispose(chart) {
if (typeof chart === 'string') {
chart = instances[chart];
} else if (!(chart instanceof ECharts)) {
// Try to treat as dom
chart = getInstanceByDom(chart);
}
if (chart instanceof ECharts && !chart.isDisposed()) {
chart.dispose();
}
}
/**
* @param {HTMLElement} dom
* @return {echarts~ECharts}
*/
export function getInstanceByDom(dom) {
return instances[modelUtil.getAttribute(dom, DOM_ATTRIBUTE_KEY)];
}
/**
* @param {string} key
* @return {echarts~ECharts}
*/
export function getInstanceById(key) {
return instances[key];
}
/**
* Register theme
*/
export function registerTheme(name, theme) {
themeStorage[name] = theme;
}
/**
* Register option preprocessor
* @param {Function} preprocessorFunc
*/
export function registerPreprocessor(preprocessorFunc) {
optionPreprocessorFuncs.push(preprocessorFunc);
}
/**
* @param {number} [priority=1000]
* @param {Object|Function} processor
*/
export function registerProcessor(priority, processor) {
normalizeRegister(dataProcessorFuncs, priority, processor, PRIORITY_PROCESSOR_FILTER);
}
/**
* Register postUpdater
* @param {Function} postUpdateFunc
*/
export function registerPostUpdate(postUpdateFunc) {
postUpdateFuncs.push(postUpdateFunc);
}
/**
* Usage:
* registerAction('someAction', 'someEvent', function () { ... });
* registerAction('someAction', function () { ... });
* registerAction(
* {type: 'someAction', event: 'someEvent', update: 'updateView'},
* function () { ... }
* );
*
* @param {(string|Object)} actionInfo
* @param {string} actionInfo.type
* @param {string} [actionInfo.event]
* @param {string} [actionInfo.update]
* @param {string} [eventName]
* @param {Function} action
*/
export function registerAction(actionInfo, eventName, action) {
if (typeof eventName === 'function') {
action = eventName;
eventName = '';
}
var actionType = isObject(actionInfo) ? actionInfo.type : [actionInfo, actionInfo = {
event: eventName
}][0]; // Event name is all lowercase
actionInfo.event = (actionInfo.event || actionType).toLowerCase();
eventName = actionInfo.event; // Validate action type and event name.
assert(ACTION_REG.test(actionType) && ACTION_REG.test(eventName));
if (!actions[actionType]) {
actions[actionType] = {
action: action,
actionInfo: actionInfo
};
}
eventActionMap[eventName] = actionType;
}
/**
* @param {string} type
* @param {*} CoordinateSystem
*/
export function registerCoordinateSystem(type, CoordinateSystem) {
CoordinateSystemManager.register(type, CoordinateSystem);
}
/**
* Get dimensions of specified coordinate system.
* @param {string} type
* @return {Array.<string|Object>}
*/
export function getCoordinateSystemDimensions(type) {
var coordSysCreator = CoordinateSystemManager.get(type);
if (coordSysCreator) {
return coordSysCreator.getDimensionsInfo ? coordSysCreator.getDimensionsInfo() : coordSysCreator.dimensions.slice();
}
}
/**
* Layout is a special stage of visual encoding
* Most visual encoding like color are common for different chart
* But each chart has it's own layout algorithm
*
* @param {number} [priority=1000]
* @param {Function} layoutTask
*/
export function registerLayout(priority, layoutTask) {
normalizeRegister(visualFuncs, priority, layoutTask, PRIORITY_VISUAL_LAYOUT, 'layout');
}
/**
* @param {number} [priority=3000]
* @param {module:echarts/stream/Task} visualTask
*/
export function registerVisual(priority, visualTask) {
normalizeRegister(visualFuncs, priority, visualTask, PRIORITY_VISUAL_CHART, 'visual');
}
/**
* @param {Object|Function} fn: {seriesType, createOnAllSeries, performRawSeries, reset}
*/
function normalizeRegister(targetList, priority, fn, defaultPriority, visualType) {
if (isFunction(priority) || isObject(priority)) {
fn = priority;
priority = defaultPriority;
}
var stageHandler = Scheduler.wrapStageHandler(fn, visualType);
stageHandler.__prio = priority;
stageHandler.__raw = fn;
targetList.push(stageHandler);
return stageHandler;
}
/**
* @param {string} name
*/
export function registerLoading(name, loadingFx) {
loadingEffects[name] = loadingFx;
}
/**
* @param {Object} opts
* @param {string} [superClass]
*/
export function extendComponentModel(opts
/*, superClass*/
) {
// var Clazz = ComponentModel;
// if (superClass) {
// var classType = parseClassType(superClass);
// Clazz = ComponentModel.getClass(classType.main, classType.sub, true);
// }
return ComponentModel.extend(opts);
}
/**
* @param {Object} opts
* @param {string} [superClass]
*/
export function extendComponentView(opts
/*, superClass*/
) {
// var Clazz = ComponentView;
// if (superClass) {
// var classType = parseClassType(superClass);
// Clazz = ComponentView.getClass(classType.main, classType.sub, true);
// }
return ComponentView.extend(opts);
}
/**
* @param {Object} opts
* @param {string} [superClass]
*/
export function extendSeriesModel(opts
/*, superClass*/
) {
// var Clazz = SeriesModel;
// if (superClass) {
// superClass = 'series.' + superClass.replace('series.', '');
// var classType = parseClassType(superClass);
// Clazz = ComponentModel.getClass(classType.main, classType.sub, true);
// }
return SeriesModel.extend(opts);
}
/**
* @param {Object} opts
* @param {string} [superClass]
*/
export function extendChartView(opts
/*, superClass*/
) {
// var Clazz = ChartView;
// if (superClass) {
// superClass = superClass.replace('series.', '');
// var classType = parseClassType(superClass);
// Clazz = ChartView.getClass(classType.main, true);
// }
return ChartView.extend(opts);
}
/**
* ZRender need a canvas context to do measureText.
* But in node environment canvas may be created by node-canvas.
* So we need to specify how to create a canvas instead of using document.createElement('canvas')
*
* Be careful of using it in the browser.
*
* @param {Function} creator
* @example
* var Canvas = require('canvas');
* var echarts = require('echarts');
* echarts.setCanvasCreator(function () {
* // Small size is enough.
* return new Canvas(32, 32);
* });
*/
export function setCanvasCreator(creator) {
zrUtil.$override('createCanvas', creator);
}
/**
* @param {string} mapName
* @param {Array.<Object>|Object|string} geoJson
* @param {Object} [specialAreas]
*
* @example GeoJSON
* $.get('USA.json', function (geoJson) {
* echarts.registerMap('USA', geoJson);
* // Or
* echarts.registerMap('USA', {
* geoJson: geoJson,
* specialAreas: {}
* })
* });
*
* $.get('airport.svg', function (svg) {
* echarts.registerMap('airport', {
* svg: svg
* }
* });
*
* echarts.registerMap('eu', [
* {svg: eu-topographic.svg},
* {geoJSON: eu.json}
* ])
*/
export function registerMap(mapName, geoJson, specialAreas) {
mapDataStorage.registerMap(mapName, geoJson, specialAreas);
}
/**
* @param {string} mapName
* @return {Object}
*/
export function getMap(mapName) {
// For backward compatibility, only return the first one.
var records = mapDataStorage.retrieveMap(mapName);
return records && records[0] && {
geoJson: records[0].geoJSON,
specialAreas: records[0].specialAreas
};
}
registerVisual(PRIORITY_VISUAL_GLOBAL, seriesColor);
registerPreprocessor(backwardCompat);
registerProcessor(PRIORITY_PROCESSOR_STATISTIC, dataStack);
registerLoading('default', loadingDefault); // Default actions
registerAction({
type: 'highlight',
event: 'highlight',
update: 'highlight'
}, zrUtil.noop);
registerAction({
type: 'downplay',
event: 'downplay',
update: 'downplay'
}, zrUtil.noop); // Default theme
registerTheme('light', lightTheme);
registerTheme('dark', darkTheme); // For backward compatibility, where the namespace `dataTool` will
// be mounted on `echarts` is the extension `dataTool` is imported.
export var dataTool = {};