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