UNPKG

echarts

Version:

A powerful charting and visualization library for browser

1,871 lines (1,674 loc) 1.99 MB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (factory((global.echarts = {}))); }(this, (function (exports) { 'use strict'; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ // (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__; } if (typeof dev === 'undefined') { dev = true; } var __DEV__ = dev; /** * zrender: 生成唯一id * * @author errorrik (errorrik@gmail.com) */ var idStart = 0x0907; var guid = function () { return idStart++; }; /** * echarts设备环境识别 * * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 * @author firede[firede@firede.us] * @desc thanks zepto. */ /* global wx */ var env = {}; if (typeof wx === 'object' && typeof wx.getSystemInfoSync === 'function') { // In Weixin Application env = { browser: {}, os: {}, node: false, wxa: true, // Weixin Application canvasSupported: true, svgSupported: false, touchEventsSupported: true, domSupported: false }; } else if (typeof document === 'undefined' && typeof self !== 'undefined') { // In worker env = { browser: {}, os: {}, node: false, worker: true, canvasSupported: true, domSupported: false }; } else if (typeof navigator === 'undefined') { // In node env = { browser: {}, os: {}, node: true, worker: false, // Assume canvas is supported canvasSupported: true, svgSupported: true, domSupported: false }; } else { env = detect(navigator.userAgent); } var env$1 = env; // 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+ var weChat = (/micromessenger/i).test(ua); // Todo: clean this up with a better OS/browser seperation: // - discern (more) between multiple browsers on android // - decide if kindle fire in silk mode is android or not // - Firefox on Android doesn't specify the Android version // - possibly devide in os, device and browser hashes // if (browser.webkit = !!webkit) browser.version = webkit[1]; // if (android) os.android = true, os.version = android[2]; // if (iphone && !ipod) os.ios = os.iphone = true, os.version = iphone[2].replace(/_/g, '.'); // if (ipad) os.ios = os.ipad = true, os.version = ipad[2].replace(/_/g, '.'); // if (ipod) os.ios = os.ipod = true, os.version = ipod[3] ? ipod[3].replace(/_/g, '.') : null; // if (webos) os.webos = true, os.version = webos[2]; // if (touchpad) os.touchpad = true; // if (blackberry) os.blackberry = true, os.version = blackberry[2]; // if (bb10) os.bb10 = true, os.version = bb10[2]; // if (rimtabletos) os.rimtabletos = true, os.version = rimtabletos[2]; // if (playbook) browser.playbook = true; // if (kindle) os.kindle = true, os.version = kindle[1]; // if (silk) browser.silk = true, browser.version = silk[1]; // if (!silk && os.android && ua.match(/Kindle Fire/)) browser.silk = true; // if (chrome) browser.chrome = true, browser.version = chrome[1]; if (firefox) { browser.firefox = true; browser.version = firefox[1]; } // if (safari && (ua.match(/Safari/) || !!os.ios)) browser.safari = true; // if (webview) browser.webview = true; if (ie) { browser.ie = true; browser.version = ie[1]; } if (edge) { browser.edge = true; browser.version = edge[1]; } // It is difficult to detect WeChat in Win Phone precisely, because ua can // not be set on win phone. So we do not consider Win Phone. if (weChat) { browser.weChat = true; } // os.tablet = !!(ipad || playbook || (android && !ua.match(/Mobile/)) || // (firefox && ua.match(/Tablet/)) || (ie && !ua.match(/Phone/) && ua.match(/Touch/))); // os.phone = !!(!os.tablet && !os.ipod && (android || iphone || webos || // (chrome && ua.match(/Android/)) || (chrome && ua.match(/CriOS\/([\d.]+)/)) || // (firefox && ua.match(/Mobile/)) || (ie && ua.match(/Touch/)))); 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: // (1) 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. // For example, in iOS 13 Mobile Chromium 78, if the touching behavior starts page // scroll, the `pointermove` event can not be fired any more. That will break some // features like "pan horizontally to move something and pan vertically to page scroll". // The horizontal pan probably be interrupted by the casually triggered page scroll. // (2) 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) 'onpointerdown' in window && (browser.edge || (browser.ie && browser.version >= 11)), // passiveSupported: detectPassiveSupport() domSupported: typeof document !== 'undefined' }; } // 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 */ // 用于处理merge时无法遍历Date等对象的问题 var BUILTIN_OBJECT = { '[object Function]': 1, '[object RegExp]': 1, '[object Date]': 1, '[object Error]': 1, '[object CanvasGradient]': 1, '[object CanvasPattern]': 1, // For node-canvas '[object Image]': 1, '[object Canvas]': 1 }; 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 }; var objToString = Object.prototype.toString; var arrayProto = Array.prototype; var nativeForEach = arrayProto.forEach; var nativeFilter = arrayProto.filter; var nativeSlice = arrayProto.slice; var nativeMap = arrayProto.map; var nativeReduce = arrayProto.reduce; // Avoid assign to an exported variable, for transforming to cjs. var methods = {}; function $override(name, fn) { // Clear ctx instance for different environment if (name === 'createCanvas') { _ctx = null; } 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; } var result = source; var typeStr = objToString.call(source); if (typeStr === '[object Array]') { if (!isPrimitive(source)) { result = []; for (var i = 0, len = source.length; i < len; i++) { result[i] = clone(source[i]); } } } else if (TYPED_ARRAY[typeStr]) { if (!isPrimitive(source)) { var Ctor = source.constructor; if (source.constructor.from) { result = Ctor.from(source); } else { result = new Ctor(source.length); for (var i = 0, len = source.length; i < len; i++) { result[i] = clone(source[i]); } } } } else if (!BUILTIN_OBJECT[typeStr] && !isPrimitive(source) && !isDom(source)) { result = {}; for (var key in source) { if (source.hasOwnProperty(key)) { result[key] = clone(source[key]); } } } 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; } for (var key in source) { if (source.hasOwnProperty(key)) { var targetProp = target[key]; var sourceProp = source[key]; 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; } var createCanvas = function () { return methods.createCanvas(); }; 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) { if (clazzPrototype.hasOwnProperty(prop)) { 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; defaults(target, source, overlay); } /** * 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) { /* eslint-disable-next-line no-self-compare */ 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]; } } } function retrieve2(value0, value1) { return value0 != null ? value0 : value1; } function retrieve3(value0, value1, value2) { return value0 != null ? value0 : value1 != null ? value1 : value2; } /** * @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, ''); } } var primitiveKey = '__ec_primitive__'; /** * Set an object as primitive to be ignored traversing children in clone or merge */ function setAsPrimitive(obj) { obj[primitiveKey] = true; } function isPrimitive(obj) { return obj[primitiveKey]; } /** * @constructor * @param {Object} obj Only apply `ownProperty`. */ function HashMap(obj) { var isArr = isArray(obj); // Key should not be set on this, otherwise // methods get/set/... may be overrided. this.data = {}; var thisMap = this; (obj instanceof HashMap) ? obj.each(visit) : (obj && each$1(obj, visit)); function visit(value, key) { isArr ? thisMap.set(value, key) : thisMap.set(key, value); } } 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.data.hasOwnProperty(key) ? this.data[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.data[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)); /* eslint-disable guard-for-in */ for (var key in this.data) { this.data.hasOwnProperty(key) && cb(this.data[key], key); } /* eslint-enable guard-for-in */ }, // Do not use this method if performance sensitive. removeKey: function (key) { delete this.data[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() {} var zrUtil = (Object.freeze || Object)({ $override: $override, clone: clone, merge: merge, mergeAll: mergeAll, extend: extend, defaults: defaults, createCanvas: createCanvas, getContext: getContext, indexOf: indexOf, inherits: inherits, mixin: mixin, isArrayLike: isArrayLike, each: each$1, map: map, reduce: reduce, filter: filter, find: find, bind: bind, curry: curry, isArray: isArray, isFunction: isFunction$1, isString: isString, isObject: isObject$1, isBuiltInObject: isBuiltInObject, isTypedArray: isTypedArray, isDom: isDom, eqNaN: eqNaN, retrieve: retrieve, retrieve2: retrieve2, retrieve3: retrieve3, slice: slice, normalizeCssArray: normalizeCssArray, assert: assert$1, trim: trim, setAsPrimitive: setAsPrimitive, isPrimitive: isPrimitive, createHashMap: createHashMap, concatArray: concatArray, noop: noop }); /* global Float32Array */ var ArrayCtor = typeof Float32Array === 'undefined' ? Array : Float32Array; /** * 创建一个向量 * @param {number} [x=0] * @param {number} [y=0] * @return {Vector2} */ function create(x, y) { var out = new ArrayCtor(2); if (x == null) { x = 0; } if (y == null) { y = 0; } out[0] = x; out[1] = y; return out; } /** * 复制向量数据 * @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; } var vector = (Object.freeze || Object)({ create: create, copy: copy, clone: clone$1, set: set, add: add, scaleAndAdd: scaleAndAdd, sub: sub, len: len, length: length, lenSquare: lenSquare, lengthSquare: lengthSquare, mul: mul, div: div, dot: dot, scale: scale, normalize: normalize, distance: distance, dist: dist, distanceSquare: distanceSquare, distSquare: distSquare, negate: negate, lerp: lerp, applyTransform: applyTransform, min: min, max: max }); // TODO Draggable for group // FIXME Draggable on element which has parent rotation or scale function Draggable() { this.on('mousedown', this._dragStart, this); this.on('mousemove', this._drag, this); this.on('mouseup', this._dragEnd, this); // `mosuemove` and `mouseup` can be continue to fire when dragging. // See [Drag outside] in `Handler.js`. So we do not need to trigger // `_dragEnd` when globalout. That would brings better user experience. // this.on('globalout', this._dragEnd, this); // this._dropTarget = null; // this._draggingTarget = null; // this._x = 0; // this._y = 0; } Draggable.prototype = { constructor: Draggable, _dragStart: function (e) { var draggingTarget = e.target; // Find if there is draggable in the ancestor while (draggingTarget && !draggingTarget.draggable) { draggingTarget = draggingTarget.parent; } if (draggingTarget) { this._draggingTarget = draggingTarget; draggingTarget.dragging = true; this._x = e.offsetX; this._y = e.offsetY; this.dispatchToElement(param(draggingTarget, e), 'dragstart', e.event); } }, _drag: function (e) { var draggingTarget = this._draggingTarget; if (draggingTarget) { var x = e.offsetX; var y = e.offsetY; var dx = x - this._x; var dy = y - this._y; this._x = x; this._y = y; draggingTarget.drift(dx, dy, e); this.dispatchToElement(param(draggingTarget, e), 'drag', e.event); var dropTarget = this.findHover(x, y, draggingTarget).target; var lastDropTarget = this._dropTarget; this._dropTarget = dropTarget; if (draggingTarget !== dropTarget) { if (lastDropTarget && dropTarget !== lastDropTarget) { this.dispatchToElement(param(lastDropTarget, e), 'dragleave', e.event); } if (dropTarget && dropTarget !== lastDropTarget) { this.dispatchToElement(param(dropTarget, e), 'dragenter', e.event); } } } }, _dragEnd: function (e) { var draggingTarget = this._draggingTarget; if (draggingTarget) { draggingTarget.dragging = false; } this.dispatchToElement(param(draggingTarget, e), 'dragend', e.event); 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}; } /** * Event Mixin * @module zrender/mixin/Eventful * @author Kener (@Kener-林峰, kener.linfeng@gmail.com) * pissang (https://www.github.com/pissang) */ var arrySlice = Array.prototype.slice; /** * Event dispatcher. * * @alias module:zrender/mixin/Eventful * @constructor * @param {Object} [eventProcessor] The object eventProcessor is the scope when * `eventProcessor.xxx` called. * @param {Function} [eventProcessor.normalizeQuery] * param: {string|Object} Raw query. * return: {string|Object} Normalized query. * @param {Function} [eventProcessor.filter] Event will be dispatched only * if it returns `true`. * param: {string} eventType * param: {string|Object} query * return: {boolean} * @param {Function} [eventProcessor.afterTrigger] Called after all handlers called. * param: {string} eventType */ var Eventful = function (eventProcessor) { this._$handlers = {}; this._$eventProcessor = eventProcessor; }; Eventful.prototype = { constructor: Eventful, /** * The handler can only be triggered once, then removed. * * @param {string} event The event name. * @param {string|Object} [query] Condition used on event filter. * @param {Function} handler The event handler. * @param {Object} context */ one: function (event, query, handler, context) { return on(this, event, query, handler, context, true); }, /** * Bind a handler. * * @param {string} event The event name. * @param {string|Object} [query] Condition used on event filter. * @param {Function} handler The event handler. * @param {Object} [context] */ on: function (event, query, handler, context) { return on(this, event, query, handler, context, false); }, /** * Whether any handler has bound. * * @param {string} event * @return {boolean} */ isSilent: function (event) { var _h = this._$handlers; return !_h[event] || !_h[event].length; }, /** * Unbind a event. * * @param {string} [event] The event name. * If no `event` input, "off" all listeners. * @param {Function} [handler] The event handler. * If no `handler` input, "off" all listeners of the `event`. */ 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; } if (_h[event] && _h[event].length === 0) { delete _h[event]; } } else { delete _h[event]; } return this; }, /** * Dispatch a event. * * @param {string} type The event name. */ trigger: function (type) { var _h = this._$handlers[type]; var eventProcessor = this._$eventProcessor; if (_h) { var args = arguments; var argLen = args.length; if (argLen > 3) { args = arrySlice.call(args, 1); } var len = _h.length; for (var i = 0; i < len;) { var hItem = _h[i]; if (eventProcessor && eventProcessor.filter && hItem.query != null && !eventProcessor.filter(type, hItem.query) ) { i++; continue; } // Optimize advise from backbone switch (argLen) { case 1: hItem.h.call(hItem.ctx); break; case 2: hItem.h.call(hItem.ctx, args[1]); break; case 3: hItem.h.call(hItem.ctx, args[1], args[2]); break; default: // have more than 2 given arguments hItem.h.apply(hItem.ctx, args); break; } if (hItem.one) { _h.splice(i, 1); len--; } else { i++; } } } eventProcessor && eventProcessor.afterTrigger && eventProcessor.afterTrigger(type); return this; }, /** * Dispatch a event with context, which is specified at the last parameter. * * @param {string} type The event name. */ triggerWithContext: function (type) { var _h = this._$handlers[type]; var eventProcessor = this._$eventProcessor; if (_h) { 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 len = _h.length; for (var i = 0; i < len;) { var hItem = _h[i]; if (eventProcessor && eventProcessor.filter && hItem.query != null && !eventProcessor.filter(type, hItem.query) ) { i++; continue; } // Optimize advise from backbone switch (argLen) { case 1: hItem.h.call(ctx); break; case 2: hItem.h.call(ctx, args[1]); break; case 3: hItem.h.call(ctx, args[1], args[2]); break; default: // have more than 2 given arguments hItem.h.apply(ctx, args); break; } if (hItem.one) { _h.splice(i, 1); len--; } else { i++; } } } eventProcessor && eventProcessor.afterTrigger && eventProcessor.afterTrigger(type); return this; } }; function normalizeQuery(host, query) { var eventProcessor = host._$eventProcessor; if (query != null && eventProcessor && eventProcessor.normalizeQuery) { query = eventProcessor.normalizeQuery(query); } return query; } function on(eventful, event, query, handler, context, isOnce) { var _h = eventful._$handlers; if (typeof query === 'function') { context = handler; handler = query; query = null; } if (!handler || !event) { return eventful; } query = normalizeQuery(eventful, query); if (!_h[event]) { _h[event] = []; } for (var i = 0; i < _h[event].length; i++) { if (_h[event][i].h === handler) { return eventful; } } var wrap = { h: handler, one: isOnce, query: query, ctx: context || eventful, // FIXME // Do not publish this feature util it is proved that it makes sense. callAtLast: handler.zrEventfulCallAtLast }; var lastIndex = _h[event].length - 1; var lastWrap = _h[event][lastIndex]; (lastWrap && lastWrap.callAtLast) ? _h[event].splice(lastIndex, 0, wrap) : _h[event].push(wrap); return eventful; } /** * The algoritm is learnt from * https://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/ * And we made some optimization for matrix inversion. * Other similar approaches: * "cv::getPerspectiveTransform", "Direct Linear Transformation". */ var LN2 = Math.log(2); function determinant(rows, rank, rowStart, rowMask, colMask, detCache) { var cacheKey = rowMask + '-' + colMask; var fullRank = rows.length; if (detCache.hasOwnProperty(cacheKey)) { return detCache[cacheKey]; } if (rank === 1) { // In this case the colMask must be like: `11101111`. We can find the place of `0`. var colStart = Math.round(Math.log(((1 << fullRank) - 1) & ~colMask) / LN2); return rows[rowStart][colStart]; } var subRowMask = rowMask | (1 << rowStart); var subRowStart = rowStart + 1; while (rowMask & (1 << subRowStart)) { subRowStart++; } var sum = 0; for (var j = 0, colLocalIdx = 0; j < fullRank; j++) { var colTag = 1 << j; if (!(colTag & colMask)) { sum += (colLocalIdx % 2 ? -1 : 1) * rows[rowStart][j] // det(subMatrix(0, j)) * determinant(rows, rank - 1, subRowStart, subRowMask, colMask | colTag, detCache); colLocalIdx++; } } detCache[cacheKey] = sum; return sum; } /** * Usage: * ```js * var transformer = buildTransformer( * [10, 44, 100, 44, 100, 300, 10, 300], * [50, 54, 130, 14, 140, 330, 14, 220] * ); * var out = []; * transformer && transformer([11, 33], out); * ``` * * Notice: `buildTransformer` may take more than 10ms in some Android device. * * @param {Array.<number>} src source four points, [x0, y0, x1, y1, x2, y2, x3, y3] * @param {Array.<number>} dest destination four points, [x0, y0, x1, y1, x2, y2, x3, y3] * @return {Function} transformer If fail, return null/undefined. */ function buildTransformer(src, dest) { var mA = [ [src[0], src[1], 1, 0, 0, 0, -dest[0] * src[0], -dest[0] * src[1]], [0, 0, 0, src[0], src[1], 1, -dest[1] * src[0], -dest[1] * src[1]], [src[2], src[3], 1, 0, 0, 0, -dest[2] * src[2], -dest[2] * src[3]], [0, 0, 0, src[2], src[3], 1, -dest[3] * src[2], -dest[3] * src[3]], [src[4], src[5], 1, 0, 0, 0, -dest[4] * src[4], -dest[4] * src[5]], [0, 0, 0, src[4], src[5], 1, -dest[5] * src[4], -dest[5] * src[5]], [src[6], src[7], 1, 0, 0, 0, -dest[6] * src[6], -dest[6] * src[7]], [0, 0, 0, src[6], src[7], 1, -dest[7] * src[6], -dest[7] * src[7]] ]; var detCache = {}; var det = determinant(mA, 8, 0, 0, 0, detCache); if (det === 0) { // can not make transformer when and only when // any three of the markers are collinear. return; } // `invert(mA) * dest`, that is, `adj(mA) / det * dest`. var vh = []; for (var i = 0; i < 8; i++) { for (var j = 0; j < 8; j++) { vh[j] == null && (vh[j] = 0); vh[j] += ((i + j) % 2 ? -1 : 1) // det(subMatrix(i, j)) * determinant(mA, 7, i === 0 ? 1 : 0, 1 << i, 1 << j, detCache) / det * dest[i]; } } return function (out, srcPointX, srcPointY) { var pk = srcPointX * vh[6] + srcPointY * vh[7] + 1; out[0] = (srcPointX * vh[0] + srcPointY * vh[1] + vh[2]) / pk; out[1] = (srcPointX * vh[3] + srcPointY * vh[4] + vh[5]) / pk; }; } var EVENT_SAVED_PROP = '___zrEVENTSAVED'; var _calcOut$1 = []; /** * Transform "local coord" from `elFrom` to `elTarget`. * "local coord": the coord based on the input `el`. The origin point is at * the position of "left: 0; top: 0;" in the `el`. * * Support when CSS transform is used. * * Having the `out` (that is, `[outX, outY]`), we can create an DOM element * and set the CSS style as "left: outX; top: outY;" and append it to `elTarge` * to locate the element. * * For example, this code below positions a child of `document.body` on the event * point, no matter whether `body` has `margin`/`paddin`/`transfrom`/... : * ```js * transformLocalCoord(out, container, document.body, event.offsetX, event.offsetY); * if (!eqNaN(out[0])) { * // Then locate the tip element on the event point. * var tipEl = document.createElement('div'); * tipEl.style.cssText = 'position: absolute; left:' + out[0] + ';top:' + out[1] + ';'; * document.body.appendChild(tipEl); * } * ``` * * Notice: In some env this method is not supported. If called, `out` will be `[NaN, NaN]`. * * @param {Array.<number>} out [inX: number, inY: number] The output.. * If can not transform, `out` will not be modified but return `false`. * @param {HTMLElement} elFrom The `[inX, inY]` is based on elFrom. * @param {HTMLElement} elTarget The `out` is based on elTarget. * @param {number} inX * @param {number} inY * @return {boolean} Whether transform successfully. */ function transformLocalCoord(out, elFrom, elTarget, inX, inY) { return transformCoordWithViewport(_calcOut$1, elFrom, inX, inY, true) && transformCoordWithViewport(out, elTarget, _calcOut$1[0], _calcOut$1[1]); } /** * Transform between a "viewport coord" and a "local coord". * "viewport coord": the coord based on the left-top corner of the viewport * of the browser. * "local coord": the coord based on the input `el`. The origin point is at * the position of "left: 0; top: 0;" in the `el`. * * Support the case when CSS transform is used on el. * * @param {Array.<number>} out [inX: number, inY: number] The output. If `inverse: false`, * it represents "local coord", otherwise "vireport coord". * If can not transform, `out` will not be modified but return `false`. * @param {HTMLElement} el The "local coord" is based on the `el`, see comment above. * @param {number} inX If `inverse: false`, * it represents "vireport coord", otherwise "local coord". * @param {number} inY If `inverse: false`, * it represents "vireport coord", otherwise "local coord". * @param {boolean} [inverse=false] * `true`: from "viewport coord" to "local coord". * `false`: from "local coord" to "viewport coord". * @return {boolean} Whether transform successfully. */ function transformCoordWithViewport(out, el, inX, inY, inverse) { if (el.getBoundingClientRect && env$1.domSupported && !isCanvasEl(el)) { var saved = el[EVENT_SAVED_PROP] || (el[EVENT_SAVED_PROP] = {}); var markers = prepareCoordMarkers(el, saved); var transformer = preparePointerTransformer(markers, saved, inverse); if (transformer) { transformer(out, inX, inY); return true; } } return false; } function prepareCoordMarkers(el, saved) { var markers = saved.markers; if (markers) { return markers; } markers = saved.markers = []; var propLR = ['left', 'right']; var propTB = ['top', 'bottom']; for (var i = 0; i < 4; i++) { var marker = document.createElement('div'); var stl = marker.style; var idxLR = i % 2; var idxTB = (i >> 1) % 2; stl.cssText = [ 'position: absolute', 'visibility: hidden', 'padding: 0', 'margin: 0', 'border-width: 0', 'user-select: none', 'width:0', 'height:0', // 'width: 5px', // 'height: 5px', propLR[idxLR] + ':0', propTB[idxTB] + ':0', propLR[1 - idxLR] + ':auto', propTB[1 - idxTB] + ':auto', '' ].join('!important;'); el.appendChild(marker); markers.push(marker); } return markers; } function preparePointerTransformer(markers, saved, inverse) { var transformerName = inverse ? 'invTrans' : 'trans'; var transformer = saved[transformerName]; var oldSrcCoords = saved.srcCoords; var oldCoordTheSame = true; var srcCoords = []; var destCoords = []; for (var i = 0; i < 4; i++) { var rect = markers[i].getBoundingClientRect(); var ii = 2 * i; var x = re