Echarts Js
Echarts Js
// (1) The code `if (__DEV__) ...` can be removed by build tool.
// (2) If intend to use `__DEV__`, this module should be imported. Use a global
// variable `__DEV__` may cause that miss the declaration (see #6535), or the
// declaration is behind of the using position (for example in `Model.extent`,
// And tools like rollup can not analysis the dependency if not import).
var dev;
// In browser
if (typeof window !== 'undefined') {
dev = window.__DEV__;
}
// In node
else if (typeof global !== 'undefined') {
dev = global.__DEV__;
}
/**
* zrender: 生成唯一 id
*
* @author errorrik ([email protected])
*/
/**
* echarts 设备环境识别
*
* @desc echarts 基于 Canvas,纯 Javascript 图表库,提供直观,生动,可交互,可个性化定制的数据
统计图表。
* @author firede[[email protected]]
* @desc thanks zepto.
*/
// Zepto.js
// (c) 2010-2013 Thomas Fuchs
// Zepto.js may be freely distributed under the MIT license.
function detect(ua) {
var os = {};
var browser = {};
// var webkit = ua.match(/Web[kK]it[\/]{0,1}([\d.]+)/);
// var android = ua.match(/(Android);?[\s\/]+([\d.]+)?/);
// var ipad = ua.match(/(iPad).*OS\s([\d_]+)/);
// var ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/);
// var iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/);
// var webos = ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/);
// var touchpad = webos && ua.match(/TouchPad/);
// var kindle = ua.match(/Kindle\/([\d.]+)/);
// var silk = ua.match(/Silk\/([\d._]+)/);
// var blackberry = ua.match(/(BlackBerry).*Version\/([\d.]+)/);
// var bb10 = ua.match(/(BB10).*Version\/([\d.]+)/);
// var rimtabletos = ua.match(/(RIM\sTablet\sOS)\s([\d.]+)/);
// var playbook = ua.match(/PlayBook/);
// var chrome = ua.match(/Chrome\/([\d.]+)/) || ua.match(/CriOS\/([\d.]+)/);
var firefox = ua.match(/Firefox\/([\d.]+)/);
// var safari = webkit && ua.match(/Mobile\//) && !chrome;
// var webview = ua.match(/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/) && !
chrome;
var ie = ua.match(/MSIE\s([\d.]+)/)
// IE 11 Trident/7.0; rv:11.0
|| ua.match(/Trident\/.+?rv:(([\d.]+))/);
var edge = ua.match(/Edge\/([\d.]+)/); // IE 12 and 12+
if (ie) {
browser.ie = true;
browser.version = ie[1];
}
if (edge) {
browser.edge = true;
browser.version = edge[1];
}
return {
browser: browser,
os: os,
node: false,
// 原生 canvas 支持,改极端点了
// canvasSupported : !(browser.ie && parseFloat(browser.version) < 9)
canvasSupported: !!document.createElement('canvas').getContext,
svgSupported: typeof SVGRect !== 'undefined',
// works on most browsers
// IE10/11 does not support touch event, and MS Edge supports them but not
by
// default, so we dont check navigator.maxTouchPoints for them here.
touchEventsSupported: 'ontouchstart' in window && !browser.ie && !
browser.edge,
// <http://caniuse.com/#search=pointer%20event>.
pointerEventsSupported: 'onpointerdown' in window
// Firefox supports pointer but not by default, only MS browsers are
reliable on pointer
// events currently. So we dont use that on other browsers unless
tested sufficiently.
// Although IE 10 supports pointer event, it use old style and is
different from the
// standard. So we exclude that. (IE 10 is hardly used on touch device)
&& (browser.edge || (browser.ie && browser.version >= 11))
// passiveSupported: detectPassiveSupport()
};
}
// See https://github.com/WICG/EventListenerOptions/blob/gh-
pages/explainer.md#feature-detection
// function detectPassiveSupport() {
// // Test via a getter in the options object to see if the passive property is
accessed
// var supportsPassive = false;
// try {
// var opts = Object.defineProperty({}, 'passive', {
// get: function() {
// supportsPassive = true;
// }
// });
// window.addEventListener('testPassive', function() {}, opts);
// } catch (e) {
// }
// return supportsPassive;
// }
/**
* @module zrender/core/util
*/
var TYPED_ARRAY = {
'[object Int8Array]': 1,
'[object Uint8Array]': 1,
'[object Uint8ClampedArray]': 1,
'[object Int16Array]': 1,
'[object Uint16Array]': 1,
'[object Int32Array]': 1,
'[object Uint32Array]': 1,
'[object Float32Array]': 1,
'[object Float64Array]': 1
};
methods[name] = fn;
}
/**
* Those data types can be cloned:
* Plain object, Array, TypedArray, number, string, null, undefined.
* Those data types will be assgined using the orginal data:
* BUILTIN_OBJECT
* Instance of user defined class will be cloned to a plain object, without
* properties in prototype.
* Other data types is not supported (not sure what will happen).
*
* Caution: do not support clone Date, for performance consideration.
* (There might be a large number of date in `series.data`).
* So date should not be modified in and out of echarts.
*
* @param {*} source
* @return {*} new
*/
function clone(source) {
if (source == null || typeof source != 'object') {
return source;
}
return result;
}
/**
* @memberOf module:zrender/core/util
* @param {*} target
* @param {*} source
* @param {boolean} [overwrite=false]
*/
function merge(target, source, overwrite) {
// We should escapse that source is string
// and enter for ... in ...
if (!isObject$1(source) || !isObject$1(target)) {
return overwrite ? clone(source) : target;
}
if (isObject$1(sourceProp)
&& isObject$1(targetProp)
&& !isArray(sourceProp)
&& !isArray(targetProp)
&& !isDom(sourceProp)
&& !isDom(targetProp)
&& !isBuiltInObject(sourceProp)
&& !isBuiltInObject(targetProp)
&& !isPrimitive(sourceProp)
&& !isPrimitive(targetProp)
) {
// 如果需要递归覆盖,就递归调用 merge
merge(targetProp, sourceProp, overwrite);
}
else if (overwrite || !(key in target)) {
// 否则只处理 overwrite 为 true,或者在目标对象中没有此属性的情况
// NOTE,在 target[key] 不存在的时候也是直接覆盖
target[key] = clone(source[key], true);
}
}
}
return target;
}
/**
* @param {Array} targetAndSources The first item is target, and the rests are
source.
* @param {boolean} [overwrite=false]
* @return {*} target
*/
function mergeAll(targetAndSources, overwrite) {
var result = targetAndSources[0];
for (var i = 1, len = targetAndSources.length; i < len; i++) {
result = merge(result, targetAndSources[i], overwrite);
}
return result;
}
/**
* @param {*} target
* @param {*} source
* @memberOf module:zrender/core/util
*/
function extend(target, source) {
for (var key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
}
/**
* @param {*} target
* @param {*} source
* @param {boolean} [overlay=false]
* @memberOf module:zrender/core/util
*/
function defaults(target, source, overlay) {
for (var key in source) {
if (source.hasOwnProperty(key)
&& (overlay ? source[key] != null : target[key] == null)
) {
target[key] = source[key];
}
}
return target;
}
methods.createCanvas = function () {
return document.createElement('canvas');
};
// FIXME
var _ctx;
function getContext() {
if (!_ctx) {
// Use util.createCanvas instead of createCanvas
// because createCanvas may be overwritten in different environment
_ctx = createCanvas().getContext('2d');
}
return _ctx;
}
/**
* 查询数组中元素的 index
* @memberOf module:zrender/core/util
*/
function indexOf(array, value) {
if (array) {
if (array.indexOf) {
return array.indexOf(value);
}
for (var i = 0, len = array.length; i < len; i++) {
if (array[i] === value) {
return i;
}
}
}
return -1;
}
/**
* 构造类继承关系
*
* @memberOf module:zrender/core/util
* @param {Function} clazz 源类
* @param {Function} baseClazz 基类
*/
function inherits(clazz, baseClazz) {
var clazzPrototype = clazz.prototype;
function F() {}
F.prototype = baseClazz.prototype;
clazz.prototype = new F();
for (var prop in clazzPrototype) {
clazz.prototype[prop] = clazzPrototype[prop];
}
clazz.prototype.constructor = clazz;
clazz.superClass = baseClazz;
}
/**
* @memberOf module:zrender/core/util
* @param {Object|Function} target
* @param {Object|Function} sorce
* @param {boolean} overlay
*/
function mixin(target, source, overlay) {
target = 'prototype' in target ? target.prototype : target;
source = 'prototype' in source ? source.prototype : source;
/**
* Consider typed array.
* @param {Array|TypedArray} data
*/
function isArrayLike(data) {
if (! data) {
return;
}
if (typeof data == 'string') {
return false;
}
return typeof data.length == 'number';
}
/**
* 数组或对象遍历
* @memberOf module:zrender/core/util
* @param {Object|Array} obj
* @param {Function} cb
* @param {*} [context]
*/
function each$1(obj, cb, context) {
if (!(obj && cb)) {
return;
}
if (obj.forEach && obj.forEach === nativeForEach) {
obj.forEach(cb, context);
}
else if (obj.length === +obj.length) {
for (var i = 0, len = obj.length; i < len; i++) {
cb.call(context, obj[i], i, obj);
}
}
else {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
cb.call(context, obj[key], key, obj);
}
}
}
}
/**
* 数组映射
* @memberOf module:zrender/core/util
* @param {Array} obj
* @param {Function} cb
* @param {*} [context]
* @return {Array}
*/
function map(obj, cb, context) {
if (!(obj && cb)) {
return;
}
if (obj.map && obj.map === nativeMap) {
return obj.map(cb, context);
}
else {
var result = [];
for (var i = 0, len = obj.length; i < len; i++) {
result.push(cb.call(context, obj[i], i, obj));
}
return result;
}
}
/**
* @memberOf module:zrender/core/util
* @param {Array} obj
* @param {Function} cb
* @param {Object} [memo]
* @param {*} [context]
* @return {Array}
*/
function reduce(obj, cb, memo, context) {
if (!(obj && cb)) {
return;
}
if (obj.reduce && obj.reduce === nativeReduce) {
return obj.reduce(cb, memo, context);
}
else {
for (var i = 0, len = obj.length; i < len; i++) {
memo = cb.call(context, memo, obj[i], i, obj);
}
return memo;
}
}
/**
* 数组过滤
* @memberOf module:zrender/core/util
* @param {Array} obj
* @param {Function} cb
* @param {*} [context]
* @return {Array}
*/
function filter(obj, cb, context) {
if (!(obj && cb)) {
return;
}
if (obj.filter && obj.filter === nativeFilter) {
return obj.filter(cb, context);
}
else {
var result = [];
for (var i = 0, len = obj.length; i < len; i++) {
if (cb.call(context, obj[i], i, obj)) {
result.push(obj[i]);
}
}
return result;
}
}
/**
* 数组项查找
* @memberOf module:zrender/core/util
* @param {Array} obj
* @param {Function} cb
* @param {*} [context]
* @return {*}
*/
function find(obj, cb, context) {
if (!(obj && cb)) {
return;
}
for (var i = 0, len = obj.length; i < len; i++) {
if (cb.call(context, obj[i], i, obj)) {
return obj[i];
}
}
}
/**
* @memberOf module:zrender/core/util
* @param {Function} func
* @param {*} context
* @return {Function}
*/
function bind(func, context) {
var args = nativeSlice.call(arguments, 2);
return function () {
return func.apply(context, args.concat(nativeSlice.call(arguments)));
};
}
/**
* @memberOf module:zrender/core/util
* @param {Function} func
* @return {Function}
*/
function curry(func) {
var args = nativeSlice.call(arguments, 1);
return function () {
return func.apply(this, args.concat(nativeSlice.call(arguments)));
};
}
/**
* @memberOf module:zrender/core/util
* @param {*} value
* @return {boolean}
*/
function isArray(value) {
return objToString.call(value) === '[object Array]';
}
/**
* @memberOf module:zrender/core/util
* @param {*} value
* @return {boolean}
*/
function isFunction$1(value) {
return typeof value === 'function';
}
/**
* @memberOf module:zrender/core/util
* @param {*} value
* @return {boolean}
*/
function isString(value) {
return objToString.call(value) === '[object String]';
}
/**
* @memberOf module:zrender/core/util
* @param {*} value
* @return {boolean}
*/
function isObject$1(value) {
// Avoid a V8 JIT bug in Chrome 19-20.
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
var type = typeof value;
return type === 'function' || (!!value && type == 'object');
}
/**
* @memberOf module:zrender/core/util
* @param {*} value
* @return {boolean}
*/
function isBuiltInObject(value) {
return !!BUILTIN_OBJECT[objToString.call(value)];
}
/**
* @memberOf module:zrender/core/util
* @param {*} value
* @return {boolean}
*/
function isTypedArray(value) {
return !!TYPED_ARRAY[objToString.call(value)];
}
/**
* @memberOf module:zrender/core/util
* @param {*} value
* @return {boolean}
*/
function isDom(value) {
return typeof value === 'object'
&& typeof value.nodeType === 'number'
&& typeof value.ownerDocument === 'object';
}
/**
* Whether is exactly NaN. Notice isNaN('a') returns true.
* @param {*} value
* @return {boolean}
*/
function eqNaN(value) {
return value !== value;
}
/**
* If value1 is not null, then return value1, otherwise judget rest of values.
* Low performance.
* @memberOf module:zrender/core/util
* @return {*} Final value
*/
function retrieve(values) {
for (var i = 0, len = arguments.length; i < len; i++) {
if (arguments[i] != null) {
return arguments[i];
}
}
}
/**
* @memberOf module:zrender/core/util
* @param {Array} arr
* @param {number} startIndex
* @param {number} endIndex
* @return {Array}
*/
function slice() {
return Function.call.apply(nativeSlice, arguments);
}
/**
* Normalize css liked array configuration
* e.g.
* 3 => [3, 3, 3, 3]
* [4, 2] => [4, 2, 4, 2]
* [4, 3, 2] => [4, 3, 2, 3]
* @param {number|Array.<number>} val
* @return {Array.<number>}
*/
function normalizeCssArray(val) {
if (typeof (val) === 'number') {
return [val, val, val, val];
}
var len = val.length;
if (len === 2) {
// vertical | horizontal
return [val[0], val[1], val[0], val[1]];
}
else if (len === 3) {
// top | horizontal | bottom
return [val[0], val[1], val[2], val[1]];
}
return val;
}
/**
* @memberOf module:zrender/core/util
* @param {boolean} condition
* @param {string} message
*/
function assert$1(condition, message) {
if (!condition) {
throw new Error(message);
}
}
/**
* @memberOf module:zrender/core/util
* @param {string} str string to be trimed
* @return {string} trimed string
*/
function trim(str) {
if (str == null) {
return null;
}
else if (typeof str.trim === 'function') {
return str.trim();
}
else {
return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}
}
function isPrimitive(obj) {
return obj[primitiveKey];
}
/**
* @constructor
* @param {Object} obj Only apply `ownProperty`.
*/
function HashMap(obj) {
var isArr = isArray(obj);
var thisMap = this;
HashMap.prototype = {
constructor: HashMap,
// Do not provide `has` method to avoid defining what is `has`.
// (We usually treat `null` and `undefined` as the same, different
// from ES6 Map).
get: function (key) {
return this.hasOwnProperty(key) ? this[key] : null;
},
set: function (key, value) {
// Comparing with invocation chaining, `return value` is more commonly
// used in this case: `var someVal = map.set('a', genVal());`
return (this[key] = value);
},
// Although util.each can be performed on this hashMap directly, user
// should not use the exposed keys, who are prefixed.
each: function (cb, context) {
context !== void 0 && (cb = bind(cb, context));
for (var key in this) {
this.hasOwnProperty(key) && cb(this[key], key);
}
},
// Do not use this method if performance sensitive.
removeKey: function (key) {
delete this[key];
}
};
function createHashMap(obj) {
return new HashMap(obj);
}
function concatArray(a, b) {
var newArray = new a.constructor(a.length + b.length);
for (var i = 0; i < a.length; i++) {
newArray[i] = a[i];
}
var offset = a.length;
for (i = 0; i < b.length; i++) {
newArray[i + offset] = b[i];
}
return newArray;
}
function noop() {}
/**
* 复制向量数据
* @param {Vector2} out
* @param {Vector2} v
* @return {Vector2}
*/
function copy(out, v) {
out[0] = v[0];
out[1] = v[1];
return out;
}
/**
* 克隆一个向量
* @param {Vector2} v
* @return {Vector2}
*/
function clone$1(v) {
var out = new ArrayCtor(2);
out[0] = v[0];
out[1] = v[1];
return out;
}
/**
* 设置向量的两个项
* @param {Vector2} out
* @param {number} a
* @param {number} b
* @return {Vector2} 结果
*/
function set(out, a, b) {
out[0] = a;
out[1] = b;
return out;
}
/**
* 向量相加
* @param {Vector2} out
* @param {Vector2} v1
* @param {Vector2} v2
*/
function add(out, v1, v2) {
out[0] = v1[0] + v2[0];
out[1] = v1[1] + v2[1];
return out;
}
/**
* 向量缩放后相加
* @param {Vector2} out
* @param {Vector2} v1
* @param {Vector2} v2
* @param {number} a
*/
function scaleAndAdd(out, v1, v2, a) {
out[0] = v1[0] + v2[0] * a;
out[1] = v1[1] + v2[1] * a;
return out;
}
/**
* 向量相减
* @param {Vector2} out
* @param {Vector2} v1
* @param {Vector2} v2
*/
function sub(out, v1, v2) {
out[0] = v1[0] - v2[0];
out[1] = v1[1] - v2[1];
return out;
}
/**
* 向量长度
* @param {Vector2} v
* @return {number}
*/
function len(v) {
return Math.sqrt(lenSquare(v));
}
var length = len; // jshint ignore:line
/**
* 向量长度平方
* @param {Vector2} v
* @return {number}
*/
function lenSquare(v) {
return v[0] * v[0] + v[1] * v[1];
}
var lengthSquare = lenSquare;
/**
* 向量乘法
* @param {Vector2} out
* @param {Vector2} v1
* @param {Vector2} v2
*/
function mul(out, v1, v2) {
out[0] = v1[0] * v2[0];
out[1] = v1[1] * v2[1];
return out;
}
/**
* 向量除法
* @param {Vector2} out
* @param {Vector2} v1
* @param {Vector2} v2
*/
function div(out, v1, v2) {
out[0] = v1[0] / v2[0];
out[1] = v1[1] / v2[1];
return out;
}
/**
* 向量点乘
* @param {Vector2} v1
* @param {Vector2} v2
* @return {number}
*/
function dot(v1, v2) {
return v1[0] * v2[0] + v1[1] * v2[1];
}
/**
* 向量缩放
* @param {Vector2} out
* @param {Vector2} v
* @param {number} s
*/
function scale(out, v, s) {
out[0] = v[0] * s;
out[1] = v[1] * s;
return out;
}
/**
* 向量归一化
* @param {Vector2} out
* @param {Vector2} v
*/
function normalize(out, v) {
var d = len(v);
if (d === 0) {
out[0] = 0;
out[1] = 0;
}
else {
out[0] = v[0] / d;
out[1] = v[1] / d;
}
return out;
}
/**
* 计算向量间距离
* @param {Vector2} v1
* @param {Vector2} v2
* @return {number}
*/
function distance(v1, v2) {
return Math.sqrt(
(v1[0] - v2[0]) * (v1[0] - v2[0])
+ (v1[1] - v2[1]) * (v1[1] - v2[1])
);
}
var dist = distance;
/**
* 向量距离平方
* @param {Vector2} v1
* @param {Vector2} v2
* @return {number}
*/
function distanceSquare(v1, v2) {
return (v1[0] - v2[0]) * (v1[0] - v2[0])
+ (v1[1] - v2[1]) * (v1[1] - v2[1]);
}
var distSquare = distanceSquare;
/**
* 求负向量
* @param {Vector2} out
* @param {Vector2} v
*/
function negate(out, v) {
out[0] = -v[0];
out[1] = -v[1];
return out;
}
/**
* 插值两个点
* @param {Vector2} out
* @param {Vector2} v1
* @param {Vector2} v2
* @param {number} t
*/
function lerp(out, v1, v2, t) {
out[0] = v1[0] + t * (v2[0] - v1[0]);
out[1] = v1[1] + t * (v2[1] - v1[1]);
return out;
}
/**
* 矩阵左乘向量
* @param {Vector2} out
* @param {Vector2} v
* @param {Vector2} m
*/
function applyTransform(out, v, m) {
var x = v[0];
var y = v[1];
out[0] = m[0] * x + m[2] * y + m[4];
out[1] = m[1] * x + m[3] * y + m[5];
return out;
}
/**
* 求两个向量最小值
* @param {Vector2} out
* @param {Vector2} v1
* @param {Vector2} v2
*/
function min(out, v1, v2) {
out[0] = Math.min(v1[0], v2[0]);
out[1] = Math.min(v1[1], v2[1]);
return out;
}
/**
* 求两个向量最大值
* @param {Vector2} out
* @param {Vector2} v1
* @param {Vector2} v2
*/
function max(out, v1, v2) {
out[0] = Math.max(v1[0], v2[0]);
out[1] = Math.max(v1[1], v2[1]);
return out;
}
// this._x = 0;
// this._y = 0;
}
Draggable.prototype = {
constructor: Draggable,
var x = e.offsetX;
var y = e.offsetY;
var dx = x - this._x;
var dy = y - this._y;
this._x = x;
this._y = y;
if (draggingTarget) {
draggingTarget.dragging = false;
}
if (this._dropTarget) {
this.dispatchToElement(param(this._dropTarget, e), 'drop', e.event);
}
this._draggingTarget = null;
this._dropTarget = null;
}
};
function param(target, e) {
return {target: target, topTarget: e && e.topTarget};
}
/**
* 事件扩展
* @module zrender/mixin/Eventful
* @author Kener (@Kener-林峰, [email protected])
* pissang (https://www.github.com/pissang)
*/
/**
* 事件分发器
* @alias module:zrender/mixin/Eventful
* @constructor
*/
var Eventful = function () {
this._$handlers = {};
};
Eventful.prototype = {
constructor: Eventful,
/**
* 单次触发绑定,trigger 后销毁
*
* @param {string} event 事件名
* @param {Function} handler 响应函数
* @param {Object} context
*/
one: function (event, handler, context) {
var _h = this._$handlers;
if (!handler || !event) {
return this;
}
if (!_h[event]) {
_h[event] = [];
}
_h[event].push({
h: handler,
one: true,
ctx: context || this
});
return this;
},
/**
* 绑定事件
* @param {string} event 事件名
* @param {Function} handler 事件处理函数
* @param {Object} [context]
*/
on: function (event, handler, context) {
var _h = this._$handlers;
if (!handler || !event) {
return this;
}
if (!_h[event]) {
_h[event] = [];
}
_h[event].push({
h: handler,
one: false,
ctx: context || this
});
return this;
},
/**
* 是否绑定了事件
* @param {string} event
* @return {boolean}
*/
isSilent: function (event) {
var _h = this._$handlers;
return _h[event] && _h[event].length;
},
/**
* 解绑事件
* @param {string} event 事件名
* @param {Function} [handler] 事件处理函数
*/
off: function (event, handler) {
var _h = this._$handlers;
if (!event) {
this._$handlers = {};
return this;
}
if (handler) {
if (_h[event]) {
var newList = [];
for (var i = 0, l = _h[event].length; i < l; i++) {
if (_h[event][i]['h'] != handler) {
newList.push(_h[event][i]);
}
}
_h[event] = newList;
}
return this;
},
/**
* 事件分发
*
* @param {string} type 事件类型
*/
trigger: function (type) {
if (this._$handlers[type]) {
var args = arguments;
var argLen = args.length;
if (argLen > 3) {
args = arrySlice.call(args, 1);
}
var _h = this._$handlers[type];
var len = _h.length;
for (var i = 0; i < len;) {
// Optimize advise from backbone
switch (argLen) {
case 1:
_h[i]['h'].call(_h[i]['ctx']);
break;
case 2:
_h[i]['h'].call(_h[i]['ctx'], args[1]);
break;
case 3:
_h[i]['h'].call(_h[i]['ctx'], args[1], args[2]);
break;
default:
// have more than 2 given arguments
_h[i]['h'].apply(_h[i]['ctx'], args);
break;
}
if (_h[i]['one']) {
_h.splice(i, 1);
len--;
}
else {
i++;
}
}
}
return this;
},
/**
* 带有 context 的事件分发, 最后一个参数是事件回调的 context
* @param {string} type 事件类型
*/
triggerWithContext: function (type) {
if (this._$handlers[type]) {
var args = arguments;
var argLen = args.length;
if (argLen > 4) {
args = arrySlice.call(args, 1, args.length - 1);
}
var ctx = args[args.length - 1];
var _h = this._$handlers[type];
var len = _h.length;
for (var i = 0; i < len;) {
// Optimize advise from backbone
switch (argLen) {
case 1:
_h[i]['h'].call(ctx);
break;
case 2:
_h[i]['h'].call(ctx, args[1]);
break;
case 3:
_h[i]['h'].call(ctx, args[1], args[2]);
break;
default:
// have more than 2 given arguments
_h[i]['h'].apply(ctx, args);
break;
}
if (_h[i]['one']) {
_h.splice(i, 1);
len--;
}
else {
i++;
}
}
}
return this;
}
};
function EmptyProxy () {}
EmptyProxy.prototype.dispose = function () {};
var handlerNames = [
'click', 'dblclick', 'mousewheel', 'mouseout',
'mouseup', 'mousedown', 'mousemove', 'contextmenu'
];
/**
* @alias module:zrender/Handler
* @constructor
* @extends module:zrender/mixin/Eventful
* @param {module:zrender/Storage} storage Storage instance.
* @param {module:zrender/Painter} painter Painter instance.
* @param {module:zrender/dom/HandlerProxy} proxy HandlerProxy instance.
* @param {HTMLElement} painterRoot painter.root (not painter.getViewportRoot()).
*/
var Handler = function(storage, painter, proxy, painterRoot) {
Eventful.call(this);
this.storage = storage;
this.painter = painter;
this.painterRoot = painterRoot;
/**
* Proxy of event. can be Dom, WebGLSurface, etc.
*/
this.proxy = null;
/**
* {target, topTarget, x, y}
* @private
* @type {Object}
*/
this._hovered = {};
/**
* @private
* @type {Date}
*/
this._lastTouchMoment;
/**
* @private
* @type {number}
*/
this._lastX;
/**
* @private
* @type {number}
*/
this._lastY;
Draggable.call(this);
this.setHandlerProxy(proxy);
};
Handler.prototype = {
constructor: Handler,
if (proxy) {
each$1(handlerNames, function (name) {
proxy.on && proxy.on(name, this[name], this);
}, this);
// Attach handler
proxy.handler = this;
}
this.proxy = proxy;
},
/**
* Resize
*/
resize: function (event) {
this._hovered = {};
},
/**
* Dispatch event
* @param {string} eventName
* @param {event=} eventArgs
*/
dispatch: function (eventName, eventArgs) {
var handler = this[eventName];
handler && handler.call(this, eventArgs);
},
/**
* Dispose
*/
dispose: function () {
this.proxy.dispose();
this.storage =
this.proxy =
this.painter = null;
},
/**
* 设置默认的 cursor style
* @param {string} [cursorStyle='default'] 例如 crosshair
*/
setCursorStyle: function (cursorStyle) {
var proxy = this.proxy;
proxy.setCursor && proxy.setCursor(cursorStyle);
},
/**
* 事件分发代理
*
* @private
* @param {Object} targetInfo {target, topTarget} 目标图形元素
* @param {string} eventName 事件名称
* @param {Object} event 事件对象
*/
dispatchToElement: function (targetInfo, eventName, event) {
targetInfo = targetInfo || {};
var el = targetInfo.target;
if (el && el.silent) {
return;
}
var eventHandler = 'on' + eventName;
var eventPacket = makeEventPacket(eventName, targetInfo, event);
while (el) {
el[eventHandler]
&& (eventPacket.cancelBubble = el[eventHandler].call(el,
eventPacket));
el.trigger(eventName, eventPacket);
el = el.parent;
if (eventPacket.cancelBubble) {
break;
}
}
if (!eventPacket.cancelBubble) {
// 冒泡到顶级 zrender 对象
this.trigger(eventName, eventPacket);
// 分发事件到用户自定义层
// 用户有可能在全局 click 事件中 dispose,所以需要判断下 painter 是否存在
this.painter && this.painter.eachOtherLayer(function (layer) {
if (typeof(layer[eventHandler]) == 'function') {
layer[eventHandler].call(layer, eventPacket);
}
if (layer.trigger) {
layer.trigger(eventName, eventPacket);
}
});
}
},
/**
* @private
* @param {number} x
* @param {number} y
* @param {module:zrender/graphic/Displayable} exclude
* @return {model:zrender/Element}
* @method
*/
findHover: function(x, y, exclude) {
var list = this.storage.getDisplayList();
var out = {x: x, y: y};
return out;
}
};
// Common handlers
each$1(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'],
function (name) {
Handler.prototype[name] = function (event) {
// Find hover again to avoid click event is dispatched manually. Or click
is triggered without mouseover
var hovered = this.findHover(event.zrX, event.zrY);
var hoveredTarget = hovered.target;
function isHover(displayable, x, y) {
if (displayable[displayable.rectHover ? 'rectContain' : 'contain'](x, y)) {
var el = displayable;
var isSilent;
while (el) {
// If clipped by ancestor.
// FIXME: If clipPath has neither stroke nor fill,
// el.clipPath.contain(x, y) will always return false.
if (el.clipPath && !el.clipPath.contain(x, y)) {
return false;
}
if (el.silent) {
isSilent = true;
}
el = el.parent;
}
return isSilent ? SILENT : true;
}
return false;
}
mixin(Handler, Eventful);
mixin(Handler, Draggable);
/**
* 3x2 矩阵操作类
* @exports zrender/tool/matrix
*/
/**
* Create a identity matrix.
* @return {Float32Array|Array.<number>}
*/
function create$1() {
var out = new ArrayCtor$1(6);
identity(out);
return out;
}
/**
* 设置矩阵为单位矩阵
* @param {Float32Array|Array.<number>} out
*/
function identity(out) {
out[0] = 1;
out[1] = 0;
out[2] = 0;
out[3] = 1;
out[4] = 0;
out[5] = 0;
return out;
}
/**
* 复制矩阵
* @param {Float32Array|Array.<number>} out
* @param {Float32Array|Array.<number>} m
*/
function copy$1(out, m) {
out[0] = m[0];
out[1] = m[1];
out[2] = m[2];
out[3] = m[3];
out[4] = m[4];
out[5] = m[5];
return out;
}
/**
* 矩阵相乘
* @param {Float32Array|Array.<number>} out
* @param {Float32Array|Array.<number>} m1
* @param {Float32Array|Array.<number>} m2
*/
function mul$1(out, m1, m2) {
// Consider matrix.mul(m, m2, m);
// where out is the same as m2.
// So use temp variable to escape error.
var out0 = m1[0] * m2[0] + m1[2] * m2[1];
var out1 = m1[1] * m2[0] + m1[3] * m2[1];
var out2 = m1[0] * m2[2] + m1[2] * m2[3];
var out3 = m1[1] * m2[2] + m1[3] * m2[3];
var out4 = m1[0] * m2[4] + m1[2] * m2[5] + m1[4];
var out5 = m1[1] * m2[4] + m1[3] * m2[5] + m1[5];
out[0] = out0;
out[1] = out1;
out[2] = out2;
out[3] = out3;
out[4] = out4;
out[5] = out5;
return out;
}
/**
* 平移变换
* @param {Float32Array|Array.<number>} out
* @param {Float32Array|Array.<number>} a
* @param {Float32Array|Array.<number>} v
*/
function translate(out, a, v) {
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
out[3] = a[3];
out[4] = a[4] + v[0];
out[5] = a[5] + v[1];
return out;
}
/**
* 旋转变换
* @param {Float32Array|Array.<number>} out
* @param {Float32Array|Array.<number>} a
* @param {number} rad
*/
function rotate(out, a, rad) {
var aa = a[0];
var ac = a[2];
var atx = a[4];
var ab = a[1];
var ad = a[3];
var aty = a[5];
var st = Math.sin(rad);
var ct = Math.cos(rad);
out[0] = aa * ct + ab * st;
out[1] = -aa * st + ab * ct;
out[2] = ac * ct + ad * st;
out[3] = -ac * st + ct * ad;
out[4] = ct * atx + st * aty;
out[5] = ct * aty - st * atx;
return out;
}
/**
* 缩放变换
* @param {Float32Array|Array.<number>} out
* @param {Float32Array|Array.<number>} a
* @param {Float32Array|Array.<number>} v
*/
function scale$1(out, a, v) {
var vx = v[0];
var vy = v[1];
out[0] = a[0] * vx;
out[1] = a[1] * vy;
out[2] = a[2] * vx;
out[3] = a[3] * vy;
out[4] = a[4] * vx;
out[5] = a[5] * vy;
return out;
}
/**
* 求逆矩阵
* @param {Float32Array|Array.<number>} out
* @param {Float32Array|Array.<number>} a
*/
function invert(out, a) {
var aa = a[0];
var ac = a[2];
var atx = a[4];
var ab = a[1];
var ad = a[3];
var aty = a[5];
out[0] = ad * det;
out[1] = -ab * det;
out[2] = -ac * det;
out[3] = aa * det;
out[4] = (ac * aty - ad * atx) * det;
out[5] = (ab * atx - aa * aty) * det;
return out;
}
/**
* Clone a new matrix.
* @param {Float32Array|Array.<number>} a
*/
function clone$2(a) {
var b = create$1();
copy$1(b, a);
return b;
}
var matrix = (Object.freeze || Object)({
create: create$1,
identity: identity,
copy: copy$1,
mul: mul$1,
translate: translate,
rotate: rotate,
scale: scale$1,
invert: invert,
clone: clone$2
});
/**
* 提供变换扩展
* @module zrender/mixin/Transformable
* @author pissang (https://www.github.com/pissang)
*/
function isNotAroundZero(val) {
return val > EPSILON || val < -EPSILON;
}
/**
* @alias module:zrender/mixin/Transformable
* @constructor
*/
var Transformable = function (opts) {
opts = opts || {};
// If there are no given position, rotation, scale
if (!opts.position) {
/**
* 平移
* @type {Array.<number>}
* @default [0, 0]
*/
this.position = [0, 0];
}
if (opts.rotation == null) {
/**
* 旋转
* @type {Array.<number>}
* @default 0
*/
this.rotation = 0;
}
if (!opts.scale) {
/**
* 缩放
* @type {Array.<number>}
* @default [1, 1]
*/
this.scale = [1, 1];
}
/**
* 旋转和缩放的原点
* @type {Array.<number>}
* @default null
*/
this.origin = this.origin || null;
};
/**
* 判断是否需要有坐标变换
* 如果有坐标变换, 则从 position, rotation, scale 以及父节点的 transform 计算出自身的
transform 矩阵
*/
transformableProto.needLocalTransform = function () {
return isNotAroundZero(this.rotation)
|| isNotAroundZero(this.position[0])
|| isNotAroundZero(this.position[1])
|| isNotAroundZero(this.scale[0] - 1)
|| isNotAroundZero(this.scale[1] - 1);
};
transformableProto.updateTransform = function () {
var parent = this.parent;
var parentHasTransform = parent && parent.transform;
var needLocalTransform = this.needLocalTransform();
var m = this.transform;
if (!(needLocalTransform || parentHasTransform)) {
m && mIdentity(m);
return;
}
m = m || create$1();
if (needLocalTransform) {
this.getLocalTransform(m);
}
else {
mIdentity(m);
}
// 应用父节点变换
if (parentHasTransform) {
if (needLocalTransform) {
mul$1(m, parent.transform, m);
}
else {
copy$1(m, parent.transform);
}
}
// 保存这个变换矩阵
this.transform = m;
/**
* 将自己的 transform 应用到 context 上
* @param {CanvasRenderingContext2D} ctx
*/
transformableProto.setTransform = function (ctx) {
var m = this.transform;
var dpr = ctx.dpr || 1;
if (m) {
ctx.setTransform(dpr * m[0], dpr * m[1], dpr * m[2], dpr * m[3], dpr *
m[4], dpr * m[5]);
}
else {
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
};
/**
* 分解`transform`矩阵到`position`, `rotation`, `scale`
*/
transformableProto.decomposeTransform = function () {
if (!this.transform) {
return;
}
var parent = this.parent;
var m = this.transform;
if (parent && parent.transform) {
// Get local transform and decompose them to position, scale, rotation
mul$1(tmpTransform, parent.invTransform, m);
m = tmpTransform;
}
var sx = m[0] * m[0] + m[1] * m[1];
var sy = m[2] * m[2] + m[3] * m[3];
var position = this.position;
var scale$$1 = this.scale;
if (isNotAroundZero(sx - 1)) {
sx = Math.sqrt(sx);
}
if (isNotAroundZero(sy - 1)) {
sy = Math.sqrt(sy);
}
if (m[0] < 0) {
sx = -sx;
}
if (m[3] < 0) {
sy = -sy;
}
position[0] = m[4];
position[1] = m[5];
scale$$1[0] = sx;
scale$$1[1] = sy;
this.rotation = Math.atan2(-m[1] / sy, m[0] / sx);
};
/**
* Get global scale
* @return {Array.<number>}
*/
transformableProto.getGlobalScale = function () {
var m = this.transform;
if (!m) {
return [1, 1];
}
var sx = Math.sqrt(m[0] * m[0] + m[1] * m[1]);
var sy = Math.sqrt(m[2] * m[2] + m[3] * m[3]);
if (m[0] < 0) {
sx = -sx;
}
if (m[3] < 0) {
sy = -sy;
}
return [sx, sy];
};
/**
* 变换坐标位置到 shape 的局部坐标空间
* @method
* @param {number} x
* @param {number} y
* @return {Array.<number>}
*/
transformableProto.transformCoordToLocal = function (x, y) {
var v2 = [x, y];
var invTransform = this.invTransform;
if (invTransform) {
applyTransform(v2, v2, invTransform);
}
return v2;
};
/**
* 变换局部坐标位置到全局坐标空间
* @method
* @param {number} x
* @param {number} y
* @return {Array.<number>}
*/
transformableProto.transformCoordToGlobal = function (x, y) {
var v2 = [x, y];
var transform = this.transform;
if (transform) {
applyTransform(v2, v2, transform);
}
return v2;
};
/**
* @static
* @param {Object} target
* @param {Array.<number>} target.origin
* @param {number} target.rotation
* @param {Array.<number>} target.position
* @param {Array.<number>} [m]
*/
Transformable.getLocalTransform = function (target, m) {
m = m || [];
mIdentity(m);
if (origin) {
// Translate to origin
m[4] -= origin[0];
m[5] -= origin[1];
}
scale$1(m, m, scale$$1);
if (rotation) {
rotate(m, m, rotation);
}
if (origin) {
// Translate back from origin
m[4] += origin[0];
m[5] += origin[1];
}
m[4] += position[0];
m[5] += position[1];
return m;
};
/**
* 缓动代码来自 https://github.com/sole/tween.js/blob/master/src/Tween.js
* @see http://sole.github.io/tween.js/examples/03_graphs.html
* @exports zrender/animation/easing
*/
var easing = {
/**
* @param {number} k
* @return {number}
*/
linear: function (k) {
return k;
},
/**
* @param {number} k
* @return {number}
*/
quadraticIn: function (k) {
return k * k;
},
/**
* @param {number} k
* @return {number}
*/
quadraticOut: function (k) {
return k * (2 - k);
},
/**
* @param {number} k
* @return {number}
*/
quadraticInOut: function (k) {
if ((k *= 2) < 1) {
return 0.5 * k * k;
}
return -0.5 * (--k * (k - 2) - 1);
},
// 三次方的缓动(t^3)
/**
* @param {number} k
* @return {number}
*/
cubicIn: function (k) {
return k * k * k;
},
/**
* @param {number} k
* @return {number}
*/
cubicOut: function (k) {
return --k * k * k + 1;
},
/**
* @param {number} k
* @return {number}
*/
cubicInOut: function (k) {
if ((k *= 2) < 1) {
return 0.5 * k * k * k;
}
return 0.5 * ((k -= 2) * k * k + 2);
},
// 四次方的缓动(t^4)
/**
* @param {number} k
* @return {number}
*/
quarticIn: function (k) {
return k * k * k * k;
},
/**
* @param {number} k
* @return {number}
*/
quarticOut: function (k) {
return 1 - (--k * k * k * k);
},
/**
* @param {number} k
* @return {number}
*/
quarticInOut: function (k) {
if ((k *= 2) < 1) {
return 0.5 * k * k * k * k;
}
return -0.5 * ((k -= 2) * k * k * k - 2);
},
// 五次方的缓动(t^5)
/**
* @param {number} k
* @return {number}
*/
quinticIn: function (k) {
return k * k * k * k * k;
},
/**
* @param {number} k
* @return {number}
*/
quinticOut: function (k) {
return --k * k * k * k * k + 1;
},
/**
* @param {number} k
* @return {number}
*/
quinticInOut: function (k) {
if ((k *= 2) < 1) {
return 0.5 * k * k * k * k * k;
}
return 0.5 * ((k -= 2) * k * k * k * k + 2);
},
// 正弦曲线的缓动(sin(t))
/**
* @param {number} k
* @return {number}
*/
sinusoidalIn: function (k) {
return 1 - Math.cos(k * Math.PI / 2);
},
/**
* @param {number} k
* @return {number}
*/
sinusoidalOut: function (k) {
return Math.sin(k * Math.PI / 2);
},
/**
* @param {number} k
* @return {number}
*/
sinusoidalInOut: function (k) {
return 0.5 * (1 - Math.cos(Math.PI * k));
},
// 指数曲线的缓动(2^t)
/**
* @param {number} k
* @return {number}
*/
exponentialIn: function (k) {
return k === 0 ? 0 : Math.pow(1024, k - 1);
},
/**
* @param {number} k
* @return {number}
*/
exponentialOut: function (k) {
return k === 1 ? 1 : 1 - Math.pow(2, -10 * k);
},
/**
* @param {number} k
* @return {number}
*/
exponentialInOut: function (k) {
if (k === 0) {
return 0;
}
if (k === 1) {
return 1;
}
if ((k *= 2) < 1) {
return 0.5 * Math.pow(1024, k - 1);
}
return 0.5 * (-Math.pow(2, -10 * (k - 1)) + 2);
},
// 圆形曲线的缓动(sqrt(1-t^2))
/**
* @param {number} k
* @return {number}
*/
circularIn: function (k) {
return 1 - Math.sqrt(1 - k * k);
},
/**
* @param {number} k
* @return {number}
*/
circularOut: function (k) {
return Math.sqrt(1 - (--k * k));
},
/**
* @param {number} k
* @return {number}
*/
circularInOut: function (k) {
if ((k *= 2) < 1) {
return -0.5 * (Math.sqrt(1 - k * k) - 1);
}
return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1);
},
// 创建类似于弹簧在停止前来回振荡的动画
/**
* @param {number} k
* @return {number}
*/
elasticIn: function (k) {
var s;
var a = 0.1;
var p = 0.4;
if (k === 0) {
return 0;
}
if (k === 1) {
return 1;
}
if (!a || a < 1) {
a = 1; s = p / 4;
}
else {
s = p * Math.asin(1 / a) / (2 * Math.PI);
}
return -(a * Math.pow(2, 10 * (k -= 1)) *
Math.sin((k - s) * (2 * Math.PI) / p));
},
/**
* @param {number} k
* @return {number}
*/
elasticOut: function (k) {
var s;
var a = 0.1;
var p = 0.4;
if (k === 0) {
return 0;
}
if (k === 1) {
return 1;
}
if (!a || a < 1) {
a = 1; s = p / 4;
}
else {
s = p * Math.asin(1 / a) / (2 * Math.PI);
}
return (a * Math.pow(2, -10 * k) *
Math.sin((k - s) * (2 * Math.PI) / p) + 1);
},
/**
* @param {number} k
* @return {number}
*/
elasticInOut: function (k) {
var s;
var a = 0.1;
var p = 0.4;
if (k === 0) {
return 0;
}
if (k === 1) {
return 1;
}
if (!a || a < 1) {
a = 1; s = p / 4;
}
else {
s = p * Math.asin(1 / a) / (2 * Math.PI);
}
if ((k *= 2) < 1) {
return -0.5 * (a * Math.pow(2, 10 * (k -= 1))
* Math.sin((k - s) * (2 * Math.PI) / p));
}
return a * Math.pow(2, -10 * (k -= 1))
* Math.sin((k - s) * (2 * Math.PI) / p) * 0.5 + 1;
},
// 在某一动画开始沿指示的路径进行动画处理前稍稍收回该动画的移动
/**
* @param {number} k
* @return {number}
*/
backIn: function (k) {
var s = 1.70158;
return k * k * ((s + 1) * k - s);
},
/**
* @param {number} k
* @return {number}
*/
backOut: function (k) {
var s = 1.70158;
return --k * k * ((s + 1) * k + s) + 1;
},
/**
* @param {number} k
* @return {number}
*/
backInOut: function (k) {
var s = 1.70158 * 1.525;
if ((k *= 2) < 1) {
return 0.5 * (k * k * ((s + 1) * k - s));
}
return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2);
},
// 创建弹跳效果
/**
* @param {number} k
* @return {number}
*/
bounceIn: function (k) {
return 1 - easing.bounceOut(1 - k);
},
/**
* @param {number} k
* @return {number}
*/
bounceOut: function (k) {
if (k < (1 / 2.75)) {
return 7.5625 * k * k;
}
else if (k < (2 / 2.75)) {
return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75;
}
else if (k < (2.5 / 2.75)) {
return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375;
}
else {
return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375;
}
},
/**
* @param {number} k
* @return {number}
*/
bounceInOut: function (k) {
if (k < 0.5) {
return easing.bounceIn(k * 2) * 0.5;
}
return easing.bounceOut(k * 2 - 1) * 0.5 + 0.5;
}
};
/**
* 动画主控制器
* @config target 动画对象,可以是数组,如果是数组的话会批量分发 onframe 等事件
* @config life(1000) 动画时长
* @config delay(0) 动画延迟时间
* @config loop(true)
* @config gap(0) 循环的间隔时间
* @config onframe
* @config easing(optional)
* @config ondestroy(optional)
* @config onrestart(optional)
*
* TODO pause
*/
function Clip(options) {
this._target = options.target;
// 生命周期
this._life = options.life || 1000;
// 延时
this._delay = options.delay || 0;
// 开始时间
// this._startTime = new Date().getTime() + this._delay;// 单位毫秒
this._initialized = false;
// 是否循环
this.loop = options.loop == null ? false : options.loop;
this.gap = options.gap || 0;
this.onframe = options.onframe;
this.ondestroy = options.ondestroy;
this.onrestart = options.onrestart;
this._pausedTime = 0;
this._paused = false;
}
Clip.prototype = {
constructor: Clip,
if (this._paused) {
this._pausedTime += deltaTime;
return;
}
// 还没开始
if (percent < 0) {
return;
}
this.fire('frame', schedule);
// 结束
if (percent == 1) {
if (this.loop) {
this.restart (globalTime);
// 重新开始周期
// 抛出而不是直接调用事件直到 stage.update 后再统一调用这些事件
return 'restart';
}
// 动画完成将这个控制器标识为待删除
// 在 Animation.update 中进行批量删除
this._needsRemove = true;
return 'destroy';
}
return null;
},
restart: function (globalTime) {
var remainder = (globalTime - this._startTime - this._pausedTime) %
this._life;
this._startTime = globalTime - remainder + this.gap;
this._pausedTime = 0;
this._needsRemove = false;
},
pause: function () {
this._paused = true;
},
resume: function () {
this._paused = false;
}
};
/**
* Simple double linked list. Compared with array, it has O(1) remove operation.
* @constructor
*/
var LinkedList = function () {
/**
* @type {module:zrender/core/LRU~Entry}
*/
this.head = null;
/**
* @type {module:zrender/core/LRU~Entry}
*/
this.tail = null;
this._len = 0;
};
/**
* Remove entry.
* @param {module:zrender/core/LRU~Entry} entry
*/
linkedListProto.remove = function (entry) {
var prev = entry.prev;
var next = entry.next;
if (prev) {
prev.next = next;
}
else {
// Is head
this.head = next;
}
if (next) {
next.prev = prev;
}
else {
// Is tail
this.tail = prev;
}
entry.next = entry.prev = null;
this._len--;
};
/**
* @return {number}
*/
linkedListProto.len = function () {
return this._len;
};
/**
* Clear list
*/
linkedListProto.clear = function () {
this.head = this.tail = null;
this._len = 0;
};
/**
* @constructor
* @param {} val
*/
var Entry = function (val) {
/**
* @type {}
*/
this.value = val;
/**
* @type {module:zrender/core/LRU~Entry}
*/
this.next;
/**
* @type {module:zrender/core/LRU~Entry}
*/
this.prev;
};
/**
* LRU Cache
* @constructor
* @alias module:zrender/core/LRU
*/
var LRU = function (maxSize) {
this._map = {};
this._lastRemovedEntry = null;
};
/**
* @param {string} key
* @param {} value
* @return {} Removed value
*/
LRUProto.put = function (key, value) {
var list = this._list;
var map = this._map;
var removed = null;
if (map[key] == null) {
var len = list.len();
// Reuse last removed entry
var entry = this._lastRemovedEntry;
removed = leastUsedEntry.value;
this._lastRemovedEntry = leastUsedEntry;
}
if (entry) {
entry.value = value;
}
else {
entry = new Entry(value);
}
entry.key = key;
list.insertEntry(entry);
map[key] = entry;
}
return removed;
};
/**
* @param {string} key
* @return {}
*/
LRUProto.get = function (key) {
var entry = this._map[key];
var list = this._list;
if (entry != null) {
// Put the latest used entry in the tail
if (entry !== list.tail) {
list.remove(entry);
list.insertEntry(entry);
}
return entry.value;
}
};
/**
* Clear the cache
*/
LRUProto.clear = function () {
this._list.clear();
this._map = {};
};
var kCSSColorTable = {
'transparent': [0,0,0,0], 'aliceblue': [240,248,255,1],
'antiquewhite': [250,235,215,1], 'aqua': [0,255,255,1],
'aquamarine': [127,255,212,1], 'azure': [240,255,255,1],
'beige': [245,245,220,1], 'bisque': [255,228,196,1],
'black': [0,0,0,1], 'blanchedalmond': [255,235,205,1],
'blue': [0,0,255,1], 'blueviolet': [138,43,226,1],
'brown': [165,42,42,1], 'burlywood': [222,184,135,1],
'cadetblue': [95,158,160,1], 'chartreuse': [127,255,0,1],
'chocolate': [210,105,30,1], 'coral': [255,127,80,1],
'cornflowerblue': [100,149,237,1], 'cornsilk': [255,248,220,1],
'crimson': [220,20,60,1], 'cyan': [0,255,255,1],
'darkblue': [0,0,139,1], 'darkcyan': [0,139,139,1],
'darkgoldenrod': [184,134,11,1], 'darkgray': [169,169,169,1],
'darkgreen': [0,100,0,1], 'darkgrey': [169,169,169,1],
'darkkhaki': [189,183,107,1], 'darkmagenta': [139,0,139,1],
'darkolivegreen': [85,107,47,1], 'darkorange': [255,140,0,1],
'darkorchid': [153,50,204,1], 'darkred': [139,0,0,1],
'darksalmon': [233,150,122,1], 'darkseagreen': [143,188,143,1],
'darkslateblue': [72,61,139,1], 'darkslategray': [47,79,79,1],
'darkslategrey': [47,79,79,1], 'darkturquoise': [0,206,209,1],
'darkviolet': [148,0,211,1], 'deeppink': [255,20,147,1],
'deepskyblue': [0,191,255,1], 'dimgray': [105,105,105,1],
'dimgrey': [105,105,105,1], 'dodgerblue': [30,144,255,1],
'firebrick': [178,34,34,1], 'floralwhite': [255,250,240,1],
'forestgreen': [34,139,34,1], 'fuchsia': [255,0,255,1],
'gainsboro': [220,220,220,1], 'ghostwhite': [248,248,255,1],
'gold': [255,215,0,1], 'goldenrod': [218,165,32,1],
'gray': [128,128,128,1], 'green': [0,128,0,1],
'greenyellow': [173,255,47,1], 'grey': [128,128,128,1],
'honeydew': [240,255,240,1], 'hotpink': [255,105,180,1],
'indianred': [205,92,92,1], 'indigo': [75,0,130,1],
'ivory': [255,255,240,1], 'khaki': [240,230,140,1],
'lavender': [230,230,250,1], 'lavenderblush': [255,240,245,1],
'lawngreen': [124,252,0,1], 'lemonchiffon': [255,250,205,1],
'lightblue': [173,216,230,1], 'lightcoral': [240,128,128,1],
'lightcyan': [224,255,255,1], 'lightgoldenrodyellow': [250,250,210,1],
'lightgray': [211,211,211,1], 'lightgreen': [144,238,144,1],
'lightgrey': [211,211,211,1], 'lightpink': [255,182,193,1],
'lightsalmon': [255,160,122,1], 'lightseagreen': [32,178,170,1],
'lightskyblue': [135,206,250,1], 'lightslategray': [119,136,153,1],
'lightslategrey': [119,136,153,1], 'lightsteelblue': [176,196,222,1],
'lightyellow': [255,255,224,1], 'lime': [0,255,0,1],
'limegreen': [50,205,50,1], 'linen': [250,240,230,1],
'magenta': [255,0,255,1], 'maroon': [128,0,0,1],
'mediumaquamarine': [102,205,170,1], 'mediumblue': [0,0,205,1],
'mediumorchid': [186,85,211,1], 'mediumpurple': [147,112,219,1],
'mediumseagreen': [60,179,113,1], 'mediumslateblue': [123,104,238,1],
'mediumspringgreen': [0,250,154,1], 'mediumturquoise': [72,209,204,1],
'mediumvioletred': [199,21,133,1], 'midnightblue': [25,25,112,1],
'mintcream': [245,255,250,1], 'mistyrose': [255,228,225,1],
'moccasin': [255,228,181,1], 'navajowhite': [255,222,173,1],
'navy': [0,0,128,1], 'oldlace': [253,245,230,1],
'olive': [128,128,0,1], 'olivedrab': [107,142,35,1],
'orange': [255,165,0,1], 'orangered': [255,69,0,1],
'orchid': [218,112,214,1], 'palegoldenrod': [238,232,170,1],
'palegreen': [152,251,152,1], 'paleturquoise': [175,238,238,1],
'palevioletred': [219,112,147,1], 'papayawhip': [255,239,213,1],
'peachpuff': [255,218,185,1], 'peru': [205,133,63,1],
'pink': [255,192,203,1], 'plum': [221,160,221,1],
'powderblue': [176,224,230,1], 'purple': [128,0,128,1],
'red': [255,0,0,1], 'rosybrown': [188,143,143,1],
'royalblue': [65,105,225,1], 'saddlebrown': [139,69,19,1],
'salmon': [250,128,114,1], 'sandybrown': [244,164,96,1],
'seagreen': [46,139,87,1], 'seashell': [255,245,238,1],
'sienna': [160,82,45,1], 'silver': [192,192,192,1],
'skyblue': [135,206,235,1], 'slateblue': [106,90,205,1],
'slategray': [112,128,144,1], 'slategrey': [112,128,144,1],
'snow': [255,250,250,1], 'springgreen': [0,255,127,1],
'steelblue': [70,130,180,1], 'tan': [210,180,140,1],
'teal': [0,128,128,1], 'thistle': [216,191,216,1],
'tomato': [255,99,71,1], 'turquoise': [64,224,208,1],
'violet': [238,130,238,1], 'wheat': [245,222,179,1],
'white': [255,255,255,1], 'whitesmoke': [245,245,245,1],
'yellow': [255,255,0,1], 'yellowgreen': [154,205,50,1]
};
if (h * 6 < 1) {
return m1 + (m2 - m1) * h * 6;
}
if (h * 2 < 1) {
return m2;
}
if (h * 3 < 2) {
return m1 + (m2 - m1) * (2/3 - h) * 6;
}
return m1;
}
function lerpNumber(a, b, p) {
return a + (b - a) * p;
}
function setRgba(out, r, g, b, a) {
out[0] = r; out[1] = g; out[2] = b; out[3] = a;
return out;
}
function copyRgba(out, a) {
out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3];
return out;
}
/**
* @param {string} colorStr
* @param {Array.<number>} out
* @return {Array.<number>}
* @memberOf module:zrender/util/color
*/
function parse(colorStr, rgbaArr) {
if (!colorStr) {
return;
}
rgbaArr = rgbaArr || [];
return;
}
var op = str.indexOf('('), ep = str.indexOf(')');
if (op !== -1 && ep + 1 === str.length) {
var fname = str.substr(0, op);
var params = str.substr(op + 1, ep - (op + 1)).split(',');
var alpha = 1; // To allow case fallthrough.
switch (fname) {
case 'rgba':
if (params.length !== 4) {
setRgba(rgbaArr, 0, 0, 0, 1);
return;
}
alpha = parseCssFloat(params.pop()); // jshint ignore:line
// Fall through.
case 'rgb':
if (params.length !== 3) {
setRgba(rgbaArr, 0, 0, 0, 1);
return;
}
setRgba(rgbaArr,
parseCssInt(params[0]),
parseCssInt(params[1]),
parseCssInt(params[2]),
alpha
);
putToCache(colorStr, rgbaArr);
return rgbaArr;
case 'hsla':
if (params.length !== 4) {
setRgba(rgbaArr, 0, 0, 0, 1);
return;
}
params[3] = parseCssFloat(params[3]);
hsla2rgba(params, rgbaArr);
putToCache(colorStr, rgbaArr);
return rgbaArr;
case 'hsl':
if (params.length !== 3) {
setRgba(rgbaArr, 0, 0, 0, 1);
return;
}
hsla2rgba(params, rgbaArr);
putToCache(colorStr, rgbaArr);
return rgbaArr;
default:
return;
}
}
setRgba(rgbaArr, 0, 0, 0, 1);
return;
}
/**
* @param {Array.<number>} hsla
* @param {Array.<number>} rgba
* @return {Array.<number>} rgba
*/
function hsla2rgba(hsla, rgba) {
var h = (((parseFloat(hsla[0]) % 360) + 360) % 360) / 360; // 0 .. 1
// NOTE(deanm): According to the CSS spec s/l should only be
// percentages, but we don't bother and let float or percentage.
var s = parseCssFloat(hsla[1]);
var l = parseCssFloat(hsla[2]);
var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
var m1 = l * 2 - m2;
if (hsla.length === 4) {
rgba[3] = hsla[3];
}
return rgba;
}
/**
* @param {Array.<number>} rgba
* @return {Array.<number>} hsla
*/
function rgba2hsla(rgba) {
if (!rgba) {
return;
}
if (R === vMax) {
H = deltaB - deltaG;
}
else if (G === vMax) {
H = (1 / 3) + deltaR - deltaB;
}
else if (B === vMax) {
H = (2 / 3) + deltaG - deltaR;
}
if (H < 0) {
H += 1;
}
if (H > 1) {
H -= 1;
}
}
if (rgba[3] != null) {
hsla.push(rgba[3]);
}
return hsla;
}
/**
* @param {string} color
* @param {number} level
* @return {string}
* @memberOf module:zrender/util/color
*/
function lift(color, level) {
var colorArr = parse(color);
if (colorArr) {
for (var i = 0; i < 3; i++) {
if (level < 0) {
colorArr[i] = colorArr[i] * (1 - level) | 0;
}
else {
colorArr[i] = ((255 - colorArr[i]) * level + colorArr[i]) | 0;
}
if (colorArr[i] > 255) {
colorArr[i] = 255;
}
else if (color[i] < 0) {
colorArr[i] = 0;
}
}
return stringify(colorArr, colorArr.length === 4 ? 'rgba' : 'rgb');
}
}
/**
* @param {string} color
* @return {string}
* @memberOf module:zrender/util/color
*/
function toHex(color) {
var colorArr = parse(color);
if (colorArr) {
return ((1 << 24) + (colorArr[0] << 16) + (colorArr[1] << 8) +
(+colorArr[2])).toString(16).slice(1);
}
}
/**
* Map value to color. Faster than lerp methods because color is represented by
rgba array.
* @param {number} normalizedValue A float between 0 and 1.
* @param {Array.<Array.<number>>} colors List of rgba color array
* @param {Array.<number>} [out] Mapped gba color array
* @return {Array.<number>} will be null/undefined if input illegal.
*/
function fastLerp(normalizedValue, colors, out) {
if (!(colors && colors.length)
|| !(normalizedValue >= 0 && normalizedValue <= 1)
) {
return;
}
/**
* @deprecated
*/
var fastMapToColor = fastLerp;
/**
* @param {number} normalizedValue A float between 0 and 1.
* @param {Array.<string>} colors Color list.
* @param {boolean=} fullOutput Default false.
* @return {(string|Object)} Result color. If fullOutput,
* return {color: ..., leftIndex: ..., rightIndex: ...,
value: ...},
* @memberOf module:zrender/util/color
*/
function lerp$1(normalizedValue, colors, fullOutput) {
if (!(colors && colors.length)
|| !(normalizedValue >= 0 && normalizedValue <= 1)
) {
return;
}
return fullOutput
? {
color: color,
leftIndex: leftIndex,
rightIndex: rightIndex,
value: value
}
: color;
}
/**
* @deprecated
*/
var mapToColor = lerp$1;
/**
* @param {string} color
* @param {number=} h 0 ~ 360, ignore when null.
* @param {number=} s 0 ~ 1, ignore when null.
* @param {number=} l 0 ~ 1, ignore when null.
* @return {string} Color string in rgba format.
* @memberOf module:zrender/util/color
*/
function modifyHSL(color, h, s, l) {
color = parse(color);
if (color) {
color = rgba2hsla(color);
h != null && (color[0] = clampCssAngle(h));
s != null && (color[1] = parseCssFloat(s));
l != null && (color[2] = parseCssFloat(l));
/**
* @param {string} color
* @param {number=} alpha 0 ~ 1
* @return {string} Color string in rgba format.
* @memberOf module:zrender/util/color
*/
function modifyAlpha(color, alpha) {
color = parse(color);
/**
* @param {Array.<number>} arrColor like [12,33,44,0.4]
* @param {string} type 'rgba', 'hsva', ...
* @return {string} Result color. (If input illegal, return undefined).
*/
function stringify(arrColor, type) {
if (!arrColor || !arrColor.length) {
return;
}
var colorStr = arrColor[0] + ',' + arrColor[1] + ',' + arrColor[2];
if (type === 'rgba' || type === 'hsva' || type === 'hsla') {
colorStr += ',' + arrColor[3];
}
return type + '(' + colorStr + ')';
}
/**
* @module echarts/animation/Animator
*/
/**
* @param {number} p0
* @param {number} p1
* @param {number} percent
* @return {number}
*/
function interpolateNumber(p0, p1, percent) {
return (p1 - p0) * percent + p0;
}
/**
* @param {string} p0
* @param {string} p1
* @param {number} percent
* @return {string}
*/
function interpolateString(p0, p1, percent) {
return percent > 0.5 ? p1 : p0;
}
/**
* @param {Array} p0
* @param {Array} p1
* @param {number} percent
* @param {Array} out
* @param {number} arrDim
*/
function interpolateArray(p0, p1, percent, out, arrDim) {
var len = p0.length;
if (arrDim == 1) {
for (var i = 0; i < len; i++) {
out[i] = interpolateNumber(p0[i], p1[i], percent);
}
}
else {
var len2 = len && p0[0].length;
for (var i = 0; i < len; i++) {
for (var j = 0; j < len2; j++) {
out[i][j] = interpolateNumber(
p0[i][j], p1[i][j], percent
);
}
}
}
}
/**
* @param {Array} arr0
* @param {Array} arr1
* @param {number} arrDim
* @return {boolean}
*/
function isArraySame(arr0, arr1, arrDim) {
if (arr0 === arr1) {
return true;
}
var len = arr0.length;
if (len !== arr1.length) {
return false;
}
if (arrDim === 1) {
for (var i = 0; i < len; i++) {
if (arr0[i] !== arr1[i]) {
return false;
}
}
}
else {
var len2 = arr0[0].length;
for (var i = 0; i < len; i++) {
for (var j = 0; j < len2; j++) {
if (arr0[i][j] !== arr1[i][j]) {
return false;
}
}
}
}
return true;
}
/**
* Catmull Rom interpolate array
* @param {Array} p0
* @param {Array} p1
* @param {Array} p2
* @param {Array} p3
* @param {number} t
* @param {number} t2
* @param {number} t3
* @param {Array} out
* @param {number} arrDim
*/
function catmullRomInterpolateArray(
p0, p1, p2, p3, t, t2, t3, out, arrDim
) {
var len = p0.length;
if (arrDim == 1) {
for (var i = 0; i < len; i++) {
out[i] = catmullRomInterpolate(
p0[i], p1[i], p2[i], p3[i], t, t2, t3
);
}
}
else {
var len2 = p0[0].length;
for (var i = 0; i < len; i++) {
for (var j = 0; j < len2; j++) {
out[i][j] = catmullRomInterpolate(
p0[i][j], p1[i][j], p2[i][j], p3[i][j],
t, t2, t3
);
}
}
}
}
/**
* Catmull Rom interpolate number
* @param {number} p0
* @param {number} p1
* @param {number} p2
* @param {number} p3
* @param {number} t
* @param {number} t2
* @param {number} t3
* @return {number}
*/
function catmullRomInterpolate(p0, p1, p2, p3, t, t2, t3) {
var v0 = (p2 - p0) * 0.5;
var v1 = (p3 - p1) * 0.5;
return (2 * (p1 - p2) + v0 + v1) * t3
+ (-3 * (p1 - p2) - 2 * v0 - v1) * t2
+ v0 * t + p1;
}
function cloneValue(value) {
if (isArrayLike(value)) {
var len = value.length;
if (isArrayLike(value[0])) {
var ret = [];
for (var i = 0; i < len; i++) {
ret.push(arraySlice.call(value[i]));
}
return ret;
}
return arraySlice.call(value);
}
return value;
}
function rgba2String(rgba) {
rgba[0] = Math.floor(rgba[0]);
rgba[1] = Math.floor(rgba[1]);
rgba[2] = Math.floor(rgba[2]);
function getArrayDim(keyframes) {
var lastValue = keyframes[keyframes.length - 1].value;
return isArrayLike(lastValue && lastValue[0]) ? 2 : 1;
}
var trackMaxTime;
// Sort keyframe as ascending
keyframes.sort(function(a, b) {
return a.time - b.time;
});
if (isValueColor) {
var rgba = [0, 0, 0, 0];
}
return clip;
}
/**
* @alias module:zrender/animation/Animator
* @constructor
* @param {Object} target
* @param {boolean} loop
* @param {Function} getter
* @param {Function} setter
*/
var Animator = function(target, loop, getter, setter) {
this._tracks = {};
this._target = target;
this._clipCount = 0;
this._delay = 0;
this._doneList = [];
this._onframeList = [];
this._clipList = [];
};
Animator.prototype = {
/**
* 设置动画关键帧
* @param {number} time 关键帧时间,单位是 ms
* @param {Object} props 关键帧的属性值,key-value 表示
* @return {module:zrender/animation/Animator}
*/
when: function(time /* ms */, props) {
var tracks = this._tracks;
for (var propName in props) {
if (!props.hasOwnProperty(propName)) {
continue;
}
if (!tracks[propName]) {
tracks[propName] = [];
// Invalid value
var value = this._getter(this._target, propName);
if (value == null) {
// zrLog('Invalid property ' + propName);
continue;
}
// If time is 0
// Then props is given initialize value
// Else
// Initialize value from current prop value
if (time !== 0) {
tracks[propName].push({
time: 0,
value: cloneValue(value)
});
}
}
tracks[propName].push({
time: time,
value: props[propName]
});
}
return this;
},
/**
* 添加动画每一帧的回调函数
* @param {Function} callback
* @return {module:zrender/animation/Animator}
*/
during: function (callback) {
this._onframeList.push(callback);
return this;
},
pause: function () {
for (var i = 0; i < this._clipList.length; i++) {
this._clipList[i].pause();
}
this._paused = true;
},
resume: function () {
for (var i = 0; i < this._clipList.length; i++) {
this._clipList[i].resume();
}
this._paused = false;
},
isPaused: function () {
return !!this._paused;
},
_doneCallback: function () {
// Clear all tracks
this._tracks = {};
// Clear all clips
this._clipList.length = 0;
var lastClip;
for (var propName in this._tracks) {
if (!this._tracks.hasOwnProperty(propName)) {
continue;
}
var clip = createTrackClip(
this, easing, oneTrackDone,
this._tracks[propName], propName, forceAnimate
);
if (clip) {
this._clipList.push(clip);
clipCount++;
lastClip = clip;
}
}
// Add during callback on the last clip
if (lastClip) {
var oldOnFrame = lastClip.onframe;
lastClip.onframe = function (target, percent) {
oldOnFrame(target, percent);
// This optimization will help the case that in the upper application
// the view may be refreshed frequently, where animation will be
// called repeatly but nothing changed.
if (!clipCount) {
this._doneCallback();
}
return this;
},
/**
* 停止动画
* @param {boolean} forwardToLast If move to last frame before stop
*/
stop: function (forwardToLast) {
var clipList = this._clipList;
var animation = this.animation;
for (var i = 0; i < clipList.length; i++) {
var clip = clipList[i];
if (forwardToLast) {
// Move to last frame before stop
clip.onframe(this._target, 1);
}
animation && animation.removeClip(clip);
}
clipList.length = 0;
},
/**
* 设置动画延迟开始的时间
* @param {number} time 单位 ms
* @return {module:zrender/animation/Animator}
*/
delay: function (time) {
this._delay = time;
return this;
},
/**
* 添加动画结束的回调
* @param {Function} cb
* @return {module:zrender/animation/Animator}
*/
done: function(cb) {
if (cb) {
this._doneList.push(cb);
}
return this;
},
/**
* @return {Array.<module:zrender/animation/Clip>}
*/
getClips: function () {
return this._clipList;
}
};
var dpr = 1;
// If in browser environment
if (typeof window !== 'undefined') {
dpr = Math.max(window.devicePixelRatio || 1, 1);
}
/**
* config 默认配置项
* @exports zrender/config
* @author Kener (@Kener-林峰, [email protected])
*/
/**
* debug 日志选项:catchBrushException 为 true 下有效
* 0 : 不生成 debug 数据,发布用
* 1 : 异常抛出,调试用
* 2 : 控制台输出,调试用
*/
var debugMode = 0;
// retina 屏幕优化
var devicePixelRatio = dpr;
if (debugMode === 1) {
log = function () {
for (var k in arguments) {
throw new Error(arguments[k]);
}
};
}
else if (debugMode > 1) {
log = function () {
for (var k in arguments) {
console.log(arguments[k]);
}
};
}
/**
* @alias modue:zrender/mixin/Animatable
* @constructor
*/
var Animatable = function () {
/**
* @type {Array.<module:zrender/animation/Animator>}
* @readOnly
*/
this.animators = [];
};
Animatable.prototype = {
constructor: Animatable,
/**
* 动画
*
* @param {string} path The path to fetch value from object, like 'a.b.c'.
* @param {boolean} [loop] Whether to loop animation.
* @return {module:zrender/animation/Animator}
* @example:
* el.animate('style', false)
* .when(1000, {x: 10} )
* .done(function(){ // Animation done })
* .start()
*/
animate: function (path, loop) {
var target;
var animatingShape = false;
var el = this;
var zr = this.__zr;
if (path) {
var pathSplitted = path.split('.');
var prop = el;
// If animating shape
animatingShape = pathSplitted[0] === 'shape';
for (var i = 0, l = pathSplitted.length; i < l; i++) {
if (!prop) {
continue;
}
prop = prop[pathSplitted[i]];
}
if (prop) {
target = prop;
}
}
else {
target = el;
}
if (!target) {
zrLog(
'Property "'
+ path
+ '" is not existed in element '
+ el.id
);
return;
}
animators.push(animator);
return animator;
},
/**
* 停止动画
* @param {boolean} forwardToLast If move to last frame before stop
*/
stopAnimation: function (forwardToLast) {
var animators = this.animators;
var len = animators.length;
for (var i = 0; i < len; i++) {
animators[i].stop(forwardToLast);
}
animators.length = 0;
return this;
},
/**
* Caution: this method will stop previous animation.
* So do not use this method to one element twice before
* animation starts, unless you know what you are doing.
* @param {Object} target
* @param {number} [time=500] Time in ms
* @param {string} [easing='linear']
* @param {number} [delay=0]
* @param {Function} [callback]
* @param {Function} [forceAnimate] Prevent stop animation and callback
* immediently when target values are the same as current values.
*
* @example
* // Animate position
* el.animateTo({
* position: [10, 10]
* }, function () { // done })
*
* // Animate shape, style and position in 100ms, delayed 100ms, with cubicOut
easing
* el.animateTo({
* shape: {
* width: 500
* },
* style: {
* fill: 'red'
* }
* position: [10, 10]
* }, 100, 100, 'cubicOut', function () { // done })
*/
// TODO Return animation key
animateTo: function (target, time, delay, easing, callback, forceAnimate) {
// animateTo(target, time, easing, callback);
if (isString(delay)) {
callback = easing;
easing = delay;
delay = 0;
}
// animateTo(target, time, delay, callback);
else if (isFunction$1(easing)) {
callback = easing;
easing = 'linear';
delay = 0;
}
// animateTo(target, time, callback);
else if (isFunction$1(delay)) {
callback = delay;
delay = 0;
}
// animateTo(target, callback)
else if (isFunction$1(time)) {
callback = time;
time = 500;
}
// animateTo(target)
else if (!time) {
time = 500;
}
// Stop all previous animations
this.stopAnimation();
this._animateToShallow('', this, target, time, delay);
/**
* @private
* @param {string} path=''
* @param {Object} source=this
* @param {Object} target
* @param {number} [time=500]
* @param {number} [delay=0]
*
* @example
* // Animate position
* el._animateToShallow({
* position: [10, 10]
* })
*
* // Animate shape, style and position in 100ms, delayed 100ms
* el._animateToShallow({
* shape: {
* width: 500
* },
* style: {
* fill: 'red'
* }
* position: [10, 10]
* }, 100, 100)
*/
_animateToShallow: function (path, source, target, time, delay) {
var objShallow = {};
var propertyCount = 0;
for (var name in target) {
if (!target.hasOwnProperty(name)) {
continue;
}
if (source[name] != null) {
if (isObject$1(target[name]) && !isArrayLike(target[name])) {
this._animateToShallow(
path ? path + '.' + name : name,
source[name],
target[name],
time,
delay
);
}
else {
objShallow[name] = target[name];
propertyCount++;
}
}
else if (target[name] != null) {
// Attr directly if not has property
// FIXME, if some property not needed for element ?
if (!path) {
this.attr(name, target[name]);
}
else { // Shape or style
var props = {};
props[path] = {};
props[path][name] = target[name];
this.attr(props);
}
}
}
if (propertyCount > 0) {
this.animate(path, false)
.when(time == null ? 500 : time, objShallow)
.delay(delay || 0);
}
return this;
}
};
/**
* @alias module:zrender/Element
* @constructor
* @extends {module:zrender/mixin/Animatable}
* @extends {module:zrender/mixin/Transformable}
* @extends {module:zrender/mixin/Eventful}
*/
var Element = function (opts) { // jshint ignore:line
Transformable.call(this, opts);
Eventful.call(this, opts);
Animatable.call(this, opts);
/**
* 画布元素 ID
* @type {string}
*/
this.id = opts.id || guid();
};
Element.prototype = {
/**
* 元素类型
* Element type
* @type {string}
*/
type: 'element',
/**
* 元素名字
* Element name
* @type {string}
*/
name: '',
/**
* ZRender 实例对象,会在 element 添加到 zrender 实例中后自动赋值
* ZRender instance will be assigned when element is associated with zrender
* @name module:/zrender/Element#__zr
* @type {module:zrender/ZRender}
*/
__zr: null,
/**
* 图形是否忽略,为 true 时忽略图形的绘制以及事件触发
* If ignore drawing and events of the element object
* @name module:/zrender/Element#ignore
* @type {boolean}
* @default false
*/
ignore: false,
/**
* 用于裁剪的路径(shape),所有 Group 内的路径在绘制时都会被这个路径裁剪
* 该路径会继承被裁减对象的变换
* @type {module:zrender/graphic/Path}
* @see http://www.w3.org/TR/2dcontext/#clipping-region
* @readOnly
*/
clipPath: null,
/**
* 是否是 Group
* @type {boolean}
*/
isGroup: false,
/**
* Drift element
* @param {number} dx dx on the global space
* @param {number} dy dy on the global space
*/
drift: function (dx, dy) {
switch (this.draggable) {
case 'horizontal':
dy = 0;
break;
case 'vertical':
dx = 0;
break;
}
var m = this.transform;
if (!m) {
m = this.transform = [1, 0, 0, 1, 0, 0];
}
m[4] += dx;
m[5] += dy;
this.decomposeTransform();
this.dirty(false);
},
/**
* Hook before update
*/
beforeUpdate: function () {},
/**
* Hook after update
*/
afterUpdate: function () {},
/**
* Update each frame
*/
update: function () {
this.updateTransform();
},
/**
* @param {Function} cb
* @param {} context
*/
traverse: function (cb, context) {},
/**
* @protected
*/
attrKV: function (key, value) {
if (key === 'position' || key === 'scale' || key === 'origin') {
// Copy the array
if (value) {
var target = this[key];
if (!target) {
target = this[key] = [];
}
target[0] = value[0];
target[1] = value[1];
}
}
else {
this[key] = value;
}
},
/**
* Hide the element
*/
hide: function () {
this.ignore = true;
this.__zr && this.__zr.refresh();
},
/**
* Show the element
*/
show: function () {
this.ignore = false;
this.__zr && this.__zr.refresh();
},
/**
* @param {string|Object} key
* @param {*} value
*/
attr: function (key, value) {
if (typeof key === 'string') {
this.attrKV(key, value);
}
else if (isObject$1(key)) {
for (var name in key) {
if (key.hasOwnProperty(name)) {
this.attrKV(name, key[name]);
}
}
}
this.dirty(false);
return this;
},
/**
* @param {module:zrender/graphic/Path} clipPath
*/
setClipPath: function (clipPath) {
var zr = this.__zr;
if (zr) {
clipPath.addSelfToZr(zr);
}
this.clipPath = clipPath;
clipPath.__zr = zr;
clipPath.__clipTarget = this;
this.dirty(false);
},
/**
*/
removeClipPath: function () {
var clipPath = this.clipPath;
if (clipPath) {
if (clipPath.__zr) {
clipPath.removeSelfFromZr(clipPath.__zr);
}
clipPath.__zr = null;
clipPath.__clipTarget = null;
this.clipPath = null;
this.dirty(false);
}
},
/**
* Add self from zrender instance.
* Not recursively because it will be invoked when element added to storage.
* @param {module:zrender/ZRender} zr
*/
addSelfToZr: function (zr) {
this.__zr = zr;
// 添加动画
var animators = this.animators;
if (animators) {
for (var i = 0; i < animators.length; i++) {
zr.animation.addAnimator(animators[i]);
}
}
if (this.clipPath) {
this.clipPath.addSelfToZr(zr);
}
},
/**
* Remove self from zrender instance.
* Not recursively because it will be invoked when element added to storage.
* @param {module:zrender/ZRender} zr
*/
removeSelfFromZr: function (zr) {
this.__zr = null;
// 移除动画
var animators = this.animators;
if (animators) {
for (var i = 0; i < animators.length; i++) {
zr.animation.removeAnimator(animators[i]);
}
}
if (this.clipPath) {
this.clipPath.removeSelfFromZr(zr);
}
}
};
mixin(Element, Animatable);
mixin(Element, Transformable);
mixin(Element, Eventful);
/**
* @module echarts/core/BoundingRect
*/
/**
* @alias module:echarts/core/BoundingRect
*/
function BoundingRect(x, y, width, height) {
if (width < 0) {
x = x + width;
width = -width;
}
if (height < 0) {
y = y + height;
height = -height;
}
/**
* @type {number}
*/
this.x = x;
/**
* @type {number}
*/
this.y = y;
/**
* @type {number}
*/
this.width = width;
/**
* @type {number}
*/
this.height = height;
}
BoundingRect.prototype = {
constructor: BoundingRect,
/**
* @param {module:echarts/core/BoundingRect} other
*/
union: function (other) {
var x = mathMin(other.x, this.x);
var y = mathMin(other.y, this.y);
this.width = mathMax(
other.x + other.width,
this.x + this.width
) - x;
this.height = mathMax(
other.y + other.height,
this.y + this.height
) - y;
this.x = x;
this.y = y;
},
/**
* @param {Array.<number>} m
* @methods
*/
applyTransform: (function () {
var lt = [];
var rb = [];
var lb = [];
var rt = [];
return function (m) {
// In case usage like this
// el.getBoundingRect().applyTransform(el.transform)
// And element has no transform
if (!m) {
return;
}
lt[0] = lb[0] = this.x;
lt[1] = rt[1] = this.y;
rb[0] = rt[0] = this.x + this.width;
rb[1] = lb[1] = this.y + this.height;
/**
* Calculate matrix of transforming from self to target rect
* @param {module:zrender/core/BoundingRect} b
* @return {Array.<number>}
*/
calculateTransform: function (b) {
var a = this;
var sx = b.width / a.width;
var sy = b.height / a.height;
var m = create$1();
// 矩阵右乘
translate(m, m, [-a.x, -a.y]);
scale$1(m, m, [sx, sy]);
translate(m, m, [b.x, b.y]);
return m;
},
/**
* @param {(module:echarts/core/BoundingRect|Object)} b
* @return {boolean}
*/
intersect: function (b) {
if (!b) {
return false;
}
var a = this;
var ax0 = a.x;
var ax1 = a.x + a.width;
var ay0 = a.y;
var ay1 = a.y + a.height;
return ! (ax1 < bx0 || bx1 < ax0 || ay1 < by0 || by1 < ay0);
},
/**
* @return {module:echarts/core/BoundingRect}
*/
clone: function () {
return new BoundingRect(this.x, this.y, this.width, this.height);
},
/**
* Copy from another rect
*/
copy: function (other) {
this.x = other.x;
this.y = other.y;
this.width = other.width;
this.height = other.height;
},
plain: function () {
return {
x: this.x,
y: this.y,
width: this.width,
height: this.height
};
}
};
/**
* @param {Object|module:zrender/core/BoundingRect} rect
* @param {number} rect.x
* @param {number} rect.y
* @param {number} rect.width
* @param {number} rect.height
* @return {module:zrender/core/BoundingRect}
*/
BoundingRect.create = function (rect) {
return new BoundingRect(rect.x, rect.y, rect.width, rect.height);
};
/**
* Group 是一个容器,可以插入子节点,Group 的变换也会被应用到子节点上
* @module zrender/graphic/Group
* @example
* var Group = require('zrender/container/Group');
* var Circle = require('zrender/graphic/shape/Circle');
* var g = new Group();
* g.position[0] = 100;
* g.position[1] = 100;
* g.add(new Circle({
* style: {
* x: 100,
* y: 100,
* r: 20,
* }
* }));
* zr.add(g);
*/
/**
* @alias module:zrender/graphic/Group
* @constructor
* @extends module:zrender/mixin/Transformable
* @extends module:zrender/mixin/Eventful
*/
var Group = function (opts) {
Element.call(this, opts);
this._children = [];
this.__storage = null;
this.__dirty = true;
};
Group.prototype = {
constructor: Group,
isGroup: true,
/**
* @type {string}
*/
type: 'group',
/**
* 所有子孙元素是否响应鼠标事件
* @name module:/zrender/container/Group#silent
* @type {boolean}
* @default false
*/
silent: false,
/**
* @return {Array.<module:zrender/Element>}
*/
children: function () {
return this._children.slice();
},
/**
* 获取指定 index 的儿子节点
* @param {number} idx
* @return {module:zrender/Element}
*/
childAt: function (idx) {
return this._children[idx];
},
/**
* 获取指定名字的儿子节点
* @param {string} name
* @return {module:zrender/Element}
*/
childOfName: function (name) {
var children = this._children;
for (var i = 0; i < children.length; i++) {
if (children[i].name === name) {
return children[i];
}
}
},
/**
* @return {number}
*/
childCount: function () {
return this._children.length;
},
/**
* 添加子节点到最后
* @param {module:zrender/Element} child
*/
add: function (child) {
if (child && child !== this && child.parent !== this) {
this._children.push(child);
this._doAdd(child);
}
return this;
},
/**
* 添加子节点在 nextSibling 之前
* @param {module:zrender/Element} child
* @param {module:zrender/Element} nextSibling
*/
addBefore: function (child, nextSibling) {
if (child && child !== this && child.parent !== this
&& nextSibling && nextSibling.parent === this) {
var children = this._children;
var idx = children.indexOf(nextSibling);
if (idx >= 0) {
children.splice(idx, 0, child);
this._doAdd(child);
}
}
return this;
},
child.parent = this;
storage.addToStorage(child);
zr && zr.refresh();
},
/**
* 移除子节点
* @param {module:zrender/Element} child
*/
remove: function (child) {
var zr = this.__zr;
var storage = this.__storage;
var children = this._children;
child.parent = null;
if (storage) {
storage.delFromStorage(child);
return this;
},
/**
* 移除所有子节点
*/
removeAll: function () {
var children = this._children;
var storage = this.__storage;
var child;
var i;
for (i = 0; i < children.length; i++) {
child = children[i];
if (storage) {
storage.delFromStorage(child);
if (child instanceof Group) {
child.delChildrenFromStorage(storage);
}
}
child.parent = null;
}
children.length = 0;
return this;
},
/**
* 遍历所有子节点
* @param {Function} cb
* @param {} context
*/
eachChild: function (cb, context) {
var children = this._children;
for (var i = 0; i < children.length; i++) {
var child = children[i];
cb.call(context, child, i);
}
return this;
},
/**
* 深度优先遍历所有子孙节点
* @param {Function} cb
* @param {} context
*/
traverse: function (cb, context) {
for (var i = 0; i < this._children.length; i++) {
var child = this._children[i];
cb.call(context, child);
dirty: function () {
this.__dirty = true;
this.__zr && this.__zr.refresh();
return this;
},
/**
* @return {module:zrender/core/BoundingRect}
*/
getBoundingRect: function (includeChildren) {
// TODO Caching
var rect = null;
var tmpRect = new BoundingRect(0, 0, 0, 0);
var children = includeChildren || this._children;
var tmpMat = [];
inherits(Group, Element);
// https://github.com/mziccard/node-timsort
var DEFAULT_MIN_MERGE = 32;
var DEFAULT_MIN_GALLOPING = 7;
function minRunLength(n) {
var r = 0;
return n + r;
}
switch (n) {
case 3:
array[left + 3] = array[left + 2];
case 2:
array[left + 2] = array[left + 1];
case 1:
array[left + 1] = array[left];
break;
default:
while (n > 0) {
array[left + n] = array[left + n - 1];
n--;
}
}
array[left] = pivot;
}
}
while (offset < maxOffset && compare(value, array[start + hint + offset]) >
0) {
lastOffset = offset;
offset = (offset << 1) + 1;
if (offset <= 0) {
offset = maxOffset;
}
}
lastOffset += hint;
offset += hint;
}
else {
maxOffset = hint + 1;
while (offset < maxOffset && compare(value, array[start + hint - offset])
<= 0) {
lastOffset = offset;
offset = (offset << 1) + 1;
if (offset <= 0) {
offset = maxOffset;
}
}
if (offset > maxOffset) {
offset = maxOffset;
}
lastOffset++;
while (lastOffset < offset) {
var m = lastOffset + (offset - lastOffset >>> 1);
while (offset < maxOffset && compare(value, array[start + hint - offset]) <
0) {
lastOffset = offset;
offset = (offset << 1) + 1;
if (offset <= 0) {
offset = maxOffset;
}
}
if (offset <= 0) {
offset = maxOffset;
}
}
lastOffset += hint;
offset += hint;
}
lastOffset++;
return offset;
}
runStart = [];
runLength = [];
function pushRun(_runStart, _runLength) {
runStart[stackSize] = _runStart;
runLength[stackSize] = _runLength;
stackSize += 1;
}
function mergeRuns() {
while (stackSize > 1) {
var n = stackSize - 2;
function forceMergeRuns() {
while (stackSize > 1) {
var n = stackSize - 2;
mergeAt(n);
}
}
function mergeAt(i) {
var start1 = runStart[i];
var length1 = runLength[i];
var start2 = runStart[i + 1];
var length2 = runLength[i + 1];
if (i === stackSize - 3) {
runStart[i + 1] = runStart[i + 2];
runLength[i + 1] = runLength[i + 2];
}
stackSize--;
if (length1 === 0) {
return;
}
length2 = gallopLeft(array[start1 + length1 - 1], array, start2, length2,
length2 - 1, compare);
if (length2 === 0) {
return;
}
var cursor1 = 0;
var cursor2 = start2;
var dest = start1;
array[dest++] = array[cursor2++];
if (--length2 === 0) {
for (i = 0; i < length1; i++) {
array[dest + i] = tmp[cursor1 + i];
}
return;
}
if (length1 === 1) {
for (i = 0; i < length2; i++) {
array[dest + i] = array[cursor2 + i];
}
array[dest + length2] = tmp[cursor1];
return;
}
while (1) {
count1 = 0;
count2 = 0;
exit = false;
do {
if (compare(array[cursor2], tmp[cursor1]) < 0) {
array[dest++] = array[cursor2++];
count2++;
count1 = 0;
if (--length2 === 0) {
exit = true;
break;
}
}
else {
array[dest++] = tmp[cursor1++];
count1++;
count2 = 0;
if (--length1 === 1) {
exit = true;
break;
}
}
} while ((count1 | count2) < _minGallop);
if (exit) {
break;
}
do {
count1 = gallopRight(array[cursor2], tmp, cursor1, length1, 0,
compare);
if (count1 !== 0) {
for (i = 0; i < count1; i++) {
array[dest + i] = tmp[cursor1 + i];
}
dest += count1;
cursor1 += count1;
length1 -= count1;
if (length1 <= 1) {
exit = true;
break;
}
}
array[dest++] = array[cursor2++];
if (--length2 === 0) {
exit = true;
break;
}
if (count2 !== 0) {
for (i = 0; i < count2; i++) {
array[dest + i] = array[cursor2 + i];
}
dest += count2;
cursor2 += count2;
length2 -= count2;
if (length2 === 0) {
exit = true;
break;
}
}
array[dest++] = tmp[cursor1++];
if (--length1 === 1) {
exit = true;
break;
}
_minGallop--;
} while (count1 >= DEFAULT_MIN_GALLOPING || count2 >=
DEFAULT_MIN_GALLOPING);
if (exit) {
break;
}
if (_minGallop < 0) {
_minGallop = 0;
}
_minGallop += 2;
}
minGallop = _minGallop;
if (length1 === 1) {
for (i = 0; i < length2; i++) {
array[dest + i] = array[cursor2 + i];
}
array[dest + length2] = tmp[cursor1];
}
else if (length1 === 0) {
throw new Error();
// throw new Error('mergeLow preconditions were not respected');
}
else {
for (i = 0; i < length1; i++) {
array[dest + i] = tmp[cursor1 + i];
}
}
}
array[dest--] = array[cursor1--];
if (--length1 === 0) {
customCursor = dest - (length2 - 1);
return;
}
if (length2 === 1) {
dest -= length1;
cursor1 -= length1;
customDest = dest + 1;
customCursor = cursor1 + 1;
array[dest] = tmp[cursor2];
return;
}
while (true) {
var count1 = 0;
var count2 = 0;
var exit = false;
do {
if (compare(tmp[cursor2], array[cursor1]) < 0) {
array[dest--] = array[cursor1--];
count1++;
count2 = 0;
if (--length1 === 0) {
exit = true;
break;
}
}
else {
array[dest--] = tmp[cursor2--];
count2++;
count1 = 0;
if (--length2 === 1) {
exit = true;
break;
}
}
} while ((count1 | count2) < _minGallop);
if (exit) {
break;
}
do {
count1 = length1 - gallopRight(tmp[cursor2], array, start1,
length1, length1 - 1, compare);
if (count1 !== 0) {
dest -= count1;
cursor1 -= count1;
length1 -= count1;
customDest = dest + 1;
customCursor = cursor1 + 1;
if (length1 === 0) {
exit = true;
break;
}
}
array[dest--] = tmp[cursor2--];
if (--length2 === 1) {
exit = true;
break;
}
if (count2 !== 0) {
dest -= count2;
cursor2 -= count2;
length2 -= count2;
customDest = dest + 1;
customCursor = cursor2 + 1;
if (length2 <= 1) {
exit = true;
break;
}
}
array[dest--] = array[cursor1--];
if (--length1 === 0) {
exit = true;
break;
}
_minGallop--;
} while (count1 >= DEFAULT_MIN_GALLOPING || count2 >=
DEFAULT_MIN_GALLOPING);
if (exit) {
break;
}
if (_minGallop < 0) {
_minGallop = 0;
}
_minGallop += 2;
}
minGallop = _minGallop;
if (minGallop < 1) {
minGallop = 1;
}
if (length2 === 1) {
dest -= length1;
cursor1 -= length1;
customDest = dest + 1;
customCursor = cursor1 + 1;
array[dest] = tmp[cursor2];
}
else if (length2 === 0) {
throw new Error();
// throw new Error('mergeHigh preconditions were not respected');
}
else {
customCursor = dest - (length2 - 1);
for (i = 0; i < length2; i++) {
array[customCursor + i] = tmp[i];
}
}
}
this.mergeRuns = mergeRuns;
this.forceMergeRuns = forceMergeRuns;
this.pushRun = pushRun;
}
if (remaining < 2) {
return;
}
var runLength = 0;
if (remaining < DEFAULT_MIN_MERGE) {
runLength = makeAscendingRun(array, lo, hi, compare);
binaryInsertionSort(array, lo, hi, lo + runLength, compare);
return;
}
do {
runLength = makeAscendingRun(array, lo, hi, compare);
if (runLength < minRun) {
var force = remaining;
if (force > minRun) {
force = minRun;
}
ts.pushRun(lo, runLength);
ts.mergeRuns();
remaining -= runLength;
lo += runLength;
} while (remaining !== 0);
ts.forceMergeRuns();
}
this._displayListLen = 0;
};
Storage.prototype = {
constructor: Storage,
/**
* @param {Function} cb
*
*/
traverse: function (cb, context) {
for (var i = 0; i < this._roots.length; i++) {
this._roots[i].traverse(cb, context);
}
},
/**
* 返回所有图形的绘制队列
* @param {boolean} [update=false] 是否在返回前更新该数组
* @param {boolean} [includeIgnore=false] 是否包含 ignore 的数组, 在 update 为
true 的时候有效
*
* 详见{@link module:zrender/graphic/Displayable.prototype.updateDisplayList}
* @return {Array.<module:zrender/graphic/Displayable>}
*/
getDisplayList: function (update, includeIgnore) {
includeIgnore = includeIgnore || false;
if (update) {
this.updateDisplayList(includeIgnore);
}
return this._displayList;
},
/**
* 更新图形的绘制队列。
* 每次绘制前都会调用,该方法会先深度优先遍历整个树,更新所有 Group 和 Shape 的变换并且把所有
可见的 Shape 保存到数组中,
* 最后根据绘制的优先级(zlevel > z > 插入顺序)排序得到绘制队列
* @param {boolean} [includeIgnore=false] 是否包含 ignore 的数组
*/
updateDisplayList: function (includeIgnore) {
this._displayListLen = 0;
displayList.length = this._displayListLen;
el.beforeUpdate();
if (el.__dirty) {
el.update();
el.afterUpdate();
// FIXME 效率影响
if (clipPaths) {
clipPaths = clipPaths.slice();
}
else {
clipPaths = [];
}
clipPaths.push(currentClipPath);
parentClipPath = currentClipPath;
currentClipPath = currentClipPath.clipPath;
}
}
if (el.isGroup) {
var children = el._children;
this._displayList[this._displayListLen++] = el;
}
},
/**
* 添加图形(Shape)或者组(Group)到根节点
* @param {module:zrender/Element} el
*/
addRoot: function (el) {
if (el.__storage === this) {
return;
}
this.addToStorage(el);
this._roots.push(el);
},
/**
* 删除指定的图形(Shape)或者组(Group)
* @param {string|Array.<string>} [el] 如果为空清空整个 Storage
*/
delRoot: function (el) {
if (el == null) {
// 不指定 el 清空
for (var i = 0; i < this._roots.length; i++) {
var root = this._roots[i];
if (root instanceof Group) {
root.delChildrenFromStorage(this);
}
}
this._roots = [];
this._displayList = [];
this._displayListLen = 0;
return;
}
return this;
},
/**
* 清空并且释放 Storage
*/
dispose: function () {
this._renderList =
this._roots = null;
},
displayableSortFunc: shapeCompareFunc
};
var SHADOW_PROPS = {
'shadowBlur': 1,
'shadowOffsetX': 1,
'shadowOffsetY': 1,
'textShadowBlur': 1,
'textShadowOffsetX': 1,
'textShadowOffsetY': 1,
'textBoxShadowBlur': 1,
'textBoxShadowOffsetX': 1,
'textBoxShadowOffsetY': 1
};
var STYLE_COMMON_PROPS = [
['shadowBlur', 0], ['shadowOffsetX', 0], ['shadowOffsetY', 0], ['shadowColor',
'#000'],
['lineCap', 'butt'], ['lineJoin', 'miter'], ['miterLimit', 10]
];
if (!obj.global) {
x = x * rect.width + rect.x;
x2 = x2 * rect.width + rect.x;
y = y * rect.height + rect.y;
y2 = y2 * rect.height + rect.y;
}
return canvasGradient;
}
return canvasGradient;
}
Style.prototype = {
constructor: Style,
/**
* @type {module:zrender/graphic/Displayable}
*/
host: null,
/**
* @type {string}
*/
fill: '#000',
/**
* @type {string}
*/
stroke: null,
/**
* @type {number}
*/
opacity: 1,
/**
* @type {Array.<number>}
*/
lineDash: null,
/**
* @type {number}
*/
lineDashOffset: 0,
/**
* @type {number}
*/
shadowBlur: 0,
/**
* @type {number}
*/
shadowOffsetX: 0,
/**
* @type {number}
*/
shadowOffsetY: 0,
/**
* @type {number}
*/
lineWidth: 1,
/**
* If stroke ignore scale
* @type {Boolean}
*/
strokeNoScale: false,
/**
* If `fontSize` or `fontFamily` exists, `font` will be reset by
* `fontSize`, `fontStyle`, `fontWeight`, `fontFamily`.
* So do not visit it directly in upper application (like echarts),
* but use `contain/text#makeFont` instead.
* @type {string}
*/
font: null,
/**
* The same as font. Use font please.
* @deprecated
* @type {string}
*/
textFont: null,
/**
* It helps merging respectively, rather than parsing an entire font string.
* @type {string}
*/
fontStyle: null,
/**
* It helps merging respectively, rather than parsing an entire font string.
* @type {string}
*/
fontWeight: null,
/**
* It helps merging respectively, rather than parsing an entire font string.
* Should be 12 but not '12px'.
* @type {number}
*/
fontSize: null,
/**
* It helps merging respectively, rather than parsing an entire font string.
* @type {string}
*/
fontFamily: null,
/**
* Reserved for special functinality, like 'hr'.
* @type {string}
*/
textTag: null,
/**
* @type {string}
*/
textFill: '#000',
/**
* @type {string}
*/
textStroke: null,
/**
* @type {number}
*/
textWidth: null,
/**
* Only for textBackground.
* @type {number}
*/
textHeight: null,
/**
* textStroke may be set as some color as a default
* value in upper applicaion, where the default value
* of textStrokeWidth should be 0 to make sure that
* user can choose to do not use text stroke.
* @type {number}
*/
textStrokeWidth: 0,
/**
* @type {number}
*/
textLineHeight: null,
/**
* 'inside', 'left', 'right', 'top', 'bottom'
* [x, y]
* Based on x, y of rect.
* @type {string|Array.<number>}
* @default 'inside'
*/
textPosition: 'inside',
/**
* If not specified, use the boundingRect of a `displayable`.
* @type {Object}
*/
textRect: null,
/**
* [x, y]
* @type {Array.<number>}
*/
textOffset: null,
/**
* @type {string}
*/
textAlign: null,
/**
* @type {string}
*/
textVerticalAlign: null,
/**
* @type {number}
*/
textDistance: 5,
/**
* @type {string}
*/
textShadowColor: 'transparent',
/**
* @type {number}
*/
textShadowBlur: 0,
/**
* @type {number}
*/
textShadowOffsetX: 0,
/**
* @type {number}
*/
textShadowOffsetY: 0,
/**
* @type {string}
*/
textBoxShadowColor: 'transparent',
/**
* @type {number}
*/
textBoxShadowBlur: 0,
/**
* @type {number}
*/
textBoxShadowOffsetX: 0,
/**
* @type {number}
*/
textBoxShadowOffsetY: 0,
/**
* Whether transform text.
* Only useful in Path and Image element
* @type {boolean}
*/
transformText: false,
/**
* Text rotate around position of Path or Image
* Only useful in Path and Image element and transformText is false.
*/
textRotation: 0,
/**
* Text origin of text rotation, like [10, 40].
* Based on x, y of rect.
* Useful in label rotation of circular symbol.
* By default, this origin is textPosition.
* Can be 'center'.
* @type {string|Array.<number>}
*/
textOrigin: null,
/**
* @type {string}
*/
textBackgroundColor: null,
/**
* @type {string}
*/
textBorderColor: null,
/**
* @type {number}
*/
textBorderWidth: 0,
/**
* @type {number}
*/
textBorderRadius: 0,
/**
* Can be `2` or `[2, 4]` or `[2, 3, 4, 5]`
* @type {number|Array.<number>}
*/
textPadding: null,
/**
* Text styles for rich text.
* @type {Object}
*/
rich: null,
/**
* {outerWidth, outerHeight, ellipsis, placeholder}
* @type {Object}
*/
truncate: null,
/**
* https://developer.mozilla.org/en-
US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
* @type {string}
*/
blend: null,
/**
* @param {CanvasRenderingContext2D} ctx
*/
bind: function (ctx, el, prevEl) {
var style = this;
var prevStyle = prevEl && prevEl.style;
var firstDraw = !prevStyle;
hasFill: function () {
var fill = this.fill;
return fill != null && fill !== 'none';
},
hasStroke: function () {
var stroke = this.stroke;
return stroke != null && stroke !== 'none' && this.lineWidth > 0;
},
/**
* Extend from other style
* @param {zrender/graphic/Style} otherStyle
* @param {boolean} overwrite true: overwrirte any way.
* false: overwrite only when !target.hasOwnProperty
* others: overwrite when property is not
null/undefined.
*/
extendFrom: function (otherStyle, overwrite) {
if (otherStyle) {
for (var name in otherStyle) {
if (otherStyle.hasOwnProperty(name)
&& (overwrite === true
|| (
overwrite === false
? !this.hasOwnProperty(name)
: otherStyle[name] != null
)
)
) {
this[name] = otherStyle[name];
}
}
}
},
/**
* Batch setting style with a given object
* @param {Object|string} obj
* @param {*} [obj]
*/
set: function (obj, value) {
if (typeof obj === 'string') {
this[obj] = value;
}
else {
this.extendFrom(obj, true);
}
},
/**
* Clone
* @return {zrender/graphic/Style} [description]
*/
clone: function () {
var newStyle = new this.constructor();
newStyle.extendFrom(this, true);
return newStyle;
},
};
this.image = image;
this.repeat = repeat;
// Can be cloned
this.type = 'pattern';
};
/**
* @module zrender/Layer
* @author pissang(https://www.github.com/pissang)
*/
function returnFalse() {
return false;
}
/**
* 创建 dom
*
* @inner
* @param {string} id dom id 待用
* @param {Painter} painter painter instance
* @param {number} number
*/
function createDom(id, painter, dpr) {
var newDom = createCanvas();
var width = painter.getWidth();
var height = painter.getHeight();
newDom.setAttribute('data-zr-dom-id', id);
}
return newDom;
}
/**
* @alias module:zrender/Layer
* @constructor
* @extends module:zrender/mixin/Transformable
* @param {string} id
* @param {module:zrender/Painter} painter
* @param {number} [dpr]
*/
var Layer = function(id, painter, dpr) {
var dom;
dpr = dpr || devicePixelRatio;
if (typeof id === 'string') {
dom = createDom(id, painter, dpr);
}
// Not using isDom because in node it will return false
else if (isObject$1(id)) {
dom = id;
id = dom.id;
}
this.id = id;
this.dom = dom;
this.domBack = null;
this.ctxBack = null;
this.painter = painter;
this.config = null;
// Configs
/**
* 每次清空画布的颜色
* @type {string}
* @default 0
*/
this.clearColor = 0;
/**
* 是否开启动态模糊
* @type {boolean}
* @default false
*/
this.motionBlur = false;
/**
* 在开启动态模糊的时候使用,与上一帧混合的 alpha 值,值越大尾迹越明显
* @type {number}
* @default 0.7
*/
this.lastFrameAlpha = 0.7;
/**
* Layer dpr
* @type {number}
*/
this.dpr = dpr;
};
Layer.prototype = {
constructor: Layer,
__dirty: true,
__used: false,
__drawIndex: 0,
__startIndex: 0,
__endIndex: 0,
incremental: false,
getElementCount: function () {
return this.__endIndex - this.__startIndex;
},
initContext: function () {
this.ctx = this.dom.getContext('2d');
this.ctx.dpr = this.dpr;
},
createBackBuffer: function () {
var dpr = this.dpr;
if (dpr != 1) {
this.ctxBack.scale(dpr, dpr);
}
},
/**
* @param {number} width
* @param {number} height
*/
resize: function (width, height) {
var dpr = this.dpr;
if (domStyle) {
domStyle.width = width + 'px';
domStyle.height = height + 'px';
}
if (domBack) {
domBack.width = width * dpr;
domBack.height = height * dpr;
if (dpr != 1) {
this.ctxBack.scale(dpr, dpr);
}
}
},
/**
* 清空该层画布
* @param {boolean} [clearAll]=false Clear all with out motion blur
* @param {Color} [clearColor]
*/
clear: function (clearAll, clearColor) {
var dom = this.dom;
var ctx = this.ctx;
var width = dom.width;
var height = dom.height;
if (haveMotionBLur) {
if (!this.domBack) {
this.createBackBuffer();
}
this.ctxBack.globalCompositeOperation = 'copy';
this.ctxBack.drawImage(
dom, 0, 0,
width / dpr,
height / dpr
);
}
clearColor.__canvasGradient = clearColorGradientOrPattern;
}
// Pattern
else if (clearColor.image) {
clearColorGradientOrPattern =
Pattern.prototype.getCanvasPattern.call(clearColor, ctx);
}
ctx.save();
ctx.fillStyle = clearColorGradientOrPattern || clearColor;
ctx.fillRect(0, 0, width, height);
ctx.restore();
}
if (haveMotionBLur) {
var domBack = this.domBack;
ctx.save();
ctx.globalAlpha = lastFrameAlpha;
ctx.drawImage(domBack, 0, 0, width, height);
ctx.restore();
}
}
};
var requestAnimationFrame = (
typeof window !== 'undefined'
&& (
(window.requestAnimationFrame && window.requestAnimationFrame.bind(window))
// https://github.com/ecomfe/zrender/issues/189#issuecomment-224919809
|| (window.msRequestAnimationFrame &&
window.msRequestAnimationFrame.bind(window))
|| window.mozRequestAnimationFrame
|| window.webkitRequestAnimationFrame
)
) || function (func) {
setTimeout(func, 16);
};
/**
* @param {string|HTMLImageElement|HTMLCanvasElement|Canvas} newImageOrSrc
* @return {HTMLImageElement|HTMLCanvasElement|Canvas} image
*/
function findExistImage(newImageOrSrc) {
if (typeof newImageOrSrc === 'string') {
var cachedImgObj = globalImageCache.get(newImageOrSrc);
return cachedImgObj && cachedImgObj.image;
}
else {
return newImageOrSrc;
}
}
/**
* Caution: User should cache loaded images, but not just count on LRU.
* Consider if required images more than LRU size, will dead loop occur?
*
* @param {string|HTMLImageElement|HTMLCanvasElement|Canvas} newImageOrSrc
* @param {HTMLImageElement|HTMLCanvasElement|Canvas} image Existent image.
* @param {module:zrender/Element} [hostEl] For calling `dirty`.
* @param {Function} [cb] params: (image, cbPayload)
* @param {Object} [cbPayload] Payload on cb calling.
* @return {HTMLImageElement|HTMLCanvasElement|Canvas} image
*/
function createOrUpdateImage(newImageOrSrc, image, hostEl, cb, cbPayload) {
if (!newImageOrSrc) {
return image;
}
else if (typeof newImageOrSrc === 'string') {
if (cachedImgObj) {
image = cachedImgObj.image;
!isImageReady(image) && cachedImgObj.pending.push(pendingWrap);
}
else {
!image && (image = new Image());
image.onload = imageOnLoad;
globalImageCache.put(
newImageOrSrc,
image.__cachedImgObj = {
image: image,
pending: [pendingWrap]
}
);
return image;
}
// newImageOrSrc is an HTMLImageElement or HTMLCanvasElement or Canvas
else {
return newImageOrSrc;
}
}
function imageOnLoad() {
var cachedImgObj = this.__cachedImgObj;
this.onload = this.__cachedImgObj = null;
function isImageReady(image) {
return image && image.width && image.height;
}
/**
* @public
* @param {string} text
* @param {string} font
* @return {number} width
*/
function getWidth(text, font) {
font = font || DEFAULT_FONT;
var key = text + ':' + font;
if (textWidthCache[key]) {
return textWidthCache[key];
}
return width;
}
/**
* @public
* @param {string} text
* @param {string} font
* @param {string} [textAlign='left']
* @param {string} [textVerticalAlign='top']
* @param {Array.<number>} [textPadding]
* @param {Object} [rich]
* @param {Object} [truncate]
* @return {Object} {x, y, width, height, lineHeight}
*/
function getBoundingRect(text, font, textAlign, textVerticalAlign, textPadding,
rich, truncate) {
return rich
? getRichTextRect(text, font, textAlign, textVerticalAlign, textPadding,
rich, truncate)
: getPlainTextRect(text, font, textAlign, textVerticalAlign, textPadding,
truncate);
}
return rect;
}
/**
* @public
* @param {number} x
* @param {number} width
* @param {string} [textAlign='left']
* @return {number} Adjusted x.
*/
function adjustTextX(x, width, textAlign) {
// FIXME Right to left language
if (textAlign === 'right') {
x -= width;
}
else if (textAlign === 'center') {
x -= width / 2;
}
return x;
}
/**
* @public
* @param {number} y
* @param {number} height
* @param {string} [textVerticalAlign='top']
* @return {number} Adjusted y.
*/
function adjustTextY(y, height, textVerticalAlign) {
if (textVerticalAlign === 'middle') {
y -= height / 2;
}
else if (textVerticalAlign === 'bottom') {
y -= height;
}
return y;
}
/**
* @public
* @param {stirng} textPosition
* @param {Object} rect {x, y, width, height}
* @param {number} distance
* @return {Object} {x, y, textAlign, textVerticalAlign}
*/
function adjustTextPositionOnRect(textPosition, rect, distance) {
var x = rect.x;
var y = rect.y;
switch (textPosition) {
case 'left':
x -= distance;
y += halfHeight;
textAlign = 'right';
textVerticalAlign = 'middle';
break;
case 'right':
x += distance + width;
y += halfHeight;
textVerticalAlign = 'middle';
break;
case 'top':
x += width / 2;
y -= distance;
textAlign = 'center';
textVerticalAlign = 'bottom';
break;
case 'bottom':
x += width / 2;
y += height + distance;
textAlign = 'center';
break;
case 'inside':
x += width / 2;
y += halfHeight;
textAlign = 'center';
textVerticalAlign = 'middle';
break;
case 'insideLeft':
x += distance;
y += halfHeight;
textVerticalAlign = 'middle';
break;
case 'insideRight':
x += width - distance;
y += halfHeight;
textAlign = 'right';
textVerticalAlign = 'middle';
break;
case 'insideTop':
x += width / 2;
y += distance;
textAlign = 'center';
break;
case 'insideBottom':
x += width / 2;
y += height - distance;
textAlign = 'center';
textVerticalAlign = 'bottom';
break;
case 'insideTopLeft':
x += distance;
y += distance;
break;
case 'insideTopRight':
x += width - distance;
y += distance;
textAlign = 'right';
break;
case 'insideBottomLeft':
x += distance;
y += height - distance;
textVerticalAlign = 'bottom';
break;
case 'insideBottomRight':
x += width - distance;
y += height - distance;
textAlign = 'right';
textVerticalAlign = 'bottom';
break;
}
return {
x: x,
y: y,
textAlign: textAlign,
textVerticalAlign: textVerticalAlign
};
}
/**
* Show ellipsis if overflow.
*
* @public
* @param {string} text
* @param {string} containerWidth
* @param {string} font
* @param {number} [ellipsis='...']
* @param {Object} [options]
* @param {number} [options.maxIterations=3]
* @param {number} [options.minChar=0] If truncate result are less
* then minChar, ellipsis will not show, which is
* better for user hint in some cases.
* @param {number} [options.placeholder=''] When all truncated, use the
placeholder.
* @return {string}
*/
function truncateText(text, containerWidth, font, ellipsis, options) {
if (!containerWidth) {
return '';
}
// FIXME
// It is not appropriate that every line has '...' when truncate multiple
lines.
for (var i = 0, len = textLines.length; i < len; i++) {
textLines[i] = truncateSingleLine(textLines[i], options);
}
return textLines.join('\n');
}
options.font = font;
var ellipsis = retrieve2(ellipsis, '...');
options.maxIterations = retrieve2(options.maxIterations, 2);
var minChar = options.minChar = retrieve2(options.minChar, 0);
// FIXME
// Other languages?
options.cnCharWidth = getWidth('国', font);
// FIXME
// Consider proportional font?
var ascCharWidth = options.ascCharWidth = getWidth('a', font);
options.placeholder = retrieve2(options.placeholder, '');
options.ellipsis = ellipsis;
options.ellipsisWidth = ellipsisWidth;
options.contentWidth = contentWidth;
options.containerWidth = containerWidth;
return options;
}
if (!containerWidth) {
return '';
}
return textLine;
}
/**
* @public
* @param {string} font
* @return {number} line height
*/
function getLineHeight(font) {
// FIXME A rough approach.
return getWidth('国', font);
}
/**
* @public
* @param {string} text
* @param {string} font
* @return {Object} width
*/
function measureText(text, font) {
return methods$1.measureText(text, font);
}
/**
* @public
* @param {string} text
* @param {string} font
* @param {Object} [truncate]
* @return {Object} block: {lineHeight, lines, height, outerHeight}
* Notice: for performance, do not calculate outerWidth util needed.
*/
function parsePlainText(text, font, padding, truncate) {
text != null && (text += '');
if (padding) {
outerHeight += padding[0] + padding[2];
}
// FIXME
// It is not appropriate that every line has '...' when truncate
multiple lines.
for (var i = 0, len = lines.length; i < len; i++) {
lines[i] = truncateSingleLine(lines[i], options);
}
}
}
return {
lines: lines,
height: height,
outerHeight: outerHeight,
lineHeight: lineHeight
};
}
/**
* For example: 'some text {a|some text}other text{b|some text}xxx{c|}xxx'
* Also consider 'bbbb{a|xxx\nzzz}xxxx\naaaa'.
*
* @public
* @param {string} text
* @param {Object} style
* @return {Object} block
* {
* width,
* height,
* lines: [{
* lineHeight,
* width,
* tokens: [[{
* styleName,
* text,
* width, // include textPadding
* height, // include textPadding
* textWidth, // pure text width
* textHeight, // pure text height
* lineHeihgt,
* font,
* textAlign,
* textVerticalAlign
* }], [...], ...]
* }, ...]
* }
* If styleName is undefined, it is plain text.
*/
function parseRichText(text, style) {
var contentBlock = {lines: [], width: 0, height: 0};
text != null && (text += '');
if (!text) {
return contentBlock;
}
// Use cases:
// (1) If image is not loaded, it will be loaded at render
phase and call
// `dirty()` and `textBackgroundColor.image` will be replaced
with the loaded
// image, and then the right size will be calculated here at
the next tick.
// See `graphic/helper/text.js`.
// (2) If image loaded, and `textBackgroundColor.image` is
image src string,
// use `imageHelper.findExistImage` to find cached image.
// `imageHelper.findExistImage` will always be called here
before
// `imageHelper.createOrUpdateImage` in
`graphic/helper/text.js#renderRichText`
// which ensures that image will not be rendered before correct
size calcualted.
if (bgImg) {
bgImg = findExistImage(bgImg);
if (isImageReady(bgImg)) {
tokenWidth = Math.max(tokenWidth, bgImg.width *
tokenHeight / bgImg.height);
}
}
}
line.width = lineWidth;
line.lineHeight = lineHeight;
contentHeight += lineHeight;
contentWidth = Math.max(contentWidth, lineWidth);
}
if (stlPadding) {
contentBlock.outerWidth += stlPadding[1] + stlPadding[3];
contentBlock.outerHeight += stlPadding[0] + stlPadding[2];
}
return contentBlock;
}
// Consider cases:
// (1) ''.split('\n') => ['', '\n', ''], the '' at the first item
// (which is a placeholder) should be replaced by new token.
// (2) A image backage, where token likes {a|}.
// (3) A redundant '' will affect textAlign in line.
// (4) tokens with the same tplName should not be merged, because
// they should be displayed in different box (with border and padding).
var tokensLen = tokens.length;
(tokensLen === 1 && tokens[0].isLineHolder)
? (tokens[0] = token)
// Consider text is '', only insert when it is the "lineHolder" or
// "emptyStr". Otherwise a redundant '' will affect textAlign in
line.
: ((text || !tokensLen || isEmptyStr) && tokens.push(token));
}
// Other tokens always start a new line.
else {
// If there is '', insert it as a placeholder.
lines.push({tokens: [token]});
}
}
}
function makeFont(style) {
// FIXME in node-canvas fontWeight is before fontStyle
// Use `fontSize` `fontFamily` to check whether font properties are defined.
var font = (style.fontSize || style.fontFamily) && [
style.fontStyle,
style.fontWeight,
(style.fontSize || 12) + 'px',
// If font properties are defined, `fontFamily` should not be ignored.
style.fontFamily || 'sans-serif'
].join(' ');
return font && trim(font) || style.textFont || style.font;
}
var total;
if (r1 + r2 > width) {
total = r1 + r2;
r1 *= width / total;
r2 *= width / total;
}
if (r3 + r4 > width) {
total = r3 + r4;
r3 *= width / total;
r4 *= width / total;
}
if (r2 + r3 > height) {
total = r2 + r3;
r2 *= height / total;
r3 *= height / total;
}
if (r1 + r4 > height) {
total = r1 + r4;
r1 *= height / total;
r4 *= height / total;
}
ctx.moveTo(x + r1, y);
ctx.lineTo(x + width - r2, y);
r2 !== 0 && ctx.arc(x + width - r2, y + r2, r2, -Math.PI / 2, 0);
ctx.lineTo(x + width, y + height - r3);
r3 !== 0 && ctx.arc(x + width - r3, y + height - r3, r3, 0, Math.PI / 2);
ctx.lineTo(x + r4, y + height);
r4 !== 0 && ctx.arc(x + r4, y + height - r4, r4, Math.PI / 2, Math.PI);
ctx.lineTo(x, y + r1);
r1 !== 0 && ctx.arc(x + r1, y + r1, r1, Math.PI, Math.PI * 1.5);
}
/**
* @param {module:zrender/graphic/Style} style
* @return {module:zrender/graphic/Style} The input style.
*/
function normalizeTextStyle(style) {
normalizeStyle(style);
each$1(style.rich, normalizeStyle);
return style;
}
function normalizeStyle(style) {
if (style) {
style.font = makeFont(style);
/**
* @param {CanvasRenderingContext2D} ctx
* @param {string} text
* @param {module:zrender/graphic/Style} style
* @param {Object|boolean} [rect] {x, y, width, height}
* If set false, rect text is not used.
*/
function renderText(hostEl, ctx, text, style, rect) {
style.rich
? renderRichText(hostEl, ctx, text, style, rect)
: renderPlainText(hostEl, ctx, text, style, rect);
}
if (textPadding) {
textX = getTextXForPadding(baseX, textAlign, textPadding);
textY += textPadding[0];
}
}
setCtx(ctx, 'textAlign', textAlign || 'left');
// Force baseline to be "middle". Otherwise, if using "top", the
// text will offset downward a little bit in font "Microsoft YaHei".
setCtx(ctx, 'textBaseline', 'middle');
if (textStroke) {
setCtx(ctx, 'lineWidth', textStrokeWidth);
setCtx(ctx, 'strokeStyle', textStroke);
}
if (textFill) {
setCtx(ctx, 'fillStyle', textFill);
}
if (!contentBlock || hostEl.__dirty) {
contentBlock = hostEl.__textCotentBlock = parseRichText(text, style);
}
var leftIndex = 0;
var lineXLeft = xLeft;
var lineXRight = xRight;
var rightIndex = tokenCount - 1;
var token;
while (
leftIndex < tokenCount
&& (token = tokens[leftIndex], !token.textAlign || token.textAlign ===
'left')
) {
placeToken(hostEl, ctx, token, style, lineHeight, lineTop, lineXLeft,
'left');
usedWidth -= token.width;
lineXLeft += token.width;
leftIndex++;
}
while (
rightIndex >= 0
&& (token = tokens[rightIndex], token.textAlign === 'right')
) {
placeToken(hostEl, ctx, token, style, lineHeight, lineTop, lineXRight,
'right');
usedWidth -= token.width;
lineXRight -= token.width;
rightIndex--;
}
lineTop += lineHeight;
}
}
ctx.translate(x, y);
// Positive: anticlockwise
ctx.rotate(-style.textRotation);
ctx.translate(-x, -y);
}
}
// Fill after stroke so the outline will not cover the main part.
if (textStroke) {
setCtx(ctx, 'lineWidth', textStrokeWidth);
setCtx(ctx, 'strokeStyle', textStroke);
ctx.strokeText(token.text, x, y);
}
if (textFill) {
setCtx(ctx, 'fillStyle', textFill);
ctx.fillText(token.text, x, y);
}
}
function needDrawBackground(style) {
return style.textBackgroundColor
|| (style.textBorderWidth && style.textBorderColor);
}
if (isPlainBg) {
setCtx(ctx, 'fillStyle', textBackgroundColor);
ctx.fill();
}
else if (isObject$1(textBackgroundColor)) {
var image = textBackgroundColor.image;
image = createOrUpdateImage(
image, null, hostEl, onBgImageLoaded, textBackgroundColor
);
if (image && isImageReady(image)) {
ctx.drawImage(image, x, y, width, height);
}
}
return {
baseX: baseX,
baseY: baseY,
textAlign: textAlign,
textVerticalAlign: textVerticalAlign
};
}
/**
* @param {string} [stroke] If specified, do not check style.textStroke.
* @param {string} [lineWidth] If specified, do not check style.textStroke.
* @param {number} style
*/
function getStroke(stroke, lineWidth) {
return (stroke == null || lineWidth <= 0 || stroke === 'transparent' || stroke
=== 'none')
? null
// TODO pattern and gradient?
: (stroke.image || stroke.colorStops)
? '#000'
: stroke;
}
function getFill(fill) {
return (fill == null || fill === 'none')
? null
// TODO pattern and gradient?
: (fill.image || fill.colorStops)
? '#000'
: fill;
}
/**
* @param {string} text
* @param {module:zrender/Style} style
* @return {boolean}
*/
function needDrawText(text, style) {
return text != null
&& (text
|| style.textBackgroundColor
|| (style.textBorderWidth && style.textBorderColor)
|| style.textPadding
);
}
/**
* Mixin for drawing text in a element bounding rect
* @module zrender/mixin/RectText
*/
RectText.prototype = {
constructor: RectText,
/**
* Draw text in a rect with specified position.
* @param {CanvasRenderingContext2D} ctx
* @param {Object} rect Displayable rect
*/
drawRectText: function (ctx, rect) {
var style = this.style;
// Convert to string
text != null && (text += '');
if (!needDrawText(text, style)) {
return;
}
// FIXME
ctx.save();
ctx.restore();
}
};
/**
* 可绘制的图形基类
* Base class of all displayable graphic objects
* @module zrender/graphic/Displayable
*/
/**
* @alias module:zrender/graphic/Displayable
* @extends module:zrender/Element
* @extends module:zrender/graphic/mixin/RectText
*/
function Displayable(opts) {
Element.call(this, opts);
// Extend properties
for (var name in opts) {
if (
opts.hasOwnProperty(name) &&
name !== 'style'
) {
this[name] = opts[name];
}
}
/**
* @type {module:zrender/graphic/Style}
*/
this.style = new Style(opts.style, this);
this._rect = null;
// Shapes for cascade clipping.
this.__clipPaths = [];
// FIXME Stateful must be mixined after style is setted
// Stateful.call(this, opts);
}
Displayable.prototype = {
constructor: Displayable,
type: 'displayable',
/**
* Displayable 是否为脏,Painter 中会根据该标记判断是否需要是否需要重新绘制
* Dirty flag. From which painter will determine if this displayable object
needs brush
* @name module:zrender/graphic/Displayable#__dirty
* @type {boolean}
*/
__dirty: true,
/**
* 图形是否可见,为 true 时不绘制图形,但是仍能触发鼠标事件
* If ignore drawing of the displayable object. Mouse event will still be
triggered
* @name module:/zrender/graphic/Displayable#invisible
* @type {boolean}
* @default false
*/
invisible: false,
/**
* @name module:/zrender/graphic/Displayable#z
* @type {number}
* @default 0
*/
z: 0,
/**
* @name module:/zrender/graphic/Displayable#z
* @type {number}
* @default 0
*/
z2: 0,
/**
* z 层 level,决定绘画在哪层 canvas 中
* @name module:/zrender/graphic/Displayable#zlevel
* @type {number}
* @default 0
*/
zlevel: 0,
/**
* 是否可拖拽
* @name module:/zrender/graphic/Displayable#draggable
* @type {boolean}
* @default false
*/
draggable: false,
/**
* 是否正在拖拽
* @name module:/zrender/graphic/Displayable#draggable
* @type {boolean}
* @default false
*/
dragging: false,
/**
* 是否相应鼠标事件
* @name module:/zrender/graphic/Displayable#silent
* @type {boolean}
* @default false
*/
silent: false,
/**
* If enable culling
* @type {boolean}
* @default false
*/
culling: false,
/**
* Mouse cursor when hovered
* @name module:/zrender/graphic/Displayable#cursor
* @type {string}
*/
cursor: 'pointer',
/**
* If hover area is bounding rect
* @name module:/zrender/graphic/Displayable#rectHover
* @type {string}
*/
rectHover: false,
/**
* Render the element progressively when the value >= 0,
* usefull for large data.
* @type {boolean}
*/
progressive: false,
/**
* @type {boolean}
*/
incremental: false,
// inplace is used with incremental
inplace: false,
/**
* 图形绘制方法
* @param {CanvasRenderingContext2D} ctx
*/
// Interface
brush: function (ctx, prevEl) {},
/**
* 获取最小包围盒
* @return {module:zrender/core/BoundingRect}
*/
// Interface
getBoundingRect: function () {},
/**
* 判断坐标 x, y 是否在图形上
* If displayable element contain coord x, y
* @param {number} x
* @param {number} y
* @return {boolean}
*/
contain: function (x, y) {
return this.rectContain(x, y);
},
/**
* @param {Function} cb
* @param {} context
*/
traverse: function (cb, context) {
cb.call(context, this);
},
/**
* 判断坐标 x, y 是否在图形的包围盒上
* If bounding rect of element contain coord x, y
* @param {number} x
* @param {number} y
* @return {boolean}
*/
rectContain: function (x, y) {
var coord = this.transformCoordToLocal(x, y);
var rect = this.getBoundingRect();
return rect.contain(coord[0], coord[1]);
},
/**
* 标记图形元素为脏,并且在下一帧重绘
* Mark displayable element dirty and refresh next frame
*/
dirty: function () {
this.__dirty = true;
this._rect = null;
/**
* 图形是否会触发事件
* If displayable object binded any event
* @return {boolean}
*/
// TODO, 通过 bind 绑定的事件
// isSilent: function () {
// return !(
// this.hoverable || this.draggable
// || this.onmousemove || this.onmouseover || this.onmouseout
// || this.onmousedown || this.onmouseup || this.onclick
// || this.ondragenter || this.ondragover || this.ondragleave
// || this.ondrop
// );
// },
/**
* Alias for animate('style')
* @param {boolean} loop
*/
animateStyle: function (loop) {
return this.animate('style', loop);
},
/**
* @param {Object|string} key
* @param {*} value
*/
setStyle: function (key, value) {
this.style.set(key, value);
this.dirty(false);
return this;
},
/**
* Use given style object
* @param {Object} obj
*/
useStyle: function (obj) {
this.style = new Style(obj, this);
this.dirty(false);
return this;
}
};
inherits(Displayable, Element);
mixin(Displayable, RectText);
/**
* @alias zrender/graphic/Image
* @extends module:zrender/graphic/Displayable
* @constructor
* @param {Object} opts
*/
function ZImage(opts) {
Displayable.call(this, opts);
}
ZImage.prototype = {
constructor: ZImage,
type: 'image',
if (!image || !isImageReady(image)) {
return;
}
// 图片已经加载完成
// if (image.nodeName.toUpperCase() == 'IMG') {
// if (!image.complete) {
// return;
// }
// }
// Else is canvas
var x = style.x || 0;
var y = style.y || 0;
var width = style.width;
var height = style.height;
var aspect = image.width / image.height;
if (width == null && height != null) {
// Keep image/height ratio
width = height * aspect;
}
else if (height == null && width != null) {
height = width / aspect;
}
else if (width == null && height == null) {
width = image.width;
height = image.height;
}
// 设置 transform
this.setTransform(ctx);
getBoundingRect: function () {
var style = this.style;
if (! this._rect) {
this._rect = new BoundingRect(
style.x || 0, style.y || 0, style.width || 0, style.height || 0
);
}
return this._rect;
}
};
inherits(ZImage, Displayable);
function parseInt10(val) {
return parseInt(val, 10);
}
function isLayerValid(layer) {
if (!layer) {
return false;
}
if (layer.__builtin__) {
return true;
}
if (typeof(layer.resize) !== 'function'
|| typeof(layer.refresh) !== 'function'
) {
return false;
}
return true;
}
clipPath.setTransform(ctx);
ctx.beginPath();
clipPath.buildPath(ctx, clipPath.shape);
ctx.clip();
// Transform back
clipPath.restoreTransform(ctx);
}
}
return domRoot;
}
/**
* @alias module:zrender/Painter
* @constructor
* @param {HTMLElement} root 绘图容器
* @param {module:zrender/Storage} storage
* @param {Object} opts
*/
var Painter = function (root, storage, opts) {
this.type = 'canvas';
/**
* @type {number}
*/
this.dpr = opts.devicePixelRatio || devicePixelRatio;
/**
* @type {boolean}
* @private
*/
this._singleCanvas = singleCanvas;
/**
* 绘图容器
* @type {HTMLElement}
*/
this.root = root;
if (rootStyle) {
rootStyle['-webkit-tap-highlight-color'] = 'transparent';
rootStyle['-webkit-user-select'] =
rootStyle['user-select'] =
rootStyle['-webkit-touch-callout'] = 'none';
root.innerHTML = '';
}
/**
* @type {module:zrender/Storage}
*/
this.storage = storage;
/**
* @type {Array.<number>}
* @private
*/
var zlevelList = this._zlevelList = [];
/**
* @type {Object.<string, module:zrender/Layer>}
* @private
*/
var layers = this._layers = {};
/**
* @type {Object.<string, Object>}
* @private
*/
this._layerConfig = {};
/**
* zrender will do compositing when root is a canvas and have multiple zlevels.
*/
this._needsManuallyCompositing = false;
if (!singleCanvas) {
this._width = this._getSize(0);
this._height = this._getSize(1);
if (opts.width != null) {
width = opts.width;
}
if (opts.height != null) {
height = opts.height;
}
this.dpr = opts.devicePixelRatio || 1;
this._width = width;
this._height = height;
this._domRoot = root;
}
/**
* @type {module:zrender/Layer}
* @private
*/
this._hoverlayer = null;
this._hoverElements = [];
};
Painter.prototype = {
constructor: Painter,
getType: function () {
return 'canvas';
},
/**
* If painter use a single canvas
* @return {boolean}
*/
isSingleCanvas: function () {
return this._singleCanvas;
},
/**
* @return {HTMLDivElement}
*/
getViewportRoot: function () {
return this._domRoot;
},
getViewportRootOffset: function () {
var viewportRoot = this.getViewportRoot();
if (viewportRoot) {
return {
offsetLeft: viewportRoot.offsetLeft || 0,
offsetTop: viewportRoot.offsetTop || 0
};
}
},
/**
* 刷新
* @param {boolean} [paintAll=false] 强制绘制所有 displayable
*/
refresh: function (paintAll) {
this._redrawId = Math.random();
this.refreshHover();
return this;
},
refreshHover: function () {
var hoverElements = this._hoverElements;
var len = hoverElements.length;
var hoverLayer = this._hoverlayer;
hoverLayer && hoverLayer.clear();
if (!len) {
return;
}
sort(hoverElements, this.storage.displayableSortFunc);
// Use transform
// FIXME style and shape ?
if (!originalEl.invisible) {
el.transform = originalEl.transform;
el.invTransform = originalEl.invTransform;
el.__clipPaths = originalEl.__clipPaths;
// el.
this._doPaintEl(el, hoverLayer, true, scope);
}
}
hoverLayer.ctx.restore();
},
getHoverLayer: function () {
return this.getLayer(HOVER_LAYER_ZLEVEL);
},
this._updateLayerStatus(list);
if (this._needsManuallyCompositing) {
this._compositeManually();
}
if (!finished) {
var self = this;
requestAnimationFrame(function () {
self._paintList(list, paintAll, redrawId);
});
}
},
_compositeManually: function () {
var ctx = this.getLayer(CANVAS_ZLEVEL).ctx;
var width = this._domRoot.width;
var height = this._domRoot.height;
ctx.clearRect(0, 0, width, height);
// PENDING, If only builtin layer?
this.eachBuiltinLayer(function (layer) {
if (layer.virtual) {
ctx.drawImage(layer.dom, 0, 0, width, height);
}
});
},
if (useTimer) {
// Date.now can be executed in 13,025,305 ops/second.
var dTime = Date.now() - startTime;
// Give 15 millisecond to draw.
// The rest elements will be drawn in the next frame.
if (dTime > 15) {
break;
}
}
}
layer.__drawIndex = i;
if (scope.prevElClipPaths) {
// Needs restore the state. If last drawn element is in the
clipping area.
ctx.restore();
}
ctx.restore();
}
if (env$1.wxa) {
// Flush for weixin application
each$1(this._layers, function (layer) {
if (layer && layer.ctx && layer.ctx.draw) {
layer.ctx.draw();
}
});
}
return finished;
},
/**
* 获取 zlevel 所在层,如果不存在则会创建一个新的层
* @param {number} zlevel
* @param {boolean} virtual Virtual layer will not be inserted into dom.
* @return {module:zrender/Layer}
*/
getLayer: function (zlevel, virtual) {
if (this._singleCanvas && !this._needsManuallyCompositing) {
zlevel = CANVAS_ZLEVEL;
}
var layer = this._layers[zlevel];
if (!layer) {
// Create a new layer
layer = new Layer('zr_' + zlevel, this, this.dpr);
layer.zlevel = zlevel;
layer.__builtin__ = true;
if (this._layerConfig[zlevel]) {
merge(layer, this._layerConfig[zlevel], true);
}
if (virtual) {
layer.virtual = virtual;
}
this.insertLayer(zlevel, layer);
// Context is created after dom inserted to document
// Or excanvas will get 0px clientWidth and clientHeight
layer.initContext();
}
return layer;
},
if (layersMap[zlevel]) {
zrLog('ZLevel ' + zlevel + ' has been used already');
return;
}
// Check if is a valid layer
if (!isLayerValid(layer)) {
zrLog('Layer of zlevel ' + zlevel + ' is not valid');
return;
}
layersMap[zlevel] = layer;
/**
* 获取所有已创建的层
* @param {Array.<module:zrender/Layer>} [prevLayer]
*/
getLayers: function () {
return this._layers;
},
_updateLayerStatus: function (list) {
this.eachBuiltinLayer(function (layer, z) {
layer.__dirty = layer.__used = false;
});
function updatePrevLayer(idx) {
if (prevLayer) {
if (prevLayer.__endIndex !== idx) {
prevLayer.__dirty = true;
}
prevLayer.__endIndex = idx;
}
}
if (this._singleCanvas) {
for (var i = 1; i < list.length; i++) {
var el = list[i];
if (el.zlevel !== list[i - 1].zlevel || el.incremental) {
this._needsManuallyCompositing = true;
break;
}
}
}
if (!layer.__builtin__) {
zrLog('ZLevel ' + zlevel + ' has been used by unkown layer ' +
layer.id);
}
updatePrevLayer(i);
this.eachBuiltinLayer(function (layer, z) {
// Used in last frame but not in this frame. Needs clear
if (!layer.__used && layer.getElementCount() > 0) {
layer.__dirty = true;
layer.__startIndex = layer.__endIndex = layer.__drawIndex = 0;
}
// For incremental layer. In case start index changed and no elements
are dirty.
if (layer.__dirty && layer.__drawIndex < 0) {
layer.__drawIndex = layer.__startIndex;
}
});
},
/**
* 清除 hover 层外所有内容
*/
clear: function () {
this.eachBuiltinLayer(this._clearLayer);
return this;
},
/**
* 修改指定 zlevel 的绘制参数
*
* @param {string} zlevel
* @param {Object} config 配置对象
* @param {string} [config.clearColor=0] 每次清空画布的颜色
* @param {string} [config.motionBlur=false] 是否开启动态模糊
* @param {number} [config.lastFrameAlpha=0.7]
* 在开启动态模糊的时候使用,与上一帧混合的 alpha 值,值越大尾迹越明显
*/
configLayer: function (zlevel, config) {
if (config) {
var layerConfig = this._layerConfig;
if (!layerConfig[zlevel]) {
layerConfig[zlevel] = config;
}
else {
merge(layerConfig[zlevel], config, true);
}
/**
* 删除指定层
* @param {number} zlevel 层所在的 zlevel
*/
delLayer: function (zlevel) {
var layers = this._layers;
var zlevelList = this._zlevelList;
var layer = layers[zlevel];
if (!layer) {
return;
}
layer.dom.parentNode.removeChild(layer.dom);
delete layers[zlevel];
/**
* 区域大小变化后重绘
*/
resize: function (width, height) {
if (!this._domRoot.style) { // Maybe in node or worker
if (width == null || height == null) {
return;
}
this._width = width;
this._height = height;
this.getLayer(CANVAS_ZLEVEL).resize(width, height);
}
else {
var domRoot = this._domRoot;
// FIXME Why ?
domRoot.style.display = 'none';
domRoot.style.display = '';
// 优化没有实际改变的 resize
if (this._width != width || height != this._height) {
domRoot.style.width = width + 'px';
domRoot.style.height = height + 'px';
this.refresh(true);
}
this._width = width;
this._height = height;
}
return this;
},
/**
* 清除单独的一个层
* @param {number} zlevel
*/
clearLayer: function (zlevel) {
var layer = this._layers[zlevel];
if (layer) {
layer.clear();
}
},
/**
* 释放
*/
dispose: function () {
this.root.innerHTML = '';
this.root =
this.storage =
this._domRoot =
this._layers = null;
},
/**
* Get canvas which has all thing rendered
* @param {Object} opts
* @param {string} [opts.backgroundColor]
* @param {number} [opts.pixelRatio]
*/
getRenderedCanvas: function (opts) {
opts = opts || {};
if (this._singleCanvas && !this._compositeManually) {
return this._layers[CANVAS_ZLEVEL].dom;
}
return imageLayer.dom;
},
/**
* 获取绘图区域宽度
*/
getWidth: function () {
return this._width;
},
/**
* 获取绘图区域高度
*/
getHeight: function () {
return this._height;
},
return (
(root[cwh] || parseInt10(stl[wh]) || parseInt10(root.style[wh]))
- (parseInt10(stl[plt]) || 0)
- (parseInt10(stl[prb]) || 0)
) | 0;
},
ctx.scale(dpr, dpr);
ctx.clearRect(0, 0, width, height);
ctx.dpr = dpr;
var pathTransform = {
position: path.position,
rotation: path.rotation,
scale: path.scale
};
path.position = [leftMargin - rect.x, topMargin - rect.y];
path.rotation = 0;
path.scale = [1, 1];
path.updateTransform();
if (path) {
path.brush(ctx);
}
if (pathTransform.position != null) {
imgShape.position = path.position = pathTransform.position;
}
if (pathTransform.rotation != null) {
imgShape.rotation = path.rotation = pathTransform.rotation;
}
if (pathTransform.scale != null) {
imgShape.scale = path.scale = pathTransform.scale;
}
return imgShape;
}
};
/**
* 事件辅助类
* @module zrender/core/event
* @author Kener (@Kener-林峰, [email protected])
*/
function getBoundingClientRect(el) {
// BlackBerry 5, iOS 3 (original iPhone) don't have getBoundingRect
return el.getBoundingClientRect ? el.getBoundingClientRect() : {left: 0, top:
0};
}
// According to the W3C Working Draft, offsetX and offsetY should be relative
// to the padding edge of the target element. The only browser using this
convention
// is IE. Webkit uses the border edge, Opera uses the content edge, and FireFox
does
// not support the properties.
// (see http://www.jacklmoore.com/notes/mouse-position/)
// In zr painter.dom, padding edge equals to border edge.
// FIXME
// When mousemove event triggered on ec tooltip, target is not zr painter.dom,
and
// offsetX/Y is relative to e.target, where the calculation of zrX/Y via
offsetX/Y
// is too complex. So css-transfrom dont support in this case temporarily.
if (calculate || !env$1.canvasSupported) {
defaultGetZrXY(el, e, out);
}
// Caution: In FireFox, layerX/layerY Mouse position relative to the closest
positioned
// ancestor element, so we should make sure el is positioned (e.g., not
position:static).
// BTW1, Webkit don't return the same results as FF in non-simple cases (like
add
// zoom-factor, overflow / opacity layers, transforms ...)
// BTW2, (ev.offsetY || ev.pageY - $(ev.target).offset().top) is not correct in
preserve-3d.
// <https://bugs.jquery.com/ticket/8523#comment:14>
// BTW3, In ff, offsetX/offsetY is always 0.
else if (env$1.browser.firefox && e.layerX != null && e.layerX !== e.offsetX) {
out.zrX = e.layerX;
out.zrY = e.layerY;
}
// For IE6+, chrome, safari, opera. (When will ff support offsetX?)
else if (e.offsetX != null) {
out.zrX = e.offsetX;
out.zrY = e.offsetY;
}
// For some other device, e.g., IOS safari.
else {
defaultGetZrXY(el, e, out);
}
return out;
}
/**
* 如果存在第三方嵌入的一些 dom 触发的事件,或 touch 事件,需要转换一下事件坐标.
* `calculate` is optional, default false.
*/
function normalizeEvent(el, e, calculate) {
e = e || window.event;
if (e.zrX != null) {
return e;
}
if (!isTouch) {
clientToLocal(el, e, e, calculate);
e.zrDelta = (e.wheelDelta) ? e.wheelDelta / 120 : -(e.detail || 0) / 3;
}
else {
var touch = eventType != 'touchend'
? e.targetTouches[0]
: e.changedTouches[0];
touch && clientToLocal(el, touch, e, calculate);
}
// Add which for click: 1 === left; 2 === middle; 3 === right; otherwise: 0;
// See jQuery: https://github.com/jquery/jquery/blob/master/src/event.js
// If e.which has been defined, if may be readonly,
// see: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which
var button = e.button;
if (e.which == null && button !== undefined && MOUSE_EVENT_REG.test(e.type)) {
e.which = (button & 1 ? 1 : (button & 2 ? 3 : (button & 4 ? 2 : 0)));
}
return e;
}
/**
* @param {HTMLElement} el
* @param {string} name
* @param {Function} handler
*/
function addEventListener(el, name, handler) {
if (isDomLevel2) {
// Reproduct the console warning:
// [Violation] Added non-passive event listener to a scroll-blocking <some>
event.
// Consider marking event handler as 'passive' to make the page more
responsive.
// Just set console log level: verbose in chrome dev tool.
// then the warning log will be printed when addEventListener called.
// See https://github.com/WICG/EventListenerOptions/blob/gh-
pages/explainer.md
// We have not yet found a neat way to using passive. Because in zrender
the dom event
// listener delegate all of the upper events of element. Some of those
events need
// to prevent default. For example, the feature `preventDefaultMouseMove`
of echarts.
// Before passive can be adopted, these issues should be considered:
// (1) Whether and how a zrender user specifies an event listener passive.
And by default,
// passive or not.
// (2) How to tread that some zrender event listener is passive, and some
is not. If
// we use other way but not preventDefault of mousewheel and touchmove,
browser
// compatibility should be handled.
/**
* preventDefault and stopPropagation.
* Notice: do not do that in zrender. Upper application
* do that if necessary.
*
* @memberOf module:zrender/core/event
* @method
* @param {Event} e : event 对象
*/
var stop = isDomLevel2
? function (e) {
e.preventDefault();
e.stopPropagation();
e.cancelBubble = true;
}
: function (e) {
e.returnValue = false;
e.cancelBubble = true;
};
function notLeftMouse(e) {
// If e.which is undefined, considered as left mouse event.
return e.which > 1;
}
/**
* 动画主类, 调度和管理所有动画控制器
*
* @module zrender/animation/Animation
* @author pissang(https://github.com/pissang)
*/
// TODO Additive animation
// http://iosoteric.com/additive-animations-animatewithduration-in-ios-8/
// https://developer.apple.com/videos/wwdc2014/#236
/**
* @typedef {Object} IZRenderStage
* @property {Function} update
*/
/**
* @alias module:zrender/animation/Animation
* @constructor
* @param {Object} [options]
* @param {Function} [options.onframe]
* @param {IZRenderStage} [options.stage]
* @example
* var animation = new Animation();
* var obj = {
* x: 100,
* y: 100
* };
* animation.animate(node.position)
* .when(1000, {
* x: 500,
* y: 500
* })
* .when(2000, {
* x: 100,
* y: 100
* })
* .start('spline');
*/
var Animation = function (options) {
// private properties
this._clips = [];
this._running = false;
this._time;
this._pausedTime;
this._pauseStart;
this._paused = false;
Eventful.call(this);
};
Animation.prototype = {
constructor: Animation,
/**
* 添加 clip
* @param {module:zrender/animation/Clip} clip
*/
addClip: function (clip) {
this._clips.push(clip);
},
/**
* 添加 animator
* @param {module:zrender/animation/Animator} animator
*/
addAnimator: function (animator) {
animator.animation = this;
var clips = animator.getClips();
for (var i = 0; i < clips.length; i++) {
this.addClip(clips[i]);
}
},
/**
* 删除动画片段
* @param {module:zrender/animation/Clip} clip
*/
removeClip: function(clip) {
var idx = indexOf(this._clips, clip);
if (idx >= 0) {
this._clips.splice(idx, 1);
}
},
/**
* 删除动画片段
* @param {module:zrender/animation/Animator} animator
*/
removeAnimator: function (animator) {
var clips = animator.getClips();
for (var i = 0; i < clips.length; i++) {
this.removeClip(clips[i]);
}
animator.animation = null;
},
_update: function() {
var time = new Date().getTime() - this._pausedTime;
var delta = time - this._time;
var clips = this._clips;
var len = clips.length;
len = deferredEvents.length;
for (var i = 0; i < len; i++) {
deferredClips[i].fire(deferredEvents[i]);
}
this._time = time;
this.onframe(delta);
if (this.stage.update) {
this.stage.update();
}
},
_startLoop: function () {
var self = this;
this._running = true;
function step() {
if (self._running) {
requestAnimationFrame(step);
requestAnimationFrame(step);
},
/**
* Start animation.
*/
start: function () {
this._startLoop();
},
/**
* Stop animation.
*/
stop: function () {
this._running = false;
},
/**
* Pause animation.
*/
pause: function () {
if (!this._paused) {
this._pauseStart = new Date().getTime();
this._paused = true;
}
},
/**
* Resume animation.
*/
resume: function () {
if (this._paused) {
this._pausedTime += (new Date().getTime()) - this._pauseStart;
this._paused = false;
}
},
/**
* Clear animation.
*/
clear: function () {
this._clips = [];
},
/**
* Whether animation finished.
*/
isFinished: function () {
return !this._clips.length;
},
/**
* Creat animator for a target, whose props can be animated.
*
* @param {Object} target
* @param {Object} options
* @param {boolean} [options.loop=false] Whether loop animation.
* @param {Function} [options.getter=null] Get value from target.
* @param {Function} [options.setter=null] Set value to target.
* @return {module:zrender/animation/Animation~Animator}
*/
// TODO Gap
animate: function (target, options) {
options = options || {};
this.addAnimator(animator);
return animator;
}
};
mixin(Animation, Eventful);
/**
* Only implements needed gestures for mobile.
*/
GestureMgr.prototype = {
constructor: GestureMgr,
clear: function () {
this._track.length = 0;
return this;
},
if (!touches) {
return;
}
var trackItem = {
points: [],
touches: [],
target: target,
event: event
};
this._track.push(trackItem);
},
function dist$1(pointPair) {
var dx = pointPair[1][0] - pointPair[0][0];
var dy = pointPair[1][1] - pointPair[0][1];
function center(pointPair) {
return [
(pointPair[0][0] + pointPair[1][0]) / 2,
(pointPair[0][1] + pointPair[1][1]) / 2
];
}
var recognizers = {
if (!trackLen) {
return;
}
if (pinchPre
&& pinchPre.length > 1
&& pinchEnd
&& pinchEnd.length > 1
) {
var pinchScale = dist$1(pinchEnd) / dist$1(pinchPre);
!isFinite(pinchScale) && (pinchScale = 1);
event.pinchScale = pinchScale;
return {
type: 'pinch',
target: track[0].target,
event: event
};
}
}
var mouseHandlerNames = [
'click', 'dblclick', 'mousewheel', 'mouseout',
'mouseup', 'mousedown', 'mousemove', 'contextmenu'
];
var touchHandlerNames = [
'touchstart', 'touchend', 'touchmove'
];
var pointerEventNames = {
pointerdown: 1, pointerup: 1, pointermove: 1, pointerout: 1
};
function eventNameFix(name) {
return (name === 'mousewheel' && env$1.browser.firefox) ? 'DOMMouseScroll' :
name;
}
/**
* Prevent mouse event from being dispatched after Touch Events action
* @see <https://github.com/deltakosh/handjs/blob/master/src/hand.base.js>
* 1. Mobile browsers dispatch mouse events 300ms after touchend.
* 2. Chrome for Android dispatch mousedown for long-touch about 650ms
* Result: Blocking Mouse Events for 700ms.
*/
function setTouchTimer(instance) {
instance._touching = true;
clearTimeout(instance._touchTimer);
instance._touchTimer = setTimeout(function () {
instance._touching = false;
}, 700);
}
var domHandlers = {
/**
* Mouse move handler
* @inner
* @param {Event} event
*/
mousemove: function (event) {
event = normalizeEvent(this.dom, event);
this.trigger('mousemove', event);
},
/**
* Mouse out handler
* @inner
* @param {Event} event
*/
mouseout: function (event) {
event = normalizeEvent(this.dom, event);
element = element.parentNode;
}
}
this.trigger('mouseout', event);
},
/**
* Touch 开始响应函数
* @inner
* @param {Event} event
*/
touchstart: function (event) {
// Default mouse behaviour should not be disabled here.
// For example, page may needs to be slided.
event = normalizeEvent(this.dom, event);
domHandlers.mousedown.call(this, event);
setTouchTimer(this);
},
/**
* Touch 移动响应函数
* @inner
* @param {Event} event
*/
touchmove: function (event) {
setTouchTimer(this);
},
/**
* Touch 结束响应函数
* @inner
* @param {Event} event
*/
touchend: function (event) {
domHandlers.mouseup.call(this, event);
setTouchTimer(this);
},
// if (useMSGuesture(this, event)) {
// this._msGesture.addPointer(event.pointerId);
// }
},
function isPointerFromTouch(event) {
var pointerType = event.pointerType;
return pointerType === 'pen' || pointerType === 'touch';
}
// Common handlers
each$1(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'],
function (name) {
domHandlers[name] = function (event) {
event = normalizeEvent(this.dom, event);
this.trigger(name, event);
};
});
/**
* 为控制类实例初始化 dom 事件处理函数
*
* @inner
* @param {module:zrender/Handler} instance 控制类实例
*/
function initDomHandler(instance) {
each$1(touchHandlerNames, function (name) {
instance._handlers[name] = bind(domHandlers[name], instance);
});
function HandlerDomProxy(dom) {
Eventful.call(this);
this.dom = dom;
/**
* @private
* @type {boolean}
*/
this._touching = false;
/**
* @private
* @type {number}
*/
this._touchTimer;
/**
* @private
* @type {module:zrender/core/GestureMgr}
*/
this._gestureMgr = new GestureMgr();
this._handlers = {};
initDomHandler(this);
// FIXME
// Note: MS Gesture require CSS touch-action set. But touch-action is not
reliable,
// which does not prevent defuault behavior occasionally (which may cause
view port
// zoomed in but use can not zoom it back). And event.preventDefault() does
not work.
// So we have to not to use MSGesture and not to support touchmove and
pinch on MS
// touch screen. And we only support click behavior on MS touch screen now.
// 1. Considering some devices that both enable touch and mouse event (like
on MS Surface
// and lenovo X240, @see #2350), we make mouse event be always listened,
otherwise
// mouse event can not be handle in those devices.
// 2. On MS Surface, Chrome will trigger both touch event and mouse event.
How to prevent
// mouseevent after touch event triggered, see `setTouchTimer`.
mountHandlers(mouseHandlerNames, this);
}
mixin(HandlerDomProxy, Eventful);
/*!
* ZRender, a high performance 2d drawing library.
*
* Copyright (c) 2013, Baidu Inc.
* All rights reserved.
*
* LICENSE
* https://github.com/ecomfe/zrender/blob/master/LICENSE.txt
*/
var painterCtors = {
canvas: Painter
};
/**
* @type {string}
*/
var version$1 = '4.0.3';
/**
* Initializing a zrender instance
* @param {HTMLElement} dom
* @param {Object} opts
* @param {string} [opts.renderer='canvas'] 'canvas' or 'svg'
* @param {number} [opts.devicePixelRatio]
* @param {number|string} [opts.width] Can be 'auto' (the same as null/undefined)
* @param {number|string} [opts.height] Can be 'auto' (the same as null/undefined)
* @return {module:zrender/ZRender}
*/
function init$1(dom, opts) {
var zr = new ZRender(guid(), dom, opts);
instances$1[zr.id] = zr;
return zr;
}
/**
* Dispose zrender instance
* @param {module:zrender/ZRender} zr
*/
function dispose$1(zr) {
if (zr) {
zr.dispose();
}
else {
for (var key in instances$1) {
if (instances$1.hasOwnProperty(key)) {
instances$1[key].dispose();
}
}
instances$1 = {};
}
return this;
}
/**
* Get zrender instance by id
* @param {string} id zrender instance id
* @return {module:zrender/ZRender}
*/
function getInstance(id) {
return instances$1[id];
}
function delInstance(id) {
delete instances$1[id];
}
/**
* @module zrender/ZRender
*/
/**
* @constructor
* @alias module:zrender/ZRender
* @param {string} id
* @param {HTMLElement} dom
* @param {Object} opts
* @param {string} [opts.renderer='canvas'] 'canvas' or 'svg'
* @param {number} [opts.devicePixelRatio]
* @param {number} [opts.width] Can be 'auto' (the same as null/undefined)
* @param {number} [opts.height] Can be 'auto' (the same as null/undefined)
*/
var ZRender = function (id, dom, opts) {
/**
* @type {HTMLDomElement}
*/
this.dom = dom;
/**
* @type {string}
*/
this.id = id;
this.storage = storage;
this.painter = painter;
/**
* @type {module:zrender/animation/Animation}
*/
this.animation = new Animation({
stage: {
update: bind(this.flush, this)
}
});
this.animation.start();
/**
* @type {boolean}
* @private
*/
this._needsRefresh;
// 修改 storage.delFromStorage, 每次删除元素之前删除动画
// FIXME 有点 ugly
var oldDelFromStorage = storage.delFromStorage;
var oldAddToStorage = storage.addToStorage;
el && el.removeSelfFromZr(self);
};
ZRender.prototype = {
constructor: ZRender,
/**
* 获取实例唯一标识
* @return {string}
*/
getId: function () {
return this.id;
},
/**
* 添加元素
* @param {module:zrender/Element} el
*/
add: function (el) {
this.storage.addRoot(el);
this._needsRefresh = true;
},
/**
* 删除元素
* @param {module:zrender/Element} el
*/
remove: function (el) {
this.storage.delRoot(el);
this._needsRefresh = true;
},
/**
* Change configuration of layer
* @param {string} zLevel
* @param {Object} config
* @param {string} [config.clearColor=0] Clear color
* @param {string} [config.motionBlur=false] If enable motion blur
* @param {number} [config.lastFrameAlpha=0.7] Motion blur factor. Larger value
cause longer trailer
*/
configLayer: function (zLevel, config) {
if (this.painter.configLayer) {
this.painter.configLayer(zLevel, config);
}
this._needsRefresh = true;
},
/**
* Set background color
* @param {string} backgroundColor
*/
setBackgroundColor: function (backgroundColor) {
if (this.painter.setBackgroundColor) {
this.painter.setBackgroundColor(backgroundColor);
}
this._needsRefresh = true;
},
/**
* Repaint the canvas immediately
*/
refreshImmediately: function () {
// var start = new Date();
// Clear needsRefresh ahead to avoid something wrong happens in refresh
// Or it will cause zrender refreshes again and again.
this._needsRefresh = false;
this.painter.refresh();
/**
* Avoid trigger zr.refresh in Element#beforeUpdate hook
*/
this._needsRefresh = false;
// var end = new Date();
/**
* Mark and repaint the canvas in the next frame of browser
*/
refresh: function() {
this._needsRefresh = true;
},
/**
* Perform all refresh
*/
flush: function () {
var triggerRendered;
if (this._needsRefresh) {
triggerRendered = true;
this.refreshImmediately();
}
if (this._needsRefreshHover) {
triggerRendered = true;
this.refreshHoverImmediately();
}
/**
* Add element to hover layer
* @param {module:zrender/Element} el
* @param {Object} style
*/
addHover: function (el, style) {
if (this.painter.addHover) {
this.painter.addHover(el, style);
this.refreshHover();
}
},
/**
* Add element from hover layer
* @param {module:zrender/Element} el
*/
removeHover: function (el) {
if (this.painter.removeHover) {
this.painter.removeHover(el);
this.refreshHover();
}
},
/**
* Clear all hover elements in hover layer
* @param {module:zrender/Element} el
*/
clearHover: function () {
if (this.painter.clearHover) {
this.painter.clearHover();
this.refreshHover();
}
},
/**
* Refresh hover in next frame
*/
refreshHover: function () {
this._needsRefreshHover = true;
},
/**
* Refresh hover immediately
*/
refreshHoverImmediately: function () {
this._needsRefreshHover = false;
this.painter.refreshHover && this.painter.refreshHover();
},
/**
* Resize the canvas.
* Should be invoked when container size is changed
* @param {Object} [opts]
* @param {number|string} [opts.width] Can be 'auto' (the same as
null/undefined)
* @param {number|string} [opts.height] Can be 'auto' (the same as
null/undefined)
*/
resize: function(opts) {
opts = opts || {};
this.painter.resize(opts.width, opts.height);
this.handler.resize();
},
/**
* Stop and clear all animation immediately
*/
clearAnimation: function () {
this.animation.clear();
},
/**
* Get container width
*/
getWidth: function() {
return this.painter.getWidth();
},
/**
* Get container height
*/
getHeight: function() {
return this.painter.getHeight();
},
/**
* Export the canvas as Base64 URL
* @param {string} type
* @param {string} [backgroundColor='#fff']
* @return {string} Base64 URL
*/
// toDataURL: function(type, backgroundColor) {
// return this.painter.getRenderedCanvas({
// backgroundColor: backgroundColor
// }).toDataURL(type);
// },
/**
* Converting a path to image.
* It has much better performance of drawing image rather than drawing a vector
path.
* @param {module:zrender/graphic/Path} e
* @param {number} width
* @param {number} height
*/
pathToImage: function(e, dpr) {
return this.painter.pathToImage(e, dpr);
},
/**
* Set default cursor
* @param {string} [cursorStyle='default'] 例如 crosshair
*/
setCursorStyle: function (cursorStyle) {
this.handler.setCursorStyle(cursorStyle);
},
/**
* Find hovered element
* @param {number} x
* @param {number} y
* @return {Object} {target, topTarget}
*/
findHover: function (x, y) {
return this.handler.findHover(x, y);
},
/**
* Bind event
*
* @param {string} eventName Event name
* @param {Function} eventHandler Handler function
* @param {Object} [context] Context object
*/
on: function(eventName, eventHandler, context) {
this.handler.on(eventName, eventHandler, context);
},
/**
* Unbind event
* @param {string} eventName Event name
* @param {Function} [eventHandler] Handler function
*/
off: function(eventName, eventHandler) {
this.handler.off(eventName, eventHandler);
},
/**
* Trigger event manually
*
* @param {string} eventName Event name
* @param {event=} event Event object
*/
trigger: function (eventName, event) {
this.handler.trigger(eventName, event);
},
/**
* Clear all objects and the canvas.
*/
clear: function () {
this.storage.delRoot();
this.painter.clear();
},
/**
* Dispose self.
*/
dispose: function () {
this.animation.stop();
this.clear();
this.storage.dispose();
this.painter.dispose();
this.handler.dispose();
this.animation =
this.storage =
this.painter =
this.handler = null;
delInstance(this.id);
}
};
var zrender = (Object.freeze || Object)({
version: version$1,
init: init$1,
dispose: dispose$1,
getInstance: getInstance,
registerPainter: registerPainter
});
/**
* Make the name displayable. But we should
* make sure it is not duplicated with user
* specified name, so use '\0';
*/
var DUMMY_COMPONENT_NAME_PREFIX = 'series\0';
/**
* If value is not array, then translate it to array.
* @param {*} value
* @return {Array} [value] or value
*/
function normalizeToArray(value) {
return value instanceof Array
? value
: value == null
? []
: [value];
}
/**
* Sync default option between normal and emphasis like `position` and `show`
* In case some one will write code like
* label: {
* show: false,
* position: 'outside',
* fontSize: 18
* },
* emphasis: {
* label: { show: true }
* }
* @param {Object} opt
* @param {string} key
* @param {Array.<string>} subOpts
*/
function defaultEmphasis(opt, key, subOpts) {
if (opt) {
opt[key] = opt[key] || {};
opt.emphasis = opt.emphasis || {};
opt.emphasis[key] = opt.emphasis[key] || {};
var TEXT_STYLE_OPTIONS = [
'fontStyle', 'fontWeight', 'fontSize', 'fontFamily',
'rich', 'tag', 'color', 'textBorderColor', 'textBorderWidth',
'width', 'height', 'lineHeight', 'align', 'verticalAlign', 'baseline',
'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY',
'textShadowColor', 'textShadowBlur', 'textShadowOffsetX', 'textShadowOffsetY',
'backgroundColor', 'borderColor', 'borderWidth', 'borderRadius', 'padding'
];
// modelUtil.LABEL_OPTIONS = modelUtil.TEXT_STYLE_OPTIONS.concat([
// 'position', 'offset', 'rotate', 'origin', 'show', 'distance', 'formatter',
// 'fontStyle', 'fontWeight', 'fontSize', 'fontFamily',
// // FIXME: deprecated, check and remove it.
// 'textStyle'
// ]);
/**
* The method do not ensure performance.
* data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}]
* This helper method retieves value from data.
* @param {string|number|Date|Array|Object} dataItem
* @return {number|string|Date|Array.<number|string|Date>}
*/
function getDataItemValue(dataItem) {
return (isObject$2(dataItem) && !isArray$1(dataItem) && !(dataItem instanceof
Date))
? dataItem.value : dataItem;
}
/**
* data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}]
* This helper method determine if dataItem has extra option besides value
* @param {string|number|Date|Array|Object} dataItem
*/
function isDataItemOption(dataItem) {
return isObject$2(dataItem)
&& !(dataItem instanceof Array);
// // markLine data can be array
// && !(dataItem[0] && isObject(dataItem[0]) && !(dataItem[0] instanceof
Array));
}
/**
* Mapping to exists for merge.
*
* @public
* @param {Array.<Object>|Array.<module:echarts/model/Component>} exists
* @param {Object|Array.<Object>} newCptOptions
* @return {Array.<Object>} Result, like [{exist: ..., option: ...}, {}],
* index of which is the same as exists.
*/
function mappingToExists(exists, newCptOptions) {
// Mapping by the order by original option (but not order of
// new option) in merge mode. Because we should ensure
// some specified index (like xAxisIndex) is consistent with
// original option, which is easy to understand, espatially in
// media query. And in most case, merge option is used to
// update partial option but not be expected to change order.
newCptOptions = (newCptOptions || []).slice();
var i = 0;
for (; i < result.length; i++) {
var exist = result[i].exist;
if (!result[i].option
// Existing model that already has id should be able to
// mapped to (because after mapping performed model may
// be assigned with a id, whish should not affect next
// mapping), except those has inner id.
&& !isIdInner(exist)
// Caution:
// Do not overwrite id. But name can be overwritten,
// because axis use name as 'show label text'.
// 'exist' always has id and name and we dont
// need to check it.
&& cptOption.id == null
) {
result[i].option = cptOption;
break;
}
}
if (i >= result.length) {
result.push({option: cptOption});
}
});
return result;
}
/**
* Make id and name for mapping result (result of mappingToExists)
* into `keyInfo` field.
*
* @public
* @param {Array.<Object>} Result, like [{exist: ..., option: ...}, {}],
* which order is the same as exists.
* @return {Array.<Object>} The input.
*/
function makeIdAndName(mapResult) {
// We use this id to hash component models and view instances
// in echarts. id can be specified by user, or auto generated.
assert$1(
!opt || opt.id == null || !idMap.get(opt.id) || idMap.get(opt.id) ===
item,
'id duplicates: ' + (opt && opt.id)
);
opt && opt.id != null && idMap.set(opt.id, item);
!item.keyInfo && (item.keyInfo = {});
});
if (!isObject$2(opt)) {
return;
}
if (existCpt) {
keyInfo.id = existCpt.id;
}
else if (opt.id != null) {
keyInfo.id = opt.id + '';
}
else {
// Consider this situatoin:
// optionA: [{name: 'a'}, {name: 'a'}, {..}]
// optionB [{..}, {name: 'a'}, {name: 'a'}]
// Series with the same name between optionA and optionB
// should be mapped.
var idNum = 0;
do {
keyInfo.id = '\0' + keyInfo.name + '\0' + idNum++;
}
while (idMap.get(keyInfo.id));
}
idMap.set(keyInfo.id, item);
});
}
function isNameSpecified(componentModel) {
var name = componentModel.name;
// Is specified when `indexOf` get -1 or > 0.
return !!(name && name.indexOf(DUMMY_COMPONENT_NAME_PREFIX));
}
/**
* @public
* @param {Object} cptOption
* @return {boolean}
*/
function isIdInner(cptOption) {
return isObject$2(cptOption)
&& cptOption.id
&& (cptOption.id + '').indexOf('\0_ec_\0') === 0;
}
/**
* A helper for removing duplicate items between batchA and batchB,
* and in themselves, and categorize by series.
*
* @param {Array.<Object>} batchA Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...]
* @param {Array.<Object>} batchB Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...]
* @return {Array.<Array.<Object>, Array.<Object>>} result: [resultBatchA,
resultBatchB]
*/
function compressBatches(batchA, batchB) {
var mapA = {};
var mapB = {};
/**
* @param {module:echarts/data/List} data
* @param {Object} payload Contains dataIndex (means rawIndex) / dataIndexInside /
name
* each of which can be Array or primary type.
* @return {number|Array.<number>} dataIndex If not found, return undefined/null.
*/
function queryDataIndex(data, payload) {
if (payload.dataIndexInside != null) {
return payload.dataIndexInside;
}
else if (payload.dataIndex != null) {
return isArray(payload.dataIndex)
? map(payload.dataIndex, function (value) {
return data.indexOfRawIndex(value);
})
: data.indexOfRawIndex(payload.dataIndex);
}
else if (payload.name != null) {
return isArray(payload.name)
? map(payload.name, function (value) {
return data.indexOfName(value);
})
: data.indexOfName(payload.name);
}
}
/**
* Enable property storage to any host object.
* Notice: Serialization is not supported.
*
* For example:
* var inner = zrUitl.makeInner();
*
* function some1(hostObj) {
* inner(hostObj).someProperty = 1212;
* ...
* }
* function some2() {
* var fields = inner(this);
* fields.someProperty1 = 1212;
* fields.someProperty2 = 'xx';
* ...
* }
*
* @return {Function}
*/
function makeInner() {
// Consider different scope by es module import.
var key = '__\0ec_inner_' + innerUniqueIndex++ + '_' +
Math.random().toFixed(5);
return function (hostObj) {
return hostObj[key] || (hostObj[key] = {});
};
}
var innerUniqueIndex = 0;
/**
* @param {module:echarts/model/Global} ecModel
* @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)
* }
* Each properties can be number|string|Array.<number>|Array.<string>
* For example, a finder could be
* {
* seriesIndex: 3,
* geoId: ['aa', 'cc'],
* gridName: ['xx', 'rr']
* }
* xxxIndex can be set as 'all' (means all xxx) or 'none' (means not
specify)
* If nothing or null/undefined specified, return nothing.
* @param {Object} [opt]
* @param {string} [opt.defaultMainType]
* @param {Array.<string>} [opt.includeMainTypes]
* @return {Object} result like:
* {
* seriesModels: [seriesModel1, seriesModel2],
* seriesModel: seriesModel1, // The first model
* geoModels: [geoModel1, geoModel2],
* geoModel: geoModel1, // The first model
* ...
* }
*/
function parseFinder(ecModel, finder, opt) {
if (isString(finder)) {
var obj = {};
obj[finder + 'Index'] = 0;
finder = obj;
}
if (!mainType
|| !queryType
|| value == null
|| (queryType === 'index' && value === 'none')
|| (opt && opt.includeMainTypes && indexOf(opt.includeMainTypes,
mainType) < 0)
) {
return;
}
return result;
}
/**
* Notice, parseClassType('') should returns {main: '', sub: ''}
* @public
*/
function parseClassType$1(componentType) {
var ret = {main: '', sub: ''};
if (componentType) {
componentType = componentType.split(TYPE_DELIMITER);
ret.main = componentType[0] || '';
ret.sub = componentType[1] || '';
}
return ret;
}
/**
* @public
*/
function checkClassType(componentType) {
assert$1(
/^[a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)?$/.test(componentType),
'componentType "' + componentType + '" illegal'
);
}
/**
* @public
*/
function enableClassExtend(RootClass, mandatoryMethods) {
RootClass.$constructor = RootClass;
RootClass.extend = function (proto) {
if (__DEV__) {
each$1(mandatoryMethods, function (method) {
if (!proto[method]) {
console.warn(
'Method `' + method + '` should be implemented'
+ (proto.type ? ' in ' + proto.type : '') + '.'
);
}
});
}
extend(ExtendedClass.prototype, proto);
ExtendedClass.extend = this.extend;
ExtendedClass.superCall = superCall;
ExtendedClass.superApply = superApply;
inherits(ExtendedClass, this);
ExtendedClass.superClass = superClass;
return ExtendedClass;
};
}
var classBase = 0;
/**
* Can not use instanceof, consider different scope by
* cross domain or es module import in ec extensions.
* Mount a method "isInstance()" to Clz.
*/
function enableClassCheck(Clz) {
var classAttr = ['__\0is_clz', classBase++,
Math.random().toFixed(3)].join('_');
Clz.prototype[classAttr] = true;
if (__DEV__) {
assert$1(!Clz.isInstance, 'The method "is" can not be defined.');
}
// superCall should have class info, which can not be fetch from 'this'.
// Consider this case:
// class A has method f,
// class B inherits class A, overrides method f, f call superApply('f'),
// class C inherits class B, do not overrides method f,
// then when method of class C is called, dead loop occured.
function superCall(context, methodName) {
var args = slice(arguments, 2);
return this.superClass.prototype[methodName].apply(context, args);
}
/**
* @param {Object} entity
* @param {Object} options
* @param {boolean} [options.registerWhenExtend]
* @public
*/
function enableClassManagement(entity, options) {
options = options || {};
/**
* Component model classes
* key: componentType,
* value:
* componentClass, when componentType is 'xxx'
* or Object.<subKey, componentClass>, when componentType is 'xxx.yy'
* @type {Object}
*/
var storage = {};
return Clazz;
};
return result;
};
/**
* If a main type is container and has sub types
* @param {string} mainType
* @return {boolean}
*/
entity.hasSubTypes = function (componentType) {
componentType = parseClassType$1(componentType);
var obj = storage[componentType.main];
return obj && obj[IS_CONTAINER];
};
entity.parseClassType = parseClassType$1;
function makeContainer(componentType) {
var container = storage[componentType.main];
if (!container || !container[IS_CONTAINER]) {
container = storage[componentType.main] = {};
container[IS_CONTAINER] = true;
}
return container;
}
if (options.registerWhenExtend) {
var originalExtend = entity.extend;
if (originalExtend) {
entity.extend = function (proto) {
var ExtendedClass = originalExtend.call(this, proto);
return entity.registerClass(ExtendedClass, proto.type);
};
}
}
return entity;
}
/**
* @param {string|Array.<string>} properties
*/
var lineStyleMixin = {
getLineStyle: function (excludes) {
var style = getLineStyle(this, excludes);
var lineDash = this.getLineDash(style.lineWidth);
lineDash && (style.lineDash = lineDash);
return style;
},
var areaStyleMixin = {
getAreaStyle: function (excludes, includes) {
return getAreaStyle(this, excludes, includes);
}
};
/**
* 曲线辅助模块
* @module zrender/core/curve
* @author pissang(https://www.github.com/pissang)
*/
// 临时变量
var _v0 = create();
var _v1 = create();
var _v2 = create();
function isAroundZero(val) {
return val > -EPSILON$1 && val < EPSILON$1;
}
function isNotAroundZero$1(val) {
return val > EPSILON$1 || val < -EPSILON$1;
}
/**
* 计算三次贝塞尔值
* @memberOf module:zrender/core/curve
* @param {number} p0
* @param {number} p1
* @param {number} p2
* @param {number} p3
* @param {number} t
* @return {number}
*/
function cubicAt(p0, p1, p2, p3, t) {
var onet = 1 - t;
return onet * onet * (onet * p0 + 3 * t * p1)
+ t * t * (t * p3 + 3 * onet * p2);
}
/**
* 计算三次贝塞尔导数值
* @memberOf module:zrender/core/curve
* @param {number} p0
* @param {number} p1
* @param {number} p2
* @param {number} p3
* @param {number} t
* @return {number}
*/
function cubicDerivativeAt(p0, p1, p2, p3, t) {
var onet = 1 - t;
return 3 * (
((p1 - p0) * onet + 2 * (p2 - p1) * t) * onet
+ (p3 - p2) * t * t
);
}
/**
* 计算三次贝塞尔方程根,使用盛金公式
* @memberOf module:zrender/core/curve
* @param {number} p0
* @param {number} p1
* @param {number} p2
* @param {number} p3
* @param {number} val
* @param {Array.<number>} roots
* @return {number} 有效根数目
*/
function cubicRootAt(p0, p1, p2, p3, val, roots) {
// Evaluate roots of cubic functions
var a = p3 + 3 * (p1 - p2) - p0;
var b = 3 * (p2 - p1 * 2 + p0);
var c = 3 * (p1 - p0);
var d = p0 - val;
var A = b * b - 3 * a * c;
var B = b * c - 9 * a * d;
var C = c * c - 3 * b * d;
var n = 0;
if (isAroundZero(disc)) {
var K = B / A;
var t1 = -b / a + K; // t1, a is not zero
var t2 = -K / 2; // t2, t3
if (t1 >= 0 && t1 <= 1) {
roots[n++] = t1;
}
if (t2 >= 0 && t2 <= 1) {
roots[n++] = t2;
}
}
else if (disc > 0) {
var discSqrt = mathSqrt$2(disc);
var Y1 = A * b + 1.5 * a * (-B + discSqrt);
var Y2 = A * b + 1.5 * a * (-B - discSqrt);
if (Y1 < 0) {
Y1 = -mathPow(-Y1, ONE_THIRD);
}
else {
Y1 = mathPow(Y1, ONE_THIRD);
}
if (Y2 < 0) {
Y2 = -mathPow(-Y2, ONE_THIRD);
}
else {
Y2 = mathPow(Y2, ONE_THIRD);
}
var t1 = (-b - (Y1 + Y2)) / (3 * a);
if (t1 >= 0 && t1 <= 1) {
roots[n++] = t1;
}
}
else {
var T = (2 * A * b - 3 * a * B) / (2 * mathSqrt$2(A * A * A));
var theta = Math.acos(T) / 3;
var ASqrt = mathSqrt$2(A);
var tmp = Math.cos(theta);
/**
* 计算三次贝塞尔方程极限值的位置
* @memberOf module:zrender/core/curve
* @param {number} p0
* @param {number} p1
* @param {number} p2
* @param {number} p3
* @param {Array.<number>} extrema
* @return {number} 有效数目
*/
function cubicExtrema(p0, p1, p2, p3, extrema) {
var b = 6 * p2 - 12 * p1 + 6 * p0;
var a = 9 * p1 + 3 * p3 - 3 * p0 - 9 * p2;
var c = 3 * p1 - 3 * p0;
var n = 0;
if (isAroundZero(a)) {
if (isNotAroundZero$1(b)) {
var t1 = -c / b;
if (t1 >= 0 && t1 <=1) {
extrema[n++] = t1;
}
}
}
else {
var disc = b * b - 4 * a * c;
if (isAroundZero(disc)) {
extrema[0] = -b / (2 * a);
}
else if (disc > 0) {
var discSqrt = mathSqrt$2(disc);
var t1 = (-b + discSqrt) / (2 * a);
var t2 = (-b - discSqrt) / (2 * a);
if (t1 >= 0 && t1 <= 1) {
extrema[n++] = t1;
}
if (t2 >= 0 && t2 <= 1) {
extrema[n++] = t2;
}
}
}
return n;
}
/**
* 细分三次贝塞尔曲线
* @memberOf module:zrender/core/curve
* @param {number} p0
* @param {number} p1
* @param {number} p2
* @param {number} p3
* @param {number} t
* @param {Array.<number>} out
*/
function cubicSubdivide(p0, p1, p2, p3, t, out) {
var p01 = (p1 - p0) * t + p0;
var p12 = (p2 - p1) * t + p1;
var p23 = (p3 - p2) * t + p2;
_v0[0] = x;
_v0[1] = y;
// 先粗略估计一下可能的最小距离的 t 值
// PENDING
for (var _t = 0; _t < 1; _t += 0.05) {
_v1[0] = cubicAt(x0, x1, x2, x3, _t);
_v1[1] = cubicAt(y0, y1, y2, y3, _t);
d1 = distSquare(_v0, _v1);
if (d1 < d) {
t = _t;
d = d1;
}
}
d = Infinity;
// At most 32 iteration
for (var i = 0; i < 32; i++) {
if (interval < EPSILON_NUMERIC) {
break;
}
prev = t - interval;
next = t + interval;
// t - interval
_v1[0] = cubicAt(x0, x1, x2, x3, prev);
_v1[1] = cubicAt(y0, y1, y2, y3, prev);
d1 = distSquare(_v1, _v0);
/**
* 计算二次方贝塞尔值
* @param {number} p0
* @param {number} p1
* @param {number} p2
* @param {number} t
* @return {number}
*/
function quadraticAt(p0, p1, p2, t) {
var onet = 1 - t;
return onet * (onet * p0 + 2 * t * p1) + t * t * p2;
}
/**
* 计算二次方贝塞尔导数值
* @param {number} p0
* @param {number} p1
* @param {number} p2
* @param {number} t
* @return {number}
*/
function quadraticDerivativeAt(p0, p1, p2, t) {
return 2 * ((1 - t) * (p1 - p0) + t * (p2 - p1));
}
/**
* 计算二次方贝塞尔方程根
* @param {number} p0
* @param {number} p1
* @param {number} p2
* @param {number} t
* @param {Array.<number>} roots
* @return {number} 有效根数目
*/
function quadraticRootAt(p0, p1, p2, val, roots) {
var a = p0 - 2 * p1 + p2;
var b = 2 * (p1 - p0);
var c = p0 - val;
var n = 0;
if (isAroundZero(a)) {
if (isNotAroundZero$1(b)) {
var t1 = -c / b;
if (t1 >= 0 && t1 <= 1) {
roots[n++] = t1;
}
}
}
else {
var disc = b * b - 4 * a * c;
if (isAroundZero(disc)) {
var t1 = -b / (2 * a);
if (t1 >= 0 && t1 <= 1) {
roots[n++] = t1;
}
}
else if (disc > 0) {
var discSqrt = mathSqrt$2(disc);
var t1 = (-b + discSqrt) / (2 * a);
var t2 = (-b - discSqrt) / (2 * a);
if (t1 >= 0 && t1 <= 1) {
roots[n++] = t1;
}
if (t2 >= 0 && t2 <= 1) {
roots[n++] = t2;
}
}
}
return n;
}
/**
* 计算二次贝塞尔方程极限值
* @memberOf module:zrender/core/curve
* @param {number} p0
* @param {number} p1
* @param {number} p2
* @return {number}
*/
function quadraticExtremum(p0, p1, p2) {
var divider = p0 + p2 - 2 * p1;
if (divider === 0) {
// p1 is center of p0 and p2
return 0.5;
}
else {
return (p0 - p1) / divider;
}
}
/**
* 细分二次贝塞尔曲线
* @memberOf module:zrender/core/curve
* @param {number} p0
* @param {number} p1
* @param {number} p2
* @param {number} t
* @param {Array.<number>} out
*/
function quadraticSubdivide(p0, p1, p2, t, out) {
var p01 = (p1 - p0) * t + p0;
var p12 = (p2 - p1) * t + p1;
var p012 = (p12 - p01) * t + p01;
// Seg0
out[0] = p0;
out[1] = p01;
out[2] = p012;
// Seg1
out[3] = p012;
out[4] = p12;
out[5] = p2;
}
/**
* 投射点到二次贝塞尔曲线上,返回投射距离。
* 投射点有可能会有一个或者多个,这里只返回其中距离最短的一个。
* @param {number} x0
* @param {number} y0
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} x
* @param {number} y
* @param {Array.<number>} out 投射点
* @return {number}
*/
function quadraticProjectPoint(
x0, y0, x1, y1, x2, y2,
x, y, out
) {
// http://pomax.github.io/bezierinfo/#projections
var t;
var interval = 0.005;
var d = Infinity;
_v0[0] = x;
_v0[1] = y;
// 先粗略估计一下可能的最小距离的 t 值
// PENDING
for (var _t = 0; _t < 1; _t += 0.05) {
_v1[0] = quadraticAt(x0, x1, x2, _t);
_v1[1] = quadraticAt(y0, y1, y2, _t);
var d1 = distSquare(_v0, _v1);
if (d1 < d) {
t = _t;
d = d1;
}
}
d = Infinity;
// At most 32 iteration
for (var i = 0; i < 32; i++) {
if (interval < EPSILON_NUMERIC) {
break;
}
var prev = t - interval;
var next = t + interval;
// t - interval
_v1[0] = quadraticAt(x0, x1, x2, prev);
_v1[1] = quadraticAt(y0, y1, y2, prev);
/**
* @author Yi Shen(https://github.com/pissang)
*/
/**
* 从顶点数组中计算出最小包围盒,写入`min`和`max`中
* @module zrender/core/bbox
* @param {Array<Object>} points 顶点数组
* @param {number} min
* @param {number} max
*/
function fromPoints(points, min$$1, max$$1) {
if (points.length === 0) {
return;
}
var p = points[0];
var left = p[0];
var right = p[0];
var top = p[1];
var bottom = p[1];
var i;
min$$1[0] = left;
min$$1[1] = top;
max$$1[0] = right;
max$$1[1] = bottom;
}
/**
* @memberOf module:zrender/core/bbox
* @param {number} x0
* @param {number} y0
* @param {number} x1
* @param {number} y1
* @param {Array.<number>} min
* @param {Array.<number>} max
*/
function fromLine(x0, y0, x1, y1, min$$1, max$$1) {
min$$1[0] = mathMin$3(x0, x1);
min$$1[1] = mathMin$3(y0, y1);
max$$1[0] = mathMax$3(x0, x1);
max$$1[1] = mathMax$3(y0, y1);
}
/**
* 从二阶贝塞尔曲线(p0, p1, p2)中计算出最小包围盒,写入`min`和`max`中
* @memberOf module:zrender/core/bbox
* @param {number} x0
* @param {number} y0
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {Array.<number>} min
* @param {Array.<number>} max
*/
function fromQuadratic(x0, y0, x1, y1, x2, y2, min$$1, max$$1) {
var quadraticExtremum$$1 = quadraticExtremum;
var quadraticAt$$1 = quadraticAt;
// Find extremities, where derivative in x dim or y dim is zero
var tx =
mathMax$3(
mathMin$3(quadraticExtremum$$1(x0, x1, x2), 1), 0
);
var ty =
mathMax$3(
mathMin$3(quadraticExtremum$$1(y0, y1, y2), 1), 0
);
/**
* 从圆弧中计算出最小包围盒,写入`min`和`max`中
* @method
* @memberOf module:zrender/core/bbox
* @param {number} x
* @param {number} y
* @param {number} rx
* @param {number} ry
* @param {number} startAngle
* @param {number} endAngle
* @param {number} anticlockwise
* @param {Array.<number>} min
* @param {Array.<number>} max
*/
function fromArc(
x, y, rx, ry, startAngle, endAngle, anticlockwise, min$$1, max$$1
) {
var vec2Min = min;
var vec2Max = max;
start[0] = mathCos$2(startAngle) * rx + x;
start[1] = mathSin$2(startAngle) * ry + y;
end[0] = mathCos$2(endAngle) * rx + x;
end[1] = mathSin$2(endAngle) * ry + y;
// var number = 0;
// var step = (anticlockwise ? -Math.PI : Math.PI) / 2;
for (var angle = 0; angle < endAngle; angle += Math.PI / 2) {
if (angle > startAngle) {
extremity[0] = mathCos$2(angle) * rx + x;
extremity[1] = mathSin$2(angle) * ry + y;
/**
* Path 代理,可以在`buildPath`中用于替代`ctx`, 会保存每个 path 操作的命令到 pathCommands 属
性中
* 可以用于 isInsidePath 判断以及获取 boundingRect
*
* @module zrender/core/PathProxy
* @author Yi Shen (http://www.github.com/pissang)
*/
var CMD = {
M: 1,
L: 2,
C: 3,
Q: 4,
A: 5,
Z: 6,
// Rect
R: 7
};
// var CMD_MEM_SIZE = {
// M: 3,
// L: 3,
// C: 7,
// Q: 5,
// A: 9,
// R: 5,
// Z: 1
// };
/**
* @alias module:zrender/core/PathProxy
* @constructor
*/
var PathProxy = function (notSaveData) {
if (this._saveData) {
/**
* Path data. Stored as flat array
* @type {Array.<Object>}
*/
this.data = [];
}
this._ctx = null;
};
/**
* 快速计算 Path 包围盒(并不是最小包围盒)
* @return {Object}
*/
PathProxy.prototype = {
constructor: PathProxy,
_xi: 0,
_yi: 0,
_x0: 0,
_y0: 0,
// Unit x, Unit y. Provide for avoiding drawing that too short line segment
_ux: 0,
_uy: 0,
_len: 0,
_lineDash: null,
_dashOffset: 0,
_dashIdx: 0,
_dashSum: 0,
/**
* @readOnly
*/
setScale: function (sx, sy) {
this._ux = mathAbs(1 / devicePixelRatio / sx) || 0;
this._uy = mathAbs(1 / devicePixelRatio / sy) || 0;
},
getContext: function () {
return this._ctx;
},
/**
* @param {CanvasRenderingContext2D} ctx
* @return {module:zrender/core/PathProxy}
*/
beginPath: function (ctx) {
this._ctx = ctx;
// Reset
if (this._saveData) {
this._len = 0;
}
if (this._lineDash) {
this._lineDash = null;
this._dashOffset = 0;
}
return this;
},
/**
* @param {number} x
* @param {number} y
* @return {module:zrender/core/PathProxy}
*/
moveTo: function (x, y) {
this.addData(CMD.M, x, y);
this._ctx && this._ctx.moveTo(x, y);
this._xi = x;
this._yi = y;
return this;
},
/**
* @param {number} x
* @param {number} y
* @return {module:zrender/core/PathProxy}
*/
lineTo: function (x, y) {
var exceedUnit = mathAbs(x - this._xi) > this._ux
|| mathAbs(y - this._yi) > this._uy
// Force draw the first segment
|| this._len < 5;
this.addData(CMD.L, x, y);
return this;
},
/**
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} x3
* @param {number} y3
* @return {module:zrender/core/PathProxy}
*/
bezierCurveTo: function (x1, y1, x2, y2, x3, y3) {
this.addData(CMD.C, x1, y1, x2, y2, x3, y3);
if (this._ctx) {
this._needsDash() ? this._dashedBezierTo(x1, y1, x2, y2, x3, y3)
: this._ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
}
this._xi = x3;
this._yi = y3;
return this;
},
/**
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @return {module:zrender/core/PathProxy}
*/
quadraticCurveTo: function (x1, y1, x2, y2) {
this.addData(CMD.Q, x1, y1, x2, y2);
if (this._ctx) {
this._needsDash() ? this._dashedQuadraticTo(x1, y1, x2, y2)
: this._ctx.quadraticCurveTo(x1, y1, x2, y2);
}
this._xi = x2;
this._yi = y2;
return this;
},
/**
* @param {number} cx
* @param {number} cy
* @param {number} r
* @param {number} startAngle
* @param {number} endAngle
* @param {boolean} anticlockwise
* @return {module:zrender/core/PathProxy}
*/
arc: function (cx, cy, r, startAngle, endAngle, anticlockwise) {
this.addData(
CMD.A, cx, cy, r, r, startAngle, endAngle - startAngle, 0,
anticlockwise ? 0 : 1
);
this._ctx && this._ctx.arc(cx, cy, r, startAngle, endAngle, anticlockwise);
// TODO
arcTo: function (x1, y1, x2, y2, radius) {
if (this._ctx) {
this._ctx.arcTo(x1, y1, x2, y2, radius);
}
return this;
},
// TODO
rect: function (x, y, w, h) {
this._ctx && this._ctx.rect(x, y, w, h);
this.addData(CMD.R, x, y, w, h);
return this;
},
/**
* @return {module:zrender/core/PathProxy}
*/
closePath: function () {
this.addData(CMD.Z);
/**
* Context 从外部传入,因为有可能是 rebuildPath 完之后再 fill。
* stroke 同样
* @param {CanvasRenderingContext2D} ctx
* @return {module:zrender/core/PathProxy}
*/
fill: function (ctx) {
ctx && ctx.fill();
this.toStatic();
},
/**
* @param {CanvasRenderingContext2D} ctx
* @return {module:zrender/core/PathProxy}
*/
stroke: function (ctx) {
ctx && ctx.stroke();
this.toStatic();
},
/**
* 必须在其它绘制命令前调用
* Must be invoked before all other path drawing methods
* @return {module:zrender/core/PathProxy}
*/
setLineDash: function (lineDash) {
if (lineDash instanceof Array) {
this._lineDash = lineDash;
this._dashIdx = 0;
var lineDashSum = 0;
for (var i = 0; i < lineDash.length; i++) {
lineDashSum += lineDash[i];
}
this._dashSum = lineDashSum;
}
return this;
},
/**
* 必须在其它绘制命令前调用
* Must be invoked before all other path drawing methods
* @return {module:zrender/core/PathProxy}
*/
setLineDashOffset: function (offset) {
this._dashOffset = offset;
return this;
},
/**
*
* @return {boolean}
*/
len: function () {
return this._len;
},
/**
* 直接设置 Path 数据
*/
setData: function (data) {
this._len = len$$1;
},
/**
* 添加子路径
* @param {module:zrender/core/PathProxy|Array.<module:zrender/core/PathProxy>}
path
*/
appendPath: function (path) {
if (!(path instanceof Array)) {
path = [path];
}
var len$$1 = path.length;
var appendSize = 0;
var offset = this._len;
for (var i = 0; i < len$$1; i++) {
appendSize += path[i].len();
}
if (hasTypedArray && (this.data instanceof Float32Array)) {
this.data = new Float32Array(offset + appendSize);
}
for (var i = 0; i < len$$1; i++) {
var appendPathData = path[i].data;
for (var k = 0; k < appendPathData.length; k++) {
this.data[offset++] = appendPathData[k];
}
}
this._len = offset;
},
/**
* 填充 Path 数据。
* 尽量复用而不申明新的数组。大部分图形重绘的指令数据长度都是不变的。
*/
addData: function (cmd) {
if (!this._saveData) {
return;
}
this._prevCmd = cmd;
},
_expandData: function () {
// Only if data is Float32Array
if (!(this.data instanceof Array)) {
var newData = [];
for (var i = 0; i < this._len; i++) {
newData[i] = this.data[i];
}
this.data = newData;
}
},
/**
* If needs js implemented dashed line
* @return {boolean}
* @private
*/
_needsDash: function () {
return this._lineDash;
},
var x0 = this._xi;
var y0 = this._yi;
var dx = x1 - x0;
var dy = y1 - y0;
var dist$$1 = mathSqrt$1(dx * dx + dy * dy);
var x = x0;
var y = y0;
var dash;
var nDash = lineDash.length;
var idx;
dx /= dist$$1;
dy /= dist$$1;
if (offset < 0) {
// Convert to positive offset
offset = dashSum + offset;
}
offset %= dashSum;
x -= offset * dx;
y -= offset * dy;
while ((dx > 0 && x <= x1) || (dx < 0 && x >= x1)
|| (dx == 0 && ((dy > 0 && y <= y1) || (dy < 0 && y >= y1)))) {
idx = this._dashIdx;
dash = lineDash[idx];
x += dx * dash;
y += dy * dash;
this._dashIdx = (idx + 1) % nDash;
// Skip positive offset
if ((dx > 0 && x < x0) || (dx < 0 && x > x0) || (dy > 0 && y < y0) ||
(dy < 0 && y > y0)) {
continue;
}
ctx[idx % 2 ? 'moveTo' : 'lineTo'](
dx >= 0 ? mathMin$2(x, x1) : mathMax$2(x, x1),
dy >= 0 ? mathMin$2(y, y1) : mathMax$2(y, y1)
);
}
// Offset for next lineTo
dx = x - x1;
dy = y - y1;
this._dashOffset = -mathSqrt$1(dx * dx + dy * dy);
},
var x0 = this._xi;
var y0 = this._yi;
var t;
var dx;
var dy;
var cubicAt$$1 = cubicAt;
var bezierLen = 0;
var idx = this._dashIdx;
var nDash = lineDash.length;
var x;
var y;
var tmpLen = 0;
if (offset < 0) {
// Convert to positive offset
offset = dashSum + offset;
}
offset %= dashSum;
// Bezier approx length
for (t = 0; t < 1; t += 0.1) {
dx = cubicAt$$1(x0, x1, x2, x3, t + 0.1)
- cubicAt$$1(x0, x1, x2, x3, t);
dy = cubicAt$$1(y0, y1, y2, y3, t + 0.1)
- cubicAt$$1(y0, y1, y2, y3, t);
bezierLen += mathSqrt$1(dx * dx + dy * dy);
}
// Find idx after add offset
for (; idx < nDash; idx++) {
tmpLen += lineDash[idx];
if (tmpLen > offset) {
break;
}
}
t = (tmpLen - offset) / bezierLen;
while (t <= 1) {
t += lineDash[idx] / bezierLen;
/**
* 转成静态的 Float32Array 减少堆内存占用
* Convert dynamic array to static Float32Array
*/
toStatic: function () {
var data = this.data;
if (data instanceof Array) {
data.length = this._len;
if (hasTypedArray) {
this.data = new Float32Array(data);
}
}
},
/**
* @return {module:zrender/core/BoundingRect}
*/
getBoundingRect: function () {
min$1[0] = min$1[1] = min2[0] = min2[1] = Number.MAX_VALUE;
max$1[0] = max$1[1] = max2[0] = max2[1] = -Number.MAX_VALUE;
if (i == 1) {
// 如果第一个命令是 L, C, Q
// 则 previous point 同绘制命令的第一个 point
//
// 第一个命令为 Arc 的情况下会在后面特殊处理
xi = data[i];
yi = data[i + 1];
x0 = xi;
y0 = yi;
}
switch (cmd) {
case CMD.M:
// moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
// 在 closePath 的时候使用
x0 = data[i++];
y0 = data[i++];
xi = x0;
yi = y0;
min2[0] = x0;
min2[1] = y0;
max2[0] = x0;
max2[1] = y0;
break;
case CMD.L:
fromLine(xi, yi, data[i], data[i + 1], min2, max2);
xi = data[i++];
yi = data[i++];
break;
case CMD.C:
fromCubic(
xi, yi, data[i++], data[i++], data[i++], data[i++],
data[i], data[i + 1],
min2, max2
);
xi = data[i++];
yi = data[i++];
break;
case CMD.Q:
fromQuadratic(
xi, yi, data[i++], data[i++], data[i], data[i + 1],
min2, max2
);
xi = data[i++];
yi = data[i++];
break;
case CMD.A:
// TODO Arc 判断的开销比较大
var cx = data[i++];
var cy = data[i++];
var rx = data[i++];
var ry = data[i++];
var startAngle = data[i++];
var endAngle = data[i++] + startAngle;
// TODO Arc 旋转
var psi = data[i++];
var anticlockwise = 1 - data[i++];
if (i == 1) {
// 直接使用 arc 命令
// 第一个命令起点还未定义
x0 = mathCos$1(startAngle) * rx + cx;
y0 = mathSin$1(startAngle) * ry + cy;
}
fromArc(
cx, cy, rx, ry, startAngle, endAngle,
anticlockwise, min2, max2
);
xi = mathCos$1(endAngle) * rx + cx;
yi = mathSin$1(endAngle) * ry + cy;
break;
case CMD.R:
x0 = xi = data[i++];
y0 = yi = data[i++];
var width = data[i++];
var height = data[i++];
// Use fromLine
fromLine(x0, y0, x0 + width, y0 + height, min2, max2);
break;
case CMD.Z:
xi = x0;
yi = y0;
break;
}
// Union
min(min$1, min$1, min2);
max(max$1, max$1, max2);
}
// No data
if (i === 0) {
min$1[0] = min$1[1] = max$1[0] = max$1[1] = 0;
}
if (i == 1) {
// 如果第一个命令是 L, C, Q
// 则 previous point 同绘制命令的第一个 point
//
// 第一个命令为 Arc 的情况下会在后面特殊处理
xi = d[i];
yi = d[i + 1];
x0 = xi;
y0 = yi;
}
switch (cmd) {
case CMD.M:
x0 = xi = d[i++];
y0 = yi = d[i++];
ctx.moveTo(xi, yi);
break;
case CMD.L:
x = d[i++];
y = d[i++];
// Not draw too small seg between
if (mathAbs(x - xi) > ux || mathAbs(y - yi) > uy || i === len$
$1 - 1) {
ctx.lineTo(x, y);
xi = x;
yi = y;
}
break;
case CMD.C:
ctx.bezierCurveTo(
d[i++], d[i++], d[i++], d[i++], d[i++], d[i++]
);
xi = d[i - 2];
yi = d[i - 1];
break;
case CMD.Q:
ctx.quadraticCurveTo(d[i++], d[i++], d[i++], d[i++]);
xi = d[i - 2];
yi = d[i - 1];
break;
case CMD.A:
var cx = d[i++];
var cy = d[i++];
var rx = d[i++];
var ry = d[i++];
var theta = d[i++];
var dTheta = d[i++];
var psi = d[i++];
var fs = d[i++];
var r = (rx > ry) ? rx : ry;
var scaleX = (rx > ry) ? 1 : rx / ry;
var scaleY = (rx > ry) ? ry / rx : 1;
var isEllipse = Math.abs(rx - ry) > 1e-3;
var endAngle = theta + dTheta;
if (isEllipse) {
ctx.translate(cx, cy);
ctx.rotate(psi);
ctx.scale(scaleX, scaleY);
ctx.arc(0, 0, r, theta, endAngle, 1 - fs);
ctx.scale(1 / scaleX, 1 / scaleY);
ctx.rotate(-psi);
ctx.translate(-cx, -cy);
}
else {
ctx.arc(cx, cy, r, theta, endAngle, 1 - fs);
}
if (i == 1) {
// 直接使用 arc 命令
// 第一个命令起点还未定义
x0 = mathCos$1(theta) * rx + cx;
y0 = mathSin$1(theta) * ry + cy;
}
xi = mathCos$1(endAngle) * rx + cx;
yi = mathSin$1(endAngle) * ry + cy;
break;
case CMD.R:
x0 = xi = d[i];
y0 = yi = d[i + 1];
ctx.rect(d[i++], d[i++], d[i++], d[i++]);
break;
case CMD.Z:
ctx.closePath();
xi = x0;
yi = y0;
}
}
}
};
PathProxy.CMD = CMD;
/**
* 线段包含判断
* @param {number} x0
* @param {number} y0
* @param {number} x1
* @param {number} y1
* @param {number} lineWidth
* @param {number} x
* @param {number} y
* @return {boolean}
*/
function containStroke$1(x0, y0, x1, y1, lineWidth, x, y) {
if (lineWidth === 0) {
return false;
}
var _l = lineWidth;
var _a = 0;
var _b = x0;
// Quick reject
if (
(y > y0 + _l && y > y1 + _l)
|| (y < y0 - _l && y < y1 - _l)
|| (x > x0 + _l && x > x1 + _l)
|| (x < x0 - _l && x < x1 - _l)
) {
return false;
}
/**
* 三次贝塞尔曲线描边包含判断
* @param {number} x0
* @param {number} y0
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} x3
* @param {number} y3
* @param {number} lineWidth
* @param {number} x
* @param {number} y
* @return {boolean}
*/
function containStroke$2(x0, y0, x1, y1, x2, y2, x3, y3, lineWidth, x, y) {
if (lineWidth === 0) {
return false;
}
var _l = lineWidth;
// Quick reject
if (
(y > y0 + _l && y > y1 + _l && y > y2 + _l && y > y3 + _l)
|| (y < y0 - _l && y < y1 - _l && y < y2 - _l && y < y3 - _l)
|| (x > x0 + _l && x > x1 + _l && x > x2 + _l && x > x3 + _l)
|| (x < x0 - _l && x < x1 - _l && x < x2 - _l && x < x3 - _l)
) {
return false;
}
var d = cubicProjectPoint(
x0, y0, x1, y1, x2, y2, x3, y3,
x, y, null
);
return d <= _l / 2;
}
/**
* 二次贝塞尔曲线描边包含判断
* @param {number} x0
* @param {number} y0
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} lineWidth
* @param {number} x
* @param {number} y
* @return {boolean}
*/
function containStroke$3(x0, y0, x1, y1, x2, y2, lineWidth, x, y) {
if (lineWidth === 0) {
return false;
}
var _l = lineWidth;
// Quick reject
if (
(y > y0 + _l && y > y1 + _l && y > y2 + _l)
|| (y < y0 - _l && y < y1 - _l && y < y2 - _l)
|| (x > x0 + _l && x > x1 + _l && x > x2 + _l)
|| (x < x0 - _l && x < x1 - _l && x < x2 - _l)
) {
return false;
}
var d = quadraticProjectPoint(
x0, y0, x1, y1, x2, y2,
x, y, null
);
return d <= _l / 2;
}
function normalizeRadian(angle) {
angle %= PI2$3;
if (angle < 0) {
angle += PI2$3;
}
return angle;
}
/**
* 圆弧描边包含判断
* @param {number} cx
* @param {number} cy
* @param {number} r
* @param {number} startAngle
* @param {number} endAngle
* @param {boolean} anticlockwise
* @param {number} lineWidth
* @param {number} x
* @param {number} y
* @return {Boolean}
*/
function containStroke$4(
cx, cy, r, startAngle, endAngle, anticlockwise,
lineWidth, x, y
) {
if (lineWidth === 0) {
return false;
}
var _l = lineWidth;
x -= cx;
y -= cy;
var d = Math.sqrt(x * x + y * y);
function isAroundEqual(a, b) {
return Math.abs(a - b) < EPSILON$2;
}
// 临时数组
var roots = [-1, -1, -1];
var extrema = [-1, -1];
function swapExtrema() {
var tmp = extrema[0];
extrema[0] = extrema[1];
extrema[1] = tmp;
}
// TODO
// Arc 旋转
function windingArc(
cx, cy, r, startAngle, endAngle, anticlockwise, x, y
) {
y -= cy;
if (y > r || y < -r) {
return 0;
}
var tmp = Math.sqrt(r * r - y * y);
roots[0] = -tmp;
roots[1] = tmp;
if (anticlockwise) {
var tmp = startAngle;
startAngle = normalizeRadian(endAngle);
endAngle = normalizeRadian(tmp);
}
else {
startAngle = normalizeRadian(startAngle);
endAngle = normalizeRadian(endAngle);
}
if (startAngle > endAngle) {
endAngle += PI2$1;
}
var w = 0;
for (var i = 0; i < 2; i++) {
var x_ = roots[i];
if (x_ + cx > x) {
var angle = Math.atan2(y, x_);
var dir = anticlockwise ? 1 : -1;
if (angle < 0) {
angle = PI2$1 + angle;
}
if (
(angle >= startAngle && angle <= endAngle)
|| (angle + PI2$1 >= startAngle && angle + PI2$1 <= endAngle)
) {
if (angle > Math.PI / 2 && angle < Math.PI * 1.5) {
dir = -dir;
}
w += dir;
}
}
}
return w;
}
if (i == 1) {
// 如果第一个命令是 L, C, Q
// 则 previous point 同绘制命令的第一个 point
//
// 第一个命令为 Arc 的情况下会在后面特殊处理
xi = data[i];
yi = data[i + 1];
x0 = xi;
y0 = yi;
}
switch (cmd) {
case CMD$1.M:
// moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
// 在 closePath 的时候使用
x0 = data[i++];
y0 = data[i++];
xi = x0;
yi = y0;
break;
case CMD$1.L:
if (isStroke) {
if (containStroke$1(xi, yi, data[i], data[i + 1], lineWidth, x,
y)) {
return true;
}
}
else {
// NOTE 在第一个命令为 L, C, Q 的时候会计算出 NaN
w += windingLine(xi, yi, data[i], data[i + 1], x, y) || 0;
}
xi = data[i++];
yi = data[i++];
break;
case CMD$1.C:
if (isStroke) {
if (containStroke$2(xi, yi,
data[i++], data[i++], data[i++], data[i++], data[i], data[i
+ 1],
lineWidth, x, y
)) {
return true;
}
}
else {
w += windingCubic(
xi, yi,
data[i++], data[i++], data[i++], data[i++], data[i], data[i
+ 1],
x, y
) || 0;
}
xi = data[i++];
yi = data[i++];
break;
case CMD$1.Q:
if (isStroke) {
if (containStroke$3(xi, yi,
data[i++], data[i++], data[i], data[i + 1],
lineWidth, x, y
)) {
return true;
}
}
else {
w += windingQuadratic(
xi, yi,
data[i++], data[i++], data[i], data[i + 1],
x, y
) || 0;
}
xi = data[i++];
yi = data[i++];
break;
case CMD$1.A:
// TODO Arc 判断的开销比较大
var cx = data[i++];
var cy = data[i++];
var rx = data[i++];
var ry = data[i++];
var theta = data[i++];
var dTheta = data[i++];
// TODO Arc 旋转
var psi = data[i++];
var anticlockwise = 1 - data[i++];
var x1 = Math.cos(theta) * rx + cx;
var y1 = Math.sin(theta) * ry + cy;
// 不是直接使用 arc 命令
if (i > 1) {
w += windingLine(xi, yi, x1, y1, x, y);
}
else {
// 第一个命令起点还未定义
x0 = x1;
y0 = y1;
}
// zr 使用 scale 来模拟椭圆, 这里也对 x 做一定的缩放
var _x = (x - cx) * ry / rx + cx;
if (isStroke) {
if (containStroke$4(
cx, cy, ry, theta, theta + dTheta, anticlockwise,
lineWidth, _x, y
)) {
return true;
}
}
else {
w += windingArc(
cx, cy, ry, theta, theta + dTheta, anticlockwise,
_x, y
);
}
xi = Math.cos(theta + dTheta) * rx + cx;
yi = Math.sin(theta + dTheta) * ry + cy;
break;
case CMD$1.R:
x0 = xi = data[i++];
y0 = yi = data[i++];
var width = data[i++];
var height = data[i++];
var x1 = x0 + width;
var y1 = y0 + height;
if (isStroke) {
if (containStroke$1(x0, y0, x1, y0, lineWidth, x, y)
|| containStroke$1(x1, y0, x1, y1, lineWidth, x, y)
|| containStroke$1(x1, y1, x0, y1, lineWidth, x, y)
|| containStroke$1(x0, y1, x0, y0, lineWidth, x, y)
) {
return true;
}
}
else {
// FIXME Clockwise ?
w += windingLine(x1, y0, x1, y1, x, y);
w += windingLine(x0, y1, x0, y0, x, y);
}
break;
case CMD$1.Z:
if (isStroke) {
if (containStroke$1(
xi, yi, x0, y0, lineWidth, x, y
)) {
return true;
}
}
else {
// Close a subpath
w += windingLine(xi, yi, x0, y0, x, y);
// 如果被任何一个 subpath 包含
// FIXME subpaths may overlap
// if (w !== 0) {
// return true;
// }
}
xi = x0;
yi = y0;
break;
}
}
if (!isStroke && !isAroundEqual(yi, y0)) {
w += windingLine(xi, yi, x0, y0, x, y) || 0;
}
return w !== 0;
}
function contain(pathData, x, y) {
return containPath(pathData, 0, false, x, y);
}
Path.prototype = {
constructor: Path,
type: 'path',
__dirtyPath: true,
strokeContainThreshold: 5,
if (this.__dirty) {
var rect;
// Update gradient because bounding rect may changed
if (hasFillGradient) {
rect = rect || this.getBoundingRect();
this._fillGradient = style.getGradient(ctx, fill, rect);
}
if (hasStrokeGradient) {
rect = rect || this.getBoundingRect();
this._strokeGradient = style.getGradient(ctx, stroke, rect);
}
}
// Use the gradient or pattern
if (hasFillGradient) {
// PENDING If may have affect the state
ctx.fillStyle = this._fillGradient;
}
else if (hasFillPattern) {
ctx.fillStyle = getCanvasPattern.call(fill, ctx);
}
if (hasStrokeGradient) {
ctx.strokeStyle = this._strokeGradient;
}
else if (hasStrokePattern) {
ctx.strokeStyle = getCanvasPattern.call(stroke, ctx);
}
var lineDash = style.lineDash;
var lineDashOffset = style.lineDashOffset;
// Proxy context
// Rebuild path in following 2 cases
// 1. Path is dirty
// 2. Path needs javascript implemented lineDash stroking.
// In this case, lineDash information will not be saved in PathProxy
if (this.__dirtyPath
|| (lineDash && !ctxLineDash && hasStroke)
) {
path.beginPath(ctx);
// When bundling path, some shape may decide if use moveTo to begin a new
subpath or closePath
// Like in circle
buildPath: function (ctx, shapeCfg, inBundle) {},
createPathProxy: function () {
this.path = new PathProxy();
},
getBoundingRect: function () {
var rect = this._rect;
var style = this.style;
var needsUpdateRect = !rect;
if (needsUpdateRect) {
var path = this.path;
if (!path) {
// Create path on demand.
path = this.path = new PathProxy();
}
if (this.__dirtyPath) {
path.beginPath();
this.buildPath(path, this.shape, false);
}
rect = path.getBoundingRect();
}
this._rect = rect;
if (style.hasStroke()) {
// Needs update rect with stroke lineWidth when
// 1. Element changes scale or lineWidth
// 2. Shape is changed
var rectWithStroke = this._rectWithStroke || (this._rectWithStroke =
rect.clone());
if (this.__dirty || needsUpdateRect) {
rectWithStroke.copy(rect);
// FIXME Must after updateTransform
var w = style.lineWidth;
// PENDING, Min line width is needed when line is horizontal or
vertical
var lineScale = style.strokeNoScale ? this.getLineScale() : 1;
return rect;
},
if (rect.contain(x, y)) {
var pathData = this.path.data;
if (style.hasStroke()) {
var lineWidth = style.lineWidth;
var lineScale = style.strokeNoScale ? this.getLineScale() : 1;
// Line scale can't be 0;
if (lineScale > 1e-10) {
// Only add extra hover lineWidth when there are no fill
if (!style.hasFill()) {
lineWidth = Math.max(lineWidth,
this.strokeContainThreshold);
}
if (containStroke(
pathData, lineWidth / lineScale, x, y
)) {
return true;
}
}
}
if (style.hasFill()) {
return contain(pathData, x, y);
}
}
return false;
},
/**
* @param {boolean} dirtyPath
*/
dirty: function (dirtyPath) {
if (dirtyPath == null) {
dirtyPath = true;
}
// Only mark dirty, not mark clean
if (dirtyPath) {
this.__dirtyPath = dirtyPath;
this._rect = null;
}
this.__dirty = true;
/**
* Alias for animate('shape')
* @param {boolean} loop
*/
animateShape: function (loop) {
return this.animate('shape', loop);
},
// Overwrite attrKV
attrKV: function (key, value) {
// FIXME
if (key === 'shape') {
this.setShape(value);
this.__dirtyPath = true;
this._rect = null;
}
else {
Displayable.prototype.attrKV.call(this, key, value);
}
},
/**
* @param {Object|string} key
* @param {*} value
*/
setShape: function (key, value) {
var shape = this.shape;
// Path from string may not have shape
if (shape) {
if (isObject$1(key)) {
for (var name in key) {
if (key.hasOwnProperty(name)) {
shape[name] = key[name];
}
}
}
else {
shape[key] = value;
}
this.dirty(true);
}
return this;
},
getLineScale: function () {
var m = this.transform;
// Get the line scale.
// Determinant of `m` means how much the area is enlarged by the
// transformation. So its square root can be used as a scale factor
// for width.
return m && abs(m[0] - 1) > 1e-10 && abs(m[3] - 1) > 1e-10
? Math.sqrt(abs(m[0] * m[3] - m[2] * m[1]))
: 1;
}
};
/**
* 扩展一个 Path element, 比如星形,圆等。
* Extend a path element
* @param {Object} props
* @param {string} props.type Path type
* @param {Function} props.init Initialize
* @param {Function} props.buildPath Overwrite buildPath method
* @param {Object} [props.style] Extended default style config
* @param {Object} [props.shape] Extended default shape config
*/
Path.extend = function (defaults$$1) {
var Sub = function (opts) {
Path.call(this, opts);
if (defaults$$1.style) {
// Extend default style
this.style.extendFrom(defaults$$1.style, false);
}
inherits(Sub, Path);
return Sub;
};
inherits(Path, Displayable);
var M = CMD$2.M;
var C = CMD$2.C;
var L = CMD$2.L;
var R = CMD$2.R;
var A = CMD$2.A;
var Q = CMD$2.Q;
switch (cmd) {
case M:
nPoint = 1;
break;
case L:
nPoint = 1;
break;
case C:
nPoint = 3;
break;
case Q:
nPoint = 2;
break;
case A:
var x = m[4];
var y = m[5];
var sx = mathSqrt$3(m[0] * m[0] + m[1] * m[1]);
var sy = mathSqrt$3(m[2] * m[2] + m[3] * m[3]);
var angle = mathAtan2(-m[1] / sy, m[0] / sx);
// cx
data[i] *= sx;
data[i++] += x;
// cy
data[i] *= sy;
data[i++] += y;
// Scale rx and ry
// FIXME Assume psi is 0 here
data[i++] *= sx;
data[i++] *= sy;
// Start angle
data[i++] += angle;
// end angle
data[i++] += angle;
// FIXME psi
i += 2;
j = i;
break;
case R:
// x0, y0
p[0] = data[i++];
p[1] = data[i++];
applyTransform(p, p, m);
data[j++] = p[0];
data[j++] = p[1];
// x1, y1
p[0] += data[i++];
p[1] += data[i++];
applyTransform(p, p, m);
data[j++] = p[0];
data[j++] = p[1];
}
applyTransform(p, p, m);
// Write back
data[j++] = p[0];
data[j++] = p[1];
}
}
};
// command chars
var cc = [
'm', 'M', 'l', 'L', 'v', 'V', 'h', 'H', 'z', 'Z',
'c', 'C', 'q', 'Q', 't', 'T', 's', 'S', 'a', 'A'
];
function processArc(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg, cmd, path) {
var psi = psiDeg * (PI / 180.0);
var xp = mathCos(psi) * (x1 - x2) / 2.0
+ mathSin(psi) * (y1 - y2) / 2.0;
var yp = -1 * mathSin(psi) * (x1 - x2) / 2.0
+ mathCos(psi) * (y1 - y2) / 2.0;
var lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry);
if (lambda > 1) {
rx *= mathSqrt(lambda);
ry *= mathSqrt(lambda);
}
function createPathProxyFromString(data) {
if (!data) {
return [];
}
// command string
var cs = data.replace(/-/g, ' -')
.replace(/ /g, ' ')
.replace(/ /g, ',')
.replace(/,,/g, ',');
var n;
// create pipes so that we can split the data
for (n = 0; n < cc.length; n++) {
cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]);
}
// create array
var arr = cs.split('|');
// init context point
var cpx = 0;
var cpy = 0;
var prevCmd;
for (n = 1; n < arr.length; n++) {
var str = arr[n];
var c = str.charAt(0);
var off = 0;
var p = str.slice(1).replace(/e,-/g, 'e-').split(',');
var cmd;
var rx;
var ry;
var psi;
var fa;
var fs;
var x1 = cpx;
var y1 = cpy;
// convert l, H, h, V, and v to L
switch (c) {
case 'l':
cpx += p[off++];
cpy += p[off++];
cmd = CMD.L;
path.addData(cmd, cpx, cpy);
break;
case 'L':
cpx = p[off++];
cpy = p[off++];
cmd = CMD.L;
path.addData(cmd, cpx, cpy);
break;
case 'm':
cpx += p[off++];
cpy += p[off++];
cmd = CMD.M;
path.addData(cmd, cpx, cpy);
c = 'l';
break;
case 'M':
cpx = p[off++];
cpy = p[off++];
cmd = CMD.M;
path.addData(cmd, cpx, cpy);
c = 'L';
break;
case 'h':
cpx += p[off++];
cmd = CMD.L;
path.addData(cmd, cpx, cpy);
break;
case 'H':
cpx = p[off++];
cmd = CMD.L;
path.addData(cmd, cpx, cpy);
break;
case 'v':
cpy += p[off++];
cmd = CMD.L;
path.addData(cmd, cpx, cpy);
break;
case 'V':
cpy = p[off++];
cmd = CMD.L;
path.addData(cmd, cpx, cpy);
break;
case 'C':
cmd = CMD.C;
path.addData(
cmd, p[off++], p[off++], p[off++], p[off++], p[off++],
p[off++]
);
cpx = p[off - 2];
cpy = p[off - 1];
break;
case 'c':
cmd = CMD.C;
path.addData(
cmd,
p[off++] + cpx, p[off++] + cpy,
p[off++] + cpx, p[off++] + cpy,
p[off++] + cpx, p[off++] + cpy
);
cpx += p[off - 2];
cpy += p[off - 1];
break;
case 'S':
ctlPtx = cpx;
ctlPty = cpy;
var len = path.len();
var pathData = path.data;
if (prevCmd === CMD.C) {
ctlPtx += cpx - pathData[len - 4];
ctlPty += cpy - pathData[len - 3];
}
cmd = CMD.C;
x1 = p[off++];
y1 = p[off++];
cpx = p[off++];
cpy = p[off++];
path.addData(cmd, ctlPtx, ctlPty, x1, y1, cpx, cpy);
break;
case 's':
ctlPtx = cpx;
ctlPty = cpy;
var len = path.len();
var pathData = path.data;
if (prevCmd === CMD.C) {
ctlPtx += cpx - pathData[len - 4];
ctlPty += cpy - pathData[len - 3];
}
cmd = CMD.C;
x1 = cpx + p[off++];
y1 = cpy + p[off++];
cpx += p[off++];
cpy += p[off++];
path.addData(cmd, ctlPtx, ctlPty, x1, y1, cpx, cpy);
break;
case 'Q':
x1 = p[off++];
y1 = p[off++];
cpx = p[off++];
cpy = p[off++];
cmd = CMD.Q;
path.addData(cmd, x1, y1, cpx, cpy);
break;
case 'q':
x1 = p[off++] + cpx;
y1 = p[off++] + cpy;
cpx += p[off++];
cpy += p[off++];
cmd = CMD.Q;
path.addData(cmd, x1, y1, cpx, cpy);
break;
case 'T':
ctlPtx = cpx;
ctlPty = cpy;
var len = path.len();
var pathData = path.data;
if (prevCmd === CMD.Q) {
ctlPtx += cpx - pathData[len - 4];
ctlPty += cpy - pathData[len - 3];
}
cpx = p[off++];
cpy = p[off++];
cmd = CMD.Q;
path.addData(cmd, ctlPtx, ctlPty, cpx, cpy);
break;
case 't':
ctlPtx = cpx;
ctlPty = cpy;
var len = path.len();
var pathData = path.data;
if (prevCmd === CMD.Q) {
ctlPtx += cpx - pathData[len - 4];
ctlPty += cpy - pathData[len - 3];
}
cpx += p[off++];
cpy += p[off++];
cmd = CMD.Q;
path.addData(cmd, ctlPtx, ctlPty, cpx, cpy);
break;
case 'A':
rx = p[off++];
ry = p[off++];
psi = p[off++];
fa = p[off++];
fs = p[off++];
x1 = cpx, y1 = cpy;
cpx = p[off++];
cpy = p[off++];
cmd = CMD.A;
processArc(
x1, y1, cpx, cpy, fa, fs, rx, ry, psi, cmd, path
);
break;
case 'a':
rx = p[off++];
ry = p[off++];
psi = p[off++];
fa = p[off++];
fs = p[off++];
x1 = cpx, y1 = cpy;
cpx += p[off++];
cpy += p[off++];
cmd = CMD.A;
processArc(
x1, y1, cpx, cpy, fa, fs, rx, ry, psi, cmd, path
);
break;
}
}
prevCmd = cmd;
}
path.toStatic();
return path;
}
return opts;
}
/**
* Create a Path object from path string data
* http://www.w3.org/TR/SVG/paths.html#PathData
* @param {Object} opts Other options
*/
function createFromString(str, opts) {
return new Path(createPathOptions(str, opts));
}
/**
* Create a Path class from path string data
* @param {string} str
* @param {Object} opts Other options
*/
function extendFromString(str, opts) {
return Path.extend(createPathOptions(str, opts));
}
/**
* Merge multiple paths
*/
// TODO Apply transform
// TODO stroke dash
// TODO Optimize double memory cost problem
function mergePath$1(pathEls, opts) {
var pathList = [];
var len = pathEls.length;
for (var i = 0; i < len; i++) {
var pathEl = pathEls[i];
if (!pathEl.path) {
pathEl.createPathProxy();
}
if (pathEl.__dirtyPath) {
pathEl.buildPath(pathEl.path, pathEl.shape, true);
}
pathList.push(pathEl.path);
}
var pathBundle = new Path(opts);
// Need path proxy.
pathBundle.createPathProxy();
pathBundle.buildPath = function (path) {
path.appendPath(pathList);
// Svg and vml renderer don't have context
var ctx = path.getContext();
if (ctx) {
path.rebuildPath(ctx);
}
};
return pathBundle;
}
/**
* @alias zrender/graphic/Text
* @extends module:zrender/graphic/Displayable
* @constructor
* @param {Object} opts
*/
var Text = function (opts) { // jshint ignore:line
Displayable.call(this, opts);
};
Text.prototype = {
constructor: Text,
type: 'text',
if (!needDrawText(text, style)) {
return;
}
this.setTransform(ctx);
this.restoreTransform(ctx);
},
getBoundingRect: function () {
var style = this.style;
if (!this._rect) {
var text = style.text;
text != null ? (text += '') : (text = '');
rect.x += style.x || 0;
rect.y += style.y || 0;
if (getStroke(style.textStroke, style.textStrokeWidth)) {
var w = style.textStrokeWidth;
rect.x -= w / 2;
rect.y -= w / 2;
rect.width += w;
rect.height += w;
}
this._rect = rect;
}
return this._rect;
}
};
inherits(Text, Displayable);
/**
* 圆形
* @module zrender/shape/Circle
*/
type: 'circle',
shape: {
cx: 0,
cy: 0,
r: 0
},
var shadowTemp = [
['shadowBlur', 0],
['shadowColor', '#000'],
['shadowOffsetX', 0],
['shadowOffsetY', 0]
];
? function () {
var clipPaths = this.__clipPaths;
var style = this.style;
var modified;
if (clipPaths) {
for (var i = 0; i < clipPaths.length; i++) {
var clipPath = clipPaths[i];
var shape = clipPath && clipPath.shape;
var type = clipPath && clipPath.type;
if (shape && (
(type === 'sector' && shape.startAngle === shape.endAngle)
|| (type === 'rect' && (!shape.width || !shape.height))
)) {
for (var j = 0; j < shadowTemp.length; j++) {
// It is save to put shadowTemp static, because
shadowTemp
// will be all modified each item brush called.
shadowTemp[j][2] = style[shadowTemp[j][0]];
style[shadowTemp[j][0]] = shadowTemp[j][1];
}
modified = true;
break;
}
}
}
orignalBrush.apply(this, arguments);
if (modified) {
for (var j = 0; j < shadowTemp.length; j++) {
style[shadowTemp[j][0]] = shadowTemp[j][2];
}
}
}
: orignalBrush;
};
/**
* 扇形
* @module zrender/graphic/shape/Sector
*/
type: 'sector',
shape: {
cx: 0,
cy: 0,
r0: 0,
r: 0,
startAngle: 0,
endAngle: Math.PI * 2,
clockwise: true
},
brush: fixClipWithShadow(Path.prototype.brush),
var x = shape.cx;
var y = shape.cy;
var r0 = Math.max(shape.r0 || 0, 0);
var r = Math.max(shape.r, 0);
var startAngle = shape.startAngle;
var endAngle = shape.endAngle;
var clockwise = shape.clockwise;
var unitX = Math.cos(startAngle);
var unitY = Math.sin(startAngle);
ctx.lineTo(
Math.cos(endAngle) * r0 + x,
Math.sin(endAngle) * r0 + y
);
if (r0 !== 0) {
ctx.arc(x, y, r0, endAngle, startAngle, clockwise);
}
ctx.closePath();
}
});
/**
* 圆环
* @module zrender/graphic/shape/Ring
*/
type: 'ring',
shape: {
cx: 0,
cy: 0,
r: 0,
r0: 0
},
/**
* Catmull-Rom spline 插值折线
* @module zrender/shape/util/smoothSpline
* @author pissang (https://www.github.com/pissang)
* Kener (@Kener-林峰, [email protected])
* errorrik ([email protected])
*/
/**
* @inner
*/
function interpolate(p0, p1, p2, p3, t, t2, t3) {
var v0 = (p2 - p0) * 0.5;
var v1 = (p3 - p1) * 0.5;
return (2 * (p1 - p2) + v0 + v1) * t3
+ (-3 * (p1 - p2) - 2 * v0 - v1) * t2
+ v0 * t + p1;
}
/**
* @alias module:zrender/shape/util/smoothSpline
* @param {Array} points 线段顶点数组
* @param {boolean} isLoop
* @return {Array}
*/
var smoothSpline = function (points, isLoop) {
var len$$1 = points.length;
var ret = [];
var distance$$1 = 0;
for (var i = 1; i < len$$1; i++) {
distance$$1 += distance(points[i - 1], points[i]);
}
var p0;
var p1 = points[idx % len$$1];
var p2;
var p3;
if (!isLoop) {
p0 = points[idx === 0 ? idx : idx - 1];
p2 = points[idx > len$$1 - 2 ? len$$1 - 1 : idx + 1];
p3 = points[idx > len$$1 - 3 ? len$$1 - 1 : idx + 2];
}
else {
p0 = points[(idx - 1 + len$$1) % len$$1];
p2 = points[(idx + 1) % len$$1];
p3 = points[(idx + 2) % len$$1];
}
var w2 = w * w;
var w3 = w * w2;
ret.push([
interpolate(p0[0], p1[0], p2[0], p3[0], w, w2, w3),
interpolate(p0[1], p1[1], p2[1], p3[1], w, w2, w3)
]);
}
return ret;
};
/**
* 贝塞尔平滑曲线
* @module zrender/shape/util/smoothBezier
* @author pissang (https://www.github.com/pissang)
* Kener (@Kener-林峰, [email protected])
* errorrik ([email protected])
*/
/**
* 贝塞尔平滑曲线
* @alias module:zrender/shape/util/smoothBezier
* @param {Array} points 线段顶点数组
* @param {number} smooth 平滑等级, 0-1
* @param {boolean} isLoop
* @param {Array} constraint 将计算出来的控制点约束在一个包围盒内
* 比如 [[0, 0], [100, 100]], 这个包围盒会与
* 整个折线的包围盒做一个并集用来约束控制点。
* @param {Array} 计算出来的控制点数组
*/
var smoothBezier = function (points, smooth, isLoop, constraint) {
var cps = [];
var v = [];
var v1 = [];
var v2 = [];
var prevPoint;
var nextPoint;
if (isLoop) {
prevPoint = points[i ? i - 1 : len$$1 - 1];
nextPoint = points[(i + 1) % len$$1];
}
else {
if (i === 0 || i === len$$1 - 1) {
cps.push(clone$1(points[i]));
continue;
}
else {
prevPoint = points[i - 1];
nextPoint = points[i + 1];
}
}
scale(v1, v, -d0);
scale(v2, v, d1);
var cp0 = add([], point, v1);
var cp1 = add([], point, v2);
if (constraint) {
max(cp0, cp0, min$$1);
min(cp0, cp0, max$$1);
max(cp1, cp1, min$$1);
min(cp1, cp1, max$$1);
}
cps.push(cp0);
cps.push(cp1);
}
if (isLoop) {
cps.push(cps.shift());
}
return cps;
};
ctx.moveTo(points[0][0], points[0][1]);
var len = points.length;
for (var i = 0; i < (closePath ? len : len - 1); i++) {
var cp1 = controlPoints[i * 2];
var cp2 = controlPoints[i * 2 + 1];
var p = points[(i + 1) % len];
ctx.bezierCurveTo(
cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1]
);
}
}
else {
if (smooth === 'spline') {
points = smoothSpline(points, closePath);
}
ctx.moveTo(points[0][0], points[0][1]);
for (var i = 1, l = points.length; i < l; i++) {
ctx.lineTo(points[i][0], points[i][1]);
}
}
/**
* 多边形
* @module zrender/shape/Polygon
*/
type: 'polygon',
shape: {
points: null,
smooth: false,
smoothConstraint: null
},
/**
* @module zrender/graphic/shape/Polyline
*/
type: 'polyline',
shape: {
points: null,
smooth: false,
smoothConstraint: null
},
style: {
stroke: '#000',
fill: null
},
/**
* 矩形
* @module zrender/graphic/shape/Rect
*/
type: 'rect',
shape: {
// 左上、右上、右下、左下角的半径依次为 r1、r2、r3、r4
// r 缩写为 1 相当于 [1, 1, 1, 1]
// r 缩写为[1] 相当于 [1, 1, 1, 1]
// r 缩写为[1, 2] 相当于 [1, 2, 1, 2]
// r 缩写为[1, 2, 3] 相当于 [1, 2, 3, 2]
r: 0,
x: 0,
y: 0,
width: 0,
height: 0
},
/**
* 直线
* @module zrender/graphic/shape/Line
*/
type: 'line',
shape: {
// Start point
x1: 0,
y1: 0,
// End point
x2: 0,
y2: 0,
percent: 1
},
style: {
stroke: '#000',
fill: null
},
if (percent === 0) {
return;
}
ctx.moveTo(x1, y1);
if (percent < 1) {
x2 = x1 * (1 - percent) + x2 * percent;
y2 = y1 * (1 - percent) + y2 * percent;
}
ctx.lineTo(x2, y2);
},
/**
* Get point at percent
* @param {number} percent
* @return {Array.<number>}
*/
pointAt: function (p) {
var shape = this.shape;
return [
shape.x1 * (1 - p) + shape.x2 * p,
shape.y1 * (1 - p) + shape.y2 * p
];
}
});
/**
* 贝塞尔曲线
* @module zrender/shape/BezierCurve
*/
type: 'bezier-curve',
shape: {
x1: 0,
y1: 0,
x2: 0,
y2: 0,
cpx1: 0,
cpy1: 0,
// cpx2: 0,
// cpy2: 0
style: {
stroke: '#000',
fill: null
},
ctx.moveTo(x1, y1);
/**
* Get point at percent
* @param {number} t
* @return {Array.<number>}
*/
pointAt: function (t) {
return someVectorAt(this.shape, t, false);
},
/**
* Get tangent at percent
* @param {number} t
* @return {Array.<number>}
*/
tangentAt: function (t) {
var p = someVectorAt(this.shape, t, true);
return normalize(p, p);
}
});
/**
* 圆弧
* @module zrender/graphic/shape/Arc
*/
type: 'arc',
shape: {
cx: 0,
cy: 0,
r: 0,
startAngle: 0,
endAngle: Math.PI * 2,
clockwise: true
},
style: {
stroke: '#000',
fill: null
},
var x = shape.cx;
var y = shape.cy;
var r = Math.max(shape.r, 0);
var startAngle = shape.startAngle;
var endAngle = shape.endAngle;
var clockwise = shape.clockwise;
type: 'compound',
shape: {
paths: null
},
_updatePathDirty: function () {
var dirtyPath = this.__dirtyPath;
var paths = this.shape.paths;
for (var i = 0; i < paths.length; i++) {
// Mark as dirty if any subpath is dirty
dirtyPath = dirtyPath || paths[i].__dirtyPath;
}
this.__dirtyPath = dirtyPath;
this.__dirty = this.__dirty || dirtyPath;
},
beforeBrush: function () {
this._updatePathDirty();
var paths = this.shape.paths || [];
var scale = this.getGlobalScale();
// Update path scale
for (var i = 0; i < paths.length; i++) {
if (!paths[i].path) {
paths[i].createPathProxy();
}
paths[i].path.setScale(scale[0], scale[1]);
}
},
afterBrush: function () {
var paths = this.shape.paths || [];
for (var i = 0; i < paths.length; i++) {
paths[i].__dirtyPath = false;
}
},
getBoundingRect: function () {
this._updatePathDirty();
return Path.prototype.getBoundingRect.call(this);
}
});
/**
* @param {Array.<Object>} colorStops
*/
var Gradient = function (colorStops) {
};
Gradient.prototype = {
constructor: Gradient,
offset: offset,
color: color
});
}
};
/**
* x, y, x2, y2 are all percent from 0 to 1
* @param {number} [x=0]
* @param {number} [y=0]
* @param {number} [x2=1]
* @param {number} [y2=0]
* @param {Array.<Object>} colorStops
* @param {boolean} [globalCoord=false]
*/
var LinearGradient = function (x, y, x2, y2, colorStops, globalCoord) {
// Should do nothing more in this constructor. Because gradient can be
// declard by `color: {type: 'linear', colorStops: ...}`, where
// this constructor will not be called.
this.x = x == null ? 0 : x;
this.y = y == null ? 0 : y;
// Can be cloned
this.type = 'linear';
Gradient.call(this, colorStops);
};
LinearGradient.prototype = {
constructor: LinearGradient
};
inherits(LinearGradient, Gradient);
/**
* x, y, r are all percent from 0 to 1
* @param {number} [x=0.5]
* @param {number} [y=0.5]
* @param {number} [r=0.5]
* @param {Array.<Object>} [colorStops]
* @param {boolean} [globalCoord=false]
*/
var RadialGradient = function (x, y, r, colorStops, globalCoord) {
// Should do nothing more in this constructor. Because gradient can be
// declard by `color: {type: 'radial', colorStops: ...}`, where
// this constructor will not be called.
// Can be cloned
this.type = 'radial';
// If use global coord
this.global = globalCoord || false;
Gradient.call(this, colorStops);
};
RadialGradient.prototype = {
constructor: RadialGradient
};
inherits(RadialGradient, Gradient);
/**
* Displayable for incremental rendering. It will be rendered in a separate layer
* IncrementalDisplay have too main methods. `clearDisplayables` and
`addDisplayables`
* addDisplayables will render the added displayables incremetally.
*
* It use a not clearFlag to tell the painter don't clear the layer if it's the
first element.
*/
// TODO Style override ?
function IncrementalDisplayble(opts) {
Displayable.call(this, opts);
this._displayables = [];
this._temporaryDisplayables = [];
this._cursor = 0;
this.notClear = true;
}
IncrementalDisplayble.prototype.incremental = true;
IncrementalDisplayble.prototype.clearDisplaybles = function () {
this._displayables = [];
this._temporaryDisplayables = [];
this._cursor = 0;
this.dirty();
this.notClear = false;
};
IncrementalDisplayble.prototype.update = function () {
this.updateTransform();
for (var i = this._cursor; i < this._displayables.length; i++) {
var displayable = this._displayables[i];
// PENDING
displayable.parent = this;
displayable.update();
displayable.parent = null;
}
for (var i = 0; i < this._temporaryDisplayables.length; i++) {
var displayable = this._temporaryDisplayables[i];
// PENDING
displayable.parent = this;
displayable.update();
displayable.parent = null;
}
};
this._temporaryDisplayables = [];
this.notClear = true;
};
var m = [];
IncrementalDisplayble.prototype.getBoundingRect = function () {
if (!this._rect) {
var rect = new BoundingRect(Infinity, Infinity, -Infinity, -Infinity);
for (var i = 0; i < this._displayables.length; i++) {
var displayable = this._displayables[i];
var childRect = displayable.getBoundingRect().clone();
if (displayable.needLocalTransform()) {
childRect.applyTransform(displayable.getLocalTransform(m));
}
rect.union(childRect);
}
this._rect = rect;
}
return this._rect;
};
if (rect.contain(localPos[0], localPos[1])) {
for (var i = 0; i < this._displayables.length; i++) {
var displayable = this._displayables[i];
if (displayable.contain(x, y)) {
return true;
}
}
}
return false;
};
inherits(IncrementalDisplayble, Displayable);
/**
* Extend shape with parameters
*/
function extendShape(opts) {
return Path.extend(opts);
}
/**
* Extend path
*/
function extendPath(pathData, opts) {
return extendFromString(pathData, opts);
}
/**
* Create a path element from path data string
* @param {string} pathData
* @param {Object} opts
* @param {module:zrender/core/BoundingRect} rect
* @param {string} [layout=cover] 'center' or 'cover'
*/
function makePath(pathData, opts, rect, layout) {
var path = createFromString(pathData, opts);
var boundingRect = path.getBoundingRect();
if (rect) {
if (layout === 'center') {
rect = centerGraphic(rect, boundingRect);
}
resizePath(path, rect);
}
return path;
}
/**
* Create a image element from image url
* @param {string} imageUrl image url
* @param {Object} opts options
* @param {module:zrender/core/BoundingRect} rect constrain rect
* @param {string} [layout=cover] 'center' or 'cover'
*/
function makeImage(imageUrl, rect, layout) {
var path = new ZImage({
style: {
image: imageUrl,
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height
},
onload: function (img) {
if (layout === 'center') {
var boundingRect = {
width: img.width,
height: img.height
};
path.setStyle(centerGraphic(rect, boundingRect));
}
}
});
return path;
}
/**
* Get position of centered element in bounding box.
*
* @param {Object} rect element local bounding box
* @param {Object} boundingRect constraint bounding box
* @return {Object} element position containing x, y, width, and height
*/
function centerGraphic(rect, boundingRect) {
// Set rect to center, keep width / height ratio.
var aspect = boundingRect.width / boundingRect.height;
var width = rect.height * aspect;
var height;
if (width <= rect.width) {
height = rect.height;
}
else {
width = rect.width;
height = width / aspect;
}
var cx = rect.x + rect.width / 2;
var cy = rect.y + rect.height / 2;
return {
x: cx - width / 2,
y: cy - height / 2,
width: width,
height: height
};
}
/**
* Resize a path to fit the rect
* @param {module:zrender/graphic/Path} path
* @param {Object} rect
*/
function resizePath(path, rect) {
if (!path.applyTransform) {
return;
}
var m = pathRect.calculateTransform(rect);
path.applyTransform(m);
}
/**
* Sub pixel optimize line for canvas
*
* @param {Object} param
* @param {Object} [param.shape]
* @param {number} [param.shape.x1]
* @param {number} [param.shape.y1]
* @param {number} [param.shape.x2]
* @param {number} [param.shape.y2]
* @param {Object} [param.style]
* @param {number} [param.style.lineWidth]
* @return {Object} Modified param
*/
function subPixelOptimizeLine(param) {
var shape = param.shape;
var lineWidth = param.style.lineWidth;
/**
* Sub pixel optimize for canvas
*
* @param {number} position Coordinate, such as x, y
* @param {number} lineWidth Should be nonnegative integer.
* @param {boolean=} positiveOrNegative Default false (negative).
* @return {number} Optimized position.
*/
function subPixelOptimize(position, lineWidth, positiveOrNegative) {
// Assure that (position + lineWidth / 2) is near integer edge,
// otherwise line will be fuzzy in canvas.
var doubledPosition = round(position * 2);
return (doubledPosition + round(lineWidth)) % 2 === 0
? doubledPosition / 2
: (doubledPosition + (positiveOrNegative ? 1 : -1)) / 2;
}
function hasFillOrStroke(fillOrStroke) {
return fillOrStroke != null && fillOrStroke != 'none';
}
function liftColor(color) {
return typeof color === 'string' ? lift(color, -0.1) : color;
}
/**
* @private
*/
function cacheElementStl(el) {
if (el.__hoverStlDirty) {
var stroke = el.style.stroke;
var fill = el.style.fill;
el.__normalStl = normalStyle;
el.__hoverStlDirty = false;
}
}
/**
* @private
*/
function doSingleEnterHover(el) {
if (el.__isHover) {
return;
}
cacheElementStl(el);
if (el.useHoverLayer) {
el.__zr && el.__zr.addHover(el, el.__hoverStl);
}
else {
var style = el.style;
var insideRollbackOpt = style.insideRollbackOpt;
el.dirty(false);
el.z2 += 1;
}
el.__isHover = true;
}
/**
* @inner
*/
function doSingleLeaveHover(el) {
if (!el.__isHover) {
return;
}
el.__isHover = false;
}
/**
* @inner
*/
function doEnterHover(el) {
el.type === 'group'
? el.traverse(function (child) {
if (child.type !== 'group') {
doSingleEnterHover(child);
}
})
: doSingleEnterHover(el);
}
function doLeaveHover(el) {
el.type === 'group'
? el.traverse(function (child) {
if (child.type !== 'group') {
doSingleLeaveHover(child);
}
})
: doSingleLeaveHover(el);
}
/**
* @inner
*/
function setElementHoverStl(el, hoverStl) {
// If element has sepcified hoverStyle, then use it instead of given hoverStyle
// Often used when item group has a label element and it's hoverStyle is
different
el.__hoverStl = el.hoverStyle || hoverStl || {};
el.__hoverStlDirty = true;
if (el.__isHover) {
cacheElementStl(el);
}
}
/**
* @inner
*/
function onElementMouseOver(e) {
if (this.__hoverSilentOnTouch && e.zrByTouch) {
return;
}
/**
* @inner
*/
function onElementMouseOut(e) {
if (this.__hoverSilentOnTouch && e.zrByTouch) {
return;
}
/**
* @inner
*/
function leaveEmphasis() {
this.__isEmphasis = false;
doLeaveHover(this);
}
/**
* Set hover style of element.
* This method can be called repeatly without side-effects.
* @param {module:zrender/Element} el
* @param {Object} [hoverStyle]
* @param {Object} [opt]
* @param {boolean} [opt.hoverSilentOnTouch=false]
* In touch device, mouseover event will be trigger on touchstart event
* (see module:zrender/dom/HandlerProxy). By this mechanism, we can
* conviniently use hoverStyle when tap on touch screen without additional
* code for compatibility.
* But if the chart/component has select feature, which usually also use
* hoverStyle, there might be conflict between 'select-highlight' and
* 'hover-highlight' especially when roam is enabled (see geo for example).
* In this case, hoverSilentOnTouch should be used to disable hover-
highlight
* on touch device.
*/
function setHoverStyle(el, hoverStyle, opt) {
el.__hoverSilentOnTouch = opt && opt.hoverSilentOnTouch;
/**
* @param {Object|module:zrender/graphic/Style} normalStyle
* @param {Object} emphasisStyle
* @param {module:echarts/model/Model} normalModel
* @param {module:echarts/model/Model} emphasisModel
* @param {Object} opt Check `opt` of `setTextStyleCommon` to find other props.
* @param {string|Function} [opt.defaultText]
* @param {module:echarts/model/Model} [opt.labelFetcher] Fetch text by
* `opt.labelFetcher.getFormattedLabel(opt.labelDataIndex,
'normal'/'emphasis', null, opt.labelDimIndex)`
* @param {module:echarts/model/Model} [opt.labelDataIndex] Fetch text by
* `opt.textFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis',
null, opt.labelDimIndex)`
* @param {module:echarts/model/Model} [opt.labelDimIndex] Fetch text by
* `opt.textFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis',
null, opt.labelDimIndex)`
* @param {Object} [normalSpecified]
* @param {Object} [emphasisSpecified]
*/
function setLabelStyle(
normalStyle, emphasisStyle,
normalModel, emphasisModel,
opt,
normalSpecified, emphasisSpecified
) {
opt = opt || EMPTY_OBJ;
var labelFetcher = opt.labelFetcher;
var labelDataIndex = opt.labelDataIndex;
var labelDimIndex = opt.labelDimIndex;
normalStyle.text = normalStyleText;
emphasisStyle.text = emphasisStyleText;
}
/**
* Set basic textStyle properties.
* @param {Object|module:zrender/graphic/Style} textStyle
* @param {module:echarts/model/Model} model
* @param {Object} [specifiedTextStyle] Can be overrided by settings in model.
* @param {Object} [opt] See `opt` of `setTextStyleCommon`.
* @param {boolean} [isEmphasis]
*/
function setTextStyle(
textStyle, textStyleModel, specifiedTextStyle, opt, isEmphasis
) {
setTextStyleCommon(textStyle, textStyleModel, opt, isEmphasis);
specifiedTextStyle && extend(textStyle, specifiedTextStyle);
textStyle.host && textStyle.host.dirty && textStyle.host.dirty(false);
return textStyle;
}
/**
* Set text option in the style.
* @deprecated
* @param {Object} textStyle
* @param {module:echarts/model/Model} labelModel
* @param {string|boolean} defaultColor Default text color.
* If set as false, it will be processed as a emphasis style.
*/
function setText(textStyle, labelModel, defaultColor) {
var opt = {isRectText: true};
var isEmphasis;
if (opt.isRectText) {
var textPosition = textStyleModel.getShallow('position')
|| (isEmphasis ? null : 'inside');
// 'outside' is not a valid zr textPostion value, but used
// in bar series, and magric type should be considered.
textPosition === 'outside' && (textPosition = 'top');
textStyle.textPosition = textPosition;
textStyle.textOffset = textStyleModel.getShallow('offset');
var labelRotate = textStyleModel.getShallow('rotate');
labelRotate != null && (labelRotate *= Math.PI / 180);
textStyle.textRotation = labelRotate;
textStyle.textDistance = retrieve2(
textStyleModel.getShallow('distance'), isEmphasis ? null : 5
);
}
// Consider case:
// {
// data: [{
// value: 12,
// label: {
// rich: {
// // no 'a' here but using parent 'a'.
// }
// }
// }],
// rich: {
// a: { ... }
// }
// }
var richItemNames = getRichItemNames(textStyleModel);
var richResult;
if (richItemNames) {
richResult = {};
for (var name in richItemNames) {
if (richItemNames.hasOwnProperty(name)) {
// Cascade is supported in rich.
var richTextStyle = textStyleModel.getModel(['rich', name]);
// In rich, never `disableBox`.
setTokenTextStyle(richResult[name] = {}, richTextStyle,
globalTextStyle, opt, isEmphasis);
}
}
}
textStyle.rich = richResult;
return textStyle;
}
// Consider case:
// {
// data: [{
// value: 12,
// label: {
// rich: {
// // no 'a' here but using parent 'a'.
// }
// }
// }],
// rich: {
// a: { ... }
// }
// }
function getRichItemNames(textStyleModel) {
// Use object to remove duplicated names.
var richItemNameMap;
while (textStyleModel && textStyleModel !== textStyleModel.ecModel) {
var rich = (textStyleModel.option || EMPTY_OBJ).rich;
if (rich) {
richItemNameMap = richItemNameMap || {};
for (var name in rich) {
if (rich.hasOwnProperty(name)) {
richItemNameMap[name] = 1;
}
}
}
textStyleModel = textStyleModel.parentModel;
}
return richItemNameMap;
}
if (!isEmphasis) {
if (isBlock) {
// Always set `insideRollback`, for clearing previous.
var originalTextPosition = textStyle.textPosition;
textStyle.insideRollback = applyInsideStyle(textStyle,
originalTextPosition, opt);
// Save original textPosition, because style.textPosition will be
repalced by
// real location (like [10, 30]) in zrender.
textStyle.insideOriginalTextPosition = originalTextPosition;
textStyle.insideRollbackOpt = opt;
}
textStyle.textAlign = textStyleModel.getShallow('align');
textStyle.textVerticalAlign = textStyleModel.getShallow('verticalAlign')
|| textStyleModel.getShallow('baseline');
textStyle.textLineHeight = textStyleModel.getShallow('lineHeight');
textStyle.textWidth = textStyleModel.getShallow('width');
textStyle.textHeight = textStyleModel.getShallow('height');
textStyle.textTag = textStyleModel.getShallow('tag');
if (!isBlock || !opt.disableBox) {
textStyle.textBackgroundColor =
getAutoColor(textStyleModel.getShallow('backgroundColor'), opt);
textStyle.textPadding = textStyleModel.getShallow('padding');
textStyle.textBorderColor =
getAutoColor(textStyleModel.getShallow('borderColor'), opt);
textStyle.textBorderWidth = textStyleModel.getShallow('borderWidth');
textStyle.textBorderRadius = textStyleModel.getShallow('borderRadius');
textStyle.textBoxShadowColor = textStyleModel.getShallow('shadowColor');
textStyle.textBoxShadowBlur = textStyleModel.getShallow('shadowBlur');
textStyle.textBoxShadowOffsetX =
textStyleModel.getShallow('shadowOffsetX');
textStyle.textBoxShadowOffsetY =
textStyleModel.getShallow('shadowOffsetY');
}
textStyle.textShadowColor = textStyleModel.getShallow('textShadowColor')
|| globalTextStyle.textShadowColor;
textStyle.textShadowBlur = textStyleModel.getShallow('textShadowBlur')
|| globalTextStyle.textShadowBlur;
textStyle.textShadowOffsetX = textStyleModel.getShallow('textShadowOffsetX')
|| globalTextStyle.textShadowOffsetX;
textStyle.textShadowOffsetY = textStyleModel.getShallow('textShadowOffsetY')
|| globalTextStyle.textShadowOffsetY;
}
if (textStyle.textFill == null
&& useInsideStyle !== false
&& (useInsideStyle === true
|| (opt.isRectText
&& textPosition
// textPosition can be [10, 30]
&& typeof textPosition === 'string'
&& textPosition.indexOf('inside') >= 0
)
)
) {
insideRollback = {
textFill: null,
textStroke: textStyle.textStroke,
textStrokeWidth: textStyle.textStrokeWidth
};
textStyle.textFill = '#fff';
// Consider text with #fff overflow its container.
if (textStyle.textStroke == null) {
textStyle.textStroke = opt.autoColor;
textStyle.textStrokeWidth == null && (textStyle.textStrokeWidth = 2);
}
}
return insideRollback;
}
function rollbackInsideStyle(style) {
var insideRollback = style.insideRollback;
if (insideRollback) {
style.textFill = insideRollback.textFill;
style.textStroke = insideRollback.textStroke;
style.textStrokeWidth = insideRollback.textStrokeWidth;
}
}
if (animationEnabled) {
var postfix = isUpdate ? 'Update' : '';
var duration = animatableModel.getShallow('animationDuration' + postfix);
var animationEasing = animatableModel.getShallow('animationEasing' +
postfix);
var animationDelay = animatableModel.getShallow('animationDelay' +
postfix);
if (typeof animationDelay === 'function') {
animationDelay = animationDelay(
dataIndex,
animatableModel.getAnimationDelayParams
? animatableModel.getAnimationDelayParams(el, dataIndex)
: null
);
}
if (typeof duration === 'function') {
duration = duration(dataIndex);
}
duration > 0
? el.animateTo(props, duration, animationDelay || 0, animationEasing,
cb, !!cb)
: (el.stopAnimation(), el.attr(props), cb && cb());
}
else {
el.stopAnimation();
el.attr(props);
cb && cb();
}
}
/**
* Update graphic element properties with or without animation according to the
* configuration in series.
*
* Caution: this method will stop previous animation.
* So if do not use this method to one element twice before
* animation starts, unless you know what you are doing.
*
* @param {module:zrender/Element} el
* @param {Object} props
* @param {module:echarts/model/Model} [animatableModel]
* @param {number} [dataIndex]
* @param {Function} [cb]
* @example
* graphic.updateProps(el, {
* position: [100, 100]
* }, seriesModel, dataIndex, function () { console.log('Animation done!'); });
* // Or
* graphic.updateProps(el, {
* position: [100, 100]
* }, seriesModel, function () { console.log('Animation done!'); });
*/
function updateProps(el, props, animatableModel, dataIndex, cb) {
animateOrSetProps(true, el, props, animatableModel, dataIndex, cb);
}
/**
* Init graphic element properties with or without animation according to the
* configuration in series.
*
* Caution: this method will stop previous animation.
* So if do not use this method to one element twice before
* animation starts, unless you know what you are doing.
*
* @param {module:zrender/Element} el
* @param {Object} props
* @param {module:echarts/model/Model} [animatableModel]
* @param {number} [dataIndex]
* @param {Function} cb
*/
function initProps(el, props, animatableModel, dataIndex, cb) {
animateOrSetProps(false, el, props, animatableModel, dataIndex, cb);
}
/**
* Get transform matrix of target (param target),
* in coordinate of its ancestor (param ancestor)
*
* @param {module:zrender/mixin/Transformable} target
* @param {module:zrender/mixin/Transformable} [ancestor]
*/
function getTransform(target, ancestor) {
var mat = identity([]);
return mat;
}
/**
* Apply transform to an vertex.
* @param {Array.<number>} target [x, y]
* @param {Array.<number>|TypedArray.<number>|Object} transform Can be:
* + Transform matrix: like [1, 0, 0, 1, 0, 0]
* + {position, rotation, scale}, the same as `zrender/Transformable`.
* @param {boolean=} invert Whether use invert matrix.
* @return {Array.<number>} [x, y]
*/
function applyTransform$1(target, transform, invert$$1) {
if (transform && !isArrayLike(transform)) {
transform = Transformable.getLocalTransform(transform);
}
if (invert$$1) {
transform = invert([], transform);
}
return applyTransform([], target, transform);
}
/**
* @param {string} direction 'left' 'right' 'top' 'bottom'
* @param {Array.<number>} transform Transform matrix: like [1, 0, 0, 1, 0, 0]
* @param {boolean=} invert Whether use invert matrix.
* @return {string} Transformed direction. 'left' 'right' 'top' 'bottom'
*/
function transformDirection(direction, transform, invert$$1) {
// Pick a base, ensure that transform result will not be (0, 0).
var hBase = (transform[4] === 0 || transform[5] === 0 || transform[0] === 0)
? 1 : Math.abs(2 * transform[4] / transform[0]);
var vBase = (transform[4] === 0 || transform[5] === 0 || transform[2] === 0)
? 1 : Math.abs(2 * transform[4] / transform[2]);
var vertex = [
direction === 'left' ? -hBase : direction === 'right' ? hBase : 0,
direction === 'top' ? -vBase : direction === 'bottom' ? vBase : 0
];
/**
* Apply group transition animation from g1 to g2.
* If no animatableModel, no animation.
*/
function groupTransition(g1, g2, animatableModel, cb) {
if (!g1 || !g2) {
return;
}
function getElMap(g) {
var elMap = {};
g.traverse(function (el) {
if (!el.isGroup && el.anid) {
elMap[el.anid] = el;
}
});
return elMap;
}
function getAnimatableProps(el) {
var obj = {
position: clone$1(el.position),
rotation: el.rotation
};
if (el.shape) {
obj.shape = extend({}, el.shape);
}
return obj;
}
var elMap1 = getElMap(g1);
g2.traverse(function (el) {
if (!el.isGroup && el.anid) {
var oldEl = elMap1[el.anid];
if (oldEl) {
var newProp = getAnimatableProps(el);
el.attr(getAnimatableProps(oldEl));
updateProps(el, newProp, animatableModel, el.dataIndex);
}
// else {
// if (el.previousProps) {
// graphic.updateProps
// }
// }
}
});
}
/**
* @param {Array.<Array.<number>>} points Like: [[23, 44], [53, 66], ...]
* @param {Object} rect {x, y, width, height}
* @return {Array.<Array.<number>>} A new clipped points.
*/
function clipPointsByRect(points, rect) {
return map(points, function (point) {
var x = point[0];
x = mathMax$1(x, rect.x);
x = mathMin$1(x, rect.x + rect.width);
var y = point[1];
y = mathMax$1(y, rect.y);
y = mathMin$1(y, rect.y + rect.height);
return [x, y];
});
}
/**
* @param {Object} targetRect {x, y, width, height}
* @param {Object} rect {x, y, width, height}
* @return {Object} A new clipped rect. If rect size are negative, return
undefined.
*/
function clipRectByRect(targetRect, rect) {
var x = mathMax$1(targetRect.x, rect.x);
var x2 = mathMin$1(targetRect.x + targetRect.width, rect.x + rect.width);
var y = mathMax$1(targetRect.y, rect.y);
var y2 = mathMin$1(targetRect.y + targetRect.height, rect.y + rect.height);
/**
* @param {string} iconStr Support 'image://' or 'path://' or direct svg path.
* @param {Object} [opt] Properties of `module:zrender/Element`, except `style`.
* @param {Object} [rect] {x, y, width, height}
* @return {module:zrender/Element} Icon path or image element.
*/
function createIcon(iconStr, opt, rect) {
opt = extend({rectHover: true}, opt);
var style = opt.style = {strokeNoScale: true};
rect = rect || {x: -1, y: -1, width: 2, height: 2};
if (iconStr) {
return iconStr.indexOf('image://') === 0
? (
style.image = iconStr.slice(8),
defaults(style, rect),
new ZImage(opt)
)
: (
makePath(
iconStr.replace('path://', ''),
opt,
rect,
'center'
)
);
}
}
var textStyleMixin = {
/**
* Get color property or get color from option.textStyle.color
* @param {boolean} [isEmphasis]
* @return {string}
*/
getTextColor: function (isEmphasis) {
var ecModel = this.ecModel;
return this.getShallow('color')
|| (
(!isEmphasis && ecModel) ? ecModel.get(PATH_COLOR) : null
);
},
/**
* Create font string from fontStyle, fontWeight, fontSize, fontFamily
* @return {string}
*/
getFont: function () {
return getFont({
fontStyle: this.getShallow('fontStyle'),
fontWeight: this.getShallow('fontWeight'),
fontSize: this.getShallow('fontSize'),
fontFamily: this.getShallow('fontFamily')
}, this.ecModel);
},
var itemStyleMixin = {
getItemStyle: function (excludes, includes) {
var style = getItemStyle(this, excludes, includes);
var lineDash = this.getBorderLineDash();
lineDash && (style.lineDash = lineDash);
return style;
},
getBorderLineDash: function () {
var lineType = this.get('borderType');
return (lineType === 'solid' || lineType == null) ? null
: (lineType === 'dashed' ? [5, 5] : [1, 1]);
}
};
/**
* @module echarts/model/Model
*/
/**
* @alias module:echarts/model/Model
* @constructor
* @param {Object} option
* @param {module:echarts/model/Model} [parentModel]
* @param {module:echarts/model/Global} [ecModel]
*/
function Model(option, parentModel, ecModel) {
/**
* @type {module:echarts/model/Model}
* @readOnly
*/
this.parentModel = parentModel;
/**
* @type {module:echarts/model/Global}
* @readOnly
*/
this.ecModel = ecModel;
/**
* @type {Object}
* @protected
*/
this.option = option;
// Simple optimization
// if (this.init) {
// if (arguments.length <= 4) {
// this.init(option, parentModel, ecModel, extraOpt);
// }
// else {
// this.init.apply(this, arguments);
// }
// }
}
Model.prototype = {
constructor: Model,
/**
* Model 的初始化函数
* @param {Object} option
*/
init: null,
/**
* 从新的 Option merge
*/
mergeOption: function (option) {
merge(this.option, option, true);
},
/**
* @param {string|Array.<string>} path
* @param {boolean} [ignoreParent=false]
* @return {*}
*/
get: function (path, ignoreParent) {
if (path == null) {
return this.option;
}
return doGet(
this.option,
this.parsePath(path),
!ignoreParent && getParent(this, path)
);
},
/**
* @param {string} key
* @param {boolean} [ignoreParent=false]
* @return {*}
*/
getShallow: function (key, ignoreParent) {
var option = this.option;
/**
* @param {string|Array.<string>} [path]
* @param {module:echarts/model/Model} [parentModel]
* @return {module:echarts/model/Model}
*/
getModel: function (path, parentModel) {
var obj = path == null
? this.option
: doGet(this.option, path = this.parsePath(path));
var thisParentModel;
parentModel = parentModel || (
(thisParentModel = getParent(this, path))
&& thisParentModel.getModel(path)
);
/**
* If model has option
*/
isEmpty: function () {
return this.option == null;
},
// Pending
clone: function () {
var Ctor = this.constructor;
return new Ctor(clone(this.option));
},
setReadOnly: function (properties) {
// clazzUtil.setReadOnly(this, properties);
},
/**
* @param {Function} getParentMethod
* param {Array.<string>|string} path
* return {module:echarts/model/Model}
*/
customizeGetParent: function (getParentMethod) {
inner(this).getParent = getParentMethod;
},
isAnimationEnabled: function () {
if (!env$1.node) {
if (this.option.animation != null) {
return !!this.option.animation;
}
else if (this.parentModel) {
return this.parentModel.isAnimationEnabled();
}
}
}
};
// Enable Model.extend.
enableClassExtend(Model);
enableClassCheck(Model);
mixin$1(Model, lineStyleMixin);
mixin$1(Model, areaStyleMixin);
mixin$1(Model, textStyleMixin);
mixin$1(Model, itemStyleMixin);
var base = 0;
/**
* @public
* @param {string} type
* @return {string}
*/
function getUID(type) {
// Considering the case of crossing js context,
// use Math.random to make id as unique as possible.
return [(type || ''), base++, Math.random().toFixed(5)].join('_');
}
/**
* @inner
*/
function enableSubTypeDefaulter(entity) {
return entity;
}
/**
* Topological travel on Activity Network (Activity On Vertices).
* Dependencies is defined in Model.prototype.dependencies, like ['xAxis',
'yAxis'].
*
* If 'xAxis' or 'yAxis' is absent in componentTypeList, just ignore it in
topology.
*
* If there is circle dependencey, Error will be thrown.
*
*/
function enableTopologicalTravel(entity, dependencyGetter) {
/**
* @public
* @param {Array.<string>} targetNameList Target Component type list.
* Can be ['aa', 'bb', 'aa.xx']
* @param {Array.<string>} fullNameList By which we can build dependency graph.
* @param {Function} callback Params: componentType, dependencies.
* @param {Object} context Scope of callback.
*/
entity.topologicalTravel = function (targetNameList, fullNameList, callback,
context) {
if (!targetNameList.length) {
return;
}
while (stack.length) {
var currComponentType = stack.pop();
var currVertex = graph[currComponentType];
var isInTargetNameSet = !!targetNameSet[currComponentType];
if (isInTargetNameSet) {
callback.call(context, currComponentType,
currVertex.originalDeps.slice());
delete targetNameSet[currComponentType];
}
each$1(
currVertex.successor,
isInTargetNameSet ? removeEdgeAndAdd : removeEdge
);
}
each$1(targetNameSet, function () {
throw new Error('Circle dependency may exists');
});
function removeEdge(succComponentType) {
graph[succComponentType].entryCount--;
if (graph[succComponentType].entryCount === 0) {
stack.push(succComponentType);
}
}
/**
* DepndencyGraph: {Object}
* key: conponentType,
* value: {
* successor: [conponentTypes...],
* originalDeps: [conponentTypes...],
* entryCount: {number}
* }
*/
function makeDepndencyGraph(fullNameList) {
var graph = {};
var noEntryList = [];
function _trim(str) {
return str.replace(/^\s+/, '').replace(/\s+$/, '');
}
/**
* Linear mapping a value from domain to range
* @memberOf module:echarts/util/number
* @param {(number|Array.<number>)} val
* @param {Array.<number>} domain Domain extent domain[0] can be bigger than
domain[1]
* @param {Array.<number>} range Range extent range[0] can be bigger than
range[1]
* @param {boolean} clamp
* @return {(number|Array.<number>}
*/
function linearMap(val, domain, range, clamp) {
var subDomain = domain[1] - domain[0];
var subRange = range[1] - range[0];
if (subDomain === 0) {
return subRange === 0
? range[0]
: (range[0] + range[1]) / 2;
}
/**
* Convert a percent string to absolute number.
* Returns NaN if percent is not a valid string or number
* @memberOf module:echarts/util/number
* @param {string|number} percent
* @param {number} all
* @return {number}
*/
function parsePercent$1(percent, all) {
switch (percent) {
case 'center':
case 'middle':
percent = '50%';
break;
case 'left':
case 'top':
percent = '0%';
break;
case 'right':
case 'bottom':
percent = '100%';
break;
}
if (typeof percent === 'string') {
if (_trim(percent).match(/%$/)) {
return parseFloat(percent) / 100 * all;
}
return parseFloat(percent);
}
/**
* (1) Fix rounding error of float numbers.
* (2) Support return string to avoid scientific notation like '3.5e-7'.
*
* @param {number} x
* @param {number} [precision]
* @param {boolean} [returnStr]
* @return {number|string}
*/
function round$1(x, precision, returnStr) {
if (precision == null) {
precision = 10;
}
// Avoid range error
precision = Math.min(Math.max(0, precision), 20);
x = (+x).toFixed(precision);
return returnStr ? x : +x;
}
function asc(arr) {
arr.sort(function (a, b) {
return a - b;
});
return arr;
}
/**
* Get precision
* @param {number} val
*/
function getPrecision(val) {
val = +val;
if (isNaN(val)) {
return 0;
}
// It is much faster than methods converting number to string as follows
// var tmp = val.toString();
// return tmp.length - 1 - tmp.indexOf('.');
// especially when precision is low
var e = 1;
var count = 0;
while (Math.round(val * e) / e !== val) {
e *= 10;
count++;
}
return count;
}
/**
* @param {string|number} val
* @return {number}
*/
function getPrecisionSafe(val) {
var str = val.toString();
/**
* Minimal dicernible data precisioin according to a single pixel.
*
* @param {Array.<number>} dataExtent
* @param {Array.<number>} pixelExtent
* @return {number} precision
*/
function getPixelPrecision(dataExtent, pixelExtent) {
var log = Math.log;
var LN10 = Math.LN10;
var dataQuantity = Math.floor(log(dataExtent[1] - dataExtent[0]) / LN10);
var sizeQuantity = Math.round(log(Math.abs(pixelExtent[1] - pixelExtent[0])) /
LN10);
// toFixed() digits argument must be between 0 and 20.
var precision = Math.min(Math.max(-dataQuantity + sizeQuantity, 0), 20);
return !isFinite(precision) ? 20 : precision;
}
/**
* Get a data of given precision, assuring the sum of percentages
* in valueList is 1.
* The largest remainer method is used.
* https://en.wikipedia.org/wiki/Largest_remainder_method
*
* @param {Array.<number>} valueList a list of all data
* @param {number} idx index of the data to be processed in valueList
* @param {number} precision integer number showing digits of precision
* @return {number} percent ranging from 0 to 100
*/
function getPercentWithPrecision(valueList, idx, precision) {
if (!valueList[idx]) {
return 0;
}
/**
* To 0 - 2 * PI, considering negative radian.
* @param {number} radian
* @return {number}
*/
function remRadian(radian) {
var pi2 = Math.PI * 2;
return (radian % pi2 + pi2) % pi2;
}
/**
* @param {type} radian
* @return {boolean}
*/
function isRadianAroundZero(val) {
return val > -RADIAN_EPSILON && val < RADIAN_EPSILON;
}
/**
* @param {string|Date|number} value These values can be accepted:
* + An instance of Date, represent a time in its own time zone.
* + Or string in a subset of ISO 8601, only including:
* + only year, month, date: '2012-03', '2012-03-01', '2012-03-01 05', '2012-
03-01 05:06',
* + separated with T or space: '2012-03-01T12:22:33.123', '2012-03-01
12:22:33.123',
* + time zone: '2012-03-01T12:22:33Z', '2012-03-01T12:22:33+8000', '2012-03-
01T12:22:33-05:00',
* all of which will be treated as local time if time zone is not specified
* (see <https://momentjs.com/>).
* + Or other string format, including (all of which will be treated as loacal
time):
* '2012', '2012-3-1', '2012/3/1', '2012/03/01',
* '2009/6/12 2:00', '2009/6/12 2:05:08', '2009/6/12 2:05:08.123'
* + a timestamp, which represent a time in UTC.
* @return {Date} date
*/
function parseDate(value) {
if (value instanceof Date) {
return value;
}
else if (typeof value === 'string') {
// Different browsers parse date in different way, so we parse it manually.
// Some other issues:
// new Date('1970-01-01') is UTC,
// new Date('1970/01/01') and new Date('1970-1-01') is local.
// See issue #3623
var match = TIME_REG.exec(value);
if (!match) {
// return Invalid Date.
return new Date(NaN);
}
/**
* Quantity of a number. e.g. 0.1, 1, 10, 100
*
* @param {number} val
* @return {number}
*/
function quantity(val) {
return Math.pow(10, quantityExponent(val));
}
function quantityExponent(val) {
return Math.floor(Math.log(val) / Math.LN10);
}
/**
* find a “nice” number approximately equal to x. Round the number if round = true,
* take ceiling if round = false. The primary observation is that the “nicest”
* numbers in decimal are 1, 2, and 5, and all power-of-ten multiples of these
numbers.
*
* See "Nice Numbers for Graph Labels" of Graphic Gems.
*
* @param {number} val Non-negative value.
* @param {boolean} round
* @return {number}
*/
function nice(val, round) {
var exponent = quantityExponent(val);
var exp10 = Math.pow(10, exponent);
var f = val / exp10; // 1 <= f < 10
var nf;
if (round) {
if (f < 1.5) { nf = 1; }
else if (f < 2.5) { nf = 2; }
else if (f < 4) { nf = 3; }
else if (f < 7) { nf = 5; }
else { nf = 10; }
}
else {
if (f < 1) { nf = 1; }
else if (f < 2) { nf = 2; }
else if (f < 3) { nf = 3; }
else if (f < 5) { nf = 5; }
else { nf = 10; }
}
val = nf * exp10;
/**
* Order intervals asc, and split them when overlap.
* expect(numberUtil.reformIntervals([
* {interval: [18, 62], close: [1, 1]},
* {interval: [-Infinity, -70], close: [0, 0]},
* {interval: [-70, -26], close: [1, 1]},
* {interval: [-26, 18], close: [1, 1]},
* {interval: [62, 150], close: [1, 1]},
* {interval: [106, 150], close: [1, 1]},
* {interval: [150, Infinity], close: [0, 0]}
* ])).toEqual([
* {interval: [-Infinity, -70], close: [0, 0]},
* {interval: [-70, -26], close: [1, 1]},
* {interval: [-26, 18], close: [0, 1]},
* {interval: [18, 62], close: [0, 1]},
* {interval: [62, 150], close: [0, 1]},
* {interval: [150, Infinity], close: [0, 0]}
* ]);
* @param {Array.<Object>} list, where `close` mean open or close
* of the interval, and Infinity can be used.
* @return {Array.<Object>} The origin list, which has been reformed.
*/
function reformIntervals(list) {
list.sort(function (a, b) {
return littleThan(a, b, 0) ? -1 : 1;
});
return list;
/**
* parseFloat NaNs numeric-cast false positives (null|true|false|"")
* ...but misinterprets leading-number strings, particularly hex literals ("0x...")
* subtraction forces infinities to NaN
*
* @param {*} v
* @return {boolean}
*/
function isNumeric(v) {
return v - parseFloat(v) >= 0;
}
/**
* 每三位默认加,格式化
* @param {string|number} x
* @return {string}
*/
function addCommas(x) {
if (isNaN(x)) {
return '-';
}
x = (x + '').split('.');
return x[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g,'$1,')
+ (x.length > 1 ? ('.' + x[1]) : '');
}
/**
* @param {string} str
* @param {boolean} [upperCaseFirst=false]
* @return {string} str
*/
function toCamelCase(str, upperCaseFirst) {
str = (str || '').toLowerCase().replace(/-(.)/g, function(match, group1) {
return group1.toUpperCase();
});
return str;
}
function encodeHTML(source) {
return String(source)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
/**
* Template formatter
* @param {string} tpl
* @param {Array.<Object>|Object} paramsList
* @param {boolean} [encode=false]
* @return {string}
*/
function formatTpl(tpl, paramsList, encode) {
if (!isArray(paramsList)) {
paramsList = [paramsList];
}
var seriesLen = paramsList.length;
if (!seriesLen) {
return '';
}
return tpl;
}
/**
* simple Template formatter
*
* @param {string} tpl
* @param {Object} param
* @param {boolean} [encode=false]
* @return {string}
*/
function formatTplSimple(tpl, param, encode) {
each$1(param, function (value, key) {
tpl = tpl.replace(
'{' + key + '}',
encode ? encodeHTML(value) : value
);
});
return tpl;
}
/**
* @param {Object|string} [opt] If string, means color.
* @param {string} [opt.color]
* @param {string} [opt.extraCssText]
* @param {string} [opt.type='item'] 'item' or 'subItem'
* @return {string}
*/
function getTooltipMarker(opt, extraCssText) {
opt = isString(opt) ? {color: opt, extraCssText: extraCssText} : (opt || {});
var color = opt.color;
var type = opt.type;
var extraCssText = opt.extraCssText;
if (!color) {
return '';
}
/**
* ISO Date format
* @param {string} tpl
* @param {number} value
* @param {boolean} [isUTC=false] Default in local time.
* see `module:echarts/scale/Time`
* and `module:echarts/util/number#parseDate`.
* @inner
*/
function formatTime(tpl, value, isUTC) {
if (tpl === 'week'
|| tpl === 'month'
|| tpl === 'quarter'
|| tpl === 'half-year'
|| tpl === 'year'
) {
tpl = 'MM-dd\nyyyy';
}
var date = parseDate(value);
var utc = isUTC ? 'UTC' : '';
var y = date['get' + utc + 'FullYear']();
var M = date['get' + utc + 'Month']() + 1;
var d = date['get' + utc + 'Date']();
var h = date['get' + utc + 'Hours']();
var m = date['get' + utc + 'Minutes']();
var s = date['get' + utc + 'Seconds']();
var S = date['get' + utc + 'Milliseconds']();
return tpl;
}
/**
* Capital first
* @param {string} str
* @return {string}
*/
function capitalFirst(str) {
return str ? str.charAt(0).toUpperCase() + str.substr(1) : str;
}
/**
* @public
*/
var HV_NAMES = [
['width', 'left', 'right'],
['height', 'top', 'bottom']
];
if (maxWidth == null) {
maxWidth = Infinity;
}
if (maxHeight == null) {
maxHeight = Infinity;
}
var currentLineMaxSize = 0;
if (child.newline) {
return;
}
position[0] = x;
position[1] = y;
/**
* VBox or HBox layouting
* @param {string} orient
* @param {module:zrender/container/Group} group
* @param {number} gap
* @param {number} [width=Infinity]
* @param {number} [height=Infinity]
*/
var box = boxLayout;
/**
* VBox layouting
* @param {module:zrender/container/Group} group
* @param {number} gap
* @param {number} [width=Infinity]
* @param {number} [height=Infinity]
*/
var vbox = curry(boxLayout, 'vertical');
/**
* HBox layouting
* @param {module:zrender/container/Group} group
* @param {number} gap
* @param {number} [width=Infinity]
* @param {number} [height=Infinity]
*/
var hbox = curry(boxLayout, 'horizontal');
/**
* If x or x2 is not specified or 'center' 'left' 'right',
* the width would be as long as possible.
* If y or y2 is not specified or 'middle' 'top' 'bottom',
* the height would be as long as possible.
*
* @param {Object} positionInfo
* @param {number|string} [positionInfo.x]
* @param {number|string} [positionInfo.y]
* @param {number|string} [positionInfo.x2]
* @param {number|string} [positionInfo.y2]
* @param {Object} containerRect {width, height}
* @param {string|number} margin
* @return {Object} {width, height}
*/
function getAvailableSize(positionInfo, containerRect, margin) {
var containerWidth = containerRect.width;
var containerHeight = containerRect.height;
return {
width: Math.max(x2 - x - margin[1] - margin[3], 0),
height: Math.max(y2 - y - margin[0] - margin[2], 0)
};
}
/**
* Parse position info.
*
* @param {Object} positionInfo
* @param {number|string} [positionInfo.left]
* @param {number|string} [positionInfo.top]
* @param {number|string} [positionInfo.right]
* @param {number|string} [positionInfo.bottom]
* @param {number|string} [positionInfo.width]
* @param {number|string} [positionInfo.height]
* @param {number|string} [positionInfo.aspect] Aspect is width / height
* @param {Object} containerRect
* @param {string|number} [margin]
*
* @return {module:zrender/core/BoundingRect}
*/
function getLayoutRect(
positionInfo, containerRect, margin
) {
margin = normalizeCssArray$1(margin || 0);
if (aspect != null) {
// If width and height are not given
// 1. Graph should not exceeds the container
// 2. Aspect must be keeped
// 3. Graph should take the space as more as possible
// FIXME
// Margin is not considered, because there is no case that both
// using margin and aspect so far.
if (isNaN(width) && isNaN(height)) {
if (aspect > containerWidth / containerHeight) {
width = containerWidth * 0.8;
}
else {
height = containerHeight * 0.8;
}
}
/**
* Position a zr element in viewport
* Group position is specified by either
* {left, top}, {right, bottom}
* If all properties exists, right and bottom will be igonred.
*
* Logic:
* 1. Scale (against origin point in parent coord)
* 2. Rotate (against origin point in parent coord)
* 3. Traslate (with el.position by this method)
* So this method only fixes the last step 'Traslate', which does not affect
* scaling and rotating.
*
* If be called repeatly with the same input el, the same result will be gotten.
*
* @param {module:zrender/Element} el Should have `getBoundingRect` method.
* @param {Object} positionInfo
* @param {number|string} [positionInfo.left]
* @param {number|string} [positionInfo.top]
* @param {number|string} [positionInfo.right]
* @param {number|string} [positionInfo.bottom]
* @param {number|string} [positionInfo.width] Only for opt.boundingModel: 'raw'
* @param {number|string} [positionInfo.height] Only for opt.boundingModel: 'raw'
* @param {Object} containerRect
* @param {string|number} margin
* @param {Object} [opt]
* @param {Array.<number>} [opt.hv=[1,1]] Only horizontal or only vertical.
* @param {Array.<number>} [opt.boundingMode='all']
* Specify how to calculate boundingRect when locating.
* 'all': Position the boundingRect that is transformed and uioned
* both itself and its descendants.
* This mode simplies confine the elements in the bounding
* of their container (e.g., using 'right: 0').
* 'raw': Position the boundingRect that is not transformed and only itself.
* This mode is useful when you want a element can overflow its
* container. (Consider a rotated circle needs to be located in a
corner.)
* In this mode positionInfo.width/height can only be number.
*/
function positionElement(el, positionInfo, containerRect, margin, opt) {
var h = !opt || !opt.hv || opt.hv[0];
var v = !opt || !opt.hv || opt.hv[1];
var boundingMode = opt && opt.boundingMode || 'all';
var rect;
if (boundingMode === 'raw') {
rect = el.type === 'group'
? new BoundingRect(0, 0, +positionInfo.width || 0, +positionInfo.height
|| 0)
: el.getBoundingRect();
}
else {
rect = el.getBoundingRect();
if (el.needLocalTransform()) {
var transform = el.getLocalTransform();
// Notice: raw rect may be inner object of el,
// which should not be modified.
rect = rect.clone();
rect.applyTransform(transform);
}
}
// The real width and height can not be specified but calculated by the given
el.
positionInfo = getLayoutRect(
defaults(
{width: rect.width, height: rect.height},
positionInfo
),
containerRect,
margin
);
/**
* @param {Object} option Contains some of the properties in HV_NAMES.
* @param {number} hvIdx 0: horizontal; 1: vertical.
*/
function sizeCalculable(option, hvIdx) {
return option[HV_NAMES[hvIdx][0]] != null
|| (option[HV_NAMES[hvIdx][1]] != null && option[HV_NAMES[hvIdx][2]] !=
null);
}
/**
* Consider Case:
* When defulat option has {left: 0, width: 100}, and we set {right: 0}
* through setOption or media query, using normal zrUtil.merge will cause
* {right: 0} does not take effect.
*
* @example
* ComponentModel.extend({
* init: function () {
* ...
* var inputPositionParams = layout.getLayoutParams(option);
* this.mergeOption(inputPositionParams);
* },
* mergeOption: function (newOption) {
* newOption && zrUtil.merge(thisOption, newOption, true);
* layout.mergeLayoutParam(thisOption, newOption);
* }
* });
*
* @param {Object} targetOption
* @param {Object} newOption
* @param {Object|string} [opt]
* @param {boolean|Array.<boolean>} [opt.ignoreSize=false] Used for the components
* that width (or height) should not be calculated by left and right (or top and
bottom).
*/
function mergeLayoutParam(targetOption, newOption, opt) {
!isObject$1(opt) && (opt = {});
/**
* Retrieve 'left', 'right', 'top', 'bottom', 'width', 'height' from object.
* @param {Object} source
* @return {Object} Result contains those props.
*/
function getLayoutParams(source) {
return copyLayoutParams({}, source);
}
/**
* Retrieve 'left', 'right', 'top', 'bottom', 'width', 'height' from object.
* @param {Object} source
* @return {Object} Result contains those props.
*/
function copyLayoutParams(target, source) {
source && target && each$3(LOCATION_PARAMS, function (name) {
source.hasOwnProperty(name) && (target[name] = source[name]);
});
return target;
}
var boxLayoutMixin = {
getBoxLayoutParams: function () {
return {
left: this.get('left'),
top: this.get('top'),
right: this.get('right'),
bottom: this.get('bottom'),
width: this.get('width'),
height: this.get('height')
};
}
};
/**
* Component model
*
* @module echarts/model/Component
*/
/**
* @alias module:echarts/model/Component
* @constructor
* @param {Object} option
* @param {module:echarts/model/Model} parentModel
* @param {module:echarts/model/Model} ecModel
*/
var ComponentModel = Model.extend({
type: 'component',
/**
* @readOnly
* @type {string}
*/
id: '',
/**
* Because simplified concept is probably better, series.name (or
component.name)
* has been having too many resposibilities:
* (1) Generating id (which requires name in option should not be modified).
* (2) As an index to mapping series when merging option or calling API (a name
* can refer to more then one components, which is convinient is some case).
* (3) Display.
* @readOnly
*/
name: '',
/**
* @readOnly
* @type {string}
*/
mainType: '',
/**
* @readOnly
* @type {string}
*/
subType: '',
/**
* @readOnly
* @type {number}
*/
componentIndex: 0,
/**
* @type {Object}
* @protected
*/
defaultOption: null,
/**
* @type {module:echarts/model/Global}
* @readOnly
*/
ecModel: null,
/**
* key: componentType
* value: Component model list, can not be null.
* @type {Object.<string, Array.<module:echarts/model/Model>>}
* @readOnly
*/
dependentModels: [],
/**
* @type {string}
* @readOnly
*/
uid: null,
/**
* Support merge layout params.
* Only support 'box' now (left/right/top/bottom/width/height).
* @type {string|Object} Object can be {ignoreSize: true}
* @readOnly
*/
layoutMode: null,
this.uid = getUID('ec_cpt_model');
},
if (layoutMode) {
mergeLayoutParam(option, inputPositionParams, layoutMode);
}
},
getDefaultOption: function () {
var fields = inner$1(this);
if (!fields.defaultOption) {
var optList = [];
var Class = this.constructor;
while (Class) {
var opt = Class.prototype.defaultOption;
opt && optList.push(opt);
Class = Class.superClass;
}
});
// this.uid = componentUtil.getUID('componentModel');
// // this.setReadOnly([
// // 'type', 'id', 'uid', 'name', 'mainType', 'subType',
// // 'dependentModels', 'componentIndex'
// // ]);
// }
// );
function getDependencies(componentType) {
var deps = [];
each$1(ComponentModel.getClassesByMainType(componentType), function (Clazz) {
deps = deps.concat(Clazz.prototype.dependencies || []);
});
return deps;
}
mixin(ComponentModel, boxLayoutMixin);
// https://dribbble.com/shots/1065960-Infographic-Pie-chart-visualization
// color: ['#5793f3', '#d14a61', '#fd9c35', '#675bba', '#fec42c', '#dd4444',
'#d4df5a', '#cd4870'],
// Light colors:
// color: ['#bcd3bb', '#e88f70', '#edc1a5', '#9dc5c8', '#e1e8c8', '#7b7c68',
'#e5b5b5', '#f0b489', '#928ea8', '#bda29a'],
// color: ['#cc5664', '#9bd6ec', '#ea946e', '#8acaaa', '#f1ec64', '#ee8686',
'#a48dc1', '#5da6bc', '#b9dcae'],
// Dark colors:
color: ['#c23531','#2f4554', '#61a0a8', '#d48265', '#91c7ae','#749f83',
'#ca8622', '#bda29a','#6e7074', '#546570', '#c4ccd3'],
textStyle: {
// color: '#000',
// decoration: 'none',
// PENDING
fontFamily: platform.match(/^Win/) ? 'Microsoft YaHei' : 'sans-serif',
// fontFamily: 'Arial, Verdana, sans-serif',
fontSize: 12,
fontStyle: 'normal',
fontWeight: 'normal'
},
// http://blogs.adobe.com/webplatform/2014/02/24/using-blend-modes-in-html-
canvas/
// https://developer.mozilla.org/en-
US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
// Default is source-over
blendMode: null,
animation: 'auto',
animationDuration: 1000,
animationDurationUpdate: 300,
animationEasing: 'exponentialOut',
animationEasingUpdate: 'cubicOut',
animationThreshold: 2000,
// Configuration for progressive/incremental rendering
progressiveThreshold: 3000,
progressive: 400,
// See: module:echarts/scale/Time
useUTC: false
};
var inner$2 = makeInner();
var colorPaletteMixin = {
clearColorPalette: function () {
inner$2(this).colorIdx = 0;
inner$2(this).colorNameMap = {};
},
/**
* @param {string} name MUST NOT be null/undefined. Otherwise call this
function
* twise with the same parameters will get different result.
* @param {Object} [scope=this]
* @param {Object} [requestColorNum]
* @return {string} color string.
*/
getColorFromPalette: function (name, scope, requestColorNum) {
scope = scope || this;
var scopeFields = inner$2(scope);
var colorIdx = scopeFields.colorIdx || 0;
var colorNameMap = scopeFields.colorNameMap = scopeFields.colorNameMap ||
{};
// Use `hasOwnProperty` to avoid conflict with Object.prototype.
if (colorNameMap.hasOwnProperty(name)) {
return colorNameMap[name];
}
var defaultColorPalette = normalizeToArray(this.get('color', true));
var layeredColorPalette = this.get('colorLayer', true);
var colorPalette = ((requestColorNum == null || !layeredColorPalette)
? defaultColorPalette : getNearestColorPalette(layeredColorPalette,
requestColorNum));
if (!colorPalette || !colorPalette.length) {
return;
}
return color;
}
};
/**
* Helper for model references.
* There are many manners to refer axis/coordSys.
*/
// TODO
// merge relevant logic to this file?
// check: "modelHelper" of tooltip and "BrushTargetManager".
/**
* @return {Object} For example:
* {
* coordSysName: 'cartesian2d',
* coordSysDims: ['x', 'y', ...],
* axisMap: HashMap({
* x: xAxisModel,
* y: yAxisModel
* }),
* categoryAxisMap: HashMap({
* x: xAxisModel,
* y: undefined
* }),
* // It also indicate that whether there is category axis.
* firstCategoryDimIndex: 1,
* // To replace user specified encode.
* }
*/
function getCoordSysDefineBySeries(seriesModel) {
var coordSysName = seriesModel.get('coordinateSystem');
var result = {
coordSysName: coordSysName,
coordSysDims: [],
axisMap: createHashMap(),
categoryAxisMap: createHashMap()
};
var fetch = fetchers[coordSysName];
if (fetch) {
fetch(seriesModel, result, result.axisMap, result.categoryAxisMap);
return result;
}
}
var fetchers = {
if (__DEV__) {
if (!xAxisModel) {
throw new Error('xAxis "' + retrieve(
seriesModel.get('xAxisIndex'),
seriesModel.get('xAxisId'),
0
) + '" not found');
}
if (!yAxisModel) {
throw new Error('yAxis "' + retrieve(
seriesModel.get('xAxisIndex'),
seriesModel.get('yAxisId'),
0
) + '" not found');
}
}
if (isCategory(xAxisModel)) {
categoryAxisMap.set('x', xAxisModel);
result.firstCategoryDimIndex = 0;
}
if (isCategory(yAxisModel)) {
categoryAxisMap.set('y', yAxisModel);
result.firstCategoryDimIndex = 1;
}
},
if (__DEV__) {
if (!singleAxisModel) {
throw new Error('singleAxis should be specified.');
}
}
result.coordSysDims = ['single'];
axisMap.set('single', singleAxisModel);
if (isCategory(singleAxisModel)) {
categoryAxisMap.set('single', singleAxisModel);
result.firstCategoryDimIndex = 0;
}
},
if (__DEV__) {
if (!angleAxisModel) {
throw new Error('angleAxis option not found');
}
if (!radiusAxisModel) {
throw new Error('radiusAxis option not found');
}
}
if (isCategory(radiusAxisModel)) {
categoryAxisMap.set('radius', radiusAxisModel);
result.firstCategoryDimIndex = 0;
}
if (isCategory(angleAxisModel)) {
categoryAxisMap.set('angle', angleAxisModel);
result.firstCategoryDimIndex = 1;
}
},
function isCategory(axisModel) {
return axisModel.get('type') === 'category';
}
// Avoid typo.
var SOURCE_FORMAT_ORIGINAL = 'original';
var SOURCE_FORMAT_ARRAY_ROWS = 'arrayRows';
var SOURCE_FORMAT_OBJECT_ROWS = 'objectRows';
var SOURCE_FORMAT_KEYED_COLUMNS = 'keyedColumns';
var SOURCE_FORMAT_UNKNOWN = 'unknown';
// ??? CHANGE A NAME
var SOURCE_FORMAT_TYPED_ARRAY = 'typedArray';
/**
* [sourceFormat]
*
* + "original":
* This format is only used in series.data, where
* itemStyle can be specified in data item.
*
* + "arrayRows":
* [
* ['product', 'score', 'amount'],
* ['Matcha Latte', 89.3, 95.8],
* ['Milk Tea', 92.1, 89.4],
* ['Cheese Cocoa', 94.4, 91.2],
* ['Walnut Brownie', 85.4, 76.9]
* ]
*
* + "objectRows":
* [
* {product: 'Matcha Latte', score: 89.3, amount: 95.8},
* {product: 'Milk Tea', score: 92.1, amount: 89.4},
* {product: 'Cheese Cocoa', score: 94.4, amount: 91.2},
* {product: 'Walnut Brownie', score: 85.4, amount: 76.9}
* ]
*
* + "keyedColumns":
* {
* 'product': ['Matcha Latte', 'Milk Tea', 'Cheese Cocoa', 'Walnut Brownie'],
* 'count': [823, 235, 1042, 988],
* 'score': [95.8, 81.4, 91.2, 76.9]
* }
*
* + "typedArray"
*
* + "unknown"
*/
/**
* @constructor
* @param {Object} fields
* @param {string} fields.sourceFormat
* @param {Array|Object} fields.fromDataset
* @param {Array|Object} [fields.data]
* @param {string} [seriesLayoutBy='column']
* @param {Array.<Object|string>} [dimensionsDefine]
* @param {Objet|HashMap} [encodeDefine]
* @param {number} [startIndex=0]
* @param {number} [dimensionsDetectCount]
*/
function Source(fields) {
/**
* @type {boolean}
*/
this.fromDataset = fields.fromDataset;
/**
* Not null/undefined.
* @type {Array|Object}
*/
this.data = fields.data || (
fields.sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS ? {} : []
);
/**
* See also "detectSourceFormat".
* Not null/undefined.
* @type {string}
*/
this.sourceFormat = fields.sourceFormat || SOURCE_FORMAT_UNKNOWN;
/**
* 'row' or 'column'
* Not null/undefined.
* @type {string} seriesLayoutBy
*/
this.seriesLayoutBy = fields.seriesLayoutBy || SERIES_LAYOUT_BY_COLUMN;
/**
* dimensions definition in option.
* can be null/undefined.
* @type {Array.<Object|string>}
*/
this.dimensionsDefine = fields.dimensionsDefine;
/**
* encode definition in option.
* can be null/undefined.
* @type {Objet|HashMap}
*/
this.encodeDefine = fields.encodeDefine && createHashMap(fields.encodeDefine);
/**
* Not null/undefined, uint.
* @type {number}
*/
this.startIndex = fields.startIndex || 0;
/**
* Can be null/undefined (when unknown), uint.
* @type {number}
*/
this.dimensionsDetectCount = fields.dimensionsDetectCount;
}
/**
* Wrap original series data for some compatibility cases.
*/
Source.seriesDataToSource = function (data) {
return new Source({
data: data,
sourceFormat: isTypedArray(data)
? SOURCE_FORMAT_TYPED_ARRAY
: SOURCE_FORMAT_ORIGINAL,
fromDataset: false
});
};
enableClassCheck(Source);
/**
* @see {module:echarts/data/Source}
* @param {module:echarts/component/dataset/DatasetModel} datasetModel
* @return {string} sourceFormat
*/
function detectSourceFormat(datasetModel) {
var data = datasetModel.option.source;
var sourceFormat = SOURCE_FORMAT_UNKNOWN;
if (isTypedArray(data)) {
sourceFormat = SOURCE_FORMAT_TYPED_ARRAY;
}
else if (isArray(data)) {
// FIXME Whether tolerate null in top level array?
for (var i = 0, len = data.length; i < len; i++) {
var item = data[i];
if (item == null) {
continue;
}
else if (isArray(item)) {
sourceFormat = SOURCE_FORMAT_ARRAY_ROWS;
break;
}
else if (isObject$1(item)) {
sourceFormat = SOURCE_FORMAT_OBJECT_ROWS;
break;
}
}
}
else if (isObject$1(data)) {
for (var key in data) {
if (data.hasOwnProperty(key) && isArrayLike(data[key])) {
sourceFormat = SOURCE_FORMAT_KEYED_COLUMNS;
break;
}
}
}
else if (data != null) {
throw new Error('Invalid data');
}
inner$3(datasetModel).sourceFormat = sourceFormat;
}
/**
* [Scenarios]:
* (1) Provide source data directly:
* series: {
* encode: {...},
* dimensions: [...]
* seriesLayoutBy: 'row',
* data: [[...]]
* }
* (2) Refer to datasetModel.
* series: [{
* encode: {...}
* // Ignore datasetIndex means `datasetIndex: 0`
* // and the dimensions defination in dataset is used
* }, {
* encode: {...},
* seriesLayoutBy: 'column',
* datasetIndex: 1
* }]
*
* Get data from series itself or datset.
* @return {module:echarts/data/Source} source
*/
function getSource(seriesModel) {
return inner$3(seriesModel).source;
}
/**
* MUST be called before mergeOption of all series.
* @param {module:echarts/model/Global} ecModel
*/
function resetSourceDefaulter(ecModel) {
// `datasetMap` is used to make default encode.
inner$3(ecModel).datasetMap = createHashMap();
}
/**
* [Caution]:
* MUST be called after series option merged and
* before "series.getInitailData()" called.
*
* [The rule of making default encode]:
* Category axis (if exists) alway map to the first dimension.
* Each other axis occupies a subsequent dimension.
*
* [Why make default encode]:
* Simplify the typing of encode in option, avoiding the case like that:
* series: [{encode: {x: 0, y: 1}}, {encode: {x: 0, y: 2}}, {encode: {x: 0, y:
3}}],
* where the "y" have to be manually typed as "1, 2, 3, ...".
*
* @param {module:echarts/model/Series} seriesModel
*/
function prepareSource(seriesModel) {
var seriesOption = seriesModel.option;
data = datasetOption.source;
sourceFormat = inner$3(datasetModel).sourceFormat;
fromDataset = true;
var dimensionsDetectCount;
var startIndex;
var findPotentialName;
dimensionsDetectCount = dimensionsDefine
? dimensionsDefine.length
: seriesLayoutBy === SERIES_LAYOUT_BY_ROW
? data.length
: data[0]
? data[0].length
: null;
}
else if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS) {
if (!dimensionsDefine) {
dimensionsDefine = objectRowsCollectDimensions(data);
findPotentialName = true;
}
}
else if (sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) {
if (!dimensionsDefine) {
dimensionsDefine = [];
findPotentialName = true;
each$1(data, function (colArr, key) {
dimensionsDefine.push(key);
});
}
}
else if (sourceFormat === SOURCE_FORMAT_ORIGINAL) {
var value0 = getDataItemValue(data[0]);
dimensionsDetectCount = isArray(value0) && value0.length || 1;
}
else if (sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) {
if (__DEV__) {
assert$1(!!dimensionsDefine, 'dimensions must be given if data is
TypedArray.');
}
}
var potentialNameDimIndex;
if (findPotentialName) {
each$1(dimensionsDefine, function (dim, idx) {
if ((isObject$1(dim) ? dim.name : dim) === 'name') {
potentialNameDimIndex = idx;
}
});
}
return {
startIndex: startIndex,
dimensionsDefine: normalizeDimensionsDefine(dimensionsDefine),
dimensionsDetectCount: dimensionsDetectCount,
potentialNameDimIndex: potentialNameDimIndex
// TODO: potentialIdDimIdx
};
}
// Consider dimensions defined like ['A', 'price', 'B', 'price', 'C', 'price'],
// which is reasonable. But dimension name is duplicated.
// Returns undefined or an array contains only object without null/undefiend or
string.
function normalizeDimensionsDefine(dimensionsDefine) {
if (!dimensionsDefine) {
// The meaning of null/undefined is different from empty array.
return;
}
var nameMap = createHashMap();
return map(dimensionsDefine, function (item, index) {
item = extend({}, isObject$1(item) ? item : {name: item});
return item;
});
}
function objectRowsCollectDimensions(data) {
var firstIndex = 0;
var obj;
while (firstIndex < data.length && !(obj = data[firstIndex++])) {} // jshint
ignore: line
if (obj) {
var dimensions = [];
each$1(obj, function (value, key) {
dimensions.push(key);
});
return dimensions;
}
}
// TODO
// Auto detect first time axis and do arrangement.
each$1(coordSysDefine.coordSysDims, function (coordDim) {
// In value way.
if (coordSysDefine.firstCategoryDimIndex == null) {
var dataDim = datasetRecord.valueWayDim++;
encode[coordDim] = dataDim;
encodeSeriesName.push(dataDim);
// encodeTooltip.push(dataDim);
// encodeLabel.push(dataDim);
}
// In category way, category axis.
else if (coordSysDefine.categoryAxisMap.get(coordDim)) {
encode[coordDim] = 0;
encodeItemName.push(0);
}
// In category way, non-category axis.
else {
var dataDim = datasetRecord.categoryWayDim++;
encode[coordDim] = dataDim;
// encodeTooltip.push(dataDim);
// encodeLabel.push(dataDim);
encodeSeriesName.push(dataDim);
}
});
}
// Do not make a complex rule! Hard to code maintain and not necessary.
// ??? TODO refactor: provide by series itself.
// [{name: ..., value: ...}, ...] like:
else if (nSeriesMap.get(seriesType) != null) {
// Find the first not ordinal. (5 is an experience value)
var firstNotOrdinal;
for (var i = 0; i < 5 && firstNotOrdinal == null; i++) {
if (!doGuessOrdinal(
data, sourceFormat, seriesLayoutBy,
completeResult.dimensionsDefine, completeResult.startIndex, i
)) {
firstNotOrdinal = i;
}
}
if (firstNotOrdinal != null) {
encode.value = firstNotOrdinal;
var nameDimIndex = completeResult.potentialNameDimIndex
|| Math.max(firstNotOrdinal - 1, 0);
// By default, label use itemName in charts.
// So we dont set encodeLabel here.
encodeSeriesName.push(nameDimIndex);
encodeItemName.push(nameDimIndex);
// encodeTooltip.push(firstNotOrdinal);
}
}
return encode;
}
/**
* If return null/undefined, indicate that should not use datasetModel.
*/
function getDatasetModel(seriesModel) {
var option = seriesModel.option;
// Caution: consider the scenario:
// A dataset is declared and a series is not expected to use the dataset,
// and at the beginning `setOption({series: { noData })` (just prepare other
// option but no data), then `setOption({series: {data: [...]}); In this case,
// the user should set an empty array to avoid that dataset is used by default.
var thisData = option.data;
if (!thisData) {
return seriesModel.ecModel.getComponent('dataset', option.datasetIndex ||
0);
}
}
/**
* The rule should not be complex, otherwise user might not
* be able to known where the data is wrong.
* The code is ugly, but how to make it neat?
*
* @param {module:echars/data/Source} source
* @param {number} dimIndex
* @return {boolean} Whether ordinal.
*/
function guessOrdinal(source, dimIndex) {
return doGuessOrdinal(
source.data,
source.sourceFormat,
source.seriesLayoutBy,
source.dimensionsDefine,
source.startIndex,
dimIndex
);
}
if (isTypedArray(data)) {
return false;
}
function detectValue(val) {
// Consider usage convenience, '1', '2' will be treated as "number".
// `isFinit('')` get `true`.
if (val != null && isFinite(val) && val !== '') {
return false;
}
else if (isString(val) && val !== '-') {
return true;
}
}
return false;
}
/**
* ECharts global model
*
* @module {echarts/model/Global}
*/
/**
* Caution: If the mechanism should be changed some day, these cases
* should be considered:
*
* (1) In `merge option` mode, if using the same option to call `setOption`
* many times, the result should be the same (try our best to ensure that).
* (2) In `merge option` mode, if a component has no id/name specified, it
* will be merged by index, and the result sequence of the components is
* consistent to the original sequence.
* (3) `reset` feature (in toolbox). Find detailed info in comments about
* `mergeOption` in module:echarts/model/OptionManager.
*/
/**
* @alias module:echarts/model/Global
*
* @param {Object} option
* @param {module:echarts/model/Model} parentModel
* @param {Object} theme
*/
var GlobalModel = Model.extend({
constructor: GlobalModel,
/**
* @type {module:echarts/model/Model}
* @private
*/
this._theme = new Model(theme);
/**
* @type {module:echarts/model/OptionManager}
*/
this._optionManager = optionManager;
},
this._optionManager.setOption(option, optionPreprocessorFuncs);
this.resetOption(null);
},
/**
* @param {string} type null/undefined: reset all.
* 'recreate': force recreate all.
* 'timeline': only reset timeline option
* 'media': only reset media query option
* @return {boolean} Whether option changed.
*/
resetOption: function (type) {
var optionChanged = false;
var optionManager = this._optionManager;
return optionChanged;
},
/**
* @protected
*/
mergeOption: function (newOption) {
var option = this.option;
var componentsMap = this._componentsMap;
var newCptTypes = [];
resetSourceDefaulter(this);
if (!ComponentModel.hasClass(mainType)) {
// globalSettingTask.dirty();
option[mainType] = option[mainType] == null
? clone(componentOption)
: merge(option[mainType], componentOption, true);
}
else if (mainType) {
newCptTypes.push(mainType);
}
});
ComponentModel.topologicalTravel(
newCptTypes, ComponentModel.getAllClassMainTypes(), visitComponent,
this
);
makeIdAndName(mapResult);
option[mainType] = [];
componentsMap.set(mainType, []);
assert$1(
isObject$1(newCptOption) || componentModel,
'Empty component definition'
);
componentsMap.get(mainType)[index] = componentModel;
option[mainType][index] = componentModel.option;
}, this);
this._seriesIndicesMap = createHashMap(
this._seriesIndices = this._seriesIndices || []
);
},
/**
* Get option for output (cloned option and inner info removed)
* @public
* @return {Object}
*/
getOption: function () {
var option = clone(this.option);
delete option[OPTION_INNER_KEY];
return option;
},
/**
* @return {module:echarts/model/Model}
*/
getTheme: function () {
return this._theme;
},
/**
* @param {string} mainType
* @param {number} [idx=0]
* @return {module:echarts/model/Component}
*/
getComponent: function (mainType, idx) {
var list = this._componentsMap.get(mainType);
if (list) {
return list[idx || 0];
}
},
/**
* If none of index and id and name used, return all components with mainType.
* @param {Object} condition
* @param {string} condition.mainType
* @param {string} [condition.subType] If ignore, only query by mainType
* @param {number|Array.<number>} [condition.index] Either input index or id or
name.
* @param {string|Array.<string>} [condition.id] Either input index or id or
name.
* @param {string|Array.<string>} [condition.name] Either input index or id or
name.
* @return {Array.<module:echarts/model/Component>}
*/
queryComponents: function (condition) {
var mainType = condition.mainType;
if (!mainType) {
return [];
}
if (!cpts || !cpts.length) {
return [];
}
var result;
if (index != null) {
if (!isArray(index)) {
index = [index];
}
result = filter(map(index, function (idx) {
return cpts[idx];
}), function (val) {
return !!val;
});
}
else if (id != null) {
var isIdArray = isArray(id);
result = filter(cpts, function (cpt) {
return (isIdArray && indexOf(id, cpt.id) >= 0)
|| (!isIdArray && cpt.id === id);
});
}
else if (name != null) {
var isNameArray = isArray(name);
result = filter(cpts, function (cpt) {
return (isNameArray && indexOf(name, cpt.name) >= 0)
|| (!isNameArray && cpt.name === name);
});
}
else {
// Return all components with mainType
result = cpts.slice();
}
/**
* The interface is different from queryComponents,
* which is convenient for inner usage.
*
* @usage
* var result = findComponents(
* {mainType: 'dataZoom', query: {dataZoomId: 'abc'}}
* );
* var result = findComponents(
* {mainType: 'series', subType: 'pie', query: {seriesName: 'uio'}}
* );
* var result = findComponents(
* {mainType: 'series'},
* function (model, index) {...}
* );
* // result like [component0, componnet1, ...]
*
* @param {Object} condition
* @param {string} condition.mainType Mandatory.
* @param {string} [condition.subType] Optional.
* @param {Object} [condition.query] like {xxxIndex, xxxId, xxxName},
* where xxx is mainType.
* If query attribute is null/undefined or has no index/id/name,
* do not filtering by query conditions, which is convenient for
* no-payload situations or when target of action is global.
* @param {Function} [condition.filter] parameter: component, return boolean.
* @return {Array.<module:echarts/model/Component>}
*/
findComponents: function (condition) {
var query = condition.query;
var mainType = condition.mainType;
function getQueryCond(q) {
var indexAttr = mainType + 'Index';
var idAttr = mainType + 'Id';
var nameAttr = mainType + 'Name';
return q && (
q[indexAttr] != null
|| q[idAttr] != null
|| q[nameAttr] != null
)
? {
mainType: mainType,
// subType will be filtered finally.
index: q[indexAttr],
id: q[idAttr],
name: q[nameAttr]
}
: null;
}
function doFilter(res) {
return condition.filter
? filter(res, condition.filter)
: res;
}
},
/**
* @usage
* eachComponent('legend', function (legendModel, index) {
* ...
* });
* eachComponent(function (componentType, model, index) {
* // componentType does not include subType
* // (componentType is 'xxx' but not 'xxx.aa')
* });
* eachComponent(
* {mainType: 'dataZoom', query: {dataZoomId: 'abc'}},
* function (model, index) {...}
* );
* eachComponent(
* {mainType: 'series', subType: 'pie', query: {seriesName: 'uio'}},
* function (model, index) {...}
* );
*
* @param {string|Object=} mainType When mainType is object, the definition
* is the same as the method 'findComponents'.
* @param {Function} cb
* @param {*} context
*/
eachComponent: function (mainType, cb, context) {
var componentsMap = this._componentsMap;
/**
* @param {string} name
* @return {Array.<module:echarts/model/Series>}
*/
getSeriesByName: function (name) {
var series = this._componentsMap.get('series');
return filter(series, function (oneSeries) {
return oneSeries.name === name;
});
},
/**
* @param {number} seriesIndex
* @return {module:echarts/model/Series}
*/
getSeriesByIndex: function (seriesIndex) {
return this._componentsMap.get('series')[seriesIndex];
},
/**
* Get series list before filtered by type.
* FIXME: rename to getRawSeriesByType?
*
* @param {string} subType
* @return {Array.<module:echarts/model/Series>}
*/
getSeriesByType: function (subType) {
var series = this._componentsMap.get('series');
return filter(series, function (oneSeries) {
return oneSeries.subType === subType;
});
},
/**
* @return {Array.<module:echarts/model/Series>}
*/
getSeries: function () {
return this._componentsMap.get('series').slice();
},
/**
* @return {number}
*/
getSeriesCount: function () {
return this._componentsMap.get('series').length;
},
/**
* After filtering, series may be different
* frome raw series.
*
* @param {Function} cb
* @param {*} context
*/
eachSeries: function (cb, context) {
assertSeriesInitialized(this);
each$1(this._seriesIndices, function (rawSeriesIndex) {
var series = this._componentsMap.get('series')[rawSeriesIndex];
cb.call(context, series, rawSeriesIndex);
}, this);
},
/**
* Iterate raw series before filtered.
*
* @param {Function} cb
* @param {*} context
*/
eachRawSeries: function (cb, context) {
each$1(this._componentsMap.get('series'), cb, context);
},
/**
* After filtering, series may be different.
* frome raw series.
*
* @parma {string} subType
* @param {Function} cb
* @param {*} context
*/
eachSeriesByType: function (subType, cb, context) {
assertSeriesInitialized(this);
each$1(this._seriesIndices, function (rawSeriesIndex) {
var series = this._componentsMap.get('series')[rawSeriesIndex];
if (series.subType === subType) {
cb.call(context, series, rawSeriesIndex);
}
}, this);
},
/**
* Iterate raw series before filtered of given type.
*
* @parma {string} subType
* @param {Function} cb
* @param {*} context
*/
eachRawSeriesByType: function (subType, cb, context) {
return each$1(this.getSeriesByType(subType), cb, context);
},
/**
* @param {module:echarts/model/Series} seriesModel
*/
isSeriesFiltered: function (seriesModel) {
assertSeriesInitialized(this);
return this._seriesIndicesMap.get(seriesModel.componentIndex) == null;
},
/**
* @return {Array.<number>}
*/
getCurrentSeriesIndices: function () {
return (this._seriesIndices || []).slice();
},
/**
* @param {Function} cb
* @param {*} context
*/
filterSeries: function (cb, context) {
assertSeriesInitialized(this);
var filteredSeries = filter(
this._componentsMap.get('series'), cb, context
);
createSeriesIndices(this, filteredSeries);
},
createSeriesIndices(this, componentsMap.get('series'));
ComponentModel.topologicalTravel(
componentTypes,
ComponentModel.getAllClassMainTypes(),
function (componentType, dependencies) {
each$1(componentsMap.get(componentType), function (component) {
(componentType !== 'series' || !isNotTargetSeries(component,
payload))
&& component.restoreData();
});
}
);
}
});
/**
* @inner
*/
function mergeTheme(option, theme) {
// PENDING
// NOT use `colorLayer` in theme if option has `color`
var notMergeColorLayer = option.color && !option.colorLayer;
function initBase(baseOption) {
baseOption = baseOption;
// Using OPTION_INNER_KEY to mark that this option can not be used outside,
// i.e. `chart.setOption(chart.getModel().option);` is forbiden.
this.option = {};
this.option[OPTION_INNER_KEY] = 1;
/**
* Init with series: [], in case of calling findSeries method
* before series initialized.
* @type {Object.<string, Array.<module:echarts/model/Model>>}
* @private
*/
this._componentsMap = createHashMap({series: []});
/**
* Mapping between filtered series list and raw series list.
* key: filtered series indices, value: raw series indices.
* @type {Array.<nubmer>}
* @private
*/
this._seriesIndices;
this._seriesIndicesMap;
mergeTheme(baseOption, this._theme.option);
this.mergeOption(baseOption);
}
/**
* @inner
* @param {Array.<string>|string} types model types
* @return {Object} key: {string} type, value: {Array.<Object>} models
*/
function getComponentsByTypes(componentsMap, types) {
if (!isArray(types)) {
types = types ? [types] : [];
}
return ret;
}
/**
* @inner
*/
function determineSubType(mainType, newCptOption, existComponent) {
var subType = newCptOption.type
? newCptOption.type
: existComponent
? existComponent.subType
// Use determineSubType only when there is no existComponent.
: ComponentModel.determineSubType(mainType, newCptOption);
/**
* @inner
*/
function createSeriesIndices(ecModel, seriesModels) {
ecModel._seriesIndicesMap = createHashMap(
ecModel._seriesIndices = map(seriesModels, function (series) {
return series.componentIndex;
}) || []
);
}
/**
* @inner
*/
function filterBySubType(components, condition) {
// Using hasOwnProperty for restrict. Consider
// subType is undefined in user payload.
return condition.hasOwnProperty('subType')
? filter(components, function (cpt) {
return cpt.subType === condition.subType;
})
: components;
}
/**
* @inner
*/
function assertSeriesInitialized(ecModel) {
// Components that use _seriesIndices should depends on series component,
// which make sure that their initialization is after series.
if (__DEV__) {
if (!ecModel._seriesIndices) {
throw new Error('Option should contains series.');
}
}
}
mixin(GlobalModel, colorPaletteMixin);
var echartsAPIList = [
'getDom', 'getZr', 'getWidth', 'getHeight', 'getDevicePixelRatio',
'dispatchAction', 'isDisposed',
'on', 'off', 'getDataURL', 'getConnectedDataURL', 'getModel', 'getOption',
'getViewOfComponentModel', 'getViewOfSeriesModel'
];
// And `getCoordinateSystems` and `getComponentByElement` will be injected in
echarts.js
function ExtensionAPI(chartInstance) {
each$1(echartsAPIList, function (name) {
this[name] = bind(chartInstance[name], chartInstance);
}, this);
}
function CoordinateSystemManager() {
this._coordinateSystems = [];
}
CoordinateSystemManager.prototype = {
constructor: CoordinateSystemManager,
this._coordinateSystems = coordinateSystems;
},
getCoordinateSystems: function () {
return this._coordinateSystems.slice();
}
};
/**
* ECharts option manager
*
* @module {echarts/model/OptionManager}
*/
/**
* TERM EXPLANATIONS:
*
* [option]:
*
* An object that contains definitions of components. For example:
* var option = {
* title: {...},
* legend: {...},
* visualMap: {...},
* series: [
* {data: [...]},
* {data: [...]},
* ...
* ]
* };
*
* [rawOption]:
*
* An object input to echarts.setOption. 'rawOption' may be an
* 'option', or may be an object contains multi-options. For example:
* var option = {
* baseOption: {
* title: {...},
* legend: {...},
* series: [
* {data: [...]},
* {data: [...]},
* ...
* ]
* },
* timeline: {...},
* options: [
* {title: {...}, series: {data: [...]}},
* {title: {...}, series: {data: [...]}},
* ...
* ],
* media: [
* {
* query: {maxWidth: 320},
* option: {series: {x: 20}, visualMap: {show: false}}
* },
* {
* query: {minWidth: 320, maxWidth: 720},
* option: {series: {x: 500}, visualMap: {show: true}}
* },
* {
* option: {series: {x: 1200}, visualMap: {show: true}}
* }
* ]
* };
*
* @alias module:echarts/model/OptionManager
* @param {module:echarts/ExtensionAPI} api
*/
function OptionManager(api) {
/**
* @private
* @type {module:echarts/ExtensionAPI}
*/
this._api = api;
/**
* @private
* @type {Array.<number>}
*/
this._timelineOptions = [];
/**
* @private
* @type {Array.<Object>}
*/
this._mediaList = [];
/**
* @private
* @type {Object}
*/
this._mediaDefault;
/**
* -1, means default.
* empty means no media.
* @private
* @type {Array.<number>}
*/
this._currentMediaIndices = [];
/**
* @private
* @type {Object}
*/
this._optionBackup;
/**
* @private
* @type {Object}
*/
this._newBaseOption;
}
OptionManager.prototype = {
constructor: OptionManager,
/**
* @public
* @param {Object} rawOption Raw option.
* @param {module:echarts/model/Global} ecModel
* @param {Array.<Function>} optionPreprocessorFuncs
* @return {Object} Init option
*/
setOption: function (rawOption, optionPreprocessorFuncs) {
if (rawOption) {
// That set dat primitive is dangerous if user reuse the data when
setOption again.
each$1(normalizeToArray(rawOption.series), function (series) {
series && series.data && isTypedArray(series.data) &&
setAsPrimitive(series.data);
});
}
// FIXME
// 如果 timeline options 或者 media 中设置了某个属性,而 baseOption 中没有设置,则进
行警告。
/**
* @param {boolean} isRecreate
* @return {Object}
*/
mountOption: function (isRecreate) {
var optionBackup = this._optionBackup;
// TODO
// 如果没有 reset 功能则不 clone。
return clone$3(isRecreate
// this._optionBackup.baseOption, which is created at the first
`setOption`
// called, and is merged into every new option by inner method
`mergeOption`
// each time `setOption` called, can be only used in `isRecreate`,
because
// its reliability is under suspicion. In other cases option merge is
// performed by `model.mergeOption`.
? optionBackup.baseOption : this._newBaseOption
);
},
/**
* @param {module:echarts/model/Global} ecModel
* @return {Object}
*/
getTimelineOption: function (ecModel) {
var option;
var timelineOptions = this._timelineOptions;
if (timelineOptions.length) {
// getTimelineOption can only be called after ecModel inited,
// so we can get currentIndex from timelineModel.
var timelineModel = ecModel.getComponent('timeline');
if (timelineModel) {
option = clone$3(
timelineOptions[timelineModel.getCurrentIndex()],
true
);
}
}
return option;
},
/**
* @param {module:echarts/model/Global} ecModel
* @return {Array.<Object>}
*/
getMediaOption: function (ecModel) {
var ecWidth = this._api.getWidth();
var ecHeight = this._api.getHeight();
var mediaList = this._mediaList;
var mediaDefault = this._mediaDefault;
var indices = [];
var result = [];
// No media defined.
if (!mediaList.length && !mediaDefault) {
return result;
}
// Multi media may be applied, the latter defined media has higher
priority.
for (var i = 0, len = mediaList.length; i < len; i++) {
if (applyMediaQuery(mediaList[i].query, ecWidth, ecHeight)) {
indices.push(i);
}
}
// FIXME
// 是否 mediaDefault 应该强制用户设置,否则可能修改不能回归。
if (!indices.length && mediaDefault) {
indices = [-1];
}
this._currentMediaIndices = indices;
return result;
}
};
if (rawOption.baseOption) {
baseOption = rawOption.baseOption;
}
// For timeline
if (timelineOpt || rawOption.options) {
baseOption = baseOption || {};
timelineOptions = (rawOption.options || []).slice();
}
// Preprocess.
each$4([baseOption].concat(timelineOptions)
.concat(map(mediaList, function (media) {
return media.option;
})),
function (option) {
each$4(optionPreprocessorFuncs, function (preProcess) {
preProcess(option, isNew);
});
}
);
return {
baseOption: baseOption,
timelineOptions: timelineOptions,
mediaDefault: mediaDefault,
mediaList: mediaList
};
}
/**
* @see <http://www.w3.org/TR/css3-mediaqueries/#media1>
* Support: width, height, aspectRatio
* Can use max or min as prefix.
*/
function applyMediaQuery(query, ecWidth, ecHeight) {
var realMap = {
width: ecWidth,
height: ecHeight,
aspectratio: ecWidth / ecHeight // lowser case for convenientce.
};
return applicatable;
}
/**
* Consider case:
* `chart.setOption(opt1);`
* Then user do some interaction like dataZoom, dataView changing.
* `chart.setOption(opt2);`
* Then user press 'reset button' in toolbox.
*
* After doing that all of the interaction effects should be reset, the
* chart should be the same as the result of invoke
* `chart.setOption(opt1); chart.setOption(opt2);`.
*
* Although it is not able ensure that
* `chart.setOption(opt1); chart.setOption(opt2);` is equivalents to
* `chart.setOption(merge(opt1, opt2));` exactly,
* this might be the only simple way to implement that feature.
*
* MEMO: We've considered some other approaches:
* 1. Each model handle its self restoration but not uniform treatment.
* (Too complex in logic and error-prone)
* 2. Use a shadow ecModel. (Performace expensive)
*/
function mergeOption(oldOption, newOption) {
newOption = newOption || {};
var POSSIBLE_STYLES = [
'areaStyle', 'lineStyle', 'nodeStyle', 'linkStyle',
'chordStyle', 'label', 'labelLine'
];
function compatEC2ItemStyle(opt) {
var itemStyleOpt = opt && opt.itemStyle;
if (!itemStyleOpt) {
return;
}
for (var i = 0, len = POSSIBLE_STYLES.length; i < len; i++) {
var styleName = POSSIBLE_STYLES[i];
var normalItemStyleOpt = itemStyleOpt.normal;
var emphasisItemStyleOpt = itemStyleOpt.emphasis;
if (normalItemStyleOpt && normalItemStyleOpt[styleName]) {
opt[styleName] = opt[styleName] || {};
if (!opt[styleName].normal) {
opt[styleName].normal = normalItemStyleOpt[styleName];
}
else {
merge(opt[styleName].normal, normalItemStyleOpt[styleName]);
}
normalItemStyleOpt[styleName] = null;
}
if (emphasisItemStyleOpt && emphasisItemStyleOpt[styleName]) {
opt[styleName] = opt[styleName] || {};
if (!opt[styleName].emphasis) {
opt[styleName].emphasis = emphasisItemStyleOpt[styleName];
}
else {
merge(opt[styleName].emphasis, emphasisItemStyleOpt[styleName]);
}
emphasisItemStyleOpt[styleName] = null;
}
}
}
function convertNormalEmphasis(opt, optType, useExtend) {
if (opt && opt[optType] && (opt[optType].normal || opt[optType].emphasis)) {
var normalOpt = opt[optType].normal;
var emphasisOpt = opt[optType].emphasis;
if (normalOpt) {
// Timeline controlStyle has other properties besides normal and
emphasis
if (useExtend) {
opt[optType].normal = opt[optType].emphasis = null;
defaults(opt[optType], normalOpt);
}
else {
opt[optType] = normalOpt;
}
}
if (emphasisOpt) {
opt.emphasis = opt.emphasis || {};
opt.emphasis[optType] = emphasisOpt;
}
}
}
function removeEC3NormalStatus(opt) {
convertNormalEmphasis(opt, 'itemStyle');
convertNormalEmphasis(opt, 'lineStyle');
convertNormalEmphasis(opt, 'areaStyle');
convertNormalEmphasis(opt, 'label');
convertNormalEmphasis(opt, 'labelLine');
// treemap
convertNormalEmphasis(opt, 'upperLabel');
// graph
convertNormalEmphasis(opt, 'edgeLabel');
}
function compatEC3CommonStyles(opt) {
if (opt) {
removeEC3NormalStatus(opt);
compatTextStyle(opt, 'label');
opt.emphasis && compatTextStyle(opt.emphasis, 'label');
}
}
function processSeries(seriesOpt) {
if (!isObject$3(seriesOpt)) {
return;
}
compatEC2ItemStyle(seriesOpt);
removeEC3NormalStatus(seriesOpt);
compatTextStyle(seriesOpt, 'label');
// treemap
compatTextStyle(seriesOpt, 'upperLabel');
// graph
compatTextStyle(seriesOpt, 'edgeLabel');
if (seriesOpt.emphasis) {
compatTextStyle(seriesOpt.emphasis, 'label');
// treemap
compatTextStyle(seriesOpt.emphasis, 'upperLabel');
// graph
compatTextStyle(seriesOpt.emphasis, 'edgeLabel');
}
// Series
if (seriesOpt.type === 'gauge') {
compatTextStyle(seriesOpt, 'axisLabel');
compatTextStyle(seriesOpt, 'title');
compatTextStyle(seriesOpt, 'detail');
}
else if (seriesOpt.type === 'treemap') {
convertNormalEmphasis(seriesOpt.breadcrumb, 'itemStyle');
each$1(seriesOpt.levels, function (opt) {
removeEC3NormalStatus(opt);
});
}
// sunburst starts from ec4, so it does not need to compat levels.
}
function toArr(o) {
return isArray(o) ? o : o ? [o] : [];
}
function toObj(o) {
return (isArray(o) ? o[0] : o) || {};
}
each$5(
axes,
function (axisName) {
each$5(toArr(option[axisName]), function (axisOpt) {
if (axisOpt) {
compatTextStyle(axisOpt, 'axisLabel');
compatTextStyle(axisOpt.axisPointer, 'label');
}
});
}
);
// radar.name.textStyle
each$5(toArr(option.radar), function (radarOpt) {
compatTextStyle(radarOpt, 'name');
});
compatTextStyle(toObj(option.axisPointer), 'label');
compatTextStyle(toObj(option.tooltip).axisPointer, 'label');
};
function compatLayoutProperties(option) {
each$1(LAYOUT_PROPERTIES, function (prop) {
if (prop[0] in option && !(prop[1] in option)) {
option[prop[1]] = option[prop[0]];
}
});
}
var LAYOUT_PROPERTIES = [
['x', 'left'], ['y', 'top'], ['x2', 'right'], ['y2', 'bottom']
];
var COMPATITABLE_COMPONENTS = [
'grid', 'geo', 'parallel', 'legend', 'toolbox', 'title', 'visualMap',
'dataZoom', 'timeline'
];
compatLayoutProperties(seriesOpt);
});
var stackInfo = {
// Used for calculate axis extent automatically.
stackResultDimension:
data.getCalculationInfo('stackResultDimension'),
stackedOverDimension:
data.getCalculationInfo('stackedOverDimension'),
stackedDimension: data.getCalculationInfo('stackedDimension'),
stackedByDimension: data.getCalculationInfo('stackedByDimension'),
isStackedByIndex: data.getCalculationInfo('isStackedByIndex'),
data: data,
seriesModel: seriesModel
};
stackInfoList.push(stackInfo);
}
});
stackInfoMap.each(calculateStack);
};
function calculateStack(stackInfoList) {
each$1(stackInfoList, function (targetStackInfo, idxInStack) {
var resultVal = [];
var resultNaN = [NaN, NaN];
var dims = [targetStackInfo.stackResultDimension,
targetStackInfo.stackedOverDimension];
var targetData = targetStackInfo.data;
var isStackedByIndex = targetStackInfo.isStackedByIndex;
// Should not write on raw data, because stack series model list changes
// depending on legend selection.
var newData = targetData.map(dims, function (v0, v1, dataIndex) {
var sum = targetData.get(targetStackInfo.stackedDimension, dataIndex);
var byValue;
var stackedDataRawIndex;
if (isStackedByIndex) {
stackedDataRawIndex = targetData.getRawIndex(dataIndex);
}
else {
byValue = targetData.get(targetStackInfo.stackedByDimension,
dataIndex);
}
if (stackedDataRawIndex >= 0) {
var val =
stackInfo.data.getByRawIndex(stackInfo.stackResultDimension, stackedDataRawIndex);
resultVal[0] = sum;
resultVal[1] = stackedOver;
return resultVal;
});
targetData.hostModel.setData(newData);
// Update for consequent calculation
targetStackInfo.data = newData;
});
}
// TODO
// ??? refactor? check the outer usage of data provider.
// merge with defaultDimValueGetter?
/**
* If normal array used, mutable chunk size is supported.
* If typed array used, chunk size must be fixed.
*/
function DefaultDataProvider(source, dimSize) {
if (!Source.isInstance(source)) {
source = Source.seriesDataToSource(source);
}
this._source = source;
if (__DEV__) {
assert$1(methods, 'Invalide sourceFormat: ' + sourceFormat);
}
extend(this, methods);
}
var providerMethods = {
'arrayRows_column': {
pure: true,
count: function () {
return Math.max(0, this._data.length - this._source.startIndex);
},
getItem: function (idx) {
return this._data[idx + this._source.startIndex];
},
appendData: appendDataSimply
},
'arrayRows_row': {
pure: true,
count: function () {
var row = this._data[0];
return row ? Math.max(0, row.length - this._source.startIndex) : 0;
},
getItem: function (idx) {
idx += this._source.startIndex;
var item = [];
var data = this._data;
for (var i = 0; i < data.length; i++) {
var row = data[i];
item.push(row ? row[idx] : null);
}
return item;
},
appendData: function () {
throw new Error('Do not support appendData when set seriesLayoutBy:
"row".');
}
},
'objectRows': {
pure: true,
count: countSimply,
getItem: getItemSimply,
appendData: appendDataSimply
},
'keyedColumns': {
pure: true,
count: function () {
var dimName = this._source.dimensionsDefine[0].name;
var col = this._data[dimName];
return col ? col.length : 0;
},
getItem: function (idx) {
var item = [];
var dims = this._source.dimensionsDefine;
for (var i = 0; i < dims.length; i++) {
var col = this._data[dims[i].name];
item.push(col ? col[idx] : null);
}
return item;
},
appendData: function (newData) {
var data = this._data;
each$1(newData, function (newCol, key) {
var oldCol = data[key] || (data[key] = []);
for (var i = 0; i < (newCol || []).length; i++) {
oldCol.push(newCol[i]);
}
});
}
},
'original': {
count: countSimply,
getItem: getItemSimply,
appendData: appendDataSimply
},
'typedArray': {
persistent: false,
pure: true,
count: function () {
return this._data ? (this._data.length / this._dimSize) : 0;
},
getItem: function (idx) {
idx = idx - this._offset;
var item = [];
var offset = this._dimSize * idx;
for (var i = 0; i < this._dimSize; i++) {
item[i] = this._data[offset + i];
}
return item;
},
appendData: function (newData) {
if (__DEV__) {
assert$1(
isTypedArray(newData),
'Added data must be TypedArray if data in initialization is
TypedArray'
);
}
this._data = newData;
},
function countSimply() {
return this._data.length;
}
function getItemSimply(idx) {
return this._data[idx];
}
function appendDataSimply(newData) {
for (var i = 0; i < newData.length; i++) {
this._data.push(newData[i]);
}
}
var rawValueGetters = {
arrayRows: getRawValueSimply,
keyedColumns: getRawValueSimply,
typedArray: getRawValueSimply
};
function getRawValueSimply(dataItem, dataIndex, dimIndex, dimName) {
return dimIndex != null ? dataItem[dimIndex] : dataItem;
}
var defaultDimValueGetters = {
arrayRows: getDimValueSimply,
keyedColumns: getDimValueSimply,
};
/**
* This helper method convert value in data.
* @param {string|number|Date} value
* @param {Object|string} [dimInfo] If string (like 'x'), dimType defaults
'number'.
* If "dimInfo.ordinalParseAndSave", ordinal value can be parsed.
*/
function converDataValue(value, dimInfo) {
// Performance sensitive.
var dimType = dimInfo && dimInfo.type;
if (dimType === 'ordinal') {
// If given value is a category string
var ordinalMeta = dimInfo && dimInfo.ordinalMeta;
return ordinalMeta
? ordinalMeta.parseAndCollect(value)
: value;
}
if (dataItem == null) {
return;
}
/**
* Compatible with some cases (in pie, map) like:
* data: [{name: 'xx', value: 5, selected: true}, ...]
* where only sourceFormat is 'original' and 'objectRows' supported.
*
* ??? TODO
* Supported detail options in data item when using 'arrayRows'.
*
* @param {module:echarts/data/List} data
* @param {number} dataIndex
* @param {string} attr like 'selected'
*/
function retrieveRawAttr(data, dataIndex, attr) {
if (!data) {
return;
}
return {
componentType: this.mainType,
componentSubType: this.subType,
seriesType: this.mainType === 'series' ? this.subType : null,
seriesIndex: this.seriesIndex,
seriesId: this.id,
seriesName: this.name,
name: name,
dataIndex: rawDataIndex,
data: itemOpt,
dataType: dataType,
value: rawValue,
color: color,
marker: getTooltipMarker(color),
// Param name list for mapping `a`, `b`, `c`, `d`, `e`
$vars: ['seriesName', 'name', 'value']
};
},
/**
* Format label
* @param {number} dataIndex
* @param {string} [status='normal'] 'normal' or 'emphasis'
* @param {string} [dataType]
* @param {number} [dimIndex]
* @param {string} [labelProp='label']
* @return {string} If not formatter, return null/undefined
*/
getFormattedLabel: function (dataIndex, status, dataType, dimIndex, labelProp)
{
status = status || 'normal';
var data = this.getData(dataType);
var itemModel = data.getItemModel(dataIndex);
// Support 'aaa{@[3]}bbb{@product}ccc'.
// Do not support '}' in dim name util have to.
return str.replace(DIMENSION_LABEL_REG, function (origin, dim) {
var len = dim.length;
if (dim.charAt(0) === '[' && dim.charAt(len - 1) === ']') {
dim = +dim.slice(1, len - 1); // Also: '[]' => 0
}
return retrieveRawValue(data, dataIndex, dim);
});
}
},
/**
* Get raw value in option
* @param {number} idx
* @param {string} [dataType]
* @return {Array|number|string}
*/
getRawValue: function (idx, dataType) {
return retrieveRawValue(this.getData(dataType), idx);
},
/**
* Should be implemented.
* @param {number} dataIndex
* @param {boolean} [multipleSeries=false]
* @param {number} [dataType]
* @return {string} tooltip string
*/
formatTooltip: function () {
// Empty function
}
};
/**
* @param {Object} define
* @return See the return of `createTask`.
*/
function createTask(define) {
return new Task(define);
}
/**
* @constructor
* @param {Object} define
* @param {Function} define.reset Custom reset
* @param {Function} [define.plan] Returns 'reset' indicate reset immediately.
* @param {Function} [define.count] count is used to determin data task.
* @param {Function} [define.onDirty] count is used to determin data task.
*/
function Task(define) {
define = define || {};
this._reset = define.reset;
this._plan = define.plan;
this._count = define.count;
this._onDirty = define.onDirty;
this._dirty = true;
/**
* @param {Object} performArgs
* @param {number} [performArgs.step] Specified step.
* @param {number} [performArgs.skip] Skip customer perform call.
*/
taskProto.perform = function (performArgs) {
var upTask = this._upstream;
var skip = performArgs && performArgs.skip;
if (this.__pipeline) {
this.__pipeline.currentTask = this;
}
var planResult;
if (this._plan && !skip) {
planResult = this._plan(this.context);
}
var forceFirstProgress;
if (this._dirty || planResult === 'reset') {
this._dirty = false;
forceFirstProgress = reset(this, skip);
}
if (upTask) {
if (__DEV__) {
assert$1(upTask._outputDueEnd != null);
}
// ??? FIXME move to schedueler?
// this._dueEnd = Math.max(upTask._outputDueEnd, this._dueEnd);
this._dueEnd = upTask._outputDueEnd;
}
// DataTask or overallTask
else {
if (__DEV__) {
assert$1(!this._progress || this._count);
}
this._dueEnd = this._count ? this._count(this.context) : Infinity;
}
// Note: Stubs, that its host overall task let it has progress, has progress.
// If no progress, pass index from upstream to downstream each time plan
called.
if (this._progress) {
var start = this._dueIndex;
var end = Math.min(
step != null ? this._dueIndex + step : Infinity,
this._dueEnd
);
!skip && (forceFirstProgress || start < end) && (
this._progress({start: start, end: end}, this.context)
);
this._dueIndex = end;
// If no `outputDueEnd`, assume that output data and
// input data is the same, so use `dueIndex` as `outputDueEnd`.
var outputDueEnd = this._settedOutputEnd != null
? this._settedOutputEnd : end;
if (__DEV__) {
// ??? Can not rollback.
assert$1(outputDueEnd >= this._outputDueEnd);
}
this._outputDueEnd = outputDueEnd;
}
else {
// (1) Some overall task has no progress.
// (2) Stubs, that its host overall task do not let it has progress, has no
progress.
// This should always be performed so it can be passed to downstream.
this._dueIndex = this._outputDueEnd = this._settedOutputEnd != null
? this._settedOutputEnd : this._dueEnd;
}
return this.unfinished();
};
taskProto.dirty = function () {
this._dirty = true;
this._onDirty && this._onDirty(this.context);
};
/**
* @param {Object} [params]
*/
function reset(taskIns, skip) {
taskIns._dueIndex = taskIns._outputDueEnd = taskIns._dueEnd = 0;
taskIns._settedOutputEnd = null;
var progress;
var forceFirstProgress;
taskIns._progress = progress;
return forceFirstProgress;
}
/**
* @return {boolean}
*/
taskProto.unfinished = function () {
return this._progress && this._dueIndex < this._dueEnd;
};
/**
* @param {Object} downTask The downstream task.
* @return {Object} The downstream task.
*/
taskProto.pipe = function (downTask) {
if (__DEV__) {
assert$1(downTask && !downTask._disposed && downTask !== this);
}
taskProto.dispose = function () {
if (this._disposed) {
return;
}
this._dirty = false;
this._disposed = true;
};
taskProto.getUpstream = function () {
return this._upstream;
};
taskProto.getDownstream = function () {
return this._downstream;
};
///////////////////////////////////////////////////////////
// For stream debug (Should be commented out after used!)
// Usage: printTask(this, 'begin');
// Usage: printTask(this, null, {someExtraProp});
// function printTask(task, prefix, extra) {
// window.ecTaskUID == null && (window.ecTaskUID = 0);
// task.uidDebug == null && (task.uidDebug = `task_${window.ecTaskUID++}`);
// task.agent && task.agent.uidDebug == null && (task.agent.uidDebug = `task_$
{window.ecTaskUID++}`);
// var props = [];
// if (task.__pipeline) {
// var val = `${task.__idxInPipeline}/$
{task.__pipeline.tail.__idxInPipeline} ${task.agent ? '(stub)' : ''}`;
// props.push({text: 'idx', value: val});
// } else {
// var stubCount = 0;
// task.agentStubMap.each(() => stubCount++);
// props.push({text: 'idx', value: `overall (stubs: ${stubCount})`});
// }
// props.push({text: 'uid', value: task.uidDebug});
// if (task.__pipeline) {
// props.push({text: 'pid', value: task.__pipeline.id});
// task.agent && props.push(
// {text: 'stubFor', value: task.agent.uidDebug}
// );
// }
// props.push(
// {text: 'dirty', value: task._dirty},
// {text: 'dueIndex', value: task._dueIndex},
// {text: 'dueEnd', value: task._dueEnd},
// {text: 'outputDueEnd', value: task._outputDueEnd}
// );
// if (extra) {
// Object.keys(extra).forEach(key => {
// props.push({text: key, value: extra[key]});
// });
// }
// var args = ['color: blue'];
// var msg = `%c[${prefix || 'T'}] %c` + props.map(item => (
// args.push('color: black', 'color: red'),
// `${item.text}: %c${item.value}`
// )).join('%c, ');
// console.log.apply(console, [msg].concat(args));
// // console.log(this);
// }
type: 'series.__base__',
/**
* @readOnly
*/
seriesIndex: 0,
/**
* Data provided for legend
* @type {Function}
*/
// PENDING
legendDataProvider: null,
/**
* Access path of color for visual
*/
visualColorAccessPath: 'itemStyle.color',
/**
* Support merge layout params.
* Only support 'box' now (left/right/top/bottom/width/height).
* @type {string|Object} Object can be {ignoreSize: true}
* @readOnly
*/
layoutMode: null,
/**
* @type {number}
* @readOnly
*/
this.seriesIndex = this.componentIndex;
this.dataTask = createTask({
count: dataTaskCount,
reset: dataTaskReset
});
this.dataTask.context = {model: this};
this.mergeDefaultAndTheme(option, ecModel);
prepareSource(this);
if (__DEV__) {
assert$1(data, 'getInitialData returned invalid data.');
}
/**
* @type {module:echarts/data/List|module:echarts/data/Tree|
module:echarts/data/Graph}
* @private
*/
inner$4(this).dataBeforeProcessed = data;
// If we reverse the order (make data firstly, and then make
// dataBeforeProcessed by cloneShallow), cloneShallow will
// cause data.graph.data !== data when using
// module:echarts/data/Graph or module:echarts/data/Tree.
// See module:echarts/data/helper/linkList
autoSeriesName(this);
},
/**
* Util for merge default and theme to option
* @param {Object} option
* @param {module:echarts/model/Global} ecModel
*/
mergeDefaultAndTheme: function (option, ecModel) {
var layoutMode = this.layoutMode;
var inputPositionParams = layoutMode
? getLayoutParams(option) : {};
this.fillDataTextStyle(option.data);
if (layoutMode) {
mergeLayoutParam(option, inputPositionParams, layoutMode);
}
},
prepareSource(this);
inner$4(this).dataBeforeProcessed = data;
autoSeriesName(this);
},
/**
* Init a data structure from data related option in series
* Must be overwritten
*/
getInitialData: function () {},
/**
* Append data to list
* @param {Object} params
* @param {Array|TypedArray} params.data
*/
appendData: function (params) {
// FIXME ???
// (1) If data from dataset, forbidden append.
// (2) support append data of dataset.
var data = this.getRawData();
data.appendData(params.data);
},
/**
* Consider some method like `filter`, `map` need make new data,
* We should make sure that `seriesModel.getData()` get correct
* data in the stream procedure. So we fetch data from upstream
* each time `task.perform` called.
* @param {string} [dataType]
* @return {module:echarts/data/List}
*/
getData: function (dataType) {
var task = getCurrentTask(this);
if (task) {
var data = task.context.data;
return dataType == null ? data : data.getLinkedData(dataType);
}
else {
// When series is not alive (that may happen when click toolbox
// restore or setOption with not merge mode), series data may
// be still need to judge animation or something when graphic
// elements want to know whether fade out.
return inner$4(this).data;
}
},
/**
* @param {module:echarts/data/List} data
*/
setData: function (data) {
var task = getCurrentTask(this);
if (task) {
var context = task.context;
// Consider case: filter, data sample.
if (context.data !== data && task.isOverallFilter) {
task.setOutputEnd(data.count());
}
context.outputData = data;
// Caution: setData should update context.data,
// Because getData may be called multiply in a
// single stage and expect to get the data just
// set. (For example, AxisProxy, x y both call
// getData and setDate sequentially).
// So the context.data should be fetched from
// upstream each time when a stage starts to be
// performed.
if (task !== this.dataTask) {
context.data = data;
}
}
inner$4(this).data = data;
},
/**
* @see {module:echarts/data/helper/sourceHelper#getSource}
* @return {module:echarts/data/Source} source
*/
getSource: function () {
return getSource(this);
},
/**
* Get data before processed
* @return {module:echarts/data/List}
*/
getRawData: function () {
return inner$4(this).dataBeforeProcessed;
},
/**
* Get base axis if has coordinate system and has axis.
* By default use coordSys.getBaseAxis();
* Can be overrided for some chart.
* @return {type} description
*/
getBaseAxis: function () {
var coordSys = this.coordinateSystem;
return coordSys && coordSys.getBaseAxis && coordSys.getBaseAxis();
},
// FIXME
/**
* Default tooltip formatter
*
* @param {number} dataIndex
* @param {boolean} [multipleSeries=false]
* @param {number} [dataType]
*/
formatTooltip: function (dataIndex, multipleSeries, dataType) {
function formatArrayValue(value) {
// ??? TODO refactor these logic.
// check: category-no-encode-has-axis-data in dataset.html
var vertially = reduce(value, function (vertially, val, idx) {
var dimItem = data.getDimensionInfo(idx);
return vertially |= dimItem && dimItem.tooltip !== false &&
dimItem.displayName != null;
}, 0);
tooltipDims.length
? each$1(tooltipDims, function (dim) {
setEachItem(retrieveRawValue(data, dataIndex, dim), dim);
})
// By default, all dims is used on tooltip.
: each$1(value, setEachItem);
function formatSingleValue(val) {
return encodeHTML(addCommas(val));
}
return !multipleSeries
? seriesName + colorEl
+ (name
? encodeHTML(name) + ': ' + formattedValue
: formattedValue
)
: colorEl + seriesName + formattedValue;
},
/**
* @return {boolean}
*/
isAnimationEnabled: function () {
if (env$1.node) {
return false;
}
restoreData: function () {
this.dataTask.dirty();
},
/**
* Use `data.mapDimension(coordDim, true)` instead.
* @deprecated
*/
coordDimToDataDim: function (coordDim) {
return this.getRawData().mapDimension(coordDim, true);
},
/**
* Get progressive rendering count each step
* @return {number}
*/
getProgressive: function () {
return this.get('progressive');
},
/**
* Get progressive rendering count each step
* @return {number}
*/
getProgressiveThreshold: function () {
return this.get('progressiveThreshold');
},
/**
* Get data indices for show tooltip content. See tooltip.
* @abstract
* @param {Array.<string>|string} dim
* @param {Array.<number>} value
* @param {module:echarts/coord/single/SingleAxis} baseAxis
* @return {Object} {dataIndices, nestestValue}.
*/
getAxisTooltipData: null,
/**
* See tooltip.
* @abstract
* @param {number} dataIndex
* @return {Array.<number>} Point of tooltip. null/undefined can be returned.
*/
getTooltipPosition: null,
/**
* @see {module:echarts/stream/Scheduler}
*/
pipeTask: null,
/**
* Convinient for override in extended class.
* @protected
* @type {Function}
*/
preventIncremental: null,
/**
* @public
* @readOnly
* @type {Object}
*/
pipelineContext: null
});
mixin(SeriesModel, dataFormatMixin);
mixin(SeriesModel, colorPaletteMixin);
/**
* MUST be called after `prepareSource` called
* Here we need to make auto series, especially for auto legend. But we
* do not modify series.name in option to avoid side effects.
*/
function autoSeriesName(seriesModel) {
// User specified name has higher priority, otherwise it may cause
// series can not be queried unexpectedly.
var name = seriesModel.name;
if (!isNameSpecified(seriesModel)) {
seriesModel.name = getSeriesAutoName(seriesModel) || name;
}
}
function getSeriesAutoName(seriesModel) {
var data = seriesModel.getRawData();
var dataDims = data.mapDimension('seriesName', true);
var nameArr = [];
each$1(dataDims, function (dataDim) {
var dimInfo = data.getDimensionInfo(dataDim);
dimInfo.displayName && nameArr.push(dimInfo.displayName);
});
return nameArr.join(' ');
}
function dataTaskCount(context) {
return context.model.getRawData().count();
}
function dataTaskReset(context) {
var seriesModel = context.model;
seriesModel.setData(seriesModel.getRawData().cloneShallow());
return dataTaskProgress;
}
// TODO refactor
function wrapData(data, seriesModel) {
each$1(data.CHANGABLE_METHODS, function (methodName) {
data.wrapMethod(methodName, curry(onDataSelfChange, seriesModel));
});
}
function onDataSelfChange(seriesModel) {
var task = getCurrentTask(seriesModel);
if (task) {
// Consider case: filter, selectRange
task.setOutputEnd(this.count());
}
}
function getCurrentTask(seriesModel) {
var scheduler = (seriesModel.ecModel || {}).scheduler;
var pipeline = scheduler && scheduler.getPipeline(seriesModel.uid);
if (pipeline) {
// When pipline finished, the currrentTask keep the last
// task (renderTask).
var task = pipeline.currentTask;
if (task) {
var agentStubMap = task.agentStubMap;
if (agentStubMap) {
task = agentStubMap.get(seriesModel.uid);
}
}
return task;
}
}
/**
* @type {string}
* @readOnly
*/
this.uid = getUID('viewComponent');
};
Component.prototype = {
constructor: Component,
dispose: function () {}
};
/**
* @return {string} If large mode changed, return string 'reset';
*/
var createRenderPlanner = function () {
var inner = makeInner();
function Chart() {
/**
* @type {module:zrender/container/Group}
* @readOnly
*/
this.group = new Group();
/**
* @type {string}
* @readOnly
*/
this.uid = getUID('viewChart');
this.renderTask = createTask({
plan: renderTaskPlan,
reset: renderTaskReset
});
this.renderTask.context = {view: this};
}
Chart.prototype = {
type: 'chart',
/**
* Init the chart.
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
*/
init: function (ecModel, api) {},
/**
* Render the chart.
* @param {module:echarts/model/Series} seriesModel
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
* @param {Object} payload
*/
render: function (seriesModel, ecModel, api, payload) {},
/**
* Highlight series or specified data item.
* @param {module:echarts/model/Series} seriesModel
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
* @param {Object} payload
*/
highlight: function (seriesModel, ecModel, api, payload) {
toggleHighlight(seriesModel.getData(), payload, 'emphasis');
},
/**
* Downplay series or specified data item.
* @param {module:echarts/model/Series} seriesModel
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
* @param {Object} payload
*/
downplay: function (seriesModel, ecModel, api, payload) {
toggleHighlight(seriesModel.getData(), payload, 'normal');
},
/**
* Remove self.
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
*/
remove: function (ecModel, api) {
this.group.removeAll();
},
/**
* Dispose self.
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
*/
dispose: function () {},
/**
* Rendering preparation in progressive mode.
* @param {module:echarts/model/Series} seriesModel
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
* @param {Object} payload
*/
incrementalPrepareRender: null,
/**
* Render in progressive mode.
* @param {module:echarts/model/Series} seriesModel
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
* @param {Object} payload
*/
incrementalRender: null,
/**
* Update transform directly.
* @param {module:echarts/model/Series} seriesModel
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
* @param {Object} payload
* @return {Object} {update: true}
*/
updateTransform: null
/**
* The view contains the given point.
* @interface
* @param {Array.<number>} point
* @return {boolean}
*/
// containPoint: function () {}
};
if (dataIndex != null) {
each$1(normalizeToArray(dataIndex), function (dataIdx) {
elSetState(data.getItemGraphicEl(dataIdx), state);
});
}
else {
data.eachItemGraphicEl(function (el) {
elSetState(el, state);
});
}
}
// Enable Chart.extend.
enableClassExtend(Chart, ['dispose']);
function renderTaskPlan(context) {
return renderPlanner(context.model);
}
function renderTaskReset(context) {
var seriesModel = context.model;
var ecModel = context.ecModel;
var api = context.api;
var payload = context.payload;
// ???! remove updateView updateVisual
var canProgressiveRender = seriesModel.pipelineContext.canProgressiveRender;
var view = context.view;
var updateMethod = payload && inner$5(payload).updateMethod;
var methodName = canProgressiveRender
? 'incrementalPrepareRender'
: (updateMethod && view[updateMethod])
? updateMethod
// `appendData` is also supported when data amount
// is less than progressive threshold.
: 'render';
return progressMethodMap[methodName];
}
var progressMethodMap = {
incrementalPrepareRender: {
progress: function (params, context) {
context.view.incrementalRender(
params, context.model, context.ecModel, context.api,
context.payload
);
}
},
render: {
// Put view.render in `progress` to support appendData. But in this case
// view.render should not be called in reset, otherwise it will be called
// twise. Use `forceFirstProgress` to make sure that view.render is called
// in any cases.
forceFirstProgress: true,
progress: function (params, context) {
context.view.render(
context.model, context.ecModel, context.api, context.payload
);
}
}
};
/**
* @public
* @param {(Function)} fn
* @param {number} [delay=0] Unit: ms.
* @param {boolean} [debounce=false]
* true: If call interval less than `delay`, only the last call works.
* false: If call interval less than `delay, call works on fixed rate.
* @return {(Function)} throttled fn.
*/
function throttle(fn, delay, debounce) {
var currCall;
var lastCall = 0;
var lastExec = 0;
var timer = null;
var diff;
var scope;
var args;
var debounceNextCall;
delay = delay || 0;
function exec() {
lastExec = (new Date()).getTime();
timer = null;
fn.apply(scope, args || []);
}
var cb = function () {
currCall = (new Date()).getTime();
scope = this;
args = arguments;
var thisDelay = debounceNextCall || delay;
var thisDebounce = debounceNextCall || debounce;
debounceNextCall = null;
diff = currCall - (thisDebounce ? lastCall : lastExec) - thisDelay;
clearTimeout(timer);
if (thisDebounce) {
timer = setTimeout(exec, thisDelay);
}
else {
if (diff >= 0) {
exec();
}
else {
timer = setTimeout(exec, -diff);
}
}
lastCall = currCall;
};
/**
* Clear throttle.
* @public
*/
cb.clear = function () {
if (timer) {
clearTimeout(timer);
timer = null;
}
};
/**
* Enable debounce once.
*/
cb.debounceNextCall = function (debounceDelay) {
debounceNextCall = debounceDelay;
};
return cb;
}
/**
* Create throttle method or update throttle rate.
*
* @example
* ComponentView.prototype.render = function () {
* ...
* throttle.createOrUpdate(
* this,
* '_dispatchAction',
* this.model.get('throttle'),
* 'fixRate'
* );
* };
* ComponentView.prototype.remove = function () {
* throttle.clear(this, '_dispatchAction');
* };
* ComponentView.prototype.dispose = function () {
* throttle.clear(this, '_dispatchAction');
* };
*
* @public
* @param {Object} obj
* @param {string} fnAttr
* @param {number} [rate]
* @param {string} [throttleType='fixRate'] 'fixRate' or 'debounce'
* @return {Function} throttled function.
*/
function createOrUpdate(obj, fnAttr, rate, throttleType) {
var fn = obj[fnAttr];
if (!fn) {
return;
}
fn = obj[fnAttr] = throttle(
originFn, rate, throttleType === 'debounce'
);
fn[ORIGIN_METHOD] = originFn;
fn[THROTTLE_TYPE] = throttleType;
fn[RATE] = rate;
}
return fn;
}
/**
* Clear throttle. Example see throttle.createOrUpdate.
*
* @public
* @param {Object} obj
* @param {string} fnAttr
*/
function clear(obj, fnAttr) {
var fn = obj[fnAttr];
if (fn && fn[ORIGIN_METHOD]) {
obj[fnAttr] = fn[ORIGIN_METHOD];
}
}
var seriesColor = {
createOnAllSeries: true,
performRawSeries: true,
reset: function (seriesModel, ecModel) {
var data = seriesModel.getData();
var colorAccessPath = (seriesModel.visualColorAccessPath ||
'itemStyle.color').split('.');
var color = seriesModel.get(colorAccessPath) // Set in itemStyle
|| seriesModel.getColorFromPalette(
// TODO series count changed.
seriesModel.name, null, ecModel.getSeriesCount()
); // Default color
var lang = {
toolbox: {
brush: {
title: {
rect: '矩形选择',
polygon: '圈选',
lineX: '横向选择',
lineY: '纵向选择',
keep: '保持选择',
clear: '清除选择'
}
},
dataView: {
title: '数据视图',
lang: ['数据视图', '关闭', '刷新']
},
dataZoom: {
title: {
zoom: '区域缩放',
back: '区域缩放还原'
}
},
magicType: {
title: {
line: '切换为折线图',
bar: '切换为柱状图',
stack: '切换为堆叠',
tiled: '切换为平铺'
}
},
restore: {
title: '还原'
},
saveAsImage: {
title: '保存为图片',
lang: ['右键另存为图片']
}
},
series: {
typeNames: {
pie: '饼图',
bar: '柱状图',
line: '折线图',
scatter: '散点图',
effectScatter: '涟漪散点图',
radar: '雷达图',
tree: '树图',
treemap: '矩形树图',
boxplot: '箱型图',
candlestick: 'K 线图',
k: 'K 线图',
heatmap: '热力图',
map: '地图',
parallel: '平行坐标图',
lines: '线图',
graph: '关系图',
sankey: '桑基图',
funnel: '漏斗图',
gauge: '仪表盘图',
pictorialBar: '象形柱图',
themeRiver: '主题河流图',
sunburst: '旭日图'
}
},
aria: {
general: {
withTitle: '这是一个关于“{title}”的图表。',
withoutTitle: '这是一个图表,'
},
series: {
single: {
prefix: '',
withName: '图表类型是{seriesType},表示{seriesName}。',
withoutName: '图表类型是{seriesType}。'
},
multiple: {
prefix: '它由{seriesCount}个图表系列组成。',
withName: '第{seriesId}个系列是一个表示{seriesName}的{seriesType},',
withoutName: '第{seriesId}个系列是一个{seriesType},',
separator: {
middle: ';',
end: '。'
}
}
},
data: {
allData: '其数据是——',
partialData: '其中,前{displayCnt}项是——',
withName: '{name}的数据是{value}',
withoutName: '{value}',
separator: {
middle: ',',
end: ''
}
}
}
};
var seriesCnt = 0;
ecModel.eachSeries(function (seriesModel, idx) {
++seriesCnt;
}, this);
var ariaLabel;
if (seriesCnt < 1) {
// No series, no aria label
return;
}
else {
var title = getTitle();
if (title) {
ariaLabel = replace(getConfig('general.withTitle'), {
title: title
});
}
else {
ariaLabel = getConfig('general.withoutTitle');
}
seriesLabel = replace(seriesLabel, {
seriesId: seriesModel.seriesIndex,
seriesName: seriesModel.get('name'),
seriesType: getSeriesTypeName(seriesModel.subType)
});
seriesLabels.push(seriesLabel);
}
});
ariaLabel += seriesLabels
.join(getConfig('series.multiple.separator.middle'))
+ getConfig('series.multiple.separator.end');
dom.setAttribute('aria-label', ariaLabel);
}
function getConfig(path) {
var userConfig = ariaModel.get(path);
if (userConfig == null) {
var pathArr = path.split('.');
var result = lang.aria;
for (var i = 0; i < pathArr.length; ++i) {
result = result[pathArr[i]];
}
return result;
}
else {
return userConfig;
}
}
function getTitle() {
var title = ecModel.getModel('title').option;
if (title && title.length) {
title = title[0];
}
return title && title.text;
}
function getSeriesTypeName(type) {
return lang.series.typeNames[type] || '自定义图';
}
};
arc.animateShape(true)
.when(1000, {
endAngle: PI$1 * 3 / 2
})
.start('circularInOut');
arc.animateShape(true)
.when(1000, {
startAngle: PI$1 * 3 / 2
})
.delay(300)
.start('circularInOut');
mask.setShape({
x: 0,
y: 0,
width: api.getWidth(),
height: api.getHeight()
});
};
group.resize();
return group;
};
/**
* @module echarts/stream/Scheduler
*/
/**
* @constructor
*/
function Scheduler(ecInstance, api, dataProcessorHandlers, visualHandlers) {
// this._pipelineMap = createHashMap();
this.ecInstance = ecInstance;
this.api = api;
this.unfinished;
/**
* @private
* @type {
* [handlerUID: string]: {
* seriesTaskMap?: {
* [seriesUID: string]: Task
* },
* overallTask?: Task
* }
* }
*/
this._stageTaskMap = createHashMap();
}
/**
* Current, progressive rendering starts from visual and layout.
* Always detect render mode in the same stage, avoiding that incorrect
* detection caused by data filtering.
* Caution:
* `updateStreamModes` use `seriesModel.getData()`.
*/
proto.updateStreamModes = function (seriesModel, view) {
var pipeline = this._pipelineMap.get(seriesModel.uid);
var data = seriesModel.getData();
var dataLen = data.count();
seriesModel.pipelineContext = pipeline.context = {
canProgressiveRender: canProgressiveRender,
large: large
};
};
pipelineMap.set(pipelineId, {
id: pipelineId,
head: null,
tail: null,
threshold: seriesModel.getProgressiveThreshold(),
progressiveEnabled: progressive
&& !(seriesModel.preventIncremental &&
seriesModel.preventIncremental()),
bockIndex: -1,
step: progressive || 700, // ??? Temporarily number
count: 0
});
proto.prepareStageTasks = function () {
var stageTaskMap = this._stageTaskMap;
var ecModel = this.ecInstance.getModel();
var api = this.api;
context.model = model;
context.ecModel = ecModel;
context.api = api;
renderTask.__block = !view.incrementalPrepareRender;
// opt
// opt.visualType: 'visual' or 'layout'
// opt.setDirty
proto.performVisualTasks = function (ecModel, payload, opt) {
performStageTasks(this, this._visualHandlers, ecModel, payload, opt);
};
if (overallTask) {
var overallNeedDirty;
var agentStubMap = overallTask.agentStubMap;
agentStubMap.each(function (stub) {
if (needSetDirty(opt, stub)) {
stub.dirty();
overallNeedDirty = true;
}
});
overallNeedDirty && overallTask.dirty();
updatePayload(overallTask, payload);
var performArgs = scheduler.getPerformArgs(overallTask, opt.block);
// Execute stubs firstly, which may set the overall task dirty,
// then execute the overall task. And stub will call
seriesModel.setData,
// which ensures that in the overallTask seriesModel.getData() will not
// return incorrect data.
agentStubMap.each(function (stub) {
stub.perform(performArgs);
});
unfinished |= overallTask.perform(performArgs);
}
else if (seriesTaskMap) {
seriesTaskMap.each(function (task, pipelineId) {
if (needSetDirty(opt, task)) {
task.dirty();
}
var performArgs = scheduler.getPerformArgs(task, opt.block);
performArgs.skip = !stageHandler.performRawSeries
&& ecModel.isSeriesFiltered(task.context.model);
updatePayload(task, payload);
unfinished |= task.perform(performArgs);
});
}
});
scheduler.unfinished |= unfinished;
}
ecModel.eachSeries(function (seriesModel) {
// Progress to the end for dataInit and dataRestore.
unfinished |= seriesModel.dataTask.perform();
});
this.unfinished |= unfinished;
};
proto.plan = function () {
// Travel pipelines, check block.
this._pipelineMap.each(function (pipeline) {
var task = pipeline.tail;
do {
if (task.__block) {
pipeline.bockIndex = task.__idxInPipeline;
break;
}
task = task.getUpstream();
}
while (task);
});
};
function create(seriesModel) {
var pipelineId = seriesModel.uid;
overallTask.context = {
ecModel: ecModel,
api: api,
overallReset: stageHandler.overallReset,
scheduler: scheduler
};
function createStub(seriesModel) {
var pipelineId = seriesModel.uid;
var stub = agentStubMap.get(pipelineId) || agentStubMap.set(pipelineId,
createTask(
{reset: stubReset, onDirty: stubOnDirty}
));
stub.context = {
model: seriesModel,
overallProgress: overallProgress,
isOverallFilter: isOverallFilter
};
stub.agent = overallTask;
stub.__block = overallProgress;
function overallTaskReset(context) {
context.overallReset(
context.ecModel, context.api, context.payload
);
}
function stubProgress() {
this.agent.dirty();
this.getDownstream().dirty();
}
function stubOnDirty() {
this.agent && this.agent.dirty();
}
function seriesTaskPlan(context) {
return context.plan && context.plan(
context.model, context.ecModel, context.api, context.payload
);
}
function seriesTaskReset(context) {
if (context.useClearVisual) {
context.data.clearAllVisual();
}
var resetDefines = context.resetDefines = normalizeToArray(context.reset(
context.model, context.ecModel, context.api, context.payload
));
if (resetDefines.length) {
return seriesTaskProgress;
}
}
function seriesTaskCount(context) {
return context.data.count();
}
stageHandler.uid = getUID('stageHandler');
visualType && (stageHandler.visualType = visualType);
return stageHandler;
};
/**
* Only some legacy stage handlers (usually in echarts extensions) are pure
function.
* To ensure that they can work normally, they should work in block mode, that is,
* they should not be started util the previous tasks finished. So they cause the
* progressive rendering disabled. We try to detect the series type, to narrow down
* the block range to only the series type they concern, but not all series.
*/
function detectSeriseType(legacyFunc) {
seriesType = null;
try {
// Assume there is no async when calling `eachSeriesByType`.
legacyFunc(ecModelMock, apiMock);
}
catch (e) {
}
return seriesType;
}
mockMethods(ecModelMock, GlobalModel);
mockMethods(apiMock, ExtensionAPI);
ecModelMock.eachSeriesByType = ecModelMock.eachRawSeriesByType = function (type) {
seriesType = type;
};
ecModelMock.eachComponent = function (cond) {
if (cond.mainType === 'series' && cond.subType) {
seriesType = cond.subType;
}
};
var lightTheme = {
color: colorAll,
colorLayer: [
['#37A2DA', '#ffd85c', '#fd7b5f'],
['#37A2DA', '#67E0E3', '#FFDB5C', '#ff9f7f', '#E062AE', '#9d96f5'],
['#37A2DA', '#32C5E9', '#9FE6B8', '#FFDB5C', '#ff9f7f', '#fb7293',
'#e7bcf3', '#8378EA', '#96BFFF'],
colorAll
]
};
var colorPalette =
['#dd6b66','#759aa0','#e69d87','#8dc1a9','#ea7e53','#eedd78','#73a373','#73b9bc','#
7289ab', '#91ca8c','#f49f42'];
var theme = {
color: colorPalette,
backgroundColor: '#333',
tooltip: {
axisPointer: {
lineStyle: {
color: contrastColor
},
crossStyle: {
color: contrastColor
}
}
},
legend: {
textStyle: {
color: contrastColor
}
},
textStyle: {
color: contrastColor
},
title: {
textStyle: {
color: contrastColor
}
},
toolbox: {
iconStyle: {
normal: {
borderColor: contrastColor
}
}
},
dataZoom: {
textStyle: {
color: contrastColor
}
},
visualMap: {
textStyle: {
color: contrastColor
}
},
timeline: {
lineStyle: {
color: contrastColor
},
itemStyle: {
normal: {
color: colorPalette[1]
}
},
label: {
normal: {
textStyle: {
color: contrastColor
}
}
},
controlStyle: {
normal: {
color: contrastColor,
borderColor: contrastColor
}
}
},
timeAxis: axisCommon(),
logAxis: axisCommon(),
valueAxis: axisCommon(),
categoryAxis: axisCommon(),
line: {
symbol: 'circle'
},
graph: {
color: colorPalette
},
gauge: {
title: {
textStyle: {
color: contrastColor
}
}
},
candlestick: {
itemStyle: {
normal: {
color: '#FD1050',
color0: '#0CF49B',
borderColor: '#FD1050',
borderColor0: '#0CF49B'
}
}
}
};
theme.categoryAxis.splitLine.show = false;
/*!
* ECharts, a free, powerful charting and visualization library.
*
* Copyright (c) 2017, Baidu Inc.
* All rights reserved.
*
* LICENSE
* https://github.com/ecomfe/echarts/blob/master/LICENSE.txt
*/
var dependencies = {
zrender: '4.0.3'
};
var TEST_FRAME_REMAIN_TIME = 1;
var PRIORITY = {
PROCESSOR: {
FILTER: PRIORITY_PROCESSOR_FILTER,
STATISTIC: PRIORITY_PROCESSOR_STATISTIC
},
VISUAL: {
LAYOUT: PRIORITY_VISUAL_LAYOUT,
GLOBAL: PRIORITY_VISUAL_GLOBAL,
CHART: PRIORITY_VISUAL_CHART,
COMPONENT: PRIORITY_VISUAL_COMPONENT,
BRUSH: PRIORITY_VISUAL_BRUSH
}
};
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');
mixin(MessageCenter, Eventful);
/**
* @module echarts~ECharts
*/
function ECharts(dom, theme$$1, opts) {
opts = opts || {};
/**
* Group id
* @type {string}
*/
this.group;
/**
* @type {HTMLElement}
* @private
*/
this._dom = dom;
/**
* @type {module:zrender/ZRender}
* @private
*/
var zr = this._zr = init$1(dom, {
renderer: opts.renderer || defaultRenderer,
devicePixelRatio: opts.devicePixelRatio,
width: opts.width,
height: opts.height
});
/**
* Expect 60 pfs.
* @type {Function}
* @private
*/
this._throttledZrFlush = throttle(bind(zr.flush, zr), 17);
/**
* @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;
}
sort(visualFuncs, prioritySortFunc);
sort(dataProcessorFuncs, prioritySortFunc);
/**
* @type {module:echarts/stream/Scheduler}
*/
this._scheduler = new Scheduler(this, api, dataProcessorFuncs, visualFuncs);
Eventful.call(this);
/**
* @type {module:echarts~MessageCenter}
* @private
*/
this._messageCenter = new MessageCenter();
echartsProto._onframe = function () {
if (this._disposed) {
return;
}
// 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);
updateStreamModes(this, ecModel);
/**
* @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) {
if (__DEV__) {
assert(!this[IN_MAIN_PROCESS], '`setOption` should not be called during
main process.');
}
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$$1 = this._theme;
var ecModel = this._model = new GlobalModel(null, null, theme$$1,
optionManager);
ecModel.scheduler = this._scheduler;
ecModel.init(null, null, theme$$1, 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);
this[OPTION_UPDATED] = false;
this[IN_MAIN_PROCESS] = false;
flushPendingActions.call(this, silent);
triggerUpdatedEvent.call(this, silent);
}
};
/**
* @DEPRECATED
*/
echartsProto.setTheme = function () {
console.log('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$1.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$1.svgSupported) {
return;
}
var zr = this._zr;
var list = zr.storage.getDisplayList();
// Stop animations
each$1(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;
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$1.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;
left *= dpr;
top *= dpr;
right *= dpr;
bottom *= dpr;
var width = right - left;
var height = bottom - top;
var targetCanvas = createCanvas();
targetCanvas.width = width;
targetCanvas.height = height;
var zr = init$1(targetCanvas);
/**
* 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 = 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 = curry(doConvertPixel, 'convertFromPixel');
if (__DEV__) {
console.warn(
'No coordinate system that supports ' + methodName + ' found by the
given finder.'
);
}
}
/**
* 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;
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;
if (__DEV__) {
if (!seriesModel) {
console.warn('There is no specified seires model');
}
}
/**
* 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 = {
ecModel.restoreData(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.
scheduler.performDataProcessorTasks(ecModel, payload);
// stackSeriesData(ecModel);
coordSysMgr.update(ecModel, api);
clearColorPalette(ecModel);
scheduler.performVisualTasks(ecModel, payload);
// Set background
var backgroundColor = ecModel.get('backgroundColor') || 'transparent';
// In IE8
if (!env$1.canvasSupported) {
var colorArr = parse(backgroundColor);
backgroundColor = stringify(colorArr, 'rgb');
if (colorArr[3] === 0) {
backgroundColor = 'transparent';
}
}
else {
zr.setBackgroundColor(backgroundColor);
}
performPostUpdateFuncs(ecModel, api);
/**
* @param {Object} payload
* @private
*/
updateTransform: function (payload) {
var ecModel = this._model;
var ecIns = this;
var api = this._api;
// ChartView.markUpdateMethod(payload, 'updateTransform');
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}
);
performPostUpdateFuncs(ecModel, this._api);
},
/**
* @param {Object} payload
* @private
*/
updateView: function (payload) {
var ecModel = this._model;
Chart.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});
performPostUpdateFuncs(ecModel, this._api);
},
/**
* @param {Object} payload
* @private
*/
updateVisual: function (payload) {
updateMethods.update.call(this, payload);
// 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);
// 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});
// performPostUpdateFuncs(ecModel, this._api);
}
};
function prepare(ecIns) {
var ecModel = ecIns._model;
var scheduler = ecIns._scheduler;
scheduler.restorePipelines(ecModel);
scheduler.prepareStageTasks();
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;
}
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) {
if (__DEV__) {
assert(!this[IN_MAIN_PROCESS], '`resize` should not be called during main
process.');
}
this._zr.resize(opts);
if (!ecModel) {
return;
}
this[IN_MAIN_PROCESS] = true;
flushPendingActions.call(this, silent);
triggerUpdatedEvent.call(this, silent);
};
/**
* 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]) {
if (__DEV__) {
console.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
*/
echartsProto.hideLoading = function () {
this._loadingFX && this._zr.remove(this._loadingFX);
this._loadingFX = null;
};
/**
* @param {Object} eventObj
* @return {Object}
*/
echartsProto.makeActionFromEvent = function (eventObj) {
var payload = 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;
}
if (opt.flush) {
this._zr.flush(true);
}
else if (opt.flush !== false && env$1.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);
};
this[IN_MAIN_PROCESS] = true;
// 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);
this[IN_MAIN_PROCESS] = false;
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');
if (__DEV__) {
assert(params.data && seriesModel);
}
seriesModel.appendData(params);
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;
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
? Component.getClass(classType.main, classType.sub)
: Chart.getClass(classType.sub);
if (__DEV__) {
assert(Clazz, classType.sub + ' does not exist.');
}
// /**
// * 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();
});
}
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;
unfinished |= renderTask.perform(scheduler.getPerformArgs(renderTask));
chartView.group.silent = !!seriesModel.get('silent');
updateZ(seriesModel, chartView);
updateBlend(seriesModel, chartView);
});
scheduler.unfinished |= unfinished;
// Add aria
aria(ecIns._zr.dom, ecModel);
}
var MOUSE_EVENT_NAMES = [
'click', 'dblclick', 'mouseover', 'mouseout', 'mousemove',
'mousedown', 'mouseup', 'globalout', 'contextmenu'
];
/**
* @private
*/
echartsProto._initEvents = function () {
each(MOUSE_EVENT_NAMES, function (eveName) {
this._zr.on(eveName, function (e) {
var ecModel = this.getModel();
var el = e.target;
var params;
if (params) {
params.event = e;
params.type = eveName;
this.trigger(eveName, params);
}
}, 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) {
if (__DEV__) {
console.warn('Instance ' + this.id + ' has been disposed');
}
return;
}
this._disposed = true;
delete instances[this.id];
};
mixin(ECharts, Eventful);
function updateHoverLayerStatus(zr, ecModel) {
var storage = zr.storage;
var elCount = 0;
storage.traverse(function (el) {
if (!el.isGroup) {
elCount++;
}
});
if (elCount > ecModel.get('hoverLayerThreshold') && !env$1.node) {
storage.traverse(function (el) {
if (!el.isGroup) {
// 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;
if (__DEV__) {
if (!env$1.canvasSupported && blendMode && blendMode !== 'source-over') {
console.warn('Only canvas support blendMode');
}
}
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 extend(new ExtensionAPI(ecInstance), {
// Inject methods
getCoordinateSystems: 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;
}
}
});
}
/**
* @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 = {};
function enableConnect(chart) {
var STATUS_PENDING = 0;
var STATUS_UPDATING = 1;
var STATUS_UPDATED = 2;
var STATUS_KEY = '__connectUpdateStatus';
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] Currently only 'canvas' is supported.
* @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)
*/
function init(dom, theme$$1, opts) {
if (__DEV__) {
// Check version
if ((version$1.replace('.', '') - 0) < (dependencies.zrender.replace('.',
'') - 0)) {
throw new Error(
'zrender/src ' + version$1
+ ' is too old for ECharts ' + version
+ '. Current version need ZRender '
+ dependencies.zrender + '+'
);
}
if (!dom) {
throw new Error('Initialize failed: invalid dom.');
}
}
if (__DEV__) {
if (isDom(dom)
&& dom.nodeName.toUpperCase() !== 'CANVAS'
&& (
(!dom.clientWidth && (!opts || opts.width == null))
|| (!dom.clientHeight && (!opts || opts.height == null))
)
) {
console.warn('Can\'t get dom width or height');
}
}
enableConnect(chart);
return chart;
}
/**
* @return {string|Array.<module:echarts~ECharts>} groupId
*/
function connect(groupId) {
// Is array of charts
if (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
*/
function disConnect(groupId) {
connectedGroups[groupId] = false;
}
/**
* @return {string} groupId
*/
var disconnect = disConnect;
/**
* Dispose a chart instance
* @param {module:echarts~ECharts|HTMLDomElement|string} chart
*/
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}
*/
function getInstanceByDom(dom) {
return instances[getAttribute(dom, DOM_ATTRIBUTE_KEY)];
}
/**
* @param {string} key
* @return {echarts~ECharts}
*/
function getInstanceById(key) {
return instances[key];
}
/**
* Register theme
*/
function registerTheme(name, theme$$1) {
themeStorage[name] = theme$$1;
}
/**
* Register option preprocessor
* @param {Function} preprocessorFunc
*/
function registerPreprocessor(preprocessorFunc) {
optionPreprocessorFuncs.push(preprocessorFunc);
}
/**
* @param {number} [priority=1000]
* @param {Object|Function} processor
*/
function registerProcessor(priority, processor) {
normalizeRegister(dataProcessorFuncs, priority, processor,
PRIORITY_PROCESSOR_FILTER);
}
/**
* Register postUpdater
* @param {Function} postUpdateFunc
*/
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
*/
function registerAction(actionInfo, eventName, action) {
if (typeof eventName === 'function') {
action = eventName;
eventName = '';
}
var actionType = isObject(actionInfo)
? actionInfo.type
: ([actionInfo, actionInfo = {
event: eventName
}][0]);
if (!actions[actionType]) {
actions[actionType] = {action: action, actionInfo: actionInfo};
}
eventActionMap[eventName] = actionType;
}
/**
* @param {string} type
* @param {*} CoordinateSystem
*/
function registerCoordinateSystem(type, CoordinateSystem$$1) {
CoordinateSystemManager.register(type, CoordinateSystem$$1);
}
/**
* Get dimensions of specified coordinate system.
* @param {string} type
* @return {Array.<string|Object>}
*/
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
*/
function registerLayout(priority, layoutTask) {
normalizeRegister(visualFuncs, priority, layoutTask, PRIORITY_VISUAL_LAYOUT,
'layout');
}
/**
* @param {number} [priority=3000]
* @param {module:echarts/stream/Task} visualTask
*/
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;
}
if (__DEV__) {
if (isNaN(priority) || priority == null) {
throw new Error('Illegal priority');
}
// Check duplicate
each(targetList, function (wrap) {
assert(wrap.__raw !== fn);
});
}
stageHandler.__prio = priority;
stageHandler.__raw = fn;
targetList.push(stageHandler);
return stageHandler;
}
/**
* @param {string} name
*/
function registerLoading(name, loadingFx) {
loadingEffects[name] = loadingFx;
}
/**
* @param {Object} opts
* @param {string} [superClass]
*/
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]
*/
function extendComponentView(opts/*, superClass*/) {
// var Clazz = ComponentView;
// if (superClass) {
// var classType = parseClassType(superClass);
// Clazz = ComponentView.getClass(classType.main, classType.sub, true);
// }
return Component.extend(opts);
}
/**
* @param {Object} opts
* @param {string} [superClass]
*/
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]
*/
function extendChartView(opts/*, superClass*/) {
// var Clazz = ChartView;
// if (superClass) {
// superClass = superClass.replace('series.', '');
// var classType = parseClassType(superClass);
// Clazz = ChartView.getClass(classType.main, true);
// }
return Chart.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);
* });
*/
function setCanvasCreator(creator) {
$override('createCanvas', creator);
}
/**
* @param {string} mapName
* @param {Object|string} geoJson
* @param {Object} [specialAreas]
*
* @example
* $.get('USA.json', function (geoJson) {
* echarts.registerMap('USA', geoJson);
* // Or
* echarts.registerMap('USA', {
* geoJson: geoJson,
* specialAreas: {}
* })
* });
*/
function registerMap(mapName, geoJson, specialAreas) {
if (geoJson.geoJson && !geoJson.features) {
specialAreas = geoJson.specialAreas;
geoJson = geoJson.geoJson;
}
if (typeof geoJson === 'string') {
geoJson = (typeof JSON !== 'undefined' && JSON.parse)
? JSON.parse(geoJson) : (new Function('return (' + geoJson + ');'))();
}
mapDataStores[mapName] = {
geoJson: geoJson,
specialAreas: specialAreas
};
}
/**
* @param {string} mapName
* @return {Object}
*/
function getMap(mapName) {
return mapDataStores[mapName];
}
registerVisual(PRIORITY_VISUAL_GLOBAL, seriesColor);
registerPreprocessor(backwardCompat);
registerProcessor(PRIORITY_PROCESSOR_STATISTIC, dataStack);
registerLoading('default', loadingDefault);
// Default actions
registerAction({
type: 'highlight',
event: 'highlight',
update: 'highlight'
}, noop);
registerAction({
type: 'downplay',
event: 'downplay',
update: 'downplay'
}, noop);
// Default theme
registerTheme('light', lightTheme);
registerTheme('dark', theme);
function defaultKeyGetter(item) {
return item;
}
/**
* @param {Array} oldArr
* @param {Array} newArr
* @param {Function} oldKeyGetter
* @param {Function} newKeyGetter
* @param {Object} [context] Can be visited by this.context in callback.
*/
function DataDiffer(oldArr, newArr, oldKeyGetter, newKeyGetter, context) {
this._old = oldArr;
this._new = newArr;
this.context = context;
}
DataDiffer.prototype = {
constructor: DataDiffer,
/**
* Callback function when add a data
*/
add: function (func) {
this._add = func;
return this;
},
/**
* Callback function when update a data
*/
update: function (func) {
this._update = func;
return this;
},
/**
* Callback function when remove a data
*/
remove: function (func) {
this._remove = func;
return this;
},
execute: function () {
var oldArr = this._old;
var newArr = this._new;
// idx can never be empty array here. see 'set null' logic below.
if (idx != null) {
// Consider there is duplicate key (for example, use dataItem.name
as key).
// We should make sure every item in newArr and oldArr can be
visited.
var len = idx.length;
if (len) {
len === 1 && (newDataIndexMap[key] = null);
idx = idx.unshift();
}
else {
newDataIndexMap[key] = null;
}
this._update && this._update(idx, i);
}
else {
this._remove && this._remove(i);
}
}
function summarizeDimensions(data) {
var summary = {};
var encode = summary.encode = {};
var notExtraCoordDimMap = createHashMap();
var defaultedLabel = [];
if (!dimItem.isExtraCoord) {
notExtraCoordDimMap.set(coordDim, 1);
// Use the last coord dim (and label friendly) as default label,
// because when dataset is used, it is hard to guess which
dimension
// can be value dimension. If both show x, y on label is not look
good,
// and conventionally y axis is focused more.
if (mayLabelDimType(dimItem.type)) {
defaultedLabel[0] = dimName;
}
}
}
summary.dataDimsOnCoord = dataDimsOnCoord;
summary.encodeFirstDimNotExtra = encodeFirstDimNotExtra;
encode.defaultedLabel = defaultedLabel;
encode.defaultedTooltip = defaultedTooltip;
return summary;
}
function getDimensionTypeByAxis(axisType) {
return axisType === 'category'
? 'ordinal'
: axisType === 'time'
? 'time'
: 'float';
}
function mayLabelDimType(dimType) {
// In most cases, ordinal and time do not suitable for label.
// Ordinal info can be displayed on axis. Time is too long.
return !(dimType === 'ordinal' || dimType === 'time');
}
// function findTheLastDimMayLabel(data) {
// // Get last value dim
// var dimensions = data.dimensions.slice();
// var valueType;
// var valueDim;
// while (dimensions.length && (
// valueDim = dimensions.pop(),
// valueType = data.getDimensionInfo(valueDim).type,
// valueType === 'ordinal' || valueType === 'time'
// )) {} // jshint ignore:line
// return valueDim;
// }
/**
* List for data storage
* @module echarts/data/List
*/
var dataCtors = {
'float': typeof globalObj.Float64Array === UNDEFINED
? Array : globalObj.Float64Array,
'int': typeof globalObj.Int32Array === UNDEFINED
? Array : globalObj.Int32Array,
// Ordinal data type can be string or int
'ordinal': Array,
'number': Array,
'time': Array
};
function getIndicesCtor(list) {
// The possible max value in this._indicies is always this._rawCount despite of
filtering.
return list._rawCount > 65535 ? CtorUint32Array : CtorUint16Array;
}
function cloneChunk(originalChunk) {
var Ctor = originalChunk.constructor;
// Only shallow clone is enough when Array.
return Ctor === Array ? originalChunk.slice() : new Ctor(originalChunk);
}
var TRANSFERABLE_PROPERTIES = [
'hasItemOption', '_nameList', '_idList', '_calculationInfo',
'_invertedIndicesMap',
'_rawData', '_rawExtent', '_chunkSize', '_chunkCount',
'_dimValueGetter', '_count', '_rawCount', '_nameDimIdx', '_idDimIdx'
];
function transferProperties(a, b) {
each$1(TRANSFERABLE_PROPERTIES.concat(b.__wrappedMethods || []), function
(propName) {
if (b.hasOwnProperty(propName)) {
a[propName] = b[propName];
}
});
a.__wrappedMethods = b.__wrappedMethods;
}
/**
* @constructor
* @alias module:echarts/data/List
*
* @param {Array.<string|Object>} dimensions
* For example, ['someDimName', {name: 'someDimName', type:
'someDimType'}, ...].
* Dimensions should be concrete names like x, y, z, lng, lat, angle, radius
* Spetial fields: {
* ordinalMeta: <module:echarts/data/OrdinalMeta>
* createInvertedIndices: <boolean>
* }
* @param {module:echarts/model/Model} hostModel
*/
var List = function (dimensions, hostModel) {
if (isString(dimensionInfo)) {
dimensionInfo = {name: dimensionInfo};
}
dimensionInfo.index = i;
if (dimensionInfo.createInvertedIndices) {
invertedIndicesMap[dimensionName] = [];
}
}
/**
* @readOnly
* @type {Array.<string>}
*/
this.dimensions = dimensionNames;
/**
* Infomation of each data dimension, like data type.
* @type {Object}
*/
this._dimensionInfos = dimensionInfos;
/**
* @type {module:echarts/model/Model}
*/
this.hostModel = hostModel;
/**
* @type {module:echarts/model/Model}
*/
this.dataType;
/**
* Indices stores the indices of data subset after filtered.
* This data subset will be used in chart.
* @type {Array.<number>}
* @readOnly
*/
this._indices = null;
this._count = 0;
this._rawCount = 0;
/**
* Data storage
* @type {Object.<key, Array.<TypedArray|Array>>}
* @private
*/
this._storage = {};
/**
* @type {Array.<string>}
*/
this._nameList = [];
/**
* @type {Array.<string>}
*/
this._idList = [];
/**
* Models of data option is stored sparse for optimizing memory cost
* @type {Array.<module:echarts/model/Model>}
* @private
*/
this._optionModels = [];
/**
* Global visual properties after visual coding
* @type {Object}
* @private
*/
this._visual = {};
/**
* Globel layout properties.
* @type {Object}
* @private
*/
this._layout = {};
/**
* Item visual properties after visual coding
* @type {Array.<Object>}
* @private
*/
this._itemVisuals = [];
/**
* Key: visual type, Value: boolean
* @type {Object}
* @readOnly
*/
this.hasItemVisual = {};
/**
* Item layout properties after layout
* @type {Array.<Object>}
* @private
*/
this._itemLayouts = [];
/**
* Graphic elemnents
* @type {Array.<module:zrender/Element>}
* @private
*/
this._graphicEls = [];
/**
* Max size of each chunk.
* @type {number}
* @private
*/
this._chunkSize = 1e5;
/**
* @type {number}
* @private
*/
this._chunkCount = 0;
/**
* @type {Array.<Array|Object>}
* @private
*/
this._rawData;
/**
* Raw extent will not be cloned, but only transfered.
* It will not be calculated util needed.
* key: dim,
* value: {end: number, extent: Array.<number>}
* @type {Object}
* @private
*/
this._rawExtent = {};
/**
* @type {Object}
* @private
*/
this._extent = {};
/**
* key: dim
* value: extent
* @type {Object}
* @private
*/
this._approximateExtent = {};
/**
* Cache summary info for fast visit. See "dimensionHelper".
* @type {Object}
* @private
*/
this._dimensionsSummary = summarizeDimensions(this);
/**
* @type {Object.<Array|TypedArray>}
* @private
*/
this._invertedIndicesMap = invertedIndicesMap;
/**
* @type {Object}
* @private
*/
this._calculationInfo = {};
};
listProto.type = 'list';
/**
* If each data item has it's own option
* @type {boolean}
*/
listProto.hasItemOption = true;
/**
* Get dimension name
* @param {string|number} dim
* Dimension can be concrete names like x, y, z, lng, lat, angle, radius
* Or a ordinal number. For example getDimensionInfo(0) will return 'x' or
'lng' or 'radius'
* @return {string} Concrete dim name.
*/
listProto.getDimension = function (dim) {
if (!isNaN(dim)) {
dim = this.dimensions[dim] || dim;
}
return dim;
};
/**
* Get type and calculation info of particular dimension
* @param {string|number} dim
* Dimension can be concrete names like x, y, z, lng, lat, angle, radius
* Or a ordinal number. For example getDimensionInfo(0) will return 'x' or
'lng' or 'radius'
*/
listProto.getDimensionInfo = function (dim) {
// Do not clone, because there may be categories in dimInfo.
return this._dimensionInfos[this.getDimension(dim)];
};
/**
* @return {Array.<string>} concrete dimension name list on coord.
*/
listProto.getDimensionsOnCoord = function () {
return this._dimensionsSummary.dataDimsOnCoord.slice();
};
/**
* @param {string} coordDim
* @param {number} [idx] A coordDim may map to more than one data dim.
* If idx is `true`, return a array of all mapped dims.
* If idx is not specified, return the first dim not extra.
* @return {string|Array.<string>} concrete data dim.
* If idx is number, and not found, return null/undefined.
* If idx is `true`, and not found, return empty array (always return
array).
*/
listProto.mapDimension = function (coordDim, idx) {
var dimensionsSummary = this._dimensionsSummary;
if (idx == null) {
return dimensionsSummary.encodeFirstDimNotExtra[coordDim];
}
/**
* Initialize from data
* @param {Array.<Object|number|Array>} data source or data or data provider.
* @param {Array.<string>} [nameLIst] The name of a datum is used on data diff and
* defualt label/tooltip.
* A name can be specified in encode.itemName,
* or dataItem.name (only for series option data),
* or provided in nameList from outside.
* @param {Function} [dimValueGetter] (dataItem, dimName, dataIndex, dimIndex) =>
number
*/
listProto.initData = function (data, nameList, dimValueGetter) {
if (__DEV__) {
if (!notProvider && (typeof data.getItem != 'function' || typeof data.count
!= 'function')) {
throw new Error('Inavlid data provider.');
}
}
this._rawData = data;
// Clear
this._storage = {};
this._indices = null;
this._idList = [];
this._nameRepeatCount = {};
if (!dimValueGetter) {
this.hasItemOption = false;
}
/**
* @readOnly
*/
this.defaultDimValueGetter = defaultDimValueGetters[
this._rawData.getSource().sourceFormat
];
this._initDataFromProvider(0, data.count());
listProto.getProvider = function () {
return this._rawData;
};
if (!storage[dim]) {
storage[dim] = [];
}
var resizeChunkArray = storage[dim][lastChunkIndex];
if (resizeChunkArray && resizeChunkArray.length < chunkSize) {
var newStore = new DataCtor(Math.min(end - lastChunkIndex * chunkSize,
chunkSize));
// The cost of the copy is probably inconsiderable
// within the initial chunkSize.
for (var j = 0; j < resizeChunkArray.length; j++) {
newStore[j] = resizeChunkArray[j];
}
storage[dim][lastChunkIndex] = newStore;
}
prepareInvertedIndex(this);
};
function prepareInvertedIndex(list) {
var invertedIndicesMap = list._invertedIndicesMap;
each$1(invertedIndicesMap, function (invertedIndices, dim) {
var dimInfo = list._dimensionInfos[dim];
// TODO refactor
listProto._getNameFromStore = function (rawIndex) {
var nameDimIdx = this._nameDimIdx;
if (nameDimIdx != null) {
var chunkSize = this._chunkSize;
var chunkIndex = Math.floor(rawIndex / chunkSize);
var chunkOffset = rawIndex % chunkSize;
var dim = this.dimensions[nameDimIdx];
var ordinalMeta = this._dimensionInfos[dim].ordinalMeta;
if (ordinalMeta) {
return ordinalMeta.categories[rawIndex];
}
else {
var chunk = this._storage[dim][chunkIndex];
return chunk && chunk[chunkOffset];
}
}
};
// TODO refactor
listProto._getIdFromStore = function (rawIndex) {
var idDimIdx = this._idDimIdx;
if (idDimIdx != null) {
var chunkSize = this._chunkSize;
var chunkIndex = Math.floor(rawIndex / chunkSize);
var chunkOffset = rawIndex % chunkSize;
var dim = this.dimensions[idDimIdx];
var ordinalMeta = this._dimensionInfos[dim].ordinalMeta;
if (ordinalMeta) {
return ordinalMeta.categories[rawIndex];
}
else {
var chunk = this._storage[dim][chunkIndex];
return chunk && chunk[chunkOffset];
}
}
};
/**
* @return {number}
*/
listProto.count = function () {
return this._count;
};
listProto.getIndices = function () {
if (this._indices) {
var Ctor = this._indices.constructor;
return new Ctor(this._indices.buffer, 0, this._count);
}
/**
* Get value. Return NaN if idx is out of range.
* @param {string} dim Dim must be concrete name.
* @param {number} idx
* @param {boolean} stack
* @return {number}
*/
listProto.get = function (dim, idx /*, stack */) {
if (!(idx >= 0 && idx < this._count)) {
return NaN;
}
var storage = this._storage;
if (!storage[dim]) {
// TODO Warn ?
return NaN;
}
idx = this.getRawIndex(idx);
return value;
};
/**
* @param {string} dim concrete dim
* @param {number} rawIndex
* @return {number|string}
*/
listProto.getByRawIndex = function (dim, rawIdx) {
if (!(rawIdx >= 0 && rawIdx < this._rawCount)) {
return NaN;
}
var dimStore = this._storage[dim];
if (!dimStore) {
// TODO Warn ?
return NaN;
}
/**
* FIXME Use `get` on chrome maybe slow(in filterSelf and selectRange).
* Hack a much simpler _getFast
* @private
*/
listProto._getFast = function (dim, rawIdx) {
var chunkIndex = Math.floor(rawIdx / this._chunkSize);
var chunkOffset = rawIdx % this._chunkSize;
var chunkStore = this._storage[dim][chunkIndex];
return chunkStore[chunkOffset];
};
/**
* Get value for multi dimensions.
* @param {Array.<string>} [dimensions] If ignored, using all dimensions.
* @param {number} idx
* @return {number}
*/
listProto.getValues = function (dimensions, idx /*, stack */) {
var values = [];
if (!isArray(dimensions)) {
// stack = idx;
idx = dimensions;
dimensions = this.dimensions;
}
return values;
};
/**
* If value is NaN. Inlcuding '-'
* Only check the coord dimensions.
* @param {string} dim
* @param {number} idx
* @return {number}
*/
listProto.hasValue = function (idx) {
var dataDimsOnCoord = this._dimensionsSummary.dataDimsOnCoord;
var dimensionInfos = this._dimensionInfos;
for (var i = 0, len = dataDimsOnCoord.length; i < len; i++) {
if (
// Ordinal type can be string or number
dimensionInfos[dataDimsOnCoord[i]].type !== 'ordinal'
// FIXME check ordinal when using index?
&& isNaN(this.get(dataDimsOnCoord[i], idx))
) {
return false;
}
}
return true;
};
/**
* Get extent of data in one dimension
* @param {string} dim
* @param {boolean} stack
*/
listProto.getDataExtent = function (dim /*, stack */) {
// Make sure use concrete dim as cache name.
dim = this.getDimension(dim);
var dimData = this._storage[dim];
var initialExtent = getInitialExtent();
if (!dimData) {
return initialExtent;
}
if (useRaw) {
return this._rawExtent[dim].slice();
}
dimExtent = this._extent[dim];
if (dimExtent) {
return dimExtent.slice();
}
dimExtent = initialExtent;
this._extent[dim] = dimExtent;
return dimExtent;
};
/**
* Optimize for the scenario that data is filtered by a given extent.
* Consider that if data amount is more than hundreds of thousand,
* extent calculation will cost more than 10ms and the cache will
* be erased because of the filtering.
*/
listProto.getApproximateExtent = function (dim /*, stack */) {
dim = this.getDimension(dim);
return this._approximateExtent[dim] || this.getDataExtent(dim /*, stack */);
};
/**
* @param {string} key
* @return {*}
*/
listProto.getCalculationInfo = function (key) {
return this._calculationInfo[key];
};
/**
* @param {string|Object} key or k-v object
* @param {*} [value]
*/
listProto.setCalculationInfo = function (key, value) {
isObject$4(key)
? extend(this._calculationInfo, key)
: (this._calculationInfo[key] = value);
};
/**
* Get sum of data in one dimension
* @param {string} dim
*/
listProto.getSum = function (dim /*, stack */) {
var dimData = this._storage[dim];
var sum = 0;
if (dimData) {
for (var i = 0, len = this.count(); i < len; i++) {
var value = this.get(dim, i /*, stack */);
if (!isNaN(value)) {
sum += value;
}
}
}
return sum;
};
// /**
// * Retreive the index with given value
// * @param {string} dim Concrete dimension.
// * @param {number} value
// * @return {number}
// */
// Currently incorrect: should return dataIndex but not rawIndex.
// Do not fix it until this method is to be used somewhere.
// FIXME Precision of float value
// listProto.indexOf = function (dim, value) {
// var storage = this._storage;
// var dimData = storage[dim];
// var chunkSize = this._chunkSize;
// if (dimData) {
// for (var i = 0, len = this.count(); i < len; i++) {
// var chunkIndex = Math.floor(i / chunkSize);
// var chunkOffset = i % chunkSize;
// if (dimData[chunkIndex][chunkOffset] === value) {
// return i;
// }
// }
// }
// return -1;
// };
/**
* Only support the dimension which inverted index created.
* Do not support other cases until required.
* @param {string} concrete dim
* @param {number|string} value
* @return {number} rawIndex
*/
listProto.rawIndexOf = function (dim, value) {
var invertedIndices = dim && this._invertedIndicesMap[dim];
if (__DEV__) {
if (!invertedIndices) {
throw new Error('Do not supported yet');
}
}
var rawIndex = invertedIndices[value];
if (rawIndex == null || isNaN(rawIndex)) {
return -1;
}
return rawIndex;
};
/**
* Retreive the index with given name
* @param {number} idx
* @param {number} name
* @return {number}
*/
listProto.indexOfName = function (name) {
for (var i = 0, len = this.count(); i < len; i++) {
if (this.getName(i) === name) {
return i;
}
}
return -1;
};
/**
* Retreive the index with given raw data index
* @param {number} idx
* @param {number} name
* @return {number}
*/
listProto.indexOfRawIndex = function (rawIndex) {
if (!this._indices) {
return rawIndex;
}
var left = 0;
var right = this._count - 1;
while (left <= right) {
var mid = (left + right) / 2 | 0;
if (indices[mid] < rawIndex) {
left = mid + 1;
}
else if (indices[mid] > rawIndex) {
right = mid - 1;
}
else {
return mid;
}
}
return -1;
};
/**
* Retreive the index of nearest value
* @param {string} dim
* @param {number} value
* @param {number} [maxDistance=Infinity]
* @return {Array.<number>} Considere multiple points has the same value.
*/
listProto.indicesOfNearest = function (dim, value, maxDistance) {
var storage = this._storage;
var dimData = storage[dim];
var nearestIndices = [];
if (!dimData) {
return nearestIndices;
}
if (maxDistance == null) {
maxDistance = Infinity;
}
/**
* Get raw data index
* @param {number} idx
* @return {number}
*/
listProto.getRawIndex = getRawIndexWithoutIndices;
function getRawIndexWithoutIndices(idx) {
return idx;
}
function getRawIndexWithIndices(idx) {
if (idx < this._count && idx >= 0) {
return this._indices[idx];
}
return -1;
}
/**
* Get raw data item
* @param {number} idx
* @return {number}
*/
listProto.getRawDataItem = function (idx) {
if (!this._rawData.persistent) {
var val = [];
for (var i = 0; i < this.dimensions.length; i++) {
var dim = this.dimensions[i];
val.push(this.get(dim, idx));
}
return val;
}
else {
return this._rawData.getItem(this.getRawIndex(idx));
}
};
/**
* @param {number} idx
* @param {boolean} [notDefaultIdx=false]
* @return {string}
*/
listProto.getName = function (idx) {
var rawIndex = this.getRawIndex(idx);
return this._nameList[rawIndex]
|| this._getNameFromStore(rawIndex)
|| '';
};
/**
* @param {number} idx
* @param {boolean} [notDefaultIdx=false]
* @return {string}
*/
listProto.getId = function (idx) {
return getId(this, this.getRawIndex(idx));
};
function normalizeDimensions(dimensions) {
if (!isArray(dimensions)) {
dimensions = [dimensions];
}
return dimensions;
}
/**
* Data iteration
* @param {string|Array.<string>}
* @param {Function} cb
* @param {*} [context=this]
*
* @example
* list.each('x', function (x, idx) {});
* list.each(['x', 'y'], function (x, y, idx) {});
* list.each(function (idx) {})
*/
listProto.each = function (dims, cb, context, contextCompat) {
'use strict';
if (!this._count) {
return;
}
if (__DEV__) {
validateDimensions(this, dims);
}
/**
* Data filter
* @param {string|Array.<string>}
* @param {Function} cb
* @param {*} [context=this]
*/
listProto.filterSelf = function (dimensions, cb, context, contextCompat) {
'use strict';
if (!this._count) {
return;
}
dimensions = map(
normalizeDimensions(dimensions), this.getDimension, this
);
if (__DEV__) {
validateDimensions(this, dimensions);
}
var offset = 0;
var dim0 = dimensions[0];
return this;
};
/**
* Select data in range. (For optimization of filter)
* (Manually inline code, support 5 million data filtering in data zoom.)
*/
listProto.selectRange = function (range /*, stack */) {
'use strict';
if (!this._count) {
return;
}
if (__DEV__) {
validateDimensions(this, dimensions);
}
var offset = 0;
var dim0 = dimensions[0];
return this;
};
/**
* Data mapping to a plain array
* @param {string|Array.<string>} [dimensions]
* @param {Function} cb
* @param {*} [context=this]
* @return {Array}
*/
listProto.mapArray = function (dimensions, cb, context, contextCompat) {
'use strict';
// Init storage
for (var i = 0; i < allDimensions.length; i++) {
var dim = allDimensions[i];
if (originalStorage[dim]) {
if (indexOf(excludeDimensions, dim) >= 0) {
storage[dim] = cloneDimStore(originalStorage[dim]);
rawExtent[dim] = getInitialExtent();
}
else {
// Direct reference for other dimensions
storage[dim] = originalStorage[dim];
}
}
}
return list;
}
function cloneDimStore(originalDimStore) {
var newDimStore = new Array(originalDimStore.length);
for (var j = 0; j < originalDimStore.length; j++) {
newDimStore[j] = cloneChunk(originalDimStore[j]);
}
return newDimStore;
}
function getInitialExtent() {
return [Infinity, -Infinity];
}
/**
* Data mapping to a new List with given dimensions
* @param {string|Array.<string>} dimensions
* @param {Function} cb
* @param {*} [context=this]
* @return {Array}
*/
listProto.map = function (dimensions, cb, context, contextCompat) {
'use strict';
if (__DEV__) {
validateDimensions(this, dimensions);
}
return list;
};
/**
* Large data down sampling on given dimension
* @param {string} dimension
* @param {number} rate
* @param {Function} sampleValue
* @param {Function} sampleIndex Sample index for name and id
*/
listProto.downSample = function (dimension, rate, sampleValue, sampleIndex) {
var list = cloneListForMapAndSample(this, [dimension]);
var targetStorage = list._storage;
var offset = 0;
for (var i = 0; i < len; i += frameSize) {
// Last frame
if (frameSize > len - i) {
frameSize = len - i;
frameValues.length = frameSize;
}
for (var k = 0; k < frameSize; k++) {
var dataIdx = this.getRawIndex(i + k);
var originalChunkIndex = Math.floor(dataIdx / chunkSize);
var originalChunkOffset = dataIdx % chunkSize;
frameValues[k] = dimStore[originalChunkIndex][originalChunkOffset];
}
var value = sampleValue(frameValues);
var sampleFrameIdx = this.getRawIndex(
Math.min(i + sampleIndex(frameValues, value) || 0, len - 1)
);
var sampleChunkIndex = Math.floor(sampleFrameIdx / chunkSize);
var sampleChunkOffset = sampleFrameIdx % chunkSize;
// Only write value on the filtered data
dimStore[sampleChunkIndex][sampleChunkOffset] = value;
newIndices[offset++] = sampleFrameIdx;
}
list._count = offset;
list._indices = newIndices;
list.getRawIndex = getRawIndexWithIndices;
return list;
};
/**
* Get model of one data item.
*
* @param {number} idx
*/
// FIXME Model proxy ?
listProto.getItemModel = function (idx) {
var hostModel = this.hostModel;
return new Model(this.getRawDataItem(idx), hostModel, hostModel &&
hostModel.ecModel);
};
/**
* Create a data differ
* @param {module:echarts/data/List} otherList
* @return {module:echarts/data/DataDiffer}
*/
listProto.diff = function (otherList) {
var thisList = this;
/**
* Set visual property
* @param {string|Object} key
* @param {*} [value]
*
* @example
* setVisual('color', color);
* setVisual({
* 'color': color
* });
*/
listProto.setVisual = function (key, val) {
if (isObject$4(key)) {
for (var name in key) {
if (key.hasOwnProperty(name)) {
this.setVisual(name, key[name]);
}
}
return;
}
this._visual = this._visual || {};
this._visual[key] = val;
};
/**
* Set layout property.
* @param {string|Object} key
* @param {*} [val]
*/
listProto.setLayout = function (key, val) {
if (isObject$4(key)) {
for (var name in key) {
if (key.hasOwnProperty(name)) {
this.setLayout(name, key[name]);
}
}
return;
}
this._layout[key] = val;
};
/**
* Get layout property.
* @param {string} key.
* @return {*}
*/
listProto.getLayout = function (key) {
return this._layout[key];
};
/**
* Get layout of single data item
* @param {number} idx
*/
listProto.getItemLayout = function (idx) {
return this._itemLayouts[idx];
};
/**
* Set layout of single data item
* @param {number} idx
* @param {Object} layout
* @param {boolean=} [merge=false]
*/
listProto.setItemLayout = function (idx, layout, merge$$1) {
this._itemLayouts[idx] = merge$$1
? extend(this._itemLayouts[idx] || {}, layout)
: layout;
};
/**
* Clear all layout of single data item
*/
listProto.clearItemLayouts = function () {
this._itemLayouts.length = 0;
};
/**
* Get visual property of single data item
* @param {number} idx
* @param {string} key
* @param {boolean} [ignoreParent=false]
*/
listProto.getItemVisual = function (idx, key, ignoreParent) {
var itemVisual = this._itemVisuals[idx];
var val = itemVisual && itemVisual[key];
if (val == null && !ignoreParent) {
// Use global visual property
return this.getVisual(key);
}
return val;
};
/**
* Set visual property of single data item
*
* @param {number} idx
* @param {string|Object} key
* @param {*} [value]
*
* @example
* setItemVisual(0, 'color', color);
* setItemVisual(0, {
* 'color': color
* });
*/
listProto.setItemVisual = function (idx, key, value) {
var itemVisual = this._itemVisuals[idx] || {};
var hasItemVisual = this.hasItemVisual;
this._itemVisuals[idx] = itemVisual;
if (isObject$4(key)) {
for (var name in key) {
if (key.hasOwnProperty(name)) {
itemVisual[name] = key[name];
hasItemVisual[name] = true;
}
}
return;
}
itemVisual[key] = value;
hasItemVisual[key] = true;
};
/**
* Clear itemVisuals and list visual.
*/
listProto.clearAllVisual = function () {
this._visual = {};
this._itemVisuals = [];
this.hasItemVisual = {};
};
if (el) {
// Add data index and series index for indexing the data by element
// Useful in tooltip
el.dataIndex = idx;
el.dataType = this.dataType;
el.seriesIndex = hostModel && hostModel.seriesIndex;
if (el.type === 'group') {
el.traverse(setItemDataAndSeriesIndex, el);
}
}
this._graphicEls[idx] = el;
};
/**
* @param {number} idx
* @return {module:zrender/Element}
*/
listProto.getItemGraphicEl = function (idx) {
return this._graphicEls[idx];
};
/**
* @param {Function} cb
* @param {*} context
*/
listProto.eachItemGraphicEl = function (cb, context) {
each$1(this._graphicEls, function (el, idx) {
if (el) {
cb && cb.call(context, el, idx);
}
});
};
/**
* Shallow clone a new list except visual and layout properties, and graph
elements.
* New list only change the indices.
*/
listProto.cloneShallow = function (list) {
if (!list) {
var dimensionInfoList = map(this.dimensions, this.getDimensionInfo, this);
list = new List(dimensionInfoList, this.hostModel);
}
// FIXME
list._storage = this._storage;
transferProperties(list, this);
list._extent = clone(this._extent);
list._approximateExtent = clone(this._approximateExtent);
return list;
};
/**
* Wrap some method to add more feature
* @param {string} methodName
* @param {Function} injectFunction
*/
listProto.wrapMethod = function (methodName, injectFunction) {
var originalMethod = this[methodName];
if (typeof originalMethod !== 'function') {
return;
}
this.__wrappedMethods = this.__wrappedMethods || [];
this.__wrappedMethods.push(methodName);
this[methodName] = function () {
var res = originalMethod.apply(this, arguments);
return injectFunction.apply(this, [res].concat(slice(arguments)));
};
};
// Methods that create a new list based on this list should be listed here.
// Notice that those method should `RETURN` the new list.
listProto.TRANSFERABLE_METHODS = ['cloneShallow', 'downSample', 'map'];
// Methods that change indices of this list should be listed here.
listProto.CHANGABLE_METHODS = ['filterSelf', 'selectRange'];
/**
* @deprecated
* Use `echarts/data/helper/createDimensions` instead.
*/
/**
* @see {module:echarts/test/ut/spec/data/completeDimensions}
*
* Complete the dimensions array, by user defined `dimension` and `encode`,
* and guessing from the data structure.
* If no 'value' dimension specified, the first no-named dimension will be
* named as 'value'.
*
* @param {Array.<string>} sysDims Necessary dimensions, like ['x', 'y'], which
* provides not only dim template, but also default order.
* properties: 'name', 'type', 'displayName'.
* `name` of each item provides default coord name.
* [{dimsDef: [string...]}, ...] dimsDef of sysDim item provides default dim
name, and
* provide dims count that the sysDim required.
* [{ordinalMeta}] can be specified.
* @param {module:echarts/data/Source|Array|Object} source or data (for compatibal
with pervious)
* @param {Object} [opt]
* @param {Array.<Object|string>} [opt.dimsDef] option.series.dimensions User
defined dimensions
* For example: ['asdf', {name, type}, ...].
* @param {Object|HashMap} [opt.encodeDef] option.series.encode {x: 2, y: [3, 1],
tooltip: [1, 2], label: 3}
* @param {string} [opt.generateCoord] Generate coord dim with the given name.
* If not specified, extra dim names will be:
* 'value', 'value0', 'value1', ...
* @param {number} [opt.generateCoordCount] By default, the generated dim name is
`generateCoord`.
* If `generateCoordCount` specified, the generated dim names will
be:
* `generateCoord` + 0, `generateCoord` + 1, ...
* can be Infinity, indicate that use all of the remain columns.
* @param {number} [opt.dimCount] If not specified, guess by the first data item.
* @param {number} [opt.encodeDefaulter] If not specified, auto find the next
available data dim.
* @return {Array.<Object>} [{
* name: string mandatory,
* displayName: string, the origin name in dimsDef, see source helper.
* If displayName given, the tooltip will displayed vertically.
* coordDim: string mandatory,
* coordDimIndex: number mandatory,
* type: string optional,
* otherDims: { never null/undefined
* tooltip: number optional,
* label: number optional,
* itemName: number optional,
* seriesName: number optional,
* },
* isExtraCoord: boolean true if coord is generated
* (not specified in encode and not series specified)
* other props ...
* }]
*/
function completeDimensions(sysDims, source, opt) {
if (!Source.isInstance(source)) {
source = Source.seriesDataToSource(source);
}
// Apply user defined dims (`name` and `type`) and init result.
for (var i = 0; i < dimCount; i++) {
var dimDefItem = dimsDef[i] = extend(
{}, isObject$1(dimsDef[i]) ? dimsDef[i] : {name: dimsDef[i]}
);
var userDimName = dimDefItem.name;
var resultItem = result[i] = {otherDims: {}};
// Name will be applied later for avoiding duplication.
if (userDimName != null && dataDimNameMap.get(userDimName) == null) {
// Only if `series.dimensions` is defined in option
// displayName, will be set, and dimension will be diplayed vertically
in
// tooltip by default.
resultItem.name = resultItem.displayName = userDimName;
dataDimNameMap.set(userDimName, i);
}
dimDefItem.type != null && (resultItem.type = dimDefItem.type);
dimDefItem.displayName != null && (resultItem.displayName =
dimDefItem.displayName);
}
// Apply templates.
each$1(dataDims, function (resultDimIdx, coordDimIndex) {
var resultItem = result[resultDimIdx];
applyDim(defaults(resultItem, sysDimItem), coordDim, coordDimIndex);
if (resultItem.name == null && sysDimItemDimsDef) {
resultItem.name = resultItem.displayName =
sysDimItemDimsDef[coordDimIndex];
}
// FIXME refactor, currently only used in case: {otherDims: {tooltip:
false}}
sysDimItemOtherDims && defaults(resultItem.otherDims,
sysDimItemOtherDims);
});
});
return result;
}
// ??? TODO
// Originally detect dimCount by data[0]. Should we
// optimize it to only by sysDims and dimensions and encode.
// So only necessary dims will be initialized.
// But
// (1) custom series should be considered. where other dims
// may be visited.
// (2) sometimes user need to calcualte bubble size or use visualMap
// on other dimensions besides coordSys needed.
// So, dims that is not used by system, should be shared in storage?
function getDimCount(source, sysDims, dimsDef, optDimCount) {
// Note that the result dimCount should not small than columns count
// of data, otherwise `dataDimNameMap` checking will be incorrect.
var dimCount = Math.max(
source.dimensionsDetectCount || 1,
sysDims.length,
dimsDef.length,
optDimCount || 0
);
each$1(sysDims, function (sysDimItem) {
var sysDimItemDimsDef = sysDimItem.dimsDef;
sysDimItemDimsDef && (dimCount = Math.max(dimCount,
sysDimItemDimsDef.length));
});
return dimCount;
}
/**
* Substitute `completeDimensions`.
* `completeDimensions` is to be deprecated.
*/
/**
* @param {module:echarts/data/Source|module:echarts/data/List} source or data.
* @param {Object|Array} [opt]
* @param {Array.<string|Object>} [opt.coordDimensions=[]]
* @param {number} [opt.dimensionsCount]
* @param {string} [opt.generateCoord]
* @param {string} [opt.generateCoordCount]
* @param {Array.<string|Object>} [opt.dimensionsDefine=source.dimensionsDefine]
Overwrite source define.
* @param {Object|HashMap} [opt.encodeDefine=source.encodeDefine] Overwrite source
define.
* @return {Array.<Object>} dimensionsInfo
*/
var createDimensions = function (source, opt) {
opt = opt || {};
return completeDimensions(opt.coordDimensions || [], source, {
dimsDef: opt.dimensionsDefine || source.dimensionsDefine,
encodeDef: opt.encodeDefine || source.encodeDefine,
dimCount: opt.dimensionsCount,
generateCoord: opt.generateCoord,
generateCoordCount: opt.generateCoordCount
});
};
/**
* Note that it is too complicated to support 3d stack by value
* (have to create two-dimension inverted index), so in 3d case
* we just support that stacked by index.
*
* @param {module:echarts/model/Series} seriesModel
* @param {Array.<string|Object>} dimensionInfoList The same as the input of
<module:echarts/data/List>.
* The input dimensionInfoList will be modified.
* @param {Object} [opt]
* @param {boolean} [opt.stackedCoordDimension=''] Specify a coord dimension if
needed.
* @param {boolean} [opt.byIndex=false]
* @return {Object} calculationInfo
* {
* stackedDimension: string
* stackedByDimension: string
* isStackedByIndex: boolean
* stackedOverDimension: string
* stackResultDimension: string
* }
*/
function enableDataStack(seriesModel, dimensionInfoList, opt) {
opt = opt || {};
var byIndex = opt.byIndex;
var stackedCoordDimension = opt.stackedCoordDimension;
dimensionInfoList.push({
name: stackResultDimension,
coordDim: stackedDimCoordDim,
coordDimIndex: stackedDimCoordIndex,
type: stackedDimType,
isExtraCoord: true,
isCalculationCoord: true
});
stackedDimCoordIndex++;
dimensionInfoList.push({
name: stackedOverDimension,
// This dimension contains stack base (generally, 0), so do not set it
as
// `stackedDimCoordDim` to avoid extent calculation, consider log
scale.
coordDim: stackedOverDimension,
coordDimIndex: stackedDimCoordIndex,
type: stackedDimType,
isExtraCoord: true,
isCalculationCoord: true
});
}
return {
stackedDimension: stackedDimInfo && stackedDimInfo.name,
stackedByDimension: stackedByDimInfo && stackedByDimInfo.name,
isStackedByIndex: byIndex,
stackedOverDimension: stackedOverDimension,
stackResultDimension: stackResultDimension
};
}
/**
* @param {module:echarts/data/List} data
* @param {string} stackedDim
* @param {string} [stackedByDim] If not input this parameter, check whether
* stacked by index.
*/
function isDimensionStacked(data, stackedDim, stackedByDim) {
return stackedDim
&& stackedDim === data.getCalculationInfo('stackedDimension')
&& (
stackedByDim != null
? stackedByDim === data.getCalculationInfo('stackedByDimension')
: data.getCalculationInfo('isStackedByIndex')
);
}
/**
* @param {module:echarts/data/Source|Array} source Or raw data.
* @param {module:echarts/model/Series} seriesModel
* @param {Object} [opt]
* @param {string} [opt.generateCoord]
*/
function createListFromArray(source, seriesModel, opt) {
opt = opt || {};
if (!Source.isInstance(source)) {
source = Source.seriesDataToSource(source);
}
var coordSysName = seriesModel.get('coordinateSystem');
var registeredCoordSys = CoordinateSystemManager.get(coordSysName);
var coordSysDimDefs;
if (coordSysDefine) {
coordSysDimDefs = map(coordSysDefine.coordSysDims, function (dim) {
var dimInfo = {name: dim};
var axisModel = coordSysDefine.axisMap.get(dim);
if (axisModel) {
var axisType = axisModel.get('type');
dimInfo.type = getDimensionTypeByAxis(axisType);
// dimInfo.stackable = isStackable(axisType);
}
return dimInfo;
});
}
if (!coordSysDimDefs) {
// Get dimensions from registered coordinate system
coordSysDimDefs = (registeredCoordSys && (
registeredCoordSys.getDimensionsInfo
? registeredCoordSys.getDimensionsInfo()
: registeredCoordSys.dimensions.slice()
)) || ['x', 'y'];
}
var firstCategoryDimIndex;
var hasNameEncode;
coordSysDefine && each$1(dimInfoList, function (dimInfo, dimIndex) {
var coordDim = dimInfo.coordDim;
var categoryAxisModel = coordSysDefine.categoryAxisMap.get(coordDim);
if (categoryAxisModel) {
if (firstCategoryDimIndex == null) {
firstCategoryDimIndex = dimIndex;
}
dimInfo.ordinalMeta = categoryAxisModel.getOrdinalMeta();
}
if (dimInfo.otherDims.itemName != null) {
hasNameEncode = true;
}
});
if (!hasNameEncode && firstCategoryDimIndex != null) {
dimInfoList[firstCategoryDimIndex].otherDims.itemName = 0;
}
list.setCalculationInfo(stackCalculationInfo);
var dimValueGetter = (firstCategoryDimIndex != null &&
isNeedCompleteOrdinalData(source))
? function (itemOpt, dimName, dataIndex, dimIndex) {
// Use dataIndex as ordinal value in categoryAxis
return dimIndex === firstCategoryDimIndex
? dataIndex
: this.defaultDimValueGetter(itemOpt, dimName, dataIndex,
dimIndex);
}
: null;
list.hasItemOption = false;
list.initData(source, null, dimValueGetter);
return list;
}
function isNeedCompleteOrdinalData(source) {
if (source.sourceFormat === SOURCE_FORMAT_ORIGINAL) {
var sampleItem = firstDataNotNull(source.data || []);
return sampleItem != null
&& !isArray(getDataItemValue(sampleItem));
}
}
function firstDataNotNull(data) {
var i = 0;
while (i < data.length && data[i] == null) {
i++;
}
return data[i];
}
/**
* // Scale class management
* @module echarts/scale/Scale
*/
/**
* @param {Object} [setting]
*/
function Scale(setting) {
this._setting = setting || {};
/**
* Extent
* @type {Array.<number>}
* @protected
*/
this._extent = [Infinity, -Infinity];
/**
* Step is calculated in adjustExtent
* @type {Array.<number>}
* @protected
*/
this._interval = 0;
this.init && this.init.apply(this, arguments);
}
/**
* Parse input val to valid inner number.
* @param {*} val
* @return {number}
*/
Scale.prototype.parse = function (val) {
// Notice: This would be a trap here, If the implementation
// of this method depends on extent, and this method is used
// before extent set (like in dataZoom), it would be wrong.
// Nevertheless, parse does not depend on extent generally.
return val;
};
/**
* Normalize value to linear [0, 1], return 0.5 if extent span is 0
* @param {number} val
* @return {number}
*/
Scale.prototype.normalize = function (val) {
var extent = this._extent;
if (extent[1] === extent[0]) {
return 0.5;
}
return (val - extent[0]) / (extent[1] - extent[0]);
};
/**
* Scale normalized value
* @param {number} val
* @return {number}
*/
Scale.prototype.scale = function (val) {
var extent = this._extent;
return val * (extent[1] - extent[0]) + extent[0];
};
/**
* Set extent from data
* @param {Array.<number>} other
*/
Scale.prototype.unionExtent = function (other) {
var extent = this._extent;
other[0] < extent[0] && (extent[0] = other[0]);
other[1] > extent[1] && (extent[1] = other[1]);
// not setExtent because in log axis it may transformed to power
// this.setExtent(extent[0], extent[1]);
};
/**
* Set extent from data
* @param {module:echarts/data/List} data
* @param {string} dim
*/
Scale.prototype.unionExtentFromData = function (data, dim) {
this.unionExtent(data.getApproximateExtent(dim));
};
/**
* Get extent
* @return {Array.<number>}
*/
Scale.prototype.getExtent = function () {
return this._extent.slice();
};
/**
* Set extent
* @param {number} start
* @param {number} end
*/
Scale.prototype.setExtent = function (start, end) {
var thisExtent = this._extent;
if (!isNaN(start)) {
thisExtent[0] = start;
}
if (!isNaN(end)) {
thisExtent[1] = end;
}
};
/**
* @return {Array.<string>}
*/
Scale.prototype.getTicksLabels = function () {
var labels = [];
var ticks = this.getTicks();
for (var i = 0; i < ticks.length; i++) {
labels.push(this.getLabel(ticks[i]));
}
return labels;
};
/**
* When axis extent depends on data and no data exists,
* axis ticks should not be drawn, which is named 'blank'.
*/
Scale.prototype.isBlank = function () {
return this._isBlank;
},
/**
* When axis extent depends on data and no data exists,
* axis ticks should not be drawn, which is named 'blank'.
*/
Scale.prototype.setBlank = function (isBlank) {
this._isBlank = isBlank;
};
enableClassExtend(Scale);
enableClassManagement(Scale, {
registerWhenExtend: true
});
/**
* @constructor
* @param {Object} [opt]
* @param {Object} [opt.categories=[]]
* @param {Object} [opt.needCollect=false]
* @param {Object} [opt.deduplication=false]
*/
function OrdinalMeta(opt) {
/**
* @readOnly
* @type {Array.<string>}
*/
this.categories = opt.categories || [];
/**
* @private
* @type {boolean}
*/
this._needCollect = opt.needCollect;
/**
* @private
* @type {boolean}
*/
this._deduplication = opt.deduplication;
/**
* @private
* @type {boolean}
*/
this._map;
}
/**
* @param {module:echarts/model/Model} axisModel
* @return {module:echarts/data/OrdinalMeta}
*/
OrdinalMeta.createByAxisModel = function (axisModel) {
var option = axisModel.option;
var data = option.data;
var categories = data && map(data, getName);
/**
* @param {string} category
* @return {number} ordinal
*/
proto$1.getOrdinal = function (category) {
return getOrCreateMap(this).get(category);
};
/**
* @param {*} category
* @return {number} The ordinal. If not found, return NaN.
*/
proto$1.parseAndCollect = function (category) {
var index;
var needCollect = this._needCollect;
// The value of category dim can be the index of the given category set.
// This feature is only supported when !needCollect, because we should
// consider a common case: a value is 2017, which is a number but is
// expected to be tread as a category. This case usually happen in dataset,
// where it happent to be no need of the index feature.
if (typeof category !== 'string' && !needCollect) {
return category;
}
if (index == null) {
if (needCollect) {
index = this.categories.length;
this.categories[index] = category;
map$$1.set(category, index);
}
else {
index = NaN;
}
}
return index;
};
function getName(obj) {
if (isObject$1(obj) && obj.value != null) {
return obj.value;
}
else {
return obj + '';
}
}
/**
* Linear continuous scale
* @module echarts/coord/scale/Ordinal
*
* http://en.wikipedia.org/wiki/Level_of_measurement
*/
type: 'ordinal',
/**
* @param {module:echarts/data/OrdianlMeta|Array.<string>} ordinalMeta
*/
init: function (ordinalMeta, extent) {
// Caution: Should not use instanceof, consider ec-extensions using
// import approach to get OrdinalMeta class.
if (!ordinalMeta || isArray(ordinalMeta)) {
ordinalMeta = new OrdinalMeta({categories: ordinalMeta});
}
this._ordinalMeta = ordinalMeta;
this._extent = extent || [0, ordinalMeta.categories.length - 1];
},
/**
* Normalize given rank or name to linear [0, 1]
* @param {number|string} [val]
* @return {number}
*/
normalize: function (val) {
return scaleProto.normalize.call(this, this.parse(val));
},
/**
* @return {Array}
*/
getTicks: function () {
var ticks = [];
var extent = this._extent;
var rank = extent[0];
return ticks;
},
/**
* Get item on rank n
* @param {number} n
* @return {string}
*/
getLabel: function (n) {
return this._ordinalMeta.categories[n];
},
/**
* @return {number}
*/
count: function () {
return this._extent[1] - this._extent[0] + 1;
},
/**
* @override
*/
unionExtentFromData: function (data, dim) {
this.unionExtent(data.getApproximateExtent(dim));
},
niceTicks: noop,
niceExtent: noop
});
/**
* @return {module:echarts/scale/Time}
*/
OrdinalScale.create = function () {
return new OrdinalScale();
};
/**
* For testable.
*/
/**
* @param {Array.<number>} extent Both extent[0] and extent[1] should be valid
number.
* Should be extent[0] < extent[1].
* @param {number} splitNumber splitNumber should be >= 1.
* @param {number} [minInterval]
* @param {number} [maxInterval]
* @return {Object} {interval, intervalPrecision, niceTickExtent}
*/
function intervalScaleNiceTicks(extent, splitNumber, minInterval, maxInterval) {
var result = {};
var span = extent[1] - extent[0];
fixExtent(niceTickExtent, extent);
return result;
}
/**
* @param {number} interval
* @return {number} interval precision
*/
function getIntervalPrecision(interval) {
// Tow more digital for tick.
return getPrecisionSafe(interval) + 2;
}
return ticks;
}
/**
* Interval scale
* @module echarts/scale/Interval
*/
/**
* @alias module:echarts/coord/scale/Interval
* @constructor
*/
var IntervalScale = Scale.extend({
type: 'interval',
_interval: 0,
_intervalPrecision: 2,
/**
* Set interval
*/
setInterval: function (interval) {
this._interval = interval;
// Dropped auto calculated niceExtent and use user setted extent
// We assume user wan't to set both interval, min, max to get a better
result
this._niceExtent = this._extent.slice();
this._intervalPrecision = getIntervalPrecision(interval);
},
/**
* @return {Array.<number>}
*/
getTicks: function () {
return intervalScaleGetTicks(
this._interval, this._extent, this._niceExtent, this._intervalPrecision
);
},
/**
* @return {Array.<string>}
*/
getTicksLabels: function () {
var labels = [];
var ticks = this.getTicks();
for (var i = 0; i < ticks.length; i++) {
labels.push(this.getLabel(ticks[i]));
}
return labels;
},
/**
* @param {number} data
* @param {Object} [opt]
* @param {number|string} [opt.precision] If 'auto', use nice presision.
* @param {boolean} [opt.pad] returns 1.50 but not 1.5 if precision is 2.
* @return {string}
*/
getLabel: function (data, opt) {
if (data == null) {
return '';
}
if (precision == null) {
precision = getPrecisionSafe(data) || 0;
}
else if (precision === 'auto') {
// Should be more precise then tick.
precision = this._intervalPrecision;
}
return addCommas(data);
},
/**
* Update interval and extent of intervals for nice ticks
*
* @param {number} [splitNumber = 5] Desired number of ticks
* @param {number} [minInterval]
* @param {number} [maxInterval]
*/
niceTicks: function (splitNumber, minInterval, maxInterval) {
splitNumber = splitNumber || 5;
var extent = this._extent;
var span = extent[1] - extent[0];
if (!isFinite(span)) {
return;
}
// User may set axis min 0 and data are all negative
// FIXME If it needs to reverse ?
if (span < 0) {
span = -span;
extent.reverse();
}
this._intervalPrecision = result.intervalPrecision;
this._interval = result.interval;
this._niceExtent = result.niceTickExtent;
},
/**
* Nice extent.
* @param {Object} opt
* @param {number} [opt.splitNumber = 5] Given approx tick number
* @param {boolean} [opt.fixMin=false]
* @param {boolean} [opt.fixMax=false]
* @param {boolean} [opt.minInterval]
* @param {boolean} [opt.maxInterval]
*/
niceExtent: function (opt) {
var extent = this._extent;
// If extent start and end are same, expand them
if (extent[0] === extent[1]) {
if (extent[0] !== 0) {
// Expand extent
var expandSize = extent[0];
// In the fowllowing case
// Axis has been fixed max 100
// Plus data are all 100 and axis extent are [100, 100].
// Extend to the both side will cause expanded max is larger than
fixed max.
// So only expand to the smaller side.
if (!opt.fixMax) {
extent[1] += expandSize / 2;
extent[0] -= expandSize / 2;
}
else {
extent[0] -= expandSize / 2;
}
}
else {
extent[1] = 1;
}
}
var span = extent[1] - extent[0];
// If there are no data and extent are [Infinity, -Infinity]
if (!isFinite(span)) {
extent[0] = 0;
extent[1] = 1;
}
if (!opt.fixMin) {
extent[0] = roundNumber(Math.floor(extent[0] / interval) * interval);
}
if (!opt.fixMax) {
extent[1] = roundNumber(Math.ceil(extent[1] / interval) * interval);
}
}
});
/**
* @return {module:echarts/scale/Time}
*/
IntervalScale.create = function () {
return new IntervalScale();
};
function getSeriesStackId(seriesModel) {
return seriesModel.get('stack') || STACK_PREFIX + seriesModel.seriesIndex;
}
function getAxisKey(axis) {
return axis.dim + axis.index;
}
/**
* @param {Object} opt
* @param {module:echarts/coord/Axis} opt.axis Only support category axis
currently.
* @param {number} opt.count Positive interger.
* @param {number} [opt.barWidth]
* @param {number} [opt.barMaxWidth]
* @param {number} [opt.barGap]
* @param {number} [opt.barCategoryGap]
* @return {Object} {width, offset, offsetCenter} If axis.type is not 'category',
return undefined.
*/
function getLayoutOnAxis(opt, api) {
var params = [];
var baseAxis = opt.axis;
var axisKey = 'axis0';
return {
bandWidth: bandWidth,
barWidth: barWidth,
barMaxWidth: barMaxWidth,
barGap: barGap,
barCategoryGap: barCategoryGap,
axisKey: getAxisKey(baseAxis),
stackId: getSeriesStackId(seriesModel)
};
});
if (!stacks[stackId]) {
columnsOnAxis.autoWidthCount++;
}
stacks[stackId] = stacks[stackId] || {
width: 0,
maxWidth: 0
};
// TODO
var barWidth = seriesInfo.barWidth;
if (barWidth && !stacks[stackId].width) {
// See #6312, do not restrict width.
stacks[stackId].width = barWidth;
barWidth = Math.min(columnsOnAxis.remainedWidth, barWidth);
columnsOnAxis.remainedWidth -= barWidth;
}
result[coordSysName] = {};
var widthSum = 0;
var lastColumn;
each$1(stacks, function (column, idx) {
if (!column.width) {
column.width = autoWidth;
}
lastColumn = column;
widthSum += column.width * (1 + barGapPercent);
});
if (lastColumn) {
widthSum -= lastColumn.width * barGapPercent;
}
return result;
}
/**
* @param {string} seriesType
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
*/
function layout(seriesType, ecModel, api) {
if (isNaN(value)) {
continue;
}
var x;
var y;
var width;
var height;
if (isValueAxisH) {
var coord = cartesian.dataToPoint([value, baseValue]);
x = baseCoord;
y = coord[1] + columnOffset;
width = coord[0] - valueAxisStart;
height = columnWidth;
data.setItemLayout(idx, {
x: x,
y: y,
width: width,
height: height
});
}
}, this);
}
// FIXME 公用?
var bisect = function (a, x, lo, hi) {
while (lo < hi) {
var mid = lo + hi >>> 1;
if (a[mid][1] < x) {
lo = mid + 1;
}
else {
hi = mid;
}
}
return lo;
};
/**
* @alias module:echarts/coord/scale/Time
* @constructor
*/
var TimeScale = IntervalScale.extend({
type: 'time',
/**
* @override
*/
getLabel: function (val) {
var stepLvl = this._stepLvl;
/**
* @override
*/
niceExtent: function (opt) {
var extent = this._extent;
// If extent start and end are same, expand them
if (extent[0] === extent[1]) {
// Expand extent
extent[0] -= ONE_DAY;
extent[1] += ONE_DAY;
}
// If there are no data and extent are [Infinity, -Infinity]
if (extent[1] === -Infinity && extent[0] === Infinity) {
var d = new Date();
extent[1] = +new Date(d.getFullYear(), d.getMonth(), d.getDate());
extent[0] = extent[1] - ONE_DAY;
}
if (!opt.fixMin) {
extent[0] = round$1(mathFloor(extent[0] / interval) * interval);
}
if (!opt.fixMax) {
extent[1] = round$1(mathCeil(extent[1] / interval) * interval);
}
},
/**
* @override
*/
niceTicks: function (approxTickNum, minInterval, maxInterval) {
approxTickNum = approxTickNum || 10;
interval *= yearStep;
}
fixExtent(niceExtent, extent);
this._stepLvl = level;
// Interval will be used in getTicks
this._interval = interval;
this._niceExtent = niceExtent;
},
// Steps from d3
var scaleLevels = [
// Format interval
['hh:mm:ss', ONE_SECOND], // 1s
['hh:mm:ss', ONE_SECOND * 5], // 5s
['hh:mm:ss', ONE_SECOND * 10], // 10s
['hh:mm:ss', ONE_SECOND * 15], // 15s
['hh:mm:ss', ONE_SECOND * 30], // 30s
['hh:mm\nMM-dd', ONE_MINUTE], // 1m
['hh:mm\nMM-dd', ONE_MINUTE * 5], // 5m
['hh:mm\nMM-dd', ONE_MINUTE * 10], // 10m
['hh:mm\nMM-dd', ONE_MINUTE * 15], // 15m
['hh:mm\nMM-dd', ONE_MINUTE * 30], // 30m
['hh:mm\nMM-dd', ONE_HOUR], // 1h
['hh:mm\nMM-dd', ONE_HOUR * 2], // 2h
['hh:mm\nMM-dd', ONE_HOUR * 6], // 6h
['hh:mm\nMM-dd', ONE_HOUR * 12], // 12h
['MM-dd\nyyyy', ONE_DAY], // 1d
['MM-dd\nyyyy', ONE_DAY * 2], // 2d
['MM-dd\nyyyy', ONE_DAY * 3], // 3d
['MM-dd\nyyyy', ONE_DAY * 4], // 4d
['MM-dd\nyyyy', ONE_DAY * 5], // 5d
['MM-dd\nyyyy', ONE_DAY * 6], // 6d
['week', ONE_DAY * 7], // 7d
['MM-dd\nyyyy', ONE_DAY * 10], // 10d
['week', ONE_DAY * 14], // 2w
['week', ONE_DAY * 21], // 3w
['month', ONE_DAY * 31], // 1M
['week', ONE_DAY * 42], // 6w
['month', ONE_DAY * 62], // 2M
['week', ONE_DAY * 42], // 10w
['quarter', ONE_DAY * 380 / 4], // 3M
['month', ONE_DAY * 31 * 4], // 4M
['month', ONE_DAY * 31 * 5], // 5M
['half-year', ONE_DAY * 380 / 2], // 6M
['month', ONE_DAY * 31 * 8], // 8M
['month', ONE_DAY * 31 * 10], // 10M
['year', ONE_DAY * 380] // 1Y
];
/**
* @param {module:echarts/model/Model}
* @return {module:echarts/scale/Time}
*/
TimeScale.create = function (model) {
return new TimeScale({useUTC: model.ecModel.get('useUTC')});
};
/**
* Log scale
* @module echarts/scale/Log
*/
type: 'log',
base: 10,
$constructor: function () {
Scale.apply(this, arguments);
this._originalScale = new IntervalScale();
},
/**
* @return {Array.<number>}
*/
getTicks: function () {
var originalScale = this._originalScale;
var extent = this._extent;
var originalExtent = originalScale.getExtent();
// Fix #4158
powVal = (val === extent[0] && originalScale.__fixMin)
? fixRoundingError(powVal, originalExtent[0])
: powVal;
powVal = (val === extent[1] && originalScale.__fixMax)
? fixRoundingError(powVal, originalExtent[1])
: powVal;
return powVal;
}, this);
},
/**
* @param {number} val
* @return {string}
*/
getLabel: intervalScaleProto$1.getLabel,
/**
* @param {number} val
* @return {number}
*/
scale: function (val) {
val = scaleProto$1.scale.call(this, val);
return mathPow$1(this.base, val);
},
/**
* @param {number} start
* @param {number} end
*/
setExtent: function (start, end) {
var base = this.base;
start = mathLog(start) / mathLog(base);
end = mathLog(end) / mathLog(base);
intervalScaleProto$1.setExtent.call(this, start, end);
},
/**
* @return {number} end
*/
getExtent: function () {
var base = this.base;
var extent = scaleProto$1.getExtent.call(this);
extent[0] = mathPow$1(base, extent[0]);
extent[1] = mathPow$1(base, extent[1]);
// Fix #4158
var originalScale = this._originalScale;
var originalExtent = originalScale.getExtent();
originalScale.__fixMin && (extent[0] = fixRoundingError(extent[0],
originalExtent[0]));
originalScale.__fixMax && (extent[1] = fixRoundingError(extent[1],
originalExtent[1]));
return extent;
},
/**
* @param {Array.<number>} extent
*/
unionExtent: function (extent) {
this._originalScale.unionExtent(extent);
/**
* @override
*/
unionExtentFromData: function (data, dim) {
// TODO
// filter value that <= 0
this.unionExtent(data.getApproximateExtent(dim));
},
/**
* Update interval and extent of intervals for nice ticks
* @param {number} [approxTickNum = 10] Given approx tick number
*/
niceTicks: function (approxTickNum) {
approxTickNum = approxTickNum || 10;
var extent = this._extent;
var span = extent[1] - extent[0];
if (span === Infinity || span <= 0) {
return;
}
var niceExtent = [
round$1(mathCeil$1(extent[0] / interval) * interval),
round$1(mathFloor$1(extent[1] / interval) * interval)
];
this._interval = interval;
this._niceExtent = niceExtent;
},
/**
* Nice extent.
* @override
*/
niceExtent: function (opt) {
intervalScaleProto$1.niceExtent.call(this, opt);
});
LogScale.create = function () {
return new LogScale();
};
/**
* Get axis scale extent before niced.
* Item of returned array can only be number (including Infinity and NaN).
*/
function getScaleExtent(scale, model) {
var scaleType = scale.type;
var axisDataLen;
var boundaryGap;
var span;
if (scaleType === 'ordinal') {
axisDataLen = model.getCategories().length;
}
else {
boundaryGap = model.get('boundaryGap');
if (!isArray(boundaryGap)) {
boundaryGap = [boundaryGap || 0, boundaryGap || 0];
}
if (typeof boundaryGap[0] === 'boolean') {
if (__DEV__) {
console.warn('Boolean type for boundaryGap is only '
+ 'allowed for ordinal axis. Please use string in '
+ 'percentage instead, e.g., "20%". Currently, '
+ 'boundaryGap is set to be 0.');
}
boundaryGap = [0, 0];
}
boundaryGap[0] = parsePercent$1(boundaryGap[0], 1);
boundaryGap[1] = parsePercent$1(boundaryGap[1], 1);
span = (originalExtent[1] - originalExtent[0])
|| Math.abs(originalExtent[0]);
}
// Notice: When min/max is not set (that is, when there are null/undefined,
// which is the most common case), these cases should be ensured:
// (1) For 'ordinal', show all axis.data.
// (2) For others:
// + `boundaryGap` is applied (if min/max set, boundaryGap is
// disabled).
// + If `needCrossZero`, min/max should be zero, otherwise, min/max should
// be the result that originalExtent enlarged by boundaryGap.
// (3) If no data, it should be ensured that `scale.setBlank` is set.
// FIXME
// (1) When min/max is 'dataMin' or 'dataMax', should boundaryGap be able to
used?
// (2) When `needCrossZero` and all data is positive/negative, should it be
ensured
// that the results processed by boundaryGap are positive/negative?
if (min == null) {
min = scaleType === 'ordinal'
? (axisDataLen ? 0 : NaN)
: originalExtent[0] - boundaryGap[0] * span;
}
if (max == null) {
max = scaleType === 'ordinal'
? (axisDataLen ? axisDataLen - 1 : NaN)
: originalExtent[1] + boundaryGap[1] * span;
}
scale.setBlank(eqNaN(min) || eqNaN(max));
// If bars are placed on a base axis of type time or interval account for axis
boundary overflow and current axis
// is base axis
// FIXME
// (1) Consider support value axis, where below zero and axis `onZero` should
be handled properly.
// (2) Refactor the logic with `barGrid`. Is it not need to
`calBarWidthAndOffset` twice with different extent?
// Should not depend on series type `bar`?
// (3) Fix that might overlap when using dataZoom.
// (4) Consider other chart types using `barGrid`?
// See #6728, #4862, `test/bar-overflow-time-plot.html`
var ecModel = model.ecModel;
if (ecModel && (scaleType === 'time' /*|| scaleType === 'interval' */)) {
var barSeriesModels = [];
var isBaseAxisAndHasBarSeries;
if (isBaseAxisAndHasBarSeries) {
// Adjust axis min and max to account for overflow
var adjustedScale = adjustScaleForOverflow(min, max, model,
barSeriesModels);
min = adjustedScale.min;
max = adjustedScale.max;
}
}
// Get bars on current base axis and calculate min and max overflow
var baseAxisKey = model.axis.dim + model.axis.index;
var barsOnCurrentAxis = barWidthAndOffset[baseAxisKey];
if (barsOnCurrentAxis === undefined) {
return {min: min, max: max};
}
// If some one specified the min, max. And the default calculated interval
// is not good enough. He can specify the interval. It is often appeared
// in angle axis with angle 0 - 360. Interval calculated in interval scale is
hard
// to be 60.
// FIXME
var interval = model.get('interval');
if (interval != null) {
scale.setInterval && scale.setInterval(interval);
}
}
/**
* @param {module:echarts/model/Model} model
* @param {string} [axisType] Default retrieve from model.type
* @return {module:echarts/scale/*}
*/
function createScaleByModel(model, axisType) {
axisType = axisType || model.get('type');
if (axisType) {
switch (axisType) {
// Buildin scale
case 'category':
return new OrdinalScale(
model.getOrdinalMeta
? model.getOrdinalMeta()
: model.getCategories(),
[Infinity, -Infinity]
);
case 'value':
return new IntervalScale();
// Extended scale, like time and log
default:
return (Scale.getClass(axisType) || IntervalScale).create(model);
}
}
}
/**
* Check if the axis corss 0
*/
function ifAxisCrossZero(axis) {
var dataExtent = axis.scale.getExtent();
var min = dataExtent[0];
var max = dataExtent[1];
return !((min > 0 && max > 0) || (min < 0 && max < 0));
}
/**
* @param {Array.<number>} tickCoords In axis self coordinate.
* @param {Array.<string>} labels
* @param {string} font
* @param {number} axisRotate 0: towards right horizontally, clock-wise is
negative.
* @param {number} [labelRotate=0] 0: towards right horizontally, clock-wise is
negative.
* @return {number}
*/
function getAxisLabelInterval(tickCoords, labels, font, axisRotate, labelRotate) {
var textSpaceTakenRect;
var autoLabelInterval = 0;
var accumulatedLabelInterval = 0;
var rotation = (axisRotate - labelRotate) / 180 * Math.PI;
var step = 1;
if (labels.length > 40) {
// Simple optimization for large amount of labels
step = Math.floor(labels.length / 40);
}
// Magic number
rect.width *= 1.3;
rect.height *= 1.3;
if (!textSpaceTakenRect) {
textSpaceTakenRect = rect.clone();
}
// There is no space for current label;
else if (textSpaceTakenRect.intersect(rect)) {
accumulatedLabelInterval++;
autoLabelInterval = Math.max(autoLabelInterval,
accumulatedLabelInterval);
}
else {
textSpaceTakenRect.union(rect);
// Reset
accumulatedLabelInterval = 0;
}
}
if (autoLabelInterval === 0 && step > 1) {
return step;
}
return (autoLabelInterval + 1) * step - 1;
}
/**
* @param {Object} axis
* @param {Function} labelFormatter
* @return {Array.<string>}
*/
function getFormattedLabels(axis, labelFormatter) {
var scale = axis.scale;
var labels = scale.getTicksLabels();
var ticks = scale.getTicks();
if (typeof labelFormatter === 'string') {
labelFormatter = (function (tpl) {
return function (val) {
return tpl.replace('{value}', val != null ? val : '');
};
})(labelFormatter);
// Consider empty array
return map(labels, labelFormatter);
}
else if (typeof labelFormatter === 'function') {
return map(ticks, function (tick, idx) {
return labelFormatter(
getAxisRawValue(axis, tick),
idx
);
}, this);
}
else {
return labels;
}
}
var axisModelCommonMixin = {
/**
* Format labels
* @return {Array.<string>}
*/
getFormattedLabels: function () {
return getFormattedLabels(
this.axis,
this.get('axisLabel.formatter')
);
},
/**
* @param {boolean} origin
* @return {number|string} min value or 'dataMin' or null/undefined (means
auto) or NaN
*/
getMin: function (origin) {
var option = this.option;
var min = (!origin && option.rangeStart != null)
? option.rangeStart : option.min;
if (this.axis
&& min != null
&& min !== 'dataMin'
&& typeof min !== 'function'
&& !eqNaN(min)
) {
min = this.axis.scale.parse(min);
}
return min;
},
/**
* @param {boolean} origin
* @return {number|string} max value or 'dataMax' or null/undefined (means
auto) or NaN
*/
getMax: function (origin) {
var option = this.option;
var max = (!origin && option.rangeEnd != null)
? option.rangeEnd : option.max;
if (this.axis
&& max != null
&& max !== 'dataMax'
&& typeof max !== 'function'
&& !eqNaN(max)
) {
max = this.axis.scale.parse(max);
}
return max;
},
/**
* @return {boolean}
*/
getNeedCrossZero: function () {
var option = this.option;
return (option.rangeStart != null || option.rangeEnd != null)
? false : !option.scale;
},
/**
* Should be implemented by each axis model if necessary.
* @return {module:echarts/model/Component} coordinate system model
*/
getCoordSysModel: noop,
/**
* @param {number} rangeStart Can only be finite number or null/undefined or
NaN.
* @param {number} rangeEnd Can only be finite number or null/undefined or NaN.
*/
setRange: function (rangeStart, rangeEnd) {
this.option.rangeStart = rangeStart;
this.option.rangeEnd = rangeEnd;
},
/**
* Reset range
*/
resetRange: function () {
// rangeStart and rangeEnd is readonly.
this.option.rangeStart = this.option.rangeEnd = null;
}
};
// Symbol factory
/**
* Triangle shape
* @inner
*/
var Triangle = extendShape({
type: 'triangle',
shape: {
cx: 0,
cy: 0,
width: 0,
height: 0
},
buildPath: function (path, shape) {
var cx = shape.cx;
var cy = shape.cy;
var width = shape.width / 2;
var height = shape.height / 2;
path.moveTo(cx, cy - height);
path.lineTo(cx + width, cy + height);
path.lineTo(cx - width, cy + height);
path.closePath();
}
});
/**
* Diamond shape
* @inner
*/
var Diamond = extendShape({
type: 'diamond',
shape: {
cx: 0,
cy: 0,
width: 0,
height: 0
},
buildPath: function (path, shape) {
var cx = shape.cx;
var cy = shape.cy;
var width = shape.width / 2;
var height = shape.height / 2;
path.moveTo(cx, cy - height);
path.lineTo(cx + width, cy);
path.lineTo(cx, cy + height);
path.lineTo(cx - width, cy);
path.closePath();
}
});
/**
* Pin shape
* @inner
*/
var Pin = extendShape({
type: 'pin',
shape: {
// x, y on the cusp
x: 0,
y: 0,
width: 0,
height: 0
},
path.arc(
x, cy, r,
Math.PI - angle,
Math.PI * 2 + angle
);
path.bezierCurveTo(
x + dx - tanX * cpLen, cy + dy + tanY * cpLen,
x, y - cpLen2,
x, y
);
path.bezierCurveTo(
x, y - cpLen2,
x - dx + tanX * cpLen, cy + dy + tanY * cpLen,
x - dx, cy + dy
);
path.closePath();
}
});
/**
* Arrow shape
* @inner
*/
var Arrow = extendShape({
type: 'arrow',
shape: {
x: 0,
y: 0,
width: 0,
height: 0
},
/**
* Map of path contructors
* @type {Object.<string, module:zrender/graphic/Path>}
*/
var symbolCtors = {
line: Line,
rect: Rect,
roundRect: Rect,
square: Rect,
circle: Circle,
diamond: Diamond,
pin: Pin,
arrow: Arrow,
triangle: Triangle
};
var symbolShapeMakers = {
line: function (x, y, w, h, shape) {
// FIXME
shape.x1 = x;
shape.y1 = y + h / 2;
shape.x2 = x + w;
shape.y2 = y + h / 2;
},
type: 'symbol',
shape: {
symbolType: '',
x: 0,
y: 0,
width: 0,
height: 0
},
beforeBrush: function () {
var style = this.style;
var shape = this.shape;
// FIXME
if (shape.symbolType === 'pin' && style.textPosition === 'inside') {
style.textPosition = ['50%', '40%'];
style.textAlign = 'center';
style.textVerticalAlign = 'middle';
}
},
// Provide setColor helper method to avoid determine if set the fill or stroke
outside
function symbolPathSetColor(color, innerColor) {
if (this.type !== 'image') {
var symbolStyle = this.style;
var symbolShape = this.shape;
if (symbolShape && symbolShape.symbolType === 'line') {
symbolStyle.stroke = color;
}
else if (this.__isEmptyBrush) {
symbolStyle.stroke = color;
symbolStyle.fill = innerColor || '#fff';
}
else {
// FIXME 判断图形默认是填充还是描边,使用 onlyStroke ?
symbolStyle.fill && (symbolStyle.fill = color);
symbolStyle.stroke && (symbolStyle.stroke = color);
}
this.dirty(false);
}
}
/**
* Create a symbol element with given symbol configuration: shape, x, y, width,
height, color
* @param {string} symbolType
* @param {number} x
* @param {number} y
* @param {number} w
* @param {number} h
* @param {string} color
* @param {boolean} [keepAspect=false] whether to keep the ratio of w/h,
* for path and image only.
*/
function createSymbol(symbolType, x, y, w, h, color, keepAspect) {
// TODO Support image object, DynamicImage.
if (symbolType.indexOf('image://') === 0) {
symbolPath = makeImage(
symbolType.slice(8),
new BoundingRect(x, y, w, h),
keepAspect ? 'center' : 'cover'
);
}
else if (symbolType.indexOf('path://') === 0) {
symbolPath = makePath(
symbolType.slice(7),
{},
new BoundingRect(x, y, w, h),
keepAspect ? 'center' : 'cover'
);
}
else {
symbolPath = new SymbolClz({
shape: {
symbolType: symbolType,
x: x,
y: y,
width: w,
height: h
}
});
}
symbolPath.__isEmptyBrush = isEmpty;
symbolPath.setColor = symbolPathSetColor;
symbolPath.setColor(color);
return symbolPath;
}
var dataStack$1 = {
isDimensionStacked: isDimensionStacked,
enableDataStack: enableDataStack
};
/**
* Create scale
* @param {Array.<number>} dataExtent
* @param {Object|module:echarts/Model} option
*/
function createScale(dataExtent, option) {
var axisModel = option;
if (!Model.isInstance(option)) {
axisModel = new Model(option);
mixin(axisModel, axisModelCommonMixin);
}
niceScaleExtent(scale, axisModel);
return scale;
}
/**
* Mixin common methods to axis model,
*
* Inlcude methods
* `getFormattedLabels() => Array.<string>`
* `getCategories() => Array.<string>`
* `getMin(origin: boolean) => number`
* `getMax(origin: boolean) => number`
* `getNeedCrossZero() => boolean`
* `setRange(start: number, end: number)`
* `resetRange()`
*/
function mixinAxisModelCommonMethods(Model$$1) {
mixin(Model$$1, axisModelCommonMixin);
}
function isAroundEqual$1(a, b) {
return Math.abs(a - b) < EPSILON$3;
}
function contain$1(points, x, y) {
var w = 0;
var p = points[0];
if (!p) {
return false;
}
// Close polygon
var p0 = points[0];
if (!isAroundEqual$1(p[0], p0[0]) || !isAroundEqual$1(p[1], p0[1])) {
w += windingLine(p[0], p[1], p0[0], p0[1], x, y);
}
return w !== 0;
}
/**
* @module echarts/coord/geo/Region
*/
/**
* @param {string} name
* @param {Array} geometries
* @param {Array.<number>} cp
*/
function Region(name, geometries, cp) {
/**
* @type {string}
* @readOnly
*/
this.name = name;
/**
* @type {Array.<Array>}
* @readOnly
*/
this.geometries = geometries;
if (!cp) {
var rect = this.getBoundingRect();
cp = [
rect.x + rect.width / 2,
rect.y + rect.height / 2
];
}
else {
cp = [cp[0], cp[1]];
}
/**
* @type {Array.<number>}
*/
this.center = cp;
}
Region.prototype = {
constructor: Region,
properties: null,
/**
* @return {module:zrender/core/BoundingRect}
*/
getBoundingRect: function () {
var rect = this._rect;
if (rect) {
return rect;
}
/**
* @param {<Array.<number>} coord
* @return {boolean}
*/
contain: function (coord) {
var rect = this.getBoundingRect();
var geometries = this.geometries;
if (!rect.contain(coord[0], coord[1])) {
return false;
}
loopGeo: for (var i = 0, len$$1 = geometries.length; i < len$$1; i++) {
// Only support polygon.
if (geometries[i].type !== 'polygon') {
continue;
}
var exterior = geometries[i].exterior;
var interiors = geometries[i].interiors;
if (contain$1(exterior, coord[0], coord[1])) {
// Not in the region if point is in the hole.
for (var k = 0; k < (interiors ? interiors.length : 0); k++) {
if (contain$1(interiors[k])) {
continue loopGeo;
}
}
return true;
}
}
return false;
},
/**
* Parse and decode geo json
* @module echarts/coord/geo/parseGeoJson
*/
function decode(json) {
if (!json.UTF8Encoding) {
return json;
}
var encodeScale = json.UTF8Scale;
if (encodeScale == null) {
encodeScale = 1024;
}
prevX = x;
prevY = y;
// Dequantize
result.push([x / encodeScale, y / encodeScale]);
}
return result;
}
/**
* @alias module:echarts/coord/geo/parseGeoJson
* @param {Object} geoJson
* @return {module:zrender/container/Group}
*/
var parseGeoJson$1 = function (geoJson) {
decode(geoJson);
/**
* Axis dimension. Such as 'x', 'y', 'z', 'angle', 'radius'
* @type {string}
*/
this.dim = dim;
/**
* Axis scale
* @type {module:echarts/coord/scale/*}
*/
this.scale = scale;
/**
* @type {Array.<number>}
* @private
*/
this._extent = extent || [0, 0];
/**
* @type {boolean}
*/
this.inverse = false;
/**
* Usually true when axis has a ordinal scale
* @type {boolean}
*/
this.onBand = false;
/**
* @private
* @type {number}
*/
this._labelInterval;
};
Axis.prototype = {
constructor: Axis,
/**
* If axis extent contain given coord
* @param {number} coord
* @return {boolean}
*/
contain: function (coord) {
var extent = this._extent;
var min = Math.min(extent[0], extent[1]);
var max = Math.max(extent[0], extent[1]);
return coord >= min && coord <= max;
},
/**
* If axis extent contain given data
* @param {number} data
* @return {boolean}
*/
containData: function (data) {
return this.contain(this.dataToCoord(data));
},
/**
* Get coord extent.
* @return {Array.<number>}
*/
getExtent: function () {
return this._extent.slice();
},
/**
* Get precision used for formatting
* @param {Array.<number>} [dataExtent]
* @return {number}
*/
getPixelPrecision: function (dataExtent) {
return getPixelPrecision(
dataExtent || this.scale.getExtent(),
this._extent
);
},
/**
* Set coord extent
* @param {number} start
* @param {number} end
*/
setExtent: function (start, end) {
var extent = this._extent;
extent[0] = start;
extent[1] = end;
},
/**
* Convert data to coord. Data is the rank if it has an ordinal scale
* @param {number} data
* @param {boolean} clamp
* @return {number}
*/
dataToCoord: function (data, clamp) {
var extent = this._extent;
var scale = this.scale;
data = scale.normalize(data);
/**
* Convert coord to data. Data is the rank if it has an ordinal scale
* @param {number} coord
* @param {boolean} clamp
* @return {number}
*/
coordToData: function (coord, clamp) {
var extent = this._extent;
var scale = this.scale;
return this.scale.scale(t);
},
/**
* Convert pixel point to data in axis
* @param {Array.<number>} point
* @param {boolean} clamp
* @return {number} data
*/
pointToData: function (point, clamp) {
// Should be implemented in derived class if necessary.
},
/**
* @return {Array.<number>}
*/
getTicksCoords: function (alignWithLabel) {
if (this.onBand && !alignWithLabel) {
var bands = this.getBands();
var coords = [];
for (var i = 0; i < bands.length; i++) {
coords.push(bands[i][0]);
}
if (bands[i - 1]) {
coords.push(bands[i - 1][1]);
}
return coords;
}
else {
return map(this.scale.getTicks(), this.dataToCoord, this);
}
},
/**
* Coords of labels are on the ticks or on the middle of bands
* @return {Array.<number>}
*/
getLabelsCoords: function () {
return map(this.scale.getTicks(), this.dataToCoord, this);
},
/**
* Get bands.
*
* If axis has labels [1, 2, 3, 4]. Bands on the axis are
* |---1---|---2---|---3---|---4---|.
*
* @return {Array}
*/
// FIXME Situation when labels is on ticks
getBands: function () {
var extent = this.getExtent();
var bands = [];
var len = this.scale.count();
var start = extent[0];
var end = extent[1];
var span = end - start;
/**
* @abstract
* @return {boolean} Is horizontal
*/
isHorizontal: null,
/**
* @abstract
* @return {number} Get axis rotate, by degree.
*/
getRotate: null,
/**
* Get interval of the axis label.
* To get precise result, at least one of `getRotate` and `isHorizontal`
* should be implemented.
* @return {number}
*/
getLabelInterval: function () {
var labelInterval = this._labelInterval;
if (!labelInterval) {
var axisModel = this.model;
var labelModel = axisModel.getModel('axisLabel');
labelInterval = labelModel.get('interval');
};
/**
* Do not mount those modules on 'src/echarts' for better tree shaking.
*/
type: 'dataset',
/**
* @protected
*/
defaultOption: {
// 'row', 'column'
seriesLayoutBy: SERIES_LAYOUT_BY_COLUMN,
dimensions: null,
source: null
},
optionUpdated: function () {
detectSourceFormat(this);
}
});
extendComponentView({type: 'dataset'});
SeriesModel.extend({
type: 'series.line',
defaultOption: {
zlevel: 0, // 一级层叠
z: 2, // 二级层叠
coordinateSystem: 'cartesian2d',
legendHoverLink: true,
hoverAnimation: true,
// stack: null
// xAxisIndex: 0,
// yAxisIndex: 0,
// polarIndex: 0,
label: {
position: 'top'
},
// itemStyle: {
// },
lineStyle: {
width: 2,
type: 'solid'
},
// areaStyle: {
// origin of areaStyle. Valid values:
// `'auto'/null/undefined`: from axisLine to data
// `'start'`: from min to data
// `'end'`: from data to max
// origin: 'auto'
// },
// false, 'start', 'end', 'middle'
step: false,
// 是否连接断点
connectNulls: false,
animationEasing: 'linear',
// Disable progressive
progressive: 0,
hoverLayerThreshold: Infinity
}
});
/**
* @param {module:echarts/data/List} data
* @param {number} dataIndex
* @return {string} label string. Not null/undefined
*/
function getDefaultLabel(data, dataIndex) {
var labelDims = data.mapDimension('defaultedLabel', true);
var len = labelDims.length;
/**
* @module echarts/chart/helper/Symbol
*/
function getScale(symbolSize) {
return [symbolSize[0] / 2, symbolSize[1] / 2];
}
/**
* @constructor
* @alias {module:echarts/chart/helper/Symbol}
* @param {module:echarts/data/List} data
* @param {number} idx
* @extends {module:zrender/graphic/Group}
*/
function SymbolClz$1(data, idx, seriesScope) {
Group.call(this);
this.updateData(data, idx, seriesScope);
}
symbolPath.attr({
z2: 100,
culling: true,
scale: getScale(symbolSize)
});
// Rewrite drift method
symbolPath.drift = driftSymbol;
this._symbolType = symbolType;
this.add(symbolPath);
};
/**
* Stop animation
* @param {boolean} toLastFrame
*/
symbolProto.stopSymbolAnimation = function (toLastFrame) {
this.childAt(0).stopAnimation(toLastFrame);
};
/**
* FIXME:
* Caution: This method breaks the encapsulation of this module,
* but it indeed brings convenience. So do not use the method
* unless you detailedly know all the implements of `Symbol`,
* especially animation.
*
* Get symbol path element.
*/
symbolProto.getSymbolPath = function () {
return this.childAt(0);
};
/**
* Get scale(aka, current symbol size).
* Including the change caused by animation
*/
symbolProto.getScale = function () {
return this.childAt(0).scale;
};
/**
* Highlight symbol
*/
symbolProto.highlight = function () {
this.childAt(0).trigger('emphasis');
};
/**
* Downplay symbol
*/
symbolProto.downplay = function () {
this.childAt(0).trigger('normal');
};
/**
* @param {number} zlevel
* @param {number} z
*/
symbolProto.setZ = function (zlevel, z) {
var symbolPath = this.childAt(0);
symbolPath.zlevel = zlevel;
symbolPath.z = z;
};
/**
* Update symbol properties
* @param {module:echarts/data/List} data
* @param {number} idx
* @param {Object} [seriesScope]
* @param {Object} [seriesScope.itemStyle]
* @param {Object} [seriesScope.hoverItemStyle]
* @param {Object} [seriesScope.symbolRotate]
* @param {Object} [seriesScope.symbolOffset]
* @param {module:echarts/model/Model} [seriesScope.labelModel]
* @param {module:echarts/model/Model} [seriesScope.hoverLabelModel]
* @param {boolean} [seriesScope.hoverAnimation]
* @param {Object} [seriesScope.cursorStyle]
* @param {module:echarts/model/Model} [seriesScope.itemModel]
* @param {string} [seriesScope.symbolInnerColor]
* @param {Object} [seriesScope.fadeIn=false]
*/
symbolProto.updateData = function (data, idx, seriesScope) {
this.silent = false;
if (isInit) {
this._createSymbol(symbolType, data, idx, symbolSize);
}
else {
var symbolPath = this.childAt(0);
symbolPath.silent = false;
updateProps(symbolPath, {
scale: getScale(symbolSize)
}, seriesModel, idx);
}
if (isInit) {
var symbolPath = this.childAt(0);
var fadeIn = seriesScope && seriesScope.fadeIn;
this._seriesModel = seriesModel;
};
/**
* @param {module:echarts/data/List} data
* @param {number} idx
* @param {Array.<number>} symbolSize
* @param {Object} [seriesScope]
*/
symbolProto._updateCommon = function (data, idx, symbolSize, seriesScope) {
var symbolPath = this.childAt(0);
var seriesModel = data.hostModel;
var color = data.getItemVisual(idx, 'color');
// Reset style
if (symbolPath.type !== 'image') {
symbolPath.useStyle({
strokeNoScale: true
});
}
if (!seriesScope || data.hasItemOption) {
var itemModel = (seriesScope && seriesScope.itemModel)
? seriesScope.itemModel : data.getItemModel(idx);
symbolRotate = itemModel.getShallow('symbolRotate');
symbolOffset = itemModel.getShallow('symbolOffset');
labelModel = itemModel.getModel(normalLabelAccessPath);
hoverLabelModel = itemModel.getModel(emphasisLabelAccessPath);
hoverAnimation = itemModel.getShallow('hoverAnimation');
cursorStyle = itemModel.getShallow('cursor');
}
else {
hoverItemStyle = extend({}, hoverItemStyle);
}
if (symbolOffset) {
symbolPath.attr('position', [
parsePercent$1(symbolOffset[0], symbolSize[0]),
parsePercent$1(symbolOffset[1], symbolSize[1])
]);
}
symbolPath.setStyle(itemStyle);
setLabelStyle(
elStyle, hoverItemStyle, labelModel, hoverLabelModel,
{
labelFetcher: seriesModel,
labelDataIndex: idx,
defaultText: getLabelDefaultText,
isRectText: true,
autoColor: color
}
);
symbolPath.off('mouseover')
.off('mouseout')
.off('emphasis')
.off('normal');
symbolPath.hoverStyle = hoverItemStyle;
// FIXME
// Do not use symbol.trigger('emphasis'), but use symbol.highlight() instead.
setHoverStyle(symbolPath);
/**
* @param {Function} cb
* @param {Object} [opt]
* @param {Object} [opt.keepLabel=true]
*/
symbolProto.fadeOut = function (cb, opt) {
var symbolPath = this.childAt(0);
// Avoid mistaken hover when fading out
this.silent = symbolPath.silent = true;
// Not show text when animating
!(opt && opt.keepLabel) && (symbolPath.style.text = null);
updateProps(
symbolPath,
{
style: {opacity: 0},
scale: [0, 0]
},
this._seriesModel,
this.dataIndex,
cb
);
};
inherits(SymbolClz$1, Group);
/**
* @module echarts/chart/helper/SymbolDraw
*/
/**
* @constructor
* @alias module:echarts/chart/helper/SymbolDraw
* @param {module:zrender/graphic/Group} [symbolCtor]
*/
function SymbolDraw(symbolCtor) {
this.group = new Group();
data.diff(oldData)
.add(function (newIdx) {
var point = data.getItemLayout(newIdx);
if (symbolNeedsDraw(data, point, newIdx, opt)) {
var symbolEl = new SymbolCtor(data, newIdx, seriesScope);
symbolEl.attr('position', point);
data.setItemGraphicEl(newIdx, symbolEl);
group.add(symbolEl);
}
})
.update(function (newIdx, oldIdx) {
var symbolEl = oldData.getItemGraphicEl(oldIdx);
var point = data.getItemLayout(newIdx);
if (!symbolNeedsDraw(data, point, newIdx, opt)) {
group.remove(symbolEl);
return;
}
if (!symbolEl) {
symbolEl = new SymbolCtor(data, newIdx);
symbolEl.attr('position', point);
}
else {
symbolEl.updateData(data, newIdx, seriesScope);
updateProps(symbolEl, {
position: point
}, seriesModel);
}
// Add back
group.add(symbolEl);
data.setItemGraphicEl(newIdx, symbolEl);
})
.remove(function (oldIdx) {
var el = oldData.getItemGraphicEl(oldIdx);
el && el.fadeOut(function () {
group.remove(el);
});
})
.execute();
this._data = data;
};
symbolDrawProto.isPersistent = function () {
return true;
};
symbolDrawProto.updateLayout = function () {
var data = this._data;
if (data) {
// Not use animation
data.eachItemGraphicEl(function (el, idx) {
var point = data.getItemLayout(idx);
el.attr('position', point);
});
}
};
/**
* Update symbols draw by new data
* @param {module:echarts/data/List} data
* @param {Object} [opt] Or isIgnore
* @param {Function} [opt.isIgnore]
* @param {Object} [opt.clipShape]
*/
symbolDrawProto.incrementalUpdate = function (taskParams, data, opt) {
opt = normalizeUpdateOpt(opt);
function updateIncrementalAndHover(el) {
if (!el.isGroup) {
el.incremental = el.useHoverLayer = true;
}
}
for (var idx = taskParams.start; idx < taskParams.end; idx++) {
var point = data.getItemLayout(idx);
if (symbolNeedsDraw(data, point, idx, opt)) {
var el = new this._symbolCtor(data, idx, this._seriesScope);
el.traverse(updateIncrementalAndHover);
el.attr('position', point);
this.group.add(el);
data.setItemGraphicEl(idx, el);
}
}
};
function normalizeUpdateOpt(opt) {
if (opt != null && !isObject$1(opt)) {
opt = {isIgnore: opt};
}
return opt || {};
}
function makeSeriesScope(data) {
var seriesModel = data.hostModel;
return {
itemStyle: seriesModel.getModel('itemStyle').getItemStyle(['color']),
hoverItemStyle: seriesModel.getModel('emphasis.itemStyle').getItemStyle(),
symbolRotate: seriesModel.get('symbolRotate'),
symbolOffset: seriesModel.get('symbolOffset'),
hoverAnimation: seriesModel.get('hoverAnimation'),
labelModel: seriesModel.getModel('label'),
hoverLabelModel: seriesModel.getModel('emphasis.label'),
cursorStyle: seriesModel.get('cursor')
};
}
/**
* @param {Object} coordSys
* @param {module:echarts/data/List} data
* @param {string} valueOrigin lineSeries.option.areaStyle.origin
*/
function prepareDataCoordInfo(coordSys, data, valueOrigin) {
var baseAxis = coordSys.getBaseAxis();
var valueAxis = coordSys.getOtherAxis(baseAxis);
var valueStart = getValueStart(valueAxis, valueOrigin);
return {
dataDimsForPoint: dataDimsForPoint,
valueStart: valueStart,
valueAxisDim: valueAxisDim,
baseAxisDim: baseAxisDim,
stacked: stacked,
valueDim: valueDim,
baseDim: baseDim,
baseDataOffset: baseDataOffset,
stackedOverDimension: data.getCalculationInfo('stackedOverDimension')
};
}
return valueStart;
}
return coordSys.dataToPoint(stackedData);
}
newData.diff(oldData)
.add(function (idx) {
diffResult.push({cmd: '+', idx: idx});
})
.update(function (newIdx, oldIdx) {
diffResult.push({cmd: '=', idx: oldIdx, idx1: newIdx});
})
.remove(function (idx) {
diffResult.push({cmd: '-', idx: idx});
})
.execute();
return diffResult;
}
// convertToIntId(newIdList, oldIdList);
currStackedPoints.push(oldStackedOnPoints[diffItem.idx]);
nextStackedPoints.push(newStackedOnPoints[diffItem.idx1]);
rawIndices.push(newData.getRawIndex(diffItem.idx1));
break;
case '+':
var idx = diffItem.idx;
currPoints.push(
oldCoordSys.dataToPoint([
newData.get(newDataOldCoordInfo.dataDimsForPoint[0], idx),
newData.get(newDataOldCoordInfo.dataDimsForPoint[1], idx)
])
);
nextPoints.push(newData.getItemLayout(idx).slice());
currStackedPoints.push(
getStackedOnPoint(newDataOldCoordInfo, oldCoordSys, newData,
idx)
);
nextStackedPoints.push(newStackedOnPoints[idx]);
rawIndices.push(newData.getRawIndex(idx));
break;
case '-':
var idx = diffItem.idx;
var rawIndex = oldData.getRawIndex(idx);
// Data is replaced. In the case of dynamic data queue
// FIXME FIXME FIXME
if (rawIndex !== idx) {
currPoints.push(oldData.getItemLayout(idx));
nextPoints.push(newCoordSys.dataToPoint([
oldData.get(oldDataNewCoordInfo.dataDimsForPoint[0], idx),
oldData.get(oldDataNewCoordInfo.dataDimsForPoint[1], idx)
]));
currStackedPoints.push(oldStackedOnPoints[idx]);
nextStackedPoints.push(
getStackedOnPoint(oldDataNewCoordInfo, newCoordSys,
oldData, idx)
);
rawIndices.push(rawIndex);
}
else {
pointAdded = false;
}
}
// Original indices
if (pointAdded) {
status.push(diffItem);
sortedIndices.push(sortedIndices.length);
}
}
sortedCurrStackedPoints[i] = currStackedPoints[idx];
sortedNextStackedPoints[i] = nextStackedPoints[idx];
sortedStatus[i] = status[idx];
}
return {
current: sortedCurrPoints,
next: sortedNextPoints,
stackedOnCurrent: sortedCurrStackedPoints,
stackedOnNext: sortedNextStackedPoints,
status: sortedStatus
};
};
// Temporary variable
var v = [];
var cp0 = [];
var cp1 = [];
function isPointNull(p) {
return isNaN(p[0]) || isNaN(p[1]);
}
function drawSegment(
ctx, points, start, segLen, allLen,
dir, smoothMin, smoothMax, smooth, smoothMonotone, connectNulls
) {
if (smoothMonotone == null) {
if (isMono(points, 'x')) {
return drawMono(ctx, points, start, segLen, allLen,
dir, smoothMin, smoothMax, smooth, 'x', connectNulls);
}
else if (isMono(points, 'y')) {
return drawMono(ctx, points, start, segLen, allLen,
dir, smoothMin, smoothMax, smooth, 'y', connectNulls);
}
else {
return drawNonMono.apply(this, arguments);
}
}
else if (smoothMonotone !== 'none' && isMono(points, smoothMonotone)) {
return drawMono.apply(this, arguments);
}
else {
return drawNonMono.apply(this, arguments);
}
}
/**
* Check if points is in monotone.
*
* @param {number[][]} points Array of points which is in [x, y] form
* @param {string} smoothMonotone 'x', 'y', or 'none', stating for which
* dimension that is checking.
* If is 'none', `drawNonMono` should be
* called.
* If is undefined, either being monotone
* in 'x' or 'y' will call `drawMono`.
*/
function isMono(points, smoothMonotone) {
if (points.length <= 1) {
return true;
}
/**
* Draw smoothed line in monotone, in which only vertical or horizontal bezier
* control points will be used. This should be used when points are monotone
* either in x or y dimension.
*/
function drawMono(
ctx, points, start, segLen, allLen,
dir, smoothMin, smoothMax, smooth, smoothMonotone, connectNulls
) {
var prevIdx = 0;
var idx = start;
for (var k = 0; k < segLen; k++) {
var p = points[idx];
if (idx >= allLen || idx < 0) {
break;
}
if (isPointNull(p)) {
if (connectNulls) {
idx += dir;
continue;
}
break;
}
v2Copy(cp0, prevP);
cp0[dim] = prevP[dim] + ctrlLen;
v2Copy(cp1, p);
cp1[dim] = p[dim] - ctrlLen;
ctx.bezierCurveTo(
cp0[0], cp0[1],
cp1[0], cp1[1],
p[0], p[1]
);
}
else {
ctx.lineTo(p[0], p[1]);
}
}
prevIdx = idx;
idx += dir;
}
return k;
}
/**
* Draw smoothed line in non-monotone, in may cause undesired curve in extreme
* situations. This should be used when points are non-monotone neither in x or
* y dimension.
*/
function drawNonMono(
ctx, points, start, segLen, allLen,
dir, smoothMin, smoothMax, smooth, smoothMonotone, connectNulls
) {
var prevIdx = 0;
var idx = start;
for (var k = 0; k < segLen; k++) {
var p = points[idx];
if (idx >= allLen || idx < 0) {
break;
}
if (isPointNull(p)) {
if (connectNulls) {
idx += dir;
continue;
}
break;
}
var lenPrevSeg;
var lenNextSeg;
if (smoothMonotone === 'x' || smoothMonotone === 'y') {
var dim = smoothMonotone === 'x' ? 0 : 1;
lenPrevSeg = Math.abs(p[dim] - prevP[dim]);
lenNextSeg = Math.abs(p[dim] - nextP[dim]);
}
else {
lenPrevSeg = dist(p, prevP);
lenNextSeg = dist(p, nextP);
}
ctx.bezierCurveTo(
cp0[0], cp0[1],
cp1[0], cp1[1],
p[0], p[1]
);
// cp0 of next segment
scaleAndAdd$1(cp0, p, v, smooth * ratioNextSeg);
}
else {
ctx.lineTo(p[0], p[1]);
}
}
prevIdx = idx;
idx += dir;
}
return k;
}
type: 'ec-polyline',
shape: {
points: [],
smooth: 0,
smoothConstraint: true,
smoothMonotone: null,
connectNulls: false
},
style: {
fill: null,
stroke: '#000'
},
brush: fixClipWithShadow(Path.prototype.brush),
var i = 0;
var len$$1 = points.length;
if (shape.connectNulls) {
// Must remove first and last null values avoid draw error in polygon
for (; len$$1 > 0; len$$1--) {
if (!isPointNull(points[len$$1 - 1])) {
break;
}
}
for (; i < len$$1; i++) {
if (!isPointNull(points[i])) {
break;
}
}
}
while (i < len$$1) {
i += drawSegment(
ctx, points, i, len$$1, len$$1,
1, result.min, result.max, shape.smooth,
shape.smoothMonotone, shape.connectNulls
) + 1;
}
}
});
type: 'ec-polygon',
shape: {
points: [],
smooth: 0,
stackedOnSmooth: 0,
smoothConstraint: true,
smoothMonotone: null,
connectNulls: false
},
brush: fixClipWithShadow(Path.prototype.brush),
var i = 0;
var len$$1 = points.length;
var smoothMonotone = shape.smoothMonotone;
var bbox = getBoundingBox(points, shape.smoothConstraint);
var stackedOnBBox = getBoundingBox(stackedOnPoints,
shape.smoothConstraint);
if (shape.connectNulls) {
// Must remove first and last null values avoid draw error in polygon
for (; len$$1 > 0; len$$1--) {
if (!isPointNull(points[len$$1 - 1])) {
break;
}
}
for (; i < len$$1; i++) {
if (!isPointNull(points[i])) {
break;
}
}
}
while (i < len$$1) {
var k = drawSegment(
ctx, points, i, len$$1, len$$1,
1, bbox.min, bbox.max, shape.smooth,
smoothMonotone, shape.connectNulls
);
drawSegment(
ctx, stackedOnPoints, i + k - 1, k, len$$1,
-1, stackedOnBBox.min, stackedOnBBox.max, shape.stackedOnSmooth,
smoothMonotone, shape.connectNulls
);
i += k + 1;
ctx.closePath();
}
}
});
function getSmooth(smooth) {
return typeof (smooth) === 'number' ? smooth : (smooth ? 0.5 : 0);
}
function getAxisExtentWithGap(axis) {
var extent = axis.getGlobalExtent();
if (axis.onBand) {
// Remove extra 1px to avoid line miter in clipped edge
var halfBandWidth = axis.getBandWidth() / 2 - 1;
var dir = extent[1] > extent[0] ? 1 : -1;
extent[0] += dir * halfBandWidth;
extent[1] -= dir * halfBandWidth;
}
return extent;
}
/**
* @param {module:echarts/coord/cartesian/Cartesian2D|
module:echarts/coord/polar/Polar} coordSys
* @param {module:echarts/data/List} data
* @param {Object} dataCoordInfo
* @param {Array.<Array.<number>>} points
*/
function getStackedOnPoints(coordSys, data, dataCoordInfo) {
if (!dataCoordInfo.valueDim) {
return [];
}
return points;
}
if (hasAnimation) {
clipPath.shape[isHorizontal ? 'width' : 'height'] = 0;
initProps(clipPath, {
shape: {
width: width,
height: height
}
}, seriesModel);
}
return clipPath;
}
if (hasAnimation) {
clipPath.shape.endAngle = -angleExtent[0] * RADIAN;
initProps(clipPath, {
shape: {
endAngle: -angleExtent[1] * RADIAN
}
}, seriesModel);
}
return clipPath;
}
var coordDim;
var visualMeta;
if (!visualMeta) {
if (__DEV__) {
console.warn('Visual map on line style only support x or y
dimension.');
}
return;
}
return gradient;
}
Chart.extend({
type: 'line',
init: function () {
var lineGroup = new Group();
group.add(lineGroup);
if (step) {
// TODO If stacked series is not step
points = turnPointsIntoStep(points, coordSys, step);
stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys,
step);
}
// Update clipPath
lineGroup.setClipPath(coordSysClipShape);
polyline.setShape({
points: points
});
polygon && polygon.setShape({
points: points,
stackedOnPoints: stackedOnPoints
});
}
}
}
polyline.useStyle(defaults(
// Use color in lineStyle first
lineStyleModel.getLineStyle(),
{
fill: 'none',
stroke: visualColor,
lineJoin: 'bevel'
}
));
if (polygon) {
var stackedOnSeries = data.getCalculationInfo('stackedOnSeries');
var stackedOnSmooth = 0;
polygon.useStyle(defaults(
areaStyleModel.getAreaStyle(),
{
fill: visualColor,
opacity: 0.7,
lineJoin: 'bevel'
}
));
if (stackedOnSeries) {
stackedOnSmooth = getSmooth(stackedOnSeries.get('smooth'));
}
polygon.setShape({
smooth: smooth,
stackedOnSmooth: stackedOnSmooth,
smoothMonotone: seriesModel.get('smoothMonotone'),
connectNulls: seriesModel.get('connectNulls')
});
}
this._data = data;
// Save the coordinate system for transition animation when data changed
this._coordSys = coordSys;
this._stackedOnPoints = stackedOnPoints;
this._points = points;
this._step = step;
this._valueOrigin = valueOrigin;
},
this.group.add(symbol);
}
symbol.highlight();
}
else {
// Highlight whole series
Chart.prototype.highlight.call(
this, seriesModel, ecModel, api, payload
);
}
},
/**
* @param {module:zrender/container/Group} group
* @param {Array.<Array.<number>>} points
* @private
*/
_newPolyline: function (points) {
var polyline = this._polyline;
// Remove previous created polyline
if (polyline) {
this._lineGroup.remove(polyline);
}
this._lineGroup.add(polyline);
this._polyline = polyline;
return polyline;
},
/**
* @param {module:zrender/container/Group} group
* @param {Array.<Array.<number>>} stackedOnPoints
* @param {Array.<Array.<number>>} points
* @private
*/
_newPolygon: function (points, stackedOnPoints) {
var polygon = this._polygon;
// Remove previous created polygon
if (polygon) {
this._lineGroup.remove(polygon);
}
this._lineGroup.add(polygon);
this._polygon = polygon;
return polygon;
},
/**
* @private
*/
_getSymbolIgnoreFunc: function (data, coordSys) {
var categoryAxis = coordSys.getAxesByScale('ordinal')[0];
// `getLabelInterval` is provided by echarts/component/axis
if (categoryAxis && categoryAxis.isLabelIgnored) {
return bind(categoryAxis.isLabelIgnored, categoryAxis);
}
},
/**
* @private
*/
// FIXME Two value axis
_updateAnimation: function (data, stackedOnPoints, coordSys, api, step,
valueOrigin) {
var polyline = this._polyline;
var polygon = this._polygon;
var seriesModel = data.hostModel;
updateProps(polyline, {
shape: {
points: next
}
}, seriesModel);
if (polygon) {
polygon.setShape({
points: current,
stackedOnPoints: stackedOnCurrent
});
updateProps(polygon, {
shape: {
points: next,
stackedOnPoints: stackedOnNext
}
}, seriesModel);
}
this._polyline =
this._polygon =
this._coordSys =
this._points =
this._stackedOnPoints =
this._data = null;
}
});
data.setVisual({
legendSymbol: legendSymbol || symbolType,
symbol: symbolType,
symbolSize: symbolSize
});
if (data.hasItemOption) {
var itemModel = data.getItemModel(idx);
var itemSymbolType = itemModel.getShallow('symbol', true);
var itemSymbolSize = itemModel.getShallow('symbolSize', true);
// If has item symbol
if (itemSymbolType != null) {
data.setItemVisual(idx, 'symbol', itemSymbolType);
}
if (itemSymbolSize != null) {
// PENDING Transform symbolSize ?
data.setItemVisual(idx, 'symbolSize', itemSymbolSize);
}
}
}
plan: createRenderPlanner(),
if (!coordSys) {
return;
}
if (dimLen === 1) {
var x = data.get(dims[0], i, true);
point = !isNaN(x) && coordSys.dataToPoint(x, null, tmpOut);
}
else {
var x = tmpIn[0] = data.get(dims[0], i, true);
var y = tmpIn[1] = data.get(dims[1], i, true);
// Also {Array.<number>}, not undefined to avoid
if...else... statement
point = !isNaN(x) && !isNaN(y) &&
coordSys.dataToPoint(tmpIn, null, tmpOut);
}
if (isLargeRender) {
points[offset++] = point ? point[0] : NaN;
points[offset++] = point ? point[1] : NaN;
}
else {
data.setItemLayout(i, (point && point.slice()) || [NaN,
NaN]);
}
}
var samplers = {
average: function (frame) {
var sum = 0;
var count = 0;
for (var i = 0; i < frame.length; i++) {
if (!isNaN(frame[i])) {
sum += frame[i];
count++;
}
}
// Return NaN if count is 0
return count === 0 ? NaN : sum / count;
},
sum: function (frame) {
var sum = 0;
for (var i = 0; i < frame.length; i++) {
// Ignore NaN
sum += frame[i] || 0;
}
return sum;
},
max: function (frame) {
var max = -Infinity;
for (var i = 0; i < frame.length; i++) {
frame[i] > max && (max = frame[i]);
}
return max;
},
min: function (frame) {
var min = Infinity;
for (var i = 0; i < frame.length; i++) {
frame[i] < min && (min = frame[i]);
}
return min;
},
// TODO
// Median
nearest: function (frame) {
return frame[0];
}
};
/**
* Cartesian coordinate system
* @module echarts/coord/Cartesian
*
*/
function dimAxisMapper(dim) {
return this._axes[dim];
}
/**
* @alias module:echarts/coord/Cartesian
* @constructor
*/
var Cartesian = function (name) {
this._axes = {};
this._dimList = [];
/**
* @type {string}
*/
this.name = name || '';
};
Cartesian.prototype = {
constructor: Cartesian,
type: 'cartesian',
/**
* Get axis
* @param {number|string} dim
* @return {module:echarts/coord/Cartesian~Axis}
*/
getAxis: function (dim) {
return this._axes[dim];
},
/**
* Get axes list
* @return {Array.<module:echarts/coord/Cartesian~Axis>}
*/
getAxes: function () {
return map(this._dimList, dimAxisMapper, this);
},
/**
* Get axes list by given scale type
*/
getAxesByScale: function (scaleType) {
scaleType = scaleType.toLowerCase();
return filter(
this.getAxes(),
function (axis) {
return axis.scale.type === scaleType;
}
);
},
/**
* Add axis
* @param {module:echarts/coord/Cartesian.Axis}
*/
addAxis: function (axis) {
var dim = axis.dim;
this._axes[dim] = axis;
this._dimList.push(dim);
},
/**
* Convert data to coord in nd space
* @param {Array.<number>|Object.<string, number>} val
* @return {Array.<number>|Object.<string, number>}
*/
dataToCoord: function (val) {
return this._dataCoordConvert(val, 'dataToCoord');
},
/**
* Convert coord in nd space to data
* @param {Array.<number>|Object.<string, number>} val
* @return {Array.<number>|Object.<string, number>}
*/
coordToData: function (val) {
return this._dataCoordConvert(val, 'coordToData');
},
output[dim] = axis[method](input[dim]);
}
return output;
}
};
function Cartesian2D(name) {
Cartesian.call(this, name);
}
Cartesian2D.prototype = {
constructor: Cartesian2D,
type: 'cartesian2d',
/**
* @type {Array.<string>}
* @readOnly
*/
dimensions: ['x', 'y'],
/**
* Base axis will be used on stacking.
*
* @return {module:echarts/coord/cartesian/Axis2D}
*/
getBaseAxis: function () {
return this.getAxesByScale('ordinal')[0]
|| this.getAxesByScale('time')[0]
|| this.getAxis('x');
},
/**
* If contain point
* @param {Array.<number>} point
* @return {boolean}
*/
containPoint: function (point) {
var axisX = this.getAxis('x');
var axisY = this.getAxis('y');
return axisX.contain(axisX.toLocalCoord(point[0]))
&& axisY.contain(axisY.toLocalCoord(point[1]));
},
/**
* If contain data
* @param {Array.<number>} data
* @return {boolean}
*/
containData: function (data) {
return this.getAxis('x').containData(data[0])
&& this.getAxis('y').containData(data[1]);
},
/**
* @param {Array.<number>} data
* @param {Array.<number>} out
* @return {Array.<number>}
*/
dataToPoint: function (data, reserved, out) {
var xAxis = this.getAxis('x');
var yAxis = this.getAxis('y');
out = out || [];
out[0] = xAxis.toGlobalCoord(xAxis.dataToCoord(data[0]));
out[1] = yAxis.toGlobalCoord(yAxis.dataToCoord(data[1]));
return out;
},
/**
* @param {Array.<number>} data
* @param {Array.<number>} out
* @return {Array.<number>}
*/
clampData: function (data, out) {
var xAxisExtent = this.getAxis('x').scale.getExtent();
var yAxisExtent = this.getAxis('y').scale.getExtent();
out = out || [];
out[0] = Math.min(
Math.max(Math.min(xAxisExtent[0], xAxisExtent[1]), data[0]),
Math.max(xAxisExtent[0], xAxisExtent[1])
);
out[1] = Math.min(
Math.max(Math.min(yAxisExtent[0], yAxisExtent[1]), data[1]),
Math.max(yAxisExtent[0], yAxisExtent[1])
);
return out;
},
/**
* @param {Array.<number>} point
* @param {Array.<number>} out
* @return {Array.<number>}
*/
pointToData: function (point, out) {
var xAxis = this.getAxis('x');
var yAxis = this.getAxis('y');
out = out || [];
out[0] = xAxis.coordToData(xAxis.toLocalCoord(point[0]));
out[1] = yAxis.coordToData(yAxis.toLocalCoord(point[1]));
return out;
},
/**
* Get other axis
* @param {module:echarts/coord/cartesian/Axis2D} axis
*/
getOtherAxis: function (axis) {
return this.getAxis(axis.dim === 'x' ? 'y' : 'x');
}
};
inherits(Cartesian2D, Cartesian);
/**
* Extend axis 2d
* @constructor module:echarts/coord/cartesian/Axis2D
* @extends {module:echarts/coord/cartesian/Axis}
* @param {string} dim
* @param {*} scale
* @param {Array.<number>} coordExtent
* @param {string} axisType
* @param {string} position
*/
var Axis2D = function (dim, scale, coordExtent, axisType, position) {
Axis.call(this, dim, scale, coordExtent);
/**
* Axis type
* - 'category'
* - 'value'
* - 'time'
* - 'log'
* @type {string}
*/
this.type = axisType || 'value';
/**
* Axis position
* - 'top'
* - 'bottom'
* - 'left'
* - 'right'
*/
this.position = position || 'bottom';
};
Axis2D.prototype = {
constructor: Axis2D,
/**
* Index of axis, can be used as key
*/
index: 0,
/**
* If axis is on the zero position of the other axis
* @type {boolean}
*/
onZero: false,
/**
* Axis model
* @param {module:echarts/coord/cartesian/AxisModel}
*/
model: null,
isHorizontal: function () {
var position = this.position;
return position === 'top' || position === 'bottom';
},
/**
* Each item cooresponds to this.getExtent(), which
* means globalExtent[0] may greater than globalExtent[1],
* unless `asc` is input.
*
* @param {boolean} [asc]
* @return {Array.<number>}
*/
getGlobalExtent: function (asc) {
var ret = this.getExtent();
ret[0] = this.toGlobalCoord(ret[0]);
ret[1] = this.toGlobalCoord(ret[1]);
asc && ret[0] > ret[1] && ret.reverse();
return ret;
},
getOtherAxis: function () {
this.grid.getOtherAxis();
},
/**
* If label is ignored.
* Automatically used when axis is category and label can not be all shown
* @param {number} idx
* @return {boolean}
*/
isLabelIgnored: function (idx) {
if (this.type === 'category') {
var labelInterval = this.getLabelInterval();
return ((typeof labelInterval === 'function')
&& !labelInterval(idx, this.scale.getLabel(idx)))
|| idx % (labelInterval + 1);
}
},
/**
* @override
*/
pointToData: function (point, clamp) {
return this.coordToData(this.toLocalCoord(point[this.dim === 'x' ? 0 : 1]),
clamp);
},
/**
* Transform global coord to local coord,
* i.e. var localCoord = axis.toLocalCoord(80);
* designate by module:echarts/coord/cartesian/Grid.
* @type {Function}
*/
toLocalCoord: null,
/**
* Transform global coord to local coord,
* i.e. var globalCoord = axis.toLocalCoord(40);
* designate by module:echarts/coord/cartesian/Grid.
* @type {Function}
*/
toGlobalCoord: null
};
inherits(Axis2D, Axis);
var defaultOption = {
show: true,
zlevel: 0, // 一级层叠
z: 0, // 二级层叠
// 反向坐标轴
inverse: false,
// 坐标轴名字,默认为空
name: '',
// 坐标轴名字位置,支持'start' | 'middle' | 'end'
nameLocation: 'end',
// 坐标轴名字旋转,degree。
nameRotate: null, // Adapt to axis rotate, when nameLocation is 'middle'.
nameTruncate: {
maxWidth: null,
ellipsis: '...',
placeholder: '.'
},
// 坐标轴文字样式,默认取全局样式
nameTextStyle: {},
// 文字与轴线距离
nameGap: 15,
tooltip: {
show: false
},
axisPointer: {},
// 坐标轴线
axisLine: {
// 默认显示,属性 show 控制显示与否
show: true,
onZero: true,
onZeroAxisIndex: null,
// 属性 lineStyle 控制线条样式
lineStyle: {
color: '#333',
width: 1,
type: 'solid'
},
// 坐标轴两端的箭头
symbol: ['none', 'none'],
symbolSize: [10, 15]
},
// 坐标轴小标记
axisTick: {
// 属性 show 控制显示与否,默认显示
show: true,
// 控制小标记是否在 grid 里
inside: false,
// 属性 length 控制线长
length: 5,
// 属性 lineStyle 控制线条样式
lineStyle: {
width: 1
}
},
// 坐标轴文本标签,详见 axis.axisLabel
axisLabel: {
show: true,
// 控制文本标签是否在 grid 里
inside: false,
rotate: 0,
showMinLabel: null, // true | false | null (auto)
showMaxLabel: null, // true | false | null (auto)
margin: 8,
// formatter: null,
// 其余属性默认使用全局文本样式,详见 TEXTSTYLE
fontSize: 12
},
// 分隔线
splitLine: {
// 默认显示,属性 show 控制显示与否
show: true,
// 属性 lineStyle(详见 lineStyle)控制线条样式
lineStyle: {
color: ['#ccc'],
width: 1,
type: 'solid'
}
},
// 分隔区域
splitArea: {
// 默认不显示,属性 show 控制显示与否
show: false,
// 属性 areaStyle(详见 areaStyle)控制区域样式
areaStyle: {
color: ['rgba(250,250,250,0.3)','rgba(200,200,200,0.3)']
}
}
};
axisDefault.categoryAxis = merge({
// 类目起始和结束两端空白策略
boundaryGap: true,
// Set false to faster category collection.
// Only usefull in the case like: category is
// ['2012-01-01', '2012-01-02', ...], where the input
// data has been ensured not duplicate and is large data.
// null means "auto":
// if axis.data provided, do not deduplication,
// else do deduplication.
deduplication: null,
// splitArea: {
// show: false
// },
splitLine: {
show: false
},
// 坐标轴小标记
axisTick: {
// If tick is align with label when boundaryGap is true
alignWithLabel: false,
interval: 'auto'
},
// 坐标轴文本标签,详见 axis.axisLabel
axisLabel: {
interval: 'auto'
}
}, defaultOption);
axisDefault.valueAxis = merge({
// 数值起始和结束两端空白策略
boundaryGap: [0, 0],
// TODO
// min/max: [30, datamin, 60] or [20, datamin] or [datamin, 60]
axisDefault.logAxis = defaults({
scale: true,
logBase: 10
}, axisDefault.valueAxis);
/**
* Generate sub axis model class
* @param {string} axisName 'x' 'y' 'radius' 'angle' 'parallel'
* @param {module:echarts/model/Component} BaseAxisModelClass
* @param {Function} axisTypeDefaulter
* @param {Object} [extraDefaultOption]
*/
var axisModelCreator = function (axisName, BaseAxisModelClass, axisTypeDefaulter,
extraDefaultOption) {
BaseAxisModelClass.extend({
/**
* @readOnly
*/
type: axisName + 'Axis.' + axisType,
if (layoutMode) {
mergeLayoutParam(option, inputPositionParams, layoutMode);
}
},
/**
* @override
*/
optionUpdated: function () {
var thisOption = this.option;
if (thisOption.type === 'category') {
this.__ordinalMeta = OrdinalMeta.createByAxisModel(this);
}
},
/**
* Should not be called before all of 'getInitailData' finished.
* Because categories are collected during initializing data.
*/
getCategories: function () {
// FIXME
// warning if called before all of 'getInitailData' finished.
if (this.option.type === 'category') {
return this.__ordinalMeta.categories;
}
},
getOrdinalMeta: function () {
return this.__ordinalMeta;
},
defaultOption: mergeAll(
[
{},
axisDefault[axisType + 'Axis'],
extraDefaultOption
],
true
)
});
});
ComponentModel.registerSubTypeDefaulter(
axisName + 'Axis',
curry(axisTypeDefaulter, axisName)
);
};
type: 'cartesian2dAxis',
/**
* @type {module:echarts/coord/cartesian/Axis2D}
*/
axis: null,
/**
* @override
*/
init: function () {
AxisModel.superApply(this, 'init', arguments);
this.resetRange();
},
/**
* @override
*/
mergeOption: function () {
AxisModel.superApply(this, 'mergeOption', arguments);
this.resetRange();
},
/**
* @override
*/
restoreData: function () {
AxisModel.superApply(this, 'restoreData', arguments);
this.resetRange();
},
/**
* @override
* @return {module:echarts/model/Component}
*/
getCoordSysModel: function () {
return this.ecModel.queryComponents({
mainType: 'grid',
index: this.option.gridIndex,
id: this.option.gridId
})[0];
}
});
merge(AxisModel.prototype, axisModelCommonMixin);
var extraOption = {
// gridIndex: 0,
// gridId: '',
// Grid 是在有直角坐标系的时候必须要存在的
// 所以这里也要被 Cartesian2D 依赖
ComponentModel.extend({
type: 'grid',
layoutMode: 'box',
/**
* @type {module:echarts/coord/cartesian/Grid}
*/
coordinateSystem: null,
defaultOption: {
show: false,
zlevel: 0,
z: 0,
left: '10%',
top: 60,
right: '10%',
bottom: 60,
// If grid size contain label
containLabel: false,
// width: {totalWidth} - left - right,
// height: {totalHeight} - top - bottom,
backgroundColor: 'rgba(0,0,0,0)',
borderWidth: 1,
borderColor: '#ccc'
}
});
/**
* Grid is a region which contains at most 4 cartesian systems
*
* TODO Default cartesian
*/
/**
* Check if the axis is used in the specified grid
* @inner
*/
function isAxisUsedInTheGrid(axisModel, gridModel, ecModel) {
return axisModel.getCoordSysModel() === gridModel;
}
return rotatedRect;
}
function getLabelUnionRect(axis) {
var axisModel = axis.model;
var labels = axisModel.get('axisLabel.show') ? axisModel.getFormattedLabels() :
[];
var axisLabelModel = axisModel.getModel('axisLabel');
var rect;
var step = 1;
var labelCount = labels.length;
if (labelCount > 40) {
// Simple optimization for large amount of labels
step = Math.ceil(labelCount / 40);
}
for (var i = 0; i < labelCount; i += step) {
if (!axis.isLabelIgnored(i)) {
var unrotatedSingleRect = axisLabelModel.getTextRect(labels[i]);
var singleRect = rotateTextRect(unrotatedSingleRect,
axisLabelModel.get('rotate') || 0);
/**
* @type {Array.<module:echarts/coord/cartesian/Cartesian>}
* @private
*/
this._coordsList = [];
/**
* @type {Object.<string, module:echarts/coord/cartesian/Axis2D>}
* @private
*/
this._axesMap = {};
/**
* @type {Array.<module:echarts/coord/cartesian/Axis2D>}
* @private
*/
this._axesList = [];
this.model = gridModel;
}
gridProto.type = 'grid';
gridProto.axisPointerEnabled = true;
gridProto.getRect = function () {
return this._rect;
};
this._updateScale(ecModel, this.model);
each$6(axesMap.x, function (xAxis) {
niceScaleExtent$1(xAxis.scale, xAxis.model);
});
each$6(axesMap.y, function (yAxis) {
niceScaleExtent$1(yAxis.scale, yAxis.model);
});
each$6(axesMap.x, function (xAxis) {
fixAxisOnZero(axesMap, 'y', xAxis);
});
each$6(axesMap.y, function (yAxis) {
fixAxisOnZero(axesMap, 'x', yAxis);
});
if (!axis.onZero) {
return;
}
if (onZeroAxisIndex == null) {
axis.onZero = false;
}
axis.onZeroAxisIndex = onZeroAxisIndex;
}
function canNotOnZeroToAxis(axis) {
return axis.type === 'category' || axis.type === 'time' || !
ifAxisCrossZero$1(axis);
}
/**
* Resize the grid
* @param {module:echarts/coord/cartesian/GridModel} gridModel
* @param {module:echarts/ExtensionAPI} api
*/
gridProto.resize = function (gridModel, api, ignoreContainLabel) {
this._rect = gridRect;
adjustAxes();
adjustAxes();
}
function adjustAxes() {
each$6(axesList, function (axis) {
var isHorizontal = axis.isHorizontal();
var extent = isHorizontal ? [0, gridRect.width] : [0, gridRect.height];
var idx = axis.inverse ? 1 : 0;
axis.setExtent(extent[idx], extent[1 - idx]);
updateAxisTransform(axis, isHorizontal ? gridRect.x : gridRect.y);
});
}
};
/**
* @param {string} axisType
* @param {number} [axisIndex]
*/
gridProto.getAxis = function (axisType, axisIndex) {
var axesMapOnDim = this._axesMap[axisType];
if (axesMapOnDim != null) {
if (axisIndex == null) {
// Find first axis
for (var name in axesMapOnDim) {
if (axesMapOnDim.hasOwnProperty(name)) {
return axesMapOnDim[name];
}
}
}
return axesMapOnDim[axisIndex];
}
};
/**
* @return {Array.<module:echarts/coord/Axis>}
*/
gridProto.getAxes = function () {
return this._axesList.slice();
};
/**
* Usage:
* grid.getCartesian(xAxisIndex, yAxisIndex);
* grid.getCartesian(xAxisIndex);
* grid.getCartesian(null, yAxisIndex);
* grid.getCartesian({xAxisIndex: ..., yAxisIndex: ...});
*
* @param {number|Object} [xAxisIndex]
* @param {number} [yAxisIndex]
*/
gridProto.getCartesian = function (xAxisIndex, yAxisIndex) {
if (xAxisIndex != null && yAxisIndex != null) {
var key = 'x' + xAxisIndex + 'y' + yAxisIndex;
return this._coordsMap[key];
}
if (isObject$1(xAxisIndex)) {
yAxisIndex = xAxisIndex.yAxisIndex;
xAxisIndex = xAxisIndex.xAxisIndex;
}
// When only xAxisIndex or yAxisIndex given, find its first cartesian.
for (var i = 0, coordList = this._coordsList; i < coordList.length; i++) {
if (coordList[i].getAxis('x').index === xAxisIndex
|| coordList[i].getAxis('y').index === yAxisIndex
) {
return coordList[i];
}
}
};
gridProto.getCartesians = function () {
return this._coordsList.slice();
};
/**
* @implements
* see {module:echarts/CoodinateSystem}
*/
gridProto.convertToPixel = function (ecModel, finder, value) {
var target = this._findConvertTarget(ecModel, finder);
return target.cartesian
? target.cartesian.dataToPoint(value)
: target.axis
? target.axis.toGlobalCoord(target.axis.dataToCoord(value))
: null;
};
/**
* @implements
* see {module:echarts/CoodinateSystem}
*/
gridProto.convertFromPixel = function (ecModel, finder, value) {
var target = this._findConvertTarget(ecModel, finder);
return target.cartesian
? target.cartesian.pointToData(value)
: target.axis
? target.axis.coordToData(target.axis.toLocalCoord(value))
: null;
};
/**
* @inner
*/
gridProto._findConvertTarget = function (ecModel, finder) {
var seriesModel = finder.seriesModel;
var xAxisModel = finder.xAxisModel
|| (seriesModel && seriesModel.getReferringComponents('xAxis')[0]);
var yAxisModel = finder.yAxisModel
|| (seriesModel && seriesModel.getReferringComponents('yAxis')[0]);
var gridModel = finder.gridModel;
var coordsList = this._coordsList;
var cartesian;
var axis;
if (seriesModel) {
cartesian = seriesModel.coordinateSystem;
indexOf(coordsList, cartesian) < 0 && (cartesian = null);
}
else if (xAxisModel && yAxisModel) {
cartesian = this.getCartesian(xAxisModel.componentIndex,
yAxisModel.componentIndex);
}
else if (xAxisModel) {
axis = this.getAxis('x', xAxisModel.componentIndex);
}
else if (yAxisModel) {
axis = this.getAxis('y', yAxisModel.componentIndex);
}
// Lowest priority.
else if (gridModel) {
var grid = gridModel.coordinateSystem;
if (grid === this) {
cartesian = this._coordsList[0];
}
}
return {cartesian: cartesian, axis: axis};
};
/**
* @implements
* see {module:echarts/CoodinateSystem}
*/
gridProto.containPoint = function (point) {
var coord = this._coordsList[0];
if (coord) {
return coord.containPoint(point);
}
};
/**
* Initialize cartesian coordinate systems
* @private
*/
gridProto._initCartesian = function (gridModel, ecModel, api) {
var axisPositionUsed = {
left: false,
right: false,
top: false,
bottom: false
};
var axesMap = {
x: {},
y: {}
};
var axesCount = {
x: 0,
y: 0
};
if (!axesCount.x || !axesCount.y) {
// Roll back when there no either x or y axis
this._axesMap = {};
this._axesList = [];
return;
}
this._axesMap = axesMap;
cartesian.grid = this;
cartesian.model = gridModel;
this._coordsMap[key] = cartesian;
this._coordsList.push(cartesian);
cartesian.addAxis(xAxis);
cartesian.addAxis(yAxis);
}, this);
}, this);
function createAxisCreator(axisType) {
return function (axisModel, idx) {
if (!isAxisUsedInTheGrid(axisModel, gridModel, ecModel)) {
return;
}
axis.onZero = axisModel.get('axisLine.onZero');
axis.onZeroAxisIndex = axisModel.get('axisLine.onZeroAxisIndex');
this._axesList.push(axis);
axesMap[axisType][idx] = axis;
axesCount[axisType]++;
};
}
};
/**
* Update cartesian properties from series
* @param {module:echarts/model/Option} option
* @private
*/
gridProto._updateScale = function (ecModel, gridModel) {
// Reset scale
each$1(this._axesList, function (axis) {
axis.scale.setExtent(Infinity, -Infinity);
});
ecModel.eachSeries(function (seriesModel) {
if (isCartesian2D(seriesModel)) {
var axesModels = findAxesModels(seriesModel, ecModel);
var xAxisModel = axesModels[0];
var yAxisModel = axesModels[1];
/**
* @param {string} [dim] 'x' or 'y' or 'auto' or null/undefined
* @return {Object} {baseAxes: [], otherAxes: []}
*/
gridProto.getTooltipAxes = function (dim) {
var baseAxes = [];
var otherAxes = [];
/**
* @inner
*/
function updateAxisTransform(axis, coordBase) {
var axisExtent = axis.getExtent();
var axisExtentSum = axisExtent[0] + axisExtent[1];
// Fast transform
axis.toGlobalCoord = axis.dim === 'x'
? function (coord) {
return coord + coordBase;
}
: function (coord) {
return axisExtentSum - coord + coordBase;
};
axis.toLocalCoord = axis.dim === 'x'
? function (coord) {
return coord - coordBase;
}
: function (coord) {
return axisExtentSum - coord + coordBase;
};
}
if (__DEV__) {
if (!axisModel) {
throw new Error(axisType + ' "' + retrieve(
seriesModel.get(axisType + 'Index'),
seriesModel.get(axisType + 'Id'),
0
) + '" not found');
}
}
return axisModel;
});
}
/**
* @inner
*/
function isCartesian2D(seriesModel) {
return seriesModel.get('coordinateSystem') === 'cartesian2d';
}
gridModel.coordinateSystem = grid;
grids.push(grid);
});
if (__DEV__) {
if (!gridModel) {
throw new Error(
'Grid "' + retrieve(
xAxisModel.get('gridIndex'),
xAxisModel.get('gridId'),
0
) + '" not found'
);
}
if (xAxisModel.getCoordSysModel() !== yAxisModel.getCoordSysModel()) {
throw new Error('xAxis and yAxis must use the same grid');
}
}
seriesModel.coordinateSystem = grid.getCartesian(
xAxisModel.componentIndex, yAxisModel.componentIndex
);
});
return grids;
};
CoordinateSystemManager.register('cartesian2d', Grid);
function makeAxisEventDataBase(axisModel) {
var eventData = {
componentType: axisModel.mainType
};
eventData[axisModel.mainType + 'Index'] = axisModel.componentIndex;
return eventData;
}
/**
* A final axis is translated and rotated from a "standard axis".
* So opt.position and opt.rotation is required.
*
* A standard axis is and axis from [0, 0] to [0, axisExtent[1]],
* for example: (0, 0) ------------> (0, 50)
*
* nameDirection or tickDirection or labelDirection is 1 means tick
* or label is below the standard axis, whereas is -1 means above
* the standard axis. labelOffset means offset between label and axis,
* which is useful when 'onZero', where axisLabel is in the grid and
* label in outside grid.
*
* Tips: like always,
* positive rotation represents anticlockwise, and negative rotation
* represents clockwise.
* The direction of position coordinate is the same as the direction
* of screen coordinate.
*
* Do not need to consider axis 'inverse', which is auto processed by
* axis extent.
*
* @param {module:zrender/container/Group} group
* @param {Object} axisModel
* @param {Object} opt Standard axis parameters.
* @param {Array.<number>} opt.position [x, y]
* @param {number} opt.rotation by radian
* @param {number} [opt.nameDirection=1] 1 or -1 Used when nameLocation is 'middle'
or 'center'.
* @param {number} [opt.tickDirection=1] 1 or -1
* @param {number} [opt.labelDirection=1] 1 or -1
* @param {number} [opt.labelOffset=0] Usefull when onZero.
* @param {string} [opt.axisLabelShow] default get from axisModel.
* @param {string} [opt.axisName] default get from axisModel.
* @param {number} [opt.axisNameAvailableWidth]
* @param {number} [opt.labelRotate] by degree, default get from axisModel.
* @param {number} [opt.labelInterval] Default label interval when label
* interval from model is null or 'auto'.
* @param {number} [opt.strokeContainThreshold] Default label interval when label
* @param {number} [opt.nameTruncateMaxWidth]
*/
var AxisBuilder = function (axisModel, opt) {
/**
* @readOnly
*/
this.opt = opt;
/**
* @readOnly
*/
this.axisModel = axisModel;
// Default value
defaults(
opt,
{
labelOffset: 0,
nameDirection: 1,
tickDirection: 1,
labelDirection: 1,
silent: true
}
);
/**
* @readOnly
*/
this.group = new Group();
// this.group.add(dumbGroup);
// this._dumbGroup = dumbGroup;
dumbGroup.updateTransform();
this._transform = dumbGroup.transform;
this._dumbGroup = dumbGroup;
};
AxisBuilder.prototype = {
constructor: AxisBuilder,
getGroup: function () {
return this.group;
}
};
var builders = {
/**
* @private
*/
axisLine: function () {
var opt = this.opt;
var axisModel = this.axisModel;
if (!axisModel.get('axisLine.show')) {
return;
}
this.group.add(new Line(subPixelOptimizeLine({
// Id for animation
anid: 'line',
shape: {
x1: pt1[0],
y1: pt1[1],
x2: pt2[0],
y2: pt2[1]
},
style: lineStyle,
strokeContainThreshold: opt.strokeContainThreshold || 5,
silent: true,
z2: 1
})));
if (arrows != null) {
if (typeof arrows === 'string') {
// Use the same arrow for start and end point
arrows = [arrows, arrows];
}
if (typeof arrowSize === 'string'
|| typeof arrowSize === 'number'
) {
// Use the same size for width and height
arrowSize = [arrowSize, arrowSize];
}
each$1([{
rotate: opt.rotation + Math.PI / 2,
offset: arrowOffset[0],
r: 0
}, {
rotate: opt.rotation - Math.PI / 2,
offset: arrowOffset[1],
r: Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0])
+ (pt1[1] - pt2[1]) * (pt1[1] - pt2[1]))
}], function (point, index) {
if (arrows[index] !== 'none' && arrows[index] != null) {
var symbol = createSymbol(
arrows[index],
-symbolWidth / 2,
-symbolHeight / 2,
symbolWidth,
symbolHeight,
lineStyle.stroke,
true
);
symbol.attr({
rotation: point.rotate,
position: pos,
silent: true
});
this.group.add(symbol);
}
}, this);
}
},
/**
* @private
*/
axisTickLabel: function () {
var axisModel = this.axisModel;
var opt = this.opt;
/**
* @private
*/
axisName: function () {
var opt = this.opt;
var axisModel = this.axisModel;
var name = retrieve(opt.axisName, axisModel.get('name'));
if (!name) {
return;
}
var labelLayout;
var axisNameAvailableWidth;
if (isNameLocationCenter(nameLocation)) {
labelLayout = innerTextLayout(
opt.rotation,
nameRotation != null ? nameRotation : opt.rotation, // Adapt to
axis.
nameDirection
);
}
else {
labelLayout = endTextLayout(
opt, nameLocation, nameRotation || 0, extent
);
axisNameAvailableWidth = opt.axisNameAvailableWidth;
if (axisNameAvailableWidth != null) {
axisNameAvailableWidth = Math.abs(
axisNameAvailableWidth / Math.sin(labelLayout.rotation)
);
!isFinite(axisNameAvailableWidth) && (axisNameAvailableWidth =
null);
}
}
__fullText: name,
__truncatedText: truncatedText,
position: pos,
rotation: labelLayout.rotation,
silent: isSilent(axisModel),
z2: 1,
tooltip: (tooltipOpt && tooltipOpt.show)
? extend({
content: name,
formatter: function () {
return name;
},
formatterParams: formatterParams
}, tooltipOpt)
: null
});
setTextStyle(textEl.style, textStyleModel, {
text: truncatedText,
textFont: textFont,
textFill: textStyleModel.getTextColor()
|| axisModel.get('axisLine.lineStyle.color'),
textAlign: labelLayout.textAlign,
textVerticalAlign: labelLayout.textVerticalAlign
});
if (axisModel.get('triggerEvent')) {
textEl.eventData = makeAxisEventDataBase(axisModel);
textEl.eventData.targetType = 'axisName';
textEl.eventData.name = name;
}
// FIXME
this._dumbGroup.add(textEl);
textEl.updateTransform();
this.group.add(textEl);
textEl.decomposeTransform();
}
};
/**
* @public
* @static
* @param {Object} opt
* @param {number} axisRotation in radian
* @param {number} textRotation in radian
* @param {number} direction
* @return {Object} {
* rotation, // according to axis
* textAlign,
* textVerticalAlign
* }
*/
var innerTextLayout = AxisBuilder.innerTextLayout = function (axisRotation,
textRotation, direction) {
var rotationDiff = remRadian(textRotation - axisRotation);
var textAlign;
var textVerticalAlign;
return {
rotation: rotationDiff,
textAlign: textAlign,
textVerticalAlign: textVerticalAlign
};
}
function isSilent(axisModel) {
var tooltipOpt = axisModel.get('tooltip');
return axisModel.get('silent')
// Consider mouse cursor, add these restrictions.
|| !(
axisModel.get('triggerEvent') || (tooltipOpt && tooltipOpt.show)
);
}
// FIXME
// Have not consider onBand yet, where tick els is more than label els.
labelEls = labelEls || [];
tickEls = tickEls || [];
function ignoreEl(el) {
el && (el.ignore = true);
}
if (!firstRect || !nextRect) {
return;
}
firstRect.applyTransform(mul$1([], mRotationBack,
current.getLocalTransform()));
nextRect.applyTransform(mul$1([], mRotationBack, next.getLocalTransform()));
return firstRect.intersect(nextRect);
}
function isNameLocationCenter(nameLocation) {
return nameLocation === 'middle' || nameLocation === 'center';
}
/**
* @static
*/
var ifIgnoreOnTick$1 = AxisBuilder.ifIgnoreOnTick = function (
axis,
i,
interval,
ticksCnt,
showMinLabel,
showMaxLabel
) {
if (i === 0 && showMinLabel || i === ticksCnt - 1 && showMaxLabel) {
return false;
}
// FIXME
// Have not consider label overlap (if label is too long) yet.
var rawTick;
var scale$$1 = axis.scale;
return scale$$1.type === 'ordinal'
&& (
typeof interval === 'function'
? (
rawTick = scale$$1.getTicks()[i],
!interval(rawTick, scale$$1.getLabel(rawTick))
)
: i % (interval + 1)
);
};
/**
* @static
*/
var getInterval$1 = AxisBuilder.getInterval = function (model, labelInterval) {
var interval = model.get('interval');
if (interval == null || interval == 'auto') {
interval = labelInterval;
}
return interval;
};
pt1[0] = tickCoord;
pt1[1] = 0;
pt2[0] = tickCoord;
pt2[1] = opt.tickDirection * tickLen;
if (matrix) {
applyTransform(pt1, pt1, matrix);
applyTransform(pt2, pt2, matrix);
}
// Tick line, Not use group transform to have better line draw
var tickEl = new Line(subPixelOptimizeLine({
// Id for animation
anid: 'tick_' + ticks[i],
shape: {
x1: pt1[0],
y1: pt1[1],
x2: pt2[0],
y2: pt2[1]
},
style: defaults(
lineStyleModel.getLineStyle(),
{
stroke: axisModel.get('axisLine.lineStyle.color')
}
),
z2: 2,
silent: true
}));
axisBuilder.group.add(tickEl);
tickEls.push(tickEl);
}
return tickEls;
}
if (!show || axis.scale.isBlank()) {
return;
}
setTextStyle(textEl.style, itemLabelModel, {
text: labels[index],
textAlign: itemLabelModel.getShallow('align', true)
|| labelLayout.textAlign,
textVerticalAlign: itemLabelModel.getShallow('verticalAlign', true)
|| itemLabelModel.getShallow('baseline', true)
|| labelLayout.textVerticalAlign,
textFill: typeof textColor === 'function'
? textColor(
// (1) In category axis with data zoom, tick is not the
original
// index of axis.data. So tick should not be exposed to user
// in category axis.
// (2) Compatible with previous version, which always returns
labelStr.
// But in interval scale labelStr is like '223,445', which
maked
// user repalce ','. So we modify it to return original val but
remain
// it as 'string' to avoid error in replacing.
axis.type === 'category' ? labelStr : axis.type === 'value' ?
tickVal + '' : tickVal,
index
)
: textColor
});
// FIXME
axisBuilder._dumbGroup.add(textEl);
textEl.updateTransform();
labelEls.push(textEl);
axisBuilder.group.add(textEl);
textEl.decomposeTransform();
});
return labelEls;
}
// Check seriesInvolved for performance, in case too many series in some chart.
result.seriesInvolved && collectSeriesInfo(result, ecModel);
return result;
}
if (triggerTooltip == null) {
triggerTooltip = axisPointerModel.get('triggerTooltip');
}
axisPointerModel = fromTooltip
? makeAxisPointerModel(
axis, baseTooltipModel, globalAxisPointerModel, ecModel,
fromTooltip, triggerTooltip
)
: axisPointerModel;
function makeAxisPointerModel(
axis, baseTooltipModel, globalAxisPointerModel, ecModel, fromTooltip,
triggerTooltip
) {
var tooltipAxisPointerModel = baseTooltipModel.getModel('axisPointer');
var volatileOption = {};
each$7(
[
'type', 'snap', 'lineStyle', 'shadowStyle', 'label',
'animation', 'animationDurationUpdate', 'animationEasingUpdate', 'z'
],
function (field) {
volatileOption[field] = clone(tooltipAxisPointerModel.get(field));
}
);
// category axis do not auto snap, otherwise some tick that do not
// has value can not be hovered. value/time/log axis default snap if
// triggered from tooltip and trigger tooltip.
volatileOption.snap = axis.type !== 'category' && !!triggerTooltip;
// Compatibel with previous behavior, tooltip axis do not show label by
default.
// Only these properties can be overrided from tooltip to axisPointer.
if (tooltipAxisPointerModel.get('type') === 'cross') {
volatileOption.type = 'line';
}
var labelOption = volatileOption.label || (volatileOption.label = {});
// Follow the convention, do not show label when triggered by tooltip by
default.
labelOption.show == null && (labelOption.show = false);
return axis.model.getModel(
'axisPointer',
new Model(volatileOption, globalAxisPointerModel, ecModel)
);
}
each$7(result.coordSysAxesInfo[makeKey(coordSys.model)], function
(axisInfo) {
var axis = axisInfo.axis;
if (coordSys.getAxis(axis.dim) === axis) {
axisInfo.seriesModels.push(seriesModel);
axisInfo.seriesDataCount == null && (axisInfo.seriesDataCount = 0);
axisInfo.seriesDataCount += seriesModel.getData().count();
}
});
}, this);
}
/**
* For example:
* {
* axisPointer: {
* links: [{
* xAxisIndex: [2, 4],
* yAxisIndex: 'all'
* }, {
* xAxisId: ['a5', 'a7'],
* xAxisName: 'xxx'
* }]
* }
* }
*/
function getLinkGroupIndex(linksOption, axis) {
var axisModel = axis.model;
var dim = axis.dim;
for (var i = 0; i < linksOption.length; i++) {
var linkOption = linksOption[i] || {};
if (checkPropInLink(linkOption[dim + 'AxisId'], axisModel.id)
|| checkPropInLink(linkOption[dim + 'AxisIndex'],
axisModel.componentIndex)
|| checkPropInLink(linkOption[dim + 'AxisName'], axisModel.name)
) {
return i;
}
}
}
function fixValue(axisModel) {
var axisInfo = getAxisInfo(axisModel);
if (!axisInfo) {
return;
}
option.value = value;
if (useHandle) {
option.status = axisInfo.axis.scale.isBlank() ? 'hide' : 'show';
}
}
function getAxisInfo(axisModel) {
var coordSysAxesInfo = (axisModel.ecModel.getComponent('axisPointer') ||
{}).coordSysAxesInfo;
return coordSysAxesInfo && coordSysAxesInfo.axesInfo[makeKey(axisModel)];
}
function getAxisPointerModel(axisModel) {
var axisInfo = getAxisInfo(axisModel);
return axisInfo && axisInfo.axisPointerModel;
}
function isHandleTrigger(axisPointerModel) {
return !!axisPointerModel.get('handle.show');
}
/**
* @param {module:echarts/model/Model} model
* @return {string} unique key
*/
function makeKey(model) {
return model.type + '||' + model.id;
}
/**
* Base class of AxisView.
*/
var AxisView = extendComponentView({
type: 'axis',
/**
* @private
*/
_axisPointer: null,
/**
* @protected
* @type {string}
*/
axisPointerClass: null,
/**
* @override
*/
render: function (axisModel, ecModel, api, payload) {
// FIXME
// This process should proformed after coordinate systems updated
// (axis scale updated), and should be performed each time update.
// So put it here temporarily, although it is not appropriate to
// put a model-writing procedure in `view`.
this.axisPointerClass && fixValue(axisModel);
/**
* Action handler.
* @public
* @param {module:echarts/coord/cartesian/AxisModel} axisModel
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
* @param {Object} payload
*/
updateAxisPointer: function (axisModel, ecModel, api, payload, force) {
updateAxisPointer(this, axisModel, ecModel, api, payload, false);
},
/**
* @override
*/
remove: function (ecModel, api) {
var axisPointer = this._axisPointer;
axisPointer && axisPointer.remove(api);
AxisView.superApply(this, 'remove', arguments);
},
/**
* @override
*/
dispose: function (ecModel, api) {
disposeAxisPointer(this, api);
AxisView.superApply(this, 'dispose', arguments);
}
});
function updateAxisPointer(axisView, axisModel, ecModel, api, payload, forceRender)
{
var Clazz = AxisView.getAxisPointerClass(axisView.axisPointerClass);
if (!Clazz) {
return;
}
var axisPointerModel = getAxisPointerModel(axisModel);
axisPointerModel
? (axisView._axisPointer || (axisView._axisPointer = new Clazz()))
.render(axisModel, axisPointerModel, api, forceRender)
: disposeAxisPointer(axisView, api);
}
/**
* @param {Object} opt {labelInside}
* @return {Object} {
* position, rotation, labelDirection, labelOffset,
* tickDirection, labelRotate, labelInterval, z2
* }
*/
function layout$1(gridModel, axisModel, opt) {
opt = opt || {};
var grid = gridModel.coordinateSystem;
var axis = axisModel.axis;
var layout = {};
// Axis position
layout.position = [
axisDim === 'y' ? posBound[idx[axisPosition]] : rectBound[0],
axisDim === 'x' ? posBound[idx[axisPosition]] : rectBound[3]
];
// Axis rotation
layout.rotation = Math.PI / 2 * (axisDim === 'x' ? 0 : 1);
if (axisModel.get('axisTick.inside')) {
layout.tickDirection = -layout.tickDirection;
}
if (retrieve(opt.labelInside, axisModel.get('axisLabel.inside'))) {
layout.labelDirection = -layout.labelDirection;
}
return layout;
}
var axisBuilderAttrs = [
'axisLine', 'axisTickLabel', 'axisName'
];
var selfBuilderAttrs = [
'splitArea', 'splitLine'
];
type: 'cartesianAxis',
axisPointerClass: 'CartesianAxisPointer',
/**
* @override
*/
render: function (axisModel, ecModel, api, payload) {
this.group.removeAll();
this.group.add(this._axisGroup);
if (!axisModel.get('show')) {
return;
}
this._axisGroup.add(axisBuilder.getGroup());
/**
* @param {module:echarts/coord/cartesian/AxisModel} axisModel
* @param {module:echarts/coord/cartesian/GridModel} gridModel
* @param {number|Function} labelInterval
* @private
*/
_splitLine: function (axisModel, gridModel, labelInterval) {
var axis = axisModel.axis;
if (axis.scale.isBlank()) {
return;
}
var lineCount = 0;
var p1 = [];
var p2 = [];
// Simple optimization
// Batching the lines if color are the same
var lineStyle = lineStyleModel.getLineStyle();
for (var i = 0; i < ticksCoords.length; i++) {
if (ifIgnoreOnTick(
axis, i, lineInterval, ticksCoords.length,
showMinLabel, showMaxLabel
)) {
continue;
}
if (isHorizontal) {
p1[0] = tickCoord;
p1[1] = gridRect.y;
p2[0] = tickCoord;
p2[1] = gridRect.y + gridRect.height;
}
else {
p1[0] = gridRect.x;
p1[1] = tickCoord;
p2[0] = gridRect.x + gridRect.width;
p2[1] = tickCoord;
}
shape: {
x1: p1[0],
y1: p1[1],
x2: p2[0],
y2: p2[1]
},
style: defaults({
stroke: lineColors[colorIndex]
}, lineStyle),
silent: true
})));
}
},
/**
* @param {module:echarts/coord/cartesian/AxisModel} axisModel
* @param {module:echarts/coord/cartesian/GridModel} gridModel
* @param {number|Function} labelInterval
* @private
*/
_splitArea: function (axisModel, gridModel, labelInterval) {
var axis = axisModel.axis;
if (axis.scale.isBlank()) {
return;
}
var count = 0;
var x;
var y;
var width;
var height;
if (axis.isHorizontal()) {
x = prevX;
y = gridRect.y;
width = tickCoord - x;
height = gridRect.height;
}
else {
x = gridRect.x;
y = prevY;
width = gridRect.width;
height = tickCoord - y;
}
shape: {
x: x,
y: y,
width: width,
height: height
},
style: defaults({
fill: areaColors[colorIndex]
}, areaStyle),
silent: true
}));
prevX = x + width;
prevY = y + height;
}
}
});
CartesianAxisView.extend({
type: 'xAxis'
});
CartesianAxisView.extend({
type: 'yAxis'
});
// Grid view
extendComponentView({
type: 'grid',
});
registerPreprocessor(function (option) {
// Only create grid when need
if (option.xAxis && option.yAxis && !option.grid) {
option.grid = {};
}
});
type: 'series.__base_bar__',
defaultOption: {
zlevel: 0, // 一级层叠
z: 2, // 二级层叠
coordinateSystem: 'cartesian2d',
legendHoverLink: true,
// stack: null
// 最小高度改为 0
barMinHeight: 0,
// 最小角度为 0,仅对极坐标系下的柱状图有效
barMinAngle: 0,
// cursor: null,
// barMaxWidth: null,
// 默认自适应
// barWidth: null,
// 柱间距离,默认为柱形宽度的 30%,可设固定值
// barGap: '30%',
// 类目间柱形距离,默认为类目间距的 20%,可设固定值
// barCategoryGap: '20%',
// label: {
// show: false
// },
itemStyle: {},
emphasis: {}
}
});
BaseBarSeries.extend({
type: 'series.bar',
brushSelector: 'rect'
});
function setLabel(
normalStyle, hoverStyle, itemModel, color, seriesModel, dataIndex,
labelPositionOutside
) {
var labelModel = itemModel.getModel('label');
var hoverLabelModel = itemModel.getModel('emphasis.label');
setLabelStyle(
normalStyle, hoverStyle, labelModel, hoverLabelModel,
{
labelFetcher: seriesModel,
labelDataIndex: dataIndex,
defaultText: getDefaultLabel(seriesModel.getData(), dataIndex),
isRectText: true,
autoColor: color
}
);
fixPosition(normalStyle);
fixPosition(hoverStyle);
}
var barItemStyle = {
getBarItemStyle: function (excludes) {
var style = getBarItemStyle(this, excludes);
if (this.getBorderLineDash) {
var lineDash = this.getBorderLineDash();
lineDash && (style.lineDash = lineDash);
}
return style;
}
};
// FIXME
// Just for compatible with ec2.
extend(Model.prototype, barItemStyle);
extendChartView({
type: 'bar',
return this.group;
},
dispose: noop,
data.diff(oldData)
.add(function (dataIndex) {
if (!data.hasValue(dataIndex)) {
return;
}
updateStyle(
el, data, dataIndex, itemModel, layout,
seriesModel, isHorizontalOrRadial, coord.type === 'polar'
);
})
.update(function (newIndex, oldIndex) {
var el = oldData.getItemGraphicEl(oldIndex);
if (!data.hasValue(newIndex)) {
group.remove(el);
return;
}
if (el) {
updateProps(el, {shape: layout}, animationModel, newIndex);
}
else {
el = elementCreator[coord.type](
data, newIndex, itemModel, layout, isHorizontalOrRadial,
animationModel, true
);
}
data.setItemGraphicEl(newIndex, el);
// Add back
group.add(el);
updateStyle(
el, data, newIndex, itemModel, layout,
seriesModel, isHorizontalOrRadial, coord.type === 'polar'
);
})
.remove(function (dataIndex) {
var el = oldData.getItemGraphicEl(dataIndex);
if (coord.type === 'cartesian2d') {
el && removeRect(dataIndex, animationModel, el);
}
else {
el && removeSector(dataIndex, animationModel, el);
}
})
.execute();
this._data = data;
},
var elementCreator = {
cartesian2d: function (
data, dataIndex, itemModel, layout, isHorizontal,
animationModel, isUpdate
) {
var rect = new Rect({shape: extend({}, layout)});
// Animation
if (animationModel) {
var rectShape = rect.shape;
var animateProperty = isHorizontal ? 'height' : 'width';
var animateTarget = {};
rectShape[animateProperty] = 0;
animateTarget[animateProperty] = layout[animateProperty];
graphic[isUpdate ? 'updateProps' : 'initProps'](rect, {
shape: animateTarget
}, animationModel, dataIndex);
}
return rect;
},
polar: function (
data, dataIndex, itemModel, layout, isRadial,
animationModel, isUpdate
) {
// Keep the same logic with bar in catesion: use end value to control
// direction. Notice that if clockwise is true (by default), the sector
// will always draw clockwisely, no matter whether endAngle is greater
// or less than startAngle.
var clockwise = layout.startAngle < layout.endAngle;
var sector = new Sector({
shape: defaults({clockwise: clockwise}, layout)
});
// Animation
if (animationModel) {
var sectorShape = sector.shape;
var animateProperty = isRadial ? 'r' : 'endAngle';
var animateTarget = {};
sectorShape[animateProperty] = isRadial ? 0 : layout.startAngle;
animateTarget[animateProperty] = layout[animateProperty];
graphic[isUpdate ? 'updateProps' : 'initProps'](sector, {
shape: animateTarget
}, animationModel, dataIndex);
}
return sector;
}
};
var getLayout = {
cartesian2d: function (data, dataIndex, itemModel) {
var layout = data.getItemLayout(dataIndex);
var fixedLineWidth = getLineWidth(itemModel, layout);
// fix layout with lineWidth
var signX = layout.width > 0 ? 1 : -1;
var signY = layout.height > 0 ? 1 : -1;
return {
x: layout.x + signX * fixedLineWidth / 2,
y: layout.y + signY * fixedLineWidth / 2,
width: layout.width - signX * fixedLineWidth,
height: layout.height - signY * fixedLineWidth
};
},
function updateStyle(
el, data, dataIndex, itemModel, layout, seriesModel, isHorizontal, isPolar
) {
var color = data.getItemVisual(dataIndex, 'color');
var opacity = data.getItemVisual(dataIndex, 'opacity');
var itemStyleModel = itemModel.getModel('itemStyle');
var hoverStyle = itemModel.getModel('emphasis.itemStyle').getBarItemStyle();
if (!isPolar) {
el.setShape('r', itemStyleModel.get('barBorderRadius') || 0);
}
el.useStyle(defaults(
{
fill: color,
opacity: opacity
},
itemStyleModel.getBarItemStyle()
));
if (!isPolar) {
setLabel(
el.style, hoverStyle, itemModel, color,
seriesModel, dataIndex, labelPositionOutside
);
}
setHoverStyle(el, hoverStyle);
}
/**
* [Usage]:
* (1)
* createListSimply(seriesModel, ['value']);
* (2)
* createListSimply(seriesModel, {
* coordDimensions: ['value'],
* dimensionsCount: 5
* });
*
* @param {module:echarts/model/Series} seriesModel
* @param {Object|Array.<string|Object>} opt opt or coordDimensions
* The options in opt, see `echarts/data/helper/createDimensions`
* @param {Array.<string>} [nameList]
* @return {module:echarts/data/List}
*/
var createListSimply = function (seriesModel, opt, nameList) {
opt = isArray(opt) && {coordDimensions: opt} || extend({}, opt);
return list;
};
/**
* Data selectable mixin for chart series.
* To eanble data select, option of series must have `selectedMode`.
* And each data item will use `selected` to toggle itself selected status
*/
var selectableMixin = {
/**
* @param {Array.<Object>} targetList [{name, value, selected}, ...]
* If targetList is an array, it should like [{name: ...,
value: ...}, ...].
* If targetList is a "List", it must have coordDim: 'value' dimension
and name.
*/
updateSelectedMap: function (targetList) {
this._targetList = isArray(targetList) ? targetList.slice() : [];
/**
* Either name or id should be passed as input here.
* If both of them are defined, id is used.
*
* @param {string|undefined} name name of data
* @param {number|undefined} id dataIndex of data
*/
// PENGING If selectedMode is null ?
select: function (name, id) {
var target = id != null
? this._targetList[id]
: this._selectTargetMap.get(name);
var selectedMode = this.get('selectedMode');
if (selectedMode === 'single') {
this._selectTargetMap.each(function (target) {
target.selected = false;
});
}
target && (target.selected = true);
},
/**
* Either name or id should be passed as input here.
* If both of them are defined, id is used.
*
* @param {string|undefined} name name of data
* @param {number|undefined} id dataIndex of data
*/
unSelect: function (name, id) {
var target = id != null
? this._targetList[id]
: this._selectTargetMap.get(name);
// var selectedMode = this.get('selectedMode');
// selectedMode !== 'single' && target && (target.selected = false);
target && (target.selected = false);
},
/**
* Either name or id should be passed as input here.
* If both of them are defined, id is used.
*
* @param {string|undefined} name name of data
* @param {number|undefined} id dataIndex of data
*/
toggleSelected: function (name, id) {
var target = id != null
? this._targetList[id]
: this._selectTargetMap.get(name);
if (target != null) {
this[target.selected ? 'unSelect' : 'select'](name, id);
return target.selected;
}
},
/**
* Either name or id should be passed as input here.
* If both of them are defined, id is used.
*
* @param {string|undefined} name name of data
* @param {number|undefined} id dataIndex of data
*/
isSelected: function (name, id) {
var target = id != null
? this._targetList[id]
: this._selectTargetMap.get(name);
return target && target.selected;
}
};
type: 'series.pie',
// Overwrite
init: function (option) {
PieSeries.superApply(this, 'init', arguments);
this.updateSelectedMap(this._createSelectableList());
this._defaultLabelLine(option);
},
// Overwrite
mergeOption: function (newOption) {
PieSeries.superCall(this, 'mergeOption', newOption);
this.updateSelectedMap(this._createSelectableList());
},
_createSelectableList: function () {
var data = this.getRawData();
var valueDim = data.mapDimension('value');
var targetList = [];
for (var i = 0, len = data.count(); i < len; i++) {
targetList.push({
name: data.getName(i),
value: data.get(valueDim, i),
selected: retrieveRawAttr(data, i, 'selected')
});
}
return targetList;
},
// Overwrite
getDataParams: function (dataIndex) {
var data = this.getData();
var params = PieSeries.superCall(this, 'getDataParams', dataIndex);
// FIXME toFixed?
params.percent = getPercentWithPrecision(
valueList,
dataIndex,
data.hostModel.get('percentPrecision')
);
params.$vars.push('percent');
return params;
},
defaultOption: {
zlevel: 0,
z: 2,
legendHoverLink: true,
hoverAnimation: true,
// 默认全局居中
center: ['50%', '50%'],
radius: [0, '75%'],
// 默认顺时针
clockwise: true,
startAngle: 90,
// 最小角度改为 0
minAngle: 0,
// 选中时扇区偏移量
selectedOffset: 10,
// 高亮扇区偏移量
hoverOffset: 10,
percentPrecision: 2,
// cursor: null,
label: {
// If rotate around circle
rotate: false,
show: true,
// 'outer', 'inside', 'center'
position: 'outer'
// formatter: 标签文本格式器,同 Tooltip.formatter,不支持异步回调
// 默认使用全局文本样式,详见 TEXTSTYLE
// distance: 当 position 为 inner 时有效,为 label 位置到圆心的距离与圆半径(环状图
为内外半径和)的比例系数
},
// Enabled when label.normal.position is 'outer'
labelLine: {
show: true,
// 引导线两段中的第一段长度
length: 15,
// 引导线两段中的第二段长度
length2: 15,
smooth: false,
lineStyle: {
// color: 各异,
width: 1,
type: 'solid'
}
},
itemStyle: {
borderWidth: 1
},
animationEasing: 'cubicOut'
}
});
mixin(PieSeries, selectableMixin);
/**
* @param {module:echarts/model/Series} seriesModel
* @param {boolean} hasAnimation
* @inner
*/
function updateDataSelected(uid, seriesModel, hasAnimation, api) {
var data = seriesModel.getData();
var dataIndex = this.dataIndex;
var name = data.getName(dataIndex);
var selectedOffset = seriesModel.get('selectedOffset');
api.dispatchAction({
type: 'pieToggleSelect',
from: uid,
name: name,
seriesId: seriesModel.id
});
data.each(function (idx) {
toggleItemSelected(
data.getItemGraphicEl(idx),
data.getItemLayout(idx),
seriesModel.isSelected(data.getName(idx)),
selectedOffset,
hasAnimation
);
});
}
/**
* @param {module:zrender/graphic/Sector} el
* @param {Object} layout
* @param {boolean} isSelected
* @param {number} selectedOffset
* @param {boolean} hasAnimation
* @inner
*/
function toggleItemSelected(el, layout, isSelected, selectedOffset, hasAnimation) {
var midAngle = (layout.startAngle + layout.endAngle) / 2;
var dx = Math.cos(midAngle);
var dy = Math.sin(midAngle);
hasAnimation
// animateTo will stop revious animation like update transition
? el.animate()
.when(200, {
position: position
})
.start('bounceOut')
: el.attr('position', position);
}
/**
* Piece of pie including Sector, Label, LabelLine
* @constructor
* @extends {module:zrender/graphic/Group}
*/
function PiePiece(data, idx) {
Group.call(this);
if (firstCreate) {
sector.setShape(sectorShape);
}
else {
updateProps(sector, {
shape: sectorShape
}, seriesModel, idx);
}
sector.useStyle(
defaults(
{
lineJoin: 'bevel',
fill: visualColor
},
itemModel.getModel('itemStyle').getItemStyle()
)
);
sector.hoverStyle = itemModel.getModel('emphasis.itemStyle').getItemStyle();
// Toggle selected
toggleItemSelected(
this,
data.getItemLayout(idx),
seriesModel.isSelected(null, idx),
seriesModel.get('selectedOffset'),
seriesModel.get('animation')
);
function onEmphasis() {
// Sector may has animation of updating data. Force to move to the last
frame
// Or it may stopped on the wrong shape
sector.stopAnimation(true);
sector.animateTo({
shape: {
r: layout.r + seriesModel.get('hoverOffset')
}
}, 300, 'elasticOut');
}
function onNormal() {
sector.stopAnimation(true);
sector.animateTo({
shape: {
r: layout.r
}
}, 300, 'elasticOut');
}
sector.off('mouseover').off('mouseout').off('emphasis').off('normal');
if (itemModel.get('hoverAnimation') && seriesModel.isAnimationEnabled()) {
sector
.on('mouseover', onEmphasis)
.on('mouseout', onNormal)
.on('emphasis', onEmphasis)
.on('normal', onNormal);
}
this._updateLabel(data, idx);
setHoverStyle(this);
};
updateProps(labelLine, {
shape: {
points: labelLayout.linePoints || [
[labelLayout.x, labelLayout.y], [labelLayout.x, labelLayout.y],
[labelLayout.x, labelLayout.y]
]
}
}, seriesModel, idx);
updateProps(labelText, {
style: {
x: labelLayout.x,
y: labelLayout.y
}
}, seriesModel, idx);
labelText.attr({
rotation: labelLayout.rotation,
origin: [labelLayout.x, labelLayout.y],
z2: 10
});
setLabelStyle(
labelText.style, labelText.hoverStyle = {}, labelModel, labelHoverModel,
{
labelFetcher: data.hostModel,
labelDataIndex: idx,
defaultText: data.getName(idx),
autoColor: visualColor,
useInsideStyle: !!labelLayout.inside
},
{
textAlign: labelLayout.textAlign,
textVerticalAlign: labelLayout.verticalAlign,
opacity: data.getItemVisual(idx, 'opacity')
}
);
labelLine.hoverStyle =
labelLineHoverModel.getModel('lineStyle').getLineStyle();
inherits(PiePiece, Group);
// Pie view
var PieView = Chart.extend({
type: 'pie',
init: function () {
var sectorGroup = new Group();
this._sectorGroup = sectorGroup;
},
data.diff(oldData)
.add(function (idx) {
var piePiece = new PiePiece(data, idx);
// Default expansion animation
if (isFirstRender && animationType !== 'scale') {
piePiece.eachChild(function (child) {
child.stopAnimation(true);
});
}
data.setItemGraphicEl(idx, piePiece);
group.add(piePiece);
})
.update(function (newIdx, oldIdx) {
var piePiece = oldData.getItemGraphicEl(oldIdx);
piePiece.updateData(data, newIdx);
piePiece.off('click');
selectedMode && piePiece.on('click', onSectorClick);
group.add(piePiece);
data.setItemGraphicEl(newIdx, piePiece);
})
.remove(function (idx) {
var piePiece = oldData.getItemGraphicEl(idx);
group.remove(piePiece);
})
.execute();
if (
hasAnimation && isFirstRender && data.count() > 0
// Default expansion animation
&& animationType !== 'scale'
) {
var shape = data.getItemLayout(0);
var r = Math.max(api.getWidth(), api.getHeight()) / 2;
this._data = data;
},
_createClipPath: function (
cx, cy, r, startAngle, clockwise, cb, seriesModel
) {
var clipPath = new Sector({
shape: {
cx: cx,
cy: cy,
r0: 0,
r: r,
startAngle: startAngle,
endAngle: startAngle,
clockwise: clockwise
}
});
initProps(clipPath, {
shape: {
endAngle: startAngle + (clockwise ? 1 : -1) * Math.PI * 2
}
}, seriesModel, cb);
return clipPath;
},
/**
* @implement
*/
containPoint: function (point, seriesModel) {
var data = seriesModel.getData();
var itemLayout = data.getItemLayout(0);
if (itemLayout) {
var dx = point[0] - itemLayout.cx;
var dy = point[1] - itemLayout.cy;
var radius = Math.sqrt(dx * dx + dy * dy);
return radius <= itemLayout.r && radius >= itemLayout.r0;
}
}
});
return seiresModelMap;
},
reset: function (seriesModel, ecModel) {
var dataAll = seriesModel.getRawData();
var idxMap = {};
var data = seriesModel.getData();
data.each(function (idx) {
var rawIdx = data.getRawIndex(idx);
idxMap[rawIdx] = idx;
});
dataAll.each(function (rawIdx) {
var filteredIdx = idxMap[rawIdx];
if (!singleDataColor) {
// FIXME Performance
var itemModel = dataAll.getItemModel(rawIdx);
// FIXME emphasis label position is not same with normal label position
// 压
function shiftDown(start, end, delta, dir) {
for (var j = start; j < end; j++) {
list[j].y += delta;
if (j > start
&& j + 1 < end
&& list[j + 1].y > list[j].y + list[j].height
) {
shiftUp(j, delta / 2);
return;
}
}
// 弹
function shiftUp(end, delta) {
for (var j = end; j >= 0; j--) {
list[j].y -= delta;
if (j > 0
&& list[j].y > list[j - 1].y + list[j - 1].height
) {
break;
}
}
}
var lastY = 0;
var delta;
var len = list.length;
var upList = [];
var downList = [];
for (var i = 0; i < len; i++) {
delta = list[i].y - lastY;
if (delta < 0) {
shiftDown(i, len, -delta, dir);
}
lastY = list[i].y + list[i].height;
}
if (viewHeight - lastY < 0) {
shiftUp(len - 1, lastY - viewHeight);
}
for (var i = 0; i < len; i++) {
if (list[i].y >= cy) {
downList.push(list[i]);
}
else {
upList.push(list[i]);
}
}
changeX(upList, false, cx, cy, r, dir);
changeX(downList, true, cx, cy, r, dir);
}
data.each(function (idx) {
var layout = data.getItemLayout(idx);
var textX;
var textY;
var linePoints;
var textAlign;
cx = layout.cx;
cy = layout.cy;
textX = x1 + dx * 3;
textY = y1 + dy * 3;
if (!isLabelInside) {
// For roseType
var x2 = x1 + dx * (labelLineLen + r - layout.r);
var y2 = y1 + dy * (labelLineLen + r - layout.r);
var x3 = x2 + ((dx < 0 ? -1 : 1) * labelLineLen2);
var y3 = y2;
if (!isArray(radius)) {
radius = [0, radius];
}
if (!isArray(center)) {
center = [center, center];
}
var validDataCount = 0;
data.each(valueDim, function (value) {
!isNaN(value) && validDataCount++;
});
// [0...max]
var extent = data.getDataExtent(valueDim);
extent[0] = 0;
currentAngle = endAngle;
});
createDataSelectAction('pie', [{
type: 'pieToggleSelect',
event: 'pieselectchanged',
method: 'toggleSelected'
}, {
type: 'pieSelect',
event: 'pieselected',
method: 'select'
}, {
type: 'pieUnSelect',
event: 'pieunselected',
method: 'unSelect'
}]);
registerVisual(dataColor('pie'));
registerLayout(curry(pieLayout, 'pie'));
registerProcessor(dataFilter('pie'));
SeriesModel.extend({
type: 'series.scatter',
brushSelector: 'point',
getProgressive: function () {
var progressive = this.option.progressive;
if (progressive == null) {
// PENDING
return this.option.large ? 5e3 : this.get('progressive');
}
return progressive;
},
getProgressiveThreshold: function () {
var progressiveThreshold = this.option.progressiveThreshold;
if (progressiveThreshold == null) {
// PENDING
return this.option.large ? 1e4 : this.get('progressiveThreshold');
}
return progressiveThreshold;
},
defaultOption: {
coordinateSystem: 'cartesian2d',
zlevel: 0,
z: 2,
legendHoverLink: true,
hoverAnimation: true,
// Cartesian coordinate system
// xAxisIndex: 0,
// yAxisIndex: 0,
large: false,
// Available when large is true
largeThreshold: 2000,
// cursor: null,
// label: {
// show: false
// distance: 5,
// formatter: 标签文本格式器,同 Tooltip.formatter,不支持异步回调
// position: 默认自适应,水平布局为'top',垂直布局为'right',可选为
// 'inside'|'left'|'right'|'top'|'bottom'
// 默认使用全局文本样式,详见 TEXTSTYLE
// },
itemStyle: {
opacity: 0.8
// color: 各异
},
progressive: null
}
});
var BOOST_SIZE_THRESHOLD = 4;
shape: {
points: null
},
symbolProxy: null,
// Do draw in afterBrush.
if (canBoost) {
return;
}
for (var i = 0; i < points.length;) {
var x = points[i++];
var y = points[i++];
if (isNaN(x) || isNaN(y)) {
continue;
}
symbolProxyShape.x = x - size[0] / 2;
symbolProxyShape.y = y - size[1] / 2;
symbolProxyShape.width = size[0];
symbolProxyShape.height = size[1];
if (!canBoost) {
return;
}
this.setTransform(ctx);
// PENDING If style or other canvas status changed?
for (var i = 0; i < points.length;) {
var x = points[i++];
var y = points[i++];
if (isNaN(x) || isNaN(y)) {
continue;
}
// fillRect is faster than building a rect path and draw.
// And it support light globalCompositeOperation.
ctx.fillRect(
x - size[0] / 2, y - size[1] / 2,
size[0], size[1]
);
}
this.restoreTransform(ctx);
},
return -1;
}
});
function LargeSymbolDraw() {
this.group = new Group();
}
largeSymbolProto.isPersistent = function () {
return !this._incremental;
};
/**
* Update symbols draw by new data
* @param {module:echarts/data/List} data
*/
largeSymbolProto.updateData = function (data) {
this.group.removeAll();
var symbolEl = new LargeSymbolPath({
rectHover: true,
cursor: 'default'
});
symbolEl.setShape({
points: data.getLayout('symbolPoints')
});
this._setCommon(symbolEl, data);
this.group.add(symbolEl);
this._incremental = null;
};
this._clearIncremental();
// Only use incremental displayables when data amount is larger than 2 million.
// PENDING Incremental data?
if (data.count() > 2e6) {
if (!this._incremental) {
this._incremental = new IncrementalDisplayble({
silent: true
});
}
this.group.add(this._incremental);
}
else {
this._incremental = null;
}
};
symbolEl.setShape({
points: data.getLayout('symbolPoints')
});
this._setCommon(symbolEl, data, !!this._incremental);
};
// TODO
// if (data.hasItemVisual.symbolSize) {
// // TODO typed array?
// symbolEl.setShape('sizes', data.mapArray(
// function (idx) {
// var size = data.getItemVisual(idx, 'symbolSize');
// return (size instanceof Array) ? size : [size, size];
// }
// ));
// }
// else {
var size = data.getVisual('symbolSize');
symbolEl.setShape('size', (size instanceof Array) ? size : [size, size]);
// }
if (!isIncremental) {
// Enable tooltip
// PENDING May have performance issue when path is extremely large
symbolEl.seriesIndex = hostModel.seriesIndex;
symbolEl.on('mousemove', function (e) {
symbolEl.dataIndex = null;
var dataIndex = symbolEl.findDataIndex(e.offsetX, e.offsetY);
if (dataIndex >= 0) {
// Provide dataIndex for tooltip
symbolEl.dataIndex = dataIndex + (symbolEl.startIndex || 0);
}
});
}
};
largeSymbolProto.remove = function () {
this._clearIncremental();
this._incremental = null;
this.group.removeAll();
};
largeSymbolProto._clearIncremental = function () {
var incremental = this._incremental;
if (incremental) {
incremental.clearDisplaybles();
}
};
extendChartView({
type: 'scatter',
symbolDraw.incrementalPrepareUpdate(data);
this._finished = false;
},
this._symbolDraw.updateLayout(data);
}
},
this.group.add(symbolDraw.group);
return symbolDraw;
},
remove: function (ecModel, api) {
this._symbolDraw && this._symbolDraw.remove(true);
this._symbolDraw = null;
},
dispose: function () {}
});
// data.selectRange(range);
// });
// });
/**
* Axis type
* - 'category'
* - 'value'
* - 'time'
* - 'log'
* @type {string}
*/
this.type = 'value';
this.angle = 0;
/**
* Indicator name
* @type {string}
*/
this.name = '';
/**
* @type {module:echarts/model/Model}
*/
this.model;
}
inherits(IndicatorAxis, Axis);
// TODO clockwise
this._model = radarModel;
/**
* Radar dimensions
* @type {Array.<string>}
*/
this.dimensions = [];
this.resize(radarModel, api);
/**
* @type {number}
* @readOnly
*/
this.cx;
/**
* @type {number}
* @readOnly
*/
this.cy;
/**
* @type {number}
* @readOnly
*/
this.r;
/**
* @type {number}
* @readOnly
*/
this.startAngle;
}
Radar.prototype.getIndicatorAxes = function () {
return this._indicatorAxes;
};
function increaseInterval(interval) {
var exp10 = Math.pow(10, Math.floor(Math.log(interval) / Math.LN10));
// Increase interval
var f = interval / exp10;
if (f === 2) {
f = 5;
}
else { // f is 2 or 5
f *= 2;
}
return f * exp10;
}
// Force all the axis fixing the maxSplitNumber.
each$1(indicatorAxes, function (indicatorAxis, idx) {
var rawExtent = getScaleExtent(indicatorAxis.scale, indicatorAxis.model);
niceScaleExtent(indicatorAxis.scale, indicatorAxis.model);
interval = increaseInterval(interval);
} while (max < rawExtent[1] && isFinite(max) &&
isFinite(rawExtent[1]));
}
else if (fixedMax != null) {
var min;
// User set min, expand extent on the other side
do {
min = fixedMax - interval * splitNumber;
scale.setExtent(min, +fixedMax);
scale.setInterval(interval);
interval = increaseInterval(interval);
} while (min > rawExtent[0] && isFinite(min) &&
isFinite(rawExtent[0]));
}
else {
var nicedSplitNumber = scale.getTicks().length - 1;
if (nicedSplitNumber > splitNumber) {
interval = increaseInterval(interval);
}
// PENDING
var center = Math.round((rawExtent[0] + rawExtent[1]) / 2 / interval) *
interval;
var halfSplitNumber = Math.round(splitNumber / 2);
scale.setExtent(
round$1(center - halfSplitNumber * interval),
round$1(center + (splitNumber - halfSplitNumber) * interval)
);
scale.setInterval(interval);
}
});
};
/**
* Radar dimensions is based on the data
* @type {Array}
*/
Radar.dimensions = [];
CoordinateSystemManager.register('radar', Radar);
type: 'radar',
optionUpdated: function () {
var boundaryGap = this.get('boundaryGap');
var splitNumber = this.get('splitNumber');
var scale = this.get('scale');
var axisLine = this.get('axisLine');
var axisTick = this.get('axisTick');
var axisLabel = this.get('axisLabel');
var nameTextStyle = this.get('name');
var showName = this.get('name.show');
var nameFormatter = this.get('name.formatter');
var nameGap = this.get('nameGap');
var triggerEvent = this.get('triggerEvent');
// For triggerEvent.
model.mainType = 'radar';
model.componentIndex = this.componentIndex;
return model;
}, this);
this.getIndicatorModels = function () {
return indicatorModels;
};
},
defaultOption: {
zlevel: 0,
z: 0,
radius: '75%',
startAngle: 90,
name: {
show: true
// formatter: null
// textStyle: {}
},
splitNumber: 5,
nameGap: 15,
scale: false,
// Polygon or circle
shape: 'polygon',
axisLine: merge(
{
lineStyle: {
color: '#bbb'
}
},
valueAxisDefault.axisLine
),
axisLabel: defaultsShow(valueAxisDefault.axisLabel, false),
axisTick: defaultsShow(valueAxisDefault.axisTick, false),
splitLine: defaultsShow(valueAxisDefault.splitLine, true),
splitArea: defaultsShow(valueAxisDefault.splitArea, true),
var axisBuilderAttrs$1 = [
'axisLine', 'axisTickLabel', 'axisName'
];
extendComponentView({
type: 'radar',
this._buildAxes(radarModel);
this._buildSplitLineAndArea(radarModel);
},
if (showSplitLine) {
var colorIndex = getColorIndex(splitLines, splitLineColors, i);
splitLines[colorIndex].push(new Polyline({
shape: {
points: points
}
}));
}
if (showSplitArea && prevPoints) {
var colorIndex = getColorIndex(splitAreas, splitAreaColors, i -
1);
splitAreas[colorIndex].push(new Polygon({
shape: {
points: points.concat(prevPoints)
}
}));
}
prevPoints = points.slice().reverse();
}
}
}
});
type: 'series.radar',
dependencies: ['radar'],
// Overwrite
init: function (option) {
RadarSeries.superApply(this, 'init', arguments);
defaultOption: {
zlevel: 0,
z: 2,
coordinateSystem: 'radar',
legendHoverLink: true,
radarIndex: 0,
lineStyle: {
width: 2,
type: 'solid'
},
label: {
position: 'top'
},
// areaStyle: {
// },
// itemStyle: {}
symbol: 'emptyCircle',
symbolSize: 4
// symbolRotate: null
}
});
function normalizeSymbolSize(symbolSize) {
if (!isArray(symbolSize)) {
symbolSize = [+symbolSize, +symbolSize];
}
return symbolSize;
}
extendChartView({
type: 'radar',
function getInitialPoints(points) {
return map(points, function (pt) {
return [polar.cx, polar.cy];
});
}
data.diff(oldData)
.add(function (idx) {
var points = data.getItemLayout(idx);
if (!points) {
return;
}
var polygon = new Polygon();
var polyline = new Polyline();
var target = {
shape: {
points: points
}
};
polygon.shape.points = getInitialPoints(points);
polyline.shape.points = getInitialPoints(points);
initProps(polygon, target, seriesModel, idx);
initProps(polyline, target, seriesModel, idx);
updateSymbols(
polyline.shape.points, points, symbolGroup, data, idx, true
);
data.setItemGraphicEl(idx, itemGroup);
})
.update(function (newIdx, oldIdx) {
var itemGroup = oldData.getItemGraphicEl(oldIdx);
var polyline = itemGroup.childAt(0);
var polygon = itemGroup.childAt(1);
var symbolGroup = itemGroup.childAt(2);
var target = {
shape: {
points: data.getItemLayout(newIdx)
}
};
if (!target.shape.points) {
return;
}
updateSymbols(
polyline.shape.points, target.shape.points, symbolGroup, data,
newIdx, false
);
data.setItemGraphicEl(newIdx, itemGroup);
})
.remove(function (idx) {
group.remove(oldData.getItemGraphicEl(idx));
})
.execute();
group.add(itemGroup);
polyline.useStyle(
defaults(
itemModel.getModel('lineStyle').getLineStyle(),
{
fill: 'none',
stroke: color
}
)
);
polyline.hoverStyle =
itemModel.getModel('emphasis.lineStyle').getLineStyle();
polygon.useStyle(
defaults(
areaStyleModel.getAreaStyle(),
{
fill: color,
opacity: 0.7
}
)
);
polygon.hoverStyle = hoverAreaStyleModel.getAreaStyle();
var itemStyle =
itemModel.getModel('itemStyle').getItemStyle(['color']);
var itemHoverStyle =
itemModel.getModel('emphasis.itemStyle').getItemStyle();
var labelModel = itemModel.getModel('label');
var labelHoverModel = itemModel.getModel('emphasis.label');
symbolGroup.eachChild(function (symbolPath) {
symbolPath.setStyle(itemStyle);
symbolPath.hoverStyle = clone(itemHoverStyle);
setLabelStyle(
symbolPath.style, symbolPath.hoverStyle, labelModel,
labelHoverModel,
{
labelFetcher: data.hostModel,
labelDataIndex: idx,
labelDimIndex: symbolPath.__dimIdx,
defaultText: data.get(data.dimensions[symbolPath.__dimIdx],
idx),
autoColor: color,
isRectText: true
}
);
});
function onEmphasis() {
polygon.attr('ignore', hoverPolygonIgnore);
}
function onNormal() {
polygon.attr('ignore', polygonIgnore);
}
itemGroup.off('mouseover').off('mouseout').off('normal').off('emphasis');
itemGroup.on('emphasis', onEmphasis)
.on('mouseover', onEmphasis)
.on('normal', onNormal)
.on('mouseout', onNormal);
setHoverStyle(itemGroup);
});
this._data = data;
},
remove: function () {
this.group.removeAll();
this._data = null;
},
dispose: function () {}
});
data.each(function (idx) {
// Close polygon
points[idx][0] && points[idx].push(points[idx][0].slice());
data.setItemLayout(idx, points[idx]);
});
});
};
/**
* Simple view coordinate system
* Mapping given x, y to transformd view x, y
*/
function View(name) {
/**
* @type {string}
*/
this.name = name;
/**
* @type {Object}
*/
this.zoomLimit;
Transformable.call(this);
this._center;
this._zoom;
}
View.prototype = {
constructor: View,
type: 'view',
/**
* @param {Array.<string>}
* @readOnly
*/
dimensions: ['x', 'y'],
/**
* Set bounding rect
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
*/
// PENDING to getRect
setBoundingRect: function (x, y, width, height) {
this._rect = new BoundingRect(x, y, width, height);
return this._rect;
},
/**
* @return {module:zrender/core/BoundingRect}
*/
// PENDING to getRect
getBoundingRect: function () {
return this._rect;
},
/**
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
*/
setViewRect: function (x, y, width, height) {
this.transformTo(x, y, width, height);
this._viewRect = new BoundingRect(x, y, width, height);
},
/**
* Transformed to particular position and size
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
*/
transformTo: function (x, y, width, height) {
var rect = this.getBoundingRect();
var rawTransform = this._rawTransformable;
rawTransform.transform = rect.calculateTransform(
new BoundingRect(x, y, width, height)
);
rawTransform.decomposeTransform();
this._updateTransform();
},
/**
* Set center of view
* @param {Array.<number>} [centerCoord]
*/
setCenter: function (centerCoord) {
if (!centerCoord) {
return;
}
this._center = centerCoord;
this._updateCenterAndZoom();
},
/**
* @param {number} zoom
*/
setZoom: function (zoom) {
zoom = zoom || 1;
this._updateCenterAndZoom();
},
/**
* Get default center without roam
*/
getDefaultCenter: function () {
// Rect before any transform
var rawRect = this.getBoundingRect();
var cx = rawRect.x + rawRect.width / 2;
var cy = rawRect.y + rawRect.height / 2;
getCenter: function () {
return this._center || this.getDefaultCenter();
},
getZoom: function () {
return this._zoom || 1;
},
/**
* @return {Array.<number}
*/
getRoamTransform: function () {
return this._roamTransformable.getLocalTransform();
},
/**
* Remove roam
*/
_updateCenterAndZoom: function () {
// Must update after view transform updated
var rawTransformMatrix = this._rawTransformable.getLocalTransform();
var roamTransform = this._roamTransformable;
var defaultCenter = this.getDefaultCenter();
var center = this.getCenter();
var zoom = this.getZoom();
roamTransform.origin = center;
roamTransform.position = [
defaultCenter[0] - center[0],
defaultCenter[1] - center[1]
];
roamTransform.scale = [zoom, zoom];
this._updateTransform();
},
/**
* Update transform from roam and mapLocation
* @private
*/
_updateTransform: function () {
var roamTransformable = this._roamTransformable;
var rawTransformable = this._rawTransformable;
rawTransformable.parent = roamTransformable;
roamTransformable.updateTransform();
rawTransformable.updateTransform();
this._rawTransform = rawTransformable.getLocalTransform();
this.decomposeTransform();
},
/**
* @return {module:zrender/core/BoundingRect}
*/
getViewRect: function () {
return this._viewRect;
},
/**
* Get view rect after roam transform
* @return {module:zrender/core/BoundingRect}
*/
getViewRectAfterRoam: function () {
var rect = this.getBoundingRect().clone();
rect.applyTransform(this.transform);
return rect;
},
/**
* Convert a single (lon, lat) data item to (x, y) point.
* @param {Array.<number>} data
* @param {boolean} noRoam
* @param {Array.<number>} [out]
* @return {Array.<number>}
*/
dataToPoint: function (data, noRoam, out) {
var transform = noRoam ? this._rawTransform : this.transform;
out = out || [];
return transform
? v2ApplyTransform$1(out, data, transform)
: copy(out, data);
},
/**
* Convert a (x, y) point to (lon, lat) data
* @param {Array.<number>} point
* @return {Array.<number>}
*/
pointToData: function (point) {
var invTransform = this.invTransform;
return invTransform
? v2ApplyTransform$1([], point, invTransform)
: [point[0], point[1]];
},
/**
* @implements
* see {module:echarts/CoodinateSystem}
*/
convertToPixel: curry(doConvert$1, 'dataToPoint'),
/**
* @implements
* see {module:echarts/CoodinateSystem}
*/
convertFromPixel: curry(doConvert$1, 'pointToData'),
/**
* @implements
* see {module:echarts/CoodinateSystem}
*/
containPoint: function (point) {
return this.getViewRectAfterRoam().contain(point[0], point[1]);
}
/**
* @return {number}
*/
// getScalarScale: function () {
// // Use determinant square root of transform to mutiply scalar
// var m = this.transform;
// var det = Math.sqrt(Math.abs(m[0] * m[3] - m[2] * m[1]));
// return det;
// }
};
mixin(View, Transformable);
var points$1 = [
[[0,3.5],[7,11.2],[15,11.9],[30,7],[42,0.7],[52,0.7],
[56,7.7],[59,0.7],[64,0.7],[64,0],[5,0],[0,3.5]],
[[13,16.1],[19,14.7],[16,21.7],[11,23.1],[13,16.1]],
[[12,32.2],[14,38.5],[15,38.5],[13,32.2],[12,32.2]],
[[16,47.6],[12,53.2],[13,53.2],[18,47.6],[16,47.6]],
[[6,64.4],[8,70],[9,70],[8,64.4],[6,64.4]],
[[23,82.6],[29,79.8],[30,79.8],[25,82.6],[23,82.6]],
[[37,70.7],[43,62.3],[44,62.3],[39,70.7],[37,70.7]],
[[48,51.1],[51,45.5],[53,45.5],[50,51.1],[48,51.1]],
[[51,35],[51,28.7],[53,28.7],[53,35],[51,35]],
[[52,22.4],[55,17.5],[56,17.5],[53,22.4],[52,22.4]],
[[58,12.6],[62,7],[63,7],[60,12.6],[58,12.6]],
[[0,3.5],[0,93.1],[64,93.1],[64,0],[63,0],[63,92.4],
[1,92.4],[1,3.5],[0,3.5]]
];
points$1[i$1][k][0] += geoCoord[0];
points$1[i$1][k][1] += geoCoord[1];
}
}
var coordsOffsetMap = {
'南海诸岛' : [32, 80],
// 全国
'广东': [0, -10],
'香港': [10, 5],
'澳门': [-10, 10],
//'北京': [-10, 0],
'天津': [5, 5]
};
var geoCoordMap = {
'Russia': [100, 60],
'United States': [-99, 38],
'United States of America': [-99, 38]
};
var points$2 = [
[
[123.45165252685547, 25.73527164402261],
[123.49731445312499, 25.73527164402261],
[123.49731445312499, 25.750734064600884],
[123.45165252685547, 25.750734064600884],
[123.45165252685547, 25.73527164402261]
]
];
/**
* [Geo description]
* @param {string} name Geo name
* @param {string} map Map type
* @param {Object} geoJson
* @param {Object} [specialAreas]
* Specify the positioned areas by left, top, width, height
* @param {Object.<string, string>} [nameMap]
* Specify name alias
*/
function Geo(name, map$$1, geoJson, specialAreas, nameMap) {
View.call(this, name);
/**
* Map type
* @type {string}
*/
this.map = map$$1;
this._nameCoordMap = createHashMap();
Geo.prototype = {
constructor: Geo,
type: 'geo',
/**
* @param {Array.<string>}
* @readOnly
*/
dimensions: ['lng', 'lat'],
/**
* If contain given lng,lat coord
* @param {Array.<number>}
* @readOnly
*/
containCoord: function (coord) {
var regions = this.regions;
for (var i = 0; i < regions.length; i++) {
if (regions[i].contain(coord)) {
return true;
}
}
return false;
},
/**
* @param {Object} geoJson
* @param {Object} [specialAreas]
* Specify the positioned areas by left, top, width, height
* @param {Object.<string, string>} [nameMap]
* Specify name alias
*/
loadGeoJson: function (geoJson, specialAreas, nameMap) {
// https://jsperf.com/try-catch-performance-overhead
try {
this.regions = geoJson ? parseGeoJson$1(geoJson) : [];
}
catch (e) {
throw 'Invalid geoJson format\n' + e.message;
}
specialAreas = specialAreas || {};
nameMap = nameMap || {};
var regions = this.regions;
var regionsMap = createHashMap();
for (var i = 0; i < regions.length; i++) {
var regionName = regions[i].name;
// Try use the alias in nameMap
regionName = nameMap.hasOwnProperty(regionName) ? nameMap[regionName] :
regionName;
regions[i].name = regionName;
regionsMap.set(regionName, regions[i]);
// Add geoJson
this.addGeoCoord(regionName, regions[i].center);
this._regionsMap = regionsMap;
this._rect = null;
// Overwrite
transformTo: function (x, y, width, height) {
var rect = this.getBoundingRect();
rect = rect.clone();
// Longitute is inverted
rect.y = -rect.y - rect.height;
rawTransformable.transform = rect.calculateTransform(
new BoundingRect(x, y, width, height)
);
rawTransformable.decomposeTransform();
rawTransformable.updateTransform();
this._updateTransform();
},
/**
* @param {string} name
* @return {module:echarts/coord/geo/Region}
*/
getRegion: function (name) {
return this._regionsMap.get(name);
},
/**
* Add geoCoord for indexing by name
* @param {string} name
* @param {Array.<number>} geoCoord
*/
addGeoCoord: function (name, geoCoord) {
this._nameCoordMap.set(name, geoCoord);
},
/**
* Get geoCoord by name
* @param {string} name
* @return {Array.<number>}
*/
getGeoCoord: function (name) {
return this._nameCoordMap.get(name);
},
// Overwrite
getBoundingRect: function () {
if (this._rect) {
return this._rect;
}
var rect;
/**
* @param {string|Array.<number>} data
* @param {boolean} noRoam
* @param {Array.<number>} [out]
* @return {Array.<number>}
*/
dataToPoint: function (data, noRoam, out) {
if (typeof data === 'string') {
// Map area name to geoCoord
data = this.getGeoCoord(data);
}
if (data) {
return View.prototype.dataToPoint.call(this, data, noRoam, out);
}
},
/**
* @inheritDoc
*/
convertToPixel: curry(doConvert, 'dataToPoint'),
/**
* @inheritDoc
*/
convertFromPixel: curry(doConvert, 'pointToData')
};
mixin(Geo, View);
var boxLayoutOption;
var viewRect;
if (useCenterAndSize) {
var viewRect = {};
if (aspect > 1) {
// Width is same with size
viewRect.width = size;
viewRect.height = size / aspect;
}
else {
viewRect.height = size;
viewRect.width = size * aspect;
}
viewRect.y = center[1] - viewRect.height / 2;
viewRect.x = center[0] - viewRect.width / 2;
}
else {
// Use left/top/width/height
boxLayoutOption = geoModel.getBoxLayoutParams();
// 0.75 rate
boxLayoutOption.aspect = aspect;
viewRect = getLayoutRect(boxLayoutOption, {
width: viewWidth,
height: viewHeight
});
}
this.setCenter(geoModel.get('center'));
this.setZoom(geoModel.get('zoom'));
}
/**
* @param {module:echarts/coord/Geo} geo
* @param {module:echarts/model/Model} model
* @inner
*/
function setGeoCoords(geo, model) {
each$1(model.get('geoCoord'), function (geoCoord, name) {
geo.addGeoCoord(name, geoCoord);
});
}
if (__DEV__) {
var mapNotExistsError = function (name) {
console.error('Map ' + name + ' not exists. You can download map file on
http://echarts.baidu.com/download-map.html');
};
}
var geoCreator = {
setGeoCoords(geo, geoModel);
geoModel.coordinateSystem = geo;
geo.model = geoModel;
geo.resize(geoModel, api);
});
ecModel.eachSeries(function (seriesModel) {
var coordSys = seriesModel.get('coordinateSystem');
if (coordSys === 'geo') {
var geoIndex = seriesModel.get('geoIndex') || 0;
seriesModel.coordinateSystem = geoList[geoIndex];
}
});
geo.resize(mapSeries[0], api);
setGeoCoords(geo, singleMapSeries);
});
});
return geoList;
},
/**
* Fill given regions array
* @param {Array.<Object>} originRegionArr
* @param {string} mapName
* @param {Object} [nameMap]
* @return {Array}
*/
getFilledRegions: function (originRegionArr, mapName, nameMap) {
// Not use the original
var regionsArr = (originRegionArr || []).slice();
nameMap = nameMap || {};
registerCoordinateSystem('geo', geoCreator);
type: 'series.map',
dependencies: ['geo'],
layoutMode: 'box',
/**
* Only first map series of same mapType will drawMap
* @type {boolean}
*/
needsDrawMap: false,
/**
* Group of all map series with same mapType
* @type {boolean}
*/
seriesGroup: [],
// this._fillOption(option, this.getMapType());
// this.option = option;
this.updateSelectedMap(this._createSelectableList());
},
this.updateSelectedMap(this._createSelectableList());
},
_createSelectableList: function () {
var data = this.getRawData();
var valueDim = data.mapDimension('value');
var targetList = [];
for (var i = 0, len = data.count(); i < len; i++) {
targetList.push({
name: data.getName(i),
value: data.get(valueDim, i),
selected: retrieveRawAttr(data, i, 'selected')
});
}
return targetList;
},
/**
* If no host geo model, return null, which means using a
* inner exclusive geo model.
*/
getHostGeoModel: function () {
var geoIndex = this.option.geoIndex;
return geoIndex != null
? this.dependentModels.geo[geoIndex]
: null;
},
getMapType: function () {
return (this.getHostGeoModel() || this).option.map;
},
// return option;
},
/**
* Get model of region
* @param {string} name
* @return {module:echarts/model/Model}
*/
getRegionModel: function (regionName) {
var data = this.getData();
return data.getItemModel(data.indexOfName(regionName));
},
/**
* Map tooltip formatter
*
* @param {number} dataIndex
*/
formatTooltip: function (dataIndex) {
// FIXME orignalData and data is a bit confusing
var data = this.getData();
var formattedValue = addCommas(this.getRawValue(dataIndex));
var name = data.getName(dataIndex);
/**
* @implement
*/
getTooltipPosition: function (dataIndex) {
if (dataIndex != null) {
var name = this.getData().getName(dataIndex);
var geo = this.coordinateSystem;
var region = geo.getRegion(name);
defaultOption: {
// 一级层叠
zlevel: 0,
// 二级层叠
z: 2,
coordinateSystem: 'geo',
// 数值合并方式,默认加和,可选为:
// 'sum' | 'average' | 'max' | 'min'
// mapValueCalculation: 'sum',
// 地图数值计算结果小数精度
// mapValuePrecision: 0,
// 显示图例颜色标识(系列标识的小圆点),图例开启时有效
showLegendSymbol: true,
// 选择模式,默认关闭,可选 single,multiple
// selectedMode: false,
dataRangeHoverLink: true,
// 是否开启缩放及漫游模式
// roam: false,
zoom: 1,
scaleLimit: null,
label: {
show: false,
color: '#000'
},
// scaleLimit: null,
itemStyle: {
borderWidth: 0.5,
borderColor: '#444',
areaColor: '#eee'
},
emphasis: {
label: {
show: true,
color: 'rgb(100,0,0)'
},
itemStyle: {
areaColor: 'rgba(255,215,0,0.8)'
}
}
}
});
mixin(MapSeries, selectableMixin);
function getStore(zr) {
return zr[ATTR] || (zr[ATTR] = {});
}
/**
* payload: {
* type: 'takeGlobalCursor',
* key: 'dataZoomSelect', or 'brush', or ...,
* If no userKey, release global cursor.
* }
*/
registerAction(
{type: 'takeGlobalCursor', event: 'globalCursorTaken', update: 'update'},
function () {}
);
/**
* @alias module:echarts/component/helper/RoamController
* @constructor
* @mixin {module:zrender/mixin/Eventful}
*
* @param {module:zrender/zrender~ZRender} zr
*/
function RoamController(zr) {
/**
* @type {Function}
*/
this.pointerChecker;
/**
* @type {module:zrender}
*/
this._zr = zr;
/**
* @type {Object}
*/
this._opt = {};
Eventful.call(this);
/**
* @param {Function} pointerChecker
* input: x, y
* output: boolean
*/
this.setPointerChecker = function (pointerChecker) {
this.pointerChecker = pointerChecker;
};
/**
* Notice: only enable needed types. For example, if 'zoom'
* is not needed, 'zoom' should not be enabled, otherwise
* default mousewheel behaviour (scroll page) will be disabled.
*
* @param {boolean|string} [controlType=true] Specify the control type,
* which can be null/undefined or true/false
* or 'pan/move' or 'zoom'/'scale'
* @param {Object} [opt]
* @param {Object} [opt.zoomOnMouseWheel=true]
* @param {Object} [opt.moveOnMouseMove=true]
* @param {Object} [opt.preventDefaultMouseMove=true] When pan.
*/
this.enable = function (controlType, opt) {
this.disable = function () {
zr.off('mousedown', mousedownHandler);
zr.off('mousemove', mousemoveHandler);
zr.off('mouseup', mouseupHandler);
zr.off('mousewheel', mousewheelHandler);
zr.off('pinch', pinchHandler);
};
this.dispose = this.disable;
this.isDragging = function () {
return this._dragging;
};
this.isPinching = function () {
return this._pinching;
};
}
mixin(RoamController, Eventful);
function mousedown(e) {
if (notLeftMouse(e)
|| (e.target && e.target.draggable)
) {
return;
}
var x = e.offsetX;
var y = e.offsetY;
function mousemove(e) {
if (notLeftMouse(e)
|| !checkKeyBinding(this, 'moveOnMouseMove', e)
|| !this._dragging
|| e.gestureEvent === 'pinch'
|| isTaken(this._zr, 'globalPan')
) {
return;
}
var x = e.offsetX;
var y = e.offsetY;
var dx = x - oldX;
var dy = y - oldY;
this._x = x;
this._y = y;
function mouseup(e) {
if (!notLeftMouse(e)) {
this._dragging = false;
}
}
function mousewheel(e) {
// wheelDelta maybe -0 in chrome mac.
if (!checkKeyBinding(this, 'zoomOnMouseWheel', e) || e.wheelDelta === 0) {
return;
}
// Convenience:
// Mac and VM Windows on Mac: scroll up: zoom out.
// Windows: scroll up: zoom in.
var zoomDelta = e.wheelDelta > 0 ? 1.1 : 1 / 1.1;
zoom.call(this, e, zoomDelta, e.offsetX, e.offsetY);
}
function pinch(e) {
if (isTaken(this._zr, 'globalPan')) {
return;
}
var zoomDelta = e.pinchScale > 1 ? 1.1 : 1 / 1.1;
zoom.call(this, e, zoomDelta, e.pinchX, e.pinchY);
}
/**
* For geo and graph.
*
* @param {Object} controllerHost
* @param {module:zrender/Element} controllerHost.target
*/
function updateViewOnPan(controllerHost, dx, dy) {
var target = controllerHost.target;
var pos = target.position;
pos[0] += dx;
pos[1] += dy;
target.dirty();
}
/**
* For geo and graph.
*
* @param {Object} controllerHost
* @param {module:zrender/Element} controllerHost.target
* @param {number} controllerHost.zoom
* @param {number} controllerHost.zoomLimit like: {min: 1, max: 2}
*/
function updateViewOnZoom(controllerHost, zoomDelta, zoomX, zoomY) {
var target = controllerHost.target;
var zoomLimit = controllerHost.zoomLimit;
var pos = target.position;
var scale = target.scale;
target.dirty();
}
var IRRELEVANT_EXCLUDES = {'axisPointer': 1, 'tooltip': 1, 'brush': 1};
/**
* Avoid that: mouse click on a elements that is over geo or graph,
* but roam is triggered.
*/
function onIrrelevantElement(e, api, targetCoordSysModel) {
var model = api.getComponentByElement(e.topTarget);
// If model is axisModel, it works only if it is injected with
coordinateSystem.
var coordSys = model && model.coordinateSystem;
return model
&& model !== targetCoordSysModel
&& !IRRELEVANT_EXCLUDES[model.mainType]
&& (coordSys && coordSys.model !== targetCoordSysModel);
}
return itemStyle;
}
if (mapOrGeoModel.get('selectedMode')) {
group.on('mousedown', function () {
mapDraw._mouseDownFlag = true;
});
var el = e.target;
while (!el.__regions) {
el = el.parent;
}
if (!el) {
return;
}
var action = {
type: (mapOrGeoModel.mainType === 'geo' ? 'geo' : 'map') +
'ToggleSelect',
batch: map(el.__regions, function (region) {
return {
name: region.name,
from: fromView.uid
};
})
};
action[mapOrGeoModel.mainType + 'Id'] = mapOrGeoModel.id;
api.dispatchAction(action);
updateMapSelected(mapOrGeoModel, group);
});
}
}
/**
* @alias module:echarts/component/helper/MapDraw
* @param {module:echarts/ExtensionAPI} api
* @param {boolean} updateGroup
*/
function MapDraw(api, updateGroup) {
/**
* @type {module:echarts/component/helper/RoamController}
* @private
*/
this._controller = new RoamController(api.getZr());
/**
* @type {Object} {target, zoom, zoomLimit}
* @private
*/
this._controllerHost = {target: updateGroup ? group : null};
/**
* @type {module:zrender/container/Group}
* @readOnly
*/
this.group = group;
/**
* @type {boolean}
* @private
*/
this._updateGroup = updateGroup;
/**
* This flag is used to make sure that only one among
* `pan`, `zoom`, `click` can occurs, otherwise 'selected'
* action may be triggered when `pan`, which is unexpected.
* @type {booelan}
*/
this._mouseDownFlag;
}
MapDraw.prototype = {
constructor: MapDraw,
// Map series has data. GEO model that controlled by map series
// will be assigned with map data. Other GEO model has no data.
var data = mapOrGeoModel.getData && mapOrGeoModel.getData();
isGeo && ecModel.eachComponent({mainType: 'series', subType: 'map'},
function (mapSeries) {
if (!data && mapSeries.getHostGeoModel() === mapOrGeoModel) {
data = mapSeries.getData();
}
});
group.removeAll();
var dataIdx;
// Use the itemStyle in data if has data
if (data) {
dataIdx = data.indexOfName(region.name);
// Only visual color of each item will be used. It can be encoded
by dataRange
// But visual color of series is used in symbol drawing
//
// Visual color for each series is for the symbol draw
var visualColor = data.getItemVisual(dataIdx, 'color', true);
if (visualColor) {
itemStyle.fill = visualColor;
}
}
compoundPath.setStyle(itemStyle);
compoundPath.style.strokeNoScale = true;
compoundPath.culling = true;
// Label
var showLabel = labelModel.get('show');
var hoverShowLabel = hoverLabelModel.get('show');
setLabelStyle(
textEl.style, textEl.hoverStyle = {}, labelModel,
hoverLabelModel,
{
labelFetcher: labelFetcher,
labelDataIndex: query,
defaultText: region.name,
useInsideStyle: false
},
{
textAlign: 'center',
textVerticalAlign: 'middle'
}
);
regionGroup.add(textEl);
}
setHoverStyle(
regionGroup,
hoverItemStyle,
{hoverSilentOnTouch: !!mapOrGeoModel.get('selectedMode')}
);
group.add(regionGroup);
});
updateMapSelected(mapOrGeoModel, group);
},
remove: function () {
this.group.removeAll();
this._controller.dispose();
this._controllerHost = {};
},
controllerHost.zoomLimit = mapOrGeoModel.get('scaleLimit');
controllerHost.zoom = geo.getZoom();
function makeActionBase() {
var action = {
type: 'geoRoam',
componentType: mainType
};
action[mainType + 'Id'] = mapOrGeoModel.id;
return action;
}
api.dispatchAction(extend(makeActionBase(), {
zoom: zoom,
originX: mouseX,
originY: mouseY
}));
if (this._updateGroup) {
var group = this.group;
var scale = group.scale;
group.traverse(function (el) {
if (el.type === 'text') {
el.attr('scale', [1 / scale[0], 1 / scale[1]]);
}
});
}
}, this);
controller.setPointerChecker(function (e, x, y) {
return geo.getViewRectAfterRoam().contain(x, y)
&& !onIrrelevantElement(e, api, mapOrGeoModel);
});
}
};
extendChartView({
type: 'map',
if (mapModel.getHostGeoModel()) {
return;
}
this._mapDraw = mapDraw;
}
else {
// Remove drawed map
this._mapDraw && this._mapDraw.remove();
this._mapDraw = null;
}
}
else {
var mapDraw = this._mapDraw;
mapDraw && group.add(mapDraw.group);
}
remove: function () {
this._mapDraw && this._mapDraw.remove();
this._mapDraw = null;
this.group.removeAll();
},
dispose: function () {
this._mapDraw && this._mapDraw.remove();
this._mapDraw = null;
},
if (!layout || !layout.point) {
// Not exists in map
return;
}
if (circle.__mapOriginalZ2 != null) {
circle.z2 = circle.__mapOriginalZ2;
circle.__mapOriginalZ2 = null;
}
};
polygonGroups.on('mouseover', onEmphasis)
.on('mouseout', onNormal)
.on('emphasis', onEmphasis)
.on('normal', onNormal);
onNormal();
}
group.add(circle);
});
}
});
/**
* @param {module:echarts/coord/View} view
* @param {Object} payload
* @param {Object} [zoomLimit]
*/
function updateCenterAndZoom(
view, payload, zoomLimit
) {
var previousZoom = view.getZoom();
var center = view.getCenter();
var zoom = payload.zoom;
position[0] -= fixX;
position[1] -= fixY;
view.updateTransform();
// Get the new center
var center = view.pointToData(point);
view.setCenter(center);
view.setZoom(zoom * previousZoom);
}
return {
center: view.getCenter(),
zoom: view.getZoom()
};
}
/**
* @payload
* @property {string} [componentType=series]
* @property {number} [dx]
* @property {number} [dy]
* @property {number} [zoom]
* @property {number} [originX]
* @property {number} [originY]
*/
registerAction({
type: 'geoRoam',
event: 'geoRoam',
update: 'updateTransform'
}, function (payload, ecModel) {
var componentType = payload.componentType || 'series';
ecModel.eachComponent(
{ mainType: componentType, query: payload },
function (componentModel) {
var geo = componentModel.coordinateSystem;
if (geo.type !== 'geo') {
return;
}
componentModel.setCenter
&& componentModel.setCenter(res.center);
componentModel.setZoom
&& componentModel.setZoom(res.zoom);
// All map series with same `map` use the same geo coordinate system
// So the center and zoom must be in sync. Include the series not
selected by legend
if (componentType === 'series') {
each$1(componentModel.seriesGroup, function (seriesModel) {
seriesModel.setCenter(res.center);
seriesModel.setZoom(res.zoom);
});
}
}
);
});
mapSymbolOffsets[name] = offset + 1;
data.setItemLayout(idx, {
point: point,
offset: offset
});
});
}
});
processedMapType[mapType] = true;
});
};
seriesModel.getData().setVisual({
'areaColor': areaColor,
'color': color
});
});
};
// FIXME 公用?
/**
* @param {Array.<module:echarts/data/List>} datas
* @param {string} statisticType 'average' 'sum'
* @inner
*/
function dataStatistics(datas, statisticType) {
var dataNameMap = {};
seriesList[i].setData(data.cloneShallow());
seriesList[i].mainSeries = seriesList[0];
}
});
};
registerLayout(mapSymbolLayout);
registerVisual(mapVisual);
registerProcessor(PRIORITY.PROCESSOR.STATISTIC, mapDataStatistic);
registerPreprocessor(backwardCompat$2);
createDataSelectAction('map', [{
type: 'mapToggleSelect',
event: 'mapselectchanged',
method: 'toggleSelected'
}, {
type: 'mapSelect',
event: 'mapselected',
method: 'select'
}, {
type: 'mapUnSelect',
event: 'mapunselected',
method: 'unSelect'
}]);
/**
* Link lists and struct (graph or tree)
*/
// Caution:
// In most case, either list or its shallow clones (see list.cloneShallow)
// is active in echarts process. So considering heap memory consumption,
// we do not clone tree or graph, but share them among list and its shallow clones.
// But in some rare case, we have to keep old list (like do animation in chart). So
// please take care that both the old list and the new list share the same
tree/graph.
/**
* @param {Object} opt
* @param {module:echarts/data/List} opt.mainData
* @param {Object} [opt.struct] For example, instance of Graph or Tree.
* @param {string} [opt.structAttr] designation: list[structAttr] = struct;
* @param {Object} [opt.datas] {dataType: data},
* like: {node: nodeList, edge: edgeList}.
* Should contain mainData.
* @param {Object} [opt.datasAttr] {dataType: attr},
* designation: struct[datasAttr[dataType]] = list;
*/
function linkList(opt) {
var mainData = opt.mainData;
var datas = opt.datas;
if (!datas) {
datas = {main: mainData};
opt.datasAttr = {main: 'data'};
}
opt.datas = opt.mainData = null;
});
/**
* Supplement method to List.
*
* @public
* @param {string} [dataType] If not specified, return mainData.
* @return {module:echarts/data/List}
*/
function getLinkedData(dataType) {
var mainData = this[MAIN_DATA];
return (dataType == null || mainData == null)
? mainData
: mainData[DATAS][dataType];
}
function isMainData(data) {
return data[MAIN_DATA] === data;
}
if (opt.struct) {
data[opt.structAttr] = opt.struct;
opt.struct[opt.datasAttr[dataType]] = data;
}
// Supplement method.
data.getLinkedData = getLinkedData;
}
/**
* Tree data structure
*
* @module echarts/data/Tree
*/
/**
* @constructor module:echarts/data/Tree~TreeNode
* @param {string} name
* @param {module:echarts/data/Tree} hostTree
*/
var TreeNode = function (name, hostTree) {
/**
* @type {string}
*/
this.name = name || '';
/**
* Depth of node
*
* @type {number}
* @readOnly
*/
this.depth = 0;
/**
* Height of the subtree rooted at this node.
* @type {number}
* @readOnly
*/
this.height = 0;
/**
* @type {module:echarts/data/Tree~TreeNode}
* @readOnly
*/
this.parentNode = null;
/**
* Reference to list item.
* Do not persistent dataIndex outside,
* besause it may be changed by list.
* If dataIndex -1,
* this node is logical deleted (filtered) in list.
*
* @type {Object}
* @readOnly
*/
this.dataIndex = -1;
/**
* @type {Array.<module:echarts/data/Tree~TreeNode>}
* @readOnly
*/
this.children = [];
/**
* @type {Array.<module:echarts/data/Tree~TreeNode>}
* @pubilc
*/
this.viewChildren = [];
/**
* @type {moduel:echarts/data/Tree}
* @readOnly
*/
this.hostTree = hostTree;
};
TreeNode.prototype = {
constructor: TreeNode,
/**
* The node is removed.
* @return {boolean} is removed.
*/
isRemoved: function () {
return this.dataIndex < 0;
},
/**
* Travel this subtree (include this node).
* Usage:
* node.eachNode(function () { ... }); // preorder
* node.eachNode('preorder', function () { ... }); // preorder
* node.eachNode('postorder', function () { ... }); // postorder
* node.eachNode(
* {order: 'postorder', attr: 'viewChildren'},
* function () { ... }
* ); // postorder
*
* @param {(Object|string)} options If string, means order.
* @param {string=} options.order 'preorder' or 'postorder'
* @param {string=} options.attr 'children' or 'viewChildren'
* @param {Function} cb If in preorder and return false,
* its subtree will not be visited.
* @param {Object} [context]
*/
eachNode: function (options, cb, context) {
if (typeof options === 'function') {
context = cb;
cb = options;
options = null;
}
var suppressVisitSub;
order === 'preorder' && (suppressVisitSub = cb.call(context, this));
/**
* Update depth and height of this subtree.
*
* @param {number} depth
*/
updateDepthAndHeight: function (depth) {
var height = 0;
this.depth = depth;
for (var i = 0; i < this.children.length; i++) {
var child = this.children[i];
child.updateDepthAndHeight(depth + 1);
if (child.height > height) {
height = child.height;
}
}
this.height = height + 1;
},
/**
* @param {string} id
* @return {module:echarts/data/Tree~TreeNode}
*/
getNodeById: function (id) {
if (this.getId() === id) {
return this;
}
for (var i = 0, children = this.children, len = children.length; i < len;
i++) {
var res = children[i].getNodeById(id);
if (res) {
return res;
}
}
},
/**
* @param {module:echarts/data/Tree~TreeNode} node
* @return {boolean}
*/
contains: function (node) {
if (node === this) {
return true;
}
for (var i = 0, children = this.children, len = children.length; i < len;
i++) {
var res = children[i].contains(node);
if (res) {
return res;
}
}
},
/**
* @param {boolean} includeSelf Default false.
* @return {Array.<module:echarts/data/Tree~TreeNode>} order: [root, child,
grandchild, ...]
*/
getAncestors: function (includeSelf) {
var ancestors = [];
var node = includeSelf ? this : this.parentNode;
while (node) {
ancestors.push(node);
node = node.parentNode;
}
ancestors.reverse();
return ancestors;
},
/**
* @param {string|Array=} [dimension='value'] Default 'value'. can be 0, 1, 2,
3
* @return {number} Value.
*/
getValue: function (dimension) {
var data = this.hostTree.data;
return data.get(data.getDimension(dimension || 'value'), this.dataIndex);
},
/**
* @param {Object} layout
* @param {boolean=} [merge=false]
*/
setLayout: function (layout, merge$$1) {
this.dataIndex >= 0
&& this.hostTree.data.setItemLayout(this.dataIndex, layout, merge$$1);
},
/**
* @return {Object} layout
*/
getLayout: function () {
return this.hostTree.data.getItemLayout(this.dataIndex);
},
/**
* @param {string} [path]
* @return {module:echarts/model/Model}
*/
getModel: function (path) {
if (this.dataIndex < 0) {
return;
}
var hostTree = this.hostTree;
var itemModel = hostTree.data.getItemModel(this.dataIndex);
var levelModel = this.getLevelModel();
var leavesModel;
if (!levelModel && (this.children.length === 0 || (this.children.length !==
0 && this.isExpand === false))) {
leavesModel = this.getLeavesModel();
}
return itemModel.getModel(path, (levelModel || leavesModel ||
hostTree.hostModel).getModel(path));
},
/**
* @return {module:echarts/model/Model}
*/
getLevelModel: function () {
return (this.hostTree.levelModels || [])[this.depth];
},
/**
* @return {module:echarts/model/Model}
*/
getLeavesModel: function () {
return this.hostTree.leavesModel;
},
/**
* @example
* setItemVisual('color', color);
* setItemVisual({
* 'color': color
* });
*/
setVisual: function (key, value) {
this.dataIndex >= 0
&& this.hostTree.data.setItemVisual(this.dataIndex, key, value);
},
/**
* Get item visual
*/
getVisual: function (key, ignoreParent) {
return this.hostTree.data.getItemVisual(this.dataIndex, key, ignoreParent);
},
/**
* @public
* @return {number}
*/
getRawIndex: function () {
return this.hostTree.data.getRawIndex(this.dataIndex);
},
/**
* @public
* @return {string}
*/
getId: function () {
return this.hostTree.data.getId(this.dataIndex);
},
/**
* if this is an ancestor of another node
*
* @public
* @param {TreeNode} node another node
* @return {boolean} if is ancestor
*/
isAncestorOf: function (node) {
var parent = node.parentNode;
while (parent) {
if (parent === this) {
return true;
}
parent = parent.parentNode;
}
return false;
},
/**
* if this is an descendant of another node
*
* @public
* @param {TreeNode} node another node
* @return {boolean} if is descendant
*/
isDescendantOf: function (node) {
return node !== this && node.isAncestorOf(this);
}
};
/**
* @constructor
* @alias module:echarts/data/Tree
* @param {module:echarts/model/Model} hostModel
* @param {Array.<Object>} levelOptions
* @param {Object} leavesOption
*/
function Tree(hostModel, levelOptions, leavesOption) {
/**
* @type {module:echarts/data/Tree~TreeNode}
* @readOnly
*/
this.root;
/**
* @type {module:echarts/data/List}
* @readOnly
*/
this.data;
/**
* Index of each item is the same as the raw index of coresponding list item.
* @private
* @type {Array.<module:echarts/data/Tree~TreeNode}
*/
this._nodes = [];
/**
* @private
* @readOnly
* @type {module:echarts/model/Model}
*/
this.hostModel = hostModel;
/**
* @private
* @readOnly
* @type {Array.<module:echarts/model/Model}
*/
this.levelModels = map(levelOptions || [], function (levelDefine) {
return new Model(levelDefine, hostModel, hostModel.ecModel);
});
Tree.prototype = {
constructor: Tree,
type: 'tree',
/**
* Travel this subtree (include this node).
* Usage:
* node.eachNode(function () { ... }); // preorder
* node.eachNode('preorder', function () { ... }); // preorder
* node.eachNode('postorder', function () { ... }); // postorder
* node.eachNode(
* {order: 'postorder', attr: 'viewChildren'},
* function () { ... }
* ); // postorder
*
* @param {(Object|string)} options If string, means order.
* @param {string=} options.order 'preorder' or 'postorder'
* @param {string=} options.attr 'children' or 'viewChildren'
* @param {Function} cb
* @param {Object} [context]
*/
eachNode: function(options, cb, context) {
this.root.eachNode(options, cb, context);
},
/**
* @param {number} dataIndex
* @return {module:echarts/data/Tree~TreeNode}
*/
getNodeByDataIndex: function (dataIndex) {
var rawIndex = this.data.getRawIndex(dataIndex);
return this._nodes[rawIndex];
},
/**
* @param {string} name
* @return {module:echarts/data/Tree~TreeNode}
*/
getNodeByName: function (name) {
return this.root.getNodeByName(name);
},
/**
* Update item available by list,
* when list has been performed options like 'filterSelf' or 'map'.
*/
update: function () {
var data = this.data;
var nodes = this._nodes;
/**
* Clear all layouts
*/
clearLayouts: function () {
this.data.clearItemLayouts();
}
};
/**
* data node format:
* {
* name: ...
* value: ...
* children: [
* {
* name: ...
* value: ...
* children: ...
* },
* ...
* ]
* }
*
* @static
* @param {Object} dataRoot Root node.
* @param {module:echarts/model/Model} hostModel
* @param {Object} treeOptions
* @param {Array.<Object>} treeOptions.levels
* @param {Array.<Object>} treeOptions.leaves
* @return module:echarts/data/Tree
*/
Tree.createTree = function (dataRoot, hostModel, treeOptions) {
var tree = new Tree(hostModel, treeOptions.levels, treeOptions.leaves);
var listData = [];
var dimMax = 1;
buildHierarchy(dataRoot);
listData.push(dataNode);
tree._nodes.push(node);
tree.root.updateDepthAndHeight(0);
linkList({
mainData: list,
struct: tree,
structAttr: 'tree'
});
tree.update();
return tree;
};
/**
* It is needed to consider the mess of 'list', 'hostModel' when creating a
TreeNote,
* so this function is not ready and not necessary to be public.
*
* @param {(module:echarts/data/Tree~TreeNode|Object)} child
*/
function addChild(child, node) {
var children = node.children;
if (child.parentNode === node) {
return;
}
children.push(child);
child.parentNode = node;
}
/**
* @file Create data struct and define tree view's series model
*/
SeriesModel.extend({
type: 'series.tree',
layoutInfo: null,
/**
* Init a tree data structure from data in option series
* @param {Object} option the object used to config echarts view
* @return {module:echarts/data/List} storage initial data
*/
getInitialData: function (option) {
treeOption.leaves = leaves;
var treeDepth = 0;
return tree.data;
},
/**
* @override
* @param {number} dataIndex
*/
formatTooltip: function (dataIndex) {
var tree = this.getData().tree;
var realRoot = tree.root.children[0];
var node = tree.getNodeByDataIndex(dataIndex);
var value = node.getValue();
var name = node.name;
while (node && (node !== realRoot)) {
name = node.parentNode.name + '.' + name;
node = node.parentNode;
}
return encodeHTML(name + (
(isNaN(value) || value == null) ? '' : ' : ' + value
));
},
defaultOption: {
zlevel: 0,
z: 2,
symbol: 'emptyCircle',
symbolSize: 7,
expandAndCollapse: true,
initialTreeDepth: 2,
lineStyle: {
color: '#ccc',
width: 1.5,
curveness: 0.5
},
itemStyle: {
color: 'lightsteelblue',
borderColor: '#c23531',
borderWidth: 1.5
},
label: {
show: true,
color: '#555'
},
leaves: {
label: {
show: true
}
},
animationEasing: 'linear',
animationDuration: 700,
animationDurationUpdate: 1000
}
});
/**
* @file The layout algorithm of node-link tree diagrams. Here we using Reingold-
Tilford algorithm to drawing
* the tree.
* @see https://github.com/d3/d3-hierarchy
*/
/**
* Initialize all computational message for following algorithm
* @param {module:echarts/data/Tree~TreeNode} root The virtual root of the tree
*/
function init$2(root) {
root.hierNode = {
defaultAncestor: null,
ancestor: root,
prelim: 0,
modifier: 0,
change: 0,
shift: 0,
i: 0,
thread: null
};
/**
* Computes a preliminary x coordinate for node. Before that, this function is
* applied recursively to the children of node, as well as the function
* apportion(). After spacing out the children by calling executeShifts(), the
* node is placed to the midpoint of its outermost children.
* @param {module:echarts/data/Tree~TreeNode} node
* @param {Function} separation
*/
function firstWalk(node, separation) {
var children = node.isExpand ? node.children : [];
var siblings = node.parentNode.children;
var subtreeW = node.hierNode.i ? siblings[node.hierNode.i -1] : null;
if (children.length) {
executeShifts(node);
var midPoint = (children[0].hierNode.prelim + children[children.length -
1].hierNode.prelim) / 2;
if (subtreeW) {
node.hierNode.prelim = subtreeW.hierNode.prelim + separation(node,
subtreeW);
node.hierNode.modifier = node.hierNode.prelim - midPoint;
}
else {
node.hierNode.prelim = midPoint;
}
}
else if (subtreeW) {
node.hierNode.prelim = subtreeW.hierNode.prelim + separation(node,
subtreeW);
}
node.parentNode.hierNode.defaultAncestor = apportion(node, subtreeW,
node.parentNode.hierNode.defaultAncestor || siblings[0], separation);
}
/**
* Computes all real x-coordinates by summing up the modifiers recursively.
* @param {module:echarts/data/Tree~TreeNode} node
*/
function secondWalk(node) {
var nodeX = node.hierNode.prelim + node.parentNode.hierNode.modifier;
node.setLayout({x: nodeX}, true);
node.hierNode.modifier += node.parentNode.hierNode.modifier;
}
function separation(cb) {
return arguments.length ? cb : defaultSeparation;
}
/**
* Transform the common coordinate to radial coordinate
* @param {number} x
* @param {number} y
* @return {Object}
*/
function radialCoordinate(x, y) {
var radialCoor = {};
x -= Math.PI / 2;
radialCoor.x = y * Math.cos(x);
radialCoor.y = y * Math.sin(x);
return radialCoor;
}
/**
* Get the layout position of the whole view
* @param {module:echarts/model/Series} seriesModel the model object of sankey
series
* @param {module:echarts/ExtensionAPI} api provide the API list that the
developer can call
* @return {module:zrender/core/BoundingRect} size of rect to draw the sankey view
*/
function getViewRect(seriesModel, api) {
return getLayoutRect(
seriesModel.getBoxLayoutParams(), {
width: api.getWidth(),
height: api.getHeight()
}
);
}
/**
* All other shifts, applied to the smaller subtrees between w- and w+, are
* performed by this function.
* @param {module:echarts/data/Tree~TreeNode} node
*/
function executeShifts(node) {
var children = node.children;
var n = children.length;
var shift = 0;
var change = 0;
while (--n >= 0) {
var child = children[n];
child.hierNode.prelim += shift;
child.hierNode.modifier += shift;
change += child.hierNode.change;
shift += child.hierNode.shift + change;
}
}
/**
* The core of the algorithm. Here, a new subtree is combined with the
* previous subtrees. Threads are used to traverse the inside and outside
* contours of the left and right subtree up to the highest common level.
* Whenever two nodes of the inside contours conflict, we compute the left
* one of the greatest uncommon ancestors using the function nextAncestor()
* and call moveSubtree() to shift the subtree and prepare the shifts of
* smaller subtrees. Finally, we add a new thread (if necessary).
* @param {module:echarts/data/Tree~TreeNode} subtreeV
* @param {module:echarts/data/Tree~TreeNode} subtreeW
* @param {module:echarts/data/Tree~TreeNode} ancestor
* @param {Function} separation
* @return {module:echarts/data/Tree~TreeNode}
*/
function apportion(subtreeV, subtreeW, ancestor, separation) {
if (subtreeW) {
var nodeOutRight = subtreeV;
var nodeInRight = subtreeV;
var nodeOutLeft = nodeInRight.parentNode.children[0];
var nodeInLeft = subtreeW;
}
if (nodeInRight && !nextLeft(nodeOutLeft)) {
nodeOutLeft.hierNode.thread = nodeInRight;
nodeOutLeft.hierNode.modifier += sumInRight - sumOutLeft;
ancestor = subtreeV;
}
}
return ancestor;
}
/**
* This function is used to traverse the right contour of a subtree.
* It returns the rightmost child of node or the thread of node. The function
* returns null if and only if node is on the highest depth of its subtree.
* @param {module:echarts/data/Tree~TreeNode} node
* @return {module:echarts/data/Tree~TreeNode}
*/
function nextRight(node) {
var children = node.children;
return children.length && node.isExpand ? children[children.length - 1] :
node.hierNode.thread;
}
/**
* This function is used to traverse the left contour of a subtree (or a
subforest).
* It returns the leftmost child of node or the thread of node. The function
* returns null if and only if node is on the highest depth of its subtree.
* @param {module:echarts/data/Tree~TreeNode} node
* @return {module:echarts/data/Tree~TreeNode}
*/
function nextLeft(node) {
var children = node.children;
return children.length && node.isExpand ? children[0] : node.hierNode.thread;
}
/**
* If nodeInLeft’s ancestor is a sibling of node, returns nodeInLeft’s ancestor.
* Otherwise, returns the specified ancestor.
* @param {module:echarts/data/Tree~TreeNode} nodeInLeft
* @param {module:echarts/data/Tree~TreeNode} node
* @param {module:echarts/data/Tree~TreeNode} ancestor
* @return {module:echarts/data/Tree~TreeNode}
*/
function nextAncestor(nodeInLeft, node, ancestor) {
return nodeInLeft.hierNode.ancestor.parentNode === node.parentNode
? nodeInLeft.hierNode.ancestor : ancestor;
}
/**
* Shifts the current subtree rooted at wr. This is done by increasing prelim(w+)
and modifier(w+) by shift.
* @param {module:echarts/data/Tree~TreeNode} wl
* @param {module:echarts/data/Tree~TreeNode} wr
* @param {number} shift [description]
*/
function moveSubtree(wl, wr,shift) {
var change = shift / (wr.hierNode.i - wl.hierNode.i);
wr.hierNode.change -= change;
wr.hierNode.shift += shift;
wr.hierNode.modifier += shift;
wr.hierNode.prelim += shift;
wl.hierNode.change += change;
}
/**
* @file This file used to draw tree view
*/
extendChartView({
type: 'tree',
/**
* Init the chart
* @override
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
*/
init: function (ecModel, api) {
/**
* @private
* @type {module:echarts/data/Tree}
*/
this._oldTree;
/**
* @private
* @type {module:zrender/container/Group}
*/
this._mainGroup = new Group();
this.group.add(this._mainGroup);
},
var seriesScope = {
expandAndCollapse: seriesModel.get('expandAndCollapse'),
layout: layout,
orient: seriesModel.get('orient'),
curvature: seriesModel.get('lineStyle.curveness'),
symbolRotate: seriesModel.get('symbolRotate'),
symbolOffset: seriesModel.get('symbolOffset'),
hoverAnimation: seriesModel.get('hoverAnimation'),
useNameLabel: true,
fadeIn: true
};
data.diff(oldData)
.add(function (newIdx) {
if (symbolNeedsDraw$1(data, newIdx)) {
// create node and edge
updateNode(data, newIdx, null, group, seriesModel,
seriesScope);
}
})
.update(function (newIdx, oldIdx) {
var symbolEl = oldData.getItemGraphicEl(oldIdx);
if (!symbolNeedsDraw$1(data, newIdx)) {
symbolEl && removeNode(data, newIdx, symbolEl, group,
seriesModel, seriesScope);
return;
}
// update node and edge
updateNode(data, newIdx, symbolEl, group, seriesModel,
seriesScope);
})
.remove(function (oldIdx) {
var symbolEl = oldData.getItemGraphicEl(oldIdx);
removeNode(data, oldIdx, symbolEl, group, seriesModel,
seriesScope);
})
.execute();
this._data = data;
},
remove: function () {
this._mainGroup.removeAll();
this._data = null;
}
});
return layout
&& !isNaN(layout.x) && !isNaN(layout.y)
&& data.getItemVisual(dataIndex, 'symbol') !== 'none';
}
return seriesScope;
}
if (isInit) {
symbolEl = new SymbolClz$1(data, dataIndex, seriesScope);
symbolEl.attr('position', [sourceOldLayout.x, sourceOldLayout.y]);
}
else {
symbolEl.updateData(data, dataIndex, seriesScope);
}
symbolEl.__radialOldRawX = symbolEl.__radialRawX;
symbolEl.__radialOldRawY = symbolEl.__radialRawY;
symbolEl.__radialRawX = targetLayout.rawX;
symbolEl.__radialRawY = targetLayout.rawY;
group.add(symbolEl);
data.setItemGraphicEl(dataIndex, symbolEl);
updateProps(symbolEl, {
position: [targetLayout.x, targetLayout.y]
}, seriesModel);
updateProps(edge, {
shape: getEdgeShape(seriesScope, sourceLayout, targetLayout),
style: {opacity: 1}
}, seriesModel);
group.add(edge);
}
}
updateProps(symbolEl, {
position: [sourceLayout.x + 1, sourceLayout.y + 1]
}, seriesModel, function () {
group.remove(symbolEl);
data.setItemGraphicEl(dataIndex, null);
});
return {
x1: radialCoor1.x,
y1: radialCoor1.y,
x2: radialCoor4.x,
y2: radialCoor4.y,
cpx1: radialCoor2.x,
cpy1: radialCoor2.y,
cpx2: radialCoor3.x,
cpy2: radialCoor3.y
};
}
else {
var x1 = sourceLayout.x;
var y1 = sourceLayout.y;
var x2 = targetLayout.x;
var y2 = targetLayout.y;
registerAction({
type: 'treeExpandAndCollapse',
event: 'treeExpandAndCollapse',
update: 'update'
}, function (payload, ecModel) {
ecModel.eachComponent({mainType: 'series', subType: 'tree', query: payload},
function (seriesModel) {
var dataIndex = payload.dataIndex;
var tree = seriesModel.getData().tree;
var node = tree.getNodeByDataIndex(dataIndex);
node.isExpand = !node.isExpand;
});
});
/**
* Traverse the tree from bottom to top and do something
* @param {module:echarts/data/Tree~TreeNode} root The real root of the tree
* @param {Function} callback
*/
function eachAfter (root, callback, separation) {
var nodes = [root];
var next = [];
var node;
/**
* Traverse the tree from top to bottom and do something
* @param {module:echarts/data/Tree~TreeNode} root The real root of the tree
* @param {Function} callback
*/
function eachBefore (root, callback) {
var nodes = [root];
var node;
while (node = nodes.pop()) { // jshint ignore:line
callback(node);
if (node.isExpand) {
var children = node.children;
if (children.length) {
for (var i = children.length - 1; i >= 0; i--) {
nodes.push(children[i]);
}
}
}
}
}
registerVisual(visualSymbol('tree', 'circle'));
registerLayout(orthogonalLayout);
registerLayout(radialLayout);
// From root to the input node (the input node will be included).
function wrapTreePathInfo(node, seriesModel) {
var treePathInfo = [];
while (node) {
var nodeDataIndex = node.dataIndex;
treePathInfo.push({
name: node.name,
dataIndex: nodeDataIndex,
value: seriesModel.getRawValue(nodeDataIndex)
});
node = node.parentNode;
}
treePathInfo.reverse();
return treePathInfo;
}
SeriesModel.extend({
type: 'series.treemap',
layoutMode: 'box',
/**
* @type {module:echarts/data/Tree~Node}
*/
_viewRoot: null,
defaultOption: {
// Disable progressive rendering
progressive: 0,
hoverLayerThreshold: Infinity,
// center: ['50%', '50%'], // not supported in ec3.
// size: ['80%', '80%'], // deprecated, compatible with ec2.
left: 'center',
top: 'middle',
right: null,
bottom: null,
width: '80%',
height: '80%',
sort: true, // Can be null or false or true
// (order by desc default, asc not
supported yet (strange effect))
clipWindow: 'origin', // Size of clipped window when zooming.
'origin' or 'fullscreen'
squareRatio: 0.5 * (1 + Math.sqrt(5)), // golden ratio
leafDepth: null, // Nodes on depth from root are
regarded as leaves.
// Count from zero (zero represents
only view root).
drillDownIcon: '▶', // Use html character temporarily
because it is complicated
// to align specialized icon. ▷▶❒❐▼✚
visualDimension: 0, // Can be 0, 1, 2, 3.
visualMin: null,
visualMax: null,
/**
* @override
*/
getInitialData: function (option, ecModel) {
// Create a virtual root.
var root = {name: option.name, children: option.data};
completeTreeValue(root);
treeOption.levels = levels;
optionUpdated: function () {
this.resetViewRoot();
},
/**
* @override
* @param {number} dataIndex
* @param {boolean} [mutipleSeries=false]
*/
formatTooltip: function (dataIndex) {
var data = this.getData();
var value = this.getRawValue(dataIndex);
var formattedValue = isArray(value)
? addCommas(value[0]) : addCommas(value);
var name = data.getName(dataIndex);
return encodeHTML(name + ': ' + formattedValue);
},
/**
* Add tree path to tooltip param
*
* @override
* @param {number} dataIndex
* @return {Object}
*/
getDataParams: function (dataIndex) {
var params = SeriesModel.prototype.getDataParams.apply(this, arguments);
return params;
},
/**
* @public
* @param {Object} layoutInfo {
* x: containerGroup x
* y: containerGroup y
* width: containerGroup width
* height: containerGroup height
* }
*/
setLayoutInfo: function (layoutInfo) {
/**
* @readOnly
* @type {Object}
*/
this.layoutInfo = this.layoutInfo || {};
extend(this.layoutInfo, layoutInfo);
},
/**
* @param {string} id
* @return {number} index
*/
mapIdToIndex: function (id) {
// A feature is implemented:
// index is monotone increasing with the sequence of
// input id at the first time.
// This feature can make sure that each data item and its
// mapped color have the same index between data list and
// color list at the beginning, which is useful for user
// to adjust data-color mapping.
/**
* @private
* @type {Object}
*/
var idIndexMap = this._idIndexMap;
if (!idIndexMap) {
idIndexMap = this._idIndexMap = createHashMap();
/**
* @private
* @type {number}
*/
this._idIndexMapCount = 0;
}
return index;
},
getViewRoot: function () {
return this._viewRoot;
},
/**
* @param {module:echarts/data/Tree~Node} [viewRoot]
*/
resetViewRoot: function (viewRoot) {
viewRoot
? (this._viewRoot = viewRoot)
: (viewRoot = this._viewRoot);
if (!viewRoot
|| (viewRoot !== root && !root.contains(viewRoot))
) {
this._viewRoot = root;
}
}
});
/**
* @param {Object} dataNode
*/
function completeTreeValue(dataNode) {
// Postorder travel tree.
// If value of none-leaf node is not set,
// calculate it by suming up the value of all children.
var sum = 0;
completeTreeValue(child);
sum += childValue;
});
isArray(dataNode.value)
? (dataNode.value[0] = thisValue)
: (dataNode.value = thisValue);
}
/**
* set default to level configuration
*/
function setDefault(levels, ecModel) {
var globalColorList = ecModel.get('color');
if (!globalColorList) {
return;
}
if (model.get('itemStyle.color')
|| (modelColor && modelColor !== 'none')
) {
hasColorDefine = true;
}
});
if (!hasColorDefine) {
var level0 = levels[0] || (levels[0] = {});
level0.color = globalColorList.slice();
}
return levels;
}
var TEXT_PADDING = 8;
var ITEM_GAP = 8;
var ARRAY_LENGTH = 5;
function Breadcrumb(containerGroup) {
/**
* @private
* @type {module:zrender/container/Group}
*/
this.group = new Group();
containerGroup.add(this.group);
}
Breadcrumb.prototype = {
constructor: Breadcrumb,
thisGroup.removeAll();
if (!model.get('show') || !targetNode) {
return;
}
var layoutParam = {
pos: {
left: model.get('left'),
right: model.get('right'),
top: model.get('top'),
bottom: model.get('bottom')
},
box: {
width: api.getWidth(),
height: api.getHeight()
},
emptyItemWidth: model.get('emptyItemWidth'),
totalWidth: 0,
renderList: []
};
/**
* Prepare render list and total width
* @private
*/
_prepare: function (targetNode, layoutParam, textStyleModel) {
for (var node = targetNode; node; node = node.parentNode) {
var text = node.getModel().get('name');
var textRect = textStyleModel.getTextRect(text);
var itemWidth = Math.max(
textRect.width + TEXT_PADDING * 2,
layoutParam.emptyItemWidth
);
layoutParam.totalWidth += itemWidth + ITEM_GAP;
layoutParam.renderList.push({node: node, text: text, width:
itemWidth});
}
},
/**
* @private
*/
_renderContent: function (
seriesModel, layoutParam, normalStyleModel, textStyleModel, onSelect
) {
// Start rendering.
var lastX = 0;
var emptyItemWidth = layoutParam.emptyItemWidth;
var height = seriesModel.get('breadcrumb.height');
var availableSize = getAvailableSize(layoutParam.pos, layoutParam.box);
var totalWidth = layoutParam.totalWidth;
var renderList = layoutParam.renderList;
/**
* @override
*/
remove: function () {
this.group.removeAll();
}
};
/**
* @param {number} [time=500] Time in ms
* @param {string} [easing='linear']
* @param {number} [delay=0]
* @param {Function} [callback]
*
* @example
* // Animate position
* animation
* .createWrap()
* .add(el1, {position: [10, 10]})
* .add(el2, {shape: {width: 500}, style: {fill: 'red'}}, 400)
* .done(function () { // done })
* .start('cubicOut');
*/
function createWrap() {
return {
/**
* Caution: a el can only be added once, otherwise 'done'
* might not be called. This method checks this (by el.id),
* suppresses adding and returns false when existing el found.
*
* @param {modele:zrender/Element} el
* @param {Object} target
* @param {number} [time=500]
* @param {number} [delay=0]
* @param {string} [easing='linear']
* @return {boolean} Whether adding succeeded.
*
* @example
* add(el, target, time, delay, easing);
* add(el, target, time, easing);
* add(el, target, time);
* add(el, target);
*/
add: function (el, target, time, delay, easing) {
if (isString(delay)) {
easing = delay;
delay = 0;
}
if (elExistsMap[el.id]) {
return false;
}
elExistsMap[el.id] = 1;
storage.push(
{el: el, target: target, time: time, delay: delay, easing: easing}
);
return true;
},
/**
* Only execute when animation finished. Will not execute when any
* of 'stop' or 'stopAnimation' called.
*
* @param {Function} callback
*/
done: function (callback) {
doneCallback = callback;
return this;
},
/**
* Will stop exist animation firstly.
*/
start: function () {
var count = storage.length;
return this;
function done() {
count--;
if (!count) {
storage.length = 0;
elExistsMap = {};
doneCallback && doneCallback();
}
}
}
};
}
var DRAG_THRESHOLD = 3;
var PATH_LABEL_NOAMAL = ['label'];
var PATH_LABEL_EMPHASIS = ['emphasis', 'label'];
var PATH_UPPERLABEL_NORMAL = ['upperLabel'];
var PATH_UPPERLABEL_EMPHASIS = ['emphasis', 'upperLabel'];
var Z_BASE = 10; // Should bigger than every z.
var Z_BG = 1;
var Z_CONTENT = 2;
extendChartView({
type: 'treemap',
/**
* @override
*/
init: function (o, api) {
/**
* @private
* @type {module:zrender/container/Group}
*/
this._containerGroup;
/**
* @private
* @type {Object.<string, Array.<module:zrender/container/Group>>}
*/
this._storage = createStorage();
/**
* @private
* @type {module:echarts/data/Tree}
*/
this._oldTree;
/**
* @private
* @type {module:echarts/chart/treemap/Breadcrumb}
*/
this._breadcrumb;
/**
* @private
* @type {module:echarts/component/helper/RoamController}
*/
this._controller;
/**
* 'ready', 'animating'
* @private
*/
this._state = 'ready';
},
/**
* @override
*/
render: function (seriesModel, ecModel, api, payload) {
this.seriesModel = seriesModel;
this.api = api;
this.ecModel = ecModel;
this._resetController(api);
/**
* @private
*/
_giveContainerGroup: function (layoutInfo) {
var containerGroup = this._containerGroup;
if (!containerGroup) {
// FIXME
// 加一层 containerGroup 是为了 clip,但是现在 clip 功能并没有实现。
containerGroup = this._containerGroup = new Group$2();
this._initEvents(containerGroup);
this.group.add(containerGroup);
}
containerGroup.attr('position', [layoutInfo.x, layoutInfo.y]);
return containerGroup;
},
/**
* @private
*/
_doRender: function (containerGroup, seriesModel, reRoot) {
var thisTree = seriesModel.getData().tree;
var oldTree = this._oldTree;
// Notice: when thisTree and oldTree are the same tree (see
list.cloneShallow),
// the oldTree is actually losted, so we can not find all of the old
graphic
// elements from tree. So we use this stragegy: make element storage, move
// from old storage to new storage, clear old storage.
dualTravel(
thisTree.root ? [thisTree.root] : [],
(oldTree && oldTree.root) ? [oldTree.root] : [],
containerGroup,
thisTree === oldTree || !oldTree,
0
);
this._oldTree = thisTree;
this._storage = thisStorage;
return {
lastsForAnimation: lastsForAnimation,
willDeleteEls: willDeleteEls,
renderFinally: renderFinally
};
function getKey(node) {
// Identify by name or raw index.
return node.getId();
}
function clearStorage(storage) {
var willDeleteEls = createStorage();
storage && each$9(storage, function (store, storageName) {
var delEls = willDeleteEls[storageName];
each$9(store, function (el) {
el && (delEls.push(el), el.__tmWillDelete = 1);
});
});
return willDeleteEls;
}
function renderFinally() {
each$9(willDeleteEls, function (els) {
each$9(els, function (el) {
el.parent && el.parent.remove(el);
});
});
each$9(willInvisibleEls, function (el) {
el.invisible = true;
// Setting invisible is for optimizing, so no need to set dirty,
// just mark as invisible.
el.dirty();
});
}
},
/**
* @private
*/
_doAnimation: function (containerGroup, renderResult, seriesModel, reRoot) {
if (!seriesModel.get('animation')) {
return;
}
if (!parent.__tmWillDelete) {
// Let node animate to right-bottom corner, cooperating
with fadeout,
// which is appropriate for user understanding.
// Divided by 2 for reRoot rolling up effect.
targetX = parent.__tmNodeWidth / 2;
targetY = parent.__tmNodeHeight / 2;
}
if (!last) {
return;
}
if (last.fadein) {
el.setStyle('opacity', 0);
target.style = {opacity: 1};
}
// When animation is stopped for succedent animation starting,
// el.style.opacity might not be 1
else if (el.style.opacity !== 1) {
target.style = {opacity: 1};
}
}
this._state = 'animating';
animationWrap
.done(bind$1(function () {
this._state = 'ready';
renderResult.renderFinally();
}, this))
.start();
},
/**
* @private
*/
_resetController: function (api) {
var controller = this._controller;
// Init controller.
if (!controller) {
controller = this._controller = new RoamController(api.getZr());
controller.enable(this.seriesModel.get('roam'));
controller.on('pan', bind$1(this._onPan, this));
controller.on('zoom', bind$1(this._onZoom, this));
}
/**
* @private
*/
_clearController: function () {
var controller = this._controller;
if (controller) {
controller.dispose();
controller = null;
}
},
/**
* @private
*/
_onPan: function (dx, dy) {
if (this._state !== 'animating'
&& (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD)
) {
// These param must not be cached.
var root = this.seriesModel.getData().tree.root;
if (!root) {
return;
}
if (!rootLayout) {
return;
}
this.api.dispatchAction({
type: 'treemapMove',
from: this.uid,
seriesId: this.seriesModel.id,
rootRect: {
x: rootLayout.x + dx, y: rootLayout.y + dy,
width: rootLayout.width, height: rootLayout.height
}
});
}
},
/**
* @private
*/
_onZoom: function (scale, mouseX, mouseY) {
if (this._state !== 'animating') {
// These param must not be cached.
var root = this.seriesModel.getData().tree.root;
if (!root) {
return;
}
if (!rootLayout) {
return;
}
rect.applyTransform(m);
this.api.dispatchAction({
type: 'treemapRender',
from: this.uid,
seriesId: this.seriesModel.id,
rootRect: {
x: rect.x, y: rect.y,
width: rect.width, height: rect.height
}
});
}
},
/**
* @private
*/
_initEvents: function (containerGroup) {
containerGroup.on('click', function (e) {
if (this._state !== 'ready') {
return;
}
if (!nodeClick) {
return;
}
if (!targetInfo) {
return;
}
}, this);
},
/**
* @private
*/
_renderBreadcrumb: function (seriesModel, api, targetInfo) {
if (!targetInfo) {
targetInfo = seriesModel.get('leafDepth', true) != null
? {node: seriesModel.getViewRoot()}
// FIXME
// better way?
// Find breadcrumb tail on center of containerGroup.
: this.findTarget(api.getWidth() / 2, api.getHeight() / 2);
if (!targetInfo) {
targetInfo = {node: seriesModel.getData().tree.root};
}
}
function onSelect(node) {
if (this._state !== 'animating') {
aboveViewRoot(seriesModel.getViewRoot(), node)
? this._rootToNode({node: node})
: this._zoomToNode({node: node});
}
}
},
/**
* @override
*/
remove: function () {
this._clearController();
this._containerGroup && this._containerGroup.removeAll();
this._storage = createStorage();
this._state = 'ready';
this._breadcrumb && this._breadcrumb.remove();
},
dispose: function () {
this._clearController();
},
/**
* @private
*/
_zoomToNode: function (targetInfo) {
this.api.dispatchAction({
type: 'treemapZoomToNode',
from: this.uid,
seriesId: this.seriesModel.id,
targetNode: targetInfo.node
});
},
/**
* @private
*/
_rootToNode: function (targetInfo) {
this.api.dispatchAction({
type: 'treemapRootToNode',
from: this.uid,
seriesId: this.seriesModel.id,
targetNode: targetInfo.node
});
},
/**
* @public
* @param {number} x Global coord x.
* @param {number} y Global coord y.
* @return {Object} info If not found, return undefined;
* @return {number} info.node Target node.
* @return {number} info.offsetX x refer to target node.
* @return {number} info.offsetY y refer to target node.
*/
findTarget: function (x, y) {
var targetInfo;
var viewRoot = this.seriesModel.getViewRoot();
return targetInfo;
}
});
/**
* @inner
*/
function createStorage() {
return {nodeGroup: [], background: [], content: []};
}
/**
* @inner
* @return Return undefined means do not travel further.
*/
function renderNode(
seriesModel, thisStorage, oldStorage, reRoot,
lastsForAnimation, willInvisibleEls,
thisNode, oldNode, parentGroup, depth
) {
// Whether under viewRoot.
if (!thisNode) {
// Deleting nodes will be performed finally. This method just find
// element from old storage, or create new element, set them to new
// storage, and set styles.
return;
}
// -------------------------------------------------------------------
// Start of closure variables available in "Procedures in renderNode".
if (!thisLayout || !thisLayout.isInView) {
return;
}
// Node group
var group = giveGraphic('nodeGroup', Group$2);
if (!group) {
return;
}
parentGroup.add(group);
// x,y are not set when el is above view root.
group.attr('position', [thisLayout.x || 0, thisLayout.y || 0]);
group.__tmNodeWidth = thisWidth;
group.__tmNodeHeight = thisHeight;
if (thisLayout.isAboveViewRoot) {
return group;
}
// Background
var bg = giveGraphic('background', Rect$1, depth, Z_BG);
bg && renderBackground(group, bg, isParent && thisLayout.upperHeight);
return group;
// ----------------------------
// | Procedures in renderNode |
// ----------------------------
updateStyle(bg, function () {
var normalStyle = getItemStyleNormal(itemStyleNormalModel);
normalStyle.fill = visualBorderColor;
var emphasisStyle = getItemStyleEmphasis(itemStyleEmphasisModel);
emphasisStyle.fill = emphasisBorderColor;
if (useUpperLabel) {
var upperLabelWidth = thisWidth - 2 * borderWidth;
prepareText(
normalStyle, emphasisStyle, visualBorderColor, upperLabelWidth,
upperHeight,
{x: borderWidth, y: 0, width: upperLabelWidth, height:
upperHeight}
);
}
// For old bg.
else {
normalStyle.text = emphasisStyle.text = null;
}
bg.setStyle(normalStyle);
setHoverStyle(bg, emphasisStyle);
});
group.add(bg);
}
content.culling = true;
content.setShape({
x: borderWidth,
y: borderWidth,
width: contentWidth,
height: contentHeight
});
content.setStyle(normalStyle);
setHoverStyle(content, emphasisStyle);
});
group.add(content);
}
if (!element.__tmWillVisible) {
element.invisible = false;
}
}
else {
// Delay invisible setting utill animation finished,
// avoid element vanish suddenly before animation.
!element.invisible && willInvisibleEls.push(element);
}
}
setLabelStyle(
normalStyle, emphasisStyle, normalLabelModel, emphasisLabelModel,
{
defaultText: isShow ? text : null,
autoColor: visualColor,
isRectText: true
}
);
if (element) {
// Remove from oldStorage
oldStorage[storageName][oldRawIndex] = null;
prepareAnimationWhenHasOld(lasts, element, storageName);
}
// If invisible and no old element, do not create new element (for
optimizing).
else if (!thisInvisible) {
element = new Ctor({z: calculateZ(depth, z)});
element.__tmDepth = depth;
element.__tmStorageName = storageName;
prepareAnimationWhenNoOld(lasts, element, storageName);
}
// Set to thisStorage
return (thisStorage[storageName][thisRawIndex] = element);
}
// Fade in, user can be aware that these nodes are new.
lastCfg.fadein = storageName !== 'nodeGroup';
}
}
// We can not set all backgroud with the same z, Because the behaviour of
// drill down and roll up differ background creation sequence from tree
// hierarchy sequence, which cause that lowser background element overlap
// upper ones. So we calculate z based on depth.
// Moreover, we try to shrink down z interval to [0, 1] to avoid that
// treemap with large z overlaps other components.
function calculateZ(depth, zInLevel) {
var zb = depth * Z_BASE + zInLevel;
return (zb - 1) / zb;
}
/**
* @file Treemap action
*/
var actionTypes = [
'treemapZoomToNode',
'treemapRender',
'treemapMove'
];
registerAction(
{type: 'treemapRootToNode', update: 'updateView'},
function (payload, ecModel) {
ecModel.eachComponent(
{mainType: 'series', subType: 'treemap', query: payload},
handleRootToNode
);
if (targetInfo) {
var originViewRoot = model.getViewRoot();
if (originViewRoot) {
payload.direction = aboveViewRoot(originViewRoot,
targetInfo.node)
? 'rollUp' : 'drillDown';
}
model.resetViewRoot(targetInfo.node);
}
}
}
);
/**
* @param {Object} option
* @param {string} [option.type] See visualHandlers.
* @param {string} [option.mappingMethod] 'linear' or 'piecewise' or 'category' or
'fixed'
* @param {Array.<number>=} [option.dataExtent] [minExtent, maxExtent],
* required when mappingMethod is
'linear'
* @param {Array.<Object>=} [option.pieceList] [
* {value: someValue},
* {interval: [min1, max1], visual:
{...}},
* {interval: [min2, max2]}
* ],
* required when mappingMethod is
'piecewise'.
* Visual for only each piece can be
specified.
* @param {Array.<string|Object>=} [option.categories] ['cate1', 'cate2']
* required when mappingMethod is
'category'.
* If no option.categories, categories
is set
* as [0, 1, 2, ...].
* @param {boolean} [option.loop=false] Whether loop mapping when mappingMethod is
'category'.
* @param {(Array|Object|*)} [option.visual] Visual data.
* when mappingMethod is 'category',
* visual data can be array or object
* (like: {cate1: '#222', none: '#fff'})
* or primary types (which represents
* defualt category visual), otherwise
visual
* can be array or primary (which will
be
* normalized to array).
*
*/
var VisualMapping = function (option) {
var mappingMethod = option.mappingMethod;
var visualType = option.type;
/**
* @readOnly
* @type {Object}
*/
var thisOption = this.option = clone(option);
/**
* @readOnly
* @type {string}
*/
this.type = visualType;
/**
* @readOnly
* @type {string}
*/
this.mappingMethod = mappingMethod;
/**
* @private
* @type {Function}
*/
this._normalizeData = normalizers[mappingMethod];
/**
* @public
* @type {Function}
*/
this.applyVisual = visualHandler.applyVisual;
/**
* @public
* @type {Function}
*/
this.getColorMapper = visualHandler.getColorMapper;
/**
* @private
* @type {Function}
*/
this._doMap = visualHandler._doMap[mappingMethod];
VisualMapping.prototype = {
constructor: VisualMapping,
getNormalizer: function () {
return bind(this._normalizeData, this);
}
};
color: {
applyVisual: makeApplyVisual('color'),
/**
* Create a mapper function
* @return {Function}
*/
getColorMapper: function () {
var thisOption = this.option;
return bind(
thisOption.mappingMethod === 'category'
? function (value, isNormalized) {
!isNormalized && (value = this._normalizeData(value));
return doMapCategory.call(this, value);
}
: function (value, isNormalized, out) {
// If output rgb array
// which will be much faster and useful in pixel
manipulation
var returnRGBArray = !!out;
!isNormalized && (value = this._normalizeData(value));
out = fastLerp(value, thisOption.parsedVisual, out);
return returnRGBArray ? out : stringify(out, 'rgba');
},
this
);
},
_doMap: {
linear: function (normalized) {
return stringify(
fastLerp(normalized, this.option.parsedVisual),
'rgba'
);
},
category: doMapCategory,
piecewise: function (normalized, value) {
var result = getSpecifiedVisual.call(this, value);
if (result == null) {
result = stringify(
fastLerp(normalized, this.option.parsedVisual),
'rgba'
);
}
return result;
},
fixed: doMapFixed
}
},
opacity: {
applyVisual: makeApplyVisual('opacity'),
_doMap: makeDoMap([0, 1])
},
symbol: {
applyVisual: function (value, getter, setter) {
var symbolCfg = this.mapValueToVisual(value);
if (isString(symbolCfg)) {
setter('symbol', symbolCfg);
}
else if (isObject$5(symbolCfg)) {
for (var name in symbolCfg) {
if (symbolCfg.hasOwnProperty(name)) {
setter(name, symbolCfg[name]);
}
}
}
},
_doMap: {
linear: doMapToArray,
category: doMapCategory,
piecewise: function (normalized, value) {
var result = getSpecifiedVisual.call(this, value);
if (result == null) {
result = doMapToArray.call(this, normalized);
}
return result;
},
fixed: doMapFixed
}
},
symbolSize: {
applyVisual: makeApplyVisual('symbolSize'),
_doMap: makeDoMap([0, 1])
}
};
function preprocessForPiecewise(thisOption) {
var pieceList = thisOption.pieceList;
thisOption.hasSpecialVisual = false;
function preprocessForSpecifiedCategory(thisOption) {
// Hash categories.
var categories = thisOption.categories;
var visual = thisOption.visual;
if (isObject$1(visual)) {
each$10(visual, function (v, cate) {
var index = categoryMap[cate];
visualArr[index != null ? index : CATEGORY_DEFAULT_VISUAL_INDEX] =
v;
});
}
else { // Is primary type, represents default visual.
visualArr[CATEGORY_DEFAULT_VISUAL_INDEX] = visual;
}
if (isObject$1(visual)) {
each$10(visual, function (v) {
visualArr.push(v);
});
}
else if (visual != null) {
visualArr.push(visual);
}
if (!isCategory
&& visualArr.length === 1
&& !doNotNeedPair.hasOwnProperty(thisOption.type)
) {
// Do not care visualArr.length === 0, which is illegal.
visualArr[1] = visualArr[0];
}
setVisualToOption(thisOption, visualArr);
}
function makePartialColorVisualHandler(applyValue) {
return {
applyVisual: function (value, getter, setter) {
value = this.mapValueToVisual(value);
// Must not be array value
setter('color', applyValue(getter('color'), value));
},
_doMap: makeDoMap([0, 1])
};
}
function doMapToArray(normalized) {
var visual = this.option.visual;
return visual[
Math.round(linearMap(normalized, [0, 1], [0, visual.length - 1], true))
] || {};
}
function makeApplyVisual(visualType) {
return function (value, getter, setter) {
setter(visualType, this.mapValueToVisual(value));
};
}
function doMapCategory(normalized) {
var visual = this.option.visual;
return visual[
(this.option.loop && normalized !== CATEGORY_DEFAULT_VISUAL_INDEX)
? normalized % visual.length
: normalized
];
}
function doMapFixed() {
return this.option.visual[0];
}
function makeDoMap(sourceExtent) {
return {
linear: function (normalized) {
return linearMap(normalized, sourceExtent, this.option.visual, true);
},
category: doMapCategory,
piecewise: function (normalized, value) {
var result = getSpecifiedVisual.call(this, value);
if (result == null) {
result = linearMap(normalized, sourceExtent, this.option.visual,
true);
}
return result;
},
fixed: doMapFixed
};
}
function getSpecifiedVisual(value) {
var thisOption = this.option;
var pieceList = thisOption.pieceList;
if (thisOption.hasSpecialVisual) {
var pieceIndex = VisualMapping.findPieceIndex(value, pieceList);
var piece = pieceList[pieceIndex];
if (piece && piece.visual) {
return piece.visual[this.type];
}
}
}
/**
* Normalizers by mapping methods.
*/
var normalizers = {
fixed: noop
};
/**
* List available visual types.
*
* @public
* @return {Array.<string>}
*/
VisualMapping.listVisualTypes = function () {
var visualTypes = [];
each$1(visualHandlers, function (handler, key) {
visualTypes.push(key);
});
return visualTypes;
};
/**
* @public
*/
VisualMapping.addVisualHandler = function (name, handler) {
visualHandlers[name] = handler;
};
/**
* @public
*/
VisualMapping.isValidType = function (visualType) {
return visualHandlers.hasOwnProperty(visualType);
};
/**
* Convinent method.
* Visual can be Object or Array or primary type.
*
* @public
*/
VisualMapping.eachVisual = function (visual, callback, context) {
if (isObject$1(visual)) {
each$1(visual, callback, context);
}
else {
callback.call(context, visual);
}
};
/**
* @public
* @param {Object} obj
* @return {Object} new object containers visual values.
* If no visuals, return null.
*/
VisualMapping.retrieveVisuals = function (obj) {
var ret = {};
var hasVisual;
/**
* Give order to visual types, considering colorSaturation, colorAlpha depends on
color.
*
* @public
* @param {(Object|Array)} visualTypes If Object, like: {color: ...,
colorSaturation: ...}
* IF Array, like: ['color', 'symbol',
'colorSaturation']
* @return {Array.<string>} Sorted visual types.
*/
VisualMapping.prepareVisualTypes = function (visualTypes) {
if (isObject$5(visualTypes)) {
var types = [];
each$10(visualTypes, function (item, type) {
types.push(type);
});
visualTypes = types;
}
else if (isArray(visualTypes)) {
visualTypes = visualTypes.slice();
}
else {
return [];
}
return visualTypes;
};
/**
* 'color', 'colorSaturation', 'colorAlpha', ... are depends on 'color'.
* Other visuals are only depends on themself.
*
* @public
* @param {string} visualType1
* @param {string} visualType2
* @return {boolean}
*/
VisualMapping.dependsOn = function (visualType1, visualType2) {
return visualType2 === 'color'
? !!(visualType1 && visualType1.indexOf(visualType2) === 0)
: visualType1 === visualType2;
};
/**
* @param {number} value
* @param {Array.<Object>} pieceList [{value: ..., interval: [min, max]}, ...]
* Always from small to big.
* @param {boolean} [findClosestWhenOutside=false]
* @return {number} index
*/
VisualMapping.findPieceIndex = function (value, pieceList, findClosestWhenOutside)
{
var possibleI;
var abs = Infinity;
if (interval) {
if (interval[0] === -Infinity) {
if (littleThan(close[1], value, interval[1])) {
return i;
}
}
else if (interval[1] === Infinity) {
if (littleThan(close[0], interval[0], value)) {
return i;
}
}
else if (
littleThan(close[0], interval[0], value)
&& littleThan(close[1], value, interval[1])
) {
return i;
}
findClosestWhenOutside && updatePossible(interval[0], i);
findClosestWhenOutside && updatePossible(interval[1], i);
}
}
if (findClosestWhenOutside) {
return value === Infinity
? pieceList.length - 1
: value === -Infinity
? 0
: possibleI;
}
function littleThan(close, a, b) {
return close ? a <= b : a < b;
}
var treemapVisual = {
seriesType: 'treemap',
reset: function (seriesModel, ecModel, api, payload) {
var tree = seriesModel.getData().tree;
var root = tree.root;
var seriesItemStyleModel = seriesModel.getModel(ITEM_STYLE_NORMAL);
if (root.isRemoved()) {
return;
}
travelTree(
root, // Visual should calculate from tree root but not view root.
{},
levelItemStyles,
seriesItemStyleModel,
seriesModel.getViewRoot().getAncestors(),
seriesModel
);
}
};
function travelTree(
node, designatedVisual, levelItemStyles, seriesItemStyleModel,
viewRootAncestors, seriesModel
) {
var nodeModel = node.getModel();
var nodeLayout = node.getLayout();
// Optimize
if (!nodeLayout || nodeLayout.invisible || !nodeLayout.isInView) {
return;
}
function buildVisuals(
nodeItemStyleModel, designatedVisual, levelItemStyle, seriesItemStyleModel
) {
var visuals = extend({}, designatedVisual);
return visuals;
}
function calculateColor(visuals) {
var color = getValueVisualDefine(visuals, 'color');
if (color) {
var colorAlpha = getValueVisualDefine(visuals, 'colorAlpha');
var colorSaturation = getValueVisualDefine(visuals, 'colorSaturation');
if (colorSaturation) {
color = modifyHSL(color, null, null, colorSaturation);
}
if (colorAlpha) {
color = modifyAlpha(color, colorAlpha);
}
return color;
}
}
function buildVisualMapping(
node, nodeModel, nodeLayout, nodeItemStyleModel, visuals, viewChildren
) {
if (!viewChildren || !viewChildren.length) {
return;
}
if (!rangeVisual) {
return;
}
return mapping;
}
if (mapping) {
var mappingType = mapping.type;
var colorMappingBy = mappingType === 'color' && mapping.__drColorMappingBy;
var value =
colorMappingBy === 'index'
? index
: colorMappingBy === 'id'
? seriesModel.mapIdToIndex(child.getId())
: child.getValue(nodeModel.get('visualDimension'));
childVisuals[mappingType] = mapping.mapValueToVisual(value);
}
return childVisuals;
}
// TODO
// optimize: if out of view clip, do not layout.
// But take care that if do not render node out of view clip,
// how to calculate start po
var viewRootLayout = {
x: 0, y: 0,
width: rootSize[0], height: rootSize[1],
area: rootSize[0] * rootSize[1]
};
viewRoot.setLayout(viewRootLayout);
treeRoot.setLayout(
calculateRootPosition(layoutInfo, rootRect, targetInfo),
true
);
seriesModel.setLayoutInfo(layoutInfo);
// FIXME
// 现在没有 clip 功能,暂时取 ec 高宽。
prunning(
treeRoot,
// Transform to base element coordinate system.
new BoundingRect(-layoutInfo.x, -layoutInfo.y, ecWidth, ecHeight),
viewAbovePath,
viewRoot,
0
);
}
};
/**
* Layout treemap with squarify algorithm.
* @see
https://graphics.ethz.ch/teaching/scivis_common/Literature/squarifiedTreeMaps.pdf
* @see https://github.com/mbostock/d3/blob/master/src/layout/treemap.js
*
* @protected
* @param {module:echarts/data/Tree~TreeNode} node
* @param {Object} options
* @param {string} options.sort 'asc' or 'desc'
* @param {number} options.squareRatio
* @param {boolean} hideChildren
* @param {number} depth
*/
function squarify(node, options, hideChildren, depth) {
var width;
var height;
if (node.isRemoved()) {
return;
}
node.setLayout({
borderWidth: borderWidth,
upperHeight: upperHeight,
upperLabelHeight: upperLabelHeight
}, true);
if (!viewChildren.length) {
return;
}
row.push(child);
row.area += child.getLayout().area;
var score = worst(row, rowFixedLength, options.squareRatio);
if (row.length) {
position(row, rowFixedLength, rect, halfGapWidth, true);
}
if (!hideChildren) {
var childrenVisibleMin = nodeModel.get('childrenVisibleMin');
if (childrenVisibleMin != null && totalArea < childrenVisibleMin) {
hideChildren = true;
}
}
/**
* Set area to each child, and calculate data extent for visual coding.
*/
function initChildren(node, nodeModel, totalArea, options, hideChildren, depth) {
var viewChildren = node.children || [];
var orderBy = options.sort;
orderBy !== 'asc' && orderBy !== 'desc' && (orderBy = null);
sort$1(viewChildren, orderBy);
if (info.sum === 0) {
return (node.viewChildren = []);
}
if (info.sum === 0) {
return (node.viewChildren = []);
}
if (overLeafDepth) {
viewChildren.length && node.setLayout({isLeafRoot: true}, true);
viewChildren.length = 0;
}
node.viewChildren = viewChildren;
node.setLayout({dataExtent: info.dataExtent}, true);
return viewChildren;
}
/**
* Consider 'visibleMin'. Modify viewChildren and get new sum.
*/
function filterByThreshold(nodeModel, totalArea, sum, orderBy, orderedChildren) {
return sum;
}
/**
* Sort
*/
function sort$1(viewChildren, orderBy) {
if (orderBy) {
viewChildren.sort(function (a, b) {
var diff = orderBy === 'asc'
? a.getValue() - b.getValue() : b.getValue() - a.getValue();
return diff === 0
? (orderBy === 'asc'
? a.dataIndex - b.dataIndex : b.dataIndex - a.dataIndex
)
: diff;
});
}
return viewChildren;
}
/**
* Statistic
*/
function statistic(nodeModel, children, orderBy) {
// Calculate sum.
var sum = 0;
for (var i = 0, len = children.length; i < len; i++) {
sum += children[i].getValue();
}
/**
* Computes the score for the specified row,
* as the worst aspect ratio.
*/
function worst(row, rowFixedLength, ratio) {
var areaMax = 0;
var areaMin = Infinity;
return squareArea
? mathMax$4(
(f * areaMax) / squareArea,
squareArea / (f * areaMin)
)
: Infinity;
}
/**
* Positions the specified row of nodes. Modifies `rect`.
*/
function position(row, rowFixedLength, rect, halfGapWidth, flush) {
// When rowFixedLength === rect.width,
// it is horizontal subdivision,
// rowFixedLength is the width of the subdivision,
// rowOtherLength is the height of the subdivision,
// and nodes will be positioned from left to right.
last += modWH;
node.setLayout(nodeLayout, true);
}
rect[xy[idx1WhenH]] += rowOtherLength;
rect[wh[idx1WhenH]] -= rowOtherLength;
}
var parent;
var viewArea = containerWidth * containerHeight;
var area = viewArea * seriesModel.option.zoomToNodeRatio;
currNode = parent;
}
if (!layout) {
return defaultPosition;
}
return {
x: layoutInfo.width / 2 - targetCenter[0],
y: layoutInfo.height / 2 - targetCenter[1]
};
}
// Mark nodes visible for prunning when visual coding and rendering.
// Prunning depends on layout and root position, so we have to do it after layout.
function prunning(node, clipRect, viewAbovePath, viewRoot, depth) {
var nodeLayout = node.getLayout();
var nodeInViewAbovePath = viewAbovePath[depth];
var isAboveViewRoot = nodeInViewAbovePath && nodeInViewAbovePath === node;
if (
(nodeInViewAbovePath && !isAboveViewRoot)
|| (depth === viewAbovePath.length && node !== viewRoot)
) {
return;
}
node.setLayout({
// isInView means: viewRoot sub tree + viewAbovePath
isInView: true,
// invisible only means: outside view clip so that the node can not
// see but still layout for animation preparation but not render.
invisible: !isAboveViewRoot && !clipRect.intersect(nodeLayout),
isAboveViewRoot: isAboveViewRoot
}, true);
function getUpperLabelHeight(model) {
return model.get(PATH_UPPER_LABEL_SHOW) ? model.get(PATH_UPPER_LABEL_HEIGHT) :
0;
}
registerVisual(treemapVisual);
registerLayout(treemapLayout);
/**
* Graph data structure
*
* @module echarts/data/Graph
* @author Yi Shen(https://www.github.com/pissang)
*/
/**
* @type {Array.<module:echarts/data/Graph.Edge>}
* @readOnly
*/
this.edges = [];
/**
* @type {Object.<string, module:echarts/data/Graph.Node>}
* @private
*/
this._nodesMap = {};
/**
* @type {Object.<string, module:echarts/data/Graph.Edge>}
* @private
*/
this._edgesMap = {};
/**
* @type {module:echarts/data/List}
* @readOnly
*/
this.data;
/**
* @type {module:echarts/data/List}
* @readOnly
*/
this.edgeData;
};
/**
* If is directed graph
* @return {boolean}
*/
graphProto.isDirected = function () {
return this._directed;
};
/**
* Add a new node
* @param {string} id
* @param {number} [dataIndex]
*/
graphProto.addNode = function (id, dataIndex) {
id = id || ('' + dataIndex);
var nodesMap = this._nodesMap;
if (nodesMap[generateNodeKey(id)]) {
if (__DEV__) {
console.error('Graph nodes have duplicate name or id');
}
return;
}
this.nodes.push(node);
nodesMap[generateNodeKey(id)] = node;
return node;
};
/**
* Get node by data index
* @param {number} dataIndex
* @return {module:echarts/data/Graph~Node}
*/
graphProto.getNodeByIndex = function (dataIndex) {
var rawIdx = this.data.getRawIndex(dataIndex);
return this.nodes[rawIdx];
};
/**
* Get node by id
* @param {string} id
* @return {module:echarts/data/Graph.Node}
*/
graphProto.getNodeById = function (id) {
return this._nodesMap[generateNodeKey(id)];
};
/**
* Add a new edge
* @param {number|string|module:echarts/data/Graph.Node} n1
* @param {number|string|module:echarts/data/Graph.Node} n2
* @param {number} [dataIndex=-1]
* @return {module:echarts/data/Graph.Edge}
*/
graphProto.addEdge = function (n1, n2, dataIndex) {
var nodesMap = this._nodesMap;
var edgesMap = this._edgesMap;
// PNEDING
if (typeof n1 === 'number') {
n1 = this.nodes[n1];
}
if (typeof n2 === 'number') {
n2 = this.nodes[n2];
}
if (!Node.isInstance(n1)) {
n1 = nodesMap[generateNodeKey(n1)];
}
if (!Node.isInstance(n2)) {
n2 = nodesMap[generateNodeKey(n2)];
}
if (!n1 || !n2) {
return;
}
if (this._directed) {
n1.outEdges.push(edge);
n2.inEdges.push(edge);
}
n1.edges.push(edge);
if (n1 !== n2) {
n2.edges.push(edge);
}
this.edges.push(edge);
edgesMap[key] = edge;
return edge;
};
/**
* Get edge by data index
* @param {number} dataIndex
* @return {module:echarts/data/Graph~Node}
*/
graphProto.getEdgeByIndex = function (dataIndex) {
var rawIdx = this.edgeData.getRawIndex(dataIndex);
return this.edges[rawIdx];
};
/**
* Get edge by two linked nodes
* @param {module:echarts/data/Graph.Node|string} n1
* @param {module:echarts/data/Graph.Node|string} n2
* @return {module:echarts/data/Graph.Edge}
*/
graphProto.getEdge = function (n1, n2) {
if (Node.isInstance(n1)) {
n1 = n1.id;
}
if (Node.isInstance(n2)) {
n2 = n2.id;
}
if (this._directed) {
return edgesMap[n1 + '-' + n2];
} else {
return edgesMap[n1 + '-' + n2]
|| edgesMap[n2 + '-' + n1];
}
};
/**
* Iterate all nodes
* @param {Function} cb
* @param {*} [context]
*/
graphProto.eachNode = function (cb, context) {
var nodes = this.nodes;
var len = nodes.length;
for (var i = 0; i < len; i++) {
if (nodes[i].dataIndex >= 0) {
cb.call(context, nodes[i], i);
}
}
};
/**
* Iterate all edges
* @param {Function} cb
* @param {*} [context]
*/
graphProto.eachEdge = function (cb, context) {
var edges = this.edges;
var len = edges.length;
for (var i = 0; i < len; i++) {
if (edges[i].dataIndex >= 0
&& edges[i].node1.dataIndex >= 0
&& edges[i].node2.dataIndex >= 0
) {
cb.call(context, edges[i], i);
}
}
};
/**
* Breadth first traverse
* @param {Function} cb
* @param {module:echarts/data/Graph.Node} startNode
* @param {string} [direction='none'] 'none'|'in'|'out'
* @param {*} [context]
*/
graphProto.breadthFirstTraverse = function (
cb, startNode, direction, context
) {
if (!Node.isInstance(startNode)) {
startNode = this._nodesMap[generateNodeKey(startNode)];
}
if (!startNode) {
return;
}
// TODO
// graphProto.depthFirstTraverse = function (
// cb, startNode, direction, context
// ) {
// };
// Filter update
graphProto.update = function () {
var data = this.data;
var edgeData = this.edgeData;
var nodes = this.nodes;
var edges = this.edges;
edgeData.filterSelf(function (idx) {
var edge = edges[edgeData.getRawIndex(idx)];
return edge.node1.dataIndex >= 0 && edge.node2.dataIndex >= 0;
});
// Update edge
for (var i = 0, len = edges.length; i < len; i++) {
edges[i].dataIndex = -1;
}
for (var i = 0, len = edgeData.count(); i < len; i++) {
edges[edgeData.getRawIndex(i)].dataIndex = i;
}
};
/**
* @return {module:echarts/data/Graph}
*/
graphProto.clone = function () {
var graph = new Graph(this._directed);
var nodes = this.nodes;
var edges = this.edges;
for (var i = 0; i < nodes.length; i++) {
graph.addNode(nodes[i].id, nodes[i].dataIndex);
}
for (var i = 0; i < edges.length; i++) {
var e = edges[i];
graph.addEdge(e.node1.id, e.node2.id, e.dataIndex);
}
return graph;
};
/**
* @alias module:echarts/data/Graph.Node
*/
function Node(id, dataIndex) {
/**
* @type {string}
*/
this.id = id == null ? '' : id;
/**
* @type {Array.<module:echarts/data/Graph.Edge>}
*/
this.inEdges = [];
/**
* @type {Array.<module:echarts/data/Graph.Edge>}
*/
this.outEdges = [];
/**
* @type {Array.<module:echarts/data/Graph.Edge>}
*/
this.edges = [];
/**
* @type {module:echarts/data/Graph}
*/
this.hostGraph;
/**
* @type {number}
*/
this.dataIndex = dataIndex == null ? -1 : dataIndex;
}
Node.prototype = {
constructor: Node,
/**
* @return {number}
*/
degree: function () {
return this.edges.length;
},
/**
* @return {number}
*/
inDegree: function () {
return this.inEdges.length;
},
/**
* @return {number}
*/
outDegree: function () {
return this.outEdges.length;
},
/**
* @param {string} [path]
* @return {module:echarts/model/Model}
*/
getModel: function (path) {
if (this.dataIndex < 0) {
return;
}
var graph = this.hostGraph;
var itemModel = graph.data.getItemModel(this.dataIndex);
return itemModel.getModel(path);
}
};
/**
* 图边
* @alias module:echarts/data/Graph.Edge
* @param {module:echarts/data/Graph.Node} n1
* @param {module:echarts/data/Graph.Node} n2
* @param {number} [dataIndex=-1]
*/
function Edge(n1, n2, dataIndex) {
/**
* 节点 1,如果是有向图则为源节点
* @type {module:echarts/data/Graph.Node}
*/
this.node1 = n1;
/**
* 节点 2,如果是有向图则为目标节点
* @type {module:echarts/data/Graph.Node}
*/
this.node2 = n2;
/**
* @param {string} [path]
* @return {module:echarts/model/Model}
*/
Edge.prototype.getModel = function (path) {
if (this.dataIndex < 0) {
return;
}
var graph = this.hostGraph;
var itemModel = graph.edgeData.getItemModel(this.dataIndex);
return itemModel.getModel(path);
};
/**
* @param {Object|string} key
* @param {*} [value]
*/
setVisual: function (key, value) {
this.dataIndex >= 0
&& this[hostName][dataName].setItemVisual(this.dataIndex, key,
value);
},
/**
* @param {string} key
* @return {boolean}
*/
getVisual: function (key, ignoreParent) {
return this[hostName][dataName].getItemVisual(this.dataIndex, key,
ignoreParent);
},
/**
* @param {Object} layout
* @return {boolean} [merge=false]
*/
setLayout: function (layout, merge$$1) {
this.dataIndex >= 0
&& this[hostName][dataName].setItemLayout(this.dataIndex, layout,
merge$$1);
},
/**
* @return {Object}
*/
getLayout: function () {
return this[hostName][dataName].getItemLayout(this.dataIndex);
},
/**
* @return {module:zrender/Element}
*/
getGraphicEl: function () {
return this[hostName][dataName].getItemGraphicEl(this.dataIndex);
},
/**
* @return {number}
*/
getRawIndex: function () {
return this[hostName][dataName].getRawIndex(this.dataIndex);
}
};
};
Graph.Node = Node;
Graph.Edge = Edge;
enableClassCheck(Node);
enableClassCheck(Edge);
linkList({
mainData: nodeData,
struct: graph,
structAttr: 'graph',
datas: {node: nodeData, edge: edgeData},
datasAttr: {node: 'data', edge: 'edgeData'}
});
// Update dataIndex of nodes and edges because invalid edge may be removed
graph.update();
return graph;
};
type: 'series.graph',
this.fillDataTextStyle(option.edges || option.links);
this._updateCategoriesData();
},
this.fillDataTextStyle(option.edges || option.links);
this._updateCategoriesData();
},
mergeDefaultAndTheme: function (option) {
GraphSeries.superApply(this, 'mergeDefaultAndTheme', arguments);
defaultEmphasis(option, ['edgeLabel'], ['show']);
},
function edgeGetParent(path) {
path = this.parsePath(path);
return (path && path[0] === 'label')
? fakeSeriesModel
: this.parentModel;
}
}
},
/**
* @return {module:echarts/data/Graph}
*/
getGraph: function () {
return this.getData().graph;
},
/**
* @return {module:echarts/data/List}
*/
getEdgeData: function () {
return this.getGraph().edgeData;
},
/**
* @return {module:echarts/data/List}
*/
getCategoriesData: function () {
return this._categoriesData;
},
/**
* @override
*/
formatTooltip: function (dataIndex, multipleSeries, dataType) {
if (dataType === 'edge') {
var nodeData = this.getData();
var params = this.getDataParams(dataIndex, dataType);
var edge = nodeData.graph.getEdgeByIndex(dataIndex);
var sourceName = nodeData.getName(edge.node1.dataIndex);
var targetName = nodeData.getName(edge.node2.dataIndex);
if (params.value) {
html += ' : ' + encodeHTML(params.value);
}
return html;
}
else { // dataType === 'node' or empty
return GraphSeries.superApply(this, 'formatTooltip', arguments);
}
},
_updateCategoriesData: function () {
var categories = map(this.option.categories || [], function (category) {
// Data must has value
return category.value != null ? category : extend({
value: 0
}, category);
});
var categoriesData = new List(['value'], this);
categoriesData.initData(categories);
this._categoriesData = categoriesData;
isAnimationEnabled: function () {
return GraphSeries.superCall(this, 'isAnimationEnabled')
// Not enable animation when do force layout
&& !(this.get('layout') === 'force' &&
this.get('force.layoutAnimation'));
},
defaultOption: {
zlevel: 0,
z: 2,
coordinateSystem: 'view',
legendHoverLink: true,
hoverAnimation: true,
layout: null,
focusNodeAdjacency: false,
layoutAnimation: true
},
left: 'center',
top: 'center',
// right: null,
// bottom: null,
// width: '80%',
// height: '80%',
symbol: 'circle',
symbolSize: 10,
draggable: false,
roam: false,
zoom: 1,
// Symbol size scale ratio in roam
nodeScaleRatio: 0.6,
// cursor: null,
// categories: [],
// data: []
// Or
// nodes: []
//
// links: []
// Or
// edges: []
label: {
show: false,
formatter: '{b}'
},
itemStyle: {},
lineStyle: {
color: '#aaa',
width: 1,
curveness: 0,
opacity: 0.5
},
emphasis: {
label: {
show: true
}
}
}
});
/**
* Line path for bezier and straight line draw
*/
function isLine(shape) {
return isNaN(+shape.cpx1) || isNaN(+shape.cpy1);
}
var LinePath = extendShape({
type: 'ec-line',
style: {
stroke: '#000',
fill: null
},
shape: {
x1: 0,
y1: 0,
x2: 0,
y2: 0,
percent: 1,
cpx1: null,
cpy1: null
},
/**
* @module echarts/chart/helper/Line
*/
function makeSymbolTypeKey(symbolCategory) {
return '_' + symbolCategory + 'Type';
}
/**
* @inner
*/
function createSymbol$1(name, lineData, idx) {
var color = lineData.getItemVisual(idx, 'color');
var symbolType = lineData.getItemVisual(idx, name);
var symbolSize = lineData.getItemVisual(idx, name + 'Size');
symbolPath.name = name;
return symbolPath;
}
function createLine(points) {
var line = new LinePath({
name: 'line'
});
setLinePoints(line.shape, points);
return line;
}
if (cp1) {
targetShape.cpx1 = cp1[0];
targetShape.cpy1 = cp1[1];
}
else {
targetShape.cpx1 = NaN;
targetShape.cpy1 = NaN;
}
}
function updateSymbolAndLabelBeforeLineUpdate () {
var lineGroup = this;
var symbolFrom = lineGroup.childOfName('fromSymbol');
var symbolTo = lineGroup.childOfName('toSymbol');
var label = lineGroup.childOfName('label');
// Quick reject
if (!symbolFrom && !symbolTo && label.ignore) {
return;
}
var invScale = 1;
var parentNode = this.parent;
while (parentNode) {
if (parentNode.scale) {
invScale /= parentNode.scale[0];
}
parentNode = parentNode.parent;
}
if (symbolFrom) {
symbolFrom.attr('position', fromPos);
var tangent = line.tangentAt(0);
symbolFrom.attr('rotation', Math.PI / 2 - Math.atan2(
tangent[1], tangent[0]
));
symbolFrom.attr('scale', [invScale * percent, invScale * percent]);
}
if (symbolTo) {
symbolTo.attr('position', toPos);
var tangent = line.tangentAt(1);
symbolTo.attr('rotation', -Math.PI / 2 - Math.atan2(
tangent[1], tangent[0]
));
symbolTo.attr('scale', [invScale * percent, invScale * percent]);
}
if (!label.ignore) {
label.attr('position', toPos);
var textPosition;
var textAlign;
var textVerticalAlign;
/**
* @constructor
* @extends {module:zrender/graphic/Group}
* @alias {module:echarts/chart/helper/Line}
*/
function Line$1(lineData, idx, seriesScope) {
Group.call(this);
this.add(line);
var label = new Text({
name: 'label'
});
this.add(label);
labelModel = itemModel.getModel('label');
hoverLabelModel = itemModel.getModel('emphasis.label');
}
line.useStyle(defaults(
{
strokeNoScale: true,
fill: 'none',
stroke: visualColor,
opacity: visualOpacity
},
lineStyle
));
line.hoverStyle = hoverLineStyle;
// Update symbol
each$1(SYMBOL_CATEGORIES, function (symbolCategory) {
var symbol = this.childOfName(symbolCategory);
if (symbol) {
symbol.setColor(visualColor);
symbol.setStyle({
opacity: visualOpacity
});
}
}, this);
if (showLabel || hoverShowLabel) {
defaultLabelColor = visualColor || '#000';
// label.afterUpdate = lineAfterUpdate;
if (showLabel) {
var labelStyle = setTextStyle(label.style, labelModel, {
text: normalText
}, {
autoColor: defaultLabelColor
});
label.__textAlign = labelStyle.textAlign;
label.__verticalAlign = labelStyle.textVerticalAlign;
// 'start', 'middle', 'end'
label.__position = labelModel.get('position') || 'middle';
}
else {
label.setStyle('text', null);
}
if (hoverShowLabel) {
// Only these properties supported in this emphasis style here.
label.hoverStyle = {
text: emphasisText,
textFill: hoverLabelModel.getTextColor(true),
// For merging hover style to normal style, do not use
// `hoverLabelModel.getFont()` here.
fontStyle: hoverLabelModel.getShallow('fontStyle'),
fontWeight: hoverLabelModel.getShallow('fontWeight'),
fontSize: hoverLabelModel.getShallow('fontSize'),
fontFamily: hoverLabelModel.getShallow('fontFamily')
};
}
else {
label.hoverStyle = {
text: null
};
}
setHoverStyle(this);
};
lineProto.highlight = function () {
this.trigger('emphasis');
};
lineProto.downplay = function () {
this.trigger('normal');
};
inherits(Line$1, Group);
/**
* @module echarts/chart/helper/LineDraw
*/
/**
* @alias module:echarts/component/marker/LineDraw
* @constructor
*/
function LineDraw(ctor) {
this._ctor = ctor || Line$1;
lineDrawProto.isPersistent = function () {
return true;
};
/**
* @param {module:echarts/data/List} lineData
*/
lineDrawProto.updateData = function (lineData) {
var lineDraw = this;
var group = lineDraw.group;
lineData.diff(oldLineData)
.add(function (idx) {
doAdd(lineDraw, lineData, idx, seriesScope);
})
.update(function (newIdx, oldIdx) {
doUpdate(lineDraw, oldLineData, lineData, oldIdx, newIdx, seriesScope);
})
.remove(function (idx) {
group.remove(oldLineData.getItemGraphicEl(idx));
})
.execute();
};
function doAdd(lineDraw, lineData, idx, seriesScope) {
var itemLayout = lineData.getItemLayout(idx);
if (!lineNeedsDraw(itemLayout)) {
return;
}
if (!lineNeedsDraw(newLineData.getItemLayout(newIdx))) {
lineDraw.group.remove(itemEl);
return;
}
if (!itemEl) {
itemEl = new lineDraw._ctor(newLineData, newIdx, seriesScope);
}
else {
itemEl.updateData(newLineData, newIdx, seriesScope);
}
newLineData.setItemGraphicEl(newIdx, itemEl);
lineDraw.group.add(itemEl);
}
lineDrawProto.updateLayout = function () {
var lineData = this._lineData;
lineData.eachItemGraphicEl(function (el, idx) {
el.updateLayout(lineData, idx);
}, this);
};
if (lineNeedsDraw(itemLayout)) {
var el = new this._ctor(lineData, idx, this._seriesScope);
el.traverse(updateIncrementalAndHover);
this.group.add(el);
}
}
};
function makeSeriesScope$1(lineData) {
var hostModel = lineData.hostModel;
return {
lineStyle: hostModel.getModel('lineStyle').getLineStyle(),
hoverLineStyle: hostModel.getModel('emphasis.lineStyle').getLineStyle(),
labelModel: hostModel.getModel('label'),
hoverLabelModel: hostModel.getModel('emphasis.label')
};
}
lineDrawProto.remove = function () {
this._clearIncremental();
this._incremental = null;
this.group.removeAll();
};
lineDrawProto._clearIncremental = function () {
var incremental = this._incremental;
if (incremental) {
incremental.clearDisplaybles();
}
};
function isPointNaN(pt) {
return isNaN(pt[0]) || isNaN(pt[1]);
}
function lineNeedsDraw(pts) {
return !isPointNaN(pts[0]) && !isPointNaN(pts[1]);
}
var v1 = [];
var v2 = [];
var v3 = [];
var quadraticAt$1 = quadraticAt;
var v2DistSquare = distSquare;
var mathAbs$1 = Math.abs;
function intersectCurveCircle(curvePoints, center, radius) {
var p0 = curvePoints[0];
var p1 = curvePoints[1];
var p2 = curvePoints[2];
var d = Infinity;
var t;
var radiusSquare = radius * radius;
var interval = 0.1;
interval /= 2;
if (diff < 0) {
if (nextDiff >= 0) {
t = t + interval;
}
else {
t = t - interval;
}
}
else {
if (nextDiff >= 0) {
t = t - interval;
}
else {
t = t + interval;
}
}
}
return t;
}
function getSymbolSize(node) {
var symbolSize = node.getVisual('symbolSize');
if (symbolSize instanceof Array) {
symbolSize = (symbolSize[0] + symbolSize[1]) / 2;
}
return symbolSize;
}
graph.eachEdge(function (edge, idx) {
var linePoints = edge.getLayout();
var fromSymbol = edge.getVisual('fromSymbol');
var toSymbol = edge.getVisual('toSymbol');
if (!linePoints.__original) {
linePoints.__original = [
clone$1(linePoints[0]),
clone$1(linePoints[1])
];
if (linePoints[2]) {
linePoints.__original.push(clone$1(linePoints[2]));
}
}
var originalPoints = linePoints.__original;
// Quadratic curve
if (linePoints[2] != null) {
copy(pts[0], originalPoints[0]);
copy(pts[1], originalPoints[2]);
copy(pts[2], originalPoints[1]);
if (fromSymbol && fromSymbol != 'none') {
var symbolSize = getSymbolSize(edge.node1);
extendChartView({
type: 'graph',
init: function (ecModel, api) {
var symbolDraw = new SymbolDraw();
var lineDraw = new LineDraw();
var group = this.group;
group.add(symbolDraw.group);
group.add(lineDraw.group);
this._symbolDraw = symbolDraw;
this._lineDraw = lineDraw;
this._firstRender = true;
},
this._model = seriesModel;
this._nodeScaleRatio = seriesModel.get('nodeScaleRatio');
this._updateNodeAndLinkScale();
clearTimeout(this._layoutTimeout);
var forceLayout = seriesModel.forceLayout;
var layoutAnimation = seriesModel.get('force.layoutAnimation');
if (forceLayout) {
this._startForceLayoutIteration(forceLayout, layoutAnimation);
}
data.eachItemGraphicEl(function (el, idx) {
var itemModel = data.getItemModel(idx);
// Update draggable
el.off('drag').off('dragend');
var draggable = data.getItemModel(idx).get('draggable');
if (draggable) {
el.on('drag', function () {
if (forceLayout) {
forceLayout.warmUp();
!this._layouting
&& this._startForceLayoutIteration(forceLayout,
layoutAnimation);
forceLayout.setFixed(idx);
// Write position back to layout
data.setItemLayout(idx, el.position);
}
}, this).on('dragend', function () {
if (forceLayout) {
forceLayout.setUnfixed(idx);
}
}, this);
}
el.setDraggable(draggable && forceLayout);
el.off('mouseover', el.__focusNodeAdjacency);
el.off('mouseout', el.__unfocusNodeAdjacency);
if (itemModel.get('focusNodeAdjacency')) {
el.on('mouseover', el.__focusNodeAdjacency = function () {
api.dispatchAction({
type: 'focusNodeAdjacency',
seriesId: seriesModel.id,
dataIndex: el.dataIndex
});
});
el.on('mouseout', el.__unfocusNodeAdjacency = function () {
api.dispatchAction({
type: 'unfocusNodeAdjacency',
seriesId: seriesModel.id
});
});
}, this);
data.graph.eachEdge(function (edge) {
var el = edge.getGraphicEl();
el.off('mouseover', el.__focusNodeAdjacency);
el.off('mouseout', el.__unfocusNodeAdjacency);
if (edge.getModel().get('focusNodeAdjacency')) {
el.on('mouseover', el.__focusNodeAdjacency = function () {
api.dispatchAction({
type: 'focusNodeAdjacency',
seriesId: seriesModel.id,
edgeDataIndex: edge.dataIndex
});
});
el.on('mouseout', el.__unfocusNodeAdjacency = function () {
api.dispatchAction({
type: 'unfocusNodeAdjacency',
seriesId: seriesModel.id
});
});
}
});
this._firstRender = false;
},
dispose: function () {
this._controller && this._controller.dispose();
this._controllerHost = {};
},
graph.eachNode(function (node) {
fadeOutItem(node, nodeOpacityPath, 0.1);
});
graph.eachEdge(function (edge) {
fadeOutItem(edge, lineOpacityPath, 0.1);
});
if (node) {
fadeInItem(node, nodeOpacityPath);
each$1(node.edges, function (adjacentEdge) {
if (adjacentEdge.dataIndex < 0) {
return;
}
fadeInItem(adjacentEdge, lineOpacityPath);
fadeInItem(adjacentEdge.node1, nodeOpacityPath);
fadeInItem(adjacentEdge.node2, nodeOpacityPath);
});
}
if (edge) {
fadeInItem(edge, lineOpacityPath);
fadeInItem(edge.node1, nodeOpacityPath);
fadeInItem(edge.node2, nodeOpacityPath);
}
},
graph.eachNode(function (node) {
fadeOutItem(node, nodeOpacityPath);
});
graph.eachEdge(function (edge) {
fadeOutItem(edge, lineOpacityPath);
});
},
controller
.off('pan')
.off('zoom')
.on('pan', function (dx, dy) {
updateViewOnPan(controllerHost, dx, dy);
api.dispatchAction({
seriesId: seriesModel.id,
type: 'graphRoam',
dx: dx,
dy: dy
});
})
.on('zoom', function (zoom, mouseX, mouseY) {
updateViewOnZoom(controllerHost, zoom, mouseX, mouseY);
api.dispatchAction({
seriesId: seriesModel.id,
type: 'graphRoam',
zoom: zoom,
originX: mouseX,
originY: mouseY
});
this._updateNodeAndLinkScale();
adjustEdge(seriesModel.getGraph(),
this._getNodeGlobalScale(seriesModel));
this._lineDraw.updateLayout();
}, this);
},
_updateNodeAndLinkScale: function () {
var seriesModel = this._model;
var data = seriesModel.getData();
this._symbolDraw.updateLayout();
this._lineDraw.updateLayout();
},
var actionInfo = {
type: 'graphRoam',
event: 'graphRoam',
update: 'none'
};
/**
* @payload
* @property {string} name Series name
* @property {number} [dx]
* @property {number} [dy]
* @property {number} [zoom]
* @property {number} [originX]
* @property {number} [originY]
*/
registerAction(actionInfo, function (payload, ecModel) {
ecModel.eachComponent({mainType: 'series', query: payload}, function
(seriesModel) {
var coordSys = seriesModel.coordinateSystem;
seriesModel.setCenter
&& seriesModel.setCenter(res.center);
seriesModel.setZoom
&& seriesModel.setZoom(res.zoom);
});
});
/**
* @payload
* @property {number} [seriesIndex]
* @property {string} [seriesId]
* @property {string} [seriesName]
* @property {number} [dataIndex]
*/
registerAction({
type: 'focusNodeAdjacency',
event: 'focusNodeAdjacency',
update: 'series.graph:focusNodeAdjacency'
}, function () {});
/**
* @payload
* @property {number} [seriesIndex]
* @property {string} [seriesId]
* @property {string} [seriesName]
*/
registerAction({
type: 'unfocusNodeAdjacency',
event: 'unfocusNodeAdjacency',
update: 'series.graph:unfocusNodeAdjacency'
}, function () {});
data.filterSelf(function (idx) {
var model = data.getItemModel(idx);
var category = model.getShallow('category');
if (category != null) {
if (typeof category === 'number') {
category = categoryNames[category];
}
// If in any legend component the status is not selected.
for (var i = 0; i < legendModels.length; i++) {
if (!legendModels[i].isSelected(category)) {
return false;
}
}
}
return true;
});
}, this);
};
categoriesData.each(function (idx) {
var name = categoriesData.getName(idx);
// Add prefix to avoid conflict with Object.prototype.
categoryNameIdxMap['ec-' + name] = idx;
function normalize$1(a) {
if (!(a instanceof Array)) {
a = [a, a];
}
return a;
}
edgeData.each(function (idx) {
var itemModel = edgeData.getItemModel(idx);
var edge = graph.getEdgeByIndex(idx);
var symbolType = normalize$1(itemModel.getShallow('symbol', true));
var symbolSize = normalize$1(itemModel.getShallow('symbolSize', true));
// Edge visual must after node visual
var color = itemModel.get(colorQuery);
var opacity = itemModel.get(opacityQuery);
switch (color) {
case 'source':
color = edge.node1.getVisual('color');
break;
case 'target':
color = edge.node2.getVisual('color');
break;
}
edge.setVisual('color', color);
edge.setVisual('opacity', opacity);
});
});
};
function simpleLayout$1(seriesModel) {
var coordSys = seriesModel.coordinateSystem;
if (coordSys && coordSys.type !== 'view') {
return;
}
var graph = seriesModel.getGraph();
graph.eachNode(function (node) {
var model = node.getModel();
node.setLayout([+model.get('x'), +model.get('y')]);
});
simpleLayoutEdge(graph);
}
function simpleLayoutEdge(graph) {
graph.eachEdge(function (edge) {
var curveness = edge.getModel().get('lineStyle.curveness') || 0;
var p1 = clone$1(edge.node1.getLayout());
var p2 = clone$1(edge.node2.getLayout());
var points = [p1, p2];
if (+curveness) {
points.push([
(p1[0] + p2[0]) / 2 - (p1[1] - p2[1]) * curveness,
(p1[1] + p2[1]) / 2 - (p2[0] - p1[0]) * curveness
]);
}
edge.setLayout(points);
});
}
simpleLayoutEdge(data.graph);
}
else if (!layout || layout === 'none') {
simpleLayout$1(seriesModel);
}
});
};
function circularLayout$1(seriesModel) {
var coordSys = seriesModel.coordinateSystem;
if (coordSys && coordSys.type !== 'view') {
return;
}
var angle = 0;
var sum = nodeData.getSum('value');
var unitAngle = Math.PI * 2 / (sum || nodeData.count());
var cx = rect.width / 2 + rect.x;
var cy = rect.height / 2 + rect.y;
graph.eachNode(function (node) {
var value = node.getValue('value');
node.setLayout([
r * Math.cos(angle) + cx,
r * Math.sin(angle) + cy
]);
nodeData.setLayout({
cx: cx,
cy: cy
});
graph.eachEdge(function (edge) {
var curveness = edge.getModel().get('lineStyle.curveness') || 0;
var p1 = clone$1(edge.node1.getLayout());
var p2 = clone$1(edge.node2.getLayout());
var cp1;
var x12 = (p1[0] + p2[0]) / 2;
var y12 = (p1[1] + p2[1]) / 2;
if (+curveness) {
curveness *= 3;
cp1 = [
cx * curveness + x12 * (1 - curveness),
cy * curveness + y12 * (1 - curveness)
];
}
edge.setLayout([p1, p2, cp1]);
});
}
// function adjacentNode(n, e) {
// return e.n1 === n ? e.n2 : e.n1;
// }
return {
warmUp: function () {
friction = 0.5;
},
if (isNaN(w)) {
w = 0;
}
normalize(v12, v12);
// Repulsive
// PENDING
for (var i = 0; i < nLen; i++) {
var n1 = nodes[i];
for (var j = i + 1; j < nLen; j++) {
var n2 = nodes[j];
sub(v12, n2.p, n1.p);
var d = len(v12);
if (d === 0) {
// Random repulse
set(v12, Math.random() - 0.5, Math.random() - 0.5);
d = 1;
}
var repFact = (n1.rep + n2.rep) / d / d;
!n1.fixed && scaleAndAdd$2(n1.pp, n1.pp, v12, repFact);
!n2.fixed && scaleAndAdd$2(n2.pp, n2.pp, v12, -repFact);
}
}
var v = [];
for (var i = 0; i < nLen; i++) {
var n = nodes[i];
if (!n.fixed) {
sub(v, n.p, n.pp);
scaleAndAdd$2(n.p, n.p, v, friction);
copy(n.pp, n.p);
}
}
cb && cb(stopped);
});
};
graphSeries.forceLayout = forceInstance;
graphSeries.preservedPoints = preservedPoints;
// If width or height is 0
if (max[0] - min[0] === 0) {
max[0] += 1;
min[0] -= 1;
}
if (max[1] - min[1] === 0) {
max[1] += 1;
min[1] -= 1;
}
var aspect = (max[0] - min[0]) / (max[1] - min[1]);
// FIXME If get view rect after data processed?
var viewRect = getViewRect$1(seriesModel, api, aspect);
// Position may be NaN, use view rect instead
if (isNaN(aspect)) {
min = [viewRect.x, viewRect.y];
max = [viewRect.x + viewRect.width, viewRect.y + viewRect.height];
}
viewCoordSys.setBoundingRect(
min[0], min[1], bbWidth, bbHeight
);
viewCoordSys.setViewRect(
viewRect.x, viewRect.y, viewWidth, viewHeight
);
viewList.push(viewCoordSys);
}
});
return viewList;
};
registerProcessor(categoryFilter);
registerLayout(simpleLayout);
registerLayout(circularLayout);
registerLayout(forceLayout);
type: 'series.gauge',
defaultOption: {
zlevel: 0,
z: 2,
// 默认全局居中
center: ['50%', '50%'],
legendHoverLink: true,
radius: '75%',
startAngle: 225,
endAngle: -45,
clockwise: true,
// 最小值
min: 0,
// 最大值
max: 100,
// 分割段数,默认为 10
splitNumber: 10,
// 坐标轴线
axisLine: {
// 默认显示,属性 show 控制显示与否
show: true,
lineStyle: { // 属性 lineStyle 控制线条样式
color: [[0.2, '#91c7ae'], [0.8, '#63869e'], [1, '#c23531']],
width: 30
}
},
// 分隔线
splitLine: {
// 默认显示,属性 show 控制显示与否
show: true,
// 属性 length 控制线长
length: 30,
// 属性 lineStyle(详见 lineStyle)控制线条样式
lineStyle: {
color: '#eee',
width: 2,
type: 'solid'
}
},
// 坐标轴小标记
axisTick: {
// 属性 show 控制显示与否,默认不显示
show: true,
// 每份 split 细分多少段
splitNumber: 5,
// 属性 length 控制线长
length: 8,
// 属性 lineStyle 控制线条样式
lineStyle: {
color: '#eee',
width: 1,
type: 'solid'
}
},
axisLabel: {
show: true,
distance: 5,
// formatter: null,
color: 'auto'
},
pointer: {
show: true,
length: '80%',
width: 8
},
itemStyle: {
color: 'auto'
},
title: {
show: true,
// x, y,单位 px
offsetCenter: [0, '-40%'],
// 其余属性默认使用全局文本样式,详见 TEXTSTYLE
color: '#333',
fontSize: 15
},
detail: {
show: true,
backgroundColor: 'rgba(0,0,0,0)',
borderWidth: 0,
borderColor: '#ccc',
width: 100,
height: null, // self-adaption
padding: [5, 10],
// x, y,单位 px
offsetCenter: [0, '40%'],
// formatter: null,
// 其余属性默认使用全局文本样式,详见 TEXTSTYLE
color: 'auto',
fontSize: 30
}
}
});
type: 'echartsGaugePointer',
shape: {
angle: 0,
width: 10,
r: 10,
x: 0,
y: 0
},
var r = shape.r;
var width = shape.width;
var angle = shape.angle;
var x = shape.x - mathCos(angle) * width * (width >= r / 3 ? 1 : 2);
var y = shape.y - mathSin(angle) * width * (width >= r / 3 ? 1 : 2);
return {
cx: cx,
cy: cy,
r: r
};
}
return label;
}
type: 'gauge',
this._renderMain(
seriesModel, ecModel, api, colorList, posInfo
);
},
sector.setStyle({
fill: colorList[i][1]
});
sector.setStyle(lineStyleModel.getLineStyle(
// Because we use sector to simulate arc
// so the properties for stroking are useless
['color', 'borderWidth', 'borderColor']
));
group.add(sector);
prevEndAngle = endAngle;
}
var getColor = function (percent) {
// Less than 0
if (percent <= 0) {
return colorList[0][1];
}
for (var i = 0; i < colorList.length; i++) {
if (colorList[i][0] >= percent
&& (i === 0 ? 0 : colorList[i - 1][0]) < percent
) {
return colorList[i][1];
}
}
// More than 1
return colorList[i - 1][1];
};
if (!clockwise) {
var tmp = startAngle;
startAngle = endAngle;
endAngle = tmp;
}
this._renderTicks(
seriesModel, ecModel, api, getColor, posInfo,
startAngle, endAngle, clockwise
);
this._renderPointer(
seriesModel, ecModel, api, getColor, posInfo,
startAngle, endAngle, clockwise
);
this._renderTitle(
seriesModel, ecModel, api, getColor, posInfo
);
this._renderDetail(
seriesModel, ecModel, api, getColor, posInfo
);
},
_renderTicks: function (
seriesModel, ecModel, api, getColor, posInfo,
startAngle, endAngle, clockwise
) {
var group = this.group;
var cx = posInfo.cx;
var cy = posInfo.cy;
var r = posInfo.r;
group.add(splitLine);
}
// Label
if (labelModel.get('show')) {
var label = formatLabel(
round$1(i / splitNumber * (maxVal - minVal) + minVal),
labelModel.get('formatter')
);
var distance = labelModel.get('distance');
var autoColor = getColor(i / splitNumber);
group.add(new Text({
style: setTextStyle({}, labelModel, {
text: label,
x: unitX * (r - splitLineLen - distance) + cx,
y: unitY * (r - splitLineLen - distance) + cy,
textVerticalAlign: unitY < -0.4 ? 'top' : (unitY > 0.4 ?
'bottom' : 'middle'),
textAlign: unitX < -0.4 ? 'left' : (unitX > 0.4 ? 'right' :
'center')
}, {autoColor: autoColor}),
silent: true
}));
}
// Axis tick
if (tickModel.get('show') && i !== splitNumber) {
for (var j = 0; j <= subSplitNumber; j++) {
var unitX = Math.cos(angle);
var unitY = Math.sin(angle);
var tickLine = new Line({
shape: {
x1: unitX * r + cx,
y1: unitY * r + cy,
x2: unitX * (r - tickLen) + cx,
y2: unitY * (r - tickLen) + cy
},
silent: true,
style: tickLineStyle
});
group.add(tickLine);
angle += subStep;
}
angle -= subStep;
}
else {
angle += step;
}
}
},
_renderPointer: function (
seriesModel, ecModel, api, getColor, posInfo,
startAngle, endAngle, clockwise
) {
if (!seriesModel.get('pointer.show')) {
// Remove old element
oldData && oldData.eachItemGraphicEl(function (el) {
group.remove(el);
});
return;
}
initProps(pointer, {
shape: {
angle: linearMap(data.get(valueDim, idx), valueExtent,
angleExtent, true)
}
}, seriesModel);
group.add(pointer);
data.setItemGraphicEl(idx, pointer);
})
.update(function (newIdx, oldIdx) {
var pointer = oldData.getItemGraphicEl(oldIdx);
updateProps(pointer, {
shape: {
angle: linearMap(data.get(valueDim, newIdx), valueExtent,
angleExtent, true)
}
}, seriesModel);
group.add(pointer);
data.setItemGraphicEl(newIdx, pointer);
})
.remove(function (idx) {
var pointer = oldData.getItemGraphicEl(idx);
group.remove(pointer);
})
.execute();
pointer.setShape({
x: posInfo.cx,
y: posInfo.cy,
width: parsePercent$1(
pointerModel.get('width'), posInfo.r
),
r: parsePercent$1(pointerModel.get('length'), posInfo.r)
});
pointer.useStyle(itemModel.getModel('itemStyle').getItemStyle());
setHoverStyle(
pointer, itemModel.getModel('emphasis.itemStyle').getItemStyle()
);
});
this._data = data;
},
_renderTitle: function (
seriesModel, ecModel, api, getColor, posInfo
) {
var data = seriesModel.getData();
var valueDim = data.mapDimension('value');
var titleModel = seriesModel.getModel('title');
if (titleModel.get('show')) {
var offsetCenter = titleModel.get('offsetCenter');
var x = posInfo.cx + parsePercent$1(offsetCenter[0], posInfo.r);
var y = posInfo.cy + parsePercent$1(offsetCenter[1], posInfo.r);
this.group.add(new Text({
silent: true,
style: setTextStyle({}, titleModel, {
x: x,
y: y,
// FIXME First data name ?
text: data.getName(0),
textAlign: 'center',
textVerticalAlign: 'middle'
}, {autoColor: autoColor, forceRich: true})
}));
}
},
_renderDetail: function (
seriesModel, ecModel, api, getColor, posInfo
) {
var detailModel = seriesModel.getModel('detail');
var minVal = +seriesModel.get('min');
var maxVal = +seriesModel.get('max');
if (detailModel.get('show')) {
var offsetCenter = detailModel.get('offsetCenter');
var x = posInfo.cx + parsePercent$1(offsetCenter[0], posInfo.r);
var y = posInfo.cy + parsePercent$1(offsetCenter[1], posInfo.r);
var width = parsePercent$1(detailModel.get('width'), posInfo.r);
var height = parsePercent$1(detailModel.get('height'), posInfo.r);
var data = seriesModel.getData();
var value = data.get(data.mapDimension('value'), 0);
var autoColor = getColor(
linearMap(value, [minVal, maxVal], [0, 1], true)
);
this.group.add(new Text({
silent: true,
style: setTextStyle({}, detailModel, {
x: x,
y: y,
text: formatLabel(
// FIXME First data name ?
value, detailModel.get('formatter')
),
textWidth: isNaN(width) ? null : width,
textHeight: isNaN(height) ? null : height,
textAlign: 'center',
textVerticalAlign: 'middle'
}, {autoColor: autoColor, forceRich: true})
}));
}
}
});
type: 'series.funnel',
// Overwrite
getDataParams: function (dataIndex) {
var data = this.getData();
var params = FunnelSeries.superCall(this, 'getDataParams', dataIndex);
var valueDim = data.mapDimension('value');
var sum = data.getSum(valueDim);
// Percent is 0 if sum is 0
params.percent = !sum ? 0 : +(data.get(valueDim, dataIndex) / sum *
100).toFixed(2);
params.$vars.push('percent');
return params;
},
defaultOption: {
zlevel: 0, // 一级层叠
z: 2, // 二级层叠
legendHoverLink: true,
left: 80,
top: 60,
right: 80,
bottom: 60,
// width: {totalWidth} - left - right,
// height: {totalHeight} - top - bottom,
// 默认取数据最小最大值
// min: 0,
// max: 100,
minSize: '0%',
maxSize: '100%',
sort: 'descending', // 'ascending', 'descending'
gap: 0,
funnelAlign: 'center',
label: {
show: true,
position: 'outer'
// formatter: 标签文本格式器,同 Tooltip.formatter,不支持异步回调
},
labelLine: {
show: true,
length: 20,
lineStyle: {
// color: 各异,
width: 1,
type: 'solid'
}
},
itemStyle: {
// color: 各异,
borderColor: '#fff',
borderWidth: 1
},
emphasis: {
label: {
show: true
}
}
}
});
/**
* Piece of pie including Sector, Label, LabelLine
* @constructor
* @extends {module:zrender/graphic/Group}
*/
function FunnelPiece(data, idx) {
Group.call(this);
// Reset style
polygon.useStyle({});
if (firstCreate) {
polygon.setShape({
points: layout.points
});
polygon.setStyle({ opacity : 0 });
initProps(polygon, {
style: {
opacity: opacity
}
}, seriesModel, idx);
}
else {
updateProps(polygon, {
style: {
opacity: opacity
},
shape: {
points: layout.points
}
}, seriesModel, idx);
}
polygon.setStyle(
defaults(
{
lineJoin: 'round',
fill: visualColor
},
itemStyleModel.getItemStyle(['opacity'])
)
);
polygon.hoverStyle = itemStyleModel.getModel('emphasis').getItemStyle();
this._updateLabel(data, idx);
setHoverStyle(this);
};
updateProps(labelLine, {
shape: {
points: labelLayout.linePoints || labelLayout.linePoints
}
}, seriesModel, idx);
updateProps(labelText, {
style: {
x: labelLayout.x,
y: labelLayout.y
}
}, seriesModel, idx);
labelText.attr({
rotation: labelLayout.rotation,
origin: [labelLayout.x, labelLayout.y],
z2: 10
});
labelLine.hoverStyle =
labelLineHoverModel.getModel('lineStyle').getLineStyle();
};
inherits(FunnelPiece, Group);
type: 'funnel',
data.diff(oldData)
.add(function (idx) {
var funnelPiece = new FunnelPiece(data, idx);
data.setItemGraphicEl(idx, funnelPiece);
group.add(funnelPiece);
})
.update(function (newIdx, oldIdx) {
var piePiece = oldData.getItemGraphicEl(oldIdx);
piePiece.updateData(data, newIdx);
group.add(piePiece);
data.setItemGraphicEl(newIdx, piePiece);
})
.remove(function (idx) {
var piePiece = oldData.getItemGraphicEl(idx);
group.remove(piePiece);
})
.execute();
this._data = data;
},
remove: function () {
this.group.removeAll();
this._data = null;
},
dispose: function () {}
});
function labelLayout$1(data) {
data.each(function (idx) {
var itemModel = data.getItemModel(idx);
var labelModel = itemModel.getModel('label');
var labelPosition = labelModel.get('position');
var textAlign;
var textX;
var textY;
var linePoints;
if (isLabelInside) {
textX = (points[0][0] + points[1][0] + points[2][0] + points[3][0]) /
4;
textY = (points[0][1] + points[1][1] + points[2][1] + points[3][1]) /
4;
textAlign = 'center';
linePoints = [
[textX, textY], [textX, textY]
];
}
else {
var x1;
var y1;
var x2;
var labelLineLen = labelLineModel.get('length');
if (labelPosition === 'left') {
// Left side
x1 = (points[3][0] + points[0][0]) / 2;
y1 = (points[3][1] + points[0][1]) / 2;
x2 = x1 - labelLineLen;
textX = x2 - 5;
textAlign = 'right';
}
else {
// Right side
x1 = (points[1][0] + points[2][0]) / 2;
y1 = (points[1][1] + points[2][1]) / 2;
x2 = x1 + labelLineLen;
textX = x2 + 5;
textAlign = 'left';
}
var y2 = y1;
layout.label = {
linePoints: linePoints,
x: textX,
y: textY,
verticalAlign: 'middle',
textAlign: textAlign,
inside: isLabelInside
};
});
}
var funnelLayout = function (ecModel, api, payload) {
ecModel.eachSeriesByType('funnel', function (seriesModel) {
var data = seriesModel.getData();
var valueDim = data.mapDimension('value');
var sort = seriesModel.get('sort');
var viewRect = getViewRect$2(seriesModel, api);
var indices = getSortedIndices(data, sort);
var sizeExtent = [
parsePercent$1(seriesModel.get('minSize'), viewRect.width),
parsePercent$1(seriesModel.get('maxSize'), viewRect.width)
];
var dataExtent = data.getDataExtent(valueDim);
var min = seriesModel.get('min');
var max = seriesModel.get('max');
if (min == null) {
min = Math.min(dataExtent[0], 0);
}
if (max == null) {
max = dataExtent[1];
}
var y = viewRect.y;
y += height + gap;
data.setItemLayout(idx, {
points: start.concat(end.slice().reverse())
});
}
labelLayout$1(data);
});
};
registerVisual(dataColor('funnel'));
registerLayout(funnelLayout);
registerProcessor(dataFilter('funnel'));
/**
* Create a parallel coordinate if not exists.
* @inner
*/
function createParallelIfNeeded(option) {
if (option.parallel) {
return;
}
if (hasParallelSeries) {
option.parallel = [{}];
}
}
/**
* Merge aixs definition from parallel option (if exists) to axis option.
* @inner
*/
function mergeAxisOptionFromParallel(option) {
var axes = normalizeToArray(option.parallelAxis);
/**
* @constructor module:echarts/coord/parallel/ParallelAxis
* @extends {module:echarts/coord/Axis}
* @param {string} dim
* @param {*} scale
* @param {Array.<number>} coordExtent
* @param {string} axisType
*/
var ParallelAxis = function (dim, scale, coordExtent, axisType, axisIndex) {
/**
* Axis type
* - 'category'
* - 'value'
* - 'time'
* - 'log'
* @type {string}
*/
this.type = axisType || 'value';
/**
* @type {number}
* @readOnly
*/
this.axisIndex = axisIndex;
};
ParallelAxis.prototype = {
constructor: ParallelAxis,
/**
* Axis model
* @param {module:echarts/coord/parallel/AxisModel}
*/
model: null,
/**
* @override
*/
isHorizontal: function () {
return this.coordinateSystem.getModel().get('layout') !== 'horizontal';
}
};
inherits(ParallelAxis, Axis);
/**
* Calculate slider move result.
* Usage:
* (1) If both handle0 and handle1 are needed to be moved, set minSpan the same as
* maxSpan and the same as `Math.abs(handleEnd[1] - handleEnds[0])`.
* (2) If handle0 is forbidden to cross handle1, set minSpan as `0`.
*
* @param {number} delta Move length.
* @param {Array.<number>} handleEnds handleEnds[0] can be bigger then
handleEnds[1].
* handleEnds will be modified in this method.
* @param {Array.<number>} extent handleEnds is restricted by extent.
* extent[0] should less or equals than extent[1].
* @param {number|string} handleIndex Can be 'all', means that both move the two
handleEnds,
* where the input minSpan and maxSpan will not work.
* @param {number} [minSpan] The range of dataZoom can not be smaller than that.
* If not set, handle0 and cross handle1. If set as a non-negative
* number (including `0`), handles will push each other when reaching
* the minSpan.
* @param {number} [maxSpan] The range of dataZoom can not be larger than that.
* @return {Array.<number>} The input handleEnds.
*/
var sliderMove = function (delta, handleEnds, extent, handleIndex, minSpan,
maxSpan) {
// Normalize firstly.
handleEnds[0] = restrict$1(handleEnds[0], extent);
handleEnds[1] = restrict$1(handleEnds[1], extent);
delta = delta || 0;
handleEnds[handleIndex] += delta;
// Restrict in extent.
var extentMinSpan = minSpan || 0;
var realExtent = extent.slice();
originalDistSign.sign < 0 ? (realExtent[0] += extentMinSpan) : (realExtent[1]
-= extentMinSpan);
handleEnds[handleIndex] = restrict$1(handleEnds[handleIndex], realExtent);
// Expand span.
var currDistSign = getSpanSign(handleEnds, handleIndex);
if (minSpan != null && (
currDistSign.sign !== originalDistSign.sign || currDistSign.span < minSpan
)) {
// If minSpan exists, 'cross' is forbinden.
handleEnds[1 - handleIndex] = handleEnds[handleIndex] +
originalDistSign.sign * minSpan;
}
// Shrink span.
var currDistSign = getSpanSign(handleEnds, handleIndex);
if (maxSpan != null && currDistSign.span > maxSpan) {
handleEnds[1 - handleIndex] = handleEnds[handleIndex] + currDistSign.sign *
maxSpan;
}
return handleEnds;
};
/**
* Parallel Coordinates
* <https://en.wikipedia.org/wiki/Parallel_coordinates>
*/
/**
* key: dimension
* value: {position: [], rotation, }
* @type {Object.<string, Object>}
* @private
*/
this._axesLayout = {};
/**
* Always follow axis order.
* @type {Array.<string>}
* @readOnly
*/
this.dimensions = parallelModel.dimensions;
/**
* @type {module:zrender/core/BoundingRect}
*/
this._rect;
/**
* @type {module:echarts/coord/parallel/ParallelModel}
*/
this._model = parallelModel;
Parallel.prototype = {
type: 'parallel',
constructor: Parallel,
/**
* Initialize cartesian coordinate systems
* @private
*/
_init: function (parallelModel, ecModel, api) {
// Injection
axisModel.axis = axis;
axis.model = axisModel;
axis.coordinateSystem = axisModel.coordinateSystem = this;
}, this);
},
/**
* Update axis scale after data processed
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
*/
update: function (ecModel, api) {
this._updateAxesFromSeries(this._model, ecModel);
},
/**
* @override
*/
containPoint: function (point) {
var layoutInfo = this._makeLayoutInfo();
var axisBase = layoutInfo.axisBase;
var layoutBase = layoutInfo.layoutBase;
var pixelDimIndex = layoutInfo.pixelDimIndex;
var pAxis = point[1 - pixelDimIndex];
var pLayout = point[pixelDimIndex];
getModel: function () {
return this._model;
},
/**
* Update properties from series
* @private
*/
_updateAxesFromSeries: function (parallelModel, ecModel) {
ecModel.eachSeries(function (seriesModel) {
if (!parallelModel.contains(seriesModel, ecModel)) {
return;
}
/**
* Resize the parallel coordinate system.
* @param {module:echarts/coord/parallel/ParallelModel} parallelModel
* @param {module:echarts/ExtensionAPI} api
*/
resize: function (parallelModel, api) {
this._rect = getLayoutRect(
parallelModel.getBoxLayoutParams(),
{
width: api.getWidth(),
height: api.getHeight()
}
);
this._layoutAxes();
},
/**
* @return {module:zrender/core/BoundingRect}
*/
getRect: function () {
return this._rect;
},
/**
* @private
*/
_makeLayoutInfo: function () {
var parallelModel = this._model;
var rect = this._rect;
var xy = ['x', 'y'];
var wh = ['width', 'height'];
var layout = parallelModel.get('layout');
var pixelDimIndex = layout === 'horizontal' ? 0 : 1;
var layoutLength = rect[wh[pixelDimIndex]];
var layoutExtent = [0, layoutLength];
var axisCount = this.dimensions.length;
// Find the first and last indices > ewin[0] and < ewin[1].
var winInnerIndices = [
mathFloor$2(round$2(axisExpandWindow[0] / axisExpandWidth, 1)) + 1,
mathCeil$2(round$2(axisExpandWindow[1] / axisExpandWidth, 1)) - 1
];
// Pos in ec coordinates.
var axisExpandWindow0Pos = axisCollapseWidth / axisExpandWidth *
axisExpandWindow[0];
return {
layout: layout,
pixelDimIndex: pixelDimIndex,
layoutBase: rect[xy[pixelDimIndex]],
layoutLength: layoutLength,
axisBase: rect[xy[1 - pixelDimIndex]],
axisLength: rect[wh[1 - pixelDimIndex]],
axisExpandable: axisExpandable,
axisExpandWidth: axisExpandWidth,
axisCollapseWidth: axisCollapseWidth,
axisExpandWindow: axisExpandWindow,
axisCount: axisCount,
winInnerIndices: winInnerIndices,
axisExpandWindow0Pos: axisExpandWindow0Pos
};
},
/**
* @private
*/
_layoutAxes: function () {
var rect = this._rect;
var axes = this._axesMap;
var dimensions = this.dimensions;
var layoutInfo = this._makeLayoutInfo();
var layout = layoutInfo.layout;
axes.each(function (axis) {
var axisExtent = [0, layoutInfo.axisLength];
var idx = axis.inverse ? 1 : 0;
axis.setExtent(axisExtent[idx], axisExtent[1 - idx]);
});
var positionTable = {
horizontal: {
x: posInfo.position,
y: layoutInfo.axisLength
},
vertical: {
x: 0,
y: posInfo.position
}
};
var rotationTable = {
horizontal: PI$3 / 2,
vertical: 0
};
var position = [
positionTable[layout].x + rect.x,
positionTable[layout].y + rect.y
];
// TODO
// tick 等排布信息。
// TODO
// 根据 axis order 更新 dimensions 顺序。
this._axesLayout[dim] = {
position: position,
rotation: rotation,
transform: transform,
axisNameAvailableWidth: posInfo.axisNameAvailableWidth,
axisLabelShow: posInfo.axisLabelShow,
nameTruncateMaxWidth: posInfo.nameTruncateMaxWidth,
tickDirection: 1,
labelDirection: 1,
labelInterval: axes.get(dim).getLabelInterval()
};
}, this);
},
/**
* Get axis by dim.
* @param {string} dim
* @return {module:echarts/coord/parallel/ParallelAxis} [description]
*/
getAxis: function (dim) {
return this._axesMap.get(dim);
},
/**
* Convert a dim value of a single item of series data to Point.
* @param {*} value
* @param {string} dim
* @return {Array}
*/
dataToPoint: function (value, dim) {
return this.axisCoordToPoint(
this._axesMap.get(dim).dataToCoord(value),
dim
);
},
/**
* Travel data for one time, get activeState of each data item.
* @param {module:echarts/data/List} data
* @param {Functio} cb param: {string} activeState 'active' or 'inactive' or
'normal'
* {number} dataIndex
* @param {Object} context
*/
eachActiveState: function (data, callback, context) {
var dimensions = this.dimensions;
var dataDimensions = map(dimensions, function (axisDim) {
return data.mapDimension(axisDim);
});
var axesMap = this._axesMap;
var hasActiveSet = this.hasAxisBrushed();
if (!hasActiveSet) {
activeState = 'normal';
}
else {
activeState = 'active';
for (var j = 0, lenj = dimensions.length; j < lenj; j++) {
var dimName = dimensions[j];
var state =
axesMap.get(dimName).model.getActiveState(values[j], j);
/**
* Whether has any activeSet.
* @return {boolean}
*/
hasAxisBrushed: function () {
var dimensions = this.dimensions;
var axesMap = this._axesMap;
var hasActiveSet = false;
return hasActiveSet;
},
/**
* Convert coords of each axis to Point.
* Return point. For example: [10, 20]
* @param {Array.<number>} coords
* @param {string} dim
* @return {Array.<number>}
*/
axisCoordToPoint: function (coord, dim) {
var axisLayout = this._axesLayout[dim];
return applyTransform$1([coord, 0], axisLayout.transform);
},
/**
* Get axis layout.
*/
getAxisLayout: function (dim) {
return clone(this._axesLayout[dim]);
},
/**
* @param {Array.<number>} point
* @return {Object} {axisExpandWindow, delta, behavior: 'jump' | 'slide' |
'none'}.
*/
getSlidedAxisExpandWindow: function (point) {
var layoutInfo = this._makeLayoutInfo();
var pixelDimIndex = layoutInfo.pixelDimIndex;
var axisExpandWindow = layoutInfo.axisExpandWindow.slice();
var winSize = axisExpandWindow[1] - axisExpandWindow[0];
var extent = [0, layoutInfo.axisExpandWidth * (layoutInfo.axisCount - 1)];
if (axisCollapseWidth) {
if (useJump && axisCollapseWidth && pointCoord < winSize *
triggerArea[0]) {
behavior = 'jump';
delta = pointCoord - winSize * triggerArea[2];
}
else if (useJump && axisCollapseWidth && pointCoord > winSize * (1 -
triggerArea[0])) {
behavior = 'jump';
delta = pointCoord - winSize * (1 - triggerArea[2]);
}
else {
(delta = pointCoord - winSize * triggerArea[1]) >= 0
&& (delta = pointCoord - winSize * (1 - triggerArea[1])) <= 0
&& (delta = 0);
}
delta *= layoutInfo.axisExpandWidth / axisCollapseWidth;
delta
? sliderMove(delta, axisExpandWindow, extent, 'all')
// Avoid nonsense triger on mousemove.
: (behavior = 'none');
}
// When screen is too narrow, make it visible and slidable, although it is
hard to interact.
else {
var winSize = axisExpandWindow[1] - axisExpandWindow[0];
var pos = extent[1] * pointCoord / winSize;
axisExpandWindow = [mathMax$5(0, pos - winSize / 2)];
axisExpandWindow[1] = mathMin$5(extent[1], axisExpandWindow[0] +
winSize);
axisExpandWindow[0] = axisExpandWindow[1] - winSize;
}
return {
axisExpandWindow: axisExpandWindow,
behavior: behavior
};
}
};
var position;
var axisNameAvailableWidth = axisCollapseWidth;
var axisLabelShow = false;
var nameTruncateMaxWidth;
return {
position: position,
axisNameAvailableWidth: axisNameAvailableWidth,
axisLabelShow: axisLabelShow,
nameTruncateMaxWidth: nameTruncateMaxWidth
};
}
/**
* Parallel coordinate system creater.
*/
parallelModel.coordinateSystem = coordSys;
coordSys.model = parallelModel;
coordSysList.push(coordSys);
});
// Inject the coordinateSystems into seriesModel
ecModel.eachSeries(function (seriesModel) {
if (seriesModel.get('coordinateSystem') === 'parallel') {
var parallelModel = ecModel.queryComponents({
mainType: 'parallel',
index: seriesModel.get('parallelIndex'),
id: seriesModel.get('parallelId')
})[0];
seriesModel.coordinateSystem = parallelModel.coordinateSystem;
}
});
return coordSysList;
}
type: 'baseParallelAxis',
/**
* @type {module:echarts/coord/parallel/Axis}
*/
axis: null,
/**
* @type {Array.<Array.<number>}
* @readOnly
*/
activeIntervals: [],
/**
* @return {Object}
*/
getAreaSelectStyle: function () {
return makeStyleMapper(
[
['fill', 'color'],
['lineWidth', 'borderWidth'],
['stroke', 'borderColor'],
['width', 'width'],
['opacity', 'opacity']
]
)(this.getModel('areaSelectStyle'));
},
/**
* The code of this feature is put on AxisModel but not ParallelAxis,
* because axisModel can be alive after echarts updating but instance of
* ParallelAxis having been disposed. this._activeInterval should be kept
* when action dispatched (i.e. legend click).
*
* @param {Array.<Array<number>>} intervals interval.length === 0
* means set all active.
* @public
*/
setActiveIntervals: function (intervals) {
var activeIntervals = this.activeIntervals = clone(intervals);
// Normalize
if (activeIntervals) {
for (var i = activeIntervals.length - 1; i >= 0; i--) {
asc(activeIntervals[i]);
}
}
},
/**
* @param {number|string} [value] When attempting to detect 'no activeIntervals
set',
* value can not be input.
* @return {string} 'normal': no activeIntervals set,
* 'active',
* 'inactive'.
* @public
*/
getActiveState: function (value) {
var activeIntervals = this.activeIntervals;
if (!activeIntervals.length) {
return 'normal';
}
if (value == null) {
return 'inactive';
}
});
var defaultOption$1 = {
type: 'value',
/**
* @type {Array.<number>}
*/
dim: null, // 0, 1, 2, ...
// parallelIndex: null,
areaSelectStyle: {
width: 20,
borderWidth: 1,
borderColor: 'rgba(160,197,232)',
color: 'rgba(160,197,232)',
opacity: 0.3
},
merge(AxisModel$2.prototype, axisModelCommonMixin);
ComponentModel.extend({
type: 'parallel',
dependencies: ['parallelAxis'],
/**
* @type {module:echarts/coord/parallel/Parallel}
*/
coordinateSystem: null,
/**
* Each item like: 'dim0', 'dim1', 'dim2', ...
* @type {Array.<string>}
* @readOnly
*/
dimensions: null,
/**
* Coresponding to dimensions.
* @type {Array.<number>}
* @readOnly
*/
parallelAxisIndex: null,
layoutMode: 'box',
defaultOption: {
zlevel: 0,
z: 0,
left: 80,
top: 60,
right: 80,
bottom: 60,
// width: {totalWidth} - left - right,
// height: {totalHeight} - top - bottom,
// FIXME
// naming?
axisExpandable: false,
axisExpandCenter: null,
axisExpandCount: 0,
axisExpandWidth: 50, // FIXME '10%' ?
axisExpandRate: 17,
axisExpandDebounce: 50,
// [out, in, jumpTarget]. In percentage. If use [null, 0.05], null means
full.
// Do not doc to user until necessary.
axisExpandSlideTriggerArea: [-0.15, 0.05, 0.4],
axisExpandTriggerOn: 'click', // 'mousemove' or 'click'
parallelAxisDefault: null
},
/**
* @override
*/
init: function () {
ComponentModel.prototype.init.apply(this, arguments);
this.mergeOption({});
},
/**
* @override
*/
mergeOption: function (newOption) {
var thisOption = this.option;
this._initDimensions();
},
/**
* Whether series or axis is in this coordinate system.
* @param {module:echarts/model/Series|module:echarts/coord/parallel/AxisModel}
model
* @param {module:echarts/model/Global} ecModel
*/
contains: function (model, ecModel) {
var parallelIndex = model.get('parallelIndex');
return parallelIndex != null
&& ecModel.getComponent('parallel', parallelIndex) === this;
},
/**
* @private
*/
_initDimensions: function () {
var dimensions = this.dimensions = [];
var parallelAxisIndex = this.parallelAxisIndex = [];
});
/**
* @payload
* @property {string} parallelAxisId
* @property {Array.<Array.<number>>} intervals
*/
var actionInfo$1 = {
type: 'axisAreaSelect',
event: 'axisAreaSelected'
// update: 'updateVisual'
};
/**
* @payload
*/
registerAction('parallelAxisExpand', function (payload, ecModel) {
ecModel.eachComponent(
{mainType: 'parallel', query: payload},
function (parallelModel) {
parallelModel.setAxisExpand(payload);
}
);
});
var DIRECTION_MAP = {
w: [0, 0],
e: [0, 1],
n: [1, 0],
s: [1, 1]
};
var CURSOR_MAP = {
w: 'ew',
e: 'ew',
n: 'ns',
s: 'ns',
ne: 'nesw',
sw: 'nesw',
nw: 'nwse',
se: 'nwse'
};
var DEFAULT_BRUSH_OPT = {
brushStyle: {
lineWidth: 2,
stroke: 'rgba(0,0,0,0.3)',
fill: 'rgba(0,0,0,0.1)'
},
transformable: true,
brushMode: 'single',
removeOnClick: false
};
var baseUID = 0;
/**
* @alias module:echarts/component/helper/BrushController
* @constructor
* @mixin {module:zrender/mixin/Eventful}
* @event module:echarts/component/helper/BrushController#brush
* params:
* areas: Array.<Array>, coord relates to container group,
* If no container specified, to global.
* opt {
* isEnd: boolean,
* removeOnClick: boolean
* }
*
* @param {module:zrender/zrender~ZRender} zr
*/
function BrushController(zr) {
if (__DEV__) {
assert$1(zr);
}
Eventful.call(this);
/**
* @type {module:zrender/zrender~ZRender}
* @private
*/
this._zr = zr;
/**
* @type {module:zrender/container/Group}
* @readOnly
*/
this.group = new Group();
/**
* Only for drawing (after enabledBrush).
* 'line', 'rect', 'polygon' or false
* If passing false/null/undefined, disable brush.
* If passing 'auto', determined by panel.defaultBrushType
* @private
* @type {string}
*/
this._brushType;
/**
* Only for drawing (after enabledBrush).
*
* @private
* @type {Object}
*/
this._brushOption;
/**
* @private
* @type {Object}
*/
this._panels;
/**
* @private
* @type {Array.<nubmer>}
*/
this._track = [];
/**
* @private
* @type {boolean}
*/
this._dragging;
/**
* @private
* @type {Array}
*/
this._covers = [];
/**
* @private
* @type {moudule:zrender/container/Group}
*/
this._creatingCover;
/**
* `true` means global panel
* @private
* @type {module:zrender/container/Group|boolean}
*/
this._creatingPanel;
/**
* @private
* @type {boolean}
*/
this._enableGlobalPan;
/**
* @private
* @type {boolean}
*/
if (__DEV__) {
this._mounted;
}
/**
* @private
* @type {string}
*/
this._uid = 'brushController_' + baseUID++;
/**
* @private
* @type {Object}
*/
this._handlers = {};
each$13(mouseHandlers, function (handler, eventName) {
this._handlers[eventName] = bind(handler, this);
}, this);
}
BrushController.prototype = {
constructor: BrushController,
/**
* If set to null/undefined/false, select disabled.
* @param {Object} brushOption
* @param {string|boolean} brushOption.brushType 'line', 'rect', 'polygon' or
false
* If passing false/null/undefined, disable brush.
* If passing 'auto', determined by
panel.defaultBrushType.
* ('auto' can not be used in global panel)
* @param {number} [brushOption.brushMode='single'] 'single' or 'multiple'
* @param {boolean} [brushOption.transformable=true]
* @param {boolean} [brushOption.removeOnClick=false]
* @param {Object} [brushOption.brushStyle]
* @param {number} [brushOption.brushStyle.width]
* @param {number} [brushOption.brushStyle.lineWidth]
* @param {string} [brushOption.brushStyle.stroke]
* @param {string} [brushOption.brushStyle.fill]
* @param {number} [brushOption.z]
*/
enableBrush: function (brushOption) {
if (__DEV__) {
assert$1(this._mounted);
}
return this;
},
/**
* @param {Array.<Object>} panelOpts If not pass, it is global brush.
* Each items: {
* panelId, // mandatory.
* clipPath, // mandatory. function.
* isTargetByCursor, // mandatory. function.
* defaultBrushType, // optional, only used when brushType is
'auto'.
* getLinearBrushOtherExtent, // optional. function.
* }
*/
setPanels: function (panelOpts) {
if (panelOpts && panelOpts.length) {
var panels = this._panels = {};
each$1(panelOpts, function (panelOpts) {
panels[panelOpts.panelId] = clone(panelOpts);
});
}
else {
this._panels = null;
}
return this;
},
/**
* @param {Object} [opt]
* @return {boolean} [opt.enableGlobalPan=false]
*/
mount: function (opt) {
opt = opt || {};
if (__DEV__) {
this._mounted = true; // should be at first.
}
this._enableGlobalPan = opt.enableGlobalPan;
thisGroup.attr({
position: opt.position || [0, 0],
rotation: opt.rotation || 0,
scale: opt.scale || [1, 1]
});
this._transform = thisGroup.getLocalTransform();
return this;
},
eachCover: function (cb, context) {
each$13(this._covers, cb, context);
},
/**
* Update covers.
* @param {Array.<Object>} brushOptionList Like:
* [
* {id: 'xx', brushType: 'line', range: [23, 44], brushStyle,
transformable},
* {id: 'yy', brushType: 'rect', range: [[23, 44], [23, 54]]},
* ...
* ]
* `brushType` is required in each cover info. (can not be 'auto')
* `id` is not mandatory.
* `brushStyle`, `transformable` is not mandatory, use DEFAULT_BRUSH_OPT
by default.
* If brushOptionList is null/undefined, all covers removed.
*/
updateCovers: function (brushOptionList) {
if (__DEV__) {
assert$1(this._mounted);
}
return this;
function remove(oldIndex) {
if (oldCovers[oldIndex] !== creatingCover) {
controller.group.remove(oldCovers[oldIndex]);
}
}
},
unmount: function () {
if (__DEV__) {
if (!this._mounted) {
return;
}
}
this.enableBrush(false);
if (__DEV__) {
this._mounted = false; // should be at last.
}
return this;
},
dispose: function () {
this.unmount();
this.off();
}
};
mixin(BrushController, Eventful);
controller._brushType = brushOption.brushType;
controller._brushOption = merge(clone(DEFAULT_BRUSH_OPT), brushOption, true);
}
function doDisableBrush(controller) {
var zr = controller._zr;
function getCoverRenderer(cover) {
return coverRenderers[cover.__brushOption.brushType];
}
function clearCovers(controller) {
var covers = controller._covers;
var originalLength = covers.length;
each$13(covers, function (cover) {
controller.group.remove(cover);
}, controller);
covers.length = 0;
return !!originalLength;
}
controller.trigger('brush', areas, {
isEnd: !!opt.isEnd,
removeOnClick: !!opt.removeOnClick
});
}
function shouldShowCover(controller) {
var track = controller._track;
if (!track.length) {
return false;
}
var p2 = track[track.length - 1];
var p1 = track[0];
var dx = p2[0] - p1[0];
var dy = p2[1] - p1[1];
var dist = mathPow$2(dx * dx + dy * dy, 0.5);
function getTrackEnds(track) {
var tail = track.length - 1;
tail < 0 && (tail = 0);
return [track[0], track[tail]];
}
cover.add(new Rect({
name: 'main',
style: makeStyle(brushOption),
silent: true,
draggable: true,
cursor: 'move',
drift: curry$2(doDrift, controller, cover, 'nswe'),
ondragend: curry$2(trigger, controller, {isEnd: true})
}));
each$13(
edgeNames,
function (name) {
cover.add(new Rect({
name: name,
style: {opacity: 0},
draggable: true,
silent: true,
invisible: true,
drift: curry$2(doDrift, controller, cover, name),
ondragend: curry$2(trigger, controller, {isEnd: true})
}));
}
);
return cover;
}
if (brushOption.transformable) {
updateRectShape(controller, cover, 'w', xa, ya, handleSize, heighta);
updateRectShape(controller, cover, 'e', x2a, ya, handleSize, heighta);
updateRectShape(controller, cover, 'n', xa, ya, widtha, handleSize);
updateRectShape(controller, cover, 's', xa, y2a, widtha, handleSize);
each$13(
['w', 'e', 'n', 's', 'se', 'sw', 'ne', 'nw'],
function (name) {
var el = cover.childOfName(name);
var globalDir = getGlobalDirection(controller, name);
el && el.attr({
silent: !transformable,
invisible: !transformable,
cursor: transformable ? CURSOR_MAP[globalDir] + '-resize' : null
});
}
);
}
function makeStyle(brushOption) {
return defaults({strokeNoScale: true}, brushOption.brushStyle);
}
function getTransform$1(controller) {
return getTransform(controller.group);
}
brushOption.range = fromRectRange(formatRectRange(
rectRange[0][0], rectRange[1][0], rectRange[0][1], rectRange[1][1]
));
updateCoverAfterCreation(controller, cover);
trigger(controller, {isEnd: false});
}
function pointsToRect(points) {
var xmin = mathMin$6(points[0][0], points[1][0]);
var ymin = mathMin$6(points[0][1], points[1][1]);
var xmax = mathMax$6(points[0][0], points[1][0]);
var ymax = mathMax$6(points[0][1], points[1][1]);
return {
x: xmin,
y: ymin,
width: xmax - xmin,
height: ymax - ymin
};
}
var zr = controller._zr;
var covers = controller._covers;
var currPanel = getPanelByPoint(controller, e, localCursorPoint);
function preventDefault(e) {
var rawE = e.event;
rawE.preventDefault && rawE.preventDefault();
}
function mainShapeContain(cover, x, y) {
return cover.childOfName('main').contain(x, y);
}
controller._track.push(localCursorPoint.slice());
if (shouldShowCover(controller) || creatingCover) {
if (creatingCover) {
var coverRenderer =
coverRenderers[determineBrushType(controller._brushType, panel)];
var coverBrushOption = creatingCover.__brushOption;
coverBrushOption.range = coverRenderer.getCreatingRange(
clipByPanel(controller, creatingCover, controller._track)
);
if (isEnd) {
endCreating(controller, creatingCover);
coverRenderer.updateCommon(controller, creatingCover);
}
updateCoverShape(controller, creatingCover);
return eventParams;
}
var mouseHandlers = {
preventDefault(e);
this._creatingCover = null;
var panel = this._creatingPanel = getPanelByPoint(this, e,
localCursorPoint);
if (panel) {
this._dragging = true;
this._track = [localCursorPoint.slice()];
}
}
},
if (this._dragging) {
preventDefault(e);
// FIXME
// in tooltip, globalout should not be triggered.
// globalout: handleDragEnd
};
function handleDragEnd(e) {
if (this._dragging) {
preventDefault(e);
this._dragging = false;
this._track = [];
this._creatingCover = null;
/**
* key: brushType
* @type {Object}
*/
var coverRenderers = {
lineX: getLineRenderer(0),
lineY: getLineRenderer(1),
rect: {
createCover: function (controller, brushOption) {
return createBaseRectCover(
curry$2(
driftRect,
function (range) {
return range;
},
function (range) {
return range;
}
),
controller,
brushOption,
['w', 'e', 'n', 's', 'se', 'sw', 'ne', 'nw']
);
},
getCreatingRange: function (localTrack) {
var ends = getTrackEnds(localTrack);
return formatRectRange(ends[1][0], ends[1][1], ends[0][0], ends[0][1]);
},
updateCoverShape: function (controller, cover, localRange, brushOption) {
updateBaseRect(controller, cover, localRange, brushOption);
},
updateCommon: updateCommon,
contain: mainShapeContain
},
polygon: {
createCover: function (controller, brushOption) {
var cover = new Group();
return cover;
},
getCreatingRange: function (localTrack) {
return localTrack;
},
endCreating: function (controller, cover) {
cover.remove(cover.childAt(0));
// Use graphic.Polygon close the shape.
cover.add(new Polygon({
name: 'main',
draggable: true,
drift: curry$2(driftPolygon, controller, cover),
ondragend: curry$2(trigger, controller, {isEnd: true})
}));
},
updateCoverShape: function (controller, cover, localRange, brushOption) {
cover.childAt(0).setShape({
points: clipByPanel(controller, cover, localRange)
});
},
updateCommon: updateCommon,
contain: mainShapeContain
}
};
function getLineRenderer(xyIndex) {
return {
createCover: function (controller, brushOption) {
return createBaseRectCover(
curry$2(
driftRect,
function (range) {
var rectRange = [range, [0, 100]];
xyIndex && rectRange.reverse();
return rectRange;
},
function (rectRange) {
return rectRange[xyIndex];
}
),
controller,
brushOption,
[['w', 'e'], ['n', 's']][xyIndex]
);
},
getCreatingRange: function (localTrack) {
var ends = getTrackEnds(localTrack);
var min = mathMin$6(ends[0][xyIndex], ends[1][xyIndex]);
var max = mathMax$6(ends[0][xyIndex], ends[1][xyIndex]);
function makeRectPanelClipPath(rect) {
rect = normalizeRect(rect);
return function (localPoints, transform) {
return clipPointsByRect(localPoints, rect);
};
}
type: 'parallelAxis',
/**
* @override
*/
init: function (ecModel, api) {
AxisView$2.superApply(this, 'init', arguments);
/**
* @type {module:echarts/component/helper/BrushController}
*/
(this._brushController = new BrushController(api.getZr()))
.on('brush', bind(this._onBrush, this));
},
/**
* @override
*/
render: function (axisModel, ecModel, api, payload) {
if (fromAxisAreaSelect(axisModel, ecModel, payload)) {
return;
}
this.axisModel = axisModel;
this.api = api;
this.group.removeAll();
if (!axisModel.get('show')) {
return;
}
this._axisGroup.add(axisBuilder.getGroup());
this._refreshBrushController(
builderOpt, areaSelectStyle, axisModel, coordSysModel, areaWidth, api
);
// /**
// * @override
// */
// updateVisual: function (axisModel, ecModel, api, payload) {
// this._brushController && this._brushController
// .updateCovers(getCoverInfoList(axisModel));
// },
_refreshBrushController: function (
builderOpt, areaSelectStyle, axisModel, coordSysModel, areaWidth, api
) {
// After filtering, axis may change, select area needs to be update.
var extent = axisModel.axis.getExtent();
var extentLen = extent[1] - extent[0];
var extra = Math.min(30, Math.abs(extentLen) * 0.1); // Arbitrary value.
this._brushController
.mount({
enableGlobalPan: true,
rotation: builderOpt.rotation,
position: builderOpt.position
})
.setPanels([{
panelId: 'pl',
clipPath: makeRectPanelClipPath(rect),
isTargetByCursor: makeRectIsTargetByCursor(rect, api,
coordSysModel),
getLinearBrushOtherExtent: makeLinearBrushOtherExtent(rect, 0)
}])
.enableBrush({
brushType: 'lineX',
brushStyle: areaSelectStyle,
removeOnClick: true
})
.updateCovers(getCoverInfoList(axisModel));
},
/**
* @override
*/
dispose: function () {
this._brushController.dispose();
}
});
function getCoverInfoList(axisModel) {
var axis = axisModel.axis;
return map(axisModel.activeIntervals, function (interval) {
return {
brushType: 'lineX',
panelId: 'pl',
range: [
axis.dataToCoord(interval[0], true),
axis.dataToCoord(interval[1], true)
]
};
});
}
// Parallel view
extendComponentView({
type: 'parallel',
if (!this._handlers) {
this._handlers = {};
each$1(handlers, function (handler, eventName) {
api.getZr().on(eventName, this._handlers[eventName] = bind(handler,
this));
}, this);
}
createOrUpdate(
this,
'_throttledDispatchExpand',
parallelModel.get('axisExpandRate'),
'fixRate'
);
},
/**
* @param {Object} [opt] If null, cancle the last action triggering for
debounce.
*/
_throttledDispatchExpand: function (opt) {
this._dispatchExpand(opt);
},
});
var handlers = {
this._mouseDownPoint = null;
},
registerPreprocessor(parallelPreprocessor);
SeriesModel.extend({
type: 'series.parallel',
dependencies: ['parallel'],
visualColorAccessPath: 'lineStyle.color',
setEncodeAndDimensions(source, this);
/**
* User can get data raw indices on 'axisAreaSelected' event received.
*
* @public
* @param {string} activeState 'active' or 'inactive' or 'normal'
* @return {Array.<number>} Raw indices
*/
getRawIndicesByActiveState: function (activeState) {
var coordSys = this.coordinateSystem;
var data = this.getData();
var indices = [];
return indices;
},
defaultOption: {
zlevel: 0, // 一级层叠
z: 2, // 二级层叠
coordinateSystem: 'parallel',
parallelIndex: 0,
label: {
show: false
},
inactiveOpacity: 0.05,
activeOpacity: 1,
lineStyle: {
width: 1,
opacity: 0.45,
type: 'solid'
},
emphasis: {
label: {
show: false
}
},
animationEasing: 'linear'
}
});
if (source.encodeDefine) {
return;
}
function convertDimNameToNumber(dimName) {
return +dimName.replace('dim', '');
}
type: 'parallel',
init: function () {
/**
* @type {module:zrender/container/Group}
* @private
*/
this._dataGroup = new Group();
this.group.add(this._dataGroup);
/**
* @type {module:echarts/data/List}
*/
this._data;
},
/**
* @override
*/
render: function (seriesModel, ecModel, api, payload) {
this._renderForNormal(seriesModel, payload);
// this[
// seriesModel.option.progressive
// ? '_renderForProgressive'
// : '_renderForNormal'
// ](seriesModel);
},
/**
* @private
*/
_renderForNormal: function (seriesModel, payload) {
var dataGroup = this._dataGroup;
var data = seriesModel.getData();
var oldData = this._data;
var coordSys = seriesModel.coordinateSystem;
var dimensions = coordSys.dimensions;
var option = seriesModel.option;
var smooth = option.smooth ? SMOOTH : null;
data.diff(oldData)
.add(add)
.update(update)
.remove(remove)
.execute();
// Update style
updateElCommon(data, smooth);
// First create
if (!this._data) {
var clipPath = createGridClipShape$1(
coordSys, seriesModel, function () {
// Callback will be invoked immediately if there is no
animation
setTimeout(function () {
dataGroup.removeClipPath();
});
}
);
dataGroup.setClipPath(clipPath);
}
this._data = data;
function add(newDataIndex) {
addEl(data, dataGroup, newDataIndex, dimensions, coordSys, null,
smooth);
}
function remove(oldDataIndex) {
var line = oldData.getItemGraphicEl(oldDataIndex);
dataGroup.remove(line);
}
},
/**
* @private
*/
// _renderForProgressive: function (seriesModel) {
// var dataGroup = this._dataGroup;
// var data = seriesModel.getData();
// var oldData = this._data;
// var coordSys = seriesModel.coordinateSystem;
// var dimensions = coordSys.dimensions;
// var option = seriesModel.option;
// var progressive = option.progressive;
// var smooth = option.smooth ? SMOOTH : null;
/**
* @override
*/
remove: function () {
this._dataGroup && this._dataGroup.removeAll();
this._data = null;
}
});
line.useStyle(extend(lineStyle, {
fill: null,
// lineStyle.color have been set to itemVisual in
module:echarts/visual/seriesColor.
stroke: data.getItemVisual(dataIndex, 'color'),
// lineStyle.opacity have been set to itemVisual in parallelVisual.
opacity: data.getItemVisual(dataIndex, 'opacity')
}));
line.shape.smooth = smooth;
});
}
// return false;
// }
// FIXME
// 公用方法?
function isEmptyValue(val, axisType) {
return axisType === 'category'
? val == null
: (val == null || isNaN(val)); // axisType === 'value'
}
var opacityMap = {
normal: lineStyle.opacity,
active: activeOpacity,
inactive: inactiveOpacity
};
data.setVisual('color', color);
});
};
registerVisual(parallelVisual);
/**
* @file Get initial data and define sankey view's series model
* @author Deqing Li([email protected])
*/
type: 'series.sankey',
layoutInfo: null,
/**
* Init a graph data structure from data in option series
*
* @param {Object} option the object used to config echarts view
* @return {module:echarts/data/List} storage initial data
*/
getInitialData: function (option) {
var links = option.edges || option.links;
var nodes = option.data || option.nodes;
if (nodes && links) {
var graph = createGraphFromNodeEdge(nodes, links, this, true);
return graph.data;
}
},
/**
* Return the graphic data structure
*
* @return {module:echarts/data/Graph} graphic data structure
*/
getGraph: function () {
return this.getData().graph;
},
/**
* Get edge data of graphic data structure
*
* @return {module:echarts/data/List} data structure of list
*/
getEdgeData: function () {
return this.getGraph().edgeData;
},
/**
* @override
*/
formatTooltip: function (dataIndex, multipleSeries, dataType) {
// dataType === 'node' or empty do not show tooltip by default
if (dataType === 'edge') {
var params = this.getDataParams(dataIndex, dataType);
var rawDataOpt = params.data;
var html = rawDataOpt.source + ' -- ' + rawDataOpt.target;
if (params.value) {
html += ' : ' + params.value;
}
return encodeHTML(html);
}
defaultOption: {
zlevel: 0,
z: 2,
coordinateSystem: 'view',
layout: null,
label: {
show: true,
position: 'right',
color: '#000',
fontSize: 12
},
itemStyle: {
borderWidth: 1,
borderColor: '#333'
},
lineStyle: {
color: '#314656',
opacity: 0.2,
curveness: 0.5
},
emphasis: {
label: {
show: true
},
lineStyle: {
opacity: 0.6
}
},
animationEasing: 'linear',
animationDuration: 1000
}
});
/**
* @file The file used to draw sankey view
* @author Deqing Li([email protected])
*/
extent: 0
},
extendChartView({
type: 'sankey',
/**
* @private
* @type {module:echarts/chart/sankey/SankeySeries}
*/
_model: null,
this._model = seriesModel;
group.removeAll();
curve.dataIndex = edge.dataIndex;
curve.seriesIndex = seriesModel.seriesIndex;
curve.dataType = 'edge';
curve.setShape({
x1: x1,
y1: y1,
x2: x2,
y2: y2,
cpx1: cpx1,
cpy1: cpy1,
cpx2: cpx2,
cpy2: cpy2
});
curve.setStyle(lineStyleModel.getItemStyle());
// Special color, use source node color or target node color
switch (curve.style.fill) {
case 'source':
curve.style.fill = edge.node1.getVisual('color');
break;
case 'target':
curve.style.fill = edge.node2.getVisual('color');
break;
}
setHoverStyle(curve,
edge.getModel('emphasis.lineStyle').getItemStyle());
group.add(curve);
edgeData.setItemGraphicEl(edge.dataIndex, curve);
});
setLabelStyle(
rect.style, hoverStyle, labelModel, labelHoverModel,
{
labelFetcher: seriesModel,
labelDataIndex: node.dataIndex,
defaultText: node.id,
isRectText: true
}
);
rect.setStyle('fill', node.getVisual('color'));
setHoverStyle(rect, hoverStyle);
group.add(rect);
nodeData.setItemGraphicEl(node.dataIndex, rect);
rect.dataType = 'node';
});
this._data = seriesModel.getData();
},
dispose: function () {}
});
return rectEl;
}
/**
* nest helper used to group by the array.
* can specified the keys and sort the keys.
*/
function nest() {
var keysFunction = [];
var sortKeysFunction = [];
/**
* map an Array into the mapObject.
* @param {Array} array
* @param {number} depth
*/
function map$$1(array, depth) {
if (depth >= keysFunction.length) {
return array;
}
var i = -1;
var n = array.length;
var keyFunction = keysFunction[depth++];
var mapObject = {};
var valuesByKey = {};
if (values) {
values.push(array[i]);
}
else {
valuesByKey[keyValue] = [array[i]];
}
}
return mapObject;
}
/**
* transform the Map Object to multidimensional Array
* @param {Object} map
* @param {number} depth
*/
function entriesMap(mapObject, depth) {
if (depth >= keysFunction.length) {
return mapObject;
}
var array = [];
var sortKeyFunction = sortKeysFunction[depth++];
if (sortKeyFunction) {
return array.sort(function (a, b) {
return sortKeyFunction(a.key, b.key);
});
}
else {
return array;
}
}
return {
/**
* specified the key to groupby the arrays.
* users can specified one more keys.
* @param {Function} d
*/
key: function (d) {
keysFunction.push(d);
return this;
},
/**
* specified the comparator to sort the keys
* @param {Function} order
*/
sortKeys: function (order) {
sortKeysFunction[keysFunction.length - 1] = order;
return this;
},
/**
* the array to be grouped by.
* @param {Array} array
*/
entries: function (array) {
return entriesMap(map$$1(array, 0), 0);
}
};
}
/**
* @file The layout algorithm of sankey view
* @author Deqing Li([email protected])
*/
seriesModel.layoutInfo = layoutInfo;
/**
* Get the layout position of the whole view
*
* @param {module:echarts/model/Series} seriesModel the model object of sankey
series
* @param {module:echarts/ExtensionAPI} api provide the API list that the
developer can call
* @return {module:zrender/core/BoundingRect} size of rect to draw the sankey view
*/
function getViewRect$3(seriesModel, api) {
return getLayoutRect(
seriesModel.getBoxLayoutParams(), {
width: api.getWidth(),
height: api.getHeight()
}
);
}
/**
* Compute the value of each node by summing the associated edge's value
*
* @param {module:echarts/data/Graph~Node} nodes node of sankey view
*/
function computeNodeValues(nodes) {
each$1(nodes, function (node) {
var value1 = sum(node.outEdges, getEdgeValue);
var value2 = sum(node.inEdges, getEdgeValue);
var value = Math.max(value1, value2);
node.setLayout({value: value}, true);
});
}
/**
* Compute the x-position for each node
*
* @param {module:echarts/data/Graph~Node} nodes node of sankey view
* @param {number} nodeWidth the dx of the node
* @param {number} width the whole width of the area to draw the view
*/
function computeNodeBreadths(nodes, nodeWidth, width) {
var remainNodes = nodes;
var nextNode = null;
var x = 0;
var kx = 0;
while (remainNodes.length) {
nextNode = [];
for (var i = 0, len = remainNodes.length; i < len; i++) {
var node = remainNodes[i];
node.setLayout({x: x}, true);
node.setLayout({dx: nodeWidth}, true);
for (var j = 0, lenj = node.outEdges.length; j < lenj; j++) {
nextNode.push(node.outEdges[j].node2);
}
}
remainNodes = nextNode;
++x;
}
moveSinksRight(nodes, x);
kx = (width - nodeWidth) / (x - 1);
scaleNodeBreadths(nodes, kx);
}
/**
* All the node without outEgdes are assigned maximum x-position and
* be aligned in the last column.
*
* @param {module:echarts/data/Graph~Node} nodes node of sankey view
* @param {number} x value (x-1) use to assign to node without outEdges
* as x-position
*/
function moveSinksRight(nodes, x) {
each$1(nodes, function (node) {
if (!node.outEdges.length) {
node.setLayout({x: x - 1}, true);
}
});
}
/**
* Scale node x-position to the width
*
* @param {module:echarts/data/Graph~Node} nodes node of sankey view
* @param {number} kx multiple used to scale nodes
*/
function scaleNodeBreadths(nodes, kx) {
each$1(nodes, function (node) {
var nodeX = node.getLayout().x * kx;
node.setLayout({x: nodeX}, true);
});
}
/**
* Using Gauss-Seidel iterations method to compute the node depth(y-position)
*
* @param {module:echarts/data/Graph~Node} nodes node of sankey view
* @param {module:echarts/data/Graph~Edge} edges edge of sankey view
* @param {number} height the whole height of the area to draw the view
* @param {number} nodeGap the vertical distance between two nodes
* in the same column.
* @param {number} iterations the number of iterations for the algorithm
*/
function computeNodeDepths(nodes, edges, height, nodeGap, iterations) {
var nodesByBreadth = nest()
.key(function (d) {
return d.getLayout().x;
})
.sortKeys(ascending)
.entries(nodes)
.map(function (d) {
return d.values;
});
/**
* Compute the original y-position for each node
*
* @param {module:echarts/data/Graph~Node} nodes node of sankey view
* @param {Array.<Array.<module:echarts/data/Graph~Node>>} nodesByBreadth
* group by the array of all sankey nodes based on the nodes x-position.
* @param {module:echarts/data/Graph~Edge} edges edge of sankey view
* @param {number} height the whole height of the area to draw the view
* @param {number} nodeGap the vertical distance between two nodes
*/
function initializeNodeDepth(nodes, nodesByBreadth, edges, height, nodeGap) {
var kyArray = [];
each$1(nodesByBreadth, function (nodes) {
var n = nodes.length;
var sum = 0;
each$1(nodes, function (node) {
sum += node.getLayout().value;
});
var ky = (height - (n - 1) * nodeGap) / sum;
kyArray.push(ky);
});
kyArray.sort(function (a, b) {
return a - b;
});
var ky0 = kyArray[0];
each$1(nodesByBreadth, function (nodes) {
each$1(nodes, function (node, i) {
node.setLayout({y: i}, true);
var nodeDy = node.getLayout().value * ky0;
node.setLayout({dy: nodeDy}, true);
});
});
/**
* Resolve the collision of initialized depth (y-position)
*
* @param {Array.<Array.<module:echarts/data/Graph~Node>>} nodesByBreadth
* group by the array of all sankey nodes based on the nodes x-position.
* @param {number} nodeGap the vertical distance between two nodes
* @param {number} height the whole height of the area to draw the view
*/
function resolveCollisions(nodesByBreadth, nodeGap, height) {
each$1(nodesByBreadth, function (nodes) {
var node;
var dy;
var y0 = 0;
var n = nodes.length;
var i;
nodes.sort(ascendingDepth);
/**
* Change the y-position of the nodes, except most the right side nodes
*
* @param {Array.<Array.<module:echarts/data/Graph~Node>>} nodesByBreadth
* group by the array of all sankey nodes based on the node x-position.
* @param {number} alpha parameter used to adjust the nodes y-position
*/
function relaxRightToLeft(nodesByBreadth, alpha) {
each$1(nodesByBreadth.slice().reverse(), function (nodes) {
each$1(nodes, function (node) {
if (node.outEdges.length) {
var y = sum(node.outEdges, weightedTarget) / sum(node.outEdges,
getEdgeValue);
var nodeY = node.getLayout().y + (y - center$1(node)) * alpha;
node.setLayout({y: nodeY}, true);
}
});
});
}
function weightedTarget(edge) {
return center$1(edge.node2) * edge.getValue();
}
/**
* Change the y-position of the nodes, except most the left side nodes
*
* @param {Array.<Array.<module:echarts/data/Graph~Node>>} nodesByBreadth
* group by the array of all sankey nodes based on the node x-position.
* @param {number} alpha parameter used to adjust the nodes y-position
*/
function relaxLeftToRight(nodesByBreadth, alpha) {
each$1(nodesByBreadth, function (nodes) {
each$1(nodes, function (node) {
if (node.inEdges.length) {
var y = sum(node.inEdges, weightedSource) / sum(node.inEdges,
getEdgeValue);
var nodeY = node.getLayout().y + (y - center$1(node)) * alpha;
node.setLayout({y: nodeY}, true);
}
});
});
}
function weightedSource(edge) {
return center$1(edge.node1) * edge.getValue();
}
/**
* Compute the depth(y-position) of each edge
*
* @param {module:echarts/data/Graph~Node} nodes node of sankey view
*/
function computeEdgeDepths(nodes) {
each$1(nodes, function (node) {
node.outEdges.sort(ascendingTargetDepth);
node.inEdges.sort(ascendingSourceDepth);
});
each$1(nodes, function (node) {
var sy = 0;
var ty = 0;
each$1(node.outEdges, function (edge) {
edge.setLayout({sy: sy}, true);
sy += edge.getLayout().dy;
});
each$1(node.inEdges, function (edge) {
edge.setLayout({ty: ty}, true);
ty += edge.getLayout().dy;
});
});
}
function ascendingTargetDepth(a, b) {
return a.node2.getLayout().y - b.node2.getLayout().y;
}
function ascendingSourceDepth(a, b) {
return a.node1.getLayout().y - b.node1.getLayout().y;
}
function sum(array, f) {
var sum = 0;
var len = array.length;
var i = -1;
while (++i < len) {
var value = +f.call(array, array[i], i);
if (!isNaN(value)) {
sum += value;
}
}
return sum;
}
function center$1(node) {
return node.getLayout().y + node.getLayout().dy / 2;
}
function ascendingDepth(a, b) {
return a.getLayout().y - b.getLayout().y;
}
function ascending(a, b) {
return a < b ? -1 : a > b ? 1 : a === b ? 0 : NaN;
}
function getEdgeValue(edge) {
return edge.getValue();
}
/**
* @file Visual encoding for sankey view
* @author Deqing Li([email protected])
*/
nodes.sort(function (a, b) {
return a.getLayout().value - b.getLayout().value;
});
});
};
registerLayout(sankeyLayout);
registerVisual(sankeyVisual);
/**
* @module echarts/chart/helper/Symbol
*/
type: 'whiskerInBox',
shape: {},
/**
* @constructor
* @alias {module:echarts/chart/helper/WhiskerBox}
* @param {module:echarts/data/List} data
* @param {number} idx
* @param {Function} styleUpdater
* @param {boolean} isInit
* @extends {module:zrender/graphic/Group}
*/
function WhiskerBox(data, idx, styleUpdater, isInit) {
Group.call(this);
/**
* @type {number}
* @readOnly
*/
this.bodyIndex;
/**
* @type {number}
* @readOnly
*/
this.whiskerIndex;
/**
* @type {Function}
*/
this.styleUpdater = styleUpdater;
/**
* Last series model.
* @type {module:echarts/model/Series}
*/
this._seriesModel;
}
// Whisker element.
this.add(new Polygon({
shape: {
points: isInit
? transInit(itemLayout.bodyEnds, constDim, itemLayout)
: itemLayout.bodyEnds
},
style: {strokeNoScale: true},
z2: 100
}));
this.bodyIndex = count++;
// Box element.
var whiskerEnds = map(itemLayout.whiskerEnds, function (ends) {
return isInit ? transInit(ends, constDim, itemLayout) : ends;
});
this.add(new WhiskerPath({
shape: makeWhiskerEndsShape(whiskerEnds),
style: {strokeNoScale: true},
z2: 100
}));
this.whiskerIndex = count++;
};
function makeWhiskerEndsShape(whiskerEnds) {
// zr animation only support 2-dim array.
var shape = {};
each$1(whiskerEnds, function (ends, i) {
shape['ends' + i] = ends;
});
return shape;
}
/**
* Update symbol properties
* @param {module:echarts/data/List} data
* @param {number} idx
*/
whiskerBoxProto.updateData = function (data, idx, isInit) {
var seriesModel = this._seriesModel = data.hostModel;
var itemLayout = data.getItemLayout(idx);
var updateMethod = graphic[isInit ? 'initProps' : 'updateProps'];
// this.childAt(this.bodyIndex).stopAnimation(true);
// this.childAt(this.whiskerIndex).stopAnimation(true);
updateMethod(
this.childAt(this.bodyIndex),
{shape: {points: itemLayout.bodyEnds}},
seriesModel, idx
);
updateMethod(
this.childAt(this.whiskerIndex),
{shape: makeWhiskerEndsShape(itemLayout.whiskerEnds)},
seriesModel, idx
);
inherits(WhiskerBox, Group);
/**
* @constructor
* @alias module:echarts/chart/helper/WhiskerBoxDraw
*/
function WhiskerBoxDraw(styleUpdater) {
this.group = new Group();
this.styleUpdater = styleUpdater;
}
/**
* Update symbols draw by new data
* @param {module:echarts/data/List} data
*/
whiskerBoxDrawProto.updateData = function (data) {
var group = this.group;
var oldData = this._data;
var styleUpdater = this.styleUpdater;
data.diff(oldData)
.add(function (newIdx) {
if (data.hasValue(newIdx)) {
var symbolEl = new WhiskerBox(data, newIdx, styleUpdater, true);
data.setItemGraphicEl(newIdx, symbolEl);
group.add(symbolEl);
}
})
.update(function (newIdx, oldIdx) {
var symbolEl = oldData.getItemGraphicEl(oldIdx);
// Empty data
if (!data.hasValue(newIdx)) {
group.remove(symbolEl);
return;
}
if (!symbolEl) {
symbolEl = new WhiskerBox(data, newIdx, styleUpdater);
}
else {
symbolEl.updateData(data, newIdx);
}
// Add back
group.add(symbolEl);
data.setItemGraphicEl(newIdx, symbolEl);
})
.remove(function (oldIdx) {
var el = oldData.getItemGraphicEl(oldIdx);
el && group.remove(el);
})
.execute();
this._data = data;
};
/**
* Remove symbols.
* @param {module:echarts/data/List} data
*/
whiskerBoxDrawProto.remove = function () {
var group = this.group;
var data = this._data;
this._data = null;
data && data.eachItemGraphicEl(function (el) {
el && group.remove(el);
});
};
var seriesModelMixin = {
/**
* @private
* @type {string}
*/
_baseAxisDim: null,
/**
* @override
*/
getInitialData: function (option, ecModel) {
// When both types of xAxis and yAxis are 'value', layout is
// needed to be specified by user. Otherwise, layout can be
// judged by which axis is category.
var ordinalMeta;
// FIXME
// 考虑时间轴
return createListSimply(
this,
{
coordDimensions: [{
name: baseAxisDim,
type: getDimensionTypeByAxis(baseAxisType),
ordinalMeta: ordinalMeta,
otherDims: {
tooltip: false,
itemName: 0
},
dimsDef: ['base']
}, {
name: otherAxisDim,
type: getDimensionTypeByAxis(otherAxisType),
dimsDef: defaultValueDimensions.slice()
}],
dimensionsCount: defaultValueDimensions.length + 1
}
);
},
/**
* If horizontal, base axis is x, otherwise y.
* @override
*/
getBaseAxis: function () {
var dim = this._baseAxisDim;
return this.ecModel.getComponent(dim + 'Axis', this.get(dim +
'AxisIndex')).axis;
}
};
var viewMixin = {
init: function () {
/**
* Old data.
* @private
* @type {module:echarts/chart/helper/WhiskerBoxDraw}
*/
var whiskerBoxDraw = this._whiskerBoxDraw = new WhiskerBoxDraw(
this.getStyleUpdater()
);
this.group.add(whiskerBoxDraw.group);
},
type: 'series.boxplot',
// TODO
// box width represents group size, so dimension should have 'size'.
/**
* @see <https://en.wikipedia.org/wiki/Box_plot>
* The meanings of 'min' and 'max' depend on user,
* and echarts do not need to know it.
* @readOnly
*/
defaultValueDimensions: ['min', 'Q1', 'median', 'Q3', 'max'],
/**
* @type {Array.<string>}
* @readOnly
*/
dimensions: null,
/**
* @override
*/
defaultOption: {
zlevel: 0, // 一级层叠
z: 2, // 二级层叠
coordinateSystem: 'cartesian2d',
legendHoverLink: true,
hoverAnimation: true,
// xAxisIndex: 0,
// yAxisIndex: 0,
itemStyle: {
color: '#fff',
borderWidth: 1
},
emphasis: {
itemStyle: {
borderWidth: 2,
shadowBlur: 5,
shadowOffsetX: 2,
shadowOffsetY: 2,
shadowColor: 'rgba(0,0,0,0.4)'
}
},
animationEasing: 'elasticOut',
animationDuration: 800
}
});
type: 'boxplot',
getStyleUpdater: function () {
return updateStyle$1;
},
dispose: noop
});
// Exclude borderColor.
var itemStyle = normalItemStyleModel.getItemStyle(['borderColor']);
data.setVisual({
legendSymbol: 'roundRect',
// Use name 'color' but not 'borderColor' for legend usage and
// visual coding from other component like dataRange.
color: seriesModel.get(borderColorQuery) || defaulColor
});
};
if (!seriesModels.length) {
return;
}
calculateBase(groupItem);
/**
* Group series by axis.
*/
function groupSeriesByAxis(ecModel) {
var result = [];
var axisList = [];
if (idx < 0) {
idx = axisList.length;
axisList[idx] = baseAxis;
result[idx] = {axis: baseAxis, seriesModels: []};
}
result[idx].seriesModels.push(seriesModel);
});
return result;
}
/**
* Calculate offset and box width for each series.
*/
function calculateBase(groupItem) {
var extent;
var baseAxis = groupItem.axis;
var seriesModels = groupItem.seriesModels;
var seriesCount = seriesModels.length;
var bandWidth;
if (baseAxis.type === 'category') {
bandWidth = baseAxis.getBandWidth();
}
else {
var maxDataCount = 0;
each$14(seriesModels, function (seriesModel) {
maxDataCount = Math.max(maxDataCount, seriesModel.getData().count());
});
extent = baseAxis.getExtent(),
Math.abs(extent[1] - extent[0]) / maxDataCount;
}
boxWidthList.push(
Math.min(Math.max(boxWidth, boundList[idx][0]), boundList[idx][1])
);
});
}
/**
* Calculate points location for each series.
*/
function layoutSingleSeries(seriesModel, offset, boxWidth) {
var coordSys = seriesModel.coordinateSystem;
var data = seriesModel.getData();
var halfWidth = boxWidth / 2;
var chartLayout = seriesModel.get('layout');
var variableDim = chartLayout === 'horizontal' ? 0 : 1;
var constDim = 1 - variableDim;
var coordDims = ['x', 'y'];
var vDims = [];
var cDim;
data.each([cDim].concat(vDims), function () {
var args = arguments;
var axisDimVal = args[0];
var idx = args[vDims.length + 1];
data.setItemLayout(idx, {
chartLayout: chartLayout,
initBaseline: median[constDim],
median: median,
bodyEnds: bodyEnds,
whiskerEnds: whiskerEnds
});
function getPoint(val) {
var p = [];
p[variableDim] = axisDimVal;
p[constDim] = val;
var point;
if (isNaN(axisDimVal) || isNaN(val)) {
point = [NaN, NaN];
}
else {
point = coordSys.dataToPoint(p);
point[variableDim] += offset;
}
return point;
}
function addBodyEnd(point, start) {
var point1 = point.slice();
var point2 = point.slice();
point1[variableDim] += halfWidth;
point2[variableDim] -= halfWidth;
start
? bodyEnds.push(point1, point2)
: bodyEnds.push(point2, point1);
}
function layEndLine(endCenter) {
var line = [endCenter.slice(), endCenter.slice()];
line[0][variableDim] -= halfWidth;
line[1][variableDim] += halfWidth;
whiskerEnds.push(line);
}
});
}
registerVisual(boxplotVisual);
registerLayout(boxplotLayout);
type: 'series.candlestick',
/**
* @readOnly
*/
defaultValueDimensions: ['open', 'close', 'lowest', 'highest'],
/**
* @type {Array.<string>}
* @readOnly
*/
dimensions: null,
/**
* @override
*/
defaultOption: {
zlevel: 0, // 一级层叠
z: 2, // 二级层叠
coordinateSystem: 'cartesian2d',
legendHoverLink: true,
hoverAnimation: true,
// xAxisIndex: 0,
// yAxisIndex: 0,
itemStyle: {
color: '#c23531', // 阳线 positive
color0: '#314656', // 阴线 negative '#c23531', '#314656'
borderWidth: 1,
// FIXME
// ec2 中使用的是 lineStyle.color 和 lineStyle.color0
borderColor: '#c23531',
borderColor0: '#314656'
},
emphasis: {
itemStyle: {
borderWidth: 2
}
},
barMaxWidth: null,
barMinWidth: null,
barWidth: null,
animationUpdate: false,
animationEasing: 'linear',
animationDuration: 300
},
/**
* Get dimension for shadow in dataZoom
* @return {string} dimension name
*/
getShadowDim: function () {
return 'open';
},
});
type: 'candlestick',
getStyleUpdater: function () {
return updateStyle$2;
},
dispose: noop
});
data.setVisual({
legendSymbol: 'roundRect'
});
data.setItemVisual(
idx,
{
color: itemModel.get(
sign > 0 ? positiveColorQuery : negativeColorQuery
),
borderColor: itemModel.get(
sign > 0 ? positiveBorderColorQuery :
negativeBorderColorQuery
)
}
);
});
}
});
};
var dataIndex = 0;
data.each([cDim].concat(vDims), function () {
var args = arguments;
var axisDimVal = args[0];
var idx = args[vDims.length + 1];
var whiskerEnds = [
[
subPixelOptimizePoint(highestPoint),
subPixelOptimizePoint(ocHighPoint)
],
[
subPixelOptimizePoint(lowestPoint),
subPixelOptimizePoint(ocLowPoint)
]
];
var sign;
if (openVal > closeVal) {
sign = -1;
}
else if (openVal < closeVal) {
sign = 1;
}
else {
// If close === open, compare with close of last record
if (dataIndex > 0) {
sign = data.getItemModel(dataIndex - 1).get()[2]
<= closeVal
? 1
: -1;
}
else {
// No record of previous, set to be positive
sign = 1;
}
}
data.setItemLayout(idx, {
chartLayout: chartLayout,
sign: sign,
initBaseline: openVal > closeVal
? ocHighPoint[constDim] : ocLowPoint[constDim], // open point.
bodyEnds: bodyEnds,
whiskerEnds: whiskerEnds,
brushRect: makeBrushRect()
});
++dataIndex;
function getPoint(val) {
var p = [];
p[variableDim] = axisDimVal;
p[constDim] = val;
return (isNaN(axisDimVal) || isNaN(val))
? [NaN, NaN]
: coordSys.dataToPoint(p);
}
point1[variableDim] = subPixelOptimize(
point1[variableDim] + candleWidth / 2, 1, false
);
point2[variableDim] = subPixelOptimize(
point2[variableDim] - candleWidth / 2, 1, true
);
start
? bodyEnds.push(point1, point2)
: bodyEnds.push(point2, point1);
}
function makeBrushRect() {
var pmin = getPoint(Math.min(openVal, closeVal, lowestVal,
highestVal));
var pmax = getPoint(Math.max(openVal, closeVal, lowestVal,
highestVal));
pmin[variableDim] -= candleWidth / 2;
pmax[variableDim] -= candleWidth / 2;
return {
x: pmin[0],
y: pmin[1],
width: constDim ? candleWidth : pmax[0] - pmin[0],
height: constDim ? pmax[1] - pmin[1] : candleWidth
};
}
function subPixelOptimizePoint(point) {
point[variableDim] = subPixelOptimize(point[variableDim], 1);
return point;
}
});
});
};
registerPreprocessor(preprocessor);
registerVisual(candlestickVisual);
registerLayout(candlestickLayout);
SeriesModel.extend({
type: 'series.effectScatter',
brushSelector: 'point',
defaultOption: {
coordinateSystem: 'cartesian2d',
zlevel: 0,
z: 2,
legendHoverLink: true,
effectType: 'ripple',
progressive: 0,
// large: false,
// Available when large is true
// largeThreshold: 2000,
// itemStyle: {
// opacity: 1
// }
}
});
/**
* Symbol with ripple effect
* @module echarts/chart/helper/EffectSymbol
*/
var EFFECT_RIPPLE_NUMBER = 3;
function normalizeSymbolSize$1(symbolSize) {
if (!isArray(symbolSize)) {
symbolSize = [+symbolSize, +symbolSize];
}
return symbolSize;
}
effectSymbolProto.stopEffectAnimation = function () {
this.childAt(1).removeAll();
};
rippleGroup.add(ripplePath);
}
updateRipplePath(rippleGroup, effectCfg);
};
/**
* Update effect symbol
*/
effectSymbolProto.updateEffectAnimation = function (effectCfg) {
var oldEffectCfg = this._effectCfg;
var rippleGroup = this.childAt(1);
updateRipplePath(rippleGroup, effectCfg);
};
/**
* Highlight symbol
*/
effectSymbolProto.highlight = function () {
this.trigger('emphasis');
};
/**
* Downplay symbol
*/
effectSymbolProto.downplay = function () {
this.trigger('normal');
};
/**
* Update symbol properties
* @param {module:echarts/data/List} data
* @param {number} idx
*/
effectSymbolProto.updateData = function (data, idx) {
var seriesModel = data.hostModel;
this.childAt(0).updateData(data, idx);
rippleGroup.attr('scale', symbolSize);
rippleGroup.traverse(function (ripplePath) {
ripplePath.attr({
fill: color
});
});
effectCfg.showEffectOn = seriesModel.get('showEffectOn');
effectCfg.rippleScale = itemModel.get('rippleEffect.scale');
effectCfg.brushType = itemModel.get('rippleEffect.brushType');
effectCfg.period = itemModel.get('rippleEffect.period') * 1000;
effectCfg.effectOffset = idx / data.count();
effectCfg.z = itemModel.getShallow('z') || 0;
effectCfg.zlevel = itemModel.getShallow('zlevel') || 0;
effectCfg.symbolType = symbolType;
effectCfg.color = color;
this.off('mouseover').off('mouseout').off('emphasis').off('normal');
this._effectCfg = effectCfg;
}
else {
// Not keep old effect config
this._effectCfg = null;
this.stopEffectAnimation();
var symbol = this.childAt(0);
var onEmphasis = function () {
symbol.highlight();
if (effectCfg.showEffectOn !== 'render') {
this.startEffectAnimation(effectCfg);
}
};
var onNormal = function () {
symbol.downplay();
if (effectCfg.showEffectOn !== 'render') {
this.stopEffectAnimation();
}
};
this.on('mouseover', onEmphasis, this)
.on('mouseout', onNormal, this)
.on('emphasis', onEmphasis, this)
.on('normal', onNormal, this);
}
this._effectCfg = effectCfg;
};
inherits(EffectSymbol, Group);
extendChartView({
type: 'effectScatter',
init: function () {
this._symbolDraw = new SymbolDraw(EffectSymbol);
},
this.group.dirty();
this._symbolDraw.updateLayout(data);
},
dispose: function () {}
});
registerVisual(visualSymbol('effectScatter', 'circle'));
registerLayout(pointsLayout('effectScatter'));
function compatEc2(seriesOpt) {
var data = seriesOpt.data;
if (data && data[0] && data[0][0] && data[0][0].coord) {
if (__DEV__) {
console.warn('Lines data configuration has been changed to'
+ ' { coords:[[1,2],[2,3]] }');
}
seriesOpt.data = map(data, function (itemOpt) {
var coords = [
itemOpt[0].coord, itemOpt[1].coord
];
var target = {
coords: coords
};
if (itemOpt[0].name) {
target.fromName = itemOpt[0].name;
}
if (itemOpt[1].name) {
target.toName = itemOpt[1].name;
}
return mergeAll([target, itemOpt[0], itemOpt[1]]);
});
}
}
type: 'series.lines',
visualColorAccessPath: 'lineStyle.color',
if (option.data) {
// Only update when have option data to merge.
var result = this._processFlatCoordsArray(option.data);
this._flatCoords = result.flatCoords;
this._flatCoordsOffset = result.flatCoordsOffset;
if (result.flatCoords) {
option.data = new Float32Array(result.count);
}
}
this.getRawData().appendData(params.data);
},
if (__DEV__) {
if (!(coords instanceof Array && coords.length > 0 && coords[0]
instanceof Array)) {
throw new Error('Invalid coords ' + JSON.stringify(coords) + '.
Lines must have 2d coords array in data item.');
}
}
return coords;
},
if (i > len) {
if (__DEV__) {
throw new Error('Invalid data format.');
}
}
}
}
return {
flatCoordsOffset: new Uint32Array(coordsOffsetAndLenStorage.buffer,
0, offsetCursor),
flatCoords: coordsStorage,
count: dataCount
};
}
return {
flatCoordsOffset: null,
flatCoords: null,
count: data.length
};
},
getInitialData: function (option, ecModel) {
if (__DEV__) {
var CoordSys = CoordinateSystemManager.get(option.coordinateSystem);
if (!CoordSys) {
throw new Error('Unkown coordinate system ' +
option.coordinateSystem);
}
}
return lineData;
},
preventIncremental: function () {
return !!this.get('effect.show');
},
getProgressive: function () {
var progressive = this.option.progressive;
if (progressive == null) {
return this.option.large ? 1e4 : this.get('progressive');
}
return progressive;
},
getProgressiveThreshold: function () {
var progressiveThreshold = this.option.progressiveThreshold;
if (progressiveThreshold == null) {
return this.option.large ? 2e4 : this.get('progressiveThreshold');
}
return progressiveThreshold;
},
defaultOption: {
coordinateSystem: 'geo',
zlevel: 0,
z: 2,
legendHoverLink: true,
hoverAnimation: true,
// Cartesian coordinate system
xAxisIndex: 0,
yAxisIndex: 0,
effect: {
show: false,
period: 4,
// Animation delay. support callback
// delay: 0,
// If move with constant speed px/sec
// period will be ignored if this property is > 0,
constantSpeed: 0,
symbol: 'circle',
symbolSize: 3,
loop: true,
// Length of trail, 0 - 1
trailLength: 0.2
// Same with lineStyle.color
// color
},
large: false,
// Available when large is true
largeThreshold: 2000,
label: {
show: false,
position: 'end'
// distance: 5,
// formatter: 标签文本格式器,同 Tooltip.formatter,不支持异步回调
},
lineStyle: {
opacity: 0.5
}
}
});
/**
* Provide effect for line
* @module echarts/chart/helper/EffectLine
*/
/**
* @constructor
* @extends {module:zrender/graphic/Group}
* @alias {module:echarts/chart/helper/Line}
*/
function EffectLine(lineData, idx, seriesScope) {
Group.call(this);
this._updateEffectSymbol(lineData, idx);
}
symbol = createSymbol(
symbolType, -0.5, -0.5, 1, 1, color
);
symbol.z2 = 100;
symbol.culling = true;
this.add(symbol);
}
symbol.setColor(color);
symbol.attr('scale', size);
this._symbolType = symbolType;
this.updateAnimationPoints(symbol, points);
if (constantSpeed > 0) {
period = this.getLineLength(symbol) / constantSpeed * 1000;
}
symbol.stopAnimation();
this._period = period;
this._loop = loop;
};
// Tangent
var tx = quadraticDerivativeAt$$1(p1[0], cp1[0], p2[0], t);
var ty = quadraticDerivativeAt$$1(p1[1], cp1[1], p2[1], t);
symbol.ignore = false;
};
/**
* @module echarts/chart/helper/Line
*/
/**
* @constructor
* @extends {module:zrender/graphic/Group}
* @alias {module:echarts/chart/helper/Polyline}
*/
function Polyline$2(lineData, idx, seriesScope) {
Group.call(this);
this.add(line);
if (!seriesScope || lineData.hasItemOption) {
lineStyle = itemModel.getModel('lineStyle').getLineStyle();
hoverLineStyle = itemModel.getModel('emphasis.lineStyle').getLineStyle();
}
line.useStyle(defaults(
{
strokeNoScale: true,
fill: 'none',
stroke: visualColor
},
lineStyle
));
line.hoverStyle = hoverLineStyle;
setHoverStyle(this);
};
inherits(Polyline$2, Group);
/**
* Provide effect for line
* @module echarts/chart/helper/EffectLine
*/
/**
* @constructor
* @extends {module:echarts/chart/helper/EffectLine}
* @alias {module:echarts/chart/helper/Polyline}
*/
function EffectPolyline(lineData, idx, seriesScope) {
EffectLine.call(this, lineData, idx, seriesScope);
this._lastFrame = 0;
this._lastFramePercent = 0;
}
// Overwrite
effectPolylineProto.createLine = function (lineData, idx, seriesScope) {
return new Polyline$2(lineData, idx, seriesScope);
};
// Overwrite
effectPolylineProto.updateAnimationPoints = function (symbol, points) {
this._points = points;
var accLenArr = [0];
var len$$1 = 0;
for (var i = 1; i < points.length; i++) {
var p1 = points[i - 1];
var p2 = points[i];
len$$1 += dist(p1, p2);
accLenArr.push(len$$1);
}
if (len$$1 === 0) {
return;
}
for (var i = 0; i < accLenArr.length; i++) {
accLenArr[i] /= len$$1;
}
this._offsets = accLenArr;
this._length = len$$1;
};
// Overwrite
effectPolylineProto.getLineLength = function (symbol) {
return this._length;
};
// Overwrite
effectPolylineProto.updateSymbolPosition = function (symbol) {
var t = symbol.__t;
var points = this._points;
var offsets = this._offsets;
var len$$1 = points.length;
if (!offsets) {
// Has length 0
return;
}
if (t < this._lastFramePercent) {
// Start from the next frame
// PENDING start from lastFrame ?
var start = Math.min(lastFrame + 1, len$$1 - 1);
for (frame = start; frame >= 0; frame--) {
if (offsets[frame] <= t) {
break;
}
}
// PENDING really need to do this ?
frame = Math.min(frame, len$$1 - 2);
}
else {
for (var frame = lastFrame; frame < len$$1; frame++) {
if (offsets[frame] > t) {
break;
}
}
frame = Math.min(frame - 1, len$$1 - 2);
}
lerp(
symbol.position, points[frame], points[frame + 1],
(t - offsets[frame]) / (offsets[frame + 1] - offsets[frame])
);
this._lastFrame = frame;
this._lastFramePercent = t;
symbol.ignore = false;
};
inherits(EffectPolyline, EffectLine);
shape: {
polyline: false,
curveness: 0,
segs: []
},
if (shape.polyline) {
for (var i = 0; i < segs.length;) {
var count = segs[i++];
if (count > 0) {
path.moveTo(segs[i++], segs[i++]);
for (var k = 1; k < count; k++) {
path.lineTo(segs[i++], segs[i++]);
}
}
}
}
else {
for (var i = 0; i < segs.length;) {
var x0 = segs[i++];
var y0 = segs[i++];
var x1 = segs[i++];
var y1 = segs[i++];
path.moveTo(x0, y0);
if (curveness > 0) {
var x2 = (x0 + x1) / 2 - (y0 - y1) * curveness;
var y2 = (y0 + y1) / 2 - (x1 - x0) * curveness;
path.quadraticCurveTo(x2, y2, x1, y1);
}
else {
path.lineTo(x1, y1);
}
}
}
},
if (shape.polyline) {
var dataIndex = 0;
for (var i = 0; i < segs.length;) {
var count = segs[i++];
if (count > 0) {
var x0 = segs[i++];
var y0 = segs[i++];
for (var k = 1; k < count; k++) {
var x1 = segs[i++];
var y1 = segs[i++];
if (containStroke$1(x0, y0, x1, y1)) {
return dataIndex;
}
}
}
dataIndex++;
}
}
else {
var dataIndex = 0;
for (var i = 0; i < segs.length;) {
var x0 = segs[i++];
var y0 = segs[i++];
var x1 = segs[i++];
var y1 = segs[i++];
if (curveness > 0) {
var x2 = (x0 + x1) / 2 - (y0 - y1) * curveness;
var y2 = (y0 + y1) / 2 - (x1 - x0) * curveness;
dataIndex++;
}
}
return -1;
}
});
function LargeLineDraw() {
this.group = new Group();
}
largeLineProto.isPersistent = function () {
return !this._incremental;
};
/**
* Update symbols draw by new data
* @param {module:echarts/data/List} data
*/
largeLineProto.updateData = function (data) {
this.group.removeAll();
this._setCommon(lineEl, data);
// Add back
this.group.add(lineEl);
this._incremental = null;
};
/**
* @override
*/
largeLineProto.incrementalPrepareUpdate = function (data) {
this.group.removeAll();
this._clearIncremental();
/**
* @override
*/
largeLineProto.incrementalUpdate = function (taskParams, data) {
var lineEl = new LargeLineShape();
lineEl.setShape({
segs: data.getLayout('linesPoints')
});
this._setCommon(lineEl, data, !!this._incremental);
if (!this._incremental) {
lineEl.rectHover = true;
lineEl.cursor = 'default';
lineEl.__startIndex = taskParams.start;
this.group.add(lineEl);
}
else {
this._incremental.addDisplayable(lineEl, true);
}
};
/**
* @override
*/
largeLineProto.remove = function () {
this._clearIncremental();
this._incremental = null;
this.group.removeAll();
};
lineEl.setShape({
polyline: hostModel.get('polyline'),
curveness: hostModel.get('lineStyle.curveness')
});
lineEl.useStyle(
hostModel.getModel('lineStyle').getLineStyle()
);
lineEl.style.strokeNoScale = true;
if (!isIncremental) {
// Enable tooltip
// PENDING May have performance issue when path is extremely large
lineEl.seriesIndex = hostModel.seriesIndex;
lineEl.on('mousemove', function (e) {
lineEl.dataIndex = null;
var dataIndex = lineEl.findDataIndex(e.offsetX, e.offsetY);
if (dataIndex > 0) {
// Provide dataIndex for tooltip
lineEl.dataIndex = dataIndex + lineEl.__startIndex;
}
});
}
};
largeLineProto._clearIncremental = function () {
var incremental = this._incremental;
if (incremental) {
incremental.clearDisplaybles();
}
};
var linesLayout = {
seriesType: 'lines',
plan: createRenderPlanner(),
var offset = 0;
var pt = [];
for (var i = params.start; i < params.end; i++) {
var len = seriesModel.getLineCoords(i, lineCoords);
if (isPolyline) {
points[offset++] = len;
}
for (var k = 0; k < len; k++) {
pt = coordSys.dataToPoint(lineCoords[k], false, pt);
points[offset++] = pt[0];
points[offset++] = pt[1];
}
}
lineData.setLayout('linesPoints', points);
}
else {
for (var i = params.start; i < params.end; i++) {
var itemModel = lineData.getItemModel(i);
var len = seriesModel.getLineCoords(i, lineCoords);
extendChartView({
type: 'lines',
var zr = api.getZr();
// Avoid the drag cause ghost shadow
// FIXME Better way ?
// SVG doesn't support
var isSvg = zr.painter.getType() === 'svg';
if (!isSvg) {
zr.painter.getLayer(zlevel).clear(true);
}
// Config layer with motion blur
if (this._lastZlevel != null && !isSvg) {
zr.configLayer(this._lastZlevel, {
motionBlur: false
});
}
if (this._showEffect(seriesModel) && trailLength) {
if (__DEV__) {
var notInIndividual = false;
ecModel.eachSeries(function (otherSeriesModel) {
if (otherSeriesModel !== seriesModel &&
otherSeriesModel.get('zlevel') === zlevel) {
notInIndividual = true;
}
});
notInIndividual && console.warn('Lines with trail effect should
have an individual zlevel');
}
if (!isSvg) {
zr.configLayer(zlevel, {
motionBlur: true,
lastFrameAlpha: Math.max(Math.min(trailLength / 10 + 0.9, 1),
0)
});
}
}
lineDraw.updateData(data);
this._lastZlevel = zlevel;
this._finished = true;
},
lineDraw.incrementalPrepareUpdate(data);
this._clearLayer(api);
this._finished = false;
},
if (!this._finished || seriesModel.pipelineContext.large) {
// TODO Don't have to do update in large mode. Only do it when there
are millions of data.
return {
update: true
};
}
else {
// TODO Use same logic with ScatterView.
// Manually update layout
var res = linesLayout.reset(seriesModel);
if (res.progress) {
res.progress({ start: 0, end: data.count() }, data);
}
this._lineDraw.updateLayout();
this._clearLayer(api);
}
},
if (__DEV__) {
if (hasEffect && isLargeDraw) {
console.warn('Large lines not support effect');
}
}
if (!lineDraw
|| hasEffect !== this._hasEffet
|| isPolyline !== this._isPolyline
|| isLargeDraw !== this._isLargeDraw
) {
if (lineDraw) {
lineDraw.remove();
}
lineDraw = this._lineDraw = isLargeDraw
? new LargeLineDraw()
: new LineDraw(
isPolyline
? (hasEffect ? EffectPolyline : Polyline$2)
: (hasEffect ? EffectLine : Line$1)
);
this._hasEffet = hasEffect;
this._isPolyline = isPolyline;
this._isLargeDraw = isLargeDraw;
this.group.removeAll();
}
this.group.add(lineDraw.group);
return lineDraw;
},
dispose: function () {}
});
function normalize$2(a) {
if (!(a instanceof Array)) {
a = [a, a];
}
return a;
}
registerLayout(linesLayout);
registerVisual(linesVisual);
SeriesModel.extend({
type: 'series.heatmap',
preventIncremental: function () {
var coordSysCreator =
CoordinateSystemManager.get(this.get('coordinateSystem'));
if (coordSysCreator && coordSysCreator.dimensions) {
return coordSysCreator.dimensions[0] === 'lng' &&
coordSysCreator.dimensions[1] === 'lat';
}
},
defaultOption: {
// Cartesian2D or geo
coordinateSystem: 'cartesian2d',
zlevel: 0,
z: 2,
blurSize: 30,
pointSize: 20,
maxOpacity: 1,
minOpacity: 0
}
});
/**
* @file defines echarts Heatmap Chart
* @author Ovilia ([email protected])
* Inspired by https://github.com/mourner/simpleheat
*
* @module
*/
/**
* Heatmap Chart
*
* @class
*/
function Heatmap() {
var canvas = createCanvas();
this.canvas = canvas;
this.blurSize = 30;
this.pointSize = 20;
this.maxOpacity = 1;
this.minOpacity = 0;
this._gradientPixels = {};
}
Heatmap.prototype = {
/**
* Renders Heatmap and returns the rendered canvas
* @param {Array} data array of data, each has x, y, value
* @param {number} width canvas width
* @param {number} height canvas height
*/
update: function(data, width, height, normalize, colorFunc, isInRange) {
var brush = this._getBrush();
var gradientInRange = this._getGradient(data, colorFunc, 'inRange');
var gradientOutOfRange = this._getGradient(data, colorFunc, 'outOfRange');
var r = this.pointSize + this.blurSize;
if (!canvas.width || !canvas.height) {
// Avoid "Uncaught DOMException: Failed to execute 'getImageData' on
// 'CanvasRenderingContext2D': The source height is 0."
return canvas;
}
// colorize the canvas using alpha value and set with gradient
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
/**
* get canvas of a black circle brush used for canvas to draw later
* @private
* @returns {Object} circle brush canvas
*/
_getBrush: function() {
var brushCanvas = this._brushCanvas || (this._brushCanvas =
createCanvas());
// set brush size
var r = this.pointSize + this.blurSize;
var d = r * 2;
brushCanvas.width = d;
brushCanvas.height = d;
/**
* get gradient color map
* @private
*/
_getGradient: function (data, colorFunc, state) {
var gradientPixels = this._gradientPixels;
var pixelsSingleState = gradientPixels[state] || (gradientPixels[state] =
new Uint8ClampedArray(256 * 4));
var color = [0, 0, 0, 0];
var off = 0;
for (var i = 0; i < 256; i++) {
colorFunc[state](i / 255, true, color);
pixelsSingleState[off++] = color[0];
pixelsSingleState[off++] = color[1];
pixelsSingleState[off++] = color[2];
pixelsSingleState[off++] = color[3];
}
return pixelsSingleState;
}
};
function isGeoCoordSys(coordSys) {
var dimensions = coordSys.dimensions;
// Not use coorSys.type === 'geo' because coordSys maybe extended
return dimensions[0] === 'lng' && dimensions[1] === 'lat';
}
extendChartView({
type: 'heatmap',
if (__DEV__) {
if (!visualMapOfThisSeries) {
throw new Error('Heatmap must use with visualMap');
}
}
this.group.removeAll();
this._incrementalDisplayable = null;
if (__DEV__) {
if (!(xAxis.type === 'category' && yAxis.type === 'category')) {
throw new Error('Heatmap on cartesian must have two category
axes');
}
if (!(xAxis.onBand && yAxis.onBand)) {
throw new Error('Heatmap on cartesian must have two axes with
boundaryGap true');
}
}
width = xAxis.getBandWidth();
height = yAxis.getBandWidth();
}
setLabelStyle(
style, hoverStl, labelModel, hoverLabelModel,
{
labelFetcher: seriesModel,
labelDataIndex: idx,
defaultText: defaultText,
isRectText: true
}
);
rect.setStyle(style);
setHoverStyle(rect, data.hasItemOption ? hoverStl : extend({},
hoverStl));
rect.incremental = incremental;
// PENDING
if (incremental) {
// Rect must use hover layer if it's incremental.
rect.useHoverLayer = true;
}
group.add(rect);
data.setItemGraphicEl(idx, rect);
}
},
_renderOnGeo: function (geo, seriesModel, visualMapModel, api) {
var inRangeVisuals = visualMapModel.targetVisuals.inRange;
var outOfRangeVisuals = visualMapModel.targetVisuals.outOfRange;
// if (!visualMapping) {
// throw new Error('Data range must have color visuals');
// }
// Clamp on viewport
var x = Math.max(rect.x, 0);
var y = Math.max(rect.y, 0);
var x2 = Math.min(rect.width + rect.x, api.getWidth());
var y2 = Math.min(rect.height + rect.y, api.getHeight());
var width = x2 - x;
var height = y2 - y;
var dims = [
data.mapDimension('lng'),
data.mapDimension('lat'),
data.mapDimension('value')
];
hmLayer.update(
points, width, height,
inRangeVisuals.color.getNormalizer(),
{
inRange: inRangeVisuals.color.getColorMapper(),
outOfRange: outOfRangeVisuals.color.getColorMapper()
},
isInRange
);
var img = new ZImage({
style: {
width: width,
height: height,
x: x,
y: y,
image: hmLayer.canvas
},
silent: true
});
this.group.add(img);
},
dispose: function () {}
});
type: 'series.pictorialBar',
dependencies: ['grid'],
defaultOption: {
symbol: 'circle', // Customized bar shape
symbolSize: null, // Can be ['100%', '100%'], null means auto.
symbolRotate: null,
symbolClip: false,
symbolBoundingData: null, // Can be 60 or -40 or [-40, 60]
symbolPatternSize: 400, // 400 * 400 px
// Disable progressive
progressive: 0,
hoverAnimation: false // Open only when needed.
},
// index: +isHorizontal
var LAYOUT_ATTRS = [
{xy: 'x', wh: 'width', index: 0, posDesc: ['left', 'right']},
{xy: 'y', wh: 'height', index: 1, posDesc: ['top', 'bottom']}
];
type: 'pictorialBar',
var opt = {
ecSize: {width: api.getWidth(), height: api.getHeight()},
seriesModel: seriesModel,
coordSys: cartesian,
coordSysExtent: [
[coordSysRect.x, coordSysRect.x + coordSysRect.width],
[coordSysRect.y, coordSysRect.y + coordSysRect.height]
],
isHorizontal: isHorizontal,
valueDim: LAYOUT_ATTRS[+isHorizontal],
categoryDim: LAYOUT_ATTRS[1 - isHorizontal]
};
data.diff(oldData)
.add(function (dataIndex) {
if (!data.hasValue(dataIndex)) {
return;
}
data.setItemGraphicEl(dataIndex, bar);
group.add(bar);
if (!data.hasValue(newIndex)) {
group.remove(bar);
return;
}
if (bar) {
updateBar(bar, opt, symbolMeta);
}
else {
bar = createBar(data, opt, symbolMeta, true);
}
data.setItemGraphicEl(newIndex, bar);
bar.__pictorialSymbolMeta = symbolMeta;
// Add back
group.add(bar);
this._data = data;
return this.group;
},
dispose: noop,
// Set or calculate default value about symbol, and calculate layout info.
function getSymbolMeta(data, dataIndex, itemModel, opt) {
var layout = data.getItemLayout(dataIndex);
var symbolRepeat = itemModel.get('symbolRepeat');
var symbolClip = itemModel.get('symbolClip');
var symbolPosition = itemModel.get('symbolPosition') || 'start';
var symbolRotate = itemModel.get('symbolRotate');
var rotation = (symbolRotate || 0) * Math.PI / 180 || 0;
var symbolPatternSize = itemModel.get('symbolPatternSize') || 2;
var isAnimationEnabled = itemModel.isAnimationEnabled();
var symbolMeta = {
dataIndex: dataIndex,
layout: layout,
itemModel: itemModel,
symbolType: data.getItemVisual(dataIndex, 'symbol') || 'circle',
color: data.getItemVisual(dataIndex, 'color'),
symbolClip: symbolClip,
symbolRepeat: symbolRepeat,
symbolRepeatDirection: itemModel.get('symbolRepeatDirection'),
symbolPatternSize: symbolPatternSize,
rotation: rotation,
animationModel: isAnimationEnabled ? itemModel : null,
hoverAnimation: isAnimationEnabled && itemModel.get('hoverAnimation'),
z2: itemModel.getShallow('z', true) || 0
};
prepareSymbolSize(
data, dataIndex, layout, symbolRepeat, symbolClip,
symbolMeta.boundingLength,
symbolMeta.pxSign, symbolPatternSize, opt, symbolMeta
);
prepareLayoutInfo(
itemModel, symbolSize, layout, symbolRepeat, symbolClip, symbolOffset,
symbolPosition, symbolMeta.valueLineWidth, symbolMeta.boundingLength,
symbolMeta.repeatCutLength,
opt, symbolMeta
);
return symbolMeta;
}
if (isArray(symbolBoundingData)) {
var symbolBoundingExtent = [
convertToCoordOnAxis(valueAxis, symbolBoundingData[0]) - zeroPx,
convertToCoordOnAxis(valueAxis, symbolBoundingData[1]) - zeroPx
];
symbolBoundingExtent[1] < symbolBoundingExtent[0] &&
(symbolBoundingExtent.reverse());
boundingLength = symbolBoundingExtent[pxSignIdx];
}
else if (symbolBoundingData != null) {
boundingLength = convertToCoordOnAxis(valueAxis, symbolBoundingData) -
zeroPx;
}
else if (symbolRepeat) {
boundingLength = opt.coordSysExtent[valueDim.index][pxSignIdx] - zeroPx;
}
else {
boundingLength = layout[valueDim.wh];
}
output.boundingLength = boundingLength;
if (symbolRepeat) {
output.repeatCutLength = layout[valueDim.wh];
}
symbolSize[categoryDim.index] = parsePercent$1(
symbolSize[categoryDim.index],
categorySize
);
symbolSize[valueDim.index] = parsePercent$1(
symbolSize[valueDim.index],
symbolRepeat ? categorySize : Math.abs(boundingLength)
);
output.symbolSize = symbolSize;
if (valueLineWidth) {
pathForLineWidth.attr({
scale: symbolScale.slice(),
rotation: rotation
});
pathForLineWidth.updateTransform();
valueLineWidth /= pathForLineWidth.getLineScale();
valueLineWidth *= symbolScale[opt.valueDim.index];
}
output.valueLineWidth = valueLineWidth;
}
function prepareLayoutInfo(
itemModel, symbolSize, layout, symbolRepeat, symbolClip, symbolOffset,
symbolPosition, valueLineWidth, boundingLength, repeatCutLength, opt, output
) {
var categoryDim = opt.categoryDim;
var valueDim = opt.valueDim;
var pxSign = output.pxSign;
// Note: rotation will not effect the layout of symbols, because user may
// want symbols to rotate on its center, which should not be translated
// when rotating.
if (symbolRepeat) {
var absBoundingLength = Math.abs(boundingLength);
function createPath(symbolMeta) {
var symbolPatternSize = symbolMeta.symbolPatternSize;
var path = createSymbol(
// Consider texture img, make a big size.
symbolMeta.symbolType,
-symbolPatternSize / 2,
-symbolPatternSize / 2,
symbolPatternSize,
symbolPatternSize,
symbolMeta.color
);
path.attr({
culling: true
});
path.type !== 'image' && path.setStyle({
strokeNoScale: true
});
return path;
}
var index = 0;
var unit = symbolSize[opt.valueDim.index] + valueLineWidth +
symbolMeta.symbolMargin * 2;
updateHoverAnimation(path, symbolMeta);
index++;
});
updateAttr(
path,
{
position: target.position,
scale: [0, 0]
},
{
scale: target.scale,
rotation: target.rotation
},
symbolMeta,
isUpdate
);
// FIXME
// If all emphasis/normal through action.
path
.on('mouseover', onMouseOver)
.on('mouseout', onMouseOut);
updateHoverAnimation(path, symbolMeta);
}
function makeTarget(index) {
var position = pathPosition.slice();
// (start && pxSign > 0) || (end && pxSign < 0): i = repeatTimes - index
// Otherwise: i = index;
var pxSign = symbolMeta.pxSign;
var i = index;
if (symbolMeta.symbolRepeatDirection === 'start' ? pxSign > 0 : pxSign < 0)
{
i = repeatTimes - 1 - index;
}
position[valueDim.index] = unit * (i - repeatTimes / 2 + 0.5) +
pathPosition[valueDim.index];
return {
position: position,
scale: symbolMeta.symbolScale.slice(),
rotation: symbolMeta.rotation
};
}
function onMouseOver() {
eachPath(bar, function (path) {
path.trigger('emphasis');
});
}
function onMouseOut() {
eachPath(bar, function (path) {
path.trigger('normal');
});
}
}
if (!mainPath) {
mainPath = bar.__pictorialMainPath = createPath(symbolMeta);
bundle.add(mainPath);
updateAttr(
mainPath,
{
position: symbolMeta.pathPosition.slice(),
scale: [0, 0],
rotation: symbolMeta.rotation
},
{
scale: symbolMeta.symbolScale.slice()
},
symbolMeta,
isUpdate
);
mainPath
.on('mouseover', onMouseOver)
.on('mouseout', onMouseOut);
}
else {
updateAttr(
mainPath,
null,
{
position: symbolMeta.pathPosition.slice(),
scale: symbolMeta.symbolScale.slice(),
rotation: symbolMeta.rotation
},
symbolMeta,
isUpdate
);
}
updateHoverAnimation(mainPath, symbolMeta);
function onMouseOver() {
this.trigger('emphasis');
}
function onMouseOut() {
this.trigger('normal');
}
}
bar.add(barRect);
}
else {
updateAttr(barRect, null, {shape: rectShape}, symbolMeta, isUpdate);
}
}
if (clipPath) {
updateProps(
clipPath, {shape: clipShape}, animationModel, dataIndex
);
}
else {
clipShape[valueDim.wh] = 0;
clipPath = new Rect({shape: clipShape});
bar.__pictorialBundle.setClipPath(clipPath);
bar.__pictorialClipPath = clipPath;
function getAnimationDelayParams(path) {
// The order is the same as the z-order, see `symbolRepeatDiretion`.
return {
index: path.__pictorialAnimationIndex,
count: path.__pictorialRepeatTimes
};
}
function isAnimationEnabled() {
// `animation` prop can be set on itemModel in pictorial bar chart.
return this.parentModel.isAnimationEnabled() && !!this.getShallow('animation');
}
if (symbolMeta.symbolRepeat) {
createOrUpdateRepeatSymbols(bar, opt, symbolMeta);
}
else {
createOrUpdateSingleSymbol(bar, opt, symbolMeta);
}
return bar;
}
updateProps(
bundle, {position: symbolMeta.bundlePosition.slice()}, animationModel,
dataIndex
);
if (symbolMeta.symbolRepeat) {
createOrUpdateRepeatSymbols(bar, opt, symbolMeta, true);
}
else {
createOrUpdateSingleSymbol(bar, opt, symbolMeta, true);
}
data.setItemGraphicEl(dataIndex, null);
}
setLabel(
barRect.style, barRectHoverStyle, itemModel,
color, opt.seriesModel, dataIndex, barPositionOutside
);
setHoverStyle(barRect, barRectHoverStyle);
}
function toIntTimes(times) {
var roundedTimes = Math.round(times);
// Escapse accurate error
return Math.abs(times - roundedTimes) < 1e-4
? roundedTimes
: Math.ceil(times);
}
/**
* @constructor module:echarts/coord/single/SingleAxis
* @extends {module:echarts/coord/Axis}
* @param {string} dim
* @param {*} scale
* @param {Array.<number>} coordExtent
* @param {string} axisType
* @param {string} position
*/
var SingleAxis = function (dim, scale, coordExtent, axisType, position) {
/**
* Axis type
* - 'category'
* - 'value'
* - 'time'
* - 'log'
* @type {string}
*/
this.type = axisType || 'value';
/**
* Axis position
* - 'top'
* - 'bottom'
* - 'left'
* - 'right'
* @type {string}
*/
this.position = position || 'bottom';
/**
* Axis orient
* - 'horizontal'
* - 'vertical'
* @type {[type]}
*/
this.orient = null;
/**
* @type {number}
*/
this._labelInterval = null;
};
SingleAxis.prototype = {
constructor: SingleAxis,
/**
* Axis model
* @type {module:echarts/coord/single/AxisModel}
*/
model: null,
/**
* Judge the orient of the axis.
* @return {boolean}
*/
isHorizontal: function () {
var position = this.position;
return position === 'top' || position === 'bottom';
},
/**
* @override
*/
pointToData: function (point, clamp) {
return this.coordinateSystem.pointToData(point, clamp)[0];
},
/**
* Convert the local coord(processed by dataToCoord())
* to global coord(concrete pixel coord).
* designated by module:echarts/coord/single/Single.
* @type {Function}
*/
toGlobalCoord: null,
/**
* Convert the global coord to local coord.
* designated by module:echarts/coord/single/Single.
* @type {Function}
*/
toLocalCoord: null
};
inherits(SingleAxis, Axis);
/**
* Single coordinates system.
*/
/**
* Create a single coordinates system.
*
* @param {module:echarts/coord/single/AxisModel} axisModel
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
*/
function Single(axisModel, ecModel, api) {
/**
* @type {string}
* @readOnly
*/
this.dimension = 'single';
/**
* Add it just for draw tooltip.
*
* @type {Array.<string>}
* @readOnly
*/
this.dimensions = ['single'];
/**
* @private
* @type {module:echarts/coord/single/SingleAxis}.
*/
this._axis = null;
/**
* @private
* @type {module:zrender/core/BoundingRect}
*/
this._rect;
/**
* @type {module:echarts/coord/single/AxisModel}
*/
this.model = axisModel;
}
Single.prototype = {
type: 'singleAxis',
axisPointerEnabled: true,
constructor: Single,
/**
* Initialize single coordinate system.
*
* @param {module:echarts/coord/single/AxisModel} axisModel
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
* @private
*/
_init: function (axisModel, ecModel, api) {
axisModel.axis = axis;
axis.model = axisModel;
axis.coordinateSystem = this;
this._axis = axis;
},
/**
* Update axis scale after data processed
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
*/
update: function (ecModel, api) {
ecModel.eachSeries(function (seriesModel) {
if (seriesModel.coordinateSystem === this) {
var data = seriesModel.getData();
each$1(data.mapDimension(this.dimension, true), function (dim) {
this._axis.scale.unionExtentFromData(data, dim);
}, this);
niceScaleExtent(this._axis.scale, this._axis.model);
}
}, this);
},
/**
* Resize the single coordinate system.
*
* @param {module:echarts/coord/single/AxisModel} axisModel
* @param {module:echarts/ExtensionAPI} api
*/
resize: function (axisModel, api) {
this._rect = getLayoutRect(
{
left: axisModel.get('left'),
top: axisModel.get('top'),
right: axisModel.get('right'),
bottom: axisModel.get('bottom'),
width: axisModel.get('width'),
height: axisModel.get('height')
},
{
width: api.getWidth(),
height: api.getHeight()
}
);
this._adjustAxis();
},
/**
* @return {module:zrender/core/BoundingRect}
*/
getRect: function () {
return this._rect;
},
/**
* @private
*/
_adjustAxis: function () {
},
/**
* @param {module:echarts/coord/single/SingleAxis} axis
* @param {number} coordBase
*/
_updateAxisTransform: function (axis, coordBase) {
axis.toGlobalCoord = isHorizontal
? function (coord) {
return coord + coordBase;
}
: function (coord) {
return extentSum - coord + coordBase;
};
axis.toLocalCoord = isHorizontal
? function (coord) {
return coord - coordBase;
}
: function (coord) {
return extentSum - coord + coordBase;
};
},
/**
* Get axis.
*
* @return {module:echarts/coord/single/SingleAxis}
*/
getAxis: function () {
return this._axis;
},
/**
* Get axis, add it just for draw tooltip.
*
* @return {[type]} [description]
*/
getBaseAxis: function () {
return this._axis;
},
/**
* @return {Array.<module:echarts/coord/Axis>}
*/
getAxes: function () {
return [this._axis];
},
/**
* @return {Object} {baseAxes: [], otherAxes: []}
*/
getTooltipAxes: function () {
return {baseAxes: [this.getAxis()]};
},
/**
* If contain point.
*
* @param {Array.<number>} point
* @return {boolean}
*/
containPoint: function (point) {
var rect = this.getRect();
var axis = this.getAxis();
var orient = axis.orient;
if (orient === 'horizontal') {
return axis.contain(axis.toLocalCoord(point[0]))
&& (point[1] >= rect.y && point[1] <= (rect.y + rect.height));
}
else {
return axis.contain(axis.toLocalCoord(point[1]))
&& (point[0] >= rect.y && point[0] <= (rect.y + rect.height));
}
},
/**
* @param {Array.<number>} point
* @return {Array.<number>}
*/
pointToData: function (point) {
var axis = this.getAxis();
return [axis.coordToData(axis.toLocalCoord(
point[axis.orient === 'horizontal' ? 0 : 1]
))];
},
/**
* Convert the series data to concrete point.
*
* @param {number|Array.<number>} val
* @return {Array.<number>}
*/
dataToPoint: function (val) {
var axis = this.getAxis();
var rect = this.getRect();
var pt = [];
var idx = axis.orient === 'horizontal' ? 0 : 1;
pt[idx] = axis.toGlobalCoord(axis.dataToCoord(+val));
pt[1 - idx] = idx === 0 ? (rect.y + rect.height / 2) : (rect.x + rect.width
/ 2);
return pt;
}
};
/**
* Single coordinate system creator.
*/
/**
* Create single coordinate system and inject it into seriesModel.
*
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
* @return {Array.<module:echarts/coord/single/Single>}
*/
function create$3(ecModel, api) {
var singles = [];
});
ecModel.eachSeries(function (seriesModel) {
if (seriesModel.get('coordinateSystem') === 'singleAxis') {
var singleAxisModel = ecModel.queryComponents({
mainType: 'singleAxis',
index: seriesModel.get('singleAxisIndex'),
id: seriesModel.get('singleAxisId')
})[0];
seriesModel.coordinateSystem = singleAxisModel &&
singleAxisModel.coordinateSystem;
}
});
return singles;
}
CoordinateSystemManager.register('single', {
create: create$3,
dimensions: Single.prototype.dimensions
});
/**
* @param {Object} opt {labelInside}
* @return {Object} {
* position, rotation, labelDirection, labelOffset,
* tickDirection, labelRotate, labelInterval, z2
* }
*/
function layout$2 (axisModel, opt) {
opt = opt || {};
var single = axisModel.coordinateSystem;
var axis = axisModel.axis;
var layout = {};
var positionMap = {
horizontal: {top: rectBound[2], bottom: rectBound[3]},
vertical: {left: rectBound[0], right: rectBound[1]}
};
layout.position = [
orient === 'vertical'
? positionMap.vertical[axisPosition]
: rectBound[0],
orient === 'horizontal'
? positionMap.horizontal[axisPosition]
: rectBound[3]
];
layout.labelDirection = layout.tickDirection
= layout.nameDirection
= directionMap[axisPosition];
if (axisModel.get('axisTick.inside')) {
layout.tickDirection = -layout.tickDirection;
}
if (retrieve(opt.labelInside, axisModel.get('axisLabel.inside'))) {
layout.labelDirection = -layout.labelDirection;
}
layout.labelInterval = axis.getLabelInterval();
layout.z2 = 1;
return layout;
}
var axisBuilderAttrs$2 = [
'axisLine', 'axisTickLabel', 'axisName'
];
type: 'singleAxis',
axisPointerClass: 'SingleAxisPointer',
group.removeAll();
group.add(axisBuilder.getGroup());
if (axisModel.get(selfBuilderAttr + '.show')) {
this['_' + selfBuilderAttr](axisModel, layout.labelInterval);
}
if (axis.scale.isBlank()) {
return;
}
var p1 = [];
var p2 = [];
type: 'singleAxis',
layoutMode: 'box',
/**
* @type {module:echarts/coord/single/SingleAxis}
*/
axis: null,
/**
* @type {module:echarts/coord/single/Single}
*/
coordinateSystem: null,
/**
* @override
*/
getCoordSysModel: function () {
return this;
}
});
var defaultOption$2 = {
left: '5%',
top: '5%',
right: '5%',
bottom: '5%',
type: 'value',
position: 'bottom',
orient: 'horizontal',
axisLine: {
show: true,
lineStyle: {
width: 2,
type: 'solid'
}
},
axisTick: {
show: true,
length: 6,
lineStyle: {
width: 2
}
},
axisLabel: {
show: true,
interval: 'auto'
},
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
opacity: 0.2
}
}
};
merge(AxisModel$4.prototype, axisModelCommonMixin);
/**
* @param {Object} finder contains {seriesIndex, dataIndex, dataIndexInside}
* @param {module:echarts/model/Global} ecModel
* @return {Object} {point: [x, y], el: ...} point Will not be null.
*/
var findPointFromSeries = function (finder, ecModel) {
var point = [];
var seriesIndex = finder.seriesIndex;
var seriesModel;
if (seriesIndex == null || !(
seriesModel = ecModel.getSeriesByIndex(seriesIndex)
)) {
return {point: []};
}
var el = data.getItemGraphicEl(dataIndex);
var coordSys = seriesModel.coordinateSystem;
if (seriesModel.getTooltipPosition) {
point = seriesModel.getTooltipPosition(dataIndex) || [];
}
else if (coordSys && coordSys.dataToPoint) {
point = coordSys.dataToPoint(
data.getValues(
map(coordSys.dimensions, function (dim) {
return data.mapDimension(dim);
}), dataIndex, true
)
) || [];
}
else if (el) {
// Use graphic bounding rect
var rect = el.getBoundingRect().clone();
rect.applyTransform(el.transform);
point = [
rect.x + rect.width / 2,
rect.y + rect.height / 2
];
}
/**
* Basic logic: check all axis, if they do not demand show/highlight,
* then hide/downplay them.
*
* @param {Object} coordSysAxesInfo
* @param {Object} payload
* @param {string} [payload.currTrigger] 'click' | 'mousemove' | 'leave'
* @param {Array.<number>} [payload.x] x and y, which are mandatory, specify a
point to
* trigger axisPointer and tooltip.
* @param {Array.<number>} [payload.y] x and y, which are mandatory, specify a
point to
* trigger axisPointer and tooltip.
* @param {Object} [payload.seriesIndex] finder, optional, restrict target axes.
* @param {Object} [payload.dataIndex] finder, restrict target axes.
* @param {Object} [payload.axesInfo] finder, restrict target axes.
* [{
* axisDim: 'x'|'y'|'angle'|...,
* axisIndex: ...,
* value: ...
* }, ...]
* @param {Function} [payload.dispatchAction]
* @param {Object} [payload.tooltipOption]
* @param {Object|Array.<number>|Function} [payload.position] Tooltip position,
* which can be specified in dispatchAction
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
* @return {Object} content of event obj for echarts.connect.
*/
var axisTrigger = function (payload, ecModel, api) {
var currTrigger = payload.currTrigger;
var point = [payload.x, payload.y];
var finder = payload;
var dispatchAction = payload.dispatchAction || bind(api.dispatchAction, api);
var coordSysAxesInfo = ecModel.getComponent('axisPointer').coordSysAxesInfo;
// Pending
// See #6121. But we are not able to reproduce it yet.
if (!coordSysAxesInfo) {
return;
}
if (illegalPoint(point)) {
// Used in the default behavior of `connection`: use the sample seriesIndex
// and dataIndex. And also used in the tooltipView trigger.
point = findPointFromSeries({
seriesIndex: finder.seriesIndex,
// Do not use dataIndexInside from other ec instance.
// FIXME: auto detect it?
dataIndex: finder.dataIndex
}, ecModel).point;
}
var isIllegalPoint = illegalPoint(point);
return outputFinder;
};
if (axis.scale.isBlank() || !axis.containData(newValue)) {
return;
}
if (!axisInfo.involveSeries) {
updaters.showPointer(axisInfo, newValue);
return;
}
if (series.getAxisTooltipData) {
var result = series.getAxisTooltipData(dataDim, value, axis);
dataIndices = result.dataIndices;
seriesNestestValue = result.nestestValue;
}
else {
dataIndices = series.getData().indicesOfNearest(
dataDim[0],
value,
// Add a threshold to avoid find the wrong dataIndex
// when data length is not same.
// false,
axis.type === 'category' ? 0.5 : null
);
if (!dataIndices.length) {
return;
}
seriesNestestValue = series.getData().get(dataDim[0], dataIndices[0]);
}
return {
payloadBatch: payloadBatch,
snapToValue: snapToValue
};
}
coordSysItem.dataByAxis.push({
axisDim: axis.dim,
axisIndex: axisModel.componentIndex,
axisType: axisModel.type,
axisId: axisModel.id,
value: value,
// Caustion: viewHelper.getValueLabel is actually on "view stage", which
// depends that all models have been updated. So it should not be performed
// here. Considering axisPointerModel used here is volatile, which is hard
// to be retrieve in TooltipView, we prepare parameters here.
valueLabelOpt: {
precision: axisPointerModel.get('label.precision'),
formatter: axisPointerModel.get('label.formatter')
},
seriesDataIndices: payloadBatch.slice()
});
}
if (valItem) {
!axisInfo.useHandle && (option.status = 'show');
option.value = valItem.value;
// For label formatter param and highlight.
option.seriesDataIndices = (valItem.payloadBatch || []).slice();
}
// When always show (e.g., handle used), remain
// original value and status.
else {
// If hide, value still need to be set, consider
// click legend to toggle axis blank.
!axisInfo.useHandle && (option.status = 'hide');
}
// In most case only one axis (or event one series is used). It is
// convinient to fetch payload.seriesIndex and payload.dataIndex
// dirtectly. So put the first seriesIndex and dataIndex of the first
// axis on the payload.
var sampleItem = ((dataByCoordSys.list[0].dataByAxis[0] ||
{}).seriesDataIndices || [])[0] || {};
dispatchAction({
type: 'showTip',
escapeConnect: true,
x: point[0],
y: point[1],
tooltipOption: payload.tooltipOption,
position: payload.position,
dataIndexInside: sampleItem.dataIndexInside,
dataIndex: sampleItem.dataIndex,
seriesIndex: sampleItem.seriesIndex,
dataByCoordSys: dataByCoordSys.list
});
}
var zr = api.getZr();
var highDownKey = 'axisPointerLastHighlights';
var lastHighlights = inner$6(zr)[highDownKey] || {};
var newHighlights = inner$6(zr)[highDownKey] = {};
// Diff.
var toHighlight = [];
var toDownplay = [];
each$1(lastHighlights, function (batchItem, key) {
!newHighlights[key] && toDownplay.push(batchItem);
});
each$1(newHighlights, function (batchItem, key) {
!lastHighlights[key] && toHighlight.push(batchItem);
});
function makeMapperParam(axisInfo) {
var axisModel = axisInfo.axis.model;
var item = {};
var dim = item.axisDim = axisInfo.axis.dim;
item.axisIndex = item[dim + 'AxisIndex'] = axisModel.componentIndex;
item.axisName = item[dim + 'AxisName'] = axisModel.name;
item.axisId = item[dim + 'AxisId'] = axisModel.id;
return item;
}
function illegalPoint(point) {
return !point || point[0] == null || isNaN(point[0]) || point[1] == null ||
isNaN(point[1]);
}
type: 'axisPointer',
coordSysAxesInfo: null,
defaultOption: {
// 'auto' means that show when triggered by tooltip or handle.
show: 'auto',
// 'click' | 'mousemove' | 'none'
triggerOn: null, // set default in AxisPonterView.js
zlevel: 0,
z: 50,
type: 'line',
// axispointer triggered by tootip determine snap automatically,
// see `modelHelper`.
snap: false,
triggerTooltip: true,
value: null,
status: null, // Init value depends on whether handle is used.
lineStyle: {
color: '#aaa',
width: 1,
type: 'solid'
},
shadowStyle: {
color: 'rgba(150,150,150,0.3)'
},
label: {
show: true,
formatter: null, // string | Function
precision: 'auto', // Or a number like 0, 1, 2 ...
margin: 3,
color: '#fff',
padding: [5, 7, 5, 7],
backgroundColor: 'auto', // default: axis line color
borderColor: null,
borderWidth: 0,
shadowBlur: 3,
shadowColor: '#aaa'
// Considering applicability, common style should
// better not have shadowOffset.
// shadowOffsetX: 0,
// shadowOffsetY: 2
},
handle: {
show: false,
icon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-
8.8,9.4c0,5,3.9,9.1,8.8,9.4h1.3c4.9-0.3,8.8-4.4,8.8-
9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7v-1.2h6.6z M13.3,22H6.7v-1.2h6.6z
M13.3,19.6H6.7v-1.2h6.6z', // jshint ignore:line
size: 45,
// handle margin is from symbol center to axis, which is stable when
circular move.
margin: 50,
// color: '#1b8bbd'
// color: '#2f4554'
color: '#333',
shadowBlur: 3,
shadowColor: '#aaa',
shadowOffsetX: 0,
shadowOffsetY: 2,
});
/**
* @param {string} key
* @param {module:echarts/ExtensionAPI} api
* @param {Function} handler
* param: {string} currTrigger
* param: {Array.<number>} point
*/
function register(key, api, handler) {
if (env$1.node) {
return;
}
var zr = api.getZr();
inner$7(zr).records || (inner$7(zr).records = {});
initGlobalListeners(zr, api);
inner$7(zr).initialized = true;
dispatchTooltipFinally(dis.pendings, api);
});
}
}
var actuallyPayload;
if (showLen) {
actuallyPayload = pendings.showTip[showLen - 1];
}
else if (hideLen) {
actuallyPayload = pendings.hideTip[hideLen - 1];
}
if (actuallyPayload) {
actuallyPayload.dispatchAction = null;
api.dispatchAction(actuallyPayload);
}
}
function makeDispatchAction(api) {
var pendings = {
showTip: [],
hideTip: []
};
// FIXME
// better approach?
// 'showTip' and 'hideTip' can be triggered by axisPointer and tooltip,
// which may be conflict, (axisPointer call showTip but tooltip call hideTip);
// So we have to add "final stage" to merge those dispatched actions.
var dispatchAction = function (payload) {
var pendingList = pendings[payload.type];
if (pendingList) {
pendingList.push(payload);
}
else {
payload.dispatchAction = dispatchAction;
api.dispatchAction(payload);
}
};
return {
dispatchAction: dispatchAction,
pendings: pendings
};
}
/**
* @param {string} key
* @param {module:echarts/ExtensionAPI} api
*/
function unregister(key, api) {
if (env$1.node) {
return;
}
var zr = api.getZr();
var record = (inner$7(zr).records || {})[key];
if (record) {
inner$7(zr).records[key] = null;
}
}
type: 'axisPointer',
/**
* @override
*/
remove: function (ecModel, api) {
unregister(api.getZr(), 'axisPointer');
AxisPointerView.superApply(this._model, 'remove', arguments);
},
/**
* @override
*/
dispose: function (ecModel, api) {
unregister('axisPointer', api);
AxisPointerView.superApply(this._model, 'dispose', arguments);
}
});
/**
* Base axis pointer class in 2D.
* Implemenents {module:echarts/component/axis/IAxisPointer}.
*/
function BaseAxisPointer () {
}
BaseAxisPointer.prototype = {
/**
* @private
*/
_group: null,
/**
* @private
*/
_lastGraphicKey: null,
/**
* @private
*/
_handle: null,
/**
* @private
*/
_dragging: false,
/**
* @private
*/
_lastValue: null,
/**
* @private
*/
_lastStatus: null,
/**
* @private
*/
_payloadInfo: null,
/**
* In px, arbitrary value. Do not set too small,
* no animation is ok for most cases.
* @protected
*/
animationThreshold: 15,
/**
* @implement
*/
render: function (axisModel, axisPointerModel, api, forceRender) {
var value = axisPointerModel.get('value');
var status = axisPointerModel.get('status');
if (!group) {
group = this._group = new Group();
this.createPointerEl(group, elOption, axisModel, axisPointerModel);
this.createLabelEl(group, elOption, axisModel, axisPointerModel);
api.getZr().add(group);
}
else {
var doUpdateProps = curry(updateProps$1, axisPointerModel,
moveAnimation);
this.updatePointerEl(group, elOption, doUpdateProps, axisPointerModel);
this.updateLabelEl(group, elOption, doUpdateProps, axisPointerModel);
}
this._renderHandle(value);
},
/**
* @implement
*/
remove: function (api) {
this.clear(api);
},
/**
* @implement
*/
dispose: function (api) {
this.clear(api);
},
/**
* @protected
*/
determineAnimation: function (axisModel, axisPointerModel) {
var animation = axisPointerModel.get('animation');
var axis = axisModel.axis;
var isCategoryAxis = axis.type === 'category';
var useSnap = axisPointerModel.get('snap');
return false;
}
/**
* add {pointer, label, graphicKey} to elOption
* @protected
*/
makeElOption: function (elOption, value, axisModel, axisPointerModel, api) {
// Shoule be implemenented by sub-class.
},
/**
* @protected
*/
createPointerEl: function (group, elOption, axisModel, axisPointerModel) {
var pointerOption = elOption.pointer;
if (pointerOption) {
var pointerEl = inner$8(group).pointerEl = new
graphic[pointerOption.type](
clone$4(elOption.pointer)
);
group.add(pointerEl);
}
},
/**
* @protected
*/
createLabelEl: function (group, elOption, axisModel, axisPointerModel) {
if (elOption.label) {
var labelEl = inner$8(group).labelEl = new Rect(
clone$4(elOption.label)
);
group.add(labelEl);
updateLabelShowHide(labelEl, axisPointerModel);
}
},
/**
* @protected
*/
updatePointerEl: function (group, elOption, updateProps$$1) {
var pointerEl = inner$8(group).pointerEl;
if (pointerEl) {
pointerEl.setStyle(elOption.pointer.style);
updateProps$$1(pointerEl, {shape: elOption.pointer.shape});
}
},
/**
* @protected
*/
updateLabelEl: function (group, elOption, updateProps$$1, axisPointerModel) {
var labelEl = inner$8(group).labelEl;
if (labelEl) {
labelEl.setStyle(elOption.label.style);
updateProps$$1(labelEl, {
// Consider text length change in vertical axis, animation should
// be used on shape, otherwise the effect will be weird.
shape: elOption.label.shape,
position: elOption.label.position
});
updateLabelShowHide(labelEl, axisPointerModel);
}
},
/**
* @private
*/
_renderHandle: function (value) {
if (this._dragging || !this.updateHandleTransform) {
return;
}
var axisPointerModel = this._axisPointerModel;
var zr = this._api.getZr();
var handle = this._handle;
var handleModel = axisPointerModel.getModel('handle');
var isInit;
if (!this._handle) {
isInit = true;
handle = this._handle = createIcon(
handleModel.get('icon'),
{
cursor: 'move',
draggable: true,
onmousemove: function (e) {
// Fot mobile devicem, prevent screen slider on the button.
stop(e.event);
},
onmousedown: bind$2(this._onHandleDragMove, this, 0, 0),
drift: bind$2(this._onHandleDragMove, this),
ondragend: bind$2(this._onHandleDragEnd, this)
}
);
zr.add(handle);
}
// update style
var includeStyles = [
'color', 'borderColor', 'borderWidth', 'opacity',
'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY'
];
handle.setStyle(handleModel.getItemStyle(null, includeStyles));
// update position
var handleSize = handleModel.get('size');
if (!isArray(handleSize)) {
handleSize = [handleSize, handleSize];
}
handle.attr('scale', [handleSize[0] / 2, handleSize[1] / 2]);
createOrUpdate(
this,
'_doDispatchAxisPointer',
handleModel.get('throttle') || 0,
'fixRate'
);
this._moveHandleToValue(value, isInit);
},
/**
* @private
*/
_moveHandleToValue: function (value, isInit) {
updateProps$1(
this._axisPointerModel,
!isInit && this._moveAnimation,
this._handle,
getHandleTransProps(this.getHandleTransform(
value, this._axisModel, this._axisPointerModel
))
);
},
/**
* @private
*/
_onHandleDragMove: function (dx, dy) {
var handle = this._handle;
if (!handle) {
return;
}
this._dragging = true;
handle.stopAnimation();
handle.attr(getHandleTransProps(trans));
inner$8(handle).lastProp = null;
this._doDispatchAxisPointer();
},
/**
* Throttled method.
* @private
*/
_doDispatchAxisPointer: function () {
var handle = this._handle;
if (!handle) {
return;
}
/**
* @private
*/
_onHandleDragEnd: function (moveAnimation) {
this._dragging = false;
var handle = this._handle;
if (!handle) {
return;
}
// For the effect: tooltip will be shown when finger holding on handle
// button, and will be hidden after finger left handle button.
this._api.dispatchAction({
type: 'hideTip'
});
},
/**
* Should be implemenented by sub-class if support `handle`.
* @protected
* @param {number} value
* @param {module:echarts/model/Model} axisModel
* @param {module:echarts/model/Model} axisPointerModel
* @return {Object} {position: [x, y], rotation: 0}
*/
getHandleTransform: null,
/**
* * Should be implemenented by sub-class if support `handle`.
* @protected
* @param {Object} transform {position, rotation}
* @param {Array.<number>} delta [dx, dy]
* @param {module:echarts/model/Model} axisModel
* @param {module:echarts/model/Model} axisPointerModel
* @return {Object} {position: [x, y], rotation: 0, cursorPoint: [x, y]}
*/
updateHandleTransform: null,
/**
* @private
*/
clear: function (api) {
this._lastValue = null;
this._lastStatus = null;
var zr = api.getZr();
var group = this._group;
var handle = this._handle;
if (zr && group) {
this._lastGraphicKey = null;
group && zr.remove(group);
handle && zr.remove(handle);
this._group = null;
this._handle = null;
this._payloadInfo = null;
}
},
/**
* @protected
*/
doClear: function () {
// Implemented by sub-class if necessary.
},
/**
* @protected
* @param {Array.<number>} xy
* @param {Array.<number>} wh
* @param {number} [xDimIndex=0] or 1
*/
buildLabel: function (xy, wh, xDimIndex) {
xDimIndex = xDimIndex || 0;
return {
x: xy[xDimIndex],
y: xy[1 - xDimIndex],
width: wh[xDimIndex],
height: wh[1 - xDimIndex]
};
}
};
BaseAxisPointer.prototype.constructor = BaseAxisPointer;
function getHandleTransProps(trans) {
return {
position: trans.position.slice(),
rotation: trans.rotation || 0
};
}
enableClassExtend(BaseAxisPointer);
/**
* @param {module:echarts/model/Model} axisPointerModel
*/
function buildElStyle(axisPointerModel) {
var axisPointerType = axisPointerModel.get('type');
var styleModel = axisPointerModel.getModel(axisPointerType + 'Style');
var style;
if (axisPointerType === 'line') {
style = styleModel.getLineStyle();
style.fill = null;
}
else if (axisPointerType === 'shadow') {
style = styleModel.getAreaStyle();
style.stroke = null;
}
return style;
}
/**
* @param {Function} labelPos {align, verticalAlign, position}
*/
function buildLabelElOption(
elOption, axisModel, axisPointerModel, api, labelPos
) {
var value = axisPointerModel.get('value');
var text = getValueLabel(
value, axisModel.axis, axisModel.ecModel,
axisPointerModel.get('seriesDataIndices'),
{
precision: axisPointerModel.get('label.precision'),
formatter: axisPointerModel.get('label.formatter')
}
);
var labelModel = axisPointerModel.getModel('label');
var paddings = normalizeCssArray$1(labelModel.get('padding') || 0);
// Adjust by align.
var align = labelPos.align;
align === 'right' && (position[0] -= width);
align === 'center' && (position[0] -= width / 2);
var verticalAlign = labelPos.verticalAlign;
verticalAlign === 'bottom' && (position[1] -= height);
verticalAlign === 'middle' && (position[1] -= height / 2);
elOption.label = {
shape: {x: 0, y: 0, width: width, height: height, r:
labelModel.get('borderRadius')},
position: position.slice(),
// TODO: rich
style: {
text: text,
textFont: font,
textFill: labelModel.getTextColor(),
textPosition: 'inside',
fill: bgColor,
stroke: labelModel.get('borderColor') || 'transparent',
lineWidth: labelModel.get('borderWidth') || 0,
shadowBlur: labelModel.get('shadowBlur'),
shadowColor: labelModel.get('shadowColor'),
shadowOffsetX: labelModel.get('shadowOffsetX'),
shadowOffsetY: labelModel.get('shadowOffsetY')
},
// Lable should be over axisPointer.
z2: 10
};
}
if (formatter) {
var params = {
value: getAxisRawValue(axis, value),
seriesData: []
};
each$1(seriesDataIndices, function (idxItem) {
var series = ecModel.getSeriesByIndex(idxItem.seriesIndex);
var dataIndex = idxItem.dataIndexInside;
var dataParams = series && series.getDataParams(dataIndex);
dataParams && params.seriesData.push(dataParams);
});
if (isString(formatter)) {
text = formatter.replace('{value}', text);
}
else if (isFunction$1(formatter)) {
text = formatter(params);
}
}
return text;
}
/**
* @param {module:echarts/coord/Axis} axis
* @param {number} value
* @param {Object} layoutInfo {
* rotation, position, labelOffset, labelDirection, labelMargin
* }
*/
function getTransformedPosition (axis, value, layoutInfo) {
var transform = create$1();
rotate(transform, transform, layoutInfo.rotation);
translate(transform, transform, layoutInfo.position);
return applyTransform$1([
axis.dataToCoord(value),
(layoutInfo.labelOffset || 0)
+ (layoutInfo.labelDirection || 1) * (layoutInfo.labelMargin || 0)
], transform);
}
function buildCartesianSingleLabelElOption(
value, elOption, layoutInfo, axisModel, axisPointerModel, api
) {
var textLayout = AxisBuilder.innerTextLayout(
layoutInfo.rotation, 0, layoutInfo.labelDirection
);
layoutInfo.labelMargin = axisPointerModel.get('label.margin');
buildLabelElOption(elOption, axisModel, axisPointerModel, api, {
position: getTransformedPosition(axisModel.axis, value, layoutInfo),
align: textLayout.textAlign,
verticalAlign: textLayout.textVerticalAlign
});
}
/**
* @param {Array.<number>} p1
* @param {Array.<number>} p2
* @param {number} [xDimIndex=0] or 1
*/
function makeLineShape(p1, p2, xDimIndex) {
xDimIndex = xDimIndex || 0;
return {
x1: p1[xDimIndex],
y1: p1[1 - xDimIndex],
x2: p2[xDimIndex],
y2: p2[1 - xDimIndex]
};
}
/**
* @param {Array.<number>} xy
* @param {Array.<number>} wh
* @param {number} [xDimIndex=0] or 1
*/
function makeRectShape(xy, wh, xDimIndex) {
xDimIndex = xDimIndex || 0;
return {
x: xy[xDimIndex],
y: xy[1 - xDimIndex],
width: wh[xDimIndex],
height: wh[1 - xDimIndex]
};
}
/**
* @override
*/
getHandleTransform: function (value, axisModel, axisPointerModel) {
var layoutInfo = layout$1(axisModel.axis.grid.model, axisModel, {
labelInside: false
});
layoutInfo.labelMargin = axisPointerModel.get('handle.margin');
return {
position: getTransformedPosition(axisModel.axis, value, layoutInfo),
rotation: layoutInfo.rotation + (layoutInfo.labelDirection < 0 ?
Math.PI : 0)
};
},
/**
* @override
*/
updateHandleTransform: function (transform, delta, axisModel, axisPointerModel)
{
var axis = axisModel.axis;
var grid = axis.grid;
var axisExtent = axis.getGlobalExtent(true);
var otherExtent = getCartesian(grid,
axis).getOtherAxis(axis).getGlobalExtent();
var dimIndex = axis.dim === 'x' ? 0 : 1;
// Make tooltip do not overlap axisPointer and in the middle of the grid.
var tooltipOptions = [{verticalAlign: 'middle'}, {align: 'center'}];
return {
position: currPosition,
rotation: transform.rotation,
cursorPoint: cursorPoint,
tooltipOption: tooltipOptions[dimIndex]
};
}
});
var pointerShapeBuilder = {
function getAxisDimIndex(axis) {
return axis.dim === 'x' ? 0 : 1;
}
AxisView.registerAxisPointerClass('CartesianAxisPointer', CartesianAxisPointer);
/**
* @override
*/
makeElOption: function (elOption, value, axisModel, axisPointerModel, api) {
var axis = axisModel.axis;
var coordSys = axis.coordinateSystem;
var otherExtent = getGlobalExtent(coordSys, 1 - getPointDimIndex(axis));
var pixelValue = coordSys.dataToPoint(value)[0];
/**
* @override
*/
getHandleTransform: function (value, axisModel, axisPointerModel) {
var layoutInfo = layout$2(axisModel, {labelInside: false});
layoutInfo.labelMargin = axisPointerModel.get('handle.margin');
return {
position: getTransformedPosition(axisModel.axis, value, layoutInfo),
rotation: layoutInfo.rotation + (layoutInfo.labelDirection < 0 ?
Math.PI : 0)
};
},
/**
* @override
*/
updateHandleTransform: function (transform, delta, axisModel, axisPointerModel)
{
var axis = axisModel.axis;
var coordSys = axis.coordinateSystem;
var dimIndex = getPointDimIndex(axis);
var axisExtent = getGlobalExtent(coordSys, dimIndex);
var currPosition = transform.position;
currPosition[dimIndex] += delta[dimIndex];
currPosition[dimIndex] = Math.min(axisExtent[1], currPosition[dimIndex]);
currPosition[dimIndex] = Math.max(axisExtent[0], currPosition[dimIndex]);
var otherExtent = getGlobalExtent(coordSys, 1 - dimIndex);
var cursorOtherValue = (otherExtent[1] + otherExtent[0]) / 2;
var cursorPoint = [cursorOtherValue, cursorOtherValue];
cursorPoint[dimIndex] = currPosition[dimIndex];
return {
position: currPosition,
rotation: transform.rotation,
cursorPoint: cursorPoint,
tooltipOption: {
verticalAlign: 'middle'
}
};
}
});
var pointerShapeBuilder$1 = {
function getPointDimIndex(axis) {
return axis.isHorizontal() ? 0 : 1;
}
AxisView.registerAxisPointerClass('SingleAxisPointer', SingleAxisPointer);
extendComponentView({
type: 'single'
});
/**
* @file Define the themeRiver view's series model
* @author Deqing Li([email protected])
*/
var DATA_NAME_INDEX = 2;
type: 'series.themeRiver',
dependencies: ['singleAxis'],
/**
* @readOnly
* @type {module:zrender/core/util#HashMap}
*/
nameMap: null,
/**
* @override
*/
init: function (option) {
ThemeRiverSeries.superApply(this, 'init', arguments);
// Put this function here is for the sake of consistency of code style.
// Enable legend selection for each data item
// Use a function instead of direct access because data reference may
changed
this.legendDataProvider = function () {
return this.getRawData();
};
},
/**
* If there is no value of a certain point in the time for some event,set it
value to 0.
*
* @param {Array} data initial data in the option
* @return {Array}
*/
fixData: function (data) {
var rawDataLength = data.length;
}
}
}
return data;
},
/**
* @override
* @param {Object} option the initial option that user gived
* @param {module:echarts/model/Model} ecModel the model object for
themeRiver option
* @return {module:echarts/data/List}
*/
getInitialData: function (option, ecModel) {
// ??? TODO design a stage to transfer data for themeRiver and lines?
var data = this.fixData(filterData || []);
var nameList = [];
var nameMap = this.nameMap = createHashMap();
var count = 0;
return list;
},
/**
* The raw data is divided into multiple layers and each layer
* has same name.
*
* @return {Array.<Array.<number>>}
*/
getLayerSeries: function () {
var data = this.getData();
var lenCount = data.count();
var indexArr = [];
return layerSeries;
},
/**
* Get data indices for show tooltip content
*
* @param {Array.<string>|string} dim single coordinate dimension
* @param {number} value axis value
* @param {module:echarts/coord/single/SingleAxis} baseAxis single Axis used
* the themeRiver.
* @return {Object} {dataIndices, nestestValue}
*/
getAxisTooltipData: function (dim, value, baseAxis) {
if (!isArray(dim)) {
dim = dim ? [dim] : [];
}
/**
* @override
* @param {number} dataIndex index of data
*/
formatTooltip: function (dataIndex) {
var data = this.getData();
var htmlName = data.getName(dataIndex);
var htmlValue = data.get(data.mapDimension('value'), dataIndex);
if (isNaN(htmlValue) || htmlValue == null) {
htmlValue = '-';
}
return encodeHTML(htmlName + ' : ' + htmlValue);
},
defaultOption: {
zlevel: 0,
z: 2,
coordinateSystem: 'singleAxis',
// legendHoverLink: true,
singleAxisIndex: 0,
animationEasing: 'linear',
label: {
margin: 4,
textAlign: 'right',
show: true,
position: 'left',
color: '#000',
fontSize: 11
},
emphasis: {
label: {
show: true
}
}
}
});
/**
* @file The file used to draw themeRiver view
* @author Deqing Li([email protected])
*/
extendChartView({
type: 'themeRiver',
init: function () {
this._layers = [];
},
function keyGetter(item) {
return item.name;
}
var dataDiffer = new DataDiffer(
this._layersSeries || [], layerSeries,
keyGetter, keyGetter
);
dataDiffer
.add(bind(process, this, 'add'))
.update(bind(process, this, 'update'))
.remove(bind(process, this, 'remove'))
.execute();
points0.push([x, y0]);
points1.push([x, y0 + y]);
var polygon;
var text;
var textLayout = data.getItemLayout(indices[0]);
var itemModel = data.getItemModel(indices[j - 1]);
var labelModel = itemModel.getModel('label');
var margin = labelModel.get('margin');
if (status === 'add') {
var layerGroup = newLayersGroups[idx] = new Group();
polygon = new Polygon$1({
shape: {
points: points0,
stackedOnPoints: points1,
smooth: 0.4,
stackedOnSmooth: 0.4,
smoothConstraint: false
},
z2: 0
});
text = new Text({
style: {
x: textLayout.x - margin,
y: textLayout.y0 + textLayout.y / 2
}
});
layerGroup.add(polygon);
layerGroup.add(text);
group.add(layerGroup);
polygon.setClipPath(createGridClipShape$3(polygon.getBoundingRect(), seriesModel,
function () {
polygon.removeClipPath();
}));
}
else {
var layerGroup = oldLayersGroups[oldIdx];
polygon = layerGroup.childAt(0);
text = layerGroup.childAt(1);
group.add(layerGroup);
newLayersGroups[idx] = layerGroup;
updateProps(polygon, {
shape: {
points: points0,
stackedOnPoints: points1
}
}, seriesModel);
updateProps(text, {
style: {
x: textLayout.x - margin,
y: textLayout.y0 + textLayout.y / 2
}
}, seriesModel);
}
setTextStyle(text.style, labelModel, {
text: labelModel.get('show')
? seriesModel.getFormattedLabel(indices[j - 1], 'normal')
|| data.getName(indices[j - 1])
: null,
textVerticalAlign: 'middle'
});
polygon.setStyle(extend({
fill: color
}, itemStyleModel.getItemStyle(['color'])));
setHoverStyle(polygon, hoverItemStyleModel.getItemStyle());
}
this._layersSeries = layerSeries;
this._layers = newLayersGroups;
},
dispose: function () {}
});
return rectEl;
}
/**
* @file Using layout algorithm transform the raw data to layout information.
* @author Deqing Li([email protected])
*/
layoutInfo.rect = rect;
layoutInfo.boundaryGap = boundaryGap;
data.setLayout('layoutInfo', layoutInfo);
});
};
/**
* The layout information about themeriver
*
* @param {module:echarts/data/List} data data in the series
* @param {module:echarts/model/Series} seriesModel the model object of themeRiver
series
* @param {number} height value used to compute every series height
*/
function themeRiverLayout$1(data, seriesModel, height) {
if (!data.count()) {
return;
}
var coordSys = seriesModel.coordinateSystem;
// the data in each layer are organized into a series.
var layerSeries = seriesModel.getLayerSeries();
/**
* Compute the baseLine of the rawdata
* Inspired by Lee Byron's paper Stacked Graphs - Geometry & Aesthetics
*
* @param {Array.<Array>} data the points in each layer
* @return {Object}
*/
function computeBaseline(data) {
var layerNum = data.length;
var pointNum = data[0].length;
var sums = [];
var y0 = [];
var max = 0;
var temp;
var base = {};
return base;
}
/**
* @file Visual encoding for themeRiver view
* @author Deqing Li([email protected])
*/
rawData.each(function (rawIndex) {
var name = rawData.getName(rawIndex);
var color = colorList[(seriesModel.nameMap.get(name) - 1) %
colorList.length];
if (idx != null) {
data.setItemVisual(idx, 'color', color);
}
});
});
};
registerLayout(themeRiverLayout);
registerVisual(themeRiverVisual);
registerProcessor(dataFilter('themeRiver'));
SeriesModel.extend({
type: 'series.sunburst',
/**
* @type {module:echarts/data/Tree~Node}
*/
_viewRoot: null,
completeTreeValue$1(root);
treeOption.levels = levels;
optionUpdated: function () {
this.resetViewRoot();
},
/*
* @override
*/
getDataParams: function (dataIndex) {
var params = SeriesModel.prototype.getDataParams.apply(this, arguments);
return params;
},
defaultOption: {
zlevel: 0,
z: 2,
// 默认全局居中
center: ['50%', '50%'],
radius: [0, '75%'],
// 默认顺时针
clockwise: true,
startAngle: 90,
// 最小角度改为 0
minAngle: 0,
percentPrecision: 2,
renderLabelForZeroData: false,
label: {
// could be: 'radial', 'tangential', or 'none'
rotate: 'radial',
show: true,
opacity: 1,
// 'left' is for inner side of inside, and 'right' is for outter
// side for inside
align: 'center',
position: 'inside',
distance: 5,
silent: true,
emphasis: {}
},
itemStyle: {
borderWidth: 1,
borderColor: 'white',
opacity: 1,
emphasis: {},
highlight: {
opacity: 1
},
downplay: {
opacity: 0.9
}
},
data: [],
levels: [],
/**
* Sort order.
*
* Valid values: 'desc', 'asc', null, or callback function.
* 'desc' and 'asc' for descend and ascendant order;
* null for not sorting;
* example of callback function:
* function(nodeA, nodeB) {
* return nodeA.getValue() - nodeB.getValue();
* }
*/
sort: 'desc'
},
getViewRoot: function () {
return this._viewRoot;
},
/**
* @param {module:echarts/data/Tree~Node} [viewRoot]
*/
resetViewRoot: function (viewRoot) {
viewRoot
? (this._viewRoot = viewRoot)
: (viewRoot = this._viewRoot);
if (!viewRoot
|| (viewRoot !== root && !root.contains(viewRoot))
) {
this._viewRoot = root;
}
}
});
/**
* @param {Object} dataNode
*/
function completeTreeValue$1(dataNode) {
// Postorder travel tree.
// If value of none-leaf node is not set,
// calculate it by suming up the value of all children.
var sum = 0;
completeTreeValue$1(child);
sum += childValue;
});
isArray(dataNode.value)
? (dataNode.value[0] = thisValue)
: (dataNode.value = thisValue);
}
var NodeHighlightPolicy = {
NONE: 'none', // not downplay others
DESCENDANT: 'descendant',
ANCESTOR: 'ancestor',
SELF: 'self'
};
var DEFAULT_SECTOR_Z = 2;
var DEFAULT_TEXT_Z = 4;
/**
* Sunburstce of Sunburst including Sector, Label, LabelLine
* @constructor
* @extends {module:zrender/graphic/Group}
*/
function SunburstPiece(node, seriesModel, ecModel) {
Group.call(this);
SunburstPieceProto.updateData = function (
firstCreate,
node,
state,
seriesModel,
ecModel
) {
this.node = node;
node.piece = this;
if (firstCreate) {
var highlightPolicy = seriesModel.getShallow('highlightPolicy');
this._initEvents(sector, node, seriesModel, highlightPolicy);
}
SunburstPieceProto.onNormal = function () {
this.node.hostTree.root.eachNode(function (n) {
if (n.piece) {
n.piece.updateData(false, n, 'normal');
}
});
};
SunburstPieceProto.onHighlight = function () {
this.updateData(false, this.node, 'highlight');
};
SunburstPieceProto.onDownplay = function () {
this.updateData(false, this.node, 'downplay');
};
setLabelStyle(
label.style, label.hoverStyle || {}, normalModel, labelHoverModel,
{
defaultText: labelModel.getShallow('show') ? text : null,
autoColor: visualColor,
useInsideStyle: true
}
);
var r;
var labelPosition = getLabelAttr('position');
var labelPadding = getLabelAttr('distance') || 0;
var textAlign = getLabelAttr('align');
if (labelPosition === 'outside') {
r = layout.r + labelPadding;
textAlign = midAngle > Math.PI / 2 ? 'right' : 'left';
}
else {
if (!textAlign || textAlign === 'center') {
r = (layout.r + layout.r0) / 2;
textAlign = 'center';
}
else if (textAlign === 'left') {
r = layout.r0 + labelPadding;
if (midAngle > Math.PI / 2) {
textAlign = 'right';
}
}
else if (textAlign === 'right') {
r = layout.r - labelPadding;
if (midAngle > Math.PI / 2) {
textAlign = 'left';
}
}
}
label.attr('style', {
text: text,
textAlign: textAlign,
textVerticalAlign: getLabelAttr('verticalAlign') || 'middle',
opacity: getLabelAttr('opacity')
});
function getLabelAttr(name) {
var stateAttr = labelModel.get(name);
if (stateAttr == null) {
return normalModel.get(name);
}
else {
return stateAttr;
}
}
};
SunburstPieceProto._initEvents = function (
sector,
node,
seriesModel,
highlightPolicy
) {
sector.off('mouseover').off('mouseout').off('emphasis').off('normal');
if (seriesModel.isAnimationEnabled()) {
sector
.on('mouseover', onEmphasis)
.on('mouseout', onNormal)
.on('emphasis', onEmphasis)
.on('normal', onNormal)
.on('downplay', onDownplay)
.on('highlight', onHighlight);
}
};
inherits(SunburstPiece, Group);
/**
* Get node color
*
* @param {TreeNode} node the node to get color
* @param {module:echarts/model/Series} seriesModel series
* @param {module:echarts/model/Global} ecModel echarts defaults
*/
function getNodeColor(node, seriesModel, ecModel) {
// Color from visualMap
var visualColor = node.getVisual('color');
var visualMetaList = node.getVisual('visualMeta');
if (!visualMetaList || visualMetaList.length === 0) {
// Use first-generation color if has no visualMap
visualColor = null;
}
/**
* Get index of root in sorted order
*
* @param {TreeNode} node current node
* @return {number} index in root
*/
function getRootId(node) {
var ancestor = node;
while (ancestor.depth > 1) {
ancestor = ancestor.parentNode;
}
type: 'sunburst',
init: function () {
},
this.seriesModel = seriesModel;
this.api = api;
this.ecModel = ecModel;
dualTravel(newChildren, oldChildren);
renderRollUp(virtualRoot, newRoot);
this._initEvents();
this._oldChildren = newChildren;
function getKey(node) {
return node.getId();
}
doRenderNode(newNode, oldNode);
}
}
// For tooltip
data.setItemGraphicEl(newNode.dataIndex, oldNode.piece);
}
else {
// Remove
removeNode(oldNode);
}
}
else if (newNode) {
// Add
var piece = new SunburstPiece(
newNode,
seriesModel,
ecModel
);
group.add(piece);
// For tooltip
data.setItemGraphicEl(newNode.dataIndex, piece);
}
}
}
function removeNode(node) {
if (!node) {
return;
}
if (node.piece) {
group.remove(node.piece);
node.piece = null;
}
}
if (viewRoot.piece._onclickEvent) {
viewRoot.piece.off('click', viewRoot.piece._onclickEvent);
}
var event = function (e) {
that._rootToNode(viewRoot.parentNode);
};
viewRoot.piece._onclickEvent = event;
virtualRoot.piece.on('click', event);
}
else if (virtualRoot.piece) {
// Remove
group.remove(virtualRoot.piece);
virtualRoot.piece = null;
}
}
},
dispose: function () {
},
/**
* @private
*/
_initEvents: function () {
var that = this;
if (this.group._onclickEvent) {
this.group.off('click', this.group._onclickEvent);
}
this.group.on('click', event);
this.group._onclickEvent = event;
},
/**
* @private
*/
_rootToNode: function (node) {
if (node !== this.seriesModel.getViewRoot()) {
this.api.dispatchAction({
type: ROOT_TO_NODE_ACTION,
from: this.uid,
seriesId: this.seriesModel.id,
targetNode: node
});
}
},
/**
* @implement
*/
containPoint: function (point, seriesModel) {
var treeRoot = seriesModel.getData();
var itemLayout = treeRoot.getItemLayout(0);
if (itemLayout) {
var dx = point[0] - itemLayout.cx;
var dy = point[1] - itemLayout.cy;
var radius = Math.sqrt(dx * dx + dy * dy);
return radius <= itemLayout.r && radius >= itemLayout.r0;
}
}
});
/**
* @file Sunburst action
*/
ecModel.eachComponent(
{mainType: 'series', subType: 'sunburst', query: payload},
handleRootToNode
);
if (targetInfo) {
var originViewRoot = model.getViewRoot();
if (originViewRoot) {
payload.direction = aboveViewRoot(originViewRoot,
targetInfo.node)
? 'rollUp' : 'drillDown';
}
model.resetViewRoot(targetInfo.node);
}
}
}
);
registerAction(
{type: HIGHLIGHT_ACTION, update: 'updateView'},
function (payload, ecModel) {
ecModel.eachComponent(
{mainType: 'series', subType: 'sunburst', query: payload},
handleHighlight
);
if (targetInfo) {
payload.highlight = targetInfo.node;
}
}
}
);
registerAction(
{type: UNHIGHLIGHT_ACTION, update: 'updateView'},
function (payload, ecModel) {
ecModel.eachComponent(
{mainType: 'series', subType: 'sunburst', query: payload},
handleUnhighlight
);
if (!isArray(radius)) {
radius = [0, radius];
}
if (!isArray(center)) {
center = [center, center];
}
var validDataCount = 0;
each$1(treeRoot.children, function (child) {
!isNaN(child.getValue()) && validDataCount++;
});
/**
* Render a tree
* @return increased angle
*/
var renderNode = function (node, startAngle) {
if (!node) {
return;
}
// Render self
if (node !== virtualRoot) {
// Tree node is virtual, so it doesn't need to be drawn
var value = node.getValue();
}
else {
node.setLayout({
angle: angle,
startAngle: startAngle,
endAngle: endAngle,
clockwise: clockwise,
cx: cx,
cy: cy,
r0: rStart,
r: rEnd
});
}
// Render children
if (node.children && node.children.length) {
// currentAngle = startAngle;
var siblingAngle = 0;
each$1(node.children, function (node) {
siblingAngle += renderNode(node, startAngle + siblingAngle);
});
}
renderNode(treeRoot, startAngle);
});
};
/**
* Init node children by order and update visual
*
* @param {TreeNode} node root node
* @param {boolean} isAsc if is in ascendant order
*/
function initChildren$1(node, isAsc) {
var children = node.children || [];
/**
* Sort children nodes
*
* @param {TreeNode[]} children children of node to be sorted
* @param {string | function | null} sort sort method
* See SunburstSeries.js for details.
*/
function sort$2(children, sortOrder) {
if (typeof sortOrder === 'function') {
return children.sort(sortOrder);
}
else {
var isAsc = sortOrder === 'asc';
return children.sort(function (a, b) {
var diff = (a.getValue() - b.getValue()) * (isAsc ? 1 : -1);
return diff === 0
? (a.dataIndex - b.dataIndex) * (isAsc ? -1 : 1)
: diff;
});
}
}
registerVisual(curry(dataColor, 'sunburst'));
registerLayout(curry(sunburstLayout, 'sunburst'));
registerProcessor(curry(dataFilter, 'sunburst'));
return {
coordSys: {
type: 'singleAxis',
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height
},
api: {
coord: function (val) {
// do not provide "out" param
return coordSys.dataToPoint(val);
},
size: bind(dataToCoordSize$2, coordSys)
}
};
};
return result;
}, this);
}
return {
coordSys: {
type: 'polar',
cx: coordSys.cx,
cy: coordSys.cy,
r: radius[1],
r0: radius[0]
},
api: {
coord: bind(function (data) {
var radius = radiusAxis.dataToRadius(data[0]);
var angle = angleAxis.dataToAngle(data[1]);
var coord = coordSys.coordToPoint([radius, angle]);
coord.push(radius, angle * Math.PI / 180);
return coord;
}),
size: bind(dataToCoordSize$3, coordSys)
}
};
};
return {
coordSys: {
type: 'calendar',
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
cellWidth: coordSys.getCellWidth(),
cellHeight: coordSys.getCellHeight(),
rangeInfo: {
start: rangeInfo.start,
end: rangeInfo.end,
weeks: rangeInfo.weeks,
dayCount: rangeInfo.allDay
}
},
api: {
coord: function (data, clamp) {
return coordSys.dataToPoint(data, clamp);
}
}
};
};
/**
* To reduce total package size of each coordinate systems, the modules
`prepareCustom`
* of each coordinate systems are not required by each coordinate systems directly,
but
* required by the module `custom`.
*
* prepareInfoForCustomSeries {Function}: optional
* @return {Object} {coordSys: {...}, api: {
* coord: function (data, clamp) {}, // return point in global.
* size: function (dataSize, dataItem) {} // return size of each axis in
coordSys.
* }}
*/
var prepareCustoms = {
cartesian2d: prepareCartesian2d,
geo: prepareGeo,
singleAxis: prepareSingleAxis,
polar: preparePolar,
calendar: prepareCalendar
};
// ------
// Model
// ------
extendSeriesModel({
type: 'series.custom',
dependencies: ['grid', 'polar', 'geo', 'singleAxis', 'calendar'],
defaultOption: {
coordinateSystem: 'cartesian2d', // Can be set as 'none'
zlevel: 0,
z: 2,
legendHoverLink: true
// label: {}
// itemStyle: {}
},
// -----
// View
// -----
extendChartView({
type: 'custom',
/**
* @private
* @type {module:echarts/data/List}
*/
_data: null,
/**
* @override
*/
render: function (customSeries, ecModel, api) {
var oldData = this._data;
var data = customSeries.getData();
var group = this.group;
var renderItem = makeRenderItem(customSeries, data, ecModel, api);
this.group.removeAll();
data.diff(oldData)
.add(function (newIdx) {
createOrUpdate$1(
null, newIdx, renderItem(newIdx), customSeries, group, data
);
})
.update(function (newIdx, oldIdx) {
var el = oldData.getItemGraphicEl(oldIdx);
createOrUpdate$1(
el, newIdx, renderItem(newIdx), customSeries, group, data
);
})
.remove(function (oldIdx) {
var el = oldData.getItemGraphicEl(oldIdx);
el && group.remove(el);
})
.execute();
this._data = data;
},
/**
* @override
*/
dispose: noop
});
function createEl(elOption) {
var graphicType = elOption.type;
var el;
if (__DEV__) {
assert$1(Clz, 'graphic type "' + graphicType + '" can not be found.');
}
el = new Clz();
}
el.__customGraphicType = graphicType;
el.name = elOption.name;
return el;
}
// Init animation.
if (isInit) {
el.style.opacity = 0;
var targetOpacity = elOptionStyle.opacity;
targetOpacity == null && (targetOpacity = 1);
initProps(el, {style: {opacity: targetOpacity}}, animatableModel,
dataIndex);
}
}
if (isInit) {
el.attr(targetProps);
}
else {
updateProps(el, targetProps, animatableModel, dataIndex);
}
if (coordSys) {
if (__DEV__) {
assert$1(renderItem, 'series.render is required.');
assert$1(
coordSys.prepareCustoms || prepareCustoms[coordSys.type],
'This coordSys does not support custom series.'
);
}
prepareResult = coordSys.prepareCustoms
? coordSys.prepareCustoms()
: prepareCustoms[coordSys.type](coordSys);
}
var userParams = {
context: {},
seriesId: customSeries.id,
seriesName: customSeries.name,
seriesIndex: customSeries.seriesIndex,
coordSys: prepareResult.coordSys,
dataInsideLength: data.count(),
encode: wrapEncodeDef(customSeries.getData())
};
currDirty = false;
}
}
/**
* @public
* @param {number|string} dim
* @param {number} [dataIndexInside=currDataIndexInside]
* @return {number|string} value
*/
function value(dim, dataIndexInside) {
dataIndexInside == null && (dataIndexInside = currDataIndexInside);
return data.get(data.getDimension(dim || 0), dataIndexInside);
}
/**
* By default, `visual` is applied to style (to support visualMap).
* `visual.color` is applied at `fill`. If user want apply visual.color on
`stroke`,
* it can be implemented as:
* `api.style({stroke: api.visual('color'), fill: null})`;
* @public
* @param {Object} [extra]
* @param {number} [dataIndexInside=currDataIndexInside]
*/
function style(extra, dataIndexInside) {
dataIndexInside == null && (dataIndexInside = currDataIndexInside);
updateCache(dataIndexInside);
var itemStyle =
currItemModel.getModel(ITEM_STYLE_NORMAL_PATH).getItemStyle();
itemStyle.text = currLabelNormalModel.getShallow('show')
? retrieve2(
customSeries.getFormattedLabel(dataIndexInside, 'normal'),
getDefaultLabel(data, dataIndexInside)
)
: null;
/**
* @public
* @param {Object} [extra]
* @param {number} [dataIndexInside=currDataIndexInside]
*/
function styleEmphasis(extra, dataIndexInside) {
dataIndexInside == null && (dataIndexInside = currDataIndexInside);
updateCache(dataIndexInside);
var itemStyle =
currItemModel.getModel(ITEM_STYLE_EMPHASIS_PATH).getItemStyle();
itemStyle.text = currLabelEmphasisModel.getShallow('show')
? retrieve3(
customSeries.getFormattedLabel(dataIndexInside, 'emphasis'),
customSeries.getFormattedLabel(dataIndexInside, 'normal'),
getDefaultLabel(data, dataIndexInside)
)
: null;
/**
* @public
* @param {string} visualType
* @param {number} [dataIndexInside=currDataIndexInside]
*/
function visual(visualType, dataIndexInside) {
dataIndexInside == null && (dataIndexInside = currDataIndexInside);
return data.getItemVisual(dataIndexInside, visualType);
}
/**
* @public
* @param {number} opt.count Positive interger.
* @param {number} [opt.barWidth]
* @param {number} [opt.barMaxWidth]
* @param {number} [opt.barGap]
* @param {number} [opt.barCategoryGap]
* @return {Object} {width, offset, offsetCenter} is not support, return
undefined.
*/
function barLayout(opt) {
if (coordSys.getBaseAxis) {
var baseAxis = coordSys.getBaseAxis();
return getLayoutOnAxis(defaults({axis: baseAxis}, opt), api);
}
}
/**
* @public
* @return {Array.<number>}
*/
function currentSeriesIndices() {
return ecModel.getCurrentSeriesIndices();
}
/**
* @public
* @param {Object} opt
* @param {string} [opt.fontStyle]
* @param {number} [opt.fontWeight]
* @param {number} [opt.fontSize]
* @param {string} [opt.fontFamily]
* @return {string} font string
*/
function font(opt) {
return getFont(opt, ecModel);
}
}
function wrapEncodeDef(data) {
var encodeDef = {};
each$1(data.dimensions, function (dimName, dataDimIndex) {
var dimInfo = data.getDimensionInfo(dimName);
if (!dimInfo.isExtraCoord) {
var coordDim = dimInfo.coordDim;
var dataDims = encodeDef[coordDim] = encodeDef[coordDim] || [];
dataDims[dimInfo.coordDimIndex] = dataDimIndex;
}
});
return encodeDef;
}
return el;
}
if (elOption.diffChildrenByName) {
// lower performance.
diffGroupChildren({
oldChildren: oldChildren,
newChildren: newChildren,
dataIndex: dataIndex,
animatableModel: animatableModel,
group: el,
data: data
});
}
else {
// better performance.
var index = 0;
for (; index < newChildren.length; index++) {
doCreateOrUpdate(
el.childAt(index),
dataIndex,
newChildren[index],
animatableModel,
el,
data
);
}
for (; index < oldChildren.length; index++) {
oldChildren[index] && el.remove(oldChildren[index]);
}
}
}
group.add(el);
return el;
}
function diffGroupChildren(context) {
(new DataDiffer(
context.oldChildren,
context.newChildren,
getKey,
getKey,
context
))
.add(processAddUpdate)
.update(processAddUpdate)
.remove(processRemove)
.execute();
}
doCreateOrUpdate(
child,
context.dataIndex,
childOption,
context.animatableModel,
context.group,
context.data
);
}
function processRemove(oldIndex) {
var context = this.context;
var child = context.oldChildren[oldIndex];
child && context.group.remove(child);
}
// -------------
// Preprocessor
// -------------
registerPreprocessor(function (option) {
var graphicOption = option.graphic;
// Convert
// {graphic: [{left: 10, type: 'circle'}, ...]}
// or
// {graphic: {left: 10, type: 'circle'}}
// to
// {graphic: [{elements: [{left: 10, type: 'circle'}, ...]}]}
if (isArray(graphicOption)) {
if (!graphicOption[0] || !graphicOption[0].elements) {
option.graphic = [{elements: graphicOption}];
}
else {
// Only one graphic instance can be instantiated. (We dont
// want that too many views are created in echarts._viewMap)
option.graphic = [option.graphic[0]];
}
}
else if (graphicOption && !graphicOption.elements) {
option.graphic = [{elements: [graphicOption]}];
}
});
// ------
// Model
// ------
type: 'graphic',
defaultOption: {
/**
* Save el options for the sake of the performance (only update modified
graphics).
* The order is the same as those in option. (ancesters -> descendants)
*
* @private
* @type {Array.<Object>}
*/
_elOptionsToUpdate: null,
/**
* @override
*/
mergeOption: function (option) {
// Prevent default merge to elements
var elements = this.option.elements;
this.option.elements = null;
this.option.elements = elements;
},
/**
* @override
*/
optionUpdated: function (newOption, isInit) {
var thisOption = this.option;
var newList = (isInit ? thisOption : newOption).elements;
var existList = thisOption.elements = isInit ? [] : thisOption.elements;
// Clear elOptionsToUpdate
var elOptionsToUpdate = this._elOptionsToUpdate = [];
if (!newElOption) {
return;
}
elOptionsToUpdate.push(newElOption);
setKeyInfoToNewElOption(resultItem, newElOption);
setLayoutInfoToExist(existList[index], newElOption);
}, this);
// Clean
for (var i = existList.length - 1; i >= 0; i--) {
if (existList[i] == null) {
existList.splice(i, 1);
}
else {
// $action should be volatile, otherwise option gotten from
// `getOption` will contain unexpected $action.
delete existList[i].$action;
}
}
},
/**
* Convert
* [{
* type: 'group',
* id: 'xx',
* children: [{type: 'circle'}, {type: 'polygon'}]
* }]
* to
* [
* {type: 'group', id: 'xx'},
* {type: 'circle', parentId: 'xx'},
* {type: 'polygon', parentId: 'xx'}
* ]
*
* @private
* @param {Array.<Object>} optionList option list
* @param {Array.<Object>} result result of flatten
* @param {Object} parentOption parent option
*/
_flatten: function (optionList, result, parentOption) {
each$1(optionList, function (option) {
if (!option) {
return;
}
if (parentOption) {
option.parentOption = parentOption;
}
result.push(option);
// FIXME
// Pass to view using payload? setOption has a payload?
useElOptionsToUpdate: function () {
var els = this._elOptionsToUpdate;
// Clear to avoid render duplicately when zooming.
this._elOptionsToUpdate = null;
return els;
}
});
// -----
// View
// -----
extendComponentView({
type: 'graphic',
/**
* @override
*/
init: function (ecModel, api) {
/**
* @private
* @type {module:zrender/core/util.HashMap}
*/
this._elMap = createHashMap();
/**
* @private
* @type {module:echarts/graphic/GraphicModel}
*/
this._lastGraphicModel;
},
/**
* @override
*/
render: function (graphicModel, ecModel, api) {
this._updateElements(graphicModel, api);
this._relocate(graphicModel, api);
},
/**
* Update graphic elements.
*
* @private
* @param {Object} graphicModel graphic model
* @param {module:echarts/ExtensionAPI} api extension API
*/
_updateElements: function (graphicModel, api) {
var elOptionsToUpdate = graphicModel.useElOptionsToUpdate();
if (!elOptionsToUpdate) {
return;
}
var el = elMap.get(id);
if (el) {
el.__ecGraphicWidth = elOption.width;
el.__ecGraphicHeight = elOption.height;
}
});
},
/**
* Locate graphic elements.
*
* @private
* @param {Object} graphicModel graphic model
* @param {module:echarts/ExtensionAPI} api extension API
*/
_relocate: function (graphicModel, api) {
var elOptions = graphicModel.option.elements;
var rootGroup = this.group;
var elMap = this._elMap;
if (!el) {
continue;
}
positionElement(
el, elOption, containerInfo, null,
{hv: elOption.hv, boundingMode: elOption.bounding}
);
}
},
/**
* Clear all elements.
*
* @private
*/
_clear: function () {
var elMap = this._elMap;
elMap.each(function (el) {
removeEl(el, elMap);
});
this._elMap = createHashMap();
},
/**
* @override
*/
dispose: function () {
this._clear();
}
});
if (__DEV__) {
assert$1(graphicType, 'graphic type MUST be set');
}
if (__DEV__) {
assert$1(Clz, 'graphic type can not be found');
}
// Clear
newElOption.parentOption = null;
}
if (__DEV__) {
var newType = newElOption.type;
assert$1(
!newType || existElOption.type === newType,
'Please set $action: "replace" to change `type`'
);
}
type: 'legend.plain',
dependencies: ['series'],
layoutMode: {
type: 'box',
// legend.width/height are maxWidth/maxHeight actually,
// whereas realy width/height is calculated by its content.
// (Setting {left: 10, right: 10} does not make sense).
// So consider the case:
// `setOption({legend: {left: 10});`
// then `setOption({legend: {right: 10});`
// The previous `left` should be cleared by setting `ignoreSize`.
ignoreSize: true
},
optionUpdated: function () {
this._updateData(this.ecModel);
ecModel.eachRawSeries(function (seriesModel) {
var seriesName = seriesModel.name;
availableNames.push(seriesName);
var isPotential;
if (seriesModel.legendDataProvider) {
var data = seriesModel.legendDataProvider();
var names = data.mapArray(data.getName);
if (!ecModel.isSeriesFiltered(seriesModel)) {
availableNames = availableNames.concat(names);
}
if (names.length) {
potentialData = potentialData.concat(names);
}
else {
isPotential = true;
}
}
else {
isPotential = true;
}
/**
* @type {Array.<string>}
* @private
*/
this._availableNames = availableNames;
/**
* @type {Array.<module:echarts/model/Model>}
* @private
*/
this._data = legendData;
},
/**
* @return {Array.<module:echarts/model/Model>}
*/
getData: function () {
return this._data;
},
/**
* @param {string} name
*/
select: function (name) {
var selected = this.option.selected;
var selectedMode = this.get('selectedMode');
if (selectedMode === 'single') {
var data = this._data;
each$1(data, function (dataItem) {
selected[dataItem.get('name')] = false;
});
}
selected[name] = true;
},
/**
* @param {string} name
*/
unSelect: function (name) {
if (this.get('selectedMode') !== 'single') {
this.option.selected[name] = false;
}
},
/**
* @param {string} name
*/
toggleSelected: function (name) {
var selected = this.option.selected;
// Default is true
if (!selected.hasOwnProperty(name)) {
selected[name] = true;
}
this[selected[name] ? 'unSelect' : 'select'](name);
},
/**
* @param {string} name
*/
isSelected: function (name) {
var selected = this.option.selected;
return !(selected.hasOwnProperty(name) && !selected[name])
&& indexOf(this._availableNames, name) >= 0;
},
defaultOption: {
// 一级层叠
zlevel: 0,
// 二级层叠
z: 4,
show: true,
// 布局方式,默认为水平布局,可选为:
// 'horizontal' | 'vertical'
orient: 'horizontal',
left: 'center',
// right: 'center',
top: 0,
// bottom: null,
// 水平对齐
// 'auto' | 'left' | 'right'
// 默认为 'auto', 根据 x 的位置判断是左对齐还是右对齐
align: 'auto',
backgroundColor: 'rgba(0,0,0,0)',
// 图例边框颜色
borderColor: '#ccc',
borderRadius: 0,
// 图例边框线宽,单位 px,默认为 0(无边框)
borderWidth: 0,
// 图例内边距,单位 px,默认各方向内边距为 5,
// 接受数组分别设定上右下左边距,同 css
padding: 5,
// 各个 item 之间的间隔,单位 px,默认为 10,
// 横向布局时为水平间隔,纵向布局时为纵向间隔
itemGap: 10,
// 图例图形宽度
itemWidth: 25,
// 图例图形高度
itemHeight: 14,
// 图例关闭时候的颜色
inactiveColor: '#ccc',
textStyle: {
// 图例文字颜色
color: '#333'
},
// formatter: '',
// 选择模式,默认开启图例开关
selectedMode: true,
// 配置默认选中状态,可配合 LEGEND.SELECTED 事件做动态数据载入
// selected: null,
// 图例内容(详见 legend.data,数组中每一项代表一个 item
// data: [],
// Tooltip 相关配置
tooltip: {
show: false
}
}
});
/**
* @event legendSelect
* @type {Object}
* @property {string} type 'legendSelect'
* @property {string} name Series name or data item name
*/
registerAction(
'legendSelect', 'legendselected',
curry(legendSelectActionHandler, 'select')
);
/**
* @event legendUnSelect
* @type {Object}
* @property {string} type 'legendUnSelect'
* @property {string} name Series name or data item name
*/
registerAction(
'legendUnSelect', 'legendunselected',
curry(legendSelectActionHandler, 'unSelect')
);
/**
* Layout list like component.
* It will box layout each items in group of component and then position the whole
group in the viewport
* @param {module:zrender/group/Group} group
* @param {module:echarts/model/Component} componentModel
* @param {module:echarts/ExtensionAPI}
*/
function layout$3(group, componentModel, api) {
var boxLayoutParams = componentModel.getBoxLayoutParams();
var padding = componentModel.get('padding');
var viewportSize = {width: api.getWidth(), height: api.getHeight()};
box(
componentModel.get('orient'),
group,
componentModel.get('itemGap'),
rect.width,
rect.height
);
positionElement(
group,
boxLayoutParams,
viewportSize,
padding
);
}
return rect;
}
type: 'legend.plain',
newlineDisabled: false,
/**
* @override
*/
init: function () {
/**
* @private
* @type {module:zrender/container/Group}
*/
this.group.add(this._contentGroup = new Group$3());
/**
* @private
* @type {module:zrender/Element}
*/
this._backgroundEl;
},
/**
* @protected
*/
getContentGroup: function () {
return this._contentGroup;
},
/**
* @override
*/
render: function (legendModel, ecModel, api) {
this.resetInner();
if (!legendModel.get('show', true)) {
return;
}
// Perform layout.
var positionInfo = legendModel.getBoxLayoutParams();
var viewportSize = {width: api.getWidth(), height: api.getHeight()};
var padding = legendModel.get('padding');
/**
* @protected
*/
resetInner: function () {
this.getContentGroup().removeAll();
this._backgroundEl && this.group.remove(this._backgroundEl);
},
/**
* @protected
*/
renderInner: function (itemAlign, legendModel, ecModel, api) {
var contentGroup = this.getContentGroup();
var legendDrawnMap = createHashMap();
var selectMode = legendModel.get('selectedMode');
if (legendDrawnMap.get(name)) {
// Have been drawed
return;
}
// Series legend
if (seriesModel) {
var data = seriesModel.getData();
var color = data.getVisual('color');
legendDrawnMap.set(name, true);
}
else {
// Data legend of pie, funnel
ecModel.eachRawSeries(function (seriesModel) {
// In case multiple series has same data name
if (legendDrawnMap.get(name)) {
return;
}
if (seriesModel.legendDataProvider) {
var data = seriesModel.legendDataProvider();
var idx = data.indexOfName(name);
if (idx < 0) {
return;
}
legendDrawnMap.set(name, true);
}
}, this);
}
if (__DEV__) {
if (!legendDrawnMap.get(name)) {
console.warn(name + ' series not exists. Legend data should be
same with series name or data name.');
}
}
}, this);
},
_createItem: function (
name, dataIndex, itemModel, legendModel,
legendSymbolType, symbolType,
itemAlign, color, selectMode
) {
var itemWidth = legendModel.get('itemWidth');
var itemHeight = legendModel.get('itemHeight');
var inactiveColor = legendModel.get('inactiveColor');
// Compose symbols
// PENDING
if (!itemIcon && symbolType
// At least show one symbol, can't be all none
&& ((symbolType !== legendSymbolType) || symbolType == 'none')
) {
var size = itemHeight * 0.8;
if (symbolType === 'none') {
symbolType = 'circle';
}
// Put symbol in the center
itemGroup.add(createSymbol(
symbolType, (itemWidth - size) / 2, (itemHeight - size) / 2, size,
size,
isSelected ? color : inactiveColor
));
}
itemGroup.add(new Text({
style: setTextStyle({}, textStyleModel, {
text: content,
x: textX,
y: itemHeight / 2,
textFill: isSelected ? textStyleModel.getTextColor() :
inactiveColor,
textAlign: textAlign,
textVerticalAlign: 'middle'
})
}));
itemGroup.eachChild(function (child) {
child.silent = true;
});
hitRect.silent = !selectMode;
this.getContentGroup().add(itemGroup);
setHoverStyle(itemGroup);
itemGroup.__legendDataIndex = dataIndex;
return itemGroup;
},
/**
* @protected
*/
layoutInner: function (legendModel, itemAlign, maxSize) {
var contentGroup = this.getContentGroup();
return this.group.getBoundingRect();
}
});
};
// Series Filter
registerProcessor(legendFilter);
ComponentModel.registerSubTypeDefaulter('legend', function () {
// Default 'plain' when no type specified.
return 'plain';
});
type: 'legend.scroll',
/**
* @param {number} scrollDataIndex
*/
setScrollDataIndex: function (scrollDataIndex) {
this.option.scrollDataIndex = scrollDataIndex;
},
defaultOption: {
scrollDataIndex: 0,
pageButtonItemGap: 5,
pageButtonGap: null,
pageButtonPosition: 'end', // 'start' or 'end'
pageFormatter: '{current}/{total}', // If null/undefined, do not show page.
pageIcons: {
horizontal: ['M0,0L12,-10L12,10z', 'M0,0L-12,-10L-12,10z'],
vertical: ['M0,0L20,0L10,-20z', 'M0,0L20,0L10,20z']
},
pageIconColor: '#2f4554',
pageIconInactiveColor: '#aaa',
pageIconSize: 15, // Can be [10, 3], which represents [width, height]
pageTextStyle: {
color: '#333'
},
animationDurationUpdate: 800
},
/**
* @override
*/
init: function (option, parentModel, ecModel, extraOpt) {
var inputPositionParams = getLayoutParams(option);
/**
* @override
*/
mergeOption: function (option, extraOpt) {
ScrollableLegendModel.superCall(this, 'mergeOption', option, extraOpt);
getOrient: function () {
return this.get('orient') === 'vertical'
? {index: 1, name: 'vertical'}
: {index: 0, name: 'horizontal'};
}
});
/**
* Separate legend and scrollable legend to reduce package size.
*/
type: 'legend.scroll',
newlineDisabled: true,
init: function () {
ScrollableLegendView.superCall(this, 'init');
/**
* @private
* @type {number} For `scroll`.
*/
this._currentIndex = 0;
/**
* @private
* @type {module:zrender/container/Group}
*/
this.group.add(this._containerGroup = new Group$4());
this._containerGroup.add(this.getContentGroup());
/**
* @private
* @type {module:zrender/container/Group}
*/
this.group.add(this._controllerGroup = new Group$4());
/**
*
* @private
*/
this._showController;
},
/**
* @override
*/
resetInner: function () {
ScrollableLegendView.superCall(this, 'resetInner');
this._controllerGroup.removeAll();
this._containerGroup.removeClipPath();
this._containerGroup.__rectSize = null;
},
/**
* @override
*/
renderInner: function (itemAlign, legendModel, ecModel, api) {
var me = this;
createPageButton('pagePrev', 0);
createPageButton('pageNext', 1);
/**
* @override
*/
layoutInner: function (legendModel, itemAlign, maxSize) {
var contentGroup = this.getContentGroup();
var containerGroup = this._containerGroup;
var controllerGroup = this._controllerGroup;
box(
// Buttons in controller are layout always horizontally.
'horizontal',
controllerGroup,
legendModel.get('pageButtonItemGap', true)
);
contentGroup.attr('position', contentPos);
containerGroup.attr('position', containerPos);
controllerGroup.attr('position', controllerPos);
containerGroup.__rectSize = maxSize[wh];
if (showController) {
var clipShape = {x: 0, y: 0};
clipShape[wh] = Math.max(maxSize[wh] - controllerRect[wh] -
pageButtonGap, 0);
clipShape[hw] = mainRect[hw];
containerGroup.setClipPath(new Rect({shape: clipShape}));
// Consider content may be larger than container, container rect
// can not be obtained from `containerGroup.getBoundingRect()`.
containerGroup.__rectSize = clipShape[wh];
}
else {
// Do not remove or ignore controller. Keep them set as place holders.
controllerGroup.eachChild(function (child) {
child.attr({invisible: true, silent: true});
});
}
this._updatePageInfoView(legendModel, pageInfo);
return mainRect;
},
/**
* @param {module:echarts/model/Model} legendModel
* @return {Object} {
* contentPosition: Array.<number>, null when data item not found.
* pageIndex: number, null when data item not found.
* pageCount: number, always be a number, can be 0.
* pagePrevDataIndex: number, null when no next page.
* pageNextDataIndex: number, null when no previous page.
* }
*/
_getPageInfo: function (legendModel) {
// Align left or top by the current dataIndex.
var currDataIndex = legendModel.get('scrollDataIndex', true);
var contentGroup = this.getContentGroup();
var contentRect = contentGroup.getBoundingRect();
var containerRectSize = this._containerGroup.__rectSize;
var pageIndex;
var pagePrevDataIndex;
var pageNextDataIndex;
var targetItemGroup;
if (this._showController) {
contentGroup.eachChild(function (child) {
if (child.__legendDataIndex === currDataIndex) {
targetItemGroup = child;
}
});
}
else {
targetItemGroup = contentGroup.childAt(0);
}
if (targetItemGroup) {
var itemRect = targetItemGroup.getBoundingRect();
var itemLoc = targetItemGroup.position[orientIdx] + itemRect[xy];
contentPos[orientIdx] = -itemLoc - contentRect[xy];
pageIndex = Math.floor(
pageCount * (itemLoc + itemRect[xy] + containerRectSize / 2) /
contentRect[wh]
);
pageIndex = (contentRect[wh] && pageCount)
? Math.max(0, Math.min(pageCount - 1, pageIndex))
: -1;
var startIdx;
var children = contentGroup.children();
if (itemRect.intersect(winRect)) {
startIdx == null && (startIdx = index);
// It is user-friendly that the last item shown in the
// current window is shown at the begining of next window.
pageNextDataIndex = child.__legendDataIndex;
}
// Always align based on the left/top most item, so the left/top most
// item in the previous window is needed to be found here.
if (startIdx != null) {
var startItem = children[startIdx];
var startRect = getItemRect(startItem);
winRect[xy] = startRect[xy] + startRect[wh] - winRect[wh];
return {
contentPosition: contentPos,
pageIndex: pageIndex,
pageCount: pageCount,
pagePrevDataIndex: pagePrevDataIndex,
pageNextDataIndex: pageNextDataIndex
};
function getItemRect(el) {
var itemRect = el.getBoundingRect().clone();
itemRect[xy] += el.position[orientIdx];
return itemRect;
}
}
});
/**
* @event legendScroll
* @type {Object}
* @property {string} type 'legendScroll'
* @property {string} scrollDataIndex
*/
registerAction(
'legendScroll', 'legendscroll',
function (payload, ecModel) {
var scrollDataIndex = payload.scrollDataIndex;
/**
* Legend component entry file8
*/
extendComponentModel({
type: 'tooltip',
dependencies: ['axisPointer'],
defaultOption: {
zlevel: 0,
z: 8,
show: true,
// tooltip 主体内容
showContent: true,
// 位置 {Array} | {Function}
// position: null
// Consider triggered from axisPointer handle, verticalAlign should be
'middle'
// align: null,
// verticalAlign: null,
// 内容格式器:{string}(Template) ¦ {Function}
// formatter: null
showDelay: 0,
// 隐藏延迟,单位 ms
hideDelay: 100,
// 动画变换时间,单位 s
transitionDuration: 0.4,
enterable: false,
// 提示边框颜色
borderColor: '#333',
// 提示边框圆角,单位 px,默认为 4
borderRadius: 4,
// 提示内边距,单位 px,默认各方向内边距为 5,
// 接受数组分别设定上右下左边距,同 css
padding: 5,
// 坐标轴指示器,坐标轴触发有效
axisPointer: {
// 默认为直线
// 可选为:'line' | 'shadow' | 'cross'
type: 'line',
animation: 'auto',
animationDurationUpdate: 200,
animationEasingUpdate: 'exponentialOut',
crossStyle: {
color: '#999',
width: 1,
type: 'dashed',
// TODO formatter
textStyle: {}
}
/**
* @param {number} duration
* @return {string}
* @inner
*/
function assembleTransition(duration) {
var transitionCurve = 'cubic-bezier(0.23, 1, 0.32, 1)';
var transitionText = 'left ' + duration + 's ' + transitionCurve + ','
+ 'top ' + duration + 's ' + transitionCurve;
return map(vendors, function (vendorPrefix) {
return vendorPrefix + 'transition:' + transitionText;
}).join(';');
}
/**
* @param {Object} textStyle
* @return {string}
* @inner
*/
function assembleFont(textStyleModel) {
var cssText = [];
cssText.push('font:' + textStyleModel.getFont());
fontSize &&
cssText.push('line-height:' + Math.round(fontSize * 3 / 2) + 'px');
return cssText.join(';');
}
/**
* @param {Object} tooltipModel
* @return {string}
* @inner
*/
function assembleCssText(tooltipModel) {
if (backgroundColor) {
if (env$1.canvasSupported) {
cssText.push('background-Color:' + backgroundColor);
}
else {
// for ie
cssText.push(
'background-Color:#' + toHex(backgroundColor)
);
cssText.push('filter:alpha(opacity=70)');
}
}
// Border style
each$19(['width', 'color', 'radius'], function (name) {
var borderName = 'border-' + name;
var camelCase = toCamelCase$1(borderName);
var val = tooltipModel.get(camelCase);
val != null &&
cssText.push(borderName + ':' + val + (name === 'color' ? '' : 'px'));
});
// Text style
cssText.push(assembleFont(textStyleModel));
// Padding
if (padding != null) {
cssText.push('padding:' + normalizeCssArray$1(padding).join('px ') + 'px');
}
/**
* @alias module:echarts/component/tooltip/TooltipContent
* @constructor
*/
function TooltipContent(container, api) {
if (env$1.wxa) {
return null;
}
var el = document.createElement('div');
var zr = this._zr = api.getZr();
this.el = el;
this._x = api.getWidth() / 2;
this._y = api.getHeight() / 2;
container.appendChild(el);
this._container = container;
this._show = false;
/**
* @private
*/
this._hideTimeout;
TooltipContent.prototype = {
constructor: TooltipContent,
/**
* @private
* @type {boolean}
*/
_enterable: true,
/**
* Update when tooltip is rendered
*/
update: function () {
// FIXME
// Move this logic to ec main?
var container = this._container;
var stl = container.currentStyle
|| document.defaultView.getComputedStyle(container);
var domStyle = container.style;
if (domStyle.position !== 'absolute' && stl.position !== 'absolute') {
domStyle.position = 'relative';
}
// Hide the tooltip
// PENDING
// this.hide();
},
this._show = true;
},
getSize: function () {
var el = this.el;
return [el.clientWidth, el.clientHeight];
},
this._x = x;
this._y = y;
},
hide: function () {
this.el.style.display = 'none';
this._show = false;
},
isShow: function () {
return this._show;
}
};
extendComponentView({
type: 'tooltip',
// Reset
this.group.removeAll();
/**
* @private
* @type {module:echarts/component/tooltip/TooltipModel}
*/
this._tooltipModel = tooltipModel;
/**
* @private
* @type {module:echarts/model/Global}
*/
this._ecModel = ecModel;
/**
* @private
* @type {module:echarts/ExtensionAPI}
*/
this._api = api;
/**
* Should be cleaned when render.
* @private
* @type {Array.<Array.<Object>>}
*/
this._lastDataByCoordSys = null;
/**
* @private
* @type {boolean}
*/
this._alwaysShowContent = tooltipModel.get('alwaysShowContent');
this._initGlobalListener();
this._keepShow();
},
_initGlobalListener: function () {
var tooltipModel = this._tooltipModel;
var triggerOn = tooltipModel.get('triggerOn');
register(
'itemTooltip',
this._api,
bind$3(function (currTrigger, e, dispatchAction) {
// If 'none', it is not controlled by mouse totally.
if (triggerOn !== 'none') {
if (triggerOn.indexOf(currTrigger) >= 0) {
this._tryShow(e, dispatchAction);
}
else if (currTrigger === 'leave') {
this._hide(dispatchAction);
}
}
}, this)
);
},
_keepShow: function () {
var tooltipModel = this._tooltipModel;
var ecModel = this._ecModel;
var api = this._api;
/**
* Show tip manually by
* dispatchAction({
* type: 'showTip',
* x: 10,
* y: 10
* });
* Or
* dispatchAction({
* type: 'showTip',
* seriesIndex: 0,
* dataIndex or dataIndexInside or name
* });
*
* TODO Batch
*/
manuallyShowTip: function (tooltipModel, ecModel, api, payload) {
if (payload.from === this.uid || env$1.node) {
return;
}
// Reset ticket
this._ticket = '';
this._tryShow({
offsetX: payload.x,
offsetY: payload.y,
position: payload.position,
target: api.getZr().findHover(payload.x, payload.y).target,
event: {}
}, dispatchAction);
}
},
// Be compatible with previous design, that is, when tooltip.type is 'axis' and
// dispatchAction 'showTip' with seriesIndex and dataIndex will trigger axis
pointer
// and tooltip.
_manuallyAxisShowTip: function (tooltipModel, ecModel, api, payload) {
var seriesIndex = payload.seriesIndex;
var dataIndex = payload.dataIndex;
var coordSysAxesInfo =
ecModel.getComponent('axisPointer').coordSysAxesInfo;
return true;
},
if (!tooltipModel) {
return;
}
// Save mouse x, mouse y. So we can try to keep showing the tip if chart is
refreshed
this._lastX = e.offsetX;
this._lastY = e.offsetY;
if (dataParams) {
singleParamsList.push(dataParams);
seriesDefaultHTML.push(series.formatTooltip(dataIndex,
true));
}
});
// In most case, the second axis is shown upper than the first one.
singleDefaultHTML.reverse();
singleDefaultHTML = singleDefaultHTML.join('<br /><br />');
this._showOrMove(tooltipModel, function () {
this._showTooltipContent(
tooltipModel, defaultHtml, params, asyncTicket,
e.offsetX, e.offsetY, e.position, e.target
);
});
// FIXME
// duplicated showtip if manuallyShowTip is called from dispatchAction.
dispatchAction({
type: 'showTip',
dataIndexInside: dataIndex,
dataIndex: data.getRawIndex(dataIndex),
seriesIndex: seriesIndex,
from: this.uid
});
},
this._showOrMove(subTooltipModel, function () {
this._showTooltipContent(
subTooltipModel, defaultHtml,
subTooltipModel.get('formatterParams') || {},
asyncTicket, e.offsetX, e.offsetY, e.position, el
);
});
_showTooltipContent: function (
tooltipModel, defaultHtml, params, asyncTicket, x, y, positionExpr, el
) {
// Reset ticket
this._ticket = '';
if (!tooltipModel.get('showContent') || !tooltipModel.get('show')) {
return;
}
tooltipContent.setContent(html);
tooltipContent.show(tooltipModel);
this._updatePosition(
tooltipModel, positionExpr, x, y, tooltipContent, params, el
);
},
/**
* @param {string|Function|Array.<number>|Object} positionExpr
* @param {number} x Mouse x
* @param {number} y Mouse y
* @param {boolean} confine Whether confine tooltip content in view rect.
* @param {Object|<Array.<Object>} params
* @param {module:zrender/Element} el target element
* @param {module:echarts/ExtensionAPI} api
* @return {Array.<number>}
*/
_updatePosition: function (tooltipModel, positionExpr, x, y, content, params,
el) {
var viewWidth = this._api.getWidth();
var viewHeight = this._api.getHeight();
positionExpr = positionExpr || tooltipModel.get('position');
var contentSize = content.getSize();
var align = tooltipModel.get('align');
var vAlign = tooltipModel.get('verticalAlign');
var rect = el && el.getBoundingRect().clone();
el && rect.applyTransform(el.transform);
if (isArray(positionExpr)) {
x = parsePercent$2(positionExpr[0], viewWidth);
y = parsePercent$2(positionExpr[1], viewHeight);
}
else if (isObject$1(positionExpr)) {
positionExpr.width = contentSize[0];
positionExpr.height = contentSize[1];
var layoutRect = getLayoutRect(
positionExpr, {width: viewWidth, height: viewHeight}
);
x = layoutRect.x;
y = layoutRect.y;
align = null;
// When positionExpr is left/top/right/bottom,
// align and verticalAlign will not work.
vAlign = null;
}
// Specify tooltip position by string 'top' 'bottom' 'left' 'right' around
graphic element
else if (typeof positionExpr === 'string' && el) {
var pos = calcTooltipPosition(
positionExpr, rect, contentSize
);
x = pos[0];
y = pos[1];
}
else {
var pos = refixTooltipPosition(
x, y, content.el, viewWidth, viewHeight, align ? null : 20,
vAlign ? null : 20
);
x = pos[0];
y = pos[1];
}
if (tooltipModel.get('confine')) {
var pos = confineTooltipPosition(
x, y, content.el, viewWidth, viewHeight
);
x = pos[0];
y = pos[1];
}
content.moveTo(x, y);
},
// FIXME
// Should we remove this but leave this to user?
_updateContentNotChangedOnAxis: function (dataByCoordSys) {
var lastCoordSys = this._lastDataByCoordSys;
var contentNotChanged = !!lastCoordSys
&& lastCoordSys.length === dataByCoordSys.length;
contentNotChanged &=
lastItem.value === thisItem.value
&& lastItem.axisType === thisItem.axisType
&& lastItem.axisId === thisItem.axisId
&& lastIndices.length === newIndices.length;
this._lastDataByCoordSys = dataByCoordSys;
return !!contentNotChanged;
},
// FIXME
// duplicated hideTip if manuallyHideTip is called from dispatchAction.
this._lastDataByCoordSys = null;
dispatchAction({
type: 'hideTip',
from: this.uid
});
},
/**
* @param {Array.<Object|module:echarts/model/Model>} modelCascade
* From top to bottom. (the last one should be globalTooltipModel);
*/
function buildTooltipModel(modelCascade) {
var resultModel = modelCascade.pop();
while (modelCascade.length) {
var tooltipOpt = modelCascade.pop();
if (tooltipOpt) {
if (Model.isInstance(tooltipOpt)) {
tooltipOpt = tooltipOpt.get('tooltip', true);
}
// In each data item tooltip can be simply write:
// {
// value: 10,
// tooltip: 'Something you need to know'
// }
if (typeof tooltipOpt === 'string') {
tooltipOpt = {formatter: tooltipOpt};
}
resultModel = new Model(tooltipOpt, resultModel, resultModel.ecModel);
}
}
return resultModel;
}
if (gapH != null) {
if (x + width + gapH > viewWidth) {
x -= width + gapH;
}
else {
x += gapH;
}
}
if (gapV != null) {
if (y + height + gapV > viewHeight) {
y -= height + gapV;
}
else {
y += gapV;
}
}
return [x, y];
}
function getOuterSize(el) {
var width = el.clientWidth;
var height = el.clientHeight;
function isCenterAlign(align) {
return align === 'center' || align === 'middle';
}
/**
* @action
* @property {string} type
* @property {number} seriesIndex
* @property {number} dataIndex
* @property {number} [x]
* @property {number} [y]
*/
registerAction(
{
type: 'showTip',
event: 'showTip',
update: 'tooltip:manuallyShowTip'
},
// noop
function () {}
);
registerAction(
{
type: 'hideTip',
event: 'hideTip',
update: 'tooltip:manuallyHideTip'
},
// noop
function () {}
);
function getSeriesStackId$1(seriesModel) {
return seriesModel.get('stack')
|| '__ec_stack_' + seriesModel.seriesIndex;
}
function getAxisKey$1(axis) {
return axis.dim;
}
/**
* @param {string} seriesType
* @param {module:echarts/model/Global} ecModel
* @param {module:echarts/ExtensionAPI} api
*/
function barLayoutPolar(seriesType, ecModel, api) {
var r0;
var r;
var startAngle;
var endAngle;
// radial sector
if (valueAxis.dim === 'radius') {
var radiusSpan = valueAxis.dataToRadius(value) - valueAxisStart;
var angle = baseAxis.dataToAngle(baseValue);
r0 = baseCoord;
r = baseCoord + radiusSpan;
startAngle = angle - columnOffset;
endAngle = startAngle - columnWidth;
r0 = radius + columnOffset;
r = r0 + columnWidth;
startAngle = baseCoord;
endAngle = baseCoord + angleSpan;
data.setItemLayout(idx, {
cx: cx,
cy: cy,
r0: r0,
r: r,
// Consider that positive angle is anti-clockwise,
// while positive radian of sector is clockwise
startAngle: -startAngle * Math.PI / 180,
endAngle: -endAngle * Math.PI / 180
});
}, this);
/**
* Calculate bar width and offset for radial bar charts
*/
function calRadialBar(barSeries, api) {
// Columns info on each category axis. Key is polar name
var columnsMap = {};
result[coordSysName] = {};
var widthSum = 0;
var lastColumn;
each$1(stacks, function (column, idx) {
if (!column.width) {
column.width = autoWidth;
}
lastColumn = column;
widthSum += column.width * (1 + barGapPercent);
});
if (lastColumn) {
widthSum -= lastColumn.width * barGapPercent;
}
return result;
}
/**
* Axis type
* - 'category'
* - 'value'
* - 'time'
* - 'log'
* @type {string}
*/
this.type = 'category';
}
RadiusAxis.prototype = {
constructor: RadiusAxis,
/**
* @override
*/
pointToData: function (point, clamp) {
return this.polar.pointToData(point, clamp)[this.dim === 'radius' ? 0 : 1];
},
dataToRadius: Axis.prototype.dataToCoord,
radiusToData: Axis.prototype.coordToData
};
inherits(RadiusAxis, Axis);
/**
* Axis type
* - 'category'
* - 'value'
* - 'time'
* - 'log'
* @type {string}
*/
this.type = 'category';
}
AngleAxis.prototype = {
constructor: AngleAxis,
/**
* @override
*/
pointToData: function (point, clamp) {
return this.polar.pointToData(point, clamp)[this.dim === 'radius' ? 0 : 1];
},
dataToAngle: Axis.prototype.dataToCoord,
angleToData: Axis.prototype.coordToData
};
inherits(AngleAxis, Axis);
/**
* @module echarts/coord/polar/Polar
*/
/**
* @alias {module:echarts/coord/polar/Polar}
* @constructor
* @param {string} name
*/
var Polar = function (name) {
/**
* @type {string}
*/
this.name = name || '';
/**
* x of polar center
* @type {number}
*/
this.cx = 0;
/**
* y of polar center
* @type {number}
*/
this.cy = 0;
/**
* @type {module:echarts/coord/polar/RadiusAxis}
* @private
*/
this._radiusAxis = new RadiusAxis();
/**
* @type {module:echarts/coord/polar/AngleAxis}
* @private
*/
this._angleAxis = new AngleAxis();
Polar.prototype = {
type: 'polar',
axisPointerEnabled: true,
constructor: Polar,
/**
* @param {Array.<string>}
* @readOnly
*/
dimensions: ['radius', 'angle'],
/**
* @type {module:echarts/coord/PolarModel}
*/
model: null,
/**
* If contain coord
* @param {Array.<number>} point
* @return {boolean}
*/
containPoint: function (point) {
var coord = this.pointToCoord(point);
return this._radiusAxis.contain(coord[0])
&& this._angleAxis.contain(coord[1]);
},
/**
* If contain data
* @param {Array.<number>} data
* @return {boolean}
*/
containData: function (data) {
return this._radiusAxis.containData(data[0])
&& this._angleAxis.containData(data[1]);
},
/**
* @param {string} dim
* @return {module:echarts/coord/polar/AngleAxis|
module:echarts/coord/polar/RadiusAxis}
*/
getAxis: function (dim) {
return this['_' + dim + 'Axis'];
},
/**
* @return {Array.<module:echarts/coord/Axis>}
*/
getAxes: function () {
return [this._radiusAxis, this._angleAxis];
},
/**
* Get axes by type of scale
* @param {string} scaleType
* @return {module:echarts/coord/polar/AngleAxis|
module:echarts/coord/polar/RadiusAxis}
*/
getAxesByScale: function (scaleType) {
var axes = [];
var angleAxis = this._angleAxis;
var radiusAxis = this._radiusAxis;
angleAxis.scale.type === scaleType && axes.push(angleAxis);
radiusAxis.scale.type === scaleType && axes.push(radiusAxis);
return axes;
},
/**
* @return {module:echarts/coord/polar/AngleAxis}
*/
getAngleAxis: function () {
return this._angleAxis;
},
/**
* @return {module:echarts/coord/polar/RadiusAxis}
*/
getRadiusAxis: function () {
return this._radiusAxis;
},
/**
* @param {module:echarts/coord/polar/Axis}
* @return {module:echarts/coord/polar/Axis}
*/
getOtherAxis: function (axis) {
var angleAxis = this._angleAxis;
return axis === angleAxis ? this._radiusAxis : angleAxis;
},
/**
* Base axis will be used on stacking.
*
* @return {module:echarts/coord/polar/Axis}
*/
getBaseAxis: function () {
return this.getAxesByScale('ordinal')[0]
|| this.getAxesByScale('time')[0]
|| this.getAngleAxis();
},
/**
* @param {string} [dim] 'radius' or 'angle' or 'auto' or null/undefined
* @return {Object} {baseAxes: [], otherAxes: []}
*/
getTooltipAxes: function (dim) {
var baseAxis = (dim != null && dim !== 'auto')
? this.getAxis(dim) : this.getBaseAxis();
return {
baseAxes: [baseAxis],
otherAxes: [this.getOtherAxis(baseAxis)]
};
},
/**
* Convert a single data item to (x, y) point.
* Parameter data is an array which the first element is radius and the second
is angle
* @param {Array.<number>} data
* @param {boolean} [clamp=false]
* @return {Array.<number>}
*/
dataToPoint: function (data, clamp) {
return this.coordToPoint([
this._radiusAxis.dataToRadius(data[0], clamp),
this._angleAxis.dataToAngle(data[1], clamp)
]);
},
/**
* Convert a (x, y) point to data
* @param {Array.<number>} point
* @param {boolean} [clamp=false]
* @return {Array.<number>}
*/
pointToData: function (point, clamp) {
var coord = this.pointToCoord(point);
return [
this._radiusAxis.radiusToData(coord[0], clamp),
this._angleAxis.angleToData(coord[1], clamp)
];
},
/**
* Convert a (x, y) point to (radius, angle) coord
* @param {Array.<number>} point
* @return {Array.<number>}
*/
pointToCoord: function (point) {
var dx = point[0] - this.cx;
var dy = point[1] - this.cy;
var angleAxis = this.getAngleAxis();
var extent = angleAxis.getExtent();
var minAngle = Math.min(extent[0], extent[1]);
var maxAngle = Math.max(extent[0], extent[1]);
// Fix fixed extent in polarCreator
// FIXME
angleAxis.inverse
? (minAngle = maxAngle - 360)
: (maxAngle = minAngle + 360);
// move to angleExtent
var dir = radian < minAngle ? 1 : -1;
while (radian < minAngle || radian > maxAngle) {
radian += dir * 360;
}
/**
* Convert a (radius, angle) coord to (x, y) point
* @param {Array.<number>} coord
* @return {Array.<number>}
*/
coordToPoint: function (coord) {
var radius = coord[0];
var radian = coord[1] / 180 * Math.PI;
var x = Math.cos(radian) * radius + this.cx;
// Inverse the y
var y = -Math.sin(radian) * radius + this.cy;
};
type: 'polarAxis',
/**
* @type {module:echarts/coord/polar/AngleAxis|
module:echarts/coord/polar/RadiusAxis}
*/
axis: null,
/**
* @override
*/
getCoordSysModel: function () {
return this.ecModel.queryComponents({
mainType: 'polar',
index: this.option.polarIndex,
id: this.option.polarId
})[0];
}
});
merge(PolarAxisModel.prototype, axisModelCommonMixin);
var polarAxisDefaultExtendedOption = {
angle: {
// polarIndex: 0,
// polarId: '',
startAngle: 90,
clockwise: true,
splitNumber: 12,
axisLabel: {
rotate: false
}
},
radius: {
// polarIndex: 0,
// polarId: '',
splitNumber: 5
}
};
extendComponentModel({
type: 'polar',
/**
* @type {module:echarts/coord/polar/Polar}
*/
coordinateSystem: null,
/**
* @param {string} axisType
* @return {module:echarts/coord/polar/AxisModel}
*/
findAxisModel: function (axisType) {
var foundAxisModel;
var ecModel = this.ecModel;
defaultOption: {
zlevel: 0,
z: 0,
radius: '80%'
}
});
// 依赖 PolarModel 做预处理
/**
* Resize method bound to the polar
* @param {module:echarts/coord/polar/PolarModel} polarModel
* @param {module:echarts/ExtensionAPI} api
*/
function resizePolar(polar, polarModel, api) {
var center = polarModel.get('center');
var width = api.getWidth();
var height = api.getHeight();
/**
* Update polar
*/
function updatePolarScale(ecModel, api) {
var polar = this;
var angleAxis = polar.getAngleAxis();
var radiusAxis = polar.getRadiusAxis();
// Reset scale
angleAxis.scale.setExtent(Infinity, -Infinity);
radiusAxis.scale.setExtent(Infinity, -Infinity);
ecModel.eachSeries(function (seriesModel) {
if (seriesModel.coordinateSystem === polar) {
var data = seriesModel.getData();
each$1(data.mapDimension('radius', true), function (dim) {
radiusAxis.scale.unionExtentFromData(data, dim);
});
each$1(data.mapDimension('angle', true), function (dim) {
angleAxis.scale.unionExtentFromData(data, dim);
});
}
});
niceScaleExtent(angleAxis.scale, angleAxis.model);
niceScaleExtent(radiusAxis.scale, radiusAxis.model);
/**
* Set common axis properties
* @param {module:echarts/coord/polar/AngleAxis|
module:echarts/coord/polar/RadiusAxis}
* @param {module:echarts/coord/polar/AxisModel}
* @inner
*/
function setAxis(axis, axisModel) {
axis.type = axisModel.get('type');
axis.scale = createScaleByModel(axisModel);
axis.onBand = axisModel.get('boundaryGap') && axis.type === 'category';
axis.inverse = axisModel.get('inverse');
var polarCreator = {
dimensions: Polar.prototype.dimensions,
setAxis(radiusAxis, radiusAxisModel);
setAxis(angleAxis, angleAxisModel);
polarList.push(polar);
polarModel.coordinateSystem = polar;
polar.model = polarModel;
});
// Inject coordinateSystem to series
ecModel.eachSeries(function (seriesModel) {
if (seriesModel.get('coordinateSystem') === 'polar') {
var polarModel = ecModel.queryComponents({
mainType: 'polar',
index: seriesModel.get('polarIndex'),
id: seriesModel.get('polarId')
})[0];
if (__DEV__) {
if (!polarModel) {
throw new Error(
'Polar "' + retrieve(
seriesModel.get('polarIndex'),
seriesModel.get('polarId'),
0
) + '" not found'
);
}
}
seriesModel.coordinateSystem = polarModel.coordinateSystem;
}
});
return polarList;
}
};
CoordinateSystemManager.register('polar', polarCreator);
function getRadiusIdx(polar) {
var radiusAxis = polar.getRadiusAxis();
return radiusAxis.inverse ? 0 : 1;
}
AxisView.extend({
type: 'angleAxis',
axisPointerClass: 'PolarAxisPointer',
/**
* @private
*/
_axisLine: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
var lineStyleModel = angleAxisModel.getModel('axisLine.lineStyle');
this.group.add(circle);
},
/**
* @private
*/
_axisTick: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
var tickModel = angleAxisModel.getModel('axisTick');
/**
* @private
*/
_axisLabel: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
var axis = angleAxisModel.axis;
// Use length of ticksAngles because it may remove the last tick to avoid
overlapping
for (var i = 0; i < ticksAngles.length; i++) {
var r = radiusExtent[getRadiusIdx(polar)];
var p = polar.coordToPoint([r + labelMargin, labelsAngles[i]]);
var cx = polar.cx;
var cy = polar.cy;
/**
* @private
*/
_splitLine: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
var splitLineModel = angleAxisModel.getModel('splitLine');
var lineStyleModel = splitLineModel.getModel('lineStyle');
var lineColors = lineStyleModel.get('color');
var lineCount = 0;
// Simple optimization
// Batching the lines if color are the same
for (var i = 0; i < splitLines.length; i++) {
this.group.add(mergePath(splitLines[i], {
style: defaults({
stroke: lineColors[i % lineColors.length]
}, lineStyleModel.getLineStyle()),
silent: true,
z: angleAxisModel.get('z')
}));
}
},
/**
* @private
*/
_splitArea: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
var splitAreaModel = angleAxisModel.getModel('splitArea');
var areaStyleModel = splitAreaModel.getModel('areaStyle');
var areaColors = areaStyleModel.get('color');
var lineCount = 0;
// Simple optimization
// Batching the lines if color are the same
for (var i = 0; i < splitAreas.length; i++) {
this.group.add(mergePath(splitAreas[i], {
style: defaults({
fill: areaColors[i % areaColors.length]
}, areaStyleModel.getAreaStyle()),
silent: true
}));
}
}
});
var axisBuilderAttrs$3 = [
'axisLine', 'axisTickLabel', 'axisName'
];
var selfBuilderAttrs$1 = [
'splitLine', 'splitArea'
];
AxisView.extend({
type: 'radiusAxis',
axisPointerClass: 'PolarAxisPointer',
/**
* @private
*/
_splitLine: function (radiusAxisModel, polar, axisAngle, radiusExtent,
ticksCoords) {
var splitLineModel = radiusAxisModel.getModel('splitLine');
var lineStyleModel = splitLineModel.getModel('lineStyle');
var lineColors = lineStyleModel.get('color');
var lineCount = 0;
// Simple optimization
// Batching the lines if color are the same
for (var i = 0; i < splitLines.length; i++) {
this.group.add(mergePath(splitLines[i], {
style: defaults({
stroke: lineColors[i % lineColors.length],
fill: null
}, lineStyleModel.getLineStyle()),
silent: true
}));
}
},
/**
* @private
*/
_splitArea: function (radiusAxisModel, polar, axisAngle, radiusExtent,
ticksCoords) {
// Simple optimization
// Batching the lines if color are the same
for (var i = 0; i < splitAreas.length; i++) {
this.group.add(mergePath(splitAreas[i], {
style: defaults({
fill: areaColors[i % areaColors.length]
}, areaStyleModel.getAreaStyle()),
silent: true
}));
}
}
});
/**
* @inner
*/
function layoutAxis(polar, radiusAxisModel, axisAngle) {
return {
position: [polar.cx, polar.cy],
rotation: axisAngle / 180 * Math.PI,
labelDirection: -1,
tickDirection: -1,
nameDirection: 1,
labelRotate: radiusAxisModel.getModel('axisLabel').get('rotate'),
// Over splitLine and splitArea
z2: 1
};
}
/**
* @override
*/
makeElOption: function (elOption, value, axisModel, axisPointerModel, api) {
var axis = axisModel.axis;
var coordValue;
coordValue = axis['dataTo' + capitalFirst(axis.dim)](value);
});
return {
position: position,
align: align,
verticalAlign: verticalAlign
};
}
var pointerShapeBuilder$2 = {
AxisView.registerAxisPointerClass('PolarAxisPointer', PolarAxisPointer);
// Polar view
extendComponentView({
type: 'polar'
});
type: 'geo',
/**
* @type {module:echarts/coord/geo/Geo}
*/
coordinateSystem: null,
layoutMode: 'box',
optionUpdated: function () {
var option = this.option;
var self = this;
this.updateSelectedMap(option.regions);
},
defaultOption: {
zlevel: 0,
z: 0,
show: true,
left: 'center',
top: 'center',
// width:,
// height:,
// right
// bottom
silent: false,
// Map type
map: '',
zoom: 1,
scaleLimit: null,
// selectedMode: false
label: {
show: false,
color: '#000'
},
itemStyle: {
// color: 各异,
borderWidth: 0.5,
borderColor: '#444',
color: '#eee'
},
emphasis: {
label: {
show: true,
color: 'rgb(100,0,0)'
},
itemStyle: {
color: 'rgba(255,215,0,0.8)'
}
},
regions: []
},
/**
* Get model of region
* @param {string} name
* @return {module:echarts/model/Model}
*/
getRegionModel: function (name) {
return this._optionModelMap.get(name) || new Model(null, this,
this.ecModel);
},
/**
* Format label
* @param {string} name Region name
* @param {string} [status='normal'] 'normal' or 'emphasis'
* @return {string}
*/
getFormattedLabel: function (name, status) {
var regionModel = this.getRegionModel(name);
var formatter = regionModel.get('label.' + status + '.formatter');
var params = {
name: name
};
if (typeof formatter === 'function') {
params.status = status;
return formatter(params);
}
else if (typeof formatter === 'string') {
return formatter.replace('{a}', name != null ? name : '');
}
},
mixin(GeoModel, selectableMixin);
extendComponentView({
type: 'geo',
this.group.add(mapDraw.group);
},
this.group.silent = geoModel.get('silent');
},
dispose: function () {
this._mapDraw && this._mapDraw.remove();
}
});
ecModel.eachComponent(
{ mainType: 'geo', query: payload},
function (geoModel) {
geoModel[method](payload.name);
var geo = geoModel.coordinateSystem;
each$1(geo.regions, function (region) {
selected[region.name] = geoModel.isSelected(region.name) ||
false;
});
}
);
return {
selected: selected,
name: payload.name
};
});
}
makeAction('toggleSelected', {
type: 'geoToggleSelect',
event: 'geoselectchanged'
});
makeAction('select', {
type: 'geoSelect',
event: 'geoselected'
});
makeAction('unSelect', {
type: 'geoUnSelect',
event: 'geounselected'
});
if (!brushComponents.length) {
return;
}
if (isArray(toolbox)) {
toolbox = toolbox[0];
}
if (!toolbox) {
toolbox = {feature: {}};
option.toolbox = [toolbox];
}
brushTypes.push.apply(brushTypes, brushComponentSpecifiedBtns);
removeDuplicate(brushTypes);
function removeDuplicate(arr) {
var map$$1 = {};
each$1(arr, function (val) {
map$$1[val] = 1;
});
arr.length = 0;
each$1(map$$1, function (flag, val) {
arr.push(val);
});
}
/**
* @file Visual solution, for consistent option specification.
*/
function hasKeys(obj) {
if (obj) {
for (var name in obj){
if (obj.hasOwnProperty(name)) {
return true;
}
}
}
}
/**
* @param {Object} option
* @param {Array.<string>} stateList
* @param {Function} [supplementVisualOption]
* @return {Object} visualMappings <state, <visualType,
module:echarts/visual/VisualMapping>>
*/
function createVisualMappings(option, stateList, supplementVisualOption) {
var visualMappings = {};
return visualMappings;
function createMappings() {
var Creater = function () {};
// Make sure hidden fields will not be visited by
// object iteration (with hasOwnProperty checking).
Creater.prototype.__hidden = Creater.prototype;
var obj = new Creater();
return obj;
}
}
/**
* @param {Object} thisOption
* @param {Object} newOption
* @param {Array.<string>} keys
*/
function replaceVisualOption(thisOption, newOption, keys) {
// Visual attributes merge is not supported, otherwise it
// brings overcomplicated merge logic. See #2853. So if
// newOption has anyone of these keys, all of these keys
// will be reset. Otherwise, all keys remain.
var has;
each$1(keys, function (key) {
if (newOption.hasOwnProperty(key) && hasKeys(newOption[key])) {
has = true;
}
});
has && each$1(keys, function (key) {
if (newOption.hasOwnProperty(key) && hasKeys(newOption[key])) {
thisOption[key] = clone(newOption[key]);
}
else {
delete thisOption[key];
}
});
}
/**
* @param {Array.<string>} stateList
* @param {Object} visualMappings <state, Object.<visualType,
module:echarts/visual/VisualMapping>>
* @param {module:echarts/data/List} list
* @param {Function} getValueState param: valueOrIndex, return: state.
* @param {object} [scope] Scope for getValueState
* @param {string} [dimension] Concrete dimension, if used.
*/
// ???! handle brush?
function applyVisual(stateList, visualMappings, data, getValueState, scope,
dimension) {
var visualTypesMap = {};
each$1(stateList, function (state) {
var visualTypes = VisualMapping.prepareVisualTypes(visualMappings[state]);
visualTypesMap[state] = visualTypes;
});
var dataIndex;
function getVisual(key) {
return data.getItemVisual(dataIndex, key);
}
if (dimension == null) {
data.each(eachItem);
}
else {
data.each([dimension], eachItem);
}
/**
* @param {module:echarts/data/List} data
* @param {Array.<string>} stateList
* @param {Object} visualMappings <state, Object.<visualType,
module:echarts/visual/VisualMapping>>
* @param {Function} getValueState param: valueOrIndex, return: state.
* @param {number} [dim] dimension or dimension index.
*/
function incrementalApplyVisual(stateList, visualMappings, getValueState, dim) {
var visualTypesMap = {};
each$1(stateList, function (state) {
var visualTypes = VisualMapping.prepareVisualTypes(visualMappings[state]);
visualTypesMap[state] = visualTypes;
});
function getVisual(key) {
return data.getItemVisual(dataIndex, key);
}
// Consider performance
if (rawDataItem && rawDataItem.visualMap === false) {
return;
}
var x = itemLayout.x;
var y = itemLayout.y;
var width = itemLayout.width;
var height = itemLayout.height;
var p = points[0];
if (contain$1(points, x, y)
|| contain$1(points, x + width, y)
|| contain$1(points, x, y + height)
|| contain$1(points, x + width, y + height)
|| BoundingRect.create(itemLayout).contain(p[0], p[1])
|| lineIntersectPolygon(x, y, x + width, y, points)
|| lineIntersectPolygon(x, y, x, y + height, points)
|| lineIntersectPolygon(x + width, y, x + width, y + height,
points)
|| lineIntersectPolygon(x, y + height, x + width, y + height,
points)
) {
return true;
}
}
}
};
function getLineSelectors(xyIndex) {
var xy = ['x', 'y'];
var wh = ['width', 'height'];
return {
point: function (itemLayout, selectors, area) {
if (itemLayout) {
var range = area.range;
var p = itemLayout[xyIndex];
return inLineRange(p, range);
}
},
rect: function (itemLayout, selectors, area) {
if (itemLayout) {
var range = area.range;
var layoutRange = [
itemLayout[xy[xyIndex]],
itemLayout[xy[xyIndex]] + itemLayout[wh[xyIndex]]
];
layoutRange[1] < layoutRange[0] && layoutRange.reverse();
return inLineRange(layoutRange[0], range)
|| inLineRange(layoutRange[1], range)
|| inLineRange(range[0], layoutRange)
|| inLineRange(range[1], layoutRange);
}
}
};
}
function nearZero(val) {
return val <= (1e-6) && val >= -(1e-6);
}
/**
* [option in constructor]:
* {
* Index/Id/Name of geo, xAxis, yAxis, grid: See util/model#parseFinder.
* }
*
*
* [targetInfo]:
*
* There can be multiple axes in a single targetInfo. Consider the case
* of `grid` component, a targetInfo represents a grid which contains one or more
* cartesian and one or more axes. And consider the case of parallel system,
* which has multiple axes in a coordinate system.
* Can be {
* panelId: ...,
* coordSys: <a representitive cartesian in grid (first cartesian by default)>,
* coordSyses: all cartesians.
* gridModel: <grid component>
* xAxes: correspond to coordSyses on index
* yAxes: correspond to coordSyses on index
* }
* or {
* panelId: ...,
* coordSys: <geo coord sys>
* coordSyses: [<geo coord sys>]
* geoModel: <geo component>
* }
*
*
* [panelOpt]:
*
* Make from targetInfo. Input to BrushController.
* {
* panelId: ...,
* rect: ...
* }
*
*
* [area]:
*
* Generated by BrushController or user input.
* {
* panelId: Used to locate coordInfo directly. If user inpput, no panelId.
* brushType: determine how to convert to/from coord('rect' or 'polygon' or
'lineX/Y').
* Index/Id/Name of geo, xAxis, yAxis, grid: See util/model#parseFinder.
* range: pixel range.
* coordRange: representitive coord range (the first one of coordRanges).
* coordRanges: <Array> coord ranges, used in multiple cartesian in one grid.
* }
*/
/**
* @param {Object} option contains Index/Id/Name of xAxis/yAxis/geo/grid
* Each can be {number|Array.<number>}. like: {xAxisIndex: [3, 4]}
* @param {module:echarts/model/Global} ecModel
* @param {Object} [opt]
* @param {Array.<string>} [opt.include] include coordinate system types.
*/
function BrushTargetManager(option, ecModel, opt) {
/**
* @private
* @type {Array.<Object>}
*/
var targetInfoList = this._targetInfoList = [];
var info = {};
var foundCpts = parseFinder$1(ecModel, option);
if (__DEV__) {
assert$1(
!targetInfo || targetInfo === true || area.coordRange,
'coordRange must be specified when coord index specified.'
);
assert$1(
!targetInfo || targetInfo !== true || area.range,
'range must be specified in global brush.'
);
}
/**
* If return Object, a coord found.
* If reutrn true, global found.
* Otherwise nothing found.
*
* @param {Object} area
* @param {Array} targetInfoList
* @return {Object|boolean}
*/
proto$2.findTargetInfo = function (area, ecModel) {
var targetInfoList = this._targetInfoList;
var foundCpts = parseFinder$1(ecModel, area);
return true;
};
function formatMinMax(minMax) {
minMax[0] > minMax[1] && minMax.reverse();
return minMax;
}
var targetInfoBuilders = {
grid: function (foundCpts, targetInfoList) {
var xAxisModels = foundCpts.xAxisModels;
var yAxisModels = foundCpts.yAxisModels;
var gridModels = foundCpts.gridModels;
// Remove duplicated.
var gridModelMap = createHashMap();
var xAxesHas = {};
var yAxesHas = {};
gridModelMap.each(function (gridModel) {
var grid = gridModel.coordinateSystem;
var cartesians = [];
var targetInfoMatchers = [
// grid
function (foundCpts, targetInfo) {
var xAxisModel = foundCpts.xAxisModel;
var yAxisModel = foundCpts.yAxisModel;
var gridModel = foundCpts.gridModel;
// geo
function (foundCpts, targetInfo) {
var geoModel = foundCpts.geoModel;
return geoModel && geoModel === targetInfo.geoModel;
}
];
var panelRectBuilder = {
grid: function () {
// grid is not Transformable.
return this.coordSys.grid.getRect().clone();
},
geo: function () {
var coordSys = this.coordSys;
var rect = coordSys.getBoundingRect().clone();
// geo roam and zoom transform
rect.applyTransform(getTransform(coordSys));
return rect;
}
};
var coordConvert = {
var diffProcessor = {
lineX: curry$5(axisDiffProcessor, 0),
function getSize(xyMinMax) {
return xyMinMax
? [xyMinMax[0][1] - xyMinMax[0][0], xyMinMax[1][1] - xyMinMax[1][0]]
: [NaN, NaN];
}
/**
* Layout for visual, the priority higher than other layout, and before brush
visual.
*/
registerLayout(PRIORITY_BRUSH, function (ecModel, api, payload) {
ecModel.eachComponent({mainType: 'brush'}, function (brushModel) {
brushTargetManager.setInputRanges(brushModel.areas, ecModel);
});
});
/**
* Register the visual encoding if this modules required.
*/
registerVisual(PRIORITY_BRUSH, function (ecModel, api, payload) {
var thisBrushSelected = {
brushId: brushModel.id,
brushIndex: brushIndex,
brushName: brushModel.name,
areas: clone(brushModel.areas),
selected: []
};
// Every brush component exists in event params, convenient
// for user to find by index.
brushSelected.push(thisBrushSelected);
function linkOthers(seriesIndex) {
return brushLink === 'all' || linkedSeriesMap[seriesIndex];
}
/**
* Logic for each series: (If the logic has to be modified one day, do it
carefully!)
*
* ( brushed ┬ && ┬hasBrushExist ┬ && linkOthers ) => StepA: ┬record, ┬
StepB: ┬visualByRecord.
* !brushed┘ ├hasBrushExist ┤ └nothing,┘
├visualByRecord.
* └!hasBrushExist┘
└nothing.
* ( !brushed && ┬hasBrushExist ┬ && linkOthers ) => StepA: nothing,
StepB: ┬visualByRecord.
* └!hasBrushExist┘
└nothing.
* ( brushed ┬ && !linkOthers ) => StepA: nothing,
StepB: ┬visualByCheck.
* !brushed┘
└nothing.
* ( !brushed && !linkOthers ) => StepA: nothing,
StepB: nothing.
*/
// Step A
ecModel.eachSeries(function (seriesModel, seriesIndex) {
var rangeInfoList = rangeInfoBySeries[seriesIndex] = [];
// Step B
ecModel.eachSeries(function (seriesModel, seriesIndex) {
var seriesBrushSelected = {
seriesId: seriesModel.id,
seriesIndex: seriesIndex,
seriesName: seriesModel.name,
dataIndex: []
};
// Every series exists in event params, convenient
// for user to find series by seriesIndex.
thisBrushSelected.selected.push(seriesBrushSelected);
});
var zr = api.getZr();
if (zr[DISPATCH_FLAG]) {
return;
}
if (!zr[DISPATCH_METHOD]) {
zr[DISPATCH_METHOD] = doDispatch;
}
fn(api, brushSelected);
}
function getSelectorsByBrushType(seriesModel) {
var brushSelector = seriesModel.brushSelector;
if (isString(brushSelector)) {
var sels = [];
each$1(selector, function (selectorsByElementType, brushType) {
sels[brushType] = function (dataIndex, data, selectors, area) {
var itemLayout = data.getItemLayout(dataIndex);
return selectorsByElementType[brushSelector](itemLayout, selectors,
area);
};
});
return sels;
}
else if (isFunction$1(brushSelector)) {
var bSelector = {};
each$1(selector, function (sel, brushType) {
bSelector[brushType] = brushSelector;
});
return bSelector;
}
return brushSelector;
}
function bindSelector(area) {
var selectors = area.selectors = {};
each$1(selector[area.brushType], function (selFn, elType) {
// Do not use function binding or curry for performance.
selectors[elType] = function (itemLayout) {
return selFn(itemLayout, selectors, area);
};
});
return area;
}
var boundingRectBuilders = {
lineX: noop,
lineY: noop,
function getBoundingRectFromMinMax(minMax) {
return new BoundingRect(
minMax[0][0],
minMax[1][0],
minMax[0][1] - minMax[0][0],
minMax[1][1] - minMax[1][0]
);
}
type: 'brush',
/**
* @protected
*/
defaultOption: {
// inBrush: null,
// outOfBrush: null,
toolbox: null, // Default value see preprocessor.
brushLink: null, // Series indices array, broadcast using dataIndex.
// or 'all', which means all series. 'none' or null
means no series.
seriesIndex: 'all', // seriesIndex array, specify series controlled by
this brush component.
geoIndex: null, //
xAxisIndex: null,
yAxisIndex: null,
// FIXME
// 试验效果
removeOnClick: true,
z: 10000
},
/**
* @readOnly
* @type {Array.<Object>}
*/
areas: [],
/**
* Current activated brush type.
* If null, brush is inactived.
* see module:echarts/component/helper/BrushController
* @readOnly
* @type {string}
*/
brushType: null,
/**
* Current brush opt.
* see module:echarts/component/helper/BrushController
* @readOnly
* @type {Object}
*/
brushOption: {},
/**
* @readOnly
* @type {Array.<Object>}
*/
coordInfoList: [],
/**
* If ranges is null/undefined, range state remain.
*
* @param {Array.<Object>} [ranges]
*/
setAreas: function (areas) {
if (__DEV__) {
assert$1(isArray(areas));
each$1(areas, function (area) {
assert$1(area.brushType, 'Illegal areas');
});
}
/**
* see module:echarts/component/helper/BrushController
* @param {Object} brushOption
*/
setBrushOption: function (brushOption) {
this.brushOption = generateBrushOption(this.option, brushOption);
this.brushType = this.brushOption.brushType;
}
});
extendComponentView({
type: 'brush',
/**
* @readOnly
* @type {module:echarts/model/Global}
*/
this.ecModel = ecModel;
/**
* @readOnly
* @type {module:echarts/ExtensionAPI}
*/
this.api = api;
/**
* @readOnly
* @type {module:echarts/component/brush/BrushModel}
*/
this.model;
/**
* @private
* @type {module:echarts/component/helper/BrushController}
*/
(this._brushController = new BrushController(api.getZr()))
.on('brush', bind(this._onBrush, this))
.mount();
},
/**
* @override
*/
render: function (brushModel) {
this.model = brushModel;
return updateController.apply(this, arguments);
},
/**
* @override
*/
updateTransform: updateController,
/**
* @override
*/
updateView: updateController,
// /**
// * @override
// */
// updateLayout: updateController,
// /**
// * @override
// */
// updateVisual: updateController,
/**
* @override
*/
dispose: function () {
this._brushController.dispose();
},
/**
* @private
*/
_onBrush: function (areas, opt) {
var modelId = this.model.id;
this.model.brushTargetManager.setOutputRanges(areas, this.ecModel);
/**
* payload: {
* brushIndex: number, or,
* brushId: string, or,
* brushName: string,
* globalRanges: Array
* }
*/
registerAction(
{type: 'brush', event: 'brush' /*, update: 'updateView' */},
function (payload, ecModel) {
ecModel.eachComponent({mainType: 'brush', query: payload}, function
(brushModel) {
brushModel.setAreas(payload.areas);
});
}
);
/**
* payload: {
* brushComponents: [
* {
* brushId,
* brushIndex,
* brushName,
* series: [
* {
* seriesId,
* seriesIndex,
* seriesName,
* rawIndices: [21, 34, ...]
* },
* ...
* ]
* },
* ...
* ]
* }
*/
registerAction(
{type: 'brushSelect', event: 'brushSelected', update: 'none'},
function () {}
);
/**
* @private
* @type {string}
*/
this._brushType;
/**
* @private
* @type {string}
*/
this._brushMode;
}
Brush.defaultOption = {
show: true,
type: ['rect', 'polygon', 'lineX', 'lineY', 'keep', 'clear'],
icon: {
rect: 'M7.3,34.7 M0.4,10V-0.2h9.8 M89.6,10V-0.2h-9.8 M0.4,60v10.2h9.8
M89.6,60v10.2h-9.8 M12.3,22.4V10.5h13.1 M33.6,10.5h7.8 M49.1,10.5h7.8
M77.5,22.4V10.5h-13 M12.3,31.1v8.2 M77.7,31.1v8.2 M12.3,47.6v11.9h13.1
M33.6,59.5h7.6 M49.1,59.5 h7.7 M77.5,47.6v11.9h-13', // jshint ignore:line
polygon: 'M55.2,34.9c1.7,0,3.1,1.4,3.1,3.1s-1.4,3.1-3.1,3.1 s-3.1-1.4-3.1-
3.1S53.5,34.9,55.2,34.9z M50.4,51c1.7,0,3.1,1.4,3.1,3.1c0,1.7-1.4,3.1-3.1,3.1c-
1.7,0-3.1-1.4-3.1-3.1 C47.3,52.4,48.7,51,50.4,51z M55.6,37.1l1.5-7.8
M60.1,13.5l1.6-8.7l-7.8,4 M59,19l-1,5.3 M24,16.1l6.4,4.9l6.4-3.3 M48.5,11.6 l-
5.9,3.1 M19.1,12.8L9.7,5.1l1.1,7.7 M13.4,29.8l1,7.3l6.6,1.6 M11.6,18.4l1,6.1
M32.8,41.9 M26.6,40.4 M27.3,40.2l6.1,1.6 M49.9,52.1l-5.6-7.6l-4.9-1.2', // jshint
ignore:line
lineX: 'M15.2,30 M19.7,15.6V1.9H29 M34.8,1.9H40.4 M55.3,15.6V1.9H45.9
M19.7,44.4V58.1H29 M34.8,58.1H40.4 M55.3,44.4 V58.1H45.9 M12.5,20.3l-
9.4,9.6l9.6,9.8 M3.1,29.9h16.5 M62.5,20.3l9.4,9.6L62.3,39.7 M71.9,29.9H55.4', //
jshint ignore:line
lineY: 'M38.8,7.7 M52.7,12h13.2v9 M65.9,26.6V32 M52.7,46.3h13.2v-9
M24.9,12H11.8v9 M11.8,26.6V32 M24.9,46.3H11.8v-9 M48.2,5.1l-9.3-9l-9.4,9.2 M38.9-
3.9V12 M48.2,53.3l-9.3,9l-9.4-9.2 M38.9,62.3V46.4', // jshint ignore:line
keep: 'M4,10.5V1h10.3 M20.7,1h6.1 M33,1h6.1 M55.4,10.5V1H45.2 M4,17.3v6.6
M55.6,17.3v6.6 M4,30.5V40h10.3 M20.7,40 h6.1 M33,40h6.1 M55.4,30.5V40H45.2
M21,18.9h62.9v48.6H21V18.9z', // jshint ignore:line
clear: 'M22,14.7l30.9,31 M52.9,14.7L22,45.7 M4.7,16.8V4.2h13.1 M26,4.2h7.8
M41.6,4.2h7.8 M70.3,16.8V4.2H57.2 M4.7,25.9v8.6 M70.3,25.9v8.6 M4.7,43.2v12.6h13.1
M26,55.8h7.8 M41.6,55.8h7.8 M70.3,43.2v12.6H57.2' // jshint ignore:line
},
// `rect`, `polygon`, `lineX`, `lineY`, `keep`, `clear`
title: clone(brushLang.title)
};
proto$3.getIcons = function () {
var model = this.model;
var availableIcons = model.get('icon', true);
var icons = {};
each$1(model.get('type', true), function (type) {
if (availableIcons[type]) {
icons[type] = availableIcons[type];
}
});
return icons;
};
api.dispatchAction({
type: 'brush',
command: 'clear',
// Clear all areas of all brush components.
areas: []
});
}
else {
api.dispatchAction({
type: 'takeGlobalCursor',
key: 'brush',
brushOption: {
brushType: type === 'keep'
? brushType
: (brushType === type ? false : type),
brushMode: type === 'keep'
? (brushMode === 'multiple' ? 'single' : 'multiple')
: brushMode
}
});
}
};
register$1('brush', Brush);
/**
* Brush component entry
*/
registerPreprocessor(preprocessor$1);
// (24*60*60*1000)
var PROXIMATE_ONE_DAY = 86400000;
/**
* Calendar
*
* @constructor
*
* @param {Object} calendarModel calendarModel
* @param {Object} ecModel ecModel
* @param {Object} api api
*/
function Calendar(calendarModel, ecModel, api) {
this._model = calendarModel;
}
Calendar.prototype = {
constructor: Calendar,
type: 'calendar',
// Required in createListFromData
getDimensionsInfo: function () {
return [{name: 'time', type: 'time'}, 'value'];
},
getRangeInfo: function () {
return this._rangeInfo;
},
getModel: function () {
return this._model;
},
getRect: function () {
return this._rect;
},
getCellWidth: function () {
return this._sw;
},
getCellHeight: function () {
return this._sh;
},
getOrient: function () {
return this._orient;
},
/**
* getFirstDayOfWeek
*
* @example
* 0 : start at Sunday
* 1 : start at Monday
*
* @return {number}
*/
getFirstDayOfWeek: function () {
return this._firstDayOfWeek;
},
/**
* get date info
*
* @param {string|number} date date
* @return {Object}
* {
* y: string, local full year, eg., '1940',
* m: string, local month, from '01' ot '12',
* d: string, local date, from '01' to '31' (if exists),
* day: It is not date.getDay(). It is the location of the cell in a week,
from 0 to 6,
* time: timestamp,
* formatedDate: string, yyyy-MM-dd,
* date: original date object.
* }
*/
getDateInfo: function (date) {
date = parseDate(date);
var y = date.getFullYear();
var m = date.getMonth() + 1;
m = m < 10 ? '0' + m : m;
var d = date.getDate();
d = d < 10 ? '0' + d : d;
var day = date.getDay();
return {
y: y,
m: m,
d: d,
day: day,
time: date.getTime(),
formatedDate: y + '-' + m + '-' + d,
date: date
};
},
return this.getDateInfo(date);
},
this._firstDayOfWeek = +this._model.getModel('dayLabel').get('firstDay');
this._orient = this._model.get('orient');
this._lineWidth =
this._model.getModel('itemStyle').getItemStyle().lineWidth || 0;
this._rangeInfo = this._getRangeInfo(this._initRangeOption());
var weeks = this._rangeInfo.weeks || 1;
var whNames = ['width', 'height'];
var cellSize = this._model.get('cellSize').slice();
var layoutParams = this._model.getBoxLayoutParams();
var cellNumbers = this._orient === 'horizontal' ? [weeks, 7] : [7, weeks];
var whGlobal = {
width: api.getWidth(),
height: api.getHeight()
};
var calendarRect = this._rect = getLayoutRect(layoutParams, whGlobal);
this._sw = cellSize[0];
this._sh = cellSize[1];
},
/**
* Convert a time data(time, value) item to (x, y) point.
*
* @override
* @param {Array|number} data data
* @param {boolean} [clamp=true] out of range
* @return {Array} point
*/
dataToPoint: function (data, clamp) {
isArray(data) && (data = data[0]);
clamp == null && (clamp = true);
return [
this._rect.x + nthWeek * this._sw + this._sw / 2,
this._rect.y + week * this._sh + this._sh / 2
];
},
/**
* Convert a (x, y) point to time data
*
* @override
* @param {string} point point
* @return {string} data
*/
pointToData: function (point) {
var date = this.pointToDate(point);
/**
* Convert a time date item to (x, y) four point.
*
* @param {Array} data date[0] is date
* @param {boolean} [clamp=true] out of range
* @return {Object} point
*/
dataToRect: function (data, clamp) {
var point = this.dataToPoint(data, clamp);
return {
contentShape: {
x: point[0] - (this._sw - this._lineWidth) / 2,
y: point[1] - (this._sh - this._lineWidth) / 2,
width: this._sw - this._lineWidth,
height: this._sh - this._lineWidth
},
center: point,
tl: [
point[0] - this._sw / 2,
point[1] - this._sh / 2
],
tr: [
point[0] + this._sw / 2,
point[1] - this._sh / 2
],
br: [
point[0] + this._sw / 2,
point[1] + this._sh / 2
],
bl: [
point[0] - this._sw / 2,
point[1] + this._sh / 2
]
};
},
/**
* Convert a (x, y) point to time date
*
* @param {Array} point point
* @return {Object} date
*/
pointToDate: function (point) {
var nthX = Math.floor((point[0] - this._rect.x) / this._sw) + 1;
var nthY = Math.floor((point[1] - this._rect.y) / this._sh) + 1;
var range = this._rangeInfo.range;
if (this._orient === 'vertical') {
return this._getDateByWeeksAndDay(nthY, nthX - 1, range);
}
/**
* @inheritDoc
*/
convertToPixel: curry(doConvert$2, 'dataToPoint'),
/**
* @inheritDoc
*/
convertFromPixel: curry(doConvert$2, 'pointToData'),
/**
* initRange
*
* @private
* @return {Array} [start, end]
*/
_initRangeOption: function () {
var range = this._model.get('range');
var rg = range;
if (/^\d{4}$/.test(rg)) {
range = [rg + '-01-01', rg + '-12-31'];
}
if (/^\d{4}[\/|-]\d{1,2}$/.test(rg)) {
if (/^\d{4}[\/|-]\d{1,2}[\/|-]\d{1,2}$/.test(rg)) {
range = [rg, rg];
}
return range;
},
/**
* range info
*
* @private
* @param {Array} range range ['2017-01-01', '2017-07-08']
* If range[0] > range[1], they will not be reversed.
* @return {Object} obj
*/
_getRangeInfo: function (range) {
range = [
this.getDateInfo(range[0]),
this.getDateInfo(range[1])
];
var reversed;
if (range[0].time > range[1].time) {
reversed = true;
range.reverse();
}
// Consider case:
// Firstly set system timezone as "Time Zone: America/Toronto",
// ```
// var first = new Date(1478412000000 - 3600 * 1000 * 2.5);
// var second = new Date(1478412000000);
// var allDays = Math.floor(second / ONE_DAY) - Math.floor(first / ONE_DAY)
+ 1;
// ```
// will get wrong result because of DST. So we should fix it.
var date = new Date(range[0].time);
var startDateNum = date.getDate();
var endDateNum = range[1].date.getDate();
date.setDate(startDateNum + allDay - 1);
// The bias can not over a month, so just compare date.
if (date.getDate() !== endDateNum) {
var sign = date.getTime() - range[1].time > 0 ? 1 : -1;
while (date.getDate() !== endDateNum && (date.getTime() -
range[1].time) * sign > 0) {
allDay -= sign;
date.setDate(startDateNum + allDay - 1);
}
}
return {
range: [range[0].formatedDate, range[1].formatedDate],
start: range[0],
end: range[1],
allDay: allDay,
weeks: weeks,
// From 0.
nthWeek: nthWeek,
fweek: range[0].day,
lweek: range[1].day
};
},
/**
* get date by nthWeeks and week day in range
*
* @private
* @param {number} nthWeek the week
* @param {number} day the week day
* @param {Array} range [d1, d2]
* @return {Object}
*/
_getDateByWeeksAndDay: function (nthWeek, day, range) {
var rangeInfo = this._getRangeInfo(range);
return this.getDateInfo(date);
}
};
Calendar.dimensions = Calendar.prototype.dimensions;
Calendar.getDimensionsInfo = Calendar.prototype.getDimensionsInfo;
ecModel.eachSeries(function (calendarSeries) {
if (calendarSeries.get('coordinateSystem') === 'calendar') {
// Inject coordinate system
calendarSeries.coordinateSystem =
calendarList[calendarSeries.get('calendarIndex') || 0];
}
});
return calendarList;
};
CoordinateSystemManager.register('calendar', Calendar);
type: 'calendar',
/**
* @type {module:echarts/coord/calendar/Calendar}
*/
coordinateSystem: null,
defaultOption: {
zlevel: 0,
z: 2,
left: 80,
top: 60,
cellSize: 20,
// horizontal vertical
orient: 'horizontal',
// start end
position: 'start',
margin: '50%', // 50% of cellSize
nameMap: 'en',
color: '#000'
},
// start end
position: 'start',
margin: 5,
// center or left
align: 'center',
// cn en []
nameMap: 'en',
formatter: null,
color: '#000'
},
/**
* @override
*/
init: function (option, parentModel, ecModel, extraOpt) {
var inputPositionParams = getLayoutParams(option);
mergeAndNormalizeLayoutParams$1(option, inputPositionParams);
},
/**
* @override
*/
mergeOption: function (option, extraOpt) {
CalendarModel.superApply(this, 'mergeOption', arguments);
mergeAndNormalizeLayoutParams$1(this.option, option);
}
});
if (!isArray(cellSize)) {
cellSize = target.cellSize = [cellSize, cellSize];
}
else if (cellSize.length === 1) {
cellSize[1] = cellSize[0];
}
mergeLayoutParam(target, raw, {
type: 'box', ignoreSize: ignoreSize
});
}
var MONTH_TEXT = {
EN: [
'Jan', 'Feb', 'Mar',
'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep',
'Oct', 'Nov', 'Dec'
],
CN: [
'一月', '二月', '三月',
'四月', '五月', '六月',
'七月', '八月', '九月',
'十月', '十一月', '十二月'
]
};
var WEEK_TEXT = {
EN: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
CN: ['日', '一', '二', '三', '四', '五', '六']
};
extendComponentView({
type: 'calendar',
/**
* top/left line points
* @private
*/
_tlpoints: null,
/**
* bottom/right line points
* @private
*/
_blpoints: null,
/**
* first day of month
* @private
*/
_firstDayOfMonth: null,
/**
* first day point of month
* @private
*/
_firstDayPoints: null,
group.removeAll();
// range info
var rangeData = coordSys.getRangeInfo();
var orient = coordSys.getOrient();
// every rect
var rect = new Rect({
shape: {
x: point[0],
y: point[1],
width: sw,
height: sh
},
cursor: 'default',
style: itemRectStyleModel
});
group.add(rect);
}
},
var lineStyleModel =
calendarModel.getModel('splitLine.lineStyle').getLineStyle();
var show = calendarModel.get('splitLine.show');
this._tlpoints = [];
this._blpoints = [];
this._firstDayOfMonth = [];
this._firstDayPoints = [];
if (i === 0) {
firstDay = coordSys.getDateInfo(rangeData.start.y + '-' +
rangeData.start.m);
}
addPoints(coordSys.getNextNDay(rangeData.end.time, 1).formatedDate);
function addPoints(date) {
self._firstDayOfMonth.push(coordSys.getDateInfo(date));
self._firstDayPoints.push(coordSys.dataToRect([date], false).tl);
self._tlpoints.push(points[0]);
self._blpoints.push(points[points.length - 1]);
},
return rs;
},
group.add(poyline);
},
return points;
},
_formatterLabel: function (formatter, params) {
return params.nameMap;
},
point = point.slice();
var aligns = ['center', 'bottom'];
var rotate = 0;
if (position === 'left' || position === 'right') {
rotate = Math.PI / 2;
}
return {
rotation: rotate,
position: point,
style: {
textAlign: aligns[0],
textVerticalAlign: aligns[1]
}
};
},
// render year
_renderYearText: function (calendarModel, rangeData, orient, group) {
var yearLabel = calendarModel.getModel('yearLabel');
if (!yearLabel.get('show')) {
return;
}
var posPoints = {
top: [xc, points[idx][1]],
bottom: [xc, points[1 - idx][1]],
left: [points[1 - idx][0], yc],
right: [points[idx][0], yc]
};
var params = {
start: rangeData.start.y,
end: rangeData.end.y,
nameMap: name
};
group.add(yearText);
},
if (isCenter) {
align = 'center';
}
if (isCenter) {
vAlign = 'middle';
}
return {
x: x,
y: y,
textAlign: align,
textVerticalAlign: vAlign
};
},
if (!monthLabel.get('show')) {
return;
}
if (isString(nameMap)) {
nameMap = MONTH_TEXT[nameMap.toUpperCase()] || [];
}
if (isCenter) {
var firstDayPoints = this._firstDayPoints[i];
tmp[axis] = (firstDayPoints[axis] + termPoints[0][i + 1][axis]) /
2;
}
group.add(monthText);
}
},
return {
x: x,
y: y,
textAlign: align,
textVerticalAlign: vAlign
};
},
// render weeks
_renderWeekText: function (calendarModel, rangeData, orient, group) {
var dayLabel = calendarModel.getModel('dayLabel');
if (!dayLabel.get('show')) {
return;
}
extend(
setTextStyle(weekText.style, dayLabel, {text: nameMap[day]}),
this._weekTextPositionControl(point, orient, pos, margin, cellSize)
);
group.add(weekText);
}
}
});
/**
* @file calendar.js
* @author dxh
*/
// Model
extendComponentModel({
type: 'title',
defaultOption: {
// 一级层叠
zlevel: 0,
// 二级层叠
z: 6,
show: true,
text: '',
// 超链接跳转
// link: null,
// 仅支持 self | blank
target: 'blank',
subtext: '',
// 超链接跳转
// sublink: null,
// 仅支持 self | blank
subtarget: 'blank',
// 水平对齐
// 'auto' | 'left' | 'right' | 'center'
// 默认根据 left 的位置判断是左对齐还是右对齐
// textAlign: null
//
// 垂直对齐
// 'auto' | 'top' | 'bottom' | 'middle'
// 默认根据 top 位置判断是上对齐还是下对齐
// textBaseline: null
backgroundColor: 'rgba(0,0,0,0)',
// 标题边框颜色
borderColor: '#ccc',
// 标题内边距,单位 px,默认各方向内边距为 5,
// 接受数组分别设定上右下左边距,同 css
padding: 5,
// View
extendComponentView({
type: 'title',
if (!titleModel.get('show')) {
return;
}
textEl.silent = !link;
subTextEl.silent = !sublink;
if (link) {
textEl.on('click', function () {
window.open(link, '_' + titleModel.get('target'));
});
}
if (sublink) {
subTextEl.on('click', function () {
window.open(sublink, '_' + titleModel.get('subtarget'));
});
}
group.add(textEl);
subText && group.add(subTextEl);
// If no subText, but add subTextEl, there will be an empty line.
// Render background
// Get groupRect again because textAlign has been changed
groupRect = group.getBoundingRect();
var padding = layoutRect.margin;
var style = titleModel.getItemStyle(['color', 'opacity']);
style.fill = titleModel.get('backgroundColor');
var rect = new Rect({
shape: {
x: groupRect.x - padding[3],
y: groupRect.y - padding[0],
width: groupRect.width + padding[1] + padding[3],
height: groupRect.height + padding[0] + padding[2],
r: titleModel.get('borderRadius')
},
style: style,
silent: true
});
subPixelOptimizeRect(rect);
group.add(rect);
}
});
ComponentModel.registerSubTypeDefaulter('dataZoom', function () {
// Default 'slider' when no type specified.
return 'slider';
});
/**
* @param {string} coordType
* @return {boolean}
*/
function isCoordSupported(coordType) {
return indexOf(COORDS, coordType) >= 0;
}
/**
* Create "each" method to iterate names.
*
* @pubilc
* @param {Array.<string>} names
* @param {Array.<string>=} attrs
* @return {Function}
*/
function createNameEach(names, attrs) {
names = names.slice();
var capitalNames = map(names, capitalFirst);
attrs = (attrs || []).slice();
var capitalAttrs = map(attrs, capitalFirst);
callback.call(context, nameObj);
});
};
}
/**
* Iterate each dimension name.
*
* @public
* @param {Function} callback The parameter is like:
* {
* name: 'angle',
* capital: 'Angle',
* axis: 'angleAxis',
* axisIndex: 'angleAixs',
* index: 'angleIndex'
* }
* @param {Object} context
*/
var eachAxisDim$1 = createNameEach(AXIS_DIMS, ['axisIndex', 'axis', 'index',
'id']);
/**
* If tow dataZoomModels has the same axis controlled, we say that they are
'linked'.
* dataZoomModels and 'links' make up one or more graphics.
* This function finds the graphic where the source dataZoomModel is in.
*
* @public
* @param {Function} forEachNode Node iterator.
* @param {Function} forEachEdgeType edgeType iterator
* @param {Function} edgeIdGetter Giving node and edgeType, return an array of edge
id.
* @return {Function} Input: sourceNode, Output: Like {nodes: [], dims: {}}
*/
function createLinkedNodesFinder(forEachNode, forEachEdgeType, edgeIdGetter) {
forEachEdgeType(function (edgeType) {
result.records[edgeType.name] = {};
});
if (!sourceNode) {
return result;
}
absorb(sourceNode, result);
var existsLink;
do {
existsLink = false;
forEachNode(processSingleNode);
}
while (existsLink);
function processSingleNode(node) {
if (!isNodeAbsorded(node, result) && isLinked(node, result)) {
absorb(node, result);
existsLink = true;
}
}
return result;
};
/**
* Operate single axis.
* One axis can only operated by one axis operator.
* Different dataZoomModels may be defined to operate the same axis.
* (i.e. 'inside' data zoom and 'slider' data zoom components)
* So dataZoomModels share one axisProxy in that case.
*
* @class
*/
var AxisProxy = function (dimName, axisIndex, dataZoomModel, ecModel) {
/**
* @private
* @type {string}
*/
this._dimName = dimName;
/**
* @private
*/
this._axisIndex = axisIndex;
/**
* @private
* @type {Array.<number>}
*/
this._valueWindow;
/**
* @private
* @type {Array.<number>}
*/
this._percentWindow;
/**
* @private
* @type {Array.<number>}
*/
this._dataExtent;
/**
* {minSpan, maxSpan, minValueSpan, maxValueSpan}
* @private
* @type {Object}
*/
this._minMaxSpan;
/**
* @readOnly
* @type {module: echarts/model/Global}
*/
this.ecModel = ecModel;
/**
* @private
* @type {module: echarts/component/dataZoom/DataZoomModel}
*/
this._dataZoomModel = dataZoomModel;
// /**
// * @readOnly
// * @private
// */
// this.hasSeriesStacked;
};
AxisProxy.prototype = {
constructor: AxisProxy,
/**
* Whether the axisProxy is hosted by dataZoomModel.
*
* @public
* @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
* @return {boolean}
*/
hostedBy: function (dataZoomModel) {
return this._dataZoomModel === dataZoomModel;
},
/**
* @return {Array.<number>} Value can only be NaN or finite value.
*/
getDataValueWindow: function () {
return this._valueWindow.slice();
},
/**
* @return {Array.<number>}
*/
getDataPercentWindow: function () {
return this._percentWindow.slice();
},
/**
* @public
* @param {number} axisIndex
* @return {Array} seriesModels
*/
getTargetSeriesModels: function () {
var seriesModels = [];
var ecModel = this.ecModel;
ecModel.eachSeries(function (seriesModel) {
if (isCoordSupported(seriesModel.get('coordinateSystem'))) {
var dimName = this._dimName;
var axisModel = ecModel.queryComponents({
mainType: dimName + 'Axis',
index: seriesModel.get(dimName + 'AxisIndex'),
id: seriesModel.get(dimName + 'AxisId')
})[0];
if (this._axisIndex === (axisModel && axisModel.componentIndex)) {
seriesModels.push(seriesModel);
}
}
}, this);
return seriesModels;
},
getAxisModel: function () {
return this.ecModel.getComponent(this._dimName + 'Axis', this._axisIndex);
},
getOtherAxisModel: function () {
var axisDim = this._dimName;
var ecModel = this.ecModel;
var axisModel = this.getAxisModel();
var isCartesian = axisDim === 'x' || axisDim === 'y';
var otherAxisDim;
var coordSysIndexName;
if (isCartesian) {
coordSysIndexName = 'gridIndex';
otherAxisDim = axisDim === 'x' ? 'y' : 'x';
}
else {
coordSysIndexName = 'polarIndex';
otherAxisDim = axisDim === 'angle' ? 'radius' : 'angle';
}
var foundOtherAxisModel;
ecModel.eachComponent(otherAxisDim + 'Axis', function (otherAxisModel) {
if ((otherAxisModel.get(coordSysIndexName) || 0)
=== (axisModel.get(coordSysIndexName) || 0)
) {
foundOtherAxisModel = otherAxisModel;
}
});
return foundOtherAxisModel;
},
getMinMaxSpan: function () {
return clone(this._minMaxSpan);
},
/**
* Only calculate by given range and this._dataExtent, do not change anything.
*
* @param {Object} opt
* @param {number} [opt.start]
* @param {number} [opt.end]
* @param {number} [opt.startValue]
* @param {number} [opt.endValue]
*/
calculateDataWindow: function (opt) {
var dataExtent = this._dataExtent;
var axisModel = this.getAxisModel();
var scale = axisModel.axis.scale;
var rangePropMode = this._dataZoomModel.getRangePropMode();
var percentExtent = [0, 100];
var percentWindow = [
opt.start,
opt.end
];
var valueWindow = [];
// Normalize bound.
each$23([0, 1], function (idx) {
var boundValue = valueWindow[idx];
var boundPercent = percentWindow[idx];
// valueWindow[idx] = round(boundValue);
// percentWindow[idx] = round(boundPercent);
valueWindow[idx] = boundValue;
percentWindow[idx] = boundPercent;
});
return {
valueWindow: asc$1(valueWindow),
percentWindow: asc$1(percentWindow)
};
},
/**
* Notice: reset should not be called before series.restoreData() called,
* so it is recommanded to be called in "process stage" but not "model init
* stage".
*
* @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
*/
reset: function (dataZoomModel) {
if (dataZoomModel !== this._dataZoomModel) {
return;
}
// this.hasSeriesStacked = false;
// each(targetSeries, function (series) {
// var data = series.getData();
// var dataDim = data.mapDimension(this._dimName);
// var stackedDimension = data.getCalculationInfo('stackedDimension');
// if (stackedDimension && stackedDimension === dataDim) {
// this.hasSeriesStacked = true;
// }
// }, this);
this._valueWindow = dataWindow.valueWindow;
this._percentWindow = dataWindow.percentWindow;
setMinMaxSpan(this);
/**
* @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
*/
restore: function (dataZoomModel) {
if (dataZoomModel !== this._dataZoomModel) {
return;
}
/**
* @param {module: echarts/component/dataZoom/DataZoomModel} dataZoomModel
*/
filterData: function (dataZoomModel, api) {
if (dataZoomModel !== this._dataZoomModel) {
return;
}
// FIXME
// Toolbox may has dataZoom injected. And if there are stacked bar chart
// with NaN data, NaN will be filtered and stack will be wrong.
// So we need to force the mode to be set empty.
// In fect, it is not a big deal that do not support filterMode-'filter'
// when using toolbox#dataZoom, utill tooltip#dataZoom support "single axis
// selection" some day, which might need "adapt to data extent on the
// otherAxis", which is disabled by filterMode-'empty'.
// But currently, stack has been fixed to based on value but not index,
// so this is not an issue any more.
// var otherAxisModel = this.getOtherAxisModel();
// if (dataZoomModel.get('$fromToolbox')
// && otherAxisModel
// && otherAxisModel.hasSeriesStacked
// ) {
// filterMode = 'empty';
// }
// TODO
// filterMode 'weakFilter' and 'empty' is not optimized for huge data yet.
// console.time('select');
seriesData.selectRange(range);
// console.timeEnd('select');
}
});
}
function isInWindow(value) {
return value >= valueWindow[0] && value <= valueWindow[1];
}
}
};
return dataExtent;
}
if (min != null && min !== 'dataMin' && typeof min !== 'function') {
dataExtent[0] = min;
}
else if (isCategoryAxis) {
dataExtent[0] = axisDataLen > 0 ? 0 : NaN;
}
if (!axisModel.get('scale', true)) {
dataExtent[0] > 0 && (dataExtent[0] = 0);
dataExtent[1] < 0 && (dataExtent[1] = 0);
}
// For value axis, if min/max/scale are not set, we just use the extent
obtained
// by series data, which may be a little different from the extent calculated
by
// `axisHelper.getScaleExtent`. But the different just affects the experience a
// little when zooming. So it will not be fixed until some users require it
strongly.
return dataExtent;
}
if (!percentWindow) {
return;
}
axisModel.setRange(
useOrigin ? null : +valueWindow[0].toFixed(precision),
useOrigin ? null : +valueWindow[1].toFixed(precision)
);
}
function setMinMaxSpan(axisProxy) {
var minMaxSpan = axisProxy._minMaxSpan = {};
var dataZoomModel = axisProxy._dataZoomModel;
if (valueSpan != null) {
minMaxSpan[minMax + 'ValueSpan'] = valueSpan;
valueSpan = axisProxy.getAxisModel().axis.scale.parse(valueSpan);
if (valueSpan != null) {
var dataExtent = axisProxy._dataExtent;
minMaxSpan[minMax + 'Span'] = linearMap(
dataExtent[0] + valueSpan, dataExtent, [0, 100], true
);
}
}
});
}
dependencies: [
'xAxis', 'yAxis', 'zAxis', 'radiusAxis', 'angleAxis', 'singleAxis',
'series'
],
/**
* @protected
*/
defaultOption: {
zlevel: 0,
z: 4, // Higher than normal component (z: 2).
orient: null, // Default auto by axisIndex. Possible value:
'horizontal', 'vertical'.
xAxisIndex: null, // Default the first horizontal category axis.
yAxisIndex: null, // Default the first vertical category axis.
/**
* @override
*/
init: function (option, parentModel, ecModel) {
/**
* key like x_0, y_1
* @private
* @type {Object}
*/
this._dataIntervalByAxis = {};
/**
* @private
*/
this._dataInfo = {};
/**
* key like x_0, y_1
* @private
*/
this._axisProxies = {};
/**
* @readOnly
*/
this.textStyleModel;
/**
* @private
*/
this._autoThrottle = true;
/**
* 'percent' or 'value'
* @private
*/
this._rangePropMode = ['percent', 'percent'];
this.mergeDefaultAndTheme(option, ecModel);
this.doInit(rawOption);
},
/**
* @override
*/
mergeOption: function (newOption) {
var rawOption = retrieveRaw(newOption);
//FIX #2591
merge(this.option, newOption, true);
this.doInit(rawOption);
},
/**
* @protected
*/
doInit: function (rawOption) {
var thisOption = this.option;
this._setDefaultThrottle(rawOption);
updateRangeUse(this, rawOption);
this.textStyleModel = this.getModel('textStyle');
this._resetTarget();
this._giveAxisProxies();
},
/**
* @private
*/
_giveAxisProxies: function () {
var axisProxies = this._axisProxies;
/**
* @private
*/
_resetTarget: function () {
var thisOption = this.option;
eachAxisDim(function (dimNames) {
var axisIndexName = dimNames.axisIndex;
thisOption[axisIndexName] = normalizeToArray(
thisOption[axisIndexName]
);
}, this);
/**
* @private
*/
_judgeAutoMode: function () {
// Auto set only works for setOption at the first time.
// The following is user's reponsibility. So using merged
// option is OK.
var thisOption = this.option;
/**
* @private
*/
_autoSetAxisIndex: function () {
var autoAxisIndex = true;
var orient = this.get('orient', true);
var thisOption = this.option;
var dependentModels = this.dependentModels;
if (autoAxisIndex) {
// Find axis that parallel to dataZoom as default.
var dimName = orient === 'vertical' ? 'y' : 'x';
if (dependentModels[dimName + 'Axis'].length) {
thisOption[dimName + 'AxisIndex'] = [0];
autoAxisIndex = false;
}
else {
each$22(dependentModels.singleAxis, function (singleAxisModel) {
if (autoAxisIndex && singleAxisModel.get('orient', true) ===
orient) {
thisOption.singleAxisIndex =
[singleAxisModel.componentIndex];
autoAxisIndex = false;
}
});
}
}
if (autoAxisIndex) {
// Find the first category axis as default. (consider polar)
eachAxisDim(function (dimNames) {
if (!autoAxisIndex) {
return;
}
var axisIndices = [];
var axisModels = this.dependentModels[dimNames.axis];
if (axisModels.length && !axisIndices.length) {
for (var i = 0, len = axisModels.length; i < len; i++) {
if (axisModels[i].get('type') === 'category') {
axisIndices.push(i);
}
}
}
thisOption[dimNames.axisIndex] = axisIndices;
if (axisIndices.length) {
autoAxisIndex = false;
}
}, this);
}
if (autoAxisIndex) {
// FIXME
// 这里是兼容 ec2 的写法(没指定 xAxisIndex 和 yAxisIndex 时把 scatter 和双数值轴折
柱纳入 dataZoom 控制),
// 但是实际是否需要 Grid.js#getScaleByOption 来判断(考虑 time,log 等 axis
type)?
if (__DEV__) {
if (!axisModel) {
throw new Error(
dimNames.axis + ' "' + retrieve(
axisIndex,
axisId,
0
) + '" not found'
);
}
}
axisIndex = axisModel.componentIndex;
/**
* @private
*/
_autoSetOrient: function () {
var dim;
/**
* @private
*/
_isSeriesHasAllAxesTypeOf: function (seriesModel, axisType) {
// FIXME
// 需要 series 的 xAxisIndex 和 yAxisIndex 都首先自动设置上。
// 例如 series.type === scatter 时。
var is = true;
eachAxisDim(function (dimNames) {
var seriesAxisIndex = seriesModel.get(dimNames.axisIndex);
var axisModel = this.dependentModels[dimNames.axis][seriesAxisIndex];
/**
* @private
*/
_setDefaultThrottle: function (rawOption) {
// When first time user set throttle, auto throttle ends.
if (rawOption.hasOwnProperty('throttle')) {
this._autoThrottle = false;
}
if (this._autoThrottle) {
var globalOption = this.ecModel.option;
this.option.throttle =
(globalOption.animation && globalOption.animationDurationUpdate >
0)
? 100 : 20;
}
},
/**
* @public
*/
getFirstTargetAxisModel: function () {
var firstAxisModel;
eachAxisDim(function (dimNames) {
if (firstAxisModel == null) {
var indices = this.get(dimNames.axisIndex);
if (indices.length) {
firstAxisModel = this.dependentModels[dimNames.axis]
[indices[0]];
}
}
}, this);
return firstAxisModel;
},
/**
* @public
* @param {Function} callback param: axisModel, dimNames, axisIndex,
dataZoomModel, ecModel
*/
eachTargetAxis: function (callback, context) {
var ecModel = this.ecModel;
eachAxisDim(function (dimNames) {
each$22(
this.get(dimNames.axisIndex),
function (axisIndex) {
callback.call(context, dimNames, axisIndex, this, ecModel);
},
this
);
}, this);
},
/**
* @param {string} dimName
* @param {number} axisIndex
* @return {module:echarts/component/dataZoom/AxisProxy} If not found, return
null/undefined.
*/
getAxisProxy: function (dimName, axisIndex) {
return this._axisProxies[dimName + '_' + axisIndex];
},
/**
* @param {string} dimName
* @param {number} axisIndex
* @return {module:echarts/model/Model} If not found, return null/undefined.
*/
getAxisModel: function (dimName, axisIndex) {
var axisProxy = this.getAxisProxy(dimName, axisIndex);
return axisProxy && axisProxy.getAxisModel();
},
/**
* If not specified, set to undefined.
*
* @public
* @param {Object} opt
* @param {number} [opt.start]
* @param {number} [opt.end]
* @param {number} [opt.startValue]
* @param {number} [opt.endValue]
* @param {boolean} [ignoreUpdateRangeUsg=false]
*/
setRawRange: function (opt, ignoreUpdateRangeUsg) {
var option = this.option;
each$22([['start', 'startValue'], ['end', 'endValue']], function (names) {
// If only one of 'start' and 'startValue' is not null/undefined, the
other
// should be cleared, which enable clear the option.
// If both of them are not set, keep option with the original value,
which
// enable use only set start but not set end when calling
`dispatchAction`.
// The same as 'end' and 'endValue'.
if (opt[names[0]] != null || opt[names[1]] != null) {
option[names[0]] = opt[names[0]];
option[names[1]] = opt[names[1]];
}
}, this);
!ignoreUpdateRangeUsg && updateRangeUse(this, opt);
},
/**
* @public
* @return {Array.<number>} [startPercent, endPercent]
*/
getPercentRange: function () {
var axisProxy = this.findRepresentativeAxisProxy();
if (axisProxy) {
return axisProxy.getDataPercentWindow();
}
},
/**
* @public
* For example, chart.getModel().getComponent('dataZoom').getValueRange('y',
0);
*
* @param {string} [axisDimName]
* @param {number} [axisIndex]
* @return {Array.<number>} [startValue, endValue] value can only be '-' or
finite number.
*/
getValueRange: function (axisDimName, axisIndex) {
if (axisDimName == null && axisIndex == null) {
var axisProxy = this.findRepresentativeAxisProxy();
if (axisProxy) {
return axisProxy.getDataValueWindow();
}
}
else {
return this.getAxisProxy(axisDimName, axisIndex).getDataValueWindow();
}
},
/**
* @public
* @param {module:echarts/model/Model} [axisModel] If axisModel given, find
axisProxy
* corresponding to the axisModel
* @return {module:echarts/component/dataZoom/AxisProxy}
*/
findRepresentativeAxisProxy: function (axisModel) {
if (axisModel) {
return axisModel.__dzAxisProxy;
}
/**
* @return {Array.<string>}
*/
getRangePropMode: function () {
return this._rangePropMode.slice();
}
});
function retrieveRaw(option) {
var ret = {};
each$22(
['start', 'end', 'startValue', 'endValue', 'throttle'],
function (name) {
option.hasOwnProperty(name) && (ret[name] = option[name]);
}
);
return ret;
}
/**
* Find the first target coordinate system.
*
* @protected
* @return {Object} {
* grid: [
* {model: coord0, axisModels: [axis1, axis3],
coordIndex: 1},
* {model: coord1, axisModels: [axis0, axis2],
coordIndex: 0},
* ...
* ], // cartesians must not be null/undefined.
* polar: [
* {model: coord0, axisModels: [axis4], coordIndex: 0},
* ...
* ], // polars must not be null/undefined.
* singleAxis: [
* {model: coord0, axisModels: [], coordIndex: 0}
* ]
*/
getTargetCoordInfo: function () {
var dataZoomModel = this.dataZoomModel;
var ecModel = this.ecModel;
var coordSysLists = {};
return coordSysLists;
}
});
type: 'dataZoom.slider',
layoutMode: 'box',
/**
* @protected
*/
defaultOption: {
show: true,
handleStyle: {
color: '#a7b7cc'
},
labelPrecision: null,
labelFormatter: null,
showDetail: true,
showDataShadow: 'auto', // Default auto decision.
realtime: true,
zoomLock: false, // Whether disable zoom.
textStyle: {
color: '#333'
}
}
});
// Constants
var DEFAULT_LOCATION_EDGE_GAP = 7;
var DEFAULT_FRAME_BORDER_WIDTH = 1;
var DEFAULT_FILLER_SIZE = 30;
var HORIZONTAL = 'horizontal';
var VERTICAL = 'vertical';
var LABEL_GAP = 5;
var SHOW_DATA_SHADOW_SERIES_TYPE = ['line', 'bar', 'candlestick', 'scatter'];
type: 'dataZoom.slider',
/**
* @private
* @type {Object}
*/
this._displayables = {};
/**
* @private
* @type {string}
*/
this._orient;
/**
* [0, 100]
* @private
*/
this._range;
/**
* [coord of the first handle, coord of the second handle]
* @private
*/
this._handleEnds;
/**
* [length, thick]
* @private
* @type {Array.<number>}
*/
this._size;
/**
* @private
* @type {number}
*/
this._handleWidth;
/**
* @private
* @type {number}
*/
this._handleHeight;
/**
* @private
*/
this._location;
/**
* @private
*/
this._dragging;
/**
* @private
*/
this._dataShadowInfo;
this.api = api;
},
/**
* @override
*/
render: function (dataZoomModel, ecModel, api, payload) {
SliderZoomView.superApply(this, 'render', arguments);
createOrUpdate(
this,
'_dispatchZoomAction',
this.dataZoomModel.get('throttle'),
'fixRate'
);
this._orient = dataZoomModel.get('orient');
this._updateView();
},
/**
* @override
*/
remove: function () {
SliderZoomView.superApply(this, 'remove', arguments);
clear(this, '_dispatchZoomAction');
},
/**
* @override
*/
dispose: function () {
SliderZoomView.superApply(this, 'dispose', arguments);
clear(this, '_dispatchZoomAction');
},
_buildView: function () {
var thisGroup = this.group;
thisGroup.removeAll();
this._resetLocation();
this._resetInterval();
this._renderBackground();
this._renderHandle();
this._renderDataShadow();
thisGroup.add(barGroup);
this._positionGroup();
},
/**
* @private
*/
_resetLocation: function () {
var dataZoomModel = this.dataZoomModel;
var api = this.api;
/**
* @private
*/
_positionGroup: function () {
var thisGroup = this.group;
var location = this._location;
var orient = this._orient;
// Transform barGroup.
barGroup.attr(
(orient === HORIZONTAL && !inverse)
? {scale: otherAxisInverse ? [1, 1] : [1, -1]}
: (orient === HORIZONTAL && inverse)
? {scale: otherAxisInverse ? [-1, 1] : [-1, -1]}
: (orient === VERTICAL && !inverse)
? {scale: otherAxisInverse ? [1, -1] : [1, 1], rotation: Math.PI / 2}
// Dont use Math.PI, considering shadow direction.
: {scale: otherAxisInverse ? [-1, -1] : [-1, 1], rotation: Math.PI / 2}
);
// Position barGroup
var rect = thisGroup.getBoundingRect([barGroup]);
thisGroup.attr('position', [location.x - rect.x, location.y - rect.y]);
},
/**
* @private
*/
_getViewExtent: function () {
return [0, this._size[0]];
},
_renderBackground: function () {
var dataZoomModel = this.dataZoomModel;
var size = this._size;
var barGroup = this._displayables.barGroup;
barGroup.add(new Rect$2({
silent: true,
shape: {
x: 0, y: 0, width: size[0], height: size[1]
},
style: {
fill: dataZoomModel.get('backgroundColor')
},
z2: -40
}));
_renderDataShadow: function () {
var info = this._dataShadowInfo = this._prepareDataShadowInfo();
if (!info) {
return;
}
if (otherDim == null) {
return;
}
// FIXME
// Should consider axis.min/axis.max when drawing dataShadow.
// FIXME
// 应该使用统一的空判断?还是在 list 里进行空判断?
var isEmpty = value == null || isNaN(value) || value === '';
// See #4235.
var otherCoord = isEmpty
? 0 : linearMap$2(value, otherDataExtent, otherShadowExtent, true);
// Attempt to draw data shadow precisely when there are empty value.
if (isEmpty && !lastIsEmpty && index) {
areaPoints.push([areaPoints[areaPoints.length - 1][0], 0]);
linePoints.push([linePoints[linePoints.length - 1][0], 0]);
}
else if (!isEmpty && lastIsEmpty) {
areaPoints.push([thisCoord, 0]);
linePoints.push([thisCoord, 0]);
}
areaPoints.push([thisCoord, otherCoord]);
linePoints.push([thisCoord, otherCoord]);
thisCoord += step;
lastIsEmpty = isEmpty;
});
_prepareDataShadowInfo: function () {
var dataZoomModel = this.dataZoomModel;
var showDataShadow = dataZoomModel.get('showDataShadow');
otherDim = seriesModel.getData().mapDimension(otherDim);
result = {
thisAxis: thisAxis,
series: seriesModel,
thisDim: dimNames.name,
otherDim: otherDim,
otherAxisInverse: otherAxisInverse
};
}, this);
}, this);
return result;
},
_renderHandle: function () {
var displaybles = this._displayables;
var handles = displaybles.handles = [];
var handleLabels = displaybles.handleLabels = [];
var barGroup = this._displayables.barGroup;
var size = this._size;
var dataZoomModel = this.dataZoomModel;
// Frame border.
barGroup.add(new Rect$2(subPixelOptimizeRect({
silent: true,
shape: {
x: 0,
y: 0,
width: size[0],
height: size[1]
},
style: {
stroke: dataZoomModel.get('dataBackgroundColor')
|| dataZoomModel.get('borderColor'),
lineWidth: DEFAULT_FRAME_BORDER_WIDTH,
fill: 'rgba(0,0,0,0)'
}
})));
path.setStyle(dataZoomModel.getModel('handleStyle').getItemStyle());
var handleColor = dataZoomModel.get('handleColor');
// Compatitable with previous version
if (handleColor != null) {
path.style.fill = handleColor;
}
barGroup.add(handles[handleIndex] = path);
this.group.add(
handleLabels[handleIndex] = new Text({
silent: true,
invisible: true,
style: {
x: 0, y: 0, text: '',
textVerticalAlign: 'middle',
textAlign: 'center',
textFill: textStyleModel.getTextColor(),
textFont: textStyleModel.getFont()
},
z2: 10
}));
}, this);
},
/**
* @private
*/
_resetInterval: function () {
var range = this._range = this.dataZoomModel.getPercentRange();
var viewExtent = this._getViewExtent();
this._handleEnds = [
linearMap$2(range[0], [0, 100], viewExtent, true),
linearMap$2(range[1], [0, 100], viewExtent, true)
];
},
/**
* @private
* @param {(number|string)} handleIndex 0 or 1 or 'all'
* @param {number} delta
*/
_updateInterval: function (handleIndex, delta) {
var dataZoomModel = this.dataZoomModel;
var handleEnds = this._handleEnds;
var viewExtend = this._getViewExtent();
var minMaxSpan =
dataZoomModel.findRepresentativeAxisProxy().getMinMaxSpan();
var percentExtent = [0, 100];
sliderMove(
delta,
handleEnds,
viewExtend,
dataZoomModel.get('zoomLock') ? 'all' : handleIndex,
minMaxSpan.minSpan != null
? linearMap$2(minMaxSpan.minSpan, percentExtent, viewExtend,
true) : null,
minMaxSpan.maxSpan != null
? linearMap$2(minMaxSpan.maxSpan, percentExtent, viewExtend,
true) : null
);
this._range = asc$2([
linearMap$2(handleEnds[0], viewExtend, percentExtent, true),
linearMap$2(handleEnds[1], viewExtend, percentExtent, true)
]);
},
/**
* @private
*/
_updateView: function (nonRealtime) {
var displaybles = this._displayables;
var handleEnds = this._handleEnds;
var handleInterval = asc$2(handleEnds.slice());
var size = this._size;
// Filler
displaybles.filler.setShape({
x: handleInterval[0],
y: 0,
width: handleInterval[1] - handleInterval[0],
height: size[1]
});
this._updateDataInfo(nonRealtime);
},
/**
* @private
*/
_updateDataInfo: function (nonRealtime) {
var dataZoomModel = this.dataZoomModel;
var displaybles = this._displayables;
var handleLabels = displaybles.handleLabels;
var orient = this._orient;
var labelTexts = ['', ''];
// FIXME
// date 型,支持 formatter,autoformatter(ec2 date.getAutoFormatter)
if (dataZoomModel.get('showDetail')) {
var axisProxy = dataZoomModel.findRepresentativeAxisProxy();
if (axisProxy) {
var axis = axisProxy.getAxisModel().axis;
var range = this._range;
labelTexts = [
this._formatLabel(dataInterval[0], axis),
this._formatLabel(dataInterval[1], axis)
];
}
}
setLabel.call(this, 0);
setLabel.call(this, 1);
function setLabel(handleIndex) {
// Label
// Text should not transform by barGroup.
// Ignore handlers transform
var barTransform = getTransform(
displaybles.handles[handleIndex].parent, this.group
);
var direction = transformDirection(
handleIndex === 0 ? 'right' : 'left', barTransform
);
var offset = this._handleWidth / 2 + LABEL_GAP;
var textPoint = applyTransform$1(
[
orderedHandleEnds[handleIndex] + (handleIndex === 0 ? -offset :
offset),
this._size[1] / 2
],
barTransform
);
handleLabels[handleIndex].setStyle({
x: textPoint[0],
y: textPoint[1],
textVerticalAlign: orient === HORIZONTAL ? 'middle' : direction,
textAlign: orient === HORIZONTAL ? direction : 'center',
text: labelTexts[handleIndex]
});
}
},
/**
* @private
*/
_formatLabel: function (value, axis) {
var dataZoomModel = this.dataZoomModel;
var labelFormatter = dataZoomModel.get('labelFormatter');
return isFunction$1(labelFormatter)
? labelFormatter(value, valueStr)
: isString(labelFormatter)
? labelFormatter.replace('{value}', valueStr)
: valueStr;
},
/**
* @private
* @param {boolean} showOrHide true: show, false: hide
*/
_showDataInfo: function (showOrHide) {
// Always show when drgging.
showOrHide = this._dragging || showOrHide;
this._updateInterval(handleIndex, vertex[0]);
this._updateView(!realtime);
_onDragEnd: function () {
this._dragging = false;
this._showDataInfo(false);
/**
* This action will be throttled.
* @private
*/
_dispatchZoomAction: function () {
var range = this._range;
this.api.dispatchAction({
type: 'dataZoom',
from: this.uid,
dataZoomId: this.dataZoomModel.id,
start: range[0],
end: range[1]
});
},
/**
* @private
*/
_findCoordRect: function () {
// Find the grid coresponding to the first axis referred by dataZoom.
var rect;
each$24(this.getTargetCoordInfo(), function (coordInfoList) {
if (!rect && coordInfoList.length) {
var coordSys = coordInfoList[0].model.coordinateSystem;
rect = coordSys.getRect && coordSys.getRect();
}
});
if (!rect) {
var width = this.api.getWidth();
var height = this.api.getHeight();
rect = {
x: width * 0.2,
y: height * 0.2,
width: width * 0.6,
height: height * 0.6
};
}
return rect;
}
});
function getOtherDim(thisDim) {
// FIXME
// 这个逻辑和 getOtherAxis 里一致,但是写在这里是否不好
var map$$1 = {x: 'y', y: 'x', radius: 'angle', angle: 'radius'};
return map$$1[thisDim];
}
function getCursor(orient) {
return orient === 'vertical' ? 'ns-resize' : 'ew-resize';
}
DataZoomModel.extend({
type: 'dataZoom.inside',
/**
* @protected
*/
defaultOption: {
disabled: false, // Whether disable this inside zoom.
zoomLock: false, // Whether disable zoom but only pan.
zoomOnMouseWheel: true, // Can be: true / false / 'shift' / 'ctrl' / 'alt'.
moveOnMouseMove: true, // Can be: true / false / 'shift' / 'ctrl' /
'alt'.
preventDefaultMouseMove: true
}
});
/**
* @public
* @param {module:echarts/ExtensionAPI} api
* @param {Object} dataZoomInfo
* @param {string} dataZoomInfo.coordId
* @param {Function} dataZoomInfo.containsPoint
* @param {Array.<string>} dataZoomInfo.allCoordIds
* @param {string} dataZoomInfo.dataZoomId
* @param {number} dataZoomInfo.throttleRate
* @param {Function} dataZoomInfo.panGetRange
* @param {Function} dataZoomInfo.zoomGetRange
* @param {boolean} [dataZoomInfo.zoomLock]
* @param {boolean} [dataZoomInfo.disabled]
*/
function register$2(api, dataZoomInfo) {
var store = giveStore(api);
var theDataZoomId = dataZoomInfo.dataZoomId;
var theCoordId = dataZoomInfo.coordId;
cleanStore(store);
// Update throttle.
createOrUpdate(
record,
'dispatchAction',
dataZoomInfo.throttleRate,
'fixRate'
);
}
/**
* @public
* @param {module:echarts/ExtensionAPI} api
* @param {string} dataZoomId
*/
function unregister$1(api, dataZoomId) {
var store = giveStore(api);
cleanStore(store);
}
/**
* @public
*/
function shouldRecordRange(payload, dataZoomId) {
if (payload && payload.type === 'dataZoom' && payload.batch) {
for (var i = 0, len = payload.batch.length; i < len; i++) {
if (payload.batch[i].dataZoomId === dataZoomId) {
return false;
}
}
}
return true;
}
/**
* @public
*/
function generateCoordId(coordModel) {
return coordModel.type + '\0_' + coordModel.id;
}
/**
* Key: coordId, value: {dataZoomInfos: [], count, controller}
* @type {Array.<Object>}
*/
function giveStore(api) {
// Mount store on zrender instance, so that we do not
// need to worry about dispose.
var zr = api.getZr();
return zr[ATTR$1] || (zr[ATTR$1] = {});
}
return controller;
}
function cleanStore(store) {
each$1(store, function (record, coordId) {
if (!record.count) {
record.controller.dispose();
delete store[coordId];
}
});
}
record.dispatchAction(batch);
}
/**
* This action will be throttled.
*/
function dispatchAction$1(api, batch) {
api.dispatchAction({
type: 'dataZoom',
batch: batch
});
}
/**
* Merge roamController settings when multiple dataZooms share one roamController.
*/
function mergeControllerParams(dataZoomInfos) {
var controlType;
var opt = {};
// DO NOT use reserved word (true, false, undefined) as key literally. Even if
encapsulated
// as string, it is probably revert to reserved word by compress tool. See
#7411.
var prefix = 'type_';
var typePriority = {
'type_true': 2,
'type_move': 1,
'type_false': 0,
'type_undefined': -1
};
each$1(dataZoomInfos, function (dataZoomInfo) {
var oneType = dataZoomInfo.disabled ? false : dataZoomInfo.zoomLock ?
'move' : true;
if (typePriority[prefix + oneType] > typePriority[prefix + controlType]) {
controlType = oneType;
}
// Do not support that different 'shift'/'ctrl'/'alt' setting used in one
coord sys.
extend(opt, dataZoomInfo.roamControllerOpt);
});
return {
controlType: controlType,
opt: opt
};
}
type: 'dataZoom.inside',
/**
* @override
*/
init: function (ecModel, api) {
/**
* 'throttle' is used in this.dispatchAction, so we save range
* to avoid missing some 'pan' info.
* @private
* @type {Array.<number>}
*/
this._range;
},
/**
* @override
*/
render: function (dataZoomModel, ecModel, api, payload) {
InsideZoomView.superApply(this, 'render', arguments);
// Reset controllers.
each$1(this.getTargetCoordInfo(), function (coordInfoList, coordSysName) {
register$2(
api,
{
coordId: generateCoordId(coordModel),
allCoordIds: allCoordIds,
containsPoint: function (e, x, y) {
return coordModel.coordinateSystem.containPoint([x,
y]);
},
dataZoomId: dataZoomModel.id,
throttleRate: dataZoomModel.get('throttle', true),
panGetRange: bind$5(this._onPan, this, coordInfo,
coordSysName),
zoomGetRange: bind$5(this._onZoom, this, coordInfo,
coordSysName),
zoomLock: dataZoomOption.zoomLock,
disabled: dataZoomOption.disabled,
roamControllerOpt: {
zoomOnMouseWheel: dataZoomOption.zoomOnMouseWheel,
moveOnMouseMove: dataZoomOption.moveOnMouseMove,
preventDefaultMouseMove:
dataZoomOption.preventDefaultMouseMove
}
}
);
}, this);
}, this);
},
/**
* @override
*/
dispose: function () {
unregister$1(this.api, this.dataZoomModel.id);
InsideZoomView.superApply(this, 'dispose', arguments);
this._range = null;
},
/**
* @private
*/
_onPan: function (coordInfo, coordSysName, controller, dx, dy, oldX, oldY,
newX, newY) {
var range = this._range.slice();
/**
* @private
*/
_onZoom: function (coordInfo, coordSysName, controller, scale, mouseX, mouseY)
{
var range = this._range.slice();
// Restrict range.
var minMaxSpan =
this.dataZoomModel.findRepresentativeAxisProxy().getMinMaxSpan();
});
var getDirectionInfo = {
return ret;
},
return ret;
},
return ret;
}
};
registerProcessor({
return seriesModelMap;
},
isOverallFilter: true,
dataZoomModel.setRawRange({
start: percentRange[0],
end: percentRange[1],
startValue: valueRange[0],
endValue: valueRange[1]
}, true);
});
}
});
ecModel.eachComponent(
{mainType: 'dataZoom', query: payload},
function (model, index) {
effectedModels.push.apply(
effectedModels, linkedNodesFinder(model).nodes
);
}
);
});
/**
* DataZoom component entry
*/
if (!isArray(visualMap)) {
visualMap = visualMap ? [visualMap] : [];
}
registerVisual(VISUAL_PRIORITY, {
createOnAllSeries: true,
reset: function (seriesModel, ecModel) {
var resetDefines = [];
ecModel.eachComponent('visualMap', function (visualMapModel) {
if (!visualMapModel.isTargetSeries(seriesModel)) {
return;
}
resetDefines.push(incrementalApplyVisual(
visualMapModel.stateList,
visualMapModel.targetVisuals,
bind(visualMapModel.getValueState, visualMapModel),
visualMapModel.getDataDimension(seriesModel.getData())
));
});
return resetDefines;
}
});
// FIXME
// performance and export for heatmap?
// value can be Infinity or -Infinity
function getColorVisual(seriesModel, visualMapModel, value, valueState) {
var mappings = visualMapModel.targetVisuals[valueState];
var visualTypes = VisualMapping.prepareVisualTypes(mappings);
var resultVisual = {
color: seriesModel.getData().getVisual('color') // default color.
};
return resultVisual.color;
function getVisual(key) {
return resultVisual[key];
}
/**
* @file Visual mapping.
*/
var visualDefault = {
/**
* @public
*/
get: function (visualType, key, isCategory) {
var value = clone(
(defaultOption$3[visualType] || {})[key]
);
return isCategory
? (isArray(value) ? value[value.length - 1] : value)
: value;
}
};
var defaultOption$3 = {
color: {
active: ['#006edd', '#e0ffff'],
inactive: ['rgba(0,0,0,0)']
},
colorHue: {
active: [0, 360],
inactive: [0, 0]
},
colorSaturation: {
active: [0.3, 1],
inactive: [0, 0]
},
colorLightness: {
active: [0.9, 0.5],
inactive: [0, 0]
},
colorAlpha: {
active: [0.3, 1],
inactive: [0, 0]
},
opacity: {
active: [0.3, 1],
inactive: [0, 0]
},
symbol: {
active: ['circle', 'roundRect', 'diamond'],
inactive: ['none']
},
symbolSize: {
active: [10, 50],
inactive: [0, 0]
}
};
type: 'visualMap',
dependencies: ['series'],
/**
* @readOnly
* @type {Array.<string>}
*/
stateList: ['inRange', 'outOfRange'],
/**
* @readOnly
* @type {Array.<string>}
*/
replacableOptionKeys: [
'inRange', 'outOfRange', 'target', 'controller', 'color'
],
/**
* [lowerBound, upperBound]
*
* @readOnly
* @type {Array.<number>}
*/
dataBound: [-Infinity, Infinity],
/**
* @readOnly
* @type {string|Object}
*/
layoutMode: {type: 'box', ignoreSize: true},
/**
* @protected
*/
defaultOption: {
show: true,
zlevel: 0,
z: 4,
dimension: null,
inRange: null, // 'color', 'colorHue', 'colorSaturation',
'colorLightness', 'colorAlpha',
// 'symbol', 'symbolSize'
outOfRange: null, // 'color', 'colorHue', 'colorSaturation',
// 'colorLightness', 'colorAlpha',
// 'symbol', 'symbolSize'
itemWidth: null,
itemHeight: null,
inverse: false,
orient: 'vertical', // 'horizontal' ¦ 'vertical'
backgroundColor: 'rgba(0,0,0,0)',
borderColor: '#ccc', // 值域边框颜色
contentColor: '#5793f3',
inactiveColor: '#aaa',
borderWidth: 0, // 值域边框线宽,单位 px,默认为 0(无边框)
padding: 5, // 值域内边距,单位 px,默认各方向内边距为 5,
// 接受数组分别设定上右下左边距,同 css
textGap: 10, //
precision: 0, // 小数精度,默认为 0,无小数点
color: null, //颜色(deprecated,兼容 ec2,顺序同 pieces,不同于
inRange/outOfRange)
formatter: null,
text: null, // 文本,如['高', '低'],兼容 ec2,text[0]对应高值,
text[1]对应低值
textStyle: {
color: '#333' // 值域文字颜色
}
},
/**
* @protected
*/
init: function (option, parentModel, ecModel) {
/**
* @private
* @type {Array.<number>}
*/
this._dataExtent;
/**
* @readOnly
*/
this.targetVisuals = {};
/**
* @readOnly
*/
this.controllerVisuals = {};
/**
* @readOnly
*/
this.textStyleModel;
/**
* [width, height]
* @readOnly
* @type {Array.<number>}
*/
this.itemSize;
this.mergeDefaultAndTheme(option, ecModel);
},
/**
* @protected
*/
optionUpdated: function (newOption, isInit) {
var thisOption = this.option;
// FIXME
// necessary?
// Disable realtime view update if canvas is not supported.
if (!env$1.canvasSupported) {
thisOption.realtime = false;
}
this.textStyleModel = this.getModel('textStyle');
this.resetItemSize();
this.completeVisualOption();
},
/**
* @protected
*/
resetVisual: function (supplementVisualOption) {
var stateList = this.stateList;
supplementVisualOption = bind(supplementVisualOption, this);
this.controllerVisuals = createVisualMappings(
this.option.controller, stateList, supplementVisualOption
);
this.targetVisuals = createVisualMappings(
this.option.target, stateList, supplementVisualOption
);
},
/**
* @protected
* @return {Array.<number>} An array of series indices.
*/
getTargetSeriesIndices: function () {
var optionSeriesIndex = this.option.seriesIndex;
var seriesIndices = [];
return seriesIndices;
},
/**
* @public
*/
eachTargetSeries: function (callback, context) {
each$1(this.getTargetSeriesIndices(), function (seriesIndex) {
callback.call(context, this.ecModel.getSeriesByIndex(seriesIndex));
}, this);
},
/**
* @pubilc
*/
isTargetSeries: function (seriesModel) {
var is = false;
this.eachTargetSeries(function (model) {
model === seriesModel && (is = true);
});
return is;
},
/**
* @example
* this.formatValueText(someVal); // format single numeric value to text.
* this.formatValueText(someVal, true); // format single category value to
text.
* this.formatValueText([min, max]); // format numeric min-max to text.
* this.formatValueText([this.dataBound[0], max]); // using data lower bound.
* this.formatValueText([min, this.dataBound[1]]); // using data upper bound.
*
* @param {number|Array.<number>} value Real value, or this.dataBound[0 or 1].
* @param {boolean} [isCategory=false] Only available when value is number.
* @param {Array.<string>} edgeSymbols Open-close symbol when value is
interval.
* @return {string}
* @protected
*/
formatValueText: function(value, isCategory, edgeSymbols) {
var option = this.option;
var precision = option.precision;
var dataBound = this.dataBound;
var formatter = option.formatter;
var isMinMax;
var textValue;
edgeSymbols = edgeSymbols || ['<', '>'];
if (isArray(value)) {
value = value.slice();
isMinMax = true;
}
textValue = isCategory
? value
: (isMinMax
? [toFixed(value[0]), toFixed(value[1])]
: toFixed(value)
);
if (isString(formatter)) {
return formatter
.replace('{value}', isMinMax ? textValue[0] : textValue)
.replace('{value2}', isMinMax ? textValue[1] : textValue);
}
else if (isFunction$1(formatter)) {
return isMinMax
? formatter(value[0], value[1])
: formatter(value);
}
if (isMinMax) {
if (value[0] === dataBound[0]) {
return edgeSymbols[0] + ' ' + textValue[1];
}
else if (value[1] === dataBound[1]) {
return edgeSymbols[1] + ' ' + textValue[0];
}
else {
return textValue[0] + ' - ' + textValue[1];
}
}
else { // Format single value (includes category case).
return textValue;
}
function toFixed(val) {
return val === dataBound[0]
? 'min'
: val === dataBound[1]
? 'max'
: (+val).toFixed(Math.min(precision, 20));
}
},
/**
* @protected
*/
resetExtent: function () {
var thisOption = this.option;
/**
* @public
* @param {module:echarts/data/List} list
* @return {string} Concrete dimention. If return null/undefined,
* no dimension used.
*/
getDataDimension: function (list) {
var optDim = this.option.dimension;
var listDimensions = list.dimensions;
if (optDim == null && !listDimensions.length) {
return;
}
if (optDim != null) {
return list.getDimension(optDim);
}
/**
* @public
* @override
*/
getExtent: function () {
return this._dataExtent.slice();
},
/**
* @protected
*/
completeVisualOption: function () {
var ecModel = this.ecModel;
var thisOption = this.option;
var base = {inRange: thisOption.inRange, outOfRange:
thisOption.outOfRange};
completeSingle.call(this, target);
completeSingle.call(this, controller);
completeInactive.call(this, target, 'inRange', 'outOfRange');
// completeInactive.call(this, target, 'outOfRange', 'inRange');
completeController.call(this, controller);
function completeSingle(base) {
// Compatible with ec2 dataRange.color.
// The mapping order of dataRange.color is: [high value, ..., low
value]
// whereas inRange.color and outOfRange.color is [low value, ..., high
value]
// Notice: ec2 has no inverse.
if (isArray$3(thisOption.color)
// If there has been inRange: {symbol: ...}, adding color is a
mistake.
// So adding color only when no inRange defined.
&& !base.inRange
) {
base.inRange = {color: thisOption.color.slice().reverse()};
}
if (isString(visualType)) {
var defa = visualDefault.get(visualType, 'active', isCategory);
if (defa) {
base[state] = {};
base[state][visualType] = defa;
}
else {
// Mark as not specified.
delete base[state];
}
}
}, this);
}
if (defa != null) {
optAbsent[visualType] = defa;
function completeController(controller) {
var symbolExists = (controller.inRange || {}).symbol
|| (controller.outOfRange || {}).symbol;
var symbolSizeExists = (controller.inRange || {}).symbolSize
|| (controller.outOfRange || {}).symbolSize;
var inactiveColor = this.get('inactiveColor');
// Normalize symbolSize
var symbolSize = visuals.symbolSize;
if (symbolSize != null) {
var max = -Infinity;
// symbolSize can be object when categories defined.
eachVisual(symbolSize, function (value) {
value > max && (max = value);
});
visuals.symbolSize = mapVisual$2(symbolSize, function (value) {
return linearMap$3(value, [0, max], [0, itemSize[0]],
true);
});
}
}, this);
}
},
/**
* @protected
*/
resetItemSize: function () {
this.itemSize = [
parseFloat(this.get('itemWidth')),
parseFloat(this.get('itemHeight'))
];
},
/**
* @public
*/
isCategory: function () {
return !!this.option.categories;
},
/**
* @public
* @abstract
*/
setSelected: noop$2,
/**
* @public
* @abstract
* @param {*|module:echarts/data/List} valueOrData
* @param {number} dataIndex
* @return {string} state See this.stateList
*/
getValueState: noop$2,
/**
* FIXME
* Do not publish to thirt-part-dev temporarily
* util the interface is stable. (Should it return
* a function but not visual meta?)
*
* @pubilc
* @abstract
* @param {Function} getColorVisual
* params: value, valueState
* return: color
* @return {Object} visualMeta
* should includes {stops, outerColors}
* outerColor means [colorBeyondMinValue, colorBeyondMaxValue]
*/
getVisualMeta: noop$2
});
// Constant
var DEFAULT_BAR_BOUND = [20, 140];
type: 'visualMap.continuous',
/**
* @protected
*/
defaultOption: {
align: 'auto', // 'auto', 'left', 'right', 'top', 'bottom'
calculable: false, // This prop effect default component type
determine,
// See echarts/component/visualMap/typeDefaulter.
range: null, // selected range. In default case `range` is [min,
max]
// and can auto change along with modification of
min max,
// util use specifid a range.
realtime: true, // Whether realtime update.
itemHeight: null, // The length of the range control edge.
itemWidth: null, // The length of the other side.
hoverLink: true, // Enable hover highlight.
hoverLinkDataSize: null,// The size of hovered data.
hoverLinkOnHandle: null // Whether trigger hoverLink when hover handle.
// If not specified, follow the value of
`realtime`.
},
/**
* @override
*/
optionUpdated: function (newOption, isInit) {
ContinuousModel.superApply(this, 'optionUpdated', arguments);
this.resetExtent();
this.resetVisual(function (mappingOption) {
mappingOption.mappingMethod = 'linear';
mappingOption.dataExtent = this.getExtent();
});
this._resetRange();
},
/**
* @protected
* @override
*/
resetItemSize: function () {
ContinuousModel.superApply(this, 'resetItemSize', arguments);
/**
* @private
*/
_resetRange: function () {
var dataExtent = this.getExtent();
var range = this.option.range;
if (!range || range.auto) {
// `range` should always be array (so we dont use other
// value like 'auto') for user-friend. (consider getOption).
dataExtent.auto = 1;
this.option.range = dataExtent;
}
else if (isArray(range)) {
if (range[0] > range[1]) {
range.reverse();
}
range[0] = Math.max(range[0], dataExtent[0]);
range[1] = Math.min(range[1], dataExtent[1]);
}
},
/**
* @protected
* @override
*/
completeVisualOption: function () {
VisualMapModel.prototype.completeVisualOption.apply(this, arguments);
/**
* @override
*/
setSelected: function (selected) {
this.option.range = selected.slice();
this._resetRange();
},
/**
* @public
*/
getSelected: function () {
var dataExtent = this.getExtent();
// Clamp
dataInterval[0] > dataExtent[1] && (dataInterval[0] = dataExtent[1]);
dataInterval[1] > dataExtent[1] && (dataInterval[1] = dataExtent[1]);
dataInterval[0] < dataExtent[0] && (dataInterval[0] = dataExtent[0]);
dataInterval[1] < dataExtent[0] && (dataInterval[1] = dataExtent[0]);
return dataInterval;
},
/**
* @override
*/
getValueState: function (value) {
var range = this.option.range;
var dataExtent = this.getExtent();
/**
* @params {Array.<number>} range target value: range[0] <= value && value <=
range[1]
* @return {Array.<Object>} [{seriesId, dataIndices: <Array.<number>>}, ...]
*/
findTargetDataIndices: function (range) {
var result = [];
this.eachTargetSeries(function (seriesModel) {
var dataIndices = [];
var data = seriesModel.getData();
return result;
},
/**
* @implement
*/
getVisualMeta: function (getColorVisual) {
var oVals = getColorStopValues(this, 'outOfRange', this.getExtent());
var iVals = getColorStopValues(this, 'inRange', this.option.range.slice());
var stops = [];
for (; oIdx < oLen && (!iVals.length || oVals[oIdx] <= iVals[0]); oIdx++) {
// If oVal[oIdx] === iVals[iIdx], oVal[oIdx] should be ignored.
if (oVals[oIdx] < iVals[iIdx]) {
setStop(oVals[oIdx], 'outOfRange');
}
}
for (var first = 1; iIdx < iLen; iIdx++, first = 0) {
// If range is full, value beyond min, max will be clamped.
// make a singularity
first && stops.length && setStop(iVals[iIdx], 'outOfRange');
setStop(iVals[iIdx], 'inRange');
}
for (var first = 1; oIdx < oLen; oIdx++) {
if (!iVals.length || iVals[iVals.length - 1] < oVals[oIdx]) {
// make a singularity
if (first) {
stops.length && setStop(stops[stops.length - 1].value,
'outOfRange');
first = 0;
}
setStop(oVals[oIdx], 'outOfRange');
}
}
return {
stops: stops,
outerColors: [
stopsLen ? stops[0].color : 'transparent',
stopsLen ? stops[stopsLen - 1].color : 'transparent'
]
};
}
});
return stopValues;
}
type: 'visualMap',
/**
* @readOnly
* @type {Object}
*/
autoPositionValues: {left: 1, right: 1, top: 1, bottom: 1},
/**
* @readOnly
* @type {module:echarts/ExtensionAPI}
*/
this.api = api;
/**
* @readOnly
* @type {module:echarts/component/visualMap/visualMapModel}
*/
this.visualMapModel;
},
/**
* @protected
*/
render: function (visualMapModel, ecModel, api, payload) {
this.visualMapModel = visualMapModel;
this.doRender.apply(this, arguments);
},
/**
* @protected
*/
renderBackground: function (group) {
var visualMapModel = this.visualMapModel;
var padding = normalizeCssArray$1(visualMapModel.get('padding') || 0);
var rect = group.getBoundingRect();
group.add(new Rect({
z2: -1, // Lay background rect on the lowest layer.
silent: true,
shape: {
x: rect.x - padding[3],
y: rect.y - padding[0],
width: rect.width + padding[3] + padding[1],
height: rect.height + padding[0] + padding[2]
},
style: {
fill: visualMapModel.get('backgroundColor'),
stroke: visualMapModel.get('borderColor'),
lineWidth: visualMapModel.get('borderWidth')
}
}));
},
/**
* @protected
* @param {number} targetValue can be Infinity or -Infinity
* @param {string=} visualCluster Only can be 'color' 'opacity' 'symbol'
'symbolSize'
* @param {Object} [opts]
* @param {string=} [opts.forceState] Specify state, instead of using
getValueState method.
* @param {string=} [opts.convertOpacityToAlpha=false] For color gradient in
controller widget.
* @return {*} Visual value.
*/
getControllerVisual: function (targetValue, visualCluster, opts) {
opts = opts || {};
// Default values.
if (visualCluster === 'symbol') {
visualObj.symbol = visualMapModel.get('itemSymbol');
}
if (visualCluster === 'color') {
var defaultColor = visualMapModel.get('contentColor');
visualObj.color = defaultColor;
}
function getter(key) {
return visualObj[key];
}
return visualObj[visualCluster];
},
/**
* @protected
*/
positionGroup: function (group) {
var model = this.visualMapModel;
var api = this.api;
positionElement(
group,
model.getBoxLayoutParams(),
{width: api.getWidth(), height: api.getHeight()}
);
},
/**
* @protected
* @abstract
*/
doRender: noop
});
/**
* @param {module:echarts/component/visualMap/VisualMapModel} visualMapModel\
* @param {module:echarts/ExtensionAPI} api
* @param {Array.<number>} itemSize always [short, long]
* @return {string} 'left' or 'right' or 'top' or 'bottom'
*/
function getItemAlign(visualMapModel, api, itemSize) {
var modelOption = visualMapModel.option;
var itemAlign = modelOption.align;
if (itemAlign != null && itemAlign !== 'auto') {
return itemAlign;
}
var paramsSet = [
['left', 'right', 'width'],
['top', 'bottom', 'height']
];
var reals = paramsSet[realIndex];
var fakeValue = [0, null, 10];
return reals[
(rect.margin[rParam[2]] || 0) + rect[rParam[0]] + rect[rParam[1]] * 0.5
< ecSize[rParam[1]] * 0.5 ? 0 : 1
];
}
/**
* Prepare dataIndex for outside usage, where dataIndex means rawIndex, and
* dataIndexInside means filtered index.
*/
function convertDataIndex(batch) {
each$1(batch || [], function (batchItem) {
if (batch.dataIndex != null) {
batch.dataIndexInside = batch.dataIndex;
batch.dataIndex = null;
}
});
return batch;
}
// Arbitrary value
var HOVER_LINK_SIZE = 12;
var HOVER_LINK_OUT = 6;
// Notice:
// Any "interval" should be by the order of [low, high].
// "handle0" (handleIndex === 0) maps to
// low data value: this._dataInterval[0] and has low coord.
// "handle1" (handleIndex === 1) maps to
// high data value: this._dataInterval[1] and has high coord.
// The logic of transform is implemented in this._createBarGroup.
type: 'visualMap.continuous',
/**
* @override
*/
init: function () {
/**
* @private
*/
this._shapes = {};
/**
* @private
*/
this._dataInterval = [];
/**
* @private
*/
this._handleEnds = [];
/**
* @private
*/
this._orient;
/**
* @private
*/
this._useHandle;
/**
* @private
*/
this._hoverLinkDataIndices = [];
/**
* @private
*/
this._dragging;
/**
* @private
*/
this._hovering;
},
/**
* @protected
* @override
*/
doRender: function (visualMapModel, ecModel, api, payload) {
if (!payload || payload.type !== 'selectDataRange' || payload.from !==
this.uid) {
this._buildView();
}
},
/**
* @private
*/
_buildView: function () {
this.group.removeAll();
this._orient = visualMapModel.get('orient');
this._useHandle = visualMapModel.get('calculable');
this._resetInterval();
this._renderBar(thisGroup);
this._enableHoverLinkToSeries();
this._enableHoverLinkFromSeries();
this.positionGroup(thisGroup);
},
/**
* @private
*/
_renderEndsText: function (group, dataRangeText, endsIndex) {
if (!dataRangeText) {
return;
}
// Compatible with ec2, text[0] map to high value, text[1] map low value.
var text = dataRangeText[1 - endsIndex];
text = text != null ? text + '' : '';
this.group.add(new Text({
style: {
x: position[0],
y: position[1],
textVerticalAlign: orient === 'horizontal' ? 'middle' : align,
textAlign: orient === 'horizontal' ? align : 'center',
text: text,
textFont: textStyleModel.getFont(),
textFill: textStyleModel.getTextColor()
}
}));
},
/**
* @private
*/
_renderBar: function (targetGroup) {
var visualMapModel = this.visualMapModel;
var shapes = this._shapes;
var itemSize = visualMapModel.itemSize;
var orient = this._orient;
var useHandle = this._useHandle;
var itemAlign = getItemAlign(visualMapModel, this.api, itemSize);
var barGroup = shapes.barGroup = this._createBarGroup(itemAlign);
// Bar
barGroup.add(shapes.outOfRange = createPolygon());
barGroup.add(shapes.inRange = createPolygon(
null,
useHandle ? getCursor$1(this._orient) : null,
bind(this._dragHandle, this, 'all', false),
bind(this._dragHandle, this, 'all', true)
));
// Handle
if (useHandle) {
shapes.handleThumbs = [];
shapes.handleLabels = [];
shapes.handleLabelPoints = [];
targetGroup.add(barGroup);
},
/**
* @private
*/
_createHandle: function (barGroup, handleIndex, itemSize, textSize, orient) {
var onDrift = bind(this._dragHandle, this, handleIndex, false);
var onDragEnd = bind(this._dragHandle, this, handleIndex, true);
var handleThumb = createPolygon(
createHandlePoints(handleIndex, textSize),
getCursor$1(this._orient),
onDrift,
onDragEnd
);
handleThumb.position[0] = itemSize[0];
barGroup.add(handleThumb);
var handleLabelPoint = [
orient === 'horizontal'
? textSize / 2
: textSize * 1.5,
orient === 'horizontal'
? (handleIndex === 0 ? -(textSize * 1.5) : (textSize * 1.5))
: (handleIndex === 0 ? -textSize / 2 : textSize / 2)
];
var indicatorLabelPoint = [
orient === 'horizontal' ? textSize / 2 : HOVER_LINK_OUT + 3,
0
];
/**
* @private
*/
_dragHandle: function (handleIndex, isEnd, dx, dy) {
if (!this._useHandle) {
return;
}
this._dragging = !isEnd;
if (!isEnd) {
// Transform dx, dy to bar coordination.
var vertex = this._applyTransform([dx, dy], this._shapes.barGroup,
true);
this._updateInterval(handleIndex, vertex[1]);
if (isEnd) {
!this._hovering && this._clearHoverLinkToSeries();
}
else if (useHoverLinkOnHandle(this.visualMapModel)) {
this._doHoverLinkToSeries(this._handleEnds[handleIndex], false);
}
},
/**
* @private
*/
_resetInterval: function () {
var visualMapModel = this.visualMapModel;
this._handleEnds = [
linearMap$4(dataInterval[0], dataExtent, sizeExtent, true),
linearMap$4(dataInterval[1], dataExtent, sizeExtent, true)
];
},
/**
* @private
* @param {(number|string)} handleIndex 0 or 1 or 'all'
* @param {number} dx
* @param {number} dy
*/
_updateInterval: function (handleIndex, delta) {
delta = delta || 0;
var visualMapModel = this.visualMapModel;
var handleEnds = this._handleEnds;
var sizeExtent = [0, visualMapModel.itemSize[1]];
sliderMove(
delta,
handleEnds,
sizeExtent,
handleIndex,
// cross is forbiden
0
);
/**
* @private
*/
_updateView: function (forSketch) {
var visualMapModel = this.visualMapModel;
var dataExtent = visualMapModel.getExtent();
var shapes = this._shapes;
shapes.inRange
.setStyle({
fill: visualInRange.barColor,
opacity: visualInRange.opacity
})
.setShape('points', visualInRange.barPoints);
shapes.outOfRange
.setStyle({
fill: visualOutOfRange.barColor,
opacity: visualOutOfRange.opacity
})
.setShape('points', visualOutOfRange.barPoints);
this._updateHandle(inRangeHandleEnds, visualInRange);
},
/**
* @private
*/
_createBarVisual: function (dataInterval, dataExtent, handleEnds, forceState) {
var opts = {
forceState: forceState,
convertOpacityToAlpha: true
};
var colorStops = this._makeColorGradient(dataInterval, opts);
var symbolSizes = [
this.getControllerVisual(dataInterval[0], 'symbolSize', opts),
this.getControllerVisual(dataInterval[1], 'symbolSize', opts)
];
var barPoints = this._createBarPoints(handleEnds, symbolSizes);
return {
barColor: new LinearGradient(0, 0, 0, 1, colorStops),
barPoints: barPoints,
handlesColor: [
colorStops[0].color,
colorStops[colorStops.length - 1].color
]
};
},
/**
* @private
*/
_makeColorGradient: function (dataInterval, opts) {
// Considering colorHue, which is not linear, so we have to sample
// to calculate gradient color stops, but not only caculate head
// and tail.
var sampleNumber = 100; // Arbitrary value.
var colorStops = [];
var step = (dataInterval[1] - dataInterval[0]) / sampleNumber;
colorStops.push({
color: this.getControllerVisual(dataInterval[0], 'color', opts),
offset: 0
});
colorStops.push({
color: this.getControllerVisual(dataInterval[1], 'color', opts),
offset: 1
});
return colorStops;
},
/**
* @private
*/
_createBarPoints: function (handleEnds, symbolSizes) {
var itemSize = this.visualMapModel.itemSize;
return [
[itemSize[0] - symbolSizes[0], handleEnds[0]],
[itemSize[0], handleEnds[0]],
[itemSize[0], handleEnds[1]],
[itemSize[0] - symbolSizes[1], handleEnds[1]]
];
},
/**
* @private
*/
_createBarGroup: function (itemAlign) {
var orient = this._orient;
var inverse = this.visualMapModel.get('inverse');
/**
* @private
*/
_updateHandle: function (handleEnds, visualInRange) {
if (!this._useHandle) {
return;
}
/**
* @private
* @param {number} cursorValue
* @param {number} textValue
* @param {string} [rangeSymbol]
* @param {number} [halfHoverLinkSize]
*/
_showIndicator: function (cursorValue, textValue, rangeSymbol,
halfHoverLinkSize) {
var visualMapModel = this.visualMapModel;
var dataExtent = visualMapModel.getExtent();
var itemSize = visualMapModel.itemSize;
var sizeExtent = [0, itemSize[1]];
var pos = linearMap$4(cursorValue, dataExtent, sizeExtent, true);
indicator.position[1] = pos;
indicator.attr('invisible', false);
indicator.setShape('points', createIndicatorPoints(
!!rangeSymbol, halfHoverLinkSize, pos, itemSize[1]
));
/**
* @private
*/
_enableHoverLinkToSeries: function () {
var self = this;
this._shapes.barGroup
if (!self._dragging) {
var itemSize = self.visualMapModel.itemSize;
var pos = self._applyTransform(
[e.offsetX, e.offsetY], self._shapes.barGroup, true, true
);
// For hover link show when hover handle, which might be
// below or upper than sizeExtent.
pos[1] = mathMin$7(mathMax$7(0, pos[1]), itemSize[1]);
self._doHoverLinkToSeries(
pos[1],
0 <= pos[0] && pos[0] <= itemSize[0]
);
}
})
.on('mouseout', function () {
// When mouse is out of handle, hoverLink still need
// to be displayed when realtime is set as false.
self._hovering = false;
!self._dragging && self._clearHoverLinkToSeries();
});
},
/**
* @private
*/
_enableHoverLinkFromSeries: function () {
var zr = this.api.getZr();
if (this.visualMapModel.option.hoverLink) {
zr.on('mouseover', this._hoverLinkFromSeriesMouseOver, this);
zr.on('mouseout', this._hideIndicator, this);
}
else {
this._clearHoverLinkFromSeries();
}
},
/**
* @private
*/
_doHoverLinkToSeries: function (cursorPos, hoverOnBar) {
var visualMapModel = this.visualMapModel;
var itemSize = visualMapModel.itemSize;
if (!visualMapModel.option.hoverLink) {
return;
}
// For hover link show when hover handle, which might be below or upper
than sizeExtent.
cursorPos = mathMin$7(mathMax$7(sizeExtent[0], cursorPos), sizeExtent[1]);
this._dispatchHighDown('downplay', convertDataIndex(resultBatches[0]));
this._dispatchHighDown('highlight', convertDataIndex(resultBatches[1]));
},
/**
* @private
*/
_hoverLinkFromSeriesMouseOver: function (e) {
var el = e.target;
var visualMapModel = this.visualMapModel;
if (!visualMapModel.isTargetSeries(dataModel)) {
return;
}
if (!isNaN(value)) {
this._showIndicator(value, value);
}
},
/**
* @private
*/
_hideIndicator: function () {
var shapes = this._shapes;
shapes.indicator && shapes.indicator.attr('invisible', true);
shapes.indicatorLabel && shapes.indicatorLabel.attr('invisible', true);
},
/**
* @private
*/
_clearHoverLinkToSeries: function () {
this._hideIndicator();
indices.length = 0;
},
/**
* @private
*/
_clearHoverLinkFromSeries: function () {
this._hideIndicator();
var zr = this.api.getZr();
zr.off('mouseover', this._hoverLinkFromSeriesMouseOver);
zr.off('mouseout', this._hideIndicator);
},
/**
* @private
*/
_applyTransform: function (vertex, element, inverse, global) {
var transform = getTransform(element, global ? null : this.group);
return graphic[
isArray(vertex) ? 'applyTransform' : 'transformDirection'
](vertex, transform, inverse);
},
/**
* @private
*/
_dispatchHighDown: function (type, batch) {
batch && batch.length && this.api.dispatchAction({
type: type,
batch: batch
});
},
/**
* @override
*/
dispose: function () {
this._clearHoverLinkFromSeries();
this._clearHoverLinkToSeries();
},
/**
* @override
*/
remove: function () {
this._clearHoverLinkFromSeries();
this._clearHoverLinkToSeries();
}
});
function useHoverLinkOnHandle(visualMapModel) {
var hoverLinkOnHandle = visualMapModel.get('hoverLinkOnHandle');
return !!(hoverLinkOnHandle == null ? visualMapModel.get('realtime') :
hoverLinkOnHandle);
}
function getCursor$1(orient) {
return orient === 'vertical' ? 'ns-resize' : 'ew-resize';
}
var actionInfo$2 = {
type: 'selectDataRange',
event: 'dataRangeSelected',
// FIXME use updateView appears wrong
update: 'update'
};
});
/**
* DataZoom component entry
*/
registerPreprocessor(preprocessor$2);
type: 'visualMap.piecewise',
/**
* Order Rule:
*
* option.categories / option.pieces / option.text / option.selected:
* If !option.inverse,
* Order when vertical: ['top', ..., 'bottom'].
* Order when horizontal: ['left', ..., 'right'].
* If option.inverse, the meaning of
* the order should be reversed.
*
* this._pieceList:
* The order is always [low, ..., high].
*
* Mapping from location to low-high:
* If !option.inverse
* When vertical, top is high.
* When horizontal, right is high.
* If option.inverse, reverse.
*/
/**
* @protected
*/
defaultOption: {
selected: null, // Object. If not specified, means selected.
// When pieces and splitNumber: {'0': true,
'5': true}
// When categories: {'cate1': false, 'cate3':
true}
// When selected === false, means all
unselected.
/**
* @override
*/
optionUpdated: function (newOption, isInit) {
PiecewiseModel.superApply(this, 'optionUpdated', arguments);
/**
* The order is always [low, ..., high].
* [{text: string, interval: Array.<number>}, ...]
* @private
* @type {Array.<Object>}
*/
this._pieceList = [];
this.resetExtent();
/**
* 'pieces', 'categories', 'splitNumber'
* @type {string}
*/
var mode = this._mode = this._determineMode();
resetMethods[this._mode].call(this);
this._resetSelected(newOption, isInit);
/**
* @protected
* @override
*/
completeVisualOption: function () {
// Consider this case:
// visualMap: {
// pieces: [{symbol: 'circle', lt: 0}, {symbol: 'rect', gte: 0}]
// }
// where no inRange/outOfRange set but only pieces. So we should make
// default inRange/outOfRange for this case, otherwise visuals that only
// appear in `pieces` will not be taken into account in visual encoding.
VisualMapModel.prototype.completeVisualOption.apply(this, arguments);
},
/**
* @public
*/
getSelectedMapKey: function (piece) {
return this._mode === 'categories'
? piece.value + '' : piece.index + '';
},
/**
* @public
*/
getPieceList: function () {
return this._pieceList;
},
/**
* @private
* @return {string}
*/
_determineMode: function () {
var option = this.option;
/**
* @public
* @override
*/
setSelected: function (selected) {
this.option.selected = clone(selected);
},
/**
* @public
* @override
*/
getValueState: function (value) {
var index = VisualMapping.findPieceIndex(value, this._pieceList);
/**
* @public
* @params {number} pieceIndex piece index in visualMapModel.getPieceList()
* @return {Array.<Object>} [{seriesId, dataIndices: <Array.<number>>}, ...]
*/
findTargetDataIndices: function (pieceIndex) {
var result = [];
this.eachTargetSeries(function (seriesModel) {
var dataIndices = [];
var data = seriesModel.getData();
return result;
},
/**
* @private
* @param {Object} piece piece.value or piece.interval is required.
* @return {number} Can be Infinity or -Infinity
*/
getRepresentValue: function (piece) {
var representValue;
if (this.isCategory()) {
representValue = piece.value;
}
else {
if (piece.value != null) {
representValue = piece.value;
}
else {
var pieceInterval = piece.interval || [];
representValue = (pieceInterval[0] === -Infinity &&
pieceInterval[1] === Infinity)
? 0
: (pieceInterval[0] + pieceInterval[1]) / 2;
}
}
return representValue;
},
// Suplement
var pieceList = this._pieceList.slice();
if (!pieceList.length) {
pieceList.push({interval: [-Infinity, Infinity]});
}
else {
var edge = pieceList[0].interval[0];
edge !== -Infinity && pieceList.unshift({interval: [-Infinity, edge]});
edge = pieceList[pieceList.length - 1].interval[1];
edge !== Infinity && pieceList.push({interval: [edge, Infinity]});
}
});
/**
* Key is this._mode
* @type {Object}
* @this {module:echarts/component/viusalMap/PiecewiseMode}
*/
var resetMethods = {
splitNumber: function () {
var thisOption = this.option;
var pieceList = this._pieceList;
var precision = Math.min(thisOption.precision, 20);
var dataExtent = this.getExtent();
var splitNumber = thisOption.splitNumber;
splitNumber = Math.max(parseInt(splitNumber, 10), 1);
thisOption.splitNumber = splitNumber;
var index = 0;
if (thisOption.minOpen) {
pieceList.push({
index: index++,
interval: [-Infinity, dataExtent[0]],
close: [0, 0]
});
}
for (
var curr = dataExtent[0], len = index + splitNumber;
index < len;
curr += splitStep
) {
var max = index === splitNumber - 1 ? dataExtent[1] : (curr +
splitStep);
pieceList.push({
index: index++,
interval: [curr, max],
close: [1, 1]
});
}
if (thisOption.maxOpen) {
pieceList.push({
index: index++,
interval: [dataExtent[1], Infinity],
close: [0, 0]
});
}
reformIntervals(pieceList);
categories: function () {
var thisOption = this.option;
each$1(thisOption.categories, function (cate) {
// FIXME category 模式也使用 pieceList,但在 visualMapping 中不是使用
pieceList。
// 是否改一致。
this._pieceList.push({
text: this.formatValueText(cate, true),
value: cate
});
}, this);
pieces: function () {
var thisOption = this.option;
var pieceList = this._pieceList;
if (!isObject$1(pieceListItem)) {
pieceListItem = {value: pieceListItem};
}
if (pieceListItem.label != null) {
item.text = pieceListItem.label;
}
if (pieceListItem.hasOwnProperty('value')) {
var value = item.value = pieceListItem.value;
item.interval = [value, value];
item.close = [1, 1];
}
else {
// `min` `max` is legacy option.
// `lt` `gt` `lte` `gte` is recommanded.
var interval = item.interval = [];
var close = item.close = [0, 0];
if (__DEV__) {
if (interval[0] > interval[1]) {
console.warn(
'Piece ' + index + 'is illegal: ' + interval
+ ' lower bound should not greater then uppper bound.'
);
}
}
item.visual = VisualMapping.retrieveVisuals(pieceListItem);
pieceList.push(item);
}, this);
type: 'visualMap.piecewise',
/**
* @protected
* @override
*/
doRender: function () {
var thisGroup = this.group;
thisGroup.removeAll();
var visualMapModel = this.visualMapModel;
var textGap = visualMapModel.get('textGap');
var textStyleModel = visualMapModel.textStyleModel;
var textFont = textStyleModel.getFont();
var textFill = textStyleModel.getTextColor();
var itemAlign = this._getItemAlign();
var itemSize = visualMapModel.itemSize;
var viewData = this._getViewData();
var endsText = viewData.endsText;
var showLabel = retrieve(visualMapModel.get('showLabel', true), !endsText);
box(
visualMapModel.get('orient'), thisGroup, visualMapModel.get('itemGap')
);
this.renderBackground(thisGroup);
this.positionGroup(thisGroup);
function renderItem(item) {
var piece = item.piece;
this._enableHoverLink(itemGroup, item.indexInModelPieceList);
this._createItemSymbol(
itemGroup, representValue, [0, 0, itemSize[0], itemSize[1]]
);
if (showLabel) {
var visualState =
this.visualMapModel.getValueState(representValue);
itemGroup.add(new Text({
style: {
x: itemAlign === 'right' ? -textGap : itemSize[0] +
textGap,
y: itemSize[1] / 2,
text: piece.text,
textVerticalAlign: 'middle',
textAlign: itemAlign,
textFont: textFont,
textFill: textFill,
opacity: visualState === 'outOfRange' ? 0.5 : 1
}
}));
}
thisGroup.add(itemGroup);
}
},
/**
* @private
*/
_enableHoverLink: function (itemGroup, pieceIndex) {
itemGroup
.on('mouseover', bind(onHoverLink, this, 'highlight'))
.on('mouseout', bind(onHoverLink, this, 'downplay'));
function onHoverLink(method) {
var visualMapModel = this.visualMapModel;
/**
* @private
*/
_getItemAlign: function () {
var visualMapModel = this.visualMapModel;
var modelOption = visualMapModel.option;
/**
* @private
*/
_renderEndsText: function (group, text, itemSize, showLabel, itemAlign) {
if (!text) {
return;
}
group.add(itemGroup);
},
/**
* @private
* @return {Object} {peiceList, endsText} The order is the same as screen pixel
order.
*/
_getViewData: function () {
var visualMapModel = this.visualMapModel;
/**
* @private
*/
_createItemSymbol: function (group, representValue, shapeParam) {
group.add(createSymbol(
this.getControllerVisual(representValue, 'symbol'),
shapeParam[0], shapeParam[1], shapeParam[2], shapeParam[3],
this.getControllerVisual(representValue, 'color')
));
},
/**
* @private
*/
_onItemClick: function (piece) {
var visualMapModel = this.visualMapModel;
var option = visualMapModel.option;
var selected = clone(option.selected);
var newKey = visualMapModel.getSelectedMapKey(piece);
this.api.dispatchAction({
type: 'selectDataRange',
from: this.uid,
visualMapId: this.visualMapModel.id,
selected: selected
});
}
});
/**
* DataZoom component entry
*/
registerPreprocessor(preprocessor$2);
/**
* visualMap component entry
*/
function fillLabel(opt) {
defaultEmphasis(opt, 'label', ['show']);
}
var MarkerModel = extendComponentModel({
type: 'marker',
/**
* @overrite
*/
init: function (option, parentModel, ecModel, extraOpt) {
if (__DEV__) {
if (this.type === 'marker') {
throw new Error('Marker component is abstract component. Use
markLine, markPoint, markArea instead.');
}
}
this.mergeDefaultAndTheme(option, ecModel);
this.mergeOption(option, ecModel, extraOpt.createdBySelf, true);
},
/**
* @return {boolean}
*/
isAnimationEnabled: function () {
if (env$1.node) {
return false;
}
extend(markerModel, {
mainType: this.mainType,
// Use the same series index and name
seriesIndex: seriesModel.seriesIndex,
name: seriesModel.name,
createdBySelf: true
});
markerModel.__hostSeries = seriesModel;
}
else {
markerModel.mergeOption(markerOpt, ecModel, true);
}
seriesModel[modelPropName] = markerModel;
}, this);
}
},
getData: function () {
return this._data;
},
mixin(MarkerModel, dataFormatMixin);
MarkerModel.extend({
type: 'markPoint',
defaultOption: {
zlevel: 0,
z: 5,
symbol: 'pin',
symbolSize: 50,
//symbolRotate: 0,
//symbolOffset: [0, 0]
tooltip: {
trigger: 'item'
},
label: {
show: true,
position: 'inside'
},
itemStyle: {
borderWidth: 2
},
emphasis: {
label: {
show: true
}
}
}
});
function hasXOrY(item) {
return !(isNaN(parseFloat(item.x)) && isNaN(parseFloat(item.y)));
}
function hasXAndY(item) {
return !isNaN(parseFloat(item.x)) && !isNaN(parseFloat(item.y));
}
// return precision;
// }
function markerTypeCalculatorWithExtent(
mlType, data, otherDataDim, targetDataDim, otherCoordIndex, targetCoordIndex
) {
var coordArr = [];
return coordArr;
}
/**
* @method
* @param {module:echarts/data/List} data
* @param {string} baseAxisDim
* @param {string} valueAxisDim
*/
average: curry$7(markerTypeCalculatorWithExtent, 'average')
};
/**
* Transform markPoint data item to format used in List by do the following
* 1. Calculate statistic like `max`, `min`, `average`
* 2. Convert `item.xAxis`, `item.yAxis` to `item.coord` array
* @param {module:echarts/model/Series} seriesModel
* @param {module:echarts/coord/*} [coordSys]
* @param {Object} item
* @return {Object}
*/
function dataTransform(seriesModel, item) {
var data = seriesModel.getData();
var coordSys = seriesModel.coordinateSystem;
if (item.type
&& markerTypeCalculator[item.type]
&& axisInfo.baseAxis && axisInfo.valueAxis
) {
var otherCoordIndex = indexOf$2(dims, axisInfo.baseAxis.dim);
var targetCoordIndex = indexOf$2(dims, axisInfo.valueAxis.dim);
item.coord = markerTypeCalculator[item.type](
data, axisInfo.baseDataDim, axisInfo.valueDataDim,
otherCoordIndex, targetCoordIndex
);
// Force to use the value of calculated value.
item.value = item.coord[targetCoordIndex];
}
else {
// FIXME Only has one of xAxis and yAxis.
var coord = [
item.xAxis != null ? item.xAxis : item.radiusAxis,
item.yAxis != null ? item.yAxis : item.angleAxis
];
// Each coord support max, min, average
for (var i = 0; i < 2; i++) {
if (markerTypeCalculator[coord[i]]) {
coord[i] = numCalculate(data, data.mapDimension(dims[i]),
coord[i]);
}
}
item.coord = coord;
}
}
return item;
}
return ret;
}
/**
* Filter data which is out of coordinateSystem range
* [dataFilter description]
* @param {module:echarts/coord/*} [coordSys]
* @param {Object} item
* @return {boolean}
*/
function dataFilter$1(coordSys, item) {
// Alwalys return true if there is no coordSys
return (coordSys && coordSys.containData && item.coord && !hasXOrY(item))
? coordSys.containData(item.coord) : true;
}
type: 'marker',
init: function () {
/**
* Markline grouped by series
* @private
* @type {module:zrender/core/util.HashMap}
*/
this.markerGroupMap = createHashMap();
},
markerGroupMap.each(function (item) {
!item.__keep && this.group.remove(item.group);
}, this);
},
renderSeries: function () {}
});
mpData.setItemLayout(idx, point);
});
}
MarkerView.extend({
type: 'markPoint',
// FIXME
mpModel.setData(mpData);
mpData.each(function (idx) {
var itemModel = mpData.getItemModel(idx);
var symbolSize = itemModel.getShallow('symbolSize');
if (typeof symbolSize === 'function') {
// FIXME 这里不兼容 ECharts 2.x,2.x 貌似参数是整个数据?
symbolSize = symbolSize(
mpModel.getRawValue(idx), mpModel.getDataParams(idx)
);
}
mpData.setItemVisual(idx, {
symbolSize: symbolSize,
color: itemModel.get('itemStyle.color')
|| seriesData.getVisual('color'),
symbol: itemModel.getShallow('symbol')
});
});
symbolDraw.__keep = true;
symbolDraw.group.silent = mpModel.get('silent') ||
seriesModel.get('silent');
}
});
/**
* @inner
* @param {module:echarts/coord/*} [coordSys]
* @param {module:echarts/model/Series} seriesModel
* @param {module:echarts/model/Model} mpModel
*/
function createList$1(coordSys, seriesModel, mpModel) {
var coordDimsInfos;
if (coordSys) {
coordDimsInfos = map(coordSys && coordSys.dimensions, function (coordDim) {
var info = seriesModel.getData().getDimensionInfo(
seriesModel.getData().mapDimension(coordDim)
) || {};
// In map series data don't have lng and lat dimension. Fallback to
same with coordSys
return defaults({name: coordDim}, info);
});
}
else {
coordDimsInfos =[{
name: 'value',
type: 'float'
}];
}
return mpData;
}
MarkerModel.extend({
type: 'markLine',
defaultOption: {
zlevel: 0,
z: 5,
//symbolRotate: 0,
precision: 2,
tooltip: {
trigger: 'item'
},
label: {
show: true,
position: 'end'
},
lineStyle: {
type: 'dashed'
},
emphasis: {
label: {
show: true
},
lineStyle: {
width: 3
}
},
animationEasing: 'linear'
}
});
if (!isArray(item)
&& (
mlType === 'min' || mlType === 'max' || mlType === 'average'
// In case
// data: [{
// yAxis: 10
// }]
|| (item.xAxis != null || item.yAxis != null)
)
) {
var valueAxis;
var valueDataDim;
var value;
mlFrom.type = null;
mlFrom.coord = [];
mlTo.coord = [];
mlFrom.coord[baseIndex] = -Infinity;
mlTo.coord[baseIndex] = Infinity;
item = [
dataTransform(seriesModel, item[0]),
dataTransform(seriesModel, item[1]),
extend({}, item[2])
];
return item;
};
function isInifinity(val) {
return !isNaN(val) && !isFinite(val);
}
function updateSingleMarkerEndLayout(
data, idx, isFrom, seriesModel, api
) {
var coordSys = seriesModel.coordinateSystem;
var itemModel = data.getItemModel(idx);
var point;
var xPx = parsePercent$1(itemModel.get('x'), api.getWidth());
var yPx = parsePercent$1(itemModel.get('y'), api.getHeight());
if (!isNaN(xPx) && !isNaN(yPx)) {
point = [xPx, yPx];
}
else {
// Chart like bar may have there own marker positioning logic
if (seriesModel.getMarkerPosition) {
// Use the getMarkerPoisition
point = seriesModel.getMarkerPosition(
data.getValues(data.dimensions, idx)
);
}
else {
var dims = coordSys.dimensions;
var x = data.get(dims[0], idx);
var y = data.get(dims[1], idx);
point = coordSys.dataToPoint([x, y]);
}
// Expand line to the edge of grid if value on one axis is Inifnity
// In case
// markLine: {
// data: [{
// yAxis: 2
// // or
// type: 'average'
// }]
// }
if (coordSys.type === 'cartesian2d') {
var xAxis = coordSys.getAxis('x');
var yAxis = coordSys.getAxis('y');
var dims = coordSys.dimensions;
if (isInifinity(data.get(dims[0], idx))) {
point[0] = xAxis.toGlobalCoord(xAxis.getExtent()[isFrom ? 0 : 1]);
}
else if (isInifinity(data.get(dims[1], idx))) {
point[1] = yAxis.toGlobalCoord(yAxis.getExtent()[isFrom ? 0 : 1]);
}
}
data.setItemLayout(idx, point);
}
MarkerView.extend({
type: 'markLine',
// this.markerGroupMap.get(seriesModel.id).updateLayout();
// }
// }, this);
// },
this.markerGroupMap.get(seriesModel.id).updateLayout();
}
}, this);
},
mlModel.__from = fromData;
mlModel.__to = toData;
// Line data for tooltip and formatter
mlModel.setData(lineData);
lineData.setItemVisual(idx, {
'fromSymbolSize': fromData.getItemVisual(idx, 'symbolSize'),
'fromSymbol': fromData.getItemVisual(idx, 'symbol'),
'toSymbolSize': toData.getItemVisual(idx, 'symbolSize'),
'toSymbol': toData.getItemVisual(idx, 'symbol')
});
});
lineDraw.updateData(lineData);
updateSingleMarkerEndLayout(
data, idx, isFrom, seriesModel, api
);
data.setItemVisual(idx, {
symbolSize: itemModel.get('symbolSize') || symbolSize[isFrom ? 0 :
1],
symbol: itemModel.get('symbol', true) || symbolType[isFrom ? 0 :
1],
color: itemModel.get('itemStyle.color') ||
seriesData.getVisual('color')
});
}
lineDraw.__keep = true;
/**
* @inner
* @param {module:echarts/coord/*} coordSys
* @param {module:echarts/model/Series} seriesModel
* @param {module:echarts/model/Model} mpModel
*/
function createList$2(coordSys, seriesModel, mlModel) {
var coordDimsInfos;
if (coordSys) {
coordDimsInfos = map(coordSys && coordSys.dimensions, function (coordDim) {
var info = seriesModel.getData().getDimensionInfo(
seriesModel.getData().mapDimension(coordDim)
) || {};
// In map series data don't have lng and lat dimension. Fallback to
same with coordSys
return defaults({name: coordDim}, info);
});
}
else {
coordDimsInfos =[{
name: 'value',
type: 'float'
}];
}
return {
from: fromData,
to: toData,
line: lineData
};
}
registerPreprocessor(function (opt) {
// Make sure markLine component is enabled
opt.markLine = opt.markLine || {};
});
MarkerModel.extend({
type: 'markArea',
defaultOption: {
zlevel: 0,
// PENDING
z: 1,
tooltip: {
trigger: 'item'
},
// markArea should fixed on the coordinate system
animation: false,
label: {
show: true,
position: 'top'
},
itemStyle: {
// color and borderColor default to use color from series
// color: 'auto'
// borderColor: 'auto'
borderWidth: 0
},
emphasis: {
label: {
show: true,
position: 'top'
}
}
}
});
result.coord = [
lt.coord, rb.coord
];
result.x0 = lt.x;
result.y0 = lt.y;
result.x1 = rb.x;
result.y1 = rb.y;
return result;
};
function isInifinity$1(val) {
return !isNaN(val) && !isFinite(val);
}
// dims can be ['x0', 'y0'], ['x1', 'y1'], ['x0', 'y1'], ['x1', 'y0']
function getSingleMarkerEndPoint(data, idx, dims, seriesModel, api) {
var coordSys = seriesModel.coordinateSystem;
var itemModel = data.getItemModel(idx);
var point;
var xPx = parsePercent$1(itemModel.get(dims[0]), api.getWidth());
var yPx = parsePercent$1(itemModel.get(dims[1]), api.getHeight());
if (!isNaN(xPx) && !isNaN(yPx)) {
point = [xPx, yPx];
}
else {
// Chart like bar may have there own marker positioning logic
if (seriesModel.getMarkerPosition) {
// Use the getMarkerPoisition
point = seriesModel.getMarkerPosition(
data.getValues(dims, idx)
);
}
else {
var x = data.get(dims[0], idx);
var y = data.get(dims[1], idx);
var pt = [x, y];
coordSys.clampData && coordSys.clampData(pt, pt);
point = coordSys.dataToPoint(pt, true);
}
if (coordSys.type === 'cartesian2d') {
var xAxis = coordSys.getAxis('x');
var yAxis = coordSys.getAxis('y');
var x = data.get(dims[0], idx);
var y = data.get(dims[1], idx);
if (isInifinity$1(x)) {
point[0] = xAxis.toGlobalCoord(xAxis.getExtent()[dims[0] === 'x0' ?
0 : 1]);
}
else if (isInifinity$1(y)) {
point[1] = yAxis.toGlobalCoord(yAxis.getExtent()[dims[1] === 'y0' ?
0 : 1]);
}
}
var dimPermutations = [['x0', 'y0'], ['x1', 'y0'], ['x1', 'y1'], ['x0', 'y1']];
MarkerView.extend({
type: 'markArea',
this.group.add(polygonGroup.group);
polygonGroup.__keep = true;
var areaData = createList$3(coordSys, seriesModel, maModel);
// Visual
areaData.setItemVisual(idx, {
color: seriesData.getVisual('color')
});
});
areaData.diff(polygonGroup.__data)
.add(function (idx) {
var polygon = new Polygon({
shape: {
points: areaData.getItemLayout(idx)
}
});
areaData.setItemGraphicEl(idx, polygon);
polygonGroup.group.add(polygon);
})
.update(function (newIdx, oldIdx) {
var polygon = polygonGroup.__data.getItemGraphicEl(oldIdx);
updateProps(polygon, {
shape: {
points: areaData.getItemLayout(newIdx)
}
}, maModel, newIdx);
polygonGroup.group.add(polygon);
areaData.setItemGraphicEl(newIdx, polygon);
})
.remove(function (idx) {
var polygon = polygonGroup.__data.getItemGraphicEl(idx);
polygonGroup.group.remove(polygon);
})
.execute();
polygon.hoverStyle =
itemModel.getModel('emphasis.itemStyle').getItemStyle();
setLabelStyle(
polygon.style, polygon.hoverStyle, labelModel, labelHoverModel,
{
labelFetcher: maModel,
labelDataIndex: idx,
defaultText: areaData.getName(idx) || '',
isRectText: true,
autoColor: color
}
);
setHoverStyle(polygon, {});
polygon.dataModel = maModel;
});
polygonGroup.__data = areaData;
polygonGroup.group.silent = maModel.get('silent') ||
seriesModel.get('silent');
}
});
/**
* @inner
* @param {module:echarts/coord/*} coordSys
* @param {module:echarts/model/Series} seriesModel
* @param {module:echarts/model/Model} mpModel
*/
function createList$3(coordSys, seriesModel, maModel) {
var coordDimsInfos;
var areaData;
var dims = ['x0', 'y0', 'x1', 'y1'];
if (coordSys) {
coordDimsInfos = map(coordSys && coordSys.dimensions, function (coordDim) {
var data = seriesModel.getData();
var info = data.getDimensionInfo(
data.mapDimension(coordDim)
) || {};
// In map series data don't have lng and lat dimension. Fallback to
same with coordSys
return defaults({name: coordDim}, info);
});
areaData = new List(map(dims, function (dim, idx) {
return {
name: dim,
type: coordDimsInfos[idx % 2].type
};
}), maModel);
}
else {
coordDimsInfos =[{
name: 'value',
type: 'float'
}];
areaData = new List(coordDimsInfos, maModel);
}
registerPreprocessor(function (opt) {
// Make sure markArea component is enabled
opt.markArea = opt.markArea || {};
});
if (!isArray(timelineOpt)) {
timelineOpt = timelineOpt ? [timelineOpt] : [];
}
compatibleEC2(opt);
});
};
function compatibleEC2(opt) {
var type = opt.type;
transferItem(opt);
if (has$2(opt, 'controlPosition')) {
var controlStyle = opt.controlStyle || (opt.controlStyle = {});
if (!has$2(controlStyle, 'position')) {
controlStyle.position = opt.controlPosition;
}
if (controlStyle.position === 'none' && !has$2(controlStyle, 'show')) {
controlStyle.show = false;
delete controlStyle.position;
}
delete opt.controlPosition;
}
function transferItem(opt) {
var itemStyle = opt.itemStyle || (opt.itemStyle = {});
ComponentModel.registerSubTypeDefaulter('timeline', function () {
// Only slider now.
return 'slider';
});
registerAction(
return defaults({
currentIndex: timelineModel.option.currentIndex
}, payload);
}
);
registerAction(
type: 'timeline',
layoutMode: 'box',
/**
* @protected
*/
defaultOption: {
zlevel: 0, // 一级层叠
z: 4, // 二级层叠
show: true,
realtime: true,
left: '20%',
top: null,
right: '20%',
bottom: 0,
width: null,
height: 40,
padding: 5,
currentIndex: 0,
itemStyle: {},
label: {
color: '#000'
},
data: []
},
/**
* @override
*/
init: function (option, parentModel, ecModel) {
/**
* @private
* @type {module:echarts/data/List}
*/
this._data;
/**
* @private
* @type {Array.<string>}
*/
this._names;
this.mergeDefaultAndTheme(option, ecModel);
this._initData();
},
/**
* @override
*/
mergeOption: function (option) {
TimelineModel.superApply(this, 'mergeOption', arguments);
this._initData();
},
/**
* @param {number} [currentIndex]
*/
setCurrentIndex: function (currentIndex) {
if (currentIndex == null) {
currentIndex = this.option.currentIndex;
}
var count = this._data.count();
if (this.option.loop) {
currentIndex = (currentIndex % count + count) % count;
}
else {
currentIndex >= count && (currentIndex = count - 1);
currentIndex < 0 && (currentIndex = 0);
}
this.option.currentIndex = currentIndex;
},
/**
* @return {number} currentIndex
*/
getCurrentIndex: function () {
return this.option.currentIndex;
},
/**
* @return {boolean}
*/
isIndexMax: function () {
return this.getCurrentIndex() >= this._data.count() - 1;
},
/**
* @param {boolean} state true: play, false: stop
*/
setPlayState: function (state) {
this.option.autoPlay = !!state;
},
/**
* @return {boolean} true: play, false: stop
*/
getPlayState: function () {
return !!this.option.autoPlay;
},
/**
* @private
*/
_initData: function () {
var thisOption = this.option;
var dataArr = thisOption.data || [];
var axisType = thisOption.axisType;
var names = this._names = [];
if (isObject$1(item)) {
newItem = clone(item);
newItem.value = index;
}
else {
newItem = index;
}
idxArr.push(newItem);
names.push(value + '');
});
dataArr = idxArr;
}
data.initData(dataArr, names);
},
getData: function () {
return this._data;
},
/**
* @public
* @return {Array.<string>} categoreis
*/
getCategories: function () {
if (this.get('axisType') === 'category') {
return this._names.slice();
}
}
});
type: 'timeline.slider',
/**
* @protected
*/
defaultOption: {
symbol: 'emptyCircle',
symbolSize: 10,
lineStyle: {
show: true,
width: 2,
color: '#304654'
},
label: { // 文本标签
position: 'auto', // auto left right top bottom
// When using number, label position is not
// restricted by viewRect.
// positive: right/bottom, negative:
left/top
show: true,
interval: 'auto',
rotate: 0,
// formatter: null,
// 其余属性默认使用全局文本样式,详见 TEXTSTYLE
color: '#304654'
},
itemStyle: {
color: '#304654',
borderWidth: 1
},
checkpointStyle: {
symbol: 'circle',
symbolSize: 13,
color: '#c23531',
borderWidth: 5,
borderColor: 'rgba(194,53,49, 0.5)',
animation: true,
animationDuration: 300,
animationEasing: 'quinticInOut'
},
controlStyle: {
show: true,
showPlayBtn: true,
showPrevBtn: true,
showNextBtn: true,
itemSize: 22,
itemGap: 12,
position: 'left', // 'left' 'right' 'top' 'bottom'
playIcon:
'path://M31.6,53C17.5,53,6,41.5,6,27.4S17.5,1.8,31.6,1.8C45.7,1.8,57.2,13.3,57.2,27
.4S45.7,53,31.6,53z M31.6,3.3
C18.4,3.3,7.5,14.1,7.5,27.4c0,13.3,10.8,24.1,24.1,24.1C44.9,51.5,55.7,40.7,55.7,27.
4C55.7,14.1,44.9,3.3,31.6,3.3z M24.9,21.3 c0-2.2,1.6-3.1,3.5-
2l10.5,6.1c1.899,1.1,1.899,2.9,0,4l-10.5,6.1c-1.9,1.1-3.5,0.2-3.5-2V21.3z', //
jshint ignore:line
stopIcon:
'path://M30.9,53.2C16.8,53.2,5.3,41.7,5.3,27.6S16.8,2,30.9,2C45,2,56.4,13.5,56.4,27
.6S45,53.2,30.9,53.2z
M30.9,3.5C17.6,3.5,6.8,14.4,6.8,27.6c0,13.3,10.8,24.1,24.101,24.1C44.2,51.7,55,40.9
,55,27.6C54.9,14.4,44.1,3.5,30.9,3.5z M36.9,35.8c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-
0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H36c0.5,0,0.9,0.4,0.9,1V35.8z M27.8,35.8
c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-
1H27c0.5,0,0.9,0.4,0.9,1L27.8,35.8L27.8,35.8z', // jshint ignore:line
nextIcon: 'path://M18.6,50.8l22.5-22.5c0.2-0.2,0.3-0.4,0.3-0.7c0-0.3-
0.1-0.5-0.3-0.7L18.7,4.4c-0.1-0.1-0.2-0.3-0.2-0.5 c0-0.4,0.3-0.8,0.8-
0.8c0.2,0,0.5,0.1,0.6,0.3l23.5,23.5l0,0c0.2,0.2,0.3,0.4,0.3,0.7c0,0.3-0.1,0.5-
0.3,0.7l-0.1,0.1L19.7,52 c-0.1,0.1-0.3,0.2-0.5,0.2c-0.4,0-0.8-0.3-0.8-
0.8C18.4,51.2,18.5,51,18.6,50.8z', // jshint ignore:line
prevIcon: 'path://M43,52.8L20.4,30.3c-0.2-0.2-0.3-0.4-0.3-0.7c0-
0.3,0.1-0.5,0.3-0.7L42.9,6.4c0.1-0.1,0.2-0.3,0.2-0.5 c0-0.4-0.3-0.8-0.8-0.8c-0.2,0-
0.5,0.1-0.6,0.3L18.3,28.8l0,0c-0.2,0.2-0.3,0.4-
0.3,0.7c0,0.3,0.1,0.5,0.3,0.7l0.1,0.1L41.9,54 c0.1,0.1,0.3,0.2,0.5,0.2c0.4,0,0.8-
0.3,0.8-0.8C43.2,53.2,43.1,53,43,52.8z', // jshint ignore:line
color: '#304654',
borderColor: '#304654',
borderWidth: 1
},
emphasis: {
label: {
show: true,
// 其余属性默认使用全局文本样式,详见 TEXTSTYLE
color: '#c23531'
},
itemStyle: {
color: '#c23531'
},
controlStyle: {
color: '#c23531',
borderColor: '#c23531',
borderWidth: 2
}
},
data: []
}
});
mixin(SliderTimelineModel, dataFormatMixin);
/**
* Extend axis 2d
* @constructor module:echarts/coord/cartesian/Axis2D
* @extends {module:echarts/coord/cartesian/Axis}
* @param {string} dim
* @param {*} scale
* @param {Array.<number>} coordExtent
* @param {string} axisType
* @param {string} position
*/
var TimelineAxis = function (dim, scale, coordExtent, axisType) {
/**
* Axis type
* - 'category'
* - 'value'
* - 'time'
* - 'log'
* @type {string}
*/
this.type = axisType || 'value';
/**
* @private
* @type {number}
*/
this._autoLabelInterval;
/**
* Axis model
* @param {module:echarts/component/TimelineModel}
*/
this.model = null;
};
TimelineAxis.prototype = {
constructor: TimelineAxis,
/**
* @public
* @return {number}
*/
getLabelInterval: function () {
var timelineModel = this.model;
var labelModel = timelineModel.getModel('label');
var labelInterval = labelModel.get('interval');
if (!labelInterval) {
labelInterval = this._autoLabelInterval = getAxisLabelInterval(
map(this.scale.getTicks(), this.dataToCoord, this),
getFormattedLabels(this, labelModel.get('formatter')),
labelModel.getFont(),
timelineModel.get('orient') === 'horizontal' ? 0 : 90,
labelModel.get('rotate')
);
}
return labelInterval;
},
/**
* If label is ignored.
* Automatically used when axis is category and label can not be all shown
* @public
* @param {number} idx
* @return {boolean}
*/
isLabelIgnored: function (idx) {
if (this.type === 'category') {
var labelInterval = this.getLabelInterval();
return ((typeof labelInterval === 'function')
&& !labelInterval(idx, this.scale.getLabel(idx)))
|| idx % (labelInterval + 1);
}
}
};
inherits(TimelineAxis, Axis);
TimelineView.extend({
type: 'timeline.slider',
this.api = api;
/**
* @private
* @type {module:echarts/component/timeline/TimelineAxis}
*/
this._axis;
/**
* @private
* @type {module:zrender/core/BoundingRect}
*/
this._viewRect;
/**
* @type {number}
*/
this._timer;
/**
* @type {module:zrender/Element}
*/
this._currentPointer;
/**
* @type {module:zrender/container/Group}
*/
this._mainGroup;
/**
* @type {module:zrender/container/Group}
*/
this._labelGroup;
},
/**
* @override
*/
render: function (timelineModel, ecModel, api, payload) {
this.model = timelineModel;
this.api = api;
this.ecModel = ecModel;
this.group.removeAll();
if (timelineModel.get('show', true)) {
/**
* @private
* @type {module:echarts/component/timeline/TimelineAxis}
*/
var axis = this._axis = this._createAxis(layoutInfo, timelineModel);
each$28(
['AxisLine', 'AxisTick', 'Control', 'CurrentPointer'],
function (name) {
this['_render' + name](layoutInfo, mainGroup, axis,
timelineModel);
},
this
);
this._doPlayStop();
},
/**
* @override
*/
remove: function () {
this._clearTimer();
this.group.removeAll();
},
/**
* @override
*/
dispose: function () {
this._clearTimer();
},
var labelAlignMap = {
horizontal: 'center',
vertical: (labelPosOpt >= 0 || labelPosOpt === '+') ? 'left' : 'right'
};
var labelBaselineMap = {
horizontal: (labelPosOpt >= 0 || labelPosOpt === '+') ? 'top' :
'bottom',
vertical: 'middle'
};
var rotationMap = {
horizontal: 0,
vertical: PI$4 / 2
};
// Position
var mainLength = orient === 'vertical' ? viewRect.height : viewRect.width;
var playPosition;
var prevBtnPosition;
var nextBtnPosition;
var axisExtent;
var controlPosition = controlModel.get('position', true);
var showPlayBtn = showControl && controlModel.get('showPlayBtn', true);
var showPrevBtn = showControl && controlModel.get('showPrevBtn', true);
var showNextBtn = showControl && controlModel.get('showNextBtn', true);
var xLeft = 0;
var xRight = mainLength;
if (timelineModel.get('inverse')) {
axisExtent.reverse();
}
return {
viewRect: viewRect,
mainLength: mainLength,
orient: orient,
rotation: rotationMap[orient],
labelRotation: labelRotation,
labelPosOpt: labelPosOpt,
labelAlign: timelineModel.get('label.align') || labelAlignMap[orient],
labelBaseline: timelineModel.get('label.verticalAlign')
|| timelineModel.get('label.baseline')
|| labelBaselineMap[orient],
// Based on mainGroup.
playPosition: playPosition,
prevBtnPosition: prevBtnPosition,
nextBtnPosition: nextBtnPosition,
axisExtent: axisExtent,
controlSize: controlSize,
controlGap: controlGap
};
},
mainGroup.attr('position', mainPosition);
labelGroup.attr('position', labelsPosition);
mainGroup.rotation = labelGroup.rotation = layoutInfo.rotation;
setOrigin(mainGroup);
setOrigin(labelGroup);
function setOrigin(targetGroup) {
var pos = targetGroup.position;
targetGroup.origin = [
viewBound[0][0] - pos[0],
viewBound[1][0] - pos[1]
];
}
function getBound(rect) {
// [[xmin, xmax], [ymin, ymax]]
return [
[rect.x, rect.x + rect.width],
[rect.y, rect.y + rect.height]
];
}
return axis;
},
scale.getTicks = function () {
return data.mapArray(['value'], function (value) {
return value;
});
};
scale.getTicksLabels = function () {
return map(this.getTicks(), scale.getLabel, scale);
};
},
if (!timelineModel.get('lineStyle.show')) {
return;
}
group.add(new Line({
shape: {
x1: axisExtent[0], y1: 0,
x2: axisExtent[1], y2: 0
},
style: extend(
{lineCap: 'round'},
timelineModel.getModel('lineStyle').getLineStyle()
),
silent: true,
z2: 1
}));
},
/**
* @private
*/
_renderAxisTick: function (layoutInfo, group, axis, timelineModel) {
var data = timelineModel.getData();
var ticks = axis.scale.getTicks();
if (itemModel.get('tooltip')) {
el.dataIndex = dataIndex;
el.dataModel = timelineModel;
}
else {
el.dataIndex = el.dataModel = null;
}
}, this);
},
/**
* @private
*/
_renderAxisLabel: function (layoutInfo, group, axis, timelineModel) {
var labelModel = timelineModel.getModel('label');
if (!labelModel.get('show')) {
return;
}
}, this);
},
/**
* @private
*/
_renderControl: function (layoutInfo, group, axis, timelineModel) {
var controlSize = layoutInfo.controlSize;
var rotation = layoutInfo.rotation;
makeBtn(
layoutInfo.nextBtnPosition,
'controlStyle.nextIcon',
bind$6(this._changeTimeline, this, inverse ? '-' : '+')
);
makeBtn(
layoutInfo.prevBtnPosition,
'controlStyle.prevIcon',
bind$6(this._changeTimeline, this, inverse ? '+' : '-')
);
makeBtn(
layoutInfo.playPosition,
'controlStyle.' + (playState ? 'stopIcon' : 'playIcon'),
bind$6(this._handlePlayClick, this, !playState),
true
);
var callback = {
onCreate: function (pointer) {
pointer.draggable = true;
pointer.drift = bind$6(me._handlePointerDrag, me);
pointer.ondragend = bind$6(me._handlePointerDragend, me);
pointerMoveTo(pointer, currentIndex, axis, timelineModel, true);
},
onUpdate: function (pointer) {
pointerMoveTo(pointer, currentIndex, axis, timelineModel);
}
};
this._currentPointer.position[0] = toCoord;
this._currentPointer.dirty();
if (trigger || (
targetDataIndex !== timelineModel.getCurrentIndex()
&& timelineModel.get('realtime')
)) {
this._changeTimeline(targetDataIndex);
}
},
_doPlayStop: function () {
this._clearTimer();
if (this.model.getPlayState()) {
this._timer = setTimeout(
bind$6(handleFrame, this),
this.model.get('playInterval')
);
}
function handleFrame() {
// Do not cache
var timelineModel = this.model;
this._changeTimeline(
timelineModel.getCurrentIndex()
+ (timelineModel.get('rewind', true) ? -1 : 1)
);
}
},
return targetDataIndex;
},
_clearTimer: function () {
if (this._timer) {
clearTimeout(this._timer);
this._timer = null;
}
},
this.api.dispatchAction({
type: 'timelineChange',
currentIndex: nextIndex,
from: this.uid
});
}
});
return icon;
}
/**
* Create symbol or update symbol
* opt: basic position and event handlers
*/
function giveSymbol(hostModel, itemStyleModel, group, opt, symbol, callback) {
var color = itemStyleModel.get('color');
if (!symbol) {
var symbolType = hostModel.get('symbol');
symbol = createSymbol(symbolType, -1, -1, 2, 2, color);
symbol.setStyle('strokeNoScale', true);
group.add(symbol);
callback && callback.onCreate(symbol);
}
else {
symbol.setColor(color);
group.add(symbol); // Group may be new, also need to add.
callback && callback.onUpdate(symbol);
}
// Style
var itemStyle = itemStyleModel.getItemStyle(['color', 'symbol', 'symbolSize']);
symbol.setStyle(itemStyle);
symbol.attr(opt);
// FIXME
// (1) When symbol.style.strokeNoScale is true and updateTransform is not
performed,
// getBoundingRect will return wrong result.
// (This is supposed to be resolved in zrender, but it is a little difficult to
// leverage performance and auto updateTransform)
// (2) All of ancesters of symbol do not scale, so we can just updateTransform
symbol.
symbol.updateTransform();
return symbol;
}
/**
* DataZoom component entry
*/
registerPreprocessor(preprocessor$3);
type: 'toolbox',
layoutMode: {
type: 'box',
ignoreSize: true
},
defaultOption: {
show: true,
z: 6,
zlevel: 0,
orient: 'horizontal',
left: 'right',
top: 'top',
// right
// bottom
backgroundColor: 'transparent',
borderColor: '#ccc',
borderRadius: 0,
borderWidth: 0,
padding: 5,
itemSize: 15,
itemGap: 8,
showTitle: true,
iconStyle: {
borderColor: '#666',
color: 'none'
},
emphasis: {
iconStyle: {
borderColor: '#3E98C5'
}
}
// textStyle: {},
// feature
}
});
extendComponentView({
type: 'toolbox',
if (!toolboxModel.get('show')) {
return;
}
if (!featureModel.get('show') || feature.unusable) {
feature.remove && feature.remove(ecModel, api);
return;
}
if (feature.render) {
feature.render(featureModel, ecModel, api, payload);
}
}
setHoverStyle(path);
if (toolboxModel.get('showTitle')) {
path.__title = titles[iconName];
path.on('mouseover', function () {
// Should not reuse above hoverStyle, which might be
modified.
var hoverStyle = iconStyleEmphasisModel.getItemStyle();
path.setStyle({
text: titles[iconName],
textPosition: hoverStyle.textPosition || 'bottom',
textFill: hoverStyle.fill || hoverStyle.stroke ||
'#000',
textAlign: hoverStyle.textAlign || 'center'
});
})
.on('mouseout', function () {
path.setStyle({
textFill: null
});
});
}
path.trigger(featureModel.get('iconStatus.' + iconName) ||
'normal');
group.add(path);
path.on('click', bind(
feature.onclick, feature, ecModel, api, iconName
));
iconPaths[iconName] = path;
});
}
function isUserFeatureName(featureName) {
return featureName.indexOf('my') === 0;
}
function SaveAsImage(model) {
this.model = model;
}
SaveAsImage.defaultOption = {
show: true,
icon:
'M4.7,22.9L29.3,45.5L54.7,23.4M4.6,43.6L4.6,58L53.8,58L53.8,43.6M29.2,45.1L29.2,0',
title: saveAsImageLang.title,
type: 'png',
// Default use option.backgroundColor
// backgroundColor: '#fff',
name: '',
excludeComponents: ['toolbox'],
pixelRatio: 1,
lang: saveAsImageLang.lang.slice()
};
SaveAsImage.prototype.unusable = !env$1.canvasSupported;
register$1(
'saveAsImage', SaveAsImage
);
function MagicType(model) {
this.model = model;
}
MagicType.defaultOption = {
show: true,
type: [],
// Icon group
icon: {
line: 'M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4',
bar: 'M6.7,22.9h10V48h-10V22.9zM24.9,13h10v35h-10V13zM43.2,2h10v46h-
10V2zM3.1,58h53.7',
stack: 'M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z
M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-
4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-
8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-
0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-
4.1L30.4,2.2z', // jshint ignore:line
tiled: 'M2.3,2.2h22.8V25H2.3V2.2z
M35,2.2h22.8V25H35V2.2zM2.3,35h22.8v22.8H2.3V35z M35,35h22.8v22.8H35V35z'
},
// `line`, `bar`, `stack`, `tiled`
title: clone(magicTypeLang.title),
option: {},
seriesIndex: {}
};
proto$5.getIcons = function () {
var model = this.model;
var availableIcons = model.get('icon');
var icons = {};
each$1(model.get('type'), function (type) {
if (availableIcons[type]) {
icons[type] = availableIcons[type];
}
});
return icons;
};
var seriesOptGenreator = {
'line': function (seriesType, seriesId, seriesModel, model) {
if (seriesType === 'bar') {
return merge({
id: seriesId,
type: 'line',
// Preserve data related option
data: seriesModel.get('data'),
stack: seriesModel.get('stack'),
markPoint: seriesModel.get('markPoint'),
markLine: seriesModel.get('markLine')
}, model.get('option.line') || {}, true);
}
},
'bar': function (seriesType, seriesId, seriesModel, model) {
if (seriesType === 'line') {
return merge({
id: seriesId,
type: 'bar',
// Preserve data related option
data: seriesModel.get('data'),
stack: seriesModel.get('stack'),
markPoint: seriesModel.get('markPoint'),
markLine: seriesModel.get('markLine')
}, model.get('option.bar') || {}, true);
}
},
'stack': function (seriesType, seriesId, seriesModel, model) {
if (seriesType === 'line' || seriesType === 'bar') {
return merge({
id: seriesId,
stack: '__ec_magicType_stack__'
}, model.get('option.stack') || {}, true);
}
},
'tiled': function (seriesType, seriesId, seriesModel, model) {
if (seriesType === 'line' || seriesType === 'bar') {
return merge({
id: seriesId,
stack: ''
}, model.get('option.tiled') || {}, true);
}
}
};
var radioTypes = [
['line', 'bar'],
['stack', 'tiled']
];
model.setIconStatus(type, 'emphasis');
ecModel.eachComponent(
{
mainType: 'series',
query: seriesIndex == null ? null : {
seriesIndex: seriesIndex
}
}, generateNewSeriesTypes
);
api.dispatchAction({
type: 'changeMagicType',
currentType: type,
newOption: newOption
});
};
registerAction({
type: 'changeMagicType',
event: 'magicTypeChanged',
update: 'prepareAndUpdate'
}, function (payload, ecModel) {
ecModel.mergeOption(payload.newOption);
});
register$1('magicType', MagicType);
return {
seriesGroupByCategoryAxis: seriesGroupByCategoryAxis,
other: otherSeries,
meta: meta
};
}
/**
* Assemble content of series on cateogory axis
* @param {Array.<module:echarts/model/Series>} series
* @return {string}
* @inner
*/
function assembleSeriesWithCategoryAxis(series) {
var tables = [];
each$1(series, function (group, key) {
var categoryAxis = group.categoryAxis;
var valueAxis = group.valueAxis;
var valueAxisDim = valueAxis.dim;
/**
* Assemble content of other series
* @param {Array.<module:echarts/model/Series>} series
* @return {string}
* @inner
*/
function assembleOtherSeries(series) {
return map(series, function (series) {
var data = series.getRawData();
var lines = [series.name];
var vals = [];
data.each(data.dimensions, function () {
var argLen = arguments.length;
var dataIndex = arguments[argLen - 1];
var name = data.getName(dataIndex);
for (var i = 0; i < argLen - 1; i++) {
vals[i] = arguments[i];
}
lines.push((name ? (name + ITEM_SPLITER) : '') +
vals.join(ITEM_SPLITER));
});
return lines.join('\n');
}).join('\n\n' + BLOCK_SPLITER + '\n\n');
}
/**
* @param {module:echarts/model/Global}
* @return {Object}
* @inner
*/
function getContentFromModel(ecModel) {
return {
value: filter([
assembleSeriesWithCategoryAxis(result.seriesGroupByCategoryAxis),
assembleOtherSeries(result.other)
], function (str) {
return str.replace(/[\n\t\s]/g, '');
}).join('\n\n' + BLOCK_SPLITER + '\n\n'),
meta: result.meta
};
}
function trim$1(str) {
return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
}
/**
* If a block is tsv format
*/
function isTSVFormat(block) {
// Simple method to find out if a block is tsv format
var firstLine = block.slice(0, block.indexOf('\n'));
if (firstLine.indexOf(ITEM_SPLITER) >= 0) {
return true;
}
}
/**
* @param {string} str
* @return {Array.<Object>}
* @inner
*/
function parseListContents(str) {
var lines = str.split(/\n+/g);
var seriesName = trim$1(lines.shift());
return {
name: seriesName,
data: data
};
}
/**
* @param {string} str
* @param {Array.<Object>} blockMetaList
* @return {Object}
* @inner
*/
function parseContents(str, blockMetaList) {
var blocks = str.split(new RegExp('\n*' + BLOCK_SPLITER + '\n*', 'g'));
var newOption = {
series: []
};
each$1(blocks, function (block, idx) {
if (isTSVFormat(block)) {
var result = parseTSVContents(block);
var blockMeta = blockMetaList[idx];
var axisKey = blockMeta.axisDim + 'Axis';
if (blockMeta) {
newOption[axisKey] = newOption[axisKey] || [];
newOption[axisKey][blockMeta.axisIndex] = {
data: result.categories
};
newOption.series = newOption.series.concat(result.series);
}
}
else {
var result = parseListContents(block);
newOption.series.push(result);
}
});
return newOption;
}
/**
* @alias {module:echarts/component/toolbox/feature/DataView}
* @constructor
* @param {module:echarts/model/Model} model
*/
function DataView(model) {
this._dom = null;
this.model = model;
}
DataView.defaultOption = {
show: true,
readOnly: false,
optionToContent: null,
contentToOption: null,
// Create elements
var header = document.createElement('h4');
var lang$$1 = model.get('lang') || [];
header.innerHTML = lang$$1[0] || model.get('title');
header.style.cssText = 'margin: 10px 20px;';
header.style.color = model.get('textColor');
function close() {
container.removeChild(root);
self._dom = null;
}
addEventListener(closeButton, 'click', close);
close();
});
closeButton.innerHTML = lang$$1[1];
refreshButton.innerHTML = lang$$1[2];
refreshButton.style.cssText = buttonStyle;
closeButton.style.cssText = buttonStyle;
// http://stackoverflow.com/questions/6637341/use-tab-to-indent-in-textarea
addEventListener(textarea, 'keydown', function (e) {
if ((e.keyCode || e.which) === 9) {
// get caret position/selection
var val = this.value;
var start = this.selectionStart;
var end = this.selectionEnd;
// set textarea value to: text before caret + tab + text after caret
this.value = val.substring(0, start) + ITEM_SPLITER +
val.substring(end);
root.appendChild(header);
root.appendChild(viewMain);
root.appendChild(buttonContainer);
container.appendChild(root);
this._dom = root;
};
/**
* @inner
*/
function tryMergeDataOption(newData, originalData) {
return map(newData, function (newVal, idx) {
var original = originalData && originalData[idx];
if (isObject$1(original) && !isArray(original)) {
if (isObject$1(newVal) && !isArray(newVal)) {
newVal = newVal.value;
}
// Original data has option
return defaults({
value: newVal
}, original);
}
else {
return newVal;
}
});
}
register$1('dataView', DataView);
registerAction({
type: 'changeDataView',
event: 'dataViewChanged',
update: 'prepareAndUpdate'
}, function (payload, ecModel) {
var newSeriesOptList = [];
each$1(payload.newOption.series, function (seriesOpt) {
var seriesModel = ecModel.getSeriesByName(seriesOpt.name)[0];
if (!seriesModel) {
// New created series
// Geuss the series type
newSeriesOptList.push(extend({
// Default is scatter
type: 'scatter'
}, seriesOpt));
}
else {
var originalData = seriesModel.get('data');
newSeriesOptList.push({
name: seriesOpt.name,
data: tryMergeDataOption(seriesOpt.data, originalData)
});
}
});
ecModel.mergeOption(defaults({
series: newSeriesOptList
}, payload.newOption));
});
/**
* @param {module:echarts/model/Global} ecModel
* @param {Object} newSnapshot {dataZoomId, batch: [payloadInfo, ...]}
*/
function push(ecModel, newSnapshot) {
var store = giveStore$1(ecModel);
store.push(newSnapshot);
}
/**
* @param {module:echarts/model/Global} ecModel
* @return {Object} snapshot
*/
function pop(ecModel) {
var store = giveStore$1(ecModel);
var head = store[store.length - 1];
store.length > 1 && store.pop();
return snapshot;
}
/**
* @param {module:echarts/model/Global} ecModel
*/
function clear$1(ecModel) {
ecModel[ATTR$2] = null;
}
/**
* @param {module:echarts/model/Global} ecModel
* @return {number} records. always >= 1.
*/
function count(ecModel) {
return giveStore$1(ecModel).length;
}
/**
* [{key: dataZoomId, value: {dataZoomId, range}}, ...]
* History length of each dataZoom may be different.
* this._history[0] is used to store origin range.
* @type {Array.<Object>}
*/
function giveStore$1(ecModel) {
var store = ecModel[ATTR$2];
if (!store) {
store = ecModel[ATTR$2] = [{}];
}
return store;
}
DataZoomModel.extend({
type: 'dataZoom.select'
});
DataZoomView.extend({
type: 'dataZoom.select'
});
/**
* DataZoom component entry
*/
// Use dataZoomSelect
var dataZoomLang = lang.toolbox.dataZoom;
var each$29 = each$1;
/**
* @private
* @type {module:echarts/component/helper/BrushController}
*/
(this._brushController = new BrushController(api.getZr()))
.on('brush', bind(this._onBrush, this))
.mount();
/**
* @private
* @type {boolean}
*/
this._isZoomActive;
}
DataZoom.defaultOption = {
show: true,
// Icon group
icon: {
zoom: 'M0,13.5h26.9 M13.5,26.9V0 M32.1,13.5H58V58H13.5 V32.1',
back: 'M22,1.4L9.9,13.5l12.3,12.3 M10.3,13.5H54.9v44.6 H10.3v-26'
},
// `zoom`, `back`
title: clone(dataZoomLang.title)
};
var proto$6 = DataZoom.prototype;
/**
* @private
*/
var handlers$1 = {
zoom: function () {
var nextActive = !this._isZoomActive;
this.api.dispatchAction({
type: 'takeGlobalCursor',
key: 'dataZoomSelect',
dataZoomSelectActive: nextActive
});
},
back: function () {
this._dispatchZoomAction(pop(this.ecModel));
}
};
/**
* @private
*/
proto$6._onBrush = function (areas, opt) {
if (!opt.isEnd || !areas.length) {
return;
}
var snapshot = {};
var ecModel = this.ecModel;
push(ecModel, snapshot);
this._dispatchZoomAction(snapshot);
// Restrict range.
var minMaxSpan =
dataZoomModel.findRepresentativeAxisProxy(axisModel).getMinMaxSpan();
if (minMaxSpan.minValueSpan != null || minMaxSpan.maxValueSpan != null) {
minMax = sliderMove(
0, minMax.slice(), axis.scale.getExtent(), 0,
minMaxSpan.minValueSpan, minMaxSpan.maxValueSpan
);
}
/**
* @private
*/
proto$6._dispatchZoomAction = function (snapshot) {
var batch = [];
function retrieveAxisSetting(option) {
var setting = {};
// Compatible with previous setting: null => all axis, false => no axis.
each$1(['xAxisIndex', 'yAxisIndex'], function (name) {
setting[name] = option[name];
setting[name] == null && (setting[name] = 'all');
(setting[name] === false || setting[name] === 'none') && (setting[name] =
[]);
});
return setting;
}
view._isZoomActive = zoomActive;
view._brushController
.setPanels(brushTargetManager.makePanelOpts(api, function (targetInfo) {
return (targetInfo.xAxisDeclared && !targetInfo.yAxisDeclared)
? 'lineX'
: (!targetInfo.xAxisDeclared && targetInfo.yAxisDeclared)
? 'lineY'
: 'rect';
}))
.enableBrush(
zoomActive
? {
brushType: 'auto',
brushStyle: {
// FIXME user customized?
lineWidth: 0,
fill: 'rgba(0,0,0,0.2)'
}
}
: false
);
}
register$1('dataZoom', DataZoom);
function Restore(model) {
this.model = model;
}
Restore.defaultOption = {
show: true,
icon: 'M3.8,33.4 M47,18.9h9.8V8.7 M56.3,20.1
C52.1,9,40.5,0.6,26.8,2.1C12.6,3.7,1.6,16.2,2.1,30.6 M13,41.1H3.1v10.2
M3.7,39.9c4.2,11.1,15.8,19.5,29.5,18 c14.2-1.6,25.2-14.1,24.7-28.5',
title: restoreLang.title
};
api.dispatchAction({
type: 'restore',
from: this.uid
});
};
register$1('restore', Restore);
registerAction(
{type: 'restore', event: 'restore', update: 'prepareAndUpdate'},
function (payload, ecModel) {
ecModel.resetOption('recreate');
}
);
function createNode(tagName) {
return doCreateNode(tagName);
}
// From raphael
function initVML() {
if (vmlInited || !doc) {
return;
}
vmlInited = true;
// http://www.w3.org/TR/NOTE-VML
// TODO Use proxy like svg instead of overwrite brush methods
if (!env$1.canvasSupported) {
var Z = 21600;
var Z2 = Z / 2;
/***************************************************
* PATH
**************************************************/
width /= scale$$1[0] * Z;
height /= scale$$1[1] * Z;
var dimension = mathMax$8(width, height);
shift = 2 * 0 / dimension;
expansion = 2 * fill.r / dimension - shift;
}
if (length$$1 >= 2) {
var color1 = colorAndAlphaList[0][0];
var color2 = colorAndAlphaList[1][0];
var opacity1 = colorAndAlphaList[0][1] * style.opacity;
var opacity2 = colorAndAlphaList[1][1] * style.opacity;
el.type = gradientType;
el.method = 'none';
el.focus = '100%';
el.angle = angle;
el.color = color1;
el.color2 = color2;
el.colors = colors.join(',');
// When colors attribute is used, the meanings of opacity and
o:opacity2
// are reversed.
el.opacity = opacity2;
// FIXME g_o_:opacity ?
el.opacity2 = opacity1;
}
if (gradientType === 'radial') {
el.focusposition = focus.join(',');
}
}
else {
// FIXME Change from Gradient fill to color fill
setColorAndOpacity(el, fill, style.opacity);
}
}
};
xi = x3;
yi = y3;
break;
case A:
var x = 0;
var y = 0;
var sx = 1;
var sy = 1;
var angle = 0;
if (m) {
// Extract SRT from matrix
x = m[4];
y = m[5];
sx = sqrt(m[0] * m[0] + m[1] * m[1]);
sy = sqrt(m[2] * m[2] + m[3] * m[3]);
angle = Math.atan2(-m[1] / sy, m[0] / sx);
}
var cx = data[i++];
var cy = data[i++];
var rx = data[i++];
var ry = data[i++];
var startAngle = data[i++] + angle;
var endAngle = data[i++] + startAngle + angle;
// FIXME
// var psi = data[i++];
i++;
var clockwise = data[i++];
xi = x1;
yi = y1;
break;
case CMD$3.R:
var p0 = points$3[0];
var p1 = points$3[1];
// x0, y0
p0[0] = data[i++];
p0[1] = data[i++];
// x1, y1
p1[0] = p0[0] + data[i++];
p1[1] = p0[1] + data[i++];
if (m) {
applyTransform(p0, p0, m);
applyTransform(p1, p1, m);
}
if (nPoint > 0) {
str.push(cmdStr);
for (var k = 0; k < nPoint; k++) {
var p = points$3[k];
return str.join('');
};
this._vmlEl = vmlEl;
}
var m = this.transform;
var needTransform = m != null;
var strokeEl = vmlEl.getElementsByTagName('stroke')[0];
if (strokeEl) {
var lineWidth = style.lineWidth;
// Get the line scale.
// Determinant of this.m_ means how much the area is enlarged by the
// transformation. So its square root can be used as a scale factor
// for width.
if (needTransform && !style.strokeNoScale) {
var det = m[0] * m[3] - m[1] * m[2];
lineWidth *= sqrt(abs$1(det));
}
strokeEl.weight = lineWidth + 'px';
}
// Append to root
append(vmlRoot, vmlEl);
// Text
if (style.text != null) {
this.drawRectText(vmlRoot, this.getBoundingRect());
}
else {
this.removeRectText(vmlRoot);
}
};
/***************************************************
* IMAGE
**************************************************/
var isImage = function (img) {
// FIXME img instanceof Image 如果 img 是一个字符串的时候,IE8 下会报错
return (typeof img === 'object') && img.tagName &&
img.tagName.toUpperCase() === 'IMG';
// return img instanceof Image;
};
if (isImage(image)) {
var src = image.src;
if (src === this._imageSrc) {
ow = this._imageWidth;
oh = this._imageHeight;
}
else {
var imageRuntimeStyle = image.runtimeStyle;
var oldRuntimeWidth = imageRuntimeStyle.width;
var oldRuntimeHeight = imageRuntimeStyle.height;
imageRuntimeStyle.width = 'auto';
imageRuntimeStyle.height = 'auto';
var x = style.x || 0;
var y = style.y || 0;
var dw = style.width;
var dh = style.height;
var sw = style.sWidth;
var sh = style.sHeight;
var sx = style.sx || 0;
var sy = style.sy || 0;
this._vmlEl = vmlEl;
}
}
else {
if (m) {
x = x * scaleX + m[4];
y = y * scaleY + m[5];
}
vmlElStyle.filter = '';
vmlElStyle.left = round$3(x) + 'px';
vmlElStyle.top = round$3(y) + 'px';
}
if (!imageEl) {
imageEl = doc.createElement('div');
this._imageEl = imageEl;
}
var imageELStyle = imageEl.style;
if (hasCrop) {
// Needs know image original width and height
if (! (ow && oh)) {
var tmpImage = new Image();
var self = this;
tmpImage.onload = function () {
tmpImage.onload = null;
ow = tmpImage.width;
oh = tmpImage.height;
// Adjust image width and height to fit the ratio
destinationSize / sourceSize
imageELStyle.width = round$3(scaleX * ow * dw / sw) + 'px';
imageELStyle.height = round$3(scaleY * oh * dh / sh) + 'px';
if (! cropEl) {
cropEl = doc.createElement('div');
cropEl.style.overflow = 'hidden';
this._cropEl = cropEl;
}
var cropElStyle = cropEl.style;
cropElStyle.width = round$3((dw + sx * dw / sw) * scaleX);
cropElStyle.height = round$3((dh + sy * dh / sh) * scaleY);
cropElStyle.filter = imageTransformPrefix + '.Matrix(Dx='
+ (-sx * dw / sw * scaleX) + ',Dy=' + (-sy * dh / sh * scaleY)
+ ')';
if (! cropEl.parentNode) {
vmlEl.appendChild(cropEl);
}
if (imageEl.parentNode != cropEl) {
cropEl.appendChild(imageEl);
}
}
else {
imageELStyle.width = round$3(scaleX * dw) + 'px';
imageELStyle.height = round$3(scaleY * dh) + 'px';
vmlEl.appendChild(imageEl);
imageELStyle.filter = filterStr;
vmlEl.style.zIndex = getZIndex(this.zlevel, this.z, this.z2);
// Append to root
append(vmlRoot, vmlEl);
// Text
if (style.text != null) {
this.drawRectText(vmlRoot, this.getBoundingRect());
}
};
this._vmlEl = null;
this._cropEl = null;
this._imageEl = null;
this.removeRectText(vmlRoot);
};
/***************************************************
* TEXT
**************************************************/
fontStyle = {
style: style.fontStyle || DEFAULT_STYLE_NORMAL,
variant: style.fontVariant || DEFAULT_STYLE_NORMAL,
weight: style.fontWeight || DEFAULT_STYLE_NORMAL,
size: parseFloat(style.fontSize || 12) | 0,
family: fontFamily || 'Microsoft YaHei'
};
fontStyleCache[fontString] = fontStyle;
fontStyleCacheCount++;
}
return fontStyle;
};
var textMeasureEl;
// Overwrite measure text method
$override$1('measureText', function (text, textFont) {
var doc$$1 = doc;
if (!textMeasureEl) {
textMeasureEl = doc$$1.createElement('div');
textMeasureEl.style.cssText = 'position:absolute;top:-20000px;left:0;'
+ 'padding:0;margin:0;border:none;white-space:pre;';
doc.body.appendChild(textMeasureEl);
}
try {
textMeasureEl.style.font = textFont;
} catch (ex) {
// Ignore failures to set to invalid font.
}
textMeasureEl.innerHTML = '';
// Don't use innerHTML or innerText because they allow markup/whitespace.
textMeasureEl.appendChild(doc$$1.createTextNode(text));
return {
width: textMeasureEl.offsetWidth
};
});
var x;
var y;
var align = style.textAlign;
var verticalAlign = style.textVerticalAlign;
if (!fromTextEl) {
var textPosition = style.textPosition;
var distance$$1 = style.textDistance;
// Text position represented by coord
if (textPosition instanceof Array) {
x = rect.x + parsePercent$3(textPosition[0], rect.width);
y = rect.y + parsePercent$3(textPosition[1], rect.height);
// switch (align) {
// case 'left':
// break;
// case 'center':
// x -= textRect.width / 2;
// break;
// case 'right':
// x -= textRect.width;
// break;
// case 'end':
// align = elementStyle.direction == 'ltr' ? 'right' : 'left';
// break;
// case 'start':
// align = elementStyle.direction == 'rtl' ? 'right' : 'left';
// break;
// default:
// align = 'left';
// }
initRootElStyle(textVmlEl);
pathEl.textpathok = true;
textPathEl.on = true;
append(textVmlEl, skewEl);
append(textVmlEl, pathEl);
append(textVmlEl, textPathEl);
this._textVmlEl = textVmlEl;
}
else {
// 这里是在前面 appendChild 保证顺序的前提下
skewEl = textVmlEl.firstChild;
pathEl = skewEl.nextSibling;
textPathEl = pathEl.nextSibling;
}
skewEl.on = true;
// Text position
skewEl.offset = (round$3(coords[0]) || 0) + ',' + (round$3(coords[1])
|| 0);
// Left top point as origin
skewEl.origin = '0 0';
textVmlElStyle.left = '0px';
textVmlElStyle.top = '0px';
}
else {
skewEl.on = false;
textVmlElStyle.left = round$3(x) + 'px';
textVmlElStyle.top = round$3(y) + 'px';
}
textPathEl.string = encodeHtmlAttribute(text);
// TODO
try {
textPathEl.style.font = font;
}
// Error font format
catch (e) {}
updateFillAndStroke(textVmlEl, 'fill', {
fill: style.textFill,
opacity: style.opacity
}, this);
updateFillAndStroke(textVmlEl, 'stroke', {
stroke: style.textStroke,
opacity: style.opacity,
lineDash: style.lineDash
}, this);
// Attached to root
append(vmlRoot, textVmlEl);
};
/**
* VML Painter.
*
* @module zrender/vml/Painter
*/
function parseInt10$1(val) {
return parseInt(val, 10);
}
/**
* @alias module:zrender/vml/Painter
*/
function VMLPainter(root, storage) {
initVML();
this.root = root;
this.storage = storage;
vmlViewport.style.cssText = 'display:inline-
block;overflow:hidden;position:relative;width:300px;height:150px;';
vmlRoot.style.cssText = 'position:absolute;left:0;top:0;';
root.appendChild(vmlViewport);
this._vmlRoot = vmlRoot;
this._vmlViewport = vmlViewport;
this.resize();
// Modify storage
var oldDelFromStorage = storage.delFromStorage;
var oldAddToStorage = storage.addToStorage;
storage.delFromStorage = function (el) {
oldDelFromStorage.call(storage, el);
if (el) {
el.onRemove && el.onRemove(vmlRoot);
}
};
oldAddToStorage.call(storage, el);
};
this._firstPaint = true;
}
VMLPainter.prototype = {
constructor: VMLPainter,
getType: function () {
return 'vml';
},
/**
* @return {HTMLDivElement}
*/
getViewportRoot: function () {
return this._vmlViewport;
},
getViewportRootOffset: function () {
var viewportRoot = this.getViewportRoot();
if (viewportRoot) {
return {
offsetLeft: viewportRoot.offsetLeft || 0,
offsetTop: viewportRoot.offsetTop || 0
};
}
},
/**
* 刷新
*/
refresh: function () {
this._paintList(list);
},
if (this._firstPaint) {
// Detached from document at first time
// to avoid page refreshing too many times
dispose: function () {
this.root.innerHTML = '';
this._vmlRoot =
this._vmlViewport =
this.storage = null;
},
getWidth: function () {
return this._width;
},
getHeight: function () {
return this._height;
},
clear: function () {
if (this._vmlViewport) {
this.root.removeChild(this._vmlViewport);
}
},
_getWidth: function () {
var root = this.root;
var stl = root.currentStyle;
_getHeight: function () {
var root = this.root;
var stl = root.currentStyle;
// Unsupported methods
each$1([
'getLayer', 'insertLayer', 'eachLayer', 'eachBuiltinLayer', 'eachOtherLayer',
'getLayers',
'modLayer', 'delLayer', 'clearLayer', 'toDataURL', 'pathToImage'
], function (name) {
VMLPainter.prototype[name] = createMethodNotSupport(name);
});
registerPainter('vml', VMLPainter);
function createElement(name) {
return document.createElementNS(svgURI, name);
}
// TODO
// 1. shadow
// 2. Image: sx, sy, sw, sh
function round4(val) {
return mathRound(val * 1e4) / 1e4;
}
function isAroundZero$1(val) {
return val < EPSILON$4 && val > -EPSILON$4;
}
function setTransform(svgEl, m) {
if (m) {
attr(svgEl, 'transform', 'matrix(' + arrayJoin.call(m, ',') + ')');
}
}
/**
* FIXME:
* This is a temporary fix for Chrome's clipping bug
* that happens when a clip-path is referring another one.
* This fix should be used before Chrome's bug is fixed.
* For an element that has clip-path, and fill is none,
* set it to be "rgba(0, 0, 0, 0.002)" will hide the element.
* Otherwise, it will show black fill color.
* 0.002 is used because this won't work for alpha values smaller
* than 0.002.
*
* See
* https://bugs.chromium.org/p/chromium/issues/detail?id=659790
* for more information.
*/
if (svgEl.getAttribute('clip-path') !== 'none' && fill === NONE) {
fill = 'rgba(0, 0, 0, 0.002)';
}
if (pathHasStroke(style, isText)) {
var stroke = isText ? style.textStroke : style.stroke;
stroke = stroke === 'transparent' ? NONE : stroke;
attr(svgEl, 'stroke', stroke);
var strokeWidth = isText
? style.textStrokeWidth
: style.lineWidth;
var strokeScale = !isText && style.strokeNoScale
? style.host.getLineScale()
: 1;
attr(svgEl, 'stroke-width', strokeWidth / strokeScale);
// stroke then fill for text; fill then stroke for others
attr(svgEl, 'paint-order', isText ? 'stroke' : 'fill');
attr(svgEl, 'stroke-opacity', style.opacity);
var lineDash = style.lineDash;
if (lineDash) {
attr(svgEl, 'stroke-dasharray', style.lineDash.join(','));
attr(svgEl, 'stroke-dashoffset', mathRound(style.lineDashOffset || 0));
}
else {
attr(svgEl, 'stroke-dasharray', '');
}
// PENDING
style.lineCap && attr(svgEl, 'stroke-linecap', style.lineCap);
style.lineJoin && attr(svgEl, 'stroke-linejoin', style.lineJoin);
style.miterLimit && attr(svgEl, 'stroke-miterlimit', style.miterLimit);
}
else {
attr(svgEl, 'stroke', NONE);
}
}
/***************************************************
* PATH
**************************************************/
function pathDataToString$1(path) {
var str = [];
var data = path.data;
var dataLength = path.len();
for (var i = 0; i < dataLength;) {
var cmd = data[i++];
var cmdStr = '';
var nData = 0;
switch (cmd) {
case CMD$4.M:
cmdStr = 'M';
nData = 2;
break;
case CMD$4.L:
cmdStr = 'L';
nData = 2;
break;
case CMD$4.Q:
cmdStr = 'Q';
nData = 4;
break;
case CMD$4.C:
cmdStr = 'C';
nData = 6;
break;
case CMD$4.A:
var cx = data[i++];
var cy = data[i++];
var rx = data[i++];
var ry = data[i++];
var theta = data[i++];
var dTheta = data[i++];
var psi = data[i++];
var clockwise = data[i++];
// It will not draw if start point and end point are exactly the
same
// We need to shift the end point with a small value
// FIXME A better way to draw circle ?
if (isCircle) {
if (clockwise) {
dTheta = PI2$7 - 1e-4;
}
else {
dTheta = -PI2$7 + 1e-4;
}
large = true;
if (i === 9) {
// Move to (x0, y0) only when CMD.A comes at the
// first position of a shape.
// For instance, when drawing a ring, CMD.A comes
// after CMD.M, so it's unnecessary to move to
// (x0, y0).
str.push('M', x0, y0);
}
}
// FIXME Ellipse
str.push('A', round4(rx), round4(ry),
mathRound(psi * degree), +large, +clockwise, x, y);
break;
case CMD$4.Z:
cmdStr = 'Z';
break;
case CMD$4.R:
var x = round4(data[i++]);
var y = round4(data[i++]);
var w = round4(data[i++]);
var h = round4(data[i++]);
str.push(
'M', x, y,
'L', x + w, y,
'L', x + w, y + h,
'L', x, y + h,
'L', x, y
);
break;
}
cmdStr && str.push(cmdStr);
for (var j = 0; j < nData; j++) {
// PENDING With scale
str.push(round4(data[i++]));
}
}
return str.join(' ');
}
if (!el.path) {
el.createPathProxy();
}
var path = el.path;
if (el.__dirtyPath) {
path.beginPath();
el.buildPath(path, el.shape);
el.__dirtyPath = false;
bindStyle(svgEl, style);
setTransform(svgEl, el.transform);
if (style.text != null) {
svgTextDrawRectText(el, el.getBoundingRect());
}
};
/***************************************************
* IMAGE
**************************************************/
var svgImage = {};
svgImage.brush = function (el) {
var style = el.style;
var image = style.image;
if (image instanceof HTMLImageElement) {
var src = image.src;
image = src;
}
if (! image) {
return;
}
var x = style.x || 0;
var y = style.y || 0;
var dw = style.width;
var dh = style.height;
setTransform(svgEl, el.transform);
if (style.text != null) {
svgTextDrawRectText(el, el.getBoundingRect());
}
};
/***************************************************
* TEXT
**************************************************/
var svgText = {};
var tmpRect$3 = new BoundingRect();
var x;
var y;
var textPosition = style.textPosition;
var distance = style.textDistance;
var align = style.textAlign || 'left';
if (font) {
textSvgEl.style.font = font;
}
var dy = 0;
if (verticalAlign === 'baseline') {
dy = -textRect.height + lineHeight;
textPadding && (dy -= textPadding[2]);
}
else if (verticalAlign === 'middle') {
dy = (-textRect.height + lineHeight) / 2;
textPadding && (y += (textPadding[0] - textPadding[2]) / 2);
}
else {
textPadding && (dy += textPadding[0]);
}
el.__text = text;
el.__textFont = font;
}
else if (el.__tspanList.length) {
// Update span x and y
var len = el.__tspanList.length;
for (var i = 0; i < len; ++i) {
var tspan = el.__tspanList[i];
if (tspan) {
attr(tspan, 'x', x);
attr(tspan, 'y', y + i * lineHeight + dy);
}
}
}
};
function getVerticalAlignForSvg(verticalAlign) {
if (verticalAlign === 'middle') {
return 'middle';
}
else if (verticalAlign === 'bottom') {
return 'baseline';
}
else {
return 'hanging';
}
}
svgText.drawRectText = svgTextDrawRectText;
svgText.brush = function (el) {
var style = el.style;
if (style.text != null) {
// 强制设置 textPosition
style.textPosition = [0, 0];
svgTextDrawRectText(el, {
x: style.x || 0, y: style.y || 0,
width: 0, height: 0
}, el.getBoundingRect());
}
};
function Diff() {}
Diff.prototype = {
diff: function (oldArr, newArr, equals) {
if (!equals) {
equals = function (a, b) {
return a === b;
};
}
this.equals = equals;
oldArr = oldArr.slice();
newArr = newArr.slice();
// Allow subclasses to massage the input prior to running
var newLen = newArr.length;
var oldLen = oldArr.length;
var editLength = 1;
var maxEditLength = newLen + oldLen;
var bestPath = [{ newPos: -1, components: [] }];
// Seed editLength = 0, i.e. the content starts with the same values
var oldPos = this.extractCommon(bestPath[0], newArr, oldArr, 0);
if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) {
var indices = [];
for (var i = 0; i < newArr.length; i++) {
indices.push(i);
}
// Identity per the equality and tokenizer
return [{
indices: indices, count: newArr.length
}];
}
// Main worker method. checks all permutations of a given edit length for
acceptance.
function execEditLength() {
for (var diagonalPath = -1 * editLength; diagonalPath <= editLength;
diagonalPath += 2) {
var basePath;
var addPath = bestPath[diagonalPath - 1];
var removePath = bestPath[diagonalPath + 1];
var oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;
if (addPath) {
// No one else is going to attempt to use this value, clear it
bestPath[diagonalPath - 1] = undefined;
}
editLength++;
}
if (commonCount) {
basePath.components.push({count: commonCount});
}
basePath.newPos = newPos;
return oldPos;
},
tokenize: function (value) {
return value.slice();
},
join: function (value) {
return value.slice();
}
};
return components;
}
function clonePath(path) {
return { newPos: path.newPos, components: path.components.slice(0) };
}
/**
* @file Manages elements that can be defined in <defs> in SVG,
* e.g., gradients, clip path, etc.
* @author Zhang Wenli
*/
/**
* Manages elements that can be defined in <defs> in SVG,
* e.g., gradients, clip path, etc.
*
* @class
* @param {number} zrId zrender instance id
* @param {SVGElement} svgRoot root of SVG document
* @param {string|string[]} tagNames possible tag names
* @param {string} markLabel label name to make if the element
* is used
*/
function Definable(
zrId,
svgRoot,
tagNames,
markLabel,
domName
) {
this._zrId = zrId;
this._svgRoot = svgRoot;
this._tagNames = typeof tagNames === 'string' ? [tagNames] : tagNames;
this._markLabel = markLabel;
this._domName = domName || '_dom';
this.nextId = 0;
}
Definable.prototype.createElement = createElement;
/**
* Get the <defs> tag for svgRoot; optionally creates one if not exists.
*
* @param {boolean} isForceCreating if need to create when not exists
* @return {SVGDefsElement} SVG <defs> element, null if it doesn't
* exist and isForceCreating is false
*/
Definable.prototype.getDefs = function (isForceCreating) {
var svgRoot = this._svgRoot;
var defs = this._svgRoot.getElementsByTagName('defs');
if (defs.length === 0) {
// Not exist
if (isForceCreating) {
defs = svgRoot.insertBefore(
this.createElement('defs'), // Create new tag
svgRoot.firstChild // Insert in the front of svg
);
if (!defs.contains) {
// IE doesn't support contains method
defs.contains = function (el) {
var children = defs.children;
if (!children) {
return false;
}
for (var i = children.length - 1; i >= 0; --i) {
if (children[i] === el) {
return true;
}
}
return false;
};
}
return defs;
}
else {
return null;
}
}
else {
return defs[0];
}
};
/**
* Update DOM element if necessary.
*
* @param {Object|string} element style element. e.g., for gradient,
* it may be '#ccc' or {type: 'linear', ...}
* @param {Function|undefined} onUpdate update callback
*/
Definable.prototype.update = function (element, onUpdate) {
if (!element) {
return;
}
/**
* Add gradient dom to defs
*
* @param {SVGElement} dom DOM to be added to <defs>
*/
Definable.prototype.addDom = function (dom) {
var defs = this.getDefs(true);
defs.appendChild(dom);
};
/**
* Remove DOM of a given element.
*
* @param {SVGElement} element element to remove dom
*/
Definable.prototype.removeDom = function (element) {
var defs = this.getDefs(false);
if (defs && element[this._domName]) {
defs.removeChild(element[this._domName]);
element[this._domName] = null;
}
};
/**
* Get DOMs of this element.
*
* @return {HTMLDomElement} doms of this defineable elements in <defs>
*/
Definable.prototype.getDoms = function () {
var defs = this.getDefs(false);
if (!defs) {
// No dom when defs is not defined
return [];
}
return doms;
};
/**
* Mark DOMs to be unused before painting, and clear unused ones at the end
* of the painting.
*/
Definable.prototype.markAllUnused = function () {
var doms = this.getDoms();
var that = this;
each$1(doms, function (dom) {
dom[that._markLabel] = MARK_UNUSED;
});
};
/**
* Mark a single DOM to be used.
*
* @param {SVGElement} dom DOM to mark
*/
Definable.prototype.markUsed = function (dom) {
if (dom) {
dom[this._markLabel] = MARK_USED;
}
};
/**
* Remove unused DOMs defined in <defs>
*/
Definable.prototype.removeUnused = function () {
var defs = this.getDefs(false);
if (!defs) {
// Nothing to remove
return;
}
/**
* Get SVG proxy.
*
* @param {Displayable} displayable displayable element
* @return {Path|Image|Text} svg proxy of given element
*/
Definable.prototype.getSvgProxy = function (displayable) {
if (displayable instanceof Path) {
return svgPath;
}
else if (displayable instanceof ZImage) {
return svgImage;
}
else if (displayable instanceof Text) {
return svgText;
}
else {
return svgPath;
}
};
/**
* Get text SVG element.
*
* @param {Displayable} displayable displayable element
* @return {SVGElement} SVG element of text
*/
Definable.prototype.getTextSvgElement = function (displayable) {
return displayable.__textSvgEl;
};
/**
* Get SVG element.
*
* @param {Displayable} displayable displayable element
* @return {SVGElement} SVG element
*/
Definable.prototype.getSvgElement = function (displayable) {
return displayable.__svgEl;
};
/**
* @file Manages SVG gradient elements.
* @author Zhang Wenli
*/
/**
* Manages SVG gradient elements.
*
* @class
* @extends Definable
* @param {number} zrId zrender instance id
* @param {SVGElement} svgRoot root of SVG document
*/
function GradientManager(zrId, svgRoot) {
Definable.call(
this,
zrId,
svgRoot,
['linearGradient', 'radialGradient'],
'__gradient_in_use__'
);
}
inherits(GradientManager, Definable);
/**
* Create new gradient DOM for fill or stroke if not exist,
* but will not update gradient if exists.
*
* @param {SvgElement} svgElement SVG element to paint
* @param {Displayable} displayable zrender displayable element
*/
GradientManager.prototype.addWithoutUpdate = function (
svgElement,
displayable
) {
if (displayable && displayable.style) {
var that = this;
each$1(['fill', 'stroke'], function (fillOrStroke) {
if (displayable.style[fillOrStroke]
&& (displayable.style[fillOrStroke].type === 'linear'
|| displayable.style[fillOrStroke].type === 'radial')
) {
var gradient = displayable.style[fillOrStroke];
var defs = that.getDefs(true);
that.markUsed(displayable);
var id = dom.getAttribute('id');
svgElement.setAttribute(fillOrStroke, 'url(#' + id + ')');
}
});
}
};
/**
* Add a new gradient tag in <defs>
*
* @param {Gradient} gradient zr gradient instance
* @return {SVGLinearGradientElement | SVGRadialGradientElement}
* created DOM
*/
GradientManager.prototype.add = function (gradient) {
var dom;
if (gradient.type === 'linear') {
dom = this.createElement('linearGradient');
}
else if (gradient.type === 'radial') {
dom = this.createElement('radialGradient');
}
else {
zrLog('Illegal gradient type.');
return null;
}
this.updateDom(gradient, dom);
this.addDom(dom);
return dom;
};
/**
* Update gradient.
*
* @param {Gradient} gradient zr gradient instance
*/
GradientManager.prototype.update = function (gradient) {
var that = this;
Definable.prototype.update.call(this, gradient, function () {
var type = gradient.type;
var tagName = gradient._dom.tagName;
if (type === 'linear' && tagName === 'linearGradient'
|| type === 'radial' && tagName === 'radialGradient'
) {
// Gradient type is not changed, update gradient
that.updateDom(gradient, gradient._dom);
}
else {
// Remove and re-create if type is changed
that.removeDom(gradient);
that.add(gradient);
}
});
};
/**
* Update gradient dom
*
* @param {Gradient} gradient zr gradient instance
* @param {SVGLinearGradientElement | SVGRadialGradientElement} dom
* DOM to update
*/
GradientManager.prototype.updateDom = function (gradient, dom) {
if (gradient.type === 'linear') {
dom.setAttribute('x1', gradient.x);
dom.setAttribute('y1', gradient.y);
dom.setAttribute('x2', gradient.x2);
dom.setAttribute('y2', gradient.y2);
}
else if (gradient.type === 'radial') {
dom.setAttribute('cx', gradient.x);
dom.setAttribute('cy', gradient.y);
dom.setAttribute('r', gradient.r);
}
else {
zrLog('Illegal gradient type.');
return;
}
if (gradient.global) {
// x1, x2, y1, y2 in range of 0 to canvas width or height
dom.setAttribute('gradientUnits', 'userSpaceOnUse');
}
else {
// x1, x2, y1, y2 in range of 0 to 1
dom.setAttribute('gradientUnits', 'objectBoundingBox');
}
/**
* Mark a single gradient to be used
*
* @param {Displayable} displayable displayable element
*/
GradientManager.prototype.markUsed = function (displayable) {
if (displayable.style) {
var gradient = displayable.style.fill;
if (gradient && gradient._dom) {
Definable.prototype.markUsed.call(this, gradient._dom);
}
gradient = displayable.style.stroke;
if (gradient && gradient._dom) {
Definable.prototype.markUsed.call(this, gradient._dom);
}
}
};
/**
* @file Manages SVG clipPath elements.
* @author Zhang Wenli
*/
/**
* Manages SVG clipPath elements.
*
* @class
* @extends Definable
* @param {number} zrId zrender instance id
* @param {SVGElement} svgRoot root of SVG document
*/
function ClippathManager(zrId, svgRoot) {
Definable.call(this, zrId, svgRoot, 'clipPath', '__clippath_in_use__');
}
inherits(ClippathManager, Definable);
/**
* Update clipPath.
*
* @param {Displayable} displayable displayable element
*/
ClippathManager.prototype.update = function (displayable) {
var svgEl = this.getSvgElement(displayable);
if (svgEl) {
this.updateDom(svgEl, displayable.__clipPaths, false);
}
this.markUsed(displayable);
};
/**
* Create an SVGElement of displayable and create a <clipPath> of its
* clipPath
*
* @param {Displayable} parentEl parent element
* @param {ClipPath[]} clipPaths clipPaths of parent element
* @param {boolean} isText if parent element is Text
*/
ClippathManager.prototype.updateDom = function (
parentEl,
clipPaths,
isText
) {
if (clipPaths && clipPaths.length > 0) {
// Has clipPath, create <clipPath> with the first clipPath
var defs = this.getDefs(true);
var clipPath = clipPaths[0];
var clipPathEl;
var id;
if (clipPath[dom]) {
// Use a dom that is already in <defs>
id = clipPath[dom].getAttribute('id');
clipPathEl = clipPath[dom];
clipPath[dom] = clipPathEl;
}
clipPathEl.innerHTML = '';
/**
* Use `cloneNode()` here to appendChild to multiple parents,
* which may happend when Text and other shapes are using the same
* clipPath. Since Text will create an extra clipPath DOM due to
* different transform rules.
*/
clipPathEl.appendChild(pathEl.cloneNode());
if (clipPaths.length > 1) {
// Make the other clipPaths recursively
this.updateDom(clipPathEl, clipPaths.slice(1), isText);
}
}
else {
// No clipPath
if (parentEl) {
parentEl.setAttribute('clip-path', 'none');
}
}
};
/**
* Mark a single clipPath to be used
*
* @param {Displayable} displayable displayable element
*/
ClippathManager.prototype.markUsed = function (displayable) {
var that = this;
if (displayable.__clipPaths && displayable.__clipPaths.length > 0) {
each$1(displayable.__clipPaths, function (clipPath) {
if (clipPath._dom) {
Definable.prototype.markUsed.call(that, clipPath._dom);
}
if (clipPath._textDom) {
Definable.prototype.markUsed.call(that, clipPath._textDom);
}
});
}
};
/**
* @file Manages SVG shadow elements.
* @author Zhang Wenli
*/
/**
* Manages SVG shadow elements.
*
* @class
* @extends Definable
* @param {number} zrId zrender instance id
* @param {SVGElement} svgRoot root of SVG document
*/
function ShadowManager(zrId, svgRoot) {
Definable.call(
this,
zrId,
svgRoot,
['filter'],
'__filter_in_use__',
'_shadowDom'
);
}
inherits(ShadowManager, Definable);
/**
* Create new shadow DOM for fill or stroke if not exist,
* but will not update shadow if exists.
*
* @param {SvgElement} svgElement SVG element to paint
* @param {Displayable} displayable zrender displayable element
*/
ShadowManager.prototype.addWithoutUpdate = function (
svgElement,
displayable
) {
if (displayable && hasShadow(displayable.style)) {
var style = displayable.style;
this.markUsed(displayable);
var id = dom.getAttribute('id');
svgElement.style.filter = 'url(#' + id + ')';
}
};
/**
* Add a new shadow tag in <defs>
*
* @param {Displayable} displayable zrender displayable element
* @return {SVGFilterElement} created DOM
*/
ShadowManager.prototype.add = function (displayable) {
var dom = this.createElement('filter');
var style = displayable.style;
this.updateDom(displayable, dom);
this.addDom(dom);
return dom;
};
/**
* Update shadow.
*
* @param {Displayable} displayable zrender displayable element
*/
ShadowManager.prototype.update = function (svgElement, displayable) {
var style = displayable.style;
if (hasShadow(style)) {
var that = this;
Definable.prototype.update.call(this, displayable, function (style) {
that.updateDom(displayable, style._shadowDom);
});
}
else {
// Remove shadow
this.remove(svgElement, style);
}
};
/**
* Remove DOM and clear parent filter
*/
ShadowManager.prototype.remove = function (svgElement, style) {
if (style._shadowDomId != null) {
this.removeDom(style);
svgElement.style.filter = '';
}
};
/**
* Update shadow dom
*
* @param {Displayable} displayable zrender displayable element
* @param {SVGFilterElement} dom DOM to update
*/
ShadowManager.prototype.updateDom = function (displayable, dom) {
var domChild = dom.getElementsByTagName('feDropShadow');
if (domChild.length === 0) {
domChild = this.createElement('feDropShadow');
}
else {
domChild = domChild[0];
}
dom.appendChild(domChild);
/**
* Mark a single shadow to be used
*
* @param {Displayable} displayable displayable element
*/
ShadowManager.prototype.markUsed = function (displayable) {
var style = displayable.style;
if (style && style._shadowDom) {
Definable.prototype.markUsed.call(this, style._shadowDom);
}
};
function hasShadow(style) {
// TODO: textBoxShadowBlur is not supported yet
return style
&& (style.shadowBlur || style.shadowOffsetX || style.shadowOffsetY
|| style.textShadowBlur || style.textShadowOffsetX
|| style.textShadowOffsetY);
}
/**
* SVG Painter
* @module zrender/svg/Painter
*/
function parseInt10$2(val) {
return parseInt(val, 10);
}
function getSvgProxy(el) {
if (el instanceof Path) {
return svgPath;
}
else if (el instanceof ZImage) {
return svgImage;
}
else if (el instanceof Text) {
return svgText;
}
else {
return svgPath;
}
}
function getTextSvgElement(displayable) {
return displayable.__textSvgEl;
}
function getSvgElement(displayable) {
return displayable.__svgEl;
}
/**
* @alias module:zrender/svg/Painter
* @constructor
* @param {HTMLElement} root 绘图容器
* @param {module:zrender/Storage} storage
* @param {Object} opts
*/
var SVGPainter = function (root, storage, opts, zrId) {
this.root = root;
this.storage = storage;
this._opts = opts = extend({}, opts || {});
this._svgRoot = svgRoot;
this._viewport = viewport;
root.appendChild(viewport);
viewport.appendChild(svgRoot);
this.resize(opts.width, opts.height);
this._visibleList = [];
};
SVGPainter.prototype = {
constructor: SVGPainter,
getType: function () {
return 'svg';
},
getViewportRoot: function () {
return this._viewport;
},
getViewportRootOffset: function () {
var viewportRoot = this.getViewportRoot();
if (viewportRoot) {
return {
offsetLeft: viewportRoot.offsetLeft || 0,
offsetTop: viewportRoot.offsetTop || 0
};
}
},
refresh: function () {
this._paintList(list);
},
// Update clipPath
this.clipPathManager.update(displayable);
this.shadowManager
.update(svgElement, displayable);
}
displayable.__dirty = false;
}
newVisibleList.push(displayable);
}
}
this.gradientManager
.addWithoutUpdate(svgElement, displayable);
this.shadowManager
.addWithoutUpdate(prevSvgElement, displayable);
this.clipPathManager.markUsed(displayable);
}
}
else if (!item.removed) {
for (var k = 0; k < item.count; k++) {
var displayable = newVisibleList[item.indices[k]];
prevSvgElement
= svgElement
= getTextSvgElement(displayable)
|| getSvgElement(displayable)
|| prevSvgElement;
this.gradientManager.markUsed(displayable);
this.gradientManager
.addWithoutUpdate(svgElement, displayable);
this.shadowManager.markUsed(displayable);
this.shadowManager
.addWithoutUpdate(svgElement, displayable);
this.clipPathManager.markUsed(displayable);
}
}
}
this.gradientManager.removeUnused();
this.clipPathManager.removeUnused();
this.shadowManager.removeUnused();
this._visibleList = newVisibleList;
},
width = this._getSize(0);
height = this._getSize(1);
viewport.style.display = '';
/**
* 获取绘图区域宽度
*/
getWidth: function () {
return this._width;
},
/**
* 获取绘图区域高度
*/
getHeight: function () {
return this._height;
},
return (
(root[cwh] || parseInt10$2(stl[wh]) || parseInt10$2(root.style[wh]))
- (parseInt10$2(stl[plt]) || 0)
- (parseInt10$2(stl[prb]) || 0)
) | 0;
},
dispose: function () {
this.root.innerHTML = '';
this._svgRoot
= this._viewport
= this.storage
= null;
},
clear: function () {
if (this._viewport) {
this.root.removeChild(this._viewport);
}
},
pathToDataUrl: function () {
this.refresh();
var html = this._svgRoot.outerHTML;
return 'data:image/svg+xml;charset=UTF-8,' + html;
}
};
// Unsuppoted methods
each$1([
'getLayer', 'insertLayer', 'eachLayer', 'eachBuiltinLayer',
'eachOtherLayer', 'getLayers', 'modLayer', 'delLayer', 'clearLayer',
'toDataURL', 'pathToImage'
], function (name) {
SVGPainter.prototype[name] = createMethodNotSupport$1(name);
});
registerPainter('svg', SVGPainter);
// Import all charts and components
exports.version = version;
exports.dependencies = dependencies;
exports.PRIORITY = PRIORITY;
exports.init = init;
exports.connect = connect;
exports.disConnect = disConnect;
exports.disconnect = disconnect;
exports.dispose = dispose;
exports.getInstanceByDom = getInstanceByDom;
exports.getInstanceById = getInstanceById;
exports.registerTheme = registerTheme;
exports.registerPreprocessor = registerPreprocessor;
exports.registerProcessor = registerProcessor;
exports.registerPostUpdate = registerPostUpdate;
exports.registerAction = registerAction;
exports.registerCoordinateSystem = registerCoordinateSystem;
exports.getCoordinateSystemDimensions = getCoordinateSystemDimensions;
exports.registerLayout = registerLayout;
exports.registerVisual = registerVisual;
exports.registerLoading = registerLoading;
exports.extendComponentModel = extendComponentModel;
exports.extendComponentView = extendComponentView;
exports.extendSeriesModel = extendSeriesModel;
exports.extendChartView = extendChartView;
exports.setCanvasCreator = setCanvasCreator;
exports.registerMap = registerMap;
exports.getMap = getMap;
exports.dataTool = dataTool;
exports.zrender = zrender;
exports.graphic = graphic;
exports.number = number;
exports.format = format;
exports.throttle = throttle;
exports.helper = helper;
exports.matrix = matrix;
exports.vector = vector;
exports.color = color;
exports.parseGeoJSON = parseGeoJson$1;
exports.parseGeoJson = parseGeoJson;
exports.util = ecUtil;
exports.List = List;
exports.Model = Model;
exports.Axis = Axis;
exports.env = env$1;
})));
//# sourceMappingURL=echarts.js.map