You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

36800 lines
1.0 MiB

  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  3. typeof define === 'function' && define.amd ? define(['exports'], factory) :
  4. (factory((global.echarts = {})));
  5. }(this, (function (exports) { 'use strict';
  6. // (1) The code `if (__DEV__) ...` can be removed by build tool.
  7. // (2) If intend to use `__DEV__`, this module should be imported. Use a global
  8. // variable `__DEV__` may cause that miss the declaration (see #6535), or the
  9. // declaration is behind of the using position (for example in `Model.extent`,
  10. // And tools like rollup can not analysis the dependency if not import).
  11. var dev;
  12. // In browser
  13. if (typeof window !== 'undefined') {
  14. dev = window.__DEV__;
  15. }
  16. // In node
  17. else if (typeof global !== 'undefined') {
  18. dev = global.__DEV__;
  19. }
  20. if (typeof dev === 'undefined') {
  21. dev = true;
  22. }
  23. var __DEV__ = dev;
  24. /**
  25. * zrender: 生成唯一id
  26. *
  27. * @author errorrik (errorrik@gmail.com)
  28. */
  29. var idStart = 0x0907;
  30. var guid = function () {
  31. return idStart++;
  32. };
  33. /**
  34. * echarts设备环境识别
  35. *
  36. * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。
  37. * @author firede[firede@firede.us]
  38. * @desc thanks zepto.
  39. */
  40. var env = {};
  41. if (typeof wx !== 'undefined') {
  42. // In Weixin Application
  43. env = {
  44. browser: {},
  45. os: {},
  46. node: false,
  47. wxa: true, // Weixin Application
  48. canvasSupported: true,
  49. svgSupported: false,
  50. touchEventsSupported: true
  51. };
  52. }
  53. else if (typeof document === 'undefined' && typeof self !== 'undefined') {
  54. // In worker
  55. env = {
  56. browser: {},
  57. os: {},
  58. node: false,
  59. worker: true,
  60. canvasSupported: true
  61. };
  62. }
  63. else if (typeof navigator === 'undefined') {
  64. // In node
  65. env = {
  66. browser: {},
  67. os: {},
  68. node: true,
  69. worker: false,
  70. // Assume canvas is supported
  71. canvasSupported: true,
  72. svgSupported: true
  73. };
  74. }
  75. else {
  76. env = detect(navigator.userAgent);
  77. }
  78. var env$1 = env;
  79. // Zepto.js
  80. // (c) 2010-2013 Thomas Fuchs
  81. // Zepto.js may be freely distributed under the MIT license.
  82. function detect(ua) {
  83. var os = {};
  84. var browser = {};
  85. // var webkit = ua.match(/Web[kK]it[\/]{0,1}([\d.]+)/);
  86. // var android = ua.match(/(Android);?[\s\/]+([\d.]+)?/);
  87. // var ipad = ua.match(/(iPad).*OS\s([\d_]+)/);
  88. // var ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/);
  89. // var iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/);
  90. // var webos = ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/);
  91. // var touchpad = webos && ua.match(/TouchPad/);
  92. // var kindle = ua.match(/Kindle\/([\d.]+)/);
  93. // var silk = ua.match(/Silk\/([\d._]+)/);
  94. // var blackberry = ua.match(/(BlackBerry).*Version\/([\d.]+)/);
  95. // var bb10 = ua.match(/(BB10).*Version\/([\d.]+)/);
  96. // var rimtabletos = ua.match(/(RIM\sTablet\sOS)\s([\d.]+)/);
  97. // var playbook = ua.match(/PlayBook/);
  98. // var chrome = ua.match(/Chrome\/([\d.]+)/) || ua.match(/CriOS\/([\d.]+)/);
  99. var firefox = ua.match(/Firefox\/([\d.]+)/);
  100. // var safari = webkit && ua.match(/Mobile\//) && !chrome;
  101. // var webview = ua.match(/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/) && !chrome;
  102. var ie = ua.match(/MSIE\s([\d.]+)/)
  103. // IE 11 Trident/7.0; rv:11.0
  104. || ua.match(/Trident\/.+?rv:(([\d.]+))/);
  105. var edge = ua.match(/Edge\/([\d.]+)/); // IE 12 and 12+
  106. var weChat = (/micromessenger/i).test(ua);
  107. // Todo: clean this up with a better OS/browser seperation:
  108. // - discern (more) between multiple browsers on android
  109. // - decide if kindle fire in silk mode is android or not
  110. // - Firefox on Android doesn't specify the Android version
  111. // - possibly devide in os, device and browser hashes
  112. // if (browser.webkit = !!webkit) browser.version = webkit[1];
  113. // if (android) os.android = true, os.version = android[2];
  114. // if (iphone && !ipod) os.ios = os.iphone = true, os.version = iphone[2].replace(/_/g, '.');
  115. // if (ipad) os.ios = os.ipad = true, os.version = ipad[2].replace(/_/g, '.');
  116. // if (ipod) os.ios = os.ipod = true, os.version = ipod[3] ? ipod[3].replace(/_/g, '.') : null;
  117. // if (webos) os.webos = true, os.version = webos[2];
  118. // if (touchpad) os.touchpad = true;
  119. // if (blackberry) os.blackberry = true, os.version = blackberry[2];
  120. // if (bb10) os.bb10 = true, os.version = bb10[2];
  121. // if (rimtabletos) os.rimtabletos = true, os.version = rimtabletos[2];
  122. // if (playbook) browser.playbook = true;
  123. // if (kindle) os.kindle = true, os.version = kindle[1];
  124. // if (silk) browser.silk = true, browser.version = silk[1];
  125. // if (!silk && os.android && ua.match(/Kindle Fire/)) browser.silk = true;
  126. // if (chrome) browser.chrome = true, browser.version = chrome[1];
  127. if (firefox) {
  128. browser.firefox = true;
  129. browser.version = firefox[1];
  130. }
  131. // if (safari && (ua.match(/Safari/) || !!os.ios)) browser.safari = true;
  132. // if (webview) browser.webview = true;
  133. if (ie) {
  134. browser.ie = true;
  135. browser.version = ie[1];
  136. }
  137. if (edge) {
  138. browser.edge = true;
  139. browser.version = edge[1];
  140. }
  141. // It is difficult to detect WeChat in Win Phone precisely, because ua can
  142. // not be set on win phone. So we do not consider Win Phone.
  143. if (weChat) {
  144. browser.weChat = true;
  145. }
  146. // os.tablet = !!(ipad || playbook || (android && !ua.match(/Mobile/)) ||
  147. // (firefox && ua.match(/Tablet/)) || (ie && !ua.match(/Phone/) && ua.match(/Touch/)));
  148. // os.phone = !!(!os.tablet && !os.ipod && (android || iphone || webos ||
  149. // (chrome && ua.match(/Android/)) || (chrome && ua.match(/CriOS\/([\d.]+)/)) ||
  150. // (firefox && ua.match(/Mobile/)) || (ie && ua.match(/Touch/))));
  151. return {
  152. browser: browser,
  153. os: os,
  154. node: false,
  155. // 原生canvas支持,改极端点了
  156. // canvasSupported : !(browser.ie && parseFloat(browser.version) < 9)
  157. canvasSupported: !!document.createElement('canvas').getContext,
  158. svgSupported: typeof SVGRect !== 'undefined',
  159. // works on most browsers
  160. // IE10/11 does not support touch event, and MS Edge supports them but not by
  161. // default, so we dont check navigator.maxTouchPoints for them here.
  162. touchEventsSupported: 'ontouchstart' in window && !browser.ie && !browser.edge,
  163. // <http://caniuse.com/#search=pointer%20event>.
  164. pointerEventsSupported: 'onpointerdown' in window
  165. // Firefox supports pointer but not by default, only MS browsers are reliable on pointer
  166. // events currently. So we dont use that on other browsers unless tested sufficiently.
  167. // Although IE 10 supports pointer event, it use old style and is different from the
  168. // standard. So we exclude that. (IE 10 is hardly used on touch device)
  169. && (browser.edge || (browser.ie && browser.version >= 11))
  170. // passiveSupported: detectPassiveSupport()
  171. };
  172. }
  173. // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection
  174. // function detectPassiveSupport() {
  175. // // Test via a getter in the options object to see if the passive property is accessed
  176. // var supportsPassive = false;
  177. // try {
  178. // var opts = Object.defineProperty({}, 'passive', {
  179. // get: function() {
  180. // supportsPassive = true;
  181. // }
  182. // });
  183. // window.addEventListener('testPassive', function() {}, opts);
  184. // } catch (e) {
  185. // }
  186. // return supportsPassive;
  187. // }
  188. /**
  189. * @module zrender/core/util
  190. */
  191. // 用于处理merge时无法遍历Date等对象的问题
  192. var BUILTIN_OBJECT = {
  193. '[object Function]': 1,
  194. '[object RegExp]': 1,
  195. '[object Date]': 1,
  196. '[object Error]': 1,
  197. '[object CanvasGradient]': 1,
  198. '[object CanvasPattern]': 1,
  199. // For node-canvas
  200. '[object Image]': 1,
  201. '[object Canvas]': 1
  202. };
  203. var TYPED_ARRAY = {
  204. '[object Int8Array]': 1,
  205. '[object Uint8Array]': 1,
  206. '[object Uint8ClampedArray]': 1,
  207. '[object Int16Array]': 1,
  208. '[object Uint16Array]': 1,
  209. '[object Int32Array]': 1,
  210. '[object Uint32Array]': 1,
  211. '[object Float32Array]': 1,
  212. '[object Float64Array]': 1
  213. };
  214. var objToString = Object.prototype.toString;
  215. var arrayProto = Array.prototype;
  216. var nativeForEach = arrayProto.forEach;
  217. var nativeFilter = arrayProto.filter;
  218. var nativeSlice = arrayProto.slice;
  219. var nativeMap = arrayProto.map;
  220. var nativeReduce = arrayProto.reduce;
  221. // Avoid assign to an exported variable, for transforming to cjs.
  222. var methods = {};
  223. function $override(name, fn) {
  224. // Clear ctx instance for different environment
  225. if (name === 'createCanvas') {
  226. _ctx = null;
  227. }
  228. methods[name] = fn;
  229. }
  230. /**
  231. * Those data types can be cloned:
  232. * Plain object, Array, TypedArray, number, string, null, undefined.
  233. * Those data types will be assgined using the orginal data:
  234. * BUILTIN_OBJECT
  235. * Instance of user defined class will be cloned to a plain object, without
  236. * properties in prototype.
  237. * Other data types is not supported (not sure what will happen).
  238. *
  239. * Caution: do not support clone Date, for performance consideration.
  240. * (There might be a large number of date in `series.data`).
  241. * So date should not be modified in and out of echarts.
  242. *
  243. * @param {*} source
  244. * @return {*} new
  245. */
  246. function clone(source) {
  247. if (source == null || typeof source != 'object') {
  248. return source;
  249. }
  250. var result = source;
  251. var typeStr = objToString.call(source);
  252. if (typeStr === '[object Array]') {
  253. if (!isPrimitive(source)) {
  254. result = [];
  255. for (var i = 0, len = source.length; i < len; i++) {
  256. result[i] = clone(source[i]);
  257. }
  258. }
  259. }
  260. else if (TYPED_ARRAY[typeStr]) {
  261. if (!isPrimitive(source)) {
  262. var Ctor = source.constructor;
  263. if (source.constructor.from) {
  264. result = Ctor.from(source);
  265. }
  266. else {
  267. result = new Ctor(source.length);
  268. for (var i = 0, len = source.length; i < len; i++) {
  269. result[i] = clone(source[i]);
  270. }
  271. }
  272. }
  273. }
  274. else if (!BUILTIN_OBJECT[typeStr] && !isPrimitive(source) && !isDom(source)) {
  275. result = {};
  276. for (var key in source) {
  277. if (source.hasOwnProperty(key)) {
  278. result[key] = clone(source[key]);
  279. }
  280. }
  281. }
  282. return result;
  283. }
  284. /**
  285. * @memberOf module:zrender/core/util
  286. * @param {*} target
  287. * @param {*} source
  288. * @param {boolean} [overwrite=false]
  289. */
  290. function merge(target, source, overwrite) {
  291. // We should escapse that source is string
  292. // and enter for ... in ...
  293. if (!isObject$1(source) || !isObject$1(target)) {
  294. return overwrite ? clone(source) : target;
  295. }
  296. for (var key in source) {
  297. if (source.hasOwnProperty(key)) {
  298. var targetProp = target[key];
  299. var sourceProp = source[key];
  300. if (isObject$1(sourceProp)
  301. && isObject$1(targetProp)
  302. && !isArray(sourceProp)
  303. && !isArray(targetProp)
  304. && !isDom(sourceProp)
  305. && !isDom(targetProp)
  306. && !isBuiltInObject(sourceProp)
  307. && !isBuiltInObject(targetProp)
  308. && !isPrimitive(sourceProp)
  309. && !isPrimitive(targetProp)
  310. ) {
  311. // 如果需要递归覆盖,就递归调用merge
  312. merge(targetProp, sourceProp, overwrite);
  313. }
  314. else if (overwrite || !(key in target)) {
  315. // 否则只处理overwrite为true,或者在目标对象中没有此属性的情况
  316. // NOTE,在 target[key] 不存在的时候也是直接覆盖
  317. target[key] = clone(source[key], true);
  318. }
  319. }
  320. }
  321. return target;
  322. }
  323. /**
  324. * @param {Array} targetAndSources The first item is target, and the rests are source.
  325. * @param {boolean} [overwrite=false]
  326. * @return {*} target
  327. */
  328. function mergeAll(targetAndSources, overwrite) {
  329. var result = targetAndSources[0];
  330. for (var i = 1, len = targetAndSources.length; i < len; i++) {
  331. result = merge(result, targetAndSources[i], overwrite);
  332. }
  333. return result;
  334. }
  335. /**
  336. * @param {*} target
  337. * @param {*} source
  338. * @memberOf module:zrender/core/util
  339. */
  340. function extend(target, source) {
  341. for (var key in source) {
  342. if (source.hasOwnProperty(key)) {
  343. target[key] = source[key];
  344. }
  345. }
  346. return target;
  347. }
  348. /**
  349. * @param {*} target
  350. * @param {*} source
  351. * @param {boolean} [overlay=false]
  352. * @memberOf module:zrender/core/util
  353. */
  354. function defaults(target, source, overlay) {
  355. for (var key in source) {
  356. if (source.hasOwnProperty(key)
  357. && (overlay ? source[key] != null : target[key] == null)
  358. ) {
  359. target[key] = source[key];
  360. }
  361. }
  362. return target;
  363. }
  364. var createCanvas = function () {
  365. return methods.createCanvas();
  366. };
  367. methods.createCanvas = function () {
  368. return document.createElement('canvas');
  369. };
  370. // FIXME
  371. var _ctx;
  372. function getContext() {
  373. if (!_ctx) {
  374. // Use util.createCanvas instead of createCanvas
  375. // because createCanvas may be overwritten in different environment
  376. _ctx = createCanvas().getContext('2d');
  377. }
  378. return _ctx;
  379. }
  380. /**
  381. * 查询数组中元素的index
  382. * @memberOf module:zrender/core/util
  383. */
  384. function indexOf(array, value) {
  385. if (array) {
  386. if (array.indexOf) {
  387. return array.indexOf(value);
  388. }
  389. for (var i = 0, len = array.length; i < len; i++) {
  390. if (array[i] === value) {
  391. return i;
  392. }
  393. }
  394. }
  395. return -1;
  396. }
  397. /**
  398. * 构造类继承关系
  399. *
  400. * @memberOf module:zrender/core/util
  401. * @param {Function} clazz 源类
  402. * @param {Function} baseClazz 基类
  403. */
  404. function inherits(clazz, baseClazz) {
  405. var clazzPrototype = clazz.prototype;
  406. function F() {}
  407. F.prototype = baseClazz.prototype;
  408. clazz.prototype = new F();
  409. for (var prop in clazzPrototype) {
  410. clazz.prototype[prop] = clazzPrototype[prop];
  411. }
  412. clazz.prototype.constructor = clazz;
  413. clazz.superClass = baseClazz;
  414. }
  415. /**
  416. * @memberOf module:zrender/core/util
  417. * @param {Object|Function} target
  418. * @param {Object|Function} sorce
  419. * @param {boolean} overlay
  420. */
  421. function mixin(target, source, overlay) {
  422. target = 'prototype' in target ? target.prototype : target;
  423. source = 'prototype' in source ? source.prototype : source;
  424. defaults(target, source, overlay);
  425. }
  426. /**
  427. * Consider typed array.
  428. * @param {Array|TypedArray} data
  429. */
  430. function isArrayLike(data) {
  431. if (! data) {
  432. return;
  433. }
  434. if (typeof data == 'string') {
  435. return false;
  436. }
  437. return typeof data.length == 'number';
  438. }
  439. /**
  440. * 数组或对象遍历
  441. * @memberOf module:zrender/core/util
  442. * @param {Object|Array} obj
  443. * @param {Function} cb
  444. * @param {*} [context]
  445. */
  446. function each$1(obj, cb, context) {
  447. if (!(obj && cb)) {
  448. return;
  449. }
  450. if (obj.forEach && obj.forEach === nativeForEach) {
  451. obj.forEach(cb, context);
  452. }
  453. else if (obj.length === +obj.length) {
  454. for (var i = 0, len = obj.length; i < len; i++) {
  455. cb.call(context, obj[i], i, obj);
  456. }
  457. }
  458. else {
  459. for (var key in obj) {
  460. if (obj.hasOwnProperty(key)) {
  461. cb.call(context, obj[key], key, obj);
  462. }
  463. }
  464. }
  465. }
  466. /**
  467. * 数组映射
  468. * @memberOf module:zrender/core/util
  469. * @param {Array} obj
  470. * @param {Function} cb
  471. * @param {*} [context]
  472. * @return {Array}
  473. */
  474. function map(obj, cb, context) {
  475. if (!(obj && cb)) {
  476. return;
  477. }
  478. if (obj.map && obj.map === nativeMap) {
  479. return obj.map(cb, context);
  480. }
  481. else {
  482. var result = [];
  483. for (var i = 0, len = obj.length; i < len; i++) {
  484. result.push(cb.call(context, obj[i], i, obj));
  485. }
  486. return result;
  487. }
  488. }
  489. /**
  490. * @memberOf module:zrender/core/util
  491. * @param {Array} obj
  492. * @param {Function} cb
  493. * @param {Object} [memo]
  494. * @param {*} [context]
  495. * @return {Array}
  496. */
  497. function reduce(obj, cb, memo, context) {
  498. if (!(obj && cb)) {
  499. return;
  500. }
  501. if (obj.reduce && obj.reduce === nativeReduce) {
  502. return obj.reduce(cb, memo, context);
  503. }
  504. else {
  505. for (var i = 0, len = obj.length; i < len; i++) {
  506. memo = cb.call(context, memo, obj[i], i, obj);
  507. }
  508. return memo;
  509. }
  510. }
  511. /**
  512. * 数组过滤
  513. * @memberOf module:zrender/core/util
  514. * @param {Array} obj
  515. * @param {Function} cb
  516. * @param {*} [context]
  517. * @return {Array}
  518. */
  519. function filter(obj, cb, context) {
  520. if (!(obj && cb)) {
  521. return;
  522. }
  523. if (obj.filter && obj.filter === nativeFilter) {
  524. return obj.filter(cb, context);
  525. }
  526. else {
  527. var result = [];
  528. for (var i = 0, len = obj.length; i < len; i++) {
  529. if (cb.call(context, obj[i], i, obj)) {
  530. result.push(obj[i]);
  531. }
  532. }
  533. return result;
  534. }
  535. }
  536. /**
  537. * 数组项查找
  538. * @memberOf module:zrender/core/util
  539. * @param {Array} obj
  540. * @param {Function} cb
  541. * @param {*} [context]
  542. * @return {*}
  543. */
  544. /**
  545. * @memberOf module:zrender/core/util
  546. * @param {Function} func
  547. * @param {*} context
  548. * @return {Function}
  549. */
  550. function bind(func, context) {
  551. var args = nativeSlice.call(arguments, 2);
  552. return function () {
  553. return func.apply(context, args.concat(nativeSlice.call(arguments)));
  554. };
  555. }
  556. /**
  557. * @memberOf module:zrender/core/util
  558. * @param {Function} func
  559. * @return {Function}
  560. */
  561. function curry(func) {
  562. var args = nativeSlice.call(arguments, 1);
  563. return function () {
  564. return func.apply(this, args.concat(nativeSlice.call(arguments)));
  565. };
  566. }
  567. /**
  568. * @memberOf module:zrender/core/util
  569. * @param {*} value
  570. * @return {boolean}
  571. */
  572. function isArray(value) {
  573. return objToString.call(value) === '[object Array]';
  574. }
  575. /**
  576. * @memberOf module:zrender/core/util
  577. * @param {*} value
  578. * @return {boolean}
  579. */
  580. function isFunction$1(value) {
  581. return typeof value === 'function';
  582. }
  583. /**
  584. * @memberOf module:zrender/core/util
  585. * @param {*} value
  586. * @return {boolean}
  587. */
  588. function isString(value) {
  589. return objToString.call(value) === '[object String]';
  590. }
  591. /**
  592. * @memberOf module:zrender/core/util
  593. * @param {*} value
  594. * @return {boolean}
  595. */
  596. function isObject$1(value) {
  597. // Avoid a V8 JIT bug in Chrome 19-20.
  598. // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
  599. var type = typeof value;
  600. return type === 'function' || (!!value && type == 'object');
  601. }
  602. /**
  603. * @memberOf module:zrender/core/util
  604. * @param {*} value
  605. * @return {boolean}
  606. */
  607. function isBuiltInObject(value) {
  608. return !!BUILTIN_OBJECT[objToString.call(value)];
  609. }
  610. /**
  611. * @memberOf module:zrender/core/util
  612. * @param {*} value
  613. * @return {boolean}
  614. */
  615. function isTypedArray(value) {
  616. return !!TYPED_ARRAY[objToString.call(value)];
  617. }
  618. /**
  619. * @memberOf module:zrender/core/util
  620. * @param {*} value
  621. * @return {boolean}
  622. */
  623. function isDom(value) {
  624. return typeof value === 'object'
  625. && typeof value.nodeType === 'number'
  626. && typeof value.ownerDocument === 'object';
  627. }
  628. /**
  629. * Whether is exactly NaN. Notice isNaN('a') returns true.
  630. * @param {*} value
  631. * @return {boolean}
  632. */
  633. function eqNaN(value) {
  634. return value !== value;
  635. }
  636. /**
  637. * If value1 is not null, then return value1, otherwise judget rest of values.
  638. * Low performance.
  639. * @memberOf module:zrender/core/util
  640. * @return {*} Final value
  641. */
  642. function retrieve(values) {
  643. for (var i = 0, len = arguments.length; i < len; i++) {
  644. if (arguments[i] != null) {
  645. return arguments[i];
  646. }
  647. }
  648. }
  649. function retrieve2(value0, value1) {
  650. return value0 != null
  651. ? value0
  652. : value1;
  653. }
  654. function retrieve3(value0, value1, value2) {
  655. return value0 != null
  656. ? value0
  657. : value1 != null
  658. ? value1
  659. : value2;
  660. }
  661. /**
  662. * @memberOf module:zrender/core/util
  663. * @param {Array} arr
  664. * @param {number} startIndex
  665. * @param {number} endIndex
  666. * @return {Array}
  667. */
  668. function slice() {
  669. return Function.call.apply(nativeSlice, arguments);
  670. }
  671. /**
  672. * Normalize css liked array configuration
  673. * e.g.
  674. * 3 => [3, 3, 3, 3]
  675. * [4, 2] => [4, 2, 4, 2]
  676. * [4, 3, 2] => [4, 3, 2, 3]
  677. * @param {number|Array.<number>} val
  678. * @return {Array.<number>}
  679. */
  680. function normalizeCssArray(val) {
  681. if (typeof (val) === 'number') {
  682. return [val, val, val, val];
  683. }
  684. var len = val.length;
  685. if (len === 2) {
  686. // vertical | horizontal
  687. return [val[0], val[1], val[0], val[1]];
  688. }
  689. else if (len === 3) {
  690. // top | horizontal | bottom
  691. return [val[0], val[1], val[2], val[1]];
  692. }
  693. return val;
  694. }
  695. /**
  696. * @memberOf module:zrender/core/util
  697. * @param {boolean} condition
  698. * @param {string} message
  699. */
  700. function assert$1(condition, message) {
  701. if (!condition) {
  702. throw new Error(message);
  703. }
  704. }
  705. /**
  706. * @memberOf module:zrender/core/util
  707. * @param {string} str string to be trimed
  708. * @return {string} trimed string
  709. */
  710. function trim(str) {
  711. if (str == null) {
  712. return null;
  713. }
  714. else if (typeof str.trim === 'function') {
  715. return str.trim();
  716. }
  717. else {
  718. return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
  719. }
  720. }
  721. var primitiveKey = '__ec_primitive__';
  722. /**
  723. * Set an object as primitive to be ignored traversing children in clone or merge
  724. */
  725. function setAsPrimitive(obj) {
  726. obj[primitiveKey] = true;
  727. }
  728. function isPrimitive(obj) {
  729. return obj[primitiveKey];
  730. }
  731. /**
  732. * @constructor
  733. * @param {Object} obj Only apply `ownProperty`.
  734. */
  735. function HashMap(obj) {
  736. var isArr = isArray(obj);
  737. var thisMap = this;
  738. (obj instanceof HashMap)
  739. ? obj.each(visit)
  740. : (obj && each$1(obj, visit));
  741. function visit(value, key) {
  742. isArr ? thisMap.set(value, key) : thisMap.set(key, value);
  743. }
  744. }
  745. // Add prefix to avoid conflict with Object.prototype.
  746. HashMap.prototype = {
  747. constructor: HashMap,
  748. // Do not provide `has` method to avoid defining what is `has`.
  749. // (We usually treat `null` and `undefined` as the same, different
  750. // from ES6 Map).
  751. get: function (key) {
  752. return this.hasOwnProperty(key) ? this[key] : null;
  753. },
  754. set: function (key, value) {
  755. // Comparing with invocation chaining, `return value` is more commonly
  756. // used in this case: `var someVal = map.set('a', genVal());`
  757. return (this[key] = value);
  758. },
  759. // Although util.each can be performed on this hashMap directly, user
  760. // should not use the exposed keys, who are prefixed.
  761. each: function (cb, context) {
  762. context !== void 0 && (cb = bind(cb, context));
  763. for (var key in this) {
  764. this.hasOwnProperty(key) && cb(this[key], key);
  765. }
  766. },
  767. // Do not use this method if performance sensitive.
  768. removeKey: function (key) {
  769. delete this[key];
  770. }
  771. };
  772. function createHashMap(obj) {
  773. return new HashMap(obj);
  774. }
  775. function noop() {}
  776. var ArrayCtor = typeof Float32Array === 'undefined'
  777. ? Array
  778. : Float32Array;
  779. /**
  780. * 创建一个向量
  781. * @param {number} [x=0]
  782. * @param {number} [y=0]
  783. * @return {Vector2}
  784. */
  785. function create(x, y) {
  786. var out = new ArrayCtor(2);
  787. if (x == null) {
  788. x = 0;
  789. }
  790. if (y == null) {
  791. y = 0;
  792. }
  793. out[0] = x;
  794. out[1] = y;
  795. return out;
  796. }
  797. /**
  798. * 复制向量数据
  799. * @param {Vector2} out
  800. * @param {Vector2} v
  801. * @return {Vector2}
  802. */
  803. function copy(out, v) {
  804. out[0] = v[0];
  805. out[1] = v[1];
  806. return out;
  807. }
  808. /**
  809. * 克隆一个向量
  810. * @param {Vector2} v
  811. * @return {Vector2}
  812. */
  813. function clone$1(v) {
  814. var out = new ArrayCtor(2);
  815. out[0] = v[0];
  816. out[1] = v[1];
  817. return out;
  818. }
  819. /**
  820. * 设置向量的两个项
  821. * @param {Vector2} out
  822. * @param {number} a
  823. * @param {number} b
  824. * @return {Vector2} 结果
  825. */
  826. /**
  827. * 向量相加
  828. * @param {Vector2} out
  829. * @param {Vector2} v1
  830. * @param {Vector2} v2
  831. */
  832. function add(out, v1, v2) {
  833. out[0] = v1[0] + v2[0];
  834. out[1] = v1[1] + v2[1];
  835. return out;
  836. }
  837. /**
  838. * 向量缩放后相加
  839. * @param {Vector2} out
  840. * @param {Vector2} v1
  841. * @param {Vector2} v2
  842. * @param {number} a
  843. */
  844. function scaleAndAdd(out, v1, v2, a) {
  845. out[0] = v1[0] + v2[0] * a;
  846. out[1] = v1[1] + v2[1] * a;
  847. return out;
  848. }
  849. /**
  850. * 向量相减
  851. * @param {Vector2} out
  852. * @param {Vector2} v1
  853. * @param {Vector2} v2
  854. */
  855. function sub(out, v1, v2) {
  856. out[0] = v1[0] - v2[0];
  857. out[1] = v1[1] - v2[1];
  858. return out;
  859. }
  860. /**
  861. * 向量长度
  862. * @param {Vector2} v
  863. * @return {number}
  864. */
  865. function len(v) {
  866. return Math.sqrt(lenSquare(v));
  867. }
  868. // jshint ignore:line
  869. /**
  870. * 向量长度平方
  871. * @param {Vector2} v
  872. * @return {number}
  873. */
  874. function lenSquare(v) {
  875. return v[0] * v[0] + v[1] * v[1];
  876. }
  877. /**
  878. * 向量乘法
  879. * @param {Vector2} out
  880. * @param {Vector2} v1
  881. * @param {Vector2} v2
  882. */
  883. /**
  884. * 向量除法
  885. * @param {Vector2} out
  886. * @param {Vector2} v1
  887. * @param {Vector2} v2
  888. */
  889. /**
  890. * 向量点乘
  891. * @param {Vector2} v1
  892. * @param {Vector2} v2
  893. * @return {number}
  894. */
  895. /**
  896. * 向量缩放
  897. * @param {Vector2} out
  898. * @param {Vector2} v
  899. * @param {number} s
  900. */
  901. function scale(out, v, s) {
  902. out[0] = v[0] * s;
  903. out[1] = v[1] * s;
  904. return out;
  905. }
  906. /**
  907. * 向量归一化
  908. * @param {Vector2} out
  909. * @param {Vector2} v
  910. */
  911. function normalize(out, v) {
  912. var d = len(v);
  913. if (d === 0) {
  914. out[0] = 0;
  915. out[1] = 0;
  916. }
  917. else {
  918. out[0] = v[0] / d;
  919. out[1] = v[1] / d;
  920. }
  921. return out;
  922. }
  923. /**
  924. * 计算向量间距离
  925. * @param {Vector2} v1
  926. * @param {Vector2} v2
  927. * @return {number}
  928. */
  929. function distance(v1, v2) {
  930. return Math.sqrt(
  931. (v1[0] - v2[0]) * (v1[0] - v2[0])
  932. + (v1[1] - v2[1]) * (v1[1] - v2[1])
  933. );
  934. }
  935. var dist = distance;
  936. /**
  937. * 向量距离平方
  938. * @param {Vector2} v1
  939. * @param {Vector2} v2
  940. * @return {number}
  941. */
  942. function distanceSquare(v1, v2) {
  943. return (v1[0] - v2[0]) * (v1[0] - v2[0])
  944. + (v1[1] - v2[1]) * (v1[1] - v2[1]);
  945. }
  946. var distSquare = distanceSquare;
  947. /**
  948. * 求负向量
  949. * @param {Vector2} out
  950. * @param {Vector2} v
  951. */
  952. /**
  953. * 插值两个点
  954. * @param {Vector2} out
  955. * @param {Vector2} v1
  956. * @param {Vector2} v2
  957. * @param {number} t
  958. */
  959. /**
  960. * 矩阵左乘向量
  961. * @param {Vector2} out
  962. * @param {Vector2} v
  963. * @param {Vector2} m
  964. */
  965. function applyTransform(out, v, m) {
  966. var x = v[0];
  967. var y = v[1];
  968. out[0] = m[0] * x + m[2] * y + m[4];
  969. out[1] = m[1] * x + m[3] * y + m[5];
  970. return out;
  971. }
  972. /**
  973. * 求两个向量最小值
  974. * @param {Vector2} out
  975. * @param {Vector2} v1
  976. * @param {Vector2} v2
  977. */
  978. function min(out, v1, v2) {
  979. out[0] = Math.min(v1[0], v2[0]);
  980. out[1] = Math.min(v1[1], v2[1]);
  981. return out;
  982. }
  983. /**
  984. * 求两个向量最大值
  985. * @param {Vector2} out
  986. * @param {Vector2} v1
  987. * @param {Vector2} v2
  988. */
  989. function max(out, v1, v2) {
  990. out[0] = Math.max(v1[0], v2[0]);
  991. out[1] = Math.max(v1[1], v2[1]);
  992. return out;
  993. }
  994. // TODO Draggable for group
  995. // FIXME Draggable on element which has parent rotation or scale
  996. function Draggable() {
  997. this.on('mousedown', this._dragStart, this);
  998. this.on('mousemove', this._drag, this);
  999. this.on('mouseup', this._dragEnd, this);
  1000. this.on('globalout', this._dragEnd, this);
  1001. // this._dropTarget = null;
  1002. // this._draggingTarget = null;
  1003. // this._x = 0;
  1004. // this._y = 0;
  1005. }
  1006. Draggable.prototype = {
  1007. constructor: Draggable,
  1008. _dragStart: function (e) {
  1009. var draggingTarget = e.target;
  1010. if (draggingTarget && draggingTarget.draggable) {
  1011. this._draggingTarget = draggingTarget;
  1012. draggingTarget.dragging = true;
  1013. this._x = e.offsetX;
  1014. this._y = e.offsetY;
  1015. this.dispatchToElement(param(draggingTarget, e), 'dragstart', e.event);
  1016. }
  1017. },
  1018. _drag: function (e) {
  1019. var draggingTarget = this._draggingTarget;
  1020. if (draggingTarget) {
  1021. var x = e.offsetX;
  1022. var y = e.offsetY;
  1023. var dx = x - this._x;
  1024. var dy = y - this._y;
  1025. this._x = x;
  1026. this._y = y;
  1027. draggingTarget.drift(dx, dy, e);
  1028. this.dispatchToElement(param(draggingTarget, e), 'drag', e.event);
  1029. var dropTarget = this.findHover(x, y, draggingTarget).target;
  1030. var lastDropTarget = this._dropTarget;
  1031. this._dropTarget = dropTarget;
  1032. if (draggingTarget !== dropTarget) {
  1033. if (lastDropTarget && dropTarget !== lastDropTarget) {
  1034. this.dispatchToElement(param(lastDropTarget, e), 'dragleave', e.event);
  1035. }
  1036. if (dropTarget && dropTarget !== lastDropTarget) {
  1037. this.dispatchToElement(param(dropTarget, e), 'dragenter', e.event);
  1038. }
  1039. }
  1040. }
  1041. },
  1042. _dragEnd: function (e) {
  1043. var draggingTarget = this._draggingTarget;
  1044. if (draggingTarget) {
  1045. draggingTarget.dragging = false;
  1046. }
  1047. this.dispatchToElement(param(draggingTarget, e), 'dragend', e.event);
  1048. if (this._dropTarget) {
  1049. this.dispatchToElement(param(this._dropTarget, e), 'drop', e.event);
  1050. }
  1051. this._draggingTarget = null;
  1052. this._dropTarget = null;
  1053. }
  1054. };
  1055. function param(target, e) {
  1056. return {target: target, topTarget: e && e.topTarget};
  1057. }
  1058. /**
  1059. * 事件扩展
  1060. * @module zrender/mixin/Eventful
  1061. * @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
  1062. * pissang (https://www.github.com/pissang)
  1063. */
  1064. var arrySlice = Array.prototype.slice;
  1065. /**
  1066. * 事件分发器
  1067. * @alias module:zrender/mixin/Eventful
  1068. * @constructor
  1069. */
  1070. var Eventful = function () {
  1071. this._$handlers = {};
  1072. };
  1073. Eventful.prototype = {
  1074. constructor: Eventful,
  1075. /**
  1076. * 单次触发绑定,trigger后销毁
  1077. *
  1078. * @param {string} event 事件名
  1079. * @param {Function} handler 响应函数
  1080. * @param {Object} context
  1081. */
  1082. one: function (event, handler, context) {
  1083. var _h = this._$handlers;
  1084. if (!handler || !event) {
  1085. return this;
  1086. }
  1087. if (!_h[event]) {
  1088. _h[event] = [];
  1089. }
  1090. for (var i = 0; i < _h[event].length; i++) {
  1091. if (_h[event][i].h === handler) {
  1092. return this;
  1093. }
  1094. }
  1095. _h[event].push({
  1096. h: handler,
  1097. one: true,
  1098. ctx: context || this
  1099. });
  1100. return this;
  1101. },
  1102. /**
  1103. * 绑定事件
  1104. * @param {string} event 事件名
  1105. * @param {Function} handler 事件处理函数
  1106. * @param {Object} [context]
  1107. */
  1108. on: function (event, handler, context) {
  1109. var _h = this._$handlers;
  1110. if (!handler || !event) {
  1111. return this;
  1112. }
  1113. if (!_h[event]) {
  1114. _h[event] = [];
  1115. }
  1116. for (var i = 0; i < _h[event].length; i++) {
  1117. if (_h[event][i].h === handler) {
  1118. return this;
  1119. }
  1120. }
  1121. _h[event].push({
  1122. h: handler,
  1123. one: false,
  1124. ctx: context || this
  1125. });
  1126. return this;
  1127. },
  1128. /**
  1129. * 是否绑定了事件
  1130. * @param {string} event
  1131. * @return {boolean}
  1132. */
  1133. isSilent: function (event) {
  1134. var _h = this._$handlers;
  1135. return _h[event] && _h[event].length;
  1136. },
  1137. /**
  1138. * 解绑事件
  1139. * @param {string} event 事件名
  1140. * @param {Function} [handler] 事件处理函数
  1141. */
  1142. off: function (event, handler) {
  1143. var _h = this._$handlers;
  1144. if (!event) {
  1145. this._$handlers = {};
  1146. return this;
  1147. }
  1148. if (handler) {
  1149. if (_h[event]) {
  1150. var newList = [];
  1151. for (var i = 0, l = _h[event].length; i < l; i++) {
  1152. if (_h[event][i]['h'] != handler) {
  1153. newList.push(_h[event][i]);
  1154. }
  1155. }
  1156. _h[event] = newList;
  1157. }
  1158. if (_h[event] && _h[event].length === 0) {
  1159. delete _h[event];
  1160. }
  1161. }
  1162. else {
  1163. delete _h[event];
  1164. }
  1165. return this;
  1166. },
  1167. /**
  1168. * 事件分发
  1169. *
  1170. * @param {string} type 事件类型
  1171. */
  1172. trigger: function (type) {
  1173. if (this._$handlers[type]) {
  1174. var args = arguments;
  1175. var argLen = args.length;
  1176. if (argLen > 3) {
  1177. args = arrySlice.call(args, 1);
  1178. }
  1179. var _h = this._$handlers[type];
  1180. var len = _h.length;
  1181. for (var i = 0; i < len;) {
  1182. // Optimize advise from backbone
  1183. switch (argLen) {
  1184. case 1:
  1185. _h[i]['h'].call(_h[i]['ctx']);
  1186. break;
  1187. case 2:
  1188. _h[i]['h'].call(_h[i]['ctx'], args[1]);
  1189. break;
  1190. case 3:
  1191. _h[i]['h'].call(_h[i]['ctx'], args[1], args[2]);
  1192. break;
  1193. default:
  1194. // have more than 2 given arguments
  1195. _h[i]['h'].apply(_h[i]['ctx'], args);
  1196. break;
  1197. }
  1198. if (_h[i]['one']) {
  1199. _h.splice(i, 1);
  1200. len--;
  1201. }
  1202. else {
  1203. i++;
  1204. }
  1205. }
  1206. }
  1207. return this;
  1208. },
  1209. /**
  1210. * 带有context的事件分发, 最后一个参数是事件回调的context
  1211. * @param {string} type 事件类型
  1212. */
  1213. triggerWithContext: function (type) {
  1214. if (this._$handlers[type]) {
  1215. var args = arguments;
  1216. var argLen = args.length;
  1217. if (argLen > 4) {
  1218. args = arrySlice.call(args, 1, args.length - 1);
  1219. }
  1220. var ctx = args[args.length - 1];
  1221. var _h = this._$handlers[type];
  1222. var len = _h.length;
  1223. for (var i = 0; i < len;) {
  1224. // Optimize advise from backbone
  1225. switch (argLen) {
  1226. case 1:
  1227. _h[i]['h'].call(ctx);
  1228. break;
  1229. case 2:
  1230. _h[i]['h'].call(ctx, args[1]);
  1231. break;
  1232. case 3:
  1233. _h[i]['h'].call(ctx, args[1], args[2]);
  1234. break;
  1235. default:
  1236. // have more than 2 given arguments
  1237. _h[i]['h'].apply(ctx, args);
  1238. break;
  1239. }
  1240. if (_h[i]['one']) {
  1241. _h.splice(i, 1);
  1242. len--;
  1243. }
  1244. else {
  1245. i++;
  1246. }
  1247. }
  1248. }
  1249. return this;
  1250. }
  1251. };
  1252. var SILENT = 'silent';
  1253. function makeEventPacket(eveType, targetInfo, event) {
  1254. return {
  1255. type: eveType,
  1256. event: event,
  1257. // target can only be an element that is not silent.
  1258. target: targetInfo.target,
  1259. // topTarget can be a silent element.
  1260. topTarget: targetInfo.topTarget,
  1261. cancelBubble: false,
  1262. offsetX: event.zrX,
  1263. offsetY: event.zrY,
  1264. gestureEvent: event.gestureEvent,
  1265. pinchX: event.pinchX,
  1266. pinchY: event.pinchY,
  1267. pinchScale: event.pinchScale,
  1268. wheelDelta: event.zrDelta,
  1269. zrByTouch: event.zrByTouch,
  1270. which: event.which
  1271. };
  1272. }
  1273. function EmptyProxy () {}
  1274. EmptyProxy.prototype.dispose = function () {};
  1275. var handlerNames = [
  1276. 'click', 'dblclick', 'mousewheel', 'mouseout',
  1277. 'mouseup', 'mousedown', 'mousemove', 'contextmenu'
  1278. ];
  1279. /**
  1280. * @alias module:zrender/Handler
  1281. * @constructor
  1282. * @extends module:zrender/mixin/Eventful
  1283. * @param {module:zrender/Storage} storage Storage instance.
  1284. * @param {module:zrender/Painter} painter Painter instance.
  1285. * @param {module:zrender/dom/HandlerProxy} proxy HandlerProxy instance.
  1286. * @param {HTMLElement} painterRoot painter.root (not painter.getViewportRoot()).
  1287. */
  1288. var Handler = function(storage, painter, proxy, painterRoot) {
  1289. Eventful.call(this);
  1290. this.storage = storage;
  1291. this.painter = painter;
  1292. this.painterRoot = painterRoot;
  1293. proxy = proxy || new EmptyProxy();
  1294. /**
  1295. * Proxy of event. can be Dom, WebGLSurface, etc.
  1296. */
  1297. this.proxy = null;
  1298. /**
  1299. * {target, topTarget, x, y}
  1300. * @private
  1301. * @type {Object}
  1302. */
  1303. this._hovered = {};
  1304. /**
  1305. * @private
  1306. * @type {Date}
  1307. */
  1308. this._lastTouchMoment;
  1309. /**
  1310. * @private
  1311. * @type {number}
  1312. */
  1313. this._lastX;
  1314. /**
  1315. * @private
  1316. * @type {number}
  1317. */
  1318. this._lastY;
  1319. Draggable.call(this);
  1320. this.setHandlerProxy(proxy);
  1321. };
  1322. Handler.prototype = {
  1323. constructor: Handler,
  1324. setHandlerProxy: function (proxy) {
  1325. if (this.proxy) {
  1326. this.proxy.dispose();
  1327. }
  1328. if (proxy) {
  1329. each$1(handlerNames, function (name) {
  1330. proxy.on && proxy.on(name, this[name], this);
  1331. }, this);
  1332. // Attach handler
  1333. proxy.handler = this;
  1334. }
  1335. this.proxy = proxy;
  1336. },
  1337. mousemove: function (event) {
  1338. var x = event.zrX;
  1339. var y = event.zrY;
  1340. var lastHovered = this._hovered;
  1341. var lastHoveredTarget = lastHovered.target;
  1342. // If lastHoveredTarget is removed from zr (detected by '__zr') by some API call
  1343. // (like 'setOption' or 'dispatchAction') in event handlers, we should find
  1344. // lastHovered again here. Otherwise 'mouseout' can not be triggered normally.
  1345. // See #6198.
  1346. if (lastHoveredTarget && !lastHoveredTarget.__zr) {
  1347. lastHovered = this.findHover(lastHovered.x, lastHovered.y);
  1348. lastHoveredTarget = lastHovered.target;
  1349. }
  1350. var hovered = this._hovered = this.findHover(x, y);
  1351. var hoveredTarget = hovered.target;
  1352. var proxy = this.proxy;
  1353. proxy.setCursor && proxy.setCursor(hoveredTarget ? hoveredTarget.cursor : 'default');
  1354. // Mouse out on previous hovered element
  1355. if (lastHoveredTarget && hoveredTarget !== lastHoveredTarget) {
  1356. this.dispatchToElement(lastHovered, 'mouseout', event);
  1357. }
  1358. // Mouse moving on one element
  1359. this.dispatchToElement(hovered, 'mousemove', event);
  1360. // Mouse over on a new element
  1361. if (hoveredTarget && hoveredTarget !== lastHoveredTarget) {
  1362. this.dispatchToElement(hovered, 'mouseover', event);
  1363. }
  1364. },
  1365. mouseout: function (event) {
  1366. this.dispatchToElement(this._hovered, 'mouseout', event);
  1367. // There might be some doms created by upper layer application
  1368. // at the same level of painter.getViewportRoot() (e.g., tooltip
  1369. // dom created by echarts), where 'globalout' event should not
  1370. // be triggered when mouse enters these doms. (But 'mouseout'
  1371. // should be triggered at the original hovered element as usual).
  1372. var element = event.toElement || event.relatedTarget;
  1373. var innerDom;
  1374. do {
  1375. element = element && element.parentNode;
  1376. }
  1377. while (element && element.nodeType != 9 && !(
  1378. innerDom = element === this.painterRoot
  1379. ));
  1380. !innerDom && this.trigger('globalout', {event: event});
  1381. },
  1382. /**
  1383. * Resize
  1384. */
  1385. resize: function (event) {
  1386. this._hovered = {};
  1387. },
  1388. /**
  1389. * Dispatch event
  1390. * @param {string} eventName
  1391. * @param {event=} eventArgs
  1392. */
  1393. dispatch: function (eventName, eventArgs) {
  1394. var handler = this[eventName];
  1395. handler && handler.call(this, eventArgs);
  1396. },
  1397. /**
  1398. * Dispose
  1399. */
  1400. dispose: function () {
  1401. this.proxy.dispose();
  1402. this.storage =
  1403. this.proxy =
  1404. this.painter = null;
  1405. },
  1406. /**
  1407. * 设置默认的cursor style
  1408. * @param {string} [cursorStyle='default'] 例如 crosshair
  1409. */
  1410. setCursorStyle: function (cursorStyle) {
  1411. var proxy = this.proxy;
  1412. proxy.setCursor && proxy.setCursor(cursorStyle);
  1413. },
  1414. /**
  1415. * 事件分发代理
  1416. *
  1417. * @private
  1418. * @param {Object} targetInfo {target, topTarget} 目标图形元素
  1419. * @param {string} eventName 事件名称
  1420. * @param {Object} event 事件对象
  1421. */
  1422. dispatchToElement: function (targetInfo, eventName, event) {
  1423. targetInfo = targetInfo || {};
  1424. var el = targetInfo.target;
  1425. if (el && el.silent) {
  1426. return;
  1427. }
  1428. var eventHandler = 'on' + eventName;
  1429. var eventPacket = makeEventPacket(eventName, targetInfo, event);
  1430. while (el) {
  1431. el[eventHandler]
  1432. && (eventPacket.cancelBubble = el[eventHandler].call(el, eventPacket));
  1433. el.trigger(eventName, eventPacket);
  1434. el = el.parent;
  1435. if (eventPacket.cancelBubble) {
  1436. break;
  1437. }
  1438. }
  1439. if (!eventPacket.cancelBubble) {
  1440. // 冒泡到顶级 zrender 对象
  1441. this.trigger(eventName, eventPacket);
  1442. // 分发事件到用户自定义层
  1443. // 用户有可能在全局 click 事件中 dispose,所以需要判断下 painter 是否存在
  1444. this.painter && this.painter.eachOtherLayer(function (layer) {
  1445. if (typeof(layer[eventHandler]) == 'function') {
  1446. layer[eventHandler].call(layer, eventPacket);
  1447. }
  1448. if (layer.trigger) {
  1449. layer.trigger(eventName, eventPacket);
  1450. }
  1451. });
  1452. }
  1453. },
  1454. /**
  1455. * @private
  1456. * @param {number} x
  1457. * @param {number} y
  1458. * @param {module:zrender/graphic/Displayable} exclude
  1459. * @return {model:zrender/Element}
  1460. * @method
  1461. */
  1462. findHover: function(x, y, exclude) {
  1463. var list = this.storage.getDisplayList();
  1464. var out = {x: x, y: y};
  1465. for (var i = list.length - 1; i >= 0 ; i--) {
  1466. var hoverCheckResult;
  1467. if (list[i] !== exclude
  1468. // getDisplayList may include ignored item in VML mode
  1469. && !list[i].ignore
  1470. && (hoverCheckResult = isHover(list[i], x, y))
  1471. ) {
  1472. !out.topTarget && (out.topTarget = list[i]);
  1473. if (hoverCheckResult !== SILENT) {
  1474. out.target = list[i];
  1475. break;
  1476. }
  1477. }
  1478. }
  1479. return out;
  1480. }
  1481. };
  1482. // Common handlers
  1483. each$1(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
  1484. Handler.prototype[name] = function (event) {
  1485. // Find hover again to avoid click event is dispatched manually. Or click is triggered without mouseover
  1486. var hovered = this.findHover(event.zrX, event.zrY);
  1487. var hoveredTarget = hovered.target;
  1488. if (name === 'mousedown') {
  1489. this._downEl = hoveredTarget;
  1490. this._downPoint = [event.zrX, event.zrY];
  1491. // In case click triggered before mouseup
  1492. this._upEl = hoveredTarget;
  1493. }
  1494. else if (name === 'mouseup') {
  1495. this._upEl = hoveredTarget;
  1496. }
  1497. else if (name === 'click') {
  1498. if (this._downEl !== this._upEl
  1499. // Original click event is triggered on the whole canvas element,
  1500. // including the case that `mousedown` - `mousemove` - `mouseup`,
  1501. // which should be filtered, otherwise it will bring trouble to
  1502. // pan and zoom.
  1503. || !this._downPoint
  1504. // Arbitrary value
  1505. || dist(this._downPoint, [event.zrX, event.zrY]) > 4
  1506. ) {
  1507. return;
  1508. }
  1509. this._downPoint = null;
  1510. }
  1511. this.dispatchToElement(hovered, name, event);
  1512. };
  1513. });
  1514. function isHover(displayable, x, y) {
  1515. if (displayable[displayable.rectHover ? 'rectContain' : 'contain'](x, y)) {
  1516. var el = displayable;
  1517. var isSilent;
  1518. while (el) {
  1519. // If clipped by ancestor.
  1520. // FIXME: If clipPath has neither stroke nor fill,
  1521. // el.clipPath.contain(x, y) will always return false.
  1522. if (el.clipPath && !el.clipPath.contain(x, y)) {
  1523. return false;
  1524. }
  1525. if (el.silent) {
  1526. isSilent = true;
  1527. }
  1528. el = el.parent;
  1529. }
  1530. return isSilent ? SILENT : true;
  1531. }
  1532. return false;
  1533. }
  1534. mixin(Handler, Eventful);
  1535. mixin(Handler, Draggable);
  1536. /**
  1537. * 3x2矩阵操作类
  1538. * @exports zrender/tool/matrix
  1539. */
  1540. var ArrayCtor$1 = typeof Float32Array === 'undefined'
  1541. ? Array
  1542. : Float32Array;
  1543. /**
  1544. * Create a identity matrix.
  1545. * @return {Float32Array|Array.<number>}
  1546. */
  1547. function create$1() {
  1548. var out = new ArrayCtor$1(6);
  1549. identity(out);
  1550. return out;
  1551. }
  1552. /**
  1553. * 设置矩阵为单位矩阵
  1554. * @param {Float32Array|Array.<number>} out
  1555. */
  1556. function identity(out) {
  1557. out[0] = 1;
  1558. out[1] = 0;
  1559. out[2] = 0;
  1560. out[3] = 1;
  1561. out[4] = 0;
  1562. out[5] = 0;
  1563. return out;
  1564. }
  1565. /**
  1566. * 复制矩阵
  1567. * @param {Float32Array|Array.<number>} out
  1568. * @param {Float32Array|Array.<number>} m
  1569. */
  1570. function copy$1(out, m) {
  1571. out[0] = m[0];
  1572. out[1] = m[1];
  1573. out[2] = m[2];
  1574. out[3] = m[3];
  1575. out[4] = m[4];
  1576. out[5] = m[5];
  1577. return out;
  1578. }
  1579. /**
  1580. * 矩阵相乘
  1581. * @param {Float32Array|Array.<number>} out
  1582. * @param {Float32Array|Array.<number>} m1
  1583. * @param {Float32Array|Array.<number>} m2
  1584. */
  1585. function mul$1(out, m1, m2) {
  1586. // Consider matrix.mul(m, m2, m);
  1587. // where out is the same as m2.
  1588. // So use temp variable to escape error.
  1589. var out0 = m1[0] * m2[0] + m1[2] * m2[1];
  1590. var out1 = m1[1] * m2[0] + m1[3] * m2[1];
  1591. var out2 = m1[0] * m2[2] + m1[2] * m2[3];
  1592. var out3 = m1[1] * m2[2] + m1[3] * m2[3];
  1593. var out4 = m1[0] * m2[4] + m1[2] * m2[5] + m1[4];
  1594. var out5 = m1[1] * m2[4] + m1[3] * m2[5] + m1[5];
  1595. out[0] = out0;
  1596. out[1] = out1;
  1597. out[2] = out2;
  1598. out[3] = out3;
  1599. out[4] = out4;
  1600. out[5] = out5;
  1601. return out;
  1602. }
  1603. /**
  1604. * 平移变换
  1605. * @param {Float32Array|Array.<number>} out
  1606. * @param {Float32Array|Array.<number>} a
  1607. * @param {Float32Array|Array.<number>} v
  1608. */
  1609. function translate(out, a, v) {
  1610. out[0] = a[0];
  1611. out[1] = a[1];
  1612. out[2] = a[2];
  1613. out[3] = a[3];
  1614. out[4] = a[4] + v[0];
  1615. out[5] = a[5] + v[1];
  1616. return out;
  1617. }
  1618. /**
  1619. * 旋转变换
  1620. * @param {Float32Array|Array.<number>} out
  1621. * @param {Float32Array|Array.<number>} a
  1622. * @param {number} rad
  1623. */
  1624. function rotate(out, a, rad) {
  1625. var aa = a[0];
  1626. var ac = a[2];
  1627. var atx = a[4];
  1628. var ab = a[1];
  1629. var ad = a[3];
  1630. var aty = a[5];
  1631. var st = Math.sin(rad);
  1632. var ct = Math.cos(rad);
  1633. out[0] = aa * ct + ab * st;
  1634. out[1] = -aa * st + ab * ct;
  1635. out[2] = ac * ct + ad * st;
  1636. out[3] = -ac * st + ct * ad;
  1637. out[4] = ct * atx + st * aty;
  1638. out[5] = ct * aty - st * atx;
  1639. return out;
  1640. }
  1641. /**
  1642. * 缩放变换
  1643. * @param {Float32Array|Array.<number>} out
  1644. * @param {Float32Array|Array.<number>} a
  1645. * @param {Float32Array|Array.<number>} v
  1646. */
  1647. function scale$1(out, a, v) {
  1648. var vx = v[0];
  1649. var vy = v[1];
  1650. out[0] = a[0] * vx;
  1651. out[1] = a[1] * vy;
  1652. out[2] = a[2] * vx;
  1653. out[3] = a[3] * vy;
  1654. out[4] = a[4] * vx;
  1655. out[5] = a[5] * vy;
  1656. return out;
  1657. }
  1658. /**
  1659. * 求逆矩阵
  1660. * @param {Float32Array|Array.<number>} out
  1661. * @param {Float32Array|Array.<number>} a
  1662. */
  1663. function invert(out, a) {
  1664. var aa = a[0];
  1665. var ac = a[2];
  1666. var atx = a[4];
  1667. var ab = a[1];
  1668. var ad = a[3];
  1669. var aty = a[5];
  1670. var det = aa * ad - ab * ac;
  1671. if (!det) {
  1672. return null;
  1673. }
  1674. det = 1.0 / det;
  1675. out[0] = ad * det;
  1676. out[1] = -ab * det;
  1677. out[2] = -ac * det;
  1678. out[3] = aa * det;
  1679. out[4] = (ac * aty - ad * atx) * det;
  1680. out[5] = (ab * atx - aa * aty) * det;
  1681. return out;
  1682. }
  1683. /**
  1684. * Clone a new matrix.
  1685. * @param {Float32Array|Array.<number>} a
  1686. */
  1687. /**
  1688. * 提供变换扩展
  1689. * @module zrender/mixin/Transformable
  1690. * @author pissang (https://www.github.com/pissang)
  1691. */
  1692. var mIdentity = identity;
  1693. var EPSILON = 5e-5;
  1694. function isNotAroundZero(val) {
  1695. return val > EPSILON || val < -EPSILON;
  1696. }
  1697. /**
  1698. * @alias module:zrender/mixin/Transformable
  1699. * @constructor
  1700. */
  1701. var Transformable = function (opts) {
  1702. opts = opts || {};
  1703. // If there are no given position, rotation, scale
  1704. if (!opts.position) {
  1705. /**
  1706. * 平移
  1707. * @type {Array.<number>}
  1708. * @default [0, 0]
  1709. */
  1710. this.position = [0, 0];
  1711. }
  1712. if (opts.rotation == null) {
  1713. /**
  1714. * 旋转
  1715. * @type {Array.<number>}
  1716. * @default 0
  1717. */
  1718. this.rotation = 0;
  1719. }
  1720. if (!opts.scale) {
  1721. /**
  1722. * 缩放
  1723. * @type {Array.<number>}
  1724. * @default [1, 1]
  1725. */
  1726. this.scale = [1, 1];
  1727. }
  1728. /**
  1729. * 旋转和缩放的原点
  1730. * @type {Array.<number>}
  1731. * @default null
  1732. */
  1733. this.origin = this.origin || null;
  1734. };
  1735. var transformableProto = Transformable.prototype;
  1736. transformableProto.transform = null;
  1737. /**
  1738. * 判断是否需要有坐标变换
  1739. * 如果有坐标变换, 则从position, rotation, scale以及父节点的transform计算出自身的transform矩阵
  1740. */
  1741. transformableProto.needLocalTransform = function () {
  1742. return isNotAroundZero(this.rotation)
  1743. || isNotAroundZero(this.position[0])
  1744. || isNotAroundZero(this.position[1])
  1745. || isNotAroundZero(this.scale[0] - 1)
  1746. || isNotAroundZero(this.scale[1] - 1);
  1747. };
  1748. transformableProto.updateTransform = function () {
  1749. var parent = this.parent;
  1750. var parentHasTransform = parent && parent.transform;
  1751. var needLocalTransform = this.needLocalTransform();
  1752. var m = this.transform;
  1753. if (!(needLocalTransform || parentHasTransform)) {
  1754. m && mIdentity(m);
  1755. return;
  1756. }
  1757. m = m || create$1();
  1758. if (needLocalTransform) {
  1759. this.getLocalTransform(m);
  1760. }
  1761. else {
  1762. mIdentity(m);
  1763. }
  1764. // 应用父节点变换
  1765. if (parentHasTransform) {
  1766. if (needLocalTransform) {
  1767. mul$1(m, parent.transform, m);
  1768. }
  1769. else {
  1770. copy$1(m, parent.transform);
  1771. }
  1772. }
  1773. // 保存这个变换矩阵
  1774. this.transform = m;
  1775. this.invTransform = this.invTransform || create$1();
  1776. invert(this.invTransform, m);
  1777. };
  1778. transformableProto.getLocalTransform = function (m) {
  1779. return Transformable.getLocalTransform(this, m);
  1780. };
  1781. /**
  1782. * 将自己的transform应用到context上
  1783. * @param {CanvasRenderingContext2D} ctx
  1784. */
  1785. transformableProto.setTransform = function (ctx) {
  1786. var m = this.transform;
  1787. var dpr = ctx.dpr || 1;
  1788. if (m) {
  1789. ctx.setTransform(dpr * m[0], dpr * m[1], dpr * m[2], dpr * m[3], dpr * m[4], dpr * m[5]);
  1790. }
  1791. else {
  1792. ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  1793. }
  1794. };
  1795. transformableProto.restoreTransform = function (ctx) {
  1796. var dpr = ctx.dpr || 1;
  1797. ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  1798. };
  1799. var tmpTransform = [];
  1800. /**
  1801. * 分解`transform`矩阵到`position`, `rotation`, `scale`
  1802. */
  1803. transformableProto.decomposeTransform = function () {
  1804. if (!this.transform) {
  1805. return;
  1806. }
  1807. var parent = this.parent;
  1808. var m = this.transform;
  1809. if (parent && parent.transform) {
  1810. // Get local transform and decompose them to position, scale, rotation
  1811. mul$1(tmpTransform, parent.invTransform, m);
  1812. m = tmpTransform;
  1813. }
  1814. var sx = m[0] * m[0] + m[1] * m[1];
  1815. var sy = m[2] * m[2] + m[3] * m[3];
  1816. var position = this.position;
  1817. var scale$$1 = this.scale;
  1818. if (isNotAroundZero(sx - 1)) {
  1819. sx = Math.sqrt(sx);
  1820. }
  1821. if (isNotAroundZero(sy - 1)) {
  1822. sy = Math.sqrt(sy);
  1823. }
  1824. if (m[0] < 0) {
  1825. sx = -sx;
  1826. }
  1827. if (m[3] < 0) {
  1828. sy = -sy;
  1829. }
  1830. position[0] = m[4];
  1831. position[1] = m[5];
  1832. scale$$1[0] = sx;
  1833. scale$$1[1] = sy;
  1834. this.rotation = Math.atan2(-m[1] / sy, m[0] / sx);
  1835. };
  1836. /**
  1837. * Get global scale
  1838. * @return {Array.<number>}
  1839. */
  1840. transformableProto.getGlobalScale = function () {
  1841. var m = this.transform;
  1842. if (!m) {
  1843. return [1, 1];
  1844. }
  1845. var sx = Math.sqrt(m[0] * m[0] + m[1] * m[1]);
  1846. var sy = Math.sqrt(m[2] * m[2] + m[3] * m[3]);
  1847. if (m[0] < 0) {
  1848. sx = -sx;
  1849. }
  1850. if (m[3] < 0) {
  1851. sy = -sy;
  1852. }
  1853. return [sx, sy];
  1854. };
  1855. /**
  1856. * 变换坐标位置到 shape 的局部坐标空间
  1857. * @method
  1858. * @param {number} x
  1859. * @param {number} y
  1860. * @return {Array.<number>}
  1861. */
  1862. transformableProto.transformCoordToLocal = function (x, y) {
  1863. var v2 = [x, y];
  1864. var invTransform = this.invTransform;
  1865. if (invTransform) {
  1866. applyTransform(v2, v2, invTransform);
  1867. }
  1868. return v2;
  1869. };
  1870. /**
  1871. * 变换局部坐标位置到全局坐标空间
  1872. * @method
  1873. * @param {number} x
  1874. * @param {number} y
  1875. * @return {Array.<number>}
  1876. */
  1877. transformableProto.transformCoordToGlobal = function (x, y) {
  1878. var v2 = [x, y];
  1879. var transform = this.transform;
  1880. if (transform) {
  1881. applyTransform(v2, v2, transform);
  1882. }
  1883. return v2;
  1884. };
  1885. /**
  1886. * @static
  1887. * @param {Object} target
  1888. * @param {Array.<number>} target.origin
  1889. * @param {number} target.rotation
  1890. * @param {Array.<number>} target.position
  1891. * @param {Array.<number>} [m]
  1892. */
  1893. Transformable.getLocalTransform = function (target, m) {
  1894. m = m || [];
  1895. mIdentity(m);
  1896. var origin = target.origin;
  1897. var scale$$1 = target.scale || [1, 1];
  1898. var rotation = target.rotation || 0;
  1899. var position = target.position || [0, 0];
  1900. if (origin) {
  1901. // Translate to origin
  1902. m[4] -= origin[0];
  1903. m[5] -= origin[1];
  1904. }
  1905. scale$1(m, m, scale$$1);
  1906. if (rotation) {
  1907. rotate(m, m, rotation);
  1908. }
  1909. if (origin) {
  1910. // Translate back from origin
  1911. m[4] += origin[0];
  1912. m[5] += origin[1];
  1913. }
  1914. m[4] += position[0];
  1915. m[5] += position[1];
  1916. return m;
  1917. };
  1918. /**
  1919. * 缓动代码来自 https://github.com/sole/tween.js/blob/master/src/Tween.js
  1920. * @see http://sole.github.io/tween.js/examples/03_graphs.html
  1921. * @exports zrender/animation/easing
  1922. */
  1923. var easing = {
  1924. /**
  1925. * @param {number} k
  1926. * @return {number}
  1927. */
  1928. linear: function (k) {
  1929. return k;
  1930. },
  1931. /**
  1932. * @param {number} k
  1933. * @return {number}
  1934. */
  1935. quadraticIn: function (k) {
  1936. return k * k;
  1937. },
  1938. /**
  1939. * @param {number} k
  1940. * @return {number}
  1941. */
  1942. quadraticOut: function (k) {
  1943. return k * (2 - k);
  1944. },
  1945. /**
  1946. * @param {number} k
  1947. * @return {number}
  1948. */
  1949. quadraticInOut: function (k) {
  1950. if ((k *= 2) < 1) {
  1951. return 0.5 * k * k;
  1952. }
  1953. return -0.5 * (--k * (k - 2) - 1);
  1954. },
  1955. // 三次方的缓动(t^3)
  1956. /**
  1957. * @param {number} k
  1958. * @return {number}
  1959. */
  1960. cubicIn: function (k) {
  1961. return k * k * k;
  1962. },
  1963. /**
  1964. * @param {number} k
  1965. * @return {number}
  1966. */
  1967. cubicOut: function (k) {
  1968. return --k * k * k + 1;
  1969. },
  1970. /**
  1971. * @param {number} k
  1972. * @return {number}
  1973. */
  1974. cubicInOut: function (k) {
  1975. if ((k *= 2) < 1) {
  1976. return 0.5 * k * k * k;
  1977. }
  1978. return 0.5 * ((k -= 2) * k * k + 2);
  1979. },
  1980. // 四次方的缓动(t^4)
  1981. /**
  1982. * @param {number} k
  1983. * @return {number}
  1984. */
  1985. quarticIn: function (k) {
  1986. return k * k * k * k;
  1987. },
  1988. /**
  1989. * @param {number} k
  1990. * @return {number}
  1991. */
  1992. quarticOut: function (k) {
  1993. return 1 - (--k * k * k * k);
  1994. },
  1995. /**
  1996. * @param {number} k
  1997. * @return {number}
  1998. */
  1999. quarticInOut: function (k) {
  2000. if ((k *= 2) < 1) {
  2001. return 0.5 * k * k * k * k;
  2002. }
  2003. return -0.5 * ((k -= 2) * k * k * k - 2);
  2004. },
  2005. // 五次方的缓动(t^5)
  2006. /**
  2007. * @param {number} k
  2008. * @return {number}
  2009. */
  2010. quinticIn: function (k) {
  2011. return k * k * k * k * k;
  2012. },
  2013. /**
  2014. * @param {number} k
  2015. * @return {number}
  2016. */
  2017. quinticOut: function (k) {
  2018. return --k * k * k * k * k + 1;
  2019. },
  2020. /**
  2021. * @param {number} k
  2022. * @return {number}
  2023. */
  2024. quinticInOut: function (k) {
  2025. if ((k *= 2) < 1) {
  2026. return 0.5 * k * k * k * k * k;
  2027. }
  2028. return 0.5 * ((k -= 2) * k * k * k * k + 2);
  2029. },
  2030. // 正弦曲线的缓动(sin(t))
  2031. /**
  2032. * @param {number} k
  2033. * @return {number}
  2034. */
  2035. sinusoidalIn: function (k) {
  2036. return 1 - Math.cos(k * Math.PI / 2);
  2037. },
  2038. /**
  2039. * @param {number} k
  2040. * @return {number}
  2041. */
  2042. sinusoidalOut: function (k) {
  2043. return Math.sin(k * Math.PI / 2);
  2044. },
  2045. /**
  2046. * @param {number} k
  2047. * @return {number}
  2048. */
  2049. sinusoidalInOut: function (k) {
  2050. return 0.5 * (1 - Math.cos(Math.PI * k));
  2051. },
  2052. // 指数曲线的缓动(2^t)
  2053. /**
  2054. * @param {number} k
  2055. * @return {number}
  2056. */
  2057. exponentialIn: function (k) {
  2058. return k === 0 ? 0 : Math.pow(1024, k - 1);
  2059. },
  2060. /**
  2061. * @param {number} k
  2062. * @return {number}
  2063. */
  2064. exponentialOut: function (k) {
  2065. return k === 1 ? 1 : 1 - Math.pow(2, -10 * k);
  2066. },
  2067. /**
  2068. * @param {number} k
  2069. * @return {number}
  2070. */
  2071. exponentialInOut: function (k) {
  2072. if (k === 0) {
  2073. return 0;
  2074. }
  2075. if (k === 1) {
  2076. return 1;
  2077. }
  2078. if ((k *= 2) < 1) {
  2079. return 0.5 * Math.pow(1024, k - 1);
  2080. }
  2081. return 0.5 * (-Math.pow(2, -10 * (k - 1)) + 2);
  2082. },
  2083. // 圆形曲线的缓动(sqrt(1-t^2))
  2084. /**
  2085. * @param {number} k
  2086. * @return {number}
  2087. */
  2088. circularIn: function (k) {
  2089. return 1 - Math.sqrt(1 - k * k);
  2090. },
  2091. /**
  2092. * @param {number} k
  2093. * @return {number}
  2094. */
  2095. circularOut: function (k) {
  2096. return Math.sqrt(1 - (--k * k));
  2097. },
  2098. /**
  2099. * @param {number} k
  2100. * @return {number}
  2101. */
  2102. circularInOut: function (k) {
  2103. if ((k *= 2) < 1) {
  2104. return -0.5 * (Math.sqrt(1 - k * k) - 1);
  2105. }
  2106. return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1);
  2107. },
  2108. // 创建类似于弹簧在停止前来回振荡的动画
  2109. /**
  2110. * @param {number} k
  2111. * @return {number}
  2112. */
  2113. elasticIn: function (k) {
  2114. var s;
  2115. var a = 0.1;
  2116. var p = 0.4;
  2117. if (k === 0) {
  2118. return 0;
  2119. }
  2120. if (k === 1) {
  2121. return 1;
  2122. }
  2123. if (!a || a < 1) {
  2124. a = 1; s = p / 4;
  2125. }
  2126. else {
  2127. s = p * Math.asin(1 / a) / (2 * Math.PI);
  2128. }
  2129. return -(a * Math.pow(2, 10 * (k -= 1)) *
  2130. Math.sin((k - s) * (2 * Math.PI) / p));
  2131. },
  2132. /**
  2133. * @param {number} k
  2134. * @return {number}
  2135. */
  2136. elasticOut: function (k) {
  2137. var s;
  2138. var a = 0.1;
  2139. var p = 0.4;
  2140. if (k === 0) {
  2141. return 0;
  2142. }
  2143. if (k === 1) {
  2144. return 1;
  2145. }
  2146. if (!a || a < 1) {
  2147. a = 1; s = p / 4;
  2148. }
  2149. else {
  2150. s = p * Math.asin(1 / a) / (2 * Math.PI);
  2151. }
  2152. return (a * Math.pow(2, -10 * k) *
  2153. Math.sin((k - s) * (2 * Math.PI) / p) + 1);
  2154. },
  2155. /**
  2156. * @param {number} k
  2157. * @return {number}
  2158. */
  2159. elasticInOut: function (k) {
  2160. var s;
  2161. var a = 0.1;
  2162. var p = 0.4;
  2163. if (k === 0) {
  2164. return 0;
  2165. }
  2166. if (k === 1) {
  2167. return 1;
  2168. }
  2169. if (!a || a < 1) {
  2170. a = 1; s = p / 4;
  2171. }
  2172. else {
  2173. s = p * Math.asin(1 / a) / (2 * Math.PI);
  2174. }
  2175. if ((k *= 2) < 1) {
  2176. return -0.5 * (a * Math.pow(2, 10 * (k -= 1))
  2177. * Math.sin((k - s) * (2 * Math.PI) / p));
  2178. }
  2179. return a * Math.pow(2, -10 * (k -= 1))
  2180. * Math.sin((k - s) * (2 * Math.PI) / p) * 0.5 + 1;
  2181. },
  2182. // 在某一动画开始沿指示的路径进行动画处理前稍稍收回该动画的移动
  2183. /**
  2184. * @param {number} k
  2185. * @return {number}
  2186. */
  2187. backIn: function (k) {
  2188. var s = 1.70158;
  2189. return k * k * ((s + 1) * k - s);
  2190. },
  2191. /**
  2192. * @param {number} k
  2193. * @return {number}
  2194. */
  2195. backOut: function (k) {
  2196. var s = 1.70158;
  2197. return --k * k * ((s + 1) * k + s) + 1;
  2198. },
  2199. /**
  2200. * @param {number} k
  2201. * @return {number}
  2202. */
  2203. backInOut: function (k) {
  2204. var s = 1.70158 * 1.525;
  2205. if ((k *= 2) < 1) {
  2206. return 0.5 * (k * k * ((s + 1) * k - s));
  2207. }
  2208. return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2);
  2209. },
  2210. // 创建弹跳效果
  2211. /**
  2212. * @param {number} k
  2213. * @return {number}
  2214. */
  2215. bounceIn: function (k) {
  2216. return 1 - easing.bounceOut(1 - k);
  2217. },
  2218. /**
  2219. * @param {number} k
  2220. * @return {number}
  2221. */
  2222. bounceOut: function (k) {
  2223. if (k < (1 / 2.75)) {
  2224. return 7.5625 * k * k;
  2225. }
  2226. else if (k < (2 / 2.75)) {
  2227. return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75;
  2228. }
  2229. else if (k < (2.5 / 2.75)) {
  2230. return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375;
  2231. }
  2232. else {
  2233. return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375;
  2234. }
  2235. },
  2236. /**
  2237. * @param {number} k
  2238. * @return {number}
  2239. */
  2240. bounceInOut: function (k) {
  2241. if (k < 0.5) {
  2242. return easing.bounceIn(k * 2) * 0.5;
  2243. }
  2244. return easing.bounceOut(k * 2 - 1) * 0.5 + 0.5;
  2245. }
  2246. };
  2247. /**
  2248. * 动画主控制器
  2249. * @config target 动画对象,可以是数组,如果是数组的话会批量分发onframe等事件
  2250. * @config life(1000) 动画时长
  2251. * @config delay(0) 动画延迟时间
  2252. * @config loop(true)
  2253. * @config gap(0) 循环的间隔时间
  2254. * @config onframe
  2255. * @config easing(optional)
  2256. * @config ondestroy(optional)
  2257. * @config onrestart(optional)
  2258. *
  2259. * TODO pause
  2260. */
  2261. function Clip(options) {
  2262. this._target = options.target;
  2263. // 生命周期
  2264. this._life = options.life || 1000;
  2265. // 延时
  2266. this._delay = options.delay || 0;
  2267. // 开始时间
  2268. // this._startTime = new Date().getTime() + this._delay;// 单位毫秒
  2269. this._initialized = false;
  2270. // 是否循环
  2271. this.loop = options.loop == null ? false : options.loop;
  2272. this.gap = options.gap || 0;
  2273. this.easing = options.easing || 'Linear';
  2274. this.onframe = options.onframe;
  2275. this.ondestroy = options.ondestroy;
  2276. this.onrestart = options.onrestart;
  2277. this._pausedTime = 0;
  2278. this._paused = false;
  2279. }
  2280. Clip.prototype = {
  2281. constructor: Clip,
  2282. step: function (globalTime, deltaTime) {
  2283. // Set startTime on first step, or _startTime may has milleseconds different between clips
  2284. // PENDING
  2285. if (!this._initialized) {
  2286. this._startTime = globalTime + this._delay;
  2287. this._initialized = true;
  2288. }
  2289. if (this._paused) {
  2290. this._pausedTime += deltaTime;
  2291. return;
  2292. }
  2293. var percent = (globalTime - this._startTime - this._pausedTime) / this._life;
  2294. // 还没开始
  2295. if (percent < 0) {
  2296. return;
  2297. }
  2298. percent = Math.min(percent, 1);
  2299. var easing$$1 = this.easing;
  2300. var easingFunc = typeof easing$$1 == 'string' ? easing[easing$$1] : easing$$1;
  2301. var schedule = typeof easingFunc === 'function'
  2302. ? easingFunc(percent)
  2303. : percent;
  2304. this.fire('frame', schedule);
  2305. // 结束
  2306. if (percent == 1) {
  2307. if (this.loop) {
  2308. this.restart (globalTime);
  2309. // 重新开始周期
  2310. // 抛出而不是直接调用事件直到 stage.update 后再统一调用这些事件
  2311. return 'restart';
  2312. }
  2313. // 动画完成将这个控制器标识为待删除
  2314. // 在Animation.update中进行批量删除
  2315. this._needsRemove = true;
  2316. return 'destroy';
  2317. }
  2318. return null;
  2319. },
  2320. restart: function (globalTime) {
  2321. var remainder = (globalTime - this._startTime - this._pausedTime) % this._life;
  2322. this._startTime = globalTime - remainder + this.gap;
  2323. this._pausedTime = 0;
  2324. this._needsRemove = false;
  2325. },
  2326. fire: function (eventType, arg) {
  2327. eventType = 'on' + eventType;
  2328. if (this[eventType]) {
  2329. this[eventType](this._target, arg);
  2330. }
  2331. },
  2332. pause: function () {
  2333. this._paused = true;
  2334. },
  2335. resume: function () {
  2336. this._paused = false;
  2337. }
  2338. };
  2339. // Simple LRU cache use doubly linked list
  2340. // @module zrender/core/LRU
  2341. /**
  2342. * Simple double linked list. Compared with array, it has O(1) remove operation.
  2343. * @constructor
  2344. */
  2345. var LinkedList = function () {
  2346. /**
  2347. * @type {module:zrender/core/LRU~Entry}
  2348. */
  2349. this.head = null;
  2350. /**
  2351. * @type {module:zrender/core/LRU~Entry}
  2352. */
  2353. this.tail = null;
  2354. this._len = 0;
  2355. };
  2356. var linkedListProto = LinkedList.prototype;
  2357. /**
  2358. * Insert a new value at the tail
  2359. * @param {} val
  2360. * @return {module:zrender/core/LRU~Entry}
  2361. */
  2362. linkedListProto.insert = function (val) {
  2363. var entry = new Entry(val);
  2364. this.insertEntry(entry);
  2365. return entry;
  2366. };
  2367. /**
  2368. * Insert an entry at the tail
  2369. * @param {module:zrender/core/LRU~Entry} entry
  2370. */
  2371. linkedListProto.insertEntry = function (entry) {
  2372. if (!this.head) {
  2373. this.head = this.tail = entry;
  2374. }
  2375. else {
  2376. this.tail.next = entry;
  2377. entry.prev = this.tail;
  2378. entry.next = null;
  2379. this.tail = entry;
  2380. }
  2381. this._len++;
  2382. };
  2383. /**
  2384. * Remove entry.
  2385. * @param {module:zrender/core/LRU~Entry} entry
  2386. */
  2387. linkedListProto.remove = function (entry) {
  2388. var prev = entry.prev;
  2389. var next = entry.next;
  2390. if (prev) {
  2391. prev.next = next;
  2392. }
  2393. else {
  2394. // Is head
  2395. this.head = next;
  2396. }
  2397. if (next) {
  2398. next.prev = prev;
  2399. }
  2400. else {
  2401. // Is tail
  2402. this.tail = prev;
  2403. }
  2404. entry.next = entry.prev = null;
  2405. this._len--;
  2406. };
  2407. /**
  2408. * @return {number}
  2409. */
  2410. linkedListProto.len = function () {
  2411. return this._len;
  2412. };
  2413. /**
  2414. * Clear list
  2415. */
  2416. linkedListProto.clear = function () {
  2417. this.head = this.tail = null;
  2418. this._len = 0;
  2419. };
  2420. /**
  2421. * @constructor
  2422. * @param {} val
  2423. */
  2424. var Entry = function (val) {
  2425. /**
  2426. * @type {}
  2427. */
  2428. this.value = val;
  2429. /**
  2430. * @type {module:zrender/core/LRU~Entry}
  2431. */
  2432. this.next;
  2433. /**
  2434. * @type {module:zrender/core/LRU~Entry}
  2435. */
  2436. this.prev;
  2437. };
  2438. /**
  2439. * LRU Cache
  2440. * @constructor
  2441. * @alias module:zrender/core/LRU
  2442. */
  2443. var LRU = function (maxSize) {
  2444. this._list = new LinkedList();
  2445. this._map = {};
  2446. this._maxSize = maxSize || 10;
  2447. this._lastRemovedEntry = null;
  2448. };
  2449. var LRUProto = LRU.prototype;
  2450. /**
  2451. * @param {string} key
  2452. * @param {} value
  2453. * @return {} Removed value
  2454. */
  2455. LRUProto.put = function (key, value) {
  2456. var list = this._list;
  2457. var map = this._map;
  2458. var removed = null;
  2459. if (map[key] == null) {
  2460. var len = list.len();
  2461. // Reuse last removed entry
  2462. var entry = this._lastRemovedEntry;
  2463. if (len >= this._maxSize && len > 0) {
  2464. // Remove the least recently used
  2465. var leastUsedEntry = list.head;
  2466. list.remove(leastUsedEntry);
  2467. delete map[leastUsedEntry.key];
  2468. removed = leastUsedEntry.value;
  2469. this._lastRemovedEntry = leastUsedEntry;
  2470. }
  2471. if (entry) {
  2472. entry.value = value;
  2473. }
  2474. else {
  2475. entry = new Entry(value);
  2476. }
  2477. entry.key = key;
  2478. list.insertEntry(entry);
  2479. map[key] = entry;
  2480. }
  2481. return removed;
  2482. };
  2483. /**
  2484. * @param {string} key
  2485. * @return {}
  2486. */
  2487. LRUProto.get = function (key) {
  2488. var entry = this._map[key];
  2489. var list = this._list;
  2490. if (entry != null) {
  2491. // Put the latest used entry in the tail
  2492. if (entry !== list.tail) {
  2493. list.remove(entry);
  2494. list.insertEntry(entry);
  2495. }
  2496. return entry.value;
  2497. }
  2498. };
  2499. /**
  2500. * Clear the cache
  2501. */
  2502. LRUProto.clear = function () {
  2503. this._list.clear();
  2504. this._map = {};
  2505. };
  2506. var kCSSColorTable = {
  2507. 'transparent': [0,0,0,0], 'aliceblue': [240,248,255,1],
  2508. 'antiquewhite': [250,235,215,1], 'aqua': [0,255,255,1],
  2509. 'aquamarine': [127,255,212,1], 'azure': [240,255,255,1],
  2510. 'beige': [245,245,220,1], 'bisque': [255,228,196,1],
  2511. 'black': [0,0,0,1], 'blanchedalmond': [255,235,205,1],
  2512. 'blue': [0,0,255,1], 'blueviolet': [138,43,226,1],
  2513. 'brown': [165,42,42,1], 'burlywood': [222,184,135,1],
  2514. 'cadetblue': [95,158,160,1], 'chartreuse': [127,255,0,1],
  2515. 'chocolate': [210,105,30,1], 'coral': [255,127,80,1],
  2516. 'cornflowerblue': [100,149,237,1], 'cornsilk': [255,248,220,1],
  2517. 'crimson': [220,20,60,1], 'cyan': [0,255,255,1],
  2518. 'darkblue': [0,0,139,1], 'darkcyan': [0,139,139,1],
  2519. 'darkgoldenrod': [184,134,11,1], 'darkgray': [169,169,169,1],
  2520. 'darkgreen': [0,100,0,1], 'darkgrey': [169,169,169,1],
  2521. 'darkkhaki': [189,183,107,1], 'darkmagenta': [139,0,139,1],
  2522. 'darkolivegreen': [85,107,47,1], 'darkorange': [255,140,0,1],
  2523. 'darkorchid': [153,50,204,1], 'darkred': [139,0,0,1],
  2524. 'darksalmon': [233,150,122,1], 'darkseagreen': [143,188,143,1],
  2525. 'darkslateblue': [72,61,139,1], 'darkslategray': [47,79,79,1],
  2526. 'darkslategrey': [47,79,79,1], 'darkturquoise': [0,206,209,1],
  2527. 'darkviolet': [148,0,211,1], 'deeppink': [255,20,147,1],
  2528. 'deepskyblue': [0,191,255,1], 'dimgray': [105,105,105,1],
  2529. 'dimgrey': [105,105,105,1], 'dodgerblue': [30,144,255,1],
  2530. 'firebrick': [178,34,34,1], 'floralwhite': [255,250,240,1],
  2531. 'forestgreen': [34,139,34,1], 'fuchsia': [255,0,255,1],
  2532. 'gainsboro': [220,220,220,1], 'ghostwhite': [248,248,255,1],
  2533. 'gold': [255,215,0,1], 'goldenrod': [218,165,32,1],
  2534. 'gray': [128,128,128,1], 'green': [0,128,0,1],
  2535. 'greenyellow': [173,255,47,1], 'grey': [128,128,128,1],
  2536. 'honeydew': [240,255,240,1], 'hotpink': [255,105,180,1],
  2537. 'indianred': [205,92,92,1], 'indigo': [75,0,130,1],
  2538. 'ivory': [255,255,240,1], 'khaki': [240,230,140,1],
  2539. 'lavender': [230,230,250,1], 'lavenderblush': [255,240,245,1],
  2540. 'lawngreen': [124,252,0,1], 'lemonchiffon': [255,250,205,1],
  2541. 'lightblue': [173,216,230,1], 'lightcoral': [240,128,128,1],
  2542. 'lightcyan': [224,255,255,1], 'lightgoldenrodyellow': [250,250,210,1],
  2543. 'lightgray': [211,211,211,1], 'lightgreen': [144,238,144,1],
  2544. 'lightgrey': [211,211,211,1], 'lightpink': [255,182,193,1],
  2545. 'lightsalmon': [255,160,122,1], 'lightseagreen': [32,178,170,1],
  2546. 'lightskyblue': [135,206,250,1], 'lightslategray': [119,136,153,1],
  2547. 'lightslategrey': [119,136,153,1], 'lightsteelblue': [176,196,222,1],
  2548. 'lightyellow': [255,255,224,1], 'lime': [0,255,0,1],
  2549. 'limegreen': [50,205,50,1], 'linen': [250,240,230,1],
  2550. 'magenta': [255,0,255,1], 'maroon': [128,0,0,1],
  2551. 'mediumaquamarine': [102,205,170,1], 'mediumblue': [0,0,205,1],
  2552. 'mediumorchid': [186,85,211,1], 'mediumpurple': [147,112,219,1],
  2553. 'mediumseagreen': [60,179,113,1], 'mediumslateblue': [123,104,238,1],
  2554. 'mediumspringgreen': [0,250,154,1], 'mediumturquoise': [72,209,204,1],
  2555. 'mediumvioletred': [199,21,133,1], 'midnightblue': [25,25,112,1],
  2556. 'mintcream': [245,255,250,1], 'mistyrose': [255,228,225,1],
  2557. 'moccasin': [255,228,181,1], 'navajowhite': [255,222,173,1],
  2558. 'navy': [0,0,128,1], 'oldlace': [253,245,230,1],
  2559. 'olive': [128,128,0,1], 'olivedrab': [107,142,35,1],
  2560. 'orange': [255,165,0,1], 'orangered': [255,69,0,1],
  2561. 'orchid': [218,112,214,1], 'palegoldenrod': [238,232,170,1],
  2562. 'palegreen': [152,251,152,1], 'paleturquoise': [175,238,238,1],
  2563. 'palevioletred': [219,112,147,1], 'papayawhip': [255,239,213,1],
  2564. 'peachpuff': [255,218,185,1], 'peru': [205,133,63,1],
  2565. 'pink': [255,192,203,1], 'plum': [221,160,221,1],
  2566. 'powderblue': [176,224,230,1], 'purple': [128,0,128,1],
  2567. 'red': [255,0,0,1], 'rosybrown': [188,143,143,1],
  2568. 'royalblue': [65,105,225,1], 'saddlebrown': [139,69,19,1],
  2569. 'salmon': [250,128,114,1], 'sandybrown': [244,164,96,1],
  2570. 'seagreen': [46,139,87,1], 'seashell': [255,245,238,1],
  2571. 'sienna': [160,82,45,1], 'silver': [192,192,192,1],
  2572. 'skyblue': [135,206,235,1], 'slateblue': [106,90,205,1],
  2573. 'slategray': [112,128,144,1], 'slategrey': [112,128,144,1],
  2574. 'snow': [255,250,250,1], 'springgreen': [0,255,127,1],
  2575. 'steelblue': [70,130,180,1], 'tan': [210,180,140,1],
  2576. 'teal': [0,128,128,1], 'thistle': [216,191,216,1],
  2577. 'tomato': [255,99,71,1], 'turquoise': [64,224,208,1],
  2578. 'violet': [238,130,238,1], 'wheat': [245,222,179,1],
  2579. 'white': [255,255,255,1], 'whitesmoke': [245,245,245,1],
  2580. 'yellow': [255,255,0,1], 'yellowgreen': [154,205,50,1]
  2581. };
  2582. function clampCssByte(i) { // Clamp to integer 0 .. 255.
  2583. i = Math.round(i); // Seems to be what Chrome does (vs truncation).
  2584. return i < 0 ? 0 : i > 255 ? 255 : i;
  2585. }
  2586. function clampCssFloat(f) { // Clamp to float 0.0 .. 1.0.
  2587. return f < 0 ? 0 : f > 1 ? 1 : f;
  2588. }
  2589. function parseCssInt(str) { // int or percentage.
  2590. if (str.length && str.charAt(str.length - 1) === '%') {
  2591. return clampCssByte(parseFloat(str) / 100 * 255);
  2592. }
  2593. return clampCssByte(parseInt(str, 10));
  2594. }
  2595. function parseCssFloat(str) { // float or percentage.
  2596. if (str.length && str.charAt(str.length - 1) === '%') {
  2597. return clampCssFloat(parseFloat(str) / 100);
  2598. }
  2599. return clampCssFloat(parseFloat(str));
  2600. }
  2601. function cssHueToRgb(m1, m2, h) {
  2602. if (h < 0) {
  2603. h += 1;
  2604. }
  2605. else if (h > 1) {
  2606. h -= 1;
  2607. }
  2608. if (h * 6 < 1) {
  2609. return m1 + (m2 - m1) * h * 6;
  2610. }
  2611. if (h * 2 < 1) {
  2612. return m2;
  2613. }
  2614. if (h * 3 < 2) {
  2615. return m1 + (m2 - m1) * (2/3 - h) * 6;
  2616. }
  2617. return m1;
  2618. }
  2619. function setRgba(out, r, g, b, a) {
  2620. out[0] = r; out[1] = g; out[2] = b; out[3] = a;
  2621. return out;
  2622. }
  2623. function copyRgba(out, a) {
  2624. out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3];
  2625. return out;
  2626. }
  2627. var colorCache = new LRU(20);
  2628. var lastRemovedArr = null;
  2629. function putToCache(colorStr, rgbaArr) {
  2630. // Reuse removed array
  2631. if (lastRemovedArr) {
  2632. copyRgba(lastRemovedArr, rgbaArr);
  2633. }
  2634. lastRemovedArr = colorCache.put(colorStr, lastRemovedArr || (rgbaArr.slice()));
  2635. }
  2636. /**
  2637. * @param {string} colorStr
  2638. * @param {Array.<number>} out
  2639. * @return {Array.<number>}
  2640. * @memberOf module:zrender/util/color
  2641. */
  2642. function parse(colorStr, rgbaArr) {
  2643. if (!colorStr) {
  2644. return;
  2645. }
  2646. rgbaArr = rgbaArr || [];
  2647. var cached = colorCache.get(colorStr);
  2648. if (cached) {
  2649. return copyRgba(rgbaArr, cached);
  2650. }
  2651. // colorStr may be not string
  2652. colorStr = colorStr + '';
  2653. // Remove all whitespace, not compliant, but should just be more accepting.
  2654. var str = colorStr.replace(/ /g, '').toLowerCase();
  2655. // Color keywords (and transparent) lookup.
  2656. if (str in kCSSColorTable) {
  2657. copyRgba(rgbaArr, kCSSColorTable[str]);
  2658. putToCache(colorStr, rgbaArr);
  2659. return rgbaArr;
  2660. }
  2661. // #abc and #abc123 syntax.
  2662. if (str.charAt(0) === '#') {
  2663. if (str.length === 4) {
  2664. var iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing.
  2665. if (!(iv >= 0 && iv <= 0xfff)) {
  2666. setRgba(rgbaArr, 0, 0, 0, 1);
  2667. return; // Covers NaN.
  2668. }
  2669. setRgba(rgbaArr,
  2670. ((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8),
  2671. (iv & 0xf0) | ((iv & 0xf0) >> 4),
  2672. (iv & 0xf) | ((iv & 0xf) << 4),
  2673. 1
  2674. );
  2675. putToCache(colorStr, rgbaArr);
  2676. return rgbaArr;
  2677. }
  2678. else if (str.length === 7) {
  2679. var iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing.
  2680. if (!(iv >= 0 && iv <= 0xffffff)) {
  2681. setRgba(rgbaArr, 0, 0, 0, 1);
  2682. return; // Covers NaN.
  2683. }
  2684. setRgba(rgbaArr,
  2685. (iv & 0xff0000) >> 16,
  2686. (iv & 0xff00) >> 8,
  2687. iv & 0xff,
  2688. 1
  2689. );
  2690. putToCache(colorStr, rgbaArr);
  2691. return rgbaArr;
  2692. }
  2693. return;
  2694. }
  2695. var op = str.indexOf('('), ep = str.indexOf(')');
  2696. if (op !== -1 && ep + 1 === str.length) {
  2697. var fname = str.substr(0, op);
  2698. var params = str.substr(op + 1, ep - (op + 1)).split(',');
  2699. var alpha = 1; // To allow case fallthrough.
  2700. switch (fname) {
  2701. case 'rgba':
  2702. if (params.length !== 4) {
  2703. setRgba(rgbaArr, 0, 0, 0, 1);
  2704. return;
  2705. }
  2706. alpha = parseCssFloat(params.pop()); // jshint ignore:line
  2707. // Fall through.
  2708. case 'rgb':
  2709. if (params.length !== 3) {
  2710. setRgba(rgbaArr, 0, 0, 0, 1);
  2711. return;
  2712. }
  2713. setRgba(rgbaArr,
  2714. parseCssInt(params[0]),
  2715. parseCssInt(params[1]),
  2716. parseCssInt(params[2]),
  2717. alpha
  2718. );
  2719. putToCache(colorStr, rgbaArr);
  2720. return rgbaArr;
  2721. case 'hsla':
  2722. if (params.length !== 4) {
  2723. setRgba(rgbaArr, 0, 0, 0, 1);
  2724. return;
  2725. }
  2726. params[3] = parseCssFloat(params[3]);
  2727. hsla2rgba(params, rgbaArr);
  2728. putToCache(colorStr, rgbaArr);
  2729. return rgbaArr;
  2730. case 'hsl':
  2731. if (params.length !== 3) {
  2732. setRgba(rgbaArr, 0, 0, 0, 1);
  2733. return;
  2734. }
  2735. hsla2rgba(params, rgbaArr);
  2736. putToCache(colorStr, rgbaArr);
  2737. return rgbaArr;
  2738. default:
  2739. return;
  2740. }
  2741. }
  2742. setRgba(rgbaArr, 0, 0, 0, 1);
  2743. return;
  2744. }
  2745. /**
  2746. * @param {Array.<number>} hsla
  2747. * @param {Array.<number>} rgba
  2748. * @return {Array.<number>} rgba
  2749. */
  2750. function hsla2rgba(hsla, rgba) {
  2751. var h = (((parseFloat(hsla[0]) % 360) + 360) % 360) / 360; // 0 .. 1
  2752. // NOTE(deanm): According to the CSS spec s/l should only be
  2753. // percentages, but we don't bother and let float or percentage.
  2754. var s = parseCssFloat(hsla[1]);
  2755. var l = parseCssFloat(hsla[2]);
  2756. var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
  2757. var m1 = l * 2 - m2;
  2758. rgba = rgba || [];
  2759. setRgba(rgba,
  2760. clampCssByte(cssHueToRgb(m1, m2, h + 1 / 3) * 255),
  2761. clampCssByte(cssHueToRgb(m1, m2, h) * 255),
  2762. clampCssByte(cssHueToRgb(m1, m2, h - 1 / 3) * 255),
  2763. 1
  2764. );
  2765. if (hsla.length === 4) {
  2766. rgba[3] = hsla[3];
  2767. }
  2768. return rgba;
  2769. }
  2770. /**
  2771. * @param {string} color
  2772. * @param {number} level
  2773. * @return {string}
  2774. * @memberOf module:zrender/util/color
  2775. */
  2776. function lift(color, level) {
  2777. var colorArr = parse(color);
  2778. if (colorArr) {
  2779. for (var i = 0; i < 3; i++) {
  2780. if (level < 0) {
  2781. colorArr[i] = colorArr[i] * (1 - level) | 0;
  2782. }
  2783. else {
  2784. colorArr[i] = ((255 - colorArr[i]) * level + colorArr[i]) | 0;
  2785. }
  2786. if (colorArr[i] > 255) {
  2787. colorArr[i] = 255;
  2788. }
  2789. else if (color[i] < 0) {
  2790. colorArr[i] = 0;
  2791. }
  2792. }
  2793. return stringify(colorArr, colorArr.length === 4 ? 'rgba' : 'rgb');
  2794. }
  2795. }
  2796. /**
  2797. * @param {string} color
  2798. * @return {string}
  2799. * @memberOf module:zrender/util/color
  2800. */
  2801. /**
  2802. * Map value to color. Faster than lerp methods because color is represented by rgba array.
  2803. * @param {number} normalizedValue A float between 0 and 1.
  2804. * @param {Array.<Array.<number>>} colors List of rgba color array
  2805. * @param {Array.<number>} [out] Mapped gba color array
  2806. * @return {Array.<number>} will be null/undefined if input illegal.
  2807. */
  2808. /**
  2809. * @deprecated
  2810. */
  2811. /**
  2812. * @param {number} normalizedValue A float between 0 and 1.
  2813. * @param {Array.<string>} colors Color list.
  2814. * @param {boolean=} fullOutput Default false.
  2815. * @return {(string|Object)} Result color. If fullOutput,
  2816. * return {color: ..., leftIndex: ..., rightIndex: ..., value: ...},
  2817. * @memberOf module:zrender/util/color
  2818. */
  2819. /**
  2820. * @deprecated
  2821. */
  2822. /**
  2823. * @param {string} color
  2824. * @param {number=} h 0 ~ 360, ignore when null.
  2825. * @param {number=} s 0 ~ 1, ignore when null.
  2826. * @param {number=} l 0 ~ 1, ignore when null.
  2827. * @return {string} Color string in rgba format.
  2828. * @memberOf module:zrender/util/color
  2829. */
  2830. /**
  2831. * @param {string} color
  2832. * @param {number=} alpha 0 ~ 1
  2833. * @return {string} Color string in rgba format.
  2834. * @memberOf module:zrender/util/color
  2835. */
  2836. /**
  2837. * @param {Array.<number>} arrColor like [12,33,44,0.4]
  2838. * @param {string} type 'rgba', 'hsva', ...
  2839. * @return {string} Result color. (If input illegal, return undefined).
  2840. */
  2841. function stringify(arrColor, type) {
  2842. if (!arrColor || !arrColor.length) {
  2843. return;
  2844. }
  2845. var colorStr = arrColor[0] + ',' + arrColor[1] + ',' + arrColor[2];
  2846. if (type === 'rgba' || type === 'hsva' || type === 'hsla') {
  2847. colorStr += ',' + arrColor[3];
  2848. }
  2849. return type + '(' + colorStr + ')';
  2850. }
  2851. /**
  2852. * @module echarts/animation/Animator
  2853. */
  2854. var arraySlice = Array.prototype.slice;
  2855. function defaultGetter(target, key) {
  2856. return target[key];
  2857. }
  2858. function defaultSetter(target, key, value) {
  2859. target[key] = value;
  2860. }
  2861. /**
  2862. * @param {number} p0
  2863. * @param {number} p1
  2864. * @param {number} percent
  2865. * @return {number}
  2866. */
  2867. function interpolateNumber(p0, p1, percent) {
  2868. return (p1 - p0) * percent + p0;
  2869. }
  2870. /**
  2871. * @param {string} p0
  2872. * @param {string} p1
  2873. * @param {number} percent
  2874. * @return {string}
  2875. */
  2876. function interpolateString(p0, p1, percent) {
  2877. return percent > 0.5 ? p1 : p0;
  2878. }
  2879. /**
  2880. * @param {Array} p0
  2881. * @param {Array} p1
  2882. * @param {number} percent
  2883. * @param {Array} out
  2884. * @param {number} arrDim
  2885. */
  2886. function interpolateArray(p0, p1, percent, out, arrDim) {
  2887. var len = p0.length;
  2888. if (arrDim == 1) {
  2889. for (var i = 0; i < len; i++) {
  2890. out[i] = interpolateNumber(p0[i], p1[i], percent);
  2891. }
  2892. }
  2893. else {
  2894. var len2 = len && p0[0].length;
  2895. for (var i = 0; i < len; i++) {
  2896. for (var j = 0; j < len2; j++) {
  2897. out[i][j] = interpolateNumber(
  2898. p0[i][j], p1[i][j], percent
  2899. );
  2900. }
  2901. }
  2902. }
  2903. }
  2904. // arr0 is source array, arr1 is target array.
  2905. // Do some preprocess to avoid error happened when interpolating from arr0 to arr1
  2906. function fillArr(arr0, arr1, arrDim) {
  2907. var arr0Len = arr0.length;
  2908. var arr1Len = arr1.length;
  2909. if (arr0Len !== arr1Len) {
  2910. // FIXME Not work for TypedArray
  2911. var isPreviousLarger = arr0Len > arr1Len;
  2912. if (isPreviousLarger) {
  2913. // Cut the previous
  2914. arr0.length = arr1Len;
  2915. }
  2916. else {
  2917. // Fill the previous
  2918. for (var i = arr0Len; i < arr1Len; i++) {
  2919. arr0.push(
  2920. arrDim === 1 ? arr1[i] : arraySlice.call(arr1[i])
  2921. );
  2922. }
  2923. }
  2924. }
  2925. // Handling NaN value
  2926. var len2 = arr0[0] && arr0[0].length;
  2927. for (var i = 0; i < arr0.length; i++) {
  2928. if (arrDim === 1) {
  2929. if (isNaN(arr0[i])) {
  2930. arr0[i] = arr1[i];
  2931. }
  2932. }
  2933. else {
  2934. for (var j = 0; j < len2; j++) {
  2935. if (isNaN(arr0[i][j])) {
  2936. arr0[i][j] = arr1[i][j];
  2937. }
  2938. }
  2939. }
  2940. }
  2941. }
  2942. /**
  2943. * @param {Array} arr0
  2944. * @param {Array} arr1
  2945. * @param {number} arrDim
  2946. * @return {boolean}
  2947. */
  2948. function isArraySame(arr0, arr1, arrDim) {
  2949. if (arr0 === arr1) {
  2950. return true;
  2951. }
  2952. var len = arr0.length;
  2953. if (len !== arr1.length) {
  2954. return false;
  2955. }
  2956. if (arrDim === 1) {
  2957. for (var i = 0; i < len; i++) {
  2958. if (arr0[i] !== arr1[i]) {
  2959. return false;
  2960. }
  2961. }
  2962. }
  2963. else {
  2964. var len2 = arr0[0].length;
  2965. for (var i = 0; i < len; i++) {
  2966. for (var j = 0; j < len2; j++) {
  2967. if (arr0[i][j] !== arr1[i][j]) {
  2968. return false;
  2969. }
  2970. }
  2971. }
  2972. }
  2973. return true;
  2974. }
  2975. /**
  2976. * Catmull Rom interpolate array
  2977. * @param {Array} p0
  2978. * @param {Array} p1
  2979. * @param {Array} p2
  2980. * @param {Array} p3
  2981. * @param {number} t
  2982. * @param {number} t2
  2983. * @param {number} t3
  2984. * @param {Array} out
  2985. * @param {number} arrDim
  2986. */
  2987. function catmullRomInterpolateArray(
  2988. p0, p1, p2, p3, t, t2, t3, out, arrDim
  2989. ) {
  2990. var len = p0.length;
  2991. if (arrDim == 1) {
  2992. for (var i = 0; i < len; i++) {
  2993. out[i] = catmullRomInterpolate(
  2994. p0[i], p1[i], p2[i], p3[i], t, t2, t3
  2995. );
  2996. }
  2997. }
  2998. else {
  2999. var len2 = p0[0].length;
  3000. for (var i = 0; i < len; i++) {
  3001. for (var j = 0; j < len2; j++) {
  3002. out[i][j] = catmullRomInterpolate(
  3003. p0[i][j], p1[i][j], p2[i][j], p3[i][j],
  3004. t, t2, t3
  3005. );
  3006. }
  3007. }
  3008. }
  3009. }
  3010. /**
  3011. * Catmull Rom interpolate number
  3012. * @param {number} p0
  3013. * @param {number} p1
  3014. * @param {number} p2
  3015. * @param {number} p3
  3016. * @param {number} t
  3017. * @param {number} t2
  3018. * @param {number} t3
  3019. * @return {number}
  3020. */
  3021. function catmullRomInterpolate(p0, p1, p2, p3, t, t2, t3) {
  3022. var v0 = (p2 - p0) * 0.5;
  3023. var v1 = (p3 - p1) * 0.5;
  3024. return (2 * (p1 - p2) + v0 + v1) * t3
  3025. + (-3 * (p1 - p2) - 2 * v0 - v1) * t2
  3026. + v0 * t + p1;
  3027. }
  3028. function cloneValue(value) {
  3029. if (isArrayLike(value)) {
  3030. var len = value.length;
  3031. if (isArrayLike(value[0])) {
  3032. var ret = [];
  3033. for (var i = 0; i < len; i++) {
  3034. ret.push(arraySlice.call(value[i]));
  3035. }
  3036. return ret;
  3037. }
  3038. return arraySlice.call(value);
  3039. }
  3040. return value;
  3041. }
  3042. function rgba2String(rgba) {
  3043. rgba[0] = Math.floor(rgba[0]);
  3044. rgba[1] = Math.floor(rgba[1]);
  3045. rgba[2] = Math.floor(rgba[2]);
  3046. return 'rgba(' + rgba.join(',') + ')';
  3047. }
  3048. function getArrayDim(keyframes) {
  3049. var lastValue = keyframes[keyframes.length - 1].value;
  3050. return isArrayLike(lastValue && lastValue[0]) ? 2 : 1;
  3051. }
  3052. function createTrackClip(animator, easing, oneTrackDone, keyframes, propName, forceAnimate) {
  3053. var getter = animator._getter;
  3054. var setter = animator._setter;
  3055. var useSpline = easing === 'spline';
  3056. var trackLen = keyframes.length;
  3057. if (!trackLen) {
  3058. return;
  3059. }
  3060. // Guess data type
  3061. var firstVal = keyframes[0].value;
  3062. var isValueArray = isArrayLike(firstVal);
  3063. var isValueColor = false;
  3064. var isValueString = false;
  3065. // For vertices morphing
  3066. var arrDim = isValueArray ? getArrayDim(keyframes) : 0;
  3067. var trackMaxTime;
  3068. // Sort keyframe as ascending
  3069. keyframes.sort(function(a, b) {
  3070. return a.time - b.time;
  3071. });
  3072. trackMaxTime = keyframes[trackLen - 1].time;
  3073. // Percents of each keyframe
  3074. var kfPercents = [];
  3075. // Value of each keyframe
  3076. var kfValues = [];
  3077. var prevValue = keyframes[0].value;
  3078. var isAllValueEqual = true;
  3079. for (var i = 0; i < trackLen; i++) {
  3080. kfPercents.push(keyframes[i].time / trackMaxTime);
  3081. // Assume value is a color when it is a string
  3082. var value = keyframes[i].value;
  3083. // Check if value is equal, deep check if value is array
  3084. if (!((isValueArray && isArraySame(value, prevValue, arrDim))
  3085. || (!isValueArray && value === prevValue))) {
  3086. isAllValueEqual = false;
  3087. }
  3088. prevValue = value;
  3089. // Try converting a string to a color array
  3090. if (typeof value == 'string') {
  3091. var colorArray = parse(value);
  3092. if (colorArray) {
  3093. value = colorArray;
  3094. isValueColor = true;
  3095. }
  3096. else {
  3097. isValueString = true;
  3098. }
  3099. }
  3100. kfValues.push(value);
  3101. }
  3102. if (!forceAnimate && isAllValueEqual) {
  3103. return;
  3104. }
  3105. var lastValue = kfValues[trackLen - 1];
  3106. // Polyfill array and NaN value
  3107. for (var i = 0; i < trackLen - 1; i++) {
  3108. if (isValueArray) {
  3109. fillArr(kfValues[i], lastValue, arrDim);
  3110. }
  3111. else {
  3112. if (isNaN(kfValues[i]) && !isNaN(lastValue) && !isValueString && !isValueColor) {
  3113. kfValues[i] = lastValue;
  3114. }
  3115. }
  3116. }
  3117. isValueArray && fillArr(getter(animator._target, propName), lastValue, arrDim);
  3118. // Cache the key of last frame to speed up when
  3119. // animation playback is sequency
  3120. var lastFrame = 0;
  3121. var lastFramePercent = 0;
  3122. var start;
  3123. var w;
  3124. var p0;
  3125. var p1;
  3126. var p2;
  3127. var p3;
  3128. if (isValueColor) {
  3129. var rgba = [0, 0, 0, 0];
  3130. }
  3131. var onframe = function (target, percent) {
  3132. // Find the range keyframes
  3133. // kf1-----kf2---------current--------kf3
  3134. // find kf2 and kf3 and do interpolation
  3135. var frame;
  3136. // In the easing function like elasticOut, percent may less than 0
  3137. if (percent < 0) {
  3138. frame = 0;
  3139. }
  3140. else if (percent < lastFramePercent) {
  3141. // Start from next key
  3142. // PENDING start from lastFrame ?
  3143. start = Math.min(lastFrame + 1, trackLen - 1);
  3144. for (frame = start; frame >= 0; frame--) {
  3145. if (kfPercents[frame] <= percent) {
  3146. break;
  3147. }
  3148. }
  3149. // PENDING really need to do this ?
  3150. frame = Math.min(frame, trackLen - 2);
  3151. }
  3152. else {
  3153. for (frame = lastFrame; frame < trackLen; frame++) {
  3154. if (kfPercents[frame] > percent) {
  3155. break;
  3156. }
  3157. }
  3158. frame = Math.min(frame - 1, trackLen - 2);
  3159. }
  3160. lastFrame = frame;
  3161. lastFramePercent = percent;
  3162. var range = (kfPercents[frame + 1] - kfPercents[frame]);
  3163. if (range === 0) {
  3164. return;
  3165. }
  3166. else {
  3167. w = (percent - kfPercents[frame]) / range;
  3168. }
  3169. if (useSpline) {
  3170. p1 = kfValues[frame];
  3171. p0 = kfValues[frame === 0 ? frame : frame - 1];
  3172. p2 = kfValues[frame > trackLen - 2 ? trackLen - 1 : frame + 1];
  3173. p3 = kfValues[frame > trackLen - 3 ? trackLen - 1 : frame + 2];
  3174. if (isValueArray) {
  3175. catmullRomInterpolateArray(
  3176. p0, p1, p2, p3, w, w * w, w * w * w,
  3177. getter(target, propName),
  3178. arrDim
  3179. );
  3180. }
  3181. else {
  3182. var value;
  3183. if (isValueColor) {
  3184. value = catmullRomInterpolateArray(
  3185. p0, p1, p2, p3, w, w * w, w * w * w,
  3186. rgba, 1
  3187. );
  3188. value = rgba2String(rgba);
  3189. }
  3190. else if (isValueString) {
  3191. // String is step(0.5)
  3192. return interpolateString(p1, p2, w);
  3193. }
  3194. else {
  3195. value = catmullRomInterpolate(
  3196. p0, p1, p2, p3, w, w * w, w * w * w
  3197. );
  3198. }
  3199. setter(
  3200. target,
  3201. propName,
  3202. value
  3203. );
  3204. }
  3205. }
  3206. else {
  3207. if (isValueArray) {
  3208. interpolateArray(
  3209. kfValues[frame], kfValues[frame + 1], w,
  3210. getter(target, propName),
  3211. arrDim
  3212. );
  3213. }
  3214. else {
  3215. var value;
  3216. if (isValueColor) {
  3217. interpolateArray(
  3218. kfValues[frame], kfValues[frame + 1], w,
  3219. rgba, 1
  3220. );
  3221. value = rgba2String(rgba);
  3222. }
  3223. else if (isValueString) {
  3224. // String is step(0.5)
  3225. return interpolateString(kfValues[frame], kfValues[frame + 1], w);
  3226. }
  3227. else {
  3228. value = interpolateNumber(kfValues[frame], kfValues[frame + 1], w);
  3229. }
  3230. setter(
  3231. target,
  3232. propName,
  3233. value
  3234. );
  3235. }
  3236. }
  3237. };
  3238. var clip = new Clip({
  3239. target: animator._target,
  3240. life: trackMaxTime,
  3241. loop: animator._loop,
  3242. delay: animator._delay,
  3243. onframe: onframe,
  3244. ondestroy: oneTrackDone
  3245. });
  3246. if (easing && easing !== 'spline') {
  3247. clip.easing = easing;
  3248. }
  3249. return clip;
  3250. }
  3251. /**
  3252. * @alias module:zrender/animation/Animator
  3253. * @constructor
  3254. * @param {Object} target
  3255. * @param {boolean} loop
  3256. * @param {Function} getter
  3257. * @param {Function} setter
  3258. */
  3259. var Animator = function(target, loop, getter, setter) {
  3260. this._tracks = {};
  3261. this._target = target;
  3262. this._loop = loop || false;
  3263. this._getter = getter || defaultGetter;
  3264. this._setter = setter || defaultSetter;
  3265. this._clipCount = 0;
  3266. this._delay = 0;
  3267. this._doneList = [];
  3268. this._onframeList = [];
  3269. this._clipList = [];
  3270. };
  3271. Animator.prototype = {
  3272. /**
  3273. * 设置动画关键帧
  3274. * @param {number} time 关键帧时间,单位是ms
  3275. * @param {Object} props 关键帧的属性值,key-value表示
  3276. * @return {module:zrender/animation/Animator}
  3277. */
  3278. when: function(time /* ms */, props) {
  3279. var tracks = this._tracks;
  3280. for (var propName in props) {
  3281. if (!props.hasOwnProperty(propName)) {
  3282. continue;
  3283. }
  3284. if (!tracks[propName]) {
  3285. tracks[propName] = [];
  3286. // Invalid value
  3287. var value = this._getter(this._target, propName);
  3288. if (value == null) {
  3289. // zrLog('Invalid property ' + propName);
  3290. continue;
  3291. }
  3292. // If time is 0
  3293. // Then props is given initialize value
  3294. // Else
  3295. // Initialize value from current prop value
  3296. if (time !== 0) {
  3297. tracks[propName].push({
  3298. time: 0,
  3299. value: cloneValue(value)
  3300. });
  3301. }
  3302. }
  3303. tracks[propName].push({
  3304. time: time,
  3305. value: props[propName]
  3306. });
  3307. }
  3308. return this;
  3309. },
  3310. /**
  3311. * 添加动画每一帧的回调函数
  3312. * @param {Function} callback
  3313. * @return {module:zrender/animation/Animator}
  3314. */
  3315. during: function (callback) {
  3316. this._onframeList.push(callback);
  3317. return this;
  3318. },
  3319. pause: function () {
  3320. for (var i = 0; i < this._clipList.length; i++) {
  3321. this._clipList[i].pause();
  3322. }
  3323. this._paused = true;
  3324. },
  3325. resume: function () {
  3326. for (var i = 0; i < this._clipList.length; i++) {
  3327. this._clipList[i].resume();
  3328. }
  3329. this._paused = false;
  3330. },
  3331. isPaused: function () {
  3332. return !!this._paused;
  3333. },
  3334. _doneCallback: function () {
  3335. // Clear all tracks
  3336. this._tracks = {};
  3337. // Clear all clips
  3338. this._clipList.length = 0;
  3339. var doneList = this._doneList;
  3340. var len = doneList.length;
  3341. for (var i = 0; i < len; i++) {
  3342. doneList[i].call(this);
  3343. }
  3344. },
  3345. /**
  3346. * 开始执行动画
  3347. * @param {string|Function} [easing]
  3348. * 动画缓动函数,详见{@link module:zrender/animation/easing}
  3349. * @param {boolean} forceAnimate
  3350. * @return {module:zrender/animation/Animator}
  3351. */
  3352. start: function (easing, forceAnimate) {
  3353. var self = this;
  3354. var clipCount = 0;
  3355. var oneTrackDone = function() {
  3356. clipCount--;
  3357. if (!clipCount) {
  3358. self._doneCallback();
  3359. }
  3360. };
  3361. var lastClip;
  3362. for (var propName in this._tracks) {
  3363. if (!this._tracks.hasOwnProperty(propName)) {
  3364. continue;
  3365. }
  3366. var clip = createTrackClip(
  3367. this, easing, oneTrackDone,
  3368. this._tracks[propName], propName, forceAnimate
  3369. );
  3370. if (clip) {
  3371. this._clipList.push(clip);
  3372. clipCount++;
  3373. // If start after added to animation
  3374. if (this.animation) {
  3375. this.animation.addClip(clip);
  3376. }
  3377. lastClip = clip;
  3378. }
  3379. }
  3380. // Add during callback on the last clip
  3381. if (lastClip) {
  3382. var oldOnFrame = lastClip.onframe;
  3383. lastClip.onframe = function (target, percent) {
  3384. oldOnFrame(target, percent);
  3385. for (var i = 0; i < self._onframeList.length; i++) {
  3386. self._onframeList[i](target, percent);
  3387. }
  3388. };
  3389. }
  3390. // This optimization will help the case that in the upper application
  3391. // the view may be refreshed frequently, where animation will be
  3392. // called repeatly but nothing changed.
  3393. if (!clipCount) {
  3394. this._doneCallback();
  3395. }
  3396. return this;
  3397. },
  3398. /**
  3399. * 停止动画
  3400. * @param {boolean} forwardToLast If move to last frame before stop
  3401. */
  3402. stop: function (forwardToLast) {
  3403. var clipList = this._clipList;
  3404. var animation = this.animation;
  3405. for (var i = 0; i < clipList.length; i++) {
  3406. var clip = clipList[i];
  3407. if (forwardToLast) {
  3408. // Move to last frame before stop
  3409. clip.onframe(this._target, 1);
  3410. }
  3411. animation && animation.removeClip(clip);
  3412. }
  3413. clipList.length = 0;
  3414. },
  3415. /**
  3416. * 设置动画延迟开始的时间
  3417. * @param {number} time 单位ms
  3418. * @return {module:zrender/animation/Animator}
  3419. */
  3420. delay: function (time) {
  3421. this._delay = time;
  3422. return this;
  3423. },
  3424. /**
  3425. * 添加动画结束的回调
  3426. * @param {Function} cb
  3427. * @return {module:zrender/animation/Animator}
  3428. */
  3429. done: function(cb) {
  3430. if (cb) {
  3431. this._doneList.push(cb);
  3432. }
  3433. return this;
  3434. },
  3435. /**
  3436. * @return {Array.<module:zrender/animation/Clip>}
  3437. */
  3438. getClips: function () {
  3439. return this._clipList;
  3440. }
  3441. };
  3442. var dpr = 1;
  3443. // If in browser environment
  3444. if (typeof window !== 'undefined') {
  3445. dpr = Math.max(window.devicePixelRatio || 1, 1);
  3446. }
  3447. /**
  3448. * config默认配置项
  3449. * @exports zrender/config
  3450. * @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
  3451. */
  3452. /**
  3453. * debug日志选项:catchBrushException为true下有效
  3454. * 0 : 不生成debug数据,发布用
  3455. * 1 : 异常抛出,调试用
  3456. * 2 : 控制台输出,调试用
  3457. */
  3458. var debugMode = 0;
  3459. // retina 屏幕优化
  3460. var devicePixelRatio = dpr;
  3461. var log = function () {
  3462. };
  3463. if (debugMode === 1) {
  3464. log = function () {
  3465. for (var k in arguments) {
  3466. throw new Error(arguments[k]);
  3467. }
  3468. };
  3469. }
  3470. else if (debugMode > 1) {
  3471. log = function () {
  3472. for (var k in arguments) {
  3473. console.log(arguments[k]);
  3474. }
  3475. };
  3476. }
  3477. var log$1 = log;
  3478. /**
  3479. * @alias modue:zrender/mixin/Animatable
  3480. * @constructor
  3481. */
  3482. var Animatable = function () {
  3483. /**
  3484. * @type {Array.<module:zrender/animation/Animator>}
  3485. * @readOnly
  3486. */
  3487. this.animators = [];
  3488. };
  3489. Animatable.prototype = {
  3490. constructor: Animatable,
  3491. /**
  3492. * 动画
  3493. *
  3494. * @param {string} path The path to fetch value from object, like 'a.b.c'.
  3495. * @param {boolean} [loop] Whether to loop animation.
  3496. * @return {module:zrender/animation/Animator}
  3497. * @example:
  3498. * el.animate('style', false)
  3499. * .when(1000, {x: 10} )
  3500. * .done(function(){ // Animation done })
  3501. * .start()
  3502. */
  3503. animate: function (path, loop) {
  3504. var target;
  3505. var animatingShape = false;
  3506. var el = this;
  3507. var zr = this.__zr;
  3508. if (path) {
  3509. var pathSplitted = path.split('.');
  3510. var prop = el;
  3511. // If animating shape
  3512. animatingShape = pathSplitted[0] === 'shape';
  3513. for (var i = 0, l = pathSplitted.length; i < l; i++) {
  3514. if (!prop) {
  3515. continue;
  3516. }
  3517. prop = prop[pathSplitted[i]];
  3518. }
  3519. if (prop) {
  3520. target = prop;
  3521. }
  3522. }
  3523. else {
  3524. target = el;
  3525. }
  3526. if (!target) {
  3527. log$1(
  3528. 'Property "'
  3529. + path
  3530. + '" is not existed in element '
  3531. + el.id
  3532. );
  3533. return;
  3534. }
  3535. var animators = el.animators;
  3536. var animator = new Animator(target, loop);
  3537. animator.during(function (target) {
  3538. el.dirty(animatingShape);
  3539. })
  3540. .done(function () {
  3541. // FIXME Animator will not be removed if use `Animator#stop` to stop animation
  3542. animators.splice(indexOf(animators, animator), 1);
  3543. });
  3544. animators.push(animator);
  3545. // If animate after added to the zrender
  3546. if (zr) {
  3547. zr.animation.addAnimator(animator);
  3548. }
  3549. return animator;
  3550. },
  3551. /**
  3552. * 停止动画
  3553. * @param {boolean} forwardToLast If move to last frame before stop
  3554. */
  3555. stopAnimation: function (forwardToLast) {
  3556. var animators = this.animators;
  3557. var len = animators.length;
  3558. for (var i = 0; i < len; i++) {
  3559. animators[i].stop(forwardToLast);
  3560. }
  3561. animators.length = 0;
  3562. return this;
  3563. },
  3564. /**
  3565. * Caution: this method will stop previous animation.
  3566. * So do not use this method to one element twice before
  3567. * animation starts, unless you know what you are doing.
  3568. * @param {Object} target
  3569. * @param {number} [time=500] Time in ms
  3570. * @param {string} [easing='linear']
  3571. * @param {number} [delay=0]
  3572. * @param {Function} [callback]
  3573. * @param {Function} [forceAnimate] Prevent stop animation and callback
  3574. * immediently when target values are the same as current values.
  3575. *
  3576. * @example
  3577. * // Animate position
  3578. * el.animateTo({
  3579. * position: [10, 10]
  3580. * }, function () { // done })
  3581. *
  3582. * // Animate shape, style and position in 100ms, delayed 100ms, with cubicOut easing
  3583. * el.animateTo({
  3584. * shape: {
  3585. * width: 500
  3586. * },
  3587. * style: {
  3588. * fill: 'red'
  3589. * }
  3590. * position: [10, 10]
  3591. * }, 100, 100, 'cubicOut', function () { // done })
  3592. */
  3593. // TODO Return animation key
  3594. animateTo: function (target, time, delay, easing, callback, forceAnimate) {
  3595. // animateTo(target, time, easing, callback);
  3596. if (isString(delay)) {
  3597. callback = easing;
  3598. easing = delay;
  3599. delay = 0;
  3600. }
  3601. // animateTo(target, time, delay, callback);
  3602. else if (isFunction$1(easing)) {
  3603. callback = easing;
  3604. easing = 'linear';
  3605. delay = 0;
  3606. }
  3607. // animateTo(target, time, callback);
  3608. else if (isFunction$1(delay)) {
  3609. callback = delay;
  3610. delay = 0;
  3611. }
  3612. // animateTo(target, callback)
  3613. else if (isFunction$1(time)) {
  3614. callback = time;
  3615. time = 500;
  3616. }
  3617. // animateTo(target)
  3618. else if (!time) {
  3619. time = 500;
  3620. }
  3621. // Stop all previous animations
  3622. this.stopAnimation();
  3623. this._animateToShallow('', this, target, time, delay);
  3624. // Animators may be removed immediately after start
  3625. // if there is nothing to animate
  3626. var animators = this.animators.slice();
  3627. var count = animators.length;
  3628. function done() {
  3629. count--;
  3630. if (!count) {
  3631. callback && callback();
  3632. }
  3633. }
  3634. // No animators. This should be checked before animators[i].start(),
  3635. // because 'done' may be executed immediately if no need to animate.
  3636. if (!count) {
  3637. callback && callback();
  3638. }
  3639. // Start after all animators created
  3640. // Incase any animator is done immediately when all animation properties are not changed
  3641. for (var i = 0; i < animators.length; i++) {
  3642. animators[i]
  3643. .done(done)
  3644. .start(easing, forceAnimate);
  3645. }
  3646. },
  3647. /**
  3648. * @private
  3649. * @param {string} path=''
  3650. * @param {Object} source=this
  3651. * @param {Object} target
  3652. * @param {number} [time=500]
  3653. * @param {number} [delay=0]
  3654. *
  3655. * @example
  3656. * // Animate position
  3657. * el._animateToShallow({
  3658. * position: [10, 10]
  3659. * })
  3660. *
  3661. * // Animate shape, style and position in 100ms, delayed 100ms
  3662. * el._animateToShallow({
  3663. * shape: {
  3664. * width: 500
  3665. * },
  3666. * style: {
  3667. * fill: 'red'
  3668. * }
  3669. * position: [10, 10]
  3670. * }, 100, 100)
  3671. */
  3672. _animateToShallow: function (path, source, target, time, delay) {
  3673. var objShallow = {};
  3674. var propertyCount = 0;
  3675. for (var name in target) {
  3676. if (!target.hasOwnProperty(name)) {
  3677. continue;
  3678. }
  3679. if (source[name] != null) {
  3680. if (isObject$1(target[name]) && !isArrayLike(target[name])) {
  3681. this._animateToShallow(
  3682. path ? path + '.' + name : name,
  3683. source[name],
  3684. target[name],
  3685. time,
  3686. delay
  3687. );
  3688. }
  3689. else {
  3690. objShallow[name] = target[name];
  3691. propertyCount++;
  3692. }
  3693. }
  3694. else if (target[name] != null) {
  3695. // Attr directly if not has property
  3696. // FIXME, if some property not needed for element ?
  3697. if (!path) {
  3698. this.attr(name, target[name]);
  3699. }
  3700. else { // Shape or style
  3701. var props = {};
  3702. props[path] = {};
  3703. props[path][name] = target[name];
  3704. this.attr(props);
  3705. }
  3706. }
  3707. }
  3708. if (propertyCount > 0) {
  3709. this.animate(path, false)
  3710. .when(time == null ? 500 : time, objShallow)
  3711. .delay(delay || 0);
  3712. }
  3713. return this;
  3714. }
  3715. };
  3716. /**
  3717. * @alias module:zrender/Element
  3718. * @constructor
  3719. * @extends {module:zrender/mixin/Animatable}
  3720. * @extends {module:zrender/mixin/Transformable}
  3721. * @extends {module:zrender/mixin/Eventful}
  3722. */
  3723. var Element = function (opts) { // jshint ignore:line
  3724. Transformable.call(this, opts);
  3725. Eventful.call(this, opts);
  3726. Animatable.call(this, opts);
  3727. /**
  3728. * 画布元素ID
  3729. * @type {string}
  3730. */
  3731. this.id = opts.id || guid();
  3732. };
  3733. Element.prototype = {
  3734. /**
  3735. * 元素类型
  3736. * Element type
  3737. * @type {string}
  3738. */
  3739. type: 'element',
  3740. /**
  3741. * 元素名字
  3742. * Element name
  3743. * @type {string}
  3744. */
  3745. name: '',
  3746. /**
  3747. * ZRender 实例对象,会在 element 添加到 zrender 实例中后自动赋值
  3748. * ZRender instance will be assigned when element is associated with zrender
  3749. * @name module:/zrender/Element#__zr
  3750. * @type {module:zrender/ZRender}
  3751. */
  3752. __zr: null,
  3753. /**
  3754. * 图形是否忽略,为true时忽略图形的绘制以及事件触发
  3755. * If ignore drawing and events of the element object
  3756. * @name module:/zrender/Element#ignore
  3757. * @type {boolean}
  3758. * @default false
  3759. */
  3760. ignore: false,
  3761. /**
  3762. * 用于裁剪的路径(shape),所有 Group 内的路径在绘制时都会被这个路径裁剪
  3763. * 该路径会继承被裁减对象的变换
  3764. * @type {module:zrender/graphic/Path}
  3765. * @see http://www.w3.org/TR/2dcontext/#clipping-region
  3766. * @readOnly
  3767. */
  3768. clipPath: null,
  3769. /**
  3770. * 是否是 Group
  3771. * @type {boolean}
  3772. */
  3773. isGroup: false,
  3774. /**
  3775. * Drift element
  3776. * @param {number} dx dx on the global space
  3777. * @param {number} dy dy on the global space
  3778. */
  3779. drift: function (dx, dy) {
  3780. switch (this.draggable) {
  3781. case 'horizontal':
  3782. dy = 0;
  3783. break;
  3784. case 'vertical':
  3785. dx = 0;
  3786. break;
  3787. }
  3788. var m = this.transform;
  3789. if (!m) {
  3790. m = this.transform = [1, 0, 0, 1, 0, 0];
  3791. }
  3792. m[4] += dx;
  3793. m[5] += dy;
  3794. this.decomposeTransform();
  3795. this.dirty(false);
  3796. },
  3797. /**
  3798. * Hook before update
  3799. */
  3800. beforeUpdate: function () {},
  3801. /**
  3802. * Hook after update
  3803. */
  3804. afterUpdate: function () {},
  3805. /**
  3806. * Update each frame
  3807. */
  3808. update: function () {
  3809. this.updateTransform();
  3810. },
  3811. /**
  3812. * @param {Function} cb
  3813. * @param {} context
  3814. */
  3815. traverse: function (cb, context) {},
  3816. /**
  3817. * @protected
  3818. */
  3819. attrKV: function (key, value) {
  3820. if (key === 'position' || key === 'scale' || key === 'origin') {
  3821. // Copy the array
  3822. if (value) {
  3823. var target = this[key];
  3824. if (!target) {
  3825. target = this[key] = [];
  3826. }
  3827. target[0] = value[0];
  3828. target[1] = value[1];
  3829. }
  3830. }
  3831. else {
  3832. this[key] = value;
  3833. }
  3834. },
  3835. /**
  3836. * Hide the element
  3837. */
  3838. hide: function () {
  3839. this.ignore = true;
  3840. this.__zr && this.__zr.refresh();
  3841. },
  3842. /**
  3843. * Show the element
  3844. */
  3845. show: function () {
  3846. this.ignore = false;
  3847. this.__zr && this.__zr.refresh();
  3848. },
  3849. /**
  3850. * @param {string|Object} key
  3851. * @param {*} value
  3852. */
  3853. attr: function (key, value) {
  3854. if (typeof key === 'string') {
  3855. this.attrKV(key, value);
  3856. }
  3857. else if (isObject$1(key)) {
  3858. for (var name in key) {
  3859. if (key.hasOwnProperty(name)) {
  3860. this.attrKV(name, key[name]);
  3861. }
  3862. }
  3863. }
  3864. this.dirty(false);
  3865. return this;
  3866. },
  3867. /**
  3868. * @param {module:zrender/graphic/Path} clipPath
  3869. */
  3870. setClipPath: function (clipPath) {
  3871. var zr = this.__zr;
  3872. if (zr) {
  3873. clipPath.addSelfToZr(zr);
  3874. }
  3875. // Remove previous clip path
  3876. if (this.clipPath && this.clipPath !== clipPath) {
  3877. this.removeClipPath();
  3878. }
  3879. this.clipPath = clipPath;
  3880. clipPath.__zr = zr;
  3881. clipPath.__clipTarget = this;
  3882. this.dirty(false);
  3883. },
  3884. /**
  3885. */
  3886. removeClipPath: function () {
  3887. var clipPath = this.clipPath;
  3888. if (clipPath) {
  3889. if (clipPath.__zr) {
  3890. clipPath.removeSelfFromZr(clipPath.__zr);
  3891. }
  3892. clipPath.__zr = null;
  3893. clipPath.__clipTarget = null;
  3894. this.clipPath = null;
  3895. this.dirty(false);
  3896. }
  3897. },
  3898. /**
  3899. * Add self from zrender instance.
  3900. * Not recursively because it will be invoked when element added to storage.
  3901. * @param {module:zrender/ZRender} zr
  3902. */
  3903. addSelfToZr: function (zr) {
  3904. this.__zr = zr;
  3905. // 添加动画
  3906. var animators = this.animators;
  3907. if (animators) {
  3908. for (var i = 0; i < animators.length; i++) {
  3909. zr.animation.addAnimator(animators[i]);
  3910. }
  3911. }
  3912. if (this.clipPath) {
  3913. this.clipPath.addSelfToZr(zr);
  3914. }
  3915. },
  3916. /**
  3917. * Remove self from zrender instance.
  3918. * Not recursively because it will be invoked when element added to storage.
  3919. * @param {module:zrender/ZRender} zr
  3920. */
  3921. removeSelfFromZr: function (zr) {
  3922. this.__zr = null;
  3923. // 移除动画
  3924. var animators = this.animators;
  3925. if (animators) {
  3926. for (var i = 0; i < animators.length; i++) {
  3927. zr.animation.removeAnimator(animators[i]);
  3928. }
  3929. }
  3930. if (this.clipPath) {
  3931. this.clipPath.removeSelfFromZr(zr);
  3932. }
  3933. }
  3934. };
  3935. mixin(Element, Animatable);
  3936. mixin(Element, Transformable);
  3937. mixin(Element, Eventful);
  3938. /**
  3939. * @module echarts/core/BoundingRect
  3940. */
  3941. var v2ApplyTransform = applyTransform;
  3942. var mathMin = Math.min;
  3943. var mathMax = Math.max;
  3944. /**
  3945. * @alias module:echarts/core/BoundingRect
  3946. */
  3947. function BoundingRect(x, y, width, height) {
  3948. if (width < 0) {
  3949. x = x + width;
  3950. width = -width;
  3951. }
  3952. if (height < 0) {
  3953. y = y + height;
  3954. height = -height;
  3955. }
  3956. /**
  3957. * @type {number}
  3958. */
  3959. this.x = x;
  3960. /**
  3961. * @type {number}
  3962. */
  3963. this.y = y;
  3964. /**
  3965. * @type {number}
  3966. */
  3967. this.width = width;
  3968. /**
  3969. * @type {number}
  3970. */
  3971. this.height = height;
  3972. }
  3973. BoundingRect.prototype = {
  3974. constructor: BoundingRect,
  3975. /**
  3976. * @param {module:echarts/core/BoundingRect} other
  3977. */
  3978. union: function (other) {
  3979. var x = mathMin(other.x, this.x);
  3980. var y = mathMin(other.y, this.y);
  3981. this.width = mathMax(
  3982. other.x + other.width,
  3983. this.x + this.width
  3984. ) - x;
  3985. this.height = mathMax(
  3986. other.y + other.height,
  3987. this.y + this.height
  3988. ) - y;
  3989. this.x = x;
  3990. this.y = y;
  3991. },
  3992. /**
  3993. * @param {Array.<number>} m
  3994. * @methods
  3995. */
  3996. applyTransform: (function () {
  3997. var lt = [];
  3998. var rb = [];
  3999. var lb = [];
  4000. var rt = [];
  4001. return function (m) {
  4002. // In case usage like this
  4003. // el.getBoundingRect().applyTransform(el.transform)
  4004. // And element has no transform
  4005. if (!m) {
  4006. return;
  4007. }
  4008. lt[0] = lb[0] = this.x;
  4009. lt[1] = rt[1] = this.y;
  4010. rb[0] = rt[0] = this.x + this.width;
  4011. rb[1] = lb[1] = this.y + this.height;
  4012. v2ApplyTransform(lt, lt, m);
  4013. v2ApplyTransform(rb, rb, m);
  4014. v2ApplyTransform(lb, lb, m);
  4015. v2ApplyTransform(rt, rt, m);
  4016. this.x = mathMin(lt[0], rb[0], lb[0], rt[0]);
  4017. this.y = mathMin(lt[1], rb[1], lb[1], rt[1]);
  4018. var maxX = mathMax(lt[0], rb[0], lb[0], rt[0]);
  4019. var maxY = mathMax(lt[1], rb[1], lb[1], rt[1]);
  4020. this.width = maxX - this.x;
  4021. this.height = maxY - this.y;
  4022. };
  4023. })(),
  4024. /**
  4025. * Calculate matrix of transforming from self to target rect
  4026. * @param {module:zrender/core/BoundingRect} b
  4027. * @return {Array.<number>}
  4028. */
  4029. calculateTransform: function (b) {
  4030. var a = this;
  4031. var sx = b.width / a.width;
  4032. var sy = b.height / a.height;
  4033. var m = create$1();
  4034. // 矩阵右乘
  4035. translate(m, m, [-a.x, -a.y]);
  4036. scale$1(m, m, [sx, sy]);
  4037. translate(m, m, [b.x, b.y]);
  4038. return m;
  4039. },
  4040. /**
  4041. * @param {(module:echarts/core/BoundingRect|Object)} b
  4042. * @return {boolean}
  4043. */
  4044. intersect: function (b) {
  4045. if (!b) {
  4046. return false;
  4047. }
  4048. if (!(b instanceof BoundingRect)) {
  4049. // Normalize negative width/height.
  4050. b = BoundingRect.create(b);
  4051. }
  4052. var a = this;
  4053. var ax0 = a.x;
  4054. var ax1 = a.x + a.width;
  4055. var ay0 = a.y;
  4056. var ay1 = a.y + a.height;
  4057. var bx0 = b.x;
  4058. var bx1 = b.x + b.width;
  4059. var by0 = b.y;
  4060. var by1 = b.y + b.height;
  4061. return ! (ax1 < bx0 || bx1 < ax0 || ay1 < by0 || by1 < ay0);
  4062. },
  4063. contain: function (x, y) {
  4064. var rect = this;
  4065. return x >= rect.x
  4066. && x <= (rect.x + rect.width)
  4067. && y >= rect.y
  4068. && y <= (rect.y + rect.height);
  4069. },
  4070. /**
  4071. * @return {module:echarts/core/BoundingRect}
  4072. */
  4073. clone: function () {
  4074. return new BoundingRect(this.x, this.y, this.width, this.height);
  4075. },
  4076. /**
  4077. * Copy from another rect
  4078. */
  4079. copy: function (other) {
  4080. this.x = other.x;
  4081. this.y = other.y;
  4082. this.width = other.width;
  4083. this.height = other.height;
  4084. },
  4085. plain: function () {
  4086. return {
  4087. x: this.x,
  4088. y: this.y,
  4089. width: this.width,
  4090. height: this.height
  4091. };
  4092. }
  4093. };
  4094. /**
  4095. * @param {Object|module:zrender/core/BoundingRect} rect
  4096. * @param {number} rect.x
  4097. * @param {number} rect.y
  4098. * @param {number} rect.width
  4099. * @param {number} rect.height
  4100. * @return {module:zrender/core/BoundingRect}
  4101. */
  4102. BoundingRect.create = function (rect) {
  4103. return new BoundingRect(rect.x, rect.y, rect.width, rect.height);
  4104. };
  4105. /**
  4106. * Group是一个容器,可以插入子节点,Group的变换也会被应用到子节点上
  4107. * @module zrender/graphic/Group
  4108. * @example
  4109. * var Group = require('zrender/container/Group');
  4110. * var Circle = require('zrender/graphic/shape/Circle');
  4111. * var g = new Group();
  4112. * g.position[0] = 100;
  4113. * g.position[1] = 100;
  4114. * g.add(new Circle({
  4115. * style: {
  4116. * x: 100,
  4117. * y: 100,
  4118. * r: 20,
  4119. * }
  4120. * }));
  4121. * zr.add(g);
  4122. */
  4123. /**
  4124. * @alias module:zrender/graphic/Group
  4125. * @constructor
  4126. * @extends module:zrender/mixin/Transformable
  4127. * @extends module:zrender/mixin/Eventful
  4128. */
  4129. var Group = function (opts) {
  4130. opts = opts || {};
  4131. Element.call(this, opts);
  4132. for (var key in opts) {
  4133. if (opts.hasOwnProperty(key)) {
  4134. this[key] = opts[key];
  4135. }
  4136. }
  4137. this._children = [];
  4138. this.__storage = null;
  4139. this.__dirty = true;
  4140. };
  4141. Group.prototype = {
  4142. constructor: Group,
  4143. isGroup: true,
  4144. /**
  4145. * @type {string}
  4146. */
  4147. type: 'group',
  4148. /**
  4149. * 所有子孙元素是否响应鼠标事件
  4150. * @name module:/zrender/container/Group#silent
  4151. * @type {boolean}
  4152. * @default false
  4153. */
  4154. silent: false,
  4155. /**
  4156. * @return {Array.<module:zrender/Element>}
  4157. */
  4158. children: function () {
  4159. return this._children.slice();
  4160. },
  4161. /**
  4162. * 获取指定 index 的儿子节点
  4163. * @param {number} idx
  4164. * @return {module:zrender/Element}
  4165. */
  4166. childAt: function (idx) {
  4167. return this._children[idx];
  4168. },
  4169. /**
  4170. * 获取指定名字的儿子节点
  4171. * @param {string} name
  4172. * @return {module:zrender/Element}
  4173. */
  4174. childOfName: function (name) {
  4175. var children = this._children;
  4176. for (var i = 0; i < children.length; i++) {
  4177. if (children[i].name === name) {
  4178. return children[i];
  4179. }
  4180. }
  4181. },
  4182. /**
  4183. * @return {number}
  4184. */
  4185. childCount: function () {
  4186. return this._children.length;
  4187. },
  4188. /**
  4189. * 添加子节点到最后
  4190. * @param {module:zrender/Element} child
  4191. */
  4192. add: function (child) {
  4193. if (child && child !== this && child.parent !== this) {
  4194. this._children.push(child);
  4195. this._doAdd(child);
  4196. }
  4197. return this;
  4198. },
  4199. /**
  4200. * 添加子节点在 nextSibling 之前
  4201. * @param {module:zrender/Element} child
  4202. * @param {module:zrender/Element} nextSibling
  4203. */
  4204. addBefore: function (child, nextSibling) {
  4205. if (child && child !== this && child.parent !== this
  4206. && nextSibling && nextSibling.parent === this) {
  4207. var children = this._children;
  4208. var idx = children.indexOf(nextSibling);
  4209. if (idx >= 0) {
  4210. children.splice(idx, 0, child);
  4211. this._doAdd(child);
  4212. }
  4213. }
  4214. return this;
  4215. },
  4216. _doAdd: function (child) {
  4217. if (child.parent) {
  4218. child.parent.remove(child);
  4219. }
  4220. child.parent = this;
  4221. var storage = this.__storage;
  4222. var zr = this.__zr;
  4223. if (storage && storage !== child.__storage) {
  4224. storage.addToStorage(child);
  4225. if (child instanceof Group) {
  4226. child.addChildrenToStorage(storage);
  4227. }
  4228. }
  4229. zr && zr.refresh();
  4230. },
  4231. /**
  4232. * 移除子节点
  4233. * @param {module:zrender/Element} child
  4234. */
  4235. remove: function (child) {
  4236. var zr = this.__zr;
  4237. var storage = this.__storage;
  4238. var children = this._children;
  4239. var idx = indexOf(children, child);
  4240. if (idx < 0) {
  4241. return this;
  4242. }
  4243. children.splice(idx, 1);
  4244. child.parent = null;
  4245. if (storage) {
  4246. storage.delFromStorage(child);
  4247. if (child instanceof Group) {
  4248. child.delChildrenFromStorage(storage);
  4249. }
  4250. }
  4251. zr && zr.refresh();
  4252. return this;
  4253. },
  4254. /**
  4255. * 移除所有子节点
  4256. */
  4257. removeAll: function () {
  4258. var children = this._children;
  4259. var storage = this.__storage;
  4260. var child;
  4261. var i;
  4262. for (i = 0; i < children.length; i++) {
  4263. child = children[i];
  4264. if (storage) {
  4265. storage.delFromStorage(child);
  4266. if (child instanceof Group) {
  4267. child.delChildrenFromStorage(storage);
  4268. }
  4269. }
  4270. child.parent = null;
  4271. }
  4272. children.length = 0;
  4273. return this;
  4274. },
  4275. /**
  4276. * 遍历所有子节点
  4277. * @param {Function} cb
  4278. * @param {} context
  4279. */
  4280. eachChild: function (cb, context) {
  4281. var children = this._children;
  4282. for (var i = 0; i < children.length; i++) {
  4283. var child = children[i];
  4284. cb.call(context, child, i);
  4285. }
  4286. return this;
  4287. },
  4288. /**
  4289. * 深度优先遍历所有子孙节点
  4290. * @param {Function} cb
  4291. * @param {} context
  4292. */
  4293. traverse: function (cb, context) {
  4294. for (var i = 0; i < this._children.length; i++) {
  4295. var child = this._children[i];
  4296. cb.call(context, child);
  4297. if (child.type === 'group') {
  4298. child.traverse(cb, context);
  4299. }
  4300. }
  4301. return this;
  4302. },
  4303. addChildrenToStorage: function (storage) {
  4304. for (var i = 0; i < this._children.length; i++) {
  4305. var child = this._children[i];
  4306. storage.addToStorage(child);
  4307. if (child instanceof Group) {
  4308. child.addChildrenToStorage(storage);
  4309. }
  4310. }
  4311. },
  4312. delChildrenFromStorage: function (storage) {
  4313. for (var i = 0; i < this._children.length; i++) {
  4314. var child = this._children[i];
  4315. storage.delFromStorage(child);
  4316. if (child instanceof Group) {
  4317. child.delChildrenFromStorage(storage);
  4318. }
  4319. }
  4320. },
  4321. dirty: function () {
  4322. this.__dirty = true;
  4323. this.__zr && this.__zr.refresh();
  4324. return this;
  4325. },
  4326. /**
  4327. * @return {module:zrender/core/BoundingRect}
  4328. */
  4329. getBoundingRect: function (includeChildren) {
  4330. // TODO Caching
  4331. var rect = null;
  4332. var tmpRect = new BoundingRect(0, 0, 0, 0);
  4333. var children = includeChildren || this._children;
  4334. var tmpMat = [];
  4335. for (var i = 0; i < children.length; i++) {
  4336. var child = children[i];
  4337. if (child.ignore || child.invisible) {
  4338. continue;
  4339. }
  4340. var childRect = child.getBoundingRect();
  4341. var transform = child.getLocalTransform(tmpMat);
  4342. // TODO
  4343. // The boundingRect cacluated by transforming original
  4344. // rect may be bigger than the actual bundingRect when rotation
  4345. // is used. (Consider a circle rotated aginst its center, where
  4346. // the actual boundingRect should be the same as that not be
  4347. // rotated.) But we can not find better approach to calculate
  4348. // actual boundingRect yet, considering performance.
  4349. if (transform) {
  4350. tmpRect.copy(childRect);
  4351. tmpRect.applyTransform(transform);
  4352. rect = rect || tmpRect.clone();
  4353. rect.union(tmpRect);
  4354. }
  4355. else {
  4356. rect = rect || childRect.clone();
  4357. rect.union(childRect);
  4358. }
  4359. }
  4360. return rect || tmpRect;
  4361. }
  4362. };
  4363. inherits(Group, Element);
  4364. // https://github.com/mziccard/node-timsort
  4365. var DEFAULT_MIN_MERGE = 32;
  4366. var DEFAULT_MIN_GALLOPING = 7;
  4367. function minRunLength(n) {
  4368. var r = 0;
  4369. while (n >= DEFAULT_MIN_MERGE) {
  4370. r |= n & 1;
  4371. n >>= 1;
  4372. }
  4373. return n + r;
  4374. }
  4375. function makeAscendingRun(array, lo, hi, compare) {
  4376. var runHi = lo + 1;
  4377. if (runHi === hi) {
  4378. return 1;
  4379. }
  4380. if (compare(array[runHi++], array[lo]) < 0) {
  4381. while (runHi < hi && compare(array[runHi], array[runHi - 1]) < 0) {
  4382. runHi++;
  4383. }
  4384. reverseRun(array, lo, runHi);
  4385. }
  4386. else {
  4387. while (runHi < hi && compare(array[runHi], array[runHi - 1]) >= 0) {
  4388. runHi++;
  4389. }
  4390. }
  4391. return runHi - lo;
  4392. }
  4393. function reverseRun(array, lo, hi) {
  4394. hi--;
  4395. while (lo < hi) {
  4396. var t = array[lo];
  4397. array[lo++] = array[hi];
  4398. array[hi--] = t;
  4399. }
  4400. }
  4401. function binaryInsertionSort(array, lo, hi, start, compare) {
  4402. if (start === lo) {
  4403. start++;
  4404. }
  4405. for (; start < hi; start++) {
  4406. var pivot = array[start];
  4407. var left = lo;
  4408. var right = start;
  4409. var mid;
  4410. while (left < right) {
  4411. mid = left + right >>> 1;
  4412. if (compare(pivot, array[mid]) < 0) {
  4413. right = mid;
  4414. }
  4415. else {
  4416. left = mid + 1;
  4417. }
  4418. }
  4419. var n = start - left;
  4420. switch (n) {
  4421. case 3:
  4422. array[left + 3] = array[left + 2];
  4423. case 2:
  4424. array[left + 2] = array[left + 1];
  4425. case 1:
  4426. array[left + 1] = array[left];
  4427. break;
  4428. default:
  4429. while (n > 0) {
  4430. array[left + n] = array[left + n - 1];
  4431. n--;
  4432. }
  4433. }
  4434. array[left] = pivot;
  4435. }
  4436. }
  4437. function gallopLeft(value, array, start, length, hint, compare) {
  4438. var lastOffset = 0;
  4439. var maxOffset = 0;
  4440. var offset = 1;
  4441. if (compare(value, array[start + hint]) > 0) {
  4442. maxOffset = length - hint;
  4443. while (offset < maxOffset && compare(value, array[start + hint + offset]) > 0) {
  4444. lastOffset = offset;
  4445. offset = (offset << 1) + 1;
  4446. if (offset <= 0) {
  4447. offset = maxOffset;
  4448. }
  4449. }
  4450. if (offset > maxOffset) {
  4451. offset = maxOffset;
  4452. }
  4453. lastOffset += hint;
  4454. offset += hint;
  4455. }
  4456. else {
  4457. maxOffset = hint + 1;
  4458. while (offset < maxOffset && compare(value, array[start + hint - offset]) <= 0) {
  4459. lastOffset = offset;
  4460. offset = (offset << 1) + 1;
  4461. if (offset <= 0) {
  4462. offset = maxOffset;
  4463. }
  4464. }
  4465. if (offset > maxOffset) {
  4466. offset = maxOffset;
  4467. }
  4468. var tmp = lastOffset;
  4469. lastOffset = hint - offset;
  4470. offset = hint - tmp;
  4471. }
  4472. lastOffset++;
  4473. while (lastOffset < offset) {
  4474. var m = lastOffset + (offset - lastOffset >>> 1);
  4475. if (compare(value, array[start + m]) > 0) {
  4476. lastOffset = m + 1;
  4477. }
  4478. else {
  4479. offset = m;
  4480. }
  4481. }
  4482. return offset;
  4483. }
  4484. function gallopRight(value, array, start, length, hint, compare) {
  4485. var lastOffset = 0;
  4486. var maxOffset = 0;
  4487. var offset = 1;
  4488. if (compare(value, array[start + hint]) < 0) {
  4489. maxOffset = hint + 1;
  4490. while (offset < maxOffset && compare(value, array[start + hint - offset]) < 0) {
  4491. lastOffset = offset;
  4492. offset = (offset << 1) + 1;
  4493. if (offset <= 0) {
  4494. offset = maxOffset;
  4495. }
  4496. }
  4497. if (offset > maxOffset) {
  4498. offset = maxOffset;
  4499. }
  4500. var tmp = lastOffset;
  4501. lastOffset = hint - offset;
  4502. offset = hint - tmp;
  4503. }
  4504. else {
  4505. maxOffset = length - hint;
  4506. while (offset < maxOffset && compare(value, array[start + hint + offset]) >= 0) {
  4507. lastOffset = offset;
  4508. offset = (offset << 1) + 1;
  4509. if (offset <= 0) {
  4510. offset = maxOffset;
  4511. }
  4512. }
  4513. if (offset > maxOffset) {
  4514. offset = maxOffset;
  4515. }
  4516. lastOffset += hint;
  4517. offset += hint;
  4518. }
  4519. lastOffset++;
  4520. while (lastOffset < offset) {
  4521. var m = lastOffset + (offset - lastOffset >>> 1);
  4522. if (compare(value, array[start + m]) < 0) {
  4523. offset = m;
  4524. }
  4525. else {
  4526. lastOffset = m + 1;
  4527. }
  4528. }
  4529. return offset;
  4530. }
  4531. function TimSort(array, compare) {
  4532. var minGallop = DEFAULT_MIN_GALLOPING;
  4533. var runStart;
  4534. var runLength;
  4535. var stackSize = 0;
  4536. var tmp = [];
  4537. runStart = [];
  4538. runLength = [];
  4539. function pushRun(_runStart, _runLength) {
  4540. runStart[stackSize] = _runStart;
  4541. runLength[stackSize] = _runLength;
  4542. stackSize += 1;
  4543. }
  4544. function mergeRuns() {
  4545. while (stackSize > 1) {
  4546. var n = stackSize - 2;
  4547. if (n >= 1 && runLength[n - 1] <= runLength[n] + runLength[n + 1] || n >= 2 && runLength[n - 2] <= runLength[n] + runLength[n - 1]) {
  4548. if (runLength[n - 1] < runLength[n + 1]) {
  4549. n--;
  4550. }
  4551. }
  4552. else if (runLength[n] > runLength[n + 1]) {
  4553. break;
  4554. }
  4555. mergeAt(n);
  4556. }
  4557. }
  4558. function forceMergeRuns() {
  4559. while (stackSize > 1) {
  4560. var n = stackSize - 2;
  4561. if (n > 0 && runLength[n - 1] < runLength[n + 1]) {
  4562. n--;
  4563. }
  4564. mergeAt(n);
  4565. }
  4566. }
  4567. function mergeAt(i) {
  4568. var start1 = runStart[i];
  4569. var length1 = runLength[i];
  4570. var start2 = runStart[i + 1];
  4571. var length2 = runLength[i + 1];
  4572. runLength[i] = length1 + length2;
  4573. if (i === stackSize - 3) {
  4574. runStart[i + 1] = runStart[i + 2];
  4575. runLength[i + 1] = runLength[i + 2];
  4576. }
  4577. stackSize--;
  4578. var k = gallopRight(array[start2], array, start1, length1, 0, compare);
  4579. start1 += k;
  4580. length1 -= k;
  4581. if (length1 === 0) {
  4582. return;
  4583. }
  4584. length2 = gallopLeft(array[start1 + length1 - 1], array, start2, length2, length2 - 1, compare);
  4585. if (length2 === 0) {
  4586. return;
  4587. }
  4588. if (length1 <= length2) {
  4589. mergeLow(start1, length1, start2, length2);
  4590. }
  4591. else {
  4592. mergeHigh(start1, length1, start2, length2);
  4593. }
  4594. }
  4595. function mergeLow(start1, length1, start2, length2) {
  4596. var i = 0;
  4597. for (i = 0; i < length1; i++) {
  4598. tmp[i] = array[start1 + i];
  4599. }
  4600. var cursor1 = 0;
  4601. var cursor2 = start2;
  4602. var dest = start1;
  4603. array[dest++] = array[cursor2++];
  4604. if (--length2 === 0) {
  4605. for (i = 0; i < length1; i++) {
  4606. array[dest + i] = tmp[cursor1 + i];
  4607. }
  4608. return;
  4609. }
  4610. if (length1 === 1) {
  4611. for (i = 0; i < length2; i++) {
  4612. array[dest + i] = array[cursor2 + i];
  4613. }
  4614. array[dest + length2] = tmp[cursor1];
  4615. return;
  4616. }
  4617. var _minGallop = minGallop;
  4618. var count1, count2, exit;
  4619. while (1) {
  4620. count1 = 0;
  4621. count2 = 0;
  4622. exit = false;
  4623. do {
  4624. if (compare(array[cursor2], tmp[cursor1]) < 0) {
  4625. array[dest++] = array[cursor2++];
  4626. count2++;
  4627. count1 = 0;
  4628. if (--length2 === 0) {
  4629. exit = true;
  4630. break;
  4631. }
  4632. }
  4633. else {
  4634. array[dest++] = tmp[cursor1++];
  4635. count1++;
  4636. count2 = 0;
  4637. if (--length1 === 1) {
  4638. exit = true;
  4639. break;
  4640. }
  4641. }
  4642. } while ((count1 | count2) < _minGallop);
  4643. if (exit) {
  4644. break;
  4645. }
  4646. do {
  4647. count1 = gallopRight(array[cursor2], tmp, cursor1, length1, 0, compare);
  4648. if (count1 !== 0) {
  4649. for (i = 0; i < count1; i++) {
  4650. array[dest + i] = tmp[cursor1 + i];
  4651. }
  4652. dest += count1;
  4653. cursor1 += count1;
  4654. length1 -= count1;
  4655. if (length1 <= 1) {
  4656. exit = true;
  4657. break;
  4658. }
  4659. }
  4660. array[dest++] = array[cursor2++];
  4661. if (--length2 === 0) {
  4662. exit = true;
  4663. break;
  4664. }
  4665. count2 = gallopLeft(tmp[cursor1], array, cursor2, length2, 0, compare);
  4666. if (count2 !== 0) {
  4667. for (i = 0; i < count2; i++) {
  4668. array[dest + i] = array[cursor2 + i];
  4669. }
  4670. dest += count2;
  4671. cursor2 += count2;
  4672. length2 -= count2;
  4673. if (length2 === 0) {
  4674. exit = true;
  4675. break;
  4676. }
  4677. }
  4678. array[dest++] = tmp[cursor1++];
  4679. if (--length1 === 1) {
  4680. exit = true;
  4681. break;
  4682. }
  4683. _minGallop--;
  4684. } while (count1 >= DEFAULT_MIN_GALLOPING || count2 >= DEFAULT_MIN_GALLOPING);
  4685. if (exit) {
  4686. break;
  4687. }
  4688. if (_minGallop < 0) {
  4689. _minGallop = 0;
  4690. }
  4691. _minGallop += 2;
  4692. }
  4693. minGallop = _minGallop;
  4694. minGallop < 1 && (minGallop = 1);
  4695. if (length1 === 1) {
  4696. for (i = 0; i < length2; i++) {
  4697. array[dest + i] = array[cursor2 + i];
  4698. }
  4699. array[dest + length2] = tmp[cursor1];
  4700. }
  4701. else if (length1 === 0) {
  4702. throw new Error();
  4703. // throw new Error('mergeLow preconditions were not respected');
  4704. }
  4705. else {
  4706. for (i = 0; i < length1; i++) {
  4707. array[dest + i] = tmp[cursor1 + i];
  4708. }
  4709. }
  4710. }
  4711. function mergeHigh (start1, length1, start2, length2) {
  4712. var i = 0;
  4713. for (i = 0; i < length2; i++) {
  4714. tmp[i] = array[start2 + i];
  4715. }
  4716. var cursor1 = start1 + length1 - 1;
  4717. var cursor2 = length2 - 1;
  4718. var dest = start2 + length2 - 1;
  4719. var customCursor = 0;
  4720. var customDest = 0;
  4721. array[dest--] = array[cursor1--];
  4722. if (--length1 === 0) {
  4723. customCursor = dest - (length2 - 1);
  4724. for (i = 0; i < length2; i++) {
  4725. array[customCursor + i] = tmp[i];
  4726. }
  4727. return;
  4728. }
  4729. if (length2 === 1) {
  4730. dest -= length1;
  4731. cursor1 -= length1;
  4732. customDest = dest + 1;
  4733. customCursor = cursor1 + 1;
  4734. for (i = length1 - 1; i >= 0; i--) {
  4735. array[customDest + i] = array[customCursor + i];
  4736. }
  4737. array[dest] = tmp[cursor2];
  4738. return;
  4739. }
  4740. var _minGallop = minGallop;
  4741. while (true) {
  4742. var count1 = 0;
  4743. var count2 = 0;
  4744. var exit = false;
  4745. do {
  4746. if (compare(tmp[cursor2], array[cursor1]) < 0) {
  4747. array[dest--] = array[cursor1--];
  4748. count1++;
  4749. count2 = 0;
  4750. if (--length1 === 0) {
  4751. exit = true;
  4752. break;
  4753. }
  4754. }
  4755. else {
  4756. array[dest--] = tmp[cursor2--];
  4757. count2++;
  4758. count1 = 0;
  4759. if (--length2 === 1) {
  4760. exit = true;
  4761. break;
  4762. }
  4763. }
  4764. } while ((count1 | count2) < _minGallop);
  4765. if (exit) {
  4766. break;
  4767. }
  4768. do {
  4769. count1 = length1 - gallopRight(tmp[cursor2], array, start1, length1, length1 - 1, compare);
  4770. if (count1 !== 0) {
  4771. dest -= count1;
  4772. cursor1 -= count1;
  4773. length1 -= count1;
  4774. customDest = dest + 1;
  4775. customCursor = cursor1 + 1;
  4776. for (i = count1 - 1; i >= 0; i--) {
  4777. array[customDest + i] = array[customCursor + i];
  4778. }
  4779. if (length1 === 0) {
  4780. exit = true;
  4781. break;
  4782. }
  4783. }
  4784. array[dest--] = tmp[cursor2--];
  4785. if (--length2 === 1) {
  4786. exit = true;
  4787. break;
  4788. }
  4789. count2 = length2 - gallopLeft(array[cursor1], tmp, 0, length2, length2 - 1, compare);
  4790. if (count2 !== 0) {
  4791. dest -= count2;
  4792. cursor2 -= count2;
  4793. length2 -= count2;
  4794. customDest = dest + 1;
  4795. customCursor = cursor2 + 1;
  4796. for (i = 0; i < count2; i++) {
  4797. array[customDest + i] = tmp[customCursor + i];
  4798. }
  4799. if (length2 <= 1) {
  4800. exit = true;
  4801. break;
  4802. }
  4803. }
  4804. array[dest--] = array[cursor1--];
  4805. if (--length1 === 0) {
  4806. exit = true;
  4807. break;
  4808. }
  4809. _minGallop--;
  4810. } while (count1 >= DEFAULT_MIN_GALLOPING || count2 >= DEFAULT_MIN_GALLOPING);
  4811. if (exit) {
  4812. break;
  4813. }
  4814. if (_minGallop < 0) {
  4815. _minGallop = 0;
  4816. }
  4817. _minGallop += 2;
  4818. }
  4819. minGallop = _minGallop;
  4820. if (minGallop < 1) {
  4821. minGallop = 1;
  4822. }
  4823. if (length2 === 1) {
  4824. dest -= length1;
  4825. cursor1 -= length1;
  4826. customDest = dest + 1;
  4827. customCursor = cursor1 + 1;
  4828. for (i = length1 - 1; i >= 0; i--) {
  4829. array[customDest + i] = array[customCursor + i];
  4830. }
  4831. array[dest] = tmp[cursor2];
  4832. }
  4833. else if (length2 === 0) {
  4834. throw new Error();
  4835. // throw new Error('mergeHigh preconditions were not respected');
  4836. }
  4837. else {
  4838. customCursor = dest - (length2 - 1);
  4839. for (i = 0; i < length2; i++) {
  4840. array[customCursor + i] = tmp[i];
  4841. }
  4842. }
  4843. }
  4844. this.mergeRuns = mergeRuns;
  4845. this.forceMergeRuns = forceMergeRuns;
  4846. this.pushRun = pushRun;
  4847. }
  4848. function sort(array, compare, lo, hi) {
  4849. if (!lo) {
  4850. lo = 0;
  4851. }
  4852. if (!hi) {
  4853. hi = array.length;
  4854. }
  4855. var remaining = hi - lo;
  4856. if (remaining < 2) {
  4857. return;
  4858. }
  4859. var runLength = 0;
  4860. if (remaining < DEFAULT_MIN_MERGE) {
  4861. runLength = makeAscendingRun(array, lo, hi, compare);
  4862. binaryInsertionSort(array, lo, hi, lo + runLength, compare);
  4863. return;
  4864. }
  4865. var ts = new TimSort(array, compare);
  4866. var minRun = minRunLength(remaining);
  4867. do {
  4868. runLength = makeAscendingRun(array, lo, hi, compare);
  4869. if (runLength < minRun) {
  4870. var force = remaining;
  4871. if (force > minRun) {
  4872. force = minRun;
  4873. }
  4874. binaryInsertionSort(array, lo, lo + force, lo + runLength, compare);
  4875. runLength = force;
  4876. }
  4877. ts.pushRun(lo, runLength);
  4878. ts.mergeRuns();
  4879. remaining -= runLength;
  4880. lo += runLength;
  4881. } while (remaining !== 0);
  4882. ts.forceMergeRuns();
  4883. }
  4884. // Use timsort because in most case elements are partially sorted
  4885. // https://jsfiddle.net/pissang/jr4x7mdm/8/
  4886. function shapeCompareFunc(a, b) {
  4887. if (a.zlevel === b.zlevel) {
  4888. if (a.z === b.z) {
  4889. // if (a.z2 === b.z2) {
  4890. // // FIXME Slow has renderidx compare
  4891. // // http://stackoverflow.com/questions/20883421/sorting-in-javascript-should-every-compare-function-have-a-return-0-statement
  4892. // // https://github.com/v8/v8/blob/47cce544a31ed5577ffe2963f67acb4144ee0232/src/js/array.js#L1012
  4893. // return a.__renderidx - b.__renderidx;
  4894. // }
  4895. return a.z2 - b.z2;
  4896. }
  4897. return a.z - b.z;
  4898. }
  4899. return a.zlevel - b.zlevel;
  4900. }
  4901. /**
  4902. * 内容仓库 (M)
  4903. * @alias module:zrender/Storage
  4904. * @constructor
  4905. */
  4906. var Storage = function () { // jshint ignore:line
  4907. this._roots = [];
  4908. this._displayList = [];
  4909. this._displayListLen = 0;
  4910. };
  4911. Storage.prototype = {
  4912. constructor: Storage,
  4913. /**
  4914. * @param {Function} cb
  4915. *
  4916. */
  4917. traverse: function (cb, context) {
  4918. for (var i = 0; i < this._roots.length; i++) {
  4919. this._roots[i].traverse(cb, context);
  4920. }
  4921. },
  4922. /**
  4923. * 返回所有图形的绘制队列
  4924. * @param {boolean} [update=false] 是否在返回前更新该数组
  4925. * @param {boolean} [includeIgnore=false] 是否包含 ignore 的数组, 在 update 为 true 的时候有效
  4926. *
  4927. * 详见{@link module:zrender/graphic/Displayable.prototype.updateDisplayList}
  4928. * @return {Array.<module:zrender/graphic/Displayable>}
  4929. */
  4930. getDisplayList: function (update, includeIgnore) {
  4931. includeIgnore = includeIgnore || false;
  4932. if (update) {
  4933. this.updateDisplayList(includeIgnore);
  4934. }
  4935. return this._displayList;
  4936. },
  4937. /**
  4938. * 更新图形的绘制队列。
  4939. * 每次绘制前都会调用,该方法会先深度优先遍历整个树,更新所有Group和Shape的变换并且把所有可见的Shape保存到数组中,
  4940. * 最后根据绘制的优先级(zlevel > z > 插入顺序)排序得到绘制队列
  4941. * @param {boolean} [includeIgnore=false] 是否包含 ignore 的数组
  4942. */
  4943. updateDisplayList: function (includeIgnore) {
  4944. this._displayListLen = 0;
  4945. var roots = this._roots;
  4946. var displayList = this._displayList;
  4947. for (var i = 0, len = roots.length; i < len; i++) {
  4948. this._updateAndAddDisplayable(roots[i], null, includeIgnore);
  4949. }
  4950. displayList.length = this._displayListLen;
  4951. env$1.canvasSupported && sort(displayList, shapeCompareFunc);
  4952. },
  4953. _updateAndAddDisplayable: function (el, clipPaths, includeIgnore) {
  4954. if (el.ignore && !includeIgnore) {
  4955. return;
  4956. }
  4957. el.beforeUpdate();
  4958. if (el.__dirty) {
  4959. el.update();
  4960. }
  4961. el.afterUpdate();
  4962. var userSetClipPath = el.clipPath;
  4963. if (userSetClipPath) {
  4964. // FIXME 效率影响
  4965. if (clipPaths) {
  4966. clipPaths = clipPaths.slice();
  4967. }
  4968. else {
  4969. clipPaths = [];
  4970. }
  4971. var currentClipPath = userSetClipPath;
  4972. var parentClipPath = el;
  4973. // Recursively add clip path
  4974. while (currentClipPath) {
  4975. // clipPath 的变换是基于使用这个 clipPath 的元素
  4976. currentClipPath.parent = parentClipPath;
  4977. currentClipPath.updateTransform();
  4978. clipPaths.push(currentClipPath);
  4979. parentClipPath = currentClipPath;
  4980. currentClipPath = currentClipPath.clipPath;
  4981. }
  4982. }
  4983. if (el.isGroup) {
  4984. var children = el._children;
  4985. for (var i = 0; i < children.length; i++) {
  4986. var child = children[i];
  4987. // Force to mark as dirty if group is dirty
  4988. // FIXME __dirtyPath ?
  4989. if (el.__dirty) {
  4990. child.__dirty = true;
  4991. }
  4992. this._updateAndAddDisplayable(child, clipPaths, includeIgnore);
  4993. }
  4994. // Mark group clean here
  4995. el.__dirty = false;
  4996. }
  4997. else {
  4998. el.__clipPaths = clipPaths;
  4999. this._displayList[this._displayListLen++] = el;
  5000. }
  5001. },
  5002. /**
  5003. * 添加图形(Shape)或者组(Group)到根节点
  5004. * @param {module:zrender/Element} el
  5005. */
  5006. addRoot: function (el) {
  5007. if (el.__storage === this) {
  5008. return;
  5009. }
  5010. if (el instanceof Group) {
  5011. el.addChildrenToStorage(this);
  5012. }
  5013. this.addToStorage(el);
  5014. this._roots.push(el);
  5015. },
  5016. /**
  5017. * 删除指定的图形(Shape)或者组(Group)
  5018. * @param {string|Array.<string>} [el] 如果为空清空整个Storage
  5019. */
  5020. delRoot: function (el) {
  5021. if (el == null) {
  5022. // 不指定el清空
  5023. for (var i = 0; i < this._roots.length; i++) {
  5024. var root = this._roots[i];
  5025. if (root instanceof Group) {
  5026. root.delChildrenFromStorage(this);
  5027. }
  5028. }
  5029. this._roots = [];
  5030. this._displayList = [];
  5031. this._displayListLen = 0;
  5032. return;
  5033. }
  5034. if (el instanceof Array) {
  5035. for (var i = 0, l = el.length; i < l; i++) {
  5036. this.delRoot(el[i]);
  5037. }
  5038. return;
  5039. }
  5040. var idx = indexOf(this._roots, el);
  5041. if (idx >= 0) {
  5042. this.delFromStorage(el);
  5043. this._roots.splice(idx, 1);
  5044. if (el instanceof Group) {
  5045. el.delChildrenFromStorage(this);
  5046. }
  5047. }
  5048. },
  5049. addToStorage: function (el) {
  5050. if (el) {
  5051. el.__storage = this;
  5052. el.dirty(false);
  5053. }
  5054. return this;
  5055. },
  5056. delFromStorage: function (el) {
  5057. if (el) {
  5058. el.__storage = null;
  5059. }
  5060. return this;
  5061. },
  5062. /**
  5063. * 清空并且释放Storage
  5064. */
  5065. dispose: function () {
  5066. this._renderList =
  5067. this._roots = null;
  5068. },
  5069. displayableSortFunc: shapeCompareFunc
  5070. };
  5071. var SHADOW_PROPS = {
  5072. 'shadowBlur': 1,
  5073. 'shadowOffsetX': 1,
  5074. 'shadowOffsetY': 1,
  5075. 'textShadowBlur': 1,
  5076. 'textShadowOffsetX': 1,
  5077. 'textShadowOffsetY': 1,
  5078. 'textBoxShadowBlur': 1,
  5079. 'textBoxShadowOffsetX': 1,
  5080. 'textBoxShadowOffsetY': 1
  5081. };
  5082. var fixShadow = function (ctx, propName, value) {
  5083. if (SHADOW_PROPS.hasOwnProperty(propName)) {
  5084. return value *= ctx.dpr;
  5085. }
  5086. return value;
  5087. };
  5088. var STYLE_COMMON_PROPS = [
  5089. ['shadowBlur', 0], ['shadowOffsetX', 0], ['shadowOffsetY', 0], ['shadowColor', '#000'],
  5090. ['lineCap', 'butt'], ['lineJoin', 'miter'], ['miterLimit', 10]
  5091. ];
  5092. // var SHADOW_PROPS = STYLE_COMMON_PROPS.slice(0, 4);
  5093. // var LINE_PROPS = STYLE_COMMON_PROPS.slice(4);
  5094. var Style = function (opts, host) {
  5095. this.extendFrom(opts, false);
  5096. this.host = host;
  5097. };
  5098. function createLinearGradient(ctx, obj, rect) {
  5099. var x = obj.x == null ? 0 : obj.x;
  5100. var x2 = obj.x2 == null ? 1 : obj.x2;
  5101. var y = obj.y == null ? 0 : obj.y;
  5102. var y2 = obj.y2 == null ? 0 : obj.y2;
  5103. if (!obj.global) {
  5104. x = x * rect.width + rect.x;
  5105. x2 = x2 * rect.width + rect.x;
  5106. y = y * rect.height + rect.y;
  5107. y2 = y2 * rect.height + rect.y;
  5108. }
  5109. // Fix NaN when rect is Infinity
  5110. x = isNaN(x) ? 0 : x;
  5111. x2 = isNaN(x2) ? 1 : x2;
  5112. y = isNaN(y) ? 0 : y;
  5113. y2 = isNaN(y2) ? 0 : y2;
  5114. var canvasGradient = ctx.createLinearGradient(x, y, x2, y2);
  5115. return canvasGradient;
  5116. }
  5117. function createRadialGradient(ctx, obj, rect) {
  5118. var width = rect.width;
  5119. var height = rect.height;
  5120. var min = Math.min(width, height);
  5121. var x = obj.x == null ? 0.5 : obj.x;
  5122. var y = obj.y == null ? 0.5 : obj.y;
  5123. var r = obj.r == null ? 0.5 : obj.r;
  5124. if (!obj.global) {
  5125. x = x * width + rect.x;
  5126. y = y * height + rect.y;
  5127. r = r * min;
  5128. }
  5129. var canvasGradient = ctx.createRadialGradient(x, y, 0, x, y, r);
  5130. return canvasGradient;
  5131. }
  5132. Style.prototype = {
  5133. constructor: Style,
  5134. /**
  5135. * @type {module:zrender/graphic/Displayable}
  5136. */
  5137. host: null,
  5138. /**
  5139. * @type {string}
  5140. */
  5141. fill: '#000',
  5142. /**
  5143. * @type {string}
  5144. */
  5145. stroke: null,
  5146. /**
  5147. * @type {number}
  5148. */
  5149. opacity: 1,
  5150. /**
  5151. * @type {Array.<number>}
  5152. */
  5153. lineDash: null,
  5154. /**
  5155. * @type {number}
  5156. */
  5157. lineDashOffset: 0,
  5158. /**
  5159. * @type {number}
  5160. */
  5161. shadowBlur: 0,
  5162. /**
  5163. * @type {number}
  5164. */
  5165. shadowOffsetX: 0,
  5166. /**
  5167. * @type {number}
  5168. */
  5169. shadowOffsetY: 0,
  5170. /**
  5171. * @type {number}
  5172. */
  5173. lineWidth: 1,
  5174. /**
  5175. * If stroke ignore scale
  5176. * @type {Boolean}
  5177. */
  5178. strokeNoScale: false,
  5179. // Bounding rect text configuration
  5180. // Not affected by element transform
  5181. /**
  5182. * @type {string}
  5183. */
  5184. text: null,
  5185. /**
  5186. * If `fontSize` or `fontFamily` exists, `font` will be reset by
  5187. * `fontSize`, `fontStyle`, `fontWeight`, `fontFamily`.
  5188. * So do not visit it directly in upper application (like echarts),
  5189. * but use `contain/text#makeFont` instead.
  5190. * @type {string}
  5191. */
  5192. font: null,
  5193. /**
  5194. * The same as font. Use font please.
  5195. * @deprecated
  5196. * @type {string}
  5197. */
  5198. textFont: null,
  5199. /**
  5200. * It helps merging respectively, rather than parsing an entire font string.
  5201. * @type {string}
  5202. */
  5203. fontStyle: null,
  5204. /**
  5205. * It helps merging respectively, rather than parsing an entire font string.
  5206. * @type {string}
  5207. */
  5208. fontWeight: null,
  5209. /**
  5210. * It helps merging respectively, rather than parsing an entire font string.
  5211. * Should be 12 but not '12px'.
  5212. * @type {number}
  5213. */
  5214. fontSize: null,
  5215. /**
  5216. * It helps merging respectively, rather than parsing an entire font string.
  5217. * @type {string}
  5218. */
  5219. fontFamily: null,
  5220. /**
  5221. * Reserved for special functinality, like 'hr'.
  5222. * @type {string}
  5223. */
  5224. textTag: null,
  5225. /**
  5226. * @type {string}
  5227. */
  5228. textFill: '#000',
  5229. /**
  5230. * @type {string}
  5231. */
  5232. textStroke: null,
  5233. /**
  5234. * @type {number}
  5235. */
  5236. textWidth: null,
  5237. /**
  5238. * Only for textBackground.
  5239. * @type {number}
  5240. */
  5241. textHeight: null,
  5242. /**
  5243. * textStroke may be set as some color as a default
  5244. * value in upper applicaion, where the default value
  5245. * of textStrokeWidth should be 0 to make sure that
  5246. * user can choose to do not use text stroke.
  5247. * @type {number}
  5248. */
  5249. textStrokeWidth: 0,
  5250. /**
  5251. * @type {number}
  5252. */
  5253. textLineHeight: null,
  5254. /**
  5255. * 'inside', 'left', 'right', 'top', 'bottom'
  5256. * [x, y]
  5257. * Based on x, y of rect.
  5258. * @type {string|Array.<number>}
  5259. * @default 'inside'
  5260. */
  5261. textPosition: 'inside',
  5262. /**
  5263. * If not specified, use the boundingRect of a `displayable`.
  5264. * @type {Object}
  5265. */
  5266. textRect: null,
  5267. /**
  5268. * [x, y]
  5269. * @type {Array.<number>}
  5270. */
  5271. textOffset: null,
  5272. /**
  5273. * @type {string}
  5274. */
  5275. textAlign: null,
  5276. /**
  5277. * @type {string}
  5278. */
  5279. textVerticalAlign: null,
  5280. /**
  5281. * @type {number}
  5282. */
  5283. textDistance: 5,
  5284. /**
  5285. * @type {string}
  5286. */
  5287. textShadowColor: 'transparent',
  5288. /**
  5289. * @type {number}
  5290. */
  5291. textShadowBlur: 0,
  5292. /**
  5293. * @type {number}
  5294. */
  5295. textShadowOffsetX: 0,
  5296. /**
  5297. * @type {number}
  5298. */
  5299. textShadowOffsetY: 0,
  5300. /**
  5301. * @type {string}
  5302. */
  5303. textBoxShadowColor: 'transparent',
  5304. /**
  5305. * @type {number}
  5306. */
  5307. textBoxShadowBlur: 0,
  5308. /**
  5309. * @type {number}
  5310. */
  5311. textBoxShadowOffsetX: 0,
  5312. /**
  5313. * @type {number}
  5314. */
  5315. textBoxShadowOffsetY: 0,
  5316. /**
  5317. * Whether transform text.
  5318. * Only useful in Path and Image element
  5319. * @type {boolean}
  5320. */
  5321. transformText: false,
  5322. /**
  5323. * Text rotate around position of Path or Image
  5324. * Only useful in Path and Image element and transformText is false.
  5325. */
  5326. textRotation: 0,
  5327. /**
  5328. * Text origin of text rotation, like [10, 40].
  5329. * Based on x, y of rect.
  5330. * Useful in label rotation of circular symbol.
  5331. * By default, this origin is textPosition.
  5332. * Can be 'center'.
  5333. * @type {string|Array.<number>}
  5334. */
  5335. textOrigin: null,
  5336. /**
  5337. * @type {string}
  5338. */
  5339. textBackgroundColor: null,
  5340. /**
  5341. * @type {string}
  5342. */
  5343. textBorderColor: null,
  5344. /**
  5345. * @type {number}
  5346. */
  5347. textBorderWidth: 0,
  5348. /**
  5349. * @type {number}
  5350. */
  5351. textBorderRadius: 0,
  5352. /**
  5353. * Can be `2` or `[2, 4]` or `[2, 3, 4, 5]`
  5354. * @type {number|Array.<number>}
  5355. */
  5356. textPadding: null,
  5357. /**
  5358. * Text styles for rich text.
  5359. * @type {Object}
  5360. */
  5361. rich: null,
  5362. /**
  5363. * {outerWidth, outerHeight, ellipsis, placeholder}
  5364. * @type {Object}
  5365. */
  5366. truncate: null,
  5367. /**
  5368. * https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
  5369. * @type {string}
  5370. */
  5371. blend: null,
  5372. /**
  5373. * @param {CanvasRenderingContext2D} ctx
  5374. */
  5375. bind: function (ctx, el, prevEl) {
  5376. var style = this;
  5377. var prevStyle = prevEl && prevEl.style;
  5378. var firstDraw = !prevStyle;
  5379. for (var i = 0; i < STYLE_COMMON_PROPS.length; i++) {
  5380. var prop = STYLE_COMMON_PROPS[i];
  5381. var styleName = prop[0];
  5382. if (firstDraw || style[styleName] !== prevStyle[styleName]) {
  5383. // FIXME Invalid property value will cause style leak from previous element.
  5384. ctx[styleName] =
  5385. fixShadow(ctx, styleName, style[styleName] || prop[1]);
  5386. }
  5387. }
  5388. if ((firstDraw || style.fill !== prevStyle.fill)) {
  5389. ctx.fillStyle = style.fill;
  5390. }
  5391. if ((firstDraw || style.stroke !== prevStyle.stroke)) {
  5392. ctx.strokeStyle = style.stroke;
  5393. }
  5394. if ((firstDraw || style.opacity !== prevStyle.opacity)) {
  5395. ctx.globalAlpha = style.opacity == null ? 1 : style.opacity;
  5396. }
  5397. if ((firstDraw || style.blend !== prevStyle.blend)) {
  5398. ctx.globalCompositeOperation = style.blend || 'source-over';
  5399. }
  5400. if (this.hasStroke()) {
  5401. var lineWidth = style.lineWidth;
  5402. ctx.lineWidth = lineWidth / (
  5403. (this.strokeNoScale && el && el.getLineScale) ? el.getLineScale() : 1
  5404. );
  5405. }
  5406. },
  5407. hasFill: function () {
  5408. var fill = this.fill;
  5409. return fill != null && fill !== 'none';
  5410. },
  5411. hasStroke: function () {
  5412. var stroke = this.stroke;
  5413. return stroke != null && stroke !== 'none' && this.lineWidth > 0;
  5414. },
  5415. /**
  5416. * Extend from other style
  5417. * @param {zrender/graphic/Style} otherStyle
  5418. * @param {boolean} overwrite true: overwrirte any way.
  5419. * false: overwrite only when !target.hasOwnProperty
  5420. * others: overwrite when property is not null/undefined.
  5421. */
  5422. extendFrom: function (otherStyle, overwrite) {
  5423. if (otherStyle) {
  5424. for (var name in otherStyle) {
  5425. if (otherStyle.hasOwnProperty(name)
  5426. && (overwrite === true
  5427. || (
  5428. overwrite === false
  5429. ? !this.hasOwnProperty(name)
  5430. : otherStyle[name] != null
  5431. )
  5432. )
  5433. ) {
  5434. this[name] = otherStyle[name];
  5435. }
  5436. }
  5437. }
  5438. },
  5439. /**
  5440. * Batch setting style with a given object
  5441. * @param {Object|string} obj
  5442. * @param {*} [obj]
  5443. */
  5444. set: function (obj, value) {
  5445. if (typeof obj === 'string') {
  5446. this[obj] = value;
  5447. }
  5448. else {
  5449. this.extendFrom(obj, true);
  5450. }
  5451. },
  5452. /**
  5453. * Clone
  5454. * @return {zrender/graphic/Style} [description]
  5455. */
  5456. clone: function () {
  5457. var newStyle = new this.constructor();
  5458. newStyle.extendFrom(this, true);
  5459. return newStyle;
  5460. },
  5461. getGradient: function (ctx, obj, rect) {
  5462. var method = obj.type === 'radial' ? createRadialGradient : createLinearGradient;
  5463. var canvasGradient = method(ctx, obj, rect);
  5464. var colorStops = obj.colorStops;
  5465. for (var i = 0; i < colorStops.length; i++) {
  5466. canvasGradient.addColorStop(
  5467. colorStops[i].offset, colorStops[i].color
  5468. );
  5469. }
  5470. return canvasGradient;
  5471. }
  5472. };
  5473. var styleProto = Style.prototype;
  5474. for (var i = 0; i < STYLE_COMMON_PROPS.length; i++) {
  5475. var prop = STYLE_COMMON_PROPS[i];
  5476. if (!(prop[0] in styleProto)) {
  5477. styleProto[prop[0]] = prop[1];
  5478. }
  5479. }
  5480. // Provide for others
  5481. Style.getGradient = styleProto.getGradient;
  5482. var Pattern = function (image, repeat) {
  5483. // Should do nothing more in this constructor. Because gradient can be
  5484. // declard by `color: {image: ...}`, where this constructor will not be called.
  5485. this.image = image;
  5486. this.repeat = repeat;
  5487. // Can be cloned
  5488. this.type = 'pattern';
  5489. };
  5490. Pattern.prototype.getCanvasPattern = function (ctx) {
  5491. return ctx.createPattern(this.image, this.repeat || 'repeat');
  5492. };
  5493. /**
  5494. * @module zrender/Layer
  5495. * @author pissang(https://www.github.com/pissang)
  5496. */
  5497. function returnFalse() {
  5498. return false;
  5499. }
  5500. /**
  5501. * 创建dom
  5502. *
  5503. * @inner
  5504. * @param {string} id dom id 待用
  5505. * @param {Painter} painter painter instance
  5506. * @param {number} number
  5507. */
  5508. function createDom(id, painter, dpr) {
  5509. var newDom = createCanvas();
  5510. var width = painter.getWidth();
  5511. var height = painter.getHeight();
  5512. var newDomStyle = newDom.style;
  5513. if (newDomStyle) { // In node or some other non-browser environment
  5514. newDomStyle.position = 'absolute';
  5515. newDomStyle.left = 0;
  5516. newDomStyle.top = 0;
  5517. newDomStyle.width = width + 'px';
  5518. newDomStyle.height = height + 'px';
  5519. newDom.setAttribute('data-zr-dom-id', id);
  5520. }
  5521. newDom.width = width * dpr;
  5522. newDom.height = height * dpr;
  5523. return newDom;
  5524. }
  5525. /**
  5526. * @alias module:zrender/Layer
  5527. * @constructor
  5528. * @extends module:zrender/mixin/Transformable
  5529. * @param {string} id
  5530. * @param {module:zrender/Painter} painter
  5531. * @param {number} [dpr]
  5532. */
  5533. var Layer = function(id, painter, dpr) {
  5534. var dom;
  5535. dpr = dpr || devicePixelRatio;
  5536. if (typeof id === 'string') {
  5537. dom = createDom(id, painter, dpr);
  5538. }
  5539. // Not using isDom because in node it will return false
  5540. else if (isObject$1(id)) {
  5541. dom = id;
  5542. id = dom.id;
  5543. }
  5544. this.id = id;
  5545. this.dom = dom;
  5546. var domStyle = dom.style;
  5547. if (domStyle) { // Not in node
  5548. dom.onselectstart = returnFalse; // 避免页面选中的尴尬
  5549. domStyle['-webkit-user-select'] = 'none';
  5550. domStyle['user-select'] = 'none';
  5551. domStyle['-webkit-touch-callout'] = 'none';
  5552. domStyle['-webkit-tap-highlight-color'] = 'rgba(0,0,0,0)';
  5553. domStyle['padding'] = 0;
  5554. domStyle['margin'] = 0;
  5555. domStyle['border-width'] = 0;
  5556. }
  5557. this.domBack = null;
  5558. this.ctxBack = null;
  5559. this.painter = painter;
  5560. this.config = null;
  5561. // Configs
  5562. /**
  5563. * 每次清空画布的颜色
  5564. * @type {string}
  5565. * @default 0
  5566. */
  5567. this.clearColor = 0;
  5568. /**
  5569. * 是否开启动态模糊
  5570. * @type {boolean}
  5571. * @default false
  5572. */
  5573. this.motionBlur = false;
  5574. /**
  5575. * 在开启动态模糊的时候使用,与上一帧混合的alpha值,值越大尾迹越明显
  5576. * @type {number}
  5577. * @default 0.7
  5578. */
  5579. this.lastFrameAlpha = 0.7;
  5580. /**
  5581. * Layer dpr
  5582. * @type {number}
  5583. */
  5584. this.dpr = dpr;
  5585. };
  5586. Layer.prototype = {
  5587. constructor: Layer,
  5588. __dirty: true,
  5589. __used: false,
  5590. __drawIndex: 0,
  5591. __startIndex: 0,
  5592. __endIndex: 0,
  5593. incremental: false,
  5594. getElementCount: function () {
  5595. return this.__endIndex - this.__startIndex;
  5596. },
  5597. initContext: function () {
  5598. this.ctx = this.dom.getContext('2d');
  5599. this.ctx.dpr = this.dpr;
  5600. },
  5601. createBackBuffer: function () {
  5602. var dpr = this.dpr;
  5603. this.domBack = createDom('back-' + this.id, this.painter, dpr);
  5604. this.ctxBack = this.domBack.getContext('2d');
  5605. if (dpr != 1) {
  5606. this.ctxBack.scale(dpr, dpr);
  5607. }
  5608. },
  5609. /**
  5610. * @param {number} width
  5611. * @param {number} height
  5612. */
  5613. resize: function (width, height) {
  5614. var dpr = this.dpr;
  5615. var dom = this.dom;
  5616. var domStyle = dom.style;
  5617. var domBack = this.domBack;
  5618. if (domStyle) {
  5619. domStyle.width = width + 'px';
  5620. domStyle.height = height + 'px';
  5621. }
  5622. dom.width = width * dpr;
  5623. dom.height = height * dpr;
  5624. if (domBack) {
  5625. domBack.width = width * dpr;
  5626. domBack.height = height * dpr;
  5627. if (dpr != 1) {
  5628. this.ctxBack.scale(dpr, dpr);
  5629. }
  5630. }
  5631. },
  5632. /**
  5633. * 清空该层画布
  5634. * @param {boolean} [clearAll]=false Clear all with out motion blur
  5635. * @param {Color} [clearColor]
  5636. */
  5637. clear: function (clearAll, clearColor) {
  5638. var dom = this.dom;
  5639. var ctx = this.ctx;
  5640. var width = dom.width;
  5641. var height = dom.height;
  5642. var clearColor = clearColor || this.clearColor;
  5643. var haveMotionBLur = this.motionBlur && !clearAll;
  5644. var lastFrameAlpha = this.lastFrameAlpha;
  5645. var dpr = this.dpr;
  5646. if (haveMotionBLur) {
  5647. if (!this.domBack) {
  5648. this.createBackBuffer();
  5649. }
  5650. this.ctxBack.globalCompositeOperation = 'copy';
  5651. this.ctxBack.drawImage(
  5652. dom, 0, 0,
  5653. width / dpr,
  5654. height / dpr
  5655. );
  5656. }
  5657. ctx.clearRect(0, 0, width, height);
  5658. if (clearColor && clearColor !== 'transparent') {
  5659. var clearColorGradientOrPattern;
  5660. // Gradient
  5661. if (clearColor.colorStops) {
  5662. // Cache canvas gradient
  5663. clearColorGradientOrPattern = clearColor.__canvasGradient || Style.getGradient(ctx, clearColor, {
  5664. x: 0,
  5665. y: 0,
  5666. width: width,
  5667. height: height
  5668. });
  5669. clearColor.__canvasGradient = clearColorGradientOrPattern;
  5670. }
  5671. // Pattern
  5672. else if (clearColor.image) {
  5673. clearColorGradientOrPattern = Pattern.prototype.getCanvasPattern.call(clearColor, ctx);
  5674. }
  5675. ctx.save();
  5676. ctx.fillStyle = clearColorGradientOrPattern || clearColor;
  5677. ctx.fillRect(0, 0, width, height);
  5678. ctx.restore();
  5679. }
  5680. if (haveMotionBLur) {
  5681. var domBack = this.domBack;
  5682. ctx.save();
  5683. ctx.globalAlpha = lastFrameAlpha;
  5684. ctx.drawImage(domBack, 0, 0, width, height);
  5685. ctx.restore();
  5686. }
  5687. }
  5688. };
  5689. var requestAnimationFrame = (
  5690. typeof window !== 'undefined'
  5691. && (
  5692. (window.requestAnimationFrame && window.requestAnimationFrame.bind(window))
  5693. // https://github.com/ecomfe/zrender/issues/189#issuecomment-224919809
  5694. || (window.msRequestAnimationFrame && window.msRequestAnimationFrame.bind(window))
  5695. || window.mozRequestAnimationFrame
  5696. || window.webkitRequestAnimationFrame
  5697. )
  5698. ) || function (func) {
  5699. setTimeout(func, 16);
  5700. };
  5701. var globalImageCache = new LRU(50);
  5702. /**
  5703. * @param {string|HTMLImageElement|HTMLCanvasElement|Canvas} newImageOrSrc
  5704. * @return {HTMLImageElement|HTMLCanvasElement|Canvas} image
  5705. */
  5706. function findExistImage(newImageOrSrc) {
  5707. if (typeof newImageOrSrc === 'string') {
  5708. var cachedImgObj = globalImageCache.get(newImageOrSrc);
  5709. return cachedImgObj && cachedImgObj.image;
  5710. }
  5711. else {
  5712. return newImageOrSrc;
  5713. }
  5714. }
  5715. /**
  5716. * Caution: User should cache loaded images, but not just count on LRU.
  5717. * Consider if required images more than LRU size, will dead loop occur?
  5718. *
  5719. * @param {string|HTMLImageElement|HTMLCanvasElement|Canvas} newImageOrSrc
  5720. * @param {HTMLImageElement|HTMLCanvasElement|Canvas} image Existent image.
  5721. * @param {module:zrender/Element} [hostEl] For calling `dirty`.
  5722. * @param {Function} [cb] params: (image, cbPayload)
  5723. * @param {Object} [cbPayload] Payload on cb calling.
  5724. * @return {HTMLImageElement|HTMLCanvasElement|Canvas} image
  5725. */
  5726. function createOrUpdateImage(newImageOrSrc, image, hostEl, cb, cbPayload) {
  5727. if (!newImageOrSrc) {
  5728. return image;
  5729. }
  5730. else if (typeof newImageOrSrc === 'string') {
  5731. // Image should not be loaded repeatly.
  5732. if ((image && image.__zrImageSrc === newImageOrSrc) || !hostEl) {
  5733. return image;
  5734. }
  5735. // Only when there is no existent image or existent image src
  5736. // is different, this method is responsible for load.
  5737. var cachedImgObj = globalImageCache.get(newImageOrSrc);
  5738. var pendingWrap = {hostEl: hostEl, cb: cb, cbPayload: cbPayload};
  5739. if (cachedImgObj) {
  5740. image = cachedImgObj.image;
  5741. !isImageReady(image) && cachedImgObj.pending.push(pendingWrap);
  5742. }
  5743. else {
  5744. !image && (image = new Image());
  5745. image.onload = imageOnLoad;
  5746. globalImageCache.put(
  5747. newImageOrSrc,
  5748. image.__cachedImgObj = {
  5749. image: image,
  5750. pending: [pendingWrap]
  5751. }
  5752. );
  5753. image.src = image.__zrImageSrc = newImageOrSrc;
  5754. }
  5755. return image;
  5756. }
  5757. // newImageOrSrc is an HTMLImageElement or HTMLCanvasElement or Canvas
  5758. else {
  5759. return newImageOrSrc;
  5760. }
  5761. }
  5762. function imageOnLoad() {
  5763. var cachedImgObj = this.__cachedImgObj;
  5764. this.onload = this.__cachedImgObj = null;
  5765. for (var i = 0; i < cachedImgObj.pending.length; i++) {
  5766. var pendingWrap = cachedImgObj.pending[i];
  5767. var cb = pendingWrap.cb;
  5768. cb && cb(this, pendingWrap.cbPayload);
  5769. pendingWrap.hostEl.dirty();
  5770. }
  5771. cachedImgObj.pending.length = 0;
  5772. }
  5773. function isImageReady(image) {
  5774. return image && image.width && image.height;
  5775. }
  5776. var textWidthCache = {};
  5777. var textWidthCacheCounter = 0;
  5778. var TEXT_CACHE_MAX = 5000;
  5779. var STYLE_REG = /\{([a-zA-Z0-9_]+)\|([^}]*)\}/g;
  5780. var DEFAULT_FONT = '12px sans-serif';
  5781. // Avoid assign to an exported variable, for transforming to cjs.
  5782. var methods$1 = {};
  5783. /**
  5784. * @public
  5785. * @param {string} text
  5786. * @param {string} font
  5787. * @return {number} width
  5788. */
  5789. function getWidth(text, font) {
  5790. font = font || DEFAULT_FONT;
  5791. var key = text + ':' + font;
  5792. if (textWidthCache[key]) {
  5793. return textWidthCache[key];
  5794. }
  5795. var textLines = (text + '').split('\n');
  5796. var width = 0;
  5797. for (var i = 0, l = textLines.length; i < l; i++) {
  5798. // textContain.measureText may be overrided in SVG or VML
  5799. width = Math.max(measureText(textLines[i], font).width, width);
  5800. }
  5801. if (textWidthCacheCounter > TEXT_CACHE_MAX) {
  5802. textWidthCacheCounter = 0;
  5803. textWidthCache = {};
  5804. }
  5805. textWidthCacheCounter++;
  5806. textWidthCache[key] = width;
  5807. return width;
  5808. }
  5809. /**
  5810. * @public
  5811. * @param {string} text
  5812. * @param {string} font
  5813. * @param {string} [textAlign='left']
  5814. * @param {string} [textVerticalAlign='top']
  5815. * @param {Array.<number>} [textPadding]
  5816. * @param {Object} [rich]
  5817. * @param {Object} [truncate]
  5818. * @return {Object} {x, y, width, height, lineHeight}
  5819. */
  5820. function getBoundingRect(text, font, textAlign, textVerticalAlign, textPadding, rich, truncate) {
  5821. return rich
  5822. ? getRichTextRect(text, font, textAlign, textVerticalAlign, textPadding, rich, truncate)
  5823. : getPlainTextRect(text, font, textAlign, textVerticalAlign, textPadding, truncate);
  5824. }
  5825. function getPlainTextRect(text, font, textAlign, textVerticalAlign, textPadding, truncate) {
  5826. var contentBlock = parsePlainText(text, font, textPadding, truncate);
  5827. var outerWidth = getWidth(text, font);
  5828. if (textPadding) {
  5829. outerWidth += textPadding[1] + textPadding[3];
  5830. }
  5831. var outerHeight = contentBlock.outerHeight;
  5832. var x = adjustTextX(0, outerWidth, textAlign);
  5833. var y = adjustTextY(0, outerHeight, textVerticalAlign);
  5834. var rect = new BoundingRect(x, y, outerWidth, outerHeight);
  5835. rect.lineHeight = contentBlock.lineHeight;
  5836. return rect;
  5837. }
  5838. function getRichTextRect(text, font, textAlign, textVerticalAlign, textPadding, rich, truncate) {
  5839. var contentBlock = parseRichText(text, {
  5840. rich: rich,
  5841. truncate: truncate,
  5842. font: font,
  5843. textAlign: textAlign,
  5844. textPadding: textPadding
  5845. });
  5846. var outerWidth = contentBlock.outerWidth;
  5847. var outerHeight = contentBlock.outerHeight;
  5848. var x = adjustTextX(0, outerWidth, textAlign);
  5849. var y = adjustTextY(0, outerHeight, textVerticalAlign);
  5850. return new BoundingRect(x, y, outerWidth, outerHeight);
  5851. }
  5852. /**
  5853. * @public
  5854. * @param {number} x
  5855. * @param {number} width
  5856. * @param {string} [textAlign='left']
  5857. * @return {number} Adjusted x.
  5858. */
  5859. function adjustTextX(x, width, textAlign) {
  5860. // FIXME Right to left language
  5861. if (textAlign === 'right') {
  5862. x -= width;
  5863. }
  5864. else if (textAlign === 'center') {
  5865. x -= width / 2;
  5866. }
  5867. return x;
  5868. }
  5869. /**
  5870. * @public
  5871. * @param {number} y
  5872. * @param {number} height
  5873. * @param {string} [textVerticalAlign='top']
  5874. * @return {number} Adjusted y.
  5875. */
  5876. function adjustTextY(y, height, textVerticalAlign) {
  5877. if (textVerticalAlign === 'middle') {
  5878. y -= height / 2;
  5879. }
  5880. else if (textVerticalAlign === 'bottom') {
  5881. y -= height;
  5882. }
  5883. return y;
  5884. }
  5885. /**
  5886. * @public
  5887. * @param {stirng} textPosition
  5888. * @param {Object} rect {x, y, width, height}
  5889. * @param {number} distance
  5890. * @return {Object} {x, y, textAlign, textVerticalAlign}
  5891. */
  5892. function adjustTextPositionOnRect(textPosition, rect, distance) {
  5893. var x = rect.x;
  5894. var y = rect.y;
  5895. var height = rect.height;
  5896. var width = rect.width;
  5897. var halfHeight = height / 2;
  5898. var textAlign = 'left';
  5899. var textVerticalAlign = 'top';
  5900. switch (textPosition) {
  5901. case 'left':
  5902. x -= distance;
  5903. y += halfHeight;
  5904. textAlign = 'right';
  5905. textVerticalAlign = 'middle';
  5906. break;
  5907. case 'right':
  5908. x += distance + width;
  5909. y += halfHeight;
  5910. textVerticalAlign = 'middle';
  5911. break;
  5912. case 'top':
  5913. x += width / 2;
  5914. y -= distance;
  5915. textAlign = 'center';
  5916. textVerticalAlign = 'bottom';
  5917. break;
  5918. case 'bottom':
  5919. x += width / 2;
  5920. y += height + distance;
  5921. textAlign = 'center';
  5922. break;
  5923. case 'inside':
  5924. x += width / 2;
  5925. y += halfHeight;
  5926. textAlign = 'center';
  5927. textVerticalAlign = 'middle';
  5928. break;
  5929. case 'insideLeft':
  5930. x += distance;
  5931. y += halfHeight;
  5932. textVerticalAlign = 'middle';
  5933. break;
  5934. case 'insideRight':
  5935. x += width - distance;
  5936. y += halfHeight;
  5937. textAlign = 'right';
  5938. textVerticalAlign = 'middle';
  5939. break;
  5940. case 'insideTop':
  5941. x += width / 2;
  5942. y += distance;
  5943. textAlign = 'center';
  5944. break;
  5945. case 'insideBottom':
  5946. x += width / 2;
  5947. y += height - distance;
  5948. textAlign = 'center';
  5949. textVerticalAlign = 'bottom';
  5950. break;
  5951. case 'insideTopLeft':
  5952. x += distance;
  5953. y += distance;
  5954. break;
  5955. case 'insideTopRight':
  5956. x += width - distance;
  5957. y += distance;
  5958. textAlign = 'right';
  5959. break;
  5960. case 'insideBottomLeft':
  5961. x += distance;
  5962. y += height - distance;
  5963. textVerticalAlign = 'bottom';
  5964. break;
  5965. case 'insideBottomRight':
  5966. x += width - distance;
  5967. y += height - distance;
  5968. textAlign = 'right';
  5969. textVerticalAlign = 'bottom';
  5970. break;
  5971. }
  5972. return {
  5973. x: x,
  5974. y: y,
  5975. textAlign: textAlign,
  5976. textVerticalAlign: textVerticalAlign
  5977. };
  5978. }
  5979. /**
  5980. * Show ellipsis if overflow.
  5981. *
  5982. * @public
  5983. * @param {string} text
  5984. * @param {string} containerWidth
  5985. * @param {string} font
  5986. * @param {number} [ellipsis='...']
  5987. * @param {Object} [options]
  5988. * @param {number} [options.maxIterations=3]
  5989. * @param {number} [options.minChar=0] If truncate result are less
  5990. * then minChar, ellipsis will not show, which is
  5991. * better for user hint in some cases.
  5992. * @param {number} [options.placeholder=''] When all truncated, use the placeholder.
  5993. * @return {string}
  5994. */
  5995. function truncateText(text, containerWidth, font, ellipsis, options) {
  5996. if (!containerWidth) {
  5997. return '';
  5998. }
  5999. var textLines = (text + '').split('\n');
  6000. options = prepareTruncateOptions(containerWidth, font, ellipsis, options);
  6001. // FIXME
  6002. // It is not appropriate that every line has '...' when truncate multiple lines.
  6003. for (var i = 0, len = textLines.length; i < len; i++) {
  6004. textLines[i] = truncateSingleLine(textLines[i], options);
  6005. }
  6006. return textLines.join('\n');
  6007. }
  6008. function prepareTruncateOptions(containerWidth, font, ellipsis, options) {
  6009. options = extend({}, options);
  6010. options.font = font;
  6011. var ellipsis = retrieve2(ellipsis, '...');
  6012. options.maxIterations = retrieve2(options.maxIterations, 2);
  6013. var minChar = options.minChar = retrieve2(options.minChar, 0);
  6014. // FIXME
  6015. // Other languages?
  6016. options.cnCharWidth = getWidth('国', font);
  6017. // FIXME
  6018. // Consider proportional font?
  6019. var ascCharWidth = options.ascCharWidth = getWidth('a', font);
  6020. options.placeholder = retrieve2(options.placeholder, '');
  6021. // Example 1: minChar: 3, text: 'asdfzxcv', truncate result: 'asdf', but not: 'a...'.
  6022. // Example 2: minChar: 3, text: '维度', truncate result: '维', but not: '...'.
  6023. var contentWidth = containerWidth = Math.max(0, containerWidth - 1); // Reserve some gap.
  6024. for (var i = 0; i < minChar && contentWidth >= ascCharWidth; i++) {
  6025. contentWidth -= ascCharWidth;
  6026. }
  6027. var ellipsisWidth = getWidth(ellipsis);
  6028. if (ellipsisWidth > contentWidth) {
  6029. ellipsis = '';
  6030. ellipsisWidth = 0;
  6031. }
  6032. contentWidth = containerWidth - ellipsisWidth;
  6033. options.ellipsis = ellipsis;
  6034. options.ellipsisWidth = ellipsisWidth;
  6035. options.contentWidth = contentWidth;
  6036. options.containerWidth = containerWidth;
  6037. return options;
  6038. }
  6039. function truncateSingleLine(textLine, options) {
  6040. var containerWidth = options.containerWidth;
  6041. var font = options.font;
  6042. var contentWidth = options.contentWidth;
  6043. if (!containerWidth) {
  6044. return '';
  6045. }
  6046. var lineWidth = getWidth(textLine, font);
  6047. if (lineWidth <= containerWidth) {
  6048. return textLine;
  6049. }
  6050. for (var j = 0;; j++) {
  6051. if (lineWidth <= contentWidth || j >= options.maxIterations) {
  6052. textLine += options.ellipsis;
  6053. break;
  6054. }
  6055. var subLength = j === 0
  6056. ? estimateLength(textLine, contentWidth, options.ascCharWidth, options.cnCharWidth)
  6057. : lineWidth > 0
  6058. ? Math.floor(textLine.length * contentWidth / lineWidth)
  6059. : 0;
  6060. textLine = textLine.substr(0, subLength);
  6061. lineWidth = getWidth(textLine, font);
  6062. }
  6063. if (textLine === '') {
  6064. textLine = options.placeholder;
  6065. }
  6066. return textLine;
  6067. }
  6068. function estimateLength(text, contentWidth, ascCharWidth, cnCharWidth) {
  6069. var width = 0;
  6070. var i = 0;
  6071. for (var len = text.length; i < len && width < contentWidth; i++) {
  6072. var charCode = text.charCodeAt(i);
  6073. width += (0 <= charCode && charCode <= 127) ? ascCharWidth : cnCharWidth;
  6074. }
  6075. return i;
  6076. }
  6077. /**
  6078. * @public
  6079. * @param {string} font
  6080. * @return {number} line height
  6081. */
  6082. function getLineHeight(font) {
  6083. // FIXME A rough approach.
  6084. return getWidth('国', font);
  6085. }
  6086. /**
  6087. * @public
  6088. * @param {string} text
  6089. * @param {string} font
  6090. * @return {Object} width
  6091. */
  6092. function measureText(text, font) {
  6093. return methods$1.measureText(text, font);
  6094. }
  6095. // Avoid assign to an exported variable, for transforming to cjs.
  6096. methods$1.measureText = function (text, font) {
  6097. var ctx = getContext();
  6098. ctx.font = font || DEFAULT_FONT;
  6099. return ctx.measureText(text);
  6100. };
  6101. /**
  6102. * @public
  6103. * @param {string} text
  6104. * @param {string} font
  6105. * @param {Object} [truncate]
  6106. * @return {Object} block: {lineHeight, lines, height, outerHeight}
  6107. * Notice: for performance, do not calculate outerWidth util needed.
  6108. */
  6109. function parsePlainText(text, font, padding, truncate) {
  6110. text != null && (text += '');
  6111. var lineHeight = getLineHeight(font);
  6112. var lines = text ? text.split('\n') : [];
  6113. var height = lines.length * lineHeight;
  6114. var outerHeight = height;
  6115. if (padding) {
  6116. outerHeight += padding[0] + padding[2];
  6117. }
  6118. if (text && truncate) {
  6119. var truncOuterHeight = truncate.outerHeight;
  6120. var truncOuterWidth = truncate.outerWidth;
  6121. if (truncOuterHeight != null && outerHeight > truncOuterHeight) {
  6122. text = '';
  6123. lines = [];
  6124. }
  6125. else if (truncOuterWidth != null) {
  6126. var options = prepareTruncateOptions(
  6127. truncOuterWidth - (padding ? padding[1] + padding[3] : 0),
  6128. font,
  6129. truncate.ellipsis,
  6130. {minChar: truncate.minChar, placeholder: truncate.placeholder}
  6131. );
  6132. // FIXME
  6133. // It is not appropriate that every line has '...' when truncate multiple lines.
  6134. for (var i = 0, len = lines.length; i < len; i++) {
  6135. lines[i] = truncateSingleLine(lines[i], options);
  6136. }
  6137. }
  6138. }
  6139. return {
  6140. lines: lines,
  6141. height: height,
  6142. outerHeight: outerHeight,
  6143. lineHeight: lineHeight
  6144. };
  6145. }
  6146. /**
  6147. * For example: 'some text {a|some text}other text{b|some text}xxx{c|}xxx'
  6148. * Also consider 'bbbb{a|xxx\nzzz}xxxx\naaaa'.
  6149. *
  6150. * @public
  6151. * @param {string} text
  6152. * @param {Object} style
  6153. * @return {Object} block
  6154. * {
  6155. * width,
  6156. * height,
  6157. * lines: [{
  6158. * lineHeight,
  6159. * width,
  6160. * tokens: [[{
  6161. * styleName,
  6162. * text,
  6163. * width, // include textPadding
  6164. * height, // include textPadding
  6165. * textWidth, // pure text width
  6166. * textHeight, // pure text height
  6167. * lineHeihgt,
  6168. * font,
  6169. * textAlign,
  6170. * textVerticalAlign
  6171. * }], [...], ...]
  6172. * }, ...]
  6173. * }
  6174. * If styleName is undefined, it is plain text.
  6175. */
  6176. function parseRichText(text, style) {
  6177. var contentBlock = {lines: [], width: 0, height: 0};
  6178. text != null && (text += '');
  6179. if (!text) {
  6180. return contentBlock;
  6181. }
  6182. var lastIndex = STYLE_REG.lastIndex = 0;
  6183. var result;
  6184. while ((result = STYLE_REG.exec(text)) != null) {
  6185. var matchedIndex = result.index;
  6186. if (matchedIndex > lastIndex) {
  6187. pushTokens(contentBlock, text.substring(lastIndex, matchedIndex));
  6188. }
  6189. pushTokens(contentBlock, result[2], result[1]);
  6190. lastIndex = STYLE_REG.lastIndex;
  6191. }
  6192. if (lastIndex < text.length) {
  6193. pushTokens(contentBlock, text.substring(lastIndex, text.length));
  6194. }
  6195. var lines = contentBlock.lines;
  6196. var contentHeight = 0;
  6197. var contentWidth = 0;
  6198. // For `textWidth: 100%`
  6199. var pendingList = [];
  6200. var stlPadding = style.textPadding;
  6201. var truncate = style.truncate;
  6202. var truncateWidth = truncate && truncate.outerWidth;
  6203. var truncateHeight = truncate && truncate.outerHeight;
  6204. if (stlPadding) {
  6205. truncateWidth != null && (truncateWidth -= stlPadding[1] + stlPadding[3]);
  6206. truncateHeight != null && (truncateHeight -= stlPadding[0] + stlPadding[2]);
  6207. }
  6208. // Calculate layout info of tokens.
  6209. for (var i = 0; i < lines.length; i++) {
  6210. var line = lines[i];
  6211. var lineHeight = 0;
  6212. var lineWidth = 0;
  6213. for (var j = 0; j < line.tokens.length; j++) {
  6214. var token = line.tokens[j];
  6215. var tokenStyle = token.styleName && style.rich[token.styleName] || {};
  6216. // textPadding should not inherit from style.
  6217. var textPadding = token.textPadding = tokenStyle.textPadding;
  6218. // textFont has been asigned to font by `normalizeStyle`.
  6219. var font = token.font = tokenStyle.font || style.font;
  6220. // textHeight can be used when textVerticalAlign is specified in token.
  6221. var tokenHeight = token.textHeight = retrieve2(
  6222. // textHeight should not be inherited, consider it can be specified
  6223. // as box height of the block.
  6224. tokenStyle.textHeight, getLineHeight(font)
  6225. );
  6226. textPadding && (tokenHeight += textPadding[0] + textPadding[2]);
  6227. token.height = tokenHeight;
  6228. token.lineHeight = retrieve3(
  6229. tokenStyle.textLineHeight, style.textLineHeight, tokenHeight
  6230. );
  6231. token.textAlign = tokenStyle && tokenStyle.textAlign || style.textAlign;
  6232. token.textVerticalAlign = tokenStyle && tokenStyle.textVerticalAlign || 'middle';
  6233. if (truncateHeight != null && contentHeight + token.lineHeight > truncateHeight) {
  6234. return {lines: [], width: 0, height: 0};
  6235. }
  6236. token.textWidth = getWidth(token.text, font);
  6237. var tokenWidth = tokenStyle.textWidth;
  6238. var tokenWidthNotSpecified = tokenWidth == null || tokenWidth === 'auto';
  6239. // Percent width, can be `100%`, can be used in drawing separate
  6240. // line when box width is needed to be auto.
  6241. if (typeof tokenWidth === 'string' && tokenWidth.charAt(tokenWidth.length - 1) === '%') {
  6242. token.percentWidth = tokenWidth;
  6243. pendingList.push(token);
  6244. tokenWidth = 0;
  6245. // Do not truncate in this case, because there is no user case
  6246. // and it is too complicated.
  6247. }
  6248. else {
  6249. if (tokenWidthNotSpecified) {
  6250. tokenWidth = token.textWidth;
  6251. // FIXME: If image is not loaded and textWidth is not specified, calling
  6252. // `getBoundingRect()` will not get correct result.
  6253. var textBackgroundColor = tokenStyle.textBackgroundColor;
  6254. var bgImg = textBackgroundColor && textBackgroundColor.image;
  6255. // Use cases:
  6256. // (1) If image is not loaded, it will be loaded at render phase and call
  6257. // `dirty()` and `textBackgroundColor.image` will be replaced with the loaded
  6258. // image, and then the right size will be calculated here at the next tick.
  6259. // See `graphic/helper/text.js`.
  6260. // (2) If image loaded, and `textBackgroundColor.image` is image src string,
  6261. // use `imageHelper.findExistImage` to find cached image.
  6262. // `imageHelper.findExistImage` will always be called here before
  6263. // `imageHelper.createOrUpdateImage` in `graphic/helper/text.js#renderRichText`
  6264. // which ensures that image will not be rendered before correct size calcualted.
  6265. if (bgImg) {
  6266. bgImg = findExistImage(bgImg);
  6267. if (isImageReady(bgImg)) {
  6268. tokenWidth = Math.max(tokenWidth, bgImg.width * tokenHeight / bgImg.height);
  6269. }
  6270. }
  6271. }
  6272. var paddingW = textPadding ? textPadding[1] + textPadding[3] : 0;
  6273. tokenWidth += paddingW;
  6274. var remianTruncWidth = truncateWidth != null ? truncateWidth - lineWidth : null;
  6275. if (remianTruncWidth != null && remianTruncWidth < tokenWidth) {
  6276. if (!tokenWidthNotSpecified || remianTruncWidth < paddingW) {
  6277. token.text = '';
  6278. token.textWidth = tokenWidth = 0;
  6279. }
  6280. else {
  6281. token.text = truncateText(
  6282. token.text, remianTruncWidth - paddingW, font, truncate.ellipsis,
  6283. {minChar: truncate.minChar}
  6284. );
  6285. token.textWidth = getWidth(token.text, font);
  6286. tokenWidth = token.textWidth + paddingW;
  6287. }
  6288. }
  6289. }
  6290. lineWidth += (token.width = tokenWidth);
  6291. tokenStyle && (lineHeight = Math.max(lineHeight, token.lineHeight));
  6292. }
  6293. line.width = lineWidth;
  6294. line.lineHeight = lineHeight;
  6295. contentHeight += lineHeight;
  6296. contentWidth = Math.max(contentWidth, lineWidth);
  6297. }
  6298. contentBlock.outerWidth = contentBlock.width = retrieve2(style.textWidth, contentWidth);
  6299. contentBlock.outerHeight = contentBlock.height = retrieve2(style.textHeight, contentHeight);
  6300. if (stlPadding) {
  6301. contentBlock.outerWidth += stlPadding[1] + stlPadding[3];
  6302. contentBlock.outerHeight += stlPadding[0] + stlPadding[2];
  6303. }
  6304. for (var i = 0; i < pendingList.length; i++) {
  6305. var token = pendingList[i];
  6306. var percentWidth = token.percentWidth;
  6307. // Should not base on outerWidth, because token can not be placed out of padding.
  6308. token.width = parseInt(percentWidth, 10) / 100 * contentWidth;
  6309. }
  6310. return contentBlock;
  6311. }
  6312. function pushTokens(block, str, styleName) {
  6313. var isEmptyStr = str === '';
  6314. var strs = str.split('\n');
  6315. var lines = block.lines;
  6316. for (var i = 0; i < strs.length; i++) {
  6317. var text = strs[i];
  6318. var token = {
  6319. styleName: styleName,
  6320. text: text,
  6321. isLineHolder: !text && !isEmptyStr
  6322. };
  6323. // The first token should be appended to the last line.
  6324. if (!i) {
  6325. var tokens = (lines[lines.length - 1] || (lines[0] = {tokens: []})).tokens;
  6326. // Consider cases:
  6327. // (1) ''.split('\n') => ['', '\n', ''], the '' at the first item
  6328. // (which is a placeholder) should be replaced by new token.
  6329. // (2) A image backage, where token likes {a|}.
  6330. // (3) A redundant '' will affect textAlign in line.
  6331. // (4) tokens with the same tplName should not be merged, because
  6332. // they should be displayed in different box (with border and padding).
  6333. var tokensLen = tokens.length;
  6334. (tokensLen === 1 && tokens[0].isLineHolder)
  6335. ? (tokens[0] = token)
  6336. // Consider text is '', only insert when it is the "lineHolder" or
  6337. // "emptyStr". Otherwise a redundant '' will affect textAlign in line.
  6338. : ((text || !tokensLen || isEmptyStr) && tokens.push(token));
  6339. }
  6340. // Other tokens always start a new line.
  6341. else {
  6342. // If there is '', insert it as a placeholder.
  6343. lines.push({tokens: [token]});
  6344. }
  6345. }
  6346. }
  6347. function makeFont(style) {
  6348. // FIXME in node-canvas fontWeight is before fontStyle
  6349. // Use `fontSize` `fontFamily` to check whether font properties are defined.
  6350. var font = (style.fontSize || style.fontFamily) && [
  6351. style.fontStyle,
  6352. style.fontWeight,
  6353. (style.fontSize || 12) + 'px',
  6354. // If font properties are defined, `fontFamily` should not be ignored.
  6355. style.fontFamily || 'sans-serif'
  6356. ].join(' ');
  6357. return font && trim(font) || style.textFont || style.font;
  6358. }
  6359. function buildPath(ctx, shape) {
  6360. var x = shape.x;
  6361. var y = shape.y;
  6362. var width = shape.width;
  6363. var height = shape.height;
  6364. var r = shape.r;
  6365. var r1;
  6366. var r2;
  6367. var r3;
  6368. var r4;
  6369. // Convert width and height to positive for better borderRadius
  6370. if (width < 0) {
  6371. x = x + width;
  6372. width = -width;
  6373. }
  6374. if (height < 0) {
  6375. y = y + height;
  6376. height = -height;
  6377. }
  6378. if (typeof r === 'number') {
  6379. r1 = r2 = r3 = r4 = r;
  6380. }
  6381. else if (r instanceof Array) {
  6382. if (r.length === 1) {
  6383. r1 = r2 = r3 = r4 = r[0];
  6384. }
  6385. else if (r.length === 2) {
  6386. r1 = r3 = r[0];
  6387. r2 = r4 = r[1];
  6388. }
  6389. else if (r.length === 3) {
  6390. r1 = r[0];
  6391. r2 = r4 = r[1];
  6392. r3 = r[2];
  6393. }
  6394. else {
  6395. r1 = r[0];
  6396. r2 = r[1];
  6397. r3 = r[2];
  6398. r4 = r[3];
  6399. }
  6400. }
  6401. else {
  6402. r1 = r2 = r3 = r4 = 0;
  6403. }
  6404. var total;
  6405. if (r1 + r2 > width) {
  6406. total = r1 + r2;
  6407. r1 *= width / total;
  6408. r2 *= width / total;
  6409. }
  6410. if (r3 + r4 > width) {
  6411. total = r3 + r4;
  6412. r3 *= width / total;
  6413. r4 *= width / total;
  6414. }
  6415. if (r2 + r3 > height) {
  6416. total = r2 + r3;
  6417. r2 *= height / total;
  6418. r3 *= height / total;
  6419. }
  6420. if (r1 + r4 > height) {
  6421. total = r1 + r4;
  6422. r1 *= height / total;
  6423. r4 *= height / total;
  6424. }
  6425. ctx.moveTo(x + r1, y);
  6426. ctx.lineTo(x + width - r2, y);
  6427. r2 !== 0 && ctx.arc(x + width - r2, y + r2, r2, -Math.PI / 2, 0);
  6428. ctx.lineTo(x + width, y + height - r3);
  6429. r3 !== 0 && ctx.arc(x + width - r3, y + height - r3, r3, 0, Math.PI / 2);
  6430. ctx.lineTo(x + r4, y + height);
  6431. r4 !== 0 && ctx.arc(x + r4, y + height - r4, r4, Math.PI / 2, Math.PI);
  6432. ctx.lineTo(x, y + r1);
  6433. r1 !== 0 && ctx.arc(x + r1, y + r1, r1, Math.PI, Math.PI * 1.5);
  6434. }
  6435. // TODO: Have not support 'start', 'end' yet.
  6436. var VALID_TEXT_ALIGN = {left: 1, right: 1, center: 1};
  6437. var VALID_TEXT_VERTICAL_ALIGN = {top: 1, bottom: 1, middle: 1};
  6438. /**
  6439. * @param {module:zrender/graphic/Style} style
  6440. * @return {module:zrender/graphic/Style} The input style.
  6441. */
  6442. function normalizeTextStyle(style) {
  6443. normalizeStyle(style);
  6444. each$1(style.rich, normalizeStyle);
  6445. return style;
  6446. }
  6447. function normalizeStyle(style) {
  6448. if (style) {
  6449. style.font = makeFont(style);
  6450. var textAlign = style.textAlign;
  6451. textAlign === 'middle' && (textAlign = 'center');
  6452. style.textAlign = (
  6453. textAlign == null || VALID_TEXT_ALIGN[textAlign]
  6454. ) ? textAlign : 'left';
  6455. // Compatible with textBaseline.
  6456. var textVerticalAlign = style.textVerticalAlign || style.textBaseline;
  6457. textVerticalAlign === 'center' && (textVerticalAlign = 'middle');
  6458. style.textVerticalAlign = (
  6459. textVerticalAlign == null || VALID_TEXT_VERTICAL_ALIGN[textVerticalAlign]
  6460. ) ? textVerticalAlign : 'top';
  6461. var textPadding = style.textPadding;
  6462. if (textPadding) {
  6463. style.textPadding = normalizeCssArray(style.textPadding);
  6464. }
  6465. }
  6466. }
  6467. /**
  6468. * @param {CanvasRenderingContext2D} ctx
  6469. * @param {string} text
  6470. * @param {module:zrender/graphic/Style} style
  6471. * @param {Object|boolean} [rect] {x, y, width, height}
  6472. * If set false, rect text is not used.
  6473. */
  6474. function renderText(hostEl, ctx, text, style, rect) {
  6475. style.rich
  6476. ? renderRichText(hostEl, ctx, text, style, rect)
  6477. : renderPlainText(hostEl, ctx, text, style, rect);
  6478. }
  6479. function renderPlainText(hostEl, ctx, text, style, rect) {
  6480. var font = setCtx(ctx, 'font', style.font || DEFAULT_FONT);
  6481. var textPadding = style.textPadding;
  6482. var contentBlock = hostEl.__textCotentBlock;
  6483. if (!contentBlock || hostEl.__dirty) {
  6484. contentBlock = hostEl.__textCotentBlock = parsePlainText(
  6485. text, font, textPadding, style.truncate
  6486. );
  6487. }
  6488. var outerHeight = contentBlock.outerHeight;
  6489. var textLines = contentBlock.lines;
  6490. var lineHeight = contentBlock.lineHeight;
  6491. var boxPos = getBoxPosition(outerHeight, style, rect);
  6492. var baseX = boxPos.baseX;
  6493. var baseY = boxPos.baseY;
  6494. var textAlign = boxPos.textAlign;
  6495. var textVerticalAlign = boxPos.textVerticalAlign;
  6496. // Origin of textRotation should be the base point of text drawing.
  6497. applyTextRotation(ctx, style, rect, baseX, baseY);
  6498. var boxY = adjustTextY(baseY, outerHeight, textVerticalAlign);
  6499. var textX = baseX;
  6500. var textY = boxY;
  6501. var needDrawBg = needDrawBackground(style);
  6502. if (needDrawBg || textPadding) {
  6503. // Consider performance, do not call getTextWidth util necessary.
  6504. var textWidth = getWidth(text, font);
  6505. var outerWidth = textWidth;
  6506. textPadding && (outerWidth += textPadding[1] + textPadding[3]);
  6507. var boxX = adjustTextX(baseX, outerWidth, textAlign);
  6508. needDrawBg && drawBackground(hostEl, ctx, style, boxX, boxY, outerWidth, outerHeight);
  6509. if (textPadding) {
  6510. textX = getTextXForPadding(baseX, textAlign, textPadding);
  6511. textY += textPadding[0];
  6512. }
  6513. }
  6514. setCtx(ctx, 'textAlign', textAlign || 'left');
  6515. // Force baseline to be "middle". Otherwise, if using "top", the
  6516. // text will offset downward a little bit in font "Microsoft YaHei".
  6517. setCtx(ctx, 'textBaseline', 'middle');
  6518. // Always set shadowBlur and shadowOffset to avoid leak from displayable.
  6519. setCtx(ctx, 'shadowBlur', style.textShadowBlur || 0);
  6520. setCtx(ctx, 'shadowColor', style.textShadowColor || 'transparent');
  6521. setCtx(ctx, 'shadowOffsetX', style.textShadowOffsetX || 0);
  6522. setCtx(ctx, 'shadowOffsetY', style.textShadowOffsetY || 0);
  6523. // `textBaseline` is set as 'middle'.
  6524. textY += lineHeight / 2;
  6525. var textStrokeWidth = style.textStrokeWidth;
  6526. var textStroke = getStroke(style.textStroke, textStrokeWidth);
  6527. var textFill = getFill(style.textFill);
  6528. if (textStroke) {
  6529. setCtx(ctx, 'lineWidth', textStrokeWidth);
  6530. setCtx(ctx, 'strokeStyle', textStroke);
  6531. }
  6532. if (textFill) {
  6533. setCtx(ctx, 'fillStyle', textFill);
  6534. }
  6535. for (var i = 0; i < textLines.length; i++) {
  6536. // Fill after stroke so the outline will not cover the main part.
  6537. textStroke && ctx.strokeText(textLines[i], textX, textY);
  6538. textFill && ctx.fillText(textLines[i], textX, textY);
  6539. textY += lineHeight;
  6540. }
  6541. }
  6542. function renderRichText(hostEl, ctx, text, style, rect) {
  6543. var contentBlock = hostEl.__textCotentBlock;
  6544. if (!contentBlock || hostEl.__dirty) {
  6545. contentBlock = hostEl.__textCotentBlock = parseRichText(text, style);
  6546. }
  6547. drawRichText(hostEl, ctx, contentBlock, style, rect);
  6548. }
  6549. function drawRichText(hostEl, ctx, contentBlock, style, rect) {
  6550. var contentWidth = contentBlock.width;
  6551. var outerWidth = contentBlock.outerWidth;
  6552. var outerHeight = contentBlock.outerHeight;
  6553. var textPadding = style.textPadding;
  6554. var boxPos = getBoxPosition(outerHeight, style, rect);
  6555. var baseX = boxPos.baseX;
  6556. var baseY = boxPos.baseY;
  6557. var textAlign = boxPos.textAlign;
  6558. var textVerticalAlign = boxPos.textVerticalAlign;
  6559. // Origin of textRotation should be the base point of text drawing.
  6560. applyTextRotation(ctx, style, rect, baseX, baseY);
  6561. var boxX = adjustTextX(baseX, outerWidth, textAlign);
  6562. var boxY = adjustTextY(baseY, outerHeight, textVerticalAlign);
  6563. var xLeft = boxX;
  6564. var lineTop = boxY;
  6565. if (textPadding) {
  6566. xLeft += textPadding[3];
  6567. lineTop += textPadding[0];
  6568. }
  6569. var xRight = xLeft + contentWidth;
  6570. needDrawBackground(style) && drawBackground(
  6571. hostEl, ctx, style, boxX, boxY, outerWidth, outerHeight
  6572. );
  6573. for (var i = 0; i < contentBlock.lines.length; i++) {
  6574. var line = contentBlock.lines[i];
  6575. var tokens = line.tokens;
  6576. var tokenCount = tokens.length;
  6577. var lineHeight = line.lineHeight;
  6578. var usedWidth = line.width;
  6579. var leftIndex = 0;
  6580. var lineXLeft = xLeft;
  6581. var lineXRight = xRight;
  6582. var rightIndex = tokenCount - 1;
  6583. var token;
  6584. while (
  6585. leftIndex < tokenCount
  6586. && (token = tokens[leftIndex], !token.textAlign || token.textAlign === 'left')
  6587. ) {
  6588. placeToken(hostEl, ctx, token, style, lineHeight, lineTop, lineXLeft, 'left');
  6589. usedWidth -= token.width;
  6590. lineXLeft += token.width;
  6591. leftIndex++;
  6592. }
  6593. while (
  6594. rightIndex >= 0
  6595. && (token = tokens[rightIndex], token.textAlign === 'right')
  6596. ) {
  6597. placeToken(hostEl, ctx, token, style, lineHeight, lineTop, lineXRight, 'right');
  6598. usedWidth -= token.width;
  6599. lineXRight -= token.width;
  6600. rightIndex--;
  6601. }
  6602. // The other tokens are placed as textAlign 'center' if there is enough space.
  6603. lineXLeft += (contentWidth - (lineXLeft - xLeft) - (xRight - lineXRight) - usedWidth) / 2;
  6604. while (leftIndex <= rightIndex) {
  6605. token = tokens[leftIndex];
  6606. // Consider width specified by user, use 'center' rather than 'left'.
  6607. placeToken(hostEl, ctx, token, style, lineHeight, lineTop, lineXLeft + token.width / 2, 'center');
  6608. lineXLeft += token.width;
  6609. leftIndex++;
  6610. }
  6611. lineTop += lineHeight;
  6612. }
  6613. }
  6614. function applyTextRotation(ctx, style, rect, x, y) {
  6615. // textRotation only apply in RectText.
  6616. if (rect && style.textRotation) {
  6617. var origin = style.textOrigin;
  6618. if (origin === 'center') {
  6619. x = rect.width / 2 + rect.x;
  6620. y = rect.height / 2 + rect.y;
  6621. }
  6622. else if (origin) {
  6623. x = origin[0] + rect.x;
  6624. y = origin[1] + rect.y;
  6625. }
  6626. ctx.translate(x, y);
  6627. // Positive: anticlockwise
  6628. ctx.rotate(-style.textRotation);
  6629. ctx.translate(-x, -y);
  6630. }
  6631. }
  6632. function placeToken(hostEl, ctx, token, style, lineHeight, lineTop, x, textAlign) {
  6633. var tokenStyle = style.rich[token.styleName] || {};
  6634. // 'ctx.textBaseline' is always set as 'middle', for sake of
  6635. // the bias of "Microsoft YaHei".
  6636. var textVerticalAlign = token.textVerticalAlign;
  6637. var y = lineTop + lineHeight / 2;
  6638. if (textVerticalAlign === 'top') {
  6639. y = lineTop + token.height / 2;
  6640. }
  6641. else if (textVerticalAlign === 'bottom') {
  6642. y = lineTop + lineHeight - token.height / 2;
  6643. }
  6644. !token.isLineHolder && needDrawBackground(tokenStyle) && drawBackground(
  6645. hostEl,
  6646. ctx,
  6647. tokenStyle,
  6648. textAlign === 'right'
  6649. ? x - token.width
  6650. : textAlign === 'center'
  6651. ? x - token.width / 2
  6652. : x,
  6653. y - token.height / 2,
  6654. token.width,
  6655. token.height
  6656. );
  6657. var textPadding = token.textPadding;
  6658. if (textPadding) {
  6659. x = getTextXForPadding(x, textAlign, textPadding);
  6660. y -= token.height / 2 - textPadding[2] - token.textHeight / 2;
  6661. }
  6662. setCtx(ctx, 'shadowBlur', retrieve3(tokenStyle.textShadowBlur, style.textShadowBlur, 0));
  6663. setCtx(ctx, 'shadowColor', tokenStyle.textShadowColor || style.textShadowColor || 'transparent');
  6664. setCtx(ctx, 'shadowOffsetX', retrieve3(tokenStyle.textShadowOffsetX, style.textShadowOffsetX, 0));
  6665. setCtx(ctx, 'shadowOffsetY', retrieve3(tokenStyle.textShadowOffsetY, style.textShadowOffsetY, 0));
  6666. setCtx(ctx, 'textAlign', textAlign);
  6667. // Force baseline to be "middle". Otherwise, if using "top", the
  6668. // text will offset downward a little bit in font "Microsoft YaHei".
  6669. setCtx(ctx, 'textBaseline', 'middle');
  6670. setCtx(ctx, 'font', token.font || DEFAULT_FONT);
  6671. var textStroke = getStroke(tokenStyle.textStroke || style.textStroke, textStrokeWidth);
  6672. var textFill = getFill(tokenStyle.textFill || style.textFill);
  6673. var textStrokeWidth = retrieve2(tokenStyle.textStrokeWidth, style.textStrokeWidth);
  6674. // Fill after stroke so the outline will not cover the main part.
  6675. if (textStroke) {
  6676. setCtx(ctx, 'lineWidth', textStrokeWidth);
  6677. setCtx(ctx, 'strokeStyle', textStroke);
  6678. ctx.strokeText(token.text, x, y);
  6679. }
  6680. if (textFill) {
  6681. setCtx(ctx, 'fillStyle', textFill);
  6682. ctx.fillText(token.text, x, y);
  6683. }
  6684. }
  6685. function needDrawBackground(style) {
  6686. return style.textBackgroundColor
  6687. || (style.textBorderWidth && style.textBorderColor);
  6688. }
  6689. // style: {textBackgroundColor, textBorderWidth, textBorderColor, textBorderRadius}
  6690. // shape: {x, y, width, height}
  6691. function drawBackground(hostEl, ctx, style, x, y, width, height) {
  6692. var textBackgroundColor = style.textBackgroundColor;
  6693. var textBorderWidth = style.textBorderWidth;
  6694. var textBorderColor = style.textBorderColor;
  6695. var isPlainBg = isString(textBackgroundColor);
  6696. setCtx(ctx, 'shadowBlur', style.textBoxShadowBlur || 0);
  6697. setCtx(ctx, 'shadowColor', style.textBoxShadowColor || 'transparent');
  6698. setCtx(ctx, 'shadowOffsetX', style.textBoxShadowOffsetX || 0);
  6699. setCtx(ctx, 'shadowOffsetY', style.textBoxShadowOffsetY || 0);
  6700. if (isPlainBg || (textBorderWidth && textBorderColor)) {
  6701. ctx.beginPath();
  6702. var textBorderRadius = style.textBorderRadius;
  6703. if (!textBorderRadius) {
  6704. ctx.rect(x, y, width, height);
  6705. }
  6706. else {
  6707. buildPath(ctx, {
  6708. x: x, y: y, width: width, height: height, r: textBorderRadius
  6709. });
  6710. }
  6711. ctx.closePath();
  6712. }
  6713. if (isPlainBg) {
  6714. setCtx(ctx, 'fillStyle', textBackgroundColor);
  6715. ctx.fill();
  6716. }
  6717. else if (isObject$1(textBackgroundColor)) {
  6718. var image = textBackgroundColor.image;
  6719. image = createOrUpdateImage(
  6720. image, null, hostEl, onBgImageLoaded, textBackgroundColor
  6721. );
  6722. if (image && isImageReady(image)) {
  6723. ctx.drawImage(image, x, y, width, height);
  6724. }
  6725. }
  6726. if (textBorderWidth && textBorderColor) {
  6727. setCtx(ctx, 'lineWidth', textBorderWidth);
  6728. setCtx(ctx, 'strokeStyle', textBorderColor);
  6729. ctx.stroke();
  6730. }
  6731. }
  6732. function onBgImageLoaded(image, textBackgroundColor) {
  6733. // Replace image, so that `contain/text.js#parseRichText`
  6734. // will get correct result in next tick.
  6735. textBackgroundColor.image = image;
  6736. }
  6737. function getBoxPosition(blockHeiht, style, rect) {
  6738. var baseX = style.x || 0;
  6739. var baseY = style.y || 0;
  6740. var textAlign = style.textAlign;
  6741. var textVerticalAlign = style.textVerticalAlign;
  6742. // Text position represented by coord
  6743. if (rect) {
  6744. var textPosition = style.textPosition;
  6745. if (textPosition instanceof Array) {
  6746. // Percent
  6747. baseX = rect.x + parsePercent(textPosition[0], rect.width);
  6748. baseY = rect.y + parsePercent(textPosition[1], rect.height);
  6749. }
  6750. else {
  6751. var res = adjustTextPositionOnRect(
  6752. textPosition, rect, style.textDistance
  6753. );
  6754. baseX = res.x;
  6755. baseY = res.y;
  6756. // Default align and baseline when has textPosition
  6757. textAlign = textAlign || res.textAlign;
  6758. textVerticalAlign = textVerticalAlign || res.textVerticalAlign;
  6759. }
  6760. // textOffset is only support in RectText, otherwise
  6761. // we have to adjust boundingRect for textOffset.
  6762. var textOffset = style.textOffset;
  6763. if (textOffset) {
  6764. baseX += textOffset[0];
  6765. baseY += textOffset[1];
  6766. }
  6767. }
  6768. return {
  6769. baseX: baseX,
  6770. baseY: baseY,
  6771. textAlign: textAlign,
  6772. textVerticalAlign: textVerticalAlign
  6773. };
  6774. }
  6775. function setCtx(ctx, prop, value) {
  6776. ctx[prop] = fixShadow(ctx, prop, value);
  6777. return ctx[prop];
  6778. }
  6779. /**
  6780. * @param {string} [stroke] If specified, do not check style.textStroke.
  6781. * @param {string} [lineWidth] If specified, do not check style.textStroke.
  6782. * @param {number} style
  6783. */
  6784. function getStroke(stroke, lineWidth) {
  6785. return (stroke == null || lineWidth <= 0 || stroke === 'transparent' || stroke === 'none')
  6786. ? null
  6787. // TODO pattern and gradient?
  6788. : (stroke.image || stroke.colorStops)
  6789. ? '#000'
  6790. : stroke;
  6791. }
  6792. function getFill(fill) {
  6793. return (fill == null || fill === 'none')
  6794. ? null
  6795. // TODO pattern and gradient?
  6796. : (fill.image || fill.colorStops)
  6797. ? '#000'
  6798. : fill;
  6799. }
  6800. function parsePercent(value, maxValue) {
  6801. if (typeof value === 'string') {
  6802. if (value.lastIndexOf('%') >= 0) {
  6803. return parseFloat(value) / 100 * maxValue;
  6804. }
  6805. return parseFloat(value);
  6806. }
  6807. return value;
  6808. }
  6809. function getTextXForPadding(x, textAlign, textPadding) {
  6810. return textAlign === 'right'
  6811. ? (x - textPadding[1])
  6812. : textAlign === 'center'
  6813. ? (x + textPadding[3] / 2 - textPadding[1] / 2)
  6814. : (x + textPadding[3]);
  6815. }
  6816. /**
  6817. * @param {string} text
  6818. * @param {module:zrender/Style} style
  6819. * @return {boolean}
  6820. */
  6821. function needDrawText(text, style) {
  6822. return text != null
  6823. && (text
  6824. || style.textBackgroundColor
  6825. || (style.textBorderWidth && style.textBorderColor)
  6826. || style.textPadding
  6827. );
  6828. }
  6829. /**
  6830. * Mixin for drawing text in a element bounding rect
  6831. * @module zrender/mixin/RectText
  6832. */
  6833. var tmpRect$1 = new BoundingRect();
  6834. var RectText = function () {};
  6835. RectText.prototype = {
  6836. constructor: RectText,
  6837. /**
  6838. * Draw text in a rect with specified position.
  6839. * @param {CanvasRenderingContext2D} ctx
  6840. * @param {Object} rect Displayable rect
  6841. */
  6842. drawRectText: function (ctx, rect) {
  6843. var style = this.style;
  6844. rect = style.textRect || rect;
  6845. // Optimize, avoid normalize every time.
  6846. this.__dirty && normalizeTextStyle(style, true);
  6847. var text = style.text;
  6848. // Convert to string
  6849. text != null && (text += '');
  6850. if (!needDrawText(text, style)) {
  6851. return;
  6852. }
  6853. // FIXME
  6854. ctx.save();
  6855. // Transform rect to view space
  6856. var transform = this.transform;
  6857. if (!style.transformText) {
  6858. if (transform) {
  6859. tmpRect$1.copy(rect);
  6860. tmpRect$1.applyTransform(transform);
  6861. rect = tmpRect$1;
  6862. }
  6863. }
  6864. else {
  6865. this.setTransform(ctx);
  6866. }
  6867. // transformText and textRotation can not be used at the same time.
  6868. renderText(this, ctx, text, style, rect);
  6869. ctx.restore();
  6870. }
  6871. };
  6872. /**
  6873. * 可绘制的图形基类
  6874. * Base class of all displayable graphic objects
  6875. * @module zrender/graphic/Displayable
  6876. */
  6877. /**
  6878. * @alias module:zrender/graphic/Displayable
  6879. * @extends module:zrender/Element
  6880. * @extends module:zrender/graphic/mixin/RectText
  6881. */
  6882. function Displayable(opts) {
  6883. opts = opts || {};
  6884. Element.call(this, opts);
  6885. // Extend properties
  6886. for (var name in opts) {
  6887. if (
  6888. opts.hasOwnProperty(name) &&
  6889. name !== 'style'
  6890. ) {
  6891. this[name] = opts[name];
  6892. }
  6893. }
  6894. /**
  6895. * @type {module:zrender/graphic/Style}
  6896. */
  6897. this.style = new Style(opts.style, this);
  6898. this._rect = null;
  6899. // Shapes for cascade clipping.
  6900. this.__clipPaths = [];
  6901. // FIXME Stateful must be mixined after style is setted
  6902. // Stateful.call(this, opts);
  6903. }
  6904. Displayable.prototype = {
  6905. constructor: Displayable,
  6906. type: 'displayable',
  6907. /**
  6908. * Displayable 是否为脏,Painter 中会根据该标记判断是否需要是否需要重新绘制
  6909. * Dirty flag. From which painter will determine if this displayable object needs brush
  6910. * @name module:zrender/graphic/Displayable#__dirty
  6911. * @type {boolean}
  6912. */
  6913. __dirty: true,
  6914. /**
  6915. * 图形是否可见,为true时不绘制图形,但是仍能触发鼠标事件
  6916. * If ignore drawing of the displayable object. Mouse event will still be triggered
  6917. * @name module:/zrender/graphic/Displayable#invisible
  6918. * @type {boolean}
  6919. * @default false
  6920. */
  6921. invisible: false,
  6922. /**
  6923. * @name module:/zrender/graphic/Displayable#z
  6924. * @type {number}
  6925. * @default 0
  6926. */
  6927. z: 0,
  6928. /**
  6929. * @name module:/zrender/graphic/Displayable#z
  6930. * @type {number}
  6931. * @default 0
  6932. */
  6933. z2: 0,
  6934. /**
  6935. * z层level,决定绘画在哪层canvas中
  6936. * @name module:/zrender/graphic/Displayable#zlevel
  6937. * @type {number}
  6938. * @default 0
  6939. */
  6940. zlevel: 0,
  6941. /**
  6942. * 是否可拖拽
  6943. * @name module:/zrender/graphic/Displayable#draggable
  6944. * @type {boolean}
  6945. * @default false
  6946. */
  6947. draggable: false,
  6948. /**
  6949. * 是否正在拖拽
  6950. * @name module:/zrender/graphic/Displayable#draggable
  6951. * @type {boolean}
  6952. * @default false
  6953. */
  6954. dragging: false,
  6955. /**
  6956. * 是否相应鼠标事件
  6957. * @name module:/zrender/graphic/Displayable#silent
  6958. * @type {boolean}
  6959. * @default false
  6960. */
  6961. silent: false,
  6962. /**
  6963. * If enable culling
  6964. * @type {boolean}
  6965. * @default false
  6966. */
  6967. culling: false,
  6968. /**
  6969. * Mouse cursor when hovered
  6970. * @name module:/zrender/graphic/Displayable#cursor
  6971. * @type {string}
  6972. */
  6973. cursor: 'pointer',
  6974. /**
  6975. * If hover area is bounding rect
  6976. * @name module:/zrender/graphic/Displayable#rectHover
  6977. * @type {string}
  6978. */
  6979. rectHover: false,
  6980. /**
  6981. * Render the element progressively when the value >= 0,
  6982. * usefull for large data.
  6983. * @type {boolean}
  6984. */
  6985. progressive: false,
  6986. /**
  6987. * @type {boolean}
  6988. */
  6989. incremental: false,
  6990. // inplace is used with incremental
  6991. inplace: false,
  6992. beforeBrush: function (ctx) {},
  6993. afterBrush: function (ctx) {},
  6994. /**
  6995. * 图形绘制方法
  6996. * @param {CanvasRenderingContext2D} ctx
  6997. */
  6998. // Interface
  6999. brush: function (ctx, prevEl) {},
  7000. /**
  7001. * 获取最小包围盒
  7002. * @return {module:zrender/core/BoundingRect}
  7003. */
  7004. // Interface
  7005. getBoundingRect: function () {},
  7006. /**
  7007. * 判断坐标 x, y 是否在图形上
  7008. * If displayable element contain coord x, y
  7009. * @param {number} x
  7010. * @param {number} y
  7011. * @return {boolean}
  7012. */
  7013. contain: function (x, y) {
  7014. return this.rectContain(x, y);
  7015. },
  7016. /**
  7017. * @param {Function} cb
  7018. * @param {} context
  7019. */
  7020. traverse: function (cb, context) {
  7021. cb.call(context, this);
  7022. },
  7023. /**
  7024. * 判断坐标 x, y 是否在图形的包围盒上
  7025. * If bounding rect of element contain coord x, y
  7026. * @param {number} x
  7027. * @param {number} y
  7028. * @return {boolean}
  7029. */
  7030. rectContain: function (x, y) {
  7031. var coord = this.transformCoordToLocal(x, y);
  7032. var rect = this.getBoundingRect();
  7033. return rect.contain(coord[0], coord[1]);
  7034. },
  7035. /**
  7036. * 标记图形元素为脏,并且在下一帧重绘
  7037. * Mark displayable element dirty and refresh next frame
  7038. */
  7039. dirty: function () {
  7040. this.__dirty = true;
  7041. this._rect = null;
  7042. this.__zr && this.__zr.refresh();
  7043. },
  7044. /**
  7045. * 图形是否会触发事件
  7046. * If displayable object binded any event
  7047. * @return {boolean}
  7048. */
  7049. // TODO, 通过 bind 绑定的事件
  7050. // isSilent: function () {
  7051. // return !(
  7052. // this.hoverable || this.draggable
  7053. // || this.onmousemove || this.onmouseover || this.onmouseout
  7054. // || this.onmousedown || this.onmouseup || this.onclick
  7055. // || this.ondragenter || this.ondragover || this.ondragleave
  7056. // || this.ondrop
  7057. // );
  7058. // },
  7059. /**
  7060. * Alias for animate('style')
  7061. * @param {boolean} loop
  7062. */
  7063. animateStyle: function (loop) {
  7064. return this.animate('style', loop);
  7065. },
  7066. attrKV: function (key, value) {
  7067. if (key !== 'style') {
  7068. Element.prototype.attrKV.call(this, key, value);
  7069. }
  7070. else {
  7071. this.style.set(value);
  7072. }
  7073. },
  7074. /**
  7075. * @param {Object|string} key
  7076. * @param {*} value
  7077. */
  7078. setStyle: function (key, value) {
  7079. this.style.set(key, value);
  7080. this.dirty(false);
  7081. return this;
  7082. },
  7083. /**
  7084. * Use given style object
  7085. * @param {Object} obj
  7086. */
  7087. useStyle: function (obj) {
  7088. this.style = new Style(obj, this);
  7089. this.dirty(false);
  7090. return this;
  7091. }
  7092. };
  7093. inherits(Displayable, Element);
  7094. mixin(Displayable, RectText);
  7095. /**
  7096. * @alias zrender/graphic/Image
  7097. * @extends module:zrender/graphic/Displayable
  7098. * @constructor
  7099. * @param {Object} opts
  7100. */
  7101. function ZImage(opts) {
  7102. Displayable.call(this, opts);
  7103. }
  7104. ZImage.prototype = {
  7105. constructor: ZImage,
  7106. type: 'image',
  7107. brush: function (ctx, prevEl) {
  7108. var style = this.style;
  7109. var src = style.image;
  7110. // Must bind each time
  7111. style.bind(ctx, this, prevEl);
  7112. var image = this._image = createOrUpdateImage(
  7113. src,
  7114. this._image,
  7115. this,
  7116. this.onload
  7117. );
  7118. if (!image || !isImageReady(image)) {
  7119. return;
  7120. }
  7121. // 图片已经加载完成
  7122. // if (image.nodeName.toUpperCase() == 'IMG') {
  7123. // if (!image.complete) {
  7124. // return;
  7125. // }
  7126. // }
  7127. // Else is canvas
  7128. var x = style.x || 0;
  7129. var y = style.y || 0;
  7130. var width = style.width;
  7131. var height = style.height;
  7132. var aspect = image.width / image.height;
  7133. if (width == null && height != null) {
  7134. // Keep image/height ratio
  7135. width = height * aspect;
  7136. }
  7137. else if (height == null && width != null) {
  7138. height = width / aspect;
  7139. }
  7140. else if (width == null && height == null) {
  7141. width = image.width;
  7142. height = image.height;
  7143. }
  7144. // 设置transform
  7145. this.setTransform(ctx);
  7146. if (style.sWidth && style.sHeight) {
  7147. var sx = style.sx || 0;
  7148. var sy = style.sy || 0;
  7149. ctx.drawImage(
  7150. image,
  7151. sx, sy, style.sWidth, style.sHeight,
  7152. x, y, width, height
  7153. );
  7154. }
  7155. else if (style.sx && style.sy) {
  7156. var sx = style.sx;
  7157. var sy = style.sy;
  7158. var sWidth = width - sx;
  7159. var sHeight = height - sy;
  7160. ctx.drawImage(
  7161. image,
  7162. sx, sy, sWidth, sHeight,
  7163. x, y, width, height
  7164. );
  7165. }
  7166. else {
  7167. ctx.drawImage(image, x, y, width, height);
  7168. }
  7169. // Draw rect text
  7170. if (style.text != null) {
  7171. // Only restore transform when needs draw text.
  7172. this.restoreTransform(ctx);
  7173. this.drawRectText(ctx, this.getBoundingRect());
  7174. }
  7175. },
  7176. getBoundingRect: function () {
  7177. var style = this.style;
  7178. if (! this._rect) {
  7179. this._rect = new BoundingRect(
  7180. style.x || 0, style.y || 0, style.width || 0, style.height || 0
  7181. );
  7182. }
  7183. return this._rect;
  7184. }
  7185. };
  7186. inherits(ZImage, Displayable);
  7187. var HOVER_LAYER_ZLEVEL = 1e5;
  7188. var CANVAS_ZLEVEL = 314159;
  7189. var EL_AFTER_INCREMENTAL_INC = 0.01;
  7190. var INCREMENTAL_INC = 0.001;
  7191. function parseInt10(val) {
  7192. return parseInt(val, 10);
  7193. }
  7194. function isLayerValid(layer) {
  7195. if (!layer) {
  7196. return false;
  7197. }
  7198. if (layer.__builtin__) {
  7199. return true;
  7200. }
  7201. if (typeof(layer.resize) !== 'function'
  7202. || typeof(layer.refresh) !== 'function'
  7203. ) {
  7204. return false;
  7205. }
  7206. return true;
  7207. }
  7208. var tmpRect = new BoundingRect(0, 0, 0, 0);
  7209. var viewRect = new BoundingRect(0, 0, 0, 0);
  7210. function isDisplayableCulled(el, width, height) {
  7211. tmpRect.copy(el.getBoundingRect());
  7212. if (el.transform) {
  7213. tmpRect.applyTransform(el.transform);
  7214. }
  7215. viewRect.width = width;
  7216. viewRect.height = height;
  7217. return !tmpRect.intersect(viewRect);
  7218. }
  7219. function isClipPathChanged(clipPaths, prevClipPaths) {
  7220. if (clipPaths == prevClipPaths) { // Can both be null or undefined
  7221. return false;
  7222. }
  7223. if (!clipPaths || !prevClipPaths || (clipPaths.length !== prevClipPaths.length)) {
  7224. return true;
  7225. }
  7226. for (var i = 0; i < clipPaths.length; i++) {
  7227. if (clipPaths[i] !== prevClipPaths[i]) {
  7228. return true;
  7229. }
  7230. }
  7231. }
  7232. function doClip(clipPaths, ctx) {
  7233. for (var i = 0; i < clipPaths.length; i++) {
  7234. var clipPath = clipPaths[i];
  7235. clipPath.setTransform(ctx);
  7236. ctx.beginPath();
  7237. clipPath.buildPath(ctx, clipPath.shape);
  7238. ctx.clip();
  7239. // Transform back
  7240. clipPath.restoreTransform(ctx);
  7241. }
  7242. }
  7243. function createRoot(width, height) {
  7244. var domRoot = document.createElement('div');
  7245. // domRoot.onselectstart = returnFalse; // 避免页面选中的尴尬
  7246. domRoot.style.cssText = [
  7247. 'position:relative',
  7248. 'overflow:hidden',
  7249. 'width:' + width + 'px',
  7250. 'height:' + height + 'px',
  7251. 'padding:0',
  7252. 'margin:0',
  7253. 'border-width:0'
  7254. ].join(';') + ';';
  7255. return domRoot;
  7256. }
  7257. /**
  7258. * @alias module:zrender/Painter
  7259. * @constructor
  7260. * @param {HTMLElement} root 绘图容器
  7261. * @param {module:zrender/Storage} storage
  7262. * @param {Object} opts
  7263. */
  7264. var Painter = function (root, storage, opts) {
  7265. this.type = 'canvas';
  7266. // In node environment using node-canvas
  7267. var singleCanvas = !root.nodeName // In node ?
  7268. || root.nodeName.toUpperCase() === 'CANVAS';
  7269. this._opts = opts = extend({}, opts || {});
  7270. /**
  7271. * @type {number}
  7272. */
  7273. this.dpr = opts.devicePixelRatio || devicePixelRatio;
  7274. /**
  7275. * @type {boolean}
  7276. * @private
  7277. */
  7278. this._singleCanvas = singleCanvas;
  7279. /**
  7280. * 绘图容器
  7281. * @type {HTMLElement}
  7282. */
  7283. this.root = root;
  7284. var rootStyle = root.style;
  7285. if (rootStyle) {
  7286. rootStyle['-webkit-tap-highlight-color'] = 'transparent';
  7287. rootStyle['-webkit-user-select'] =
  7288. rootStyle['user-select'] =
  7289. rootStyle['-webkit-touch-callout'] = 'none';
  7290. root.innerHTML = '';
  7291. }
  7292. /**
  7293. * @type {module:zrender/Storage}
  7294. */
  7295. this.storage = storage;
  7296. /**
  7297. * @type {Array.<number>}
  7298. * @private
  7299. */
  7300. var zlevelList = this._zlevelList = [];
  7301. /**
  7302. * @type {Object.<string, module:zrender/Layer>}
  7303. * @private
  7304. */
  7305. var layers = this._layers = {};
  7306. /**
  7307. * @type {Object.<string, Object>}
  7308. * @private
  7309. */
  7310. this._layerConfig = {};
  7311. /**
  7312. * zrender will do compositing when root is a canvas and have multiple zlevels.
  7313. */
  7314. this._needsManuallyCompositing = false;
  7315. if (!singleCanvas) {
  7316. this._width = this._getSize(0);
  7317. this._height = this._getSize(1);
  7318. var domRoot = this._domRoot = createRoot(
  7319. this._width, this._height
  7320. );
  7321. root.appendChild(domRoot);
  7322. }
  7323. else {
  7324. var width = root.width;
  7325. var height = root.height;
  7326. if (opts.width != null) {
  7327. width = opts.width;
  7328. }
  7329. if (opts.height != null) {
  7330. height = opts.height;
  7331. }
  7332. this.dpr = opts.devicePixelRatio || 1;
  7333. // Use canvas width and height directly
  7334. root.width = width * this.dpr;
  7335. root.height = height * this.dpr;
  7336. this._width = width;
  7337. this._height = height;
  7338. // Create layer if only one given canvas
  7339. // Device can be specified to create a high dpi image.
  7340. var mainLayer = new Layer(root, this, this.dpr);
  7341. mainLayer.__builtin__ = true;
  7342. mainLayer.initContext();
  7343. // FIXME Use canvas width and height
  7344. // mainLayer.resize(width, height);
  7345. layers[CANVAS_ZLEVEL] = mainLayer;
  7346. // Not use common zlevel.
  7347. zlevelList.push(CANVAS_ZLEVEL);
  7348. this._domRoot = root;
  7349. }
  7350. /**
  7351. * @type {module:zrender/Layer}
  7352. * @private
  7353. */
  7354. this._hoverlayer = null;
  7355. this._hoverElements = [];
  7356. };
  7357. Painter.prototype = {
  7358. constructor: Painter,
  7359. getType: function () {
  7360. return 'canvas';
  7361. },
  7362. /**
  7363. * If painter use a single canvas
  7364. * @return {boolean}
  7365. */
  7366. isSingleCanvas: function () {
  7367. return this._singleCanvas;
  7368. },
  7369. /**
  7370. * @return {HTMLDivElement}
  7371. */
  7372. getViewportRoot: function () {
  7373. return this._domRoot;
  7374. },
  7375. getViewportRootOffset: function () {
  7376. var viewportRoot = this.getViewportRoot();
  7377. if (viewportRoot) {
  7378. return {
  7379. offsetLeft: viewportRoot.offsetLeft || 0,
  7380. offsetTop: viewportRoot.offsetTop || 0
  7381. };
  7382. }
  7383. },
  7384. /**
  7385. * 刷新
  7386. * @param {boolean} [paintAll=false] 强制绘制所有displayable
  7387. */
  7388. refresh: function (paintAll) {
  7389. var list = this.storage.getDisplayList(true);
  7390. var zlevelList = this._zlevelList;
  7391. this._redrawId = Math.random();
  7392. this._paintList(list, paintAll, this._redrawId);
  7393. // Paint custum layers
  7394. for (var i = 0; i < zlevelList.length; i++) {
  7395. var z = zlevelList[i];
  7396. var layer = this._layers[z];
  7397. if (!layer.__builtin__ && layer.refresh) {
  7398. var clearColor = i === 0 ? this._backgroundColor : null;
  7399. layer.refresh(clearColor);
  7400. }
  7401. }
  7402. this.refreshHover();
  7403. return this;
  7404. },
  7405. addHover: function (el, hoverStyle) {
  7406. if (el.__hoverMir) {
  7407. return;
  7408. }
  7409. var elMirror = new el.constructor({
  7410. style: el.style,
  7411. shape: el.shape
  7412. });
  7413. elMirror.__from = el;
  7414. el.__hoverMir = elMirror;
  7415. elMirror.setStyle(hoverStyle);
  7416. this._hoverElements.push(elMirror);
  7417. },
  7418. removeHover: function (el) {
  7419. var elMirror = el.__hoverMir;
  7420. var hoverElements = this._hoverElements;
  7421. var idx = indexOf(hoverElements, elMirror);
  7422. if (idx >= 0) {
  7423. hoverElements.splice(idx, 1);
  7424. }
  7425. el.__hoverMir = null;
  7426. },
  7427. clearHover: function (el) {
  7428. var hoverElements = this._hoverElements;
  7429. for (var i = 0; i < hoverElements.length; i++) {
  7430. var from = hoverElements[i].__from;
  7431. if (from) {
  7432. from.__hoverMir = null;
  7433. }
  7434. }
  7435. hoverElements.length = 0;
  7436. },
  7437. refreshHover: function () {
  7438. var hoverElements = this._hoverElements;
  7439. var len = hoverElements.length;
  7440. var hoverLayer = this._hoverlayer;
  7441. hoverLayer && hoverLayer.clear();
  7442. if (!len) {
  7443. return;
  7444. }
  7445. sort(hoverElements, this.storage.displayableSortFunc);
  7446. // Use a extream large zlevel
  7447. // FIXME?
  7448. if (!hoverLayer) {
  7449. hoverLayer = this._hoverlayer = this.getLayer(HOVER_LAYER_ZLEVEL);
  7450. }
  7451. var scope = {};
  7452. hoverLayer.ctx.save();
  7453. for (var i = 0; i < len;) {
  7454. var el = hoverElements[i];
  7455. var originalEl = el.__from;
  7456. // Original el is removed
  7457. // PENDING
  7458. if (!(originalEl && originalEl.__zr)) {
  7459. hoverElements.splice(i, 1);
  7460. originalEl.__hoverMir = null;
  7461. len--;
  7462. continue;
  7463. }
  7464. i++;
  7465. // Use transform
  7466. // FIXME style and shape ?
  7467. if (!originalEl.invisible) {
  7468. el.transform = originalEl.transform;
  7469. el.invTransform = originalEl.invTransform;
  7470. el.__clipPaths = originalEl.__clipPaths;
  7471. // el.
  7472. this._doPaintEl(el, hoverLayer, true, scope);
  7473. }
  7474. }
  7475. hoverLayer.ctx.restore();
  7476. },
  7477. getHoverLayer: function () {
  7478. return this.getLayer(HOVER_LAYER_ZLEVEL);
  7479. },
  7480. _paintList: function (list, paintAll, redrawId) {
  7481. if (this._redrawId !== redrawId) {
  7482. return;
  7483. }
  7484. paintAll = paintAll || false;
  7485. this._updateLayerStatus(list);
  7486. var finished = this._doPaintList(list, paintAll);
  7487. if (this._needsManuallyCompositing) {
  7488. this._compositeManually();
  7489. }
  7490. if (!finished) {
  7491. var self = this;
  7492. requestAnimationFrame(function () {
  7493. self._paintList(list, paintAll, redrawId);
  7494. });
  7495. }
  7496. },
  7497. _compositeManually: function () {
  7498. var ctx = this.getLayer(CANVAS_ZLEVEL).ctx;
  7499. var width = this._domRoot.width;
  7500. var height = this._domRoot.height;
  7501. ctx.clearRect(0, 0, width, height);
  7502. // PENDING, If only builtin layer?
  7503. this.eachBuiltinLayer(function (layer) {
  7504. if (layer.virtual) {
  7505. ctx.drawImage(layer.dom, 0, 0, width, height);
  7506. }
  7507. });
  7508. },
  7509. _doPaintList: function (list, paintAll) {
  7510. var layerList = [];
  7511. for (var zi = 0; zi < this._zlevelList.length; zi++) {
  7512. var zlevel = this._zlevelList[zi];
  7513. var layer = this._layers[zlevel];
  7514. if (layer.__builtin__
  7515. && layer !== this._hoverlayer
  7516. && (layer.__dirty || paintAll)
  7517. ) {
  7518. layerList.push(layer);
  7519. }
  7520. }
  7521. var finished = true;
  7522. for (var k = 0; k < layerList.length; k++) {
  7523. var layer = layerList[k];
  7524. var ctx = layer.ctx;
  7525. var scope = {};
  7526. ctx.save();
  7527. var start = paintAll ? layer.__startIndex : layer.__drawIndex;
  7528. var useTimer = !paintAll && layer.incremental && Date.now;
  7529. var startTime = useTimer && Date.now();
  7530. var clearColor = layer.zlevel === this._zlevelList[0]
  7531. ? this._backgroundColor : null;
  7532. // All elements in this layer are cleared.
  7533. if (layer.__startIndex === layer.__endIndex) {
  7534. layer.clear(false, clearColor);
  7535. }
  7536. else if (start === layer.__startIndex) {
  7537. var firstEl = list[start];
  7538. if (!firstEl.incremental || !firstEl.notClear || paintAll) {
  7539. layer.clear(false, clearColor);
  7540. }
  7541. }
  7542. if (start === -1) {
  7543. console.error('For some unknown reason. drawIndex is -1');
  7544. start = layer.__startIndex;
  7545. }
  7546. for (var i = start; i < layer.__endIndex; i++) {
  7547. var el = list[i];
  7548. this._doPaintEl(el, layer, paintAll, scope);
  7549. el.__dirty = false;
  7550. if (useTimer) {
  7551. // Date.now can be executed in 13,025,305 ops/second.
  7552. var dTime = Date.now() - startTime;
  7553. // Give 15 millisecond to draw.
  7554. // The rest elements will be drawn in the next frame.
  7555. if (dTime > 15) {
  7556. break;
  7557. }
  7558. }
  7559. }
  7560. layer.__drawIndex = i;
  7561. if (layer.__drawIndex < layer.__endIndex) {
  7562. finished = false;
  7563. }
  7564. if (scope.prevElClipPaths) {
  7565. // Needs restore the state. If last drawn element is in the clipping area.
  7566. ctx.restore();
  7567. }
  7568. ctx.restore();
  7569. }
  7570. if (env$1.wxa) {
  7571. // Flush for weixin application
  7572. each$1(this._layers, function (layer) {
  7573. if (layer && layer.ctx && layer.ctx.draw) {
  7574. layer.ctx.draw();
  7575. }
  7576. });
  7577. }
  7578. return finished;
  7579. },
  7580. _doPaintEl: function (el, currentLayer, forcePaint, scope) {
  7581. var ctx = currentLayer.ctx;
  7582. var m = el.transform;
  7583. if (
  7584. (currentLayer.__dirty || forcePaint)
  7585. // Ignore invisible element
  7586. && !el.invisible
  7587. // Ignore transparent element
  7588. && el.style.opacity !== 0
  7589. // Ignore scale 0 element, in some environment like node-canvas
  7590. // Draw a scale 0 element can cause all following draw wrong
  7591. // And setTransform with scale 0 will cause set back transform failed.
  7592. && !(m && !m[0] && !m[3])
  7593. // Ignore culled element
  7594. && !(el.culling && isDisplayableCulled(el, this._width, this._height))
  7595. ) {
  7596. var clipPaths = el.__clipPaths;
  7597. // Optimize when clipping on group with several elements
  7598. if (!scope.prevElClipPaths
  7599. || isClipPathChanged(clipPaths, scope.prevElClipPaths)
  7600. ) {
  7601. // If has previous clipping state, restore from it
  7602. if (scope.prevElClipPaths) {
  7603. currentLayer.ctx.restore();
  7604. scope.prevElClipPaths = null;
  7605. // Reset prevEl since context has been restored
  7606. scope.prevEl = null;
  7607. }
  7608. // New clipping state
  7609. if (clipPaths) {
  7610. ctx.save();
  7611. doClip(clipPaths, ctx);
  7612. scope.prevElClipPaths = clipPaths;
  7613. }
  7614. }
  7615. el.beforeBrush && el.beforeBrush(ctx);
  7616. el.brush(ctx, scope.prevEl || null);
  7617. scope.prevEl = el;
  7618. el.afterBrush && el.afterBrush(ctx);
  7619. }
  7620. },
  7621. /**
  7622. * 获取 zlevel 所在层,如果不存在则会创建一个新的层
  7623. * @param {number} zlevel
  7624. * @param {boolean} virtual Virtual layer will not be inserted into dom.
  7625. * @return {module:zrender/Layer}
  7626. */
  7627. getLayer: function (zlevel, virtual) {
  7628. if (this._singleCanvas && !this._needsManuallyCompositing) {
  7629. zlevel = CANVAS_ZLEVEL;
  7630. }
  7631. var layer = this._layers[zlevel];
  7632. if (!layer) {
  7633. // Create a new layer
  7634. layer = new Layer('zr_' + zlevel, this, this.dpr);
  7635. layer.zlevel = zlevel;
  7636. layer.__builtin__ = true;
  7637. if (this._layerConfig[zlevel]) {
  7638. merge(layer, this._layerConfig[zlevel], true);
  7639. }
  7640. if (virtual) {
  7641. layer.virtual = virtual;
  7642. }
  7643. this.insertLayer(zlevel, layer);
  7644. // Context is created after dom inserted to document
  7645. // Or excanvas will get 0px clientWidth and clientHeight
  7646. layer.initContext();
  7647. }
  7648. return layer;
  7649. },
  7650. insertLayer: function (zlevel, layer) {
  7651. var layersMap = this._layers;
  7652. var zlevelList = this._zlevelList;
  7653. var len = zlevelList.length;
  7654. var prevLayer = null;
  7655. var i = -1;
  7656. var domRoot = this._domRoot;
  7657. if (layersMap[zlevel]) {
  7658. log$1('ZLevel ' + zlevel + ' has been used already');
  7659. return;
  7660. }
  7661. // Check if is a valid layer
  7662. if (!isLayerValid(layer)) {
  7663. log$1('Layer of zlevel ' + zlevel + ' is not valid');
  7664. return;
  7665. }
  7666. if (len > 0 && zlevel > zlevelList[0]) {
  7667. for (i = 0; i < len - 1; i++) {
  7668. if (
  7669. zlevelList[i] < zlevel
  7670. && zlevelList[i + 1] > zlevel
  7671. ) {
  7672. break;
  7673. }
  7674. }
  7675. prevLayer = layersMap[zlevelList[i]];
  7676. }
  7677. zlevelList.splice(i + 1, 0, zlevel);
  7678. layersMap[zlevel] = layer;
  7679. // Vitual layer will not directly show on the screen.
  7680. // (It can be a WebGL layer and assigned to a ZImage element)
  7681. // But it still under management of zrender.
  7682. if (!layer.virtual) {
  7683. if (prevLayer) {
  7684. var prevDom = prevLayer.dom;
  7685. if (prevDom.nextSibling) {
  7686. domRoot.insertBefore(
  7687. layer.dom,
  7688. prevDom.nextSibling
  7689. );
  7690. }
  7691. else {
  7692. domRoot.appendChild(layer.dom);
  7693. }
  7694. }
  7695. else {
  7696. if (domRoot.firstChild) {
  7697. domRoot.insertBefore(layer.dom, domRoot.firstChild);
  7698. }
  7699. else {
  7700. domRoot.appendChild(layer.dom);
  7701. }
  7702. }
  7703. }
  7704. },
  7705. // Iterate each layer
  7706. eachLayer: function (cb, context) {
  7707. var zlevelList = this._zlevelList;
  7708. var z;
  7709. var i;
  7710. for (i = 0; i < zlevelList.length; i++) {
  7711. z = zlevelList[i];
  7712. cb.call(context, this._layers[z], z);
  7713. }
  7714. },
  7715. // Iterate each buildin layer
  7716. eachBuiltinLayer: function (cb, context) {
  7717. var zlevelList = this._zlevelList;
  7718. var layer;
  7719. var z;
  7720. var i;
  7721. for (i = 0; i < zlevelList.length; i++) {
  7722. z = zlevelList[i];
  7723. layer = this._layers[z];
  7724. if (layer.__builtin__) {
  7725. cb.call(context, layer, z);
  7726. }
  7727. }
  7728. },
  7729. // Iterate each other layer except buildin layer
  7730. eachOtherLayer: function (cb, context) {
  7731. var zlevelList = this._zlevelList;
  7732. var layer;
  7733. var z;
  7734. var i;
  7735. for (i = 0; i < zlevelList.length; i++) {
  7736. z = zlevelList[i];
  7737. layer = this._layers[z];
  7738. if (!layer.__builtin__) {
  7739. cb.call(context, layer, z);
  7740. }
  7741. }
  7742. },
  7743. /**
  7744. * 获取所有已创建的层
  7745. * @param {Array.<module:zrender/Layer>} [prevLayer]
  7746. */
  7747. getLayers: function () {
  7748. return this._layers;
  7749. },
  7750. _updateLayerStatus: function (list) {
  7751. this.eachBuiltinLayer(function (layer, z) {
  7752. layer.__dirty = layer.__used = false;
  7753. });
  7754. function updatePrevLayer(idx) {
  7755. if (prevLayer) {
  7756. if (prevLayer.__endIndex !== idx) {
  7757. prevLayer.__dirty = true;
  7758. }
  7759. prevLayer.__endIndex = idx;
  7760. }
  7761. }
  7762. if (this._singleCanvas) {
  7763. for (var i = 1; i < list.length; i++) {
  7764. var el = list[i];
  7765. if (el.zlevel !== list[i - 1].zlevel || el.incremental) {
  7766. this._needsManuallyCompositing = true;
  7767. break;
  7768. }
  7769. }
  7770. }
  7771. var prevLayer = null;
  7772. var incrementalLayerCount = 0;
  7773. for (var i = 0; i < list.length; i++) {
  7774. var el = list[i];
  7775. var zlevel = el.zlevel;
  7776. var layer;
  7777. // PENDING If change one incremental element style ?
  7778. // TODO Where there are non-incremental elements between incremental elements.
  7779. if (el.incremental) {
  7780. layer = this.getLayer(zlevel + INCREMENTAL_INC, this._needsManuallyCompositing);
  7781. layer.incremental = true;
  7782. incrementalLayerCount = 1;
  7783. }
  7784. else {
  7785. layer = this.getLayer(zlevel + (incrementalLayerCount > 0 ? EL_AFTER_INCREMENTAL_INC : 0), this._needsManuallyCompositing);
  7786. }
  7787. if (!layer.__builtin__) {
  7788. log$1('ZLevel ' + zlevel + ' has been used by unkown layer ' + layer.id);
  7789. }
  7790. if (layer !== prevLayer) {
  7791. layer.__used = true;
  7792. if (layer.__startIndex !== i) {
  7793. layer.__dirty = true;
  7794. }
  7795. layer.__startIndex = i;
  7796. if (!layer.incremental) {
  7797. layer.__drawIndex = i;
  7798. }
  7799. else {
  7800. // Mark layer draw index needs to update.
  7801. layer.__drawIndex = -1;
  7802. }
  7803. updatePrevLayer(i);
  7804. prevLayer = layer;
  7805. }
  7806. if (el.__dirty) {
  7807. layer.__dirty = true;
  7808. if (layer.incremental && layer.__drawIndex < 0) {
  7809. // Start draw from the first dirty element.
  7810. layer.__drawIndex = i;
  7811. }
  7812. }
  7813. }
  7814. updatePrevLayer(i);
  7815. this.eachBuiltinLayer(function (layer, z) {
  7816. // Used in last frame but not in this frame. Needs clear
  7817. if (!layer.__used && layer.getElementCount() > 0) {
  7818. layer.__dirty = true;
  7819. layer.__startIndex = layer.__endIndex = layer.__drawIndex = 0;
  7820. }
  7821. // For incremental layer. In case start index changed and no elements are dirty.
  7822. if (layer.__dirty && layer.__drawIndex < 0) {
  7823. layer.__drawIndex = layer.__startIndex;
  7824. }
  7825. });
  7826. },
  7827. /**
  7828. * 清除hover层外所有内容
  7829. */
  7830. clear: function () {
  7831. this.eachBuiltinLayer(this._clearLayer);
  7832. return this;
  7833. },
  7834. _clearLayer: function (layer) {
  7835. layer.clear();
  7836. },
  7837. setBackgroundColor: function (backgroundColor) {
  7838. this._backgroundColor = backgroundColor;
  7839. },
  7840. /**
  7841. * 修改指定zlevel的绘制参数
  7842. *
  7843. * @param {string} zlevel
  7844. * @param {Object} config 配置对象
  7845. * @param {string} [config.clearColor=0] 每次清空画布的颜色
  7846. * @param {string} [config.motionBlur=false] 是否开启动态模糊
  7847. * @param {number} [config.lastFrameAlpha=0.7]
  7848. * 在开启动态模糊的时候使用,与上一帧混合的alpha值,值越大尾迹越明显
  7849. */
  7850. configLayer: function (zlevel, config) {
  7851. if (config) {
  7852. var layerConfig = this._layerConfig;
  7853. if (!layerConfig[zlevel]) {
  7854. layerConfig[zlevel] = config;
  7855. }
  7856. else {
  7857. merge(layerConfig[zlevel], config, true);
  7858. }
  7859. for (var i = 0; i < this._zlevelList.length; i++) {
  7860. var _zlevel = this._zlevelList[i];
  7861. if (_zlevel === zlevel || _zlevel === zlevel + EL_AFTER_INCREMENTAL_INC) {
  7862. var layer = this._layers[_zlevel];
  7863. merge(layer, layerConfig[zlevel], true);
  7864. }
  7865. }
  7866. }
  7867. },
  7868. /**
  7869. * 删除指定层
  7870. * @param {number} zlevel 层所在的zlevel
  7871. */
  7872. delLayer: function (zlevel) {
  7873. var layers = this._layers;
  7874. var zlevelList = this._zlevelList;
  7875. var layer = layers[zlevel];
  7876. if (!layer) {
  7877. return;
  7878. }
  7879. layer.dom.parentNode.removeChild(layer.dom);
  7880. delete layers[zlevel];
  7881. zlevelList.splice(indexOf(zlevelList, zlevel), 1);
  7882. },
  7883. /**
  7884. * 区域大小变化后重绘
  7885. */
  7886. resize: function (width, height) {
  7887. if (!this._domRoot.style) { // Maybe in node or worker
  7888. if (width == null || height == null) {
  7889. return;
  7890. }
  7891. this._width = width;
  7892. this._height = height;
  7893. this.getLayer(CANVAS_ZLEVEL).resize(width, height);
  7894. }
  7895. else {
  7896. var domRoot = this._domRoot;
  7897. // FIXME Why ?
  7898. domRoot.style.display = 'none';
  7899. // Save input w/h
  7900. var opts = this._opts;
  7901. width != null && (opts.width = width);
  7902. height != null && (opts.height = height);
  7903. width = this._getSize(0);
  7904. height = this._getSize(1);
  7905. domRoot.style.display = '';
  7906. // 优化没有实际改变的resize
  7907. if (this._width != width || height != this._height) {
  7908. domRoot.style.width = width + 'px';
  7909. domRoot.style.height = height + 'px';
  7910. for (var id in this._layers) {
  7911. if (this._layers.hasOwnProperty(id)) {
  7912. this._layers[id].resize(width, height);
  7913. }
  7914. }
  7915. each$1(this._progressiveLayers, function (layer) {
  7916. layer.resize(width, height);
  7917. });
  7918. this.refresh(true);
  7919. }
  7920. this._width = width;
  7921. this._height = height;
  7922. }
  7923. return this;
  7924. },
  7925. /**
  7926. * 清除单独的一个层
  7927. * @param {number} zlevel
  7928. */
  7929. clearLayer: function (zlevel) {
  7930. var layer = this._layers[zlevel];
  7931. if (layer) {
  7932. layer.clear();
  7933. }
  7934. },
  7935. /**
  7936. * 释放
  7937. */
  7938. dispose: function () {
  7939. this.root.innerHTML = '';
  7940. this.root =
  7941. this.storage =
  7942. this._domRoot =
  7943. this._layers = null;
  7944. },
  7945. /**
  7946. * Get canvas which has all thing rendered
  7947. * @param {Object} opts
  7948. * @param {string} [opts.backgroundColor]
  7949. * @param {number} [opts.pixelRatio]
  7950. */
  7951. getRenderedCanvas: function (opts) {
  7952. opts = opts || {};
  7953. if (this._singleCanvas && !this._compositeManually) {
  7954. return this._layers[CANVAS_ZLEVEL].dom;
  7955. }
  7956. var imageLayer = new Layer('image', this, opts.pixelRatio || this.dpr);
  7957. imageLayer.initContext();
  7958. imageLayer.clear(false, opts.backgroundColor || this._backgroundColor);
  7959. if (opts.pixelRatio <= this.dpr) {
  7960. this.refresh();
  7961. var width = imageLayer.dom.width;
  7962. var height = imageLayer.dom.height;
  7963. var ctx = imageLayer.ctx;
  7964. this.eachLayer(function (layer) {
  7965. if (layer.__builtin__) {
  7966. ctx.drawImage(layer.dom, 0, 0, width, height);
  7967. }
  7968. else if (layer.renderToCanvas) {
  7969. imageLayer.ctx.save();
  7970. layer.renderToCanvas(imageLayer.ctx);
  7971. imageLayer.ctx.restore();
  7972. }
  7973. });
  7974. }
  7975. else {
  7976. // PENDING, echarts-gl and incremental rendering.
  7977. var scope = {};
  7978. var displayList = this.storage.getDisplayList(true);
  7979. for (var i = 0; i < displayList.length; i++) {
  7980. var el = displayList[i];
  7981. this._doPaintEl(el, imageLayer, true, scope);
  7982. }
  7983. }
  7984. return imageLayer.dom;
  7985. },
  7986. /**
  7987. * 获取绘图区域宽度
  7988. */
  7989. getWidth: function () {
  7990. return this._width;
  7991. },
  7992. /**
  7993. * 获取绘图区域高度
  7994. */
  7995. getHeight: function () {
  7996. return this._height;
  7997. },
  7998. _getSize: function (whIdx) {
  7999. var opts = this._opts;
  8000. var wh = ['width', 'height'][whIdx];
  8001. var cwh = ['clientWidth', 'clientHeight'][whIdx];
  8002. var plt = ['paddingLeft', 'paddingTop'][whIdx];
  8003. var prb = ['paddingRight', 'paddingBottom'][whIdx];
  8004. if (opts[wh] != null && opts[wh] !== 'auto') {
  8005. return parseFloat(opts[wh]);
  8006. }
  8007. var root = this.root;
  8008. // IE8 does not support getComputedStyle, but it use VML.
  8009. var stl = document.defaultView.getComputedStyle(root);
  8010. return (
  8011. (root[cwh] || parseInt10(stl[wh]) || parseInt10(root.style[wh]))
  8012. - (parseInt10(stl[plt]) || 0)
  8013. - (parseInt10(stl[prb]) || 0)
  8014. ) | 0;
  8015. },
  8016. pathToImage: function (path, dpr) {
  8017. dpr = dpr || this.dpr;
  8018. var canvas = document.createElement('canvas');
  8019. var ctx = canvas.getContext('2d');
  8020. var rect = path.getBoundingRect();
  8021. var style = path.style;
  8022. var shadowBlurSize = style.shadowBlur * dpr;
  8023. var shadowOffsetX = style.shadowOffsetX * dpr;
  8024. var shadowOffsetY = style.shadowOffsetY * dpr;
  8025. var lineWidth = style.hasStroke() ? style.lineWidth : 0;
  8026. var leftMargin = Math.max(lineWidth / 2, -shadowOffsetX + shadowBlurSize);
  8027. var rightMargin = Math.max(lineWidth / 2, shadowOffsetX + shadowBlurSize);
  8028. var topMargin = Math.max(lineWidth / 2, -shadowOffsetY + shadowBlurSize);
  8029. var bottomMargin = Math.max(lineWidth / 2, shadowOffsetY + shadowBlurSize);
  8030. var width = rect.width + leftMargin + rightMargin;
  8031. var height = rect.height + topMargin + bottomMargin;
  8032. canvas.width = width * dpr;
  8033. canvas.height = height * dpr;
  8034. ctx.scale(dpr, dpr);
  8035. ctx.clearRect(0, 0, width, height);
  8036. ctx.dpr = dpr;
  8037. var pathTransform = {
  8038. position: path.position,
  8039. rotation: path.rotation,
  8040. scale: path.scale
  8041. };
  8042. path.position = [leftMargin - rect.x, topMargin - rect.y];
  8043. path.rotation = 0;
  8044. path.scale = [1, 1];
  8045. path.updateTransform();
  8046. if (path) {
  8047. path.brush(ctx);
  8048. }
  8049. var ImageShape = ZImage;
  8050. var imgShape = new ImageShape({
  8051. style: {
  8052. x: 0,
  8053. y: 0,
  8054. image: canvas
  8055. }
  8056. });
  8057. if (pathTransform.position != null) {
  8058. imgShape.position = path.position = pathTransform.position;
  8059. }
  8060. if (pathTransform.rotation != null) {
  8061. imgShape.rotation = path.rotation = pathTransform.rotation;
  8062. }
  8063. if (pathTransform.scale != null) {
  8064. imgShape.scale = path.scale = pathTransform.scale;
  8065. }
  8066. return imgShape;
  8067. }
  8068. };
  8069. /**
  8070. * 事件辅助类
  8071. * @module zrender/core/event
  8072. * @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
  8073. */
  8074. var isDomLevel2 = (typeof window !== 'undefined') && !!window.addEventListener;
  8075. var MOUSE_EVENT_REG = /^(?:mouse|pointer|contextmenu|drag|drop)|click/;
  8076. function getBoundingClientRect(el) {
  8077. // BlackBerry 5, iOS 3 (original iPhone) don't have getBoundingRect
  8078. return el.getBoundingClientRect ? el.getBoundingClientRect() : {left: 0, top: 0};
  8079. }
  8080. // `calculate` is optional, default false
  8081. function clientToLocal(el, e, out, calculate) {
  8082. out = out || {};
  8083. // According to the W3C Working Draft, offsetX and offsetY should be relative
  8084. // to the padding edge of the target element. The only browser using this convention
  8085. // is IE. Webkit uses the border edge, Opera uses the content edge, and FireFox does
  8086. // not support the properties.
  8087. // (see http://www.jacklmoore.com/notes/mouse-position/)
  8088. // In zr painter.dom, padding edge equals to border edge.
  8089. // FIXME
  8090. // When mousemove event triggered on ec tooltip, target is not zr painter.dom, and
  8091. // offsetX/Y is relative to e.target, where the calculation of zrX/Y via offsetX/Y
  8092. // is too complex. So css-transfrom dont support in this case temporarily.
  8093. if (calculate || !env$1.canvasSupported) {
  8094. defaultGetZrXY(el, e, out);
  8095. }
  8096. // Caution: In FireFox, layerX/layerY Mouse position relative to the closest positioned
  8097. // ancestor element, so we should make sure el is positioned (e.g., not position:static).
  8098. // BTW1, Webkit don't return the same results as FF in non-simple cases (like add
  8099. // zoom-factor, overflow / opacity layers, transforms ...)
  8100. // BTW2, (ev.offsetY || ev.pageY - $(ev.target).offset().top) is not correct in preserve-3d.
  8101. // <https://bugs.jquery.com/ticket/8523#comment:14>
  8102. // BTW3, In ff, offsetX/offsetY is always 0.
  8103. else if (env$1.browser.firefox && e.layerX != null && e.layerX !== e.offsetX) {
  8104. out.zrX = e.layerX;
  8105. out.zrY = e.layerY;
  8106. }
  8107. // For IE6+, chrome, safari, opera. (When will ff support offsetX?)
  8108. else if (e.offsetX != null) {
  8109. out.zrX = e.offsetX;
  8110. out.zrY = e.offsetY;
  8111. }
  8112. // For some other device, e.g., IOS safari.
  8113. else {
  8114. defaultGetZrXY(el, e, out);
  8115. }
  8116. return out;
  8117. }
  8118. function defaultGetZrXY(el, e, out) {
  8119. // This well-known method below does not support css transform.
  8120. var box = getBoundingClientRect(el);
  8121. out.zrX = e.clientX - box.left;
  8122. out.zrY = e.clientY - box.top;
  8123. }
  8124. /**
  8125. * 如果存在第三方嵌入的一些dom触发的事件,或touch事件,需要转换一下事件坐标.
  8126. * `calculate` is optional, default false.
  8127. */
  8128. function normalizeEvent(el, e, calculate) {
  8129. e = e || window.event;
  8130. if (e.zrX != null) {
  8131. return e;
  8132. }
  8133. var eventType = e.type;
  8134. var isTouch = eventType && eventType.indexOf('touch') >= 0;
  8135. if (!isTouch) {
  8136. clientToLocal(el, e, e, calculate);
  8137. e.zrDelta = (e.wheelDelta) ? e.wheelDelta / 120 : -(e.detail || 0) / 3;
  8138. }
  8139. else {
  8140. var touch = eventType != 'touchend'
  8141. ? e.targetTouches[0]
  8142. : e.changedTouches[0];
  8143. touch && clientToLocal(el, touch, e, calculate);
  8144. }
  8145. // Add which for click: 1 === left; 2 === middle; 3 === right; otherwise: 0;
  8146. // See jQuery: https://github.com/jquery/jquery/blob/master/src/event.js
  8147. // If e.which has been defined, if may be readonly,
  8148. // see: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which
  8149. var button = e.button;
  8150. if (e.which == null && button !== undefined && MOUSE_EVENT_REG.test(e.type)) {
  8151. e.which = (button & 1 ? 1 : (button & 2 ? 3 : (button & 4 ? 2 : 0)));
  8152. }
  8153. return e;
  8154. }
  8155. /**
  8156. * @param {HTMLElement} el
  8157. * @param {string} name
  8158. * @param {Function} handler
  8159. */
  8160. function addEventListener(el, name, handler) {
  8161. if (isDomLevel2) {
  8162. // Reproduct the console warning:
  8163. // [Violation] Added non-passive event listener to a scroll-blocking <some> event.
  8164. // Consider marking event handler as 'passive' to make the page more responsive.
  8165. // Just set console log level: verbose in chrome dev tool.
  8166. // then the warning log will be printed when addEventListener called.
  8167. // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
  8168. // We have not yet found a neat way to using passive. Because in zrender the dom event
  8169. // listener delegate all of the upper events of element. Some of those events need
  8170. // to prevent default. For example, the feature `preventDefaultMouseMove` of echarts.
  8171. // Before passive can be adopted, these issues should be considered:
  8172. // (1) Whether and how a zrender user specifies an event listener passive. And by default,
  8173. // passive or not.
  8174. // (2) How to tread that some zrender event listener is passive, and some is not. If
  8175. // we use other way but not preventDefault of mousewheel and touchmove, browser
  8176. // compatibility should be handled.
  8177. // var opts = (env.passiveSupported && name === 'mousewheel')
  8178. // ? {passive: true}
  8179. // // By default, the third param of el.addEventListener is `capture: false`.
  8180. // : void 0;
  8181. // el.addEventListener(name, handler /* , opts */);
  8182. el.addEventListener(name, handler);
  8183. }
  8184. else {
  8185. el.attachEvent('on' + name, handler);
  8186. }
  8187. }
  8188. function removeEventListener(el, name, handler) {
  8189. if (isDomLevel2) {
  8190. el.removeEventListener(name, handler);
  8191. }
  8192. else {
  8193. el.detachEvent('on' + name, handler);
  8194. }
  8195. }
  8196. /**
  8197. * preventDefault and stopPropagation.
  8198. * Notice: do not do that in zrender. Upper application
  8199. * do that if necessary.
  8200. *
  8201. * @memberOf module:zrender/core/event
  8202. * @method
  8203. * @param {Event} e : event对象
  8204. */
  8205. /**
  8206. * 动画主类, 调度和管理所有动画控制器
  8207. *
  8208. * @module zrender/animation/Animation
  8209. * @author pissang(https://github.com/pissang)
  8210. */
  8211. // TODO Additive animation
  8212. // http://iosoteric.com/additive-animations-animatewithduration-in-ios-8/
  8213. // https://developer.apple.com/videos/wwdc2014/#236
  8214. /**
  8215. * @typedef {Object} IZRenderStage
  8216. * @property {Function} update
  8217. */
  8218. /**
  8219. * @alias module:zrender/animation/Animation
  8220. * @constructor
  8221. * @param {Object} [options]
  8222. * @param {Function} [options.onframe]
  8223. * @param {IZRenderStage} [options.stage]
  8224. * @example
  8225. * var animation = new Animation();
  8226. * var obj = {
  8227. * x: 100,
  8228. * y: 100
  8229. * };
  8230. * animation.animate(node.position)
  8231. * .when(1000, {
  8232. * x: 500,
  8233. * y: 500
  8234. * })
  8235. * .when(2000, {
  8236. * x: 100,
  8237. * y: 100
  8238. * })
  8239. * .start('spline');
  8240. */
  8241. var Animation = function (options) {
  8242. options = options || {};
  8243. this.stage = options.stage || {};
  8244. this.onframe = options.onframe || function() {};
  8245. // private properties
  8246. this._clips = [];
  8247. this._running = false;
  8248. this._time;
  8249. this._pausedTime;
  8250. this._pauseStart;
  8251. this._paused = false;
  8252. Eventful.call(this);
  8253. };
  8254. Animation.prototype = {
  8255. constructor: Animation,
  8256. /**
  8257. * 添加 clip
  8258. * @param {module:zrender/animation/Clip} clip
  8259. */
  8260. addClip: function (clip) {
  8261. this._clips.push(clip);
  8262. },
  8263. /**
  8264. * 添加 animator
  8265. * @param {module:zrender/animation/Animator} animator
  8266. */
  8267. addAnimator: function (animator) {
  8268. animator.animation = this;
  8269. var clips = animator.getClips();
  8270. for (var i = 0; i < clips.length; i++) {
  8271. this.addClip(clips[i]);
  8272. }
  8273. },
  8274. /**
  8275. * 删除动画片段
  8276. * @param {module:zrender/animation/Clip} clip
  8277. */
  8278. removeClip: function(clip) {
  8279. var idx = indexOf(this._clips, clip);
  8280. if (idx >= 0) {
  8281. this._clips.splice(idx, 1);
  8282. }
  8283. },
  8284. /**
  8285. * 删除动画片段
  8286. * @param {module:zrender/animation/Animator} animator
  8287. */
  8288. removeAnimator: function (animator) {
  8289. var clips = animator.getClips();
  8290. for (var i = 0; i < clips.length; i++) {
  8291. this.removeClip(clips[i]);
  8292. }
  8293. animator.animation = null;
  8294. },
  8295. _update: function() {
  8296. var time = new Date().getTime() - this._pausedTime;
  8297. var delta = time - this._time;
  8298. var clips = this._clips;
  8299. var len = clips.length;
  8300. var deferredEvents = [];
  8301. var deferredClips = [];
  8302. for (var i = 0; i < len; i++) {
  8303. var clip = clips[i];
  8304. var e = clip.step(time, delta);
  8305. // Throw out the events need to be called after
  8306. // stage.update, like destroy
  8307. if (e) {
  8308. deferredEvents.push(e);
  8309. deferredClips.push(clip);
  8310. }
  8311. }
  8312. // Remove the finished clip
  8313. for (var i = 0; i < len;) {
  8314. if (clips[i]._needsRemove) {
  8315. clips[i] = clips[len - 1];
  8316. clips.pop();
  8317. len--;
  8318. }
  8319. else {
  8320. i++;
  8321. }
  8322. }
  8323. len = deferredEvents.length;
  8324. for (var i = 0; i < len; i++) {
  8325. deferredClips[i].fire(deferredEvents[i]);
  8326. }
  8327. this._time = time;
  8328. this.onframe(delta);
  8329. // 'frame' should be triggered before stage, because upper application
  8330. // depends on the sequence (e.g., echarts-stream and finish
  8331. // event judge)
  8332. this.trigger('frame', delta);
  8333. if (this.stage.update) {
  8334. this.stage.update();
  8335. }
  8336. },
  8337. _startLoop: function () {
  8338. var self = this;
  8339. this._running = true;
  8340. function step() {
  8341. if (self._running) {
  8342. requestAnimationFrame(step);
  8343. !self._paused && self._update();
  8344. }
  8345. }
  8346. requestAnimationFrame(step);
  8347. },
  8348. /**
  8349. * Start animation.
  8350. */
  8351. start: function () {
  8352. this._time = new Date().getTime();
  8353. this._pausedTime = 0;
  8354. this._startLoop();
  8355. },
  8356. /**
  8357. * Stop animation.
  8358. */
  8359. stop: function () {
  8360. this._running = false;
  8361. },
  8362. /**
  8363. * Pause animation.
  8364. */
  8365. pause: function () {
  8366. if (!this._paused) {
  8367. this._pauseStart = new Date().getTime();
  8368. this._paused = true;
  8369. }
  8370. },
  8371. /**
  8372. * Resume animation.
  8373. */
  8374. resume: function () {
  8375. if (this._paused) {
  8376. this._pausedTime += (new Date().getTime()) - this._pauseStart;
  8377. this._paused = false;
  8378. }
  8379. },
  8380. /**
  8381. * Clear animation.
  8382. */
  8383. clear: function () {
  8384. this._clips = [];
  8385. },
  8386. /**
  8387. * Whether animation finished.
  8388. */
  8389. isFinished: function () {
  8390. return !this._clips.length;
  8391. },
  8392. /**
  8393. * Creat animator for a target, whose props can be animated.
  8394. *
  8395. * @param {Object} target
  8396. * @param {Object} options
  8397. * @param {boolean} [options.loop=false] Whether loop animation.
  8398. * @param {Function} [options.getter=null] Get value from target.
  8399. * @param {Function} [options.setter=null] Set value to target.
  8400. * @return {module:zrender/animation/Animation~Animator}
  8401. */
  8402. // TODO Gap
  8403. animate: function (target, options) {
  8404. options = options || {};
  8405. var animator = new Animator(
  8406. target,
  8407. options.loop,
  8408. options.getter,
  8409. options.setter
  8410. );
  8411. this.addAnimator(animator);
  8412. return animator;
  8413. }
  8414. };
  8415. mixin(Animation, Eventful);
  8416. /**
  8417. * Only implements needed gestures for mobile.
  8418. */
  8419. var GestureMgr = function () {
  8420. /**
  8421. * @private
  8422. * @type {Array.<Object>}
  8423. */
  8424. this._track = [];
  8425. };
  8426. GestureMgr.prototype = {
  8427. constructor: GestureMgr,
  8428. recognize: function (event, target, root) {
  8429. this._doTrack(event, target, root);
  8430. return this._recognize(event);
  8431. },
  8432. clear: function () {
  8433. this._track.length = 0;
  8434. return this;
  8435. },
  8436. _doTrack: function (event, target, root) {
  8437. var touches = event.touches;
  8438. if (!touches) {
  8439. return;
  8440. }
  8441. var trackItem = {
  8442. points: [],
  8443. touches: [],
  8444. target: target,
  8445. event: event
  8446. };
  8447. for (var i = 0, len = touches.length; i < len; i++) {
  8448. var touch = touches[i];
  8449. var pos = clientToLocal(root, touch, {});
  8450. trackItem.points.push([pos.zrX, pos.zrY]);
  8451. trackItem.touches.push(touch);
  8452. }
  8453. this._track.push(trackItem);
  8454. },
  8455. _recognize: function (event) {
  8456. for (var eventName in recognizers) {
  8457. if (recognizers.hasOwnProperty(eventName)) {
  8458. var gestureInfo = recognizers[eventName](this._track, event);
  8459. if (gestureInfo) {
  8460. return gestureInfo;
  8461. }
  8462. }
  8463. }
  8464. }
  8465. };
  8466. function dist$1(pointPair) {
  8467. var dx = pointPair[1][0] - pointPair[0][0];
  8468. var dy = pointPair[1][1] - pointPair[0][1];
  8469. return Math.sqrt(dx * dx + dy * dy);
  8470. }
  8471. function center(pointPair) {
  8472. return [
  8473. (pointPair[0][0] + pointPair[1][0]) / 2,
  8474. (pointPair[0][1] + pointPair[1][1]) / 2
  8475. ];
  8476. }
  8477. var recognizers = {
  8478. pinch: function (track, event) {
  8479. var trackLen = track.length;
  8480. if (!trackLen) {
  8481. return;
  8482. }
  8483. var pinchEnd = (track[trackLen - 1] || {}).points;
  8484. var pinchPre = (track[trackLen - 2] || {}).points || pinchEnd;
  8485. if (pinchPre
  8486. && pinchPre.length > 1
  8487. && pinchEnd
  8488. && pinchEnd.length > 1
  8489. ) {
  8490. var pinchScale = dist$1(pinchEnd) / dist$1(pinchPre);
  8491. !isFinite(pinchScale) && (pinchScale = 1);
  8492. event.pinchScale = pinchScale;
  8493. var pinchCenter = center(pinchEnd);
  8494. event.pinchX = pinchCenter[0];
  8495. event.pinchY = pinchCenter[1];
  8496. return {
  8497. type: 'pinch',
  8498. target: track[0].target,
  8499. event: event
  8500. };
  8501. }
  8502. }
  8503. // Only pinch currently.
  8504. };
  8505. var TOUCH_CLICK_DELAY = 300;
  8506. var mouseHandlerNames = [
  8507. 'click', 'dblclick', 'mousewheel', 'mouseout',
  8508. 'mouseup', 'mousedown', 'mousemove', 'contextmenu'
  8509. ];
  8510. var touchHandlerNames = [
  8511. 'touchstart', 'touchend', 'touchmove'
  8512. ];
  8513. var pointerEventNames = {
  8514. pointerdown: 1, pointerup: 1, pointermove: 1, pointerout: 1
  8515. };
  8516. var pointerHandlerNames = map(mouseHandlerNames, function (name) {
  8517. var nm = name.replace('mouse', 'pointer');
  8518. return pointerEventNames[nm] ? nm : name;
  8519. });
  8520. function eventNameFix(name) {
  8521. return (name === 'mousewheel' && env$1.browser.firefox) ? 'DOMMouseScroll' : name;
  8522. }
  8523. function processGesture(proxy, event, stage) {
  8524. var gestureMgr = proxy._gestureMgr;
  8525. stage === 'start' && gestureMgr.clear();
  8526. var gestureInfo = gestureMgr.recognize(
  8527. event,
  8528. proxy.handler.findHover(event.zrX, event.zrY, null).target,
  8529. proxy.dom
  8530. );
  8531. stage === 'end' && gestureMgr.clear();
  8532. // Do not do any preventDefault here. Upper application do that if necessary.
  8533. if (gestureInfo) {
  8534. var type = gestureInfo.type;
  8535. event.gestureEvent = type;
  8536. proxy.handler.dispatchToElement({target: gestureInfo.target}, type, gestureInfo.event);
  8537. }
  8538. }
  8539. // function onMSGestureChange(proxy, event) {
  8540. // if (event.translationX || event.translationY) {
  8541. // // mousemove is carried by MSGesture to reduce the sensitivity.
  8542. // proxy.handler.dispatchToElement(event.target, 'mousemove', event);
  8543. // }
  8544. // if (event.scale !== 1) {
  8545. // event.pinchX = event.offsetX;
  8546. // event.pinchY = event.offsetY;
  8547. // event.pinchScale = event.scale;
  8548. // proxy.handler.dispatchToElement(event.target, 'pinch', event);
  8549. // }
  8550. // }
  8551. /**
  8552. * Prevent mouse event from being dispatched after Touch Events action
  8553. * @see <https://github.com/deltakosh/handjs/blob/master/src/hand.base.js>
  8554. * 1. Mobile browsers dispatch mouse events 300ms after touchend.
  8555. * 2. Chrome for Android dispatch mousedown for long-touch about 650ms
  8556. * Result: Blocking Mouse Events for 700ms.
  8557. */
  8558. function setTouchTimer(instance) {
  8559. instance._touching = true;
  8560. clearTimeout(instance._touchTimer);
  8561. instance._touchTimer = setTimeout(function () {
  8562. instance._touching = false;
  8563. }, 700);
  8564. }
  8565. var domHandlers = {
  8566. /**
  8567. * Mouse move handler
  8568. * @inner
  8569. * @param {Event} event
  8570. */
  8571. mousemove: function (event) {
  8572. event = normalizeEvent(this.dom, event);
  8573. this.trigger('mousemove', event);
  8574. },
  8575. /**
  8576. * Mouse out handler
  8577. * @inner
  8578. * @param {Event} event
  8579. */
  8580. mouseout: function (event) {
  8581. event = normalizeEvent(this.dom, event);
  8582. var element = event.toElement || event.relatedTarget;
  8583. if (element != this.dom) {
  8584. while (element && element.nodeType != 9) {
  8585. // 忽略包含在root中的dom引起的mouseOut
  8586. if (element === this.dom) {
  8587. return;
  8588. }
  8589. element = element.parentNode;
  8590. }
  8591. }
  8592. this.trigger('mouseout', event);
  8593. },
  8594. /**
  8595. * Touch开始响应函数
  8596. * @inner
  8597. * @param {Event} event
  8598. */
  8599. touchstart: function (event) {
  8600. // Default mouse behaviour should not be disabled here.
  8601. // For example, page may needs to be slided.
  8602. event = normalizeEvent(this.dom, event);
  8603. // Mark touch, which is useful in distinguish touch and
  8604. // mouse event in upper applicatoin.
  8605. event.zrByTouch = true;
  8606. this._lastTouchMoment = new Date();
  8607. processGesture(this, event, 'start');
  8608. // In touch device, trigger `mousemove`(`mouseover`) should
  8609. // be triggered, and must before `mousedown` triggered.
  8610. domHandlers.mousemove.call(this, event);
  8611. domHandlers.mousedown.call(this, event);
  8612. setTouchTimer(this);
  8613. },
  8614. /**
  8615. * Touch移动响应函数
  8616. * @inner
  8617. * @param {Event} event
  8618. */
  8619. touchmove: function (event) {
  8620. event = normalizeEvent(this.dom, event);
  8621. // Mark touch, which is useful in distinguish touch and
  8622. // mouse event in upper applicatoin.
  8623. event.zrByTouch = true;
  8624. processGesture(this, event, 'change');
  8625. // Mouse move should always be triggered no matter whether
  8626. // there is gestrue event, because mouse move and pinch may
  8627. // be used at the same time.
  8628. domHandlers.mousemove.call(this, event);
  8629. setTouchTimer(this);
  8630. },
  8631. /**
  8632. * Touch结束响应函数
  8633. * @inner
  8634. * @param {Event} event
  8635. */
  8636. touchend: function (event) {
  8637. event = normalizeEvent(this.dom, event);
  8638. // Mark touch, which is useful in distinguish touch and
  8639. // mouse event in upper applicatoin.
  8640. event.zrByTouch = true;
  8641. processGesture(this, event, 'end');
  8642. domHandlers.mouseup.call(this, event);
  8643. // Do not trigger `mouseout` here, in spite of `mousemove`(`mouseover`) is
  8644. // triggered in `touchstart`. This seems to be illogical, but by this mechanism,
  8645. // we can conveniently implement "hover style" in both PC and touch device just
  8646. // by listening to `mouseover` to add "hover style" and listening to `mouseout`
  8647. // to remove "hover style" on an element, without any additional code for
  8648. // compatibility. (`mouseout` will not be triggered in `touchend`, so "hover
  8649. // style" will remain for user view)
  8650. // click event should always be triggered no matter whether
  8651. // there is gestrue event. System click can not be prevented.
  8652. if (+new Date() - this._lastTouchMoment < TOUCH_CLICK_DELAY) {
  8653. domHandlers.click.call(this, event);
  8654. }
  8655. setTouchTimer(this);
  8656. },
  8657. pointerdown: function (event) {
  8658. domHandlers.mousedown.call(this, event);
  8659. // if (useMSGuesture(this, event)) {
  8660. // this._msGesture.addPointer(event.pointerId);
  8661. // }
  8662. },
  8663. pointermove: function (event) {
  8664. // FIXME
  8665. // pointermove is so sensitive that it always triggered when
  8666. // tap(click) on touch screen, which affect some judgement in
  8667. // upper application. So, we dont support mousemove on MS touch
  8668. // device yet.
  8669. if (!isPointerFromTouch(event)) {
  8670. domHandlers.mousemove.call(this, event);
  8671. }
  8672. },
  8673. pointerup: function (event) {
  8674. domHandlers.mouseup.call(this, event);
  8675. },
  8676. pointerout: function (event) {
  8677. // pointerout will be triggered when tap on touch screen
  8678. // (IE11+/Edge on MS Surface) after click event triggered,
  8679. // which is inconsistent with the mousout behavior we defined
  8680. // in touchend. So we unify them.
  8681. // (check domHandlers.touchend for detailed explanation)
  8682. if (!isPointerFromTouch(event)) {
  8683. domHandlers.mouseout.call(this, event);
  8684. }
  8685. }
  8686. };
  8687. function isPointerFromTouch(event) {
  8688. var pointerType = event.pointerType;
  8689. return pointerType === 'pen' || pointerType === 'touch';
  8690. }
  8691. // function useMSGuesture(handlerProxy, event) {
  8692. // return isPointerFromTouch(event) && !!handlerProxy._msGesture;
  8693. // }
  8694. // Common handlers
  8695. each$1(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
  8696. domHandlers[name] = function (event) {
  8697. event = normalizeEvent(this.dom, event);
  8698. this.trigger(name, event);
  8699. };
  8700. });
  8701. /**
  8702. * 为控制类实例初始化dom 事件处理函数
  8703. *
  8704. * @inner
  8705. * @param {module:zrender/Handler} instance 控制类实例
  8706. */
  8707. function initDomHandler(instance) {
  8708. each$1(touchHandlerNames, function (name) {
  8709. instance._handlers[name] = bind(domHandlers[name], instance);
  8710. });
  8711. each$1(pointerHandlerNames, function (name) {
  8712. instance._handlers[name] = bind(domHandlers[name], instance);
  8713. });
  8714. each$1(mouseHandlerNames, function (name) {
  8715. instance._handlers[name] = makeMouseHandler(domHandlers[name], instance);
  8716. });
  8717. function makeMouseHandler(fn, instance) {
  8718. return function () {
  8719. if (instance._touching) {
  8720. return;
  8721. }
  8722. return fn.apply(instance, arguments);
  8723. };
  8724. }
  8725. }
  8726. function HandlerDomProxy(dom) {
  8727. Eventful.call(this);
  8728. this.dom = dom;
  8729. /**
  8730. * @private
  8731. * @type {boolean}
  8732. */
  8733. this._touching = false;
  8734. /**
  8735. * @private
  8736. * @type {number}
  8737. */
  8738. this._touchTimer;
  8739. /**
  8740. * @private
  8741. * @type {module:zrender/core/GestureMgr}
  8742. */
  8743. this._gestureMgr = new GestureMgr();
  8744. this._handlers = {};
  8745. initDomHandler(this);
  8746. if (env$1.pointerEventsSupported) { // Only IE11+/Edge
  8747. // 1. On devices that both enable touch and mouse (e.g., MS Surface and lenovo X240),
  8748. // IE11+/Edge do not trigger touch event, but trigger pointer event and mouse event
  8749. // at the same time.
  8750. // 2. On MS Surface, it probablely only trigger mousedown but no mouseup when tap on
  8751. // screen, which do not occurs in pointer event.
  8752. // So we use pointer event to both detect touch gesture and mouse behavior.
  8753. mountHandlers(pointerHandlerNames, this);
  8754. // FIXME
  8755. // Note: MS Gesture require CSS touch-action set. But touch-action is not reliable,
  8756. // which does not prevent defuault behavior occasionally (which may cause view port
  8757. // zoomed in but use can not zoom it back). And event.preventDefault() does not work.
  8758. // So we have to not to use MSGesture and not to support touchmove and pinch on MS
  8759. // touch screen. And we only support click behavior on MS touch screen now.
  8760. // MS Gesture Event is only supported on IE11+/Edge and on Windows 8+.
  8761. // We dont support touch on IE on win7.
  8762. // See <https://msdn.microsoft.com/en-us/library/dn433243(v=vs.85).aspx>
  8763. // if (typeof MSGesture === 'function') {
  8764. // (this._msGesture = new MSGesture()).target = dom; // jshint ignore:line
  8765. // dom.addEventListener('MSGestureChange', onMSGestureChange);
  8766. // }
  8767. }
  8768. else {
  8769. if (env$1.touchEventsSupported) {
  8770. mountHandlers(touchHandlerNames, this);
  8771. // Handler of 'mouseout' event is needed in touch mode, which will be mounted below.
  8772. // addEventListener(root, 'mouseout', this._mouseoutHandler);
  8773. }
  8774. // 1. Considering some devices that both enable touch and mouse event (like on MS Surface
  8775. // and lenovo X240, @see #2350), we make mouse event be always listened, otherwise
  8776. // mouse event can not be handle in those devices.
  8777. // 2. On MS Surface, Chrome will trigger both touch event and mouse event. How to prevent
  8778. // mouseevent after touch event triggered, see `setTouchTimer`.
  8779. mountHandlers(mouseHandlerNames, this);
  8780. }
  8781. function mountHandlers(handlerNames, instance) {
  8782. each$1(handlerNames, function (name) {
  8783. addEventListener(dom, eventNameFix(name), instance._handlers[name]);
  8784. }, instance);
  8785. }
  8786. }
  8787. var handlerDomProxyProto = HandlerDomProxy.prototype;
  8788. handlerDomProxyProto.dispose = function () {
  8789. var handlerNames = mouseHandlerNames.concat(touchHandlerNames);
  8790. for (var i = 0; i < handlerNames.length; i++) {
  8791. var name = handlerNames[i];
  8792. removeEventListener(this.dom, eventNameFix(name), this._handlers[name]);
  8793. }
  8794. };
  8795. handlerDomProxyProto.setCursor = function (cursorStyle) {
  8796. this.dom.style && (this.dom.style.cursor = cursorStyle || 'default');
  8797. };
  8798. mixin(HandlerDomProxy, Eventful);
  8799. /*!
  8800. * ZRender, a high performance 2d drawing library.
  8801. *
  8802. * Copyright (c) 2013, Baidu Inc.
  8803. * All rights reserved.
  8804. *
  8805. * LICENSE
  8806. * https://github.com/ecomfe/zrender/blob/master/LICENSE.txt
  8807. */
  8808. var useVML = !env$1.canvasSupported;
  8809. var painterCtors = {
  8810. canvas: Painter
  8811. };
  8812. /**
  8813. * @type {string}
  8814. */
  8815. var version$1 = '4.0.3';
  8816. /**
  8817. * Initializing a zrender instance
  8818. * @param {HTMLElement} dom
  8819. * @param {Object} opts
  8820. * @param {string} [opts.renderer='canvas'] 'canvas' or 'svg'
  8821. * @param {number} [opts.devicePixelRatio]
  8822. * @param {number|string} [opts.width] Can be 'auto' (the same as null/undefined)
  8823. * @param {number|string} [opts.height] Can be 'auto' (the same as null/undefined)
  8824. * @return {module:zrender/ZRender}
  8825. */
  8826. function init$1(dom, opts) {
  8827. var zr = new ZRender(guid(), dom, opts);
  8828. return zr;
  8829. }
  8830. /**
  8831. * Dispose zrender instance
  8832. * @param {module:zrender/ZRender} zr
  8833. */
  8834. /**
  8835. * Get zrender instance by id
  8836. * @param {string} id zrender instance id
  8837. * @return {module:zrender/ZRender}
  8838. */
  8839. /**
  8840. * @module zrender/ZRender
  8841. */
  8842. /**
  8843. * @constructor
  8844. * @alias module:zrender/ZRender
  8845. * @param {string} id
  8846. * @param {HTMLElement} dom
  8847. * @param {Object} opts
  8848. * @param {string} [opts.renderer='canvas'] 'canvas' or 'svg'
  8849. * @param {number} [opts.devicePixelRatio]
  8850. * @param {number} [opts.width] Can be 'auto' (the same as null/undefined)
  8851. * @param {number} [opts.height] Can be 'auto' (the same as null/undefined)
  8852. */
  8853. var ZRender = function (id, dom, opts) {
  8854. opts = opts || {};
  8855. /**
  8856. * @type {HTMLDomElement}
  8857. */
  8858. this.dom = dom;
  8859. /**
  8860. * @type {string}
  8861. */
  8862. this.id = id;
  8863. var self = this;
  8864. var storage = new Storage();
  8865. var rendererType = opts.renderer;
  8866. // TODO WebGL
  8867. if (useVML) {
  8868. if (!painterCtors.vml) {
  8869. throw new Error('You need to require \'zrender/vml/vml\' to support IE8');
  8870. }
  8871. rendererType = 'vml';
  8872. }
  8873. else if (!rendererType || !painterCtors[rendererType]) {
  8874. rendererType = 'canvas';
  8875. }
  8876. var painter = new painterCtors[rendererType](dom, storage, opts, id);
  8877. this.storage = storage;
  8878. this.painter = painter;
  8879. var handerProxy = (!env$1.node && !env$1.worker) ? new HandlerDomProxy(painter.getViewportRoot()) : null;
  8880. this.handler = new Handler(storage, painter, handerProxy, painter.root);
  8881. /**
  8882. * @type {module:zrender/animation/Animation}
  8883. */
  8884. this.animation = new Animation({
  8885. stage: {
  8886. update: bind(this.flush, this)
  8887. }
  8888. });
  8889. this.animation.start();
  8890. /**
  8891. * @type {boolean}
  8892. * @private
  8893. */
  8894. this._needsRefresh;
  8895. // 修改 storage.delFromStorage, 每次删除元素之前删除动画
  8896. // FIXME 有点ugly
  8897. var oldDelFromStorage = storage.delFromStorage;
  8898. var oldAddToStorage = storage.addToStorage;
  8899. storage.delFromStorage = function (el) {
  8900. oldDelFromStorage.call(storage, el);
  8901. el && el.removeSelfFromZr(self);
  8902. };
  8903. storage.addToStorage = function (el) {
  8904. oldAddToStorage.call(storage, el);
  8905. el.addSelfToZr(self);
  8906. };
  8907. };
  8908. ZRender.prototype = {
  8909. constructor: ZRender,
  8910. /**
  8911. * 获取实例唯一标识
  8912. * @return {string}
  8913. */
  8914. getId: function () {
  8915. return this.id;
  8916. },
  8917. /**
  8918. * 添加元素
  8919. * @param {module:zrender/Element} el
  8920. */
  8921. add: function (el) {
  8922. this.storage.addRoot(el);
  8923. this._needsRefresh = true;
  8924. },
  8925. /**
  8926. * 删除元素
  8927. * @param {module:zrender/Element} el
  8928. */
  8929. remove: function (el) {
  8930. this.storage.delRoot(el);
  8931. this._needsRefresh = true;
  8932. },
  8933. /**
  8934. * Change configuration of layer
  8935. * @param {string} zLevel
  8936. * @param {Object} config
  8937. * @param {string} [config.clearColor=0] Clear color
  8938. * @param {string} [config.motionBlur=false] If enable motion blur
  8939. * @param {number} [config.lastFrameAlpha=0.7] Motion blur factor. Larger value cause longer trailer
  8940. */
  8941. configLayer: function (zLevel, config) {
  8942. if (this.painter.configLayer) {
  8943. this.painter.configLayer(zLevel, config);
  8944. }
  8945. this._needsRefresh = true;
  8946. },
  8947. /**
  8948. * Set background color
  8949. * @param {string} backgroundColor
  8950. */
  8951. setBackgroundColor: function (backgroundColor) {
  8952. if (this.painter.setBackgroundColor) {
  8953. this.painter.setBackgroundColor(backgroundColor);
  8954. }
  8955. this._needsRefresh = true;
  8956. },
  8957. /**
  8958. * Repaint the canvas immediately
  8959. */
  8960. refreshImmediately: function () {
  8961. // var start = new Date();
  8962. // Clear needsRefresh ahead to avoid something wrong happens in refresh
  8963. // Or it will cause zrender refreshes again and again.
  8964. this._needsRefresh = false;
  8965. this.painter.refresh();
  8966. /**
  8967. * Avoid trigger zr.refresh in Element#beforeUpdate hook
  8968. */
  8969. this._needsRefresh = false;
  8970. // var end = new Date();
  8971. // var log = document.getElementById('log');
  8972. // if (log) {
  8973. // log.innerHTML = log.innerHTML + '<br>' + (end - start);
  8974. // }
  8975. },
  8976. /**
  8977. * Mark and repaint the canvas in the next frame of browser
  8978. */
  8979. refresh: function() {
  8980. this._needsRefresh = true;
  8981. },
  8982. /**
  8983. * Perform all refresh
  8984. */
  8985. flush: function () {
  8986. var triggerRendered;
  8987. if (this._needsRefresh) {
  8988. triggerRendered = true;
  8989. this.refreshImmediately();
  8990. }
  8991. if (this._needsRefreshHover) {
  8992. triggerRendered = true;
  8993. this.refreshHoverImmediately();
  8994. }
  8995. triggerRendered && this.trigger('rendered');
  8996. },
  8997. /**
  8998. * Add element to hover layer
  8999. * @param {module:zrender/Element} el
  9000. * @param {Object} style
  9001. */
  9002. addHover: function (el, style) {
  9003. if (this.painter.addHover) {
  9004. this.painter.addHover(el, style);
  9005. this.refreshHover();
  9006. }
  9007. },
  9008. /**
  9009. * Add element from hover layer
  9010. * @param {module:zrender/Element} el
  9011. */
  9012. removeHover: function (el) {
  9013. if (this.painter.removeHover) {
  9014. this.painter.removeHover(el);
  9015. this.refreshHover();
  9016. }
  9017. },
  9018. /**
  9019. * Clear all hover elements in hover layer
  9020. * @param {module:zrender/Element} el
  9021. */
  9022. clearHover: function () {
  9023. if (this.painter.clearHover) {
  9024. this.painter.clearHover();
  9025. this.refreshHover();
  9026. }
  9027. },
  9028. /**
  9029. * Refresh hover in next frame
  9030. */
  9031. refreshHover: function () {
  9032. this._needsRefreshHover = true;
  9033. },
  9034. /**
  9035. * Refresh hover immediately
  9036. */
  9037. refreshHoverImmediately: function () {
  9038. this._needsRefreshHover = false;
  9039. this.painter.refreshHover && this.painter.refreshHover();
  9040. },
  9041. /**
  9042. * Resize the canvas.
  9043. * Should be invoked when container size is changed
  9044. * @param {Object} [opts]
  9045. * @param {number|string} [opts.width] Can be 'auto' (the same as null/undefined)
  9046. * @param {number|string} [opts.height] Can be 'auto' (the same as null/undefined)
  9047. */
  9048. resize: function(opts) {
  9049. opts = opts || {};
  9050. this.painter.resize(opts.width, opts.height);
  9051. this.handler.resize();
  9052. },
  9053. /**
  9054. * Stop and clear all animation immediately
  9055. */
  9056. clearAnimation: function () {
  9057. this.animation.clear();
  9058. },
  9059. /**
  9060. * Get container width
  9061. */
  9062. getWidth: function() {
  9063. return this.painter.getWidth();
  9064. },
  9065. /**
  9066. * Get container height
  9067. */
  9068. getHeight: function() {
  9069. return this.painter.getHeight();
  9070. },
  9071. /**
  9072. * Export the canvas as Base64 URL
  9073. * @param {string} type
  9074. * @param {string} [backgroundColor='#fff']
  9075. * @return {string} Base64 URL
  9076. */
  9077. // toDataURL: function(type, backgroundColor) {
  9078. // return this.painter.getRenderedCanvas({
  9079. // backgroundColor: backgroundColor
  9080. // }).toDataURL(type);
  9081. // },
  9082. /**
  9083. * Converting a path to image.
  9084. * It has much better performance of drawing image rather than drawing a vector path.
  9085. * @param {module:zrender/graphic/Path} e
  9086. * @param {number} width
  9087. * @param {number} height
  9088. */
  9089. pathToImage: function(e, dpr) {
  9090. return this.painter.pathToImage(e, dpr);
  9091. },
  9092. /**
  9093. * Set default cursor
  9094. * @param {string} [cursorStyle='default'] 例如 crosshair
  9095. */
  9096. setCursorStyle: function (cursorStyle) {
  9097. this.handler.setCursorStyle(cursorStyle);
  9098. },
  9099. /**
  9100. * Find hovered element
  9101. * @param {number} x
  9102. * @param {number} y
  9103. * @return {Object} {target, topTarget}
  9104. */
  9105. findHover: function (x, y) {
  9106. return this.handler.findHover(x, y);
  9107. },
  9108. /**
  9109. * Bind event
  9110. *
  9111. * @param {string} eventName Event name
  9112. * @param {Function} eventHandler Handler function
  9113. * @param {Object} [context] Context object
  9114. */
  9115. on: function(eventName, eventHandler, context) {
  9116. this.handler.on(eventName, eventHandler, context);
  9117. },
  9118. /**
  9119. * Unbind event
  9120. * @param {string} eventName Event name
  9121. * @param {Function} [eventHandler] Handler function
  9122. */
  9123. off: function(eventName, eventHandler) {
  9124. this.handler.off(eventName, eventHandler);
  9125. },
  9126. /**
  9127. * Trigger event manually
  9128. *
  9129. * @param {string} eventName Event name
  9130. * @param {event=} event Event object
  9131. */
  9132. trigger: function (eventName, event) {
  9133. this.handler.trigger(eventName, event);
  9134. },
  9135. /**
  9136. * Clear all objects and the canvas.
  9137. */
  9138. clear: function () {
  9139. this.storage.delRoot();
  9140. this.painter.clear();
  9141. },
  9142. /**
  9143. * Dispose self.
  9144. */
  9145. dispose: function () {
  9146. this.animation.stop();
  9147. this.clear();
  9148. this.storage.dispose();
  9149. this.painter.dispose();
  9150. this.handler.dispose();
  9151. this.animation =
  9152. this.storage =
  9153. this.painter =
  9154. this.handler = null;
  9155. }
  9156. };
  9157. var each$2 = each$1;
  9158. var isObject$2 = isObject$1;
  9159. var isArray$1 = isArray;
  9160. /**
  9161. * Make the name displayable. But we should
  9162. * make sure it is not duplicated with user
  9163. * specified name, so use '\0';
  9164. */
  9165. var DUMMY_COMPONENT_NAME_PREFIX = 'series\0';
  9166. /**
  9167. * If value is not array, then translate it to array.
  9168. * @param {*} value
  9169. * @return {Array} [value] or value
  9170. */
  9171. function normalizeToArray(value) {
  9172. return value instanceof Array
  9173. ? value
  9174. : value == null
  9175. ? []
  9176. : [value];
  9177. }
  9178. /**
  9179. * Sync default option between normal and emphasis like `position` and `show`
  9180. * In case some one will write code like
  9181. * label: {
  9182. * show: false,
  9183. * position: 'outside',
  9184. * fontSize: 18
  9185. * },
  9186. * emphasis: {
  9187. * label: { show: true }
  9188. * }
  9189. * @param {Object} opt
  9190. * @param {string} key
  9191. * @param {Array.<string>} subOpts
  9192. */
  9193. function defaultEmphasis(opt, key, subOpts) {
  9194. if (opt) {
  9195. opt[key] = opt[key] || {};
  9196. opt.emphasis = opt.emphasis || {};
  9197. opt.emphasis[key] = opt.emphasis[key] || {};
  9198. // Default emphasis option from normal
  9199. for (var i = 0, len = subOpts.length; i < len; i++) {
  9200. var subOptName = subOpts[i];
  9201. if (!opt.emphasis[key].hasOwnProperty(subOptName)
  9202. && opt[key].hasOwnProperty(subOptName)
  9203. ) {
  9204. opt.emphasis[key][subOptName] = opt[key][subOptName];
  9205. }
  9206. }
  9207. }
  9208. }
  9209. var TEXT_STYLE_OPTIONS = [
  9210. 'fontStyle', 'fontWeight', 'fontSize', 'fontFamily',
  9211. 'rich', 'tag', 'color', 'textBorderColor', 'textBorderWidth',
  9212. 'width', 'height', 'lineHeight', 'align', 'verticalAlign', 'baseline',
  9213. 'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY',
  9214. 'textShadowColor', 'textShadowBlur', 'textShadowOffsetX', 'textShadowOffsetY',
  9215. 'backgroundColor', 'borderColor', 'borderWidth', 'borderRadius', 'padding'
  9216. ];
  9217. // modelUtil.LABEL_OPTIONS = modelUtil.TEXT_STYLE_OPTIONS.concat([
  9218. // 'position', 'offset', 'rotate', 'origin', 'show', 'distance', 'formatter',
  9219. // 'fontStyle', 'fontWeight', 'fontSize', 'fontFamily',
  9220. // // FIXME: deprecated, check and remove it.
  9221. // 'textStyle'
  9222. // ]);
  9223. /**
  9224. * The method do not ensure performance.
  9225. * data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}]
  9226. * This helper method retieves value from data.
  9227. * @param {string|number|Date|Array|Object} dataItem
  9228. * @return {number|string|Date|Array.<number|string|Date>}
  9229. */
  9230. function getDataItemValue(dataItem) {
  9231. return (isObject$2(dataItem) && !isArray$1(dataItem) && !(dataItem instanceof Date))
  9232. ? dataItem.value : dataItem;
  9233. }
  9234. /**
  9235. * data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}]
  9236. * This helper method determine if dataItem has extra option besides value
  9237. * @param {string|number|Date|Array|Object} dataItem
  9238. */
  9239. function isDataItemOption(dataItem) {
  9240. return isObject$2(dataItem)
  9241. && !(dataItem instanceof Array);
  9242. // // markLine data can be array
  9243. // && !(dataItem[0] && isObject(dataItem[0]) && !(dataItem[0] instanceof Array));
  9244. }
  9245. /**
  9246. * Mapping to exists for merge.
  9247. *
  9248. * @public
  9249. * @param {Array.<Object>|Array.<module:echarts/model/Component>} exists
  9250. * @param {Object|Array.<Object>} newCptOptions
  9251. * @return {Array.<Object>} Result, like [{exist: ..., option: ...}, {}],
  9252. * index of which is the same as exists.
  9253. */
  9254. function mappingToExists(exists, newCptOptions) {
  9255. // Mapping by the order by original option (but not order of
  9256. // new option) in merge mode. Because we should ensure
  9257. // some specified index (like xAxisIndex) is consistent with
  9258. // original option, which is easy to understand, espatially in
  9259. // media query. And in most case, merge option is used to
  9260. // update partial option but not be expected to change order.
  9261. newCptOptions = (newCptOptions || []).slice();
  9262. var result = map(exists || [], function (obj, index) {
  9263. return {exist: obj};
  9264. });
  9265. // Mapping by id or name if specified.
  9266. each$2(newCptOptions, function (cptOption, index) {
  9267. if (!isObject$2(cptOption)) {
  9268. return;
  9269. }
  9270. // id has highest priority.
  9271. for (var i = 0; i < result.length; i++) {
  9272. if (!result[i].option // Consider name: two map to one.
  9273. && cptOption.id != null
  9274. && result[i].exist.id === cptOption.id + ''
  9275. ) {
  9276. result[i].option = cptOption;
  9277. newCptOptions[index] = null;
  9278. return;
  9279. }
  9280. }
  9281. for (var i = 0; i < result.length; i++) {
  9282. var exist = result[i].exist;
  9283. if (!result[i].option // Consider name: two map to one.
  9284. // Can not match when both ids exist but different.
  9285. && (exist.id == null || cptOption.id == null)
  9286. && cptOption.name != null
  9287. && !isIdInner(cptOption)
  9288. && !isIdInner(exist)
  9289. && exist.name === cptOption.name + ''
  9290. ) {
  9291. result[i].option = cptOption;
  9292. newCptOptions[index] = null;
  9293. return;
  9294. }
  9295. }
  9296. });
  9297. // Otherwise mapping by index.
  9298. each$2(newCptOptions, function (cptOption, index) {
  9299. if (!isObject$2(cptOption)) {
  9300. return;
  9301. }
  9302. var i = 0;
  9303. for (; i < result.length; i++) {
  9304. var exist = result[i].exist;
  9305. if (!result[i].option
  9306. // Existing model that already has id should be able to
  9307. // mapped to (because after mapping performed model may
  9308. // be assigned with a id, whish should not affect next
  9309. // mapping), except those has inner id.
  9310. && !isIdInner(exist)
  9311. // Caution:
  9312. // Do not overwrite id. But name can be overwritten,
  9313. // because axis use name as 'show label text'.
  9314. // 'exist' always has id and name and we dont
  9315. // need to check it.
  9316. && cptOption.id == null
  9317. ) {
  9318. result[i].option = cptOption;
  9319. break;
  9320. }
  9321. }
  9322. if (i >= result.length) {
  9323. result.push({option: cptOption});
  9324. }
  9325. });
  9326. return result;
  9327. }
  9328. /**
  9329. * Make id and name for mapping result (result of mappingToExists)
  9330. * into `keyInfo` field.
  9331. *
  9332. * @public
  9333. * @param {Array.<Object>} Result, like [{exist: ..., option: ...}, {}],
  9334. * which order is the same as exists.
  9335. * @return {Array.<Object>} The input.
  9336. */
  9337. function makeIdAndName(mapResult) {
  9338. // We use this id to hash component models and view instances
  9339. // in echarts. id can be specified by user, or auto generated.
  9340. // The id generation rule ensures new view instance are able
  9341. // to mapped to old instance when setOption are called in
  9342. // no-merge mode. So we generate model id by name and plus
  9343. // type in view id.
  9344. // name can be duplicated among components, which is convenient
  9345. // to specify multi components (like series) by one name.
  9346. // Ensure that each id is distinct.
  9347. var idMap = createHashMap();
  9348. each$2(mapResult, function (item, index) {
  9349. var existCpt = item.exist;
  9350. existCpt && idMap.set(existCpt.id, item);
  9351. });
  9352. each$2(mapResult, function (item, index) {
  9353. var opt = item.option;
  9354. assert$1(
  9355. !opt || opt.id == null || !idMap.get(opt.id) || idMap.get(opt.id) === item,
  9356. 'id duplicates: ' + (opt && opt.id)
  9357. );
  9358. opt && opt.id != null && idMap.set(opt.id, item);
  9359. !item.keyInfo && (item.keyInfo = {});
  9360. });
  9361. // Make name and id.
  9362. each$2(mapResult, function (item, index) {
  9363. var existCpt = item.exist;
  9364. var opt = item.option;
  9365. var keyInfo = item.keyInfo;
  9366. if (!isObject$2(opt)) {
  9367. return;
  9368. }
  9369. // name can be overwitten. Consider case: axis.name = '20km'.
  9370. // But id generated by name will not be changed, which affect
  9371. // only in that case: setOption with 'not merge mode' and view
  9372. // instance will be recreated, which can be accepted.
  9373. keyInfo.name = opt.name != null
  9374. ? opt.name + ''
  9375. : existCpt
  9376. ? existCpt.name
  9377. // Avoid diffferent series has the same name,
  9378. // because name may be used like in color pallet.
  9379. : DUMMY_COMPONENT_NAME_PREFIX + index;
  9380. if (existCpt) {
  9381. keyInfo.id = existCpt.id;
  9382. }
  9383. else if (opt.id != null) {
  9384. keyInfo.id = opt.id + '';
  9385. }
  9386. else {
  9387. // Consider this situatoin:
  9388. // optionA: [{name: 'a'}, {name: 'a'}, {..}]
  9389. // optionB [{..}, {name: 'a'}, {name: 'a'}]
  9390. // Series with the same name between optionA and optionB
  9391. // should be mapped.
  9392. var idNum = 0;
  9393. do {
  9394. keyInfo.id = '\0' + keyInfo.name + '\0' + idNum++;
  9395. }
  9396. while (idMap.get(keyInfo.id));
  9397. }
  9398. idMap.set(keyInfo.id, item);
  9399. });
  9400. }
  9401. function isNameSpecified(componentModel) {
  9402. var name = componentModel.name;
  9403. // Is specified when `indexOf` get -1 or > 0.
  9404. return !!(name && name.indexOf(DUMMY_COMPONENT_NAME_PREFIX));
  9405. }
  9406. /**
  9407. * @public
  9408. * @param {Object} cptOption
  9409. * @return {boolean}
  9410. */
  9411. function isIdInner(cptOption) {
  9412. return isObject$2(cptOption)
  9413. && cptOption.id
  9414. && (cptOption.id + '').indexOf('\0_ec_\0') === 0;
  9415. }
  9416. /**
  9417. * A helper for removing duplicate items between batchA and batchB,
  9418. * and in themselves, and categorize by series.
  9419. *
  9420. * @param {Array.<Object>} batchA Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...]
  9421. * @param {Array.<Object>} batchB Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...]
  9422. * @return {Array.<Array.<Object>, Array.<Object>>} result: [resultBatchA, resultBatchB]
  9423. */
  9424. /**
  9425. * @param {module:echarts/data/List} data
  9426. * @param {Object} payload Contains dataIndex (means rawIndex) / dataIndexInside / name
  9427. * each of which can be Array or primary type.
  9428. * @return {number|Array.<number>} dataIndex If not found, return undefined/null.
  9429. */
  9430. function queryDataIndex(data, payload) {
  9431. if (payload.dataIndexInside != null) {
  9432. return payload.dataIndexInside;
  9433. }
  9434. else if (payload.dataIndex != null) {
  9435. return isArray(payload.dataIndex)
  9436. ? map(payload.dataIndex, function (value) {
  9437. return data.indexOfRawIndex(value);
  9438. })
  9439. : data.indexOfRawIndex(payload.dataIndex);
  9440. }
  9441. else if (payload.name != null) {
  9442. return isArray(payload.name)
  9443. ? map(payload.name, function (value) {
  9444. return data.indexOfName(value);
  9445. })
  9446. : data.indexOfName(payload.name);
  9447. }
  9448. }
  9449. /**
  9450. * Enable property storage to any host object.
  9451. * Notice: Serialization is not supported.
  9452. *
  9453. * For example:
  9454. * var inner = zrUitl.makeInner();
  9455. *
  9456. * function some1(hostObj) {
  9457. * inner(hostObj).someProperty = 1212;
  9458. * ...
  9459. * }
  9460. * function some2() {
  9461. * var fields = inner(this);
  9462. * fields.someProperty1 = 1212;
  9463. * fields.someProperty2 = 'xx';
  9464. * ...
  9465. * }
  9466. *
  9467. * @return {Function}
  9468. */
  9469. function makeInner() {
  9470. // Consider different scope by es module import.
  9471. var key = '__\0ec_inner_' + innerUniqueIndex++ + '_' + Math.random().toFixed(5);
  9472. return function (hostObj) {
  9473. return hostObj[key] || (hostObj[key] = {});
  9474. };
  9475. }
  9476. var innerUniqueIndex = 0;
  9477. /**
  9478. * @param {module:echarts/model/Global} ecModel
  9479. * @param {string|Object} finder
  9480. * If string, e.g., 'geo', means {geoIndex: 0}.
  9481. * If Object, could contain some of these properties below:
  9482. * {
  9483. * seriesIndex, seriesId, seriesName,
  9484. * geoIndex, geoId, geoName,
  9485. * bmapIndex, bmapId, bmapName,
  9486. * xAxisIndex, xAxisId, xAxisName,
  9487. * yAxisIndex, yAxisId, yAxisName,
  9488. * gridIndex, gridId, gridName,
  9489. * ... (can be extended)
  9490. * }
  9491. * Each properties can be number|string|Array.<number>|Array.<string>
  9492. * For example, a finder could be
  9493. * {
  9494. * seriesIndex: 3,
  9495. * geoId: ['aa', 'cc'],
  9496. * gridName: ['xx', 'rr']
  9497. * }
  9498. * xxxIndex can be set as 'all' (means all xxx) or 'none' (means not specify)
  9499. * If nothing or null/undefined specified, return nothing.
  9500. * @param {Object} [opt]
  9501. * @param {string} [opt.defaultMainType]
  9502. * @param {Array.<string>} [opt.includeMainTypes]
  9503. * @return {Object} result like:
  9504. * {
  9505. * seriesModels: [seriesModel1, seriesModel2],
  9506. * seriesModel: seriesModel1, // The first model
  9507. * geoModels: [geoModel1, geoModel2],
  9508. * geoModel: geoModel1, // The first model
  9509. * ...
  9510. * }
  9511. */
  9512. function parseFinder(ecModel, finder, opt) {
  9513. if (isString(finder)) {
  9514. var obj = {};
  9515. obj[finder + 'Index'] = 0;
  9516. finder = obj;
  9517. }
  9518. var defaultMainType = opt && opt.defaultMainType;
  9519. if (defaultMainType
  9520. && !has(finder, defaultMainType + 'Index')
  9521. && !has(finder, defaultMainType + 'Id')
  9522. && !has(finder, defaultMainType + 'Name')
  9523. ) {
  9524. finder[defaultMainType + 'Index'] = 0;
  9525. }
  9526. var result = {};
  9527. each$2(finder, function (value, key) {
  9528. var value = finder[key];
  9529. // Exclude 'dataIndex' and other illgal keys.
  9530. if (key === 'dataIndex' || key === 'dataIndexInside') {
  9531. result[key] = value;
  9532. return;
  9533. }
  9534. var parsedKey = key.match(/^(\w+)(Index|Id|Name)$/) || [];
  9535. var mainType = parsedKey[1];
  9536. var queryType = (parsedKey[2] || '').toLowerCase();
  9537. if (!mainType
  9538. || !queryType
  9539. || value == null
  9540. || (queryType === 'index' && value === 'none')
  9541. || (opt && opt.includeMainTypes && indexOf(opt.includeMainTypes, mainType) < 0)
  9542. ) {
  9543. return;
  9544. }
  9545. var queryParam = {mainType: mainType};
  9546. if (queryType !== 'index' || value !== 'all') {
  9547. queryParam[queryType] = value;
  9548. }
  9549. var models = ecModel.queryComponents(queryParam);
  9550. result[mainType + 'Models'] = models;
  9551. result[mainType + 'Model'] = models[0];
  9552. });
  9553. return result;
  9554. }
  9555. function has(obj, prop) {
  9556. return obj && obj.hasOwnProperty(prop);
  9557. }
  9558. function setAttribute(dom, key, value) {
  9559. dom.setAttribute
  9560. ? dom.setAttribute(key, value)
  9561. : (dom[key] = value);
  9562. }
  9563. function getAttribute(dom, key) {
  9564. return dom.getAttribute
  9565. ? dom.getAttribute(key)
  9566. : dom[key];
  9567. }
  9568. var TYPE_DELIMITER = '.';
  9569. var IS_CONTAINER = '___EC__COMPONENT__CONTAINER___';
  9570. /**
  9571. * Notice, parseClassType('') should returns {main: '', sub: ''}
  9572. * @public
  9573. */
  9574. function parseClassType$1(componentType) {
  9575. var ret = {main: '', sub: ''};
  9576. if (componentType) {
  9577. componentType = componentType.split(TYPE_DELIMITER);
  9578. ret.main = componentType[0] || '';
  9579. ret.sub = componentType[1] || '';
  9580. }
  9581. return ret;
  9582. }
  9583. /**
  9584. * @public
  9585. */
  9586. function checkClassType(componentType) {
  9587. assert$1(
  9588. /^[a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)?$/.test(componentType),
  9589. 'componentType "' + componentType + '" illegal'
  9590. );
  9591. }
  9592. /**
  9593. * @public
  9594. */
  9595. function enableClassExtend(RootClass, mandatoryMethods) {
  9596. RootClass.$constructor = RootClass;
  9597. RootClass.extend = function (proto) {
  9598. if (__DEV__) {
  9599. each$1(mandatoryMethods, function (method) {
  9600. if (!proto[method]) {
  9601. console.warn(
  9602. 'Method `' + method + '` should be implemented'
  9603. + (proto.type ? ' in ' + proto.type : '') + '.'
  9604. );
  9605. }
  9606. });
  9607. }
  9608. var superClass = this;
  9609. var ExtendedClass = function () {
  9610. if (!proto.$constructor) {
  9611. superClass.apply(this, arguments);
  9612. }
  9613. else {
  9614. proto.$constructor.apply(this, arguments);
  9615. }
  9616. };
  9617. extend(ExtendedClass.prototype, proto);
  9618. ExtendedClass.extend = this.extend;
  9619. ExtendedClass.superCall = superCall;
  9620. ExtendedClass.superApply = superApply;
  9621. inherits(ExtendedClass, this);
  9622. ExtendedClass.superClass = superClass;
  9623. return ExtendedClass;
  9624. };
  9625. }
  9626. var classBase = 0;
  9627. /**
  9628. * Can not use instanceof, consider different scope by
  9629. * cross domain or es module import in ec extensions.
  9630. * Mount a method "isInstance()" to Clz.
  9631. */
  9632. function enableClassCheck(Clz) {
  9633. var classAttr = ['__\0is_clz', classBase++, Math.random().toFixed(3)].join('_');
  9634. Clz.prototype[classAttr] = true;
  9635. if (__DEV__) {
  9636. assert$1(!Clz.isInstance, 'The method "is" can not be defined.');
  9637. }
  9638. Clz.isInstance = function (obj) {
  9639. return !!(obj && obj[classAttr]);
  9640. };
  9641. }
  9642. // superCall should have class info, which can not be fetch from 'this'.
  9643. // Consider this case:
  9644. // class A has method f,
  9645. // class B inherits class A, overrides method f, f call superApply('f'),
  9646. // class C inherits class B, do not overrides method f,
  9647. // then when method of class C is called, dead loop occured.
  9648. function superCall(context, methodName) {
  9649. var args = slice(arguments, 2);
  9650. return this.superClass.prototype[methodName].apply(context, args);
  9651. }
  9652. function superApply(context, methodName, args) {
  9653. return this.superClass.prototype[methodName].apply(context, args);
  9654. }
  9655. /**
  9656. * @param {Object} entity
  9657. * @param {Object} options
  9658. * @param {boolean} [options.registerWhenExtend]
  9659. * @public
  9660. */
  9661. function enableClassManagement(entity, options) {
  9662. options = options || {};
  9663. /**
  9664. * Component model classes
  9665. * key: componentType,
  9666. * value:
  9667. * componentClass, when componentType is 'xxx'
  9668. * or Object.<subKey, componentClass>, when componentType is 'xxx.yy'
  9669. * @type {Object}
  9670. */
  9671. var storage = {};
  9672. entity.registerClass = function (Clazz, componentType) {
  9673. if (componentType) {
  9674. checkClassType(componentType);
  9675. componentType = parseClassType$1(componentType);
  9676. if (!componentType.sub) {
  9677. if (__DEV__) {
  9678. if (storage[componentType.main]) {
  9679. console.warn(componentType.main + ' exists.');
  9680. }
  9681. }
  9682. storage[componentType.main] = Clazz;
  9683. }
  9684. else if (componentType.sub !== IS_CONTAINER) {
  9685. var container = makeContainer(componentType);
  9686. container[componentType.sub] = Clazz;
  9687. }
  9688. }
  9689. return Clazz;
  9690. };
  9691. entity.getClass = function (componentMainType, subType, throwWhenNotFound) {
  9692. var Clazz = storage[componentMainType];
  9693. if (Clazz && Clazz[IS_CONTAINER]) {
  9694. Clazz = subType ? Clazz[subType] : null;
  9695. }
  9696. if (throwWhenNotFound && !Clazz) {
  9697. throw new Error(
  9698. !subType
  9699. ? componentMainType + '.' + 'type should be specified.'
  9700. : 'Component ' + componentMainType + '.' + (subType || '') + ' not exists. Load it first.'
  9701. );
  9702. }
  9703. return Clazz;
  9704. };
  9705. entity.getClassesByMainType = function (componentType) {
  9706. componentType = parseClassType$1(componentType);
  9707. var result = [];
  9708. var obj = storage[componentType.main];
  9709. if (obj && obj[IS_CONTAINER]) {
  9710. each$1(obj, function (o, type) {
  9711. type !== IS_CONTAINER && result.push(o);
  9712. });
  9713. }
  9714. else {
  9715. result.push(obj);
  9716. }
  9717. return result;
  9718. };
  9719. entity.hasClass = function (componentType) {
  9720. // Just consider componentType.main.
  9721. componentType = parseClassType$1(componentType);
  9722. return !!storage[componentType.main];
  9723. };
  9724. /**
  9725. * @return {Array.<string>} Like ['aa', 'bb'], but can not be ['aa.xx']
  9726. */
  9727. entity.getAllClassMainTypes = function () {
  9728. var types = [];
  9729. each$1(storage, function (obj, type) {
  9730. types.push(type);
  9731. });
  9732. return types;
  9733. };
  9734. /**
  9735. * If a main type is container and has sub types
  9736. * @param {string} mainType
  9737. * @return {boolean}
  9738. */
  9739. entity.hasSubTypes = function (componentType) {
  9740. componentType = parseClassType$1(componentType);
  9741. var obj = storage[componentType.main];
  9742. return obj && obj[IS_CONTAINER];
  9743. };
  9744. entity.parseClassType = parseClassType$1;
  9745. function makeContainer(componentType) {
  9746. var container = storage[componentType.main];
  9747. if (!container || !container[IS_CONTAINER]) {
  9748. container = storage[componentType.main] = {};
  9749. container[IS_CONTAINER] = true;
  9750. }
  9751. return container;
  9752. }
  9753. if (options.registerWhenExtend) {
  9754. var originalExtend = entity.extend;
  9755. if (originalExtend) {
  9756. entity.extend = function (proto) {
  9757. var ExtendedClass = originalExtend.call(this, proto);
  9758. return entity.registerClass(ExtendedClass, proto.type);
  9759. };
  9760. }
  9761. }
  9762. return entity;
  9763. }
  9764. /**
  9765. * @param {string|Array.<string>} properties
  9766. */
  9767. // TODO Parse shadow style
  9768. // TODO Only shallow path support
  9769. var makeStyleMapper = function (properties) {
  9770. // Normalize
  9771. for (var i = 0; i < properties.length; i++) {
  9772. if (!properties[i][1]) {
  9773. properties[i][1] = properties[i][0];
  9774. }
  9775. }
  9776. return function (model, excludes, includes) {
  9777. var style = {};
  9778. for (var i = 0; i < properties.length; i++) {
  9779. var propName = properties[i][1];
  9780. if ((excludes && indexOf(excludes, propName) >= 0)
  9781. || (includes && indexOf(includes, propName) < 0)
  9782. ) {
  9783. continue;
  9784. }
  9785. var val = model.getShallow(propName);
  9786. if (val != null) {
  9787. style[properties[i][0]] = val;
  9788. }
  9789. }
  9790. return style;
  9791. };
  9792. };
  9793. var getLineStyle = makeStyleMapper(
  9794. [
  9795. ['lineWidth', 'width'],
  9796. ['stroke', 'color'],
  9797. ['opacity'],
  9798. ['shadowBlur'],
  9799. ['shadowOffsetX'],
  9800. ['shadowOffsetY'],
  9801. ['shadowColor']
  9802. ]
  9803. );
  9804. var lineStyleMixin = {
  9805. getLineStyle: function (excludes) {
  9806. var style = getLineStyle(this, excludes);
  9807. var lineDash = this.getLineDash(style.lineWidth);
  9808. lineDash && (style.lineDash = lineDash);
  9809. return style;
  9810. },
  9811. getLineDash: function (lineWidth) {
  9812. if (lineWidth == null) {
  9813. lineWidth = 1;
  9814. }
  9815. var lineType = this.get('type');
  9816. var dotSize = Math.max(lineWidth, 2);
  9817. var dashSize = lineWidth * 4;
  9818. return (lineType === 'solid' || lineType == null) ? null
  9819. : (lineType === 'dashed' ? [dashSize, dashSize] : [dotSize, dotSize]);
  9820. }
  9821. };
  9822. var getAreaStyle = makeStyleMapper(
  9823. [
  9824. ['fill', 'color'],
  9825. ['shadowBlur'],
  9826. ['shadowOffsetX'],
  9827. ['shadowOffsetY'],
  9828. ['opacity'],
  9829. ['shadowColor']
  9830. ]
  9831. );
  9832. var areaStyleMixin = {
  9833. getAreaStyle: function (excludes, includes) {
  9834. return getAreaStyle(this, excludes, includes);
  9835. }
  9836. };
  9837. /**
  9838. * 曲线辅助模块
  9839. * @module zrender/core/curve
  9840. * @author pissang(https://www.github.com/pissang)
  9841. */
  9842. var mathPow = Math.pow;
  9843. var mathSqrt$2 = Math.sqrt;
  9844. var EPSILON$1 = 1e-8;
  9845. var EPSILON_NUMERIC = 1e-4;
  9846. var THREE_SQRT = mathSqrt$2(3);
  9847. var ONE_THIRD = 1 / 3;
  9848. // 临时变量
  9849. var _v0 = create();
  9850. var _v1 = create();
  9851. var _v2 = create();
  9852. function isAroundZero(val) {
  9853. return val > -EPSILON$1 && val < EPSILON$1;
  9854. }
  9855. function isNotAroundZero$1(val) {
  9856. return val > EPSILON$1 || val < -EPSILON$1;
  9857. }
  9858. /**
  9859. * 计算三次贝塞尔值
  9860. * @memberOf module:zrender/core/curve
  9861. * @param {number} p0
  9862. * @param {number} p1
  9863. * @param {number} p2
  9864. * @param {number} p3
  9865. * @param {number} t
  9866. * @return {number}
  9867. */
  9868. function cubicAt(p0, p1, p2, p3, t) {
  9869. var onet = 1 - t;
  9870. return onet * onet * (onet * p0 + 3 * t * p1)
  9871. + t * t * (t * p3 + 3 * onet * p2);
  9872. }
  9873. /**
  9874. * 计算三次贝塞尔导数值
  9875. * @memberOf module:zrender/core/curve
  9876. * @param {number} p0
  9877. * @param {number} p1
  9878. * @param {number} p2
  9879. * @param {number} p3
  9880. * @param {number} t
  9881. * @return {number}
  9882. */
  9883. function cubicDerivativeAt(p0, p1, p2, p3, t) {
  9884. var onet = 1 - t;
  9885. return 3 * (
  9886. ((p1 - p0) * onet + 2 * (p2 - p1) * t) * onet
  9887. + (p3 - p2) * t * t
  9888. );
  9889. }
  9890. /**
  9891. * 计算三次贝塞尔方程根,使用盛金公式
  9892. * @memberOf module:zrender/core/curve
  9893. * @param {number} p0
  9894. * @param {number} p1
  9895. * @param {number} p2
  9896. * @param {number} p3
  9897. * @param {number} val
  9898. * @param {Array.<number>} roots
  9899. * @return {number} 有效根数目
  9900. */
  9901. function cubicRootAt(p0, p1, p2, p3, val, roots) {
  9902. // Evaluate roots of cubic functions
  9903. var a = p3 + 3 * (p1 - p2) - p0;
  9904. var b = 3 * (p2 - p1 * 2 + p0);
  9905. var c = 3 * (p1 - p0);
  9906. var d = p0 - val;
  9907. var A = b * b - 3 * a * c;
  9908. var B = b * c - 9 * a * d;
  9909. var C = c * c - 3 * b * d;
  9910. var n = 0;
  9911. if (isAroundZero(A) && isAroundZero(B)) {
  9912. if (isAroundZero(b)) {
  9913. roots[0] = 0;
  9914. }
  9915. else {
  9916. var t1 = -c / b; //t1, t2, t3, b is not zero
  9917. if (t1 >= 0 && t1 <= 1) {
  9918. roots[n++] = t1;
  9919. }
  9920. }
  9921. }
  9922. else {
  9923. var disc = B * B - 4 * A * C;
  9924. if (isAroundZero(disc)) {
  9925. var K = B / A;
  9926. var t1 = -b / a + K; // t1, a is not zero
  9927. var t2 = -K / 2; // t2, t3
  9928. if (t1 >= 0 && t1 <= 1) {
  9929. roots[n++] = t1;
  9930. }
  9931. if (t2 >= 0 && t2 <= 1) {
  9932. roots[n++] = t2;
  9933. }
  9934. }
  9935. else if (disc > 0) {
  9936. var discSqrt = mathSqrt$2(disc);
  9937. var Y1 = A * b + 1.5 * a * (-B + discSqrt);
  9938. var Y2 = A * b + 1.5 * a * (-B - discSqrt);
  9939. if (Y1 < 0) {
  9940. Y1 = -mathPow(-Y1, ONE_THIRD);
  9941. }
  9942. else {
  9943. Y1 = mathPow(Y1, ONE_THIRD);
  9944. }
  9945. if (Y2 < 0) {
  9946. Y2 = -mathPow(-Y2, ONE_THIRD);
  9947. }
  9948. else {
  9949. Y2 = mathPow(Y2, ONE_THIRD);
  9950. }
  9951. var t1 = (-b - (Y1 + Y2)) / (3 * a);
  9952. if (t1 >= 0 && t1 <= 1) {
  9953. roots[n++] = t1;
  9954. }
  9955. }
  9956. else {
  9957. var T = (2 * A * b - 3 * a * B) / (2 * mathSqrt$2(A * A * A));
  9958. var theta = Math.acos(T) / 3;
  9959. var ASqrt = mathSqrt$2(A);
  9960. var tmp = Math.cos(theta);
  9961. var t1 = (-b - 2 * ASqrt * tmp) / (3 * a);
  9962. var t2 = (-b + ASqrt * (tmp + THREE_SQRT * Math.sin(theta))) / (3 * a);
  9963. var t3 = (-b + ASqrt * (tmp - THREE_SQRT * Math.sin(theta))) / (3 * a);
  9964. if (t1 >= 0 && t1 <= 1) {
  9965. roots[n++] = t1;
  9966. }
  9967. if (t2 >= 0 && t2 <= 1) {
  9968. roots[n++] = t2;
  9969. }
  9970. if (t3 >= 0 && t3 <= 1) {
  9971. roots[n++] = t3;
  9972. }
  9973. }
  9974. }
  9975. return n;
  9976. }
  9977. /**
  9978. * 计算三次贝塞尔方程极限值的位置
  9979. * @memberOf module:zrender/core/curve
  9980. * @param {number} p0
  9981. * @param {number} p1
  9982. * @param {number} p2
  9983. * @param {number} p3
  9984. * @param {Array.<number>} extrema
  9985. * @return {number} 有效数目
  9986. */
  9987. function cubicExtrema(p0, p1, p2, p3, extrema) {
  9988. var b = 6 * p2 - 12 * p1 + 6 * p0;
  9989. var a = 9 * p1 + 3 * p3 - 3 * p0 - 9 * p2;
  9990. var c = 3 * p1 - 3 * p0;
  9991. var n = 0;
  9992. if (isAroundZero(a)) {
  9993. if (isNotAroundZero$1(b)) {
  9994. var t1 = -c / b;
  9995. if (t1 >= 0 && t1 <=1) {
  9996. extrema[n++] = t1;
  9997. }
  9998. }
  9999. }
  10000. else {
  10001. var disc = b * b - 4 * a * c;
  10002. if (isAroundZero(disc)) {
  10003. extrema[0] = -b / (2 * a);
  10004. }
  10005. else if (disc > 0) {
  10006. var discSqrt = mathSqrt$2(disc);
  10007. var t1 = (-b + discSqrt) / (2 * a);
  10008. var t2 = (-b - discSqrt) / (2 * a);
  10009. if (t1 >= 0 && t1 <= 1) {
  10010. extrema[n++] = t1;
  10011. }
  10012. if (t2 >= 0 && t2 <= 1) {
  10013. extrema[n++] = t2;
  10014. }
  10015. }
  10016. }
  10017. return n;
  10018. }
  10019. /**
  10020. * 细分三次贝塞尔曲线
  10021. * @memberOf module:zrender/core/curve
  10022. * @param {number} p0
  10023. * @param {number} p1
  10024. * @param {number} p2
  10025. * @param {number} p3
  10026. * @param {number} t
  10027. * @param {Array.<number>} out
  10028. */
  10029. function cubicSubdivide(p0, p1, p2, p3, t, out) {
  10030. var p01 = (p1 - p0) * t + p0;
  10031. var p12 = (p2 - p1) * t + p1;
  10032. var p23 = (p3 - p2) * t + p2;
  10033. var p012 = (p12 - p01) * t + p01;
  10034. var p123 = (p23 - p12) * t + p12;
  10035. var p0123 = (p123 - p012) * t + p012;
  10036. // Seg0
  10037. out[0] = p0;
  10038. out[1] = p01;
  10039. out[2] = p012;
  10040. out[3] = p0123;
  10041. // Seg1
  10042. out[4] = p0123;
  10043. out[5] = p123;
  10044. out[6] = p23;
  10045. out[7] = p3;
  10046. }
  10047. /**
  10048. * 投射点到三次贝塞尔曲线上,返回投射距离。
  10049. * 投射点有可能会有一个或者多个,这里只返回其中距离最短的一个。
  10050. * @param {number} x0
  10051. * @param {number} y0
  10052. * @param {number} x1
  10053. * @param {number} y1
  10054. * @param {number} x2
  10055. * @param {number} y2
  10056. * @param {number} x3
  10057. * @param {number} y3
  10058. * @param {number} x
  10059. * @param {number} y
  10060. * @param {Array.<number>} [out] 投射点
  10061. * @return {number}
  10062. */
  10063. function cubicProjectPoint(
  10064. x0, y0, x1, y1, x2, y2, x3, y3,
  10065. x, y, out
  10066. ) {
  10067. // http://pomax.github.io/bezierinfo/#projections
  10068. var t;
  10069. var interval = 0.005;
  10070. var d = Infinity;
  10071. var prev;
  10072. var next;
  10073. var d1;
  10074. var d2;
  10075. _v0[0] = x;
  10076. _v0[1] = y;
  10077. // 先粗略估计一下可能的最小距离的 t 值
  10078. // PENDING
  10079. for (var _t = 0; _t < 1; _t += 0.05) {
  10080. _v1[0] = cubicAt(x0, x1, x2, x3, _t);
  10081. _v1[1] = cubicAt(y0, y1, y2, y3, _t);
  10082. d1 = distSquare(_v0, _v1);
  10083. if (d1 < d) {
  10084. t = _t;
  10085. d = d1;
  10086. }
  10087. }
  10088. d = Infinity;
  10089. // At most 32 iteration
  10090. for (var i = 0; i < 32; i++) {
  10091. if (interval < EPSILON_NUMERIC) {
  10092. break;
  10093. }
  10094. prev = t - interval;
  10095. next = t + interval;
  10096. // t - interval
  10097. _v1[0] = cubicAt(x0, x1, x2, x3, prev);
  10098. _v1[1] = cubicAt(y0, y1, y2, y3, prev);
  10099. d1 = distSquare(_v1, _v0);
  10100. if (prev >= 0 && d1 < d) {
  10101. t = prev;
  10102. d = d1;
  10103. }
  10104. else {
  10105. // t + interval
  10106. _v2[0] = cubicAt(x0, x1, x2, x3, next);
  10107. _v2[1] = cubicAt(y0, y1, y2, y3, next);
  10108. d2 = distSquare(_v2, _v0);
  10109. if (next <= 1 && d2 < d) {
  10110. t = next;
  10111. d = d2;
  10112. }
  10113. else {
  10114. interval *= 0.5;
  10115. }
  10116. }
  10117. }
  10118. // t
  10119. if (out) {
  10120. out[0] = cubicAt(x0, x1, x2, x3, t);
  10121. out[1] = cubicAt(y0, y1, y2, y3, t);
  10122. }
  10123. // console.log(interval, i);
  10124. return mathSqrt$2(d);
  10125. }
  10126. /**
  10127. * 计算二次方贝塞尔值
  10128. * @param {number} p0
  10129. * @param {number} p1
  10130. * @param {number} p2
  10131. * @param {number} t
  10132. * @return {number}
  10133. */
  10134. function quadraticAt(p0, p1, p2, t) {
  10135. var onet = 1 - t;
  10136. return onet * (onet * p0 + 2 * t * p1) + t * t * p2;
  10137. }
  10138. /**
  10139. * 计算二次方贝塞尔导数值
  10140. * @param {number} p0
  10141. * @param {number} p1
  10142. * @param {number} p2
  10143. * @param {number} t
  10144. * @return {number}
  10145. */
  10146. function quadraticDerivativeAt(p0, p1, p2, t) {
  10147. return 2 * ((1 - t) * (p1 - p0) + t * (p2 - p1));
  10148. }
  10149. /**
  10150. * 计算二次方贝塞尔方程根
  10151. * @param {number} p0
  10152. * @param {number} p1
  10153. * @param {number} p2
  10154. * @param {number} t
  10155. * @param {Array.<number>} roots
  10156. * @return {number} 有效根数目
  10157. */
  10158. function quadraticRootAt(p0, p1, p2, val, roots) {
  10159. var a = p0 - 2 * p1 + p2;
  10160. var b = 2 * (p1 - p0);
  10161. var c = p0 - val;
  10162. var n = 0;
  10163. if (isAroundZero(a)) {
  10164. if (isNotAroundZero$1(b)) {
  10165. var t1 = -c / b;
  10166. if (t1 >= 0 && t1 <= 1) {
  10167. roots[n++] = t1;
  10168. }
  10169. }
  10170. }
  10171. else {
  10172. var disc = b * b - 4 * a * c;
  10173. if (isAroundZero(disc)) {
  10174. var t1 = -b / (2 * a);
  10175. if (t1 >= 0 && t1 <= 1) {
  10176. roots[n++] = t1;
  10177. }
  10178. }
  10179. else if (disc > 0) {
  10180. var discSqrt = mathSqrt$2(disc);
  10181. var t1 = (-b + discSqrt) / (2 * a);
  10182. var t2 = (-b - discSqrt) / (2 * a);
  10183. if (t1 >= 0 && t1 <= 1) {
  10184. roots[n++] = t1;
  10185. }
  10186. if (t2 >= 0 && t2 <= 1) {
  10187. roots[n++] = t2;
  10188. }
  10189. }
  10190. }
  10191. return n;
  10192. }
  10193. /**
  10194. * 计算二次贝塞尔方程极限值
  10195. * @memberOf module:zrender/core/curve
  10196. * @param {number} p0
  10197. * @param {number} p1
  10198. * @param {number} p2
  10199. * @return {number}
  10200. */
  10201. function quadraticExtremum(p0, p1, p2) {
  10202. var divider = p0 + p2 - 2 * p1;
  10203. if (divider === 0) {
  10204. // p1 is center of p0 and p2
  10205. return 0.5;
  10206. }
  10207. else {
  10208. return (p0 - p1) / divider;
  10209. }
  10210. }
  10211. /**
  10212. * 细分二次贝塞尔曲线
  10213. * @memberOf module:zrender/core/curve
  10214. * @param {number} p0
  10215. * @param {number} p1
  10216. * @param {number} p2
  10217. * @param {number} t
  10218. * @param {Array.<number>} out
  10219. */
  10220. function quadraticSubdivide(p0, p1, p2, t, out) {
  10221. var p01 = (p1 - p0) * t + p0;
  10222. var p12 = (p2 - p1) * t + p1;
  10223. var p012 = (p12 - p01) * t + p01;
  10224. // Seg0
  10225. out[0] = p0;
  10226. out[1] = p01;
  10227. out[2] = p012;
  10228. // Seg1
  10229. out[3] = p012;
  10230. out[4] = p12;
  10231. out[5] = p2;
  10232. }
  10233. /**
  10234. * 投射点到二次贝塞尔曲线上,返回投射距离。
  10235. * 投射点有可能会有一个或者多个,这里只返回其中距离最短的一个。
  10236. * @param {number} x0
  10237. * @param {number} y0
  10238. * @param {number} x1
  10239. * @param {number} y1
  10240. * @param {number} x2
  10241. * @param {number} y2
  10242. * @param {number} x
  10243. * @param {number} y
  10244. * @param {Array.<number>} out 投射点
  10245. * @return {number}
  10246. */
  10247. function quadraticProjectPoint(
  10248. x0, y0, x1, y1, x2, y2,
  10249. x, y, out
  10250. ) {
  10251. // http://pomax.github.io/bezierinfo/#projections
  10252. var t;
  10253. var interval = 0.005;
  10254. var d = Infinity;
  10255. _v0[0] = x;
  10256. _v0[1] = y;
  10257. // 先粗略估计一下可能的最小距离的 t 值
  10258. // PENDING
  10259. for (var _t = 0; _t < 1; _t += 0.05) {
  10260. _v1[0] = quadraticAt(x0, x1, x2, _t);
  10261. _v1[1] = quadraticAt(y0, y1, y2, _t);
  10262. var d1 = distSquare(_v0, _v1);
  10263. if (d1 < d) {
  10264. t = _t;
  10265. d = d1;
  10266. }
  10267. }
  10268. d = Infinity;
  10269. // At most 32 iteration
  10270. for (var i = 0; i < 32; i++) {
  10271. if (interval < EPSILON_NUMERIC) {
  10272. break;
  10273. }
  10274. var prev = t - interval;
  10275. var next = t + interval;
  10276. // t - interval
  10277. _v1[0] = quadraticAt(x0, x1, x2, prev);
  10278. _v1[1] = quadraticAt(y0, y1, y2, prev);
  10279. var d1 = distSquare(_v1, _v0);
  10280. if (prev >= 0 && d1 < d) {
  10281. t = prev;
  10282. d = d1;
  10283. }
  10284. else {
  10285. // t + interval
  10286. _v2[0] = quadraticAt(x0, x1, x2, next);
  10287. _v2[1] = quadraticAt(y0, y1, y2, next);
  10288. var d2 = distSquare(_v2, _v0);
  10289. if (next <= 1 && d2 < d) {
  10290. t = next;
  10291. d = d2;
  10292. }
  10293. else {
  10294. interval *= 0.5;
  10295. }
  10296. }
  10297. }
  10298. // t
  10299. if (out) {
  10300. out[0] = quadraticAt(x0, x1, x2, t);
  10301. out[1] = quadraticAt(y0, y1, y2, t);
  10302. }
  10303. // console.log(interval, i);
  10304. return mathSqrt$2(d);
  10305. }
  10306. /**
  10307. * @author Yi Shen(https://github.com/pissang)
  10308. */
  10309. var mathMin$3 = Math.min;
  10310. var mathMax$3 = Math.max;
  10311. var mathSin$2 = Math.sin;
  10312. var mathCos$2 = Math.cos;
  10313. var PI2 = Math.PI * 2;
  10314. var start = create();
  10315. var end = create();
  10316. var extremity = create();
  10317. /**
  10318. * 从顶点数组中计算出最小包围盒,写入`min`和`max`中
  10319. * @module zrender/core/bbox
  10320. * @param {Array<Object>} points 顶点数组
  10321. * @param {number} min
  10322. * @param {number} max
  10323. */
  10324. /**
  10325. * @memberOf module:zrender/core/bbox
  10326. * @param {number} x0
  10327. * @param {number} y0
  10328. * @param {number} x1
  10329. * @param {number} y1
  10330. * @param {Array.<number>} min
  10331. * @param {Array.<number>} max
  10332. */
  10333. function fromLine(x0, y0, x1, y1, min$$1, max$$1) {
  10334. min$$1[0] = mathMin$3(x0, x1);
  10335. min$$1[1] = mathMin$3(y0, y1);
  10336. max$$1[0] = mathMax$3(x0, x1);
  10337. max$$1[1] = mathMax$3(y0, y1);
  10338. }
  10339. var xDim = [];
  10340. var yDim = [];
  10341. /**
  10342. * 从三阶贝塞尔曲线(p0, p1, p2, p3)中计算出最小包围盒,写入`min`和`max`中
  10343. * @memberOf module:zrender/core/bbox
  10344. * @param {number} x0
  10345. * @param {number} y0
  10346. * @param {number} x1
  10347. * @param {number} y1
  10348. * @param {number} x2
  10349. * @param {number} y2
  10350. * @param {number} x3
  10351. * @param {number} y3
  10352. * @param {Array.<number>} min
  10353. * @param {Array.<number>} max
  10354. */
  10355. function fromCubic(
  10356. x0, y0, x1, y1, x2, y2, x3, y3, min$$1, max$$1
  10357. ) {
  10358. var cubicExtrema$$1 = cubicExtrema;
  10359. var cubicAt$$1 = cubicAt;
  10360. var i;
  10361. var n = cubicExtrema$$1(x0, x1, x2, x3, xDim);
  10362. min$$1[0] = Infinity;
  10363. min$$1[1] = Infinity;
  10364. max$$1[0] = -Infinity;
  10365. max$$1[1] = -Infinity;
  10366. for (i = 0; i < n; i++) {
  10367. var x = cubicAt$$1(x0, x1, x2, x3, xDim[i]);
  10368. min$$1[0] = mathMin$3(x, min$$1[0]);
  10369. max$$1[0] = mathMax$3(x, max$$1[0]);
  10370. }
  10371. n = cubicExtrema$$1(y0, y1, y2, y3, yDim);
  10372. for (i = 0; i < n; i++) {
  10373. var y = cubicAt$$1(y0, y1, y2, y3, yDim[i]);
  10374. min$$1[1] = mathMin$3(y, min$$1[1]);
  10375. max$$1[1] = mathMax$3(y, max$$1[1]);
  10376. }
  10377. min$$1[0] = mathMin$3(x0, min$$1[0]);
  10378. max$$1[0] = mathMax$3(x0, max$$1[0]);
  10379. min$$1[0] = mathMin$3(x3, min$$1[0]);
  10380. max$$1[0] = mathMax$3(x3, max$$1[0]);
  10381. min$$1[1] = mathMin$3(y0, min$$1[1]);
  10382. max$$1[1] = mathMax$3(y0, max$$1[1]);
  10383. min$$1[1] = mathMin$3(y3, min$$1[1]);
  10384. max$$1[1] = mathMax$3(y3, max$$1[1]);
  10385. }
  10386. /**
  10387. * 从二阶贝塞尔曲线(p0, p1, p2)中计算出最小包围盒,写入`min`和`max`中
  10388. * @memberOf module:zrender/core/bbox
  10389. * @param {number} x0
  10390. * @param {number} y0
  10391. * @param {number} x1
  10392. * @param {number} y1
  10393. * @param {number} x2
  10394. * @param {number} y2
  10395. * @param {Array.<number>} min
  10396. * @param {Array.<number>} max
  10397. */
  10398. function fromQuadratic(x0, y0, x1, y1, x2, y2, min$$1, max$$1) {
  10399. var quadraticExtremum$$1 = quadraticExtremum;
  10400. var quadraticAt$$1 = quadraticAt;
  10401. // Find extremities, where derivative in x dim or y dim is zero
  10402. var tx =
  10403. mathMax$3(
  10404. mathMin$3(quadraticExtremum$$1(x0, x1, x2), 1), 0
  10405. );
  10406. var ty =
  10407. mathMax$3(
  10408. mathMin$3(quadraticExtremum$$1(y0, y1, y2), 1), 0
  10409. );
  10410. var x = quadraticAt$$1(x0, x1, x2, tx);
  10411. var y = quadraticAt$$1(y0, y1, y2, ty);
  10412. min$$1[0] = mathMin$3(x0, x2, x);
  10413. min$$1[1] = mathMin$3(y0, y2, y);
  10414. max$$1[0] = mathMax$3(x0, x2, x);
  10415. max$$1[1] = mathMax$3(y0, y2, y);
  10416. }
  10417. /**
  10418. * 从圆弧中计算出最小包围盒,写入`min`和`max`中
  10419. * @method
  10420. * @memberOf module:zrender/core/bbox
  10421. * @param {number} x
  10422. * @param {number} y
  10423. * @param {number} rx
  10424. * @param {number} ry
  10425. * @param {number} startAngle
  10426. * @param {number} endAngle
  10427. * @param {number} anticlockwise
  10428. * @param {Array.<number>} min
  10429. * @param {Array.<number>} max
  10430. */
  10431. function fromArc(
  10432. x, y, rx, ry, startAngle, endAngle, anticlockwise, min$$1, max$$1
  10433. ) {
  10434. var vec2Min = min;
  10435. var vec2Max = max;
  10436. var diff = Math.abs(startAngle - endAngle);
  10437. if (diff % PI2 < 1e-4 && diff > 1e-4) {
  10438. // Is a circle
  10439. min$$1[0] = x - rx;
  10440. min$$1[1] = y - ry;
  10441. max$$1[0] = x + rx;
  10442. max$$1[1] = y + ry;
  10443. return;
  10444. }
  10445. start[0] = mathCos$2(startAngle) * rx + x;
  10446. start[1] = mathSin$2(startAngle) * ry + y;
  10447. end[0] = mathCos$2(endAngle) * rx + x;
  10448. end[1] = mathSin$2(endAngle) * ry + y;
  10449. vec2Min(min$$1, start, end);
  10450. vec2Max(max$$1, start, end);
  10451. // Thresh to [0, Math.PI * 2]
  10452. startAngle = startAngle % (PI2);
  10453. if (startAngle < 0) {
  10454. startAngle = startAngle + PI2;
  10455. }
  10456. endAngle = endAngle % (PI2);
  10457. if (endAngle < 0) {
  10458. endAngle = endAngle + PI2;
  10459. }
  10460. if (startAngle > endAngle && !anticlockwise) {
  10461. endAngle += PI2;
  10462. }
  10463. else if (startAngle < endAngle && anticlockwise) {
  10464. startAngle += PI2;
  10465. }
  10466. if (anticlockwise) {
  10467. var tmp = endAngle;
  10468. endAngle = startAngle;
  10469. startAngle = tmp;
  10470. }
  10471. // var number = 0;
  10472. // var step = (anticlockwise ? -Math.PI : Math.PI) / 2;
  10473. for (var angle = 0; angle < endAngle; angle += Math.PI / 2) {
  10474. if (angle > startAngle) {
  10475. extremity[0] = mathCos$2(angle) * rx + x;
  10476. extremity[1] = mathSin$2(angle) * ry + y;
  10477. vec2Min(min$$1, extremity, min$$1);
  10478. vec2Max(max$$1, extremity, max$$1);
  10479. }
  10480. }
  10481. }
  10482. /**
  10483. * Path 代理,可以在`buildPath`中用于替代`ctx`, 会保存每个path操作的命令到pathCommands属性中
  10484. * 可以用于 isInsidePath 判断以及获取boundingRect
  10485. *
  10486. * @module zrender/core/PathProxy
  10487. * @author Yi Shen (http://www.github.com/pissang)
  10488. */
  10489. // TODO getTotalLength, getPointAtLength
  10490. var CMD = {
  10491. M: 1,
  10492. L: 2,
  10493. C: 3,
  10494. Q: 4,
  10495. A: 5,
  10496. Z: 6,
  10497. // Rect
  10498. R: 7
  10499. };
  10500. // var CMD_MEM_SIZE = {
  10501. // M: 3,
  10502. // L: 3,
  10503. // C: 7,
  10504. // Q: 5,
  10505. // A: 9,
  10506. // R: 5,
  10507. // Z: 1
  10508. // };
  10509. var min$1 = [];
  10510. var max$1 = [];
  10511. var min2 = [];
  10512. var max2 = [];
  10513. var mathMin$2 = Math.min;
  10514. var mathMax$2 = Math.max;
  10515. var mathCos$1 = Math.cos;
  10516. var mathSin$1 = Math.sin;
  10517. var mathSqrt$1 = Math.sqrt;
  10518. var mathAbs = Math.abs;
  10519. var hasTypedArray = typeof Float32Array != 'undefined';
  10520. /**
  10521. * @alias module:zrender/core/PathProxy
  10522. * @constructor
  10523. */
  10524. var PathProxy = function (notSaveData) {
  10525. this._saveData = !(notSaveData || false);
  10526. if (this._saveData) {
  10527. /**
  10528. * Path data. Stored as flat array
  10529. * @type {Array.<Object>}
  10530. */
  10531. this.data = [];
  10532. }
  10533. this._ctx = null;
  10534. };
  10535. /**
  10536. * 快速计算Path包围盒(并不是最小包围盒)
  10537. * @return {Object}
  10538. */
  10539. PathProxy.prototype = {
  10540. constructor: PathProxy,
  10541. _xi: 0,
  10542. _yi: 0,
  10543. _x0: 0,
  10544. _y0: 0,
  10545. // Unit x, Unit y. Provide for avoiding drawing that too short line segment
  10546. _ux: 0,
  10547. _uy: 0,
  10548. _len: 0,
  10549. _lineDash: null,
  10550. _dashOffset: 0,
  10551. _dashIdx: 0,
  10552. _dashSum: 0,
  10553. /**
  10554. * @readOnly
  10555. */
  10556. setScale: function (sx, sy) {
  10557. this._ux = mathAbs(1 / devicePixelRatio / sx) || 0;
  10558. this._uy = mathAbs(1 / devicePixelRatio / sy) || 0;
  10559. },
  10560. getContext: function () {
  10561. return this._ctx;
  10562. },
  10563. /**
  10564. * @param {CanvasRenderingContext2D} ctx
  10565. * @return {module:zrender/core/PathProxy}
  10566. */
  10567. beginPath: function (ctx) {
  10568. this._ctx = ctx;
  10569. ctx && ctx.beginPath();
  10570. ctx && (this.dpr = ctx.dpr);
  10571. // Reset
  10572. if (this._saveData) {
  10573. this._len = 0;
  10574. }
  10575. if (this._lineDash) {
  10576. this._lineDash = null;
  10577. this._dashOffset = 0;
  10578. }
  10579. return this;
  10580. },
  10581. /**
  10582. * @param {number} x
  10583. * @param {number} y
  10584. * @return {module:zrender/core/PathProxy}
  10585. */
  10586. moveTo: function (x, y) {
  10587. this.addData(CMD.M, x, y);
  10588. this._ctx && this._ctx.moveTo(x, y);
  10589. // x0, y0, xi, yi 是记录在 _dashedXXXXTo 方法中使用
  10590. // xi, yi 记录当前点, x0, y0 在 closePath 的时候回到起始点。
  10591. // 有可能在 beginPath 之后直接调用 lineTo,这时候 x0, y0 需要
  10592. // 在 lineTo 方法中记录,这里先不考虑这种情况,dashed line 也只在 IE10- 中不支持
  10593. this._x0 = x;
  10594. this._y0 = y;
  10595. this._xi = x;
  10596. this._yi = y;
  10597. return this;
  10598. },
  10599. /**
  10600. * @param {number} x
  10601. * @param {number} y
  10602. * @return {module:zrender/core/PathProxy}
  10603. */
  10604. lineTo: function (x, y) {
  10605. var exceedUnit = mathAbs(x - this._xi) > this._ux
  10606. || mathAbs(y - this._yi) > this._uy
  10607. // Force draw the first segment
  10608. || this._len < 5;
  10609. this.addData(CMD.L, x, y);
  10610. if (this._ctx && exceedUnit) {
  10611. this._needsDash() ? this._dashedLineTo(x, y)
  10612. : this._ctx.lineTo(x, y);
  10613. }
  10614. if (exceedUnit) {
  10615. this._xi = x;
  10616. this._yi = y;
  10617. }
  10618. return this;
  10619. },
  10620. /**
  10621. * @param {number} x1
  10622. * @param {number} y1
  10623. * @param {number} x2
  10624. * @param {number} y2
  10625. * @param {number} x3
  10626. * @param {number} y3
  10627. * @return {module:zrender/core/PathProxy}
  10628. */
  10629. bezierCurveTo: function (x1, y1, x2, y2, x3, y3) {
  10630. this.addData(CMD.C, x1, y1, x2, y2, x3, y3);
  10631. if (this._ctx) {
  10632. this._needsDash() ? this._dashedBezierTo(x1, y1, x2, y2, x3, y3)
  10633. : this._ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
  10634. }
  10635. this._xi = x3;
  10636. this._yi = y3;
  10637. return this;
  10638. },
  10639. /**
  10640. * @param {number} x1
  10641. * @param {number} y1
  10642. * @param {number} x2
  10643. * @param {number} y2
  10644. * @return {module:zrender/core/PathProxy}
  10645. */
  10646. quadraticCurveTo: function (x1, y1, x2, y2) {
  10647. this.addData(CMD.Q, x1, y1, x2, y2);
  10648. if (this._ctx) {
  10649. this._needsDash() ? this._dashedQuadraticTo(x1, y1, x2, y2)
  10650. : this._ctx.quadraticCurveTo(x1, y1, x2, y2);
  10651. }
  10652. this._xi = x2;
  10653. this._yi = y2;
  10654. return this;
  10655. },
  10656. /**
  10657. * @param {number} cx
  10658. * @param {number} cy
  10659. * @param {number} r
  10660. * @param {number} startAngle
  10661. * @param {number} endAngle
  10662. * @param {boolean} anticlockwise
  10663. * @return {module:zrender/core/PathProxy}
  10664. */
  10665. arc: function (cx, cy, r, startAngle, endAngle, anticlockwise) {
  10666. this.addData(
  10667. CMD.A, cx, cy, r, r, startAngle, endAngle - startAngle, 0, anticlockwise ? 0 : 1
  10668. );
  10669. this._ctx && this._ctx.arc(cx, cy, r, startAngle, endAngle, anticlockwise);
  10670. this._xi = mathCos$1(endAngle) * r + cx;
  10671. this._yi = mathSin$1(endAngle) * r + cx;
  10672. return this;
  10673. },
  10674. // TODO
  10675. arcTo: function (x1, y1, x2, y2, radius) {
  10676. if (this._ctx) {
  10677. this._ctx.arcTo(x1, y1, x2, y2, radius);
  10678. }
  10679. return this;
  10680. },
  10681. // TODO
  10682. rect: function (x, y, w, h) {
  10683. this._ctx && this._ctx.rect(x, y, w, h);
  10684. this.addData(CMD.R, x, y, w, h);
  10685. return this;
  10686. },
  10687. /**
  10688. * @return {module:zrender/core/PathProxy}
  10689. */
  10690. closePath: function () {
  10691. this.addData(CMD.Z);
  10692. var ctx = this._ctx;
  10693. var x0 = this._x0;
  10694. var y0 = this._y0;
  10695. if (ctx) {
  10696. this._needsDash() && this._dashedLineTo(x0, y0);
  10697. ctx.closePath();
  10698. }
  10699. this._xi = x0;
  10700. this._yi = y0;
  10701. return this;
  10702. },
  10703. /**
  10704. * Context 从外部传入,因为有可能是 rebuildPath 完之后再 fill。
  10705. * stroke 同样
  10706. * @param {CanvasRenderingContext2D} ctx
  10707. * @return {module:zrender/core/PathProxy}
  10708. */
  10709. fill: function (ctx) {
  10710. ctx && ctx.fill();
  10711. this.toStatic();
  10712. },
  10713. /**
  10714. * @param {CanvasRenderingContext2D} ctx
  10715. * @return {module:zrender/core/PathProxy}
  10716. */
  10717. stroke: function (ctx) {
  10718. ctx && ctx.stroke();
  10719. this.toStatic();
  10720. },
  10721. /**
  10722. * 必须在其它绘制命令前调用
  10723. * Must be invoked before all other path drawing methods
  10724. * @return {module:zrender/core/PathProxy}
  10725. */
  10726. setLineDash: function (lineDash) {
  10727. if (lineDash instanceof Array) {
  10728. this._lineDash = lineDash;
  10729. this._dashIdx = 0;
  10730. var lineDashSum = 0;
  10731. for (var i = 0; i < lineDash.length; i++) {
  10732. lineDashSum += lineDash[i];
  10733. }
  10734. this._dashSum = lineDashSum;
  10735. }
  10736. return this;
  10737. },
  10738. /**
  10739. * 必须在其它绘制命令前调用
  10740. * Must be invoked before all other path drawing methods
  10741. * @return {module:zrender/core/PathProxy}
  10742. */
  10743. setLineDashOffset: function (offset) {
  10744. this._dashOffset = offset;
  10745. return this;
  10746. },
  10747. /**
  10748. *
  10749. * @return {boolean}
  10750. */
  10751. len: function () {
  10752. return this._len;
  10753. },
  10754. /**
  10755. * 直接设置 Path 数据
  10756. */
  10757. setData: function (data) {
  10758. var len$$1 = data.length;
  10759. if (! (this.data && this.data.length == len$$1) && hasTypedArray) {
  10760. this.data = new Float32Array(len$$1);
  10761. }
  10762. for (var i = 0; i < len$$1; i++) {
  10763. this.data[i] = data[i];
  10764. }
  10765. this._len = len$$1;
  10766. },
  10767. /**
  10768. * 添加子路径
  10769. * @param {module:zrender/core/PathProxy|Array.<module:zrender/core/PathProxy>} path
  10770. */
  10771. appendPath: function (path) {
  10772. if (!(path instanceof Array)) {
  10773. path = [path];
  10774. }
  10775. var len$$1 = path.length;
  10776. var appendSize = 0;
  10777. var offset = this._len;
  10778. for (var i = 0; i < len$$1; i++) {
  10779. appendSize += path[i].len();
  10780. }
  10781. if (hasTypedArray && (this.data instanceof Float32Array)) {
  10782. this.data = new Float32Array(offset + appendSize);
  10783. }
  10784. for (var i = 0; i < len$$1; i++) {
  10785. var appendPathData = path[i].data;
  10786. for (var k = 0; k < appendPathData.length; k++) {
  10787. this.data[offset++] = appendPathData[k];
  10788. }
  10789. }
  10790. this._len = offset;
  10791. },
  10792. /**
  10793. * 填充 Path 数据。
  10794. * 尽量复用而不申明新的数组。大部分图形重绘的指令数据长度都是不变的。
  10795. */
  10796. addData: function (cmd) {
  10797. if (!this._saveData) {
  10798. return;
  10799. }
  10800. var data = this.data;
  10801. if (this._len + arguments.length > data.length) {
  10802. // 因为之前的数组已经转换成静态的 Float32Array
  10803. // 所以不够用时需要扩展一个新的动态数组
  10804. this._expandData();
  10805. data = this.data;
  10806. }
  10807. for (var i = 0; i < arguments.length; i++) {
  10808. data[this._len++] = arguments[i];
  10809. }
  10810. this._prevCmd = cmd;
  10811. },
  10812. _expandData: function () {
  10813. // Only if data is Float32Array
  10814. if (!(this.data instanceof Array)) {
  10815. var newData = [];
  10816. for (var i = 0; i < this._len; i++) {
  10817. newData[i] = this.data[i];
  10818. }
  10819. this.data = newData;
  10820. }
  10821. },
  10822. /**
  10823. * If needs js implemented dashed line
  10824. * @return {boolean}
  10825. * @private
  10826. */
  10827. _needsDash: function () {
  10828. return this._lineDash;
  10829. },
  10830. _dashedLineTo: function (x1, y1) {
  10831. var dashSum = this._dashSum;
  10832. var offset = this._dashOffset;
  10833. var lineDash = this._lineDash;
  10834. var ctx = this._ctx;
  10835. var x0 = this._xi;
  10836. var y0 = this._yi;
  10837. var dx = x1 - x0;
  10838. var dy = y1 - y0;
  10839. var dist$$1 = mathSqrt$1(dx * dx + dy * dy);
  10840. var x = x0;
  10841. var y = y0;
  10842. var dash;
  10843. var nDash = lineDash.length;
  10844. var idx;
  10845. dx /= dist$$1;
  10846. dy /= dist$$1;
  10847. if (offset < 0) {
  10848. // Convert to positive offset
  10849. offset = dashSum + offset;
  10850. }
  10851. offset %= dashSum;
  10852. x -= offset * dx;
  10853. y -= offset * dy;
  10854. while ((dx > 0 && x <= x1) || (dx < 0 && x >= x1)
  10855. || (dx == 0 && ((dy > 0 && y <= y1) || (dy < 0 && y >= y1)))) {
  10856. idx = this._dashIdx;
  10857. dash = lineDash[idx];
  10858. x += dx * dash;
  10859. y += dy * dash;
  10860. this._dashIdx = (idx + 1) % nDash;
  10861. // Skip positive offset
  10862. if ((dx > 0 && x < x0) || (dx < 0 && x > x0) || (dy > 0 && y < y0) || (dy < 0 && y > y0)) {
  10863. continue;
  10864. }
  10865. ctx[idx % 2 ? 'moveTo' : 'lineTo'](
  10866. dx >= 0 ? mathMin$2(x, x1) : mathMax$2(x, x1),
  10867. dy >= 0 ? mathMin$2(y, y1) : mathMax$2(y, y1)
  10868. );
  10869. }
  10870. // Offset for next lineTo
  10871. dx = x - x1;
  10872. dy = y - y1;
  10873. this._dashOffset = -mathSqrt$1(dx * dx + dy * dy);
  10874. },
  10875. // Not accurate dashed line to
  10876. _dashedBezierTo: function (x1, y1, x2, y2, x3, y3) {
  10877. var dashSum = this._dashSum;
  10878. var offset = this._dashOffset;
  10879. var lineDash = this._lineDash;
  10880. var ctx = this._ctx;
  10881. var x0 = this._xi;
  10882. var y0 = this._yi;
  10883. var t;
  10884. var dx;
  10885. var dy;
  10886. var cubicAt$$1 = cubicAt;
  10887. var bezierLen = 0;
  10888. var idx = this._dashIdx;
  10889. var nDash = lineDash.length;
  10890. var x;
  10891. var y;
  10892. var tmpLen = 0;
  10893. if (offset < 0) {
  10894. // Convert to positive offset
  10895. offset = dashSum + offset;
  10896. }
  10897. offset %= dashSum;
  10898. // Bezier approx length
  10899. for (t = 0; t < 1; t += 0.1) {
  10900. dx = cubicAt$$1(x0, x1, x2, x3, t + 0.1)
  10901. - cubicAt$$1(x0, x1, x2, x3, t);
  10902. dy = cubicAt$$1(y0, y1, y2, y3, t + 0.1)
  10903. - cubicAt$$1(y0, y1, y2, y3, t);
  10904. bezierLen += mathSqrt$1(dx * dx + dy * dy);
  10905. }
  10906. // Find idx after add offset
  10907. for (; idx < nDash; idx++) {
  10908. tmpLen += lineDash[idx];
  10909. if (tmpLen > offset) {
  10910. break;
  10911. }
  10912. }
  10913. t = (tmpLen - offset) / bezierLen;
  10914. while (t <= 1) {
  10915. x = cubicAt$$1(x0, x1, x2, x3, t);
  10916. y = cubicAt$$1(y0, y1, y2, y3, t);
  10917. // Use line to approximate dashed bezier
  10918. // Bad result if dash is long
  10919. idx % 2 ? ctx.moveTo(x, y)
  10920. : ctx.lineTo(x, y);
  10921. t += lineDash[idx] / bezierLen;
  10922. idx = (idx + 1) % nDash;
  10923. }
  10924. // Finish the last segment and calculate the new offset
  10925. (idx % 2 !== 0) && ctx.lineTo(x3, y3);
  10926. dx = x3 - x;
  10927. dy = y3 - y;
  10928. this._dashOffset = -mathSqrt$1(dx * dx + dy * dy);
  10929. },
  10930. _dashedQuadraticTo: function (x1, y1, x2, y2) {
  10931. // Convert quadratic to cubic using degree elevation
  10932. var x3 = x2;
  10933. var y3 = y2;
  10934. x2 = (x2 + 2 * x1) / 3;
  10935. y2 = (y2 + 2 * y1) / 3;
  10936. x1 = (this._xi + 2 * x1) / 3;
  10937. y1 = (this._yi + 2 * y1) / 3;
  10938. this._dashedBezierTo(x1, y1, x2, y2, x3, y3);
  10939. },
  10940. /**
  10941. * 转成静态的 Float32Array 减少堆内存占用
  10942. * Convert dynamic array to static Float32Array
  10943. */
  10944. toStatic: function () {
  10945. var data = this.data;
  10946. if (data instanceof Array) {
  10947. data.length = this._len;
  10948. if (hasTypedArray) {
  10949. this.data = new Float32Array(data);
  10950. }
  10951. }
  10952. },
  10953. /**
  10954. * @return {module:zrender/core/BoundingRect}
  10955. */
  10956. getBoundingRect: function () {
  10957. min$1[0] = min$1[1] = min2[0] = min2[1] = Number.MAX_VALUE;
  10958. max$1[0] = max$1[1] = max2[0] = max2[1] = -Number.MAX_VALUE;
  10959. var data = this.data;
  10960. var xi = 0;
  10961. var yi = 0;
  10962. var x0 = 0;
  10963. var y0 = 0;
  10964. for (var i = 0; i < data.length;) {
  10965. var cmd = data[i++];
  10966. if (i == 1) {
  10967. // 如果第一个命令是 L, C, Q
  10968. // 则 previous point 同绘制命令的第一个 point
  10969. //
  10970. // 第一个命令为 Arc 的情况下会在后面特殊处理
  10971. xi = data[i];
  10972. yi = data[i + 1];
  10973. x0 = xi;
  10974. y0 = yi;
  10975. }
  10976. switch (cmd) {
  10977. case CMD.M:
  10978. // moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
  10979. // 在 closePath 的时候使用
  10980. x0 = data[i++];
  10981. y0 = data[i++];
  10982. xi = x0;
  10983. yi = y0;
  10984. min2[0] = x0;
  10985. min2[1] = y0;
  10986. max2[0] = x0;
  10987. max2[1] = y0;
  10988. break;
  10989. case CMD.L:
  10990. fromLine(xi, yi, data[i], data[i + 1], min2, max2);
  10991. xi = data[i++];
  10992. yi = data[i++];
  10993. break;
  10994. case CMD.C:
  10995. fromCubic(
  10996. xi, yi, data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1],
  10997. min2, max2
  10998. );
  10999. xi = data[i++];
  11000. yi = data[i++];
  11001. break;
  11002. case CMD.Q:
  11003. fromQuadratic(
  11004. xi, yi, data[i++], data[i++], data[i], data[i + 1],
  11005. min2, max2
  11006. );
  11007. xi = data[i++];
  11008. yi = data[i++];
  11009. break;
  11010. case CMD.A:
  11011. // TODO Arc 判断的开销比较大
  11012. var cx = data[i++];
  11013. var cy = data[i++];
  11014. var rx = data[i++];
  11015. var ry = data[i++];
  11016. var startAngle = data[i++];
  11017. var endAngle = data[i++] + startAngle;
  11018. // TODO Arc 旋转
  11019. var psi = data[i++];
  11020. var anticlockwise = 1 - data[i++];
  11021. if (i == 1) {
  11022. // 直接使用 arc 命令
  11023. // 第一个命令起点还未定义
  11024. x0 = mathCos$1(startAngle) * rx + cx;
  11025. y0 = mathSin$1(startAngle) * ry + cy;
  11026. }
  11027. fromArc(
  11028. cx, cy, rx, ry, startAngle, endAngle,
  11029. anticlockwise, min2, max2
  11030. );
  11031. xi = mathCos$1(endAngle) * rx + cx;
  11032. yi = mathSin$1(endAngle) * ry + cy;
  11033. break;
  11034. case CMD.R:
  11035. x0 = xi = data[i++];
  11036. y0 = yi = data[i++];
  11037. var width = data[i++];
  11038. var height = data[i++];
  11039. // Use fromLine
  11040. fromLine(x0, y0, x0 + width, y0 + height, min2, max2);
  11041. break;
  11042. case CMD.Z:
  11043. xi = x0;
  11044. yi = y0;
  11045. break;
  11046. }
  11047. // Union
  11048. min(min$1, min$1, min2);
  11049. max(max$1, max$1, max2);
  11050. }
  11051. // No data
  11052. if (i === 0) {
  11053. min$1[0] = min$1[1] = max$1[0] = max$1[1] = 0;
  11054. }
  11055. return new BoundingRect(
  11056. min$1[0], min$1[1], max$1[0] - min$1[0], max$1[1] - min$1[1]
  11057. );
  11058. },
  11059. /**
  11060. * Rebuild path from current data
  11061. * Rebuild path will not consider javascript implemented line dash.
  11062. * @param {CanvasRenderingContext2D} ctx
  11063. */
  11064. rebuildPath: function (ctx) {
  11065. var d = this.data;
  11066. var x0, y0;
  11067. var xi, yi;
  11068. var x, y;
  11069. var ux = this._ux;
  11070. var uy = this._uy;
  11071. var len$$1 = this._len;
  11072. for (var i = 0; i < len$$1;) {
  11073. var cmd = d[i++];
  11074. if (i == 1) {
  11075. // 如果第一个命令是 L, C, Q
  11076. // 则 previous point 同绘制命令的第一个 point
  11077. //
  11078. // 第一个命令为 Arc 的情况下会在后面特殊处理
  11079. xi = d[i];
  11080. yi = d[i + 1];
  11081. x0 = xi;
  11082. y0 = yi;
  11083. }
  11084. switch (cmd) {
  11085. case CMD.M:
  11086. x0 = xi = d[i++];
  11087. y0 = yi = d[i++];
  11088. ctx.moveTo(xi, yi);
  11089. break;
  11090. case CMD.L:
  11091. x = d[i++];
  11092. y = d[i++];
  11093. // Not draw too small seg between
  11094. if (mathAbs(x - xi) > ux || mathAbs(y - yi) > uy || i === len$$1 - 1) {
  11095. ctx.lineTo(x, y);
  11096. xi = x;
  11097. yi = y;
  11098. }
  11099. break;
  11100. case CMD.C:
  11101. ctx.bezierCurveTo(
  11102. d[i++], d[i++], d[i++], d[i++], d[i++], d[i++]
  11103. );
  11104. xi = d[i - 2];
  11105. yi = d[i - 1];
  11106. break;
  11107. case CMD.Q:
  11108. ctx.quadraticCurveTo(d[i++], d[i++], d[i++], d[i++]);
  11109. xi = d[i - 2];
  11110. yi = d[i - 1];
  11111. break;
  11112. case CMD.A:
  11113. var cx = d[i++];
  11114. var cy = d[i++];
  11115. var rx = d[i++];
  11116. var ry = d[i++];
  11117. var theta = d[i++];
  11118. var dTheta = d[i++];
  11119. var psi = d[i++];
  11120. var fs = d[i++];
  11121. var r = (rx > ry) ? rx : ry;
  11122. var scaleX = (rx > ry) ? 1 : rx / ry;
  11123. var scaleY = (rx > ry) ? ry / rx : 1;
  11124. var isEllipse = Math.abs(rx - ry) > 1e-3;
  11125. var endAngle = theta + dTheta;
  11126. if (isEllipse) {
  11127. ctx.translate(cx, cy);
  11128. ctx.rotate(psi);
  11129. ctx.scale(scaleX, scaleY);
  11130. ctx.arc(0, 0, r, theta, endAngle, 1 - fs);
  11131. ctx.scale(1 / scaleX, 1 / scaleY);
  11132. ctx.rotate(-psi);
  11133. ctx.translate(-cx, -cy);
  11134. }
  11135. else {
  11136. ctx.arc(cx, cy, r, theta, endAngle, 1 - fs);
  11137. }
  11138. if (i == 1) {
  11139. // 直接使用 arc 命令
  11140. // 第一个命令起点还未定义
  11141. x0 = mathCos$1(theta) * rx + cx;
  11142. y0 = mathSin$1(theta) * ry + cy;
  11143. }
  11144. xi = mathCos$1(endAngle) * rx + cx;
  11145. yi = mathSin$1(endAngle) * ry + cy;
  11146. break;
  11147. case CMD.R:
  11148. x0 = xi = d[i];
  11149. y0 = yi = d[i + 1];
  11150. ctx.rect(d[i++], d[i++], d[i++], d[i++]);
  11151. break;
  11152. case CMD.Z:
  11153. ctx.closePath();
  11154. xi = x0;
  11155. yi = y0;
  11156. }
  11157. }
  11158. }
  11159. };
  11160. PathProxy.CMD = CMD;
  11161. /**
  11162. * 线段包含判断
  11163. * @param {number} x0
  11164. * @param {number} y0
  11165. * @param {number} x1
  11166. * @param {number} y1
  11167. * @param {number} lineWidth
  11168. * @param {number} x
  11169. * @param {number} y
  11170. * @return {boolean}
  11171. */
  11172. function containStroke$1(x0, y0, x1, y1, lineWidth, x, y) {
  11173. if (lineWidth === 0) {
  11174. return false;
  11175. }
  11176. var _l = lineWidth;
  11177. var _a = 0;
  11178. var _b = x0;
  11179. // Quick reject
  11180. if (
  11181. (y > y0 + _l && y > y1 + _l)
  11182. || (y < y0 - _l && y < y1 - _l)
  11183. || (x > x0 + _l && x > x1 + _l)
  11184. || (x < x0 - _l && x < x1 - _l)
  11185. ) {
  11186. return false;
  11187. }
  11188. if (x0 !== x1) {
  11189. _a = (y0 - y1) / (x0 - x1);
  11190. _b = (x0 * y1 - x1 * y0) / (x0 - x1) ;
  11191. }
  11192. else {
  11193. return Math.abs(x - x0) <= _l / 2;
  11194. }
  11195. var tmp = _a * x - y + _b;
  11196. var _s = tmp * tmp / (_a * _a + 1);
  11197. return _s <= _l / 2 * _l / 2;
  11198. }
  11199. /**
  11200. * 三次贝塞尔曲线描边包含判断
  11201. * @param {number} x0
  11202. * @param {number} y0
  11203. * @param {number} x1
  11204. * @param {number} y1
  11205. * @param {number} x2
  11206. * @param {number} y2
  11207. * @param {number} x3
  11208. * @param {number} y3
  11209. * @param {number} lineWidth
  11210. * @param {number} x
  11211. * @param {number} y
  11212. * @return {boolean}
  11213. */
  11214. function containStroke$2(x0, y0, x1, y1, x2, y2, x3, y3, lineWidth, x, y) {
  11215. if (lineWidth === 0) {
  11216. return false;
  11217. }
  11218. var _l = lineWidth;
  11219. // Quick reject
  11220. if (
  11221. (y > y0 + _l && y > y1 + _l && y > y2 + _l && y > y3 + _l)
  11222. || (y < y0 - _l && y < y1 - _l && y < y2 - _l && y < y3 - _l)
  11223. || (x > x0 + _l && x > x1 + _l && x > x2 + _l && x > x3 + _l)
  11224. || (x < x0 - _l && x < x1 - _l && x < x2 - _l && x < x3 - _l)
  11225. ) {
  11226. return false;
  11227. }
  11228. var d = cubicProjectPoint(
  11229. x0, y0, x1, y1, x2, y2, x3, y3,
  11230. x, y, null
  11231. );
  11232. return d <= _l / 2;
  11233. }
  11234. /**
  11235. * 二次贝塞尔曲线描边包含判断
  11236. * @param {number} x0
  11237. * @param {number} y0
  11238. * @param {number} x1
  11239. * @param {number} y1
  11240. * @param {number} x2
  11241. * @param {number} y2
  11242. * @param {number} lineWidth
  11243. * @param {number} x
  11244. * @param {number} y
  11245. * @return {boolean}
  11246. */
  11247. function containStroke$3(x0, y0, x1, y1, x2, y2, lineWidth, x, y) {
  11248. if (lineWidth === 0) {
  11249. return false;
  11250. }
  11251. var _l = lineWidth;
  11252. // Quick reject
  11253. if (
  11254. (y > y0 + _l && y > y1 + _l && y > y2 + _l)
  11255. || (y < y0 - _l && y < y1 - _l && y < y2 - _l)
  11256. || (x > x0 + _l && x > x1 + _l && x > x2 + _l)
  11257. || (x < x0 - _l && x < x1 - _l && x < x2 - _l)
  11258. ) {
  11259. return false;
  11260. }
  11261. var d = quadraticProjectPoint(
  11262. x0, y0, x1, y1, x2, y2,
  11263. x, y, null
  11264. );
  11265. return d <= _l / 2;
  11266. }
  11267. var PI2$3 = Math.PI * 2;
  11268. function normalizeRadian(angle) {
  11269. angle %= PI2$3;
  11270. if (angle < 0) {
  11271. angle += PI2$3;
  11272. }
  11273. return angle;
  11274. }
  11275. var PI2$2 = Math.PI * 2;
  11276. /**
  11277. * 圆弧描边包含判断
  11278. * @param {number} cx
  11279. * @param {number} cy
  11280. * @param {number} r
  11281. * @param {number} startAngle
  11282. * @param {number} endAngle
  11283. * @param {boolean} anticlockwise
  11284. * @param {number} lineWidth
  11285. * @param {number} x
  11286. * @param {number} y
  11287. * @return {Boolean}
  11288. */
  11289. function containStroke$4(
  11290. cx, cy, r, startAngle, endAngle, anticlockwise,
  11291. lineWidth, x, y
  11292. ) {
  11293. if (lineWidth === 0) {
  11294. return false;
  11295. }
  11296. var _l = lineWidth;
  11297. x -= cx;
  11298. y -= cy;
  11299. var d = Math.sqrt(x * x + y * y);
  11300. if ((d - _l > r) || (d + _l < r)) {
  11301. return false;
  11302. }
  11303. if (Math.abs(startAngle - endAngle) % PI2$2 < 1e-4) {
  11304. // Is a circle
  11305. return true;
  11306. }
  11307. if (anticlockwise) {
  11308. var tmp = startAngle;
  11309. startAngle = normalizeRadian(endAngle);
  11310. endAngle = normalizeRadian(tmp);
  11311. } else {
  11312. startAngle = normalizeRadian(startAngle);
  11313. endAngle = normalizeRadian(endAngle);
  11314. }
  11315. if (startAngle > endAngle) {
  11316. endAngle += PI2$2;
  11317. }
  11318. var angle = Math.atan2(y, x);
  11319. if (angle < 0) {
  11320. angle += PI2$2;
  11321. }
  11322. return (angle >= startAngle && angle <= endAngle)
  11323. || (angle + PI2$2 >= startAngle && angle + PI2$2 <= endAngle);
  11324. }
  11325. function windingLine(x0, y0, x1, y1, x, y) {
  11326. if ((y > y0 && y > y1) || (y < y0 && y < y1)) {
  11327. return 0;
  11328. }
  11329. // Ignore horizontal line
  11330. if (y1 === y0) {
  11331. return 0;
  11332. }
  11333. var dir = y1 < y0 ? 1 : -1;
  11334. var t = (y - y0) / (y1 - y0);
  11335. // Avoid winding error when intersection point is the connect point of two line of polygon
  11336. if (t === 1 || t === 0) {
  11337. dir = y1 < y0 ? 0.5 : -0.5;
  11338. }
  11339. var x_ = t * (x1 - x0) + x0;
  11340. return x_ > x ? dir : 0;
  11341. }
  11342. var CMD$1 = PathProxy.CMD;
  11343. var PI2$1 = Math.PI * 2;
  11344. var EPSILON$2 = 1e-4;
  11345. function isAroundEqual(a, b) {
  11346. return Math.abs(a - b) < EPSILON$2;
  11347. }
  11348. // 临时数组
  11349. var roots = [-1, -1, -1];
  11350. var extrema = [-1, -1];
  11351. function swapExtrema() {
  11352. var tmp = extrema[0];
  11353. extrema[0] = extrema[1];
  11354. extrema[1] = tmp;
  11355. }
  11356. function windingCubic(x0, y0, x1, y1, x2, y2, x3, y3, x, y) {
  11357. // Quick reject
  11358. if (
  11359. (y > y0 && y > y1 && y > y2 && y > y3)
  11360. || (y < y0 && y < y1 && y < y2 && y < y3)
  11361. ) {
  11362. return 0;
  11363. }
  11364. var nRoots = cubicRootAt(y0, y1, y2, y3, y, roots);
  11365. if (nRoots === 0) {
  11366. return 0;
  11367. }
  11368. else {
  11369. var w = 0;
  11370. var nExtrema = -1;
  11371. var y0_, y1_;
  11372. for (var i = 0; i < nRoots; i++) {
  11373. var t = roots[i];
  11374. // Avoid winding error when intersection point is the connect point of two line of polygon
  11375. var unit = (t === 0 || t === 1) ? 0.5 : 1;
  11376. var x_ = cubicAt(x0, x1, x2, x3, t);
  11377. if (x_ < x) { // Quick reject
  11378. continue;
  11379. }
  11380. if (nExtrema < 0) {
  11381. nExtrema = cubicExtrema(y0, y1, y2, y3, extrema);
  11382. if (extrema[1] < extrema[0] && nExtrema > 1) {
  11383. swapExtrema();
  11384. }
  11385. y0_ = cubicAt(y0, y1, y2, y3, extrema[0]);
  11386. if (nExtrema > 1) {
  11387. y1_ = cubicAt(y0, y1, y2, y3, extrema[1]);
  11388. }
  11389. }
  11390. if (nExtrema == 2) {
  11391. // 分成三段单调函数
  11392. if (t < extrema[0]) {
  11393. w += y0_ < y0 ? unit : -unit;
  11394. }
  11395. else if (t < extrema[1]) {
  11396. w += y1_ < y0_ ? unit : -unit;
  11397. }
  11398. else {
  11399. w += y3 < y1_ ? unit : -unit;
  11400. }
  11401. }
  11402. else {
  11403. // 分成两段单调函数
  11404. if (t < extrema[0]) {
  11405. w += y0_ < y0 ? unit : -unit;
  11406. }
  11407. else {
  11408. w += y3 < y0_ ? unit : -unit;
  11409. }
  11410. }
  11411. }
  11412. return w;
  11413. }
  11414. }
  11415. function windingQuadratic(x0, y0, x1, y1, x2, y2, x, y) {
  11416. // Quick reject
  11417. if (
  11418. (y > y0 && y > y1 && y > y2)
  11419. || (y < y0 && y < y1 && y < y2)
  11420. ) {
  11421. return 0;
  11422. }
  11423. var nRoots = quadraticRootAt(y0, y1, y2, y, roots);
  11424. if (nRoots === 0) {
  11425. return 0;
  11426. }
  11427. else {
  11428. var t = quadraticExtremum(y0, y1, y2);
  11429. if (t >= 0 && t <= 1) {
  11430. var w = 0;
  11431. var y_ = quadraticAt(y0, y1, y2, t);
  11432. for (var i = 0; i < nRoots; i++) {
  11433. // Remove one endpoint.
  11434. var unit = (roots[i] === 0 || roots[i] === 1) ? 0.5 : 1;
  11435. var x_ = quadraticAt(x0, x1, x2, roots[i]);
  11436. if (x_ < x) { // Quick reject
  11437. continue;
  11438. }
  11439. if (roots[i] < t) {
  11440. w += y_ < y0 ? unit : -unit;
  11441. }
  11442. else {
  11443. w += y2 < y_ ? unit : -unit;
  11444. }
  11445. }
  11446. return w;
  11447. }
  11448. else {
  11449. // Remove one endpoint.
  11450. var unit = (roots[0] === 0 || roots[0] === 1) ? 0.5 : 1;
  11451. var x_ = quadraticAt(x0, x1, x2, roots[0]);
  11452. if (x_ < x) { // Quick reject
  11453. return 0;
  11454. }
  11455. return y2 < y0 ? unit : -unit;
  11456. }
  11457. }
  11458. }
  11459. // TODO
  11460. // Arc 旋转
  11461. function windingArc(
  11462. cx, cy, r, startAngle, endAngle, anticlockwise, x, y
  11463. ) {
  11464. y -= cy;
  11465. if (y > r || y < -r) {
  11466. return 0;
  11467. }
  11468. var tmp = Math.sqrt(r * r - y * y);
  11469. roots[0] = -tmp;
  11470. roots[1] = tmp;
  11471. var diff = Math.abs(startAngle - endAngle);
  11472. if (diff < 1e-4) {
  11473. return 0;
  11474. }
  11475. if (diff % PI2$1 < 1e-4) {
  11476. // Is a circle
  11477. startAngle = 0;
  11478. endAngle = PI2$1;
  11479. var dir = anticlockwise ? 1 : -1;
  11480. if (x >= roots[0] + cx && x <= roots[1] + cx) {
  11481. return dir;
  11482. } else {
  11483. return 0;
  11484. }
  11485. }
  11486. if (anticlockwise) {
  11487. var tmp = startAngle;
  11488. startAngle = normalizeRadian(endAngle);
  11489. endAngle = normalizeRadian(tmp);
  11490. }
  11491. else {
  11492. startAngle = normalizeRadian(startAngle);
  11493. endAngle = normalizeRadian(endAngle);
  11494. }
  11495. if (startAngle > endAngle) {
  11496. endAngle += PI2$1;
  11497. }
  11498. var w = 0;
  11499. for (var i = 0; i < 2; i++) {
  11500. var x_ = roots[i];
  11501. if (x_ + cx > x) {
  11502. var angle = Math.atan2(y, x_);
  11503. var dir = anticlockwise ? 1 : -1;
  11504. if (angle < 0) {
  11505. angle = PI2$1 + angle;
  11506. }
  11507. if (
  11508. (angle >= startAngle && angle <= endAngle)
  11509. || (angle + PI2$1 >= startAngle && angle + PI2$1 <= endAngle)
  11510. ) {
  11511. if (angle > Math.PI / 2 && angle < Math.PI * 1.5) {
  11512. dir = -dir;
  11513. }
  11514. w += dir;
  11515. }
  11516. }
  11517. }
  11518. return w;
  11519. }
  11520. function containPath(data, lineWidth, isStroke, x, y) {
  11521. var w = 0;
  11522. var xi = 0;
  11523. var yi = 0;
  11524. var x0 = 0;
  11525. var y0 = 0;
  11526. for (var i = 0; i < data.length;) {
  11527. var cmd = data[i++];
  11528. // Begin a new subpath
  11529. if (cmd === CMD$1.M && i > 1) {
  11530. // Close previous subpath
  11531. if (!isStroke) {
  11532. w += windingLine(xi, yi, x0, y0, x, y);
  11533. }
  11534. // 如果被任何一个 subpath 包含
  11535. // if (w !== 0) {
  11536. // return true;
  11537. // }
  11538. }
  11539. if (i == 1) {
  11540. // 如果第一个命令是 L, C, Q
  11541. // 则 previous point 同绘制命令的第一个 point
  11542. //
  11543. // 第一个命令为 Arc 的情况下会在后面特殊处理
  11544. xi = data[i];
  11545. yi = data[i + 1];
  11546. x0 = xi;
  11547. y0 = yi;
  11548. }
  11549. switch (cmd) {
  11550. case CMD$1.M:
  11551. // moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
  11552. // 在 closePath 的时候使用
  11553. x0 = data[i++];
  11554. y0 = data[i++];
  11555. xi = x0;
  11556. yi = y0;
  11557. break;
  11558. case CMD$1.L:
  11559. if (isStroke) {
  11560. if (containStroke$1(xi, yi, data[i], data[i + 1], lineWidth, x, y)) {
  11561. return true;
  11562. }
  11563. }
  11564. else {
  11565. // NOTE 在第一个命令为 L, C, Q 的时候会计算出 NaN
  11566. w += windingLine(xi, yi, data[i], data[i + 1], x, y) || 0;
  11567. }
  11568. xi = data[i++];
  11569. yi = data[i++];
  11570. break;
  11571. case CMD$1.C:
  11572. if (isStroke) {
  11573. if (containStroke$2(xi, yi,
  11574. data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1],
  11575. lineWidth, x, y
  11576. )) {
  11577. return true;
  11578. }
  11579. }
  11580. else {
  11581. w += windingCubic(
  11582. xi, yi,
  11583. data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1],
  11584. x, y
  11585. ) || 0;
  11586. }
  11587. xi = data[i++];
  11588. yi = data[i++];
  11589. break;
  11590. case CMD$1.Q:
  11591. if (isStroke) {
  11592. if (containStroke$3(xi, yi,
  11593. data[i++], data[i++], data[i], data[i + 1],
  11594. lineWidth, x, y
  11595. )) {
  11596. return true;
  11597. }
  11598. }
  11599. else {
  11600. w += windingQuadratic(
  11601. xi, yi,
  11602. data[i++], data[i++], data[i], data[i + 1],
  11603. x, y
  11604. ) || 0;
  11605. }
  11606. xi = data[i++];
  11607. yi = data[i++];
  11608. break;
  11609. case CMD$1.A:
  11610. // TODO Arc 判断的开销比较大
  11611. var cx = data[i++];
  11612. var cy = data[i++];
  11613. var rx = data[i++];
  11614. var ry = data[i++];
  11615. var theta = data[i++];
  11616. var dTheta = data[i++];
  11617. // TODO Arc 旋转
  11618. var psi = data[i++];
  11619. var anticlockwise = 1 - data[i++];
  11620. var x1 = Math.cos(theta) * rx + cx;
  11621. var y1 = Math.sin(theta) * ry + cy;
  11622. // 不是直接使用 arc 命令
  11623. if (i > 1) {
  11624. w += windingLine(xi, yi, x1, y1, x, y);
  11625. }
  11626. else {
  11627. // 第一个命令起点还未定义
  11628. x0 = x1;
  11629. y0 = y1;
  11630. }
  11631. // zr 使用scale来模拟椭圆, 这里也对x做一定的缩放
  11632. var _x = (x - cx) * ry / rx + cx;
  11633. if (isStroke) {
  11634. if (containStroke$4(
  11635. cx, cy, ry, theta, theta + dTheta, anticlockwise,
  11636. lineWidth, _x, y
  11637. )) {
  11638. return true;
  11639. }
  11640. }
  11641. else {
  11642. w += windingArc(
  11643. cx, cy, ry, theta, theta + dTheta, anticlockwise,
  11644. _x, y
  11645. );
  11646. }
  11647. xi = Math.cos(theta + dTheta) * rx + cx;
  11648. yi = Math.sin(theta + dTheta) * ry + cy;
  11649. break;
  11650. case CMD$1.R:
  11651. x0 = xi = data[i++];
  11652. y0 = yi = data[i++];
  11653. var width = data[i++];
  11654. var height = data[i++];
  11655. var x1 = x0 + width;
  11656. var y1 = y0 + height;
  11657. if (isStroke) {
  11658. if (containStroke$1(x0, y0, x1, y0, lineWidth, x, y)
  11659. || containStroke$1(x1, y0, x1, y1, lineWidth, x, y)
  11660. || containStroke$1(x1, y1, x0, y1, lineWidth, x, y)
  11661. || containStroke$1(x0, y1, x0, y0, lineWidth, x, y)
  11662. ) {
  11663. return true;
  11664. }
  11665. }
  11666. else {
  11667. // FIXME Clockwise ?
  11668. w += windingLine(x1, y0, x1, y1, x, y);
  11669. w += windingLine(x0, y1, x0, y0, x, y);
  11670. }
  11671. break;
  11672. case CMD$1.Z:
  11673. if (isStroke) {
  11674. if (containStroke$1(
  11675. xi, yi, x0, y0, lineWidth, x, y
  11676. )) {
  11677. return true;
  11678. }
  11679. }
  11680. else {
  11681. // Close a subpath
  11682. w += windingLine(xi, yi, x0, y0, x, y);
  11683. // 如果被任何一个 subpath 包含
  11684. // FIXME subpaths may overlap
  11685. // if (w !== 0) {
  11686. // return true;
  11687. // }
  11688. }
  11689. xi = x0;
  11690. yi = y0;
  11691. break;
  11692. }
  11693. }
  11694. if (!isStroke && !isAroundEqual(yi, y0)) {
  11695. w += windingLine(xi, yi, x0, y0, x, y) || 0;
  11696. }
  11697. return w !== 0;
  11698. }
  11699. function contain(pathData, x, y) {
  11700. return containPath(pathData, 0, false, x, y);
  11701. }
  11702. function containStroke(pathData, lineWidth, x, y) {
  11703. return containPath(pathData, lineWidth, true, x, y);
  11704. }
  11705. var getCanvasPattern = Pattern.prototype.getCanvasPattern;
  11706. var abs = Math.abs;
  11707. var pathProxyForDraw = new PathProxy(true);
  11708. /**
  11709. * @alias module:zrender/graphic/Path
  11710. * @extends module:zrender/graphic/Displayable
  11711. * @constructor
  11712. * @param {Object} opts
  11713. */
  11714. function Path(opts) {
  11715. Displayable.call(this, opts);
  11716. /**
  11717. * @type {module:zrender/core/PathProxy}
  11718. * @readOnly
  11719. */
  11720. this.path = null;
  11721. }
  11722. Path.prototype = {
  11723. constructor: Path,
  11724. type: 'path',
  11725. __dirtyPath: true,
  11726. strokeContainThreshold: 5,
  11727. brush: function (ctx, prevEl) {
  11728. var style = this.style;
  11729. var path = this.path || pathProxyForDraw;
  11730. var hasStroke = style.hasStroke();
  11731. var hasFill = style.hasFill();
  11732. var fill = style.fill;
  11733. var stroke = style.stroke;
  11734. var hasFillGradient = hasFill && !!(fill.colorStops);
  11735. var hasStrokeGradient = hasStroke && !!(stroke.colorStops);
  11736. var hasFillPattern = hasFill && !!(fill.image);
  11737. var hasStrokePattern = hasStroke && !!(stroke.image);
  11738. style.bind(ctx, this, prevEl);
  11739. this.setTransform(ctx);
  11740. if (this.__dirty) {
  11741. var rect;
  11742. // Update gradient because bounding rect may changed
  11743. if (hasFillGradient) {
  11744. rect = rect || this.getBoundingRect();
  11745. this._fillGradient = style.getGradient(ctx, fill, rect);
  11746. }
  11747. if (hasStrokeGradient) {
  11748. rect = rect || this.getBoundingRect();
  11749. this._strokeGradient = style.getGradient(ctx, stroke, rect);
  11750. }
  11751. }
  11752. // Use the gradient or pattern
  11753. if (hasFillGradient) {
  11754. // PENDING If may have affect the state
  11755. ctx.fillStyle = this._fillGradient;
  11756. }
  11757. else if (hasFillPattern) {
  11758. ctx.fillStyle = getCanvasPattern.call(fill, ctx);
  11759. }
  11760. if (hasStrokeGradient) {
  11761. ctx.strokeStyle = this._strokeGradient;
  11762. }
  11763. else if (hasStrokePattern) {
  11764. ctx.strokeStyle = getCanvasPattern.call(stroke, ctx);
  11765. }
  11766. var lineDash = style.lineDash;
  11767. var lineDashOffset = style.lineDashOffset;
  11768. var ctxLineDash = !!ctx.setLineDash;
  11769. // Update path sx, sy
  11770. var scale = this.getGlobalScale();
  11771. path.setScale(scale[0], scale[1]);
  11772. // Proxy context
  11773. // Rebuild path in following 2 cases
  11774. // 1. Path is dirty
  11775. // 2. Path needs javascript implemented lineDash stroking.
  11776. // In this case, lineDash information will not be saved in PathProxy
  11777. if (this.__dirtyPath
  11778. || (lineDash && !ctxLineDash && hasStroke)
  11779. ) {
  11780. path.beginPath(ctx);
  11781. // Setting line dash before build path
  11782. if (lineDash && !ctxLineDash) {
  11783. path.setLineDash(lineDash);
  11784. path.setLineDashOffset(lineDashOffset);
  11785. }
  11786. this.buildPath(path, this.shape, false);
  11787. // Clear path dirty flag
  11788. if (this.path) {
  11789. this.__dirtyPath = false;
  11790. }
  11791. }
  11792. else {
  11793. // Replay path building
  11794. ctx.beginPath();
  11795. this.path.rebuildPath(ctx);
  11796. }
  11797. hasFill && path.fill(ctx);
  11798. if (lineDash && ctxLineDash) {
  11799. ctx.setLineDash(lineDash);
  11800. ctx.lineDashOffset = lineDashOffset;
  11801. }
  11802. hasStroke && path.stroke(ctx);
  11803. if (lineDash && ctxLineDash) {
  11804. // PENDING
  11805. // Remove lineDash
  11806. ctx.setLineDash([]);
  11807. }
  11808. // Draw rect text
  11809. if (style.text != null) {
  11810. // Only restore transform when needs draw text.
  11811. this.restoreTransform(ctx);
  11812. this.drawRectText(ctx, this.getBoundingRect());
  11813. }
  11814. },
  11815. // When bundling path, some shape may decide if use moveTo to begin a new subpath or closePath
  11816. // Like in circle
  11817. buildPath: function (ctx, shapeCfg, inBundle) {},
  11818. createPathProxy: function () {
  11819. this.path = new PathProxy();
  11820. },
  11821. getBoundingRect: function () {
  11822. var rect = this._rect;
  11823. var style = this.style;
  11824. var needsUpdateRect = !rect;
  11825. if (needsUpdateRect) {
  11826. var path = this.path;
  11827. if (!path) {
  11828. // Create path on demand.
  11829. path = this.path = new PathProxy();
  11830. }
  11831. if (this.__dirtyPath) {
  11832. path.beginPath();
  11833. this.buildPath(path, this.shape, false);
  11834. }
  11835. rect = path.getBoundingRect();
  11836. }
  11837. this._rect = rect;
  11838. if (style.hasStroke()) {
  11839. // Needs update rect with stroke lineWidth when
  11840. // 1. Element changes scale or lineWidth
  11841. // 2. Shape is changed
  11842. var rectWithStroke = this._rectWithStroke || (this._rectWithStroke = rect.clone());
  11843. if (this.__dirty || needsUpdateRect) {
  11844. rectWithStroke.copy(rect);
  11845. // FIXME Must after updateTransform
  11846. var w = style.lineWidth;
  11847. // PENDING, Min line width is needed when line is horizontal or vertical
  11848. var lineScale = style.strokeNoScale ? this.getLineScale() : 1;
  11849. // Only add extra hover lineWidth when there are no fill
  11850. if (!style.hasFill()) {
  11851. w = Math.max(w, this.strokeContainThreshold || 4);
  11852. }
  11853. // Consider line width
  11854. // Line scale can't be 0;
  11855. if (lineScale > 1e-10) {
  11856. rectWithStroke.width += w / lineScale;
  11857. rectWithStroke.height += w / lineScale;
  11858. rectWithStroke.x -= w / lineScale / 2;
  11859. rectWithStroke.y -= w / lineScale / 2;
  11860. }
  11861. }
  11862. // Return rect with stroke
  11863. return rectWithStroke;
  11864. }
  11865. return rect;
  11866. },
  11867. contain: function (x, y) {
  11868. var localPos = this.transformCoordToLocal(x, y);
  11869. var rect = this.getBoundingRect();
  11870. var style = this.style;
  11871. x = localPos[0];
  11872. y = localPos[1];
  11873. if (rect.contain(x, y)) {
  11874. var pathData = this.path.data;
  11875. if (style.hasStroke()) {
  11876. var lineWidth = style.lineWidth;
  11877. var lineScale = style.strokeNoScale ? this.getLineScale() : 1;
  11878. // Line scale can't be 0;
  11879. if (lineScale > 1e-10) {
  11880. // Only add extra hover lineWidth when there are no fill
  11881. if (!style.hasFill()) {
  11882. lineWidth = Math.max(lineWidth, this.strokeContainThreshold);
  11883. }
  11884. if (containStroke(
  11885. pathData, lineWidth / lineScale, x, y
  11886. )) {
  11887. return true;
  11888. }
  11889. }
  11890. }
  11891. if (style.hasFill()) {
  11892. return contain(pathData, x, y);
  11893. }
  11894. }
  11895. return false;
  11896. },
  11897. /**
  11898. * @param {boolean} dirtyPath
  11899. */
  11900. dirty: function (dirtyPath) {
  11901. if (dirtyPath == null) {
  11902. dirtyPath = true;
  11903. }
  11904. // Only mark dirty, not mark clean
  11905. if (dirtyPath) {
  11906. this.__dirtyPath = dirtyPath;
  11907. this._rect = null;
  11908. }
  11909. this.__dirty = true;
  11910. this.__zr && this.__zr.refresh();
  11911. // Used as a clipping path
  11912. if (this.__clipTarget) {
  11913. this.__clipTarget.dirty();
  11914. }
  11915. },
  11916. /**
  11917. * Alias for animate('shape')
  11918. * @param {boolean} loop
  11919. */
  11920. animateShape: function (loop) {
  11921. return this.animate('shape', loop);
  11922. },
  11923. // Overwrite attrKV
  11924. attrKV: function (key, value) {
  11925. // FIXME
  11926. if (key === 'shape') {
  11927. this.setShape(value);
  11928. this.__dirtyPath = true;
  11929. this._rect = null;
  11930. }
  11931. else {
  11932. Displayable.prototype.attrKV.call(this, key, value);
  11933. }
  11934. },
  11935. /**
  11936. * @param {Object|string} key
  11937. * @param {*} value
  11938. */
  11939. setShape: function (key, value) {
  11940. var shape = this.shape;
  11941. // Path from string may not have shape
  11942. if (shape) {
  11943. if (isObject$1(key)) {
  11944. for (var name in key) {
  11945. if (key.hasOwnProperty(name)) {
  11946. shape[name] = key[name];
  11947. }
  11948. }
  11949. }
  11950. else {
  11951. shape[key] = value;
  11952. }
  11953. this.dirty(true);
  11954. }
  11955. return this;
  11956. },
  11957. getLineScale: function () {
  11958. var m = this.transform;
  11959. // Get the line scale.
  11960. // Determinant of `m` means how much the area is enlarged by the
  11961. // transformation. So its square root can be used as a scale factor
  11962. // for width.
  11963. return m && abs(m[0] - 1) > 1e-10 && abs(m[3] - 1) > 1e-10
  11964. ? Math.sqrt(abs(m[0] * m[3] - m[2] * m[1]))
  11965. : 1;
  11966. }
  11967. };
  11968. /**
  11969. * 扩展一个 Path element, 比如星形,圆等。
  11970. * Extend a path element
  11971. * @param {Object} props
  11972. * @param {string} props.type Path type
  11973. * @param {Function} props.init Initialize
  11974. * @param {Function} props.buildPath Overwrite buildPath method
  11975. * @param {Object} [props.style] Extended default style config
  11976. * @param {Object} [props.shape] Extended default shape config
  11977. */
  11978. Path.extend = function (defaults$$1) {
  11979. var Sub = function (opts) {
  11980. Path.call(this, opts);
  11981. if (defaults$$1.style) {
  11982. // Extend default style
  11983. this.style.extendFrom(defaults$$1.style, false);
  11984. }
  11985. // Extend default shape
  11986. var defaultShape = defaults$$1.shape;
  11987. if (defaultShape) {
  11988. this.shape = this.shape || {};
  11989. var thisShape = this.shape;
  11990. for (var name in defaultShape) {
  11991. if (
  11992. ! thisShape.hasOwnProperty(name)
  11993. && defaultShape.hasOwnProperty(name)
  11994. ) {
  11995. thisShape[name] = defaultShape[name];
  11996. }
  11997. }
  11998. }
  11999. defaults$$1.init && defaults$$1.init.call(this, opts);
  12000. };
  12001. inherits(Sub, Path);
  12002. // FIXME 不能 extend position, rotation 等引用对象
  12003. for (var name in defaults$$1) {
  12004. // Extending prototype values and methods
  12005. if (name !== 'style' && name !== 'shape') {
  12006. Sub.prototype[name] = defaults$$1[name];
  12007. }
  12008. }
  12009. return Sub;
  12010. };
  12011. inherits(Path, Displayable);
  12012. var CMD$2 = PathProxy.CMD;
  12013. var points = [[], [], []];
  12014. var mathSqrt$3 = Math.sqrt;
  12015. var mathAtan2 = Math.atan2;
  12016. var transformPath = function (path, m) {
  12017. var data = path.data;
  12018. var cmd;
  12019. var nPoint;
  12020. var i;
  12021. var j;
  12022. var k;
  12023. var p;
  12024. var M = CMD$2.M;
  12025. var C = CMD$2.C;
  12026. var L = CMD$2.L;
  12027. var R = CMD$2.R;
  12028. var A = CMD$2.A;
  12029. var Q = CMD$2.Q;
  12030. for (i = 0, j = 0; i < data.length;) {
  12031. cmd = data[i++];
  12032. j = i;
  12033. nPoint = 0;
  12034. switch (cmd) {
  12035. case M:
  12036. nPoint = 1;
  12037. break;
  12038. case L:
  12039. nPoint = 1;
  12040. break;
  12041. case C:
  12042. nPoint = 3;
  12043. break;
  12044. case Q:
  12045. nPoint = 2;
  12046. break;
  12047. case A:
  12048. var x = m[4];
  12049. var y = m[5];
  12050. var sx = mathSqrt$3(m[0] * m[0] + m[1] * m[1]);
  12051. var sy = mathSqrt$3(m[2] * m[2] + m[3] * m[3]);
  12052. var angle = mathAtan2(-m[1] / sy, m[0] / sx);
  12053. // cx
  12054. data[i] *= sx;
  12055. data[i++] += x;
  12056. // cy
  12057. data[i] *= sy;
  12058. data[i++] += y;
  12059. // Scale rx and ry
  12060. // FIXME Assume psi is 0 here
  12061. data[i++] *= sx;
  12062. data[i++] *= sy;
  12063. // Start angle
  12064. data[i++] += angle;
  12065. // end angle
  12066. data[i++] += angle;
  12067. // FIXME psi
  12068. i += 2;
  12069. j = i;
  12070. break;
  12071. case R:
  12072. // x0, y0
  12073. p[0] = data[i++];
  12074. p[1] = data[i++];
  12075. applyTransform(p, p, m);
  12076. data[j++] = p[0];
  12077. data[j++] = p[1];
  12078. // x1, y1
  12079. p[0] += data[i++];
  12080. p[1] += data[i++];
  12081. applyTransform(p, p, m);
  12082. data[j++] = p[0];
  12083. data[j++] = p[1];
  12084. }
  12085. for (k = 0; k < nPoint; k++) {
  12086. var p = points[k];
  12087. p[0] = data[i++];
  12088. p[1] = data[i++];
  12089. applyTransform(p, p, m);
  12090. // Write back
  12091. data[j++] = p[0];
  12092. data[j++] = p[1];
  12093. }
  12094. }
  12095. };
  12096. // command chars
  12097. var cc = [
  12098. 'm', 'M', 'l', 'L', 'v', 'V', 'h', 'H', 'z', 'Z',
  12099. 'c', 'C', 'q', 'Q', 't', 'T', 's', 'S', 'a', 'A'
  12100. ];
  12101. var mathSqrt = Math.sqrt;
  12102. var mathSin = Math.sin;
  12103. var mathCos = Math.cos;
  12104. var PI = Math.PI;
  12105. var vMag = function(v) {
  12106. return Math.sqrt(v[0] * v[0] + v[1] * v[1]);
  12107. };
  12108. var vRatio = function(u, v) {
  12109. return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v));
  12110. };
  12111. var vAngle = function(u, v) {
  12112. return (u[0] * v[1] < u[1] * v[0] ? -1 : 1)
  12113. * Math.acos(vRatio(u, v));
  12114. };
  12115. function processArc(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg, cmd, path) {
  12116. var psi = psiDeg * (PI / 180.0);
  12117. var xp = mathCos(psi) * (x1 - x2) / 2.0
  12118. + mathSin(psi) * (y1 - y2) / 2.0;
  12119. var yp = -1 * mathSin(psi) * (x1 - x2) / 2.0
  12120. + mathCos(psi) * (y1 - y2) / 2.0;
  12121. var lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry);
  12122. if (lambda > 1) {
  12123. rx *= mathSqrt(lambda);
  12124. ry *= mathSqrt(lambda);
  12125. }
  12126. var f = (fa === fs ? -1 : 1)
  12127. * mathSqrt((((rx * rx) * (ry * ry))
  12128. - ((rx * rx) * (yp * yp))
  12129. - ((ry * ry) * (xp * xp))) / ((rx * rx) * (yp * yp)
  12130. + (ry * ry) * (xp * xp))
  12131. ) || 0;
  12132. var cxp = f * rx * yp / ry;
  12133. var cyp = f * -ry * xp / rx;
  12134. var cx = (x1 + x2) / 2.0
  12135. + mathCos(psi) * cxp
  12136. - mathSin(psi) * cyp;
  12137. var cy = (y1 + y2) / 2.0
  12138. + mathSin(psi) * cxp
  12139. + mathCos(psi) * cyp;
  12140. var theta = vAngle([ 1, 0 ], [ (xp - cxp) / rx, (yp - cyp) / ry ]);
  12141. var u = [ (xp - cxp) / rx, (yp - cyp) / ry ];
  12142. var v = [ (-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry ];
  12143. var dTheta = vAngle(u, v);
  12144. if (vRatio(u, v) <= -1) {
  12145. dTheta = PI;
  12146. }
  12147. if (vRatio(u, v) >= 1) {
  12148. dTheta = 0;
  12149. }
  12150. if (fs === 0 && dTheta > 0) {
  12151. dTheta = dTheta - 2 * PI;
  12152. }
  12153. if (fs === 1 && dTheta < 0) {
  12154. dTheta = dTheta + 2 * PI;
  12155. }
  12156. path.addData(cmd, cx, cy, rx, ry, theta, dTheta, psi, fs);
  12157. }
  12158. function createPathProxyFromString(data) {
  12159. if (!data) {
  12160. return [];
  12161. }
  12162. // command string
  12163. var cs = data.replace(/-/g, ' -')
  12164. .replace(/ /g, ' ')
  12165. .replace(/ /g, ',')
  12166. .replace(/,,/g, ',');
  12167. var n;
  12168. // create pipes so that we can split the data
  12169. for (n = 0; n < cc.length; n++) {
  12170. cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]);
  12171. }
  12172. // create array
  12173. var arr = cs.split('|');
  12174. // init context point
  12175. var cpx = 0;
  12176. var cpy = 0;
  12177. var path = new PathProxy();
  12178. var CMD = PathProxy.CMD;
  12179. var prevCmd;
  12180. for (n = 1; n < arr.length; n++) {
  12181. var str = arr[n];
  12182. var c = str.charAt(0);
  12183. var off = 0;
  12184. var p = str.slice(1).replace(/e,-/g, 'e-').split(',');
  12185. var cmd;
  12186. if (p.length > 0 && p[0] === '') {
  12187. p.shift();
  12188. }
  12189. for (var i = 0; i < p.length; i++) {
  12190. p[i] = parseFloat(p[i]);
  12191. }
  12192. while (off < p.length && !isNaN(p[off])) {
  12193. if (isNaN(p[0])) {
  12194. break;
  12195. }
  12196. var ctlPtx;
  12197. var ctlPty;
  12198. var rx;
  12199. var ry;
  12200. var psi;
  12201. var fa;
  12202. var fs;
  12203. var x1 = cpx;
  12204. var y1 = cpy;
  12205. // convert l, H, h, V, and v to L
  12206. switch (c) {
  12207. case 'l':
  12208. cpx += p[off++];
  12209. cpy += p[off++];
  12210. cmd = CMD.L;
  12211. path.addData(cmd, cpx, cpy);
  12212. break;
  12213. case 'L':
  12214. cpx = p[off++];
  12215. cpy = p[off++];
  12216. cmd = CMD.L;
  12217. path.addData(cmd, cpx, cpy);
  12218. break;
  12219. case 'm':
  12220. cpx += p[off++];
  12221. cpy += p[off++];
  12222. cmd = CMD.M;
  12223. path.addData(cmd, cpx, cpy);
  12224. c = 'l';
  12225. break;
  12226. case 'M':
  12227. cpx = p[off++];
  12228. cpy = p[off++];
  12229. cmd = CMD.M;
  12230. path.addData(cmd, cpx, cpy);
  12231. c = 'L';
  12232. break;
  12233. case 'h':
  12234. cpx += p[off++];
  12235. cmd = CMD.L;
  12236. path.addData(cmd, cpx, cpy);
  12237. break;
  12238. case 'H':
  12239. cpx = p[off++];
  12240. cmd = CMD.L;
  12241. path.addData(cmd, cpx, cpy);
  12242. break;
  12243. case 'v':
  12244. cpy += p[off++];
  12245. cmd = CMD.L;
  12246. path.addData(cmd, cpx, cpy);
  12247. break;
  12248. case 'V':
  12249. cpy = p[off++];
  12250. cmd = CMD.L;
  12251. path.addData(cmd, cpx, cpy);
  12252. break;
  12253. case 'C':
  12254. cmd = CMD.C;
  12255. path.addData(
  12256. cmd, p[off++], p[off++], p[off++], p[off++], p[off++], p[off++]
  12257. );
  12258. cpx = p[off - 2];
  12259. cpy = p[off - 1];
  12260. break;
  12261. case 'c':
  12262. cmd = CMD.C;
  12263. path.addData(
  12264. cmd,
  12265. p[off++] + cpx, p[off++] + cpy,
  12266. p[off++] + cpx, p[off++] + cpy,
  12267. p[off++] + cpx, p[off++] + cpy
  12268. );
  12269. cpx += p[off - 2];
  12270. cpy += p[off - 1];
  12271. break;
  12272. case 'S':
  12273. ctlPtx = cpx;
  12274. ctlPty = cpy;
  12275. var len = path.len();
  12276. var pathData = path.data;
  12277. if (prevCmd === CMD.C) {
  12278. ctlPtx += cpx - pathData[len - 4];
  12279. ctlPty += cpy - pathData[len - 3];
  12280. }
  12281. cmd = CMD.C;
  12282. x1 = p[off++];
  12283. y1 = p[off++];
  12284. cpx = p[off++];
  12285. cpy = p[off++];
  12286. path.addData(cmd, ctlPtx, ctlPty, x1, y1, cpx, cpy);
  12287. break;
  12288. case 's':
  12289. ctlPtx = cpx;
  12290. ctlPty = cpy;
  12291. var len = path.len();
  12292. var pathData = path.data;
  12293. if (prevCmd === CMD.C) {
  12294. ctlPtx += cpx - pathData[len - 4];
  12295. ctlPty += cpy - pathData[len - 3];
  12296. }
  12297. cmd = CMD.C;
  12298. x1 = cpx + p[off++];
  12299. y1 = cpy + p[off++];
  12300. cpx += p[off++];
  12301. cpy += p[off++];
  12302. path.addData(cmd, ctlPtx, ctlPty, x1, y1, cpx, cpy);
  12303. break;
  12304. case 'Q':
  12305. x1 = p[off++];
  12306. y1 = p[off++];
  12307. cpx = p[off++];
  12308. cpy = p[off++];
  12309. cmd = CMD.Q;
  12310. path.addData(cmd, x1, y1, cpx, cpy);
  12311. break;
  12312. case 'q':
  12313. x1 = p[off++] + cpx;
  12314. y1 = p[off++] + cpy;
  12315. cpx += p[off++];
  12316. cpy += p[off++];
  12317. cmd = CMD.Q;
  12318. path.addData(cmd, x1, y1, cpx, cpy);
  12319. break;
  12320. case 'T':
  12321. ctlPtx = cpx;
  12322. ctlPty = cpy;
  12323. var len = path.len();
  12324. var pathData = path.data;
  12325. if (prevCmd === CMD.Q) {
  12326. ctlPtx += cpx - pathData[len - 4];
  12327. ctlPty += cpy - pathData[len - 3];
  12328. }
  12329. cpx = p[off++];
  12330. cpy = p[off++];
  12331. cmd = CMD.Q;
  12332. path.addData(cmd, ctlPtx, ctlPty, cpx, cpy);
  12333. break;
  12334. case 't':
  12335. ctlPtx = cpx;
  12336. ctlPty = cpy;
  12337. var len = path.len();
  12338. var pathData = path.data;
  12339. if (prevCmd === CMD.Q) {
  12340. ctlPtx += cpx - pathData[len - 4];
  12341. ctlPty += cpy - pathData[len - 3];
  12342. }
  12343. cpx += p[off++];
  12344. cpy += p[off++];
  12345. cmd = CMD.Q;
  12346. path.addData(cmd, ctlPtx, ctlPty, cpx, cpy);
  12347. break;
  12348. case 'A':
  12349. rx = p[off++];
  12350. ry = p[off++];
  12351. psi = p[off++];
  12352. fa = p[off++];
  12353. fs = p[off++];
  12354. x1 = cpx, y1 = cpy;
  12355. cpx = p[off++];
  12356. cpy = p[off++];
  12357. cmd = CMD.A;
  12358. processArc(
  12359. x1, y1, cpx, cpy, fa, fs, rx, ry, psi, cmd, path
  12360. );
  12361. break;
  12362. case 'a':
  12363. rx = p[off++];
  12364. ry = p[off++];
  12365. psi = p[off++];
  12366. fa = p[off++];
  12367. fs = p[off++];
  12368. x1 = cpx, y1 = cpy;
  12369. cpx += p[off++];
  12370. cpy += p[off++];
  12371. cmd = CMD.A;
  12372. processArc(
  12373. x1, y1, cpx, cpy, fa, fs, rx, ry, psi, cmd, path
  12374. );
  12375. break;
  12376. }
  12377. }
  12378. if (c === 'z' || c === 'Z') {
  12379. cmd = CMD.Z;
  12380. path.addData(cmd);
  12381. }
  12382. prevCmd = cmd;
  12383. }
  12384. path.toStatic();
  12385. return path;
  12386. }
  12387. // TODO Optimize double memory cost problem
  12388. function createPathOptions(str, opts) {
  12389. var pathProxy = createPathProxyFromString(str);
  12390. opts = opts || {};
  12391. opts.buildPath = function (path) {
  12392. if (path.setData) {
  12393. path.setData(pathProxy.data);
  12394. // Svg and vml renderer don't have context
  12395. var ctx = path.getContext();
  12396. if (ctx) {
  12397. path.rebuildPath(ctx);
  12398. }
  12399. }
  12400. else {
  12401. var ctx = path;
  12402. pathProxy.rebuildPath(ctx);
  12403. }
  12404. };
  12405. opts.applyTransform = function (m) {
  12406. transformPath(pathProxy, m);
  12407. this.dirty(true);
  12408. };
  12409. return opts;
  12410. }
  12411. /**
  12412. * Create a Path object from path string data
  12413. * http://www.w3.org/TR/SVG/paths.html#PathData
  12414. * @param {Object} opts Other options
  12415. */
  12416. function createFromString(str, opts) {
  12417. return new Path(createPathOptions(str, opts));
  12418. }
  12419. /**
  12420. * Create a Path class from path string data
  12421. * @param {string} str
  12422. * @param {Object} opts Other options
  12423. */
  12424. function extendFromString(str, opts) {
  12425. return Path.extend(createPathOptions(str, opts));
  12426. }
  12427. /**
  12428. * Merge multiple paths
  12429. */
  12430. // TODO Apply transform
  12431. // TODO stroke dash
  12432. // TODO Optimize double memory cost problem
  12433. function mergePath$1(pathEls, opts) {
  12434. var pathList = [];
  12435. var len = pathEls.length;
  12436. for (var i = 0; i < len; i++) {
  12437. var pathEl = pathEls[i];
  12438. if (!pathEl.path) {
  12439. pathEl.createPathProxy();
  12440. }
  12441. if (pathEl.__dirtyPath) {
  12442. pathEl.buildPath(pathEl.path, pathEl.shape, true);
  12443. }
  12444. pathList.push(pathEl.path);
  12445. }
  12446. var pathBundle = new Path(opts);
  12447. // Need path proxy.
  12448. pathBundle.createPathProxy();
  12449. pathBundle.buildPath = function (path) {
  12450. path.appendPath(pathList);
  12451. // Svg and vml renderer don't have context
  12452. var ctx = path.getContext();
  12453. if (ctx) {
  12454. path.rebuildPath(ctx);
  12455. }
  12456. };
  12457. return pathBundle;
  12458. }
  12459. /**
  12460. * @alias zrender/graphic/Text
  12461. * @extends module:zrender/graphic/Displayable
  12462. * @constructor
  12463. * @param {Object} opts
  12464. */
  12465. var Text = function (opts) { // jshint ignore:line
  12466. Displayable.call(this, opts);
  12467. };
  12468. Text.prototype = {
  12469. constructor: Text,
  12470. type: 'text',
  12471. brush: function (ctx, prevEl) {
  12472. var style = this.style;
  12473. // Optimize, avoid normalize every time.
  12474. this.__dirty && normalizeTextStyle(style, true);
  12475. // Use props with prefix 'text'.
  12476. style.fill = style.stroke = style.shadowBlur = style.shadowColor =
  12477. style.shadowOffsetX = style.shadowOffsetY = null;
  12478. var text = style.text;
  12479. // Convert to string
  12480. text != null && (text += '');
  12481. // Always bind style
  12482. style.bind(ctx, this, prevEl);
  12483. if (!needDrawText(text, style)) {
  12484. return;
  12485. }
  12486. this.setTransform(ctx);
  12487. renderText(this, ctx, text, style);
  12488. this.restoreTransform(ctx);
  12489. },
  12490. getBoundingRect: function () {
  12491. var style = this.style;
  12492. // Optimize, avoid normalize every time.
  12493. this.__dirty && normalizeTextStyle(style, true);
  12494. if (!this._rect) {
  12495. var text = style.text;
  12496. text != null ? (text += '') : (text = '');
  12497. var rect = getBoundingRect(
  12498. style.text + '',
  12499. style.font,
  12500. style.textAlign,
  12501. style.textVerticalAlign,
  12502. style.textPadding,
  12503. style.rich
  12504. );
  12505. rect.x += style.x || 0;
  12506. rect.y += style.y || 0;
  12507. if (getStroke(style.textStroke, style.textStrokeWidth)) {
  12508. var w = style.textStrokeWidth;
  12509. rect.x -= w / 2;
  12510. rect.y -= w / 2;
  12511. rect.width += w;
  12512. rect.height += w;
  12513. }
  12514. this._rect = rect;
  12515. }
  12516. return this._rect;
  12517. }
  12518. };
  12519. inherits(Text, Displayable);
  12520. /**
  12521. * 圆形
  12522. * @module zrender/shape/Circle
  12523. */
  12524. var Circle = Path.extend({
  12525. type: 'circle',
  12526. shape: {
  12527. cx: 0,
  12528. cy: 0,
  12529. r: 0
  12530. },
  12531. buildPath : function (ctx, shape, inBundle) {
  12532. // Better stroking in ShapeBundle
  12533. // Always do it may have performence issue ( fill may be 2x more cost)
  12534. if (inBundle) {
  12535. ctx.moveTo(shape.cx + shape.r, shape.cy);
  12536. }
  12537. // else {
  12538. // if (ctx.allocate && !ctx.data.length) {
  12539. // ctx.allocate(ctx.CMD_MEM_SIZE.A);
  12540. // }
  12541. // }
  12542. // Better stroking in ShapeBundle
  12543. // ctx.moveTo(shape.cx + shape.r, shape.cy);
  12544. ctx.arc(shape.cx, shape.cy, shape.r, 0, Math.PI * 2, true);
  12545. }
  12546. });
  12547. // Fix weird bug in some version of IE11 (like 11.0.9600.178**),
  12548. // where exception "unexpected call to method or property access"
  12549. // might be thrown when calling ctx.fill or ctx.stroke after a path
  12550. // whose area size is zero is drawn and ctx.clip() is called and
  12551. // shadowBlur is set. See #4572, #3112, #5777.
  12552. // (e.g.,
  12553. // ctx.moveTo(10, 10);
  12554. // ctx.lineTo(20, 10);
  12555. // ctx.closePath();
  12556. // ctx.clip();
  12557. // ctx.shadowBlur = 10;
  12558. // ...
  12559. // ctx.fill();
  12560. // )
  12561. var shadowTemp = [
  12562. ['shadowBlur', 0],
  12563. ['shadowColor', '#000'],
  12564. ['shadowOffsetX', 0],
  12565. ['shadowOffsetY', 0]
  12566. ];
  12567. var fixClipWithShadow = function (orignalBrush) {
  12568. // version string can be: '11.0'
  12569. return (env$1.browser.ie && env$1.browser.version >= 11)
  12570. ? function () {
  12571. var clipPaths = this.__clipPaths;
  12572. var style = this.style;
  12573. var modified;
  12574. if (clipPaths) {
  12575. for (var i = 0; i < clipPaths.length; i++) {
  12576. var clipPath = clipPaths[i];
  12577. var shape = clipPath && clipPath.shape;
  12578. var type = clipPath && clipPath.type;
  12579. if (shape && (
  12580. (type === 'sector' && shape.startAngle === shape.endAngle)
  12581. || (type === 'rect' && (!shape.width || !shape.height))
  12582. )) {
  12583. for (var j = 0; j < shadowTemp.length; j++) {
  12584. // It is save to put shadowTemp static, because shadowTemp
  12585. // will be all modified each item brush called.
  12586. shadowTemp[j][2] = style[shadowTemp[j][0]];
  12587. style[shadowTemp[j][0]] = shadowTemp[j][1];
  12588. }
  12589. modified = true;
  12590. break;
  12591. }
  12592. }
  12593. }
  12594. orignalBrush.apply(this, arguments);
  12595. if (modified) {
  12596. for (var j = 0; j < shadowTemp.length; j++) {
  12597. style[shadowTemp[j][0]] = shadowTemp[j][2];
  12598. }
  12599. }
  12600. }
  12601. : orignalBrush;
  12602. };
  12603. /**
  12604. * 扇形
  12605. * @module zrender/graphic/shape/Sector
  12606. */
  12607. var Sector = Path.extend({
  12608. type: 'sector',
  12609. shape: {
  12610. cx: 0,
  12611. cy: 0,
  12612. r0: 0,
  12613. r: 0,
  12614. startAngle: 0,
  12615. endAngle: Math.PI * 2,
  12616. clockwise: true
  12617. },
  12618. brush: fixClipWithShadow(Path.prototype.brush),
  12619. buildPath: function (ctx, shape) {
  12620. var x = shape.cx;
  12621. var y = shape.cy;
  12622. var r0 = Math.max(shape.r0 || 0, 0);
  12623. var r = Math.max(shape.r, 0);
  12624. var startAngle = shape.startAngle;
  12625. var endAngle = shape.endAngle;
  12626. var clockwise = shape.clockwise;
  12627. var unitX = Math.cos(startAngle);
  12628. var unitY = Math.sin(startAngle);
  12629. ctx.moveTo(unitX * r0 + x, unitY * r0 + y);
  12630. ctx.lineTo(unitX * r + x, unitY * r + y);
  12631. ctx.arc(x, y, r, startAngle, endAngle, !clockwise);
  12632. ctx.lineTo(
  12633. Math.cos(endAngle) * r0 + x,
  12634. Math.sin(endAngle) * r0 + y
  12635. );
  12636. if (r0 !== 0) {
  12637. ctx.arc(x, y, r0, endAngle, startAngle, clockwise);
  12638. }
  12639. ctx.closePath();
  12640. }
  12641. });
  12642. /**
  12643. * 圆环
  12644. * @module zrender/graphic/shape/Ring
  12645. */
  12646. var Ring = Path.extend({
  12647. type: 'ring',
  12648. shape: {
  12649. cx: 0,
  12650. cy: 0,
  12651. r: 0,
  12652. r0: 0
  12653. },
  12654. buildPath: function (ctx, shape) {
  12655. var x = shape.cx;
  12656. var y = shape.cy;
  12657. var PI2 = Math.PI * 2;
  12658. ctx.moveTo(x + shape.r, y);
  12659. ctx.arc(x, y, shape.r, 0, PI2, false);
  12660. ctx.moveTo(x + shape.r0, y);
  12661. ctx.arc(x, y, shape.r0, 0, PI2, true);
  12662. }
  12663. });
  12664. /**
  12665. * Catmull-Rom spline 插值折线
  12666. * @module zrender/shape/util/smoothSpline
  12667. * @author pissang (https://www.github.com/pissang)
  12668. * Kener (@Kener-林峰, kener.linfeng@gmail.com)
  12669. * errorrik (errorrik@gmail.com)
  12670. */
  12671. /**
  12672. * @inner
  12673. */
  12674. function interpolate(p0, p1, p2, p3, t, t2, t3) {
  12675. var v0 = (p2 - p0) * 0.5;
  12676. var v1 = (p3 - p1) * 0.5;
  12677. return (2 * (p1 - p2) + v0 + v1) * t3
  12678. + (-3 * (p1 - p2) - 2 * v0 - v1) * t2
  12679. + v0 * t + p1;
  12680. }
  12681. /**
  12682. * @alias module:zrender/shape/util/smoothSpline
  12683. * @param {Array} points 线段顶点数组
  12684. * @param {boolean} isLoop
  12685. * @return {Array}
  12686. */
  12687. var smoothSpline = function (points, isLoop) {
  12688. var len$$1 = points.length;
  12689. var ret = [];
  12690. var distance$$1 = 0;
  12691. for (var i = 1; i < len$$1; i++) {
  12692. distance$$1 += distance(points[i - 1], points[i]);
  12693. }
  12694. var segs = distance$$1 / 2;
  12695. segs = segs < len$$1 ? len$$1 : segs;
  12696. for (var i = 0; i < segs; i++) {
  12697. var pos = i / (segs - 1) * (isLoop ? len$$1 : len$$1 - 1);
  12698. var idx = Math.floor(pos);
  12699. var w = pos - idx;
  12700. var p0;
  12701. var p1 = points[idx % len$$1];
  12702. var p2;
  12703. var p3;
  12704. if (!isLoop) {
  12705. p0 = points[idx === 0 ? idx : idx - 1];
  12706. p2 = points[idx > len$$1 - 2 ? len$$1 - 1 : idx + 1];
  12707. p3 = points[idx > len$$1 - 3 ? len$$1 - 1 : idx + 2];
  12708. }
  12709. else {
  12710. p0 = points[(idx - 1 + len$$1) % len$$1];
  12711. p2 = points[(idx + 1) % len$$1];
  12712. p3 = points[(idx + 2) % len$$1];
  12713. }
  12714. var w2 = w * w;
  12715. var w3 = w * w2;
  12716. ret.push([
  12717. interpolate(p0[0], p1[0], p2[0], p3[0], w, w2, w3),
  12718. interpolate(p0[1], p1[1], p2[1], p3[1], w, w2, w3)
  12719. ]);
  12720. }
  12721. return ret;
  12722. };
  12723. /**
  12724. * 贝塞尔平滑曲线
  12725. * @module zrender/shape/util/smoothBezier
  12726. * @author pissang (https://www.github.com/pissang)
  12727. * Kener (@Kener-林峰, kener.linfeng@gmail.com)
  12728. * errorrik (errorrik@gmail.com)
  12729. */
  12730. /**
  12731. * 贝塞尔平滑曲线
  12732. * @alias module:zrender/shape/util/smoothBezier
  12733. * @param {Array} points 线段顶点数组
  12734. * @param {number} smooth 平滑等级, 0-1
  12735. * @param {boolean} isLoop
  12736. * @param {Array} constraint 将计算出来的控制点约束在一个包围盒内
  12737. * 比如 [[0, 0], [100, 100]], 这个包围盒会与
  12738. * 整个折线的包围盒做一个并集用来约束控制点。
  12739. * @param {Array} 计算出来的控制点数组
  12740. */
  12741. var smoothBezier = function (points, smooth, isLoop, constraint) {
  12742. var cps = [];
  12743. var v = [];
  12744. var v1 = [];
  12745. var v2 = [];
  12746. var prevPoint;
  12747. var nextPoint;
  12748. var min$$1, max$$1;
  12749. if (constraint) {
  12750. min$$1 = [Infinity, Infinity];
  12751. max$$1 = [-Infinity, -Infinity];
  12752. for (var i = 0, len$$1 = points.length; i < len$$1; i++) {
  12753. min(min$$1, min$$1, points[i]);
  12754. max(max$$1, max$$1, points[i]);
  12755. }
  12756. // 与指定的包围盒做并集
  12757. min(min$$1, min$$1, constraint[0]);
  12758. max(max$$1, max$$1, constraint[1]);
  12759. }
  12760. for (var i = 0, len$$1 = points.length; i < len$$1; i++) {
  12761. var point = points[i];
  12762. if (isLoop) {
  12763. prevPoint = points[i ? i - 1 : len$$1 - 1];
  12764. nextPoint = points[(i + 1) % len$$1];
  12765. }
  12766. else {
  12767. if (i === 0 || i === len$$1 - 1) {
  12768. cps.push(clone$1(points[i]));
  12769. continue;
  12770. }
  12771. else {
  12772. prevPoint = points[i - 1];
  12773. nextPoint = points[i + 1];
  12774. }
  12775. }
  12776. sub(v, nextPoint, prevPoint);
  12777. // use degree to scale the handle length
  12778. scale(v, v, smooth);
  12779. var d0 = distance(point, prevPoint);
  12780. var d1 = distance(point, nextPoint);
  12781. var sum = d0 + d1;
  12782. if (sum !== 0) {
  12783. d0 /= sum;
  12784. d1 /= sum;
  12785. }
  12786. scale(v1, v, -d0);
  12787. scale(v2, v, d1);
  12788. var cp0 = add([], point, v1);
  12789. var cp1 = add([], point, v2);
  12790. if (constraint) {
  12791. max(cp0, cp0, min$$1);
  12792. min(cp0, cp0, max$$1);
  12793. max(cp1, cp1, min$$1);
  12794. min(cp1, cp1, max$$1);
  12795. }
  12796. cps.push(cp0);
  12797. cps.push(cp1);
  12798. }
  12799. if (isLoop) {
  12800. cps.push(cps.shift());
  12801. }
  12802. return cps;
  12803. };
  12804. function buildPath$1(ctx, shape, closePath) {
  12805. var points = shape.points;
  12806. var smooth = shape.smooth;
  12807. if (points && points.length >= 2) {
  12808. if (smooth && smooth !== 'spline') {
  12809. var controlPoints = smoothBezier(
  12810. points, smooth, closePath, shape.smoothConstraint
  12811. );
  12812. ctx.moveTo(points[0][0], points[0][1]);
  12813. var len = points.length;
  12814. for (var i = 0; i < (closePath ? len : len - 1); i++) {
  12815. var cp1 = controlPoints[i * 2];
  12816. var cp2 = controlPoints[i * 2 + 1];
  12817. var p = points[(i + 1) % len];
  12818. ctx.bezierCurveTo(
  12819. cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1]
  12820. );
  12821. }
  12822. }
  12823. else {
  12824. if (smooth === 'spline') {
  12825. points = smoothSpline(points, closePath);
  12826. }
  12827. ctx.moveTo(points[0][0], points[0][1]);
  12828. for (var i = 1, l = points.length; i < l; i++) {
  12829. ctx.lineTo(points[i][0], points[i][1]);
  12830. }
  12831. }
  12832. closePath && ctx.closePath();
  12833. }
  12834. }
  12835. /**
  12836. * 多边形
  12837. * @module zrender/shape/Polygon
  12838. */
  12839. var Polygon = Path.extend({
  12840. type: 'polygon',
  12841. shape: {
  12842. points: null,
  12843. smooth: false,
  12844. smoothConstraint: null
  12845. },
  12846. buildPath: function (ctx, shape) {
  12847. buildPath$1(ctx, shape, true);
  12848. }
  12849. });
  12850. /**
  12851. * @module zrender/graphic/shape/Polyline
  12852. */
  12853. var Polyline = Path.extend({
  12854. type: 'polyline',
  12855. shape: {
  12856. points: null,
  12857. smooth: false,
  12858. smoothConstraint: null
  12859. },
  12860. style: {
  12861. stroke: '#000',
  12862. fill: null
  12863. },
  12864. buildPath: function (ctx, shape) {
  12865. buildPath$1(ctx, shape, false);
  12866. }
  12867. });
  12868. /**
  12869. * 矩形
  12870. * @module zrender/graphic/shape/Rect
  12871. */
  12872. var Rect = Path.extend({
  12873. type: 'rect',
  12874. shape: {
  12875. // 左上、右上、右下、左下角的半径依次为r1、r2、r3、r4
  12876. // r缩写为1 相当于 [1, 1, 1, 1]
  12877. // r缩写为[1] 相当于 [1, 1, 1, 1]
  12878. // r缩写为[1, 2] 相当于 [1, 2, 1, 2]
  12879. // r缩写为[1, 2, 3] 相当于 [1, 2, 3, 2]
  12880. r: 0,
  12881. x: 0,
  12882. y: 0,
  12883. width: 0,
  12884. height: 0
  12885. },
  12886. buildPath: function (ctx, shape) {
  12887. var x = shape.x;
  12888. var y = shape.y;
  12889. var width = shape.width;
  12890. var height = shape.height;
  12891. if (!shape.r) {
  12892. ctx.rect(x, y, width, height);
  12893. }
  12894. else {
  12895. buildPath(ctx, shape);
  12896. }
  12897. ctx.closePath();
  12898. return;
  12899. }
  12900. });
  12901. /**
  12902. * 直线
  12903. * @module zrender/graphic/shape/Line
  12904. */
  12905. var Line = Path.extend({
  12906. type: 'line',
  12907. shape: {
  12908. // Start point
  12909. x1: 0,
  12910. y1: 0,
  12911. // End point
  12912. x2: 0,
  12913. y2: 0,
  12914. percent: 1
  12915. },
  12916. style: {
  12917. stroke: '#000',
  12918. fill: null
  12919. },
  12920. buildPath: function (ctx, shape) {
  12921. var x1 = shape.x1;
  12922. var y1 = shape.y1;
  12923. var x2 = shape.x2;
  12924. var y2 = shape.y2;
  12925. var percent = shape.percent;
  12926. if (percent === 0) {
  12927. return;
  12928. }
  12929. ctx.moveTo(x1, y1);
  12930. if (percent < 1) {
  12931. x2 = x1 * (1 - percent) + x2 * percent;
  12932. y2 = y1 * (1 - percent) + y2 * percent;
  12933. }
  12934. ctx.lineTo(x2, y2);
  12935. },
  12936. /**
  12937. * Get point at percent
  12938. * @param {number} percent
  12939. * @return {Array.<number>}
  12940. */
  12941. pointAt: function (p) {
  12942. var shape = this.shape;
  12943. return [
  12944. shape.x1 * (1 - p) + shape.x2 * p,
  12945. shape.y1 * (1 - p) + shape.y2 * p
  12946. ];
  12947. }
  12948. });
  12949. /**
  12950. * 贝塞尔曲线
  12951. * @module zrender/shape/BezierCurve
  12952. */
  12953. var out = [];
  12954. function someVectorAt(shape, t, isTangent) {
  12955. var cpx2 = shape.cpx2;
  12956. var cpy2 = shape.cpy2;
  12957. if (cpx2 === null || cpy2 === null) {
  12958. return [
  12959. (isTangent ? cubicDerivativeAt : cubicAt)(shape.x1, shape.cpx1, shape.cpx2, shape.x2, t),
  12960. (isTangent ? cubicDerivativeAt : cubicAt)(shape.y1, shape.cpy1, shape.cpy2, shape.y2, t)
  12961. ];
  12962. }
  12963. else {
  12964. return [
  12965. (isTangent ? quadraticDerivativeAt : quadraticAt)(shape.x1, shape.cpx1, shape.x2, t),
  12966. (isTangent ? quadraticDerivativeAt : quadraticAt)(shape.y1, shape.cpy1, shape.y2, t)
  12967. ];
  12968. }
  12969. }
  12970. var BezierCurve = Path.extend({
  12971. type: 'bezier-curve',
  12972. shape: {
  12973. x1: 0,
  12974. y1: 0,
  12975. x2: 0,
  12976. y2: 0,
  12977. cpx1: 0,
  12978. cpy1: 0,
  12979. // cpx2: 0,
  12980. // cpy2: 0
  12981. // Curve show percent, for animating
  12982. percent: 1
  12983. },
  12984. style: {
  12985. stroke: '#000',
  12986. fill: null
  12987. },
  12988. buildPath: function (ctx, shape) {
  12989. var x1 = shape.x1;
  12990. var y1 = shape.y1;
  12991. var x2 = shape.x2;
  12992. var y2 = shape.y2;
  12993. var cpx1 = shape.cpx1;
  12994. var cpy1 = shape.cpy1;
  12995. var cpx2 = shape.cpx2;
  12996. var cpy2 = shape.cpy2;
  12997. var percent = shape.percent;
  12998. if (percent === 0) {
  12999. return;
  13000. }
  13001. ctx.moveTo(x1, y1);
  13002. if (cpx2 == null || cpy2 == null) {
  13003. if (percent < 1) {
  13004. quadraticSubdivide(
  13005. x1, cpx1, x2, percent, out
  13006. );
  13007. cpx1 = out[1];
  13008. x2 = out[2];
  13009. quadraticSubdivide(
  13010. y1, cpy1, y2, percent, out
  13011. );
  13012. cpy1 = out[1];
  13013. y2 = out[2];
  13014. }
  13015. ctx.quadraticCurveTo(
  13016. cpx1, cpy1,
  13017. x2, y2
  13018. );
  13019. }
  13020. else {
  13021. if (percent < 1) {
  13022. cubicSubdivide(
  13023. x1, cpx1, cpx2, x2, percent, out
  13024. );
  13025. cpx1 = out[1];
  13026. cpx2 = out[2];
  13027. x2 = out[3];
  13028. cubicSubdivide(
  13029. y1, cpy1, cpy2, y2, percent, out
  13030. );
  13031. cpy1 = out[1];
  13032. cpy2 = out[2];
  13033. y2 = out[3];
  13034. }
  13035. ctx.bezierCurveTo(
  13036. cpx1, cpy1,
  13037. cpx2, cpy2,
  13038. x2, y2
  13039. );
  13040. }
  13041. },
  13042. /**
  13043. * Get point at percent
  13044. * @param {number} t
  13045. * @return {Array.<number>}
  13046. */
  13047. pointAt: function (t) {
  13048. return someVectorAt(this.shape, t, false);
  13049. },
  13050. /**
  13051. * Get tangent at percent
  13052. * @param {number} t
  13053. * @return {Array.<number>}
  13054. */
  13055. tangentAt: function (t) {
  13056. var p = someVectorAt(this.shape, t, true);
  13057. return normalize(p, p);
  13058. }
  13059. });
  13060. /**
  13061. * 圆弧
  13062. * @module zrender/graphic/shape/Arc
  13063. */
  13064. var Arc = Path.extend({
  13065. type: 'arc',
  13066. shape: {
  13067. cx: 0,
  13068. cy: 0,
  13069. r: 0,
  13070. startAngle: 0,
  13071. endAngle: Math.PI * 2,
  13072. clockwise: true
  13073. },
  13074. style: {
  13075. stroke: '#000',
  13076. fill: null
  13077. },
  13078. buildPath: function (ctx, shape) {
  13079. var x = shape.cx;
  13080. var y = shape.cy;
  13081. var r = Math.max(shape.r, 0);
  13082. var startAngle = shape.startAngle;
  13083. var endAngle = shape.endAngle;
  13084. var clockwise = shape.clockwise;
  13085. var unitX = Math.cos(startAngle);
  13086. var unitY = Math.sin(startAngle);
  13087. ctx.moveTo(unitX * r + x, unitY * r + y);
  13088. ctx.arc(x, y, r, startAngle, endAngle, !clockwise);
  13089. }
  13090. });
  13091. // CompoundPath to improve performance
  13092. var CompoundPath = Path.extend({
  13093. type: 'compound',
  13094. shape: {
  13095. paths: null
  13096. },
  13097. _updatePathDirty: function () {
  13098. var dirtyPath = this.__dirtyPath;
  13099. var paths = this.shape.paths;
  13100. for (var i = 0; i < paths.length; i++) {
  13101. // Mark as dirty if any subpath is dirty
  13102. dirtyPath = dirtyPath || paths[i].__dirtyPath;
  13103. }
  13104. this.__dirtyPath = dirtyPath;
  13105. this.__dirty = this.__dirty || dirtyPath;
  13106. },
  13107. beforeBrush: function () {
  13108. this._updatePathDirty();
  13109. var paths = this.shape.paths || [];
  13110. var scale = this.getGlobalScale();
  13111. // Update path scale
  13112. for (var i = 0; i < paths.length; i++) {
  13113. if (!paths[i].path) {
  13114. paths[i].createPathProxy();
  13115. }
  13116. paths[i].path.setScale(scale[0], scale[1]);
  13117. }
  13118. },
  13119. buildPath: function (ctx, shape) {
  13120. var paths = shape.paths || [];
  13121. for (var i = 0; i < paths.length; i++) {
  13122. paths[i].buildPath(ctx, paths[i].shape, true);
  13123. }
  13124. },
  13125. afterBrush: function () {
  13126. var paths = this.shape.paths || [];
  13127. for (var i = 0; i < paths.length; i++) {
  13128. paths[i].__dirtyPath = false;
  13129. }
  13130. },
  13131. getBoundingRect: function () {
  13132. this._updatePathDirty();
  13133. return Path.prototype.getBoundingRect.call(this);
  13134. }
  13135. });
  13136. /**
  13137. * @param {Array.<Object>} colorStops
  13138. */
  13139. var Gradient = function (colorStops) {
  13140. this.colorStops = colorStops || [];
  13141. };
  13142. Gradient.prototype = {
  13143. constructor: Gradient,
  13144. addColorStop: function (offset, color) {
  13145. this.colorStops.push({
  13146. offset: offset,
  13147. color: color
  13148. });
  13149. }
  13150. };
  13151. /**
  13152. * x, y, x2, y2 are all percent from 0 to 1
  13153. * @param {number} [x=0]
  13154. * @param {number} [y=0]
  13155. * @param {number} [x2=1]
  13156. * @param {number} [y2=0]
  13157. * @param {Array.<Object>} colorStops
  13158. * @param {boolean} [globalCoord=false]
  13159. */
  13160. var LinearGradient = function (x, y, x2, y2, colorStops, globalCoord) {
  13161. // Should do nothing more in this constructor. Because gradient can be
  13162. // declard by `color: {type: 'linear', colorStops: ...}`, where
  13163. // this constructor will not be called.
  13164. this.x = x == null ? 0 : x;
  13165. this.y = y == null ? 0 : y;
  13166. this.x2 = x2 == null ? 1 : x2;
  13167. this.y2 = y2 == null ? 0 : y2;
  13168. // Can be cloned
  13169. this.type = 'linear';
  13170. // If use global coord
  13171. this.global = globalCoord || false;
  13172. Gradient.call(this, colorStops);
  13173. };
  13174. LinearGradient.prototype = {
  13175. constructor: LinearGradient
  13176. };
  13177. inherits(LinearGradient, Gradient);
  13178. /**
  13179. * x, y, r are all percent from 0 to 1
  13180. * @param {number} [x=0.5]
  13181. * @param {number} [y=0.5]
  13182. * @param {number} [r=0.5]
  13183. * @param {Array.<Object>} [colorStops]
  13184. * @param {boolean} [globalCoord=false]
  13185. */
  13186. var RadialGradient = function (x, y, r, colorStops, globalCoord) {
  13187. // Should do nothing more in this constructor. Because gradient can be
  13188. // declard by `color: {type: 'radial', colorStops: ...}`, where
  13189. // this constructor will not be called.
  13190. this.x = x == null ? 0.5 : x;
  13191. this.y = y == null ? 0.5 : y;
  13192. this.r = r == null ? 0.5 : r;
  13193. // Can be cloned
  13194. this.type = 'radial';
  13195. // If use global coord
  13196. this.global = globalCoord || false;
  13197. Gradient.call(this, colorStops);
  13198. };
  13199. RadialGradient.prototype = {
  13200. constructor: RadialGradient
  13201. };
  13202. inherits(RadialGradient, Gradient);
  13203. /**
  13204. * Displayable for incremental rendering. It will be rendered in a separate layer
  13205. * IncrementalDisplay have too main methods. `clearDisplayables` and `addDisplayables`
  13206. * addDisplayables will render the added displayables incremetally.
  13207. *
  13208. * It use a not clearFlag to tell the painter don't clear the layer if it's the first element.
  13209. */
  13210. // TODO Style override ?
  13211. function IncrementalDisplayble(opts) {
  13212. Displayable.call(this, opts);
  13213. this._displayables = [];
  13214. this._temporaryDisplayables = [];
  13215. this._cursor = 0;
  13216. this.notClear = true;
  13217. }
  13218. IncrementalDisplayble.prototype.incremental = true;
  13219. IncrementalDisplayble.prototype.clearDisplaybles = function () {
  13220. this._displayables = [];
  13221. this._temporaryDisplayables = [];
  13222. this._cursor = 0;
  13223. this.dirty();
  13224. this.notClear = false;
  13225. };
  13226. IncrementalDisplayble.prototype.addDisplayable = function (displayable, notPersistent) {
  13227. if (notPersistent) {
  13228. this._temporaryDisplayables.push(displayable);
  13229. }
  13230. else {
  13231. this._displayables.push(displayable);
  13232. }
  13233. this.dirty();
  13234. };
  13235. IncrementalDisplayble.prototype.addDisplayables = function (displayables, notPersistent) {
  13236. notPersistent = notPersistent || false;
  13237. for (var i = 0; i < displayables.length; i++) {
  13238. this.addDisplayable(displayables[i], notPersistent);
  13239. }
  13240. };
  13241. IncrementalDisplayble.prototype.eachPendingDisplayable = function (cb) {
  13242. for (var i = this._cursor; i < this._displayables.length; i++) {
  13243. cb && cb(this._displayables[i]);
  13244. }
  13245. for (var i = 0; i < this._temporaryDisplayables.length; i++) {
  13246. cb && cb(this._temporaryDisplayables[i]);
  13247. }
  13248. };
  13249. IncrementalDisplayble.prototype.update = function () {
  13250. this.updateTransform();
  13251. for (var i = this._cursor; i < this._displayables.length; i++) {
  13252. var displayable = this._displayables[i];
  13253. // PENDING
  13254. displayable.parent = this;
  13255. displayable.update();
  13256. displayable.parent = null;
  13257. }
  13258. for (var i = 0; i < this._temporaryDisplayables.length; i++) {
  13259. var displayable = this._temporaryDisplayables[i];
  13260. // PENDING
  13261. displayable.parent = this;
  13262. displayable.update();
  13263. displayable.parent = null;
  13264. }
  13265. };
  13266. IncrementalDisplayble.prototype.brush = function (ctx, prevEl) {
  13267. // Render persistant displayables.
  13268. for (var i = this._cursor; i < this._displayables.length; i++) {
  13269. var displayable = this._temporaryDisplayables[i];
  13270. displayable.beforeBrush && displayable.beforeBrush(ctx);
  13271. displayable.brush(ctx, i === this._cursor ? null : this._displayables[i - 1]);
  13272. displayable.afterBrush && displayable.afterBrush(ctx);
  13273. }
  13274. this._cursor = i;
  13275. // Render temporary displayables.
  13276. for (var i = 0; i < this._temporaryDisplayables.length; i++) {
  13277. var displayable = this._temporaryDisplayables[i];
  13278. displayable.beforeBrush && displayable.beforeBrush(ctx);
  13279. displayable.brush(ctx, i === 0 ? null : this._temporaryDisplayables[i - 1]);
  13280. displayable.afterBrush && displayable.afterBrush(ctx);
  13281. }
  13282. this._temporaryDisplayables = [];
  13283. this.notClear = true;
  13284. };
  13285. var m = [];
  13286. IncrementalDisplayble.prototype.getBoundingRect = function () {
  13287. if (!this._rect) {
  13288. var rect = new BoundingRect(Infinity, Infinity, -Infinity, -Infinity);
  13289. for (var i = 0; i < this._displayables.length; i++) {
  13290. var displayable = this._displayables[i];
  13291. var childRect = displayable.getBoundingRect().clone();
  13292. if (displayable.needLocalTransform()) {
  13293. childRect.applyTransform(displayable.getLocalTransform(m));
  13294. }
  13295. rect.union(childRect);
  13296. }
  13297. this._rect = rect;
  13298. }
  13299. return this._rect;
  13300. };
  13301. IncrementalDisplayble.prototype.contain = function (x, y) {
  13302. var localPos = this.transformCoordToLocal(x, y);
  13303. var rect = this.getBoundingRect();
  13304. if (rect.contain(localPos[0], localPos[1])) {
  13305. for (var i = 0; i < this._displayables.length; i++) {
  13306. var displayable = this._displayables[i];
  13307. if (displayable.contain(x, y)) {
  13308. return true;
  13309. }
  13310. }
  13311. }
  13312. return false;
  13313. };
  13314. inherits(IncrementalDisplayble, Displayable);
  13315. var round = Math.round;
  13316. var mathMax$1 = Math.max;
  13317. var mathMin$1 = Math.min;
  13318. var EMPTY_OBJ = {};
  13319. /**
  13320. * Extend shape with parameters
  13321. */
  13322. function extendShape(opts) {
  13323. return Path.extend(opts);
  13324. }
  13325. /**
  13326. * Extend path
  13327. */
  13328. function extendPath(pathData, opts) {
  13329. return extendFromString(pathData, opts);
  13330. }
  13331. /**
  13332. * Create a path element from path data string
  13333. * @param {string} pathData
  13334. * @param {Object} opts
  13335. * @param {module:zrender/core/BoundingRect} rect
  13336. * @param {string} [layout=cover] 'center' or 'cover'
  13337. */
  13338. function makePath(pathData, opts, rect, layout) {
  13339. var path = createFromString(pathData, opts);
  13340. var boundingRect = path.getBoundingRect();
  13341. if (rect) {
  13342. if (layout === 'center') {
  13343. rect = centerGraphic(rect, boundingRect);
  13344. }
  13345. resizePath(path, rect);
  13346. }
  13347. return path;
  13348. }
  13349. /**
  13350. * Create a image element from image url
  13351. * @param {string} imageUrl image url
  13352. * @param {Object} opts options
  13353. * @param {module:zrender/core/BoundingRect} rect constrain rect
  13354. * @param {string} [layout=cover] 'center' or 'cover'
  13355. */
  13356. function makeImage(imageUrl, rect, layout) {
  13357. var path = new ZImage({
  13358. style: {
  13359. image: imageUrl,
  13360. x: rect.x,
  13361. y: rect.y,
  13362. width: rect.width,
  13363. height: rect.height
  13364. },
  13365. onload: function (img) {
  13366. if (layout === 'center') {
  13367. var boundingRect = {
  13368. width: img.width,
  13369. height: img.height
  13370. };
  13371. path.setStyle(centerGraphic(rect, boundingRect));
  13372. }
  13373. }
  13374. });
  13375. return path;
  13376. }
  13377. /**
  13378. * Get position of centered element in bounding box.
  13379. *
  13380. * @param {Object} rect element local bounding box
  13381. * @param {Object} boundingRect constraint bounding box
  13382. * @return {Object} element position containing x, y, width, and height
  13383. */
  13384. function centerGraphic(rect, boundingRect) {
  13385. // Set rect to center, keep width / height ratio.
  13386. var aspect = boundingRect.width / boundingRect.height;
  13387. var width = rect.height * aspect;
  13388. var height;
  13389. if (width <= rect.width) {
  13390. height = rect.height;
  13391. }
  13392. else {
  13393. width = rect.width;
  13394. height = width / aspect;
  13395. }
  13396. var cx = rect.x + rect.width / 2;
  13397. var cy = rect.y + rect.height / 2;
  13398. return {
  13399. x: cx - width / 2,
  13400. y: cy - height / 2,
  13401. width: width,
  13402. height: height
  13403. };
  13404. }
  13405. var mergePath = mergePath$1;
  13406. /**
  13407. * Resize a path to fit the rect
  13408. * @param {module:zrender/graphic/Path} path
  13409. * @param {Object} rect
  13410. */
  13411. function resizePath(path, rect) {
  13412. if (!path.applyTransform) {
  13413. return;
  13414. }
  13415. var pathRect = path.getBoundingRect();
  13416. var m = pathRect.calculateTransform(rect);
  13417. path.applyTransform(m);
  13418. }
  13419. /**
  13420. * Sub pixel optimize line for canvas
  13421. *
  13422. * @param {Object} param
  13423. * @param {Object} [param.shape]
  13424. * @param {number} [param.shape.x1]
  13425. * @param {number} [param.shape.y1]
  13426. * @param {number} [param.shape.x2]
  13427. * @param {number} [param.shape.y2]
  13428. * @param {Object} [param.style]
  13429. * @param {number} [param.style.lineWidth]
  13430. * @return {Object} Modified param
  13431. */
  13432. function subPixelOptimizeLine(param) {
  13433. var shape = param.shape;
  13434. var lineWidth = param.style.lineWidth;
  13435. if (round(shape.x1 * 2) === round(shape.x2 * 2)) {
  13436. shape.x1 = shape.x2 = subPixelOptimize(shape.x1, lineWidth, true);
  13437. }
  13438. if (round(shape.y1 * 2) === round(shape.y2 * 2)) {
  13439. shape.y1 = shape.y2 = subPixelOptimize(shape.y1, lineWidth, true);
  13440. }
  13441. return param;
  13442. }
  13443. /**
  13444. * Sub pixel optimize rect for canvas
  13445. *
  13446. * @param {Object} param
  13447. * @param {Object} [param.shape]
  13448. * @param {number} [param.shape.x]
  13449. * @param {number} [param.shape.y]
  13450. * @param {number} [param.shape.width]
  13451. * @param {number} [param.shape.height]
  13452. * @param {Object} [param.style]
  13453. * @param {number} [param.style.lineWidth]
  13454. * @return {Object} Modified param
  13455. */
  13456. function subPixelOptimizeRect(param) {
  13457. var shape = param.shape;
  13458. var lineWidth = param.style.lineWidth;
  13459. var originX = shape.x;
  13460. var originY = shape.y;
  13461. var originWidth = shape.width;
  13462. var originHeight = shape.height;
  13463. shape.x = subPixelOptimize(shape.x, lineWidth, true);
  13464. shape.y = subPixelOptimize(shape.y, lineWidth, true);
  13465. shape.width = Math.max(
  13466. subPixelOptimize(originX + originWidth, lineWidth, false) - shape.x,
  13467. originWidth === 0 ? 0 : 1
  13468. );
  13469. shape.height = Math.max(
  13470. subPixelOptimize(originY + originHeight, lineWidth, false) - shape.y,
  13471. originHeight === 0 ? 0 : 1
  13472. );
  13473. return param;
  13474. }
  13475. /**
  13476. * Sub pixel optimize for canvas
  13477. *
  13478. * @param {number} position Coordinate, such as x, y
  13479. * @param {number} lineWidth Should be nonnegative integer.
  13480. * @param {boolean=} positiveOrNegative Default false (negative).
  13481. * @return {number} Optimized position.
  13482. */
  13483. function subPixelOptimize(position, lineWidth, positiveOrNegative) {
  13484. // Assure that (position + lineWidth / 2) is near integer edge,
  13485. // otherwise line will be fuzzy in canvas.
  13486. var doubledPosition = round(position * 2);
  13487. return (doubledPosition + round(lineWidth)) % 2 === 0
  13488. ? doubledPosition / 2
  13489. : (doubledPosition + (positiveOrNegative ? 1 : -1)) / 2;
  13490. }
  13491. function hasFillOrStroke(fillOrStroke) {
  13492. return fillOrStroke != null && fillOrStroke != 'none';
  13493. }
  13494. function liftColor(color) {
  13495. return typeof color === 'string' ? lift(color, -0.1) : color;
  13496. }
  13497. /**
  13498. * @private
  13499. */
  13500. function cacheElementStl(el) {
  13501. if (el.__hoverStlDirty) {
  13502. var stroke = el.style.stroke;
  13503. var fill = el.style.fill;
  13504. // Create hoverStyle on mouseover
  13505. var hoverStyle = el.__hoverStl;
  13506. hoverStyle.fill = hoverStyle.fill
  13507. || (hasFillOrStroke(fill) ? liftColor(fill) : null);
  13508. hoverStyle.stroke = hoverStyle.stroke
  13509. || (hasFillOrStroke(stroke) ? liftColor(stroke) : null);
  13510. var normalStyle = {};
  13511. for (var name in hoverStyle) {
  13512. // See comment in `doSingleEnterHover`.
  13513. if (hoverStyle[name] != null) {
  13514. normalStyle[name] = el.style[name];
  13515. }
  13516. }
  13517. el.__normalStl = normalStyle;
  13518. el.__hoverStlDirty = false;
  13519. }
  13520. }
  13521. /**
  13522. * @private
  13523. */
  13524. function doSingleEnterHover(el) {
  13525. if (el.__isHover) {
  13526. return;
  13527. }
  13528. cacheElementStl(el);
  13529. if (el.useHoverLayer) {
  13530. el.__zr && el.__zr.addHover(el, el.__hoverStl);
  13531. }
  13532. else {
  13533. var style = el.style;
  13534. var insideRollbackOpt = style.insideRollbackOpt;
  13535. // Consider case: only `position: 'top'` is set on emphasis, then text
  13536. // color should be returned to `autoColor`, rather than remain '#fff'.
  13537. // So we should rollback then apply again after style merging.
  13538. insideRollbackOpt && rollbackInsideStyle(style);
  13539. // styles can be:
  13540. // {
  13541. // label: {
  13542. // show: false,
  13543. // position: 'outside',
  13544. // fontSize: 18
  13545. // },
  13546. // emphasis: {
  13547. // label: {
  13548. // show: true
  13549. // }
  13550. // }
  13551. // },
  13552. // where properties of `emphasis` may not appear in `normal`. We previously use
  13553. // module:echarts/util/model#defaultEmphasis to merge `normal` to `emphasis`.
  13554. // But consider rich text and setOption in merge mode, it is impossible to cover
  13555. // all properties in merge. So we use merge mode when setting style here, where
  13556. // only properties that is not `null/undefined` can be set. The disadventage:
  13557. // null/undefined can not be used to remove style any more in `emphasis`.
  13558. style.extendFrom(el.__hoverStl);
  13559. // Do not save `insideRollback`.
  13560. if (insideRollbackOpt) {
  13561. applyInsideStyle(style, style.insideOriginalTextPosition, insideRollbackOpt);
  13562. // textFill may be rollbacked to null.
  13563. if (style.textFill == null) {
  13564. style.textFill = insideRollbackOpt.autoColor;
  13565. }
  13566. }
  13567. el.dirty(false);
  13568. el.z2 += 1;
  13569. }
  13570. el.__isHover = true;
  13571. }
  13572. /**
  13573. * @inner
  13574. */
  13575. function doSingleLeaveHover(el) {
  13576. if (!el.__isHover) {
  13577. return;
  13578. }
  13579. var normalStl = el.__normalStl;
  13580. if (el.useHoverLayer) {
  13581. el.__zr && el.__zr.removeHover(el);
  13582. }
  13583. else {
  13584. // Consider null/undefined value, should use
  13585. // `setStyle` but not `extendFrom(stl, true)`.
  13586. normalStl && el.setStyle(normalStl);
  13587. el.z2 -= 1;
  13588. }
  13589. el.__isHover = false;
  13590. }
  13591. /**
  13592. * @inner
  13593. */
  13594. function doEnterHover(el) {
  13595. el.type === 'group'
  13596. ? el.traverse(function (child) {
  13597. if (child.type !== 'group') {
  13598. doSingleEnterHover(child);
  13599. }
  13600. })
  13601. : doSingleEnterHover(el);
  13602. }
  13603. function doLeaveHover(el) {
  13604. el.type === 'group'
  13605. ? el.traverse(function (child) {
  13606. if (child.type !== 'group') {
  13607. doSingleLeaveHover(child);
  13608. }
  13609. })
  13610. : doSingleLeaveHover(el);
  13611. }
  13612. /**
  13613. * @inner
  13614. */
  13615. function setElementHoverStl(el, hoverStl) {
  13616. // If element has sepcified hoverStyle, then use it instead of given hoverStyle
  13617. // Often used when item group has a label element and it's hoverStyle is different
  13618. el.__hoverStl = el.hoverStyle || hoverStl || {};
  13619. el.__hoverStlDirty = true;
  13620. if (el.__isHover) {
  13621. cacheElementStl(el);
  13622. }
  13623. }
  13624. /**
  13625. * @inner
  13626. */
  13627. function onElementMouseOver(e) {
  13628. if (this.__hoverSilentOnTouch && e.zrByTouch) {
  13629. return;
  13630. }
  13631. // Only if element is not in emphasis status
  13632. !this.__isEmphasis && doEnterHover(this);
  13633. }
  13634. /**
  13635. * @inner
  13636. */
  13637. function onElementMouseOut(e) {
  13638. if (this.__hoverSilentOnTouch && e.zrByTouch) {
  13639. return;
  13640. }
  13641. // Only if element is not in emphasis status
  13642. !this.__isEmphasis && doLeaveHover(this);
  13643. }
  13644. /**
  13645. * @inner
  13646. */
  13647. function enterEmphasis() {
  13648. this.__isEmphasis = true;
  13649. doEnterHover(this);
  13650. }
  13651. /**
  13652. * @inner
  13653. */
  13654. function leaveEmphasis() {
  13655. this.__isEmphasis = false;
  13656. doLeaveHover(this);
  13657. }
  13658. /**
  13659. * Set hover style of element.
  13660. * This method can be called repeatly without side-effects.
  13661. * @param {module:zrender/Element} el
  13662. * @param {Object} [hoverStyle]
  13663. * @param {Object} [opt]
  13664. * @param {boolean} [opt.hoverSilentOnTouch=false]
  13665. * In touch device, mouseover event will be trigger on touchstart event
  13666. * (see module:zrender/dom/HandlerProxy). By this mechanism, we can
  13667. * conviniently use hoverStyle when tap on touch screen without additional
  13668. * code for compatibility.
  13669. * But if the chart/component has select feature, which usually also use
  13670. * hoverStyle, there might be conflict between 'select-highlight' and
  13671. * 'hover-highlight' especially when roam is enabled (see geo for example).
  13672. * In this case, hoverSilentOnTouch should be used to disable hover-highlight
  13673. * on touch device.
  13674. */
  13675. function setHoverStyle(el, hoverStyle, opt) {
  13676. el.__hoverSilentOnTouch = opt && opt.hoverSilentOnTouch;
  13677. el.type === 'group'
  13678. ? el.traverse(function (child) {
  13679. if (child.type !== 'group') {
  13680. setElementHoverStl(child, hoverStyle);
  13681. }
  13682. })
  13683. : setElementHoverStl(el, hoverStyle);
  13684. // Duplicated function will be auto-ignored, see Eventful.js.
  13685. el.on('mouseover', onElementMouseOver)
  13686. .on('mouseout', onElementMouseOut);
  13687. // Emphasis, normal can be triggered manually
  13688. el.on('emphasis', enterEmphasis)
  13689. .on('normal', leaveEmphasis);
  13690. }
  13691. /**
  13692. * @param {Object|module:zrender/graphic/Style} normalStyle
  13693. * @param {Object} emphasisStyle
  13694. * @param {module:echarts/model/Model} normalModel
  13695. * @param {module:echarts/model/Model} emphasisModel
  13696. * @param {Object} opt Check `opt` of `setTextStyleCommon` to find other props.
  13697. * @param {string|Function} [opt.defaultText]
  13698. * @param {module:echarts/model/Model} [opt.labelFetcher] Fetch text by
  13699. * `opt.labelFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex)`
  13700. * @param {module:echarts/model/Model} [opt.labelDataIndex] Fetch text by
  13701. * `opt.textFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex)`
  13702. * @param {module:echarts/model/Model} [opt.labelDimIndex] Fetch text by
  13703. * `opt.textFetcher.getFormattedLabel(opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex)`
  13704. * @param {Object} [normalSpecified]
  13705. * @param {Object} [emphasisSpecified]
  13706. */
  13707. function setLabelStyle(
  13708. normalStyle, emphasisStyle,
  13709. normalModel, emphasisModel,
  13710. opt,
  13711. normalSpecified, emphasisSpecified
  13712. ) {
  13713. opt = opt || EMPTY_OBJ;
  13714. var labelFetcher = opt.labelFetcher;
  13715. var labelDataIndex = opt.labelDataIndex;
  13716. var labelDimIndex = opt.labelDimIndex;
  13717. // This scenario, `label.normal.show = true; label.emphasis.show = false`,
  13718. // is not supported util someone requests.
  13719. var showNormal = normalModel.getShallow('show');
  13720. var showEmphasis = emphasisModel.getShallow('show');
  13721. // Consider performance, only fetch label when necessary.
  13722. // If `normal.show` is `false` and `emphasis.show` is `true` and `emphasis.formatter` is not set,
  13723. // label should be displayed, where text is fetched by `normal.formatter` or `opt.defaultText`.
  13724. var baseText;
  13725. if (showNormal || showEmphasis) {
  13726. if (labelFetcher) {
  13727. baseText = labelFetcher.getFormattedLabel(labelDataIndex, 'normal', null, labelDimIndex);
  13728. }
  13729. if (baseText == null) {
  13730. baseText = isFunction$1(opt.defaultText) ? opt.defaultText(labelDataIndex, opt) : opt.defaultText;
  13731. }
  13732. }
  13733. var normalStyleText = showNormal ? baseText : null;
  13734. var emphasisStyleText = showEmphasis
  13735. ? retrieve2(
  13736. labelFetcher
  13737. ? labelFetcher.getFormattedLabel(labelDataIndex, 'emphasis', null, labelDimIndex)
  13738. : null,
  13739. baseText
  13740. )
  13741. : null;
  13742. // Optimize: If style.text is null, text will not be drawn.
  13743. if (normalStyleText != null || emphasisStyleText != null) {
  13744. // Always set `textStyle` even if `normalStyle.text` is null, because default
  13745. // values have to be set on `normalStyle`.
  13746. // If we set default values on `emphasisStyle`, consider case:
  13747. // Firstly, `setOption(... label: {normal: {text: null}, emphasis: {show: true}} ...);`
  13748. // Secondly, `setOption(... label: {noraml: {show: true, text: 'abc', color: 'red'} ...);`
  13749. // Then the 'red' will not work on emphasis.
  13750. setTextStyle(normalStyle, normalModel, normalSpecified, opt);
  13751. setTextStyle(emphasisStyle, emphasisModel, emphasisSpecified, opt, true);
  13752. }
  13753. normalStyle.text = normalStyleText;
  13754. emphasisStyle.text = emphasisStyleText;
  13755. }
  13756. /**
  13757. * Set basic textStyle properties.
  13758. * @param {Object|module:zrender/graphic/Style} textStyle
  13759. * @param {module:echarts/model/Model} model
  13760. * @param {Object} [specifiedTextStyle] Can be overrided by settings in model.
  13761. * @param {Object} [opt] See `opt` of `setTextStyleCommon`.
  13762. * @param {boolean} [isEmphasis]
  13763. */
  13764. function setTextStyle(
  13765. textStyle, textStyleModel, specifiedTextStyle, opt, isEmphasis
  13766. ) {
  13767. setTextStyleCommon(textStyle, textStyleModel, opt, isEmphasis);
  13768. specifiedTextStyle && extend(textStyle, specifiedTextStyle);
  13769. textStyle.host && textStyle.host.dirty && textStyle.host.dirty(false);
  13770. return textStyle;
  13771. }
  13772. /**
  13773. * Set text option in the style.
  13774. * @deprecated
  13775. * @param {Object} textStyle
  13776. * @param {module:echarts/model/Model} labelModel
  13777. * @param {string|boolean} defaultColor Default text color.
  13778. * If set as false, it will be processed as a emphasis style.
  13779. */
  13780. function setText(textStyle, labelModel, defaultColor) {
  13781. var opt = {isRectText: true};
  13782. var isEmphasis;
  13783. if (defaultColor === false) {
  13784. isEmphasis = true;
  13785. }
  13786. else {
  13787. // Support setting color as 'auto' to get visual color.
  13788. opt.autoColor = defaultColor;
  13789. }
  13790. setTextStyleCommon(textStyle, labelModel, opt, isEmphasis);
  13791. textStyle.host && textStyle.host.dirty && textStyle.host.dirty(false);
  13792. }
  13793. /**
  13794. * {
  13795. * disableBox: boolean, Whether diable drawing box of block (outer most).
  13796. * isRectText: boolean,
  13797. * autoColor: string, specify a color when color is 'auto',
  13798. * for textFill, textStroke, textBackgroundColor, and textBorderColor.
  13799. * If autoColor specified, it is used as default textFill.
  13800. * useInsideStyle:
  13801. * `true`: Use inside style (textFill, textStroke, textStrokeWidth)
  13802. * if `textFill` is not specified.
  13803. * `false`: Do not use inside style.
  13804. * `null/undefined`: use inside style if `isRectText` is true and
  13805. * `textFill` is not specified and textPosition contains `'inside'`.
  13806. * forceRich: boolean
  13807. * }
  13808. */
  13809. function setTextStyleCommon(textStyle, textStyleModel, opt, isEmphasis) {
  13810. // Consider there will be abnormal when merge hover style to normal style if given default value.
  13811. opt = opt || EMPTY_OBJ;
  13812. if (opt.isRectText) {
  13813. var textPosition = textStyleModel.getShallow('position')
  13814. || (isEmphasis ? null : 'inside');
  13815. // 'outside' is not a valid zr textPostion value, but used
  13816. // in bar series, and magric type should be considered.
  13817. textPosition === 'outside' && (textPosition = 'top');
  13818. textStyle.textPosition = textPosition;
  13819. textStyle.textOffset = textStyleModel.getShallow('offset');
  13820. var labelRotate = textStyleModel.getShallow('rotate');
  13821. labelRotate != null && (labelRotate *= Math.PI / 180);
  13822. textStyle.textRotation = labelRotate;
  13823. textStyle.textDistance = retrieve2(
  13824. textStyleModel.getShallow('distance'), isEmphasis ? null : 5
  13825. );
  13826. }
  13827. var ecModel = textStyleModel.ecModel;
  13828. var globalTextStyle = ecModel && ecModel.option.textStyle;
  13829. // Consider case:
  13830. // {
  13831. // data: [{
  13832. // value: 12,
  13833. // label: {
  13834. // rich: {
  13835. // // no 'a' here but using parent 'a'.
  13836. // }
  13837. // }
  13838. // }],
  13839. // rich: {
  13840. // a: { ... }
  13841. // }
  13842. // }
  13843. var richItemNames = getRichItemNames(textStyleModel);
  13844. var richResult;
  13845. if (richItemNames) {
  13846. richResult = {};
  13847. for (var name in richItemNames) {
  13848. if (richItemNames.hasOwnProperty(name)) {
  13849. // Cascade is supported in rich.
  13850. var richTextStyle = textStyleModel.getModel(['rich', name]);
  13851. // In rich, never `disableBox`.
  13852. setTokenTextStyle(richResult[name] = {}, richTextStyle, globalTextStyle, opt, isEmphasis);
  13853. }
  13854. }
  13855. }
  13856. textStyle.rich = richResult;
  13857. setTokenTextStyle(textStyle, textStyleModel, globalTextStyle, opt, isEmphasis, true);
  13858. if (opt.forceRich && !opt.textStyle) {
  13859. opt.textStyle = {};
  13860. }
  13861. return textStyle;
  13862. }
  13863. // Consider case:
  13864. // {
  13865. // data: [{
  13866. // value: 12,
  13867. // label: {
  13868. // rich: {
  13869. // // no 'a' here but using parent 'a'.
  13870. // }
  13871. // }
  13872. // }],
  13873. // rich: {
  13874. // a: { ... }
  13875. // }
  13876. // }
  13877. function getRichItemNames(textStyleModel) {
  13878. // Use object to remove duplicated names.
  13879. var richItemNameMap;
  13880. while (textStyleModel && textStyleModel !== textStyleModel.ecModel) {
  13881. var rich = (textStyleModel.option || EMPTY_OBJ).rich;
  13882. if (rich) {
  13883. richItemNameMap = richItemNameMap || {};
  13884. for (var name in rich) {
  13885. if (rich.hasOwnProperty(name)) {
  13886. richItemNameMap[name] = 1;
  13887. }
  13888. }
  13889. }
  13890. textStyleModel = textStyleModel.parentModel;
  13891. }
  13892. return richItemNameMap;
  13893. }
  13894. function setTokenTextStyle(textStyle, textStyleModel, globalTextStyle, opt, isEmphasis, isBlock) {
  13895. // In merge mode, default value should not be given.
  13896. globalTextStyle = !isEmphasis && globalTextStyle || EMPTY_OBJ;
  13897. textStyle.textFill = getAutoColor(textStyleModel.getShallow('color'), opt)
  13898. || globalTextStyle.color;
  13899. textStyle.textStroke = getAutoColor(textStyleModel.getShallow('textBorderColor'), opt)
  13900. || globalTextStyle.textBorderColor;
  13901. textStyle.textStrokeWidth = retrieve2(
  13902. textStyleModel.getShallow('textBorderWidth'),
  13903. globalTextStyle.textBorderWidth
  13904. );
  13905. if (!isEmphasis) {
  13906. if (isBlock) {
  13907. // Always set `insideRollback`, for clearing previous.
  13908. var originalTextPosition = textStyle.textPosition;
  13909. textStyle.insideRollback = applyInsideStyle(textStyle, originalTextPosition, opt);
  13910. // Save original textPosition, because style.textPosition will be repalced by
  13911. // real location (like [10, 30]) in zrender.
  13912. textStyle.insideOriginalTextPosition = originalTextPosition;
  13913. textStyle.insideRollbackOpt = opt;
  13914. }
  13915. // Set default finally.
  13916. if (textStyle.textFill == null) {
  13917. textStyle.textFill = opt.autoColor;
  13918. }
  13919. }
  13920. // Do not use `getFont` here, because merge should be supported, where
  13921. // part of these properties may be changed in emphasis style, and the
  13922. // others should remain their original value got from normal style.
  13923. textStyle.fontStyle = textStyleModel.getShallow('fontStyle') || globalTextStyle.fontStyle;
  13924. textStyle.fontWeight = textStyleModel.getShallow('fontWeight') || globalTextStyle.fontWeight;
  13925. textStyle.fontSize = textStyleModel.getShallow('fontSize') || globalTextStyle.fontSize;
  13926. textStyle.fontFamily = textStyleModel.getShallow('fontFamily') || globalTextStyle.fontFamily;
  13927. textStyle.textAlign = textStyleModel.getShallow('align');
  13928. textStyle.textVerticalAlign = textStyleModel.getShallow('verticalAlign')
  13929. || textStyleModel.getShallow('baseline');
  13930. textStyle.textLineHeight = textStyleModel.getShallow('lineHeight');
  13931. textStyle.textWidth = textStyleModel.getShallow('width');
  13932. textStyle.textHeight = textStyleModel.getShallow('height');
  13933. textStyle.textTag = textStyleModel.getShallow('tag');
  13934. if (!isBlock || !opt.disableBox) {
  13935. textStyle.textBackgroundColor = getAutoColor(textStyleModel.getShallow('backgroundColor'), opt);
  13936. textStyle.textPadding = textStyleModel.getShallow('padding');
  13937. textStyle.textBorderColor = getAutoColor(textStyleModel.getShallow('borderColor'), opt);
  13938. textStyle.textBorderWidth = textStyleModel.getShallow('borderWidth');
  13939. textStyle.textBorderRadius = textStyleModel.getShallow('borderRadius');
  13940. textStyle.textBoxShadowColor = textStyleModel.getShallow('shadowColor');
  13941. textStyle.textBoxShadowBlur = textStyleModel.getShallow('shadowBlur');
  13942. textStyle.textBoxShadowOffsetX = textStyleModel.getShallow('shadowOffsetX');
  13943. textStyle.textBoxShadowOffsetY = textStyleModel.getShallow('shadowOffsetY');
  13944. }
  13945. textStyle.textShadowColor = textStyleModel.getShallow('textShadowColor')
  13946. || globalTextStyle.textShadowColor;
  13947. textStyle.textShadowBlur = textStyleModel.getShallow('textShadowBlur')
  13948. || globalTextStyle.textShadowBlur;
  13949. textStyle.textShadowOffsetX = textStyleModel.getShallow('textShadowOffsetX')
  13950. || globalTextStyle.textShadowOffsetX;
  13951. textStyle.textShadowOffsetY = textStyleModel.getShallow('textShadowOffsetY')
  13952. || globalTextStyle.textShadowOffsetY;
  13953. }
  13954. function getAutoColor(color, opt) {
  13955. return color !== 'auto' ? color : (opt && opt.autoColor) ? opt.autoColor : null;
  13956. }
  13957. function applyInsideStyle(textStyle, textPosition, opt) {
  13958. var useInsideStyle = opt.useInsideStyle;
  13959. var insideRollback;
  13960. if (textStyle.textFill == null
  13961. && useInsideStyle !== false
  13962. && (useInsideStyle === true
  13963. || (opt.isRectText
  13964. && textPosition
  13965. // textPosition can be [10, 30]
  13966. && typeof textPosition === 'string'
  13967. && textPosition.indexOf('inside') >= 0
  13968. )
  13969. )
  13970. ) {
  13971. insideRollback = {
  13972. textFill: null,
  13973. textStroke: textStyle.textStroke,
  13974. textStrokeWidth: textStyle.textStrokeWidth
  13975. };
  13976. textStyle.textFill = '#fff';
  13977. // Consider text with #fff overflow its container.
  13978. if (textStyle.textStroke == null) {
  13979. textStyle.textStroke = opt.autoColor;
  13980. textStyle.textStrokeWidth == null && (textStyle.textStrokeWidth = 2);
  13981. }
  13982. }
  13983. return insideRollback;
  13984. }
  13985. function rollbackInsideStyle(style) {
  13986. var insideRollback = style.insideRollback;
  13987. if (insideRollback) {
  13988. style.textFill = insideRollback.textFill;
  13989. style.textStroke = insideRollback.textStroke;
  13990. style.textStrokeWidth = insideRollback.textStrokeWidth;
  13991. }
  13992. }
  13993. function getFont(opt, ecModel) {
  13994. // ecModel or default text style model.
  13995. var gTextStyleModel = ecModel || ecModel.getModel('textStyle');
  13996. return trim([
  13997. // FIXME in node-canvas fontWeight is before fontStyle
  13998. opt.fontStyle || gTextStyleModel && gTextStyleModel.getShallow('fontStyle') || '',
  13999. opt.fontWeight || gTextStyleModel && gTextStyleModel.getShallow('fontWeight') || '',
  14000. (opt.fontSize || gTextStyleModel && gTextStyleModel.getShallow('fontSize') || 12) + 'px',
  14001. opt.fontFamily || gTextStyleModel && gTextStyleModel.getShallow('fontFamily') || 'sans-serif'
  14002. ].join(' '));
  14003. }
  14004. function animateOrSetProps(isUpdate, el, props, animatableModel, dataIndex, cb) {
  14005. if (typeof dataIndex === 'function') {
  14006. cb = dataIndex;
  14007. dataIndex = null;
  14008. }
  14009. // Do not check 'animation' property directly here. Consider this case:
  14010. // animation model is an `itemModel`, whose does not have `isAnimationEnabled`
  14011. // but its parent model (`seriesModel`) does.
  14012. var animationEnabled = animatableModel && animatableModel.isAnimationEnabled();
  14013. if (animationEnabled) {
  14014. var postfix = isUpdate ? 'Update' : '';
  14015. var duration = animatableModel.getShallow('animationDuration' + postfix);
  14016. var animationEasing = animatableModel.getShallow('animationEasing' + postfix);
  14017. var animationDelay = animatableModel.getShallow('animationDelay' + postfix);
  14018. if (typeof animationDelay === 'function') {
  14019. animationDelay = animationDelay(
  14020. dataIndex,
  14021. animatableModel.getAnimationDelayParams
  14022. ? animatableModel.getAnimationDelayParams(el, dataIndex)
  14023. : null
  14024. );
  14025. }
  14026. if (typeof duration === 'function') {
  14027. duration = duration(dataIndex);
  14028. }
  14029. duration > 0
  14030. ? el.animateTo(props, duration, animationDelay || 0, animationEasing, cb, !!cb)
  14031. : (el.stopAnimation(), el.attr(props), cb && cb());
  14032. }
  14033. else {
  14034. el.stopAnimation();
  14035. el.attr(props);
  14036. cb && cb();
  14037. }
  14038. }
  14039. /**
  14040. * Update graphic element properties with or without animation according to the
  14041. * configuration in series.
  14042. *
  14043. * Caution: this method will stop previous animation.
  14044. * So if do not use this method to one element twice before
  14045. * animation starts, unless you know what you are doing.
  14046. *
  14047. * @param {module:zrender/Element} el
  14048. * @param {Object} props
  14049. * @param {module:echarts/model/Model} [animatableModel]
  14050. * @param {number} [dataIndex]
  14051. * @param {Function} [cb]
  14052. * @example
  14053. * graphic.updateProps(el, {
  14054. * position: [100, 100]
  14055. * }, seriesModel, dataIndex, function () { console.log('Animation done!'); });
  14056. * // Or
  14057. * graphic.updateProps(el, {
  14058. * position: [100, 100]
  14059. * }, seriesModel, function () { console.log('Animation done!'); });
  14060. */
  14061. function updateProps(el, props, animatableModel, dataIndex, cb) {
  14062. animateOrSetProps(true, el, props, animatableModel, dataIndex, cb);
  14063. }
  14064. /**
  14065. * Init graphic element properties with or without animation according to the
  14066. * configuration in series.
  14067. *
  14068. * Caution: this method will stop previous animation.
  14069. * So if do not use this method to one element twice before
  14070. * animation starts, unless you know what you are doing.
  14071. *
  14072. * @param {module:zrender/Element} el
  14073. * @param {Object} props
  14074. * @param {module:echarts/model/Model} [animatableModel]
  14075. * @param {number} [dataIndex]
  14076. * @param {Function} cb
  14077. */
  14078. function initProps(el, props, animatableModel, dataIndex, cb) {
  14079. animateOrSetProps(false, el, props, animatableModel, dataIndex, cb);
  14080. }
  14081. /**
  14082. * Get transform matrix of target (param target),
  14083. * in coordinate of its ancestor (param ancestor)
  14084. *
  14085. * @param {module:zrender/mixin/Transformable} target
  14086. * @param {module:zrender/mixin/Transformable} [ancestor]
  14087. */
  14088. function getTransform(target, ancestor) {
  14089. var mat = identity([]);
  14090. while (target && target !== ancestor) {
  14091. mul$1(mat, target.getLocalTransform(), mat);
  14092. target = target.parent;
  14093. }
  14094. return mat;
  14095. }
  14096. /**
  14097. * Apply transform to an vertex.
  14098. * @param {Array.<number>} target [x, y]
  14099. * @param {Array.<number>|TypedArray.<number>|Object} transform Can be:
  14100. * + Transform matrix: like [1, 0, 0, 1, 0, 0]
  14101. * + {position, rotation, scale}, the same as `zrender/Transformable`.
  14102. * @param {boolean=} invert Whether use invert matrix.
  14103. * @return {Array.<number>} [x, y]
  14104. */
  14105. function applyTransform$1(target, transform, invert$$1) {
  14106. if (transform && !isArrayLike(transform)) {
  14107. transform = Transformable.getLocalTransform(transform);
  14108. }
  14109. if (invert$$1) {
  14110. transform = invert([], transform);
  14111. }
  14112. return applyTransform([], target, transform);
  14113. }
  14114. /**
  14115. * @param {string} direction 'left' 'right' 'top' 'bottom'
  14116. * @param {Array.<number>} transform Transform matrix: like [1, 0, 0, 1, 0, 0]
  14117. * @param {boolean=} invert Whether use invert matrix.
  14118. * @return {string} Transformed direction. 'left' 'right' 'top' 'bottom'
  14119. */
  14120. function transformDirection(direction, transform, invert$$1) {
  14121. // Pick a base, ensure that transform result will not be (0, 0).
  14122. var hBase = (transform[4] === 0 || transform[5] === 0 || transform[0] === 0)
  14123. ? 1 : Math.abs(2 * transform[4] / transform[0]);
  14124. var vBase = (transform[4] === 0 || transform[5] === 0 || transform[2] === 0)
  14125. ? 1 : Math.abs(2 * transform[4] / transform[2]);
  14126. var vertex = [
  14127. direction === 'left' ? -hBase : direction === 'right' ? hBase : 0,
  14128. direction === 'top' ? -vBase : direction === 'bottom' ? vBase : 0
  14129. ];
  14130. vertex = applyTransform$1(vertex, transform, invert$$1);
  14131. return Math.abs(vertex[0]) > Math.abs(vertex[1])
  14132. ? (vertex[0] > 0 ? 'right' : 'left')
  14133. : (vertex[1] > 0 ? 'bottom' : 'top');
  14134. }
  14135. /**
  14136. * Apply group transition animation from g1 to g2.
  14137. * If no animatableModel, no animation.
  14138. */
  14139. function groupTransition(g1, g2, animatableModel, cb) {
  14140. if (!g1 || !g2) {
  14141. return;
  14142. }
  14143. function getElMap(g) {
  14144. var elMap = {};
  14145. g.traverse(function (el) {
  14146. if (!el.isGroup && el.anid) {
  14147. elMap[el.anid] = el;
  14148. }
  14149. });
  14150. return elMap;
  14151. }
  14152. function getAnimatableProps(el) {
  14153. var obj = {
  14154. position: clone$1(el.position),
  14155. rotation: el.rotation
  14156. };
  14157. if (el.shape) {
  14158. obj.shape = extend({}, el.shape);
  14159. }
  14160. return obj;
  14161. }
  14162. var elMap1 = getElMap(g1);
  14163. g2.traverse(function (el) {
  14164. if (!el.isGroup && el.anid) {
  14165. var oldEl = elMap1[el.anid];
  14166. if (oldEl) {
  14167. var newProp = getAnimatableProps(el);
  14168. el.attr(getAnimatableProps(oldEl));
  14169. updateProps(el, newProp, animatableModel, el.dataIndex);
  14170. }
  14171. // else {
  14172. // if (el.previousProps) {
  14173. // graphic.updateProps
  14174. // }
  14175. // }
  14176. }
  14177. });
  14178. }
  14179. /**
  14180. * @param {Array.<Array.<number>>} points Like: [[23, 44], [53, 66], ...]
  14181. * @param {Object} rect {x, y, width, height}
  14182. * @return {Array.<Array.<number>>} A new clipped points.
  14183. */
  14184. function clipPointsByRect(points, rect) {
  14185. return map(points, function (point) {
  14186. var x = point[0];
  14187. x = mathMax$1(x, rect.x);
  14188. x = mathMin$1(x, rect.x + rect.width);
  14189. var y = point[1];
  14190. y = mathMax$1(y, rect.y);
  14191. y = mathMin$1(y, rect.y + rect.height);
  14192. return [x, y];
  14193. });
  14194. }
  14195. /**
  14196. * @param {Object} targetRect {x, y, width, height}
  14197. * @param {Object} rect {x, y, width, height}
  14198. * @return {Object} A new clipped rect. If rect size are negative, return undefined.
  14199. */
  14200. function clipRectByRect(targetRect, rect) {
  14201. var x = mathMax$1(targetRect.x, rect.x);
  14202. var x2 = mathMin$1(targetRect.x + targetRect.width, rect.x + rect.width);
  14203. var y = mathMax$1(targetRect.y, rect.y);
  14204. var y2 = mathMin$1(targetRect.y + targetRect.height, rect.y + rect.height);
  14205. if (x2 >= x && y2 >= y) {
  14206. return {
  14207. x: x,
  14208. y: y,
  14209. width: x2 - x,
  14210. height: y2 - y
  14211. };
  14212. }
  14213. }
  14214. /**
  14215. * @param {string} iconStr Support 'image://' or 'path://' or direct svg path.
  14216. * @param {Object} [opt] Properties of `module:zrender/Element`, except `style`.
  14217. * @param {Object} [rect] {x, y, width, height}
  14218. * @return {module:zrender/Element} Icon path or image element.
  14219. */
  14220. function createIcon(iconStr, opt, rect) {
  14221. opt = extend({rectHover: true}, opt);
  14222. var style = opt.style = {strokeNoScale: true};
  14223. rect = rect || {x: -1, y: -1, width: 2, height: 2};
  14224. if (iconStr) {
  14225. return iconStr.indexOf('image://') === 0
  14226. ? (
  14227. style.image = iconStr.slice(8),
  14228. defaults(style, rect),
  14229. new ZImage(opt)
  14230. )
  14231. : (
  14232. makePath(
  14233. iconStr.replace('path://', ''),
  14234. opt,
  14235. rect,
  14236. 'center'
  14237. )
  14238. );
  14239. }
  14240. }
  14241. var graphic = (Object.freeze || Object)({
  14242. extendShape: extendShape,
  14243. extendPath: extendPath,
  14244. makePath: makePath,
  14245. makeImage: makeImage,
  14246. mergePath: mergePath,
  14247. resizePath: resizePath,
  14248. subPixelOptimizeLine: subPixelOptimizeLine,
  14249. subPixelOptimizeRect: subPixelOptimizeRect,
  14250. subPixelOptimize: subPixelOptimize,
  14251. setHoverStyle: setHoverStyle,
  14252. setLabelStyle: setLabelStyle,
  14253. setTextStyle: setTextStyle,
  14254. setText: setText,
  14255. getFont: getFont,
  14256. updateProps: updateProps,
  14257. initProps: initProps,
  14258. getTransform: getTransform,
  14259. applyTransform: applyTransform$1,
  14260. transformDirection: transformDirection,
  14261. groupTransition: groupTransition,
  14262. clipPointsByRect: clipPointsByRect,
  14263. clipRectByRect: clipRectByRect,
  14264. createIcon: createIcon,
  14265. Group: Group,
  14266. Image: ZImage,
  14267. Text: Text,
  14268. Circle: Circle,
  14269. Sector: Sector,
  14270. Ring: Ring,
  14271. Polygon: Polygon,
  14272. Polyline: Polyline,
  14273. Rect: Rect,
  14274. Line: Line,
  14275. BezierCurve: BezierCurve,
  14276. Arc: Arc,
  14277. IncrementalDisplayable: IncrementalDisplayble,
  14278. CompoundPath: CompoundPath,
  14279. LinearGradient: LinearGradient,
  14280. RadialGradient: RadialGradient,
  14281. BoundingRect: BoundingRect
  14282. });
  14283. var PATH_COLOR = ['textStyle', 'color'];
  14284. var textStyleMixin = {
  14285. /**
  14286. * Get color property or get color from option.textStyle.color
  14287. * @param {boolean} [isEmphasis]
  14288. * @return {string}
  14289. */
  14290. getTextColor: function (isEmphasis) {
  14291. var ecModel = this.ecModel;
  14292. return this.getShallow('color')
  14293. || (
  14294. (!isEmphasis && ecModel) ? ecModel.get(PATH_COLOR) : null
  14295. );
  14296. },
  14297. /**
  14298. * Create font string from fontStyle, fontWeight, fontSize, fontFamily
  14299. * @return {string}
  14300. */
  14301. getFont: function () {
  14302. return getFont({
  14303. fontStyle: this.getShallow('fontStyle'),
  14304. fontWeight: this.getShallow('fontWeight'),
  14305. fontSize: this.getShallow('fontSize'),
  14306. fontFamily: this.getShallow('fontFamily')
  14307. }, this.ecModel);
  14308. },
  14309. getTextRect: function (text) {
  14310. return getBoundingRect(
  14311. text,
  14312. this.getFont(),
  14313. this.getShallow('align'),
  14314. this.getShallow('verticalAlign') || this.getShallow('baseline'),
  14315. this.getShallow('padding'),
  14316. this.getShallow('rich'),
  14317. this.getShallow('truncateText')
  14318. );
  14319. }
  14320. };
  14321. var getItemStyle = makeStyleMapper(
  14322. [
  14323. ['fill', 'color'],
  14324. ['stroke', 'borderColor'],
  14325. ['lineWidth', 'borderWidth'],
  14326. ['opacity'],
  14327. ['shadowBlur'],
  14328. ['shadowOffsetX'],
  14329. ['shadowOffsetY'],
  14330. ['shadowColor'],
  14331. ['textPosition'],
  14332. ['textAlign']
  14333. ]
  14334. );
  14335. var itemStyleMixin = {
  14336. getItemStyle: function (excludes, includes) {
  14337. var style = getItemStyle(this, excludes, includes);
  14338. var lineDash = this.getBorderLineDash();
  14339. lineDash && (style.lineDash = lineDash);
  14340. return style;
  14341. },
  14342. getBorderLineDash: function () {
  14343. var lineType = this.get('borderType');
  14344. return (lineType === 'solid' || lineType == null) ? null
  14345. : (lineType === 'dashed' ? [5, 5] : [1, 1]);
  14346. }
  14347. };
  14348. /**
  14349. * @module echarts/model/Model
  14350. */
  14351. var mixin$1 = mixin;
  14352. var inner = makeInner();
  14353. /**
  14354. * @alias module:echarts/model/Model
  14355. * @constructor
  14356. * @param {Object} option
  14357. * @param {module:echarts/model/Model} [parentModel]
  14358. * @param {module:echarts/model/Global} [ecModel]
  14359. */
  14360. function Model(option, parentModel, ecModel) {
  14361. /**
  14362. * @type {module:echarts/model/Model}
  14363. * @readOnly
  14364. */
  14365. this.parentModel = parentModel;
  14366. /**
  14367. * @type {module:echarts/model/Global}
  14368. * @readOnly
  14369. */
  14370. this.ecModel = ecModel;
  14371. /**
  14372. * @type {Object}
  14373. * @protected
  14374. */
  14375. this.option = option;
  14376. // Simple optimization
  14377. // if (this.init) {
  14378. // if (arguments.length <= 4) {
  14379. // this.init(option, parentModel, ecModel, extraOpt);
  14380. // }
  14381. // else {
  14382. // this.init.apply(this, arguments);
  14383. // }
  14384. // }
  14385. }
  14386. Model.prototype = {
  14387. constructor: Model,
  14388. /**
  14389. * Model 的初始化函数
  14390. * @param {Object} option
  14391. */
  14392. init: null,
  14393. /**
  14394. * 从新的 Option merge
  14395. */
  14396. mergeOption: function (option) {
  14397. merge(this.option, option, true);
  14398. },
  14399. /**
  14400. * @param {string|Array.<string>} path
  14401. * @param {boolean} [ignoreParent=false]
  14402. * @return {*}
  14403. */
  14404. get: function (path, ignoreParent) {
  14405. if (path == null) {
  14406. return this.option;
  14407. }
  14408. return doGet(
  14409. this.option,
  14410. this.parsePath(path),
  14411. !ignoreParent && getParent(this, path)
  14412. );
  14413. },
  14414. /**
  14415. * @param {string} key
  14416. * @param {boolean} [ignoreParent=false]
  14417. * @return {*}
  14418. */
  14419. getShallow: function (key, ignoreParent) {
  14420. var option = this.option;
  14421. var val = option == null ? option : option[key];
  14422. var parentModel = !ignoreParent && getParent(this, key);
  14423. if (val == null && parentModel) {
  14424. val = parentModel.getShallow(key);
  14425. }
  14426. return val;
  14427. },
  14428. /**
  14429. * @param {string|Array.<string>} [path]
  14430. * @param {module:echarts/model/Model} [parentModel]
  14431. * @return {module:echarts/model/Model}
  14432. */
  14433. getModel: function (path, parentModel) {
  14434. var obj = path == null
  14435. ? this.option
  14436. : doGet(this.option, path = this.parsePath(path));
  14437. var thisParentModel;
  14438. parentModel = parentModel || (
  14439. (thisParentModel = getParent(this, path))
  14440. && thisParentModel.getModel(path)
  14441. );
  14442. return new Model(obj, parentModel, this.ecModel);
  14443. },
  14444. /**
  14445. * If model has option
  14446. */
  14447. isEmpty: function () {
  14448. return this.option == null;
  14449. },
  14450. restoreData: function () {},
  14451. // Pending
  14452. clone: function () {
  14453. var Ctor = this.constructor;
  14454. return new Ctor(clone(this.option));
  14455. },
  14456. setReadOnly: function (properties) {
  14457. // clazzUtil.setReadOnly(this, properties);
  14458. },
  14459. // If path is null/undefined, return null/undefined.
  14460. parsePath: function(path) {
  14461. if (typeof path === 'string') {
  14462. path = path.split('.');
  14463. }
  14464. return path;
  14465. },
  14466. /**
  14467. * @param {Function} getParentMethod
  14468. * param {Array.<string>|string} path
  14469. * return {module:echarts/model/Model}
  14470. */
  14471. customizeGetParent: function (getParentMethod) {
  14472. inner(this).getParent = getParentMethod;
  14473. },
  14474. isAnimationEnabled: function () {
  14475. if (!env$1.node) {
  14476. if (this.option.animation != null) {
  14477. return !!this.option.animation;
  14478. }
  14479. else if (this.parentModel) {
  14480. return this.parentModel.isAnimationEnabled();
  14481. }
  14482. }
  14483. }
  14484. };
  14485. function doGet(obj, pathArr, parentModel) {
  14486. for (var i = 0; i < pathArr.length; i++) {
  14487. // Ignore empty
  14488. if (!pathArr[i]) {
  14489. continue;
  14490. }
  14491. // obj could be number/string/... (like 0)
  14492. obj = (obj && typeof obj === 'object') ? obj[pathArr[i]] : null;
  14493. if (obj == null) {
  14494. break;
  14495. }
  14496. }
  14497. if (obj == null && parentModel) {
  14498. obj = parentModel.get(pathArr);
  14499. }
  14500. return obj;
  14501. }
  14502. // `path` can be null/undefined
  14503. function getParent(model, path) {
  14504. var getParentMethod = inner(model).getParent;
  14505. return getParentMethod ? getParentMethod.call(model, path) : model.parentModel;
  14506. }
  14507. // Enable Model.extend.
  14508. enableClassExtend(Model);
  14509. enableClassCheck(Model);
  14510. mixin$1(Model, lineStyleMixin);
  14511. mixin$1(Model, areaStyleMixin);
  14512. mixin$1(Model, textStyleMixin);
  14513. mixin$1(Model, itemStyleMixin);
  14514. var base = 0;
  14515. /**
  14516. * @public
  14517. * @param {string} type
  14518. * @return {string}
  14519. */
  14520. function getUID(type) {
  14521. // Considering the case of crossing js context,
  14522. // use Math.random to make id as unique as possible.
  14523. return [(type || ''), base++, Math.random().toFixed(5)].join('_');
  14524. }
  14525. /**
  14526. * @inner
  14527. */
  14528. function enableSubTypeDefaulter(entity) {
  14529. var subTypeDefaulters = {};
  14530. entity.registerSubTypeDefaulter = function (componentType, defaulter) {
  14531. componentType = parseClassType$1(componentType);
  14532. subTypeDefaulters[componentType.main] = defaulter;
  14533. };
  14534. entity.determineSubType = function (componentType, option) {
  14535. var type = option.type;
  14536. if (!type) {
  14537. var componentTypeMain = parseClassType$1(componentType).main;
  14538. if (entity.hasSubTypes(componentType) && subTypeDefaulters[componentTypeMain]) {
  14539. type = subTypeDefaulters[componentTypeMain](option);
  14540. }
  14541. }
  14542. return type;
  14543. };
  14544. return entity;
  14545. }
  14546. /**
  14547. * Topological travel on Activity Network (Activity On Vertices).
  14548. * Dependencies is defined in Model.prototype.dependencies, like ['xAxis', 'yAxis'].
  14549. *
  14550. * If 'xAxis' or 'yAxis' is absent in componentTypeList, just ignore it in topology.
  14551. *
  14552. * If there is circle dependencey, Error will be thrown.
  14553. *
  14554. */
  14555. function enableTopologicalTravel(entity, dependencyGetter) {
  14556. /**
  14557. * @public
  14558. * @param {Array.<string>} targetNameList Target Component type list.
  14559. * Can be ['aa', 'bb', 'aa.xx']
  14560. * @param {Array.<string>} fullNameList By which we can build dependency graph.
  14561. * @param {Function} callback Params: componentType, dependencies.
  14562. * @param {Object} context Scope of callback.
  14563. */
  14564. entity.topologicalTravel = function (targetNameList, fullNameList, callback, context) {
  14565. if (!targetNameList.length) {
  14566. return;
  14567. }
  14568. var result = makeDepndencyGraph(fullNameList);
  14569. var graph = result.graph;
  14570. var stack = result.noEntryList;
  14571. var targetNameSet = {};
  14572. each$1(targetNameList, function (name) {
  14573. targetNameSet[name] = true;
  14574. });
  14575. while (stack.length) {
  14576. var currComponentType = stack.pop();
  14577. var currVertex = graph[currComponentType];
  14578. var isInTargetNameSet = !!targetNameSet[currComponentType];
  14579. if (isInTargetNameSet) {
  14580. callback.call(context, currComponentType, currVertex.originalDeps.slice());
  14581. delete targetNameSet[currComponentType];
  14582. }
  14583. each$1(
  14584. currVertex.successor,
  14585. isInTargetNameSet ? removeEdgeAndAdd : removeEdge
  14586. );
  14587. }
  14588. each$1(targetNameSet, function () {
  14589. throw new Error('Circle dependency may exists');
  14590. });
  14591. function removeEdge(succComponentType) {
  14592. graph[succComponentType].entryCount--;
  14593. if (graph[succComponentType].entryCount === 0) {
  14594. stack.push(succComponentType);
  14595. }
  14596. }
  14597. // Consider this case: legend depends on series, and we call
  14598. // chart.setOption({series: [...]}), where only series is in option.
  14599. // If we do not have 'removeEdgeAndAdd', legendModel.mergeOption will
  14600. // not be called, but only sereis.mergeOption is called. Thus legend
  14601. // have no chance to update its local record about series (like which
  14602. // name of series is available in legend).
  14603. function removeEdgeAndAdd(succComponentType) {
  14604. targetNameSet[succComponentType] = true;
  14605. removeEdge(succComponentType);
  14606. }
  14607. };
  14608. /**
  14609. * DepndencyGraph: {Object}
  14610. * key: conponentType,
  14611. * value: {
  14612. * successor: [conponentTypes...],
  14613. * originalDeps: [conponentTypes...],
  14614. * entryCount: {number}
  14615. * }
  14616. */
  14617. function makeDepndencyGraph(fullNameList) {
  14618. var graph = {};
  14619. var noEntryList = [];
  14620. each$1(fullNameList, function (name) {
  14621. var thisItem = createDependencyGraphItem(graph, name);
  14622. var originalDeps = thisItem.originalDeps = dependencyGetter(name);
  14623. var availableDeps = getAvailableDependencies(originalDeps, fullNameList);
  14624. thisItem.entryCount = availableDeps.length;
  14625. if (thisItem.entryCount === 0) {
  14626. noEntryList.push(name);
  14627. }
  14628. each$1(availableDeps, function (dependentName) {
  14629. if (indexOf(thisItem.predecessor, dependentName) < 0) {
  14630. thisItem.predecessor.push(dependentName);
  14631. }
  14632. var thatItem = createDependencyGraphItem(graph, dependentName);
  14633. if (indexOf(thatItem.successor, dependentName) < 0) {
  14634. thatItem.successor.push(name);
  14635. }
  14636. });
  14637. });
  14638. return {graph: graph, noEntryList: noEntryList};
  14639. }
  14640. function createDependencyGraphItem(graph, name) {
  14641. if (!graph[name]) {
  14642. graph[name] = {predecessor: [], successor: []};
  14643. }
  14644. return graph[name];
  14645. }
  14646. function getAvailableDependencies(originalDeps, fullNameList) {
  14647. var availableDeps = [];
  14648. each$1(originalDeps, function (dep) {
  14649. indexOf(fullNameList, dep) >= 0 && availableDeps.push(dep);
  14650. });
  14651. return availableDeps;
  14652. }
  14653. }
  14654. var RADIAN_EPSILON = 1e-4;
  14655. function _trim(str) {
  14656. return str.replace(/^\s+/, '').replace(/\s+$/, '');
  14657. }
  14658. /**
  14659. * Linear mapping a value from domain to range
  14660. * @memberOf module:echarts/util/number
  14661. * @param {(number|Array.<number>)} val
  14662. * @param {Array.<number>} domain Domain extent domain[0] can be bigger than domain[1]
  14663. * @param {Array.<number>} range Range extent range[0] can be bigger than range[1]
  14664. * @param {boolean} clamp
  14665. * @return {(number|Array.<number>}
  14666. */
  14667. function linearMap(val, domain, range, clamp) {
  14668. var subDomain = domain[1] - domain[0];
  14669. var subRange = range[1] - range[0];
  14670. if (subDomain === 0) {
  14671. return subRange === 0
  14672. ? range[0]
  14673. : (range[0] + range[1]) / 2;
  14674. }
  14675. // Avoid accuracy problem in edge, such as
  14676. // 146.39 - 62.83 === 83.55999999999999.
  14677. // See echarts/test/ut/spec/util/number.js#linearMap#accuracyError
  14678. // It is a little verbose for efficiency considering this method
  14679. // is a hotspot.
  14680. if (clamp) {
  14681. if (subDomain > 0) {
  14682. if (val <= domain[0]) {
  14683. return range[0];
  14684. }
  14685. else if (val >= domain[1]) {
  14686. return range[1];
  14687. }
  14688. }
  14689. else {
  14690. if (val >= domain[0]) {
  14691. return range[0];
  14692. }
  14693. else if (val <= domain[1]) {
  14694. return range[1];
  14695. }
  14696. }
  14697. }
  14698. else {
  14699. if (val === domain[0]) {
  14700. return range[0];
  14701. }
  14702. if (val === domain[1]) {
  14703. return range[1];
  14704. }
  14705. }
  14706. return (val - domain[0]) / subDomain * subRange + range[0];
  14707. }
  14708. /**
  14709. * Convert a percent string to absolute number.
  14710. * Returns NaN if percent is not a valid string or number
  14711. * @memberOf module:echarts/util/number
  14712. * @param {string|number} percent
  14713. * @param {number} all
  14714. * @return {number}
  14715. */
  14716. function parsePercent$1(percent, all) {
  14717. switch (percent) {
  14718. case 'center':
  14719. case 'middle':
  14720. percent = '50%';
  14721. break;
  14722. case 'left':
  14723. case 'top':
  14724. percent = '0%';
  14725. break;
  14726. case 'right':
  14727. case 'bottom':
  14728. percent = '100%';
  14729. break;
  14730. }
  14731. if (typeof percent === 'string') {
  14732. if (_trim(percent).match(/%$/)) {
  14733. return parseFloat(percent) / 100 * all;
  14734. }
  14735. return parseFloat(percent);
  14736. }
  14737. return percent == null ? NaN : +percent;
  14738. }
  14739. /**
  14740. * (1) Fix rounding error of float numbers.
  14741. * (2) Support return string to avoid scientific notation like '3.5e-7'.
  14742. *
  14743. * @param {number} x
  14744. * @param {number} [precision]
  14745. * @param {boolean} [returnStr]
  14746. * @return {number|string}
  14747. */
  14748. function round$1(x, precision, returnStr) {
  14749. if (precision == null) {
  14750. precision = 10;
  14751. }
  14752. // Avoid range error
  14753. precision = Math.min(Math.max(0, precision), 20);
  14754. x = (+x).toFixed(precision);
  14755. return returnStr ? x : +x;
  14756. }
  14757. /**
  14758. * Get precision
  14759. * @param {number} val
  14760. */
  14761. /**
  14762. * @param {string|number} val
  14763. * @return {number}
  14764. */
  14765. function getPrecisionSafe(val) {
  14766. var str = val.toString();
  14767. // Consider scientific notation: '3.4e-12' '3.4e+12'
  14768. var eIndex = str.indexOf('e');
  14769. if (eIndex > 0) {
  14770. var precision = +str.slice(eIndex + 1);
  14771. return precision < 0 ? -precision : 0;
  14772. }
  14773. else {
  14774. var dotIndex = str.indexOf('.');
  14775. return dotIndex < 0 ? 0 : str.length - 1 - dotIndex;
  14776. }
  14777. }
  14778. /**
  14779. * Minimal dicernible data precisioin according to a single pixel.
  14780. *
  14781. * @param {Array.<number>} dataExtent
  14782. * @param {Array.<number>} pixelExtent
  14783. * @return {number} precision
  14784. */
  14785. function getPixelPrecision(dataExtent, pixelExtent) {
  14786. var log = Math.log;
  14787. var LN10 = Math.LN10;
  14788. var dataQuantity = Math.floor(log(dataExtent[1] - dataExtent[0]) / LN10);
  14789. var sizeQuantity = Math.round(log(Math.abs(pixelExtent[1] - pixelExtent[0])) / LN10);
  14790. // toFixed() digits argument must be between 0 and 20.
  14791. var precision = Math.min(Math.max(-dataQuantity + sizeQuantity, 0), 20);
  14792. return !isFinite(precision) ? 20 : precision;
  14793. }
  14794. /**
  14795. * Get a data of given precision, assuring the sum of percentages
  14796. * in valueList is 1.
  14797. * The largest remainer method is used.
  14798. * https://en.wikipedia.org/wiki/Largest_remainder_method
  14799. *
  14800. * @param {Array.<number>} valueList a list of all data
  14801. * @param {number} idx index of the data to be processed in valueList
  14802. * @param {number} precision integer number showing digits of precision
  14803. * @return {number} percent ranging from 0 to 100
  14804. */
  14805. function getPercentWithPrecision(valueList, idx, precision) {
  14806. if (!valueList[idx]) {
  14807. return 0;
  14808. }
  14809. var sum = reduce(valueList, function (acc, val) {
  14810. return acc + (isNaN(val) ? 0 : val);
  14811. }, 0);
  14812. if (sum === 0) {
  14813. return 0;
  14814. }
  14815. var digits = Math.pow(10, precision);
  14816. var votesPerQuota = map(valueList, function (val) {
  14817. return (isNaN(val) ? 0 : val) / sum * digits * 100;
  14818. });
  14819. var targetSeats = digits * 100;
  14820. var seats = map(votesPerQuota, function (votes) {
  14821. // Assign automatic seats.
  14822. return Math.floor(votes);
  14823. });
  14824. var currentSum = reduce(seats, function (acc, val) {
  14825. return acc + val;
  14826. }, 0);
  14827. var remainder = map(votesPerQuota, function (votes, idx) {
  14828. return votes - seats[idx];
  14829. });
  14830. // Has remainding votes.
  14831. while (currentSum < targetSeats) {
  14832. // Find next largest remainder.
  14833. var max = Number.NEGATIVE_INFINITY;
  14834. var maxId = null;
  14835. for (var i = 0, len = remainder.length; i < len; ++i) {
  14836. if (remainder[i] > max) {
  14837. max = remainder[i];
  14838. maxId = i;
  14839. }
  14840. }
  14841. // Add a vote to max remainder.
  14842. ++seats[maxId];
  14843. remainder[maxId] = 0;
  14844. ++currentSum;
  14845. }
  14846. return seats[idx] / digits;
  14847. }
  14848. // Number.MAX_SAFE_INTEGER, ie do not support.
  14849. /**
  14850. * To 0 - 2 * PI, considering negative radian.
  14851. * @param {number} radian
  14852. * @return {number}
  14853. */
  14854. function remRadian(radian) {
  14855. var pi2 = Math.PI * 2;
  14856. return (radian % pi2 + pi2) % pi2;
  14857. }
  14858. /**
  14859. * @param {type} radian
  14860. * @return {boolean}
  14861. */
  14862. function isRadianAroundZero(val) {
  14863. return val > -RADIAN_EPSILON && val < RADIAN_EPSILON;
  14864. }
  14865. var TIME_REG = /^(?:(\d{4})(?:[-\/](\d{1,2})(?:[-\/](\d{1,2})(?:[T ](\d{1,2})(?::(\d\d)(?::(\d\d)(?:[.,](\d+))?)?)?(Z|[\+\-]\d\d:?\d\d)?)?)?)?)?$/; // jshint ignore:line
  14866. /**
  14867. * @param {string|Date|number} value These values can be accepted:
  14868. * + An instance of Date, represent a time in its own time zone.
  14869. * + Or string in a subset of ISO 8601, only including:
  14870. * + only year, month, date: '2012-03', '2012-03-01', '2012-03-01 05', '2012-03-01 05:06',
  14871. * + separated with T or space: '2012-03-01T12:22:33.123', '2012-03-01 12:22:33.123',
  14872. * + time zone: '2012-03-01T12:22:33Z', '2012-03-01T12:22:33+8000', '2012-03-01T12:22:33-05:00',
  14873. * all of which will be treated as local time if time zone is not specified
  14874. * (see <https://momentjs.com/>).
  14875. * + Or other string format, including (all of which will be treated as loacal time):
  14876. * '2012', '2012-3-1', '2012/3/1', '2012/03/01',
  14877. * '2009/6/12 2:00', '2009/6/12 2:05:08', '2009/6/12 2:05:08.123'
  14878. * + a timestamp, which represent a time in UTC.
  14879. * @return {Date} date
  14880. */
  14881. function parseDate(value) {
  14882. if (value instanceof Date) {
  14883. return value;
  14884. }
  14885. else if (typeof value === 'string') {
  14886. // Different browsers parse date in different way, so we parse it manually.
  14887. // Some other issues:
  14888. // new Date('1970-01-01') is UTC,
  14889. // new Date('1970/01/01') and new Date('1970-1-01') is local.
  14890. // See issue #3623
  14891. var match = TIME_REG.exec(value);
  14892. if (!match) {
  14893. // return Invalid Date.
  14894. return new Date(NaN);
  14895. }
  14896. // Use local time when no timezone offset specifed.
  14897. if (!match[8]) {
  14898. // match[n] can only be string or undefined.
  14899. // But take care of '12' + 1 => '121'.
  14900. return new Date(
  14901. +match[1],
  14902. +(match[2] || 1) - 1,
  14903. +match[3] || 1,
  14904. +match[4] || 0,
  14905. +(match[5] || 0),
  14906. +match[6] || 0,
  14907. +match[7] || 0
  14908. );
  14909. }
  14910. // Timezoneoffset of Javascript Date has considered DST (Daylight Saving Time,
  14911. // https://tc39.github.io/ecma262/#sec-daylight-saving-time-adjustment).
  14912. // For example, system timezone is set as "Time Zone: America/Toronto",
  14913. // then these code will get different result:
  14914. // `new Date(1478411999999).getTimezoneOffset(); // get 240`
  14915. // `new Date(1478412000000).getTimezoneOffset(); // get 300`
  14916. // So we should not use `new Date`, but use `Date.UTC`.
  14917. else {
  14918. var hour = +match[4] || 0;
  14919. if (match[8].toUpperCase() !== 'Z') {
  14920. hour -= match[8].slice(0, 3);
  14921. }
  14922. return new Date(Date.UTC(
  14923. +match[1],
  14924. +(match[2] || 1) - 1,
  14925. +match[3] || 1,
  14926. hour,
  14927. +(match[5] || 0),
  14928. +match[6] || 0,
  14929. +match[7] || 0
  14930. ));
  14931. }
  14932. }
  14933. else if (value == null) {
  14934. return new Date(NaN);
  14935. }
  14936. return new Date(Math.round(value));
  14937. }
  14938. /**
  14939. * Quantity of a number. e.g. 0.1, 1, 10, 100
  14940. *
  14941. * @param {number} val
  14942. * @return {number}
  14943. */
  14944. function quantity(val) {
  14945. return Math.pow(10, quantityExponent(val));
  14946. }
  14947. function quantityExponent(val) {
  14948. return Math.floor(Math.log(val) / Math.LN10);
  14949. }
  14950. /**
  14951. * find a “nice” number approximately equal to x. Round the number if round = true,
  14952. * take ceiling if round = false. The primary observation is that the “nicest”
  14953. * numbers in decimal are 1, 2, and 5, and all power-of-ten multiples of these numbers.
  14954. *
  14955. * See "Nice Numbers for Graph Labels" of Graphic Gems.
  14956. *
  14957. * @param {number} val Non-negative value.
  14958. * @param {boolean} round
  14959. * @return {number}
  14960. */
  14961. function nice(val, round) {
  14962. var exponent = quantityExponent(val);
  14963. var exp10 = Math.pow(10, exponent);
  14964. var f = val / exp10; // 1 <= f < 10
  14965. var nf;
  14966. if (round) {
  14967. if (f < 1.5) { nf = 1; }
  14968. else if (f < 2.5) { nf = 2; }
  14969. else if (f < 4) { nf = 3; }
  14970. else if (f < 7) { nf = 5; }
  14971. else { nf = 10; }
  14972. }
  14973. else {
  14974. if (f < 1) { nf = 1; }
  14975. else if (f < 2) { nf = 2; }
  14976. else if (f < 3) { nf = 3; }
  14977. else if (f < 5) { nf = 5; }
  14978. else { nf = 10; }
  14979. }
  14980. val = nf * exp10;
  14981. // Fix 3 * 0.1 === 0.30000000000000004 issue (see IEEE 754).
  14982. // 20 is the uppper bound of toFixed.
  14983. return exponent >= -20 ? +val.toFixed(exponent < 0 ? -exponent : 0) : val;
  14984. }
  14985. /**
  14986. * Order intervals asc, and split them when overlap.
  14987. * expect(numberUtil.reformIntervals([
  14988. * {interval: [18, 62], close: [1, 1]},
  14989. * {interval: [-Infinity, -70], close: [0, 0]},
  14990. * {interval: [-70, -26], close: [1, 1]},
  14991. * {interval: [-26, 18], close: [1, 1]},
  14992. * {interval: [62, 150], close: [1, 1]},
  14993. * {interval: [106, 150], close: [1, 1]},
  14994. * {interval: [150, Infinity], close: [0, 0]}
  14995. * ])).toEqual([
  14996. * {interval: [-Infinity, -70], close: [0, 0]},
  14997. * {interval: [-70, -26], close: [1, 1]},
  14998. * {interval: [-26, 18], close: [0, 1]},
  14999. * {interval: [18, 62], close: [0, 1]},
  15000. * {interval: [62, 150], close: [0, 1]},
  15001. * {interval: [150, Infinity], close: [0, 0]}
  15002. * ]);
  15003. * @param {Array.<Object>} list, where `close` mean open or close
  15004. * of the interval, and Infinity can be used.
  15005. * @return {Array.<Object>} The origin list, which has been reformed.
  15006. */
  15007. /**
  15008. * parseFloat NaNs numeric-cast false positives (null|true|false|"")
  15009. * ...but misinterprets leading-number strings, particularly hex literals ("0x...")
  15010. * subtraction forces infinities to NaN
  15011. *
  15012. * @param {*} v
  15013. * @return {boolean}
  15014. */
  15015. /**
  15016. * 每三位默认加,格式化
  15017. * @param {string|number} x
  15018. * @return {string}
  15019. */
  15020. function addCommas(x) {
  15021. if (isNaN(x)) {
  15022. return '-';
  15023. }
  15024. x = (x + '').split('.');
  15025. return x[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g,'$1,')
  15026. + (x.length > 1 ? ('.' + x[1]) : '');
  15027. }
  15028. /**
  15029. * @param {string} str
  15030. * @param {boolean} [upperCaseFirst=false]
  15031. * @return {string} str
  15032. */
  15033. var normalizeCssArray$1 = normalizeCssArray;
  15034. function encodeHTML(source) {
  15035. return String(source)
  15036. .replace(/&/g, '&amp;')
  15037. .replace(/</g, '&lt;')
  15038. .replace(/>/g, '&gt;')
  15039. .replace(/"/g, '&quot;')
  15040. .replace(/'/g, '&#39;');
  15041. }
  15042. var TPL_VAR_ALIAS = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
  15043. var wrapVar = function (varName, seriesIdx) {
  15044. return '{' + varName + (seriesIdx == null ? '' : seriesIdx) + '}';
  15045. };
  15046. /**
  15047. * Template formatter
  15048. * @param {string} tpl
  15049. * @param {Array.<Object>|Object} paramsList
  15050. * @param {boolean} [encode=false]
  15051. * @return {string}
  15052. */
  15053. function formatTpl(tpl, paramsList, encode) {
  15054. if (!isArray(paramsList)) {
  15055. paramsList = [paramsList];
  15056. }
  15057. var seriesLen = paramsList.length;
  15058. if (!seriesLen) {
  15059. return '';
  15060. }
  15061. var $vars = paramsList[0].$vars || [];
  15062. for (var i = 0; i < $vars.length; i++) {
  15063. var alias = TPL_VAR_ALIAS[i];
  15064. tpl = tpl.replace(wrapVar(alias), wrapVar(alias, 0));
  15065. }
  15066. for (var seriesIdx = 0; seriesIdx < seriesLen; seriesIdx++) {
  15067. for (var k = 0; k < $vars.length; k++) {
  15068. var val = paramsList[seriesIdx][$vars[k]];
  15069. tpl = tpl.replace(
  15070. wrapVar(TPL_VAR_ALIAS[k], seriesIdx),
  15071. encode ? encodeHTML(val) : val
  15072. );
  15073. }
  15074. }
  15075. return tpl;
  15076. }
  15077. /**
  15078. * simple Template formatter
  15079. *
  15080. * @param {string} tpl
  15081. * @param {Object} param
  15082. * @param {boolean} [encode=false]
  15083. * @return {string}
  15084. */
  15085. /**
  15086. * @param {Object|string} [opt] If string, means color.
  15087. * @param {string} [opt.color]
  15088. * @param {string} [opt.extraCssText]
  15089. * @param {string} [opt.type='item'] 'item' or 'subItem'
  15090. * @return {string}
  15091. */
  15092. function getTooltipMarker(opt, extraCssText) {
  15093. opt = isString(opt) ? {color: opt, extraCssText: extraCssText} : (opt || {});
  15094. var color = opt.color;
  15095. var type = opt.type;
  15096. var extraCssText = opt.extraCssText;
  15097. if (!color) {
  15098. return '';
  15099. }
  15100. return type === 'subItem'
  15101. ? '<span style="display:inline-block;vertical-align:middle;margin-right:8px;margin-left:3px;'
  15102. + 'border-radius:4px;width:4px;height:4px;background-color:'
  15103. + encodeHTML(color) + ';' + (extraCssText || '') + '"></span>'
  15104. : '<span style="display:inline-block;margin-right:5px;'
  15105. + 'border-radius:10px;width:10px;height:10px;background-color:'
  15106. + encodeHTML(color) + ';' + (extraCssText || '') + '"></span>';
  15107. }
  15108. function pad(str, len) {
  15109. str += '';
  15110. return '0000'.substr(0, len - str.length) + str;
  15111. }
  15112. /**
  15113. * ISO Date format
  15114. * @param {string} tpl
  15115. * @param {number} value
  15116. * @param {boolean} [isUTC=false] Default in local time.
  15117. * see `module:echarts/scale/Time`
  15118. * and `module:echarts/util/number#parseDate`.
  15119. * @inner
  15120. */
  15121. function formatTime(tpl, value, isUTC) {
  15122. if (tpl === 'week'
  15123. || tpl === 'month'
  15124. || tpl === 'quarter'
  15125. || tpl === 'half-year'
  15126. || tpl === 'year'
  15127. ) {
  15128. tpl = 'MM-dd\nyyyy';
  15129. }
  15130. var date = parseDate(value);
  15131. var utc = isUTC ? 'UTC' : '';
  15132. var y = date['get' + utc + 'FullYear']();
  15133. var M = date['get' + utc + 'Month']() + 1;
  15134. var d = date['get' + utc + 'Date']();
  15135. var h = date['get' + utc + 'Hours']();
  15136. var m = date['get' + utc + 'Minutes']();
  15137. var s = date['get' + utc + 'Seconds']();
  15138. var S = date['get' + utc + 'Milliseconds']();
  15139. tpl = tpl.replace('MM', pad(M, 2))
  15140. .replace('M', M)
  15141. .replace('yyyy', y)
  15142. .replace('yy', y % 100)
  15143. .replace('dd', pad(d, 2))
  15144. .replace('d', d)
  15145. .replace('hh', pad(h, 2))
  15146. .replace('h', h)
  15147. .replace('mm', pad(m, 2))
  15148. .replace('m', m)
  15149. .replace('ss', pad(s, 2))
  15150. .replace('s', s)
  15151. .replace('SSS', pad(S, 3));
  15152. return tpl;
  15153. }
  15154. /**
  15155. * Capital first
  15156. * @param {string} str
  15157. * @return {string}
  15158. */
  15159. var truncateText$1 = truncateText;
  15160. // Layout helpers for each component positioning
  15161. var each$3 = each$1;
  15162. /**
  15163. * @public
  15164. */
  15165. var LOCATION_PARAMS = [
  15166. 'left', 'right', 'top', 'bottom', 'width', 'height'
  15167. ];
  15168. /**
  15169. * @public
  15170. */
  15171. var HV_NAMES = [
  15172. ['width', 'left', 'right'],
  15173. ['height', 'top', 'bottom']
  15174. ];
  15175. function boxLayout(orient, group, gap, maxWidth, maxHeight) {
  15176. var x = 0;
  15177. var y = 0;
  15178. if (maxWidth == null) {
  15179. maxWidth = Infinity;
  15180. }
  15181. if (maxHeight == null) {
  15182. maxHeight = Infinity;
  15183. }
  15184. var currentLineMaxSize = 0;
  15185. group.eachChild(function (child, idx) {
  15186. var position = child.position;
  15187. var rect = child.getBoundingRect();
  15188. var nextChild = group.childAt(idx + 1);
  15189. var nextChildRect = nextChild && nextChild.getBoundingRect();
  15190. var nextX;
  15191. var nextY;
  15192. if (orient === 'horizontal') {
  15193. var moveX = rect.width + (nextChildRect ? (-nextChildRect.x + rect.x) : 0);
  15194. nextX = x + moveX;
  15195. // Wrap when width exceeds maxWidth or meet a `newline` group
  15196. // FIXME compare before adding gap?
  15197. if (nextX > maxWidth || child.newline) {
  15198. x = 0;
  15199. nextX = moveX;
  15200. y += currentLineMaxSize + gap;
  15201. currentLineMaxSize = rect.height;
  15202. }
  15203. else {
  15204. // FIXME: consider rect.y is not `0`?
  15205. currentLineMaxSize = Math.max(currentLineMaxSize, rect.height);
  15206. }
  15207. }
  15208. else {
  15209. var moveY = rect.height + (nextChildRect ? (-nextChildRect.y + rect.y) : 0);
  15210. nextY = y + moveY;
  15211. // Wrap when width exceeds maxHeight or meet a `newline` group
  15212. if (nextY > maxHeight || child.newline) {
  15213. x += currentLineMaxSize + gap;
  15214. y = 0;
  15215. nextY = moveY;
  15216. currentLineMaxSize = rect.width;
  15217. }
  15218. else {
  15219. currentLineMaxSize = Math.max(currentLineMaxSize, rect.width);
  15220. }
  15221. }
  15222. if (child.newline) {
  15223. return;
  15224. }
  15225. position[0] = x;
  15226. position[1] = y;
  15227. orient === 'horizontal'
  15228. ? (x = nextX + gap)
  15229. : (y = nextY + gap);
  15230. });
  15231. }
  15232. /**
  15233. * VBox or HBox layouting
  15234. * @param {string} orient
  15235. * @param {module:zrender/container/Group} group
  15236. * @param {number} gap
  15237. * @param {number} [width=Infinity]
  15238. * @param {number} [height=Infinity]
  15239. */
  15240. /**
  15241. * VBox layouting
  15242. * @param {module:zrender/container/Group} group
  15243. * @param {number} gap
  15244. * @param {number} [width=Infinity]
  15245. * @param {number} [height=Infinity]
  15246. */
  15247. var vbox = curry(boxLayout, 'vertical');
  15248. /**
  15249. * HBox layouting
  15250. * @param {module:zrender/container/Group} group
  15251. * @param {number} gap
  15252. * @param {number} [width=Infinity]
  15253. * @param {number} [height=Infinity]
  15254. */
  15255. var hbox = curry(boxLayout, 'horizontal');
  15256. /**
  15257. * If x or x2 is not specified or 'center' 'left' 'right',
  15258. * the width would be as long as possible.
  15259. * If y or y2 is not specified or 'middle' 'top' 'bottom',
  15260. * the height would be as long as possible.
  15261. *
  15262. * @param {Object} positionInfo
  15263. * @param {number|string} [positionInfo.x]
  15264. * @param {number|string} [positionInfo.y]
  15265. * @param {number|string} [positionInfo.x2]
  15266. * @param {number|string} [positionInfo.y2]
  15267. * @param {Object} containerRect {width, height}
  15268. * @param {string|number} margin
  15269. * @return {Object} {width, height}
  15270. */
  15271. /**
  15272. * Parse position info.
  15273. *
  15274. * @param {Object} positionInfo
  15275. * @param {number|string} [positionInfo.left]
  15276. * @param {number|string} [positionInfo.top]
  15277. * @param {number|string} [positionInfo.right]
  15278. * @param {number|string} [positionInfo.bottom]
  15279. * @param {number|string} [positionInfo.width]
  15280. * @param {number|string} [positionInfo.height]
  15281. * @param {number|string} [positionInfo.aspect] Aspect is width / height
  15282. * @param {Object} containerRect
  15283. * @param {string|number} [margin]
  15284. *
  15285. * @return {module:zrender/core/BoundingRect}
  15286. */
  15287. function getLayoutRect(
  15288. positionInfo, containerRect, margin
  15289. ) {
  15290. margin = normalizeCssArray$1(margin || 0);
  15291. var containerWidth = containerRect.width;
  15292. var containerHeight = containerRect.height;
  15293. var left = parsePercent$1(positionInfo.left, containerWidth);
  15294. var top = parsePercent$1(positionInfo.top, containerHeight);
  15295. var right = parsePercent$1(positionInfo.right, containerWidth);
  15296. var bottom = parsePercent$1(positionInfo.bottom, containerHeight);
  15297. var width = parsePercent$1(positionInfo.width, containerWidth);
  15298. var height = parsePercent$1(positionInfo.height, containerHeight);
  15299. var verticalMargin = margin[2] + margin[0];
  15300. var horizontalMargin = margin[1] + margin[3];
  15301. var aspect = positionInfo.aspect;
  15302. // If width is not specified, calculate width from left and right
  15303. if (isNaN(width)) {
  15304. width = containerWidth - right - horizontalMargin - left;
  15305. }
  15306. if (isNaN(height)) {
  15307. height = containerHeight - bottom - verticalMargin - top;
  15308. }
  15309. if (aspect != null) {
  15310. // If width and height are not given
  15311. // 1. Graph should not exceeds the container
  15312. // 2. Aspect must be keeped
  15313. // 3. Graph should take the space as more as possible
  15314. // FIXME
  15315. // Margin is not considered, because there is no case that both
  15316. // using margin and aspect so far.
  15317. if (isNaN(width) && isNaN(height)) {
  15318. if (aspect > containerWidth / containerHeight) {
  15319. width = containerWidth * 0.8;
  15320. }
  15321. else {
  15322. height = containerHeight * 0.8;
  15323. }
  15324. }
  15325. // Calculate width or height with given aspect
  15326. if (isNaN(width)) {
  15327. width = aspect * height;
  15328. }
  15329. if (isNaN(height)) {
  15330. height = width / aspect;
  15331. }
  15332. }
  15333. // If left is not specified, calculate left from right and width
  15334. if (isNaN(left)) {
  15335. left = containerWidth - right - width - horizontalMargin;
  15336. }
  15337. if (isNaN(top)) {
  15338. top = containerHeight - bottom - height - verticalMargin;
  15339. }
  15340. // Align left and top
  15341. switch (positionInfo.left || positionInfo.right) {
  15342. case 'center':
  15343. left = containerWidth / 2 - width / 2 - margin[3];
  15344. break;
  15345. case 'right':
  15346. left = containerWidth - width - horizontalMargin;
  15347. break;
  15348. }
  15349. switch (positionInfo.top || positionInfo.bottom) {
  15350. case 'middle':
  15351. case 'center':
  15352. top = containerHeight / 2 - height / 2 - margin[0];
  15353. break;
  15354. case 'bottom':
  15355. top = containerHeight - height - verticalMargin;
  15356. break;
  15357. }
  15358. // If something is wrong and left, top, width, height are calculated as NaN
  15359. left = left || 0;
  15360. top = top || 0;
  15361. if (isNaN(width)) {
  15362. // Width may be NaN if only one value is given except width
  15363. width = containerWidth - horizontalMargin - left - (right || 0);
  15364. }
  15365. if (isNaN(height)) {
  15366. // Height may be NaN if only one value is given except height
  15367. height = containerHeight - verticalMargin - top - (bottom || 0);
  15368. }
  15369. var rect = new BoundingRect(left + margin[3], top + margin[0], width, height);
  15370. rect.margin = margin;
  15371. return rect;
  15372. }
  15373. /**
  15374. * Position a zr element in viewport
  15375. * Group position is specified by either
  15376. * {left, top}, {right, bottom}
  15377. * If all properties exists, right and bottom will be igonred.
  15378. *
  15379. * Logic:
  15380. * 1. Scale (against origin point in parent coord)
  15381. * 2. Rotate (against origin point in parent coord)
  15382. * 3. Traslate (with el.position by this method)
  15383. * So this method only fixes the last step 'Traslate', which does not affect
  15384. * scaling and rotating.
  15385. *
  15386. * If be called repeatly with the same input el, the same result will be gotten.
  15387. *
  15388. * @param {module:zrender/Element} el Should have `getBoundingRect` method.
  15389. * @param {Object} positionInfo
  15390. * @param {number|string} [positionInfo.left]
  15391. * @param {number|string} [positionInfo.top]
  15392. * @param {number|string} [positionInfo.right]
  15393. * @param {number|string} [positionInfo.bottom]
  15394. * @param {number|string} [positionInfo.width] Only for opt.boundingModel: 'raw'
  15395. * @param {number|string} [positionInfo.height] Only for opt.boundingModel: 'raw'
  15396. * @param {Object} containerRect
  15397. * @param {string|number} margin
  15398. * @param {Object} [opt]
  15399. * @param {Array.<number>} [opt.hv=[1,1]] Only horizontal or only vertical.
  15400. * @param {Array.<number>} [opt.boundingMode='all']
  15401. * Specify how to calculate boundingRect when locating.
  15402. * 'all': Position the boundingRect that is transformed and uioned
  15403. * both itself and its descendants.
  15404. * This mode simplies confine the elements in the bounding
  15405. * of their container (e.g., using 'right: 0').
  15406. * 'raw': Position the boundingRect that is not transformed and only itself.
  15407. * This mode is useful when you want a element can overflow its
  15408. * container. (Consider a rotated circle needs to be located in a corner.)
  15409. * In this mode positionInfo.width/height can only be number.
  15410. */
  15411. /**
  15412. * @param {Object} option Contains some of the properties in HV_NAMES.
  15413. * @param {number} hvIdx 0: horizontal; 1: vertical.
  15414. */
  15415. /**
  15416. * Consider Case:
  15417. * When defulat option has {left: 0, width: 100}, and we set {right: 0}
  15418. * through setOption or media query, using normal zrUtil.merge will cause
  15419. * {right: 0} does not take effect.
  15420. *
  15421. * @example
  15422. * ComponentModel.extend({
  15423. * init: function () {
  15424. * ...
  15425. * var inputPositionParams = layout.getLayoutParams(option);
  15426. * this.mergeOption(inputPositionParams);
  15427. * },
  15428. * mergeOption: function (newOption) {
  15429. * newOption && zrUtil.merge(thisOption, newOption, true);
  15430. * layout.mergeLayoutParam(thisOption, newOption);
  15431. * }
  15432. * });
  15433. *
  15434. * @param {Object} targetOption
  15435. * @param {Object} newOption
  15436. * @param {Object|string} [opt]
  15437. * @param {boolean|Array.<boolean>} [opt.ignoreSize=false] Used for the components
  15438. * that width (or height) should not be calculated by left and right (or top and bottom).
  15439. */
  15440. function mergeLayoutParam(targetOption, newOption, opt) {
  15441. !isObject$1(opt) && (opt = {});
  15442. var ignoreSize = opt.ignoreSize;
  15443. !isArray(ignoreSize) && (ignoreSize = [ignoreSize, ignoreSize]);
  15444. var hResult = merge$$1(HV_NAMES[0], 0);
  15445. var vResult = merge$$1(HV_NAMES[1], 1);
  15446. copy(HV_NAMES[0], targetOption, hResult);
  15447. copy(HV_NAMES[1], targetOption, vResult);
  15448. function merge$$1(names, hvIdx) {
  15449. var newParams = {};
  15450. var newValueCount = 0;
  15451. var merged = {};
  15452. var mergedValueCount = 0;
  15453. var enoughParamNumber = 2;
  15454. each$3(names, function (name) {
  15455. merged[name] = targetOption[name];
  15456. });
  15457. each$3(names, function (name) {
  15458. // Consider case: newOption.width is null, which is
  15459. // set by user for removing width setting.
  15460. hasProp(newOption, name) && (newParams[name] = merged[name] = newOption[name]);
  15461. hasValue(newParams, name) && newValueCount++;
  15462. hasValue(merged, name) && mergedValueCount++;
  15463. });
  15464. if (ignoreSize[hvIdx]) {
  15465. // Only one of left/right is premitted to exist.
  15466. if (hasValue(newOption, names[1])) {
  15467. merged[names[2]] = null;
  15468. }
  15469. else if (hasValue(newOption, names[2])) {
  15470. merged[names[1]] = null;
  15471. }
  15472. return merged;
  15473. }
  15474. // Case: newOption: {width: ..., right: ...},
  15475. // or targetOption: {right: ...} and newOption: {width: ...},
  15476. // There is no conflict when merged only has params count
  15477. // little than enoughParamNumber.
  15478. if (mergedValueCount === enoughParamNumber || !newValueCount) {
  15479. return merged;
  15480. }
  15481. // Case: newOption: {width: ..., right: ...},
  15482. // Than we can make sure user only want those two, and ignore
  15483. // all origin params in targetOption.
  15484. else if (newValueCount >= enoughParamNumber) {
  15485. return newParams;
  15486. }
  15487. else {
  15488. // Chose another param from targetOption by priority.
  15489. for (var i = 0; i < names.length; i++) {
  15490. var name = names[i];
  15491. if (!hasProp(newParams, name) && hasProp(targetOption, name)) {
  15492. newParams[name] = targetOption[name];
  15493. break;
  15494. }
  15495. }
  15496. return newParams;
  15497. }
  15498. }
  15499. function hasProp(obj, name) {
  15500. return obj.hasOwnProperty(name);
  15501. }
  15502. function hasValue(obj, name) {
  15503. return obj[name] != null && obj[name] !== 'auto';
  15504. }
  15505. function copy(names, target, source) {
  15506. each$3(names, function (name) {
  15507. target[name] = source[name];
  15508. });
  15509. }
  15510. }
  15511. /**
  15512. * Retrieve 'left', 'right', 'top', 'bottom', 'width', 'height' from object.
  15513. * @param {Object} source
  15514. * @return {Object} Result contains those props.
  15515. */
  15516. function getLayoutParams(source) {
  15517. return copyLayoutParams({}, source);
  15518. }
  15519. /**
  15520. * Retrieve 'left', 'right', 'top', 'bottom', 'width', 'height' from object.
  15521. * @param {Object} source
  15522. * @return {Object} Result contains those props.
  15523. */
  15524. function copyLayoutParams(target, source) {
  15525. source && target && each$3(LOCATION_PARAMS, function (name) {
  15526. source.hasOwnProperty(name) && (target[name] = source[name]);
  15527. });
  15528. return target;
  15529. }
  15530. var boxLayoutMixin = {
  15531. getBoxLayoutParams: function () {
  15532. return {
  15533. left: this.get('left'),
  15534. top: this.get('top'),
  15535. right: this.get('right'),
  15536. bottom: this.get('bottom'),
  15537. width: this.get('width'),
  15538. height: this.get('height')
  15539. };
  15540. }
  15541. };
  15542. /**
  15543. * Component model
  15544. *
  15545. * @module echarts/model/Component
  15546. */
  15547. var inner$1 = makeInner();
  15548. /**
  15549. * @alias module:echarts/model/Component
  15550. * @constructor
  15551. * @param {Object} option
  15552. * @param {module:echarts/model/Model} parentModel
  15553. * @param {module:echarts/model/Model} ecModel
  15554. */
  15555. var ComponentModel = Model.extend({
  15556. type: 'component',
  15557. /**
  15558. * @readOnly
  15559. * @type {string}
  15560. */
  15561. id: '',
  15562. /**
  15563. * Because simplified concept is probably better, series.name (or component.name)
  15564. * has been having too many resposibilities:
  15565. * (1) Generating id (which requires name in option should not be modified).
  15566. * (2) As an index to mapping series when merging option or calling API (a name
  15567. * can refer to more then one components, which is convinient is some case).
  15568. * (3) Display.
  15569. * @readOnly
  15570. */
  15571. name: '',
  15572. /**
  15573. * @readOnly
  15574. * @type {string}
  15575. */
  15576. mainType: '',
  15577. /**
  15578. * @readOnly
  15579. * @type {string}
  15580. */
  15581. subType: '',
  15582. /**
  15583. * @readOnly
  15584. * @type {number}
  15585. */
  15586. componentIndex: 0,
  15587. /**
  15588. * @type {Object}
  15589. * @protected
  15590. */
  15591. defaultOption: null,
  15592. /**
  15593. * @type {module:echarts/model/Global}
  15594. * @readOnly
  15595. */
  15596. ecModel: null,
  15597. /**
  15598. * key: componentType
  15599. * value: Component model list, can not be null.
  15600. * @type {Object.<string, Array.<module:echarts/model/Model>>}
  15601. * @readOnly
  15602. */
  15603. dependentModels: [],
  15604. /**
  15605. * @type {string}
  15606. * @readOnly
  15607. */
  15608. uid: null,
  15609. /**
  15610. * Support merge layout params.
  15611. * Only support 'box' now (left/right/top/bottom/width/height).
  15612. * @type {string|Object} Object can be {ignoreSize: true}
  15613. * @readOnly
  15614. */
  15615. layoutMode: null,
  15616. $constructor: function (option, parentModel, ecModel, extraOpt) {
  15617. Model.call(this, option, parentModel, ecModel, extraOpt);
  15618. this.uid = getUID('ec_cpt_model');
  15619. },
  15620. init: function (option, parentModel, ecModel, extraOpt) {
  15621. this.mergeDefaultAndTheme(option, ecModel);
  15622. },
  15623. mergeDefaultAndTheme: function (option, ecModel) {
  15624. var layoutMode = this.layoutMode;
  15625. var inputPositionParams = layoutMode
  15626. ? getLayoutParams(option) : {};
  15627. var themeModel = ecModel.getTheme();
  15628. merge(option, themeModel.get(this.mainType));
  15629. merge(option, this.getDefaultOption());
  15630. if (layoutMode) {
  15631. mergeLayoutParam(option, inputPositionParams, layoutMode);
  15632. }
  15633. },
  15634. mergeOption: function (option, extraOpt) {
  15635. merge(this.option, option, true);
  15636. var layoutMode = this.layoutMode;
  15637. if (layoutMode) {
  15638. mergeLayoutParam(this.option, option, layoutMode);
  15639. }
  15640. },
  15641. // Hooker after init or mergeOption
  15642. optionUpdated: function (newCptOption, isInit) {},
  15643. getDefaultOption: function () {
  15644. var fields = inner$1(this);
  15645. if (!fields.defaultOption) {
  15646. var optList = [];
  15647. var Class = this.constructor;
  15648. while (Class) {
  15649. var opt = Class.prototype.defaultOption;
  15650. opt && optList.push(opt);
  15651. Class = Class.superClass;
  15652. }
  15653. var defaultOption = {};
  15654. for (var i = optList.length - 1; i >= 0; i--) {
  15655. defaultOption = merge(defaultOption, optList[i], true);
  15656. }
  15657. fields.defaultOption = defaultOption;
  15658. }
  15659. return fields.defaultOption;
  15660. },
  15661. getReferringComponents: function (mainType) {
  15662. return this.ecModel.queryComponents({
  15663. mainType: mainType,
  15664. index: this.get(mainType + 'Index', true),
  15665. id: this.get(mainType + 'Id', true)
  15666. });
  15667. }
  15668. });
  15669. // Reset ComponentModel.extend, add preConstruct.
  15670. // clazzUtil.enableClassExtend(
  15671. // ComponentModel,
  15672. // function (option, parentModel, ecModel, extraOpt) {
  15673. // // Set dependentModels, componentIndex, name, id, mainType, subType.
  15674. // zrUtil.extend(this, extraOpt);
  15675. // this.uid = componentUtil.getUID('componentModel');
  15676. // // this.setReadOnly([
  15677. // // 'type', 'id', 'uid', 'name', 'mainType', 'subType',
  15678. // // 'dependentModels', 'componentIndex'
  15679. // // ]);
  15680. // }
  15681. // );
  15682. // Add capability of registerClass, getClass, hasClass, registerSubTypeDefaulter and so on.
  15683. enableClassManagement(
  15684. ComponentModel, {registerWhenExtend: true}
  15685. );
  15686. enableSubTypeDefaulter(ComponentModel);
  15687. // Add capability of ComponentModel.topologicalTravel.
  15688. enableTopologicalTravel(ComponentModel, getDependencies);
  15689. function getDependencies(componentType) {
  15690. var deps = [];
  15691. each$1(ComponentModel.getClassesByMainType(componentType), function (Clazz) {
  15692. deps = deps.concat(Clazz.prototype.dependencies || []);
  15693. });
  15694. // Ensure main type.
  15695. deps = map(deps, function (type) {
  15696. return parseClassType$1(type).main;
  15697. });
  15698. // Hack dataset for convenience.
  15699. if (componentType !== 'dataset' && indexOf(deps, 'dataset') <= 0) {
  15700. deps.unshift('dataset');
  15701. }
  15702. return deps;
  15703. }
  15704. mixin(ComponentModel, boxLayoutMixin);
  15705. var platform = '';
  15706. // Navigator not exists in node
  15707. if (typeof navigator !== 'undefined') {
  15708. platform = navigator.platform || '';
  15709. }
  15710. var globalDefault = {
  15711. // backgroundColor: 'rgba(0,0,0,0)',
  15712. // https://dribbble.com/shots/1065960-Infographic-Pie-chart-visualization
  15713. // color: ['#5793f3', '#d14a61', '#fd9c35', '#675bba', '#fec42c', '#dd4444', '#d4df5a', '#cd4870'],
  15714. // Light colors:
  15715. // color: ['#bcd3bb', '#e88f70', '#edc1a5', '#9dc5c8', '#e1e8c8', '#7b7c68', '#e5b5b5', '#f0b489', '#928ea8', '#bda29a'],
  15716. // color: ['#cc5664', '#9bd6ec', '#ea946e', '#8acaaa', '#f1ec64', '#ee8686', '#a48dc1', '#5da6bc', '#b9dcae'],
  15717. // Dark colors:
  15718. color: ['#c23531','#2f4554', '#61a0a8', '#d48265', '#91c7ae','#749f83', '#ca8622', '#bda29a','#6e7074', '#546570', '#c4ccd3'],
  15719. gradientColor: ['#f6efa6', '#d88273', '#bf444c'],
  15720. // If xAxis and yAxis declared, grid is created by default.
  15721. // grid: {},
  15722. textStyle: {
  15723. // color: '#000',
  15724. // decoration: 'none',
  15725. // PENDING
  15726. fontFamily: platform.match(/^Win/) ? 'Microsoft YaHei' : 'sans-serif',
  15727. // fontFamily: 'Arial, Verdana, sans-serif',
  15728. fontSize: 12,
  15729. fontStyle: 'normal',
  15730. fontWeight: 'normal'
  15731. },
  15732. // http://blogs.adobe.com/webplatform/2014/02/24/using-blend-modes-in-html-canvas/
  15733. // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
  15734. // Default is source-over
  15735. blendMode: null,
  15736. animation: 'auto',
  15737. animationDuration: 1000,
  15738. animationDurationUpdate: 300,
  15739. animationEasing: 'exponentialOut',
  15740. animationEasingUpdate: 'cubicOut',
  15741. animationThreshold: 2000,
  15742. // Configuration for progressive/incremental rendering
  15743. progressiveThreshold: 3000,
  15744. progressive: 400,
  15745. // Threshold of if use single hover layer to optimize.
  15746. // It is recommended that `hoverLayerThreshold` is equivalent to or less than
  15747. // `progressiveThreshold`, otherwise hover will cause restart of progressive,
  15748. // which is unexpected.
  15749. // see example <echarts/test/heatmap-large.html>.
  15750. hoverLayerThreshold: 3000,
  15751. // See: module:echarts/scale/Time
  15752. useUTC: false
  15753. };
  15754. var inner$2 = makeInner();
  15755. function getNearestColorPalette(colors, requestColorNum) {
  15756. var paletteNum = colors.length;
  15757. // TODO colors must be in order
  15758. for (var i = 0; i < paletteNum; i++) {
  15759. if (colors[i].length > requestColorNum) {
  15760. return colors[i];
  15761. }
  15762. }
  15763. return colors[paletteNum - 1];
  15764. }
  15765. var colorPaletteMixin = {
  15766. clearColorPalette: function () {
  15767. inner$2(this).colorIdx = 0;
  15768. inner$2(this).colorNameMap = {};
  15769. },
  15770. /**
  15771. * @param {string} name MUST NOT be null/undefined. Otherwise call this function
  15772. * twise with the same parameters will get different result.
  15773. * @param {Object} [scope=this]
  15774. * @param {Object} [requestColorNum]
  15775. * @return {string} color string.
  15776. */
  15777. getColorFromPalette: function (name, scope, requestColorNum) {
  15778. scope = scope || this;
  15779. var scopeFields = inner$2(scope);
  15780. var colorIdx = scopeFields.colorIdx || 0;
  15781. var colorNameMap = scopeFields.colorNameMap = scopeFields.colorNameMap || {};
  15782. // Use `hasOwnProperty` to avoid conflict with Object.prototype.
  15783. if (colorNameMap.hasOwnProperty(name)) {
  15784. return colorNameMap[name];
  15785. }
  15786. var defaultColorPalette = normalizeToArray(this.get('color', true));
  15787. var layeredColorPalette = this.get('colorLayer', true);
  15788. var colorPalette = ((requestColorNum == null || !layeredColorPalette)
  15789. ? defaultColorPalette : getNearestColorPalette(layeredColorPalette, requestColorNum));
  15790. // In case can't find in layered color palette.
  15791. colorPalette = colorPalette || defaultColorPalette;
  15792. if (!colorPalette || !colorPalette.length) {
  15793. return;
  15794. }
  15795. var color = colorPalette[colorIdx];
  15796. if (name) {
  15797. colorNameMap[name] = color;
  15798. }
  15799. scopeFields.colorIdx = (colorIdx + 1) % colorPalette.length;
  15800. return color;
  15801. }
  15802. };
  15803. /**
  15804. * Helper for model references.
  15805. * There are many manners to refer axis/coordSys.
  15806. */
  15807. // TODO
  15808. // merge relevant logic to this file?
  15809. // check: "modelHelper" of tooltip and "BrushTargetManager".
  15810. /**
  15811. * @return {Object} For example:
  15812. * {
  15813. * coordSysName: 'cartesian2d',
  15814. * coordSysDims: ['x', 'y', ...],
  15815. * axisMap: HashMap({
  15816. * x: xAxisModel,
  15817. * y: yAxisModel
  15818. * }),
  15819. * categoryAxisMap: HashMap({
  15820. * x: xAxisModel,
  15821. * y: undefined
  15822. * }),
  15823. * // It also indicate that whether there is category axis.
  15824. * firstCategoryDimIndex: 1,
  15825. * // To replace user specified encode.
  15826. * }
  15827. */
  15828. function getCoordSysDefineBySeries(seriesModel) {
  15829. var coordSysName = seriesModel.get('coordinateSystem');
  15830. var result = {
  15831. coordSysName: coordSysName,
  15832. coordSysDims: [],
  15833. axisMap: createHashMap(),
  15834. categoryAxisMap: createHashMap()
  15835. };
  15836. var fetch = fetchers[coordSysName];
  15837. if (fetch) {
  15838. fetch(seriesModel, result, result.axisMap, result.categoryAxisMap);
  15839. return result;
  15840. }
  15841. }
  15842. var fetchers = {
  15843. cartesian2d: function (seriesModel, result, axisMap, categoryAxisMap) {
  15844. var xAxisModel = seriesModel.getReferringComponents('xAxis')[0];
  15845. var yAxisModel = seriesModel.getReferringComponents('yAxis')[0];
  15846. if (__DEV__) {
  15847. if (!xAxisModel) {
  15848. throw new Error('xAxis "' + retrieve(
  15849. seriesModel.get('xAxisIndex'),
  15850. seriesModel.get('xAxisId'),
  15851. 0
  15852. ) + '" not found');
  15853. }
  15854. if (!yAxisModel) {
  15855. throw new Error('yAxis "' + retrieve(
  15856. seriesModel.get('xAxisIndex'),
  15857. seriesModel.get('yAxisId'),
  15858. 0
  15859. ) + '" not found');
  15860. }
  15861. }
  15862. result.coordSysDims = ['x', 'y'];
  15863. axisMap.set('x', xAxisModel);
  15864. axisMap.set('y', yAxisModel);
  15865. if (isCategory(xAxisModel)) {
  15866. categoryAxisMap.set('x', xAxisModel);
  15867. result.firstCategoryDimIndex = 0;
  15868. }
  15869. if (isCategory(yAxisModel)) {
  15870. categoryAxisMap.set('y', yAxisModel);
  15871. result.firstCategoryDimIndex = 1;
  15872. }
  15873. },
  15874. singleAxis: function (seriesModel, result, axisMap, categoryAxisMap) {
  15875. var singleAxisModel = seriesModel.getReferringComponents('singleAxis')[0];
  15876. if (__DEV__) {
  15877. if (!singleAxisModel) {
  15878. throw new Error('singleAxis should be specified.');
  15879. }
  15880. }
  15881. result.coordSysDims = ['single'];
  15882. axisMap.set('single', singleAxisModel);
  15883. if (isCategory(singleAxisModel)) {
  15884. categoryAxisMap.set('single', singleAxisModel);
  15885. result.firstCategoryDimIndex = 0;
  15886. }
  15887. },
  15888. polar: function (seriesModel, result, axisMap, categoryAxisMap) {
  15889. var polarModel = seriesModel.getReferringComponents('polar')[0];
  15890. var radiusAxisModel = polarModel.findAxisModel('radiusAxis');
  15891. var angleAxisModel = polarModel.findAxisModel('angleAxis');
  15892. if (__DEV__) {
  15893. if (!angleAxisModel) {
  15894. throw new Error('angleAxis option not found');
  15895. }
  15896. if (!radiusAxisModel) {
  15897. throw new Error('radiusAxis option not found');
  15898. }
  15899. }
  15900. result.coordSysDims = ['radius', 'angle'];
  15901. axisMap.set('radius', radiusAxisModel);
  15902. axisMap.set('angle', angleAxisModel);
  15903. if (isCategory(radiusAxisModel)) {
  15904. categoryAxisMap.set('radius', radiusAxisModel);
  15905. result.firstCategoryDimIndex = 0;
  15906. }
  15907. if (isCategory(angleAxisModel)) {
  15908. categoryAxisMap.set('angle', angleAxisModel);
  15909. result.firstCategoryDimIndex = 1;
  15910. }
  15911. },
  15912. geo: function (seriesModel, result, axisMap, categoryAxisMap) {
  15913. result.coordSysDims = ['lng', 'lat'];
  15914. },
  15915. parallel: function (seriesModel, result, axisMap, categoryAxisMap) {
  15916. var ecModel = seriesModel.ecModel;
  15917. var parallelModel = ecModel.getComponent(
  15918. 'parallel', seriesModel.get('parallelIndex')
  15919. );
  15920. var coordSysDims = result.coordSysDims = parallelModel.dimensions.slice();
  15921. each$1(parallelModel.parallelAxisIndex, function (axisIndex, index) {
  15922. var axisModel = ecModel.getComponent('parallelAxis', axisIndex);
  15923. var axisDim = coordSysDims[index];
  15924. axisMap.set(axisDim, axisModel);
  15925. if (isCategory(axisModel) && result.firstCategoryDimIndex == null) {
  15926. categoryAxisMap.set(axisDim, axisModel);
  15927. result.firstCategoryDimIndex = index;
  15928. }
  15929. });
  15930. }
  15931. };
  15932. function isCategory(axisModel) {
  15933. return axisModel.get('type') === 'category';
  15934. }
  15935. // Avoid typo.
  15936. var SOURCE_FORMAT_ORIGINAL = 'original';
  15937. var SOURCE_FORMAT_ARRAY_ROWS = 'arrayRows';
  15938. var SOURCE_FORMAT_OBJECT_ROWS = 'objectRows';
  15939. var SOURCE_FORMAT_KEYED_COLUMNS = 'keyedColumns';
  15940. var SOURCE_FORMAT_UNKNOWN = 'unknown';
  15941. // ??? CHANGE A NAME
  15942. var SOURCE_FORMAT_TYPED_ARRAY = 'typedArray';
  15943. var SERIES_LAYOUT_BY_COLUMN = 'column';
  15944. var SERIES_LAYOUT_BY_ROW = 'row';
  15945. /**
  15946. * [sourceFormat]
  15947. *
  15948. * + "original":
  15949. * This format is only used in series.data, where
  15950. * itemStyle can be specified in data item.
  15951. *
  15952. * + "arrayRows":
  15953. * [
  15954. * ['product', 'score', 'amount'],
  15955. * ['Matcha Latte', 89.3, 95.8],
  15956. * ['Milk Tea', 92.1, 89.4],
  15957. * ['Cheese Cocoa', 94.4, 91.2],
  15958. * ['Walnut Brownie', 85.4, 76.9]
  15959. * ]
  15960. *
  15961. * + "objectRows":
  15962. * [
  15963. * {product: 'Matcha Latte', score: 89.3, amount: 95.8},
  15964. * {product: 'Milk Tea', score: 92.1, amount: 89.4},
  15965. * {product: 'Cheese Cocoa', score: 94.4, amount: 91.2},
  15966. * {product: 'Walnut Brownie', score: 85.4, amount: 76.9}
  15967. * ]
  15968. *
  15969. * + "keyedColumns":
  15970. * {
  15971. * 'product': ['Matcha Latte', 'Milk Tea', 'Cheese Cocoa', 'Walnut Brownie'],
  15972. * 'count': [823, 235, 1042, 988],
  15973. * 'score': [95.8, 81.4, 91.2, 76.9]
  15974. * }
  15975. *
  15976. * + "typedArray"
  15977. *
  15978. * + "unknown"
  15979. */
  15980. /**
  15981. * @constructor
  15982. * @param {Object} fields
  15983. * @param {string} fields.sourceFormat
  15984. * @param {Array|Object} fields.fromDataset
  15985. * @param {Array|Object} [fields.data]
  15986. * @param {string} [seriesLayoutBy='column']
  15987. * @param {Array.<Object|string>} [dimensionsDefine]
  15988. * @param {Objet|HashMap} [encodeDefine]
  15989. * @param {number} [startIndex=0]
  15990. * @param {number} [dimensionsDetectCount]
  15991. */
  15992. function Source(fields) {
  15993. /**
  15994. * @type {boolean}
  15995. */
  15996. this.fromDataset = fields.fromDataset;
  15997. /**
  15998. * Not null/undefined.
  15999. * @type {Array|Object}
  16000. */
  16001. this.data = fields.data || (
  16002. fields.sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS ? {} : []
  16003. );
  16004. /**
  16005. * See also "detectSourceFormat".
  16006. * Not null/undefined.
  16007. * @type {string}
  16008. */
  16009. this.sourceFormat = fields.sourceFormat || SOURCE_FORMAT_UNKNOWN;
  16010. /**
  16011. * 'row' or 'column'
  16012. * Not null/undefined.
  16013. * @type {string} seriesLayoutBy
  16014. */
  16015. this.seriesLayoutBy = fields.seriesLayoutBy || SERIES_LAYOUT_BY_COLUMN;
  16016. /**
  16017. * dimensions definition in option.
  16018. * can be null/undefined.
  16019. * @type {Array.<Object|string>}
  16020. */
  16021. this.dimensionsDefine = fields.dimensionsDefine;
  16022. /**
  16023. * encode definition in option.
  16024. * can be null/undefined.
  16025. * @type {Objet|HashMap}
  16026. */
  16027. this.encodeDefine = fields.encodeDefine && createHashMap(fields.encodeDefine);
  16028. /**
  16029. * Not null/undefined, uint.
  16030. * @type {number}
  16031. */
  16032. this.startIndex = fields.startIndex || 0;
  16033. /**
  16034. * Can be null/undefined (when unknown), uint.
  16035. * @type {number}
  16036. */
  16037. this.dimensionsDetectCount = fields.dimensionsDetectCount;
  16038. }
  16039. /**
  16040. * Wrap original series data for some compatibility cases.
  16041. */
  16042. Source.seriesDataToSource = function (data) {
  16043. return new Source({
  16044. data: data,
  16045. sourceFormat: isTypedArray(data)
  16046. ? SOURCE_FORMAT_TYPED_ARRAY
  16047. : SOURCE_FORMAT_ORIGINAL,
  16048. fromDataset: false
  16049. });
  16050. };
  16051. enableClassCheck(Source);
  16052. var inner$3 = makeInner();
  16053. /**
  16054. * @see {module:echarts/data/Source}
  16055. * @param {module:echarts/component/dataset/DatasetModel} datasetModel
  16056. * @return {string} sourceFormat
  16057. */
  16058. function detectSourceFormat(datasetModel) {
  16059. var data = datasetModel.option.source;
  16060. var sourceFormat = SOURCE_FORMAT_UNKNOWN;
  16061. if (isTypedArray(data)) {
  16062. sourceFormat = SOURCE_FORMAT_TYPED_ARRAY;
  16063. }
  16064. else if (isArray(data)) {
  16065. // FIXME Whether tolerate null in top level array?
  16066. for (var i = 0, len = data.length; i < len; i++) {
  16067. var item = data[i];
  16068. if (item == null) {
  16069. continue;
  16070. }
  16071. else if (isArray(item)) {
  16072. sourceFormat = SOURCE_FORMAT_ARRAY_ROWS;
  16073. break;
  16074. }
  16075. else if (isObject$1(item)) {
  16076. sourceFormat = SOURCE_FORMAT_OBJECT_ROWS;
  16077. break;
  16078. }
  16079. }
  16080. }
  16081. else if (isObject$1(data)) {
  16082. for (var key in data) {
  16083. if (data.hasOwnProperty(key) && isArrayLike(data[key])) {
  16084. sourceFormat = SOURCE_FORMAT_KEYED_COLUMNS;
  16085. break;
  16086. }
  16087. }
  16088. }
  16089. else if (data != null) {
  16090. throw new Error('Invalid data');
  16091. }
  16092. inner$3(datasetModel).sourceFormat = sourceFormat;
  16093. }
  16094. /**
  16095. * [Scenarios]:
  16096. * (1) Provide source data directly:
  16097. * series: {
  16098. * encode: {...},
  16099. * dimensions: [...]
  16100. * seriesLayoutBy: 'row',
  16101. * data: [[...]]
  16102. * }
  16103. * (2) Refer to datasetModel.
  16104. * series: [{
  16105. * encode: {...}
  16106. * // Ignore datasetIndex means `datasetIndex: 0`
  16107. * // and the dimensions defination in dataset is used
  16108. * }, {
  16109. * encode: {...},
  16110. * seriesLayoutBy: 'column',
  16111. * datasetIndex: 1
  16112. * }]
  16113. *
  16114. * Get data from series itself or datset.
  16115. * @return {module:echarts/data/Source} source
  16116. */
  16117. function getSource(seriesModel) {
  16118. return inner$3(seriesModel).source;
  16119. }
  16120. /**
  16121. * MUST be called before mergeOption of all series.
  16122. * @param {module:echarts/model/Global} ecModel
  16123. */
  16124. function resetSourceDefaulter(ecModel) {
  16125. // `datasetMap` is used to make default encode.
  16126. inner$3(ecModel).datasetMap = createHashMap();
  16127. }
  16128. /**
  16129. * [Caution]:
  16130. * MUST be called after series option merged and
  16131. * before "series.getInitailData()" called.
  16132. *
  16133. * [The rule of making default encode]:
  16134. * Category axis (if exists) alway map to the first dimension.
  16135. * Each other axis occupies a subsequent dimension.
  16136. *
  16137. * [Why make default encode]:
  16138. * Simplify the typing of encode in option, avoiding the case like that:
  16139. * series: [{encode: {x: 0, y: 1}}, {encode: {x: 0, y: 2}}, {encode: {x: 0, y: 3}}],
  16140. * where the "y" have to be manually typed as "1, 2, 3, ...".
  16141. *
  16142. * @param {module:echarts/model/Series} seriesModel
  16143. */
  16144. function prepareSource(seriesModel) {
  16145. var seriesOption = seriesModel.option;
  16146. var data = seriesOption.data;
  16147. var sourceFormat = isTypedArray(data)
  16148. ? SOURCE_FORMAT_TYPED_ARRAY : SOURCE_FORMAT_ORIGINAL;
  16149. var fromDataset = false;
  16150. var seriesLayoutBy = seriesOption.seriesLayoutBy;
  16151. var sourceHeader = seriesOption.sourceHeader;
  16152. var dimensionsDefine = seriesOption.dimensions;
  16153. var datasetModel = getDatasetModel(seriesModel);
  16154. if (datasetModel) {
  16155. var datasetOption = datasetModel.option;
  16156. data = datasetOption.source;
  16157. sourceFormat = inner$3(datasetModel).sourceFormat;
  16158. fromDataset = true;
  16159. // These settings from series has higher priority.
  16160. seriesLayoutBy = seriesLayoutBy || datasetOption.seriesLayoutBy;
  16161. sourceHeader == null && (sourceHeader = datasetOption.sourceHeader);
  16162. dimensionsDefine = dimensionsDefine || datasetOption.dimensions;
  16163. }
  16164. var completeResult = completeBySourceData(
  16165. data, sourceFormat, seriesLayoutBy, sourceHeader, dimensionsDefine
  16166. );
  16167. // Note: dataset option does not have `encode`.
  16168. var encodeDefine = seriesOption.encode;
  16169. if (!encodeDefine && datasetModel) {
  16170. encodeDefine = makeDefaultEncode(
  16171. seriesModel, datasetModel, data, sourceFormat, seriesLayoutBy, completeResult
  16172. );
  16173. }
  16174. inner$3(seriesModel).source = new Source({
  16175. data: data,
  16176. fromDataset: fromDataset,
  16177. seriesLayoutBy: seriesLayoutBy,
  16178. sourceFormat: sourceFormat,
  16179. dimensionsDefine: completeResult.dimensionsDefine,
  16180. startIndex: completeResult.startIndex,
  16181. dimensionsDetectCount: completeResult.dimensionsDetectCount,
  16182. encodeDefine: encodeDefine
  16183. });
  16184. }
  16185. // return {startIndex, dimensionsDefine, dimensionsCount}
  16186. function completeBySourceData(data, sourceFormat, seriesLayoutBy, sourceHeader, dimensionsDefine) {
  16187. if (!data) {
  16188. return {dimensionsDefine: normalizeDimensionsDefine(dimensionsDefine)};
  16189. }
  16190. var dimensionsDetectCount;
  16191. var startIndex;
  16192. var findPotentialName;
  16193. if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) {
  16194. // Rule: Most of the first line are string: it is header.
  16195. // Caution: consider a line with 5 string and 1 number,
  16196. // it still can not be sure it is a head, because the
  16197. // 5 string may be 5 values of category columns.
  16198. if (sourceHeader === 'auto' || sourceHeader == null) {
  16199. arrayRowsTravelFirst(function (val) {
  16200. // '-' is regarded as null/undefined.
  16201. if (val != null && val !== '-') {
  16202. if (isString(val)) {
  16203. startIndex == null && (startIndex = 1);
  16204. }
  16205. else {
  16206. startIndex = 0;
  16207. }
  16208. }
  16209. // 10 is an experience number, avoid long loop.
  16210. }, seriesLayoutBy, data, 10);
  16211. }
  16212. else {
  16213. startIndex = sourceHeader ? 1 : 0;
  16214. }
  16215. if (!dimensionsDefine && startIndex === 1) {
  16216. dimensionsDefine = [];
  16217. arrayRowsTravelFirst(function (val, index) {
  16218. dimensionsDefine[index] = val != null ? val : '';
  16219. }, seriesLayoutBy, data);
  16220. }
  16221. dimensionsDetectCount = dimensionsDefine
  16222. ? dimensionsDefine.length
  16223. : seriesLayoutBy === SERIES_LAYOUT_BY_ROW
  16224. ? data.length
  16225. : data[0]
  16226. ? data[0].length
  16227. : null;
  16228. }
  16229. else if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS) {
  16230. if (!dimensionsDefine) {
  16231. dimensionsDefine = objectRowsCollectDimensions(data);
  16232. findPotentialName = true;
  16233. }
  16234. }
  16235. else if (sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) {
  16236. if (!dimensionsDefine) {
  16237. dimensionsDefine = [];
  16238. findPotentialName = true;
  16239. each$1(data, function (colArr, key) {
  16240. dimensionsDefine.push(key);
  16241. });
  16242. }
  16243. }
  16244. else if (sourceFormat === SOURCE_FORMAT_ORIGINAL) {
  16245. var value0 = getDataItemValue(data[0]);
  16246. dimensionsDetectCount = isArray(value0) && value0.length || 1;
  16247. }
  16248. else if (sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) {
  16249. if (__DEV__) {
  16250. assert$1(!!dimensionsDefine, 'dimensions must be given if data is TypedArray.');
  16251. }
  16252. }
  16253. var potentialNameDimIndex;
  16254. if (findPotentialName) {
  16255. each$1(dimensionsDefine, function (dim, idx) {
  16256. if ((isObject$1(dim) ? dim.name : dim) === 'name') {
  16257. potentialNameDimIndex = idx;
  16258. }
  16259. });
  16260. }
  16261. return {
  16262. startIndex: startIndex,
  16263. dimensionsDefine: normalizeDimensionsDefine(dimensionsDefine),
  16264. dimensionsDetectCount: dimensionsDetectCount,
  16265. potentialNameDimIndex: potentialNameDimIndex
  16266. // TODO: potentialIdDimIdx
  16267. };
  16268. }
  16269. // Consider dimensions defined like ['A', 'price', 'B', 'price', 'C', 'price'],
  16270. // which is reasonable. But dimension name is duplicated.
  16271. // Returns undefined or an array contains only object without null/undefiend or string.
  16272. function normalizeDimensionsDefine(dimensionsDefine) {
  16273. if (!dimensionsDefine) {
  16274. // The meaning of null/undefined is different from empty array.
  16275. return;
  16276. }
  16277. var nameMap = createHashMap();
  16278. return map(dimensionsDefine, function (item, index) {
  16279. item = extend({}, isObject$1(item) ? item : {name: item});
  16280. // User can set null in dimensions.
  16281. // We dont auto specify name, othewise a given name may
  16282. // cause it be refered unexpectedly.
  16283. if (item.name == null) {
  16284. return item;
  16285. }
  16286. // Also consider number form like 2012.
  16287. item.name += '';
  16288. // User may also specify displayName.
  16289. // displayName will always exists except user not
  16290. // specified or dim name is not specified or detected.
  16291. // (A auto generated dim name will not be used as
  16292. // displayName).
  16293. if (item.displayName == null) {
  16294. item.displayName = item.name;
  16295. }
  16296. var exist = nameMap.get(item.name);
  16297. if (!exist) {
  16298. nameMap.set(item.name, {count: 1});
  16299. }
  16300. else {
  16301. item.name += '-' + exist.count++;
  16302. }
  16303. return item;
  16304. });
  16305. }
  16306. function arrayRowsTravelFirst(cb, seriesLayoutBy, data, maxLoop) {
  16307. maxLoop == null && (maxLoop = Infinity);
  16308. if (seriesLayoutBy === SERIES_LAYOUT_BY_ROW) {
  16309. for (var i = 0; i < data.length && i < maxLoop; i++) {
  16310. cb(data[i] ? data[i][0] : null, i);
  16311. }
  16312. }
  16313. else {
  16314. var value0 = data[0] || [];
  16315. for (var i = 0; i < value0.length && i < maxLoop; i++) {
  16316. cb(value0[i], i);
  16317. }
  16318. }
  16319. }
  16320. function objectRowsCollectDimensions(data) {
  16321. var firstIndex = 0;
  16322. var obj;
  16323. while (firstIndex < data.length && !(obj = data[firstIndex++])) {} // jshint ignore: line
  16324. if (obj) {
  16325. var dimensions = [];
  16326. each$1(obj, function (value, key) {
  16327. dimensions.push(key);
  16328. });
  16329. return dimensions;
  16330. }
  16331. }
  16332. // ??? TODO merge to completedimensions, where also has
  16333. // default encode making logic. And the default rule
  16334. // should depends on series? consider 'map'.
  16335. function makeDefaultEncode(
  16336. seriesModel, datasetModel, data, sourceFormat, seriesLayoutBy, completeResult
  16337. ) {
  16338. var coordSysDefine = getCoordSysDefineBySeries(seriesModel);
  16339. var encode = {};
  16340. // var encodeTooltip = [];
  16341. // var encodeLabel = [];
  16342. var encodeItemName = [];
  16343. var encodeSeriesName = [];
  16344. var seriesType = seriesModel.subType;
  16345. // ??? TODO refactor: provide by series itself.
  16346. // Consider the case: 'map' series is based on geo coordSys,
  16347. // 'graph', 'heatmap' can be based on cartesian. But can not
  16348. // give default rule simply here.
  16349. var nSeriesMap = createHashMap(['pie', 'map', 'funnel']);
  16350. var cSeriesMap = createHashMap([
  16351. 'line', 'bar', 'pictorialBar', 'scatter', 'effectScatter', 'candlestick', 'boxplot'
  16352. ]);
  16353. // Usually in this case series will use the first data
  16354. // dimension as the "value" dimension, or other default
  16355. // processes respectively.
  16356. if (coordSysDefine && cSeriesMap.get(seriesType) != null) {
  16357. var ecModel = seriesModel.ecModel;
  16358. var datasetMap = inner$3(ecModel).datasetMap;
  16359. var key = datasetModel.uid + '_' + seriesLayoutBy;
  16360. var datasetRecord = datasetMap.get(key)
  16361. || datasetMap.set(key, {categoryWayDim: 1, valueWayDim: 0});
  16362. // TODO
  16363. // Auto detect first time axis and do arrangement.
  16364. each$1(coordSysDefine.coordSysDims, function (coordDim) {
  16365. // In value way.
  16366. if (coordSysDefine.firstCategoryDimIndex == null) {
  16367. var dataDim = datasetRecord.valueWayDim++;
  16368. encode[coordDim] = dataDim;
  16369. // ??? TODO give a better default series name rule?
  16370. // especially when encode x y specified.
  16371. // consider: when mutiple series share one dimension
  16372. // category axis, series name should better use
  16373. // the other dimsion name. On the other hand, use
  16374. // both dimensions name.
  16375. encodeSeriesName.push(dataDim);
  16376. // encodeTooltip.push(dataDim);
  16377. // encodeLabel.push(dataDim);
  16378. }
  16379. // In category way, category axis.
  16380. else if (coordSysDefine.categoryAxisMap.get(coordDim)) {
  16381. encode[coordDim] = 0;
  16382. encodeItemName.push(0);
  16383. }
  16384. // In category way, non-category axis.
  16385. else {
  16386. var dataDim = datasetRecord.categoryWayDim++;
  16387. encode[coordDim] = dataDim;
  16388. // encodeTooltip.push(dataDim);
  16389. // encodeLabel.push(dataDim);
  16390. encodeSeriesName.push(dataDim);
  16391. }
  16392. });
  16393. }
  16394. // Do not make a complex rule! Hard to code maintain and not necessary.
  16395. // ??? TODO refactor: provide by series itself.
  16396. // [{name: ..., value: ...}, ...] like:
  16397. else if (nSeriesMap.get(seriesType) != null) {
  16398. // Find the first not ordinal. (5 is an experience value)
  16399. var firstNotOrdinal;
  16400. for (var i = 0; i < 5 && firstNotOrdinal == null; i++) {
  16401. if (!doGuessOrdinal(
  16402. data, sourceFormat, seriesLayoutBy,
  16403. completeResult.dimensionsDefine, completeResult.startIndex, i
  16404. )) {
  16405. firstNotOrdinal = i;
  16406. }
  16407. }
  16408. if (firstNotOrdinal != null) {
  16409. encode.value = firstNotOrdinal;
  16410. var nameDimIndex = completeResult.potentialNameDimIndex
  16411. || Math.max(firstNotOrdinal - 1, 0);
  16412. // By default, label use itemName in charts.
  16413. // So we dont set encodeLabel here.
  16414. encodeSeriesName.push(nameDimIndex);
  16415. encodeItemName.push(nameDimIndex);
  16416. // encodeTooltip.push(firstNotOrdinal);
  16417. }
  16418. }
  16419. // encodeTooltip.length && (encode.tooltip = encodeTooltip);
  16420. // encodeLabel.length && (encode.label = encodeLabel);
  16421. encodeItemName.length && (encode.itemName = encodeItemName);
  16422. encodeSeriesName.length && (encode.seriesName = encodeSeriesName);
  16423. return encode;
  16424. }
  16425. /**
  16426. * If return null/undefined, indicate that should not use datasetModel.
  16427. */
  16428. function getDatasetModel(seriesModel) {
  16429. var option = seriesModel.option;
  16430. // Caution: consider the scenario:
  16431. // A dataset is declared and a series is not expected to use the dataset,
  16432. // and at the beginning `setOption({series: { noData })` (just prepare other
  16433. // option but no data), then `setOption({series: {data: [...]}); In this case,
  16434. // the user should set an empty array to avoid that dataset is used by default.
  16435. var thisData = option.data;
  16436. if (!thisData) {
  16437. return seriesModel.ecModel.getComponent('dataset', option.datasetIndex || 0);
  16438. }
  16439. }
  16440. /**
  16441. * The rule should not be complex, otherwise user might not
  16442. * be able to known where the data is wrong.
  16443. * The code is ugly, but how to make it neat?
  16444. *
  16445. * @param {module:echars/data/Source} source
  16446. * @param {number} dimIndex
  16447. * @return {boolean} Whether ordinal.
  16448. */
  16449. function guessOrdinal(source, dimIndex) {
  16450. return doGuessOrdinal(
  16451. source.data,
  16452. source.sourceFormat,
  16453. source.seriesLayoutBy,
  16454. source.dimensionsDefine,
  16455. source.startIndex,
  16456. dimIndex
  16457. );
  16458. }
  16459. // dimIndex may be overflow source data.
  16460. function doGuessOrdinal(
  16461. data, sourceFormat, seriesLayoutBy, dimensionsDefine, startIndex, dimIndex
  16462. ) {
  16463. var result;
  16464. // Experience value.
  16465. var maxLoop = 5;
  16466. if (isTypedArray(data)) {
  16467. return false;
  16468. }
  16469. // When sourceType is 'objectRows' or 'keyedColumns', dimensionsDefine
  16470. // always exists in source.
  16471. var dimName;
  16472. if (dimensionsDefine) {
  16473. dimName = dimensionsDefine[dimIndex];
  16474. dimName = isObject$1(dimName) ? dimName.name : dimName;
  16475. }
  16476. if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) {
  16477. if (seriesLayoutBy === SERIES_LAYOUT_BY_ROW) {
  16478. var sample = data[dimIndex];
  16479. for (var i = 0; i < (sample || []).length && i < maxLoop; i++) {
  16480. if ((result = detectValue(sample[startIndex + i])) != null) {
  16481. return result;
  16482. }
  16483. }
  16484. }
  16485. else {
  16486. for (var i = 0; i < data.length && i < maxLoop; i++) {
  16487. var row = data[startIndex + i];
  16488. if (row && (result = detectValue(row[dimIndex])) != null) {
  16489. return result;
  16490. }
  16491. }
  16492. }
  16493. }
  16494. else if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS) {
  16495. if (!dimName) {
  16496. return;
  16497. }
  16498. for (var i = 0; i < data.length && i < maxLoop; i++) {
  16499. var item = data[i];
  16500. if (item && (result = detectValue(item[dimName])) != null) {
  16501. return result;
  16502. }
  16503. }
  16504. }
  16505. else if (sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) {
  16506. if (!dimName) {
  16507. return;
  16508. }
  16509. var sample = data[dimName];
  16510. if (!sample || isTypedArray(sample)) {
  16511. return false;
  16512. }
  16513. for (var i = 0; i < sample.length && i < maxLoop; i++) {
  16514. if ((result = detectValue(sample[i])) != null) {
  16515. return result;
  16516. }
  16517. }
  16518. }
  16519. else if (sourceFormat === SOURCE_FORMAT_ORIGINAL) {
  16520. for (var i = 0; i < data.length && i < maxLoop; i++) {
  16521. var item = data[i];
  16522. var val = getDataItemValue(item);
  16523. if (!isArray(val)) {
  16524. return false;
  16525. }
  16526. if ((result = detectValue(val[dimIndex])) != null) {
  16527. return result;
  16528. }
  16529. }
  16530. }
  16531. function detectValue(val) {
  16532. // Consider usage convenience, '1', '2' will be treated as "number".
  16533. // `isFinit('')` get `true`.
  16534. if (val != null && isFinite(val) && val !== '') {
  16535. return false;
  16536. }
  16537. else if (isString(val) && val !== '-') {
  16538. return true;
  16539. }
  16540. }
  16541. return false;
  16542. }
  16543. /**
  16544. * ECharts global model
  16545. *
  16546. * @module {echarts/model/Global}
  16547. */
  16548. /**
  16549. * Caution: If the mechanism should be changed some day, these cases
  16550. * should be considered:
  16551. *
  16552. * (1) In `merge option` mode, if using the same option to call `setOption`
  16553. * many times, the result should be the same (try our best to ensure that).
  16554. * (2) In `merge option` mode, if a component has no id/name specified, it
  16555. * will be merged by index, and the result sequence of the components is
  16556. * consistent to the original sequence.
  16557. * (3) `reset` feature (in toolbox). Find detailed info in comments about
  16558. * `mergeOption` in module:echarts/model/OptionManager.
  16559. */
  16560. var OPTION_INNER_KEY = '\0_ec_inner';
  16561. /**
  16562. * @alias module:echarts/model/Global
  16563. *
  16564. * @param {Object} option
  16565. * @param {module:echarts/model/Model} parentModel
  16566. * @param {Object} theme
  16567. */
  16568. var GlobalModel = Model.extend({
  16569. constructor: GlobalModel,
  16570. init: function (option, parentModel, theme, optionManager) {
  16571. theme = theme || {};
  16572. this.option = null; // Mark as not initialized.
  16573. /**
  16574. * @type {module:echarts/model/Model}
  16575. * @private
  16576. */
  16577. this._theme = new Model(theme);
  16578. /**
  16579. * @type {module:echarts/model/OptionManager}
  16580. */
  16581. this._optionManager = optionManager;
  16582. },
  16583. setOption: function (option, optionPreprocessorFuncs) {
  16584. assert$1(
  16585. !(OPTION_INNER_KEY in option),
  16586. 'please use chart.getOption()'
  16587. );
  16588. this._optionManager.setOption(option, optionPreprocessorFuncs);
  16589. this.resetOption(null);
  16590. },
  16591. /**
  16592. * @param {string} type null/undefined: reset all.
  16593. * 'recreate': force recreate all.
  16594. * 'timeline': only reset timeline option
  16595. * 'media': only reset media query option
  16596. * @return {boolean} Whether option changed.
  16597. */
  16598. resetOption: function (type) {
  16599. var optionChanged = false;
  16600. var optionManager = this._optionManager;
  16601. if (!type || type === 'recreate') {
  16602. var baseOption = optionManager.mountOption(type === 'recreate');
  16603. if (!this.option || type === 'recreate') {
  16604. initBase.call(this, baseOption);
  16605. }
  16606. else {
  16607. this.restoreData();
  16608. this.mergeOption(baseOption);
  16609. }
  16610. optionChanged = true;
  16611. }
  16612. if (type === 'timeline' || type === 'media') {
  16613. this.restoreData();
  16614. }
  16615. if (!type || type === 'recreate' || type === 'timeline') {
  16616. var timelineOption = optionManager.getTimelineOption(this);
  16617. timelineOption && (this.mergeOption(timelineOption), optionChanged = true);
  16618. }
  16619. if (!type || type === 'recreate' || type === 'media') {
  16620. var mediaOptions = optionManager.getMediaOption(this, this._api);
  16621. if (mediaOptions.length) {
  16622. each$1(mediaOptions, function (mediaOption) {
  16623. this.mergeOption(mediaOption, optionChanged = true);
  16624. }, this);
  16625. }
  16626. }
  16627. return optionChanged;
  16628. },
  16629. /**
  16630. * @protected
  16631. */
  16632. mergeOption: function (newOption) {
  16633. var option = this.option;
  16634. var componentsMap = this._componentsMap;
  16635. var newCptTypes = [];
  16636. resetSourceDefaulter(this);
  16637. // If no component class, merge directly.
  16638. // For example: color, animaiton options, etc.
  16639. each$1(newOption, function (componentOption, mainType) {
  16640. if (componentOption == null) {
  16641. return;
  16642. }
  16643. if (!ComponentModel.hasClass(mainType)) {
  16644. // globalSettingTask.dirty();
  16645. option[mainType] = option[mainType] == null
  16646. ? clone(componentOption)
  16647. : merge(option[mainType], componentOption, true);
  16648. }
  16649. else if (mainType) {
  16650. newCptTypes.push(mainType);
  16651. }
  16652. });
  16653. ComponentModel.topologicalTravel(
  16654. newCptTypes, ComponentModel.getAllClassMainTypes(), visitComponent, this
  16655. );
  16656. function visitComponent(mainType, dependencies) {
  16657. var newCptOptionList = normalizeToArray(newOption[mainType]);
  16658. var mapResult = mappingToExists(
  16659. componentsMap.get(mainType), newCptOptionList
  16660. );
  16661. makeIdAndName(mapResult);
  16662. // Set mainType and complete subType.
  16663. each$1(mapResult, function (item, index) {
  16664. var opt = item.option;
  16665. if (isObject$1(opt)) {
  16666. item.keyInfo.mainType = mainType;
  16667. item.keyInfo.subType = determineSubType(mainType, opt, item.exist);
  16668. }
  16669. });
  16670. var dependentModels = getComponentsByTypes(
  16671. componentsMap, dependencies
  16672. );
  16673. option[mainType] = [];
  16674. componentsMap.set(mainType, []);
  16675. each$1(mapResult, function (resultItem, index) {
  16676. var componentModel = resultItem.exist;
  16677. var newCptOption = resultItem.option;
  16678. assert$1(
  16679. isObject$1(newCptOption) || componentModel,
  16680. 'Empty component definition'
  16681. );
  16682. // Consider where is no new option and should be merged using {},
  16683. // see removeEdgeAndAdd in topologicalTravel and
  16684. // ComponentModel.getAllClassMainTypes.
  16685. if (!newCptOption) {
  16686. componentModel.mergeOption({}, this);
  16687. componentModel.optionUpdated({}, false);
  16688. }
  16689. else {
  16690. var ComponentModelClass = ComponentModel.getClass(
  16691. mainType, resultItem.keyInfo.subType, true
  16692. );
  16693. if (componentModel && componentModel instanceof ComponentModelClass) {
  16694. componentModel.name = resultItem.keyInfo.name;
  16695. // componentModel.settingTask && componentModel.settingTask.dirty();
  16696. componentModel.mergeOption(newCptOption, this);
  16697. componentModel.optionUpdated(newCptOption, false);
  16698. }
  16699. else {
  16700. // PENDING Global as parent ?
  16701. var extraOpt = extend(
  16702. {
  16703. dependentModels: dependentModels,
  16704. componentIndex: index
  16705. },
  16706. resultItem.keyInfo
  16707. );
  16708. componentModel = new ComponentModelClass(
  16709. newCptOption, this, this, extraOpt
  16710. );
  16711. extend(componentModel, extraOpt);
  16712. componentModel.init(newCptOption, this, this, extraOpt);
  16713. // Call optionUpdated after init.
  16714. // newCptOption has been used as componentModel.option
  16715. // and may be merged with theme and default, so pass null
  16716. // to avoid confusion.
  16717. componentModel.optionUpdated(null, true);
  16718. }
  16719. }
  16720. componentsMap.get(mainType)[index] = componentModel;
  16721. option[mainType][index] = componentModel.option;
  16722. }, this);
  16723. // Backup series for filtering.
  16724. if (mainType === 'series') {
  16725. createSeriesIndices(this, componentsMap.get('series'));
  16726. }
  16727. }
  16728. this._seriesIndicesMap = createHashMap(
  16729. this._seriesIndices = this._seriesIndices || []
  16730. );
  16731. },
  16732. /**
  16733. * Get option for output (cloned option and inner info removed)
  16734. * @public
  16735. * @return {Object}
  16736. */
  16737. getOption: function () {
  16738. var option = clone(this.option);
  16739. each$1(option, function (opts, mainType) {
  16740. if (ComponentModel.hasClass(mainType)) {
  16741. var opts = normalizeToArray(opts);
  16742. for (var i = opts.length - 1; i >= 0; i--) {
  16743. // Remove options with inner id.
  16744. if (isIdInner(opts[i])) {
  16745. opts.splice(i, 1);
  16746. }
  16747. }
  16748. option[mainType] = opts;
  16749. }
  16750. });
  16751. delete option[OPTION_INNER_KEY];
  16752. return option;
  16753. },
  16754. /**
  16755. * @return {module:echarts/model/Model}
  16756. */
  16757. getTheme: function () {
  16758. return this._theme;
  16759. },
  16760. /**
  16761. * @param {string} mainType
  16762. * @param {number} [idx=0]
  16763. * @return {module:echarts/model/Component}
  16764. */
  16765. getComponent: function (mainType, idx) {
  16766. var list = this._componentsMap.get(mainType);
  16767. if (list) {
  16768. return list[idx || 0];
  16769. }
  16770. },
  16771. /**
  16772. * If none of index and id and name used, return all components with mainType.
  16773. * @param {Object} condition
  16774. * @param {string} condition.mainType
  16775. * @param {string} [condition.subType] If ignore, only query by mainType
  16776. * @param {number|Array.<number>} [condition.index] Either input index or id or name.
  16777. * @param {string|Array.<string>} [condition.id] Either input index or id or name.
  16778. * @param {string|Array.<string>} [condition.name] Either input index or id or name.
  16779. * @return {Array.<module:echarts/model/Component>}
  16780. */
  16781. queryComponents: function (condition) {
  16782. var mainType = condition.mainType;
  16783. if (!mainType) {
  16784. return [];
  16785. }
  16786. var index = condition.index;
  16787. var id = condition.id;
  16788. var name = condition.name;
  16789. var cpts = this._componentsMap.get(mainType);
  16790. if (!cpts || !cpts.length) {
  16791. return [];
  16792. }
  16793. var result;
  16794. if (index != null) {
  16795. if (!isArray(index)) {
  16796. index = [index];
  16797. }
  16798. result = filter(map(index, function (idx) {
  16799. return cpts[idx];
  16800. }), function (val) {
  16801. return !!val;
  16802. });
  16803. }
  16804. else if (id != null) {
  16805. var isIdArray = isArray(id);
  16806. result = filter(cpts, function (cpt) {
  16807. return (isIdArray && indexOf(id, cpt.id) >= 0)
  16808. || (!isIdArray && cpt.id === id);
  16809. });
  16810. }
  16811. else if (name != null) {
  16812. var isNameArray = isArray(name);
  16813. result = filter(cpts, function (cpt) {
  16814. return (isNameArray && indexOf(name, cpt.name) >= 0)
  16815. || (!isNameArray && cpt.name === name);
  16816. });
  16817. }
  16818. else {
  16819. // Return all components with mainType
  16820. result = cpts.slice();
  16821. }
  16822. return filterBySubType(result, condition);
  16823. },
  16824. /**
  16825. * The interface is different from queryComponents,
  16826. * which is convenient for inner usage.
  16827. *
  16828. * @usage
  16829. * var result = findComponents(
  16830. * {mainType: 'dataZoom', query: {dataZoomId: 'abc'}}
  16831. * );
  16832. * var result = findComponents(
  16833. * {mainType: 'series', subType: 'pie', query: {seriesName: 'uio'}}
  16834. * );
  16835. * var result = findComponents(
  16836. * {mainType: 'series'},
  16837. * function (model, index) {...}
  16838. * );
  16839. * // result like [component0, componnet1, ...]
  16840. *
  16841. * @param {Object} condition
  16842. * @param {string} condition.mainType Mandatory.
  16843. * @param {string} [condition.subType] Optional.
  16844. * @param {Object} [condition.query] like {xxxIndex, xxxId, xxxName},
  16845. * where xxx is mainType.
  16846. * If query attribute is null/undefined or has no index/id/name,
  16847. * do not filtering by query conditions, which is convenient for
  16848. * no-payload situations or when target of action is global.
  16849. * @param {Function} [condition.filter] parameter: component, return boolean.
  16850. * @return {Array.<module:echarts/model/Component>}
  16851. */
  16852. findComponents: function (condition) {
  16853. var query = condition.query;
  16854. var mainType = condition.mainType;
  16855. var queryCond = getQueryCond(query);
  16856. var result = queryCond
  16857. ? this.queryComponents(queryCond)
  16858. : this._componentsMap.get(mainType);
  16859. return doFilter(filterBySubType(result, condition));
  16860. function getQueryCond(q) {
  16861. var indexAttr = mainType + 'Index';
  16862. var idAttr = mainType + 'Id';
  16863. var nameAttr = mainType + 'Name';
  16864. return q && (
  16865. q[indexAttr] != null
  16866. || q[idAttr] != null
  16867. || q[nameAttr] != null
  16868. )
  16869. ? {
  16870. mainType: mainType,
  16871. // subType will be filtered finally.
  16872. index: q[indexAttr],
  16873. id: q[idAttr],
  16874. name: q[nameAttr]
  16875. }
  16876. : null;
  16877. }
  16878. function doFilter(res) {
  16879. return condition.filter
  16880. ? filter(res, condition.filter)
  16881. : res;
  16882. }
  16883. },
  16884. /**
  16885. * @usage
  16886. * eachComponent('legend', function (legendModel, index) {
  16887. * ...
  16888. * });
  16889. * eachComponent(function (componentType, model, index) {
  16890. * // componentType does not include subType
  16891. * // (componentType is 'xxx' but not 'xxx.aa')
  16892. * });
  16893. * eachComponent(
  16894. * {mainType: 'dataZoom', query: {dataZoomId: 'abc'}},
  16895. * function (model, index) {...}
  16896. * );
  16897. * eachComponent(
  16898. * {mainType: 'series', subType: 'pie', query: {seriesName: 'uio'}},
  16899. * function (model, index) {...}
  16900. * );
  16901. *
  16902. * @param {string|Object=} mainType When mainType is object, the definition
  16903. * is the same as the method 'findComponents'.
  16904. * @param {Function} cb
  16905. * @param {*} context
  16906. */
  16907. eachComponent: function (mainType, cb, context) {
  16908. var componentsMap = this._componentsMap;
  16909. if (typeof mainType === 'function') {
  16910. context = cb;
  16911. cb = mainType;
  16912. componentsMap.each(function (components, componentType) {
  16913. each$1(components, function (component, index) {
  16914. cb.call(context, componentType, component, index);
  16915. });
  16916. });
  16917. }
  16918. else if (isString(mainType)) {
  16919. each$1(componentsMap.get(mainType), cb, context);
  16920. }
  16921. else if (isObject$1(mainType)) {
  16922. var queryResult = this.findComponents(mainType);
  16923. each$1(queryResult, cb, context);
  16924. }
  16925. },
  16926. /**
  16927. * @param {string} name
  16928. * @return {Array.<module:echarts/model/Series>}
  16929. */
  16930. getSeriesByName: function (name) {
  16931. var series = this._componentsMap.get('series');
  16932. return filter(series, function (oneSeries) {
  16933. return oneSeries.name === name;
  16934. });
  16935. },
  16936. /**
  16937. * @param {number} seriesIndex
  16938. * @return {module:echarts/model/Series}
  16939. */
  16940. getSeriesByIndex: function (seriesIndex) {
  16941. return this._componentsMap.get('series')[seriesIndex];
  16942. },
  16943. /**
  16944. * Get series list before filtered by type.
  16945. * FIXME: rename to getRawSeriesByType?
  16946. *
  16947. * @param {string} subType
  16948. * @return {Array.<module:echarts/model/Series>}
  16949. */
  16950. getSeriesByType: function (subType) {
  16951. var series = this._componentsMap.get('series');
  16952. return filter(series, function (oneSeries) {
  16953. return oneSeries.subType === subType;
  16954. });
  16955. },
  16956. /**
  16957. * @return {Array.<module:echarts/model/Series>}
  16958. */
  16959. getSeries: function () {
  16960. return this._componentsMap.get('series').slice();
  16961. },
  16962. /**
  16963. * @return {number}
  16964. */
  16965. getSeriesCount: function () {
  16966. return this._componentsMap.get('series').length;
  16967. },
  16968. /**
  16969. * After filtering, series may be different
  16970. * frome raw series.
  16971. *
  16972. * @param {Function} cb
  16973. * @param {*} context
  16974. */
  16975. eachSeries: function (cb, context) {
  16976. assertSeriesInitialized(this);
  16977. each$1(this._seriesIndices, function (rawSeriesIndex) {
  16978. var series = this._componentsMap.get('series')[rawSeriesIndex];
  16979. cb.call(context, series, rawSeriesIndex);
  16980. }, this);
  16981. },
  16982. /**
  16983. * Iterate raw series before filtered.
  16984. *
  16985. * @param {Function} cb
  16986. * @param {*} context
  16987. */
  16988. eachRawSeries: function (cb, context) {
  16989. each$1(this._componentsMap.get('series'), cb, context);
  16990. },
  16991. /**
  16992. * After filtering, series may be different.
  16993. * frome raw series.
  16994. *
  16995. * @parma {string} subType
  16996. * @param {Function} cb
  16997. * @param {*} context
  16998. */
  16999. eachSeriesByType: function (subType, cb, context) {
  17000. assertSeriesInitialized(this);
  17001. each$1(this._seriesIndices, function (rawSeriesIndex) {
  17002. var series = this._componentsMap.get('series')[rawSeriesIndex];
  17003. if (series.subType === subType) {
  17004. cb.call(context, series, rawSeriesIndex);
  17005. }
  17006. }, this);
  17007. },
  17008. /**
  17009. * Iterate raw series before filtered of given type.
  17010. *
  17011. * @parma {string} subType
  17012. * @param {Function} cb
  17013. * @param {*} context
  17014. */
  17015. eachRawSeriesByType: function (subType, cb, context) {
  17016. return each$1(this.getSeriesByType(subType), cb, context);
  17017. },
  17018. /**
  17019. * @param {module:echarts/model/Series} seriesModel
  17020. */
  17021. isSeriesFiltered: function (seriesModel) {
  17022. assertSeriesInitialized(this);
  17023. return this._seriesIndicesMap.get(seriesModel.componentIndex) == null;
  17024. },
  17025. /**
  17026. * @return {Array.<number>}
  17027. */
  17028. getCurrentSeriesIndices: function () {
  17029. return (this._seriesIndices || []).slice();
  17030. },
  17031. /**
  17032. * @param {Function} cb
  17033. * @param {*} context
  17034. */
  17035. filterSeries: function (cb, context) {
  17036. assertSeriesInitialized(this);
  17037. var filteredSeries = filter(
  17038. this._componentsMap.get('series'), cb, context
  17039. );
  17040. createSeriesIndices(this, filteredSeries);
  17041. },
  17042. restoreData: function (payload) {
  17043. var componentsMap = this._componentsMap;
  17044. createSeriesIndices(this, componentsMap.get('series'));
  17045. var componentTypes = [];
  17046. componentsMap.each(function (components, componentType) {
  17047. componentTypes.push(componentType);
  17048. });
  17049. ComponentModel.topologicalTravel(
  17050. componentTypes,
  17051. ComponentModel.getAllClassMainTypes(),
  17052. function (componentType, dependencies) {
  17053. each$1(componentsMap.get(componentType), function (component) {
  17054. (componentType !== 'series' || !isNotTargetSeries(component, payload))
  17055. && component.restoreData();
  17056. });
  17057. }
  17058. );
  17059. }
  17060. });
  17061. function isNotTargetSeries(seriesModel, payload) {
  17062. if (payload) {
  17063. var index = payload.seiresIndex;
  17064. var id = payload.seriesId;
  17065. var name = payload.seriesName;
  17066. return (index != null && seriesModel.componentIndex !== index)
  17067. || (id != null && seriesModel.id !== id)
  17068. || (name != null && seriesModel.name !== name);
  17069. }
  17070. }
  17071. /**
  17072. * @inner
  17073. */
  17074. function mergeTheme(option, theme) {
  17075. // PENDING
  17076. // NOT use `colorLayer` in theme if option has `color`
  17077. var notMergeColorLayer = option.color && !option.colorLayer;
  17078. each$1(theme, function (themeItem, name) {
  17079. if (name === 'colorLayer' && notMergeColorLayer) {
  17080. return;
  17081. }
  17082. // 如果有 component model 则把具体的 merge 逻辑交给该 model 处理
  17083. if (!ComponentModel.hasClass(name)) {
  17084. if (typeof themeItem === 'object') {
  17085. option[name] = !option[name]
  17086. ? clone(themeItem)
  17087. : merge(option[name], themeItem, false);
  17088. }
  17089. else {
  17090. if (option[name] == null) {
  17091. option[name] = themeItem;
  17092. }
  17093. }
  17094. }
  17095. });
  17096. }
  17097. function initBase(baseOption) {
  17098. baseOption = baseOption;
  17099. // Using OPTION_INNER_KEY to mark that this option can not be used outside,
  17100. // i.e. `chart.setOption(chart.getModel().option);` is forbiden.
  17101. this.option = {};
  17102. this.option[OPTION_INNER_KEY] = 1;
  17103. /**
  17104. * Init with series: [], in case of calling findSeries method
  17105. * before series initialized.
  17106. * @type {Object.<string, Array.<module:echarts/model/Model>>}
  17107. * @private
  17108. */
  17109. this._componentsMap = createHashMap({series: []});
  17110. /**
  17111. * Mapping between filtered series list and raw series list.
  17112. * key: filtered series indices, value: raw series indices.
  17113. * @type {Array.<nubmer>}
  17114. * @private
  17115. */
  17116. this._seriesIndices;
  17117. this._seriesIndicesMap;
  17118. mergeTheme(baseOption, this._theme.option);
  17119. // TODO Needs clone when merging to the unexisted property
  17120. merge(baseOption, globalDefault, false);
  17121. this.mergeOption(baseOption);
  17122. }
  17123. /**
  17124. * @inner
  17125. * @param {Array.<string>|string} types model types
  17126. * @return {Object} key: {string} type, value: {Array.<Object>} models
  17127. */
  17128. function getComponentsByTypes(componentsMap, types) {
  17129. if (!isArray(types)) {
  17130. types = types ? [types] : [];
  17131. }
  17132. var ret = {};
  17133. each$1(types, function (type) {
  17134. ret[type] = (componentsMap.get(type) || []).slice();
  17135. });
  17136. return ret;
  17137. }
  17138. /**
  17139. * @inner
  17140. */
  17141. function determineSubType(mainType, newCptOption, existComponent) {
  17142. var subType = newCptOption.type
  17143. ? newCptOption.type
  17144. : existComponent
  17145. ? existComponent.subType
  17146. // Use determineSubType only when there is no existComponent.
  17147. : ComponentModel.determineSubType(mainType, newCptOption);
  17148. // tooltip, markline, markpoint may always has no subType
  17149. return subType;
  17150. }
  17151. /**
  17152. * @inner
  17153. */
  17154. function createSeriesIndices(ecModel, seriesModels) {
  17155. ecModel._seriesIndicesMap = createHashMap(
  17156. ecModel._seriesIndices = map(seriesModels, function (series) {
  17157. return series.componentIndex;
  17158. }) || []
  17159. );
  17160. }
  17161. /**
  17162. * @inner
  17163. */
  17164. function filterBySubType(components, condition) {
  17165. // Using hasOwnProperty for restrict. Consider
  17166. // subType is undefined in user payload.
  17167. return condition.hasOwnProperty('subType')
  17168. ? filter(components, function (cpt) {
  17169. return cpt.subType === condition.subType;
  17170. })
  17171. : components;
  17172. }
  17173. /**
  17174. * @inner
  17175. */
  17176. function assertSeriesInitialized(ecModel) {
  17177. // Components that use _seriesIndices should depends on series component,
  17178. // which make sure that their initialization is after series.
  17179. if (__DEV__) {
  17180. if (!ecModel._seriesIndices) {
  17181. throw new Error('Option should contains series.');
  17182. }
  17183. }
  17184. }
  17185. mixin(GlobalModel, colorPaletteMixin);
  17186. var echartsAPIList = [
  17187. 'getDom', 'getZr', 'getWidth', 'getHeight', 'getDevicePixelRatio', 'dispatchAction', 'isDisposed',
  17188. 'on', 'off', 'getDataURL', 'getConnectedDataURL', 'getModel', 'getOption',
  17189. 'getViewOfComponentModel', 'getViewOfSeriesModel'
  17190. ];
  17191. // And `getCoordinateSystems` and `getComponentByElement` will be injected in echarts.js
  17192. function ExtensionAPI(chartInstance) {
  17193. each$1(echartsAPIList, function (name) {
  17194. this[name] = bind(chartInstance[name], chartInstance);
  17195. }, this);
  17196. }
  17197. var coordinateSystemCreators = {};
  17198. function CoordinateSystemManager() {
  17199. this._coordinateSystems = [];
  17200. }
  17201. CoordinateSystemManager.prototype = {
  17202. constructor: CoordinateSystemManager,
  17203. create: function (ecModel, api) {
  17204. var coordinateSystems = [];
  17205. each$1(coordinateSystemCreators, function (creater, type) {
  17206. var list = creater.create(ecModel, api);
  17207. coordinateSystems = coordinateSystems.concat(list || []);
  17208. });
  17209. this._coordinateSystems = coordinateSystems;
  17210. },
  17211. update: function (ecModel, api) {
  17212. each$1(this._coordinateSystems, function (coordSys) {
  17213. coordSys.update && coordSys.update(ecModel, api);
  17214. });
  17215. },
  17216. getCoordinateSystems: function () {
  17217. return this._coordinateSystems.slice();
  17218. }
  17219. };
  17220. CoordinateSystemManager.register = function (type, coordinateSystemCreator) {
  17221. coordinateSystemCreators[type] = coordinateSystemCreator;
  17222. };
  17223. CoordinateSystemManager.get = function (type) {
  17224. return coordinateSystemCreators[type];
  17225. };
  17226. /**
  17227. * ECharts option manager
  17228. *
  17229. * @module {echarts/model/OptionManager}
  17230. */
  17231. var each$4 = each$1;
  17232. var clone$3 = clone;
  17233. var map$1 = map;
  17234. var merge$1 = merge;
  17235. var QUERY_REG = /^(min|max)?(.+)$/;
  17236. /**
  17237. * TERM EXPLANATIONS:
  17238. *
  17239. * [option]:
  17240. *
  17241. * An object that contains definitions of components. For example:
  17242. * var option = {
  17243. * title: {...},
  17244. * legend: {...},
  17245. * visualMap: {...},
  17246. * series: [
  17247. * {data: [...]},
  17248. * {data: [...]},
  17249. * ...
  17250. * ]
  17251. * };
  17252. *
  17253. * [rawOption]:
  17254. *
  17255. * An object input to echarts.setOption. 'rawOption' may be an
  17256. * 'option', or may be an object contains multi-options. For example:
  17257. * var option = {
  17258. * baseOption: {
  17259. * title: {...},
  17260. * legend: {...},
  17261. * series: [
  17262. * {data: [...]},
  17263. * {data: [...]},
  17264. * ...
  17265. * ]
  17266. * },
  17267. * timeline: {...},
  17268. * options: [
  17269. * {title: {...}, series: {data: [...]}},
  17270. * {title: {...}, series: {data: [...]}},
  17271. * ...
  17272. * ],
  17273. * media: [
  17274. * {
  17275. * query: {maxWidth: 320},
  17276. * option: {series: {x: 20}, visualMap: {show: false}}
  17277. * },
  17278. * {
  17279. * query: {minWidth: 320, maxWidth: 720},
  17280. * option: {series: {x: 500}, visualMap: {show: true}}
  17281. * },
  17282. * {
  17283. * option: {series: {x: 1200}, visualMap: {show: true}}
  17284. * }
  17285. * ]
  17286. * };
  17287. *
  17288. * @alias module:echarts/model/OptionManager
  17289. * @param {module:echarts/ExtensionAPI} api
  17290. */
  17291. function OptionManager(api) {
  17292. /**
  17293. * @private
  17294. * @type {module:echarts/ExtensionAPI}
  17295. */
  17296. this._api = api;
  17297. /**
  17298. * @private
  17299. * @type {Array.<number>}
  17300. */
  17301. this._timelineOptions = [];
  17302. /**
  17303. * @private
  17304. * @type {Array.<Object>}
  17305. */
  17306. this._mediaList = [];
  17307. /**
  17308. * @private
  17309. * @type {Object}
  17310. */
  17311. this._mediaDefault;
  17312. /**
  17313. * -1, means default.
  17314. * empty means no media.
  17315. * @private
  17316. * @type {Array.<number>}
  17317. */
  17318. this._currentMediaIndices = [];
  17319. /**
  17320. * @private
  17321. * @type {Object}
  17322. */
  17323. this._optionBackup;
  17324. /**
  17325. * @private
  17326. * @type {Object}
  17327. */
  17328. this._newBaseOption;
  17329. }
  17330. // timeline.notMerge is not supported in ec3. Firstly there is rearly
  17331. // case that notMerge is needed. Secondly supporting 'notMerge' requires
  17332. // rawOption cloned and backuped when timeline changed, which does no
  17333. // good to performance. What's more, that both timeline and setOption
  17334. // method supply 'notMerge' brings complex and some problems.
  17335. // Consider this case:
  17336. // (step1) chart.setOption({timeline: {notMerge: false}, ...}, false);
  17337. // (step2) chart.setOption({timeline: {notMerge: true}, ...}, false);
  17338. OptionManager.prototype = {
  17339. constructor: OptionManager,
  17340. /**
  17341. * @public
  17342. * @param {Object} rawOption Raw option.
  17343. * @param {module:echarts/model/Global} ecModel
  17344. * @param {Array.<Function>} optionPreprocessorFuncs
  17345. * @return {Object} Init option
  17346. */
  17347. setOption: function (rawOption, optionPreprocessorFuncs) {
  17348. if (rawOption) {
  17349. // That set dat primitive is dangerous if user reuse the data when setOption again.
  17350. each$1(normalizeToArray(rawOption.series), function (series) {
  17351. series && series.data && isTypedArray(series.data) && setAsPrimitive(series.data);
  17352. });
  17353. }
  17354. // Caution: some series modify option data, if do not clone,
  17355. // it should ensure that the repeat modify correctly
  17356. // (create a new object when modify itself).
  17357. rawOption = clone$3(rawOption, true);
  17358. // FIXME
  17359. // 如果 timeline options 或者 media 中设置了某个属性,而baseOption中没有设置,则进行警告。
  17360. var oldOptionBackup = this._optionBackup;
  17361. var newParsedOption = parseRawOption.call(
  17362. this, rawOption, optionPreprocessorFuncs, !oldOptionBackup
  17363. );
  17364. this._newBaseOption = newParsedOption.baseOption;
  17365. // For setOption at second time (using merge mode);
  17366. if (oldOptionBackup) {
  17367. // Only baseOption can be merged.
  17368. mergeOption(oldOptionBackup.baseOption, newParsedOption.baseOption);
  17369. // For simplicity, timeline options and media options do not support merge,
  17370. // that is, if you `setOption` twice and both has timeline options, the latter
  17371. // timeline opitons will not be merged to the formers, but just substitude them.
  17372. if (newParsedOption.timelineOptions.length) {
  17373. oldOptionBackup.timelineOptions = newParsedOption.timelineOptions;
  17374. }
  17375. if (newParsedOption.mediaList.length) {
  17376. oldOptionBackup.mediaList = newParsedOption.mediaList;
  17377. }
  17378. if (newParsedOption.mediaDefault) {
  17379. oldOptionBackup.mediaDefault = newParsedOption.mediaDefault;
  17380. }
  17381. }
  17382. else {
  17383. this._optionBackup = newParsedOption;
  17384. }
  17385. },
  17386. /**
  17387. * @param {boolean} isRecreate
  17388. * @return {Object}
  17389. */
  17390. mountOption: function (isRecreate) {
  17391. var optionBackup = this._optionBackup;
  17392. // TODO
  17393. // 如果没有reset功能则不clone。
  17394. this._timelineOptions = map$1(optionBackup.timelineOptions, clone$3);
  17395. this._mediaList = map$1(optionBackup.mediaList, clone$3);
  17396. this._mediaDefault = clone$3(optionBackup.mediaDefault);
  17397. this._currentMediaIndices = [];
  17398. return clone$3(isRecreate
  17399. // this._optionBackup.baseOption, which is created at the first `setOption`
  17400. // called, and is merged into every new option by inner method `mergeOption`
  17401. // each time `setOption` called, can be only used in `isRecreate`, because
  17402. // its reliability is under suspicion. In other cases option merge is
  17403. // performed by `model.mergeOption`.
  17404. ? optionBackup.baseOption : this._newBaseOption
  17405. );
  17406. },
  17407. /**
  17408. * @param {module:echarts/model/Global} ecModel
  17409. * @return {Object}
  17410. */
  17411. getTimelineOption: function (ecModel) {
  17412. var option;
  17413. var timelineOptions = this._timelineOptions;
  17414. if (timelineOptions.length) {
  17415. // getTimelineOption can only be called after ecModel inited,
  17416. // so we can get currentIndex from timelineModel.
  17417. var timelineModel = ecModel.getComponent('timeline');
  17418. if (timelineModel) {
  17419. option = clone$3(
  17420. timelineOptions[timelineModel.getCurrentIndex()],
  17421. true
  17422. );
  17423. }
  17424. }
  17425. return option;
  17426. },
  17427. /**
  17428. * @param {module:echarts/model/Global} ecModel
  17429. * @return {Array.<Object>}
  17430. */
  17431. getMediaOption: function (ecModel) {
  17432. var ecWidth = this._api.getWidth();
  17433. var ecHeight = this._api.getHeight();
  17434. var mediaList = this._mediaList;
  17435. var mediaDefault = this._mediaDefault;
  17436. var indices = [];
  17437. var result = [];
  17438. // No media defined.
  17439. if (!mediaList.length && !mediaDefault) {
  17440. return result;
  17441. }
  17442. // Multi media may be applied, the latter defined media has higher priority.
  17443. for (var i = 0, len = mediaList.length; i < len; i++) {
  17444. if (applyMediaQuery(mediaList[i].query, ecWidth, ecHeight)) {
  17445. indices.push(i);
  17446. }
  17447. }
  17448. // FIXME
  17449. // 是否mediaDefault应该强制用户设置,否则可能修改不能回归。
  17450. if (!indices.length && mediaDefault) {
  17451. indices = [-1];
  17452. }
  17453. if (indices.length && !indicesEquals(indices, this._currentMediaIndices)) {
  17454. result = map$1(indices, function (index) {
  17455. return clone$3(
  17456. index === -1 ? mediaDefault.option : mediaList[index].option
  17457. );
  17458. });
  17459. }
  17460. // Otherwise return nothing.
  17461. this._currentMediaIndices = indices;
  17462. return result;
  17463. }
  17464. };
  17465. function parseRawOption(rawOption, optionPreprocessorFuncs, isNew) {
  17466. var timelineOptions = [];
  17467. var mediaList = [];
  17468. var mediaDefault;
  17469. var baseOption;
  17470. // Compatible with ec2.
  17471. var timelineOpt = rawOption.timeline;
  17472. if (rawOption.baseOption) {
  17473. baseOption = rawOption.baseOption;
  17474. }
  17475. // For timeline
  17476. if (timelineOpt || rawOption.options) {
  17477. baseOption = baseOption || {};
  17478. timelineOptions = (rawOption.options || []).slice();
  17479. }
  17480. // For media query
  17481. if (rawOption.media) {
  17482. baseOption = baseOption || {};
  17483. var media = rawOption.media;
  17484. each$4(media, function (singleMedia) {
  17485. if (singleMedia && singleMedia.option) {
  17486. if (singleMedia.query) {
  17487. mediaList.push(singleMedia);
  17488. }
  17489. else if (!mediaDefault) {
  17490. // Use the first media default.
  17491. mediaDefault = singleMedia;
  17492. }
  17493. }
  17494. });
  17495. }
  17496. // For normal option
  17497. if (!baseOption) {
  17498. baseOption = rawOption;
  17499. }
  17500. // Set timelineOpt to baseOption in ec3,
  17501. // which is convenient for merge option.
  17502. if (!baseOption.timeline) {
  17503. baseOption.timeline = timelineOpt;
  17504. }
  17505. // Preprocess.
  17506. each$4([baseOption].concat(timelineOptions)
  17507. .concat(map(mediaList, function (media) {
  17508. return media.option;
  17509. })),
  17510. function (option) {
  17511. each$4(optionPreprocessorFuncs, function (preProcess) {
  17512. preProcess(option, isNew);
  17513. });
  17514. }
  17515. );
  17516. return {
  17517. baseOption: baseOption,
  17518. timelineOptions: timelineOptions,
  17519. mediaDefault: mediaDefault,
  17520. mediaList: mediaList
  17521. };
  17522. }
  17523. /**
  17524. * @see <http://www.w3.org/TR/css3-mediaqueries/#media1>
  17525. * Support: width, height, aspectRatio
  17526. * Can use max or min as prefix.
  17527. */
  17528. function applyMediaQuery(query, ecWidth, ecHeight) {
  17529. var realMap = {
  17530. width: ecWidth,
  17531. height: ecHeight,
  17532. aspectratio: ecWidth / ecHeight // lowser case for convenientce.
  17533. };
  17534. var applicatable = true;
  17535. each$1(query, function (value, attr) {
  17536. var matched = attr.match(QUERY_REG);
  17537. if (!matched || !matched[1] || !matched[2]) {
  17538. return;
  17539. }
  17540. var operator = matched[1];
  17541. var realAttr = matched[2].toLowerCase();
  17542. if (!compare(realMap[realAttr], value, operator)) {
  17543. applicatable = false;
  17544. }
  17545. });
  17546. return applicatable;
  17547. }
  17548. function compare(real, expect, operator) {
  17549. if (operator === 'min') {
  17550. return real >= expect;
  17551. }
  17552. else if (operator === 'max') {
  17553. return real <= expect;
  17554. }
  17555. else { // Equals
  17556. return real === expect;
  17557. }
  17558. }
  17559. function indicesEquals(indices1, indices2) {
  17560. // indices is always order by asc and has only finite number.
  17561. return indices1.join(',') === indices2.join(',');
  17562. }
  17563. /**
  17564. * Consider case:
  17565. * `chart.setOption(opt1);`
  17566. * Then user do some interaction like dataZoom, dataView changing.
  17567. * `chart.setOption(opt2);`
  17568. * Then user press 'reset button' in toolbox.
  17569. *
  17570. * After doing that all of the interaction effects should be reset, the
  17571. * chart should be the same as the result of invoke
  17572. * `chart.setOption(opt1); chart.setOption(opt2);`.
  17573. *
  17574. * Although it is not able ensure that
  17575. * `chart.setOption(opt1); chart.setOption(opt2);` is equivalents to
  17576. * `chart.setOption(merge(opt1, opt2));` exactly,
  17577. * this might be the only simple way to implement that feature.
  17578. *
  17579. * MEMO: We've considered some other approaches:
  17580. * 1. Each model handle its self restoration but not uniform treatment.
  17581. * (Too complex in logic and error-prone)
  17582. * 2. Use a shadow ecModel. (Performace expensive)
  17583. */
  17584. function mergeOption(oldOption, newOption) {
  17585. newOption = newOption || {};
  17586. each$4(newOption, function (newCptOpt, mainType) {
  17587. if (newCptOpt == null) {
  17588. return;
  17589. }
  17590. var oldCptOpt = oldOption[mainType];
  17591. if (!ComponentModel.hasClass(mainType)) {
  17592. oldOption[mainType] = merge$1(oldCptOpt, newCptOpt, true);
  17593. }
  17594. else {
  17595. newCptOpt = normalizeToArray(newCptOpt);
  17596. oldCptOpt = normalizeToArray(oldCptOpt);
  17597. var mapResult = mappingToExists(oldCptOpt, newCptOpt);
  17598. oldOption[mainType] = map$1(mapResult, function (item) {
  17599. return (item.option && item.exist)
  17600. ? merge$1(item.exist, item.option, true)
  17601. : (item.exist || item.option);
  17602. });
  17603. }
  17604. });
  17605. }
  17606. var each$5 = each$1;
  17607. var isObject$3 = isObject$1;
  17608. var POSSIBLE_STYLES = [
  17609. 'areaStyle', 'lineStyle', 'nodeStyle', 'linkStyle',
  17610. 'chordStyle', 'label', 'labelLine'
  17611. ];
  17612. function compatEC2ItemStyle(opt) {
  17613. var itemStyleOpt = opt && opt.itemStyle;
  17614. if (!itemStyleOpt) {
  17615. return;
  17616. }
  17617. for (var i = 0, len = POSSIBLE_STYLES.length; i < len; i++) {
  17618. var styleName = POSSIBLE_STYLES[i];
  17619. var normalItemStyleOpt = itemStyleOpt.normal;
  17620. var emphasisItemStyleOpt = itemStyleOpt.emphasis;
  17621. if (normalItemStyleOpt && normalItemStyleOpt[styleName]) {
  17622. opt[styleName] = opt[styleName] || {};
  17623. if (!opt[styleName].normal) {
  17624. opt[styleName].normal = normalItemStyleOpt[styleName];
  17625. }
  17626. else {
  17627. merge(opt[styleName].normal, normalItemStyleOpt[styleName]);
  17628. }
  17629. normalItemStyleOpt[styleName] = null;
  17630. }
  17631. if (emphasisItemStyleOpt && emphasisItemStyleOpt[styleName]) {
  17632. opt[styleName] = opt[styleName] || {};
  17633. if (!opt[styleName].emphasis) {
  17634. opt[styleName].emphasis = emphasisItemStyleOpt[styleName];
  17635. }
  17636. else {
  17637. merge(opt[styleName].emphasis, emphasisItemStyleOpt[styleName]);
  17638. }
  17639. emphasisItemStyleOpt[styleName] = null;
  17640. }
  17641. }
  17642. }
  17643. function convertNormalEmphasis(opt, optType, useExtend) {
  17644. if (opt && opt[optType] && (opt[optType].normal || opt[optType].emphasis)) {
  17645. var normalOpt = opt[optType].normal;
  17646. var emphasisOpt = opt[optType].emphasis;
  17647. if (normalOpt) {
  17648. // Timeline controlStyle has other properties besides normal and emphasis
  17649. if (useExtend) {
  17650. opt[optType].normal = opt[optType].emphasis = null;
  17651. defaults(opt[optType], normalOpt);
  17652. }
  17653. else {
  17654. opt[optType] = normalOpt;
  17655. }
  17656. }
  17657. if (emphasisOpt) {
  17658. opt.emphasis = opt.emphasis || {};
  17659. opt.emphasis[optType] = emphasisOpt;
  17660. }
  17661. }
  17662. }
  17663. function removeEC3NormalStatus(opt) {
  17664. convertNormalEmphasis(opt, 'itemStyle');
  17665. convertNormalEmphasis(opt, 'lineStyle');
  17666. convertNormalEmphasis(opt, 'areaStyle');
  17667. convertNormalEmphasis(opt, 'label');
  17668. convertNormalEmphasis(opt, 'labelLine');
  17669. // treemap
  17670. convertNormalEmphasis(opt, 'upperLabel');
  17671. // graph
  17672. convertNormalEmphasis(opt, 'edgeLabel');
  17673. }
  17674. function compatTextStyle(opt, propName) {
  17675. // Check whether is not object (string\null\undefined ...)
  17676. var labelOptSingle = isObject$3(opt) && opt[propName];
  17677. var textStyle = isObject$3(labelOptSingle) && labelOptSingle.textStyle;
  17678. if (textStyle) {
  17679. for (var i = 0, len = TEXT_STYLE_OPTIONS.length; i < len; i++) {
  17680. var propName = TEXT_STYLE_OPTIONS[i];
  17681. if (textStyle.hasOwnProperty(propName)) {
  17682. labelOptSingle[propName] = textStyle[propName];
  17683. }
  17684. }
  17685. }
  17686. }
  17687. function compatEC3CommonStyles(opt) {
  17688. if (opt) {
  17689. removeEC3NormalStatus(opt);
  17690. compatTextStyle(opt, 'label');
  17691. opt.emphasis && compatTextStyle(opt.emphasis, 'label');
  17692. }
  17693. }
  17694. function processSeries(seriesOpt) {
  17695. if (!isObject$3(seriesOpt)) {
  17696. return;
  17697. }
  17698. compatEC2ItemStyle(seriesOpt);
  17699. removeEC3NormalStatus(seriesOpt);
  17700. compatTextStyle(seriesOpt, 'label');
  17701. // treemap
  17702. compatTextStyle(seriesOpt, 'upperLabel');
  17703. // graph
  17704. compatTextStyle(seriesOpt, 'edgeLabel');
  17705. if (seriesOpt.emphasis) {
  17706. compatTextStyle(seriesOpt.emphasis, 'label');
  17707. // treemap
  17708. compatTextStyle(seriesOpt.emphasis, 'upperLabel');
  17709. // graph
  17710. compatTextStyle(seriesOpt.emphasis, 'edgeLabel');
  17711. }
  17712. var markPoint = seriesOpt.markPoint;
  17713. if (markPoint) {
  17714. compatEC2ItemStyle(markPoint);
  17715. compatEC3CommonStyles(markPoint);
  17716. }
  17717. var markLine = seriesOpt.markLine;
  17718. if (markLine) {
  17719. compatEC2ItemStyle(markLine);
  17720. compatEC3CommonStyles(markLine);
  17721. }
  17722. var markArea = seriesOpt.markArea;
  17723. if (markArea) {
  17724. compatEC3CommonStyles(markArea);
  17725. }
  17726. var data = seriesOpt.data;
  17727. // Break with ec3: if `setOption` again, there may be no `type` in option,
  17728. // then the backward compat based on option type will not be performed.
  17729. if (seriesOpt.type === 'graph') {
  17730. data = data || seriesOpt.nodes;
  17731. var edgeData = seriesOpt.links || seriesOpt.edges;
  17732. if (edgeData && !isTypedArray(edgeData)) {
  17733. for (var i = 0; i < edgeData.length; i++) {
  17734. compatEC3CommonStyles(edgeData[i]);
  17735. }
  17736. }
  17737. each$1(seriesOpt.categories, function (opt) {
  17738. removeEC3NormalStatus(opt);
  17739. });
  17740. }
  17741. if (data && !isTypedArray(data)) {
  17742. for (var i = 0; i < data.length; i++) {
  17743. compatEC3CommonStyles(data[i]);
  17744. }
  17745. }
  17746. // mark point data
  17747. var markPoint = seriesOpt.markPoint;
  17748. if (markPoint && markPoint.data) {
  17749. var mpData = markPoint.data;
  17750. for (var i = 0; i < mpData.length; i++) {
  17751. compatEC3CommonStyles(mpData[i]);
  17752. }
  17753. }
  17754. // mark line data
  17755. var markLine = seriesOpt.markLine;
  17756. if (markLine && markLine.data) {
  17757. var mlData = markLine.data;
  17758. for (var i = 0; i < mlData.length; i++) {
  17759. if (isArray(mlData[i])) {
  17760. compatEC3CommonStyles(mlData[i][0]);
  17761. compatEC3CommonStyles(mlData[i][1]);
  17762. }
  17763. else {
  17764. compatEC3CommonStyles(mlData[i]);
  17765. }
  17766. }
  17767. }
  17768. // Series
  17769. if (seriesOpt.type === 'gauge') {
  17770. compatTextStyle(seriesOpt, 'axisLabel');
  17771. compatTextStyle(seriesOpt, 'title');
  17772. compatTextStyle(seriesOpt, 'detail');
  17773. }
  17774. else if (seriesOpt.type === 'treemap') {
  17775. convertNormalEmphasis(seriesOpt.breadcrumb, 'itemStyle');
  17776. each$1(seriesOpt.levels, function (opt) {
  17777. removeEC3NormalStatus(opt);
  17778. });
  17779. }
  17780. // sunburst starts from ec4, so it does not need to compat levels.
  17781. }
  17782. function toArr(o) {
  17783. return isArray(o) ? o : o ? [o] : [];
  17784. }
  17785. function toObj(o) {
  17786. return (isArray(o) ? o[0] : o) || {};
  17787. }
  17788. var compatStyle = function (option, isTheme) {
  17789. each$5(toArr(option.series), function (seriesOpt) {
  17790. isObject$3(seriesOpt) && processSeries(seriesOpt);
  17791. });
  17792. var axes = ['xAxis', 'yAxis', 'radiusAxis', 'angleAxis', 'singleAxis', 'parallelAxis', 'radar'];
  17793. isTheme && axes.push('valueAxis', 'categoryAxis', 'logAxis', 'timeAxis');
  17794. each$5(
  17795. axes,
  17796. function (axisName) {
  17797. each$5(toArr(option[axisName]), function (axisOpt) {
  17798. if (axisOpt) {
  17799. compatTextStyle(axisOpt, 'axisLabel');
  17800. compatTextStyle(axisOpt.axisPointer, 'label');
  17801. }
  17802. });
  17803. }
  17804. );
  17805. each$5(toArr(option.parallel), function (parallelOpt) {
  17806. var parallelAxisDefault = parallelOpt && parallelOpt.parallelAxisDefault;
  17807. compatTextStyle(parallelAxisDefault, 'axisLabel');
  17808. compatTextStyle(parallelAxisDefault && parallelAxisDefault.axisPointer, 'label');
  17809. });
  17810. each$5(toArr(option.calendar), function (calendarOpt) {
  17811. convertNormalEmphasis(calendarOpt, 'itemStyle');
  17812. compatTextStyle(calendarOpt, 'dayLabel');
  17813. compatTextStyle(calendarOpt, 'monthLabel');
  17814. compatTextStyle(calendarOpt, 'yearLabel');
  17815. });
  17816. // radar.name.textStyle
  17817. each$5(toArr(option.radar), function (radarOpt) {
  17818. compatTextStyle(radarOpt, 'name');
  17819. });
  17820. each$5(toArr(option.geo), function (geoOpt) {
  17821. if (isObject$3(geoOpt)) {
  17822. compatEC3CommonStyles(geoOpt);
  17823. each$5(toArr(geoOpt.regions), function (regionObj) {
  17824. compatEC3CommonStyles(regionObj);
  17825. });
  17826. }
  17827. });
  17828. each$5(toArr(option.timeline), function (timelineOpt) {
  17829. compatEC3CommonStyles(timelineOpt);
  17830. convertNormalEmphasis(timelineOpt, 'label');
  17831. convertNormalEmphasis(timelineOpt, 'itemStyle');
  17832. convertNormalEmphasis(timelineOpt, 'controlStyle', true);
  17833. var data = timelineOpt.data;
  17834. isArray(data) && each$1(data, function (item) {
  17835. if (isObject$1(item)) {
  17836. convertNormalEmphasis(item, 'label');
  17837. convertNormalEmphasis(item, 'itemStyle');
  17838. }
  17839. });
  17840. });
  17841. each$5(toArr(option.toolbox), function (toolboxOpt) {
  17842. convertNormalEmphasis(toolboxOpt, 'iconStyle');
  17843. each$5(toolboxOpt.feature, function (featureOpt) {
  17844. convertNormalEmphasis(featureOpt, 'iconStyle');
  17845. });
  17846. });
  17847. compatTextStyle(toObj(option.axisPointer), 'label');
  17848. compatTextStyle(toObj(option.tooltip).axisPointer, 'label');
  17849. };
  17850. // Compatitable with 2.0
  17851. function get(opt, path) {
  17852. path = path.split(',');
  17853. var obj = opt;
  17854. for (var i = 0; i < path.length; i++) {
  17855. obj = obj && obj[path[i]];
  17856. if (obj == null) {
  17857. break;
  17858. }
  17859. }
  17860. return obj;
  17861. }
  17862. function set$1(opt, path, val, overwrite) {
  17863. path = path.split(',');
  17864. var obj = opt;
  17865. var key;
  17866. for (var i = 0; i < path.length - 1; i++) {
  17867. key = path[i];
  17868. if (obj[key] == null) {
  17869. obj[key] = {};
  17870. }
  17871. obj = obj[key];
  17872. }
  17873. if (overwrite || obj[path[i]] == null) {
  17874. obj[path[i]] = val;
  17875. }
  17876. }
  17877. function compatLayoutProperties(option) {
  17878. each$1(LAYOUT_PROPERTIES, function (prop) {
  17879. if (prop[0] in option && !(prop[1] in option)) {
  17880. option[prop[1]] = option[prop[0]];
  17881. }
  17882. });
  17883. }
  17884. var LAYOUT_PROPERTIES = [
  17885. ['x', 'left'], ['y', 'top'], ['x2', 'right'], ['y2', 'bottom']
  17886. ];
  17887. var COMPATITABLE_COMPONENTS = [
  17888. 'grid', 'geo', 'parallel', 'legend', 'toolbox', 'title', 'visualMap', 'dataZoom', 'timeline'
  17889. ];
  17890. var backwardCompat = function (option, isTheme) {
  17891. compatStyle(option, isTheme);
  17892. // Make sure series array for model initialization.
  17893. option.series = normalizeToArray(option.series);
  17894. each$1(option.series, function (seriesOpt) {
  17895. if (!isObject$1(seriesOpt)) {
  17896. return;
  17897. }
  17898. var seriesType = seriesOpt.type;
  17899. if (seriesType === 'pie' || seriesType === 'gauge') {
  17900. if (seriesOpt.clockWise != null) {
  17901. seriesOpt.clockwise = seriesOpt.clockWise;
  17902. }
  17903. }
  17904. if (seriesType === 'gauge') {
  17905. var pointerColor = get(seriesOpt, 'pointer.color');
  17906. pointerColor != null
  17907. && set$1(seriesOpt, 'itemStyle.normal.color', pointerColor);
  17908. }
  17909. compatLayoutProperties(seriesOpt);
  17910. });
  17911. // dataRange has changed to visualMap
  17912. if (option.dataRange) {
  17913. option.visualMap = option.dataRange;
  17914. }
  17915. each$1(COMPATITABLE_COMPONENTS, function (componentName) {
  17916. var options = option[componentName];
  17917. if (options) {
  17918. if (!isArray(options)) {
  17919. options = [options];
  17920. }
  17921. each$1(options, function (option) {
  17922. compatLayoutProperties(option);
  17923. });
  17924. }
  17925. });
  17926. };
  17927. // (1) [Caution]: the logic is correct based on the premises:
  17928. // data processing stage is blocked in stream.
  17929. // See <module:echarts/stream/Scheduler#performDataProcessorTasks>
  17930. // (2) Only register once when import repeatly.
  17931. // Should be executed before after series filtered and before stack calculation.
  17932. var dataStack = function (ecModel) {
  17933. var stackInfoMap = createHashMap();
  17934. ecModel.eachSeries(function (seriesModel) {
  17935. var stack = seriesModel.get('stack');
  17936. // Compatibal: when `stack` is set as '', do not stack.
  17937. if (stack) {
  17938. var stackInfoList = stackInfoMap.get(stack) || stackInfoMap.set(stack, []);
  17939. var data = seriesModel.getData();
  17940. var stackInfo = {
  17941. // Used for calculate axis extent automatically.
  17942. stackResultDimension: data.getCalculationInfo('stackResultDimension'),
  17943. stackedOverDimension: data.getCalculationInfo('stackedOverDimension'),
  17944. stackedDimension: data.getCalculationInfo('stackedDimension'),
  17945. stackedByDimension: data.getCalculationInfo('stackedByDimension'),
  17946. isStackedByIndex: data.getCalculationInfo('isStackedByIndex'),
  17947. data: data,
  17948. seriesModel: seriesModel
  17949. };
  17950. // If stacked on axis that do not support data stack.
  17951. if (!stackInfo.stackedDimension
  17952. || !(stackInfo.isStackedByIndex || stackInfo.stackedByDimension)
  17953. ) {
  17954. return;
  17955. }
  17956. stackInfoList.length && data.setCalculationInfo(
  17957. 'stackedOnSeries', stackInfoList[stackInfoList.length - 1].seriesModel
  17958. );
  17959. stackInfoList.push(stackInfo);
  17960. }
  17961. });
  17962. stackInfoMap.each(calculateStack);
  17963. };
  17964. function calculateStack(stackInfoList) {
  17965. each$1(stackInfoList, function (targetStackInfo, idxInStack) {
  17966. var resultVal = [];
  17967. var resultNaN = [NaN, NaN];
  17968. var dims = [targetStackInfo.stackResultDimension, targetStackInfo.stackedOverDimension];
  17969. var targetData = targetStackInfo.data;
  17970. var isStackedByIndex = targetStackInfo.isStackedByIndex;
  17971. // Should not write on raw data, because stack series model list changes
  17972. // depending on legend selection.
  17973. var newData = targetData.map(dims, function (v0, v1, dataIndex) {
  17974. var sum = targetData.get(targetStackInfo.stackedDimension, dataIndex);
  17975. // Consider `connectNulls` of line area, if value is NaN, stackedOver
  17976. // should also be NaN, to draw a appropriate belt area.
  17977. if (isNaN(sum)) {
  17978. return resultNaN;
  17979. }
  17980. var byValue;
  17981. var stackedDataRawIndex;
  17982. if (isStackedByIndex) {
  17983. stackedDataRawIndex = targetData.getRawIndex(dataIndex);
  17984. }
  17985. else {
  17986. byValue = targetData.get(targetStackInfo.stackedByDimension, dataIndex);
  17987. }
  17988. // If stackOver is NaN, chart view will render point on value start.
  17989. var stackedOver = NaN;
  17990. for (var j = idxInStack - 1; j >= 0; j--) {
  17991. var stackInfo = stackInfoList[j];
  17992. // Has been optimized by inverted indices on `stackedByDimension`.
  17993. if (!isStackedByIndex) {
  17994. stackedDataRawIndex = stackInfo.data.rawIndexOf(stackInfo.stackedByDimension, byValue);
  17995. }
  17996. if (stackedDataRawIndex >= 0) {
  17997. var val = stackInfo.data.getByRawIndex(stackInfo.stackResultDimension, stackedDataRawIndex);
  17998. // Considering positive stack, negative stack and empty data
  17999. if ((sum >= 0 && val > 0) // Positive stack
  18000. || (sum <= 0 && val < 0) // Negative stack
  18001. ) {
  18002. sum += val;
  18003. stackedOver = val;
  18004. break;
  18005. }
  18006. }
  18007. }
  18008. resultVal[0] = sum;
  18009. resultVal[1] = stackedOver;
  18010. return resultVal;
  18011. });
  18012. targetData.hostModel.setData(newData);
  18013. // Update for consequent calculation
  18014. targetStackInfo.data = newData;
  18015. });
  18016. }
  18017. // TODO
  18018. // ??? refactor? check the outer usage of data provider.
  18019. // merge with defaultDimValueGetter?
  18020. /**
  18021. * If normal array used, mutable chunk size is supported.
  18022. * If typed array used, chunk size must be fixed.
  18023. */
  18024. function DefaultDataProvider(source, dimSize) {
  18025. if (!Source.isInstance(source)) {
  18026. source = Source.seriesDataToSource(source);
  18027. }
  18028. this._source = source;
  18029. var data = this._data = source.data;
  18030. var sourceFormat = source.sourceFormat;
  18031. // Typed array. TODO IE10+?
  18032. if (sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) {
  18033. if (__DEV__) {
  18034. if (dimSize == null) {
  18035. throw new Error('Typed array data must specify dimension size');
  18036. }
  18037. }
  18038. this._offset = 0;
  18039. this._dimSize = dimSize;
  18040. this._data = data;
  18041. }
  18042. var methods = providerMethods[
  18043. sourceFormat === SOURCE_FORMAT_ARRAY_ROWS
  18044. ? sourceFormat + '_' + source.seriesLayoutBy
  18045. : sourceFormat
  18046. ];
  18047. if (__DEV__) {
  18048. assert$1(methods, 'Invalide sourceFormat: ' + sourceFormat);
  18049. }
  18050. extend(this, methods);
  18051. }
  18052. var providerProto = DefaultDataProvider.prototype;
  18053. // If data is pure without style configuration
  18054. providerProto.pure = false;
  18055. // If data is persistent and will not be released after use.
  18056. providerProto.persistent = true;
  18057. // ???! FIXME legacy data provider do not has method getSource
  18058. providerProto.getSource = function () {
  18059. return this._source;
  18060. };
  18061. var providerMethods = {
  18062. 'arrayRows_column': {
  18063. pure: true,
  18064. count: function () {
  18065. return Math.max(0, this._data.length - this._source.startIndex);
  18066. },
  18067. getItem: function (idx) {
  18068. return this._data[idx + this._source.startIndex];
  18069. },
  18070. appendData: appendDataSimply
  18071. },
  18072. 'arrayRows_row': {
  18073. pure: true,
  18074. count: function () {
  18075. var row = this._data[0];
  18076. return row ? Math.max(0, row.length - this._source.startIndex) : 0;
  18077. },
  18078. getItem: function (idx) {
  18079. idx += this._source.startIndex;
  18080. var item = [];
  18081. var data = this._data;
  18082. for (var i = 0; i < data.length; i++) {
  18083. var row = data[i];
  18084. item.push(row ? row[idx] : null);
  18085. }
  18086. return item;
  18087. },
  18088. appendData: function () {
  18089. throw new Error('Do not support appendData when set seriesLayoutBy: "row".');
  18090. }
  18091. },
  18092. 'objectRows': {
  18093. pure: true,
  18094. count: countSimply,
  18095. getItem: getItemSimply,
  18096. appendData: appendDataSimply
  18097. },
  18098. 'keyedColumns': {
  18099. pure: true,
  18100. count: function () {
  18101. var dimName = this._source.dimensionsDefine[0].name;
  18102. var col = this._data[dimName];
  18103. return col ? col.length : 0;
  18104. },
  18105. getItem: function (idx) {
  18106. var item = [];
  18107. var dims = this._source.dimensionsDefine;
  18108. for (var i = 0; i < dims.length; i++) {
  18109. var col = this._data[dims[i].name];
  18110. item.push(col ? col[idx] : null);
  18111. }
  18112. return item;
  18113. },
  18114. appendData: function (newData) {
  18115. var data = this._data;
  18116. each$1(newData, function (newCol, key) {
  18117. var oldCol = data[key] || (data[key] = []);
  18118. for (var i = 0; i < (newCol || []).length; i++) {
  18119. oldCol.push(newCol[i]);
  18120. }
  18121. });
  18122. }
  18123. },
  18124. 'original': {
  18125. count: countSimply,
  18126. getItem: getItemSimply,
  18127. appendData: appendDataSimply
  18128. },
  18129. 'typedArray': {
  18130. persistent: false,
  18131. pure: true,
  18132. count: function () {
  18133. return this._data ? (this._data.length / this._dimSize) : 0;
  18134. },
  18135. getItem: function (idx) {
  18136. idx = idx - this._offset;
  18137. var item = [];
  18138. var offset = this._dimSize * idx;
  18139. for (var i = 0; i < this._dimSize; i++) {
  18140. item[i] = this._data[offset + i];
  18141. }
  18142. return item;
  18143. },
  18144. appendData: function (newData) {
  18145. if (__DEV__) {
  18146. assert$1(
  18147. isTypedArray(newData),
  18148. 'Added data must be TypedArray if data in initialization is TypedArray'
  18149. );
  18150. }
  18151. this._data = newData;
  18152. },
  18153. // Clean self if data is already used.
  18154. clean: function () {
  18155. // PENDING
  18156. this._offset += this.count();
  18157. this._data = null;
  18158. }
  18159. }
  18160. };
  18161. function countSimply() {
  18162. return this._data.length;
  18163. }
  18164. function getItemSimply(idx) {
  18165. return this._data[idx];
  18166. }
  18167. function appendDataSimply(newData) {
  18168. for (var i = 0; i < newData.length; i++) {
  18169. this._data.push(newData[i]);
  18170. }
  18171. }
  18172. var rawValueGetters = {
  18173. arrayRows: getRawValueSimply,
  18174. objectRows: function (dataItem, dataIndex, dimIndex, dimName) {
  18175. return dimIndex != null ? dataItem[dimName] : dataItem;
  18176. },
  18177. keyedColumns: getRawValueSimply,
  18178. original: function (dataItem, dataIndex, dimIndex, dimName) {
  18179. // FIXME
  18180. // In some case (markpoint in geo (geo-map.html)), dataItem
  18181. // is {coord: [...]}
  18182. var value = getDataItemValue(dataItem);
  18183. return (dimIndex == null || !(value instanceof Array))
  18184. ? value
  18185. : value[dimIndex];
  18186. },
  18187. typedArray: getRawValueSimply
  18188. };
  18189. function getRawValueSimply(dataItem, dataIndex, dimIndex, dimName) {
  18190. return dimIndex != null ? dataItem[dimIndex] : dataItem;
  18191. }
  18192. var defaultDimValueGetters = {
  18193. arrayRows: getDimValueSimply,
  18194. objectRows: function (dataItem, dimName, dataIndex, dimIndex) {
  18195. return converDataValue(dataItem[dimName], this._dimensionInfos[dimName]);
  18196. },
  18197. keyedColumns: getDimValueSimply,
  18198. original: function (dataItem, dimName, dataIndex, dimIndex) {
  18199. // Performance sensitive, do not use modelUtil.getDataItemValue.
  18200. // If dataItem is an plain object with no value field, the var `value`
  18201. // will be assigned with the object, but it will be tread correctly
  18202. // in the `convertDataValue`.
  18203. var value = dataItem && (dataItem.value == null ? dataItem : dataItem.value);
  18204. // If any dataItem is like { value: 10 }
  18205. if (!this._rawData.pure && isDataItemOption(dataItem)) {
  18206. this.hasItemOption = true;
  18207. }
  18208. return converDataValue(
  18209. (value instanceof Array)
  18210. ? value[dimIndex]
  18211. // If value is a single number or something else not array.
  18212. : value,
  18213. this._dimensionInfos[dimName]
  18214. );
  18215. },
  18216. typedArray: function (dataItem, dimName, dataIndex, dimIndex) {
  18217. return dataItem[dimIndex];
  18218. }
  18219. };
  18220. function getDimValueSimply(dataItem, dimName, dataIndex, dimIndex) {
  18221. return converDataValue(dataItem[dimIndex], this._dimensionInfos[dimName]);
  18222. }
  18223. /**
  18224. * This helper method convert value in data.
  18225. * @param {string|number|Date} value
  18226. * @param {Object|string} [dimInfo] If string (like 'x'), dimType defaults 'number'.
  18227. * If "dimInfo.ordinalParseAndSave", ordinal value can be parsed.
  18228. */
  18229. function converDataValue(value, dimInfo) {
  18230. // Performance sensitive.
  18231. var dimType = dimInfo && dimInfo.type;
  18232. if (dimType === 'ordinal') {
  18233. // If given value is a category string
  18234. var ordinalMeta = dimInfo && dimInfo.ordinalMeta;
  18235. return ordinalMeta
  18236. ? ordinalMeta.parseAndCollect(value)
  18237. : value;
  18238. }
  18239. if (dimType === 'time'
  18240. // spead up when using timestamp
  18241. && typeof value !== 'number'
  18242. && value != null
  18243. && value !== '-'
  18244. ) {
  18245. value = +parseDate(value);
  18246. }
  18247. // dimType defaults 'number'.
  18248. // If dimType is not ordinal and value is null or undefined or NaN or '-',
  18249. // parse to NaN.
  18250. return (value == null || value === '')
  18251. ? NaN
  18252. // If string (like '-'), using '+' parse to NaN
  18253. // If object, also parse to NaN
  18254. : +value;
  18255. }
  18256. // ??? FIXME can these logic be more neat: getRawValue, getRawDataItem,
  18257. // Consider persistent.
  18258. // Caution: why use raw value to display on label or tooltip?
  18259. // A reason is to avoid format. For example time value we do not know
  18260. // how to format is expected. More over, if stack is used, calculated
  18261. // value may be 0.91000000001, which have brings trouble to display.
  18262. // TODO: consider how to treat null/undefined/NaN when display?
  18263. /**
  18264. * @param {module:echarts/data/List} data
  18265. * @param {number} dataIndex
  18266. * @param {string|number} [dim] dimName or dimIndex
  18267. * @return {Array.<number>|string|number} can be null/undefined.
  18268. */
  18269. function retrieveRawValue(data, dataIndex, dim) {
  18270. if (!data) {
  18271. return;
  18272. }
  18273. // Consider data may be not persistent.
  18274. var dataItem = data.getRawDataItem(dataIndex);
  18275. if (dataItem == null) {
  18276. return;
  18277. }
  18278. var sourceFormat = data.getProvider().getSource().sourceFormat;
  18279. var dimName;
  18280. var dimIndex;
  18281. var dimInfo = data.getDimensionInfo(dim);
  18282. if (dimInfo) {
  18283. dimName = dimInfo.name;
  18284. dimIndex = dimInfo.index;
  18285. }
  18286. return rawValueGetters[sourceFormat](dataItem, dataIndex, dimIndex, dimName);
  18287. }
  18288. /**
  18289. * Compatible with some cases (in pie, map) like:
  18290. * data: [{name: 'xx', value: 5, selected: true}, ...]
  18291. * where only sourceFormat is 'original' and 'objectRows' supported.
  18292. *
  18293. * ??? TODO
  18294. * Supported detail options in data item when using 'arrayRows'.
  18295. *
  18296. * @param {module:echarts/data/List} data
  18297. * @param {number} dataIndex
  18298. * @param {string} attr like 'selected'
  18299. */
  18300. function retrieveRawAttr(data, dataIndex, attr) {
  18301. if (!data) {
  18302. return;
  18303. }
  18304. var sourceFormat = data.getProvider().getSource().sourceFormat;
  18305. if (sourceFormat !== SOURCE_FORMAT_ORIGINAL
  18306. && sourceFormat !== SOURCE_FORMAT_OBJECT_ROWS
  18307. ) {
  18308. return;
  18309. }
  18310. var dataItem = data.getRawDataItem(dataIndex);
  18311. if (sourceFormat === SOURCE_FORMAT_ORIGINAL && !isObject$1(dataItem)) {
  18312. dataItem = null;
  18313. }
  18314. if (dataItem) {
  18315. return dataItem[attr];
  18316. }
  18317. }
  18318. var DIMENSION_LABEL_REG = /\{@(.+?)\}/g;
  18319. // PENDING A little ugly
  18320. var dataFormatMixin = {
  18321. /**
  18322. * Get params for formatter
  18323. * @param {number} dataIndex
  18324. * @param {string} [dataType]
  18325. * @return {Object}
  18326. */
  18327. getDataParams: function (dataIndex, dataType) {
  18328. var data = this.getData(dataType);
  18329. var rawValue = this.getRawValue(dataIndex, dataType);
  18330. var rawDataIndex = data.getRawIndex(dataIndex);
  18331. var name = data.getName(dataIndex, true);
  18332. var itemOpt = data.getRawDataItem(dataIndex);
  18333. var color = data.getItemVisual(dataIndex, 'color');
  18334. return {
  18335. componentType: this.mainType,
  18336. componentSubType: this.subType,
  18337. seriesType: this.mainType === 'series' ? this.subType : null,
  18338. seriesIndex: this.seriesIndex,
  18339. seriesId: this.id,
  18340. seriesName: this.name,
  18341. name: name,
  18342. dataIndex: rawDataIndex,
  18343. data: itemOpt,
  18344. dataType: dataType,
  18345. value: rawValue,
  18346. color: color,
  18347. marker: getTooltipMarker(color),
  18348. // Param name list for mapping `a`, `b`, `c`, `d`, `e`
  18349. $vars: ['seriesName', 'name', 'value']
  18350. };
  18351. },
  18352. /**
  18353. * Format label
  18354. * @param {number} dataIndex
  18355. * @param {string} [status='normal'] 'normal' or 'emphasis'
  18356. * @param {string} [dataType]
  18357. * @param {number} [dimIndex]
  18358. * @param {string} [labelProp='label']
  18359. * @return {string} If not formatter, return null/undefined
  18360. */
  18361. getFormattedLabel: function (dataIndex, status, dataType, dimIndex, labelProp) {
  18362. status = status || 'normal';
  18363. var data = this.getData(dataType);
  18364. var itemModel = data.getItemModel(dataIndex);
  18365. var params = this.getDataParams(dataIndex, dataType);
  18366. if (dimIndex != null && (params.value instanceof Array)) {
  18367. params.value = params.value[dimIndex];
  18368. }
  18369. var formatter = itemModel.get(
  18370. status === 'normal'
  18371. ? [labelProp || 'label', 'formatter']
  18372. : [status, labelProp || 'label', 'formatter']
  18373. );
  18374. if (typeof formatter === 'function') {
  18375. params.status = status;
  18376. return formatter(params);
  18377. }
  18378. else if (typeof formatter === 'string') {
  18379. var str = formatTpl(formatter, params);
  18380. // Support 'aaa{@[3]}bbb{@product}ccc'.
  18381. // Do not support '}' in dim name util have to.
  18382. return str.replace(DIMENSION_LABEL_REG, function (origin, dim) {
  18383. var len = dim.length;
  18384. if (dim.charAt(0) === '[' && dim.charAt(len - 1) === ']') {
  18385. dim = +dim.slice(1, len - 1); // Also: '[]' => 0
  18386. }
  18387. return retrieveRawValue(data, dataIndex, dim);
  18388. });
  18389. }
  18390. },
  18391. /**
  18392. * Get raw value in option
  18393. * @param {number} idx
  18394. * @param {string} [dataType]
  18395. * @return {Array|number|string}
  18396. */
  18397. getRawValue: function (idx, dataType) {
  18398. return retrieveRawValue(this.getData(dataType), idx);
  18399. },
  18400. /**
  18401. * Should be implemented.
  18402. * @param {number} dataIndex
  18403. * @param {boolean} [multipleSeries=false]
  18404. * @param {number} [dataType]
  18405. * @return {string} tooltip string
  18406. */
  18407. formatTooltip: function () {
  18408. // Empty function
  18409. }
  18410. };
  18411. /**
  18412. * @param {Object} define
  18413. * @return See the return of `createTask`.
  18414. */
  18415. function createTask(define) {
  18416. return new Task(define);
  18417. }
  18418. /**
  18419. * @constructor
  18420. * @param {Object} define
  18421. * @param {Function} define.reset Custom reset
  18422. * @param {Function} [define.plan] Returns 'reset' indicate reset immediately.
  18423. * @param {Function} [define.count] count is used to determin data task.
  18424. * @param {Function} [define.onDirty] count is used to determin data task.
  18425. */
  18426. function Task(define) {
  18427. define = define || {};
  18428. this._reset = define.reset;
  18429. this._plan = define.plan;
  18430. this._count = define.count;
  18431. this._onDirty = define.onDirty;
  18432. this._dirty = true;
  18433. // Context must be specified implicitly, to
  18434. // avoid miss update context when model changed.
  18435. this.context;
  18436. }
  18437. var taskProto = Task.prototype;
  18438. /**
  18439. * @param {Object} performArgs
  18440. * @param {number} [performArgs.step] Specified step.
  18441. * @param {number} [performArgs.skip] Skip customer perform call.
  18442. */
  18443. taskProto.perform = function (performArgs) {
  18444. var upTask = this._upstream;
  18445. var skip = performArgs && performArgs.skip;
  18446. // TODO some refactor.
  18447. // Pull data. Must pull data each time, because context.data
  18448. // may be updated by Series.setData.
  18449. if (this._dirty && upTask) {
  18450. var context = this.context;
  18451. context.data = context.outputData = upTask.context.outputData;
  18452. }
  18453. if (this.__pipeline) {
  18454. this.__pipeline.currentTask = this;
  18455. }
  18456. var planResult;
  18457. if (this._plan && !skip) {
  18458. planResult = this._plan(this.context);
  18459. }
  18460. var forceFirstProgress;
  18461. if (this._dirty || planResult === 'reset') {
  18462. this._dirty = false;
  18463. forceFirstProgress = reset(this, skip);
  18464. }
  18465. var step = performArgs && performArgs.step;
  18466. if (upTask) {
  18467. if (__DEV__) {
  18468. assert$1(upTask._outputDueEnd != null);
  18469. }
  18470. // ??? FIXME move to schedueler?
  18471. // this._dueEnd = Math.max(upTask._outputDueEnd, this._dueEnd);
  18472. this._dueEnd = upTask._outputDueEnd;
  18473. }
  18474. // DataTask or overallTask
  18475. else {
  18476. if (__DEV__) {
  18477. assert$1(!this._progress || this._count);
  18478. }
  18479. this._dueEnd = this._count ? this._count(this.context) : Infinity;
  18480. }
  18481. // Note: Stubs, that its host overall task let it has progress, has progress.
  18482. // If no progress, pass index from upstream to downstream each time plan called.
  18483. if (this._progress) {
  18484. var start = this._dueIndex;
  18485. var end = Math.min(
  18486. step != null ? this._dueIndex + step : Infinity,
  18487. this._dueEnd
  18488. );
  18489. !skip && (forceFirstProgress || start < end) && (
  18490. this._progress({start: start, end: end}, this.context)
  18491. );
  18492. this._dueIndex = end;
  18493. // If no `outputDueEnd`, assume that output data and
  18494. // input data is the same, so use `dueIndex` as `outputDueEnd`.
  18495. var outputDueEnd = this._settedOutputEnd != null
  18496. ? this._settedOutputEnd : end;
  18497. if (__DEV__) {
  18498. // ??? Can not rollback.
  18499. assert$1(outputDueEnd >= this._outputDueEnd);
  18500. }
  18501. this._outputDueEnd = outputDueEnd;
  18502. }
  18503. else {
  18504. // (1) Some overall task has no progress.
  18505. // (2) Stubs, that its host overall task do not let it has progress, has no progress.
  18506. // This should always be performed so it can be passed to downstream.
  18507. this._dueIndex = this._outputDueEnd = this._settedOutputEnd != null
  18508. ? this._settedOutputEnd : this._dueEnd;
  18509. }
  18510. return this.unfinished();
  18511. };
  18512. taskProto.dirty = function () {
  18513. this._dirty = true;
  18514. this._onDirty && this._onDirty(this.context);
  18515. };
  18516. /**
  18517. * @param {Object} [params]
  18518. */
  18519. function reset(taskIns, skip) {
  18520. taskIns._dueIndex = taskIns._outputDueEnd = taskIns._dueEnd = 0;
  18521. taskIns._settedOutputEnd = null;
  18522. var progress;
  18523. var forceFirstProgress;
  18524. if (!skip && taskIns._reset) {
  18525. progress = taskIns._reset(taskIns.context);
  18526. if (progress && progress.progress) {
  18527. forceFirstProgress = progress.forceFirstProgress;
  18528. progress = progress.progress;
  18529. }
  18530. }
  18531. taskIns._progress = progress;
  18532. var downstream = taskIns._downstream;
  18533. downstream && downstream.dirty();
  18534. return forceFirstProgress;
  18535. }
  18536. /**
  18537. * @return {boolean}
  18538. */
  18539. taskProto.unfinished = function () {
  18540. return this._progress && this._dueIndex < this._dueEnd;
  18541. };
  18542. /**
  18543. * @param {Object} downTask The downstream task.
  18544. * @return {Object} The downstream task.
  18545. */
  18546. taskProto.pipe = function (downTask) {
  18547. if (__DEV__) {
  18548. assert$1(downTask && !downTask._disposed && downTask !== this);
  18549. }
  18550. // If already downstream, do not dirty downTask.
  18551. if (this._downstream !== downTask || this._dirty) {
  18552. this._downstream = downTask;
  18553. downTask._upstream = this;
  18554. downTask.dirty();
  18555. }
  18556. };
  18557. taskProto.dispose = function () {
  18558. if (this._disposed) {
  18559. return;
  18560. }
  18561. this._upstream && (this._upstream._downstream = null);
  18562. this._downstream && (this._downstream._upstream = null);
  18563. this._dirty = false;
  18564. this._disposed = true;
  18565. };
  18566. taskProto.getUpstream = function () {
  18567. return this._upstream;
  18568. };
  18569. taskProto.getDownstream = function () {
  18570. return this._downstream;
  18571. };
  18572. taskProto.setOutputEnd = function (end) {
  18573. // ??? FIXME: check
  18574. // This only happend in dataTask, dataZoom, map, currently.
  18575. // where dataZoom do not set end each time, but only set
  18576. // when reset. So we should record the setted end, in case
  18577. // that the stub of dataZoom perform again and earse the
  18578. // setted end by upstream.
  18579. this._outputDueEnd = this._settedOutputEnd = end;
  18580. // this._outputDueEnd = end;
  18581. };
  18582. ///////////////////////////////////////////////////////////
  18583. // For stream debug (Should be commented out after used!)
  18584. // Usage: printTask(this, 'begin');
  18585. // Usage: printTask(this, null, {someExtraProp});
  18586. // function printTask(task, prefix, extra) {
  18587. // window.ecTaskUID == null && (window.ecTaskUID = 0);
  18588. // task.uidDebug == null && (task.uidDebug = `task_${window.ecTaskUID++}`);
  18589. // task.agent && task.agent.uidDebug == null && (task.agent.uidDebug = `task_${window.ecTaskUID++}`);
  18590. // var props = [];
  18591. // if (task.__pipeline) {
  18592. // var val = `${task.__idxInPipeline}/${task.__pipeline.tail.__idxInPipeline} ${task.agent ? '(stub)' : ''}`;
  18593. // props.push({text: 'idx', value: val});
  18594. // } else {
  18595. // var stubCount = 0;
  18596. // task.agentStubMap.each(() => stubCount++);
  18597. // props.push({text: 'idx', value: `overall (stubs: ${stubCount})`});
  18598. // }
  18599. // props.push({text: 'uid', value: task.uidDebug});
  18600. // if (task.__pipeline) {
  18601. // props.push({text: 'pid', value: task.__pipeline.id});
  18602. // task.agent && props.push(
  18603. // {text: 'stubFor', value: task.agent.uidDebug}
  18604. // );
  18605. // }
  18606. // props.push(
  18607. // {text: 'dirty', value: task._dirty},
  18608. // {text: 'dueIndex', value: task._dueIndex},
  18609. // {text: 'dueEnd', value: task._dueEnd},
  18610. // {text: 'outputDueEnd', value: task._outputDueEnd}
  18611. // );
  18612. // if (extra) {
  18613. // Object.keys(extra).forEach(key => {
  18614. // props.push({text: key, value: extra[key]});
  18615. // });
  18616. // }
  18617. // var args = ['color: blue'];
  18618. // var msg = `%c[${prefix || 'T'}] %c` + props.map(item => (
  18619. // args.push('color: black', 'color: red'),
  18620. // `${item.text}: %c${item.value}`
  18621. // )).join('%c, ');
  18622. // console.log.apply(console, [msg].concat(args));
  18623. // // console.log(this);
  18624. // }
  18625. var inner$4 = makeInner();
  18626. var SeriesModel = ComponentModel.extend({
  18627. type: 'series.__base__',
  18628. /**
  18629. * @readOnly
  18630. */
  18631. seriesIndex: 0,
  18632. // coodinateSystem will be injected in the echarts/CoordinateSystem
  18633. coordinateSystem: null,
  18634. /**
  18635. * @type {Object}
  18636. * @protected
  18637. */
  18638. defaultOption: null,
  18639. /**
  18640. * Data provided for legend
  18641. * @type {Function}
  18642. */
  18643. // PENDING
  18644. legendDataProvider: null,
  18645. /**
  18646. * Access path of color for visual
  18647. */
  18648. visualColorAccessPath: 'itemStyle.color',
  18649. /**
  18650. * Support merge layout params.
  18651. * Only support 'box' now (left/right/top/bottom/width/height).
  18652. * @type {string|Object} Object can be {ignoreSize: true}
  18653. * @readOnly
  18654. */
  18655. layoutMode: null,
  18656. init: function (option, parentModel, ecModel, extraOpt) {
  18657. /**
  18658. * @type {number}
  18659. * @readOnly
  18660. */
  18661. this.seriesIndex = this.componentIndex;
  18662. this.dataTask = createTask({
  18663. count: dataTaskCount,
  18664. reset: dataTaskReset
  18665. });
  18666. this.dataTask.context = {model: this};
  18667. this.mergeDefaultAndTheme(option, ecModel);
  18668. prepareSource(this);
  18669. var data = this.getInitialData(option, ecModel);
  18670. wrapData(data, this);
  18671. this.dataTask.context.data = data;
  18672. if (__DEV__) {
  18673. assert$1(data, 'getInitialData returned invalid data.');
  18674. }
  18675. /**
  18676. * @type {module:echarts/data/List|module:echarts/data/Tree|module:echarts/data/Graph}
  18677. * @private
  18678. */
  18679. inner$4(this).dataBeforeProcessed = data;
  18680. // If we reverse the order (make data firstly, and then make
  18681. // dataBeforeProcessed by cloneShallow), cloneShallow will
  18682. // cause data.graph.data !== data when using
  18683. // module:echarts/data/Graph or module:echarts/data/Tree.
  18684. // See module:echarts/data/helper/linkList
  18685. // Theoretically, it is unreasonable to call `seriesModel.getData()` in the model
  18686. // init or merge stage, because the data can be restored. So we do not `restoreData`
  18687. // and `setData` here, which forbids calling `seriesModel.getData()` in this stage.
  18688. // Call `seriesModel.getRawData()` instead.
  18689. // this.restoreData();
  18690. autoSeriesName(this);
  18691. },
  18692. /**
  18693. * Util for merge default and theme to option
  18694. * @param {Object} option
  18695. * @param {module:echarts/model/Global} ecModel
  18696. */
  18697. mergeDefaultAndTheme: function (option, ecModel) {
  18698. var layoutMode = this.layoutMode;
  18699. var inputPositionParams = layoutMode
  18700. ? getLayoutParams(option) : {};
  18701. // Backward compat: using subType on theme.
  18702. // But if name duplicate between series subType
  18703. // (for example: parallel) add component mainType,
  18704. // add suffix 'Series'.
  18705. var themeSubType = this.subType;
  18706. if (ComponentModel.hasClass(themeSubType)) {
  18707. themeSubType += 'Series';
  18708. }
  18709. merge(
  18710. option,
  18711. ecModel.getTheme().get(this.subType)
  18712. );
  18713. merge(option, this.getDefaultOption());
  18714. // Default label emphasis `show`
  18715. defaultEmphasis(option, 'label', ['show']);
  18716. this.fillDataTextStyle(option.data);
  18717. if (layoutMode) {
  18718. mergeLayoutParam(option, inputPositionParams, layoutMode);
  18719. }
  18720. },
  18721. mergeOption: function (newSeriesOption, ecModel) {
  18722. // this.settingTask.dirty();
  18723. newSeriesOption = merge(this.option, newSeriesOption, true);
  18724. this.fillDataTextStyle(newSeriesOption.data);
  18725. var layoutMode = this.layoutMode;
  18726. if (layoutMode) {
  18727. mergeLayoutParam(this.option, newSeriesOption, layoutMode);
  18728. }
  18729. prepareSource(this);
  18730. var data = this.getInitialData(newSeriesOption, ecModel);
  18731. wrapData(data, this);
  18732. this.dataTask.dirty();
  18733. this.dataTask.context.data = data;
  18734. inner$4(this).dataBeforeProcessed = data;
  18735. autoSeriesName(this);
  18736. },
  18737. fillDataTextStyle: function (data) {
  18738. // Default data label emphasis `show`
  18739. // FIXME Tree structure data ?
  18740. // FIXME Performance ?
  18741. if (data) {
  18742. var props = ['show'];
  18743. for (var i = 0; i < data.length; i++) {
  18744. if (data[i] && data[i].label) {
  18745. defaultEmphasis(data[i], 'label', props);
  18746. }
  18747. }
  18748. }
  18749. },
  18750. /**
  18751. * Init a data structure from data related option in series
  18752. * Must be overwritten
  18753. */
  18754. getInitialData: function () {},
  18755. /**
  18756. * Append data to list
  18757. * @param {Object} params
  18758. * @param {Array|TypedArray} params.data
  18759. */
  18760. appendData: function (params) {
  18761. // FIXME ???
  18762. // (1) If data from dataset, forbidden append.
  18763. // (2) support append data of dataset.
  18764. var data = this.getRawData();
  18765. data.appendData(params.data);
  18766. },
  18767. /**
  18768. * Consider some method like `filter`, `map` need make new data,
  18769. * We should make sure that `seriesModel.getData()` get correct
  18770. * data in the stream procedure. So we fetch data from upstream
  18771. * each time `task.perform` called.
  18772. * @param {string} [dataType]
  18773. * @return {module:echarts/data/List}
  18774. */
  18775. getData: function (dataType) {
  18776. var task = getCurrentTask(this);
  18777. if (task) {
  18778. var data = task.context.data;
  18779. return dataType == null ? data : data.getLinkedData(dataType);
  18780. }
  18781. else {
  18782. // When series is not alive (that may happen when click toolbox
  18783. // restore or setOption with not merge mode), series data may
  18784. // be still need to judge animation or something when graphic
  18785. // elements want to know whether fade out.
  18786. return inner$4(this).data;
  18787. }
  18788. },
  18789. /**
  18790. * @param {module:echarts/data/List} data
  18791. */
  18792. setData: function (data) {
  18793. var task = getCurrentTask(this);
  18794. if (task) {
  18795. var context = task.context;
  18796. // Consider case: filter, data sample.
  18797. if (context.data !== data && task.isOverallFilter) {
  18798. task.setOutputEnd(data.count());
  18799. }
  18800. context.outputData = data;
  18801. // Caution: setData should update context.data,
  18802. // Because getData may be called multiply in a
  18803. // single stage and expect to get the data just
  18804. // set. (For example, AxisProxy, x y both call
  18805. // getData and setDate sequentially).
  18806. // So the context.data should be fetched from
  18807. // upstream each time when a stage starts to be
  18808. // performed.
  18809. if (task !== this.dataTask) {
  18810. context.data = data;
  18811. }
  18812. }
  18813. inner$4(this).data = data;
  18814. },
  18815. /**
  18816. * @see {module:echarts/data/helper/sourceHelper#getSource}
  18817. * @return {module:echarts/data/Source} source
  18818. */
  18819. getSource: function () {
  18820. return getSource(this);
  18821. },
  18822. /**
  18823. * Get data before processed
  18824. * @return {module:echarts/data/List}
  18825. */
  18826. getRawData: function () {
  18827. return inner$4(this).dataBeforeProcessed;
  18828. },
  18829. /**
  18830. * Get base axis if has coordinate system and has axis.
  18831. * By default use coordSys.getBaseAxis();
  18832. * Can be overrided for some chart.
  18833. * @return {type} description
  18834. */
  18835. getBaseAxis: function () {
  18836. var coordSys = this.coordinateSystem;
  18837. return coordSys && coordSys.getBaseAxis && coordSys.getBaseAxis();
  18838. },
  18839. // FIXME
  18840. /**
  18841. * Default tooltip formatter
  18842. *
  18843. * @param {number} dataIndex
  18844. * @param {boolean} [multipleSeries=false]
  18845. * @param {number} [dataType]
  18846. */
  18847. formatTooltip: function (dataIndex, multipleSeries, dataType) {
  18848. function formatArrayValue(value) {
  18849. // ??? TODO refactor these logic.
  18850. // check: category-no-encode-has-axis-data in dataset.html
  18851. var vertially = reduce(value, function (vertially, val, idx) {
  18852. var dimItem = data.getDimensionInfo(idx);
  18853. return vertially |= dimItem && dimItem.tooltip !== false && dimItem.displayName != null;
  18854. }, 0);
  18855. var result = [];
  18856. tooltipDims.length
  18857. ? each$1(tooltipDims, function (dim) {
  18858. setEachItem(retrieveRawValue(data, dataIndex, dim), dim);
  18859. })
  18860. // By default, all dims is used on tooltip.
  18861. : each$1(value, setEachItem);
  18862. function setEachItem(val, dim) {
  18863. var dimInfo = data.getDimensionInfo(dim);
  18864. // If `dimInfo.tooltip` is not set, show tooltip.
  18865. if (!dimInfo || dimInfo.otherDims.tooltip === false) {
  18866. return;
  18867. }
  18868. var dimType = dimInfo.type;
  18869. var dimHead = getTooltipMarker({color: color, type: 'subItem'});
  18870. var valStr = (vertially
  18871. ? dimHead + encodeHTML(dimInfo.displayName || '-') + ': '
  18872. : ''
  18873. )
  18874. // FIXME should not format time for raw data?
  18875. + encodeHTML(dimType === 'ordinal'
  18876. ? val + ''
  18877. : dimType === 'time'
  18878. ? (multipleSeries ? '' : formatTime('yyyy/MM/dd hh:mm:ss', val))
  18879. : addCommas(val)
  18880. );
  18881. valStr && result.push(valStr);
  18882. }
  18883. return (vertially ? '<br/>' : '') + result.join(vertially ? '<br/>' : ', ');
  18884. }
  18885. function formatSingleValue(val) {
  18886. return encodeHTML(addCommas(val));
  18887. }
  18888. var data = this.getData();
  18889. var tooltipDims = data.mapDimension('defaultedTooltip', true);
  18890. var tooltipDimLen = tooltipDims.length;
  18891. var value = this.getRawValue(dataIndex);
  18892. var isValueArr = isArray(value);
  18893. var color = data.getItemVisual(dataIndex, 'color');
  18894. if (isObject$1(color) && color.colorStops) {
  18895. color = (color.colorStops[0] || {}).color;
  18896. }
  18897. color = color || 'transparent';
  18898. // Complicated rule for pretty tooltip.
  18899. var formattedValue = (tooltipDimLen > 1 || (isValueArr && !tooltipDimLen))
  18900. ? formatArrayValue(value)
  18901. : tooltipDimLen
  18902. ? formatSingleValue(retrieveRawValue(data, dataIndex, tooltipDims[0]))
  18903. : formatSingleValue(isValueArr ? value[0] : value);
  18904. var colorEl = getTooltipMarker(color);
  18905. var name = data.getName(dataIndex);
  18906. var seriesName = this.name;
  18907. if (!isNameSpecified(this)) {
  18908. seriesName = '';
  18909. }
  18910. seriesName = seriesName
  18911. ? encodeHTML(seriesName) + (!multipleSeries ? '<br/>' : ': ')
  18912. : '';
  18913. return !multipleSeries
  18914. ? seriesName + colorEl
  18915. + (name
  18916. ? encodeHTML(name) + ': ' + formattedValue
  18917. : formattedValue
  18918. )
  18919. : colorEl + seriesName + formattedValue;
  18920. },
  18921. /**
  18922. * @return {boolean}
  18923. */
  18924. isAnimationEnabled: function () {
  18925. if (env$1.node) {
  18926. return false;
  18927. }
  18928. var animationEnabled = this.getShallow('animation');
  18929. if (animationEnabled) {
  18930. if (this.getData().count() > this.getShallow('animationThreshold')) {
  18931. animationEnabled = false;
  18932. }
  18933. }
  18934. return animationEnabled;
  18935. },
  18936. restoreData: function () {
  18937. this.dataTask.dirty();
  18938. },
  18939. getColorFromPalette: function (name, scope, requestColorNum) {
  18940. var ecModel = this.ecModel;
  18941. // PENDING
  18942. var color = colorPaletteMixin.getColorFromPalette.call(this, name, scope, requestColorNum);
  18943. if (!color) {
  18944. color = ecModel.getColorFromPalette(name, scope, requestColorNum);
  18945. }
  18946. return color;
  18947. },
  18948. /**
  18949. * Use `data.mapDimension(coordDim, true)` instead.
  18950. * @deprecated
  18951. */
  18952. coordDimToDataDim: function (coordDim) {
  18953. return this.getRawData().mapDimension(coordDim, true);
  18954. },
  18955. /**
  18956. * Get progressive rendering count each step
  18957. * @return {number}
  18958. */
  18959. getProgressive: function () {
  18960. return this.get('progressive');
  18961. },
  18962. /**
  18963. * Get progressive rendering count each step
  18964. * @return {number}
  18965. */
  18966. getProgressiveThreshold: function () {
  18967. return this.get('progressiveThreshold');
  18968. },
  18969. /**
  18970. * Get data indices for show tooltip content. See tooltip.
  18971. * @abstract
  18972. * @param {Array.<string>|string} dim
  18973. * @param {Array.<number>} value
  18974. * @param {module:echarts/coord/single/SingleAxis} baseAxis
  18975. * @return {Object} {dataIndices, nestestValue}.
  18976. */
  18977. getAxisTooltipData: null,
  18978. /**
  18979. * See tooltip.
  18980. * @abstract
  18981. * @param {number} dataIndex
  18982. * @return {Array.<number>} Point of tooltip. null/undefined can be returned.
  18983. */
  18984. getTooltipPosition: null,
  18985. /**
  18986. * @see {module:echarts/stream/Scheduler}
  18987. */
  18988. pipeTask: null,
  18989. /**
  18990. * Convinient for override in extended class.
  18991. * @protected
  18992. * @type {Function}
  18993. */
  18994. preventIncremental: null,
  18995. /**
  18996. * @public
  18997. * @readOnly
  18998. * @type {Object}
  18999. */
  19000. pipelineContext: null
  19001. });
  19002. mixin(SeriesModel, dataFormatMixin);
  19003. mixin(SeriesModel, colorPaletteMixin);
  19004. /**
  19005. * MUST be called after `prepareSource` called
  19006. * Here we need to make auto series, especially for auto legend. But we
  19007. * do not modify series.name in option to avoid side effects.
  19008. */
  19009. function autoSeriesName(seriesModel) {
  19010. // User specified name has higher priority, otherwise it may cause
  19011. // series can not be queried unexpectedly.
  19012. var name = seriesModel.name;
  19013. if (!isNameSpecified(seriesModel)) {
  19014. seriesModel.name = getSeriesAutoName(seriesModel) || name;
  19015. }
  19016. }
  19017. function getSeriesAutoName(seriesModel) {
  19018. var data = seriesModel.getRawData();
  19019. var dataDims = data.mapDimension('seriesName', true);
  19020. var nameArr = [];
  19021. each$1(dataDims, function (dataDim) {
  19022. var dimInfo = data.getDimensionInfo(dataDim);
  19023. dimInfo.displayName && nameArr.push(dimInfo.displayName);
  19024. });
  19025. return nameArr.join(' ');
  19026. }
  19027. function dataTaskCount(context) {
  19028. return context.model.getRawData().count();
  19029. }
  19030. function dataTaskReset(context) {
  19031. var seriesModel = context.model;
  19032. seriesModel.setData(seriesModel.getRawData().cloneShallow());
  19033. return dataTaskProgress;
  19034. }
  19035. function dataTaskProgress(param, context) {
  19036. // Avoid repead cloneShallow when data just created in reset.
  19037. if (param.end > context.outputData.count()) {
  19038. context.model.getRawData().cloneShallow(context.outputData);
  19039. }
  19040. }
  19041. // TODO refactor
  19042. function wrapData(data, seriesModel) {
  19043. each$1(data.CHANGABLE_METHODS, function (methodName) {
  19044. data.wrapMethod(methodName, curry(onDataSelfChange, seriesModel));
  19045. });
  19046. }
  19047. function onDataSelfChange(seriesModel) {
  19048. var task = getCurrentTask(seriesModel);
  19049. if (task) {
  19050. // Consider case: filter, selectRange
  19051. task.setOutputEnd(this.count());
  19052. }
  19053. }
  19054. function getCurrentTask(seriesModel) {
  19055. var scheduler = (seriesModel.ecModel || {}).scheduler;
  19056. var pipeline = scheduler && scheduler.getPipeline(seriesModel.uid);
  19057. if (pipeline) {
  19058. // When pipline finished, the currrentTask keep the last
  19059. // task (renderTask).
  19060. var task = pipeline.currentTask;
  19061. if (task) {
  19062. var agentStubMap = task.agentStubMap;
  19063. if (agentStubMap) {
  19064. task = agentStubMap.get(seriesModel.uid);
  19065. }
  19066. }
  19067. return task;
  19068. }
  19069. }
  19070. var Component = function () {
  19071. /**
  19072. * @type {module:zrender/container/Group}
  19073. * @readOnly
  19074. */
  19075. this.group = new Group();
  19076. /**
  19077. * @type {string}
  19078. * @readOnly
  19079. */
  19080. this.uid = getUID('viewComponent');
  19081. };
  19082. Component.prototype = {
  19083. constructor: Component,
  19084. init: function (ecModel, api) {},
  19085. render: function (componentModel, ecModel, api, payload) {},
  19086. dispose: function () {}
  19087. };
  19088. var componentProto = Component.prototype;
  19089. componentProto.updateView
  19090. = componentProto.updateLayout
  19091. = componentProto.updateVisual
  19092. = function (seriesModel, ecModel, api, payload) {
  19093. // Do nothing;
  19094. };
  19095. // Enable Component.extend.
  19096. enableClassExtend(Component);
  19097. // Enable capability of registerClass, getClass, hasClass, registerSubTypeDefaulter and so on.
  19098. enableClassManagement(Component, {registerWhenExtend: true});
  19099. /**
  19100. * @return {string} If large mode changed, return string 'reset';
  19101. */
  19102. var createRenderPlanner = function () {
  19103. var inner = makeInner();
  19104. return function (seriesModel) {
  19105. var fields = inner(seriesModel);
  19106. var pipelineContext = seriesModel.pipelineContext;
  19107. var originalLarge = fields.large;
  19108. var originalProgressive = fields.canProgressiveRender;
  19109. var large = fields.large = pipelineContext.large;
  19110. var progressive = fields.canProgressiveRender = pipelineContext.canProgressiveRender;
  19111. return !!((originalLarge ^ large) || (originalProgressive ^ progressive)) && 'reset';
  19112. };
  19113. };
  19114. var inner$5 = makeInner();
  19115. var renderPlanner = createRenderPlanner();
  19116. function Chart() {
  19117. /**
  19118. * @type {module:zrender/container/Group}
  19119. * @readOnly
  19120. */
  19121. this.group = new Group();
  19122. /**
  19123. * @type {string}
  19124. * @readOnly
  19125. */
  19126. this.uid = getUID('viewChart');
  19127. this.renderTask = createTask({
  19128. plan: renderTaskPlan,
  19129. reset: renderTaskReset
  19130. });
  19131. this.renderTask.context = {view: this};
  19132. }
  19133. Chart.prototype = {
  19134. type: 'chart',
  19135. /**
  19136. * Init the chart.
  19137. * @param {module:echarts/model/Global} ecModel
  19138. * @param {module:echarts/ExtensionAPI} api
  19139. */
  19140. init: function (ecModel, api) {},
  19141. /**
  19142. * Render the chart.
  19143. * @param {module:echarts/model/Series} seriesModel
  19144. * @param {module:echarts/model/Global} ecModel
  19145. * @param {module:echarts/ExtensionAPI} api
  19146. * @param {Object} payload
  19147. */
  19148. render: function (seriesModel, ecModel, api, payload) {},
  19149. /**
  19150. * Highlight series or specified data item.
  19151. * @param {module:echarts/model/Series} seriesModel
  19152. * @param {module:echarts/model/Global} ecModel
  19153. * @param {module:echarts/ExtensionAPI} api
  19154. * @param {Object} payload
  19155. */
  19156. highlight: function (seriesModel, ecModel, api, payload) {
  19157. toggleHighlight(seriesModel.getData(), payload, 'emphasis');
  19158. },
  19159. /**
  19160. * Downplay series or specified data item.
  19161. * @param {module:echarts/model/Series} seriesModel
  19162. * @param {module:echarts/model/Global} ecModel
  19163. * @param {module:echarts/ExtensionAPI} api
  19164. * @param {Object} payload
  19165. */
  19166. downplay: function (seriesModel, ecModel, api, payload) {
  19167. toggleHighlight(seriesModel.getData(), payload, 'normal');
  19168. },
  19169. /**
  19170. * Remove self.
  19171. * @param {module:echarts/model/Global} ecModel
  19172. * @param {module:echarts/ExtensionAPI} api
  19173. */
  19174. remove: function (ecModel, api) {
  19175. this.group.removeAll();
  19176. },
  19177. /**
  19178. * Dispose self.
  19179. * @param {module:echarts/model/Global} ecModel
  19180. * @param {module:echarts/ExtensionAPI} api
  19181. */
  19182. dispose: function () {},
  19183. /**
  19184. * Rendering preparation in progressive mode.
  19185. * @param {module:echarts/model/Series} seriesModel
  19186. * @param {module:echarts/model/Global} ecModel
  19187. * @param {module:echarts/ExtensionAPI} api
  19188. * @param {Object} payload
  19189. */
  19190. incrementalPrepareRender: null,
  19191. /**
  19192. * Render in progressive mode.
  19193. * @param {module:echarts/model/Series} seriesModel
  19194. * @param {module:echarts/model/Global} ecModel
  19195. * @param {module:echarts/ExtensionAPI} api
  19196. * @param {Object} payload
  19197. */
  19198. incrementalRender: null,
  19199. /**
  19200. * Update transform directly.
  19201. * @param {module:echarts/model/Series} seriesModel
  19202. * @param {module:echarts/model/Global} ecModel
  19203. * @param {module:echarts/ExtensionAPI} api
  19204. * @param {Object} payload
  19205. * @return {Object} {update: true}
  19206. */
  19207. updateTransform: null
  19208. /**
  19209. * The view contains the given point.
  19210. * @interface
  19211. * @param {Array.<number>} point
  19212. * @return {boolean}
  19213. */
  19214. // containPoint: function () {}
  19215. };
  19216. var chartProto = Chart.prototype;
  19217. chartProto.updateView
  19218. = chartProto.updateLayout
  19219. = chartProto.updateVisual
  19220. = function (seriesModel, ecModel, api, payload) {
  19221. this.render(seriesModel, ecModel, api, payload);
  19222. };
  19223. /**
  19224. * Set state of single element
  19225. * @param {module:zrender/Element} el
  19226. * @param {string} state
  19227. */
  19228. function elSetState(el, state) {
  19229. if (el) {
  19230. el.trigger(state);
  19231. if (el.type === 'group') {
  19232. for (var i = 0; i < el.childCount(); i++) {
  19233. elSetState(el.childAt(i), state);
  19234. }
  19235. }
  19236. }
  19237. }
  19238. /**
  19239. * @param {module:echarts/data/List} data
  19240. * @param {Object} payload
  19241. * @param {string} state 'normal'|'emphasis'
  19242. */
  19243. function toggleHighlight(data, payload, state) {
  19244. var dataIndex = queryDataIndex(data, payload);
  19245. if (dataIndex != null) {
  19246. each$1(normalizeToArray(dataIndex), function (dataIdx) {
  19247. elSetState(data.getItemGraphicEl(dataIdx), state);
  19248. });
  19249. }
  19250. else {
  19251. data.eachItemGraphicEl(function (el) {
  19252. elSetState(el, state);
  19253. });
  19254. }
  19255. }
  19256. // Enable Chart.extend.
  19257. enableClassExtend(Chart, ['dispose']);
  19258. // Add capability of registerClass, getClass, hasClass, registerSubTypeDefaulter and so on.
  19259. enableClassManagement(Chart, {registerWhenExtend: true});
  19260. Chart.markUpdateMethod = function (payload, methodName) {
  19261. inner$5(payload).updateMethod = methodName;
  19262. };
  19263. function renderTaskPlan(context) {
  19264. return renderPlanner(context.model);
  19265. }
  19266. function renderTaskReset(context) {
  19267. var seriesModel = context.model;
  19268. var ecModel = context.ecModel;
  19269. var api = context.api;
  19270. var payload = context.payload;
  19271. // ???! remove updateView updateVisual
  19272. var canProgressiveRender = seriesModel.pipelineContext.canProgressiveRender;
  19273. var view = context.view;
  19274. var updateMethod = payload && inner$5(payload).updateMethod;
  19275. var methodName = canProgressiveRender
  19276. ? 'incrementalPrepareRender'
  19277. : (updateMethod && view[updateMethod])
  19278. ? updateMethod
  19279. // `appendData` is also supported when data amount
  19280. // is less than progressive threshold.
  19281. : 'render';
  19282. if (methodName !== 'render') {
  19283. view[methodName](seriesModel, ecModel, api, payload);
  19284. }
  19285. return progressMethodMap[methodName];
  19286. }
  19287. var progressMethodMap = {
  19288. incrementalPrepareRender: {
  19289. progress: function (params, context) {
  19290. context.view.incrementalRender(
  19291. params, context.model, context.ecModel, context.api, context.payload
  19292. );
  19293. }
  19294. },
  19295. render: {
  19296. // Put view.render in `progress` to support appendData. But in this case
  19297. // view.render should not be called in reset, otherwise it will be called
  19298. // twise. Use `forceFirstProgress` to make sure that view.render is called
  19299. // in any cases.
  19300. forceFirstProgress: true,
  19301. progress: function (params, context) {
  19302. context.view.render(
  19303. context.model, context.ecModel, context.api, context.payload
  19304. );
  19305. }
  19306. }
  19307. };
  19308. /**
  19309. * @public
  19310. * @param {(Function)} fn
  19311. * @param {number} [delay=0] Unit: ms.
  19312. * @param {boolean} [debounce=false]
  19313. * true: If call interval less than `delay`, only the last call works.
  19314. * false: If call interval less than `delay, call works on fixed rate.
  19315. * @return {(Function)} throttled fn.
  19316. */
  19317. function throttle(fn, delay, debounce) {
  19318. var currCall;
  19319. var lastCall = 0;
  19320. var lastExec = 0;
  19321. var timer = null;
  19322. var diff;
  19323. var scope;
  19324. var args;
  19325. var debounceNextCall;
  19326. delay = delay || 0;
  19327. function exec() {
  19328. lastExec = (new Date()).getTime();
  19329. timer = null;
  19330. fn.apply(scope, args || []);
  19331. }
  19332. var cb = function () {
  19333. currCall = (new Date()).getTime();
  19334. scope = this;
  19335. args = arguments;
  19336. var thisDelay = debounceNextCall || delay;
  19337. var thisDebounce = debounceNextCall || debounce;
  19338. debounceNextCall = null;
  19339. diff = currCall - (thisDebounce ? lastCall : lastExec) - thisDelay;
  19340. clearTimeout(timer);
  19341. if (thisDebounce) {
  19342. timer = setTimeout(exec, thisDelay);
  19343. }
  19344. else {
  19345. if (diff >= 0) {
  19346. exec();
  19347. }
  19348. else {
  19349. timer = setTimeout(exec, -diff);
  19350. }
  19351. }
  19352. lastCall = currCall;
  19353. };
  19354. /**
  19355. * Clear throttle.
  19356. * @public
  19357. */
  19358. cb.clear = function () {
  19359. if (timer) {
  19360. clearTimeout(timer);
  19361. timer = null;
  19362. }
  19363. };
  19364. /**
  19365. * Enable debounce once.
  19366. */
  19367. cb.debounceNextCall = function (debounceDelay) {
  19368. debounceNextCall = debounceDelay;
  19369. };
  19370. return cb;
  19371. }
  19372. /**
  19373. * Create throttle method or update throttle rate.
  19374. *
  19375. * @example
  19376. * ComponentView.prototype.render = function () {
  19377. * ...
  19378. * throttle.createOrUpdate(
  19379. * this,
  19380. * '_dispatchAction',
  19381. * this.model.get('throttle'),
  19382. * 'fixRate'
  19383. * );
  19384. * };
  19385. * ComponentView.prototype.remove = function () {
  19386. * throttle.clear(this, '_dispatchAction');
  19387. * };
  19388. * ComponentView.prototype.dispose = function () {
  19389. * throttle.clear(this, '_dispatchAction');
  19390. * };
  19391. *
  19392. * @public
  19393. * @param {Object} obj
  19394. * @param {string} fnAttr
  19395. * @param {number} [rate]
  19396. * @param {string} [throttleType='fixRate'] 'fixRate' or 'debounce'
  19397. * @return {Function} throttled function.
  19398. */
  19399. /**
  19400. * Clear throttle. Example see throttle.createOrUpdate.
  19401. *
  19402. * @public
  19403. * @param {Object} obj
  19404. * @param {string} fnAttr
  19405. */
  19406. var seriesColor = {
  19407. createOnAllSeries: true,
  19408. performRawSeries: true,
  19409. reset: function (seriesModel, ecModel) {
  19410. var data = seriesModel.getData();
  19411. var colorAccessPath = (seriesModel.visualColorAccessPath || 'itemStyle.color').split('.');
  19412. var color = seriesModel.get(colorAccessPath) // Set in itemStyle
  19413. || seriesModel.getColorFromPalette(
  19414. // TODO series count changed.
  19415. seriesModel.name, null, ecModel.getSeriesCount()
  19416. ); // Default color
  19417. // FIXME Set color function or use the platte color
  19418. data.setVisual('color', color);
  19419. // Only visible series has each data be visual encoded
  19420. if (!ecModel.isSeriesFiltered(seriesModel)) {
  19421. if (typeof color === 'function' && !(color instanceof Gradient)) {
  19422. data.each(function (idx) {
  19423. data.setItemVisual(
  19424. idx, 'color', color(seriesModel.getDataParams(idx))
  19425. );
  19426. });
  19427. }
  19428. // itemStyle in each data item
  19429. var dataEach = function (data, idx) {
  19430. var itemModel = data.getItemModel(idx);
  19431. var color = itemModel.get(colorAccessPath, true);
  19432. if (color != null) {
  19433. data.setItemVisual(idx, 'color', color);
  19434. }
  19435. };
  19436. return { dataEach: data.hasItemOption ? dataEach : null };
  19437. }
  19438. }
  19439. };
  19440. var lang = {
  19441. toolbox: {
  19442. brush: {
  19443. title: {
  19444. rect: '矩形选择',
  19445. polygon: '圈选',
  19446. lineX: '横向选择',
  19447. lineY: '纵向选择',
  19448. keep: '保持选择',
  19449. clear: '清除选择'
  19450. }
  19451. },
  19452. dataView: {
  19453. title: '数据视图',
  19454. lang: ['数据视图', '关闭', '刷新']
  19455. },
  19456. dataZoom: {
  19457. title: {
  19458. zoom: '区域缩放',
  19459. back: '区域缩放还原'
  19460. }
  19461. },
  19462. magicType: {
  19463. title: {
  19464. line: '切换为折线图',
  19465. bar: '切换为柱状图',
  19466. stack: '切换为堆叠',
  19467. tiled: '切换为平铺'
  19468. }
  19469. },
  19470. restore: {
  19471. title: '还原'
  19472. },
  19473. saveAsImage: {
  19474. title: '保存为图片',
  19475. lang: ['右键另存为图片']
  19476. }
  19477. },
  19478. series: {
  19479. typeNames: {
  19480. pie: '饼图',
  19481. bar: '柱状图',
  19482. line: '折线图',
  19483. scatter: '散点图',
  19484. effectScatter: '涟漪散点图',
  19485. radar: '雷达图',
  19486. tree: '树图',
  19487. treemap: '矩形树图',
  19488. boxplot: '箱型图',
  19489. candlestick: 'K线图',
  19490. k: 'K线图',
  19491. heatmap: '热力图',
  19492. map: '地图',
  19493. parallel: '平行坐标图',
  19494. lines: '线图',
  19495. graph: '关系图',
  19496. sankey: '桑基图',
  19497. funnel: '漏斗图',
  19498. gauge: '仪表盘图',
  19499. pictorialBar: '象形柱图',
  19500. themeRiver: '主题河流图',
  19501. sunburst: '旭日图'
  19502. }
  19503. },
  19504. aria: {
  19505. general: {
  19506. withTitle: '这是一个关于“{title}”的图表。',
  19507. withoutTitle: '这是一个图表,'
  19508. },
  19509. series: {
  19510. single: {
  19511. prefix: '',
  19512. withName: '图表类型是{seriesType},表示{seriesName}。',
  19513. withoutName: '图表类型是{seriesType}。'
  19514. },
  19515. multiple: {
  19516. prefix: '它由{seriesCount}个图表系列组成。',
  19517. withName: '第{seriesId}个系列是一个表示{seriesName}的{seriesType},',
  19518. withoutName: '第{seriesId}个系列是一个{seriesType},',
  19519. separator: {
  19520. middle: ';',
  19521. end: '。'
  19522. }
  19523. }
  19524. },
  19525. data: {
  19526. allData: '其数据是——',
  19527. partialData: '其中,前{displayCnt}项是——',
  19528. withName: '{name}的数据是{value}',
  19529. withoutName: '{value}',
  19530. separator: {
  19531. middle: ',',
  19532. end: ''
  19533. }
  19534. }
  19535. }
  19536. };
  19537. var aria = function (dom, ecModel) {
  19538. var ariaModel = ecModel.getModel('aria');
  19539. if (!ariaModel.get('show')) {
  19540. return;
  19541. }
  19542. else if (ariaModel.get('description')) {
  19543. dom.setAttribute('aria-label', ariaModel.get('description'));
  19544. return;
  19545. }
  19546. var seriesCnt = 0;
  19547. ecModel.eachSeries(function (seriesModel, idx) {
  19548. ++seriesCnt;
  19549. }, this);
  19550. var maxDataCnt = ariaModel.get('data.maxCount') || 10;
  19551. var maxSeriesCnt = ariaModel.get('series.maxCount') || 10;
  19552. var displaySeriesCnt = Math.min(seriesCnt, maxSeriesCnt);
  19553. var ariaLabel;
  19554. if (seriesCnt < 1) {
  19555. // No series, no aria label
  19556. return;
  19557. }
  19558. else {
  19559. var title = getTitle();
  19560. if (title) {
  19561. ariaLabel = replace(getConfig('general.withTitle'), {
  19562. title: title
  19563. });
  19564. }
  19565. else {
  19566. ariaLabel = getConfig('general.withoutTitle');
  19567. }
  19568. var seriesLabels = [];
  19569. var prefix = seriesCnt > 1
  19570. ? 'series.multiple.prefix'
  19571. : 'series.single.prefix';
  19572. ariaLabel += replace(getConfig(prefix), { seriesCount: seriesCnt });
  19573. ecModel.eachSeries(function (seriesModel, idx) {
  19574. if (idx < displaySeriesCnt) {
  19575. var seriesLabel;
  19576. var seriesName = seriesModel.get('name');
  19577. var seriesTpl = 'series.'
  19578. + (seriesCnt > 1 ? 'multiple' : 'single') + '.';
  19579. seriesLabel = getConfig(seriesName
  19580. ? seriesTpl + 'withName'
  19581. : seriesTpl + 'withoutName');
  19582. seriesLabel = replace(seriesLabel, {
  19583. seriesId: seriesModel.seriesIndex,
  19584. seriesName: seriesModel.get('name'),
  19585. seriesType: getSeriesTypeName(seriesModel.subType)
  19586. });
  19587. var data = seriesModel.getData();
  19588. window.data = data;
  19589. if (data.count() > maxDataCnt) {
  19590. // Show part of data
  19591. seriesLabel += replace(getConfig('data.partialData'), {
  19592. displayCnt: maxDataCnt
  19593. });
  19594. }
  19595. else {
  19596. seriesLabel += getConfig('data.allData');
  19597. }
  19598. var dataLabels = [];
  19599. for (var i = 0; i < data.count(); i++) {
  19600. if (i < maxDataCnt) {
  19601. var name = data.getName(i);
  19602. var value = retrieveRawValue(data, i);
  19603. dataLabels.push(
  19604. replace(
  19605. name
  19606. ? getConfig('data.withName')
  19607. : getConfig('data.withoutName'),
  19608. {
  19609. name: name,
  19610. value: value
  19611. }
  19612. )
  19613. );
  19614. }
  19615. }
  19616. seriesLabel += dataLabels
  19617. .join(getConfig('data.separator.middle'))
  19618. + getConfig('data.separator.end');
  19619. seriesLabels.push(seriesLabel);
  19620. }
  19621. });
  19622. ariaLabel += seriesLabels
  19623. .join(getConfig('series.multiple.separator.middle'))
  19624. + getConfig('series.multiple.separator.end');
  19625. dom.setAttribute('aria-label', ariaLabel);
  19626. }
  19627. function replace(str, keyValues) {
  19628. if (typeof str !== 'string') {
  19629. return str;
  19630. }
  19631. var result = str;
  19632. each$1(keyValues, function (value, key) {
  19633. result = result.replace(
  19634. new RegExp('\\{\\s*' + key + '\\s*\\}', 'g'),
  19635. value
  19636. );
  19637. });
  19638. return result;
  19639. }
  19640. function getConfig(path) {
  19641. var userConfig = ariaModel.get(path);
  19642. if (userConfig == null) {
  19643. var pathArr = path.split('.');
  19644. var result = lang.aria;
  19645. for (var i = 0; i < pathArr.length; ++i) {
  19646. result = result[pathArr[i]];
  19647. }
  19648. return result;
  19649. }
  19650. else {
  19651. return userConfig;
  19652. }
  19653. }
  19654. function getTitle() {
  19655. var title = ecModel.getModel('title').option;
  19656. if (title && title.length) {
  19657. title = title[0];
  19658. }
  19659. return title && title.text;
  19660. }
  19661. function getSeriesTypeName(type) {
  19662. return lang.series.typeNames[type] || '自定义图';
  19663. }
  19664. };
  19665. var PI$1 = Math.PI;
  19666. /**
  19667. * @param {module:echarts/ExtensionAPI} api
  19668. * @param {Object} [opts]
  19669. * @param {string} [opts.text]
  19670. * @param {string} [opts.color]
  19671. * @param {string} [opts.textColor]
  19672. * @return {module:zrender/Element}
  19673. */
  19674. var loadingDefault = function (api, opts) {
  19675. opts = opts || {};
  19676. defaults(opts, {
  19677. text: 'loading',
  19678. color: '#c23531',
  19679. textColor: '#000',
  19680. maskColor: 'rgba(255, 255, 255, 0.8)',
  19681. zlevel: 0
  19682. });
  19683. var mask = new Rect({
  19684. style: {
  19685. fill: opts.maskColor
  19686. },
  19687. zlevel: opts.zlevel,
  19688. z: 10000
  19689. });
  19690. var arc = new Arc({
  19691. shape: {
  19692. startAngle: -PI$1 / 2,
  19693. endAngle: -PI$1 / 2 + 0.1,
  19694. r: 10
  19695. },
  19696. style: {
  19697. stroke: opts.color,
  19698. lineCap: 'round',
  19699. lineWidth: 5
  19700. },
  19701. zlevel: opts.zlevel,
  19702. z: 10001
  19703. });
  19704. var labelRect = new Rect({
  19705. style: {
  19706. fill: 'none',
  19707. text: opts.text,
  19708. textPosition: 'right',
  19709. textDistance: 10,
  19710. textFill: opts.textColor
  19711. },
  19712. zlevel: opts.zlevel,
  19713. z: 10001
  19714. });
  19715. arc.animateShape(true)
  19716. .when(1000, {
  19717. endAngle: PI$1 * 3 / 2
  19718. })
  19719. .start('circularInOut');
  19720. arc.animateShape(true)
  19721. .when(1000, {
  19722. startAngle: PI$1 * 3 / 2
  19723. })
  19724. .delay(300)
  19725. .start('circularInOut');
  19726. var group = new Group();
  19727. group.add(arc);
  19728. group.add(labelRect);
  19729. group.add(mask);
  19730. // Inject resize
  19731. group.resize = function () {
  19732. var cx = api.getWidth() / 2;
  19733. var cy = api.getHeight() / 2;
  19734. arc.setShape({
  19735. cx: cx,
  19736. cy: cy
  19737. });
  19738. var r = arc.shape.r;
  19739. labelRect.setShape({
  19740. x: cx - r,
  19741. y: cy - r,
  19742. width: r * 2,
  19743. height: r * 2
  19744. });
  19745. mask.setShape({
  19746. x: 0,
  19747. y: 0,
  19748. width: api.getWidth(),
  19749. height: api.getHeight()
  19750. });
  19751. };
  19752. group.resize();
  19753. return group;
  19754. };
  19755. /**
  19756. * @module echarts/stream/Scheduler
  19757. */
  19758. /**
  19759. * @constructor
  19760. */
  19761. function Scheduler(ecInstance, api, dataProcessorHandlers, visualHandlers) {
  19762. // this._pipelineMap = createHashMap();
  19763. this.ecInstance = ecInstance;
  19764. this.api = api;
  19765. this.unfinished;
  19766. // Fix current processors in case that in some rear cases that
  19767. // processors might be registered after echarts instance created.
  19768. // Register processors incrementally for a echarts instance is
  19769. // not supported by this stream architecture.
  19770. this._dataProcessorHandlers = dataProcessorHandlers.slice();
  19771. this._visualHandlers = visualHandlers.slice();
  19772. /**
  19773. * @private
  19774. * @type {
  19775. * [handlerUID: string]: {
  19776. * seriesTaskMap?: {
  19777. * [seriesUID: string]: Task
  19778. * },
  19779. * overallTask?: Task
  19780. * }
  19781. * }
  19782. */
  19783. this._stageTaskMap = createHashMap();
  19784. }
  19785. var proto = Scheduler.prototype;
  19786. // If seriesModel provided, incremental threshold is check by series data.
  19787. proto.getPerformArgs = function (task, isBlock) {
  19788. // For overall task
  19789. if (!task.__pipeline) {
  19790. return;
  19791. }
  19792. var pipeline = this._pipelineMap.get(task.__pipeline.id);
  19793. var pCtx = pipeline.context;
  19794. var incremental = !isBlock
  19795. && pipeline.progressiveEnabled
  19796. && (!pCtx || pCtx.canProgressiveRender)
  19797. && task.__idxInPipeline > pipeline.bockIndex;
  19798. return {step: incremental ? pipeline.step : null};
  19799. };
  19800. proto.getPipeline = function (pipelineId) {
  19801. return this._pipelineMap.get(pipelineId);
  19802. };
  19803. /**
  19804. * Current, progressive rendering starts from visual and layout.
  19805. * Always detect render mode in the same stage, avoiding that incorrect
  19806. * detection caused by data filtering.
  19807. * Caution:
  19808. * `updateStreamModes` use `seriesModel.getData()`.
  19809. */
  19810. proto.updateStreamModes = function (seriesModel, view) {
  19811. var pipeline = this._pipelineMap.get(seriesModel.uid);
  19812. var data = seriesModel.getData();
  19813. var dataLen = data.count();
  19814. // `canProgressiveRender` means that can render progressively in each
  19815. // animation frame. Note that some types of series do not provide
  19816. // `view.incrementalPrepareRender` but support `chart.appendData`. We
  19817. // use the term `incremental` but not `progressive` to describe the
  19818. // case that `chart.appendData`.
  19819. var canProgressiveRender = pipeline.progressiveEnabled
  19820. && view.incrementalPrepareRender
  19821. && dataLen >= pipeline.threshold;
  19822. var large = seriesModel.get('large') && dataLen >= seriesModel.get('largeThreshold');
  19823. seriesModel.pipelineContext = pipeline.context = {
  19824. canProgressiveRender: canProgressiveRender,
  19825. large: large
  19826. };
  19827. };
  19828. proto.restorePipelines = function (ecModel) {
  19829. var scheduler = this;
  19830. var pipelineMap = scheduler._pipelineMap = createHashMap();
  19831. ecModel.eachSeries(function (seriesModel) {
  19832. var progressive = seriesModel.getProgressive();
  19833. var pipelineId = seriesModel.uid;
  19834. pipelineMap.set(pipelineId, {
  19835. id: pipelineId,
  19836. head: null,
  19837. tail: null,
  19838. threshold: seriesModel.getProgressiveThreshold(),
  19839. progressiveEnabled: progressive
  19840. && !(seriesModel.preventIncremental && seriesModel.preventIncremental()),
  19841. bockIndex: -1,
  19842. step: progressive || 700, // ??? Temporarily number
  19843. count: 0
  19844. });
  19845. pipe(scheduler, seriesModel, seriesModel.dataTask);
  19846. });
  19847. };
  19848. proto.prepareStageTasks = function () {
  19849. var stageTaskMap = this._stageTaskMap;
  19850. var ecModel = this.ecInstance.getModel();
  19851. var api = this.api;
  19852. each$1([this._dataProcessorHandlers, this._visualHandlers], function (stageHandlers) {
  19853. each$1(stageHandlers, function (handler) {
  19854. var record = stageTaskMap.get(handler.uid) || stageTaskMap.set(handler.uid, []);
  19855. handler.reset && createSeriesStageTask(this, handler, record, ecModel, api);
  19856. handler.overallReset && createOverallStageTask(this, handler, record, ecModel, api);
  19857. }, this);
  19858. }, this);
  19859. };
  19860. proto.prepareView = function (view, model, ecModel, api) {
  19861. var renderTask = view.renderTask;
  19862. var context = renderTask.context;
  19863. context.model = model;
  19864. context.ecModel = ecModel;
  19865. context.api = api;
  19866. renderTask.__block = !view.incrementalPrepareRender;
  19867. pipe(this, model, renderTask);
  19868. };
  19869. proto.performDataProcessorTasks = function (ecModel, payload) {
  19870. // If we do not use `block` here, it should be considered when to update modes.
  19871. performStageTasks(this, this._dataProcessorHandlers, ecModel, payload, {block: true});
  19872. };
  19873. // opt
  19874. // opt.visualType: 'visual' or 'layout'
  19875. // opt.setDirty
  19876. proto.performVisualTasks = function (ecModel, payload, opt) {
  19877. performStageTasks(this, this._visualHandlers, ecModel, payload, opt);
  19878. };
  19879. function performStageTasks(scheduler, stageHandlers, ecModel, payload, opt) {
  19880. opt = opt || {};
  19881. var unfinished;
  19882. each$1(stageHandlers, function (stageHandler, idx) {
  19883. if (opt.visualType && opt.visualType !== stageHandler.visualType) {
  19884. return;
  19885. }
  19886. var stageHandlerRecord = scheduler._stageTaskMap.get(stageHandler.uid);
  19887. var seriesTaskMap = stageHandlerRecord.seriesTaskMap;
  19888. var overallTask = stageHandlerRecord.overallTask;
  19889. if (overallTask) {
  19890. var overallNeedDirty;
  19891. var agentStubMap = overallTask.agentStubMap;
  19892. agentStubMap.each(function (stub) {
  19893. if (needSetDirty(opt, stub)) {
  19894. stub.dirty();
  19895. overallNeedDirty = true;
  19896. }
  19897. });
  19898. overallNeedDirty && overallTask.dirty();
  19899. updatePayload(overallTask, payload);
  19900. var performArgs = scheduler.getPerformArgs(overallTask, opt.block);
  19901. // Execute stubs firstly, which may set the overall task dirty,
  19902. // then execute the overall task. And stub will call seriesModel.setData,
  19903. // which ensures that in the overallTask seriesModel.getData() will not
  19904. // return incorrect data.
  19905. agentStubMap.each(function (stub) {
  19906. stub.perform(performArgs);
  19907. });
  19908. unfinished |= overallTask.perform(performArgs);
  19909. }
  19910. else if (seriesTaskMap) {
  19911. seriesTaskMap.each(function (task, pipelineId) {
  19912. if (needSetDirty(opt, task)) {
  19913. task.dirty();
  19914. }
  19915. var performArgs = scheduler.getPerformArgs(task, opt.block);
  19916. performArgs.skip = !stageHandler.performRawSeries
  19917. && ecModel.isSeriesFiltered(task.context.model);
  19918. updatePayload(task, payload);
  19919. unfinished |= task.perform(performArgs);
  19920. });
  19921. }
  19922. });
  19923. function needSetDirty(opt, task) {
  19924. return opt.setDirty && (!opt.dirtyMap || opt.dirtyMap.get(task.__pipeline.id));
  19925. }
  19926. scheduler.unfinished |= unfinished;
  19927. }
  19928. proto.performSeriesTasks = function (ecModel) {
  19929. var unfinished;
  19930. ecModel.eachSeries(function (seriesModel) {
  19931. // Progress to the end for dataInit and dataRestore.
  19932. unfinished |= seriesModel.dataTask.perform();
  19933. });
  19934. this.unfinished |= unfinished;
  19935. };
  19936. proto.plan = function () {
  19937. // Travel pipelines, check block.
  19938. this._pipelineMap.each(function (pipeline) {
  19939. var task = pipeline.tail;
  19940. do {
  19941. if (task.__block) {
  19942. pipeline.bockIndex = task.__idxInPipeline;
  19943. break;
  19944. }
  19945. task = task.getUpstream();
  19946. }
  19947. while (task);
  19948. });
  19949. };
  19950. var updatePayload = proto.updatePayload = function (task, payload) {
  19951. payload !== 'remain' && (task.context.payload = payload);
  19952. };
  19953. function createSeriesStageTask(scheduler, stageHandler, stageHandlerRecord, ecModel, api) {
  19954. var seriesTaskMap = stageHandlerRecord.seriesTaskMap
  19955. || (stageHandlerRecord.seriesTaskMap = createHashMap());
  19956. var seriesType = stageHandler.seriesType;
  19957. var getTargetSeries = stageHandler.getTargetSeries;
  19958. // If a stageHandler should cover all series, `createOnAllSeries` should be declared mandatorily,
  19959. // to avoid some typo or abuse. Otherwise if an extension do not specify a `seriesType`,
  19960. // it works but it may cause other irrelevant charts blocked.
  19961. if (stageHandler.createOnAllSeries) {
  19962. ecModel.eachRawSeries(create);
  19963. }
  19964. else if (seriesType) {
  19965. ecModel.eachRawSeriesByType(seriesType, create);
  19966. }
  19967. else if (getTargetSeries) {
  19968. getTargetSeries(ecModel, api).each(create);
  19969. }
  19970. function create(seriesModel) {
  19971. var pipelineId = seriesModel.uid;
  19972. // Init tasks for each seriesModel only once.
  19973. // Reuse original task instance.
  19974. var task = seriesTaskMap.get(pipelineId)
  19975. || seriesTaskMap.set(pipelineId, createTask({
  19976. plan: seriesTaskPlan,
  19977. reset: seriesTaskReset,
  19978. count: seriesTaskCount
  19979. }));
  19980. task.context = {
  19981. model: seriesModel,
  19982. ecModel: ecModel,
  19983. api: api,
  19984. useClearVisual: stageHandler.isVisual && !stageHandler.isLayout,
  19985. plan: stageHandler.plan,
  19986. reset: stageHandler.reset,
  19987. scheduler: scheduler
  19988. };
  19989. pipe(scheduler, seriesModel, task);
  19990. }
  19991. // Clear unused series tasks.
  19992. var pipelineMap = scheduler._pipelineMap;
  19993. seriesTaskMap.each(function (task, pipelineId) {
  19994. if (!pipelineMap.get(pipelineId)) {
  19995. task.dispose();
  19996. seriesTaskMap.removeKey(pipelineId);
  19997. }
  19998. });
  19999. }
  20000. function createOverallStageTask(scheduler, stageHandler, stageHandlerRecord, ecModel, api) {
  20001. var overallTask = stageHandlerRecord.overallTask = stageHandlerRecord.overallTask
  20002. // For overall task, the function only be called on reset stage.
  20003. || createTask({reset: overallTaskReset});
  20004. overallTask.context = {
  20005. ecModel: ecModel,
  20006. api: api,
  20007. overallReset: stageHandler.overallReset,
  20008. scheduler: scheduler
  20009. };
  20010. // Reuse orignal stubs.
  20011. var agentStubMap = overallTask.agentStubMap = overallTask.agentStubMap || createHashMap();
  20012. var seriesType = stageHandler.seriesType;
  20013. var getTargetSeries = stageHandler.getTargetSeries;
  20014. var overallProgress = true;
  20015. var isOverallFilter = stageHandler.isOverallFilter;
  20016. // An overall task with seriesType detected or has `getTargetSeries`, we add
  20017. // stub in each pipelines, it will set the overall task dirty when the pipeline
  20018. // progress. Moreover, to avoid call the overall task each frame (too frequent),
  20019. // we set the pipeline block.
  20020. if (seriesType) {
  20021. ecModel.eachRawSeriesByType(seriesType, createStub);
  20022. }
  20023. else if (getTargetSeries) {
  20024. getTargetSeries(ecModel, api).each(createStub);
  20025. }
  20026. // Otherwise, (usually it is legancy case), the overall task will only be
  20027. // executed when upstream dirty. Otherwise the progressive rendering of all
  20028. // pipelines will be disabled unexpectedly. But it still needs stubs to receive
  20029. // dirty info from upsteam.
  20030. else {
  20031. overallProgress = false;
  20032. each$1(ecModel.getSeries(), createStub);
  20033. }
  20034. function createStub(seriesModel) {
  20035. var pipelineId = seriesModel.uid;
  20036. var stub = agentStubMap.get(pipelineId) || agentStubMap.set(pipelineId, createTask(
  20037. {reset: stubReset, onDirty: stubOnDirty}
  20038. ));
  20039. stub.context = {
  20040. model: seriesModel,
  20041. overallProgress: overallProgress,
  20042. isOverallFilter: isOverallFilter
  20043. };
  20044. stub.agent = overallTask;
  20045. stub.__block = overallProgress;
  20046. pipe(scheduler, seriesModel, stub);
  20047. }
  20048. // Clear unused stubs.
  20049. var pipelineMap = scheduler._pipelineMap;
  20050. agentStubMap.each(function (stub, pipelineId) {
  20051. if (!pipelineMap.get(pipelineId)) {
  20052. stub.dispose();
  20053. agentStubMap.removeKey(pipelineId);
  20054. }
  20055. });
  20056. }
  20057. function overallTaskReset(context) {
  20058. context.overallReset(
  20059. context.ecModel, context.api, context.payload
  20060. );
  20061. }
  20062. function stubReset(context, upstreamContext) {
  20063. return context.overallProgress && stubProgress;
  20064. }
  20065. function stubProgress() {
  20066. this.agent.dirty();
  20067. this.getDownstream().dirty();
  20068. }
  20069. function stubOnDirty() {
  20070. this.agent && this.agent.dirty();
  20071. }
  20072. function seriesTaskPlan(context) {
  20073. return context.plan && context.plan(
  20074. context.model, context.ecModel, context.api, context.payload
  20075. );
  20076. }
  20077. function seriesTaskReset(context) {
  20078. if (context.useClearVisual) {
  20079. context.data.clearAllVisual();
  20080. }
  20081. var resetDefines = context.resetDefines = normalizeToArray(context.reset(
  20082. context.model, context.ecModel, context.api, context.payload
  20083. ));
  20084. if (resetDefines.length) {
  20085. return seriesTaskProgress;
  20086. }
  20087. }
  20088. function seriesTaskProgress(params, context) {
  20089. var data = context.data;
  20090. var resetDefines = context.resetDefines;
  20091. for (var k = 0; k < resetDefines.length; k++) {
  20092. var resetDefine = resetDefines[k];
  20093. if (resetDefine && resetDefine.dataEach) {
  20094. for (var i = params.start; i < params.end; i++) {
  20095. resetDefine.dataEach(data, i);
  20096. }
  20097. }
  20098. else if (resetDefine && resetDefine.progress) {
  20099. resetDefine.progress(params, data);
  20100. }
  20101. }
  20102. }
  20103. function seriesTaskCount(context) {
  20104. return context.data.count();
  20105. }
  20106. function pipe(scheduler, seriesModel, task) {
  20107. var pipelineId = seriesModel.uid;
  20108. var pipeline = scheduler._pipelineMap.get(pipelineId);
  20109. !pipeline.head && (pipeline.head = task);
  20110. pipeline.tail && pipeline.tail.pipe(task);
  20111. pipeline.tail = task;
  20112. task.__idxInPipeline = pipeline.count++;
  20113. task.__pipeline = pipeline;
  20114. }
  20115. Scheduler.wrapStageHandler = function (stageHandler, visualType) {
  20116. if (isFunction$1(stageHandler)) {
  20117. stageHandler = {
  20118. overallReset: stageHandler,
  20119. seriesType: detectSeriseType(stageHandler)
  20120. };
  20121. }
  20122. stageHandler.uid = getUID('stageHandler');
  20123. visualType && (stageHandler.visualType = visualType);
  20124. return stageHandler;
  20125. };
  20126. /**
  20127. * Only some legacy stage handlers (usually in echarts extensions) are pure function.
  20128. * To ensure that they can work normally, they should work in block mode, that is,
  20129. * they should not be started util the previous tasks finished. So they cause the
  20130. * progressive rendering disabled. We try to detect the series type, to narrow down
  20131. * the block range to only the series type they concern, but not all series.
  20132. */
  20133. function detectSeriseType(legacyFunc) {
  20134. seriesType = null;
  20135. try {
  20136. // Assume there is no async when calling `eachSeriesByType`.
  20137. legacyFunc(ecModelMock, apiMock);
  20138. }
  20139. catch (e) {
  20140. }
  20141. return seriesType;
  20142. }
  20143. var ecModelMock = {};
  20144. var apiMock = {};
  20145. var seriesType;
  20146. mockMethods(ecModelMock, GlobalModel);
  20147. mockMethods(apiMock, ExtensionAPI);
  20148. ecModelMock.eachSeriesByType = ecModelMock.eachRawSeriesByType = function (type) {
  20149. seriesType = type;
  20150. };
  20151. ecModelMock.eachComponent = function (cond) {
  20152. if (cond.mainType === 'series' && cond.subType) {
  20153. seriesType = cond.subType;
  20154. }
  20155. };
  20156. function mockMethods(target, Clz) {
  20157. for (var name in Clz.prototype) {
  20158. // Do not use hasOwnProperty
  20159. target[name] = noop;
  20160. }
  20161. }
  20162. var colorAll = ['#37A2DA', '#32C5E9', '#67E0E3', '#9FE6B8', '#FFDB5C','#ff9f7f', '#fb7293', '#E062AE', '#E690D1', '#e7bcf3', '#9d96f5', '#8378EA', '#96BFFF'];
  20163. var lightTheme = {
  20164. color: colorAll,
  20165. colorLayer: [
  20166. ['#37A2DA', '#ffd85c', '#fd7b5f'],
  20167. ['#37A2DA', '#67E0E3', '#FFDB5C', '#ff9f7f', '#E062AE', '#9d96f5'],
  20168. ['#37A2DA', '#32C5E9', '#9FE6B8', '#FFDB5C', '#ff9f7f', '#fb7293', '#e7bcf3', '#8378EA', '#96BFFF'],
  20169. colorAll
  20170. ]
  20171. };
  20172. var contrastColor = '#eee';
  20173. var axisCommon = function () {
  20174. return {
  20175. axisLine: {
  20176. lineStyle: {
  20177. color: contrastColor
  20178. }
  20179. },
  20180. axisTick: {
  20181. lineStyle: {
  20182. color: contrastColor
  20183. }
  20184. },
  20185. axisLabel: {
  20186. textStyle: {
  20187. color: contrastColor
  20188. }
  20189. },
  20190. splitLine: {
  20191. lineStyle: {
  20192. type: 'dashed',
  20193. color: '#aaa'
  20194. }
  20195. },
  20196. splitArea: {
  20197. areaStyle: {
  20198. color: contrastColor
  20199. }
  20200. }
  20201. };
  20202. };
  20203. var colorPalette = ['#dd6b66','#759aa0','#e69d87','#8dc1a9','#ea7e53','#eedd78','#73a373','#73b9bc','#7289ab', '#91ca8c','#f49f42'];
  20204. var theme = {
  20205. color: colorPalette,
  20206. backgroundColor: '#333',
  20207. tooltip: {
  20208. axisPointer: {
  20209. lineStyle: {
  20210. color: contrastColor
  20211. },
  20212. crossStyle: {
  20213. color: contrastColor
  20214. }
  20215. }
  20216. },
  20217. legend: {
  20218. textStyle: {
  20219. color: contrastColor
  20220. }
  20221. },
  20222. textStyle: {
  20223. color: contrastColor
  20224. },
  20225. title: {
  20226. textStyle: {
  20227. color: contrastColor
  20228. }
  20229. },
  20230. toolbox: {
  20231. iconStyle: {
  20232. normal: {
  20233. borderColor: contrastColor
  20234. }
  20235. }
  20236. },
  20237. dataZoom: {
  20238. textStyle: {
  20239. color: contrastColor
  20240. }
  20241. },
  20242. visualMap: {
  20243. textStyle: {
  20244. color: contrastColor
  20245. }
  20246. },
  20247. timeline: {
  20248. lineStyle: {
  20249. color: contrastColor
  20250. },
  20251. itemStyle: {
  20252. normal: {
  20253. color: colorPalette[1]
  20254. }
  20255. },
  20256. label: {
  20257. normal: {
  20258. textStyle: {
  20259. color: contrastColor
  20260. }
  20261. }
  20262. },
  20263. controlStyle: {
  20264. normal: {
  20265. color: contrastColor,
  20266. borderColor: contrastColor
  20267. }
  20268. }
  20269. },
  20270. timeAxis: axisCommon(),
  20271. logAxis: axisCommon(),
  20272. valueAxis: axisCommon(),
  20273. categoryAxis: axisCommon(),
  20274. line: {
  20275. symbol: 'circle'
  20276. },
  20277. graph: {
  20278. color: colorPalette
  20279. },
  20280. gauge: {
  20281. title: {
  20282. textStyle: {
  20283. color: contrastColor
  20284. }
  20285. }
  20286. },
  20287. candlestick: {
  20288. itemStyle: {
  20289. normal: {
  20290. color: '#FD1050',
  20291. color0: '#0CF49B',
  20292. borderColor: '#FD1050',
  20293. borderColor0: '#0CF49B'
  20294. }
  20295. }
  20296. }
  20297. };
  20298. theme.categoryAxis.splitLine.show = false;
  20299. /*!
  20300. * ECharts, a free, powerful charting and visualization library.
  20301. *
  20302. * Copyright (c) 2017, Baidu Inc.
  20303. * All rights reserved.
  20304. *
  20305. * LICENSE
  20306. * https://github.com/ecomfe/echarts/blob/master/LICENSE.txt
  20307. */
  20308. var assert = assert$1;
  20309. var each = each$1;
  20310. var isFunction = isFunction$1;
  20311. var isObject = isObject$1;
  20312. var parseClassType = ComponentModel.parseClassType;
  20313. var version = '4.0.4';
  20314. var dependencies = {
  20315. zrender: '4.0.3'
  20316. };
  20317. var TEST_FRAME_REMAIN_TIME = 1;
  20318. var PRIORITY_PROCESSOR_FILTER = 1000;
  20319. var PRIORITY_PROCESSOR_STATISTIC = 5000;
  20320. var PRIORITY_VISUAL_LAYOUT = 1000;
  20321. var PRIORITY_VISUAL_GLOBAL = 2000;
  20322. var PRIORITY_VISUAL_CHART = 3000;
  20323. var PRIORITY_VISUAL_COMPONENT = 4000;
  20324. // FIXME
  20325. // necessary?
  20326. var PRIORITY_VISUAL_BRUSH = 5000;
  20327. var PRIORITY = {
  20328. PROCESSOR: {
  20329. FILTER: PRIORITY_PROCESSOR_FILTER,
  20330. STATISTIC: PRIORITY_PROCESSOR_STATISTIC
  20331. },
  20332. VISUAL: {
  20333. LAYOUT: PRIORITY_VISUAL_LAYOUT,
  20334. GLOBAL: PRIORITY_VISUAL_GLOBAL,
  20335. CHART: PRIORITY_VISUAL_CHART,
  20336. COMPONENT: PRIORITY_VISUAL_COMPONENT,
  20337. BRUSH: PRIORITY_VISUAL_BRUSH
  20338. }
  20339. };
  20340. // Main process have three entries: `setOption`, `dispatchAction` and `resize`,
  20341. // where they must not be invoked nestedly, except the only case: invoke
  20342. // dispatchAction with updateMethod "none" in main process.
  20343. // This flag is used to carry out this rule.
  20344. // All events will be triggered out side main process (i.e. when !this[IN_MAIN_PROCESS]).
  20345. var IN_MAIN_PROCESS = '__flagInMainProcess';
  20346. var OPTION_UPDATED = '__optionUpdated';
  20347. var ACTION_REG = /^[a-zA-Z0-9_]+$/;
  20348. function createRegisterEventWithLowercaseName(method) {
  20349. return function (eventName, handler, context) {
  20350. // Event name is all lowercase
  20351. eventName = eventName && eventName.toLowerCase();
  20352. Eventful.prototype[method].call(this, eventName, handler, context);
  20353. };
  20354. }
  20355. /**
  20356. * @module echarts~MessageCenter
  20357. */
  20358. function MessageCenter() {
  20359. Eventful.call(this);
  20360. }
  20361. MessageCenter.prototype.on = createRegisterEventWithLowercaseName('on');
  20362. MessageCenter.prototype.off = createRegisterEventWithLowercaseName('off');
  20363. MessageCenter.prototype.one = createRegisterEventWithLowercaseName('one');
  20364. mixin(MessageCenter, Eventful);
  20365. /**
  20366. * @module echarts~ECharts
  20367. */
  20368. function ECharts(dom, theme$$1, opts) {
  20369. opts = opts || {};
  20370. // Get theme by name
  20371. if (typeof theme$$1 === 'string') {
  20372. theme$$1 = themeStorage[theme$$1];
  20373. }
  20374. /**
  20375. * @type {string}
  20376. */
  20377. this.id;
  20378. /**
  20379. * Group id
  20380. * @type {string}
  20381. */
  20382. this.group;
  20383. /**
  20384. * @type {HTMLElement}
  20385. * @private
  20386. */
  20387. this._dom = dom;
  20388. var defaultRenderer = 'canvas';
  20389. if (__DEV__) {
  20390. defaultRenderer = (
  20391. typeof window === 'undefined' ? global : window
  20392. ).__ECHARTS__DEFAULT__RENDERER__ || defaultRenderer;
  20393. }
  20394. /**
  20395. * @type {module:zrender/ZRender}
  20396. * @private
  20397. */
  20398. var zr = this._zr = init$1(dom, {
  20399. renderer: opts.renderer || defaultRenderer,
  20400. devicePixelRatio: opts.devicePixelRatio,
  20401. width: opts.width,
  20402. height: opts.height
  20403. });
  20404. /**
  20405. * Expect 60 pfs.
  20406. * @type {Function}
  20407. * @private
  20408. */
  20409. this._throttledZrFlush = throttle(bind(zr.flush, zr), 17);
  20410. var theme$$1 = clone(theme$$1);
  20411. theme$$1 && backwardCompat(theme$$1, true);
  20412. /**
  20413. * @type {Object}
  20414. * @private
  20415. */
  20416. this._theme = theme$$1;
  20417. /**
  20418. * @type {Array.<module:echarts/view/Chart>}
  20419. * @private
  20420. */
  20421. this._chartsViews = [];
  20422. /**
  20423. * @type {Object.<string, module:echarts/view/Chart>}
  20424. * @private
  20425. */
  20426. this._chartsMap = {};
  20427. /**
  20428. * @type {Array.<module:echarts/view/Component>}
  20429. * @private
  20430. */
  20431. this._componentsViews = [];
  20432. /**
  20433. * @type {Object.<string, module:echarts/view/Component>}
  20434. * @private
  20435. */
  20436. this._componentsMap = {};
  20437. /**
  20438. * @type {module:echarts/CoordinateSystem}
  20439. * @private
  20440. */
  20441. this._coordSysMgr = new CoordinateSystemManager();
  20442. /**
  20443. * @type {module:echarts/ExtensionAPI}
  20444. * @private
  20445. */
  20446. var api = this._api = createExtensionAPI(this);
  20447. // Sort on demand
  20448. function prioritySortFunc(a, b) {
  20449. return a.__prio - b.__prio;
  20450. }
  20451. sort(visualFuncs, prioritySortFunc);
  20452. sort(dataProcessorFuncs, prioritySortFunc);
  20453. /**
  20454. * @type {module:echarts/stream/Scheduler}
  20455. */
  20456. this._scheduler = new Scheduler(this, api, dataProcessorFuncs, visualFuncs);
  20457. Eventful.call(this);
  20458. /**
  20459. * @type {module:echarts~MessageCenter}
  20460. * @private
  20461. */
  20462. this._messageCenter = new MessageCenter();
  20463. // Init mouse events
  20464. this._initEvents();
  20465. // In case some people write `window.onresize = chart.resize`
  20466. this.resize = bind(this.resize, this);
  20467. // Can't dispatch action during rendering procedure
  20468. this._pendingActions = [];
  20469. zr.animation.on('frame', this._onframe, this);
  20470. bindRenderedEvent(zr, this);
  20471. // ECharts instance can be used as value.
  20472. setAsPrimitive(this);
  20473. }
  20474. var echartsProto = ECharts.prototype;
  20475. echartsProto._onframe = function () {
  20476. if (this._disposed) {
  20477. return;
  20478. }
  20479. var scheduler = this._scheduler;
  20480. // Lazy update
  20481. if (this[OPTION_UPDATED]) {
  20482. var silent = this[OPTION_UPDATED].silent;
  20483. this[IN_MAIN_PROCESS] = true;
  20484. prepare(this);
  20485. updateMethods.update.call(this);
  20486. this[IN_MAIN_PROCESS] = false;
  20487. this[OPTION_UPDATED] = false;
  20488. flushPendingActions.call(this, silent);
  20489. triggerUpdatedEvent.call(this, silent);
  20490. }
  20491. // Avoid do both lazy update and progress in one frame.
  20492. else if (scheduler.unfinished) {
  20493. // Stream progress.
  20494. var remainTime = TEST_FRAME_REMAIN_TIME;
  20495. var ecModel = this._model;
  20496. var api = this._api;
  20497. scheduler.unfinished = false;
  20498. do {
  20499. var startTime = +new Date();
  20500. scheduler.performSeriesTasks(ecModel);
  20501. // Currently dataProcessorFuncs do not check threshold.
  20502. scheduler.performDataProcessorTasks(ecModel);
  20503. updateStreamModes(this, ecModel);
  20504. // Do not update coordinate system here. Because that coord system update in
  20505. // each frame is not a good user experience. So we follow the rule that
  20506. // the extent of the coordinate system is determin in the first frame (the
  20507. // frame is executed immedietely after task reset.
  20508. // this._coordSysMgr.update(ecModel, api);
  20509. // console.log('--- ec frame visual ---', remainTime);
  20510. scheduler.performVisualTasks(ecModel);
  20511. renderSeries(this, this._model, api, 'remain');
  20512. remainTime -= (+new Date() - startTime);
  20513. }
  20514. while (remainTime > 0 && scheduler.unfinished);
  20515. // Call flush explicitly for trigger finished event.
  20516. if (!scheduler.unfinished) {
  20517. this._zr.flush();
  20518. }
  20519. // Else, zr flushing be ensue within the same frame,
  20520. // because zr flushing is after onframe event.
  20521. }
  20522. };
  20523. /**
  20524. * @return {HTMLElement}
  20525. */
  20526. echartsProto.getDom = function () {
  20527. return this._dom;
  20528. };
  20529. /**
  20530. * @return {module:zrender~ZRender}
  20531. */
  20532. echartsProto.getZr = function () {
  20533. return this._zr;
  20534. };
  20535. /**
  20536. * Usage:
  20537. * chart.setOption(option, notMerge, lazyUpdate);
  20538. * chart.setOption(option, {
  20539. * notMerge: ...,
  20540. * lazyUpdate: ...,
  20541. * silent: ...
  20542. * });
  20543. *
  20544. * @param {Object} option
  20545. * @param {Object|boolean} [opts] opts or notMerge.
  20546. * @param {boolean} [opts.notMerge=false]
  20547. * @param {boolean} [opts.lazyUpdate=false] Useful when setOption frequently.
  20548. */
  20549. echartsProto.setOption = function (option, notMerge, lazyUpdate) {
  20550. if (__DEV__) {
  20551. assert(!this[IN_MAIN_PROCESS], '`setOption` should not be called during main process.');
  20552. }
  20553. var silent;
  20554. if (isObject(notMerge)) {
  20555. lazyUpdate = notMerge.lazyUpdate;
  20556. silent = notMerge.silent;
  20557. notMerge = notMerge.notMerge;
  20558. }
  20559. this[IN_MAIN_PROCESS] = true;
  20560. if (!this._model || notMerge) {
  20561. var optionManager = new OptionManager(this._api);
  20562. var theme$$1 = this._theme;
  20563. var ecModel = this._model = new GlobalModel(null, null, theme$$1, optionManager);
  20564. ecModel.scheduler = this._scheduler;
  20565. ecModel.init(null, null, theme$$1, optionManager);
  20566. }
  20567. this._model.setOption(option, optionPreprocessorFuncs);
  20568. if (lazyUpdate) {
  20569. this[OPTION_UPDATED] = {silent: silent};
  20570. this[IN_MAIN_PROCESS] = false;
  20571. }
  20572. else {
  20573. prepare(this);
  20574. updateMethods.update.call(this);
  20575. // Ensure zr refresh sychronously, and then pixel in canvas can be
  20576. // fetched after `setOption`.
  20577. this._zr.flush();
  20578. this[OPTION_UPDATED] = false;
  20579. this[IN_MAIN_PROCESS] = false;
  20580. flushPendingActions.call(this, silent);
  20581. triggerUpdatedEvent.call(this, silent);
  20582. }
  20583. };
  20584. /**
  20585. * @DEPRECATED
  20586. */
  20587. echartsProto.setTheme = function () {
  20588. console.log('ECharts#setTheme() is DEPRECATED in ECharts 3.0');
  20589. };
  20590. /**
  20591. * @return {module:echarts/model/Global}
  20592. */
  20593. echartsProto.getModel = function () {
  20594. return this._model;
  20595. };
  20596. /**
  20597. * @return {Object}
  20598. */
  20599. echartsProto.getOption = function () {
  20600. return this._model && this._model.getOption();
  20601. };
  20602. /**
  20603. * @return {number}
  20604. */
  20605. echartsProto.getWidth = function () {
  20606. return this._zr.getWidth();
  20607. };
  20608. /**
  20609. * @return {number}
  20610. */
  20611. echartsProto.getHeight = function () {
  20612. return this._zr.getHeight();
  20613. };
  20614. /**
  20615. * @return {number}
  20616. */
  20617. echartsProto.getDevicePixelRatio = function () {
  20618. return this._zr.painter.dpr || window.devicePixelRatio || 1;
  20619. };
  20620. /**
  20621. * Get canvas which has all thing rendered
  20622. * @param {Object} opts
  20623. * @param {string} [opts.backgroundColor]
  20624. * @return {string}
  20625. */
  20626. echartsProto.getRenderedCanvas = function (opts) {
  20627. if (!env$1.canvasSupported) {
  20628. return;
  20629. }
  20630. opts = opts || {};
  20631. opts.pixelRatio = opts.pixelRatio || 1;
  20632. opts.backgroundColor = opts.backgroundColor
  20633. || this._model.get('backgroundColor');
  20634. var zr = this._zr;
  20635. // var list = zr.storage.getDisplayList();
  20636. // Stop animations
  20637. // Never works before in init animation, so remove it.
  20638. // zrUtil.each(list, function (el) {
  20639. // el.stopAnimation(true);
  20640. // });
  20641. return zr.painter.getRenderedCanvas(opts);
  20642. };
  20643. /**
  20644. * Get svg data url
  20645. * @return {string}
  20646. */
  20647. echartsProto.getSvgDataUrl = function () {
  20648. if (!env$1.svgSupported) {
  20649. return;
  20650. }
  20651. var zr = this._zr;
  20652. var list = zr.storage.getDisplayList();
  20653. // Stop animations
  20654. each$1(list, function (el) {
  20655. el.stopAnimation(true);
  20656. });
  20657. return zr.painter.pathToDataUrl();
  20658. };
  20659. /**
  20660. * @return {string}
  20661. * @param {Object} opts
  20662. * @param {string} [opts.type='png']
  20663. * @param {string} [opts.pixelRatio=1]
  20664. * @param {string} [opts.backgroundColor]
  20665. * @param {string} [opts.excludeComponents]
  20666. */
  20667. echartsProto.getDataURL = function (opts) {
  20668. opts = opts || {};
  20669. var excludeComponents = opts.excludeComponents;
  20670. var ecModel = this._model;
  20671. var excludesComponentViews = [];
  20672. var self = this;
  20673. each(excludeComponents, function (componentType) {
  20674. ecModel.eachComponent({
  20675. mainType: componentType
  20676. }, function (component) {
  20677. var view = self._componentsMap[component.__viewId];
  20678. if (!view.group.ignore) {
  20679. excludesComponentViews.push(view);
  20680. view.group.ignore = true;
  20681. }
  20682. });
  20683. });
  20684. var url = this._zr.painter.getType() === 'svg'
  20685. ? this.getSvgDataUrl()
  20686. : this.getRenderedCanvas(opts).toDataURL(
  20687. 'image/' + (opts && opts.type || 'png')
  20688. );
  20689. each(excludesComponentViews, function (view) {
  20690. view.group.ignore = false;
  20691. });
  20692. return url;
  20693. };
  20694. /**
  20695. * @return {string}
  20696. * @param {Object} opts
  20697. * @param {string} [opts.type='png']
  20698. * @param {string} [opts.pixelRatio=1]
  20699. * @param {string} [opts.backgroundColor]
  20700. */
  20701. echartsProto.getConnectedDataURL = function (opts) {
  20702. if (!env$1.canvasSupported) {
  20703. return;
  20704. }
  20705. var groupId = this.group;
  20706. var mathMin = Math.min;
  20707. var mathMax = Math.max;
  20708. var MAX_NUMBER = Infinity;
  20709. if (connectedGroups[groupId]) {
  20710. var left = MAX_NUMBER;
  20711. var top = MAX_NUMBER;
  20712. var right = -MAX_NUMBER;
  20713. var bottom = -MAX_NUMBER;
  20714. var canvasList = [];
  20715. var dpr = (opts && opts.pixelRatio) || 1;
  20716. each$1(instances, function (chart, id) {
  20717. if (chart.group === groupId) {
  20718. var canvas = chart.getRenderedCanvas(
  20719. clone(opts)
  20720. );
  20721. var boundingRect = chart.getDom().getBoundingClientRect();
  20722. left = mathMin(boundingRect.left, left);
  20723. top = mathMin(boundingRect.top, top);
  20724. right = mathMax(boundingRect.right, right);
  20725. bottom = mathMax(boundingRect.bottom, bottom);
  20726. canvasList.push({
  20727. dom: canvas,
  20728. left: boundingRect.left,
  20729. top: boundingRect.top
  20730. });
  20731. }
  20732. });
  20733. left *= dpr;
  20734. top *= dpr;
  20735. right *= dpr;
  20736. bottom *= dpr;
  20737. var width = right - left;
  20738. var height = bottom - top;
  20739. var targetCanvas = createCanvas();
  20740. targetCanvas.width = width;
  20741. targetCanvas.height = height;
  20742. var zr = init$1(targetCanvas);
  20743. each(canvasList, function (item) {
  20744. var img = new ZImage({
  20745. style: {
  20746. x: item.left * dpr - left,
  20747. y: item.top * dpr - top,
  20748. image: item.dom
  20749. }
  20750. });
  20751. zr.add(img);
  20752. });
  20753. zr.refreshImmediately();
  20754. return targetCanvas.toDataURL('image/' + (opts && opts.type || 'png'));
  20755. }
  20756. else {
  20757. return this.getDataURL(opts);
  20758. }
  20759. };
  20760. /**
  20761. * Convert from logical coordinate system to pixel coordinate system.
  20762. * See CoordinateSystem#convertToPixel.
  20763. * @param {string|Object} finder
  20764. * If string, e.g., 'geo', means {geoIndex: 0}.
  20765. * If Object, could contain some of these properties below:
  20766. * {
  20767. * seriesIndex / seriesId / seriesName,
  20768. * geoIndex / geoId, geoName,
  20769. * bmapIndex / bmapId / bmapName,
  20770. * xAxisIndex / xAxisId / xAxisName,
  20771. * yAxisIndex / yAxisId / yAxisName,
  20772. * gridIndex / gridId / gridName,
  20773. * ... (can be extended)
  20774. * }
  20775. * @param {Array|number} value
  20776. * @return {Array|number} result
  20777. */
  20778. echartsProto.convertToPixel = curry(doConvertPixel, 'convertToPixel');
  20779. /**
  20780. * Convert from pixel coordinate system to logical coordinate system.
  20781. * See CoordinateSystem#convertFromPixel.
  20782. * @param {string|Object} finder
  20783. * If string, e.g., 'geo', means {geoIndex: 0}.
  20784. * If Object, could contain some of these properties below:
  20785. * {
  20786. * seriesIndex / seriesId / seriesName,
  20787. * geoIndex / geoId / geoName,
  20788. * bmapIndex / bmapId / bmapName,
  20789. * xAxisIndex / xAxisId / xAxisName,
  20790. * yAxisIndex / yAxisId / yAxisName
  20791. * gridIndex / gridId / gridName,
  20792. * ... (can be extended)
  20793. * }
  20794. * @param {Array|number} value
  20795. * @return {Array|number} result
  20796. */
  20797. echartsProto.convertFromPixel = curry(doConvertPixel, 'convertFromPixel');
  20798. function doConvertPixel(methodName, finder, value) {
  20799. var ecModel = this._model;
  20800. var coordSysList = this._coordSysMgr.getCoordinateSystems();
  20801. var result;
  20802. finder = parseFinder(ecModel, finder);
  20803. for (var i = 0; i < coordSysList.length; i++) {
  20804. var coordSys = coordSysList[i];
  20805. if (coordSys[methodName]
  20806. && (result = coordSys[methodName](ecModel, finder, value)) != null
  20807. ) {
  20808. return result;
  20809. }
  20810. }
  20811. if (__DEV__) {
  20812. console.warn(
  20813. 'No coordinate system that supports ' + methodName + ' found by the given finder.'
  20814. );
  20815. }
  20816. }
  20817. /**
  20818. * Is the specified coordinate systems or components contain the given pixel point.
  20819. * @param {string|Object} finder
  20820. * If string, e.g., 'geo', means {geoIndex: 0}.
  20821. * If Object, could contain some of these properties below:
  20822. * {
  20823. * seriesIndex / seriesId / seriesName,
  20824. * geoIndex / geoId / geoName,
  20825. * bmapIndex / bmapId / bmapName,
  20826. * xAxisIndex / xAxisId / xAxisName,
  20827. * yAxisIndex / yAxisId / yAxisName,
  20828. * gridIndex / gridId / gridName,
  20829. * ... (can be extended)
  20830. * }
  20831. * @param {Array|number} value
  20832. * @return {boolean} result
  20833. */
  20834. echartsProto.containPixel = function (finder, value) {
  20835. var ecModel = this._model;
  20836. var result;
  20837. finder = parseFinder(ecModel, finder);
  20838. each$1(finder, function (models, key) {
  20839. key.indexOf('Models') >= 0 && each$1(models, function (model) {
  20840. var coordSys = model.coordinateSystem;
  20841. if (coordSys && coordSys.containPoint) {
  20842. result |= !!coordSys.containPoint(value);
  20843. }
  20844. else if (key === 'seriesModels') {
  20845. var view = this._chartsMap[model.__viewId];
  20846. if (view && view.containPoint) {
  20847. result |= view.containPoint(value, model);
  20848. }
  20849. else {
  20850. if (__DEV__) {
  20851. console.warn(key + ': ' + (view
  20852. ? 'The found component do not support containPoint.'
  20853. : 'No view mapping to the found component.'
  20854. ));
  20855. }
  20856. }
  20857. }
  20858. else {
  20859. if (__DEV__) {
  20860. console.warn(key + ': containPoint is not supported');
  20861. }
  20862. }
  20863. }, this);
  20864. }, this);
  20865. return !!result;
  20866. };
  20867. /**
  20868. * Get visual from series or data.
  20869. * @param {string|Object} finder
  20870. * If string, e.g., 'series', means {seriesIndex: 0}.
  20871. * If Object, could contain some of these properties below:
  20872. * {
  20873. * seriesIndex / seriesId / seriesName,
  20874. * dataIndex / dataIndexInside
  20875. * }
  20876. * If dataIndex is not specified, series visual will be fetched,
  20877. * but not data item visual.
  20878. * If all of seriesIndex, seriesId, seriesName are not specified,
  20879. * visual will be fetched from first series.
  20880. * @param {string} visualType 'color', 'symbol', 'symbolSize'
  20881. */
  20882. echartsProto.getVisual = function (finder, visualType) {
  20883. var ecModel = this._model;
  20884. finder = parseFinder(ecModel, finder, {defaultMainType: 'series'});
  20885. var seriesModel = finder.seriesModel;
  20886. if (__DEV__) {
  20887. if (!seriesModel) {
  20888. console.warn('There is no specified seires model');
  20889. }
  20890. }
  20891. var data = seriesModel.getData();
  20892. var dataIndexInside = finder.hasOwnProperty('dataIndexInside')
  20893. ? finder.dataIndexInside
  20894. : finder.hasOwnProperty('dataIndex')
  20895. ? data.indexOfRawIndex(finder.dataIndex)
  20896. : null;
  20897. return dataIndexInside != null
  20898. ? data.getItemVisual(dataIndexInside, visualType)
  20899. : data.getVisual(visualType);
  20900. };
  20901. /**
  20902. * Get view of corresponding component model
  20903. * @param {module:echarts/model/Component} componentModel
  20904. * @return {module:echarts/view/Component}
  20905. */
  20906. echartsProto.getViewOfComponentModel = function (componentModel) {
  20907. return this._componentsMap[componentModel.__viewId];
  20908. };
  20909. /**
  20910. * Get view of corresponding series model
  20911. * @param {module:echarts/model/Series} seriesModel
  20912. * @return {module:echarts/view/Chart}
  20913. */
  20914. echartsProto.getViewOfSeriesModel = function (seriesModel) {
  20915. return this._chartsMap[seriesModel.__viewId];
  20916. };
  20917. var updateMethods = {
  20918. prepareAndUpdate: function (payload) {
  20919. prepare(this);
  20920. updateMethods.update.call(this, payload);
  20921. },
  20922. /**
  20923. * @param {Object} payload
  20924. * @private
  20925. */
  20926. update: function (payload) {
  20927. // console.profile && console.profile('update');
  20928. var ecModel = this._model;
  20929. var api = this._api;
  20930. var zr = this._zr;
  20931. var coordSysMgr = this._coordSysMgr;
  20932. var scheduler = this._scheduler;
  20933. // update before setOption
  20934. if (!ecModel) {
  20935. return;
  20936. }
  20937. ecModel.restoreData(payload);
  20938. scheduler.performSeriesTasks(ecModel);
  20939. // TODO
  20940. // Save total ecModel here for undo/redo (after restoring data and before processing data).
  20941. // Undo (restoration of total ecModel) can be carried out in 'action' or outside API call.
  20942. // Create new coordinate system each update
  20943. // In LineView may save the old coordinate system and use it to get the orignal point
  20944. coordSysMgr.create(ecModel, api);
  20945. scheduler.performDataProcessorTasks(ecModel, payload);
  20946. // Current stream render is not supported in data process. So we can update
  20947. // stream modes after data processing, where the filtered data is used to
  20948. // deteming whether use progressive rendering.
  20949. updateStreamModes(this, ecModel);
  20950. // stackSeriesData(ecModel);
  20951. coordSysMgr.update(ecModel, api);
  20952. clearColorPalette(ecModel);
  20953. scheduler.performVisualTasks(ecModel, payload);
  20954. render(this, ecModel, api, payload);
  20955. // Set background
  20956. var backgroundColor = ecModel.get('backgroundColor') || 'transparent';
  20957. // In IE8
  20958. if (!env$1.canvasSupported) {
  20959. var colorArr = parse(backgroundColor);
  20960. backgroundColor = stringify(colorArr, 'rgb');
  20961. if (colorArr[3] === 0) {
  20962. backgroundColor = 'transparent';
  20963. }
  20964. }
  20965. else {
  20966. zr.setBackgroundColor(backgroundColor);
  20967. }
  20968. performPostUpdateFuncs(ecModel, api);
  20969. // console.profile && console.profileEnd('update');
  20970. },
  20971. /**
  20972. * @param {Object} payload
  20973. * @private
  20974. */
  20975. updateTransform: function (payload) {
  20976. var ecModel = this._model;
  20977. var ecIns = this;
  20978. var api = this._api;
  20979. // update before setOption
  20980. if (!ecModel) {
  20981. return;
  20982. }
  20983. // ChartView.markUpdateMethod(payload, 'updateTransform');
  20984. var componentDirtyList = [];
  20985. ecModel.eachComponent(function (componentType, componentModel) {
  20986. var componentView = ecIns.getViewOfComponentModel(componentModel);
  20987. if (componentView && componentView.__alive) {
  20988. if (componentView.updateTransform) {
  20989. var result = componentView.updateTransform(componentModel, ecModel, api, payload);
  20990. result && result.update && componentDirtyList.push(componentView);
  20991. }
  20992. else {
  20993. componentDirtyList.push(componentView);
  20994. }
  20995. }
  20996. });
  20997. var seriesDirtyMap = createHashMap();
  20998. ecModel.eachSeries(function (seriesModel) {
  20999. var chartView = ecIns._chartsMap[seriesModel.__viewId];
  21000. if (chartView.updateTransform) {
  21001. var result = chartView.updateTransform(seriesModel, ecModel, api, payload);
  21002. result && result.update && seriesDirtyMap.set(seriesModel.uid, 1);
  21003. }
  21004. else {
  21005. seriesDirtyMap.set(seriesModel.uid, 1);
  21006. }
  21007. });
  21008. clearColorPalette(ecModel);
  21009. // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
  21010. // this._scheduler.performVisualTasks(ecModel, payload, 'layout', true);
  21011. this._scheduler.performVisualTasks(
  21012. ecModel, payload, {setDirty: true, dirtyMap: seriesDirtyMap}
  21013. );
  21014. // Currently, not call render of components. Geo render cost a lot.
  21015. // renderComponents(ecIns, ecModel, api, payload, componentDirtyList);
  21016. renderSeries(ecIns, ecModel, api, payload, seriesDirtyMap);
  21017. performPostUpdateFuncs(ecModel, this._api);
  21018. },
  21019. /**
  21020. * @param {Object} payload
  21021. * @private
  21022. */
  21023. updateView: function (payload) {
  21024. var ecModel = this._model;
  21025. // update before setOption
  21026. if (!ecModel) {
  21027. return;
  21028. }
  21029. Chart.markUpdateMethod(payload, 'updateView');
  21030. clearColorPalette(ecModel);
  21031. // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
  21032. this._scheduler.performVisualTasks(ecModel, payload, {setDirty: true});
  21033. render(this, this._model, this._api, payload);
  21034. performPostUpdateFuncs(ecModel, this._api);
  21035. },
  21036. /**
  21037. * @param {Object} payload
  21038. * @private
  21039. */
  21040. updateVisual: function (payload) {
  21041. updateMethods.update.call(this, payload);
  21042. // var ecModel = this._model;
  21043. // // update before setOption
  21044. // if (!ecModel) {
  21045. // return;
  21046. // }
  21047. // ChartView.markUpdateMethod(payload, 'updateVisual');
  21048. // clearColorPalette(ecModel);
  21049. // // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
  21050. // this._scheduler.performVisualTasks(ecModel, payload, {visualType: 'visual', setDirty: true});
  21051. // render(this, this._model, this._api, payload);
  21052. // performPostUpdateFuncs(ecModel, this._api);
  21053. },
  21054. /**
  21055. * @param {Object} payload
  21056. * @private
  21057. */
  21058. updateLayout: function (payload) {
  21059. updateMethods.update.call(this, payload);
  21060. // var ecModel = this._model;
  21061. // // update before setOption
  21062. // if (!ecModel) {
  21063. // return;
  21064. // }
  21065. // ChartView.markUpdateMethod(payload, 'updateLayout');
  21066. // // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
  21067. // // this._scheduler.performVisualTasks(ecModel, payload, 'layout', true);
  21068. // this._scheduler.performVisualTasks(ecModel, payload, {setDirty: true});
  21069. // render(this, this._model, this._api, payload);
  21070. // performPostUpdateFuncs(ecModel, this._api);
  21071. }
  21072. };
  21073. function prepare(ecIns) {
  21074. var ecModel = ecIns._model;
  21075. var scheduler = ecIns._scheduler;
  21076. scheduler.restorePipelines(ecModel);
  21077. scheduler.prepareStageTasks();
  21078. prepareView(ecIns, 'component', ecModel, scheduler);
  21079. prepareView(ecIns, 'chart', ecModel, scheduler);
  21080. scheduler.plan();
  21081. }
  21082. /**
  21083. * @private
  21084. */
  21085. function updateDirectly(ecIns, method, payload, mainType, subType) {
  21086. var ecModel = ecIns._model;
  21087. // broadcast
  21088. if (!mainType) {
  21089. // FIXME
  21090. // Chart will not be update directly here, except set dirty.
  21091. // But there is no such scenario now.
  21092. each(ecIns._componentsViews.concat(ecIns._chartsViews), callView);
  21093. return;
  21094. }
  21095. var query = {};
  21096. query[mainType + 'Id'] = payload[mainType + 'Id'];
  21097. query[mainType + 'Index'] = payload[mainType + 'Index'];
  21098. query[mainType + 'Name'] = payload[mainType + 'Name'];
  21099. var condition = {mainType: mainType, query: query};
  21100. subType && (condition.subType = subType); // subType may be '' by parseClassType;
  21101. // If dispatchAction before setOption, do nothing.
  21102. ecModel && ecModel.eachComponent(condition, function (model, index) {
  21103. callView(ecIns[
  21104. mainType === 'series' ? '_chartsMap' : '_componentsMap'
  21105. ][model.__viewId]);
  21106. }, ecIns);
  21107. function callView(view) {
  21108. view && view.__alive && view[method] && view[method](
  21109. view.__model, ecModel, ecIns._api, payload
  21110. );
  21111. }
  21112. }
  21113. /**
  21114. * Resize the chart
  21115. * @param {Object} opts
  21116. * @param {number} [opts.width] Can be 'auto' (the same as null/undefined)
  21117. * @param {number} [opts.height] Can be 'auto' (the same as null/undefined)
  21118. * @param {boolean} [opts.silent=false]
  21119. */
  21120. echartsProto.resize = function (opts) {
  21121. if (__DEV__) {
  21122. assert(!this[IN_MAIN_PROCESS], '`resize` should not be called during main process.');
  21123. }
  21124. this._zr.resize(opts);
  21125. var ecModel = this._model;
  21126. // Resize loading effect
  21127. this._loadingFX && this._loadingFX.resize();
  21128. if (!ecModel) {
  21129. return;
  21130. }
  21131. var optionChanged = ecModel.resetOption('media');
  21132. var silent = opts && opts.silent;
  21133. this[IN_MAIN_PROCESS] = true;
  21134. optionChanged && prepare(this);
  21135. updateMethods.update.call(this);
  21136. this[IN_MAIN_PROCESS] = false;
  21137. flushPendingActions.call(this, silent);
  21138. triggerUpdatedEvent.call(this, silent);
  21139. };
  21140. function updateStreamModes(ecIns, ecModel) {
  21141. var chartsMap = ecIns._chartsMap;
  21142. var scheduler = ecIns._scheduler;
  21143. ecModel.eachSeries(function (seriesModel) {
  21144. scheduler.updateStreamModes(seriesModel, chartsMap[seriesModel.__viewId]);
  21145. });
  21146. }
  21147. /**
  21148. * Show loading effect
  21149. * @param {string} [name='default']
  21150. * @param {Object} [cfg]
  21151. */
  21152. echartsProto.showLoading = function (name, cfg) {
  21153. if (isObject(name)) {
  21154. cfg = name;
  21155. name = '';
  21156. }
  21157. name = name || 'default';
  21158. this.hideLoading();
  21159. if (!loadingEffects[name]) {
  21160. if (__DEV__) {
  21161. console.warn('Loading effects ' + name + ' not exists.');
  21162. }
  21163. return;
  21164. }
  21165. var el = loadingEffects[name](this._api, cfg);
  21166. var zr = this._zr;
  21167. this._loadingFX = el;
  21168. zr.add(el);
  21169. };
  21170. /**
  21171. * Hide loading effect
  21172. */
  21173. echartsProto.hideLoading = function () {
  21174. this._loadingFX && this._zr.remove(this._loadingFX);
  21175. this._loadingFX = null;
  21176. };
  21177. /**
  21178. * @param {Object} eventObj
  21179. * @return {Object}
  21180. */
  21181. echartsProto.makeActionFromEvent = function (eventObj) {
  21182. var payload = extend({}, eventObj);
  21183. payload.type = eventActionMap[eventObj.type];
  21184. return payload;
  21185. };
  21186. /**
  21187. * @pubilc
  21188. * @param {Object} payload
  21189. * @param {string} [payload.type] Action type
  21190. * @param {Object|boolean} [opt] If pass boolean, means opt.silent
  21191. * @param {boolean} [opt.silent=false] Whether trigger events.
  21192. * @param {boolean} [opt.flush=undefined]
  21193. * true: Flush immediately, and then pixel in canvas can be fetched
  21194. * immediately. Caution: it might affect performance.
  21195. * false: Not not flush.
  21196. * undefined: Auto decide whether perform flush.
  21197. */
  21198. echartsProto.dispatchAction = function (payload, opt) {
  21199. if (!isObject(opt)) {
  21200. opt = {silent: !!opt};
  21201. }
  21202. if (!actions[payload.type]) {
  21203. return;
  21204. }
  21205. // Avoid dispatch action before setOption. Especially in `connect`.
  21206. if (!this._model) {
  21207. return;
  21208. }
  21209. // May dispatchAction in rendering procedure
  21210. if (this[IN_MAIN_PROCESS]) {
  21211. this._pendingActions.push(payload);
  21212. return;
  21213. }
  21214. doDispatchAction.call(this, payload, opt.silent);
  21215. if (opt.flush) {
  21216. this._zr.flush(true);
  21217. }
  21218. else if (opt.flush !== false && env$1.browser.weChat) {
  21219. // In WeChat embeded browser, `requestAnimationFrame` and `setInterval`
  21220. // hang when sliding page (on touch event), which cause that zr does not
  21221. // refresh util user interaction finished, which is not expected.
  21222. // But `dispatchAction` may be called too frequently when pan on touch
  21223. // screen, which impacts performance if do not throttle them.
  21224. this._throttledZrFlush();
  21225. }
  21226. flushPendingActions.call(this, opt.silent);
  21227. triggerUpdatedEvent.call(this, opt.silent);
  21228. };
  21229. function doDispatchAction(payload, silent) {
  21230. var payloadType = payload.type;
  21231. var escapeConnect = payload.escapeConnect;
  21232. var actionWrap = actions[payloadType];
  21233. var actionInfo = actionWrap.actionInfo;
  21234. var cptType = (actionInfo.update || 'update').split(':');
  21235. var updateMethod = cptType.pop();
  21236. cptType = cptType[0] != null && parseClassType(cptType[0]);
  21237. this[IN_MAIN_PROCESS] = true;
  21238. var payloads = [payload];
  21239. var batched = false;
  21240. // Batch action
  21241. if (payload.batch) {
  21242. batched = true;
  21243. payloads = map(payload.batch, function (item) {
  21244. item = defaults(extend({}, item), payload);
  21245. item.batch = null;
  21246. return item;
  21247. });
  21248. }
  21249. var eventObjBatch = [];
  21250. var eventObj;
  21251. var isHighDown = payloadType === 'highlight' || payloadType === 'downplay';
  21252. each(payloads, function (batchItem) {
  21253. // Action can specify the event by return it.
  21254. eventObj = actionWrap.action(batchItem, this._model, this._api);
  21255. // Emit event outside
  21256. eventObj = eventObj || extend({}, batchItem);
  21257. // Convert type to eventType
  21258. eventObj.type = actionInfo.event || eventObj.type;
  21259. eventObjBatch.push(eventObj);
  21260. // light update does not perform data process, layout and visual.
  21261. if (isHighDown) {
  21262. // method, payload, mainType, subType
  21263. updateDirectly(this, updateMethod, batchItem, 'series');
  21264. }
  21265. else if (cptType) {
  21266. updateDirectly(this, updateMethod, batchItem, cptType.main, cptType.sub);
  21267. }
  21268. }, this);
  21269. if (updateMethod !== 'none' && !isHighDown && !cptType) {
  21270. // Still dirty
  21271. if (this[OPTION_UPDATED]) {
  21272. // FIXME Pass payload ?
  21273. prepare(this);
  21274. updateMethods.update.call(this, payload);
  21275. this[OPTION_UPDATED] = false;
  21276. }
  21277. else {
  21278. updateMethods[updateMethod].call(this, payload);
  21279. }
  21280. }
  21281. // Follow the rule of action batch
  21282. if (batched) {
  21283. eventObj = {
  21284. type: actionInfo.event || payloadType,
  21285. escapeConnect: escapeConnect,
  21286. batch: eventObjBatch
  21287. };
  21288. }
  21289. else {
  21290. eventObj = eventObjBatch[0];
  21291. }
  21292. this[IN_MAIN_PROCESS] = false;
  21293. !silent && this._messageCenter.trigger(eventObj.type, eventObj);
  21294. }
  21295. function flushPendingActions(silent) {
  21296. var pendingActions = this._pendingActions;
  21297. while (pendingActions.length) {
  21298. var payload = pendingActions.shift();
  21299. doDispatchAction.call(this, payload, silent);
  21300. }
  21301. }
  21302. function triggerUpdatedEvent(silent) {
  21303. !silent && this.trigger('updated');
  21304. }
  21305. /**
  21306. * Event `rendered` is triggered when zr
  21307. * rendered. It is useful for realtime
  21308. * snapshot (reflect animation).
  21309. *
  21310. * Event `finished` is triggered when:
  21311. * (1) zrender rendering finished.
  21312. * (2) initial animation finished.
  21313. * (3) progressive rendering finished.
  21314. * (4) no pending action.
  21315. * (5) no delayed setOption needs to be processed.
  21316. */
  21317. function bindRenderedEvent(zr, ecIns) {
  21318. zr.on('rendered', function () {
  21319. ecIns.trigger('rendered');
  21320. // The `finished` event should not be triggered repeatly,
  21321. // so it should only be triggered when rendering indeed happend
  21322. // in zrender. (Consider the case that dipatchAction is keep
  21323. // triggering when mouse move).
  21324. if (
  21325. // Although zr is dirty if initial animation is not finished
  21326. // and this checking is called on frame, we also check
  21327. // animation finished for robustness.
  21328. zr.animation.isFinished()
  21329. && !ecIns[OPTION_UPDATED]
  21330. && !ecIns._scheduler.unfinished
  21331. && !ecIns._pendingActions.length
  21332. ) {
  21333. ecIns.trigger('finished');
  21334. }
  21335. });
  21336. }
  21337. /**
  21338. * @param {Object} params
  21339. * @param {number} params.seriesIndex
  21340. * @param {Array|TypedArray} params.data
  21341. */
  21342. echartsProto.appendData = function (params) {
  21343. var seriesIndex = params.seriesIndex;
  21344. var ecModel = this.getModel();
  21345. var seriesModel = ecModel.getSeriesByIndex(seriesIndex);
  21346. if (__DEV__) {
  21347. assert(params.data && seriesModel);
  21348. }
  21349. seriesModel.appendData(params);
  21350. // Note: `appendData` does not support that update extent of coordinate
  21351. // system, util some scenario require that. In the expected usage of
  21352. // `appendData`, the initial extent of coordinate system should better
  21353. // be fixed by axis `min`/`max` setting or initial data, otherwise if
  21354. // the extent changed while `appendData`, the location of the painted
  21355. // graphic elements have to be changed, which make the usage of
  21356. // `appendData` meaningless.
  21357. this._scheduler.unfinished = true;
  21358. };
  21359. /**
  21360. * Register event
  21361. * @method
  21362. */
  21363. echartsProto.on = createRegisterEventWithLowercaseName('on');
  21364. echartsProto.off = createRegisterEventWithLowercaseName('off');
  21365. echartsProto.one = createRegisterEventWithLowercaseName('one');
  21366. /**
  21367. * Prepare view instances of charts and components
  21368. * @param {module:echarts/model/Global} ecModel
  21369. * @private
  21370. */
  21371. function prepareView(ecIns, type, ecModel, scheduler) {
  21372. var isComponent = type === 'component';
  21373. var viewList = isComponent ? ecIns._componentsViews : ecIns._chartsViews;
  21374. var viewMap = isComponent ? ecIns._componentsMap : ecIns._chartsMap;
  21375. var zr = ecIns._zr;
  21376. var api = ecIns._api;
  21377. for (var i = 0; i < viewList.length; i++) {
  21378. viewList[i].__alive = false;
  21379. }
  21380. isComponent
  21381. ? ecModel.eachComponent(function (componentType, model) {
  21382. componentType !== 'series' && doPrepare(model);
  21383. })
  21384. : ecModel.eachSeries(doPrepare);
  21385. function doPrepare(model) {
  21386. // Consider: id same and type changed.
  21387. var viewId = '_ec_' + model.id + '_' + model.type;
  21388. var view = viewMap[viewId];
  21389. if (!view) {
  21390. var classType = parseClassType(model.type);
  21391. var Clazz = isComponent
  21392. ? Component.getClass(classType.main, classType.sub)
  21393. : Chart.getClass(classType.sub);
  21394. if (__DEV__) {
  21395. assert(Clazz, classType.sub + ' does not exist.');
  21396. }
  21397. view = new Clazz();
  21398. view.init(ecModel, api);
  21399. viewMap[viewId] = view;
  21400. viewList.push(view);
  21401. zr.add(view.group);
  21402. }
  21403. model.__viewId = view.__id = viewId;
  21404. view.__alive = true;
  21405. view.__model = model;
  21406. view.group.__ecComponentInfo = {
  21407. mainType: model.mainType,
  21408. index: model.componentIndex
  21409. };
  21410. !isComponent && scheduler.prepareView(view, model, ecModel, api);
  21411. }
  21412. for (var i = 0; i < viewList.length;) {
  21413. var view = viewList[i];
  21414. if (!view.__alive) {
  21415. !isComponent && view.renderTask.dispose();
  21416. zr.remove(view.group);
  21417. view.dispose(ecModel, api);
  21418. viewList.splice(i, 1);
  21419. delete viewMap[view.__id];
  21420. view.__id = view.group.__ecComponentInfo = null;
  21421. }
  21422. else {
  21423. i++;
  21424. }
  21425. }
  21426. }
  21427. // /**
  21428. // * Encode visual infomation from data after data processing
  21429. // *
  21430. // * @param {module:echarts/model/Global} ecModel
  21431. // * @param {object} layout
  21432. // * @param {boolean} [layoutFilter] `true`: only layout,
  21433. // * `false`: only not layout,
  21434. // * `null`/`undefined`: all.
  21435. // * @param {string} taskBaseTag
  21436. // * @private
  21437. // */
  21438. // function startVisualEncoding(ecIns, ecModel, api, payload, layoutFilter) {
  21439. // each(visualFuncs, function (visual, index) {
  21440. // var isLayout = visual.isLayout;
  21441. // if (layoutFilter == null
  21442. // || (layoutFilter === false && !isLayout)
  21443. // || (layoutFilter === true && isLayout)
  21444. // ) {
  21445. // visual.func(ecModel, api, payload);
  21446. // }
  21447. // });
  21448. // }
  21449. function clearColorPalette(ecModel) {
  21450. ecModel.clearColorPalette();
  21451. ecModel.eachSeries(function (seriesModel) {
  21452. seriesModel.clearColorPalette();
  21453. });
  21454. }
  21455. function render(ecIns, ecModel, api, payload) {
  21456. renderComponents(ecIns, ecModel, api, payload);
  21457. each(ecIns._chartsViews, function (chart) {
  21458. chart.__alive = false;
  21459. });
  21460. renderSeries(ecIns, ecModel, api, payload);
  21461. // Remove groups of unrendered charts
  21462. each(ecIns._chartsViews, function (chart) {
  21463. if (!chart.__alive) {
  21464. chart.remove(ecModel, api);
  21465. }
  21466. });
  21467. }
  21468. function renderComponents(ecIns, ecModel, api, payload, dirtyList) {
  21469. each(dirtyList || ecIns._componentsViews, function (componentView) {
  21470. var componentModel = componentView.__model;
  21471. componentView.render(componentModel, ecModel, api, payload);
  21472. updateZ(componentModel, componentView);
  21473. });
  21474. }
  21475. /**
  21476. * Render each chart and component
  21477. * @private
  21478. */
  21479. function renderSeries(ecIns, ecModel, api, payload, dirtyMap) {
  21480. // Render all charts
  21481. var scheduler = ecIns._scheduler;
  21482. var unfinished;
  21483. ecModel.eachSeries(function (seriesModel) {
  21484. var chartView = ecIns._chartsMap[seriesModel.__viewId];
  21485. chartView.__alive = true;
  21486. var renderTask = chartView.renderTask;
  21487. scheduler.updatePayload(renderTask, payload);
  21488. if (dirtyMap && dirtyMap.get(seriesModel.uid)) {
  21489. renderTask.dirty();
  21490. }
  21491. unfinished |= renderTask.perform(scheduler.getPerformArgs(renderTask));
  21492. chartView.group.silent = !!seriesModel.get('silent');
  21493. updateZ(seriesModel, chartView);
  21494. updateBlend(seriesModel, chartView);
  21495. });
  21496. scheduler.unfinished |= unfinished;
  21497. // If use hover layer
  21498. updateHoverLayerStatus(ecIns._zr, ecModel);
  21499. // Add aria
  21500. aria(ecIns._zr.dom, ecModel);
  21501. }
  21502. function performPostUpdateFuncs(ecModel, api) {
  21503. each(postUpdateFuncs, function (func) {
  21504. func(ecModel, api);
  21505. });
  21506. }
  21507. var MOUSE_EVENT_NAMES = [
  21508. 'click', 'dblclick', 'mouseover', 'mouseout', 'mousemove',
  21509. 'mousedown', 'mouseup', 'globalout', 'contextmenu'
  21510. ];
  21511. /**
  21512. * @private
  21513. */
  21514. echartsProto._initEvents = function () {
  21515. each(MOUSE_EVENT_NAMES, function (eveName) {
  21516. this._zr.on(eveName, function (e) {
  21517. var ecModel = this.getModel();
  21518. var el = e.target;
  21519. var params;
  21520. // no e.target when 'globalout'.
  21521. if (eveName === 'globalout') {
  21522. params = {};
  21523. }
  21524. else if (el && el.dataIndex != null) {
  21525. var dataModel = el.dataModel || ecModel.getSeriesByIndex(el.seriesIndex);
  21526. params = dataModel && dataModel.getDataParams(el.dataIndex, el.dataType) || {};
  21527. }
  21528. // If element has custom eventData of components
  21529. else if (el && el.eventData) {
  21530. params = extend({}, el.eventData);
  21531. }
  21532. if (params) {
  21533. params.event = e;
  21534. params.type = eveName;
  21535. this.trigger(eveName, params);
  21536. }
  21537. }, this);
  21538. }, this);
  21539. each(eventActionMap, function (actionType, eventType) {
  21540. this._messageCenter.on(eventType, function (event) {
  21541. this.trigger(eventType, event);
  21542. }, this);
  21543. }, this);
  21544. };
  21545. /**
  21546. * @return {boolean}
  21547. */
  21548. echartsProto.isDisposed = function () {
  21549. return this._disposed;
  21550. };
  21551. /**
  21552. * Clear
  21553. */
  21554. echartsProto.clear = function () {
  21555. this.setOption({ series: [] }, true);
  21556. };
  21557. /**
  21558. * Dispose instance
  21559. */
  21560. echartsProto.dispose = function () {
  21561. if (this._disposed) {
  21562. if (__DEV__) {
  21563. console.warn('Instance ' + this.id + ' has been disposed');
  21564. }
  21565. return;
  21566. }
  21567. this._disposed = true;
  21568. setAttribute(this.getDom(), DOM_ATTRIBUTE_KEY, '');
  21569. var api = this._api;
  21570. var ecModel = this._model;
  21571. each(this._componentsViews, function (component) {
  21572. component.dispose(ecModel, api);
  21573. });
  21574. each(this._chartsViews, function (chart) {
  21575. chart.dispose(ecModel, api);
  21576. });
  21577. // Dispose after all views disposed
  21578. this._zr.dispose();
  21579. delete instances[this.id];
  21580. };
  21581. mixin(ECharts, Eventful);
  21582. function updateHoverLayerStatus(zr, ecModel) {
  21583. var storage = zr.storage;
  21584. var elCount = 0;
  21585. storage.traverse(function (el) {
  21586. if (!el.isGroup) {
  21587. elCount++;
  21588. }
  21589. });
  21590. if (elCount > ecModel.get('hoverLayerThreshold') && !env$1.node) {
  21591. storage.traverse(function (el) {
  21592. if (!el.isGroup) {
  21593. // Don't switch back.
  21594. el.useHoverLayer = true;
  21595. }
  21596. });
  21597. }
  21598. }
  21599. /**
  21600. * Update chart progressive and blend.
  21601. * @param {module:echarts/model/Series|module:echarts/model/Component} model
  21602. * @param {module:echarts/view/Component|module:echarts/view/Chart} view
  21603. */
  21604. function updateBlend(seriesModel, chartView) {
  21605. var blendMode = seriesModel.get('blendMode') || null;
  21606. if (__DEV__) {
  21607. if (!env$1.canvasSupported && blendMode && blendMode !== 'source-over') {
  21608. console.warn('Only canvas support blendMode');
  21609. }
  21610. }
  21611. chartView.group.traverse(function (el) {
  21612. // FIXME marker and other components
  21613. if (!el.isGroup) {
  21614. // Only set if blendMode is changed. In case element is incremental and don't wan't to rerender.
  21615. if (el.style.blend !== blendMode) {
  21616. el.setStyle('blend', blendMode);
  21617. }
  21618. }
  21619. if (el.eachPendingDisplayable) {
  21620. el.eachPendingDisplayable(function (displayable) {
  21621. displayable.setStyle('blend', blendMode);
  21622. });
  21623. }
  21624. });
  21625. }
  21626. /**
  21627. * @param {module:echarts/model/Series|module:echarts/model/Component} model
  21628. * @param {module:echarts/view/Component|module:echarts/view/Chart} view
  21629. */
  21630. function updateZ(model, view) {
  21631. var z = model.get('z');
  21632. var zlevel = model.get('zlevel');
  21633. // Set z and zlevel
  21634. view.group.traverse(function (el) {
  21635. if (el.type !== 'group') {
  21636. z != null && (el.z = z);
  21637. zlevel != null && (el.zlevel = zlevel);
  21638. }
  21639. });
  21640. }
  21641. function createExtensionAPI(ecInstance) {
  21642. var coordSysMgr = ecInstance._coordSysMgr;
  21643. return extend(new ExtensionAPI(ecInstance), {
  21644. // Inject methods
  21645. getCoordinateSystems: bind(
  21646. coordSysMgr.getCoordinateSystems, coordSysMgr
  21647. ),
  21648. getComponentByElement: function (el) {
  21649. while (el) {
  21650. var modelInfo = el.__ecComponentInfo;
  21651. if (modelInfo != null) {
  21652. return ecInstance._model.getComponent(modelInfo.mainType, modelInfo.index);
  21653. }
  21654. el = el.parent;
  21655. }
  21656. }
  21657. });
  21658. }
  21659. /**
  21660. * @type {Object} key: actionType.
  21661. * @inner
  21662. */
  21663. var actions = {};
  21664. /**
  21665. * Map eventType to actionType
  21666. * @type {Object}
  21667. */
  21668. var eventActionMap = {};
  21669. /**
  21670. * Data processor functions of each stage
  21671. * @type {Array.<Object.<string, Function>>}
  21672. * @inner
  21673. */
  21674. var dataProcessorFuncs = [];
  21675. /**
  21676. * @type {Array.<Function>}
  21677. * @inner
  21678. */
  21679. var optionPreprocessorFuncs = [];
  21680. /**
  21681. * @type {Array.<Function>}
  21682. * @inner
  21683. */
  21684. var postUpdateFuncs = [];
  21685. /**
  21686. * Visual encoding functions of each stage
  21687. * @type {Array.<Object.<string, Function>>}
  21688. */
  21689. var visualFuncs = [];
  21690. /**
  21691. * Theme storage
  21692. * @type {Object.<key, Object>}
  21693. */
  21694. var themeStorage = {};
  21695. /**
  21696. * Loading effects
  21697. */
  21698. var loadingEffects = {};
  21699. var instances = {};
  21700. var connectedGroups = {};
  21701. var idBase = new Date() - 0;
  21702. var groupIdBase = new Date() - 0;
  21703. var DOM_ATTRIBUTE_KEY = '_echarts_instance_';
  21704. var mapDataStores = {};
  21705. function enableConnect(chart) {
  21706. var STATUS_PENDING = 0;
  21707. var STATUS_UPDATING = 1;
  21708. var STATUS_UPDATED = 2;
  21709. var STATUS_KEY = '__connectUpdateStatus';
  21710. function updateConnectedChartsStatus(charts, status) {
  21711. for (var i = 0; i < charts.length; i++) {
  21712. var otherChart = charts[i];
  21713. otherChart[STATUS_KEY] = status;
  21714. }
  21715. }
  21716. each(eventActionMap, function (actionType, eventType) {
  21717. chart._messageCenter.on(eventType, function (event) {
  21718. if (connectedGroups[chart.group] && chart[STATUS_KEY] !== STATUS_PENDING) {
  21719. if (event && event.escapeConnect) {
  21720. return;
  21721. }
  21722. var action = chart.makeActionFromEvent(event);
  21723. var otherCharts = [];
  21724. each(instances, function (otherChart) {
  21725. if (otherChart !== chart && otherChart.group === chart.group) {
  21726. otherCharts.push(otherChart);
  21727. }
  21728. });
  21729. updateConnectedChartsStatus(otherCharts, STATUS_PENDING);
  21730. each(otherCharts, function (otherChart) {
  21731. if (otherChart[STATUS_KEY] !== STATUS_UPDATING) {
  21732. otherChart.dispatchAction(action);
  21733. }
  21734. });
  21735. updateConnectedChartsStatus(otherCharts, STATUS_UPDATED);
  21736. }
  21737. });
  21738. });
  21739. }
  21740. /**
  21741. * @param {HTMLElement} dom
  21742. * @param {Object} [theme]
  21743. * @param {Object} opts
  21744. * @param {number} [opts.devicePixelRatio] Use window.devicePixelRatio by default
  21745. * @param {string} [opts.renderer] Currently only 'canvas' is supported.
  21746. * @param {number} [opts.width] Use clientWidth of the input `dom` by default.
  21747. * Can be 'auto' (the same as null/undefined)
  21748. * @param {number} [opts.height] Use clientHeight of the input `dom` by default.
  21749. * Can be 'auto' (the same as null/undefined)
  21750. */
  21751. function init(dom, theme$$1, opts) {
  21752. if (__DEV__) {
  21753. // Check version
  21754. if ((version$1.replace('.', '') - 0) < (dependencies.zrender.replace('.', '') - 0)) {
  21755. throw new Error(
  21756. 'zrender/src ' + version$1
  21757. + ' is too old for ECharts ' + version
  21758. + '. Current version need ZRender '
  21759. + dependencies.zrender + '+'
  21760. );
  21761. }
  21762. if (!dom) {
  21763. throw new Error('Initialize failed: invalid dom.');
  21764. }
  21765. }
  21766. var existInstance = getInstanceByDom(dom);
  21767. if (existInstance) {
  21768. if (__DEV__) {
  21769. console.warn('There is a chart instance already initialized on the dom.');
  21770. }
  21771. return existInstance;
  21772. }
  21773. if (__DEV__) {
  21774. if (isDom(dom)
  21775. && dom.nodeName.toUpperCase() !== 'CANVAS'
  21776. && (
  21777. (!dom.clientWidth && (!opts || opts.width == null))
  21778. || (!dom.clientHeight && (!opts || opts.height == null))
  21779. )
  21780. ) {
  21781. console.warn('Can\'t get dom width or height');
  21782. }
  21783. }
  21784. var chart = new ECharts(dom, theme$$1, opts);
  21785. chart.id = 'ec_' + idBase++;
  21786. instances[chart.id] = chart;
  21787. setAttribute(dom, DOM_ATTRIBUTE_KEY, chart.id);
  21788. enableConnect(chart);
  21789. return chart;
  21790. }
  21791. /**
  21792. * @return {string|Array.<module:echarts~ECharts>} groupId
  21793. */
  21794. function connect(groupId) {
  21795. // Is array of charts
  21796. if (isArray(groupId)) {
  21797. var charts = groupId;
  21798. groupId = null;
  21799. // If any chart has group
  21800. each(charts, function (chart) {
  21801. if (chart.group != null) {
  21802. groupId = chart.group;
  21803. }
  21804. });
  21805. groupId = groupId || ('g_' + groupIdBase++);
  21806. each(charts, function (chart) {
  21807. chart.group = groupId;
  21808. });
  21809. }
  21810. connectedGroups[groupId] = true;
  21811. return groupId;
  21812. }
  21813. /**
  21814. * @DEPRECATED
  21815. * @return {string} groupId
  21816. */
  21817. function disConnect(groupId) {
  21818. connectedGroups[groupId] = false;
  21819. }
  21820. /**
  21821. * @return {string} groupId
  21822. */
  21823. var disconnect = disConnect;
  21824. /**
  21825. * Dispose a chart instance
  21826. * @param {module:echarts~ECharts|HTMLDomElement|string} chart
  21827. */
  21828. function dispose(chart) {
  21829. if (typeof chart === 'string') {
  21830. chart = instances[chart];
  21831. }
  21832. else if (!(chart instanceof ECharts)){
  21833. // Try to treat as dom
  21834. chart = getInstanceByDom(chart);
  21835. }
  21836. if ((chart instanceof ECharts) && !chart.isDisposed()) {
  21837. chart.dispose();
  21838. }
  21839. }
  21840. /**
  21841. * @param {HTMLElement} dom
  21842. * @return {echarts~ECharts}
  21843. */
  21844. function getInstanceByDom(dom) {
  21845. return instances[getAttribute(dom, DOM_ATTRIBUTE_KEY)];
  21846. }
  21847. /**
  21848. * @param {string} key
  21849. * @return {echarts~ECharts}
  21850. */
  21851. function getInstanceById(key) {
  21852. return instances[key];
  21853. }
  21854. /**
  21855. * Register theme
  21856. */
  21857. function registerTheme(name, theme$$1) {
  21858. themeStorage[name] = theme$$1;
  21859. }
  21860. /**
  21861. * Register option preprocessor
  21862. * @param {Function} preprocessorFunc
  21863. */
  21864. function registerPreprocessor(preprocessorFunc) {
  21865. optionPreprocessorFuncs.push(preprocessorFunc);
  21866. }
  21867. /**
  21868. * @param {number} [priority=1000]
  21869. * @param {Object|Function} processor
  21870. */
  21871. function registerProcessor(priority, processor) {
  21872. normalizeRegister(dataProcessorFuncs, priority, processor, PRIORITY_PROCESSOR_FILTER);
  21873. }
  21874. /**
  21875. * Register postUpdater
  21876. * @param {Function} postUpdateFunc
  21877. */
  21878. function registerPostUpdate(postUpdateFunc) {
  21879. postUpdateFuncs.push(postUpdateFunc);
  21880. }
  21881. /**
  21882. * Usage:
  21883. * registerAction('someAction', 'someEvent', function () { ... });
  21884. * registerAction('someAction', function () { ... });
  21885. * registerAction(
  21886. * {type: 'someAction', event: 'someEvent', update: 'updateView'},
  21887. * function () { ... }
  21888. * );
  21889. *
  21890. * @param {(string|Object)} actionInfo
  21891. * @param {string} actionInfo.type
  21892. * @param {string} [actionInfo.event]
  21893. * @param {string} [actionInfo.update]
  21894. * @param {string} [eventName]
  21895. * @param {Function} action
  21896. */
  21897. function registerAction(actionInfo, eventName, action) {
  21898. if (typeof eventName === 'function') {
  21899. action = eventName;
  21900. eventName = '';
  21901. }
  21902. var actionType = isObject(actionInfo)
  21903. ? actionInfo.type
  21904. : ([actionInfo, actionInfo = {
  21905. event: eventName
  21906. }][0]);
  21907. // Event name is all lowercase
  21908. actionInfo.event = (actionInfo.event || actionType).toLowerCase();
  21909. eventName = actionInfo.event;
  21910. // Validate action type and event name.
  21911. assert(ACTION_REG.test(actionType) && ACTION_REG.test(eventName));
  21912. if (!actions[actionType]) {
  21913. actions[actionType] = {action: action, actionInfo: actionInfo};
  21914. }
  21915. eventActionMap[eventName] = actionType;
  21916. }
  21917. /**
  21918. * @param {string} type
  21919. * @param {*} CoordinateSystem
  21920. */
  21921. function registerCoordinateSystem(type, CoordinateSystem$$1) {
  21922. CoordinateSystemManager.register(type, CoordinateSystem$$1);
  21923. }
  21924. /**
  21925. * Get dimensions of specified coordinate system.
  21926. * @param {string} type
  21927. * @return {Array.<string|Object>}
  21928. */
  21929. function getCoordinateSystemDimensions(type) {
  21930. var coordSysCreator = CoordinateSystemManager.get(type);
  21931. if (coordSysCreator) {
  21932. return coordSysCreator.getDimensionsInfo
  21933. ? coordSysCreator.getDimensionsInfo()
  21934. : coordSysCreator.dimensions.slice();
  21935. }
  21936. }
  21937. /**
  21938. * Layout is a special stage of visual encoding
  21939. * Most visual encoding like color are common for different chart
  21940. * But each chart has it's own layout algorithm
  21941. *
  21942. * @param {number} [priority=1000]
  21943. * @param {Function} layoutTask
  21944. */
  21945. function registerLayout(priority, layoutTask) {
  21946. normalizeRegister(visualFuncs, priority, layoutTask, PRIORITY_VISUAL_LAYOUT, 'layout');
  21947. }
  21948. /**
  21949. * @param {number} [priority=3000]
  21950. * @param {module:echarts/stream/Task} visualTask
  21951. */
  21952. function registerVisual(priority, visualTask) {
  21953. normalizeRegister(visualFuncs, priority, visualTask, PRIORITY_VISUAL_CHART, 'visual');
  21954. }
  21955. /**
  21956. * @param {Object|Function} fn: {seriesType, createOnAllSeries, performRawSeries, reset}
  21957. */
  21958. function normalizeRegister(targetList, priority, fn, defaultPriority, visualType) {
  21959. if (isFunction(priority) || isObject(priority)) {
  21960. fn = priority;
  21961. priority = defaultPriority;
  21962. }
  21963. if (__DEV__) {
  21964. if (isNaN(priority) || priority == null) {
  21965. throw new Error('Illegal priority');
  21966. }
  21967. // Check duplicate
  21968. each(targetList, function (wrap) {
  21969. assert(wrap.__raw !== fn);
  21970. });
  21971. }
  21972. var stageHandler = Scheduler.wrapStageHandler(fn, visualType);
  21973. stageHandler.__prio = priority;
  21974. stageHandler.__raw = fn;
  21975. targetList.push(stageHandler);
  21976. return stageHandler;
  21977. }
  21978. /**
  21979. * @param {string} name
  21980. */
  21981. function registerLoading(name, loadingFx) {
  21982. loadingEffects[name] = loadingFx;
  21983. }
  21984. /**
  21985. * @param {Object} opts
  21986. * @param {string} [superClass]
  21987. */
  21988. function extendComponentModel(opts/*, superClass*/) {
  21989. // var Clazz = ComponentModel;
  21990. // if (superClass) {
  21991. // var classType = parseClassType(superClass);
  21992. // Clazz = ComponentModel.getClass(classType.main, classType.sub, true);
  21993. // }
  21994. return ComponentModel.extend(opts);
  21995. }
  21996. /**
  21997. * @param {Object} opts
  21998. * @param {string} [superClass]
  21999. */
  22000. function extendComponentView(opts/*, superClass*/) {
  22001. // var Clazz = ComponentView;
  22002. // if (superClass) {
  22003. // var classType = parseClassType(superClass);
  22004. // Clazz = ComponentView.getClass(classType.main, classType.sub, true);
  22005. // }
  22006. return Component.extend(opts);
  22007. }
  22008. /**
  22009. * @param {Object} opts
  22010. * @param {string} [superClass]
  22011. */
  22012. function extendSeriesModel(opts/*, superClass*/) {
  22013. // var Clazz = SeriesModel;
  22014. // if (superClass) {
  22015. // superClass = 'series.' + superClass.replace('series.', '');
  22016. // var classType = parseClassType(superClass);
  22017. // Clazz = ComponentModel.getClass(classType.main, classType.sub, true);
  22018. // }
  22019. return SeriesModel.extend(opts);
  22020. }
  22021. /**
  22022. * @param {Object} opts
  22023. * @param {string} [superClass]
  22024. */
  22025. function extendChartView(opts/*, superClass*/) {
  22026. // var Clazz = ChartView;
  22027. // if (superClass) {
  22028. // superClass = superClass.replace('series.', '');
  22029. // var classType = parseClassType(superClass);
  22030. // Clazz = ChartView.getClass(classType.main, true);
  22031. // }
  22032. return Chart.extend(opts);
  22033. }
  22034. /**
  22035. * ZRender need a canvas context to do measureText.
  22036. * But in node environment canvas may be created by node-canvas.
  22037. * So we need to specify how to create a canvas instead of using document.createElement('canvas')
  22038. *
  22039. * Be careful of using it in the browser.
  22040. *
  22041. * @param {Function} creator
  22042. * @example
  22043. * var Canvas = require('canvas');
  22044. * var echarts = require('echarts');
  22045. * echarts.setCanvasCreator(function () {
  22046. * // Small size is enough.
  22047. * return new Canvas(32, 32);
  22048. * });
  22049. */
  22050. function setCanvasCreator(creator) {
  22051. $override('createCanvas', creator);
  22052. }
  22053. /**
  22054. * @param {string} mapName
  22055. * @param {Object|string} geoJson
  22056. * @param {Object} [specialAreas]
  22057. *
  22058. * @example
  22059. * $.get('USA.json', function (geoJson) {
  22060. * echarts.registerMap('USA', geoJson);
  22061. * // Or
  22062. * echarts.registerMap('USA', {
  22063. * geoJson: geoJson,
  22064. * specialAreas: {}
  22065. * })
  22066. * });
  22067. */
  22068. function registerMap(mapName, geoJson, specialAreas) {
  22069. if (geoJson.geoJson && !geoJson.features) {
  22070. specialAreas = geoJson.specialAreas;
  22071. geoJson = geoJson.geoJson;
  22072. }
  22073. if (typeof geoJson === 'string') {
  22074. geoJson = (typeof JSON !== 'undefined' && JSON.parse)
  22075. ? JSON.parse(geoJson) : (new Function('return (' + geoJson + ');'))();
  22076. }
  22077. mapDataStores[mapName] = {
  22078. geoJson: geoJson,
  22079. specialAreas: specialAreas
  22080. };
  22081. }
  22082. /**
  22083. * @param {string} mapName
  22084. * @return {Object}
  22085. */
  22086. function getMap(mapName) {
  22087. return mapDataStores[mapName];
  22088. }
  22089. registerVisual(PRIORITY_VISUAL_GLOBAL, seriesColor);
  22090. registerPreprocessor(backwardCompat);
  22091. registerProcessor(PRIORITY_PROCESSOR_STATISTIC, dataStack);
  22092. registerLoading('default', loadingDefault);
  22093. // Default actions
  22094. registerAction({
  22095. type: 'highlight',
  22096. event: 'highlight',
  22097. update: 'highlight'
  22098. }, noop);
  22099. registerAction({
  22100. type: 'downplay',
  22101. event: 'downplay',
  22102. update: 'downplay'
  22103. }, noop);
  22104. // Default theme
  22105. registerTheme('light', lightTheme);
  22106. registerTheme('dark', theme);
  22107. // For backward compatibility, where the namespace `dataTool` will
  22108. // be mounted on `echarts` is the extension `dataTool` is imported.
  22109. var dataTool = {};
  22110. var DatasetModel = extendComponentModel({
  22111. type: 'dataset',
  22112. /**
  22113. * @protected
  22114. */
  22115. defaultOption: {
  22116. // 'row', 'column'
  22117. seriesLayoutBy: SERIES_LAYOUT_BY_COLUMN,
  22118. // null/'auto': auto detect header, see "module:echarts/data/helper/sourceHelper"
  22119. sourceHeader: null,
  22120. dimensions: null,
  22121. source: null
  22122. },
  22123. optionUpdated: function () {
  22124. detectSourceFormat(this);
  22125. }
  22126. });
  22127. extendComponentView({type: 'dataset'});
  22128. function defaultKeyGetter(item) {
  22129. return item;
  22130. }
  22131. /**
  22132. * @param {Array} oldArr
  22133. * @param {Array} newArr
  22134. * @param {Function} oldKeyGetter
  22135. * @param {Function} newKeyGetter
  22136. * @param {Object} [context] Can be visited by this.context in callback.
  22137. */
  22138. function DataDiffer(oldArr, newArr, oldKeyGetter, newKeyGetter, context) {
  22139. this._old = oldArr;
  22140. this._new = newArr;
  22141. this._oldKeyGetter = oldKeyGetter || defaultKeyGetter;
  22142. this._newKeyGetter = newKeyGetter || defaultKeyGetter;
  22143. this.context = context;
  22144. }
  22145. DataDiffer.prototype = {
  22146. constructor: DataDiffer,
  22147. /**
  22148. * Callback function when add a data
  22149. */
  22150. add: function (func) {
  22151. this._add = func;
  22152. return this;
  22153. },
  22154. /**
  22155. * Callback function when update a data
  22156. */
  22157. update: function (func) {
  22158. this._update = func;
  22159. return this;
  22160. },
  22161. /**
  22162. * Callback function when remove a data
  22163. */
  22164. remove: function (func) {
  22165. this._remove = func;
  22166. return this;
  22167. },
  22168. execute: function () {
  22169. var oldArr = this._old;
  22170. var newArr = this._new;
  22171. var oldDataIndexMap = {};
  22172. var newDataIndexMap = {};
  22173. var oldDataKeyArr = [];
  22174. var newDataKeyArr = [];
  22175. var i;
  22176. initIndexMap(oldArr, oldDataIndexMap, oldDataKeyArr, '_oldKeyGetter', this);
  22177. initIndexMap(newArr, newDataIndexMap, newDataKeyArr, '_newKeyGetter', this);
  22178. // Travel by inverted order to make sure order consistency
  22179. // when duplicate keys exists (consider newDataIndex.pop() below).
  22180. // For performance consideration, these code below do not look neat.
  22181. for (i = 0; i < oldArr.length; i++) {
  22182. var key = oldDataKeyArr[i];
  22183. var idx = newDataIndexMap[key];
  22184. // idx can never be empty array here. see 'set null' logic below.
  22185. if (idx != null) {
  22186. // Consider there is duplicate key (for example, use dataItem.name as key).
  22187. // We should make sure every item in newArr and oldArr can be visited.
  22188. var len = idx.length;
  22189. if (len) {
  22190. len === 1 && (newDataIndexMap[key] = null);
  22191. idx = idx.unshift();
  22192. }
  22193. else {
  22194. newDataIndexMap[key] = null;
  22195. }
  22196. this._update && this._update(idx, i);
  22197. }
  22198. else {
  22199. this._remove && this._remove(i);
  22200. }
  22201. }
  22202. for (var i = 0; i < newDataKeyArr.length; i++) {
  22203. var key = newDataKeyArr[i];
  22204. if (newDataIndexMap.hasOwnProperty(key)) {
  22205. var idx = newDataIndexMap[key];
  22206. if (idx == null) {
  22207. continue;
  22208. }
  22209. // idx can never be empty array here. see 'set null' logic above.
  22210. if (!idx.length) {
  22211. this._add && this._add(idx);
  22212. }
  22213. else {
  22214. for (var j = 0, len = idx.length; j < len; j++) {
  22215. this._add && this._add(idx[j]);
  22216. }
  22217. }
  22218. }
  22219. }
  22220. }
  22221. };
  22222. function initIndexMap(arr, map, keyArr, keyGetterName, dataDiffer) {
  22223. for (var i = 0; i < arr.length; i++) {
  22224. // Add prefix to avoid conflict with Object.prototype.
  22225. var key = '_ec_' + dataDiffer[keyGetterName](arr[i], i);
  22226. var existence = map[key];
  22227. if (existence == null) {
  22228. keyArr.push(key);
  22229. map[key] = i;
  22230. }
  22231. else {
  22232. if (!existence.length) {
  22233. map[key] = existence = [existence];
  22234. }
  22235. existence.push(i);
  22236. }
  22237. }
  22238. }
  22239. var OTHER_DIMENSIONS = createHashMap([
  22240. 'tooltip', 'label', 'itemName', 'itemId', 'seriesName'
  22241. ]);
  22242. function summarizeDimensions(data) {
  22243. var summary = {};
  22244. var encode = summary.encode = {};
  22245. var notExtraCoordDimMap = createHashMap();
  22246. var defaultedLabel = [];
  22247. each$1(data.dimensions, function (dimName) {
  22248. var dimItem = data.getDimensionInfo(dimName);
  22249. var coordDim = dimItem.coordDim;
  22250. if (coordDim) {
  22251. if (__DEV__) {
  22252. assert$1(OTHER_DIMENSIONS.get(coordDim) == null);
  22253. }
  22254. var coordDimArr = encode[coordDim];
  22255. if (!encode.hasOwnProperty(coordDim)) {
  22256. coordDimArr = encode[coordDim] = [];
  22257. }
  22258. coordDimArr[dimItem.coordDimIndex] = dimName;
  22259. if (!dimItem.isExtraCoord) {
  22260. notExtraCoordDimMap.set(coordDim, 1);
  22261. // Use the last coord dim (and label friendly) as default label,
  22262. // because when dataset is used, it is hard to guess which dimension
  22263. // can be value dimension. If both show x, y on label is not look good,
  22264. // and conventionally y axis is focused more.
  22265. if (mayLabelDimType(dimItem.type)) {
  22266. defaultedLabel[0] = dimName;
  22267. }
  22268. }
  22269. }
  22270. OTHER_DIMENSIONS.each(function (v, otherDim) {
  22271. var otherDimArr = encode[otherDim];
  22272. if (!encode.hasOwnProperty(otherDim)) {
  22273. otherDimArr = encode[otherDim] = [];
  22274. }
  22275. var dimIndex = dimItem.otherDims[otherDim];
  22276. if (dimIndex != null && dimIndex !== false) {
  22277. otherDimArr[dimIndex] = dimItem.name;
  22278. }
  22279. });
  22280. });
  22281. var dataDimsOnCoord = [];
  22282. var encodeFirstDimNotExtra = {};
  22283. notExtraCoordDimMap.each(function (v, coordDim) {
  22284. var dimArr = encode[coordDim];
  22285. // ??? FIXME extra coord should not be set in dataDimsOnCoord.
  22286. // But should fix the case that radar axes: simplify the logic
  22287. // of `completeDimension`, remove `extraPrefix`.
  22288. encodeFirstDimNotExtra[coordDim] = dimArr[0];
  22289. // Not necessary to remove duplicate, because a data
  22290. // dim canot on more than one coordDim.
  22291. dataDimsOnCoord = dataDimsOnCoord.concat(dimArr);
  22292. });
  22293. summary.dataDimsOnCoord = dataDimsOnCoord;
  22294. summary.encodeFirstDimNotExtra = encodeFirstDimNotExtra;
  22295. var encodeLabel = encode.label;
  22296. // FIXME `encode.label` is not recommanded, because formatter can not be set
  22297. // in this way. Use label.formatter instead. May be remove this approach someday.
  22298. if (encodeLabel && encodeLabel.length) {
  22299. defaultedLabel = encodeLabel.slice();
  22300. }
  22301. var defaultedTooltip = defaultedLabel.slice();
  22302. var encodeTooltip = encode.tooltip;
  22303. if (encodeTooltip && encodeTooltip.length) {
  22304. defaultedTooltip = encodeTooltip.slice();
  22305. }
  22306. encode.defaultedLabel = defaultedLabel;
  22307. encode.defaultedTooltip = defaultedTooltip;
  22308. return summary;
  22309. }
  22310. function getDimensionTypeByAxis(axisType) {
  22311. return axisType === 'category'
  22312. ? 'ordinal'
  22313. : axisType === 'time'
  22314. ? 'time'
  22315. : 'float';
  22316. }
  22317. function mayLabelDimType(dimType) {
  22318. // In most cases, ordinal and time do not suitable for label.
  22319. // Ordinal info can be displayed on axis. Time is too long.
  22320. return !(dimType === 'ordinal' || dimType === 'time');
  22321. }
  22322. // function findTheLastDimMayLabel(data) {
  22323. // // Get last value dim
  22324. // var dimensions = data.dimensions.slice();
  22325. // var valueType;
  22326. // var valueDim;
  22327. // while (dimensions.length && (
  22328. // valueDim = dimensions.pop(),
  22329. // valueType = data.getDimensionInfo(valueDim).type,
  22330. // valueType === 'ordinal' || valueType === 'time'
  22331. // )) {} // jshint ignore:line
  22332. // return valueDim;
  22333. // }
  22334. /**
  22335. * List for data storage
  22336. * @module echarts/data/List
  22337. */
  22338. var isObject$4 = isObject$1;
  22339. var UNDEFINED = 'undefined';
  22340. var globalObj = typeof window === UNDEFINED ? global : window;
  22341. // Use prefix to avoid index to be the same as otherIdList[idx],
  22342. // which will cause weird udpate animation.
  22343. var ID_PREFIX = 'e\0\0';
  22344. var dataCtors = {
  22345. 'float': typeof globalObj.Float64Array === UNDEFINED
  22346. ? Array : globalObj.Float64Array,
  22347. 'int': typeof globalObj.Int32Array === UNDEFINED
  22348. ? Array : globalObj.Int32Array,
  22349. // Ordinal data type can be string or int
  22350. 'ordinal': Array,
  22351. 'number': Array,
  22352. 'time': Array
  22353. };
  22354. var CtorUint32Array = typeof globalObj.Uint32Array === UNDEFINED ? Array : globalObj.Uint32Array;
  22355. var CtorUint16Array = typeof globalObj.Uint16Array === UNDEFINED ? Array : globalObj.Uint16Array;
  22356. function getIndicesCtor(list) {
  22357. // The possible max value in this._indicies is always this._rawCount despite of filtering.
  22358. return list._rawCount > 65535 ? CtorUint32Array : CtorUint16Array;
  22359. }
  22360. function cloneChunk(originalChunk) {
  22361. var Ctor = originalChunk.constructor;
  22362. // Only shallow clone is enough when Array.
  22363. return Ctor === Array ? originalChunk.slice() : new Ctor(originalChunk);
  22364. }
  22365. var TRANSFERABLE_PROPERTIES = [
  22366. 'hasItemOption', '_nameList', '_idList', '_calculationInfo', '_invertedIndicesMap',
  22367. '_rawData', '_rawExtent', '_chunkSize', '_chunkCount',
  22368. '_dimValueGetter', '_count', '_rawCount', '_nameDimIdx', '_idDimIdx'
  22369. ];
  22370. function transferProperties(a, b) {
  22371. each$1(TRANSFERABLE_PROPERTIES.concat(b.__wrappedMethods || []), function (propName) {
  22372. if (b.hasOwnProperty(propName)) {
  22373. a[propName] = b[propName];
  22374. }
  22375. });
  22376. a.__wrappedMethods = b.__wrappedMethods;
  22377. }
  22378. /**
  22379. * @constructor
  22380. * @alias module:echarts/data/List
  22381. *
  22382. * @param {Array.<string|Object>} dimensions
  22383. * For example, ['someDimName', {name: 'someDimName', type: 'someDimType'}, ...].
  22384. * Dimensions should be concrete names like x, y, z, lng, lat, angle, radius
  22385. * Spetial fields: {
  22386. * ordinalMeta: <module:echarts/data/OrdinalMeta>
  22387. * createInvertedIndices: <boolean>
  22388. * }
  22389. * @param {module:echarts/model/Model} hostModel
  22390. */
  22391. var List = function (dimensions, hostModel) {
  22392. dimensions = dimensions || ['x', 'y'];
  22393. var dimensionInfos = {};
  22394. var dimensionNames = [];
  22395. var invertedIndicesMap = {};
  22396. for (var i = 0; i < dimensions.length; i++) {
  22397. // Use the original dimensions[i], where other flag props may exists.
  22398. var dimensionInfo = dimensions[i];
  22399. if (isString(dimensionInfo)) {
  22400. dimensionInfo = {name: dimensionInfo};
  22401. }
  22402. var dimensionName = dimensionInfo.name;
  22403. dimensionInfo.type = dimensionInfo.type || 'float';
  22404. if (!dimensionInfo.coordDim) {
  22405. dimensionInfo.coordDim = dimensionName;
  22406. dimensionInfo.coordDimIndex = 0;
  22407. }
  22408. dimensionInfo.otherDims = dimensionInfo.otherDims || {};
  22409. dimensionNames.push(dimensionName);
  22410. dimensionInfos[dimensionName] = dimensionInfo;
  22411. dimensionInfo.index = i;
  22412. if (dimensionInfo.createInvertedIndices) {
  22413. invertedIndicesMap[dimensionName] = [];
  22414. }
  22415. }
  22416. /**
  22417. * @readOnly
  22418. * @type {Array.<string>}
  22419. */
  22420. this.dimensions = dimensionNames;
  22421. /**
  22422. * Infomation of each data dimension, like data type.
  22423. * @type {Object}
  22424. */
  22425. this._dimensionInfos = dimensionInfos;
  22426. /**
  22427. * @type {module:echarts/model/Model}
  22428. */
  22429. this.hostModel = hostModel;
  22430. /**
  22431. * @type {module:echarts/model/Model}
  22432. */
  22433. this.dataType;
  22434. /**
  22435. * Indices stores the indices of data subset after filtered.
  22436. * This data subset will be used in chart.
  22437. * @type {Array.<number>}
  22438. * @readOnly
  22439. */
  22440. this._indices = null;
  22441. this._count = 0;
  22442. this._rawCount = 0;
  22443. /**
  22444. * Data storage
  22445. * @type {Object.<key, Array.<TypedArray|Array>>}
  22446. * @private
  22447. */
  22448. this._storage = {};
  22449. /**
  22450. * @type {Array.<string>}
  22451. */
  22452. this._nameList = [];
  22453. /**
  22454. * @type {Array.<string>}
  22455. */
  22456. this._idList = [];
  22457. /**
  22458. * Models of data option is stored sparse for optimizing memory cost
  22459. * @type {Array.<module:echarts/model/Model>}
  22460. * @private
  22461. */
  22462. this._optionModels = [];
  22463. /**
  22464. * Global visual properties after visual coding
  22465. * @type {Object}
  22466. * @private
  22467. */
  22468. this._visual = {};
  22469. /**
  22470. * Globel layout properties.
  22471. * @type {Object}
  22472. * @private
  22473. */
  22474. this._layout = {};
  22475. /**
  22476. * Item visual properties after visual coding
  22477. * @type {Array.<Object>}
  22478. * @private
  22479. */
  22480. this._itemVisuals = [];
  22481. /**
  22482. * Key: visual type, Value: boolean
  22483. * @type {Object}
  22484. * @readOnly
  22485. */
  22486. this.hasItemVisual = {};
  22487. /**
  22488. * Item layout properties after layout
  22489. * @type {Array.<Object>}
  22490. * @private
  22491. */
  22492. this._itemLayouts = [];
  22493. /**
  22494. * Graphic elemnents
  22495. * @type {Array.<module:zrender/Element>}
  22496. * @private
  22497. */
  22498. this._graphicEls = [];
  22499. /**
  22500. * Max size of each chunk.
  22501. * @type {number}
  22502. * @private
  22503. */
  22504. this._chunkSize = 1e5;
  22505. /**
  22506. * @type {number}
  22507. * @private
  22508. */
  22509. this._chunkCount = 0;
  22510. /**
  22511. * @type {Array.<Array|Object>}
  22512. * @private
  22513. */
  22514. this._rawData;
  22515. /**
  22516. * Raw extent will not be cloned, but only transfered.
  22517. * It will not be calculated util needed.
  22518. * key: dim,
  22519. * value: {end: number, extent: Array.<number>}
  22520. * @type {Object}
  22521. * @private
  22522. */
  22523. this._rawExtent = {};
  22524. /**
  22525. * @type {Object}
  22526. * @private
  22527. */
  22528. this._extent = {};
  22529. /**
  22530. * key: dim
  22531. * value: extent
  22532. * @type {Object}
  22533. * @private
  22534. */
  22535. this._approximateExtent = {};
  22536. /**
  22537. * Cache summary info for fast visit. See "dimensionHelper".
  22538. * @type {Object}
  22539. * @private
  22540. */
  22541. this._dimensionsSummary = summarizeDimensions(this);
  22542. /**
  22543. * @type {Object.<Array|TypedArray>}
  22544. * @private
  22545. */
  22546. this._invertedIndicesMap = invertedIndicesMap;
  22547. /**
  22548. * @type {Object}
  22549. * @private
  22550. */
  22551. this._calculationInfo = {};
  22552. };
  22553. var listProto = List.prototype;
  22554. listProto.type = 'list';
  22555. /**
  22556. * If each data item has it's own option
  22557. * @type {boolean}
  22558. */
  22559. listProto.hasItemOption = true;
  22560. /**
  22561. * Get dimension name
  22562. * @param {string|number} dim
  22563. * Dimension can be concrete names like x, y, z, lng, lat, angle, radius
  22564. * Or a ordinal number. For example getDimensionInfo(0) will return 'x' or 'lng' or 'radius'
  22565. * @return {string} Concrete dim name.
  22566. */
  22567. listProto.getDimension = function (dim) {
  22568. if (!isNaN(dim)) {
  22569. dim = this.dimensions[dim] || dim;
  22570. }
  22571. return dim;
  22572. };
  22573. /**
  22574. * Get type and calculation info of particular dimension
  22575. * @param {string|number} dim
  22576. * Dimension can be concrete names like x, y, z, lng, lat, angle, radius
  22577. * Or a ordinal number. For example getDimensionInfo(0) will return 'x' or 'lng' or 'radius'
  22578. */
  22579. listProto.getDimensionInfo = function (dim) {
  22580. // Do not clone, because there may be categories in dimInfo.
  22581. return this._dimensionInfos[this.getDimension(dim)];
  22582. };
  22583. /**
  22584. * @return {Array.<string>} concrete dimension name list on coord.
  22585. */
  22586. listProto.getDimensionsOnCoord = function () {
  22587. return this._dimensionsSummary.dataDimsOnCoord.slice();
  22588. };
  22589. /**
  22590. * @param {string} coordDim
  22591. * @param {number} [idx] A coordDim may map to more than one data dim.
  22592. * If idx is `true`, return a array of all mapped dims.
  22593. * If idx is not specified, return the first dim not extra.
  22594. * @return {string|Array.<string>} concrete data dim.
  22595. * If idx is number, and not found, return null/undefined.
  22596. * If idx is `true`, and not found, return empty array (always return array).
  22597. */
  22598. listProto.mapDimension = function (coordDim, idx) {
  22599. var dimensionsSummary = this._dimensionsSummary;
  22600. if (idx == null) {
  22601. return dimensionsSummary.encodeFirstDimNotExtra[coordDim];
  22602. }
  22603. var dims = dimensionsSummary.encode[coordDim];
  22604. return idx === true
  22605. // always return array if idx is `true`
  22606. ? (dims || []).slice()
  22607. : (dims && dims[idx]);
  22608. };
  22609. /**
  22610. * Initialize from data
  22611. * @param {Array.<Object|number|Array>} data source or data or data provider.
  22612. * @param {Array.<string>} [nameLIst] The name of a datum is used on data diff and
  22613. * defualt label/tooltip.
  22614. * A name can be specified in encode.itemName,
  22615. * or dataItem.name (only for series option data),
  22616. * or provided in nameList from outside.
  22617. * @param {Function} [dimValueGetter] (dataItem, dimName, dataIndex, dimIndex) => number
  22618. */
  22619. listProto.initData = function (data, nameList, dimValueGetter) {
  22620. var notProvider = Source.isInstance(data) || isArrayLike(data);
  22621. if (notProvider) {
  22622. data = new DefaultDataProvider(data, this.dimensions.length);
  22623. }
  22624. if (__DEV__) {
  22625. if (!notProvider && (typeof data.getItem != 'function' || typeof data.count != 'function')) {
  22626. throw new Error('Inavlid data provider.');
  22627. }
  22628. }
  22629. this._rawData = data;
  22630. // Clear
  22631. this._storage = {};
  22632. this._indices = null;
  22633. this._nameList = nameList || [];
  22634. this._idList = [];
  22635. this._nameRepeatCount = {};
  22636. if (!dimValueGetter) {
  22637. this.hasItemOption = false;
  22638. }
  22639. /**
  22640. * @readOnly
  22641. */
  22642. this.defaultDimValueGetter = defaultDimValueGetters[
  22643. this._rawData.getSource().sourceFormat
  22644. ];
  22645. // Default dim value getter
  22646. this._dimValueGetter = dimValueGetter = dimValueGetter
  22647. || this.defaultDimValueGetter;
  22648. // Reset raw extent.
  22649. this._rawExtent = {};
  22650. this._initDataFromProvider(0, data.count());
  22651. // If data has no item option.
  22652. if (data.pure) {
  22653. this.hasItemOption = false;
  22654. }
  22655. };
  22656. listProto.getProvider = function () {
  22657. return this._rawData;
  22658. };
  22659. listProto.appendData = function (data) {
  22660. if (__DEV__) {
  22661. assert$1(!this._indices, 'appendData can only be called on raw data.');
  22662. }
  22663. var rawData = this._rawData;
  22664. var start = this.count();
  22665. rawData.appendData(data);
  22666. var end = rawData.count();
  22667. if (!rawData.persistent) {
  22668. end += start;
  22669. }
  22670. this._initDataFromProvider(start, end);
  22671. };
  22672. listProto._initDataFromProvider = function (start, end) {
  22673. // Optimize.
  22674. if (start >= end) {
  22675. return;
  22676. }
  22677. var chunkSize = this._chunkSize;
  22678. var rawData = this._rawData;
  22679. var storage = this._storage;
  22680. var dimensions = this.dimensions;
  22681. var dimensionInfoMap = this._dimensionInfos;
  22682. var nameList = this._nameList;
  22683. var idList = this._idList;
  22684. var rawExtent = this._rawExtent;
  22685. var nameRepeatCount = this._nameRepeatCount = {};
  22686. var nameDimIdx;
  22687. var chunkCount = this._chunkCount;
  22688. var lastChunkIndex = chunkCount - 1;
  22689. for (var i = 0; i < dimensions.length; i++) {
  22690. var dim = dimensions[i];
  22691. if (!rawExtent[dim]) {
  22692. rawExtent[dim] = getInitialExtent();
  22693. }
  22694. var dimInfo = dimensionInfoMap[dim];
  22695. if (dimInfo.otherDims.itemName === 0) {
  22696. nameDimIdx = this._nameDimIdx = i;
  22697. }
  22698. if (dimInfo.otherDims.itemId === 0) {
  22699. this._idDimIdx = i;
  22700. }
  22701. var DataCtor = dataCtors[dimInfo.type];
  22702. if (!storage[dim]) {
  22703. storage[dim] = [];
  22704. }
  22705. var resizeChunkArray = storage[dim][lastChunkIndex];
  22706. if (resizeChunkArray && resizeChunkArray.length < chunkSize) {
  22707. var newStore = new DataCtor(Math.min(end - lastChunkIndex * chunkSize, chunkSize));
  22708. // The cost of the copy is probably inconsiderable
  22709. // within the initial chunkSize.
  22710. for (var j = 0; j < resizeChunkArray.length; j++) {
  22711. newStore[j] = resizeChunkArray[j];
  22712. }
  22713. storage[dim][lastChunkIndex] = newStore;
  22714. }
  22715. // Create new chunks.
  22716. for (var k = chunkCount * chunkSize; k < end; k += chunkSize) {
  22717. storage[dim].push(new DataCtor(Math.min(end - k, chunkSize)));
  22718. }
  22719. this._chunkCount = storage[dim].length;
  22720. }
  22721. for (var idx = start; idx < end; idx++) {
  22722. // NOTICE: Try not to write things into dataItem
  22723. var dataItem = rawData.getItem(idx);
  22724. // Each data item is value
  22725. // [1, 2]
  22726. // 2
  22727. // Bar chart, line chart which uses category axis
  22728. // only gives the 'y' value. 'x' value is the indices of category
  22729. // Use a tempValue to normalize the value to be a (x, y) value
  22730. var chunkIndex = Math.floor(idx / chunkSize);
  22731. var chunkOffset = idx % chunkSize;
  22732. // Store the data by dimensions
  22733. for (var k = 0; k < dimensions.length; k++) {
  22734. var dim = dimensions[k];
  22735. var dimStorage = storage[dim][chunkIndex];
  22736. // PENDING NULL is empty or zero
  22737. var val = this._dimValueGetter(dataItem, dim, idx, k);
  22738. dimStorage[chunkOffset] = val;
  22739. if (val < rawExtent[dim][0]) {
  22740. rawExtent[dim][0] = val;
  22741. }
  22742. if (val > rawExtent[dim][1]) {
  22743. rawExtent[dim][1] = val;
  22744. }
  22745. }
  22746. // ??? FIXME not check by pure but sourceFormat?
  22747. // TODO refactor these logic.
  22748. if (!rawData.pure) {
  22749. var name = nameList[idx];
  22750. if (dataItem && !name) {
  22751. if (nameDimIdx != null) {
  22752. name = this._getNameFromStore(idx);
  22753. }
  22754. else if (dataItem.name != null) {
  22755. // There is no other place to persistent dataItem.name,
  22756. // so save it to nameList.
  22757. nameList[idx] = name = dataItem.name;
  22758. }
  22759. }
  22760. // Try using the id in option
  22761. // id or name is used on dynamical data, mapping old and new items.
  22762. var id = dataItem == null ? null : dataItem.id;
  22763. if (id == null && name != null) {
  22764. // Use name as id and add counter to avoid same name
  22765. nameRepeatCount[name] = nameRepeatCount[name] || 0;
  22766. id = name;
  22767. if (nameRepeatCount[name] > 0) {
  22768. id += '__ec__' + nameRepeatCount[name];
  22769. }
  22770. nameRepeatCount[name]++;
  22771. }
  22772. id != null && (idList[idx] = id);
  22773. }
  22774. }
  22775. if (!rawData.persistent && rawData.clean) {
  22776. // Clean unused data if data source is typed array.
  22777. rawData.clean();
  22778. }
  22779. this._rawCount = this._count = end;
  22780. // Reset data extent
  22781. this._extent = {};
  22782. prepareInvertedIndex(this);
  22783. };
  22784. function prepareInvertedIndex(list) {
  22785. var invertedIndicesMap = list._invertedIndicesMap;
  22786. each$1(invertedIndicesMap, function (invertedIndices, dim) {
  22787. var dimInfo = list._dimensionInfos[dim];
  22788. // Currently, only dimensions that has ordinalMeta can create inverted indices.
  22789. var ordinalMeta = dimInfo.ordinalMeta;
  22790. if (ordinalMeta) {
  22791. invertedIndices = invertedIndicesMap[dim] = new CtorUint32Array(
  22792. ordinalMeta.categories.length
  22793. );
  22794. // The default value of TypedArray is 0. To avoid miss
  22795. // mapping to 0, we should set it as NaN.
  22796. for (var i = 0; i < invertedIndices.length; i++) {
  22797. invertedIndices[i] = NaN;
  22798. }
  22799. for (var i = 0; i < list._count; i++) {
  22800. // Only support the case that all values are distinct.
  22801. invertedIndices[list.get(dim, i)] = i;
  22802. }
  22803. }
  22804. });
  22805. }
  22806. // TODO refactor
  22807. listProto._getNameFromStore = function (rawIndex) {
  22808. var nameDimIdx = this._nameDimIdx;
  22809. if (nameDimIdx != null) {
  22810. var chunkSize = this._chunkSize;
  22811. var chunkIndex = Math.floor(rawIndex / chunkSize);
  22812. var chunkOffset = rawIndex % chunkSize;
  22813. var dim = this.dimensions[nameDimIdx];
  22814. var ordinalMeta = this._dimensionInfos[dim].ordinalMeta;
  22815. if (ordinalMeta) {
  22816. return ordinalMeta.categories[rawIndex];
  22817. }
  22818. else {
  22819. var chunk = this._storage[dim][chunkIndex];
  22820. return chunk && chunk[chunkOffset];
  22821. }
  22822. }
  22823. };
  22824. // TODO refactor
  22825. listProto._getIdFromStore = function (rawIndex) {
  22826. var idDimIdx = this._idDimIdx;
  22827. if (idDimIdx != null) {
  22828. var chunkSize = this._chunkSize;
  22829. var chunkIndex = Math.floor(rawIndex / chunkSize);
  22830. var chunkOffset = rawIndex % chunkSize;
  22831. var dim = this.dimensions[idDimIdx];
  22832. var ordinalMeta = this._dimensionInfos[dim].ordinalMeta;
  22833. if (ordinalMeta) {
  22834. return ordinalMeta.categories[rawIndex];
  22835. }
  22836. else {
  22837. var chunk = this._storage[dim][chunkIndex];
  22838. return chunk && chunk[chunkOffset];
  22839. }
  22840. }
  22841. };
  22842. /**
  22843. * @return {number}
  22844. */
  22845. listProto.count = function () {
  22846. return this._count;
  22847. };
  22848. listProto.getIndices = function () {
  22849. if (this._indices) {
  22850. var Ctor = this._indices.constructor;
  22851. return new Ctor(this._indices.buffer, 0, this._count);
  22852. }
  22853. var Ctor = getIndicesCtor(this);
  22854. var arr = new Ctor(this.count());
  22855. for (var i = 0; i < arr.length; i++) {
  22856. arr[i] = i;
  22857. }
  22858. return arr;
  22859. };
  22860. /**
  22861. * Get value. Return NaN if idx is out of range.
  22862. * @param {string} dim Dim must be concrete name.
  22863. * @param {number} idx
  22864. * @param {boolean} stack
  22865. * @return {number}
  22866. */
  22867. listProto.get = function (dim, idx /*, stack */) {
  22868. if (!(idx >= 0 && idx < this._count)) {
  22869. return NaN;
  22870. }
  22871. var storage = this._storage;
  22872. if (!storage[dim]) {
  22873. // TODO Warn ?
  22874. return NaN;
  22875. }
  22876. idx = this.getRawIndex(idx);
  22877. var chunkIndex = Math.floor(idx / this._chunkSize);
  22878. var chunkOffset = idx % this._chunkSize;
  22879. var chunkStore = storage[dim][chunkIndex];
  22880. var value = chunkStore[chunkOffset];
  22881. // FIXME ordinal data type is not stackable
  22882. // if (stack) {
  22883. // var dimensionInfo = this._dimensionInfos[dim];
  22884. // if (dimensionInfo && dimensionInfo.stackable) {
  22885. // var stackedOn = this.stackedOn;
  22886. // while (stackedOn) {
  22887. // // Get no stacked data of stacked on
  22888. // var stackedValue = stackedOn.get(dim, idx);
  22889. // // Considering positive stack, negative stack and empty data
  22890. // if ((value >= 0 && stackedValue > 0) // Positive stack
  22891. // || (value <= 0 && stackedValue < 0) // Negative stack
  22892. // ) {
  22893. // value += stackedValue;
  22894. // }
  22895. // stackedOn = stackedOn.stackedOn;
  22896. // }
  22897. // }
  22898. // }
  22899. return value;
  22900. };
  22901. /**
  22902. * @param {string} dim concrete dim
  22903. * @param {number} rawIndex
  22904. * @return {number|string}
  22905. */
  22906. listProto.getByRawIndex = function (dim, rawIdx) {
  22907. if (!(rawIdx >= 0 && rawIdx < this._rawCount)) {
  22908. return NaN;
  22909. }
  22910. var dimStore = this._storage[dim];
  22911. if (!dimStore) {
  22912. // TODO Warn ?
  22913. return NaN;
  22914. }
  22915. var chunkIndex = Math.floor(rawIdx / this._chunkSize);
  22916. var chunkOffset = rawIdx % this._chunkSize;
  22917. var chunkStore = dimStore[chunkIndex];
  22918. return chunkStore[chunkOffset];
  22919. };
  22920. /**
  22921. * FIXME Use `get` on chrome maybe slow(in filterSelf and selectRange).
  22922. * Hack a much simpler _getFast
  22923. * @private
  22924. */
  22925. listProto._getFast = function (dim, rawIdx) {
  22926. var chunkIndex = Math.floor(rawIdx / this._chunkSize);
  22927. var chunkOffset = rawIdx % this._chunkSize;
  22928. var chunkStore = this._storage[dim][chunkIndex];
  22929. return chunkStore[chunkOffset];
  22930. };
  22931. /**
  22932. * Get value for multi dimensions.
  22933. * @param {Array.<string>} [dimensions] If ignored, using all dimensions.
  22934. * @param {number} idx
  22935. * @return {number}
  22936. */
  22937. listProto.getValues = function (dimensions, idx /*, stack */) {
  22938. var values = [];
  22939. if (!isArray(dimensions)) {
  22940. // stack = idx;
  22941. idx = dimensions;
  22942. dimensions = this.dimensions;
  22943. }
  22944. for (var i = 0, len = dimensions.length; i < len; i++) {
  22945. values.push(this.get(dimensions[i], idx /*, stack */));
  22946. }
  22947. return values;
  22948. };
  22949. /**
  22950. * If value is NaN. Inlcuding '-'
  22951. * Only check the coord dimensions.
  22952. * @param {string} dim
  22953. * @param {number} idx
  22954. * @return {number}
  22955. */
  22956. listProto.hasValue = function (idx) {
  22957. var dataDimsOnCoord = this._dimensionsSummary.dataDimsOnCoord;
  22958. var dimensionInfos = this._dimensionInfos;
  22959. for (var i = 0, len = dataDimsOnCoord.length; i < len; i++) {
  22960. if (
  22961. // Ordinal type can be string or number
  22962. dimensionInfos[dataDimsOnCoord[i]].type !== 'ordinal'
  22963. // FIXME check ordinal when using index?
  22964. && isNaN(this.get(dataDimsOnCoord[i], idx))
  22965. ) {
  22966. return false;
  22967. }
  22968. }
  22969. return true;
  22970. };
  22971. /**
  22972. * Get extent of data in one dimension
  22973. * @param {string} dim
  22974. * @param {boolean} stack
  22975. */
  22976. listProto.getDataExtent = function (dim /*, stack */) {
  22977. // Make sure use concrete dim as cache name.
  22978. dim = this.getDimension(dim);
  22979. var dimData = this._storage[dim];
  22980. var initialExtent = getInitialExtent();
  22981. // stack = !!((stack || false) && this.getCalculationInfo(dim));
  22982. if (!dimData) {
  22983. return initialExtent;
  22984. }
  22985. // Make more strict checkings to ensure hitting cache.
  22986. var currEnd = this.count();
  22987. // var cacheName = [dim, !!stack].join('_');
  22988. // var cacheName = dim;
  22989. // Consider the most cases when using data zoom, `getDataExtent`
  22990. // happened before filtering. We cache raw extent, which is not
  22991. // necessary to be cleared and recalculated when restore data.
  22992. var useRaw = !this._indices; // && !stack;
  22993. var dimExtent;
  22994. if (useRaw) {
  22995. return this._rawExtent[dim].slice();
  22996. }
  22997. dimExtent = this._extent[dim];
  22998. if (dimExtent) {
  22999. return dimExtent.slice();
  23000. }
  23001. dimExtent = initialExtent;
  23002. var min = dimExtent[0];
  23003. var max = dimExtent[1];
  23004. for (var i = 0; i < currEnd; i++) {
  23005. // var value = stack ? this.get(dim, i, true) : this._getFast(dim, this.getRawIndex(i));
  23006. var value = this._getFast(dim, this.getRawIndex(i));
  23007. value < min && (min = value);
  23008. value > max && (max = value);
  23009. }
  23010. dimExtent = [min, max];
  23011. this._extent[dim] = dimExtent;
  23012. return dimExtent;
  23013. };
  23014. /**
  23015. * Optimize for the scenario that data is filtered by a given extent.
  23016. * Consider that if data amount is more than hundreds of thousand,
  23017. * extent calculation will cost more than 10ms and the cache will
  23018. * be erased because of the filtering.
  23019. */
  23020. listProto.getApproximateExtent = function (dim /*, stack */) {
  23021. dim = this.getDimension(dim);
  23022. return this._approximateExtent[dim] || this.getDataExtent(dim /*, stack */);
  23023. };
  23024. listProto.setApproximateExtent = function (extent, dim /*, stack */) {
  23025. dim = this.getDimension(dim);
  23026. this._approximateExtent[dim] = extent.slice();
  23027. };
  23028. /**
  23029. * @param {string} key
  23030. * @return {*}
  23031. */
  23032. listProto.getCalculationInfo = function (key) {
  23033. return this._calculationInfo[key];
  23034. };
  23035. /**
  23036. * @param {string|Object} key or k-v object
  23037. * @param {*} [value]
  23038. */
  23039. listProto.setCalculationInfo = function (key, value) {
  23040. isObject$4(key)
  23041. ? extend(this._calculationInfo, key)
  23042. : (this._calculationInfo[key] = value);
  23043. };
  23044. /**
  23045. * Get sum of data in one dimension
  23046. * @param {string} dim
  23047. */
  23048. listProto.getSum = function (dim /*, stack */) {
  23049. var dimData = this._storage[dim];
  23050. var sum = 0;
  23051. if (dimData) {
  23052. for (var i = 0, len = this.count(); i < len; i++) {
  23053. var value = this.get(dim, i /*, stack */);
  23054. if (!isNaN(value)) {
  23055. sum += value;
  23056. }
  23057. }
  23058. }
  23059. return sum;
  23060. };
  23061. // /**
  23062. // * Retreive the index with given value
  23063. // * @param {string} dim Concrete dimension.
  23064. // * @param {number} value
  23065. // * @return {number}
  23066. // */
  23067. // Currently incorrect: should return dataIndex but not rawIndex.
  23068. // Do not fix it until this method is to be used somewhere.
  23069. // FIXME Precision of float value
  23070. // listProto.indexOf = function (dim, value) {
  23071. // var storage = this._storage;
  23072. // var dimData = storage[dim];
  23073. // var chunkSize = this._chunkSize;
  23074. // if (dimData) {
  23075. // for (var i = 0, len = this.count(); i < len; i++) {
  23076. // var chunkIndex = Math.floor(i / chunkSize);
  23077. // var chunkOffset = i % chunkSize;
  23078. // if (dimData[chunkIndex][chunkOffset] === value) {
  23079. // return i;
  23080. // }
  23081. // }
  23082. // }
  23083. // return -1;
  23084. // };
  23085. /**
  23086. * Only support the dimension which inverted index created.
  23087. * Do not support other cases until required.
  23088. * @param {string} concrete dim
  23089. * @param {number|string} value
  23090. * @return {number} rawIndex
  23091. */
  23092. listProto.rawIndexOf = function (dim, value) {
  23093. var invertedIndices = dim && this._invertedIndicesMap[dim];
  23094. if (__DEV__) {
  23095. if (!invertedIndices) {
  23096. throw new Error('Do not supported yet');
  23097. }
  23098. }
  23099. var rawIndex = invertedIndices[value];
  23100. if (rawIndex == null || isNaN(rawIndex)) {
  23101. return -1;
  23102. }
  23103. return rawIndex;
  23104. };
  23105. /**
  23106. * Retreive the index with given name
  23107. * @param {number} idx
  23108. * @param {number} name
  23109. * @return {number}
  23110. */
  23111. listProto.indexOfName = function (name) {
  23112. for (var i = 0, len = this.count(); i < len; i++) {
  23113. if (this.getName(i) === name) {
  23114. return i;
  23115. }
  23116. }
  23117. return -1;
  23118. };
  23119. /**
  23120. * Retreive the index with given raw data index
  23121. * @param {number} idx
  23122. * @param {number} name
  23123. * @return {number}
  23124. */
  23125. listProto.indexOfRawIndex = function (rawIndex) {
  23126. if (!this._indices) {
  23127. return rawIndex;
  23128. }
  23129. if (rawIndex >= this._rawCount || rawIndex < 0) {
  23130. return -1;
  23131. }
  23132. // Indices are ascending
  23133. var indices = this._indices;
  23134. // If rawIndex === dataIndex
  23135. var rawDataIndex = indices[rawIndex];
  23136. if (rawDataIndex != null && rawDataIndex < this._count && rawDataIndex === rawIndex) {
  23137. return rawIndex;
  23138. }
  23139. var left = 0;
  23140. var right = this._count - 1;
  23141. while (left <= right) {
  23142. var mid = (left + right) / 2 | 0;
  23143. if (indices[mid] < rawIndex) {
  23144. left = mid + 1;
  23145. }
  23146. else if (indices[mid] > rawIndex) {
  23147. right = mid - 1;
  23148. }
  23149. else {
  23150. return mid;
  23151. }
  23152. }
  23153. return -1;
  23154. };
  23155. /**
  23156. * Retreive the index of nearest value
  23157. * @param {string} dim
  23158. * @param {number} value
  23159. * @param {number} [maxDistance=Infinity]
  23160. * @return {Array.<number>} Considere multiple points has the same value.
  23161. */
  23162. listProto.indicesOfNearest = function (dim, value, maxDistance) {
  23163. var storage = this._storage;
  23164. var dimData = storage[dim];
  23165. var nearestIndices = [];
  23166. if (!dimData) {
  23167. return nearestIndices;
  23168. }
  23169. if (maxDistance == null) {
  23170. maxDistance = Infinity;
  23171. }
  23172. var minDist = Number.MAX_VALUE;
  23173. var minDiff = -1;
  23174. for (var i = 0, len = this.count(); i < len; i++) {
  23175. var diff = value - this.get(dim, i /*, stack */);
  23176. var dist = Math.abs(diff);
  23177. if (diff <= maxDistance && dist <= minDist) {
  23178. // For the case of two data are same on xAxis, which has sequence data.
  23179. // Show the nearest index
  23180. // https://github.com/ecomfe/echarts/issues/2869
  23181. if (dist < minDist || (diff >= 0 && minDiff < 0)) {
  23182. minDist = dist;
  23183. minDiff = diff;
  23184. nearestIndices.length = 0;
  23185. }
  23186. nearestIndices.push(i);
  23187. }
  23188. }
  23189. return nearestIndices;
  23190. };
  23191. /**
  23192. * Get raw data index
  23193. * @param {number} idx
  23194. * @return {number}
  23195. */
  23196. listProto.getRawIndex = getRawIndexWithoutIndices;
  23197. function getRawIndexWithoutIndices(idx) {
  23198. return idx;
  23199. }
  23200. function getRawIndexWithIndices(idx) {
  23201. if (idx < this._count && idx >= 0) {
  23202. return this._indices[idx];
  23203. }
  23204. return -1;
  23205. }
  23206. /**
  23207. * Get raw data item
  23208. * @param {number} idx
  23209. * @return {number}
  23210. */
  23211. listProto.getRawDataItem = function (idx) {
  23212. if (!this._rawData.persistent) {
  23213. var val = [];
  23214. for (var i = 0; i < this.dimensions.length; i++) {
  23215. var dim = this.dimensions[i];
  23216. val.push(this.get(dim, idx));
  23217. }
  23218. return val;
  23219. }
  23220. else {
  23221. return this._rawData.getItem(this.getRawIndex(idx));
  23222. }
  23223. };
  23224. /**
  23225. * @param {number} idx
  23226. * @param {boolean} [notDefaultIdx=false]
  23227. * @return {string}
  23228. */
  23229. listProto.getName = function (idx) {
  23230. var rawIndex = this.getRawIndex(idx);
  23231. return this._nameList[rawIndex]
  23232. || this._getNameFromStore(rawIndex)
  23233. || '';
  23234. };
  23235. /**
  23236. * @param {number} idx
  23237. * @param {boolean} [notDefaultIdx=false]
  23238. * @return {string}
  23239. */
  23240. listProto.getId = function (idx) {
  23241. return getId(this, this.getRawIndex(idx));
  23242. };
  23243. function getId(list, rawIndex) {
  23244. var id = list._idList[rawIndex];
  23245. if (id == null) {
  23246. id = list._getIdFromStore(rawIndex);
  23247. }
  23248. if (id == null) {
  23249. // FIXME Check the usage in graph, should not use prefix.
  23250. id = ID_PREFIX + rawIndex;
  23251. }
  23252. return id;
  23253. }
  23254. function normalizeDimensions(dimensions) {
  23255. if (!isArray(dimensions)) {
  23256. dimensions = [dimensions];
  23257. }
  23258. return dimensions;
  23259. }
  23260. function validateDimensions(list, dims) {
  23261. for (var i = 0; i < dims.length; i++) {
  23262. // stroage may be empty when no data, so use
  23263. // dimensionInfos to check.
  23264. if (!list._dimensionInfos[dims[i]]) {
  23265. console.error('Unkown dimension ' + dims[i]);
  23266. }
  23267. }
  23268. }
  23269. /**
  23270. * Data iteration
  23271. * @param {string|Array.<string>}
  23272. * @param {Function} cb
  23273. * @param {*} [context=this]
  23274. *
  23275. * @example
  23276. * list.each('x', function (x, idx) {});
  23277. * list.each(['x', 'y'], function (x, y, idx) {});
  23278. * list.each(function (idx) {})
  23279. */
  23280. listProto.each = function (dims, cb, context, contextCompat) {
  23281. 'use strict';
  23282. if (!this._count) {
  23283. return;
  23284. }
  23285. if (typeof dims === 'function') {
  23286. contextCompat = context;
  23287. context = cb;
  23288. cb = dims;
  23289. dims = [];
  23290. }
  23291. // contextCompat just for compat echarts3
  23292. context = context || contextCompat || this;
  23293. dims = map(normalizeDimensions(dims), this.getDimension, this);
  23294. if (__DEV__) {
  23295. validateDimensions(this, dims);
  23296. }
  23297. var dimSize = dims.length;
  23298. for (var i = 0; i < this.count(); i++) {
  23299. // Simple optimization
  23300. switch (dimSize) {
  23301. case 0:
  23302. cb.call(context, i);
  23303. break;
  23304. case 1:
  23305. cb.call(context, this.get(dims[0], i), i);
  23306. break;
  23307. case 2:
  23308. cb.call(context, this.get(dims[0], i), this.get(dims[1], i), i);
  23309. break;
  23310. default:
  23311. var k = 0;
  23312. var value = [];
  23313. for (; k < dimSize; k++) {
  23314. value[k] = this.get(dims[k], i);
  23315. }
  23316. // Index
  23317. value[k] = i;
  23318. cb.apply(context, value);
  23319. }
  23320. }
  23321. };
  23322. /**
  23323. * Data filter
  23324. * @param {string|Array.<string>}
  23325. * @param {Function} cb
  23326. * @param {*} [context=this]
  23327. */
  23328. listProto.filterSelf = function (dimensions, cb, context, contextCompat) {
  23329. 'use strict';
  23330. if (!this._count) {
  23331. return;
  23332. }
  23333. if (typeof dimensions === 'function') {
  23334. contextCompat = context;
  23335. context = cb;
  23336. cb = dimensions;
  23337. dimensions = [];
  23338. }
  23339. // contextCompat just for compat echarts3
  23340. context = context || contextCompat || this;
  23341. dimensions = map(
  23342. normalizeDimensions(dimensions), this.getDimension, this
  23343. );
  23344. if (__DEV__) {
  23345. validateDimensions(this, dimensions);
  23346. }
  23347. var count = this.count();
  23348. var Ctor = getIndicesCtor(this);
  23349. var newIndices = new Ctor(count);
  23350. var value = [];
  23351. var dimSize = dimensions.length;
  23352. var offset = 0;
  23353. var dim0 = dimensions[0];
  23354. for (var i = 0; i < count; i++) {
  23355. var keep;
  23356. var rawIdx = this.getRawIndex(i);
  23357. // Simple optimization
  23358. if (dimSize === 0) {
  23359. keep = cb.call(context, i);
  23360. }
  23361. else if (dimSize === 1) {
  23362. var val = this._getFast(dim0, rawIdx);
  23363. keep = cb.call(context, val, i);
  23364. }
  23365. else {
  23366. for (var k = 0; k < dimSize; k++) {
  23367. value[k] = this._getFast(dim0, rawIdx);
  23368. }
  23369. value[k] = i;
  23370. keep = cb.apply(context, value);
  23371. }
  23372. if (keep) {
  23373. newIndices[offset++] = rawIdx;
  23374. }
  23375. }
  23376. // Set indices after filtered.
  23377. if (offset < count) {
  23378. this._indices = newIndices;
  23379. }
  23380. this._count = offset;
  23381. // Reset data extent
  23382. this._extent = {};
  23383. this.getRawIndex = this._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
  23384. return this;
  23385. };
  23386. /**
  23387. * Select data in range. (For optimization of filter)
  23388. * (Manually inline code, support 5 million data filtering in data zoom.)
  23389. */
  23390. listProto.selectRange = function (range /*, stack */) {
  23391. 'use strict';
  23392. if (!this._count) {
  23393. return;
  23394. }
  23395. // stack = stack || false;
  23396. var dimensions = [];
  23397. for (var dim in range) {
  23398. if (range.hasOwnProperty(dim)) {
  23399. dimensions.push(dim);
  23400. }
  23401. }
  23402. if (__DEV__) {
  23403. validateDimensions(this, dimensions);
  23404. }
  23405. var dimSize = dimensions.length;
  23406. if (!dimSize) {
  23407. return;
  23408. }
  23409. var originalCount = this.count();
  23410. var Ctor = getIndicesCtor(this);
  23411. var newIndices = new Ctor(originalCount);
  23412. var offset = 0;
  23413. var dim0 = dimensions[0];
  23414. var min = range[dim0][0];
  23415. var max = range[dim0][1];
  23416. var quickFinished = false;
  23417. if (!this._indices /* && !stack */) {
  23418. // Extreme optimization for common case. About 2x faster in chrome.
  23419. var idx = 0;
  23420. if (dimSize === 1) {
  23421. var dimStorage = this._storage[dimensions[0]];
  23422. for (var k = 0; k < this._chunkCount; k++) {
  23423. var chunkStorage = dimStorage[k];
  23424. var len = Math.min(this._count - k * this._chunkSize, this._chunkSize);
  23425. for (var i = 0; i < len; i++) {
  23426. var val = chunkStorage[i];
  23427. if (val >= min && val <= max) {
  23428. newIndices[offset++] = idx;
  23429. }
  23430. idx++;
  23431. }
  23432. }
  23433. quickFinished = true;
  23434. }
  23435. else if (dimSize === 2) {
  23436. var dimStorage = this._storage[dim0];
  23437. var dimStorage2 = this._storage[dimensions[1]];
  23438. var min2 = range[dimensions[1]][0];
  23439. var max2 = range[dimensions[1]][1];
  23440. for (var k = 0; k < this._chunkCount; k++) {
  23441. var chunkStorage = dimStorage[k];
  23442. var chunkStorage2= dimStorage2[k];
  23443. var len = Math.min(this._count - k * this._chunkSize, this._chunkSize);
  23444. for (var i = 0; i < len; i++) {
  23445. var val = chunkStorage[i];
  23446. var val2 = chunkStorage2[i];
  23447. if (val >= min && val <= max && val2 >= min2 && val2 <= max2) {
  23448. newIndices[offset++] = idx;
  23449. }
  23450. idx++;
  23451. }
  23452. }
  23453. quickFinished = true;
  23454. }
  23455. }
  23456. if (!quickFinished) {
  23457. if (dimSize === 1) {
  23458. // stack = stack || !!this.getCalculationInfo(dim0);
  23459. for (var i = 0; i < originalCount; i++) {
  23460. var rawIndex = this.getRawIndex(i);
  23461. // var val = stack ? this.get(dim0, i, true) : this._getFast(dim0, rawIndex);
  23462. var val = this._getFast(dim0, rawIndex);
  23463. if (val >= min && val <= max) {
  23464. newIndices[offset++] = rawIndex;
  23465. }
  23466. }
  23467. }
  23468. else {
  23469. for (var i = 0; i < originalCount; i++) {
  23470. var keep = true;
  23471. var rawIndex = this.getRawIndex(i);
  23472. for (var k = 0; k < dimSize; k++) {
  23473. var dimk = dimensions[k];
  23474. // var val = stack ? this.get(dimk, i, true) : this._getFast(dim, rawIndex);
  23475. var val = this._getFast(dim, rawIndex);
  23476. if (val < range[dimk][0] || val > range[dimk][1]) {
  23477. keep = false;
  23478. }
  23479. }
  23480. if (keep) {
  23481. newIndices[offset++] = this.getRawIndex(i);
  23482. }
  23483. }
  23484. }
  23485. }
  23486. // Set indices after filtered.
  23487. if (offset < originalCount) {
  23488. this._indices = newIndices;
  23489. }
  23490. this._count = offset;
  23491. // Reset data extent
  23492. this._extent = {};
  23493. this.getRawIndex = this._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
  23494. return this;
  23495. };
  23496. /**
  23497. * Data mapping to a plain array
  23498. * @param {string|Array.<string>} [dimensions]
  23499. * @param {Function} cb
  23500. * @param {*} [context=this]
  23501. * @return {Array}
  23502. */
  23503. listProto.mapArray = function (dimensions, cb, context, contextCompat) {
  23504. 'use strict';
  23505. if (typeof dimensions === 'function') {
  23506. contextCompat = context;
  23507. context = cb;
  23508. cb = dimensions;
  23509. dimensions = [];
  23510. }
  23511. // contextCompat just for compat echarts3
  23512. context = context || contextCompat || this;
  23513. var result = [];
  23514. this.each(dimensions, function () {
  23515. result.push(cb && cb.apply(this, arguments));
  23516. }, context);
  23517. return result;
  23518. };
  23519. // Data in excludeDimensions is copied, otherwise transfered.
  23520. function cloneListForMapAndSample(original, excludeDimensions) {
  23521. var allDimensions = original.dimensions;
  23522. var list = new List(
  23523. map(allDimensions, original.getDimensionInfo, original),
  23524. original.hostModel
  23525. );
  23526. // FIXME If needs stackedOn, value may already been stacked
  23527. transferProperties(list, original);
  23528. var storage = list._storage = {};
  23529. var originalStorage = original._storage;
  23530. var rawExtent = extend({}, original._rawExtent);
  23531. // Init storage
  23532. for (var i = 0; i < allDimensions.length; i++) {
  23533. var dim = allDimensions[i];
  23534. if (originalStorage[dim]) {
  23535. if (indexOf(excludeDimensions, dim) >= 0) {
  23536. storage[dim] = cloneDimStore(originalStorage[dim]);
  23537. rawExtent[dim] = getInitialExtent();
  23538. }
  23539. else {
  23540. // Direct reference for other dimensions
  23541. storage[dim] = originalStorage[dim];
  23542. }
  23543. }
  23544. }
  23545. return list;
  23546. }
  23547. function cloneDimStore(originalDimStore) {
  23548. var newDimStore = new Array(originalDimStore.length);
  23549. for (var j = 0; j < originalDimStore.length; j++) {
  23550. newDimStore[j] = cloneChunk(originalDimStore[j]);
  23551. }
  23552. return newDimStore;
  23553. }
  23554. function getInitialExtent() {
  23555. return [Infinity, -Infinity];
  23556. }
  23557. /**
  23558. * Data mapping to a new List with given dimensions
  23559. * @param {string|Array.<string>} dimensions
  23560. * @param {Function} cb
  23561. * @param {*} [context=this]
  23562. * @return {Array}
  23563. */
  23564. listProto.map = function (dimensions, cb, context, contextCompat) {
  23565. 'use strict';
  23566. // contextCompat just for compat echarts3
  23567. context = context || contextCompat || this;
  23568. dimensions = map(
  23569. normalizeDimensions(dimensions), this.getDimension, this
  23570. );
  23571. if (__DEV__) {
  23572. validateDimensions(this, dimensions);
  23573. }
  23574. var list = cloneListForMapAndSample(this, dimensions);
  23575. // Following properties are all immutable.
  23576. // So we can reference to the same value
  23577. list._indices = this._indices;
  23578. list.getRawIndex = list._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
  23579. var storage = list._storage;
  23580. var tmpRetValue = [];
  23581. var chunkSize = this._chunkSize;
  23582. var dimSize = dimensions.length;
  23583. var dataCount = this.count();
  23584. var values = [];
  23585. var rawExtent = list._rawExtent;
  23586. for (var dataIndex = 0; dataIndex < dataCount; dataIndex++) {
  23587. for (var dimIndex = 0; dimIndex < dimSize; dimIndex++) {
  23588. values[dimIndex] = this.get(dimensions[dimIndex], dataIndex /*, stack */);
  23589. }
  23590. values[dimSize] = dataIndex;
  23591. var retValue = cb && cb.apply(context, values);
  23592. if (retValue != null) {
  23593. // a number or string (in oridinal dimension)?
  23594. if (typeof retValue !== 'object') {
  23595. tmpRetValue[0] = retValue;
  23596. retValue = tmpRetValue;
  23597. }
  23598. var rawIndex = this.getRawIndex(dataIndex);
  23599. var chunkIndex = Math.floor(rawIndex / chunkSize);
  23600. var chunkOffset = rawIndex % chunkSize;
  23601. for (var i = 0; i < retValue.length; i++) {
  23602. var dim = dimensions[i];
  23603. var val = retValue[i];
  23604. var rawExtentOnDim = rawExtent[dim];
  23605. var dimStore = storage[dim];
  23606. if (dimStore) {
  23607. dimStore[chunkIndex][chunkOffset] = val;
  23608. }
  23609. if (val < rawExtentOnDim[0]) {
  23610. rawExtentOnDim[0] = val;
  23611. }
  23612. if (val > rawExtentOnDim[1]) {
  23613. rawExtentOnDim[1] = val;
  23614. }
  23615. }
  23616. }
  23617. }
  23618. return list;
  23619. };
  23620. /**
  23621. * Large data down sampling on given dimension
  23622. * @param {string} dimension
  23623. * @param {number} rate
  23624. * @param {Function} sampleValue
  23625. * @param {Function} sampleIndex Sample index for name and id
  23626. */
  23627. listProto.downSample = function (dimension, rate, sampleValue, sampleIndex) {
  23628. var list = cloneListForMapAndSample(this, [dimension]);
  23629. var targetStorage = list._storage;
  23630. var frameValues = [];
  23631. var frameSize = Math.floor(1 / rate);
  23632. var dimStore = targetStorage[dimension];
  23633. var len = this.count();
  23634. var chunkSize = this._chunkSize;
  23635. var rawExtentOnDim = list._rawExtent[dimension];
  23636. var newIndices = new (getIndicesCtor(this))(len);
  23637. var offset = 0;
  23638. for (var i = 0; i < len; i += frameSize) {
  23639. // Last frame
  23640. if (frameSize > len - i) {
  23641. frameSize = len - i;
  23642. frameValues.length = frameSize;
  23643. }
  23644. for (var k = 0; k < frameSize; k++) {
  23645. var dataIdx = this.getRawIndex(i + k);
  23646. var originalChunkIndex = Math.floor(dataIdx / chunkSize);
  23647. var originalChunkOffset = dataIdx % chunkSize;
  23648. frameValues[k] = dimStore[originalChunkIndex][originalChunkOffset];
  23649. }
  23650. var value = sampleValue(frameValues);
  23651. var sampleFrameIdx = this.getRawIndex(
  23652. Math.min(i + sampleIndex(frameValues, value) || 0, len - 1)
  23653. );
  23654. var sampleChunkIndex = Math.floor(sampleFrameIdx / chunkSize);
  23655. var sampleChunkOffset = sampleFrameIdx % chunkSize;
  23656. // Only write value on the filtered data
  23657. dimStore[sampleChunkIndex][sampleChunkOffset] = value;
  23658. if (value < rawExtentOnDim[0]) {
  23659. rawExtentOnDim[0] = value;
  23660. }
  23661. if (value > rawExtentOnDim[1]) {
  23662. rawExtentOnDim[1] = value;
  23663. }
  23664. newIndices[offset++] = sampleFrameIdx;
  23665. }
  23666. list._count = offset;
  23667. list._indices = newIndices;
  23668. list.getRawIndex = getRawIndexWithIndices;
  23669. return list;
  23670. };
  23671. /**
  23672. * Get model of one data item.
  23673. *
  23674. * @param {number} idx
  23675. */
  23676. // FIXME Model proxy ?
  23677. listProto.getItemModel = function (idx) {
  23678. var hostModel = this.hostModel;
  23679. return new Model(this.getRawDataItem(idx), hostModel, hostModel && hostModel.ecModel);
  23680. };
  23681. /**
  23682. * Create a data differ
  23683. * @param {module:echarts/data/List} otherList
  23684. * @return {module:echarts/data/DataDiffer}
  23685. */
  23686. listProto.diff = function (otherList) {
  23687. var thisList = this;
  23688. return new DataDiffer(
  23689. otherList ? otherList.getIndices() : [],
  23690. this.getIndices(),
  23691. function (idx) {
  23692. return getId(otherList, idx);
  23693. },
  23694. function (idx) {
  23695. return getId(thisList, idx);
  23696. }
  23697. );
  23698. };
  23699. /**
  23700. * Get visual property.
  23701. * @param {string} key
  23702. */
  23703. listProto.getVisual = function (key) {
  23704. var visual = this._visual;
  23705. return visual && visual[key];
  23706. };
  23707. /**
  23708. * Set visual property
  23709. * @param {string|Object} key
  23710. * @param {*} [value]
  23711. *
  23712. * @example
  23713. * setVisual('color', color);
  23714. * setVisual({
  23715. * 'color': color
  23716. * });
  23717. */
  23718. listProto.setVisual = function (key, val) {
  23719. if (isObject$4(key)) {
  23720. for (var name in key) {
  23721. if (key.hasOwnProperty(name)) {
  23722. this.setVisual(name, key[name]);
  23723. }
  23724. }
  23725. return;
  23726. }
  23727. this._visual = this._visual || {};
  23728. this._visual[key] = val;
  23729. };
  23730. /**
  23731. * Set layout property.
  23732. * @param {string|Object} key
  23733. * @param {*} [val]
  23734. */
  23735. listProto.setLayout = function (key, val) {
  23736. if (isObject$4(key)) {
  23737. for (var name in key) {
  23738. if (key.hasOwnProperty(name)) {
  23739. this.setLayout(name, key[name]);
  23740. }
  23741. }
  23742. return;
  23743. }
  23744. this._layout[key] = val;
  23745. };
  23746. /**
  23747. * Get layout property.
  23748. * @param {string} key.
  23749. * @return {*}
  23750. */
  23751. listProto.getLayout = function (key) {
  23752. return this._layout[key];
  23753. };
  23754. /**
  23755. * Get layout of single data item
  23756. * @param {number} idx
  23757. */
  23758. listProto.getItemLayout = function (idx) {
  23759. return this._itemLayouts[idx];
  23760. };
  23761. /**
  23762. * Set layout of single data item
  23763. * @param {number} idx
  23764. * @param {Object} layout
  23765. * @param {boolean=} [merge=false]
  23766. */
  23767. listProto.setItemLayout = function (idx, layout, merge$$1) {
  23768. this._itemLayouts[idx] = merge$$1
  23769. ? extend(this._itemLayouts[idx] || {}, layout)
  23770. : layout;
  23771. };
  23772. /**
  23773. * Clear all layout of single data item
  23774. */
  23775. listProto.clearItemLayouts = function () {
  23776. this._itemLayouts.length = 0;
  23777. };
  23778. /**
  23779. * Get visual property of single data item
  23780. * @param {number} idx
  23781. * @param {string} key
  23782. * @param {boolean} [ignoreParent=false]
  23783. */
  23784. listProto.getItemVisual = function (idx, key, ignoreParent) {
  23785. var itemVisual = this._itemVisuals[idx];
  23786. var val = itemVisual && itemVisual[key];
  23787. if (val == null && !ignoreParent) {
  23788. // Use global visual property
  23789. return this.getVisual(key);
  23790. }
  23791. return val;
  23792. };
  23793. /**
  23794. * Set visual property of single data item
  23795. *
  23796. * @param {number} idx
  23797. * @param {string|Object} key
  23798. * @param {*} [value]
  23799. *
  23800. * @example
  23801. * setItemVisual(0, 'color', color);
  23802. * setItemVisual(0, {
  23803. * 'color': color
  23804. * });
  23805. */
  23806. listProto.setItemVisual = function (idx, key, value) {
  23807. var itemVisual = this._itemVisuals[idx] || {};
  23808. var hasItemVisual = this.hasItemVisual;
  23809. this._itemVisuals[idx] = itemVisual;
  23810. if (isObject$4(key)) {
  23811. for (var name in key) {
  23812. if (key.hasOwnProperty(name)) {
  23813. itemVisual[name] = key[name];
  23814. hasItemVisual[name] = true;
  23815. }
  23816. }
  23817. return;
  23818. }
  23819. itemVisual[key] = value;
  23820. hasItemVisual[key] = true;
  23821. };
  23822. /**
  23823. * Clear itemVisuals and list visual.
  23824. */
  23825. listProto.clearAllVisual = function () {
  23826. this._visual = {};
  23827. this._itemVisuals = [];
  23828. this.hasItemVisual = {};
  23829. };
  23830. var setItemDataAndSeriesIndex = function (child) {
  23831. child.seriesIndex = this.seriesIndex;
  23832. child.dataIndex = this.dataIndex;
  23833. child.dataType = this.dataType;
  23834. };
  23835. /**
  23836. * Set graphic element relative to data. It can be set as null
  23837. * @param {number} idx
  23838. * @param {module:zrender/Element} [el]
  23839. */
  23840. listProto.setItemGraphicEl = function (idx, el) {
  23841. var hostModel = this.hostModel;
  23842. if (el) {
  23843. // Add data index and series index for indexing the data by element
  23844. // Useful in tooltip
  23845. el.dataIndex = idx;
  23846. el.dataType = this.dataType;
  23847. el.seriesIndex = hostModel && hostModel.seriesIndex;
  23848. if (el.type === 'group') {
  23849. el.traverse(setItemDataAndSeriesIndex, el);
  23850. }
  23851. }
  23852. this._graphicEls[idx] = el;
  23853. };
  23854. /**
  23855. * @param {number} idx
  23856. * @return {module:zrender/Element}
  23857. */
  23858. listProto.getItemGraphicEl = function (idx) {
  23859. return this._graphicEls[idx];
  23860. };
  23861. /**
  23862. * @param {Function} cb
  23863. * @param {*} context
  23864. */
  23865. listProto.eachItemGraphicEl = function (cb, context) {
  23866. each$1(this._graphicEls, function (el, idx) {
  23867. if (el) {
  23868. cb && cb.call(context, el, idx);
  23869. }
  23870. });
  23871. };
  23872. /**
  23873. * Shallow clone a new list except visual and layout properties, and graph elements.
  23874. * New list only change the indices.
  23875. */
  23876. listProto.cloneShallow = function (list) {
  23877. if (!list) {
  23878. var dimensionInfoList = map(this.dimensions, this.getDimensionInfo, this);
  23879. list = new List(dimensionInfoList, this.hostModel);
  23880. }
  23881. // FIXME
  23882. list._storage = this._storage;
  23883. transferProperties(list, this);
  23884. // Clone will not change the data extent and indices
  23885. if (this._indices) {
  23886. var Ctor = this._indices.constructor;
  23887. list._indices = new Ctor(this._indices);
  23888. }
  23889. else {
  23890. list._indices = null;
  23891. }
  23892. list.getRawIndex = list._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
  23893. list._extent = clone(this._extent);
  23894. list._approximateExtent = clone(this._approximateExtent);
  23895. return list;
  23896. };
  23897. /**
  23898. * Wrap some method to add more feature
  23899. * @param {string} methodName
  23900. * @param {Function} injectFunction
  23901. */
  23902. listProto.wrapMethod = function (methodName, injectFunction) {
  23903. var originalMethod = this[methodName];
  23904. if (typeof originalMethod !== 'function') {
  23905. return;
  23906. }
  23907. this.__wrappedMethods = this.__wrappedMethods || [];
  23908. this.__wrappedMethods.push(methodName);
  23909. this[methodName] = function () {
  23910. var res = originalMethod.apply(this, arguments);
  23911. return injectFunction.apply(this, [res].concat(slice(arguments)));
  23912. };
  23913. };
  23914. // Methods that create a new list based on this list should be listed here.
  23915. // Notice that those method should `RETURN` the new list.
  23916. listProto.TRANSFERABLE_METHODS = ['cloneShallow', 'downSample', 'map'];
  23917. // Methods that change indices of this list should be listed here.
  23918. listProto.CHANGABLE_METHODS = ['filterSelf', 'selectRange'];
  23919. /**
  23920. * @deprecated
  23921. * Use `echarts/data/helper/createDimensions` instead.
  23922. */
  23923. /**
  23924. * @see {module:echarts/test/ut/spec/data/completeDimensions}
  23925. *
  23926. * Complete the dimensions array, by user defined `dimension` and `encode`,
  23927. * and guessing from the data structure.
  23928. * If no 'value' dimension specified, the first no-named dimension will be
  23929. * named as 'value'.
  23930. *
  23931. * @param {Array.<string>} sysDims Necessary dimensions, like ['x', 'y'], which
  23932. * provides not only dim template, but also default order.
  23933. * properties: 'name', 'type', 'displayName'.
  23934. * `name` of each item provides default coord name.
  23935. * [{dimsDef: [string...]}, ...] dimsDef of sysDim item provides default dim name, and
  23936. * provide dims count that the sysDim required.
  23937. * [{ordinalMeta}] can be specified.
  23938. * @param {module:echarts/data/Source|Array|Object} source or data (for compatibal with pervious)
  23939. * @param {Object} [opt]
  23940. * @param {Array.<Object|string>} [opt.dimsDef] option.series.dimensions User defined dimensions
  23941. * For example: ['asdf', {name, type}, ...].
  23942. * @param {Object|HashMap} [opt.encodeDef] option.series.encode {x: 2, y: [3, 1], tooltip: [1, 2], label: 3}
  23943. * @param {string} [opt.generateCoord] Generate coord dim with the given name.
  23944. * If not specified, extra dim names will be:
  23945. * 'value', 'value0', 'value1', ...
  23946. * @param {number} [opt.generateCoordCount] By default, the generated dim name is `generateCoord`.
  23947. * If `generateCoordCount` specified, the generated dim names will be:
  23948. * `generateCoord` + 0, `generateCoord` + 1, ...
  23949. * can be Infinity, indicate that use all of the remain columns.
  23950. * @param {number} [opt.dimCount] If not specified, guess by the first data item.
  23951. * @param {number} [opt.encodeDefaulter] If not specified, auto find the next available data dim.
  23952. * @return {Array.<Object>} [{
  23953. * name: string mandatory,
  23954. * displayName: string, the origin name in dimsDef, see source helper.
  23955. * If displayName given, the tooltip will displayed vertically.
  23956. * coordDim: string mandatory,
  23957. * coordDimIndex: number mandatory,
  23958. * type: string optional,
  23959. * otherDims: { never null/undefined
  23960. * tooltip: number optional,
  23961. * label: number optional,
  23962. * itemName: number optional,
  23963. * seriesName: number optional,
  23964. * },
  23965. * isExtraCoord: boolean true if coord is generated
  23966. * (not specified in encode and not series specified)
  23967. * other props ...
  23968. * }]
  23969. */
  23970. function completeDimensions(sysDims, source, opt) {
  23971. if (!Source.isInstance(source)) {
  23972. source = Source.seriesDataToSource(source);
  23973. }
  23974. opt = opt || {};
  23975. sysDims = (sysDims || []).slice();
  23976. var dimsDef = (opt.dimsDef || []).slice();
  23977. var encodeDef = createHashMap(opt.encodeDef);
  23978. var dataDimNameMap = createHashMap();
  23979. var coordDimNameMap = createHashMap();
  23980. // var valueCandidate;
  23981. var result = [];
  23982. var dimCount = getDimCount(source, sysDims, dimsDef, opt.dimCount);
  23983. // Apply user defined dims (`name` and `type`) and init result.
  23984. for (var i = 0; i < dimCount; i++) {
  23985. var dimDefItem = dimsDef[i] = extend(
  23986. {}, isObject$1(dimsDef[i]) ? dimsDef[i] : {name: dimsDef[i]}
  23987. );
  23988. var userDimName = dimDefItem.name;
  23989. var resultItem = result[i] = {otherDims: {}};
  23990. // Name will be applied later for avoiding duplication.
  23991. if (userDimName != null && dataDimNameMap.get(userDimName) == null) {
  23992. // Only if `series.dimensions` is defined in option
  23993. // displayName, will be set, and dimension will be diplayed vertically in
  23994. // tooltip by default.
  23995. resultItem.name = resultItem.displayName = userDimName;
  23996. dataDimNameMap.set(userDimName, i);
  23997. }
  23998. dimDefItem.type != null && (resultItem.type = dimDefItem.type);
  23999. dimDefItem.displayName != null && (resultItem.displayName = dimDefItem.displayName);
  24000. }
  24001. // Set `coordDim` and `coordDimIndex` by `encodeDef` and normalize `encodeDef`.
  24002. encodeDef.each(function (dataDims, coordDim) {
  24003. dataDims = normalizeToArray(dataDims).slice();
  24004. var validDataDims = encodeDef.set(coordDim, []);
  24005. each$1(dataDims, function (resultDimIdx, idx) {
  24006. // The input resultDimIdx can be dim name or index.
  24007. isString(resultDimIdx) && (resultDimIdx = dataDimNameMap.get(resultDimIdx));
  24008. if (resultDimIdx != null && resultDimIdx < dimCount) {
  24009. validDataDims[idx] = resultDimIdx;
  24010. applyDim(result[resultDimIdx], coordDim, idx);
  24011. }
  24012. });
  24013. });
  24014. // Apply templetes and default order from `sysDims`.
  24015. var availDimIdx = 0;
  24016. each$1(sysDims, function (sysDimItem, sysDimIndex) {
  24017. var coordDim;
  24018. var sysDimItem;
  24019. var sysDimItemDimsDef;
  24020. var sysDimItemOtherDims;
  24021. if (isString(sysDimItem)) {
  24022. coordDim = sysDimItem;
  24023. sysDimItem = {};
  24024. }
  24025. else {
  24026. coordDim = sysDimItem.name;
  24027. var ordinalMeta = sysDimItem.ordinalMeta;
  24028. sysDimItem.ordinalMeta = null;
  24029. sysDimItem = clone(sysDimItem);
  24030. sysDimItem.ordinalMeta = ordinalMeta;
  24031. // `coordDimIndex` should not be set directly.
  24032. sysDimItemDimsDef = sysDimItem.dimsDef;
  24033. sysDimItemOtherDims = sysDimItem.otherDims;
  24034. sysDimItem.name = sysDimItem.coordDim = sysDimItem.coordDimIndex
  24035. = sysDimItem.dimsDef = sysDimItem.otherDims = null;
  24036. }
  24037. var dataDims = normalizeToArray(encodeDef.get(coordDim));
  24038. // dimensions provides default dim sequences.
  24039. if (!dataDims.length) {
  24040. for (var i = 0; i < (sysDimItemDimsDef && sysDimItemDimsDef.length || 1); i++) {
  24041. while (availDimIdx < result.length && result[availDimIdx].coordDim != null) {
  24042. availDimIdx++;
  24043. }
  24044. availDimIdx < result.length && dataDims.push(availDimIdx++);
  24045. }
  24046. }
  24047. // Apply templates.
  24048. each$1(dataDims, function (resultDimIdx, coordDimIndex) {
  24049. var resultItem = result[resultDimIdx];
  24050. applyDim(defaults(resultItem, sysDimItem), coordDim, coordDimIndex);
  24051. if (resultItem.name == null && sysDimItemDimsDef) {
  24052. resultItem.name = resultItem.displayName = sysDimItemDimsDef[coordDimIndex];
  24053. }
  24054. // FIXME refactor, currently only used in case: {otherDims: {tooltip: false}}
  24055. sysDimItemOtherDims && defaults(resultItem.otherDims, sysDimItemOtherDims);
  24056. });
  24057. });
  24058. function applyDim(resultItem, coordDim, coordDimIndex) {
  24059. if (OTHER_DIMENSIONS.get(coordDim) != null) {
  24060. resultItem.otherDims[coordDim] = coordDimIndex;
  24061. }
  24062. else {
  24063. resultItem.coordDim = coordDim;
  24064. resultItem.coordDimIndex = coordDimIndex;
  24065. coordDimNameMap.set(coordDim, true);
  24066. }
  24067. }
  24068. // Make sure the first extra dim is 'value'.
  24069. var generateCoord = opt.generateCoord;
  24070. var generateCoordCount = opt.generateCoordCount;
  24071. var fromZero = generateCoordCount != null;
  24072. generateCoordCount = generateCoord ? (generateCoordCount || 1) : 0;
  24073. var extra = generateCoord || 'value';
  24074. // Set dim `name` and other `coordDim` and other props.
  24075. for (var resultDimIdx = 0; resultDimIdx < dimCount; resultDimIdx++) {
  24076. var resultItem = result[resultDimIdx] = result[resultDimIdx] || {};
  24077. var coordDim = resultItem.coordDim;
  24078. if (coordDim == null) {
  24079. resultItem.coordDim = genName(
  24080. extra, coordDimNameMap, fromZero
  24081. );
  24082. resultItem.coordDimIndex = 0;
  24083. if (!generateCoord || generateCoordCount <= 0) {
  24084. resultItem.isExtraCoord = true;
  24085. }
  24086. generateCoordCount--;
  24087. }
  24088. resultItem.name == null && (resultItem.name = genName(
  24089. resultItem.coordDim,
  24090. dataDimNameMap
  24091. ));
  24092. if (resultItem.type == null && guessOrdinal(source, resultDimIdx, resultItem.name)) {
  24093. resultItem.type = 'ordinal';
  24094. }
  24095. }
  24096. return result;
  24097. }
  24098. // ??? TODO
  24099. // Originally detect dimCount by data[0]. Should we
  24100. // optimize it to only by sysDims and dimensions and encode.
  24101. // So only necessary dims will be initialized.
  24102. // But
  24103. // (1) custom series should be considered. where other dims
  24104. // may be visited.
  24105. // (2) sometimes user need to calcualte bubble size or use visualMap
  24106. // on other dimensions besides coordSys needed.
  24107. // So, dims that is not used by system, should be shared in storage?
  24108. function getDimCount(source, sysDims, dimsDef, optDimCount) {
  24109. // Note that the result dimCount should not small than columns count
  24110. // of data, otherwise `dataDimNameMap` checking will be incorrect.
  24111. var dimCount = Math.max(
  24112. source.dimensionsDetectCount || 1,
  24113. sysDims.length,
  24114. dimsDef.length,
  24115. optDimCount || 0
  24116. );
  24117. each$1(sysDims, function (sysDimItem) {
  24118. var sysDimItemDimsDef = sysDimItem.dimsDef;
  24119. sysDimItemDimsDef && (dimCount = Math.max(dimCount, sysDimItemDimsDef.length));
  24120. });
  24121. return dimCount;
  24122. }
  24123. function genName(name, map$$1, fromZero) {
  24124. if (fromZero || map$$1.get(name) != null) {
  24125. var i = 0;
  24126. while (map$$1.get(name + i) != null) {
  24127. i++;
  24128. }
  24129. name += i;
  24130. }
  24131. map$$1.set(name, true);
  24132. return name;
  24133. }
  24134. /**
  24135. * Substitute `completeDimensions`.
  24136. * `completeDimensions` is to be deprecated.
  24137. */
  24138. /**
  24139. * @param {module:echarts/data/Source|module:echarts/data/List} source or data.
  24140. * @param {Object|Array} [opt]
  24141. * @param {Array.<string|Object>} [opt.coordDimensions=[]]
  24142. * @param {number} [opt.dimensionsCount]
  24143. * @param {string} [opt.generateCoord]
  24144. * @param {string} [opt.generateCoordCount]
  24145. * @param {Array.<string|Object>} [opt.dimensionsDefine=source.dimensionsDefine] Overwrite source define.
  24146. * @param {Object|HashMap} [opt.encodeDefine=source.encodeDefine] Overwrite source define.
  24147. * @return {Array.<Object>} dimensionsInfo
  24148. */
  24149. var createDimensions = function (source, opt) {
  24150. opt = opt || {};
  24151. return completeDimensions(opt.coordDimensions || [], source, {
  24152. dimsDef: opt.dimensionsDefine || source.dimensionsDefine,
  24153. encodeDef: opt.encodeDefine || source.encodeDefine,
  24154. dimCount: opt.dimensionsCount,
  24155. generateCoord: opt.generateCoord,
  24156. generateCoordCount: opt.generateCoordCount
  24157. });
  24158. };
  24159. /**
  24160. * Note that it is too complicated to support 3d stack by value
  24161. * (have to create two-dimension inverted index), so in 3d case
  24162. * we just support that stacked by index.
  24163. *
  24164. * @param {module:echarts/model/Series} seriesModel
  24165. * @param {Array.<string|Object>} dimensionInfoList The same as the input of <module:echarts/data/List>.
  24166. * The input dimensionInfoList will be modified.
  24167. * @param {Object} [opt]
  24168. * @param {boolean} [opt.stackedCoordDimension=''] Specify a coord dimension if needed.
  24169. * @param {boolean} [opt.byIndex=false]
  24170. * @return {Object} calculationInfo
  24171. * {
  24172. * stackedDimension: string
  24173. * stackedByDimension: string
  24174. * isStackedByIndex: boolean
  24175. * stackedOverDimension: string
  24176. * stackResultDimension: string
  24177. * }
  24178. */
  24179. function enableDataStack(seriesModel, dimensionInfoList, opt) {
  24180. opt = opt || {};
  24181. var byIndex = opt.byIndex;
  24182. var stackedCoordDimension = opt.stackedCoordDimension;
  24183. // Compatibal: when `stack` is set as '', do not stack.
  24184. var mayStack = !!(seriesModel && seriesModel.get('stack'));
  24185. var stackedByDimInfo;
  24186. var stackedDimInfo;
  24187. var stackResultDimension;
  24188. var stackedOverDimension;
  24189. each$1(dimensionInfoList, function (dimensionInfo, index) {
  24190. if (isString(dimensionInfo)) {
  24191. dimensionInfoList[index] = dimensionInfo = {name: dimensionInfo};
  24192. }
  24193. if (mayStack && !dimensionInfo.isExtraCoord) {
  24194. // Find the first ordinal dimension as the stackedByDimInfo.
  24195. if (!byIndex && !stackedByDimInfo && dimensionInfo.ordinalMeta) {
  24196. stackedByDimInfo = dimensionInfo;
  24197. }
  24198. // Find the first stackable dimension as the stackedDimInfo.
  24199. if (!stackedDimInfo
  24200. && dimensionInfo.type !== 'ordinal'
  24201. && dimensionInfo.type !== 'time'
  24202. && (!stackedCoordDimension || stackedCoordDimension === dimensionInfo.coordDim)
  24203. ) {
  24204. stackedDimInfo = dimensionInfo;
  24205. }
  24206. }
  24207. });
  24208. // Add stack dimension, they can be both calculated by coordinate system in `unionExtent`.
  24209. // That put stack logic in List is for using conveniently in echarts extensions, but it
  24210. // might not be a good way.
  24211. if (stackedDimInfo && (byIndex || stackedByDimInfo)) {
  24212. // Use a weird name that not duplicated with other names.
  24213. stackResultDimension = '__\0ecstackresult';
  24214. stackedOverDimension = '__\0ecstackedover';
  24215. // Create inverted index to fast query index by value.
  24216. if (stackedByDimInfo) {
  24217. stackedByDimInfo.createInvertedIndices = true;
  24218. }
  24219. var stackedDimCoordDim = stackedDimInfo.coordDim;
  24220. var stackedDimType = stackedDimInfo.type;
  24221. var stackedDimCoordIndex = 0;
  24222. each$1(dimensionInfoList, function (dimensionInfo) {
  24223. if (dimensionInfo.coordDim === stackedDimCoordDim) {
  24224. stackedDimCoordIndex++;
  24225. }
  24226. });
  24227. dimensionInfoList.push({
  24228. name: stackResultDimension,
  24229. coordDim: stackedDimCoordDim,
  24230. coordDimIndex: stackedDimCoordIndex,
  24231. type: stackedDimType,
  24232. isExtraCoord: true,
  24233. isCalculationCoord: true
  24234. });
  24235. stackedDimCoordIndex++;
  24236. dimensionInfoList.push({
  24237. name: stackedOverDimension,
  24238. // This dimension contains stack base (generally, 0), so do not set it as
  24239. // `stackedDimCoordDim` to avoid extent calculation, consider log scale.
  24240. coordDim: stackedOverDimension,
  24241. coordDimIndex: stackedDimCoordIndex,
  24242. type: stackedDimType,
  24243. isExtraCoord: true,
  24244. isCalculationCoord: true
  24245. });
  24246. }
  24247. return {
  24248. stackedDimension: stackedDimInfo && stackedDimInfo.name,
  24249. stackedByDimension: stackedByDimInfo && stackedByDimInfo.name,
  24250. isStackedByIndex: byIndex,
  24251. stackedOverDimension: stackedOverDimension,
  24252. stackResultDimension: stackResultDimension
  24253. };
  24254. }
  24255. /**
  24256. * @param {module:echarts/data/List} data
  24257. * @param {string} stackedDim
  24258. * @param {string} [stackedByDim] If not input this parameter, check whether
  24259. * stacked by index.
  24260. */
  24261. function isDimensionStacked(data, stackedDim, stackedByDim) {
  24262. return stackedDim
  24263. && stackedDim === data.getCalculationInfo('stackedDimension')
  24264. && (
  24265. stackedByDim != null
  24266. ? stackedByDim === data.getCalculationInfo('stackedByDimension')
  24267. : data.getCalculationInfo('isStackedByIndex')
  24268. );
  24269. }
  24270. /**
  24271. * @param {module:echarts/data/Source|Array} source Or raw data.
  24272. * @param {module:echarts/model/Series} seriesModel
  24273. * @param {Object} [opt]
  24274. * @param {string} [opt.generateCoord]
  24275. */
  24276. function createListFromArray(source, seriesModel, opt) {
  24277. opt = opt || {};
  24278. if (!Source.isInstance(source)) {
  24279. source = Source.seriesDataToSource(source);
  24280. }
  24281. var coordSysName = seriesModel.get('coordinateSystem');
  24282. var registeredCoordSys = CoordinateSystemManager.get(coordSysName);
  24283. var coordSysDefine = getCoordSysDefineBySeries(seriesModel);
  24284. var coordSysDimDefs;
  24285. if (coordSysDefine) {
  24286. coordSysDimDefs = map(coordSysDefine.coordSysDims, function (dim) {
  24287. var dimInfo = {name: dim};
  24288. var axisModel = coordSysDefine.axisMap.get(dim);
  24289. if (axisModel) {
  24290. var axisType = axisModel.get('type');
  24291. dimInfo.type = getDimensionTypeByAxis(axisType);
  24292. // dimInfo.stackable = isStackable(axisType);
  24293. }
  24294. return dimInfo;
  24295. });
  24296. }
  24297. if (!coordSysDimDefs) {
  24298. // Get dimensions from registered coordinate system
  24299. coordSysDimDefs = (registeredCoordSys && (
  24300. registeredCoordSys.getDimensionsInfo
  24301. ? registeredCoordSys.getDimensionsInfo()
  24302. : registeredCoordSys.dimensions.slice()
  24303. )) || ['x', 'y'];
  24304. }
  24305. var dimInfoList = createDimensions(source, {
  24306. coordDimensions: coordSysDimDefs,
  24307. generateCoord: opt.generateCoord
  24308. });
  24309. var firstCategoryDimIndex;
  24310. var hasNameEncode;
  24311. coordSysDefine && each$1(dimInfoList, function (dimInfo, dimIndex) {
  24312. var coordDim = dimInfo.coordDim;
  24313. var categoryAxisModel = coordSysDefine.categoryAxisMap.get(coordDim);
  24314. if (categoryAxisModel) {
  24315. if (firstCategoryDimIndex == null) {
  24316. firstCategoryDimIndex = dimIndex;
  24317. }
  24318. dimInfo.ordinalMeta = categoryAxisModel.getOrdinalMeta();
  24319. }
  24320. if (dimInfo.otherDims.itemName != null) {
  24321. hasNameEncode = true;
  24322. }
  24323. });
  24324. if (!hasNameEncode && firstCategoryDimIndex != null) {
  24325. dimInfoList[firstCategoryDimIndex].otherDims.itemName = 0;
  24326. }
  24327. var stackCalculationInfo = enableDataStack(seriesModel, dimInfoList);
  24328. var list = new List(dimInfoList, seriesModel);
  24329. list.setCalculationInfo(stackCalculationInfo);
  24330. var dimValueGetter = (firstCategoryDimIndex != null && isNeedCompleteOrdinalData(source))
  24331. ? function (itemOpt, dimName, dataIndex, dimIndex) {
  24332. // Use dataIndex as ordinal value in categoryAxis
  24333. return dimIndex === firstCategoryDimIndex
  24334. ? dataIndex
  24335. : this.defaultDimValueGetter(itemOpt, dimName, dataIndex, dimIndex);
  24336. }
  24337. : null;
  24338. list.hasItemOption = false;
  24339. list.initData(source, null, dimValueGetter);
  24340. return list;
  24341. }
  24342. function isNeedCompleteOrdinalData(source) {
  24343. if (source.sourceFormat === SOURCE_FORMAT_ORIGINAL) {
  24344. var sampleItem = firstDataNotNull(source.data || []);
  24345. return sampleItem != null
  24346. && !isArray(getDataItemValue(sampleItem));
  24347. }
  24348. }
  24349. function firstDataNotNull(data) {
  24350. var i = 0;
  24351. while (i < data.length && data[i] == null) {
  24352. i++;
  24353. }
  24354. return data[i];
  24355. }
  24356. SeriesModel.extend({
  24357. type: 'series.line',
  24358. dependencies: ['grid', 'polar'],
  24359. getInitialData: function (option, ecModel) {
  24360. if (__DEV__) {
  24361. var coordSys = option.coordinateSystem;
  24362. if (coordSys !== 'polar' && coordSys !== 'cartesian2d') {
  24363. throw new Error('Line not support coordinateSystem besides cartesian and polar');
  24364. }
  24365. }
  24366. return createListFromArray(this.getSource(), this);
  24367. },
  24368. defaultOption: {
  24369. zlevel: 0, // 一级层叠
  24370. z: 2, // 二级层叠
  24371. coordinateSystem: 'cartesian2d',
  24372. legendHoverLink: true,
  24373. hoverAnimation: true,
  24374. // stack: null
  24375. // xAxisIndex: 0,
  24376. // yAxisIndex: 0,
  24377. // polarIndex: 0,
  24378. // If clip the overflow value
  24379. clipOverflow: true,
  24380. // cursor: null,
  24381. label: {
  24382. position: 'top'
  24383. },
  24384. // itemStyle: {
  24385. // },
  24386. lineStyle: {
  24387. width: 2,
  24388. type: 'solid'
  24389. },
  24390. // areaStyle: {
  24391. // origin of areaStyle. Valid values:
  24392. // `'auto'/null/undefined`: from axisLine to data
  24393. // `'start'`: from min to data
  24394. // `'end'`: from data to max
  24395. // origin: 'auto'
  24396. // },
  24397. // false, 'start', 'end', 'middle'
  24398. step: false,
  24399. // Disabled if step is true
  24400. smooth: false,
  24401. smoothMonotone: null,
  24402. // 拐点图形类型
  24403. symbol: 'emptyCircle',
  24404. // 拐点图形大小
  24405. symbolSize: 4,
  24406. // 拐点图形旋转控制
  24407. symbolRotate: null,
  24408. // 是否显示 symbol, 只有在 tooltip hover 的时候显示
  24409. showSymbol: true,
  24410. // 标志图形默认只有主轴显示(随主轴标签间隔隐藏策略)
  24411. showAllSymbol: false,
  24412. // 是否连接断点
  24413. connectNulls: false,
  24414. // 数据过滤,'average', 'max', 'min', 'sum'
  24415. sampling: 'none',
  24416. animationEasing: 'linear',
  24417. // Disable progressive
  24418. progressive: 0,
  24419. hoverLayerThreshold: Infinity
  24420. }
  24421. });
  24422. // Symbol factory
  24423. /**
  24424. * Triangle shape
  24425. * @inner
  24426. */
  24427. var Triangle = extendShape({
  24428. type: 'triangle',
  24429. shape: {
  24430. cx: 0,
  24431. cy: 0,
  24432. width: 0,
  24433. height: 0
  24434. },
  24435. buildPath: function (path, shape) {
  24436. var cx = shape.cx;
  24437. var cy = shape.cy;
  24438. var width = shape.width / 2;
  24439. var height = shape.height / 2;
  24440. path.moveTo(cx, cy - height);
  24441. path.lineTo(cx + width, cy + height);
  24442. path.lineTo(cx - width, cy + height);
  24443. path.closePath();
  24444. }
  24445. });
  24446. /**
  24447. * Diamond shape
  24448. * @inner
  24449. */
  24450. var Diamond = extendShape({
  24451. type: 'diamond',
  24452. shape: {
  24453. cx: 0,
  24454. cy: 0,
  24455. width: 0,
  24456. height: 0
  24457. },
  24458. buildPath: function (path, shape) {
  24459. var cx = shape.cx;
  24460. var cy = shape.cy;
  24461. var width = shape.width / 2;
  24462. var height = shape.height / 2;
  24463. path.moveTo(cx, cy - height);
  24464. path.lineTo(cx + width, cy);
  24465. path.lineTo(cx, cy + height);
  24466. path.lineTo(cx - width, cy);
  24467. path.closePath();
  24468. }
  24469. });
  24470. /**
  24471. * Pin shape
  24472. * @inner
  24473. */
  24474. var Pin = extendShape({
  24475. type: 'pin',
  24476. shape: {
  24477. // x, y on the cusp
  24478. x: 0,
  24479. y: 0,
  24480. width: 0,
  24481. height: 0
  24482. },
  24483. buildPath: function (path, shape) {
  24484. var x = shape.x;
  24485. var y = shape.y;
  24486. var w = shape.width / 5 * 3;
  24487. // Height must be larger than width
  24488. var h = Math.max(w, shape.height);
  24489. var r = w / 2;
  24490. // Dist on y with tangent point and circle center
  24491. var dy = r * r / (h - r);
  24492. var cy = y - h + r + dy;
  24493. var angle = Math.asin(dy / r);
  24494. // Dist on x with tangent point and circle center
  24495. var dx = Math.cos(angle) * r;
  24496. var tanX = Math.sin(angle);
  24497. var tanY = Math.cos(angle);
  24498. var cpLen = r * 0.6;
  24499. var cpLen2 = r * 0.7;
  24500. path.moveTo(x - dx, cy + dy);
  24501. path.arc(
  24502. x, cy, r,
  24503. Math.PI - angle,
  24504. Math.PI * 2 + angle
  24505. );
  24506. path.bezierCurveTo(
  24507. x + dx - tanX * cpLen, cy + dy + tanY * cpLen,
  24508. x, y - cpLen2,
  24509. x, y
  24510. );
  24511. path.bezierCurveTo(
  24512. x, y - cpLen2,
  24513. x - dx + tanX * cpLen, cy + dy + tanY * cpLen,
  24514. x - dx, cy + dy
  24515. );
  24516. path.closePath();
  24517. }
  24518. });
  24519. /**
  24520. * Arrow shape
  24521. * @inner
  24522. */
  24523. var Arrow = extendShape({
  24524. type: 'arrow',
  24525. shape: {
  24526. x: 0,
  24527. y: 0,
  24528. width: 0,
  24529. height: 0
  24530. },
  24531. buildPath: function (ctx, shape) {
  24532. var height = shape.height;
  24533. var width = shape.width;
  24534. var x = shape.x;
  24535. var y = shape.y;
  24536. var dx = width / 3 * 2;
  24537. ctx.moveTo(x, y);
  24538. ctx.lineTo(x + dx, y + height);
  24539. ctx.lineTo(x, y + height / 4 * 3);
  24540. ctx.lineTo(x - dx, y + height);
  24541. ctx.lineTo(x, y);
  24542. ctx.closePath();
  24543. }
  24544. });
  24545. /**
  24546. * Map of path contructors
  24547. * @type {Object.<string, module:zrender/graphic/Path>}
  24548. */
  24549. var symbolCtors = {
  24550. line: Line,
  24551. rect: Rect,
  24552. roundRect: Rect,
  24553. square: Rect,
  24554. circle: Circle,
  24555. diamond: Diamond,
  24556. pin: Pin,
  24557. arrow: Arrow,
  24558. triangle: Triangle
  24559. };
  24560. var symbolShapeMakers = {
  24561. line: function (x, y, w, h, shape) {
  24562. // FIXME
  24563. shape.x1 = x;
  24564. shape.y1 = y + h / 2;
  24565. shape.x2 = x + w;
  24566. shape.y2 = y + h / 2;
  24567. },
  24568. rect: function (x, y, w, h, shape) {
  24569. shape.x = x;
  24570. shape.y = y;
  24571. shape.width = w;
  24572. shape.height = h;
  24573. },
  24574. roundRect: function (x, y, w, h, shape) {
  24575. shape.x = x;
  24576. shape.y = y;
  24577. shape.width = w;
  24578. shape.height = h;
  24579. shape.r = Math.min(w, h) / 4;
  24580. },
  24581. square: function (x, y, w, h, shape) {
  24582. var size = Math.min(w, h);
  24583. shape.x = x;
  24584. shape.y = y;
  24585. shape.width = size;
  24586. shape.height = size;
  24587. },
  24588. circle: function (x, y, w, h, shape) {
  24589. // Put circle in the center of square
  24590. shape.cx = x + w / 2;
  24591. shape.cy = y + h / 2;
  24592. shape.r = Math.min(w, h) / 2;
  24593. },
  24594. diamond: function (x, y, w, h, shape) {
  24595. shape.cx = x + w / 2;
  24596. shape.cy = y + h / 2;
  24597. shape.width = w;
  24598. shape.height = h;
  24599. },
  24600. pin: function (x, y, w, h, shape) {
  24601. shape.x = x + w / 2;
  24602. shape.y = y + h / 2;
  24603. shape.width = w;
  24604. shape.height = h;
  24605. },
  24606. arrow: function (x, y, w, h, shape) {
  24607. shape.x = x + w / 2;
  24608. shape.y = y + h / 2;
  24609. shape.width = w;
  24610. shape.height = h;
  24611. },
  24612. triangle: function (x, y, w, h, shape) {
  24613. shape.cx = x + w / 2;
  24614. shape.cy = y + h / 2;
  24615. shape.width = w;
  24616. shape.height = h;
  24617. }
  24618. };
  24619. var symbolBuildProxies = {};
  24620. each$1(symbolCtors, function (Ctor, name) {
  24621. symbolBuildProxies[name] = new Ctor();
  24622. });
  24623. var SymbolClz$2 = extendShape({
  24624. type: 'symbol',
  24625. shape: {
  24626. symbolType: '',
  24627. x: 0,
  24628. y: 0,
  24629. width: 0,
  24630. height: 0
  24631. },
  24632. beforeBrush: function () {
  24633. var style = this.style;
  24634. var shape = this.shape;
  24635. // FIXME
  24636. if (shape.symbolType === 'pin' && style.textPosition === 'inside') {
  24637. style.textPosition = ['50%', '40%'];
  24638. style.textAlign = 'center';
  24639. style.textVerticalAlign = 'middle';
  24640. }
  24641. },
  24642. buildPath: function (ctx, shape, inBundle) {
  24643. var symbolType = shape.symbolType;
  24644. var proxySymbol = symbolBuildProxies[symbolType];
  24645. if (shape.symbolType !== 'none') {
  24646. if (!proxySymbol) {
  24647. // Default rect
  24648. symbolType = 'rect';
  24649. proxySymbol = symbolBuildProxies[symbolType];
  24650. }
  24651. symbolShapeMakers[symbolType](
  24652. shape.x, shape.y, shape.width, shape.height, proxySymbol.shape
  24653. );
  24654. proxySymbol.buildPath(ctx, proxySymbol.shape, inBundle);
  24655. }
  24656. }
  24657. });
  24658. // Provide setColor helper method to avoid determine if set the fill or stroke outside
  24659. function symbolPathSetColor(color, innerColor) {
  24660. if (this.type !== 'image') {
  24661. var symbolStyle = this.style;
  24662. var symbolShape = this.shape;
  24663. if (symbolShape && symbolShape.symbolType === 'line') {
  24664. symbolStyle.stroke = color;
  24665. }
  24666. else if (this.__isEmptyBrush) {
  24667. symbolStyle.stroke = color;
  24668. symbolStyle.fill = innerColor || '#fff';
  24669. }
  24670. else {
  24671. // FIXME 判断图形默认是填充还是描边,使用 onlyStroke ?
  24672. symbolStyle.fill && (symbolStyle.fill = color);
  24673. symbolStyle.stroke && (symbolStyle.stroke = color);
  24674. }
  24675. this.dirty(false);
  24676. }
  24677. }
  24678. /**
  24679. * Create a symbol element with given symbol configuration: shape, x, y, width, height, color
  24680. * @param {string} symbolType
  24681. * @param {number} x
  24682. * @param {number} y
  24683. * @param {number} w
  24684. * @param {number} h
  24685. * @param {string} color
  24686. * @param {boolean} [keepAspect=false] whether to keep the ratio of w/h,
  24687. * for path and image only.
  24688. */
  24689. function createSymbol(symbolType, x, y, w, h, color, keepAspect) {
  24690. // TODO Support image object, DynamicImage.
  24691. var isEmpty = symbolType.indexOf('empty') === 0;
  24692. if (isEmpty) {
  24693. symbolType = symbolType.substr(5, 1).toLowerCase() + symbolType.substr(6);
  24694. }
  24695. var symbolPath;
  24696. if (symbolType.indexOf('image://') === 0) {
  24697. symbolPath = makeImage(
  24698. symbolType.slice(8),
  24699. new BoundingRect(x, y, w, h),
  24700. keepAspect ? 'center' : 'cover'
  24701. );
  24702. }
  24703. else if (symbolType.indexOf('path://') === 0) {
  24704. symbolPath = makePath(
  24705. symbolType.slice(7),
  24706. {},
  24707. new BoundingRect(x, y, w, h),
  24708. keepAspect ? 'center' : 'cover'
  24709. );
  24710. }
  24711. else {
  24712. symbolPath = new SymbolClz$2({
  24713. shape: {
  24714. symbolType: symbolType,
  24715. x: x,
  24716. y: y,
  24717. width: w,
  24718. height: h
  24719. }
  24720. });
  24721. }
  24722. symbolPath.__isEmptyBrush = isEmpty;
  24723. symbolPath.setColor = symbolPathSetColor;
  24724. symbolPath.setColor(color);
  24725. return symbolPath;
  24726. }
  24727. /**
  24728. * @param {module:echarts/data/List} data
  24729. * @param {number} dataIndex
  24730. * @return {string} label string. Not null/undefined
  24731. */
  24732. function getDefaultLabel(data, dataIndex) {
  24733. var labelDims = data.mapDimension('defaultedLabel', true);
  24734. var len = labelDims.length;
  24735. // Simple optimization (in lots of cases, label dims length is 1)
  24736. if (len === 1) {
  24737. return retrieveRawValue(data, dataIndex, labelDims[0]);
  24738. }
  24739. else if (len) {
  24740. var vals = [];
  24741. for (var i = 0; i < labelDims.length; i++) {
  24742. var val = retrieveRawValue(data, dataIndex, labelDims[i]);
  24743. vals.push(val);
  24744. }
  24745. return vals.join(' ');
  24746. }
  24747. }
  24748. /**
  24749. * @module echarts/chart/helper/Symbol
  24750. */
  24751. function getSymbolSize(data, idx) {
  24752. var symbolSize = data.getItemVisual(idx, 'symbolSize');
  24753. return symbolSize instanceof Array
  24754. ? symbolSize.slice()
  24755. : [+symbolSize, +symbolSize];
  24756. }
  24757. function getScale(symbolSize) {
  24758. return [symbolSize[0] / 2, symbolSize[1] / 2];
  24759. }
  24760. /**
  24761. * @constructor
  24762. * @alias {module:echarts/chart/helper/Symbol}
  24763. * @param {module:echarts/data/List} data
  24764. * @param {number} idx
  24765. * @extends {module:zrender/graphic/Group}
  24766. */
  24767. function SymbolClz(data, idx, seriesScope) {
  24768. Group.call(this);
  24769. this.updateData(data, idx, seriesScope);
  24770. }
  24771. var symbolProto = SymbolClz.prototype;
  24772. function driftSymbol(dx, dy) {
  24773. this.parent.drift(dx, dy);
  24774. }
  24775. symbolProto._createSymbol = function (symbolType, data, idx, symbolSize) {
  24776. // Remove paths created before
  24777. this.removeAll();
  24778. var color = data.getItemVisual(idx, 'color');
  24779. // var symbolPath = createSymbol(
  24780. // symbolType, -0.5, -0.5, 1, 1, color
  24781. // );
  24782. // If width/height are set too small (e.g., set to 1) on ios10
  24783. // and macOS Sierra, a circle stroke become a rect, no matter what
  24784. // the scale is set. So we set width/height as 2. See #4150.
  24785. var symbolPath = createSymbol(
  24786. symbolType, -1, -1, 2, 2, color
  24787. );
  24788. symbolPath.attr({
  24789. z2: 100,
  24790. culling: true,
  24791. scale: getScale(symbolSize)
  24792. });
  24793. // Rewrite drift method
  24794. symbolPath.drift = driftSymbol;
  24795. this._symbolType = symbolType;
  24796. this.add(symbolPath);
  24797. };
  24798. /**
  24799. * Stop animation
  24800. * @param {boolean} toLastFrame
  24801. */
  24802. symbolProto.stopSymbolAnimation = function (toLastFrame) {
  24803. this.childAt(0).stopAnimation(toLastFrame);
  24804. };
  24805. /**
  24806. * FIXME:
  24807. * Caution: This method breaks the encapsulation of this module,
  24808. * but it indeed brings convenience. So do not use the method
  24809. * unless you detailedly know all the implements of `Symbol`,
  24810. * especially animation.
  24811. *
  24812. * Get symbol path element.
  24813. */
  24814. symbolProto.getSymbolPath = function () {
  24815. return this.childAt(0);
  24816. };
  24817. /**
  24818. * Get scale(aka, current symbol size).
  24819. * Including the change caused by animation
  24820. */
  24821. symbolProto.getScale = function () {
  24822. return this.childAt(0).scale;
  24823. };
  24824. /**
  24825. * Highlight symbol
  24826. */
  24827. symbolProto.highlight = function () {
  24828. this.childAt(0).trigger('emphasis');
  24829. };
  24830. /**
  24831. * Downplay symbol
  24832. */
  24833. symbolProto.downplay = function () {
  24834. this.childAt(0).trigger('normal');
  24835. };
  24836. /**
  24837. * @param {number} zlevel
  24838. * @param {number} z
  24839. */
  24840. symbolProto.setZ = function (zlevel, z) {
  24841. var symbolPath = this.childAt(0);
  24842. symbolPath.zlevel = zlevel;
  24843. symbolPath.z = z;
  24844. };
  24845. symbolProto.setDraggable = function (draggable) {
  24846. var symbolPath = this.childAt(0);
  24847. symbolPath.draggable = draggable;
  24848. symbolPath.cursor = draggable ? 'move' : 'pointer';
  24849. };
  24850. /**
  24851. * Update symbol properties
  24852. * @param {module:echarts/data/List} data
  24853. * @param {number} idx
  24854. * @param {Object} [seriesScope]
  24855. * @param {Object} [seriesScope.itemStyle]
  24856. * @param {Object} [seriesScope.hoverItemStyle]
  24857. * @param {Object} [seriesScope.symbolRotate]
  24858. * @param {Object} [seriesScope.symbolOffset]
  24859. * @param {module:echarts/model/Model} [seriesScope.labelModel]
  24860. * @param {module:echarts/model/Model} [seriesScope.hoverLabelModel]
  24861. * @param {boolean} [seriesScope.hoverAnimation]
  24862. * @param {Object} [seriesScope.cursorStyle]
  24863. * @param {module:echarts/model/Model} [seriesScope.itemModel]
  24864. * @param {string} [seriesScope.symbolInnerColor]
  24865. * @param {Object} [seriesScope.fadeIn=false]
  24866. */
  24867. symbolProto.updateData = function (data, idx, seriesScope) {
  24868. this.silent = false;
  24869. var symbolType = data.getItemVisual(idx, 'symbol') || 'circle';
  24870. var seriesModel = data.hostModel;
  24871. var symbolSize = getSymbolSize(data, idx);
  24872. var isInit = symbolType !== this._symbolType;
  24873. if (isInit) {
  24874. this._createSymbol(symbolType, data, idx, symbolSize);
  24875. }
  24876. else {
  24877. var symbolPath = this.childAt(0);
  24878. symbolPath.silent = false;
  24879. updateProps(symbolPath, {
  24880. scale: getScale(symbolSize)
  24881. }, seriesModel, idx);
  24882. }
  24883. this._updateCommon(data, idx, symbolSize, seriesScope);
  24884. if (isInit) {
  24885. var symbolPath = this.childAt(0);
  24886. var fadeIn = seriesScope && seriesScope.fadeIn;
  24887. var target = {scale: symbolPath.scale.slice()};
  24888. fadeIn && (target.style = {opacity: symbolPath.style.opacity});
  24889. symbolPath.scale = [0, 0];
  24890. fadeIn && (symbolPath.style.opacity = 0);
  24891. initProps(symbolPath, target, seriesModel, idx);
  24892. }
  24893. this._seriesModel = seriesModel;
  24894. };
  24895. // Update common properties
  24896. var normalStyleAccessPath = ['itemStyle'];
  24897. var emphasisStyleAccessPath = ['emphasis', 'itemStyle'];
  24898. var normalLabelAccessPath = ['label'];
  24899. var emphasisLabelAccessPath = ['emphasis', 'label'];
  24900. /**
  24901. * @param {module:echarts/data/List} data
  24902. * @param {number} idx
  24903. * @param {Array.<number>} symbolSize
  24904. * @param {Object} [seriesScope]
  24905. */
  24906. symbolProto._updateCommon = function (data, idx, symbolSize, seriesScope) {
  24907. var symbolPath = this.childAt(0);
  24908. var seriesModel = data.hostModel;
  24909. var color = data.getItemVisual(idx, 'color');
  24910. // Reset style
  24911. if (symbolPath.type !== 'image') {
  24912. symbolPath.useStyle({
  24913. strokeNoScale: true
  24914. });
  24915. }
  24916. var itemStyle = seriesScope && seriesScope.itemStyle;
  24917. var hoverItemStyle = seriesScope && seriesScope.hoverItemStyle;
  24918. var symbolRotate = seriesScope && seriesScope.symbolRotate;
  24919. var symbolOffset = seriesScope && seriesScope.symbolOffset;
  24920. var labelModel = seriesScope && seriesScope.labelModel;
  24921. var hoverLabelModel = seriesScope && seriesScope.hoverLabelModel;
  24922. var hoverAnimation = seriesScope && seriesScope.hoverAnimation;
  24923. var cursorStyle = seriesScope && seriesScope.cursorStyle;
  24924. if (!seriesScope || data.hasItemOption) {
  24925. var itemModel = (seriesScope && seriesScope.itemModel)
  24926. ? seriesScope.itemModel : data.getItemModel(idx);
  24927. // Color must be excluded.
  24928. // Because symbol provide setColor individually to set fill and stroke
  24929. itemStyle = itemModel.getModel(normalStyleAccessPath).getItemStyle(['color']);
  24930. hoverItemStyle = itemModel.getModel(emphasisStyleAccessPath).getItemStyle();
  24931. symbolRotate = itemModel.getShallow('symbolRotate');
  24932. symbolOffset = itemModel.getShallow('symbolOffset');
  24933. labelModel = itemModel.getModel(normalLabelAccessPath);
  24934. hoverLabelModel = itemModel.getModel(emphasisLabelAccessPath);
  24935. hoverAnimation = itemModel.getShallow('hoverAnimation');
  24936. cursorStyle = itemModel.getShallow('cursor');
  24937. }
  24938. else {
  24939. hoverItemStyle = extend({}, hoverItemStyle);
  24940. }
  24941. var elStyle = symbolPath.style;
  24942. symbolPath.attr('rotation', (symbolRotate || 0) * Math.PI / 180 || 0);
  24943. if (symbolOffset) {
  24944. symbolPath.attr('position', [
  24945. parsePercent$1(symbolOffset[0], symbolSize[0]),
  24946. parsePercent$1(symbolOffset[1], symbolSize[1])
  24947. ]);
  24948. }
  24949. cursorStyle && symbolPath.attr('cursor', cursorStyle);
  24950. // PENDING setColor before setStyle!!!
  24951. symbolPath.setColor(color, seriesScope && seriesScope.symbolInnerColor);
  24952. symbolPath.setStyle(itemStyle);
  24953. var opacity = data.getItemVisual(idx, 'opacity');
  24954. if (opacity != null) {
  24955. elStyle.opacity = opacity;
  24956. }
  24957. var useNameLabel = seriesScope && seriesScope.useNameLabel;
  24958. setLabelStyle(
  24959. elStyle, hoverItemStyle, labelModel, hoverLabelModel,
  24960. {
  24961. labelFetcher: seriesModel,
  24962. labelDataIndex: idx,
  24963. defaultText: getLabelDefaultText,
  24964. isRectText: true,
  24965. autoColor: color
  24966. }
  24967. );
  24968. // Do not execute util needed.
  24969. function getLabelDefaultText(idx, opt) {
  24970. return useNameLabel ? data.getName(idx) : getDefaultLabel(data, idx);
  24971. }
  24972. symbolPath.off('mouseover')
  24973. .off('mouseout')
  24974. .off('emphasis')
  24975. .off('normal');
  24976. symbolPath.hoverStyle = hoverItemStyle;
  24977. // FIXME
  24978. // Do not use symbol.trigger('emphasis'), but use symbol.highlight() instead.
  24979. setHoverStyle(symbolPath);
  24980. var scale = getScale(symbolSize);
  24981. if (hoverAnimation && seriesModel.isAnimationEnabled()) {
  24982. var onEmphasis = function() {
  24983. // Do not support this hover animation util some scenario required.
  24984. // Animation can only be supported in hover layer when using `el.incremetal`.
  24985. if (this.incremental) {
  24986. return;
  24987. }
  24988. var ratio = scale[1] / scale[0];
  24989. this.animateTo({
  24990. scale: [
  24991. Math.max(scale[0] * 1.1, scale[0] + 3),
  24992. Math.max(scale[1] * 1.1, scale[1] + 3 * ratio)
  24993. ]
  24994. }, 400, 'elasticOut');
  24995. };
  24996. var onNormal = function() {
  24997. if (this.incremental) {
  24998. return;
  24999. }
  25000. this.animateTo({
  25001. scale: scale
  25002. }, 400, 'elasticOut');
  25003. };
  25004. symbolPath.on('mouseover', onEmphasis)
  25005. .on('mouseout', onNormal)
  25006. .on('emphasis', onEmphasis)
  25007. .on('normal', onNormal);
  25008. }
  25009. };
  25010. /**
  25011. * @param {Function} cb
  25012. * @param {Object} [opt]
  25013. * @param {Object} [opt.keepLabel=true]
  25014. */
  25015. symbolProto.fadeOut = function (cb, opt) {
  25016. var symbolPath = this.childAt(0);
  25017. // Avoid mistaken hover when fading out
  25018. this.silent = symbolPath.silent = true;
  25019. // Not show text when animating
  25020. !(opt && opt.keepLabel) && (symbolPath.style.text = null);
  25021. updateProps(
  25022. symbolPath,
  25023. {
  25024. style: {opacity: 0},
  25025. scale: [0, 0]
  25026. },
  25027. this._seriesModel,
  25028. this.dataIndex,
  25029. cb
  25030. );
  25031. };
  25032. inherits(SymbolClz, Group);
  25033. /**
  25034. * @module echarts/chart/helper/SymbolDraw
  25035. */
  25036. /**
  25037. * @constructor
  25038. * @alias module:echarts/chart/helper/SymbolDraw
  25039. * @param {module:zrender/graphic/Group} [symbolCtor]
  25040. */
  25041. function SymbolDraw(symbolCtor) {
  25042. this.group = new Group();
  25043. this._symbolCtor = symbolCtor || SymbolClz;
  25044. }
  25045. var symbolDrawProto = SymbolDraw.prototype;
  25046. function symbolNeedsDraw(data, point, idx, opt) {
  25047. return point && !isNaN(point[0]) && !isNaN(point[1])
  25048. && !(opt.isIgnore && opt.isIgnore(idx))
  25049. // We do not set clipShape on group, because it will
  25050. // cut part of the symbol element shape.
  25051. && !(opt.clipShape && !opt.clipShape.contain(point[0], point[1]))
  25052. && data.getItemVisual(idx, 'symbol') !== 'none';
  25053. }
  25054. /**
  25055. * Update symbols draw by new data
  25056. * @param {module:echarts/data/List} data
  25057. * @param {Object} [opt] Or isIgnore
  25058. * @param {Function} [opt.isIgnore]
  25059. * @param {Object} [opt.clipShape]
  25060. */
  25061. symbolDrawProto.updateData = function (data, opt) {
  25062. opt = normalizeUpdateOpt(opt);
  25063. var group = this.group;
  25064. var seriesModel = data.hostModel;
  25065. var oldData = this._data;
  25066. var SymbolCtor = this._symbolCtor;
  25067. var seriesScope = makeSeriesScope(data);
  25068. // There is no oldLineData only when first rendering or switching from
  25069. // stream mode to normal mode, where previous elements should be removed.
  25070. if (!oldData) {
  25071. group.removeAll();
  25072. }
  25073. data.diff(oldData)
  25074. .add(function (newIdx) {
  25075. var point = data.getItemLayout(newIdx);
  25076. if (symbolNeedsDraw(data, point, newIdx, opt)) {
  25077. var symbolEl = new SymbolCtor(data, newIdx, seriesScope);
  25078. symbolEl.attr('position', point);
  25079. data.setItemGraphicEl(newIdx, symbolEl);
  25080. group.add(symbolEl);
  25081. }
  25082. })
  25083. .update(function (newIdx, oldIdx) {
  25084. var symbolEl = oldData.getItemGraphicEl(oldIdx);
  25085. var point = data.getItemLayout(newIdx);
  25086. if (!symbolNeedsDraw(data, point, newIdx, opt)) {
  25087. group.remove(symbolEl);
  25088. return;
  25089. }
  25090. if (!symbolEl) {
  25091. symbolEl = new SymbolCtor(data, newIdx);
  25092. symbolEl.attr('position', point);
  25093. }
  25094. else {
  25095. symbolEl.updateData(data, newIdx, seriesScope);
  25096. updateProps(symbolEl, {
  25097. position: point
  25098. }, seriesModel);
  25099. }
  25100. // Add back
  25101. group.add(symbolEl);
  25102. data.setItemGraphicEl(newIdx, symbolEl);
  25103. })
  25104. .remove(function (oldIdx) {
  25105. var el = oldData.getItemGraphicEl(oldIdx);
  25106. el && el.fadeOut(function () {
  25107. group.remove(el);
  25108. });
  25109. })
  25110. .execute();
  25111. this._data = data;
  25112. };
  25113. symbolDrawProto.isPersistent = function () {
  25114. return true;
  25115. };
  25116. symbolDrawProto.updateLayout = function () {
  25117. var data = this._data;
  25118. if (data) {
  25119. // Not use animation
  25120. data.eachItemGraphicEl(function (el, idx) {
  25121. var point = data.getItemLayout(idx);
  25122. el.attr('position', point);
  25123. });
  25124. }
  25125. };
  25126. symbolDrawProto.incrementalPrepareUpdate = function (data) {
  25127. this._seriesScope = makeSeriesScope(data);
  25128. this._data = null;
  25129. this.group.removeAll();
  25130. };
  25131. /**
  25132. * Update symbols draw by new data
  25133. * @param {module:echarts/data/List} data
  25134. * @param {Object} [opt] Or isIgnore
  25135. * @param {Function} [opt.isIgnore]
  25136. * @param {Object} [opt.clipShape]
  25137. */
  25138. symbolDrawProto.incrementalUpdate = function (taskParams, data, opt) {
  25139. opt = normalizeUpdateOpt(opt);
  25140. function updateIncrementalAndHover(el) {
  25141. if (!el.isGroup) {
  25142. el.incremental = el.useHoverLayer = true;
  25143. }
  25144. }
  25145. for (var idx = taskParams.start; idx < taskParams.end; idx++) {
  25146. var point = data.getItemLayout(idx);
  25147. if (symbolNeedsDraw(data, point, idx, opt)) {
  25148. var el = new this._symbolCtor(data, idx, this._seriesScope);
  25149. el.traverse(updateIncrementalAndHover);
  25150. el.attr('position', point);
  25151. this.group.add(el);
  25152. data.setItemGraphicEl(idx, el);
  25153. }
  25154. }
  25155. };
  25156. function normalizeUpdateOpt(opt) {
  25157. if (opt != null && !isObject$1(opt)) {
  25158. opt = {isIgnore: opt};
  25159. }
  25160. return opt || {};
  25161. }
  25162. symbolDrawProto.remove = function (enableAnimation) {
  25163. var group = this.group;
  25164. var data = this._data;
  25165. // Incremental model do not have this._data.
  25166. if (data && enableAnimation) {
  25167. data.eachItemGraphicEl(function (el) {
  25168. el.fadeOut(function () {
  25169. group.remove(el);
  25170. });
  25171. });
  25172. }
  25173. else {
  25174. group.removeAll();
  25175. }
  25176. };
  25177. function makeSeriesScope(data) {
  25178. var seriesModel = data.hostModel;
  25179. return {
  25180. itemStyle: seriesModel.getModel('itemStyle').getItemStyle(['color']),
  25181. hoverItemStyle: seriesModel.getModel('emphasis.itemStyle').getItemStyle(),
  25182. symbolRotate: seriesModel.get('symbolRotate'),
  25183. symbolOffset: seriesModel.get('symbolOffset'),
  25184. hoverAnimation: seriesModel.get('hoverAnimation'),
  25185. labelModel: seriesModel.getModel('label'),
  25186. hoverLabelModel: seriesModel.getModel('emphasis.label'),
  25187. cursorStyle: seriesModel.get('cursor')
  25188. };
  25189. }
  25190. /**
  25191. * @param {Object} coordSys
  25192. * @param {module:echarts/data/List} data
  25193. * @param {string} valueOrigin lineSeries.option.areaStyle.origin
  25194. */
  25195. function prepareDataCoordInfo(coordSys, data, valueOrigin) {
  25196. var baseAxis = coordSys.getBaseAxis();
  25197. var valueAxis = coordSys.getOtherAxis(baseAxis);
  25198. var valueStart = getValueStart(valueAxis, valueOrigin);
  25199. var baseAxisDim = baseAxis.dim;
  25200. var valueAxisDim = valueAxis.dim;
  25201. var valueDim = data.mapDimension(valueAxisDim);
  25202. var baseDim = data.mapDimension(baseAxisDim);
  25203. var baseDataOffset = valueAxisDim === 'x' || valueAxisDim === 'radius' ? 1 : 0;
  25204. var stacked = isDimensionStacked(data, valueDim, baseDim);
  25205. var dataDimsForPoint = map(coordSys.dimensions, function (coordDim) {
  25206. return data.mapDimension(coordDim);
  25207. });
  25208. return {
  25209. dataDimsForPoint: dataDimsForPoint,
  25210. valueStart: valueStart,
  25211. valueAxisDim: valueAxisDim,
  25212. baseAxisDim: baseAxisDim,
  25213. stacked: stacked,
  25214. valueDim: valueDim,
  25215. baseDim: baseDim,
  25216. baseDataOffset: baseDataOffset,
  25217. stackedOverDimension: data.getCalculationInfo('stackedOverDimension')
  25218. };
  25219. }
  25220. function getValueStart(valueAxis, valueOrigin) {
  25221. var valueStart = 0;
  25222. var extent = valueAxis.scale.getExtent();
  25223. if (valueOrigin === 'start') {
  25224. valueStart = extent[0];
  25225. }
  25226. else if (valueOrigin === 'end') {
  25227. valueStart = extent[1];
  25228. }
  25229. // auto
  25230. else {
  25231. // Both positive
  25232. if (extent[0] > 0) {
  25233. valueStart = extent[0];
  25234. }
  25235. // Both negative
  25236. else if (extent[1] < 0) {
  25237. valueStart = extent[1];
  25238. }
  25239. // If is one positive, and one negative, onZero shall be true
  25240. }
  25241. return valueStart;
  25242. }
  25243. function getStackedOnPoint(dataCoordInfo, coordSys, data, idx) {
  25244. var value = NaN;
  25245. if (dataCoordInfo.stacked) {
  25246. value = data.get(data.getCalculationInfo('stackedOverDimension'), idx);
  25247. }
  25248. if (isNaN(value)) {
  25249. value = dataCoordInfo.valueStart;
  25250. }
  25251. var baseDataOffset = dataCoordInfo.baseDataOffset;
  25252. var stackedData = [];
  25253. stackedData[baseDataOffset] = data.get(dataCoordInfo.baseDim, idx);
  25254. stackedData[1 - baseDataOffset] = value;
  25255. return coordSys.dataToPoint(stackedData);
  25256. }
  25257. // var arrayDiff = require('zrender/src/core/arrayDiff');
  25258. // 'zrender/src/core/arrayDiff' has been used before, but it did
  25259. // not do well in performance when roam with fixed dataZoom window.
  25260. // function convertToIntId(newIdList, oldIdList) {
  25261. // // Generate int id instead of string id.
  25262. // // Compare string maybe slow in score function of arrDiff
  25263. // // Assume id in idList are all unique
  25264. // var idIndicesMap = {};
  25265. // var idx = 0;
  25266. // for (var i = 0; i < newIdList.length; i++) {
  25267. // idIndicesMap[newIdList[i]] = idx;
  25268. // newIdList[i] = idx++;
  25269. // }
  25270. // for (var i = 0; i < oldIdList.length; i++) {
  25271. // var oldId = oldIdList[i];
  25272. // // Same with newIdList
  25273. // if (idIndicesMap[oldId]) {
  25274. // oldIdList[i] = idIndicesMap[oldId];
  25275. // }
  25276. // else {
  25277. // oldIdList[i] = idx++;
  25278. // }
  25279. // }
  25280. // }
  25281. function diffData(oldData, newData) {
  25282. var diffResult = [];
  25283. newData.diff(oldData)
  25284. .add(function (idx) {
  25285. diffResult.push({cmd: '+', idx: idx});
  25286. })
  25287. .update(function (newIdx, oldIdx) {
  25288. diffResult.push({cmd: '=', idx: oldIdx, idx1: newIdx});
  25289. })
  25290. .remove(function (idx) {
  25291. diffResult.push({cmd: '-', idx: idx});
  25292. })
  25293. .execute();
  25294. return diffResult;
  25295. }
  25296. var lineAnimationDiff = function (
  25297. oldData, newData,
  25298. oldStackedOnPoints, newStackedOnPoints,
  25299. oldCoordSys, newCoordSys,
  25300. oldValueOrigin, newValueOrigin
  25301. ) {
  25302. var diff = diffData(oldData, newData);
  25303. // var newIdList = newData.mapArray(newData.getId);
  25304. // var oldIdList = oldData.mapArray(oldData.getId);
  25305. // convertToIntId(newIdList, oldIdList);
  25306. // // FIXME One data ?
  25307. // diff = arrayDiff(oldIdList, newIdList);
  25308. var currPoints = [];
  25309. var nextPoints = [];
  25310. // Points for stacking base line
  25311. var currStackedPoints = [];
  25312. var nextStackedPoints = [];
  25313. var status = [];
  25314. var sortedIndices = [];
  25315. var rawIndices = [];
  25316. var newDataOldCoordInfo = prepareDataCoordInfo(oldCoordSys, newData, oldValueOrigin);
  25317. var oldDataNewCoordInfo = prepareDataCoordInfo(newCoordSys, oldData, newValueOrigin);
  25318. for (var i = 0; i < diff.length; i++) {
  25319. var diffItem = diff[i];
  25320. var pointAdded = true;
  25321. // FIXME, animation is not so perfect when dataZoom window moves fast
  25322. // Which is in case remvoing or add more than one data in the tail or head
  25323. switch (diffItem.cmd) {
  25324. case '=':
  25325. var currentPt = oldData.getItemLayout(diffItem.idx);
  25326. var nextPt = newData.getItemLayout(diffItem.idx1);
  25327. // If previous data is NaN, use next point directly
  25328. if (isNaN(currentPt[0]) || isNaN(currentPt[1])) {
  25329. currentPt = nextPt.slice();
  25330. }
  25331. currPoints.push(currentPt);
  25332. nextPoints.push(nextPt);
  25333. currStackedPoints.push(oldStackedOnPoints[diffItem.idx]);
  25334. nextStackedPoints.push(newStackedOnPoints[diffItem.idx1]);
  25335. rawIndices.push(newData.getRawIndex(diffItem.idx1));
  25336. break;
  25337. case '+':
  25338. var idx = diffItem.idx;
  25339. currPoints.push(
  25340. oldCoordSys.dataToPoint([
  25341. newData.get(newDataOldCoordInfo.dataDimsForPoint[0], idx),
  25342. newData.get(newDataOldCoordInfo.dataDimsForPoint[1], idx)
  25343. ])
  25344. );
  25345. nextPoints.push(newData.getItemLayout(idx).slice());
  25346. currStackedPoints.push(
  25347. getStackedOnPoint(newDataOldCoordInfo, oldCoordSys, newData, idx)
  25348. );
  25349. nextStackedPoints.push(newStackedOnPoints[idx]);
  25350. rawIndices.push(newData.getRawIndex(idx));
  25351. break;
  25352. case '-':
  25353. var idx = diffItem.idx;
  25354. var rawIndex = oldData.getRawIndex(idx);
  25355. // Data is replaced. In the case of dynamic data queue
  25356. // FIXME FIXME FIXME
  25357. if (rawIndex !== idx) {
  25358. currPoints.push(oldData.getItemLayout(idx));
  25359. nextPoints.push(newCoordSys.dataToPoint([
  25360. oldData.get(oldDataNewCoordInfo.dataDimsForPoint[0], idx),
  25361. oldData.get(oldDataNewCoordInfo.dataDimsForPoint[1], idx)
  25362. ]));
  25363. currStackedPoints.push(oldStackedOnPoints[idx]);
  25364. nextStackedPoints.push(
  25365. getStackedOnPoint(oldDataNewCoordInfo, newCoordSys, oldData, idx)
  25366. );
  25367. rawIndices.push(rawIndex);
  25368. }
  25369. else {
  25370. pointAdded = false;
  25371. }
  25372. }
  25373. // Original indices
  25374. if (pointAdded) {
  25375. status.push(diffItem);
  25376. sortedIndices.push(sortedIndices.length);
  25377. }
  25378. }
  25379. // Diff result may be crossed if all items are changed
  25380. // Sort by data index
  25381. sortedIndices.sort(function (a, b) {
  25382. return rawIndices[a] - rawIndices[b];
  25383. });
  25384. var sortedCurrPoints = [];
  25385. var sortedNextPoints = [];
  25386. var sortedCurrStackedPoints = [];
  25387. var sortedNextStackedPoints = [];
  25388. var sortedStatus = [];
  25389. for (var i = 0; i < sortedIndices.length; i++) {
  25390. var idx = sortedIndices[i];
  25391. sortedCurrPoints[i] = currPoints[idx];
  25392. sortedNextPoints[i] = nextPoints[idx];
  25393. sortedCurrStackedPoints[i] = currStackedPoints[idx];
  25394. sortedNextStackedPoints[i] = nextStackedPoints[idx];
  25395. sortedStatus[i] = status[idx];
  25396. }
  25397. return {
  25398. current: sortedCurrPoints,
  25399. next: sortedNextPoints,
  25400. stackedOnCurrent: sortedCurrStackedPoints,
  25401. stackedOnNext: sortedNextStackedPoints,
  25402. status: sortedStatus
  25403. };
  25404. };
  25405. // Poly path support NaN point
  25406. var vec2Min = min;
  25407. var vec2Max = max;
  25408. var scaleAndAdd$1 = scaleAndAdd;
  25409. var v2Copy = copy;
  25410. // Temporary variable
  25411. var v = [];
  25412. var cp0 = [];
  25413. var cp1 = [];
  25414. function isPointNull(p) {
  25415. return isNaN(p[0]) || isNaN(p[1]);
  25416. }
  25417. function drawSegment(
  25418. ctx, points, start, segLen, allLen,
  25419. dir, smoothMin, smoothMax, smooth, smoothMonotone, connectNulls
  25420. ) {
  25421. if (smoothMonotone == null) {
  25422. if (isMono(points, 'x')) {
  25423. return drawMono(ctx, points, start, segLen, allLen,
  25424. dir, smoothMin, smoothMax, smooth, 'x', connectNulls);
  25425. }
  25426. else if (isMono(points, 'y')) {
  25427. return drawMono(ctx, points, start, segLen, allLen,
  25428. dir, smoothMin, smoothMax, smooth, 'y', connectNulls);
  25429. }
  25430. else {
  25431. return drawNonMono.apply(this, arguments);
  25432. }
  25433. }
  25434. else if (smoothMonotone !== 'none' && isMono(points, smoothMonotone)) {
  25435. return drawMono.apply(this, arguments);
  25436. }
  25437. else {
  25438. return drawNonMono.apply(this, arguments);
  25439. }
  25440. }
  25441. /**
  25442. * Check if points is in monotone.
  25443. *
  25444. * @param {number[][]} points Array of points which is in [x, y] form
  25445. * @param {string} smoothMonotone 'x', 'y', or 'none', stating for which
  25446. * dimension that is checking.
  25447. * If is 'none', `drawNonMono` should be
  25448. * called.
  25449. * If is undefined, either being monotone
  25450. * in 'x' or 'y' will call `drawMono`.
  25451. */
  25452. function isMono(points, smoothMonotone) {
  25453. if (points.length <= 1) {
  25454. return true;
  25455. }
  25456. var dim = smoothMonotone === 'x' ? 0 : 1;
  25457. var last = points[0][dim];
  25458. var lastDiff = 0;
  25459. for (var i = 1; i < points.length; ++i) {
  25460. var diff = points[i][dim] - last;
  25461. if (!isNaN(diff) && !isNaN(lastDiff)
  25462. && diff !== 0 && lastDiff !== 0
  25463. && ((diff >= 0) !== (lastDiff >= 0))
  25464. ) {
  25465. return false;
  25466. }
  25467. if (!isNaN(diff) && diff !== 0) {
  25468. lastDiff = diff;
  25469. last = points[i][dim];
  25470. }
  25471. }
  25472. return true;
  25473. }
  25474. /**
  25475. * Draw smoothed line in monotone, in which only vertical or horizontal bezier
  25476. * control points will be used. This should be used when points are monotone
  25477. * either in x or y dimension.
  25478. */
  25479. function drawMono(
  25480. ctx, points, start, segLen, allLen,
  25481. dir, smoothMin, smoothMax, smooth, smoothMonotone, connectNulls
  25482. ) {
  25483. var prevIdx = 0;
  25484. var idx = start;
  25485. for (var k = 0; k < segLen; k++) {
  25486. var p = points[idx];
  25487. if (idx >= allLen || idx < 0) {
  25488. break;
  25489. }
  25490. if (isPointNull(p)) {
  25491. if (connectNulls) {
  25492. idx += dir;
  25493. continue;
  25494. }
  25495. break;
  25496. }
  25497. if (idx === start) {
  25498. ctx[dir > 0 ? 'moveTo' : 'lineTo'](p[0], p[1]);
  25499. }
  25500. else {
  25501. if (smooth > 0) {
  25502. var prevP = points[prevIdx];
  25503. var dim = smoothMonotone === 'y' ? 1 : 0;
  25504. // Length of control point to p, either in x or y, but not both
  25505. var ctrlLen = (p[dim] - prevP[dim]) * smooth;
  25506. v2Copy(cp0, prevP);
  25507. cp0[dim] = prevP[dim] + ctrlLen;
  25508. v2Copy(cp1, p);
  25509. cp1[dim] = p[dim] - ctrlLen;
  25510. ctx.bezierCurveTo(
  25511. cp0[0], cp0[1],
  25512. cp1[0], cp1[1],
  25513. p[0], p[1]
  25514. );
  25515. }
  25516. else {
  25517. ctx.lineTo(p[0], p[1]);
  25518. }
  25519. }
  25520. prevIdx = idx;
  25521. idx += dir;
  25522. }
  25523. return k;
  25524. }
  25525. /**
  25526. * Draw smoothed line in non-monotone, in may cause undesired curve in extreme
  25527. * situations. This should be used when points are non-monotone neither in x or
  25528. * y dimension.
  25529. */
  25530. function drawNonMono(
  25531. ctx, points, start, segLen, allLen,
  25532. dir, smoothMin, smoothMax, smooth, smoothMonotone, connectNulls
  25533. ) {
  25534. var prevIdx = 0;
  25535. var idx = start;
  25536. for (var k = 0; k < segLen; k++) {
  25537. var p = points[idx];
  25538. if (idx >= allLen || idx < 0) {
  25539. break;
  25540. }
  25541. if (isPointNull(p)) {
  25542. if (connectNulls) {
  25543. idx += dir;
  25544. continue;
  25545. }
  25546. break;
  25547. }
  25548. if (idx === start) {
  25549. ctx[dir > 0 ? 'moveTo' : 'lineTo'](p[0], p[1]);
  25550. v2Copy(cp0, p);
  25551. }
  25552. else {
  25553. if (smooth > 0) {
  25554. var nextIdx = idx + dir;
  25555. var nextP = points[nextIdx];
  25556. if (connectNulls) {
  25557. // Find next point not null
  25558. while (nextP && isPointNull(points[nextIdx])) {
  25559. nextIdx += dir;
  25560. nextP = points[nextIdx];
  25561. }
  25562. }
  25563. var ratioNextSeg = 0.5;
  25564. var prevP = points[prevIdx];
  25565. var nextP = points[nextIdx];
  25566. // Last point
  25567. if (!nextP || isPointNull(nextP)) {
  25568. v2Copy(cp1, p);
  25569. }
  25570. else {
  25571. // If next data is null in not connect case
  25572. if (isPointNull(nextP) && !connectNulls) {
  25573. nextP = p;
  25574. }
  25575. sub(v, nextP, prevP);
  25576. var lenPrevSeg;
  25577. var lenNextSeg;
  25578. if (smoothMonotone === 'x' || smoothMonotone === 'y') {
  25579. var dim = smoothMonotone === 'x' ? 0 : 1;
  25580. lenPrevSeg = Math.abs(p[dim] - prevP[dim]);
  25581. lenNextSeg = Math.abs(p[dim] - nextP[dim]);
  25582. }
  25583. else {
  25584. lenPrevSeg = dist(p, prevP);
  25585. lenNextSeg = dist(p, nextP);
  25586. }
  25587. // Use ratio of seg length
  25588. ratioNextSeg = lenNextSeg / (lenNextSeg + lenPrevSeg);
  25589. scaleAndAdd$1(cp1, p, v, -smooth * (1 - ratioNextSeg));
  25590. }
  25591. // Smooth constraint
  25592. vec2Min(cp0, cp0, smoothMax);
  25593. vec2Max(cp0, cp0, smoothMin);
  25594. vec2Min(cp1, cp1, smoothMax);
  25595. vec2Max(cp1, cp1, smoothMin);
  25596. ctx.bezierCurveTo(
  25597. cp0[0], cp0[1],
  25598. cp1[0], cp1[1],
  25599. p[0], p[1]
  25600. );
  25601. // cp0 of next segment
  25602. scaleAndAdd$1(cp0, p, v, smooth * ratioNextSeg);
  25603. }
  25604. else {
  25605. ctx.lineTo(p[0], p[1]);
  25606. }
  25607. }
  25608. prevIdx = idx;
  25609. idx += dir;
  25610. }
  25611. return k;
  25612. }
  25613. function getBoundingBox(points, smoothConstraint) {
  25614. var ptMin = [Infinity, Infinity];
  25615. var ptMax = [-Infinity, -Infinity];
  25616. if (smoothConstraint) {
  25617. for (var i = 0; i < points.length; i++) {
  25618. var pt = points[i];
  25619. if (pt[0] < ptMin[0]) { ptMin[0] = pt[0]; }
  25620. if (pt[1] < ptMin[1]) { ptMin[1] = pt[1]; }
  25621. if (pt[0] > ptMax[0]) { ptMax[0] = pt[0]; }
  25622. if (pt[1] > ptMax[1]) { ptMax[1] = pt[1]; }
  25623. }
  25624. }
  25625. return {
  25626. min: smoothConstraint ? ptMin : ptMax,
  25627. max: smoothConstraint ? ptMax : ptMin
  25628. };
  25629. }
  25630. var Polyline$1 = Path.extend({
  25631. type: 'ec-polyline',
  25632. shape: {
  25633. points: [],
  25634. smooth: 0,
  25635. smoothConstraint: true,
  25636. smoothMonotone: null,
  25637. connectNulls: false
  25638. },
  25639. style: {
  25640. fill: null,
  25641. stroke: '#000'
  25642. },
  25643. brush: fixClipWithShadow(Path.prototype.brush),
  25644. buildPath: function (ctx, shape) {
  25645. var points = shape.points;
  25646. var i = 0;
  25647. var len$$1 = points.length;
  25648. var result = getBoundingBox(points, shape.smoothConstraint);
  25649. if (shape.connectNulls) {
  25650. // Must remove first and last null values avoid draw error in polygon
  25651. for (; len$$1 > 0; len$$1--) {
  25652. if (!isPointNull(points[len$$1 - 1])) {
  25653. break;
  25654. }
  25655. }
  25656. for (; i < len$$1; i++) {
  25657. if (!isPointNull(points[i])) {
  25658. break;
  25659. }
  25660. }
  25661. }
  25662. while (i < len$$1) {
  25663. i += drawSegment(
  25664. ctx, points, i, len$$1, len$$1,
  25665. 1, result.min, result.max, shape.smooth,
  25666. shape.smoothMonotone, shape.connectNulls
  25667. ) + 1;
  25668. }
  25669. }
  25670. });
  25671. var Polygon$1 = Path.extend({
  25672. type: 'ec-polygon',
  25673. shape: {
  25674. points: [],
  25675. // Offset between stacked base points and points
  25676. stackedOnPoints: [],
  25677. smooth: 0,
  25678. stackedOnSmooth: 0,
  25679. smoothConstraint: true,
  25680. smoothMonotone: null,
  25681. connectNulls: false
  25682. },
  25683. brush: fixClipWithShadow(Path.prototype.brush),
  25684. buildPath: function (ctx, shape) {
  25685. var points = shape.points;
  25686. var stackedOnPoints = shape.stackedOnPoints;
  25687. var i = 0;
  25688. var len$$1 = points.length;
  25689. var smoothMonotone = shape.smoothMonotone;
  25690. var bbox = getBoundingBox(points, shape.smoothConstraint);
  25691. var stackedOnBBox = getBoundingBox(stackedOnPoints, shape.smoothConstraint);
  25692. if (shape.connectNulls) {
  25693. // Must remove first and last null values avoid draw error in polygon
  25694. for (; len$$1 > 0; len$$1--) {
  25695. if (!isPointNull(points[len$$1 - 1])) {
  25696. break;
  25697. }
  25698. }
  25699. for (; i < len$$1; i++) {
  25700. if (!isPointNull(points[i])) {
  25701. break;
  25702. }
  25703. }
  25704. }
  25705. while (i < len$$1) {
  25706. var k = drawSegment(
  25707. ctx, points, i, len$$1, len$$1,
  25708. 1, bbox.min, bbox.max, shape.smooth,
  25709. smoothMonotone, shape.connectNulls
  25710. );
  25711. drawSegment(
  25712. ctx, stackedOnPoints, i + k - 1, k, len$$1,
  25713. -1, stackedOnBBox.min, stackedOnBBox.max, shape.stackedOnSmooth,
  25714. smoothMonotone, shape.connectNulls
  25715. );
  25716. i += k + 1;
  25717. ctx.closePath();
  25718. }
  25719. }
  25720. });
  25721. // FIXME step not support polar
  25722. function isPointsSame(points1, points2) {
  25723. if (points1.length !== points2.length) {
  25724. return;
  25725. }
  25726. for (var i = 0; i < points1.length; i++) {
  25727. var p1 = points1[i];
  25728. var p2 = points2[i];
  25729. if (p1[0] !== p2[0] || p1[1] !== p2[1]) {
  25730. return;
  25731. }
  25732. }
  25733. return true;
  25734. }
  25735. function getSmooth(smooth) {
  25736. return typeof (smooth) === 'number' ? smooth : (smooth ? 0.5 : 0);
  25737. }
  25738. function getAxisExtentWithGap(axis) {
  25739. var extent = axis.getGlobalExtent();
  25740. if (axis.onBand) {
  25741. // Remove extra 1px to avoid line miter in clipped edge
  25742. var halfBandWidth = axis.getBandWidth() / 2 - 1;
  25743. var dir = extent[1] > extent[0] ? 1 : -1;
  25744. extent[0] += dir * halfBandWidth;
  25745. extent[1] -= dir * halfBandWidth;
  25746. }
  25747. return extent;
  25748. }
  25749. /**
  25750. * @param {module:echarts/coord/cartesian/Cartesian2D|module:echarts/coord/polar/Polar} coordSys
  25751. * @param {module:echarts/data/List} data
  25752. * @param {Object} dataCoordInfo
  25753. * @param {Array.<Array.<number>>} points
  25754. */
  25755. function getStackedOnPoints(coordSys, data, dataCoordInfo) {
  25756. if (!dataCoordInfo.valueDim) {
  25757. return [];
  25758. }
  25759. var points = [];
  25760. for (var idx = 0, len = data.count(); idx < len; idx++) {
  25761. points.push(getStackedOnPoint(dataCoordInfo, coordSys, data, idx));
  25762. }
  25763. return points;
  25764. }
  25765. function createGridClipShape(cartesian, hasAnimation, seriesModel) {
  25766. var xExtent = getAxisExtentWithGap(cartesian.getAxis('x'));
  25767. var yExtent = getAxisExtentWithGap(cartesian.getAxis('y'));
  25768. var isHorizontal = cartesian.getBaseAxis().isHorizontal();
  25769. var x = Math.min(xExtent[0], xExtent[1]);
  25770. var y = Math.min(yExtent[0], yExtent[1]);
  25771. var width = Math.max(xExtent[0], xExtent[1]) - x;
  25772. var height = Math.max(yExtent[0], yExtent[1]) - y;
  25773. var lineWidth = seriesModel.get('lineStyle.width') || 2;
  25774. // Expand clip shape to avoid clipping when line value exceeds axis
  25775. var expandSize = seriesModel.get('clipOverflow') ? lineWidth / 2 : Math.max(width, height);
  25776. if (isHorizontal) {
  25777. y -= expandSize;
  25778. height += expandSize * 2;
  25779. }
  25780. else {
  25781. x -= expandSize;
  25782. width += expandSize * 2;
  25783. }
  25784. var clipPath = new Rect({
  25785. shape: {
  25786. x: x,
  25787. y: y,
  25788. width: width,
  25789. height: height
  25790. }
  25791. });
  25792. if (hasAnimation) {
  25793. clipPath.shape[isHorizontal ? 'width' : 'height'] = 0;
  25794. initProps(clipPath, {
  25795. shape: {
  25796. width: width,
  25797. height: height
  25798. }
  25799. }, seriesModel);
  25800. }
  25801. return clipPath;
  25802. }
  25803. function createPolarClipShape(polar, hasAnimation, seriesModel) {
  25804. var angleAxis = polar.getAngleAxis();
  25805. var radiusAxis = polar.getRadiusAxis();
  25806. var radiusExtent = radiusAxis.getExtent();
  25807. var angleExtent = angleAxis.getExtent();
  25808. var RADIAN = Math.PI / 180;
  25809. var clipPath = new Sector({
  25810. shape: {
  25811. cx: polar.cx,
  25812. cy: polar.cy,
  25813. r0: radiusExtent[0],
  25814. r: radiusExtent[1],
  25815. startAngle: -angleExtent[0] * RADIAN,
  25816. endAngle: -angleExtent[1] * RADIAN,
  25817. clockwise: angleAxis.inverse
  25818. }
  25819. });
  25820. if (hasAnimation) {
  25821. clipPath.shape.endAngle = -angleExtent[0] * RADIAN;
  25822. initProps(clipPath, {
  25823. shape: {
  25824. endAngle: -angleExtent[1] * RADIAN
  25825. }
  25826. }, seriesModel);
  25827. }
  25828. return clipPath;
  25829. }
  25830. function createClipShape(coordSys, hasAnimation, seriesModel) {
  25831. return coordSys.type === 'polar'
  25832. ? createPolarClipShape(coordSys, hasAnimation, seriesModel)
  25833. : createGridClipShape(coordSys, hasAnimation, seriesModel);
  25834. }
  25835. function turnPointsIntoStep(points, coordSys, stepTurnAt) {
  25836. var baseAxis = coordSys.getBaseAxis();
  25837. var baseIndex = baseAxis.dim === 'x' || baseAxis.dim === 'radius' ? 0 : 1;
  25838. var stepPoints = [];
  25839. for (var i = 0; i < points.length - 1; i++) {
  25840. var nextPt = points[i + 1];
  25841. var pt = points[i];
  25842. stepPoints.push(pt);
  25843. var stepPt = [];
  25844. switch (stepTurnAt) {
  25845. case 'end':
  25846. stepPt[baseIndex] = nextPt[baseIndex];
  25847. stepPt[1 - baseIndex] = pt[1 - baseIndex];
  25848. // default is start
  25849. stepPoints.push(stepPt);
  25850. break;
  25851. case 'middle':
  25852. // default is start
  25853. var middle = (pt[baseIndex] + nextPt[baseIndex]) / 2;
  25854. var stepPt2 = [];
  25855. stepPt[baseIndex] = stepPt2[baseIndex] = middle;
  25856. stepPt[1 - baseIndex] = pt[1 - baseIndex];
  25857. stepPt2[1 - baseIndex] = nextPt[1 - baseIndex];
  25858. stepPoints.push(stepPt);
  25859. stepPoints.push(stepPt2);
  25860. break;
  25861. default:
  25862. stepPt[baseIndex] = pt[baseIndex];
  25863. stepPt[1 - baseIndex] = nextPt[1 - baseIndex];
  25864. // default is start
  25865. stepPoints.push(stepPt);
  25866. }
  25867. }
  25868. // Last points
  25869. points[i] && stepPoints.push(points[i]);
  25870. return stepPoints;
  25871. }
  25872. function getVisualGradient(data, coordSys) {
  25873. var visualMetaList = data.getVisual('visualMeta');
  25874. if (!visualMetaList || !visualMetaList.length || !data.count()) {
  25875. // When data.count() is 0, gradient range can not be calculated.
  25876. return;
  25877. }
  25878. if (coordSys.type !== 'cartesian2d') {
  25879. if (__DEV__) {
  25880. console.warn('Visual map on line style is only supported on cartesian2d.');
  25881. }
  25882. return;
  25883. }
  25884. var coordDim;
  25885. var visualMeta;
  25886. for (var i = visualMetaList.length - 1; i >= 0; i--) {
  25887. var dimIndex = visualMetaList[i].dimension;
  25888. var dimName = data.dimensions[dimIndex];
  25889. var dimInfo = data.getDimensionInfo(dimName);
  25890. coordDim = dimInfo && dimInfo.coordDim;
  25891. // Can only be x or y
  25892. if (coordDim === 'x' || coordDim === 'y') {
  25893. visualMeta = visualMetaList[i];
  25894. break;
  25895. }
  25896. }
  25897. if (!visualMeta) {
  25898. if (__DEV__) {
  25899. console.warn('Visual map on line style only support x or y dimension.');
  25900. }
  25901. return;
  25902. }
  25903. // If the area to be rendered is bigger than area defined by LinearGradient,
  25904. // the canvas spec prescribes that the color of the first stop and the last
  25905. // stop should be used. But if two stops are added at offset 0, in effect
  25906. // browsers use the color of the second stop to render area outside
  25907. // LinearGradient. So we can only infinitesimally extend area defined in
  25908. // LinearGradient to render `outerColors`.
  25909. var axis = coordSys.getAxis(coordDim);
  25910. // dataToCoor mapping may not be linear, but must be monotonic.
  25911. var colorStops = map(visualMeta.stops, function (stop) {
  25912. return {
  25913. coord: axis.toGlobalCoord(axis.dataToCoord(stop.value)),
  25914. color: stop.color
  25915. };
  25916. });
  25917. var stopLen = colorStops.length;
  25918. var outerColors = visualMeta.outerColors.slice();
  25919. if (stopLen && colorStops[0].coord > colorStops[stopLen - 1].coord) {
  25920. colorStops.reverse();
  25921. outerColors.reverse();
  25922. }
  25923. var tinyExtent = 10; // Arbitrary value: 10px
  25924. var minCoord = colorStops[0].coord - tinyExtent;
  25925. var maxCoord = colorStops[stopLen - 1].coord + tinyExtent;
  25926. var coordSpan = maxCoord - minCoord;
  25927. if (coordSpan < 1e-3) {
  25928. return 'transparent';
  25929. }
  25930. each$1(colorStops, function (stop) {
  25931. stop.offset = (stop.coord - minCoord) / coordSpan;
  25932. });
  25933. colorStops.push({
  25934. offset: stopLen ? colorStops[stopLen - 1].offset : 0.5,
  25935. color: outerColors[1] || 'transparent'
  25936. });
  25937. colorStops.unshift({ // notice colorStops.length have been changed.
  25938. offset: stopLen ? colorStops[0].offset : 0.5,
  25939. color: outerColors[0] || 'transparent'
  25940. });
  25941. // zrUtil.each(colorStops, function (colorStop) {
  25942. // // Make sure each offset has rounded px to avoid not sharp edge
  25943. // colorStop.offset = (Math.round(colorStop.offset * (end - start) + start) - start) / (end - start);
  25944. // });
  25945. var gradient = new LinearGradient(0, 0, 0, 0, colorStops, true);
  25946. gradient[coordDim] = minCoord;
  25947. gradient[coordDim + '2'] = maxCoord;
  25948. return gradient;
  25949. }
  25950. Chart.extend({
  25951. type: 'line',
  25952. init: function () {
  25953. var lineGroup = new Group();
  25954. var symbolDraw = new SymbolDraw();
  25955. this.group.add(symbolDraw.group);
  25956. this._symbolDraw = symbolDraw;
  25957. this._lineGroup = lineGroup;
  25958. },
  25959. render: function (seriesModel, ecModel, api) {
  25960. var coordSys = seriesModel.coordinateSystem;
  25961. var group = this.group;
  25962. var data = seriesModel.getData();
  25963. var lineStyleModel = seriesModel.getModel('lineStyle');
  25964. var areaStyleModel = seriesModel.getModel('areaStyle');
  25965. var points = data.mapArray(data.getItemLayout);
  25966. var isCoordSysPolar = coordSys.type === 'polar';
  25967. var prevCoordSys = this._coordSys;
  25968. var symbolDraw = this._symbolDraw;
  25969. var polyline = this._polyline;
  25970. var polygon = this._polygon;
  25971. var lineGroup = this._lineGroup;
  25972. var hasAnimation = seriesModel.get('animation');
  25973. var isAreaChart = !areaStyleModel.isEmpty();
  25974. var valueOrigin = areaStyleModel.get('origin');
  25975. var dataCoordInfo = prepareDataCoordInfo(coordSys, data, valueOrigin);
  25976. var stackedOnPoints = getStackedOnPoints(coordSys, data, dataCoordInfo);
  25977. var showSymbol = seriesModel.get('showSymbol');
  25978. var isSymbolIgnore = showSymbol && !isCoordSysPolar && !seriesModel.get('showAllSymbol')
  25979. && this._getSymbolIgnoreFunc(data, coordSys);
  25980. // Remove temporary symbols
  25981. var oldData = this._data;
  25982. oldData && oldData.eachItemGraphicEl(function (el, idx) {
  25983. if (el.__temp) {
  25984. group.remove(el);
  25985. oldData.setItemGraphicEl(idx, null);
  25986. }
  25987. });
  25988. // Remove previous created symbols if showSymbol changed to false
  25989. if (!showSymbol) {
  25990. symbolDraw.remove();
  25991. }
  25992. group.add(lineGroup);
  25993. // FIXME step not support polar
  25994. var step = !isCoordSysPolar && seriesModel.get('step');
  25995. // Initialization animation or coordinate system changed
  25996. if (
  25997. !(polyline && prevCoordSys.type === coordSys.type && step === this._step)
  25998. ) {
  25999. showSymbol && symbolDraw.updateData(data, {
  26000. isIgnore: isSymbolIgnore,
  26001. clipShape: createClipShape(coordSys, false, seriesModel)
  26002. });
  26003. if (step) {
  26004. // TODO If stacked series is not step
  26005. points = turnPointsIntoStep(points, coordSys, step);
  26006. stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step);
  26007. }
  26008. polyline = this._newPolyline(points, coordSys, hasAnimation);
  26009. if (isAreaChart) {
  26010. polygon = this._newPolygon(
  26011. points, stackedOnPoints,
  26012. coordSys, hasAnimation
  26013. );
  26014. }
  26015. lineGroup.setClipPath(createClipShape(coordSys, true, seriesModel));
  26016. }
  26017. else {
  26018. if (isAreaChart && !polygon) {
  26019. // If areaStyle is added
  26020. polygon = this._newPolygon(
  26021. points, stackedOnPoints,
  26022. coordSys, hasAnimation
  26023. );
  26024. }
  26025. else if (polygon && !isAreaChart) {
  26026. // If areaStyle is removed
  26027. lineGroup.remove(polygon);
  26028. polygon = this._polygon = null;
  26029. }
  26030. var coordSysClipShape = createClipShape(coordSys, false, seriesModel);
  26031. // Update clipPath
  26032. lineGroup.setClipPath(coordSysClipShape);
  26033. // Always update, or it is wrong in the case turning on legend
  26034. // because points are not changed
  26035. showSymbol && symbolDraw.updateData(data, {
  26036. isIgnore: isSymbolIgnore,
  26037. clipShape: coordSysClipShape
  26038. });
  26039. // Stop symbol animation and sync with line points
  26040. // FIXME performance?
  26041. data.eachItemGraphicEl(function (el) {
  26042. el.stopAnimation(true);
  26043. });
  26044. // In the case data zoom triggerred refreshing frequently
  26045. // Data may not change if line has a category axis. So it should animate nothing
  26046. if (!isPointsSame(this._stackedOnPoints, stackedOnPoints)
  26047. || !isPointsSame(this._points, points)
  26048. ) {
  26049. if (hasAnimation) {
  26050. this._updateAnimation(
  26051. data, stackedOnPoints, coordSys, api, step, valueOrigin
  26052. );
  26053. }
  26054. else {
  26055. // Not do it in update with animation
  26056. if (step) {
  26057. // TODO If stacked series is not step
  26058. points = turnPointsIntoStep(points, coordSys, step);
  26059. stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step);
  26060. }
  26061. polyline.setShape({
  26062. points: points
  26063. });
  26064. polygon && polygon.setShape({
  26065. points: points,
  26066. stackedOnPoints: stackedOnPoints
  26067. });
  26068. }
  26069. }
  26070. }
  26071. var visualColor = getVisualGradient(data, coordSys) || data.getVisual('color');
  26072. polyline.useStyle(defaults(
  26073. // Use color in lineStyle first
  26074. lineStyleModel.getLineStyle(),
  26075. {
  26076. fill: 'none',
  26077. stroke: visualColor,
  26078. lineJoin: 'bevel'
  26079. }
  26080. ));
  26081. var smooth = seriesModel.get('smooth');
  26082. smooth = getSmooth(seriesModel.get('smooth'));
  26083. polyline.setShape({
  26084. smooth: smooth,
  26085. smoothMonotone: seriesModel.get('smoothMonotone'),
  26086. connectNulls: seriesModel.get('connectNulls')
  26087. });
  26088. if (polygon) {
  26089. var stackedOnSeries = data.getCalculationInfo('stackedOnSeries');
  26090. var stackedOnSmooth = 0;
  26091. polygon.useStyle(defaults(
  26092. areaStyleModel.getAreaStyle(),
  26093. {
  26094. fill: visualColor,
  26095. opacity: 0.7,
  26096. lineJoin: 'bevel'
  26097. }
  26098. ));
  26099. if (stackedOnSeries) {
  26100. stackedOnSmooth = getSmooth(stackedOnSeries.get('smooth'));
  26101. }
  26102. polygon.setShape({
  26103. smooth: smooth,
  26104. stackedOnSmooth: stackedOnSmooth,
  26105. smoothMonotone: seriesModel.get('smoothMonotone'),
  26106. connectNulls: seriesModel.get('connectNulls')
  26107. });
  26108. }
  26109. this._data = data;
  26110. // Save the coordinate system for transition animation when data changed
  26111. this._coordSys = coordSys;
  26112. this._stackedOnPoints = stackedOnPoints;
  26113. this._points = points;
  26114. this._step = step;
  26115. this._valueOrigin = valueOrigin;
  26116. },
  26117. dispose: function () {},
  26118. highlight: function (seriesModel, ecModel, api, payload) {
  26119. var data = seriesModel.getData();
  26120. var dataIndex = queryDataIndex(data, payload);
  26121. if (!(dataIndex instanceof Array) && dataIndex != null && dataIndex >= 0) {
  26122. var symbol = data.getItemGraphicEl(dataIndex);
  26123. if (!symbol) {
  26124. // Create a temporary symbol if it is not exists
  26125. var pt = data.getItemLayout(dataIndex);
  26126. if (!pt) {
  26127. // Null data
  26128. return;
  26129. }
  26130. symbol = new SymbolClz(data, dataIndex);
  26131. symbol.position = pt;
  26132. symbol.setZ(
  26133. seriesModel.get('zlevel'),
  26134. seriesModel.get('z')
  26135. );
  26136. symbol.ignore = isNaN(pt[0]) || isNaN(pt[1]);
  26137. symbol.__temp = true;
  26138. data.setItemGraphicEl(dataIndex, symbol);
  26139. // Stop scale animation
  26140. symbol.stopSymbolAnimation(true);
  26141. this.group.add(symbol);
  26142. }
  26143. symbol.highlight();
  26144. }
  26145. else {
  26146. // Highlight whole series
  26147. Chart.prototype.highlight.call(
  26148. this, seriesModel, ecModel, api, payload
  26149. );
  26150. }
  26151. },
  26152. downplay: function (seriesModel, ecModel, api, payload) {
  26153. var data = seriesModel.getData();
  26154. var dataIndex = queryDataIndex(data, payload);
  26155. if (dataIndex != null && dataIndex >= 0) {
  26156. var symbol = data.getItemGraphicEl(dataIndex);
  26157. if (symbol) {
  26158. if (symbol.__temp) {
  26159. data.setItemGraphicEl(dataIndex, null);
  26160. this.group.remove(symbol);
  26161. }
  26162. else {
  26163. symbol.downplay();
  26164. }
  26165. }
  26166. }
  26167. else {
  26168. // FIXME
  26169. // can not downplay completely.
  26170. // Downplay whole series
  26171. Chart.prototype.downplay.call(
  26172. this, seriesModel, ecModel, api, payload
  26173. );
  26174. }
  26175. },
  26176. /**
  26177. * @param {module:zrender/container/Group} group
  26178. * @param {Array.<Array.<number>>} points
  26179. * @private
  26180. */
  26181. _newPolyline: function (points) {
  26182. var polyline = this._polyline;
  26183. // Remove previous created polyline
  26184. if (polyline) {
  26185. this._lineGroup.remove(polyline);
  26186. }
  26187. polyline = new Polyline$1({
  26188. shape: {
  26189. points: points
  26190. },
  26191. silent: true,
  26192. z2: 10
  26193. });
  26194. this._lineGroup.add(polyline);
  26195. this._polyline = polyline;
  26196. return polyline;
  26197. },
  26198. /**
  26199. * @param {module:zrender/container/Group} group
  26200. * @param {Array.<Array.<number>>} stackedOnPoints
  26201. * @param {Array.<Array.<number>>} points
  26202. * @private
  26203. */
  26204. _newPolygon: function (points, stackedOnPoints) {
  26205. var polygon = this._polygon;
  26206. // Remove previous created polygon
  26207. if (polygon) {
  26208. this._lineGroup.remove(polygon);
  26209. }
  26210. polygon = new Polygon$1({
  26211. shape: {
  26212. points: points,
  26213. stackedOnPoints: stackedOnPoints
  26214. },
  26215. silent: true
  26216. });
  26217. this._lineGroup.add(polygon);
  26218. this._polygon = polygon;
  26219. return polygon;
  26220. },
  26221. /**
  26222. * @private
  26223. */
  26224. _getSymbolIgnoreFunc: function (data, coordSys) {
  26225. var categoryAxis = coordSys.getAxesByScale('ordinal')[0];
  26226. // `getLabelInterval` is provided by echarts/component/axis
  26227. if (categoryAxis && categoryAxis.isLabelIgnored) {
  26228. return bind(categoryAxis.isLabelIgnored, categoryAxis);
  26229. }
  26230. },
  26231. /**
  26232. * @private
  26233. */
  26234. // FIXME Two value axis
  26235. _updateAnimation: function (data, stackedOnPoints, coordSys, api, step, valueOrigin) {
  26236. var polyline = this._polyline;
  26237. var polygon = this._polygon;
  26238. var seriesModel = data.hostModel;
  26239. var diff = lineAnimationDiff(
  26240. this._data, data,
  26241. this._stackedOnPoints, stackedOnPoints,
  26242. this._coordSys, coordSys,
  26243. this._valueOrigin, valueOrigin
  26244. );
  26245. var current = diff.current;
  26246. var stackedOnCurrent = diff.stackedOnCurrent;
  26247. var next = diff.next;
  26248. var stackedOnNext = diff.stackedOnNext;
  26249. if (step) {
  26250. // TODO If stacked series is not step
  26251. current = turnPointsIntoStep(diff.current, coordSys, step);
  26252. stackedOnCurrent = turnPointsIntoStep(diff.stackedOnCurrent, coordSys, step);
  26253. next = turnPointsIntoStep(diff.next, coordSys, step);
  26254. stackedOnNext = turnPointsIntoStep(diff.stackedOnNext, coordSys, step);
  26255. }
  26256. // `diff.current` is subset of `current` (which should be ensured by
  26257. // turnPointsIntoStep), so points in `__points` can be updated when
  26258. // points in `current` are update during animation.
  26259. polyline.shape.__points = diff.current;
  26260. polyline.shape.points = current;
  26261. updateProps(polyline, {
  26262. shape: {
  26263. points: next
  26264. }
  26265. }, seriesModel);
  26266. if (polygon) {
  26267. polygon.setShape({
  26268. points: current,
  26269. stackedOnPoints: stackedOnCurrent
  26270. });
  26271. updateProps(polygon, {
  26272. shape: {
  26273. points: next,
  26274. stackedOnPoints: stackedOnNext
  26275. }
  26276. }, seriesModel);
  26277. }
  26278. var updatedDataInfo = [];
  26279. var diffStatus = diff.status;
  26280. for (var i = 0; i < diffStatus.length; i++) {
  26281. var cmd = diffStatus[i].cmd;
  26282. if (cmd === '=') {
  26283. var el = data.getItemGraphicEl(diffStatus[i].idx1);
  26284. if (el) {
  26285. updatedDataInfo.push({
  26286. el: el,
  26287. ptIdx: i // Index of points
  26288. });
  26289. }
  26290. }
  26291. }
  26292. if (polyline.animators && polyline.animators.length) {
  26293. polyline.animators[0].during(function () {
  26294. for (var i = 0; i < updatedDataInfo.length; i++) {
  26295. var el = updatedDataInfo[i].el;
  26296. el.attr('position', polyline.shape.__points[updatedDataInfo[i].ptIdx]);
  26297. }
  26298. });
  26299. }
  26300. },
  26301. remove: function (ecModel) {
  26302. var group = this.group;
  26303. var oldData = this._data;
  26304. this._lineGroup.removeAll();
  26305. this._symbolDraw.remove(true);
  26306. // Remove temporary created elements when highlighting
  26307. oldData && oldData.eachItemGraphicEl(function (el, idx) {
  26308. if (el.__temp) {
  26309. group.remove(el);
  26310. oldData.setItemGraphicEl(idx, null);
  26311. }
  26312. });
  26313. this._polyline =
  26314. this._polygon =
  26315. this._coordSys =
  26316. this._points =
  26317. this._stackedOnPoints =
  26318. this._data = null;
  26319. }
  26320. });
  26321. var visualSymbol = function (seriesType, defaultSymbolType, legendSymbol) {
  26322. // Encoding visual for all series include which is filtered for legend drawing
  26323. return {
  26324. seriesType: seriesType,
  26325. performRawSeries: true,
  26326. reset: function (seriesModel, ecModel, api) {
  26327. var data = seriesModel.getData();
  26328. var symbolType = seriesModel.get('symbol') || defaultSymbolType;
  26329. var symbolSize = seriesModel.get('symbolSize');
  26330. data.setVisual({
  26331. legendSymbol: legendSymbol || symbolType,
  26332. symbol: symbolType,
  26333. symbolSize: symbolSize
  26334. });
  26335. // Only visible series has each data be visual encoded
  26336. if (ecModel.isSeriesFiltered(seriesModel)) {
  26337. return;
  26338. }
  26339. var hasCallback = typeof symbolSize === 'function';
  26340. function dataEach(data, idx) {
  26341. if (typeof symbolSize === 'function') {
  26342. var rawValue = seriesModel.getRawValue(idx);
  26343. // FIXME
  26344. var params = seriesModel.getDataParams(idx);
  26345. data.setItemVisual(idx, 'symbolSize', symbolSize(rawValue, params));
  26346. }
  26347. if (data.hasItemOption) {
  26348. var itemModel = data.getItemModel(idx);
  26349. var itemSymbolType = itemModel.getShallow('symbol', true);
  26350. var itemSymbolSize = itemModel.getShallow('symbolSize', true);
  26351. // If has item symbol
  26352. if (itemSymbolType != null) {
  26353. data.setItemVisual(idx, 'symbol', itemSymbolType);
  26354. }
  26355. if (itemSymbolSize != null) {
  26356. // PENDING Transform symbolSize ?
  26357. data.setItemVisual(idx, 'symbolSize', itemSymbolSize);
  26358. }
  26359. }
  26360. }
  26361. return { dataEach: (data.hasItemOption || hasCallback) ? dataEach : null };
  26362. }
  26363. };
  26364. };
  26365. var layoutPoints = function (seriesType) {
  26366. return {
  26367. seriesType: seriesType,
  26368. plan: createRenderPlanner(),
  26369. reset: function (seriesModel) {
  26370. var data = seriesModel.getData();
  26371. var coordSys = seriesModel.coordinateSystem;
  26372. var pipelineContext = seriesModel.pipelineContext;
  26373. var isLargeRender = pipelineContext.large;
  26374. if (!coordSys) {
  26375. return;
  26376. }
  26377. var dims = map(coordSys.dimensions, function (dim) {
  26378. return data.mapDimension(dim);
  26379. }).slice(0, 2);
  26380. var dimLen = dims.length;
  26381. if (isDimensionStacked(data, dims[0], dims[1])) {
  26382. dims[0] = data.getCalculationInfo('stackResultDimension');
  26383. }
  26384. if (isDimensionStacked(data, dims[1], dims[0])) {
  26385. dims[1] = data.getCalculationInfo('stackResultDimension');
  26386. }
  26387. function progress(params, data) {
  26388. var segCount = params.end - params.start;
  26389. var points = isLargeRender && new Float32Array(segCount * dimLen);
  26390. for (var i = params.start, offset = 0, tmpIn = [], tmpOut = []; i < params.end; i++) {
  26391. var point;
  26392. if (dimLen === 1) {
  26393. var x = data.get(dims[0], i, true);
  26394. point = !isNaN(x) && coordSys.dataToPoint(x, null, tmpOut);
  26395. }
  26396. else {
  26397. var x = tmpIn[0] = data.get(dims[0], i, true);
  26398. var y = tmpIn[1] = data.get(dims[1], i, true);
  26399. // Also {Array.<number>}, not undefined to avoid if...else... statement
  26400. point = !isNaN(x) && !isNaN(y) && coordSys.dataToPoint(tmpIn, null, tmpOut);
  26401. }
  26402. if (isLargeRender) {
  26403. points[offset++] = point ? point[0] : NaN;
  26404. points[offset++] = point ? point[1] : NaN;
  26405. }
  26406. else {
  26407. data.setItemLayout(i, (point && point.slice()) || [NaN, NaN]);
  26408. }
  26409. }
  26410. isLargeRender && data.setLayout('symbolPoints', points);
  26411. }
  26412. return dimLen && {progress: progress};
  26413. }
  26414. };
  26415. };
  26416. var samplers = {
  26417. average: function (frame) {
  26418. var sum = 0;
  26419. var count = 0;
  26420. for (var i = 0; i < frame.length; i++) {
  26421. if (!isNaN(frame[i])) {
  26422. sum += frame[i];
  26423. count++;
  26424. }
  26425. }
  26426. // Return NaN if count is 0
  26427. return count === 0 ? NaN : sum / count;
  26428. },
  26429. sum: function (frame) {
  26430. var sum = 0;
  26431. for (var i = 0; i < frame.length; i++) {
  26432. // Ignore NaN
  26433. sum += frame[i] || 0;
  26434. }
  26435. return sum;
  26436. },
  26437. max: function (frame) {
  26438. var max = -Infinity;
  26439. for (var i = 0; i < frame.length; i++) {
  26440. frame[i] > max && (max = frame[i]);
  26441. }
  26442. return max;
  26443. },
  26444. min: function (frame) {
  26445. var min = Infinity;
  26446. for (var i = 0; i < frame.length; i++) {
  26447. frame[i] < min && (min = frame[i]);
  26448. }
  26449. return min;
  26450. },
  26451. // TODO
  26452. // Median
  26453. nearest: function (frame) {
  26454. return frame[0];
  26455. }
  26456. };
  26457. var indexSampler = function (frame, value) {
  26458. return Math.round(frame.length / 2);
  26459. };
  26460. var dataSample = function (seriesType) {
  26461. return {
  26462. seriesType: seriesType,
  26463. reset: function (seriesModel, ecModel, api) {
  26464. var data = seriesModel.getData();
  26465. var sampling = seriesModel.get('sampling');
  26466. var coordSys = seriesModel.coordinateSystem;
  26467. // Only cartesian2d support down sampling
  26468. if (coordSys.type === 'cartesian2d' && sampling) {
  26469. var baseAxis = coordSys.getBaseAxis();
  26470. var valueAxis = coordSys.getOtherAxis(baseAxis);
  26471. var extent = baseAxis.getExtent();
  26472. // Coordinste system has been resized
  26473. var size = extent[1] - extent[0];
  26474. var rate = Math.round(data.count() / size);
  26475. if (rate > 1) {
  26476. var sampler;
  26477. if (typeof sampling === 'string') {
  26478. sampler = samplers[sampling];
  26479. }
  26480. else if (typeof sampling === 'function') {
  26481. sampler = sampling;
  26482. }
  26483. if (sampler) {
  26484. seriesModel.setData(data.downSample(
  26485. valueAxis.dim, 1 / rate, sampler, indexSampler
  26486. ));
  26487. }
  26488. }
  26489. }
  26490. }
  26491. };
  26492. };
  26493. /**
  26494. * // Scale class management
  26495. * @module echarts/scale/Scale
  26496. */
  26497. /**
  26498. * @param {Object} [setting]
  26499. */
  26500. function Scale(setting) {
  26501. this._setting = setting || {};
  26502. /**
  26503. * Extent
  26504. * @type {Array.<number>}
  26505. * @protected
  26506. */
  26507. this._extent = [Infinity, -Infinity];
  26508. /**
  26509. * Step is calculated in adjustExtent
  26510. * @type {Array.<number>}
  26511. * @protected
  26512. */
  26513. this._interval = 0;
  26514. this.init && this.init.apply(this, arguments);
  26515. }
  26516. /**
  26517. * Parse input val to valid inner number.
  26518. * @param {*} val
  26519. * @return {number}
  26520. */
  26521. Scale.prototype.parse = function (val) {
  26522. // Notice: This would be a trap here, If the implementation
  26523. // of this method depends on extent, and this method is used
  26524. // before extent set (like in dataZoom), it would be wrong.
  26525. // Nevertheless, parse does not depend on extent generally.
  26526. return val;
  26527. };
  26528. Scale.prototype.getSetting = function (name) {
  26529. return this._setting[name];
  26530. };
  26531. Scale.prototype.contain = function (val) {
  26532. var extent = this._extent;
  26533. return val >= extent[0] && val <= extent[1];
  26534. };
  26535. /**
  26536. * Normalize value to linear [0, 1], return 0.5 if extent span is 0
  26537. * @param {number} val
  26538. * @return {number}
  26539. */
  26540. Scale.prototype.normalize = function (val) {
  26541. var extent = this._extent;
  26542. if (extent[1] === extent[0]) {
  26543. return 0.5;
  26544. }
  26545. return (val - extent[0]) / (extent[1] - extent[0]);
  26546. };
  26547. /**
  26548. * Scale normalized value
  26549. * @param {number} val
  26550. * @return {number}
  26551. */
  26552. Scale.prototype.scale = function (val) {
  26553. var extent = this._extent;
  26554. return val * (extent[1] - extent[0]) + extent[0];
  26555. };
  26556. /**
  26557. * Set extent from data
  26558. * @param {Array.<number>} other
  26559. */
  26560. Scale.prototype.unionExtent = function (other) {
  26561. var extent = this._extent;
  26562. other[0] < extent[0] && (extent[0] = other[0]);
  26563. other[1] > extent[1] && (extent[1] = other[1]);
  26564. // not setExtent because in log axis it may transformed to power
  26565. // this.setExtent(extent[0], extent[1]);
  26566. };
  26567. /**
  26568. * Set extent from data
  26569. * @param {module:echarts/data/List} data
  26570. * @param {string} dim
  26571. */
  26572. Scale.prototype.unionExtentFromData = function (data, dim) {
  26573. this.unionExtent(data.getApproximateExtent(dim));
  26574. };
  26575. /**
  26576. * Get extent
  26577. * @return {Array.<number>}
  26578. */
  26579. Scale.prototype.getExtent = function () {
  26580. return this._extent.slice();
  26581. };
  26582. /**
  26583. * Set extent
  26584. * @param {number} start
  26585. * @param {number} end
  26586. */
  26587. Scale.prototype.setExtent = function (start, end) {
  26588. var thisExtent = this._extent;
  26589. if (!isNaN(start)) {
  26590. thisExtent[0] = start;
  26591. }
  26592. if (!isNaN(end)) {
  26593. thisExtent[1] = end;
  26594. }
  26595. };
  26596. /**
  26597. * @return {Array.<string>}
  26598. */
  26599. Scale.prototype.getTicksLabels = function () {
  26600. var labels = [];
  26601. var ticks = this.getTicks();
  26602. for (var i = 0; i < ticks.length; i++) {
  26603. labels.push(this.getLabel(ticks[i]));
  26604. }
  26605. return labels;
  26606. };
  26607. /**
  26608. * When axis extent depends on data and no data exists,
  26609. * axis ticks should not be drawn, which is named 'blank'.
  26610. */
  26611. Scale.prototype.isBlank = function () {
  26612. return this._isBlank;
  26613. },
  26614. /**
  26615. * When axis extent depends on data and no data exists,
  26616. * axis ticks should not be drawn, which is named 'blank'.
  26617. */
  26618. Scale.prototype.setBlank = function (isBlank) {
  26619. this._isBlank = isBlank;
  26620. };
  26621. enableClassExtend(Scale);
  26622. enableClassManagement(Scale, {
  26623. registerWhenExtend: true
  26624. });
  26625. /**
  26626. * @constructor
  26627. * @param {Object} [opt]
  26628. * @param {Object} [opt.categories=[]]
  26629. * @param {Object} [opt.needCollect=false]
  26630. * @param {Object} [opt.deduplication=false]
  26631. */
  26632. function OrdinalMeta(opt) {
  26633. /**
  26634. * @readOnly
  26635. * @type {Array.<string>}
  26636. */
  26637. this.categories = opt.categories || [];
  26638. /**
  26639. * @private
  26640. * @type {boolean}
  26641. */
  26642. this._needCollect = opt.needCollect;
  26643. /**
  26644. * @private
  26645. * @type {boolean}
  26646. */
  26647. this._deduplication = opt.deduplication;
  26648. /**
  26649. * @private
  26650. * @type {boolean}
  26651. */
  26652. this._map;
  26653. }
  26654. /**
  26655. * @param {module:echarts/model/Model} axisModel
  26656. * @return {module:echarts/data/OrdinalMeta}
  26657. */
  26658. OrdinalMeta.createByAxisModel = function (axisModel) {
  26659. var option = axisModel.option;
  26660. var data = option.data;
  26661. var categories = data && map(data, getName);
  26662. return new OrdinalMeta({
  26663. categories: categories,
  26664. needCollect: !categories,
  26665. // deduplication is default in axis.
  26666. deduplication: option.dedplication !== false
  26667. });
  26668. };
  26669. var proto$1 = OrdinalMeta.prototype;
  26670. /**
  26671. * @param {string} category
  26672. * @return {number} ordinal
  26673. */
  26674. proto$1.getOrdinal = function (category) {
  26675. return getOrCreateMap(this).get(category);
  26676. };
  26677. /**
  26678. * @param {*} category
  26679. * @return {number} The ordinal. If not found, return NaN.
  26680. */
  26681. proto$1.parseAndCollect = function (category) {
  26682. var index;
  26683. var needCollect = this._needCollect;
  26684. // The value of category dim can be the index of the given category set.
  26685. // This feature is only supported when !needCollect, because we should
  26686. // consider a common case: a value is 2017, which is a number but is
  26687. // expected to be tread as a category. This case usually happen in dataset,
  26688. // where it happent to be no need of the index feature.
  26689. if (typeof category !== 'string' && !needCollect) {
  26690. return category;
  26691. }
  26692. // Optimize for the scenario:
  26693. // category is ['2012-01-01', '2012-01-02', ...], where the input
  26694. // data has been ensured not duplicate and is large data.
  26695. // Notice, if a dataset dimension provide categroies, usually echarts
  26696. // should remove duplication except user tell echarts dont do that
  26697. // (set axis.deduplication = false), because echarts do not know whether
  26698. // the values in the category dimension has duplication (consider the
  26699. // parallel-aqi example)
  26700. if (needCollect && !this._deduplication) {
  26701. index = this.categories.length;
  26702. this.categories[index] = category;
  26703. return index;
  26704. }
  26705. var map$$1 = getOrCreateMap(this);
  26706. index = map$$1.get(category);
  26707. if (index == null) {
  26708. if (needCollect) {
  26709. index = this.categories.length;
  26710. this.categories[index] = category;
  26711. map$$1.set(category, index);
  26712. }
  26713. else {
  26714. index = NaN;
  26715. }
  26716. }
  26717. return index;
  26718. };
  26719. // Consider big data, do not create map until needed.
  26720. function getOrCreateMap(ordinalMeta) {
  26721. return ordinalMeta._map || (
  26722. ordinalMeta._map = createHashMap(ordinalMeta.categories)
  26723. );
  26724. }
  26725. function getName(obj) {
  26726. if (isObject$1(obj) && obj.value != null) {
  26727. return obj.value;
  26728. }
  26729. else {
  26730. return obj + '';
  26731. }
  26732. }
  26733. /**
  26734. * Linear continuous scale
  26735. * @module echarts/coord/scale/Ordinal
  26736. *
  26737. * http://en.wikipedia.org/wiki/Level_of_measurement
  26738. */
  26739. // FIXME only one data
  26740. var scaleProto = Scale.prototype;
  26741. var OrdinalScale = Scale.extend({
  26742. type: 'ordinal',
  26743. /**
  26744. * @param {module:echarts/data/OrdianlMeta|Array.<string>} ordinalMeta
  26745. */
  26746. init: function (ordinalMeta, extent) {
  26747. // Caution: Should not use instanceof, consider ec-extensions using
  26748. // import approach to get OrdinalMeta class.
  26749. if (!ordinalMeta || isArray(ordinalMeta)) {
  26750. ordinalMeta = new OrdinalMeta({categories: ordinalMeta});
  26751. }
  26752. this._ordinalMeta = ordinalMeta;
  26753. this._extent = extent || [0, ordinalMeta.categories.length - 1];
  26754. },
  26755. parse: function (val) {
  26756. return typeof val === 'string'
  26757. ? this._ordinalMeta.getOrdinal(val)
  26758. // val might be float.
  26759. : Math.round(val);
  26760. },
  26761. contain: function (rank) {
  26762. rank = this.parse(rank);
  26763. return scaleProto.contain.call(this, rank)
  26764. && this._ordinalMeta.categories[rank] != null;
  26765. },
  26766. /**
  26767. * Normalize given rank or name to linear [0, 1]
  26768. * @param {number|string} [val]
  26769. * @return {number}
  26770. */
  26771. normalize: function (val) {
  26772. return scaleProto.normalize.call(this, this.parse(val));
  26773. },
  26774. scale: function (val) {
  26775. return Math.round(scaleProto.scale.call(this, val));
  26776. },
  26777. /**
  26778. * @return {Array}
  26779. */
  26780. getTicks: function () {
  26781. var ticks = [];
  26782. var extent = this._extent;
  26783. var rank = extent[0];
  26784. while (rank <= extent[1]) {
  26785. ticks.push(rank);
  26786. rank++;
  26787. }
  26788. return ticks;
  26789. },
  26790. /**
  26791. * Get item on rank n
  26792. * @param {number} n
  26793. * @return {string}
  26794. */
  26795. getLabel: function (n) {
  26796. return this._ordinalMeta.categories[n];
  26797. },
  26798. /**
  26799. * @return {number}
  26800. */
  26801. count: function () {
  26802. return this._extent[1] - this._extent[0] + 1;
  26803. },
  26804. /**
  26805. * @override
  26806. */
  26807. unionExtentFromData: function (data, dim) {
  26808. this.unionExtent(data.getApproximateExtent(dim));
  26809. },
  26810. niceTicks: noop,
  26811. niceExtent: noop
  26812. });
  26813. /**
  26814. * @return {module:echarts/scale/Time}
  26815. */
  26816. OrdinalScale.create = function () {
  26817. return new OrdinalScale();
  26818. };
  26819. /**
  26820. * For testable.
  26821. */
  26822. var roundNumber$1 = round$1;
  26823. /**
  26824. * @param {Array.<number>} extent Both extent[0] and extent[1] should be valid number.
  26825. * Should be extent[0] < extent[1].
  26826. * @param {number} splitNumber splitNumber should be >= 1.
  26827. * @param {number} [minInterval]
  26828. * @param {number} [maxInterval]
  26829. * @return {Object} {interval, intervalPrecision, niceTickExtent}
  26830. */
  26831. function intervalScaleNiceTicks(extent, splitNumber, minInterval, maxInterval) {
  26832. var result = {};
  26833. var span = extent[1] - extent[0];
  26834. var interval = result.interval = nice(span / splitNumber, true);
  26835. if (minInterval != null && interval < minInterval) {
  26836. interval = result.interval = minInterval;
  26837. }
  26838. if (maxInterval != null && interval > maxInterval) {
  26839. interval = result.interval = maxInterval;
  26840. }
  26841. // Tow more digital for tick.
  26842. var precision = result.intervalPrecision = getIntervalPrecision(interval);
  26843. // Niced extent inside original extent
  26844. var niceTickExtent = result.niceTickExtent = [
  26845. roundNumber$1(Math.ceil(extent[0] / interval) * interval, precision),
  26846. roundNumber$1(Math.floor(extent[1] / interval) * interval, precision)
  26847. ];
  26848. fixExtent(niceTickExtent, extent);
  26849. return result;
  26850. }
  26851. /**
  26852. * @param {number} interval
  26853. * @return {number} interval precision
  26854. */
  26855. function getIntervalPrecision(interval) {
  26856. // Tow more digital for tick.
  26857. return getPrecisionSafe(interval) + 2;
  26858. }
  26859. function clamp(niceTickExtent, idx, extent) {
  26860. niceTickExtent[idx] = Math.max(Math.min(niceTickExtent[idx], extent[1]), extent[0]);
  26861. }
  26862. // In some cases (e.g., splitNumber is 1), niceTickExtent may be out of extent.
  26863. function fixExtent(niceTickExtent, extent) {
  26864. !isFinite(niceTickExtent[0]) && (niceTickExtent[0] = extent[0]);
  26865. !isFinite(niceTickExtent[1]) && (niceTickExtent[1] = extent[1]);
  26866. clamp(niceTickExtent, 0, extent);
  26867. clamp(niceTickExtent, 1, extent);
  26868. if (niceTickExtent[0] > niceTickExtent[1]) {
  26869. niceTickExtent[0] = niceTickExtent[1];
  26870. }
  26871. }
  26872. function intervalScaleGetTicks(interval, extent, niceTickExtent, intervalPrecision) {
  26873. var ticks = [];
  26874. // If interval is 0, return [];
  26875. if (!interval) {
  26876. return ticks;
  26877. }
  26878. // Consider this case: using dataZoom toolbox, zoom and zoom.
  26879. var safeLimit = 10000;
  26880. if (extent[0] < niceTickExtent[0]) {
  26881. ticks.push(extent[0]);
  26882. }
  26883. var tick = niceTickExtent[0];
  26884. while (tick <= niceTickExtent[1]) {
  26885. ticks.push(tick);
  26886. // Avoid rounding error
  26887. tick = roundNumber$1(tick + interval, intervalPrecision);
  26888. if (tick === ticks[ticks.length - 1]) {
  26889. // Consider out of safe float point, e.g.,
  26890. // -3711126.9907707 + 2e-10 === -3711126.9907707
  26891. break;
  26892. }
  26893. if (ticks.length > safeLimit) {
  26894. return [];
  26895. }
  26896. }
  26897. // Consider this case: the last item of ticks is smaller
  26898. // than niceTickExtent[1] and niceTickExtent[1] === extent[1].
  26899. if (extent[1] > (ticks.length ? ticks[ticks.length - 1] : niceTickExtent[1])) {
  26900. ticks.push(extent[1]);
  26901. }
  26902. return ticks;
  26903. }
  26904. /**
  26905. * Interval scale
  26906. * @module echarts/scale/Interval
  26907. */
  26908. var roundNumber = round$1;
  26909. /**
  26910. * @alias module:echarts/coord/scale/Interval
  26911. * @constructor
  26912. */
  26913. var IntervalScale = Scale.extend({
  26914. type: 'interval',
  26915. _interval: 0,
  26916. _intervalPrecision: 2,
  26917. setExtent: function (start, end) {
  26918. var thisExtent = this._extent;
  26919. //start,end may be a Number like '25',so...
  26920. if (!isNaN(start)) {
  26921. thisExtent[0] = parseFloat(start);
  26922. }
  26923. if (!isNaN(end)) {
  26924. thisExtent[1] = parseFloat(end);
  26925. }
  26926. },
  26927. unionExtent: function (other) {
  26928. var extent = this._extent;
  26929. other[0] < extent[0] && (extent[0] = other[0]);
  26930. other[1] > extent[1] && (extent[1] = other[1]);
  26931. // unionExtent may called by it's sub classes
  26932. IntervalScale.prototype.setExtent.call(this, extent[0], extent[1]);
  26933. },
  26934. /**
  26935. * Get interval
  26936. */
  26937. getInterval: function () {
  26938. return this._interval;
  26939. },
  26940. /**
  26941. * Set interval
  26942. */
  26943. setInterval: function (interval) {
  26944. this._interval = interval;
  26945. // Dropped auto calculated niceExtent and use user setted extent
  26946. // We assume user wan't to set both interval, min, max to get a better result
  26947. this._niceExtent = this._extent.slice();
  26948. this._intervalPrecision = getIntervalPrecision(interval);
  26949. },
  26950. /**
  26951. * @return {Array.<number>}
  26952. */
  26953. getTicks: function () {
  26954. return intervalScaleGetTicks(
  26955. this._interval, this._extent, this._niceExtent, this._intervalPrecision
  26956. );
  26957. },
  26958. /**
  26959. * @return {Array.<string>}
  26960. */
  26961. getTicksLabels: function () {
  26962. var labels = [];
  26963. var ticks = this.getTicks();
  26964. for (var i = 0; i < ticks.length; i++) {
  26965. labels.push(this.getLabel(ticks[i]));
  26966. }
  26967. return labels;
  26968. },
  26969. /**
  26970. * @param {number} data
  26971. * @param {Object} [opt]
  26972. * @param {number|string} [opt.precision] If 'auto', use nice presision.
  26973. * @param {boolean} [opt.pad] returns 1.50 but not 1.5 if precision is 2.
  26974. * @return {string}
  26975. */
  26976. getLabel: function (data, opt) {
  26977. if (data == null) {
  26978. return '';
  26979. }
  26980. var precision = opt && opt.precision;
  26981. if (precision == null) {
  26982. precision = getPrecisionSafe(data) || 0;
  26983. }
  26984. else if (precision === 'auto') {
  26985. // Should be more precise then tick.
  26986. precision = this._intervalPrecision;
  26987. }
  26988. // (1) If `precision` is set, 12.005 should be display as '12.00500'.
  26989. // (2) Use roundNumber (toFixed) to avoid scientific notation like '3.5e-7'.
  26990. data = roundNumber(data, precision, true);
  26991. return addCommas(data);
  26992. },
  26993. /**
  26994. * Update interval and extent of intervals for nice ticks
  26995. *
  26996. * @param {number} [splitNumber = 5] Desired number of ticks
  26997. * @param {number} [minInterval]
  26998. * @param {number} [maxInterval]
  26999. */
  27000. niceTicks: function (splitNumber, minInterval, maxInterval) {
  27001. splitNumber = splitNumber || 5;
  27002. var extent = this._extent;
  27003. var span = extent[1] - extent[0];
  27004. if (!isFinite(span)) {
  27005. return;
  27006. }
  27007. // User may set axis min 0 and data are all negative
  27008. // FIXME If it needs to reverse ?
  27009. if (span < 0) {
  27010. span = -span;
  27011. extent.reverse();
  27012. }
  27013. var result = intervalScaleNiceTicks(
  27014. extent, splitNumber, minInterval, maxInterval
  27015. );
  27016. this._intervalPrecision = result.intervalPrecision;
  27017. this._interval = result.interval;
  27018. this._niceExtent = result.niceTickExtent;
  27019. },
  27020. /**
  27021. * Nice extent.
  27022. * @param {Object} opt
  27023. * @param {number} [opt.splitNumber = 5] Given approx tick number
  27024. * @param {boolean} [opt.fixMin=false]
  27025. * @param {boolean} [opt.fixMax=false]
  27026. * @param {boolean} [opt.minInterval]
  27027. * @param {boolean} [opt.maxInterval]
  27028. */
  27029. niceExtent: function (opt) {
  27030. var extent = this._extent;
  27031. // If extent start and end are same, expand them
  27032. if (extent[0] === extent[1]) {
  27033. if (extent[0] !== 0) {
  27034. // Expand extent
  27035. var expandSize = extent[0];
  27036. // In the fowllowing case
  27037. // Axis has been fixed max 100
  27038. // Plus data are all 100 and axis extent are [100, 100].
  27039. // Extend to the both side will cause expanded max is larger than fixed max.
  27040. // So only expand to the smaller side.
  27041. if (!opt.fixMax) {
  27042. extent[1] += expandSize / 2;
  27043. extent[0] -= expandSize / 2;
  27044. }
  27045. else {
  27046. extent[0] -= expandSize / 2;
  27047. }
  27048. }
  27049. else {
  27050. extent[1] = 1;
  27051. }
  27052. }
  27053. var span = extent[1] - extent[0];
  27054. // If there are no data and extent are [Infinity, -Infinity]
  27055. if (!isFinite(span)) {
  27056. extent[0] = 0;
  27057. extent[1] = 1;
  27058. }
  27059. this.niceTicks(opt.splitNumber, opt.minInterval, opt.maxInterval);
  27060. // var extent = this._extent;
  27061. var interval = this._interval;
  27062. if (!opt.fixMin) {
  27063. extent[0] = roundNumber(Math.floor(extent[0] / interval) * interval);
  27064. }
  27065. if (!opt.fixMax) {
  27066. extent[1] = roundNumber(Math.ceil(extent[1] / interval) * interval);
  27067. }
  27068. }
  27069. });
  27070. /**
  27071. * @return {module:echarts/scale/Time}
  27072. */
  27073. IntervalScale.create = function () {
  27074. return new IntervalScale();
  27075. };
  27076. var STACK_PREFIX = '__ec_stack_';
  27077. function getSeriesStackId(seriesModel) {
  27078. return seriesModel.get('stack') || STACK_PREFIX + seriesModel.seriesIndex;
  27079. }
  27080. function getAxisKey(axis) {
  27081. return axis.dim + axis.index;
  27082. }
  27083. /**
  27084. * @param {Object} opt
  27085. * @param {module:echarts/coord/Axis} opt.axis Only support category axis currently.
  27086. * @param {number} opt.count Positive interger.
  27087. * @param {number} [opt.barWidth]
  27088. * @param {number} [opt.barMaxWidth]
  27089. * @param {number} [opt.barGap]
  27090. * @param {number} [opt.barCategoryGap]
  27091. * @return {Object} {width, offset, offsetCenter} If axis.type is not 'category', return undefined.
  27092. */
  27093. function calBarWidthAndOffset(barSeries, api) {
  27094. var seriesInfoList = map(barSeries, function (seriesModel) {
  27095. var data = seriesModel.getData();
  27096. var cartesian = seriesModel.coordinateSystem;
  27097. var baseAxis = cartesian.getBaseAxis();
  27098. var axisExtent = baseAxis.getExtent();
  27099. var bandWidth = baseAxis.type === 'category'
  27100. ? baseAxis.getBandWidth()
  27101. : (Math.abs(axisExtent[1] - axisExtent[0]) / data.count());
  27102. var barWidth = parsePercent$1(
  27103. seriesModel.get('barWidth'), bandWidth
  27104. );
  27105. var barMaxWidth = parsePercent$1(
  27106. seriesModel.get('barMaxWidth'), bandWidth
  27107. );
  27108. var barGap = seriesModel.get('barGap');
  27109. var barCategoryGap = seriesModel.get('barCategoryGap');
  27110. return {
  27111. bandWidth: bandWidth,
  27112. barWidth: barWidth,
  27113. barMaxWidth: barMaxWidth,
  27114. barGap: barGap,
  27115. barCategoryGap: barCategoryGap,
  27116. axisKey: getAxisKey(baseAxis),
  27117. stackId: getSeriesStackId(seriesModel)
  27118. };
  27119. });
  27120. return doCalBarWidthAndOffset(seriesInfoList, api);
  27121. }
  27122. function doCalBarWidthAndOffset(seriesInfoList, api) {
  27123. // Columns info on each category axis. Key is cartesian name
  27124. var columnsMap = {};
  27125. each$1(seriesInfoList, function (seriesInfo, idx) {
  27126. var axisKey = seriesInfo.axisKey;
  27127. var bandWidth = seriesInfo.bandWidth;
  27128. var columnsOnAxis = columnsMap[axisKey] || {
  27129. bandWidth: bandWidth,
  27130. remainedWidth: bandWidth,
  27131. autoWidthCount: 0,
  27132. categoryGap: '20%',
  27133. gap: '30%',
  27134. stacks: {}
  27135. };
  27136. var stacks = columnsOnAxis.stacks;
  27137. columnsMap[axisKey] = columnsOnAxis;
  27138. var stackId = seriesInfo.stackId;
  27139. if (!stacks[stackId]) {
  27140. columnsOnAxis.autoWidthCount++;
  27141. }
  27142. stacks[stackId] = stacks[stackId] || {
  27143. width: 0,
  27144. maxWidth: 0
  27145. };
  27146. // Caution: In a single coordinate system, these barGrid attributes
  27147. // will be shared by series. Consider that they have default values,
  27148. // only the attributes set on the last series will work.
  27149. // Do not change this fact unless there will be a break change.
  27150. // TODO
  27151. var barWidth = seriesInfo.barWidth;
  27152. if (barWidth && !stacks[stackId].width) {
  27153. // See #6312, do not restrict width.
  27154. stacks[stackId].width = barWidth;
  27155. barWidth = Math.min(columnsOnAxis.remainedWidth, barWidth);
  27156. columnsOnAxis.remainedWidth -= barWidth;
  27157. }
  27158. var barMaxWidth = seriesInfo.barMaxWidth;
  27159. barMaxWidth && (stacks[stackId].maxWidth = barMaxWidth);
  27160. var barGap = seriesInfo.barGap;
  27161. (barGap != null) && (columnsOnAxis.gap = barGap);
  27162. var barCategoryGap = seriesInfo.barCategoryGap;
  27163. (barCategoryGap != null) && (columnsOnAxis.categoryGap = barCategoryGap);
  27164. });
  27165. var result = {};
  27166. each$1(columnsMap, function (columnsOnAxis, coordSysName) {
  27167. result[coordSysName] = {};
  27168. var stacks = columnsOnAxis.stacks;
  27169. var bandWidth = columnsOnAxis.bandWidth;
  27170. var categoryGap = parsePercent$1(columnsOnAxis.categoryGap, bandWidth);
  27171. var barGapPercent = parsePercent$1(columnsOnAxis.gap, 1);
  27172. var remainedWidth = columnsOnAxis.remainedWidth;
  27173. var autoWidthCount = columnsOnAxis.autoWidthCount;
  27174. var autoWidth = (remainedWidth - categoryGap)
  27175. / (autoWidthCount + (autoWidthCount - 1) * barGapPercent);
  27176. autoWidth = Math.max(autoWidth, 0);
  27177. // Find if any auto calculated bar exceeded maxBarWidth
  27178. each$1(stacks, function (column, stack) {
  27179. var maxWidth = column.maxWidth;
  27180. if (maxWidth && maxWidth < autoWidth) {
  27181. maxWidth = Math.min(maxWidth, remainedWidth);
  27182. if (column.width) {
  27183. maxWidth = Math.min(maxWidth, column.width);
  27184. }
  27185. remainedWidth -= maxWidth;
  27186. column.width = maxWidth;
  27187. autoWidthCount--;
  27188. }
  27189. });
  27190. // Recalculate width again
  27191. autoWidth = (remainedWidth - categoryGap)
  27192. / (autoWidthCount + (autoWidthCount - 1) * barGapPercent);
  27193. autoWidth = Math.max(autoWidth, 0);
  27194. var widthSum = 0;
  27195. var lastColumn;
  27196. each$1(stacks, function (column, idx) {
  27197. if (!column.width) {
  27198. column.width = autoWidth;
  27199. }
  27200. lastColumn = column;
  27201. widthSum += column.width * (1 + barGapPercent);
  27202. });
  27203. if (lastColumn) {
  27204. widthSum -= lastColumn.width * barGapPercent;
  27205. }
  27206. var offset = -widthSum / 2;
  27207. each$1(stacks, function (column, stackId) {
  27208. result[coordSysName][stackId] = result[coordSysName][stackId] || {
  27209. offset: offset,
  27210. width: column.width
  27211. };
  27212. offset += column.width * (1 + barGapPercent);
  27213. });
  27214. });
  27215. return result;
  27216. }
  27217. /**
  27218. * @param {string} seriesType
  27219. * @param {module:echarts/model/Global} ecModel
  27220. * @param {module:echarts/ExtensionAPI} api
  27221. */
  27222. function layout(seriesType, ecModel, api) {
  27223. var seriesModels = [];
  27224. ecModel.eachSeriesByType(seriesType, function (seriesModel) {
  27225. // Check series coordinate, do layout for cartesian2d only
  27226. if (seriesModel.coordinateSystem && seriesModel.coordinateSystem.type === 'cartesian2d') {
  27227. seriesModels.push(seriesModel);
  27228. }
  27229. });
  27230. var barWidthAndOffset = calBarWidthAndOffset(seriesModels);
  27231. var lastStackCoords = {};
  27232. each$1(seriesModels, function (seriesModel) {
  27233. var data = seriesModel.getData();
  27234. var cartesian = seriesModel.coordinateSystem;
  27235. var baseAxis = cartesian.getBaseAxis();
  27236. var stackId = getSeriesStackId(seriesModel);
  27237. var columnLayoutInfo = barWidthAndOffset[getAxisKey(baseAxis)][stackId];
  27238. var columnOffset = columnLayoutInfo.offset;
  27239. var columnWidth = columnLayoutInfo.width;
  27240. var valueAxis = cartesian.getOtherAxis(baseAxis);
  27241. var barMinHeight = seriesModel.get('barMinHeight') || 0;
  27242. lastStackCoords[stackId] = lastStackCoords[stackId] || [];
  27243. data.setLayout({
  27244. offset: columnOffset,
  27245. size: columnWidth
  27246. });
  27247. var valueDim = data.mapDimension(valueAxis.dim);
  27248. var baseDim = data.mapDimension(baseAxis.dim);
  27249. var stacked = isDimensionStacked(data, valueDim, baseDim);
  27250. var isValueAxisH = valueAxis.isHorizontal();
  27251. var valueAxisStart = (baseAxis.onZero || stacked)
  27252. ? valueAxis.toGlobalCoord(valueAxis.dataToCoord(0))
  27253. : valueAxis.getGlobalExtent()[0];
  27254. for (var idx = 0, len = data.count(); idx < len; idx++) {
  27255. var value = data.get(valueDim, idx);
  27256. var baseValue = data.get(baseDim, idx);
  27257. if (isNaN(value)) {
  27258. continue;
  27259. }
  27260. var sign = value >= 0 ? 'p' : 'n';
  27261. var baseCoord = valueAxisStart;
  27262. // Because of the barMinHeight, we can not use the value in
  27263. // stackResultDimension directly.
  27264. if (stacked) {
  27265. // Only ordinal axis can be stacked.
  27266. if (!lastStackCoords[stackId][baseValue]) {
  27267. lastStackCoords[stackId][baseValue] = {
  27268. p: valueAxisStart, // Positive stack
  27269. n: valueAxisStart // Negative stack
  27270. };
  27271. }
  27272. // Should also consider #4243
  27273. baseCoord = lastStackCoords[stackId][baseValue][sign];
  27274. }
  27275. var x;
  27276. var y;
  27277. var width;
  27278. var height;
  27279. if (isValueAxisH) {
  27280. var coord = cartesian.dataToPoint([value, baseValue]);
  27281. x = baseCoord;
  27282. y = coord[1] + columnOffset;
  27283. width = coord[0] - valueAxisStart;
  27284. height = columnWidth;
  27285. if (Math.abs(width) < barMinHeight) {
  27286. width = (width < 0 ? -1 : 1) * barMinHeight;
  27287. }
  27288. stacked && (lastStackCoords[stackId][baseValue][sign] += width);
  27289. }
  27290. else {
  27291. var coord = cartesian.dataToPoint([baseValue, value]);
  27292. x = coord[0] + columnOffset;
  27293. y = baseCoord;
  27294. width = columnWidth;
  27295. height = coord[1] - valueAxisStart;
  27296. if (Math.abs(height) < barMinHeight) {
  27297. // Include zero to has a positive bar
  27298. height = (height <= 0 ? -1 : 1) * barMinHeight;
  27299. }
  27300. stacked && (lastStackCoords[stackId][baseValue][sign] += height);
  27301. }
  27302. data.setItemLayout(idx, {
  27303. x: x,
  27304. y: y,
  27305. width: width,
  27306. height: height
  27307. });
  27308. }
  27309. }, this);
  27310. }
  27311. // [About UTC and local time zone]:
  27312. // In most cases, `number.parseDate` will treat input data string as local time
  27313. // (except time zone is specified in time string). And `format.formateTime` returns
  27314. // local time by default. option.useUTC is false by default. This design have
  27315. // concidered these common case:
  27316. // (1) Time that is persistent in server is in UTC, but it is needed to be diplayed
  27317. // in local time by default.
  27318. // (2) By default, the input data string (e.g., '2011-01-02') should be displayed
  27319. // as its original time, without any time difference.
  27320. var intervalScaleProto = IntervalScale.prototype;
  27321. var mathCeil = Math.ceil;
  27322. var mathFloor = Math.floor;
  27323. var ONE_SECOND = 1000;
  27324. var ONE_MINUTE = ONE_SECOND * 60;
  27325. var ONE_HOUR = ONE_MINUTE * 60;
  27326. var ONE_DAY = ONE_HOUR * 24;
  27327. // FIXME 公用?
  27328. var bisect = function (a, x, lo, hi) {
  27329. while (lo < hi) {
  27330. var mid = lo + hi >>> 1;
  27331. if (a[mid][1] < x) {
  27332. lo = mid + 1;
  27333. }
  27334. else {
  27335. hi = mid;
  27336. }
  27337. }
  27338. return lo;
  27339. };
  27340. /**
  27341. * @alias module:echarts/coord/scale/Time
  27342. * @constructor
  27343. */
  27344. var TimeScale = IntervalScale.extend({
  27345. type: 'time',
  27346. /**
  27347. * @override
  27348. */
  27349. getLabel: function (val) {
  27350. var stepLvl = this._stepLvl;
  27351. var date = new Date(val);
  27352. return formatTime(stepLvl[0], date, this.getSetting('useUTC'));
  27353. },
  27354. /**
  27355. * @override
  27356. */
  27357. niceExtent: function (opt) {
  27358. var extent = this._extent;
  27359. // If extent start and end are same, expand them
  27360. if (extent[0] === extent[1]) {
  27361. // Expand extent
  27362. extent[0] -= ONE_DAY;
  27363. extent[1] += ONE_DAY;
  27364. }
  27365. // If there are no data and extent are [Infinity, -Infinity]
  27366. if (extent[1] === -Infinity && extent[0] === Infinity) {
  27367. var d = new Date();
  27368. extent[1] = +new Date(d.getFullYear(), d.getMonth(), d.getDate());
  27369. extent[0] = extent[1] - ONE_DAY;
  27370. }
  27371. this.niceTicks(opt.splitNumber, opt.minInterval, opt.maxInterval);
  27372. // var extent = this._extent;
  27373. var interval = this._interval;
  27374. if (!opt.fixMin) {
  27375. extent[0] = round$1(mathFloor(extent[0] / interval) * interval);
  27376. }
  27377. if (!opt.fixMax) {
  27378. extent[1] = round$1(mathCeil(extent[1] / interval) * interval);
  27379. }
  27380. },
  27381. /**
  27382. * @override
  27383. */
  27384. niceTicks: function (approxTickNum, minInterval, maxInterval) {
  27385. approxTickNum = approxTickNum || 10;
  27386. var extent = this._extent;
  27387. var span = extent[1] - extent[0];
  27388. var approxInterval = span / approxTickNum;
  27389. if (minInterval != null && approxInterval < minInterval) {
  27390. approxInterval = minInterval;
  27391. }
  27392. if (maxInterval != null && approxInterval > maxInterval) {
  27393. approxInterval = maxInterval;
  27394. }
  27395. var scaleLevelsLen = scaleLevels.length;
  27396. var idx = bisect(scaleLevels, approxInterval, 0, scaleLevelsLen);
  27397. var level = scaleLevels[Math.min(idx, scaleLevelsLen - 1)];
  27398. var interval = level[1];
  27399. // Same with interval scale if span is much larger than 1 year
  27400. if (level[0] === 'year') {
  27401. var yearSpan = span / interval;
  27402. // From "Nice Numbers for Graph Labels" of Graphic Gems
  27403. // var niceYearSpan = numberUtil.nice(yearSpan, false);
  27404. var yearStep = nice(yearSpan / approxTickNum, true);
  27405. interval *= yearStep;
  27406. }
  27407. var timezoneOffset = this.getSetting('useUTC')
  27408. ? 0 : (new Date(+extent[0] || +extent[1])).getTimezoneOffset() * 60 * 1000;
  27409. var niceExtent = [
  27410. Math.round(mathCeil((extent[0] - timezoneOffset) / interval) * interval + timezoneOffset),
  27411. Math.round(mathFloor((extent[1] - timezoneOffset) / interval) * interval + timezoneOffset)
  27412. ];
  27413. fixExtent(niceExtent, extent);
  27414. this._stepLvl = level;
  27415. // Interval will be used in getTicks
  27416. this._interval = interval;
  27417. this._niceExtent = niceExtent;
  27418. },
  27419. parse: function (val) {
  27420. // val might be float.
  27421. return +parseDate(val);
  27422. }
  27423. });
  27424. each$1(['contain', 'normalize'], function (methodName) {
  27425. TimeScale.prototype[methodName] = function (val) {
  27426. return intervalScaleProto[methodName].call(this, this.parse(val));
  27427. };
  27428. });
  27429. // Steps from d3
  27430. var scaleLevels = [
  27431. // Format interval
  27432. ['hh:mm:ss', ONE_SECOND], // 1s
  27433. ['hh:mm:ss', ONE_SECOND * 5], // 5s
  27434. ['hh:mm:ss', ONE_SECOND * 10], // 10s
  27435. ['hh:mm:ss', ONE_SECOND * 15], // 15s
  27436. ['hh:mm:ss', ONE_SECOND * 30], // 30s
  27437. ['hh:mm\nMM-dd', ONE_MINUTE], // 1m
  27438. ['hh:mm\nMM-dd', ONE_MINUTE * 5], // 5m
  27439. ['hh:mm\nMM-dd', ONE_MINUTE * 10], // 10m
  27440. ['hh:mm\nMM-dd', ONE_MINUTE * 15], // 15m
  27441. ['hh:mm\nMM-dd', ONE_MINUTE * 30], // 30m
  27442. ['hh:mm\nMM-dd', ONE_HOUR], // 1h
  27443. ['hh:mm\nMM-dd', ONE_HOUR * 2], // 2h
  27444. ['hh:mm\nMM-dd', ONE_HOUR * 6], // 6h
  27445. ['hh:mm\nMM-dd', ONE_HOUR * 12], // 12h
  27446. ['MM-dd\nyyyy', ONE_DAY], // 1d
  27447. ['MM-dd\nyyyy', ONE_DAY * 2], // 2d
  27448. ['MM-dd\nyyyy', ONE_DAY * 3], // 3d
  27449. ['MM-dd\nyyyy', ONE_DAY * 4], // 4d
  27450. ['MM-dd\nyyyy', ONE_DAY * 5], // 5d
  27451. ['MM-dd\nyyyy', ONE_DAY * 6], // 6d
  27452. ['week', ONE_DAY * 7], // 7d
  27453. ['MM-dd\nyyyy', ONE_DAY * 10], // 10d
  27454. ['week', ONE_DAY * 14], // 2w
  27455. ['week', ONE_DAY * 21], // 3w
  27456. ['month', ONE_DAY * 31], // 1M
  27457. ['week', ONE_DAY * 42], // 6w
  27458. ['month', ONE_DAY * 62], // 2M
  27459. ['week', ONE_DAY * 42], // 10w
  27460. ['quarter', ONE_DAY * 380 / 4], // 3M
  27461. ['month', ONE_DAY * 31 * 4], // 4M
  27462. ['month', ONE_DAY * 31 * 5], // 5M
  27463. ['half-year', ONE_DAY * 380 / 2], // 6M
  27464. ['month', ONE_DAY * 31 * 8], // 8M
  27465. ['month', ONE_DAY * 31 * 10], // 10M
  27466. ['year', ONE_DAY * 380] // 1Y
  27467. ];
  27468. /**
  27469. * @param {module:echarts/model/Model}
  27470. * @return {module:echarts/scale/Time}
  27471. */
  27472. TimeScale.create = function (model) {
  27473. return new TimeScale({useUTC: model.ecModel.get('useUTC')});
  27474. };
  27475. /**
  27476. * Log scale
  27477. * @module echarts/scale/Log
  27478. */
  27479. // Use some method of IntervalScale
  27480. var scaleProto$1 = Scale.prototype;
  27481. var intervalScaleProto$1 = IntervalScale.prototype;
  27482. var getPrecisionSafe$1 = getPrecisionSafe;
  27483. var roundingErrorFix = round$1;
  27484. var mathFloor$1 = Math.floor;
  27485. var mathCeil$1 = Math.ceil;
  27486. var mathPow$1 = Math.pow;
  27487. var mathLog = Math.log;
  27488. var LogScale = Scale.extend({
  27489. type: 'log',
  27490. base: 10,
  27491. $constructor: function () {
  27492. Scale.apply(this, arguments);
  27493. this._originalScale = new IntervalScale();
  27494. },
  27495. /**
  27496. * @return {Array.<number>}
  27497. */
  27498. getTicks: function () {
  27499. var originalScale = this._originalScale;
  27500. var extent = this._extent;
  27501. var originalExtent = originalScale.getExtent();
  27502. return map(intervalScaleProto$1.getTicks.call(this), function (val) {
  27503. var powVal = round$1(mathPow$1(this.base, val));
  27504. // Fix #4158
  27505. powVal = (val === extent[0] && originalScale.__fixMin)
  27506. ? fixRoundingError(powVal, originalExtent[0])
  27507. : powVal;
  27508. powVal = (val === extent[1] && originalScale.__fixMax)
  27509. ? fixRoundingError(powVal, originalExtent[1])
  27510. : powVal;
  27511. return powVal;
  27512. }, this);
  27513. },
  27514. /**
  27515. * @param {number} val
  27516. * @return {string}
  27517. */
  27518. getLabel: intervalScaleProto$1.getLabel,
  27519. /**
  27520. * @param {number} val
  27521. * @return {number}
  27522. */
  27523. scale: function (val) {
  27524. val = scaleProto$1.scale.call(this, val);
  27525. return mathPow$1(this.base, val);
  27526. },
  27527. /**
  27528. * @param {number} start
  27529. * @param {number} end
  27530. */
  27531. setExtent: function (start, end) {
  27532. var base = this.base;
  27533. start = mathLog(start) / mathLog(base);
  27534. end = mathLog(end) / mathLog(base);
  27535. intervalScaleProto$1.setExtent.call(this, start, end);
  27536. },
  27537. /**
  27538. * @return {number} end
  27539. */
  27540. getExtent: function () {
  27541. var base = this.base;
  27542. var extent = scaleProto$1.getExtent.call(this);
  27543. extent[0] = mathPow$1(base, extent[0]);
  27544. extent[1] = mathPow$1(base, extent[1]);
  27545. // Fix #4158
  27546. var originalScale = this._originalScale;
  27547. var originalExtent = originalScale.getExtent();
  27548. originalScale.__fixMin && (extent[0] = fixRoundingError(extent[0], originalExtent[0]));
  27549. originalScale.__fixMax && (extent[1] = fixRoundingError(extent[1], originalExtent[1]));
  27550. return extent;
  27551. },
  27552. /**
  27553. * @param {Array.<number>} extent
  27554. */
  27555. unionExtent: function (extent) {
  27556. this._originalScale.unionExtent(extent);
  27557. var base = this.base;
  27558. extent[0] = mathLog(extent[0]) / mathLog(base);
  27559. extent[1] = mathLog(extent[1]) / mathLog(base);
  27560. scaleProto$1.unionExtent.call(this, extent);
  27561. },
  27562. /**
  27563. * @override
  27564. */
  27565. unionExtentFromData: function (data, dim) {
  27566. // TODO
  27567. // filter value that <= 0
  27568. this.unionExtent(data.getApproximateExtent(dim));
  27569. },
  27570. /**
  27571. * Update interval and extent of intervals for nice ticks
  27572. * @param {number} [approxTickNum = 10] Given approx tick number
  27573. */
  27574. niceTicks: function (approxTickNum) {
  27575. approxTickNum = approxTickNum || 10;
  27576. var extent = this._extent;
  27577. var span = extent[1] - extent[0];
  27578. if (span === Infinity || span <= 0) {
  27579. return;
  27580. }
  27581. var interval = quantity(span);
  27582. var err = approxTickNum / span * interval;
  27583. // Filter ticks to get closer to the desired count.
  27584. if (err <= 0.5) {
  27585. interval *= 10;
  27586. }
  27587. // Interval should be integer
  27588. while (!isNaN(interval) && Math.abs(interval) < 1 && Math.abs(interval) > 0) {
  27589. interval *= 10;
  27590. }
  27591. var niceExtent = [
  27592. round$1(mathCeil$1(extent[0] / interval) * interval),
  27593. round$1(mathFloor$1(extent[1] / interval) * interval)
  27594. ];
  27595. this._interval = interval;
  27596. this._niceExtent = niceExtent;
  27597. },
  27598. /**
  27599. * Nice extent.
  27600. * @override
  27601. */
  27602. niceExtent: function (opt) {
  27603. intervalScaleProto$1.niceExtent.call(this, opt);
  27604. var originalScale = this._originalScale;
  27605. originalScale.__fixMin = opt.fixMin;
  27606. originalScale.__fixMax = opt.fixMax;
  27607. }
  27608. });
  27609. each$1(['contain', 'normalize'], function (methodName) {
  27610. LogScale.prototype[methodName] = function (val) {
  27611. val = mathLog(val) / mathLog(this.base);
  27612. return scaleProto$1[methodName].call(this, val);
  27613. };
  27614. });
  27615. LogScale.create = function () {
  27616. return new LogScale();
  27617. };
  27618. function fixRoundingError(val, originalVal) {
  27619. return roundingErrorFix(val, getPrecisionSafe$1(originalVal));
  27620. }
  27621. /**
  27622. * Get axis scale extent before niced.
  27623. * Item of returned array can only be number (including Infinity and NaN).
  27624. */
  27625. function getScaleExtent(scale, model) {
  27626. var scaleType = scale.type;
  27627. var min = model.getMin();
  27628. var max = model.getMax();
  27629. var fixMin = min != null;
  27630. var fixMax = max != null;
  27631. var originalExtent = scale.getExtent();
  27632. var axisDataLen;
  27633. var boundaryGap;
  27634. var span;
  27635. if (scaleType === 'ordinal') {
  27636. axisDataLen = model.getCategories().length;
  27637. }
  27638. else {
  27639. boundaryGap = model.get('boundaryGap');
  27640. if (!isArray(boundaryGap)) {
  27641. boundaryGap = [boundaryGap || 0, boundaryGap || 0];
  27642. }
  27643. if (typeof boundaryGap[0] === 'boolean') {
  27644. if (__DEV__) {
  27645. console.warn('Boolean type for boundaryGap is only '
  27646. + 'allowed for ordinal axis. Please use string in '
  27647. + 'percentage instead, e.g., "20%". Currently, '
  27648. + 'boundaryGap is set to be 0.');
  27649. }
  27650. boundaryGap = [0, 0];
  27651. }
  27652. boundaryGap[0] = parsePercent$1(boundaryGap[0], 1);
  27653. boundaryGap[1] = parsePercent$1(boundaryGap[1], 1);
  27654. span = (originalExtent[1] - originalExtent[0])
  27655. || Math.abs(originalExtent[0]);
  27656. }
  27657. // Notice: When min/max is not set (that is, when there are null/undefined,
  27658. // which is the most common case), these cases should be ensured:
  27659. // (1) For 'ordinal', show all axis.data.
  27660. // (2) For others:
  27661. // + `boundaryGap` is applied (if min/max set, boundaryGap is
  27662. // disabled).
  27663. // + If `needCrossZero`, min/max should be zero, otherwise, min/max should
  27664. // be the result that originalExtent enlarged by boundaryGap.
  27665. // (3) If no data, it should be ensured that `scale.setBlank` is set.
  27666. // FIXME
  27667. // (1) When min/max is 'dataMin' or 'dataMax', should boundaryGap be able to used?
  27668. // (2) When `needCrossZero` and all data is positive/negative, should it be ensured
  27669. // that the results processed by boundaryGap are positive/negative?
  27670. if (min == null) {
  27671. min = scaleType === 'ordinal'
  27672. ? (axisDataLen ? 0 : NaN)
  27673. : originalExtent[0] - boundaryGap[0] * span;
  27674. }
  27675. if (max == null) {
  27676. max = scaleType === 'ordinal'
  27677. ? (axisDataLen ? axisDataLen - 1 : NaN)
  27678. : originalExtent[1] + boundaryGap[1] * span;
  27679. }
  27680. if (min === 'dataMin') {
  27681. min = originalExtent[0];
  27682. }
  27683. else if (typeof min === 'function') {
  27684. min = min({
  27685. min: originalExtent[0],
  27686. max: originalExtent[1]
  27687. });
  27688. }
  27689. if (max === 'dataMax') {
  27690. max = originalExtent[1];
  27691. }
  27692. else if (typeof max === 'function') {
  27693. max = max({
  27694. min: originalExtent[0],
  27695. max: originalExtent[1]
  27696. });
  27697. }
  27698. (min == null || !isFinite(min)) && (min = NaN);
  27699. (max == null || !isFinite(max)) && (max = NaN);
  27700. scale.setBlank(eqNaN(min) || eqNaN(max));
  27701. // Evaluate if axis needs cross zero
  27702. if (model.getNeedCrossZero()) {
  27703. // Axis is over zero and min is not set
  27704. if (min > 0 && max > 0 && !fixMin) {
  27705. min = 0;
  27706. }
  27707. // Axis is under zero and max is not set
  27708. if (min < 0 && max < 0 && !fixMax) {
  27709. max = 0;
  27710. }
  27711. }
  27712. // If bars are placed on a base axis of type time or interval account for axis boundary overflow and current axis
  27713. // is base axis
  27714. // FIXME
  27715. // (1) Consider support value axis, where below zero and axis `onZero` should be handled properly.
  27716. // (2) Refactor the logic with `barGrid`. Is it not need to `calBarWidthAndOffset` twice with different extent?
  27717. // Should not depend on series type `bar`?
  27718. // (3) Fix that might overlap when using dataZoom.
  27719. // (4) Consider other chart types using `barGrid`?
  27720. // See #6728, #4862, `test/bar-overflow-time-plot.html`
  27721. var ecModel = model.ecModel;
  27722. if (ecModel && (scaleType === 'time' /*|| scaleType === 'interval' */)) {
  27723. var barSeriesModels = [];
  27724. var isBaseAxisAndHasBarSeries;
  27725. ecModel.eachSeriesByType('bar', function (seriesModel) {
  27726. if (seriesModel.coordinateSystem && seriesModel.coordinateSystem.type === 'cartesian2d') {
  27727. barSeriesModels.push(seriesModel);
  27728. isBaseAxisAndHasBarSeries |= seriesModel.getBaseAxis() === model.axis;
  27729. }
  27730. });
  27731. if (isBaseAxisAndHasBarSeries) {
  27732. // Adjust axis min and max to account for overflow
  27733. var adjustedScale = adjustScaleForOverflow(min, max, model, barSeriesModels);
  27734. min = adjustedScale.min;
  27735. max = adjustedScale.max;
  27736. }
  27737. }
  27738. return [min, max];
  27739. }
  27740. function adjustScaleForOverflow(min, max, model, barSeriesModels) {
  27741. // Get Axis Length
  27742. var axisExtent = model.axis.getExtent();
  27743. var axisLength = axisExtent[1] - axisExtent[0];
  27744. // Calculate placement of bars on axis
  27745. var barWidthAndOffset = calBarWidthAndOffset(barSeriesModels);
  27746. // Get bars on current base axis and calculate min and max overflow
  27747. var baseAxisKey = model.axis.dim + model.axis.index;
  27748. var barsOnCurrentAxis = barWidthAndOffset[baseAxisKey];
  27749. if (barsOnCurrentAxis === undefined) {
  27750. return {min: min, max: max};
  27751. }
  27752. var minOverflow = Infinity;
  27753. each$1(barsOnCurrentAxis, function (item) {
  27754. minOverflow = Math.min(item.offset, minOverflow);
  27755. });
  27756. var maxOverflow = -Infinity;
  27757. each$1(barsOnCurrentAxis, function (item) {
  27758. maxOverflow = Math.max(item.offset + item.width, maxOverflow);
  27759. });
  27760. minOverflow = Math.abs(minOverflow);
  27761. maxOverflow = Math.abs(maxOverflow);
  27762. var totalOverFlow = minOverflow + maxOverflow;
  27763. // Calulate required buffer based on old range and overflow
  27764. var oldRange = max - min;
  27765. var oldRangePercentOfNew = (1 - (minOverflow + maxOverflow) / axisLength);
  27766. var overflowBuffer = ((oldRange / oldRangePercentOfNew) - oldRange);
  27767. max += overflowBuffer * (maxOverflow / totalOverFlow);
  27768. min -= overflowBuffer * (minOverflow / totalOverFlow);
  27769. return {min: min, max: max};
  27770. }
  27771. function niceScaleExtent$1(scale, model) {
  27772. var extent = getScaleExtent(scale, model);
  27773. var fixMin = model.getMin() != null;
  27774. var fixMax = model.getMax() != null;
  27775. var splitNumber = model.get('splitNumber');
  27776. if (scale.type === 'log') {
  27777. scale.base = model.get('logBase');
  27778. }
  27779. var scaleType = scale.type;
  27780. scale.setExtent(extent[0], extent[1]);
  27781. scale.niceExtent({
  27782. splitNumber: splitNumber,
  27783. fixMin: fixMin,
  27784. fixMax: fixMax,
  27785. minInterval: (scaleType === 'interval' || scaleType === 'time')
  27786. ? model.get('minInterval') : null,
  27787. maxInterval: (scaleType === 'interval' || scaleType === 'time')
  27788. ? model.get('maxInterval') : null
  27789. });
  27790. // If some one specified the min, max. And the default calculated interval
  27791. // is not good enough. He can specify the interval. It is often appeared
  27792. // in angle axis with angle 0 - 360. Interval calculated in interval scale is hard
  27793. // to be 60.
  27794. // FIXME
  27795. var interval = model.get('interval');
  27796. if (interval != null) {
  27797. scale.setInterval && scale.setInterval(interval);
  27798. }
  27799. }
  27800. /**
  27801. * @param {module:echarts/model/Model} model
  27802. * @param {string} [axisType] Default retrieve from model.type
  27803. * @return {module:echarts/scale/*}
  27804. */
  27805. function createScaleByModel(model, axisType) {
  27806. axisType = axisType || model.get('type');
  27807. if (axisType) {
  27808. switch (axisType) {
  27809. // Buildin scale
  27810. case 'category':
  27811. return new OrdinalScale(
  27812. model.getOrdinalMeta
  27813. ? model.getOrdinalMeta()
  27814. : model.getCategories(),
  27815. [Infinity, -Infinity]
  27816. );
  27817. case 'value':
  27818. return new IntervalScale();
  27819. // Extended scale, like time and log
  27820. default:
  27821. return (Scale.getClass(axisType) || IntervalScale).create(model);
  27822. }
  27823. }
  27824. }
  27825. /**
  27826. * Check if the axis corss 0
  27827. */
  27828. function ifAxisCrossZero$1(axis) {
  27829. var dataExtent = axis.scale.getExtent();
  27830. var min = dataExtent[0];
  27831. var max = dataExtent[1];
  27832. return !((min > 0 && max > 0) || (min < 0 && max < 0));
  27833. }
  27834. /**
  27835. * @param {Array.<number>} tickCoords In axis self coordinate.
  27836. * @param {Array.<string>} labels
  27837. * @param {string} font
  27838. * @param {number} axisRotate 0: towards right horizontally, clock-wise is negative.
  27839. * @param {number} [labelRotate=0] 0: towards right horizontally, clock-wise is negative.
  27840. * @return {number}
  27841. */
  27842. function getAxisLabelInterval(tickCoords, labels, font, axisRotate, labelRotate) {
  27843. var textSpaceTakenRect;
  27844. var autoLabelInterval = 0;
  27845. var accumulatedLabelInterval = 0;
  27846. var rotation = (axisRotate - labelRotate) / 180 * Math.PI;
  27847. var step = 1;
  27848. if (labels.length > 40) {
  27849. // Simple optimization for large amount of labels
  27850. step = Math.floor(labels.length / 40);
  27851. }
  27852. for (var i = 0; i < tickCoords.length; i += step) {
  27853. var tickCoord = tickCoords[i];
  27854. // Not precise, do not consider align and vertical align
  27855. // and each distance from axis line yet.
  27856. var rect = getBoundingRect(
  27857. labels[i], font, 'center', 'top'
  27858. );
  27859. rect.x += tickCoord * Math.cos(rotation);
  27860. rect.y += tickCoord * Math.sin(rotation);
  27861. // Magic number
  27862. rect.width *= 1.3;
  27863. rect.height *= 1.3;
  27864. if (!textSpaceTakenRect) {
  27865. textSpaceTakenRect = rect.clone();
  27866. }
  27867. // There is no space for current label;
  27868. else if (textSpaceTakenRect.intersect(rect)) {
  27869. accumulatedLabelInterval++;
  27870. autoLabelInterval = Math.max(autoLabelInterval, accumulatedLabelInterval);
  27871. }
  27872. else {
  27873. textSpaceTakenRect.union(rect);
  27874. // Reset
  27875. accumulatedLabelInterval = 0;
  27876. }
  27877. }
  27878. if (autoLabelInterval === 0 && step > 1) {
  27879. return step;
  27880. }
  27881. return (autoLabelInterval + 1) * step - 1;
  27882. }
  27883. /**
  27884. * @param {Object} axis
  27885. * @param {Function} labelFormatter
  27886. * @return {Array.<string>}
  27887. */
  27888. function getFormattedLabels(axis, labelFormatter) {
  27889. var scale = axis.scale;
  27890. var labels = scale.getTicksLabels();
  27891. var ticks = scale.getTicks();
  27892. if (typeof labelFormatter === 'string') {
  27893. labelFormatter = (function (tpl) {
  27894. return function (val) {
  27895. return tpl.replace('{value}', val != null ? val : '');
  27896. };
  27897. })(labelFormatter);
  27898. // Consider empty array
  27899. return map(labels, labelFormatter);
  27900. }
  27901. else if (typeof labelFormatter === 'function') {
  27902. return map(ticks, function (tick, idx) {
  27903. return labelFormatter(
  27904. getAxisRawValue(axis, tick),
  27905. idx
  27906. );
  27907. }, this);
  27908. }
  27909. else {
  27910. return labels;
  27911. }
  27912. }
  27913. function getAxisRawValue(axis, value) {
  27914. // In category axis with data zoom, tick is not the original
  27915. // index of axis.data. So tick should not be exposed to user
  27916. // in category axis.
  27917. return axis.type === 'category' ? axis.scale.getLabel(value) : value;
  27918. }
  27919. /**
  27920. * Cartesian coordinate system
  27921. * @module echarts/coord/Cartesian
  27922. *
  27923. */
  27924. function dimAxisMapper(dim) {
  27925. return this._axes[dim];
  27926. }
  27927. /**
  27928. * @alias module:echarts/coord/Cartesian
  27929. * @constructor
  27930. */
  27931. var Cartesian = function (name) {
  27932. this._axes = {};
  27933. this._dimList = [];
  27934. /**
  27935. * @type {string}
  27936. */
  27937. this.name = name || '';
  27938. };
  27939. Cartesian.prototype = {
  27940. constructor: Cartesian,
  27941. type: 'cartesian',
  27942. /**
  27943. * Get axis
  27944. * @param {number|string} dim
  27945. * @return {module:echarts/coord/Cartesian~Axis}
  27946. */
  27947. getAxis: function (dim) {
  27948. return this._axes[dim];
  27949. },
  27950. /**
  27951. * Get axes list
  27952. * @return {Array.<module:echarts/coord/Cartesian~Axis>}
  27953. */
  27954. getAxes: function () {
  27955. return map(this._dimList, dimAxisMapper, this);
  27956. },
  27957. /**
  27958. * Get axes list by given scale type
  27959. */
  27960. getAxesByScale: function (scaleType) {
  27961. scaleType = scaleType.toLowerCase();
  27962. return filter(
  27963. this.getAxes(),
  27964. function (axis) {
  27965. return axis.scale.type === scaleType;
  27966. }
  27967. );
  27968. },
  27969. /**
  27970. * Add axis
  27971. * @param {module:echarts/coord/Cartesian.Axis}
  27972. */
  27973. addAxis: function (axis) {
  27974. var dim = axis.dim;
  27975. this._axes[dim] = axis;
  27976. this._dimList.push(dim);
  27977. },
  27978. /**
  27979. * Convert data to coord in nd space
  27980. * @param {Array.<number>|Object.<string, number>} val
  27981. * @return {Array.<number>|Object.<string, number>}
  27982. */
  27983. dataToCoord: function (val) {
  27984. return this._dataCoordConvert(val, 'dataToCoord');
  27985. },
  27986. /**
  27987. * Convert coord in nd space to data
  27988. * @param {Array.<number>|Object.<string, number>} val
  27989. * @return {Array.<number>|Object.<string, number>}
  27990. */
  27991. coordToData: function (val) {
  27992. return this._dataCoordConvert(val, 'coordToData');
  27993. },
  27994. _dataCoordConvert: function (input, method) {
  27995. var dimList = this._dimList;
  27996. var output = input instanceof Array ? [] : {};
  27997. for (var i = 0; i < dimList.length; i++) {
  27998. var dim = dimList[i];
  27999. var axis = this._axes[dim];
  28000. output[dim] = axis[method](input[dim]);
  28001. }
  28002. return output;
  28003. }
  28004. };
  28005. function Cartesian2D(name) {
  28006. Cartesian.call(this, name);
  28007. }
  28008. Cartesian2D.prototype = {
  28009. constructor: Cartesian2D,
  28010. type: 'cartesian2d',
  28011. /**
  28012. * @type {Array.<string>}
  28013. * @readOnly
  28014. */
  28015. dimensions: ['x', 'y'],
  28016. /**
  28017. * Base axis will be used on stacking.
  28018. *
  28019. * @return {module:echarts/coord/cartesian/Axis2D}
  28020. */
  28021. getBaseAxis: function () {
  28022. return this.getAxesByScale('ordinal')[0]
  28023. || this.getAxesByScale('time')[0]
  28024. || this.getAxis('x');
  28025. },
  28026. /**
  28027. * If contain point
  28028. * @param {Array.<number>} point
  28029. * @return {boolean}
  28030. */
  28031. containPoint: function (point) {
  28032. var axisX = this.getAxis('x');
  28033. var axisY = this.getAxis('y');
  28034. return axisX.contain(axisX.toLocalCoord(point[0]))
  28035. && axisY.contain(axisY.toLocalCoord(point[1]));
  28036. },
  28037. /**
  28038. * If contain data
  28039. * @param {Array.<number>} data
  28040. * @return {boolean}
  28041. */
  28042. containData: function (data) {
  28043. return this.getAxis('x').containData(data[0])
  28044. && this.getAxis('y').containData(data[1]);
  28045. },
  28046. /**
  28047. * @param {Array.<number>} data
  28048. * @param {Array.<number>} out
  28049. * @return {Array.<number>}
  28050. */
  28051. dataToPoint: function (data, reserved, out) {
  28052. var xAxis = this.getAxis('x');
  28053. var yAxis = this.getAxis('y');
  28054. out = out || [];
  28055. out[0] = xAxis.toGlobalCoord(xAxis.dataToCoord(data[0]));
  28056. out[1] = yAxis.toGlobalCoord(yAxis.dataToCoord(data[1]));
  28057. return out;
  28058. },
  28059. /**
  28060. * @param {Array.<number>} data
  28061. * @param {Array.<number>} out
  28062. * @return {Array.<number>}
  28063. */
  28064. clampData: function (data, out) {
  28065. var xAxisExtent = this.getAxis('x').scale.getExtent();
  28066. var yAxisExtent = this.getAxis('y').scale.getExtent();
  28067. out = out || [];
  28068. out[0] = Math.min(
  28069. Math.max(Math.min(xAxisExtent[0], xAxisExtent[1]), data[0]),
  28070. Math.max(xAxisExtent[0], xAxisExtent[1])
  28071. );
  28072. out[1] = Math.min(
  28073. Math.max(Math.min(yAxisExtent[0], yAxisExtent[1]), data[1]),
  28074. Math.max(yAxisExtent[0], yAxisExtent[1])
  28075. );
  28076. return out;
  28077. },
  28078. /**
  28079. * @param {Array.<number>} point
  28080. * @param {Array.<number>} out
  28081. * @return {Array.<number>}
  28082. */
  28083. pointToData: function (point, out) {
  28084. var xAxis = this.getAxis('x');
  28085. var yAxis = this.getAxis('y');
  28086. out = out || [];
  28087. out[0] = xAxis.coordToData(xAxis.toLocalCoord(point[0]));
  28088. out[1] = yAxis.coordToData(yAxis.toLocalCoord(point[1]));
  28089. return out;
  28090. },
  28091. /**
  28092. * Get other axis
  28093. * @param {module:echarts/coord/cartesian/Axis2D} axis
  28094. */
  28095. getOtherAxis: function (axis) {
  28096. return this.getAxis(axis.dim === 'x' ? 'y' : 'x');
  28097. }
  28098. };
  28099. inherits(Cartesian2D, Cartesian);
  28100. var linearMap$1 = linearMap;
  28101. function fixExtentWithBands(extent, nTick) {
  28102. var size = extent[1] - extent[0];
  28103. var len = nTick;
  28104. var margin = size / len / 2;
  28105. extent[0] += margin;
  28106. extent[1] -= margin;
  28107. }
  28108. var normalizedExtent = [0, 1];
  28109. /**
  28110. * @name module:echarts/coord/CartesianAxis
  28111. * @constructor
  28112. */
  28113. var Axis = function (dim, scale, extent) {
  28114. /**
  28115. * Axis dimension. Such as 'x', 'y', 'z', 'angle', 'radius'
  28116. * @type {string}
  28117. */
  28118. this.dim = dim;
  28119. /**
  28120. * Axis scale
  28121. * @type {module:echarts/coord/scale/*}
  28122. */
  28123. this.scale = scale;
  28124. /**
  28125. * @type {Array.<number>}
  28126. * @private
  28127. */
  28128. this._extent = extent || [0, 0];
  28129. /**
  28130. * @type {boolean}
  28131. */
  28132. this.inverse = false;
  28133. /**
  28134. * Usually true when axis has a ordinal scale
  28135. * @type {boolean}
  28136. */
  28137. this.onBand = false;
  28138. /**
  28139. * @private
  28140. * @type {number}
  28141. */
  28142. this._labelInterval;
  28143. };
  28144. Axis.prototype = {
  28145. constructor: Axis,
  28146. /**
  28147. * If axis extent contain given coord
  28148. * @param {number} coord
  28149. * @return {boolean}
  28150. */
  28151. contain: function (coord) {
  28152. var extent = this._extent;
  28153. var min = Math.min(extent[0], extent[1]);
  28154. var max = Math.max(extent[0], extent[1]);
  28155. return coord >= min && coord <= max;
  28156. },
  28157. /**
  28158. * If axis extent contain given data
  28159. * @param {number} data
  28160. * @return {boolean}
  28161. */
  28162. containData: function (data) {
  28163. return this.contain(this.dataToCoord(data));
  28164. },
  28165. /**
  28166. * Get coord extent.
  28167. * @return {Array.<number>}
  28168. */
  28169. getExtent: function () {
  28170. return this._extent.slice();
  28171. },
  28172. /**
  28173. * Get precision used for formatting
  28174. * @param {Array.<number>} [dataExtent]
  28175. * @return {number}
  28176. */
  28177. getPixelPrecision: function (dataExtent) {
  28178. return getPixelPrecision(
  28179. dataExtent || this.scale.getExtent(),
  28180. this._extent
  28181. );
  28182. },
  28183. /**
  28184. * Set coord extent
  28185. * @param {number} start
  28186. * @param {number} end
  28187. */
  28188. setExtent: function (start, end) {
  28189. var extent = this._extent;
  28190. extent[0] = start;
  28191. extent[1] = end;
  28192. },
  28193. /**
  28194. * Convert data to coord. Data is the rank if it has an ordinal scale
  28195. * @param {number} data
  28196. * @param {boolean} clamp
  28197. * @return {number}
  28198. */
  28199. dataToCoord: function (data, clamp) {
  28200. var extent = this._extent;
  28201. var scale = this.scale;
  28202. data = scale.normalize(data);
  28203. if (this.onBand && scale.type === 'ordinal') {
  28204. extent = extent.slice();
  28205. fixExtentWithBands(extent, scale.count());
  28206. }
  28207. return linearMap$1(data, normalizedExtent, extent, clamp);
  28208. },
  28209. /**
  28210. * Convert coord to data. Data is the rank if it has an ordinal scale
  28211. * @param {number} coord
  28212. * @param {boolean} clamp
  28213. * @return {number}
  28214. */
  28215. coordToData: function (coord, clamp) {
  28216. var extent = this._extent;
  28217. var scale = this.scale;
  28218. if (this.onBand && scale.type === 'ordinal') {
  28219. extent = extent.slice();
  28220. fixExtentWithBands(extent, scale.count());
  28221. }
  28222. var t = linearMap$1(coord, extent, normalizedExtent, clamp);
  28223. return this.scale.scale(t);
  28224. },
  28225. /**
  28226. * Convert pixel point to data in axis
  28227. * @param {Array.<number>} point
  28228. * @param {boolean} clamp
  28229. * @return {number} data
  28230. */
  28231. pointToData: function (point, clamp) {
  28232. // Should be implemented in derived class if necessary.
  28233. },
  28234. /**
  28235. * @return {Array.<number>}
  28236. */
  28237. getTicksCoords: function (alignWithLabel) {
  28238. if (this.onBand && !alignWithLabel) {
  28239. var bands = this.getBands();
  28240. var coords = [];
  28241. for (var i = 0; i < bands.length; i++) {
  28242. coords.push(bands[i][0]);
  28243. }
  28244. if (bands[i - 1]) {
  28245. coords.push(bands[i - 1][1]);
  28246. }
  28247. return coords;
  28248. }
  28249. else {
  28250. return map(this.scale.getTicks(), this.dataToCoord, this);
  28251. }
  28252. },
  28253. /**
  28254. * Coords of labels are on the ticks or on the middle of bands
  28255. * @return {Array.<number>}
  28256. */
  28257. getLabelsCoords: function () {
  28258. return map(this.scale.getTicks(), this.dataToCoord, this);
  28259. },
  28260. /**
  28261. * Get bands.
  28262. *
  28263. * If axis has labels [1, 2, 3, 4]. Bands on the axis are
  28264. * |---1---|---2---|---3---|---4---|.
  28265. *
  28266. * @return {Array}
  28267. */
  28268. // FIXME Situation when labels is on ticks
  28269. getBands: function () {
  28270. var extent = this.getExtent();
  28271. var bands = [];
  28272. var len = this.scale.count();
  28273. var start = extent[0];
  28274. var end = extent[1];
  28275. var span = end - start;
  28276. for (var i = 0; i < len; i++) {
  28277. bands.push([
  28278. span * i / len + start,
  28279. span * (i + 1) / len + start
  28280. ]);
  28281. }
  28282. return bands;
  28283. },
  28284. /**
  28285. * Get width of band
  28286. * @return {number}
  28287. */
  28288. getBandWidth: function () {
  28289. var axisExtent = this._extent;
  28290. var dataExtent = this.scale.getExtent();
  28291. var len = dataExtent[1] - dataExtent[0] + (this.onBand ? 1 : 0);
  28292. // Fix #2728, avoid NaN when only one data.
  28293. len === 0 && (len = 1);
  28294. var size = Math.abs(axisExtent[1] - axisExtent[0]);
  28295. return Math.abs(size) / len;
  28296. },
  28297. /**
  28298. * @abstract
  28299. * @return {boolean} Is horizontal
  28300. */
  28301. isHorizontal: null,
  28302. /**
  28303. * @abstract
  28304. * @return {number} Get axis rotate, by degree.
  28305. */
  28306. getRotate: null,
  28307. /**
  28308. * Get interval of the axis label.
  28309. * To get precise result, at least one of `getRotate` and `isHorizontal`
  28310. * should be implemented.
  28311. * @return {number}
  28312. */
  28313. getLabelInterval: function () {
  28314. var labelInterval = this._labelInterval;
  28315. if (!labelInterval) {
  28316. var axisModel = this.model;
  28317. var labelModel = axisModel.getModel('axisLabel');
  28318. labelInterval = labelModel.get('interval');
  28319. if (this.type === 'category'
  28320. && (labelInterval == null || labelInterval === 'auto')
  28321. ) {
  28322. labelInterval = getAxisLabelInterval(
  28323. map(this.scale.getTicks(), this.dataToCoord, this),
  28324. axisModel.getFormattedLabels(),
  28325. labelModel.getFont(),
  28326. this.getRotate
  28327. ? this.getRotate()
  28328. : (this.isHorizontal && !this.isHorizontal())
  28329. ? 90
  28330. : 0,
  28331. labelModel.get('rotate')
  28332. );
  28333. }
  28334. this._labelInterval = labelInterval;
  28335. }
  28336. return labelInterval;
  28337. }
  28338. };
  28339. /**
  28340. * Extend axis 2d
  28341. * @constructor module:echarts/coord/cartesian/Axis2D
  28342. * @extends {module:echarts/coord/cartesian/Axis}
  28343. * @param {string} dim
  28344. * @param {*} scale
  28345. * @param {Array.<number>} coordExtent
  28346. * @param {string} axisType
  28347. * @param {string} position
  28348. */
  28349. var Axis2D = function (dim, scale, coordExtent, axisType, position) {
  28350. Axis.call(this, dim, scale, coordExtent);
  28351. /**
  28352. * Axis type
  28353. * - 'category'
  28354. * - 'value'
  28355. * - 'time'
  28356. * - 'log'
  28357. * @type {string}
  28358. */
  28359. this.type = axisType || 'value';
  28360. /**
  28361. * Axis position
  28362. * - 'top'
  28363. * - 'bottom'
  28364. * - 'left'
  28365. * - 'right'
  28366. */
  28367. this.position = position || 'bottom';
  28368. };
  28369. Axis2D.prototype = {
  28370. constructor: Axis2D,
  28371. /**
  28372. * Index of axis, can be used as key
  28373. */
  28374. index: 0,
  28375. /**
  28376. * If axis is on the zero position of the other axis
  28377. * @type {boolean}
  28378. */
  28379. onZero: false,
  28380. /**
  28381. * Axis model
  28382. * @param {module:echarts/coord/cartesian/AxisModel}
  28383. */
  28384. model: null,
  28385. isHorizontal: function () {
  28386. var position = this.position;
  28387. return position === 'top' || position === 'bottom';
  28388. },
  28389. /**
  28390. * Each item cooresponds to this.getExtent(), which
  28391. * means globalExtent[0] may greater than globalExtent[1],
  28392. * unless `asc` is input.
  28393. *
  28394. * @param {boolean} [asc]
  28395. * @return {Array.<number>}
  28396. */
  28397. getGlobalExtent: function (asc) {
  28398. var ret = this.getExtent();
  28399. ret[0] = this.toGlobalCoord(ret[0]);
  28400. ret[1] = this.toGlobalCoord(ret[1]);
  28401. asc && ret[0] > ret[1] && ret.reverse();
  28402. return ret;
  28403. },
  28404. getOtherAxis: function () {
  28405. this.grid.getOtherAxis();
  28406. },
  28407. /**
  28408. * If label is ignored.
  28409. * Automatically used when axis is category and label can not be all shown
  28410. * @param {number} idx
  28411. * @return {boolean}
  28412. */
  28413. isLabelIgnored: function (idx) {
  28414. if (this.type === 'category') {
  28415. var labelInterval = this.getLabelInterval();
  28416. return ((typeof labelInterval === 'function')
  28417. && !labelInterval(idx, this.scale.getLabel(idx)))
  28418. || idx % (labelInterval + 1);
  28419. }
  28420. },
  28421. /**
  28422. * @override
  28423. */
  28424. pointToData: function (point, clamp) {
  28425. return this.coordToData(this.toLocalCoord(point[this.dim === 'x' ? 0 : 1]), clamp);
  28426. },
  28427. /**
  28428. * Transform global coord to local coord,
  28429. * i.e. var localCoord = axis.toLocalCoord(80);
  28430. * designate by module:echarts/coord/cartesian/Grid.
  28431. * @type {Function}
  28432. */
  28433. toLocalCoord: null,
  28434. /**
  28435. * Transform global coord to local coord,
  28436. * i.e. var globalCoord = axis.toLocalCoord(40);
  28437. * designate by module:echarts/coord/cartesian/Grid.
  28438. * @type {Function}
  28439. */
  28440. toGlobalCoord: null
  28441. };
  28442. inherits(Axis2D, Axis);
  28443. var defaultOption = {
  28444. show: true,
  28445. zlevel: 0, // 一级层叠
  28446. z: 0, // 二级层叠
  28447. // 反向坐标轴
  28448. inverse: false,
  28449. // 坐标轴名字,默认为空
  28450. name: '',
  28451. // 坐标轴名字位置,支持'start' | 'middle' | 'end'
  28452. nameLocation: 'end',
  28453. // 坐标轴名字旋转,degree。
  28454. nameRotate: null, // Adapt to axis rotate, when nameLocation is 'middle'.
  28455. nameTruncate: {
  28456. maxWidth: null,
  28457. ellipsis: '...',
  28458. placeholder: '.'
  28459. },
  28460. // 坐标轴文字样式,默认取全局样式
  28461. nameTextStyle: {},
  28462. // 文字与轴线距离
  28463. nameGap: 15,
  28464. silent: false, // Default false to support tooltip.
  28465. triggerEvent: false, // Default false to avoid legacy user event listener fail.
  28466. tooltip: {
  28467. show: false
  28468. },
  28469. axisPointer: {},
  28470. // 坐标轴线
  28471. axisLine: {
  28472. // 默认显示,属性show控制显示与否
  28473. show: true,
  28474. onZero: true,
  28475. onZeroAxisIndex: null,
  28476. // 属性lineStyle控制线条样式
  28477. lineStyle: {
  28478. color: '#333',
  28479. width: 1,
  28480. type: 'solid'
  28481. },
  28482. // 坐标轴两端的箭头
  28483. symbol: ['none', 'none'],
  28484. symbolSize: [10, 15]
  28485. },
  28486. // 坐标轴小标记
  28487. axisTick: {
  28488. // 属性show控制显示与否,默认显示
  28489. show: true,
  28490. // 控制小标记是否在grid里
  28491. inside: false,
  28492. // 属性length控制线长
  28493. length: 5,
  28494. // 属性lineStyle控制线条样式
  28495. lineStyle: {
  28496. width: 1
  28497. }
  28498. },
  28499. // 坐标轴文本标签,详见axis.axisLabel
  28500. axisLabel: {
  28501. show: true,
  28502. // 控制文本标签是否在grid里
  28503. inside: false,
  28504. rotate: 0,
  28505. showMinLabel: null, // true | false | null (auto)
  28506. showMaxLabel: null, // true | false | null (auto)
  28507. margin: 8,
  28508. // formatter: null,
  28509. // 其余属性默认使用全局文本样式,详见TEXTSTYLE
  28510. fontSize: 12
  28511. },
  28512. // 分隔线
  28513. splitLine: {
  28514. // 默认显示,属性show控制显示与否
  28515. show: true,
  28516. // 属性lineStyle(详见lineStyle)控制线条样式
  28517. lineStyle: {
  28518. color: ['#ccc'],
  28519. width: 1,
  28520. type: 'solid'
  28521. }
  28522. },
  28523. // 分隔区域
  28524. splitArea: {
  28525. // 默认不显示,属性show控制显示与否
  28526. show: false,
  28527. // 属性areaStyle(详见areaStyle)控制区域样式
  28528. areaStyle: {
  28529. color: ['rgba(250,250,250,0.3)','rgba(200,200,200,0.3)']
  28530. }
  28531. }
  28532. };
  28533. var axisDefault = {};
  28534. axisDefault.categoryAxis = merge({
  28535. // 类目起始和结束两端空白策略
  28536. boundaryGap: true,
  28537. // Set false to faster category collection.
  28538. // Only usefull in the case like: category is
  28539. // ['2012-01-01', '2012-01-02', ...], where the input
  28540. // data has been ensured not duplicate and is large data.
  28541. // null means "auto":
  28542. // if axis.data provided, do not deduplication,
  28543. // else do deduplication.
  28544. deduplication: null,
  28545. // splitArea: {
  28546. // show: false
  28547. // },
  28548. splitLine: {
  28549. show: false
  28550. },
  28551. // 坐标轴小标记
  28552. axisTick: {
  28553. // If tick is align with label when boundaryGap is true
  28554. alignWithLabel: false,
  28555. interval: 'auto'
  28556. },
  28557. // 坐标轴文本标签,详见axis.axisLabel
  28558. axisLabel: {
  28559. interval: 'auto'
  28560. }
  28561. }, defaultOption);
  28562. axisDefault.valueAxis = merge({
  28563. // 数值起始和结束两端空白策略
  28564. boundaryGap: [0, 0],
  28565. // TODO
  28566. // min/max: [30, datamin, 60] or [20, datamin] or [datamin, 60]
  28567. // 最小值, 设置成 'dataMin' 则从数据中计算最小值
  28568. // min: null,
  28569. // 最大值,设置成 'dataMax' 则从数据中计算最大值
  28570. // max: null,
  28571. // Readonly prop, specifies start value of the range when using data zoom.
  28572. // rangeStart: null
  28573. // Readonly prop, specifies end value of the range when using data zoom.
  28574. // rangeEnd: null
  28575. // 脱离0值比例,放大聚焦到最终_min,_max区间
  28576. // scale: false,
  28577. // 分割段数,默认为5
  28578. splitNumber: 5
  28579. // Minimum interval
  28580. // minInterval: null
  28581. // maxInterval: null
  28582. }, defaultOption);
  28583. // FIXME
  28584. axisDefault.timeAxis = defaults({
  28585. scale: true,
  28586. min: 'dataMin',
  28587. max: 'dataMax'
  28588. }, axisDefault.valueAxis);
  28589. axisDefault.logAxis = defaults({
  28590. scale: true,
  28591. logBase: 10
  28592. }, axisDefault.valueAxis);
  28593. // FIXME axisType is fixed ?
  28594. var AXIS_TYPES = ['value', 'category', 'time', 'log'];
  28595. /**
  28596. * Generate sub axis model class
  28597. * @param {string} axisName 'x' 'y' 'radius' 'angle' 'parallel'
  28598. * @param {module:echarts/model/Component} BaseAxisModelClass
  28599. * @param {Function} axisTypeDefaulter
  28600. * @param {Object} [extraDefaultOption]
  28601. */
  28602. var axisModelCreator = function (axisName, BaseAxisModelClass, axisTypeDefaulter, extraDefaultOption) {
  28603. each$1(AXIS_TYPES, function (axisType) {
  28604. BaseAxisModelClass.extend({
  28605. /**
  28606. * @readOnly
  28607. */
  28608. type: axisName + 'Axis.' + axisType,
  28609. mergeDefaultAndTheme: function (option, ecModel) {
  28610. var layoutMode = this.layoutMode;
  28611. var inputPositionParams = layoutMode
  28612. ? getLayoutParams(option) : {};
  28613. var themeModel = ecModel.getTheme();
  28614. merge(option, themeModel.get(axisType + 'Axis'));
  28615. merge(option, this.getDefaultOption());
  28616. option.type = axisTypeDefaulter(axisName, option);
  28617. if (layoutMode) {
  28618. mergeLayoutParam(option, inputPositionParams, layoutMode);
  28619. }
  28620. },
  28621. /**
  28622. * @override
  28623. */
  28624. optionUpdated: function () {
  28625. var thisOption = this.option;
  28626. if (thisOption.type === 'category') {
  28627. this.__ordinalMeta = OrdinalMeta.createByAxisModel(this);
  28628. }
  28629. },
  28630. /**
  28631. * Should not be called before all of 'getInitailData' finished.
  28632. * Because categories are collected during initializing data.
  28633. */
  28634. getCategories: function () {
  28635. // FIXME
  28636. // warning if called before all of 'getInitailData' finished.
  28637. if (this.option.type === 'category') {
  28638. return this.__ordinalMeta.categories;
  28639. }
  28640. },
  28641. getOrdinalMeta: function () {
  28642. return this.__ordinalMeta;
  28643. },
  28644. defaultOption: mergeAll(
  28645. [
  28646. {},
  28647. axisDefault[axisType + 'Axis'],
  28648. extraDefaultOption
  28649. ],
  28650. true
  28651. )
  28652. });
  28653. });
  28654. ComponentModel.registerSubTypeDefaulter(
  28655. axisName + 'Axis',
  28656. curry(axisTypeDefaulter, axisName)
  28657. );
  28658. };
  28659. var axisModelCommonMixin = {
  28660. /**
  28661. * Format labels
  28662. * @return {Array.<string>}
  28663. */
  28664. getFormattedLabels: function () {
  28665. return getFormattedLabels(
  28666. this.axis,
  28667. this.get('axisLabel.formatter')
  28668. );
  28669. },
  28670. /**
  28671. * @param {boolean} origin
  28672. * @return {number|string} min value or 'dataMin' or null/undefined (means auto) or NaN
  28673. */
  28674. getMin: function (origin) {
  28675. var option = this.option;
  28676. var min = (!origin && option.rangeStart != null)
  28677. ? option.rangeStart : option.min;
  28678. if (this.axis
  28679. && min != null
  28680. && min !== 'dataMin'
  28681. && typeof min !== 'function'
  28682. && !eqNaN(min)
  28683. ) {
  28684. min = this.axis.scale.parse(min);
  28685. }
  28686. return min;
  28687. },
  28688. /**
  28689. * @param {boolean} origin
  28690. * @return {number|string} max value or 'dataMax' or null/undefined (means auto) or NaN
  28691. */
  28692. getMax: function (origin) {
  28693. var option = this.option;
  28694. var max = (!origin && option.rangeEnd != null)
  28695. ? option.rangeEnd : option.max;
  28696. if (this.axis
  28697. && max != null
  28698. && max !== 'dataMax'
  28699. && typeof max !== 'function'
  28700. && !eqNaN(max)
  28701. ) {
  28702. max = this.axis.scale.parse(max);
  28703. }
  28704. return max;
  28705. },
  28706. /**
  28707. * @return {boolean}
  28708. */
  28709. getNeedCrossZero: function () {
  28710. var option = this.option;
  28711. return (option.rangeStart != null || option.rangeEnd != null)
  28712. ? false : !option.scale;
  28713. },
  28714. /**
  28715. * Should be implemented by each axis model if necessary.
  28716. * @return {module:echarts/model/Component} coordinate system model
  28717. */
  28718. getCoordSysModel: noop,
  28719. /**
  28720. * @param {number} rangeStart Can only be finite number or null/undefined or NaN.
  28721. * @param {number} rangeEnd Can only be finite number or null/undefined or NaN.
  28722. */
  28723. setRange: function (rangeStart, rangeEnd) {
  28724. this.option.rangeStart = rangeStart;
  28725. this.option.rangeEnd = rangeEnd;
  28726. },
  28727. /**
  28728. * Reset range
  28729. */
  28730. resetRange: function () {
  28731. // rangeStart and rangeEnd is readonly.
  28732. this.option.rangeStart = this.option.rangeEnd = null;
  28733. }
  28734. };
  28735. var AxisModel = ComponentModel.extend({
  28736. type: 'cartesian2dAxis',
  28737. /**
  28738. * @type {module:echarts/coord/cartesian/Axis2D}
  28739. */
  28740. axis: null,
  28741. /**
  28742. * @override
  28743. */
  28744. init: function () {
  28745. AxisModel.superApply(this, 'init', arguments);
  28746. this.resetRange();
  28747. },
  28748. /**
  28749. * @override
  28750. */
  28751. mergeOption: function () {
  28752. AxisModel.superApply(this, 'mergeOption', arguments);
  28753. this.resetRange();
  28754. },
  28755. /**
  28756. * @override
  28757. */
  28758. restoreData: function () {
  28759. AxisModel.superApply(this, 'restoreData', arguments);
  28760. this.resetRange();
  28761. },
  28762. /**
  28763. * @override
  28764. * @return {module:echarts/model/Component}
  28765. */
  28766. getCoordSysModel: function () {
  28767. return this.ecModel.queryComponents({
  28768. mainType: 'grid',
  28769. index: this.option.gridIndex,
  28770. id: this.option.gridId
  28771. })[0];
  28772. }
  28773. });
  28774. function getAxisType(axisDim, option) {
  28775. // Default axis with data is category axis
  28776. return option.type || (option.data ? 'category' : 'value');
  28777. }
  28778. merge(AxisModel.prototype, axisModelCommonMixin);
  28779. var extraOption = {
  28780. // gridIndex: 0,
  28781. // gridId: '',
  28782. // Offset is for multiple axis on the same position
  28783. offset: 0
  28784. };
  28785. axisModelCreator('x', AxisModel, getAxisType, extraOption);
  28786. axisModelCreator('y', AxisModel, getAxisType, extraOption);
  28787. // Grid 是在有直角坐标系的时候必须要存在的
  28788. // 所以这里也要被 Cartesian2D 依赖
  28789. ComponentModel.extend({
  28790. type: 'grid',
  28791. dependencies: ['xAxis', 'yAxis'],
  28792. layoutMode: 'box',
  28793. /**
  28794. * @type {module:echarts/coord/cartesian/Grid}
  28795. */
  28796. coordinateSystem: null,
  28797. defaultOption: {
  28798. show: false,
  28799. zlevel: 0,
  28800. z: 0,
  28801. left: '10%',
  28802. top: 60,
  28803. right: '10%',
  28804. bottom: 60,
  28805. // If grid size contain label
  28806. containLabel: false,
  28807. // width: {totalWidth} - left - right,
  28808. // height: {totalHeight} - top - bottom,
  28809. backgroundColor: 'rgba(0,0,0,0)',
  28810. borderWidth: 1,
  28811. borderColor: '#ccc'
  28812. }
  28813. });
  28814. /**
  28815. * Grid is a region which contains at most 4 cartesian systems
  28816. *
  28817. * TODO Default cartesian
  28818. */
  28819. // Depends on GridModel, AxisModel, which performs preprocess.
  28820. var each$6 = each$1;
  28821. var ifAxisCrossZero = ifAxisCrossZero$1;
  28822. var niceScaleExtent = niceScaleExtent$1;
  28823. /**
  28824. * Check if the axis is used in the specified grid
  28825. * @inner
  28826. */
  28827. function isAxisUsedInTheGrid(axisModel, gridModel, ecModel) {
  28828. return axisModel.getCoordSysModel() === gridModel;
  28829. }
  28830. function rotateTextRect(textRect, rotate) {
  28831. var rotateRadians = rotate * Math.PI / 180;
  28832. var boundingBox = textRect.plain();
  28833. var beforeWidth = boundingBox.width;
  28834. var beforeHeight = boundingBox.height;
  28835. var afterWidth = beforeWidth * Math.cos(rotateRadians) + beforeHeight * Math.sin(rotateRadians);
  28836. var afterHeight = beforeWidth * Math.sin(rotateRadians) + beforeHeight * Math.cos(rotateRadians);
  28837. var rotatedRect = new BoundingRect(boundingBox.x, boundingBox.y, afterWidth, afterHeight);
  28838. return rotatedRect;
  28839. }
  28840. function getLabelUnionRect(axis) {
  28841. var axisModel = axis.model;
  28842. var labels = axisModel.get('axisLabel.show') ? axisModel.getFormattedLabels() : [];
  28843. var axisLabelModel = axisModel.getModel('axisLabel');
  28844. var rect;
  28845. var step = 1;
  28846. var labelCount = labels.length;
  28847. if (labelCount > 40) {
  28848. // Simple optimization for large amount of labels
  28849. step = Math.ceil(labelCount / 40);
  28850. }
  28851. for (var i = 0; i < labelCount; i += step) {
  28852. if (!axis.isLabelIgnored(i)) {
  28853. var unrotatedSingleRect = axisLabelModel.getTextRect(labels[i]);
  28854. var singleRect = rotateTextRect(unrotatedSingleRect, axisLabelModel.get('rotate') || 0);
  28855. rect ? rect.union(singleRect) : (rect = singleRect);
  28856. }
  28857. }
  28858. return rect;
  28859. }
  28860. function Grid(gridModel, ecModel, api) {
  28861. /**
  28862. * @type {Object.<string, module:echarts/coord/cartesian/Cartesian2D>}
  28863. * @private
  28864. */
  28865. this._coordsMap = {};
  28866. /**
  28867. * @type {Array.<module:echarts/coord/cartesian/Cartesian>}
  28868. * @private
  28869. */
  28870. this._coordsList = [];
  28871. /**
  28872. * @type {Object.<string, module:echarts/coord/cartesian/Axis2D>}
  28873. * @private
  28874. */
  28875. this._axesMap = {};
  28876. /**
  28877. * @type {Array.<module:echarts/coord/cartesian/Axis2D>}
  28878. * @private
  28879. */
  28880. this._axesList = [];
  28881. this._initCartesian(gridModel, ecModel, api);
  28882. this.model = gridModel;
  28883. }
  28884. var gridProto = Grid.prototype;
  28885. gridProto.type = 'grid';
  28886. gridProto.axisPointerEnabled = true;
  28887. gridProto.getRect = function () {
  28888. return this._rect;
  28889. };
  28890. gridProto.update = function (ecModel, api) {
  28891. var axesMap = this._axesMap;
  28892. this._updateScale(ecModel, this.model);
  28893. each$6(axesMap.x, function (xAxis) {
  28894. niceScaleExtent(xAxis.scale, xAxis.model);
  28895. });
  28896. each$6(axesMap.y, function (yAxis) {
  28897. niceScaleExtent(yAxis.scale, yAxis.model);
  28898. });
  28899. each$6(axesMap.x, function (xAxis) {
  28900. fixAxisOnZero(axesMap, 'y', xAxis);
  28901. });
  28902. each$6(axesMap.y, function (yAxis) {
  28903. fixAxisOnZero(axesMap, 'x', yAxis);
  28904. });
  28905. // Resize again if containLabel is enabled
  28906. // FIXME It may cause getting wrong grid size in data processing stage
  28907. this.resize(this.model, api);
  28908. };
  28909. function fixAxisOnZero(axesMap, otherAxisDim, axis) {
  28910. // onZero can not be enabled in these two situations:
  28911. // 1. When any other axis is a category axis.
  28912. // 2. When no axis is cross 0 point.
  28913. var axes = axesMap[otherAxisDim];
  28914. if (!axis.onZero) {
  28915. return;
  28916. }
  28917. var onZeroAxisIndex = axis.onZeroAxisIndex;
  28918. // If target axis is specified.
  28919. if (onZeroAxisIndex != null) {
  28920. var otherAxis = axes[onZeroAxisIndex];
  28921. if (otherAxis && canNotOnZeroToAxis(otherAxis)) {
  28922. axis.onZero = false;
  28923. }
  28924. return;
  28925. }
  28926. for (var idx in axes) {
  28927. if (axes.hasOwnProperty(idx)) {
  28928. var otherAxis = axes[idx];
  28929. if (otherAxis && !canNotOnZeroToAxis(otherAxis)) {
  28930. onZeroAxisIndex = +idx;
  28931. break;
  28932. }
  28933. }
  28934. }
  28935. if (onZeroAxisIndex == null) {
  28936. axis.onZero = false;
  28937. }
  28938. axis.onZeroAxisIndex = onZeroAxisIndex;
  28939. }
  28940. function canNotOnZeroToAxis(axis) {
  28941. return axis.type === 'category' || axis.type === 'time' || !ifAxisCrossZero(axis);
  28942. }
  28943. /**
  28944. * Resize the grid
  28945. * @param {module:echarts/coord/cartesian/GridModel} gridModel
  28946. * @param {module:echarts/ExtensionAPI} api
  28947. */
  28948. gridProto.resize = function (gridModel, api, ignoreContainLabel) {
  28949. var gridRect = getLayoutRect(
  28950. gridModel.getBoxLayoutParams(), {
  28951. width: api.getWidth(),
  28952. height: api.getHeight()
  28953. });
  28954. this._rect = gridRect;
  28955. var axesList = this._axesList;
  28956. adjustAxes();
  28957. // Minus label size
  28958. if (!ignoreContainLabel && gridModel.get('containLabel')) {
  28959. each$6(axesList, function (axis) {
  28960. if (!axis.model.get('axisLabel.inside')) {
  28961. var labelUnionRect = getLabelUnionRect(axis);
  28962. if (labelUnionRect) {
  28963. var dim = axis.isHorizontal() ? 'height' : 'width';
  28964. var margin = axis.model.get('axisLabel.margin');
  28965. gridRect[dim] -= labelUnionRect[dim] + margin;
  28966. if (axis.position === 'top') {
  28967. gridRect.y += labelUnionRect.height + margin;
  28968. }
  28969. else if (axis.position === 'left') {
  28970. gridRect.x += labelUnionRect.width + margin;
  28971. }
  28972. }
  28973. }
  28974. });
  28975. adjustAxes();
  28976. }
  28977. function adjustAxes() {
  28978. each$6(axesList, function (axis) {
  28979. var isHorizontal = axis.isHorizontal();
  28980. var extent = isHorizontal ? [0, gridRect.width] : [0, gridRect.height];
  28981. var idx = axis.inverse ? 1 : 0;
  28982. axis.setExtent(extent[idx], extent[1 - idx]);
  28983. updateAxisTransform(axis, isHorizontal ? gridRect.x : gridRect.y);
  28984. });
  28985. }
  28986. };
  28987. /**
  28988. * @param {string} axisType
  28989. * @param {number} [axisIndex]
  28990. */
  28991. gridProto.getAxis = function (axisType, axisIndex) {
  28992. var axesMapOnDim = this._axesMap[axisType];
  28993. if (axesMapOnDim != null) {
  28994. if (axisIndex == null) {
  28995. // Find first axis
  28996. for (var name in axesMapOnDim) {
  28997. if (axesMapOnDim.hasOwnProperty(name)) {
  28998. return axesMapOnDim[name];
  28999. }
  29000. }
  29001. }
  29002. return axesMapOnDim[axisIndex];
  29003. }
  29004. };
  29005. /**
  29006. * @return {Array.<module:echarts/coord/Axis>}
  29007. */
  29008. gridProto.getAxes = function () {
  29009. return this._axesList.slice();
  29010. };
  29011. /**
  29012. * Usage:
  29013. * grid.getCartesian(xAxisIndex, yAxisIndex);
  29014. * grid.getCartesian(xAxisIndex);
  29015. * grid.getCartesian(null, yAxisIndex);
  29016. * grid.getCartesian({xAxisIndex: ..., yAxisIndex: ...});
  29017. *
  29018. * @param {number|Object} [xAxisIndex]
  29019. * @param {number} [yAxisIndex]
  29020. */
  29021. gridProto.getCartesian = function (xAxisIndex, yAxisIndex) {
  29022. if (xAxisIndex != null && yAxisIndex != null) {
  29023. var key = 'x' + xAxisIndex + 'y' + yAxisIndex;
  29024. return this._coordsMap[key];
  29025. }
  29026. if (isObject$1(xAxisIndex)) {
  29027. yAxisIndex = xAxisIndex.yAxisIndex;
  29028. xAxisIndex = xAxisIndex.xAxisIndex;
  29029. }
  29030. // When only xAxisIndex or yAxisIndex given, find its first cartesian.
  29031. for (var i = 0, coordList = this._coordsList; i < coordList.length; i++) {
  29032. if (coordList[i].getAxis('x').index === xAxisIndex
  29033. || coordList[i].getAxis('y').index === yAxisIndex
  29034. ) {
  29035. return coordList[i];
  29036. }
  29037. }
  29038. };
  29039. gridProto.getCartesians = function () {
  29040. return this._coordsList.slice();
  29041. };
  29042. /**
  29043. * @implements
  29044. * see {module:echarts/CoodinateSystem}
  29045. */
  29046. gridProto.convertToPixel = function (ecModel, finder, value) {
  29047. var target = this._findConvertTarget(ecModel, finder);
  29048. return target.cartesian
  29049. ? target.cartesian.dataToPoint(value)
  29050. : target.axis
  29051. ? target.axis.toGlobalCoord(target.axis.dataToCoord(value))
  29052. : null;
  29053. };
  29054. /**
  29055. * @implements
  29056. * see {module:echarts/CoodinateSystem}
  29057. */
  29058. gridProto.convertFromPixel = function (ecModel, finder, value) {
  29059. var target = this._findConvertTarget(ecModel, finder);
  29060. return target.cartesian
  29061. ? target.cartesian.pointToData(value)
  29062. : target.axis
  29063. ? target.axis.coordToData(target.axis.toLocalCoord(value))
  29064. : null;
  29065. };
  29066. /**
  29067. * @inner
  29068. */
  29069. gridProto._findConvertTarget = function (ecModel, finder) {
  29070. var seriesModel = finder.seriesModel;
  29071. var xAxisModel = finder.xAxisModel
  29072. || (seriesModel && seriesModel.getReferringComponents('xAxis')[0]);
  29073. var yAxisModel = finder.yAxisModel
  29074. || (seriesModel && seriesModel.getReferringComponents('yAxis')[0]);
  29075. var gridModel = finder.gridModel;
  29076. var coordsList = this._coordsList;
  29077. var cartesian;
  29078. var axis;
  29079. if (seriesModel) {
  29080. cartesian = seriesModel.coordinateSystem;
  29081. indexOf(coordsList, cartesian) < 0 && (cartesian = null);
  29082. }
  29083. else if (xAxisModel && yAxisModel) {
  29084. cartesian = this.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex);
  29085. }
  29086. else if (xAxisModel) {
  29087. axis = this.getAxis('x', xAxisModel.componentIndex);
  29088. }
  29089. else if (yAxisModel) {
  29090. axis = this.getAxis('y', yAxisModel.componentIndex);
  29091. }
  29092. // Lowest priority.
  29093. else if (gridModel) {
  29094. var grid = gridModel.coordinateSystem;
  29095. if (grid === this) {
  29096. cartesian = this._coordsList[0];
  29097. }
  29098. }
  29099. return {cartesian: cartesian, axis: axis};
  29100. };
  29101. /**
  29102. * @implements
  29103. * see {module:echarts/CoodinateSystem}
  29104. */
  29105. gridProto.containPoint = function (point) {
  29106. var coord = this._coordsList[0];
  29107. if (coord) {
  29108. return coord.containPoint(point);
  29109. }
  29110. };
  29111. /**
  29112. * Initialize cartesian coordinate systems
  29113. * @private
  29114. */
  29115. gridProto._initCartesian = function (gridModel, ecModel, api) {
  29116. var axisPositionUsed = {
  29117. left: false,
  29118. right: false,
  29119. top: false,
  29120. bottom: false
  29121. };
  29122. var axesMap = {
  29123. x: {},
  29124. y: {}
  29125. };
  29126. var axesCount = {
  29127. x: 0,
  29128. y: 0
  29129. };
  29130. /// Create axis
  29131. ecModel.eachComponent('xAxis', createAxisCreator('x'), this);
  29132. ecModel.eachComponent('yAxis', createAxisCreator('y'), this);
  29133. if (!axesCount.x || !axesCount.y) {
  29134. // Roll back when there no either x or y axis
  29135. this._axesMap = {};
  29136. this._axesList = [];
  29137. return;
  29138. }
  29139. this._axesMap = axesMap;
  29140. /// Create cartesian2d
  29141. each$6(axesMap.x, function (xAxis, xAxisIndex) {
  29142. each$6(axesMap.y, function (yAxis, yAxisIndex) {
  29143. var key = 'x' + xAxisIndex + 'y' + yAxisIndex;
  29144. var cartesian = new Cartesian2D(key);
  29145. cartesian.grid = this;
  29146. cartesian.model = gridModel;
  29147. this._coordsMap[key] = cartesian;
  29148. this._coordsList.push(cartesian);
  29149. cartesian.addAxis(xAxis);
  29150. cartesian.addAxis(yAxis);
  29151. }, this);
  29152. }, this);
  29153. function createAxisCreator(axisType) {
  29154. return function (axisModel, idx) {
  29155. if (!isAxisUsedInTheGrid(axisModel, gridModel, ecModel)) {
  29156. return;
  29157. }
  29158. var axisPosition = axisModel.get('position');
  29159. if (axisType === 'x') {
  29160. // Fix position
  29161. if (axisPosition !== 'top' && axisPosition !== 'bottom') {
  29162. // Default bottom of X
  29163. axisPosition = 'bottom';
  29164. if (axisPositionUsed[axisPosition]) {
  29165. axisPosition = axisPosition === 'top' ? 'bottom' : 'top';
  29166. }
  29167. }
  29168. }
  29169. else {
  29170. // Fix position
  29171. if (axisPosition !== 'left' && axisPosition !== 'right') {
  29172. // Default left of Y
  29173. axisPosition = 'left';
  29174. if (axisPositionUsed[axisPosition]) {
  29175. axisPosition = axisPosition === 'left' ? 'right' : 'left';
  29176. }
  29177. }
  29178. }
  29179. axisPositionUsed[axisPosition] = true;
  29180. var axis = new Axis2D(
  29181. axisType, createScaleByModel(axisModel),
  29182. [0, 0],
  29183. axisModel.get('type'),
  29184. axisPosition
  29185. );
  29186. var isCategory = axis.type === 'category';
  29187. axis.onBand = isCategory && axisModel.get('boundaryGap');
  29188. axis.inverse = axisModel.get('inverse');
  29189. axis.onZero = axisModel.get('axisLine.onZero');
  29190. axis.onZeroAxisIndex = axisModel.get('axisLine.onZeroAxisIndex');
  29191. // Inject axis into axisModel
  29192. axisModel.axis = axis;
  29193. // Inject axisModel into axis
  29194. axis.model = axisModel;
  29195. // Inject grid info axis
  29196. axis.grid = this;
  29197. // Index of axis, can be used as key
  29198. axis.index = idx;
  29199. this._axesList.push(axis);
  29200. axesMap[axisType][idx] = axis;
  29201. axesCount[axisType]++;
  29202. };
  29203. }
  29204. };
  29205. /**
  29206. * Update cartesian properties from series
  29207. * @param {module:echarts/model/Option} option
  29208. * @private
  29209. */
  29210. gridProto._updateScale = function (ecModel, gridModel) {
  29211. // Reset scale
  29212. each$1(this._axesList, function (axis) {
  29213. axis.scale.setExtent(Infinity, -Infinity);
  29214. });
  29215. ecModel.eachSeries(function (seriesModel) {
  29216. if (isCartesian2D(seriesModel)) {
  29217. var axesModels = findAxesModels(seriesModel, ecModel);
  29218. var xAxisModel = axesModels[0];
  29219. var yAxisModel = axesModels[1];
  29220. if (!isAxisUsedInTheGrid(xAxisModel, gridModel, ecModel)
  29221. || !isAxisUsedInTheGrid(yAxisModel, gridModel, ecModel)
  29222. ) {
  29223. return;
  29224. }
  29225. var cartesian = this.getCartesian(
  29226. xAxisModel.componentIndex, yAxisModel.componentIndex
  29227. );
  29228. var data = seriesModel.getData();
  29229. var xAxis = cartesian.getAxis('x');
  29230. var yAxis = cartesian.getAxis('y');
  29231. if (data.type === 'list') {
  29232. unionExtent(data, xAxis, seriesModel);
  29233. unionExtent(data, yAxis, seriesModel);
  29234. }
  29235. }
  29236. }, this);
  29237. function unionExtent(data, axis, seriesModel) {
  29238. each$6(data.mapDimension(axis.dim, true), function (dim) {
  29239. axis.scale.unionExtentFromData(data, dim);
  29240. });
  29241. }
  29242. };
  29243. /**
  29244. * @param {string} [dim] 'x' or 'y' or 'auto' or null/undefined
  29245. * @return {Object} {baseAxes: [], otherAxes: []}
  29246. */
  29247. gridProto.getTooltipAxes = function (dim) {
  29248. var baseAxes = [];
  29249. var otherAxes = [];
  29250. each$6(this.getCartesians(), function (cartesian) {
  29251. var baseAxis = (dim != null && dim !== 'auto')
  29252. ? cartesian.getAxis(dim) : cartesian.getBaseAxis();
  29253. var otherAxis = cartesian.getOtherAxis(baseAxis);
  29254. indexOf(baseAxes, baseAxis) < 0 && baseAxes.push(baseAxis);
  29255. indexOf(otherAxes, otherAxis) < 0 && otherAxes.push(otherAxis);
  29256. });
  29257. return {baseAxes: baseAxes, otherAxes: otherAxes};
  29258. };
  29259. /**
  29260. * @inner
  29261. */
  29262. function updateAxisTransform(axis, coordBase) {
  29263. var axisExtent = axis.getExtent();
  29264. var axisExtentSum = axisExtent[0] + axisExtent[1];
  29265. // Fast transform
  29266. axis.toGlobalCoord = axis.dim === 'x'
  29267. ? function (coord) {
  29268. return coord + coordBase;
  29269. }
  29270. : function (coord) {
  29271. return axisExtentSum - coord + coordBase;
  29272. };
  29273. axis.toLocalCoord = axis.dim === 'x'
  29274. ? function (coord) {
  29275. return coord - coordBase;
  29276. }
  29277. : function (coord) {
  29278. return axisExtentSum - coord + coordBase;
  29279. };
  29280. }
  29281. var axesTypes = ['xAxis', 'yAxis'];
  29282. /**
  29283. * @inner
  29284. */
  29285. function findAxesModels(seriesModel, ecModel) {
  29286. return map(axesTypes, function (axisType) {
  29287. var axisModel = seriesModel.getReferringComponents(axisType)[0];
  29288. if (__DEV__) {
  29289. if (!axisModel) {
  29290. throw new Error(axisType + ' "' + retrieve(
  29291. seriesModel.get(axisType + 'Index'),
  29292. seriesModel.get(axisType + 'Id'),
  29293. 0
  29294. ) + '" not found');
  29295. }
  29296. }
  29297. return axisModel;
  29298. });
  29299. }
  29300. /**
  29301. * @inner
  29302. */
  29303. function isCartesian2D(seriesModel) {
  29304. return seriesModel.get('coordinateSystem') === 'cartesian2d';
  29305. }
  29306. Grid.create = function (ecModel, api) {
  29307. var grids = [];
  29308. ecModel.eachComponent('grid', function (gridModel, idx) {
  29309. var grid = new Grid(gridModel, ecModel, api);
  29310. grid.name = 'grid_' + idx;
  29311. // dataSampling requires axis extent, so resize
  29312. // should be performed in create stage.
  29313. grid.resize(gridModel, api, true);
  29314. gridModel.coordinateSystem = grid;
  29315. grids.push(grid);
  29316. });
  29317. // Inject the coordinateSystems into seriesModel
  29318. ecModel.eachSeries(function (seriesModel) {
  29319. if (!isCartesian2D(seriesModel)) {
  29320. return;
  29321. }
  29322. var axesModels = findAxesModels(seriesModel, ecModel);
  29323. var xAxisModel = axesModels[0];
  29324. var yAxisModel = axesModels[1];
  29325. var gridModel = xAxisModel.getCoordSysModel();
  29326. if (__DEV__) {
  29327. if (!gridModel) {
  29328. throw new Error(
  29329. 'Grid "' + retrieve(
  29330. xAxisModel.get('gridIndex'),
  29331. xAxisModel.get('gridId'),
  29332. 0
  29333. ) + '" not found'
  29334. );
  29335. }
  29336. if (xAxisModel.getCoordSysModel() !== yAxisModel.getCoordSysModel()) {
  29337. throw new Error('xAxis and yAxis must use the same grid');
  29338. }
  29339. }
  29340. var grid = gridModel.coordinateSystem;
  29341. seriesModel.coordinateSystem = grid.getCartesian(
  29342. xAxisModel.componentIndex, yAxisModel.componentIndex
  29343. );
  29344. });
  29345. return grids;
  29346. };
  29347. // For deciding which dimensions to use when creating list data
  29348. Grid.dimensions = Grid.prototype.dimensions = Cartesian2D.prototype.dimensions;
  29349. CoordinateSystemManager.register('cartesian2d', Grid);
  29350. var PI$2 = Math.PI;
  29351. function makeAxisEventDataBase(axisModel) {
  29352. var eventData = {
  29353. componentType: axisModel.mainType
  29354. };
  29355. eventData[axisModel.mainType + 'Index'] = axisModel.componentIndex;
  29356. return eventData;
  29357. }
  29358. /**
  29359. * A final axis is translated and rotated from a "standard axis".
  29360. * So opt.position and opt.rotation is required.
  29361. *
  29362. * A standard axis is and axis from [0, 0] to [0, axisExtent[1]],
  29363. * for example: (0, 0) ------------> (0, 50)
  29364. *
  29365. * nameDirection or tickDirection or labelDirection is 1 means tick
  29366. * or label is below the standard axis, whereas is -1 means above
  29367. * the standard axis. labelOffset means offset between label and axis,
  29368. * which is useful when 'onZero', where axisLabel is in the grid and
  29369. * label in outside grid.
  29370. *
  29371. * Tips: like always,
  29372. * positive rotation represents anticlockwise, and negative rotation
  29373. * represents clockwise.
  29374. * The direction of position coordinate is the same as the direction
  29375. * of screen coordinate.
  29376. *
  29377. * Do not need to consider axis 'inverse', which is auto processed by
  29378. * axis extent.
  29379. *
  29380. * @param {module:zrender/container/Group} group
  29381. * @param {Object} axisModel
  29382. * @param {Object} opt Standard axis parameters.
  29383. * @param {Array.<number>} opt.position [x, y]
  29384. * @param {number} opt.rotation by radian
  29385. * @param {number} [opt.nameDirection=1] 1 or -1 Used when nameLocation is 'middle' or 'center'.
  29386. * @param {number} [opt.tickDirection=1] 1 or -1
  29387. * @param {number} [opt.labelDirection=1] 1 or -1
  29388. * @param {number} [opt.labelOffset=0] Usefull when onZero.
  29389. * @param {string} [opt.axisLabelShow] default get from axisModel.
  29390. * @param {string} [opt.axisName] default get from axisModel.
  29391. * @param {number} [opt.axisNameAvailableWidth]
  29392. * @param {number} [opt.labelRotate] by degree, default get from axisModel.
  29393. * @param {number} [opt.labelInterval] Default label interval when label
  29394. * interval from model is null or 'auto'.
  29395. * @param {number} [opt.strokeContainThreshold] Default label interval when label
  29396. * @param {number} [opt.nameTruncateMaxWidth]
  29397. */
  29398. var AxisBuilder = function (axisModel, opt) {
  29399. /**
  29400. * @readOnly
  29401. */
  29402. this.opt = opt;
  29403. /**
  29404. * @readOnly
  29405. */
  29406. this.axisModel = axisModel;
  29407. // Default value
  29408. defaults(
  29409. opt,
  29410. {
  29411. labelOffset: 0,
  29412. nameDirection: 1,
  29413. tickDirection: 1,
  29414. labelDirection: 1,
  29415. silent: true
  29416. }
  29417. );
  29418. /**
  29419. * @readOnly
  29420. */
  29421. this.group = new Group();
  29422. // FIXME Not use a seperate text group?
  29423. var dumbGroup = new Group({
  29424. position: opt.position.slice(),
  29425. rotation: opt.rotation
  29426. });
  29427. // this.group.add(dumbGroup);
  29428. // this._dumbGroup = dumbGroup;
  29429. dumbGroup.updateTransform();
  29430. this._transform = dumbGroup.transform;
  29431. this._dumbGroup = dumbGroup;
  29432. };
  29433. AxisBuilder.prototype = {
  29434. constructor: AxisBuilder,
  29435. hasBuilder: function (name) {
  29436. return !!builders[name];
  29437. },
  29438. add: function (name) {
  29439. builders[name].call(this);
  29440. },
  29441. getGroup: function () {
  29442. return this.group;
  29443. }
  29444. };
  29445. var builders = {
  29446. /**
  29447. * @private
  29448. */
  29449. axisLine: function () {
  29450. var opt = this.opt;
  29451. var axisModel = this.axisModel;
  29452. if (!axisModel.get('axisLine.show')) {
  29453. return;
  29454. }
  29455. var extent = this.axisModel.axis.getExtent();
  29456. var matrix = this._transform;
  29457. var pt1 = [extent[0], 0];
  29458. var pt2 = [extent[1], 0];
  29459. if (matrix) {
  29460. applyTransform(pt1, pt1, matrix);
  29461. applyTransform(pt2, pt2, matrix);
  29462. }
  29463. var lineStyle = extend(
  29464. {
  29465. lineCap: 'round'
  29466. },
  29467. axisModel.getModel('axisLine.lineStyle').getLineStyle()
  29468. );
  29469. this.group.add(new Line(subPixelOptimizeLine({
  29470. // Id for animation
  29471. anid: 'line',
  29472. shape: {
  29473. x1: pt1[0],
  29474. y1: pt1[1],
  29475. x2: pt2[0],
  29476. y2: pt2[1]
  29477. },
  29478. style: lineStyle,
  29479. strokeContainThreshold: opt.strokeContainThreshold || 5,
  29480. silent: true,
  29481. z2: 1
  29482. })));
  29483. var arrows = axisModel.get('axisLine.symbol');
  29484. var arrowSize = axisModel.get('axisLine.symbolSize');
  29485. var arrowOffset = axisModel.get('axisLine.symbolOffset') || 0;
  29486. if (typeof arrowOffset === 'number') {
  29487. arrowOffset = [arrowOffset, arrowOffset];
  29488. }
  29489. if (arrows != null) {
  29490. if (typeof arrows === 'string') {
  29491. // Use the same arrow for start and end point
  29492. arrows = [arrows, arrows];
  29493. }
  29494. if (typeof arrowSize === 'string'
  29495. || typeof arrowSize === 'number'
  29496. ) {
  29497. // Use the same size for width and height
  29498. arrowSize = [arrowSize, arrowSize];
  29499. }
  29500. var symbolWidth = arrowSize[0];
  29501. var symbolHeight = arrowSize[1];
  29502. each$1([{
  29503. rotate: opt.rotation + Math.PI / 2,
  29504. offset: arrowOffset[0],
  29505. r: 0
  29506. }, {
  29507. rotate: opt.rotation - Math.PI / 2,
  29508. offset: arrowOffset[1],
  29509. r: Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0])
  29510. + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1]))
  29511. }], function (point, index) {
  29512. if (arrows[index] !== 'none' && arrows[index] != null) {
  29513. var symbol = createSymbol(
  29514. arrows[index],
  29515. -symbolWidth / 2,
  29516. -symbolHeight / 2,
  29517. symbolWidth,
  29518. symbolHeight,
  29519. lineStyle.stroke,
  29520. true
  29521. );
  29522. // Calculate arrow position with offset
  29523. var r = point.r + point.offset;
  29524. var pos = [
  29525. pt1[0] + r * Math.cos(opt.rotation),
  29526. pt1[1] - r * Math.sin(opt.rotation)
  29527. ];
  29528. symbol.attr({
  29529. rotation: point.rotate,
  29530. position: pos,
  29531. silent: true
  29532. });
  29533. this.group.add(symbol);
  29534. }
  29535. }, this);
  29536. }
  29537. },
  29538. /**
  29539. * @private
  29540. */
  29541. axisTickLabel: function () {
  29542. var axisModel = this.axisModel;
  29543. var opt = this.opt;
  29544. var tickEls = buildAxisTick(this, axisModel, opt);
  29545. var labelEls = buildAxisLabel(this, axisModel, opt);
  29546. fixMinMaxLabelShow(axisModel, labelEls, tickEls);
  29547. },
  29548. /**
  29549. * @private
  29550. */
  29551. axisName: function () {
  29552. var opt = this.opt;
  29553. var axisModel = this.axisModel;
  29554. var name = retrieve(opt.axisName, axisModel.get('name'));
  29555. if (!name) {
  29556. return;
  29557. }
  29558. var nameLocation = axisModel.get('nameLocation');
  29559. var nameDirection = opt.nameDirection;
  29560. var textStyleModel = axisModel.getModel('nameTextStyle');
  29561. var gap = axisModel.get('nameGap') || 0;
  29562. var extent = this.axisModel.axis.getExtent();
  29563. var gapSignal = extent[0] > extent[1] ? -1 : 1;
  29564. var pos = [
  29565. nameLocation === 'start'
  29566. ? extent[0] - gapSignal * gap
  29567. : nameLocation === 'end'
  29568. ? extent[1] + gapSignal * gap
  29569. : (extent[0] + extent[1]) / 2, // 'middle'
  29570. // Reuse labelOffset.
  29571. isNameLocationCenter(nameLocation) ? opt.labelOffset + nameDirection * gap : 0
  29572. ];
  29573. var labelLayout;
  29574. var nameRotation = axisModel.get('nameRotate');
  29575. if (nameRotation != null) {
  29576. nameRotation = nameRotation * PI$2 / 180; // To radian.
  29577. }
  29578. var axisNameAvailableWidth;
  29579. if (isNameLocationCenter(nameLocation)) {
  29580. labelLayout = innerTextLayout(
  29581. opt.rotation,
  29582. nameRotation != null ? nameRotation : opt.rotation, // Adapt to axis.
  29583. nameDirection
  29584. );
  29585. }
  29586. else {
  29587. labelLayout = endTextLayout(
  29588. opt, nameLocation, nameRotation || 0, extent
  29589. );
  29590. axisNameAvailableWidth = opt.axisNameAvailableWidth;
  29591. if (axisNameAvailableWidth != null) {
  29592. axisNameAvailableWidth = Math.abs(
  29593. axisNameAvailableWidth / Math.sin(labelLayout.rotation)
  29594. );
  29595. !isFinite(axisNameAvailableWidth) && (axisNameAvailableWidth = null);
  29596. }
  29597. }
  29598. var textFont = textStyleModel.getFont();
  29599. var truncateOpt = axisModel.get('nameTruncate', true) || {};
  29600. var ellipsis = truncateOpt.ellipsis;
  29601. var maxWidth = retrieve(
  29602. opt.nameTruncateMaxWidth, truncateOpt.maxWidth, axisNameAvailableWidth
  29603. );
  29604. // FIXME
  29605. // truncate rich text? (consider performance)
  29606. var truncatedText = (ellipsis != null && maxWidth != null)
  29607. ? truncateText$1(
  29608. name, maxWidth, textFont, ellipsis,
  29609. {minChar: 2, placeholder: truncateOpt.placeholder}
  29610. )
  29611. : name;
  29612. var tooltipOpt = axisModel.get('tooltip', true);
  29613. var mainType = axisModel.mainType;
  29614. var formatterParams = {
  29615. componentType: mainType,
  29616. name: name,
  29617. $vars: ['name']
  29618. };
  29619. formatterParams[mainType + 'Index'] = axisModel.componentIndex;
  29620. var textEl = new Text({
  29621. // Id for animation
  29622. anid: 'name',
  29623. __fullText: name,
  29624. __truncatedText: truncatedText,
  29625. position: pos,
  29626. rotation: labelLayout.rotation,
  29627. silent: isSilent(axisModel),
  29628. z2: 1,
  29629. tooltip: (tooltipOpt && tooltipOpt.show)
  29630. ? extend({
  29631. content: name,
  29632. formatter: function () {
  29633. return name;
  29634. },
  29635. formatterParams: formatterParams
  29636. }, tooltipOpt)
  29637. : null
  29638. });
  29639. setTextStyle(textEl.style, textStyleModel, {
  29640. text: truncatedText,
  29641. textFont: textFont,
  29642. textFill: textStyleModel.getTextColor()
  29643. || axisModel.get('axisLine.lineStyle.color'),
  29644. textAlign: labelLayout.textAlign,
  29645. textVerticalAlign: labelLayout.textVerticalAlign
  29646. });
  29647. if (axisModel.get('triggerEvent')) {
  29648. textEl.eventData = makeAxisEventDataBase(axisModel);
  29649. textEl.eventData.targetType = 'axisName';
  29650. textEl.eventData.name = name;
  29651. }
  29652. // FIXME
  29653. this._dumbGroup.add(textEl);
  29654. textEl.updateTransform();
  29655. this.group.add(textEl);
  29656. textEl.decomposeTransform();
  29657. }
  29658. };
  29659. /**
  29660. * @public
  29661. * @static
  29662. * @param {Object} opt
  29663. * @param {number} axisRotation in radian
  29664. * @param {number} textRotation in radian
  29665. * @param {number} direction
  29666. * @return {Object} {
  29667. * rotation, // according to axis
  29668. * textAlign,
  29669. * textVerticalAlign
  29670. * }
  29671. */
  29672. var innerTextLayout = AxisBuilder.innerTextLayout = function (axisRotation, textRotation, direction) {
  29673. var rotationDiff = remRadian(textRotation - axisRotation);
  29674. var textAlign;
  29675. var textVerticalAlign;
  29676. if (isRadianAroundZero(rotationDiff)) { // Label is parallel with axis line.
  29677. textVerticalAlign = direction > 0 ? 'top' : 'bottom';
  29678. textAlign = 'center';
  29679. }
  29680. else if (isRadianAroundZero(rotationDiff - PI$2)) { // Label is inverse parallel with axis line.
  29681. textVerticalAlign = direction > 0 ? 'bottom' : 'top';
  29682. textAlign = 'center';
  29683. }
  29684. else {
  29685. textVerticalAlign = 'middle';
  29686. if (rotationDiff > 0 && rotationDiff < PI$2) {
  29687. textAlign = direction > 0 ? 'right' : 'left';
  29688. }
  29689. else {
  29690. textAlign = direction > 0 ? 'left' : 'right';
  29691. }
  29692. }
  29693. return {
  29694. rotation: rotationDiff,
  29695. textAlign: textAlign,
  29696. textVerticalAlign: textVerticalAlign
  29697. };
  29698. };
  29699. function endTextLayout(opt, textPosition, textRotate, extent) {
  29700. var rotationDiff = remRadian(textRotate - opt.rotation);
  29701. var textAlign;
  29702. var textVerticalAlign;
  29703. var inverse = extent[0] > extent[1];
  29704. var onLeft = (textPosition === 'start' && !inverse)
  29705. || (textPosition !== 'start' && inverse);
  29706. if (isRadianAroundZero(rotationDiff - PI$2 / 2)) {
  29707. textVerticalAlign = onLeft ? 'bottom' : 'top';
  29708. textAlign = 'center';
  29709. }
  29710. else if (isRadianAroundZero(rotationDiff - PI$2 * 1.5)) {
  29711. textVerticalAlign = onLeft ? 'top' : 'bottom';
  29712. textAlign = 'center';
  29713. }
  29714. else {
  29715. textVerticalAlign = 'middle';
  29716. if (rotationDiff < PI$2 * 1.5 && rotationDiff > PI$2 / 2) {
  29717. textAlign = onLeft ? 'left' : 'right';
  29718. }
  29719. else {
  29720. textAlign = onLeft ? 'right' : 'left';
  29721. }
  29722. }
  29723. return {
  29724. rotation: rotationDiff,
  29725. textAlign: textAlign,
  29726. textVerticalAlign: textVerticalAlign
  29727. };
  29728. }
  29729. function isSilent(axisModel) {
  29730. var tooltipOpt = axisModel.get('tooltip');
  29731. return axisModel.get('silent')
  29732. // Consider mouse cursor, add these restrictions.
  29733. || !(
  29734. axisModel.get('triggerEvent') || (tooltipOpt && tooltipOpt.show)
  29735. );
  29736. }
  29737. function fixMinMaxLabelShow(axisModel, labelEls, tickEls) {
  29738. // If min or max are user set, we need to check
  29739. // If the tick on min(max) are overlap on their neighbour tick
  29740. // If they are overlapped, we need to hide the min(max) tick label
  29741. var showMinLabel = axisModel.get('axisLabel.showMinLabel');
  29742. var showMaxLabel = axisModel.get('axisLabel.showMaxLabel');
  29743. // FIXME
  29744. // Have not consider onBand yet, where tick els is more than label els.
  29745. labelEls = labelEls || [];
  29746. tickEls = tickEls || [];
  29747. var firstLabel = labelEls[0];
  29748. var nextLabel = labelEls[1];
  29749. var lastLabel = labelEls[labelEls.length - 1];
  29750. var prevLabel = labelEls[labelEls.length - 2];
  29751. var firstTick = tickEls[0];
  29752. var nextTick = tickEls[1];
  29753. var lastTick = tickEls[tickEls.length - 1];
  29754. var prevTick = tickEls[tickEls.length - 2];
  29755. if (showMinLabel === false) {
  29756. ignoreEl(firstLabel);
  29757. ignoreEl(firstTick);
  29758. }
  29759. else if (isTwoLabelOverlapped(firstLabel, nextLabel)) {
  29760. if (showMinLabel) {
  29761. ignoreEl(nextLabel);
  29762. ignoreEl(nextTick);
  29763. }
  29764. else {
  29765. ignoreEl(firstLabel);
  29766. ignoreEl(firstTick);
  29767. }
  29768. }
  29769. if (showMaxLabel === false) {
  29770. ignoreEl(lastLabel);
  29771. ignoreEl(lastTick);
  29772. }
  29773. else if (isTwoLabelOverlapped(prevLabel, lastLabel)) {
  29774. if (showMaxLabel) {
  29775. ignoreEl(prevLabel);
  29776. ignoreEl(prevTick);
  29777. }
  29778. else {
  29779. ignoreEl(lastLabel);
  29780. ignoreEl(lastTick);
  29781. }
  29782. }
  29783. }
  29784. function ignoreEl(el) {
  29785. el && (el.ignore = true);
  29786. }
  29787. function isTwoLabelOverlapped(current, next, labelLayout) {
  29788. // current and next has the same rotation.
  29789. var firstRect = current && current.getBoundingRect().clone();
  29790. var nextRect = next && next.getBoundingRect().clone();
  29791. if (!firstRect || !nextRect) {
  29792. return;
  29793. }
  29794. // When checking intersect of two rotated labels, we use mRotationBack
  29795. // to avoid that boundingRect is enlarge when using `boundingRect.applyTransform`.
  29796. var mRotationBack = identity([]);
  29797. rotate(mRotationBack, mRotationBack, -current.rotation);
  29798. firstRect.applyTransform(mul$1([], mRotationBack, current.getLocalTransform()));
  29799. nextRect.applyTransform(mul$1([], mRotationBack, next.getLocalTransform()));
  29800. return firstRect.intersect(nextRect);
  29801. }
  29802. function isNameLocationCenter(nameLocation) {
  29803. return nameLocation === 'middle' || nameLocation === 'center';
  29804. }
  29805. /**
  29806. * @static
  29807. */
  29808. var ifIgnoreOnTick$1 = AxisBuilder.ifIgnoreOnTick = function (
  29809. axis,
  29810. i,
  29811. interval,
  29812. ticksCnt,
  29813. showMinLabel,
  29814. showMaxLabel
  29815. ) {
  29816. if (i === 0 && showMinLabel || i === ticksCnt - 1 && showMaxLabel) {
  29817. return false;
  29818. }
  29819. // FIXME
  29820. // Have not consider label overlap (if label is too long) yet.
  29821. var rawTick;
  29822. var scale$$1 = axis.scale;
  29823. return scale$$1.type === 'ordinal'
  29824. && (
  29825. typeof interval === 'function'
  29826. ? (
  29827. rawTick = scale$$1.getTicks()[i],
  29828. !interval(rawTick, scale$$1.getLabel(rawTick))
  29829. )
  29830. : i % (interval + 1)
  29831. );
  29832. };
  29833. /**
  29834. * @static
  29835. */
  29836. var getInterval$1 = AxisBuilder.getInterval = function (model, labelInterval) {
  29837. var interval = model.get('interval');
  29838. if (interval == null || interval == 'auto') {
  29839. interval = labelInterval;
  29840. }
  29841. return interval;
  29842. };
  29843. function buildAxisTick(axisBuilder, axisModel, opt) {
  29844. var axis = axisModel.axis;
  29845. if (!axisModel.get('axisTick.show') || axis.scale.isBlank()) {
  29846. return;
  29847. }
  29848. var tickModel = axisModel.getModel('axisTick');
  29849. var lineStyleModel = tickModel.getModel('lineStyle');
  29850. var tickLen = tickModel.get('length');
  29851. var tickInterval = getInterval$1(tickModel, opt.labelInterval);
  29852. var ticksCoords = axis.getTicksCoords(tickModel.get('alignWithLabel'));
  29853. // FIXME
  29854. // Corresponds to ticksCoords ?
  29855. var ticks = axis.scale.getTicks();
  29856. var showMinLabel = axisModel.get('axisLabel.showMinLabel');
  29857. var showMaxLabel = axisModel.get('axisLabel.showMaxLabel');
  29858. var pt1 = [];
  29859. var pt2 = [];
  29860. var matrix = axisBuilder._transform;
  29861. var tickEls = [];
  29862. var ticksCnt = ticksCoords.length;
  29863. for (var i = 0; i < ticksCnt; i++) {
  29864. // Only ordinal scale support tick interval
  29865. if (ifIgnoreOnTick$1(
  29866. axis, i, tickInterval, ticksCnt,
  29867. showMinLabel, showMaxLabel
  29868. )) {
  29869. continue;
  29870. }
  29871. var tickCoord = ticksCoords[i];
  29872. pt1[0] = tickCoord;
  29873. pt1[1] = 0;
  29874. pt2[0] = tickCoord;
  29875. pt2[1] = opt.tickDirection * tickLen;
  29876. if (matrix) {
  29877. applyTransform(pt1, pt1, matrix);
  29878. applyTransform(pt2, pt2, matrix);
  29879. }
  29880. // Tick line, Not use group transform to have better line draw
  29881. var tickEl = new Line(subPixelOptimizeLine({
  29882. // Id for animation
  29883. anid: 'tick_' + ticks[i],
  29884. shape: {
  29885. x1: pt1[0],
  29886. y1: pt1[1],
  29887. x2: pt2[0],
  29888. y2: pt2[1]
  29889. },
  29890. style: defaults(
  29891. lineStyleModel.getLineStyle(),
  29892. {
  29893. stroke: axisModel.get('axisLine.lineStyle.color')
  29894. }
  29895. ),
  29896. z2: 2,
  29897. silent: true
  29898. }));
  29899. axisBuilder.group.add(tickEl);
  29900. tickEls.push(tickEl);
  29901. }
  29902. return tickEls;
  29903. }
  29904. function buildAxisLabel(axisBuilder, axisModel, opt) {
  29905. var axis = axisModel.axis;
  29906. var show = retrieve(opt.axisLabelShow, axisModel.get('axisLabel.show'));
  29907. if (!show || axis.scale.isBlank()) {
  29908. return;
  29909. }
  29910. var labelModel = axisModel.getModel('axisLabel');
  29911. var labelMargin = labelModel.get('margin');
  29912. var ticks = axis.scale.getTicks();
  29913. var labels = axisModel.getFormattedLabels();
  29914. // Special label rotate.
  29915. var labelRotation = (
  29916. retrieve(opt.labelRotate, labelModel.get('rotate')) || 0
  29917. ) * PI$2 / 180;
  29918. var labelLayout = innerTextLayout(opt.rotation, labelRotation, opt.labelDirection);
  29919. var categoryData = axisModel.getCategories();
  29920. var labelEls = [];
  29921. var silent = isSilent(axisModel);
  29922. var triggerEvent = axisModel.get('triggerEvent');
  29923. var showMinLabel = axisModel.get('axisLabel.showMinLabel');
  29924. var showMaxLabel = axisModel.get('axisLabel.showMaxLabel');
  29925. each$1(ticks, function (tickVal, index) {
  29926. if (ifIgnoreOnTick$1(
  29927. axis, index, opt.labelInterval, ticks.length,
  29928. showMinLabel, showMaxLabel
  29929. )) {
  29930. return;
  29931. }
  29932. var itemLabelModel = labelModel;
  29933. if (categoryData && categoryData[tickVal] && categoryData[tickVal].textStyle) {
  29934. itemLabelModel = new Model(
  29935. categoryData[tickVal].textStyle, labelModel, axisModel.ecModel
  29936. );
  29937. }
  29938. var textColor = itemLabelModel.getTextColor()
  29939. || axisModel.get('axisLine.lineStyle.color');
  29940. var tickCoord = axis.dataToCoord(tickVal);
  29941. var pos = [
  29942. tickCoord,
  29943. opt.labelOffset + opt.labelDirection * labelMargin
  29944. ];
  29945. var labelStr = axis.scale.getLabel(tickVal);
  29946. var textEl = new Text({
  29947. // Id for animation
  29948. anid: 'label_' + tickVal,
  29949. position: pos,
  29950. rotation: labelLayout.rotation,
  29951. silent: silent,
  29952. z2: 10
  29953. });
  29954. setTextStyle(textEl.style, itemLabelModel, {
  29955. text: labels[index],
  29956. textAlign: itemLabelModel.getShallow('align', true)
  29957. || labelLayout.textAlign,
  29958. textVerticalAlign: itemLabelModel.getShallow('verticalAlign', true)
  29959. || itemLabelModel.getShallow('baseline', true)
  29960. || labelLayout.textVerticalAlign,
  29961. textFill: typeof textColor === 'function'
  29962. ? textColor(
  29963. // (1) In category axis with data zoom, tick is not the original
  29964. // index of axis.data. So tick should not be exposed to user
  29965. // in category axis.
  29966. // (2) Compatible with previous version, which always returns labelStr.
  29967. // But in interval scale labelStr is like '223,445', which maked
  29968. // user repalce ','. So we modify it to return original val but remain
  29969. // it as 'string' to avoid error in replacing.
  29970. axis.type === 'category' ? labelStr : axis.type === 'value' ? tickVal + '' : tickVal,
  29971. index
  29972. )
  29973. : textColor
  29974. });
  29975. // Pack data for mouse event
  29976. if (triggerEvent) {
  29977. textEl.eventData = makeAxisEventDataBase(axisModel);
  29978. textEl.eventData.targetType = 'axisLabel';
  29979. textEl.eventData.value = labelStr;
  29980. }
  29981. // FIXME
  29982. axisBuilder._dumbGroup.add(textEl);
  29983. textEl.updateTransform();
  29984. labelEls.push(textEl);
  29985. axisBuilder.group.add(textEl);
  29986. textEl.decomposeTransform();
  29987. });
  29988. return labelEls;
  29989. }
  29990. // Build axisPointerModel, mergin tooltip.axisPointer model for each axis.
  29991. // allAxesInfo should be updated when setOption performed.
  29992. function fixValue(axisModel) {
  29993. var axisInfo = getAxisInfo(axisModel);
  29994. if (!axisInfo) {
  29995. return;
  29996. }
  29997. var axisPointerModel = axisInfo.axisPointerModel;
  29998. var scale = axisInfo.axis.scale;
  29999. var option = axisPointerModel.option;
  30000. var status = axisPointerModel.get('status');
  30001. var value = axisPointerModel.get('value');
  30002. // Parse init value for category and time axis.
  30003. if (value != null) {
  30004. value = scale.parse(value);
  30005. }
  30006. var useHandle = isHandleTrigger(axisPointerModel);
  30007. // If `handle` used, `axisPointer` will always be displayed, so value
  30008. // and status should be initialized.
  30009. if (status == null) {
  30010. option.status = useHandle ? 'show' : 'hide';
  30011. }
  30012. var extent = scale.getExtent().slice();
  30013. extent[0] > extent[1] && extent.reverse();
  30014. if (// Pick a value on axis when initializing.
  30015. value == null
  30016. // If both `handle` and `dataZoom` are used, value may be out of axis extent,
  30017. // where we should re-pick a value to keep `handle` displaying normally.
  30018. || value > extent[1]
  30019. ) {
  30020. // Make handle displayed on the end of the axis when init, which looks better.
  30021. value = extent[1];
  30022. }
  30023. if (value < extent[0]) {
  30024. value = extent[0];
  30025. }
  30026. option.value = value;
  30027. if (useHandle) {
  30028. option.status = axisInfo.axis.scale.isBlank() ? 'hide' : 'show';
  30029. }
  30030. }
  30031. function getAxisInfo(axisModel) {
  30032. var coordSysAxesInfo = (axisModel.ecModel.getComponent('axisPointer') || {}).coordSysAxesInfo;
  30033. return coordSysAxesInfo && coordSysAxesInfo.axesInfo[makeKey(axisModel)];
  30034. }
  30035. function getAxisPointerModel(axisModel) {
  30036. var axisInfo = getAxisInfo(axisModel);
  30037. return axisInfo && axisInfo.axisPointerModel;
  30038. }
  30039. function isHandleTrigger(axisPointerModel) {
  30040. return !!axisPointerModel.get('handle.show');
  30041. }
  30042. /**
  30043. * @param {module:echarts/model/Model} model
  30044. * @return {string} unique key
  30045. */
  30046. function makeKey(model) {
  30047. return model.type + '||' + model.id;
  30048. }
  30049. /**
  30050. * Base class of AxisView.
  30051. */
  30052. var AxisView = extendComponentView({
  30053. type: 'axis',
  30054. /**
  30055. * @private
  30056. */
  30057. _axisPointer: null,
  30058. /**
  30059. * @protected
  30060. * @type {string}
  30061. */
  30062. axisPointerClass: null,
  30063. /**
  30064. * @override
  30065. */
  30066. render: function (axisModel, ecModel, api, payload) {
  30067. // FIXME
  30068. // This process should proformed after coordinate systems updated
  30069. // (axis scale updated), and should be performed each time update.
  30070. // So put it here temporarily, although it is not appropriate to
  30071. // put a model-writing procedure in `view`.
  30072. this.axisPointerClass && fixValue(axisModel);
  30073. AxisView.superApply(this, 'render', arguments);
  30074. updateAxisPointer(this, axisModel, ecModel, api, payload, true);
  30075. },
  30076. /**
  30077. * Action handler.
  30078. * @public
  30079. * @param {module:echarts/coord/cartesian/AxisModel} axisModel
  30080. * @param {module:echarts/model/Global} ecModel
  30081. * @param {module:echarts/ExtensionAPI} api
  30082. * @param {Object} payload
  30083. */
  30084. updateAxisPointer: function (axisModel, ecModel, api, payload, force) {
  30085. updateAxisPointer(this, axisModel, ecModel, api, payload, false);
  30086. },
  30087. /**
  30088. * @override
  30089. */
  30090. remove: function (ecModel, api) {
  30091. var axisPointer = this._axisPointer;
  30092. axisPointer && axisPointer.remove(api);
  30093. AxisView.superApply(this, 'remove', arguments);
  30094. },
  30095. /**
  30096. * @override
  30097. */
  30098. dispose: function (ecModel, api) {
  30099. disposeAxisPointer(this, api);
  30100. AxisView.superApply(this, 'dispose', arguments);
  30101. }
  30102. });
  30103. function updateAxisPointer(axisView, axisModel, ecModel, api, payload, forceRender) {
  30104. var Clazz = AxisView.getAxisPointerClass(axisView.axisPointerClass);
  30105. if (!Clazz) {
  30106. return;
  30107. }
  30108. var axisPointerModel = getAxisPointerModel(axisModel);
  30109. axisPointerModel
  30110. ? (axisView._axisPointer || (axisView._axisPointer = new Clazz()))
  30111. .render(axisModel, axisPointerModel, api, forceRender)
  30112. : disposeAxisPointer(axisView, api);
  30113. }
  30114. function disposeAxisPointer(axisView, ecModel, api) {
  30115. var axisPointer = axisView._axisPointer;
  30116. axisPointer && axisPointer.dispose(ecModel, api);
  30117. axisView._axisPointer = null;
  30118. }
  30119. var axisPointerClazz = [];
  30120. AxisView.registerAxisPointerClass = function (type, clazz) {
  30121. if (__DEV__) {
  30122. if (axisPointerClazz[type]) {
  30123. throw new Error('axisPointer ' + type + ' exists');
  30124. }
  30125. }
  30126. axisPointerClazz[type] = clazz;
  30127. };
  30128. AxisView.getAxisPointerClass = function (type) {
  30129. return type && axisPointerClazz[type];
  30130. };
  30131. /**
  30132. * @param {Object} opt {labelInside}
  30133. * @return {Object} {
  30134. * position, rotation, labelDirection, labelOffset,
  30135. * tickDirection, labelRotate, labelInterval, z2
  30136. * }
  30137. */
  30138. function layout$1(gridModel, axisModel, opt) {
  30139. opt = opt || {};
  30140. var grid = gridModel.coordinateSystem;
  30141. var axis = axisModel.axis;
  30142. var layout = {};
  30143. var rawAxisPosition = axis.position;
  30144. var axisPosition = axis.onZero ? 'onZero' : rawAxisPosition;
  30145. var axisDim = axis.dim;
  30146. var rect = grid.getRect();
  30147. var rectBound = [rect.x, rect.x + rect.width, rect.y, rect.y + rect.height];
  30148. var idx = {left: 0, right: 1, top: 0, bottom: 1, onZero: 2};
  30149. var axisOffset = axisModel.get('offset') || 0;
  30150. var posBound = axisDim === 'x'
  30151. ? [rectBound[2] - axisOffset, rectBound[3] + axisOffset]
  30152. : [rectBound[0] - axisOffset, rectBound[1] + axisOffset];
  30153. if (axis.onZero) {
  30154. var otherAxis = grid.getAxis(axisDim === 'x' ? 'y' : 'x', axis.onZeroAxisIndex);
  30155. var onZeroCoord = otherAxis.toGlobalCoord(otherAxis.dataToCoord(0));
  30156. posBound[idx['onZero']] = Math.max(Math.min(onZeroCoord, posBound[1]), posBound[0]);
  30157. }
  30158. // Axis position
  30159. layout.position = [
  30160. axisDim === 'y' ? posBound[idx[axisPosition]] : rectBound[0],
  30161. axisDim === 'x' ? posBound[idx[axisPosition]] : rectBound[3]
  30162. ];
  30163. // Axis rotation
  30164. layout.rotation = Math.PI / 2 * (axisDim === 'x' ? 0 : 1);
  30165. // Tick and label direction, x y is axisDim
  30166. var dirMap = {top: -1, bottom: 1, left: -1, right: 1};
  30167. layout.labelDirection = layout.tickDirection = layout.nameDirection = dirMap[rawAxisPosition];
  30168. layout.labelOffset = axis.onZero ? posBound[idx[rawAxisPosition]] - posBound[idx['onZero']] : 0;
  30169. if (axisModel.get('axisTick.inside')) {
  30170. layout.tickDirection = -layout.tickDirection;
  30171. }
  30172. if (retrieve(opt.labelInside, axisModel.get('axisLabel.inside'))) {
  30173. layout.labelDirection = -layout.labelDirection;
  30174. }
  30175. // Special label rotation
  30176. var labelRotate = axisModel.get('axisLabel.rotate');
  30177. layout.labelRotate = axisPosition === 'top' ? -labelRotate : labelRotate;
  30178. // label interval when auto mode.
  30179. layout.labelInterval = axis.getLabelInterval();
  30180. // Over splitLine and splitArea
  30181. layout.z2 = 1;
  30182. return layout;
  30183. }
  30184. var ifIgnoreOnTick = AxisBuilder.ifIgnoreOnTick;
  30185. var getInterval = AxisBuilder.getInterval;
  30186. var axisBuilderAttrs = [
  30187. 'axisLine', 'axisTickLabel', 'axisName'
  30188. ];
  30189. var selfBuilderAttrs = [
  30190. 'splitArea', 'splitLine'
  30191. ];
  30192. // function getAlignWithLabel(model, axisModel) {
  30193. // var alignWithLabel = model.get('alignWithLabel');
  30194. // if (alignWithLabel === 'auto') {
  30195. // alignWithLabel = axisModel.get('axisTick.alignWithLabel');
  30196. // }
  30197. // return alignWithLabel;
  30198. // }
  30199. var CartesianAxisView = AxisView.extend({
  30200. type: 'cartesianAxis',
  30201. axisPointerClass: 'CartesianAxisPointer',
  30202. /**
  30203. * @override
  30204. */
  30205. render: function (axisModel, ecModel, api, payload) {
  30206. this.group.removeAll();
  30207. var oldAxisGroup = this._axisGroup;
  30208. this._axisGroup = new Group();
  30209. this.group.add(this._axisGroup);
  30210. if (!axisModel.get('show')) {
  30211. return;
  30212. }
  30213. var gridModel = axisModel.getCoordSysModel();
  30214. var layout = layout$1(gridModel, axisModel);
  30215. var axisBuilder = new AxisBuilder(axisModel, layout);
  30216. each$1(axisBuilderAttrs, axisBuilder.add, axisBuilder);
  30217. this._axisGroup.add(axisBuilder.getGroup());
  30218. each$1(selfBuilderAttrs, function (name) {
  30219. if (axisModel.get(name + '.show')) {
  30220. this['_' + name](axisModel, gridModel, layout.labelInterval);
  30221. }
  30222. }, this);
  30223. groupTransition(oldAxisGroup, this._axisGroup, axisModel);
  30224. CartesianAxisView.superCall(this, 'render', axisModel, ecModel, api, payload);
  30225. },
  30226. /**
  30227. * @param {module:echarts/coord/cartesian/AxisModel} axisModel
  30228. * @param {module:echarts/coord/cartesian/GridModel} gridModel
  30229. * @param {number|Function} labelInterval
  30230. * @private
  30231. */
  30232. _splitLine: function (axisModel, gridModel, labelInterval) {
  30233. var axis = axisModel.axis;
  30234. if (axis.scale.isBlank()) {
  30235. return;
  30236. }
  30237. var splitLineModel = axisModel.getModel('splitLine');
  30238. var lineStyleModel = splitLineModel.getModel('lineStyle');
  30239. var lineColors = lineStyleModel.get('color');
  30240. var lineInterval = getInterval(splitLineModel, labelInterval);
  30241. lineColors = isArray(lineColors) ? lineColors : [lineColors];
  30242. var gridRect = gridModel.coordinateSystem.getRect();
  30243. var isHorizontal = axis.isHorizontal();
  30244. var lineCount = 0;
  30245. var ticksCoords = axis.getTicksCoords(
  30246. // splitLineModel.get('alignWithLabel')
  30247. );
  30248. var ticks = axis.scale.getTicks();
  30249. var showMinLabel = axisModel.get('axisLabel.showMinLabel');
  30250. var showMaxLabel = axisModel.get('axisLabel.showMaxLabel');
  30251. var p1 = [];
  30252. var p2 = [];
  30253. // Simple optimization
  30254. // Batching the lines if color are the same
  30255. var lineStyle = lineStyleModel.getLineStyle();
  30256. for (var i = 0; i < ticksCoords.length; i++) {
  30257. if (ifIgnoreOnTick(
  30258. axis, i, lineInterval, ticksCoords.length,
  30259. showMinLabel, showMaxLabel
  30260. )) {
  30261. continue;
  30262. }
  30263. var tickCoord = axis.toGlobalCoord(ticksCoords[i]);
  30264. if (isHorizontal) {
  30265. p1[0] = tickCoord;
  30266. p1[1] = gridRect.y;
  30267. p2[0] = tickCoord;
  30268. p2[1] = gridRect.y + gridRect.height;
  30269. }
  30270. else {
  30271. p1[0] = gridRect.x;
  30272. p1[1] = tickCoord;
  30273. p2[0] = gridRect.x + gridRect.width;
  30274. p2[1] = tickCoord;
  30275. }
  30276. var colorIndex = (lineCount++) % lineColors.length;
  30277. this._axisGroup.add(new Line(subPixelOptimizeLine({
  30278. anid: 'line_' + ticks[i],
  30279. shape: {
  30280. x1: p1[0],
  30281. y1: p1[1],
  30282. x2: p2[0],
  30283. y2: p2[1]
  30284. },
  30285. style: defaults({
  30286. stroke: lineColors[colorIndex]
  30287. }, lineStyle),
  30288. silent: true
  30289. })));
  30290. }
  30291. },
  30292. /**
  30293. * @param {module:echarts/coord/cartesian/AxisModel} axisModel
  30294. * @param {module:echarts/coord/cartesian/GridModel} gridModel
  30295. * @param {number|Function} labelInterval
  30296. * @private
  30297. */
  30298. _splitArea: function (axisModel, gridModel, labelInterval) {
  30299. var axis = axisModel.axis;
  30300. if (axis.scale.isBlank()) {
  30301. return;
  30302. }
  30303. var splitAreaModel = axisModel.getModel('splitArea');
  30304. var areaStyleModel = splitAreaModel.getModel('areaStyle');
  30305. var areaColors = areaStyleModel.get('color');
  30306. var gridRect = gridModel.coordinateSystem.getRect();
  30307. var ticksCoords = axis.getTicksCoords(
  30308. // splitAreaModel.get('alignWithLabel')
  30309. );
  30310. var ticks = axis.scale.getTicks();
  30311. var prevX = axis.toGlobalCoord(ticksCoords[0]);
  30312. var prevY = axis.toGlobalCoord(ticksCoords[0]);
  30313. var count = 0;
  30314. var areaInterval = getInterval(splitAreaModel, labelInterval);
  30315. var areaStyle = areaStyleModel.getAreaStyle();
  30316. areaColors = isArray(areaColors) ? areaColors : [areaColors];
  30317. var showMinLabel = axisModel.get('axisLabel.showMinLabel');
  30318. var showMaxLabel = axisModel.get('axisLabel.showMaxLabel');
  30319. for (var i = 1; i < ticksCoords.length; i++) {
  30320. if (ifIgnoreOnTick(
  30321. axis, i, areaInterval, ticksCoords.length,
  30322. showMinLabel, showMaxLabel
  30323. ) && (i < ticksCoords.length - 1)) {
  30324. continue;
  30325. }
  30326. var tickCoord = axis.toGlobalCoord(ticksCoords[i]);
  30327. var x;
  30328. var y;
  30329. var width;
  30330. var height;
  30331. if (axis.isHorizontal()) {
  30332. x = prevX;
  30333. y = gridRect.y;
  30334. width = tickCoord - x;
  30335. height = gridRect.height;
  30336. }
  30337. else {
  30338. x = gridRect.x;
  30339. y = prevY;
  30340. width = gridRect.width;
  30341. height = tickCoord - y;
  30342. }
  30343. var colorIndex = (count++) % areaColors.length;
  30344. this._axisGroup.add(new Rect({
  30345. anid: 'area_' + ticks[i],
  30346. shape: {
  30347. x: x,
  30348. y: y,
  30349. width: width,
  30350. height: height
  30351. },
  30352. style: defaults({
  30353. fill: areaColors[colorIndex]
  30354. }, areaStyle),
  30355. silent: true
  30356. }));
  30357. prevX = x + width;
  30358. prevY = y + height;
  30359. }
  30360. }
  30361. });
  30362. CartesianAxisView.extend({
  30363. type: 'xAxis'
  30364. });
  30365. CartesianAxisView.extend({
  30366. type: 'yAxis'
  30367. });
  30368. // Grid view
  30369. extendComponentView({
  30370. type: 'grid',
  30371. render: function (gridModel, ecModel) {
  30372. this.group.removeAll();
  30373. if (gridModel.get('show')) {
  30374. this.group.add(new Rect({
  30375. shape: gridModel.coordinateSystem.getRect(),
  30376. style: defaults({
  30377. fill: gridModel.get('backgroundColor')
  30378. }, gridModel.getItemStyle()),
  30379. silent: true,
  30380. z2: -1
  30381. }));
  30382. }
  30383. }
  30384. });
  30385. registerPreprocessor(function (option) {
  30386. // Only create grid when need
  30387. if (option.xAxis && option.yAxis && !option.grid) {
  30388. option.grid = {};
  30389. }
  30390. });
  30391. // In case developer forget to include grid component
  30392. registerVisual(visualSymbol('line', 'circle', 'line'));
  30393. registerLayout(layoutPoints('line'));
  30394. // Down sample after filter
  30395. registerProcessor(
  30396. PRIORITY.PROCESSOR.STATISTIC,
  30397. dataSample('line')
  30398. );
  30399. var BaseBarSeries = SeriesModel.extend({
  30400. type: 'series.__base_bar__',
  30401. getInitialData: function (option, ecModel) {
  30402. return createListFromArray(this.getSource(), this);
  30403. },
  30404. getMarkerPosition: function (value) {
  30405. var coordSys = this.coordinateSystem;
  30406. if (coordSys) {
  30407. // PENDING if clamp ?
  30408. var pt = coordSys.dataToPoint(coordSys.clampData(value));
  30409. var data = this.getData();
  30410. var offset = data.getLayout('offset');
  30411. var size = data.getLayout('size');
  30412. var offsetIndex = coordSys.getBaseAxis().isHorizontal() ? 0 : 1;
  30413. pt[offsetIndex] += offset + size / 2;
  30414. return pt;
  30415. }
  30416. return [NaN, NaN];
  30417. },
  30418. defaultOption: {
  30419. zlevel: 0, // 一级层叠
  30420. z: 2, // 二级层叠
  30421. coordinateSystem: 'cartesian2d',
  30422. legendHoverLink: true,
  30423. // stack: null
  30424. // Cartesian coordinate system
  30425. // xAxisIndex: 0,
  30426. // yAxisIndex: 0,
  30427. // 最小高度改为0
  30428. barMinHeight: 0,
  30429. // 最小角度为0,仅对极坐标系下的柱状图有效
  30430. barMinAngle: 0,
  30431. // cursor: null,
  30432. // barMaxWidth: null,
  30433. // 默认自适应
  30434. // barWidth: null,
  30435. // 柱间距离,默认为柱形宽度的30%,可设固定值
  30436. // barGap: '30%',
  30437. // 类目间柱形距离,默认为类目间距的20%,可设固定值
  30438. // barCategoryGap: '20%',
  30439. // label: {
  30440. // show: false
  30441. // },
  30442. itemStyle: {},
  30443. emphasis: {}
  30444. }
  30445. });
  30446. BaseBarSeries.extend({
  30447. type: 'series.bar',
  30448. dependencies: ['grid', 'polar'],
  30449. brushSelector: 'rect'
  30450. });
  30451. function setLabel(
  30452. normalStyle, hoverStyle, itemModel, color, seriesModel, dataIndex, labelPositionOutside
  30453. ) {
  30454. var labelModel = itemModel.getModel('label');
  30455. var hoverLabelModel = itemModel.getModel('emphasis.label');
  30456. setLabelStyle(
  30457. normalStyle, hoverStyle, labelModel, hoverLabelModel,
  30458. {
  30459. labelFetcher: seriesModel,
  30460. labelDataIndex: dataIndex,
  30461. defaultText: getDefaultLabel(seriesModel.getData(), dataIndex),
  30462. isRectText: true,
  30463. autoColor: color
  30464. }
  30465. );
  30466. fixPosition(normalStyle);
  30467. fixPosition(hoverStyle);
  30468. }
  30469. function fixPosition(style, labelPositionOutside) {
  30470. if (style.textPosition === 'outside') {
  30471. style.textPosition = labelPositionOutside;
  30472. }
  30473. }
  30474. var getBarItemStyle = makeStyleMapper(
  30475. [
  30476. ['fill', 'color'],
  30477. ['stroke', 'borderColor'],
  30478. ['lineWidth', 'borderWidth'],
  30479. // Compatitable with 2
  30480. ['stroke', 'barBorderColor'],
  30481. ['lineWidth', 'barBorderWidth'],
  30482. ['opacity'],
  30483. ['shadowBlur'],
  30484. ['shadowOffsetX'],
  30485. ['shadowOffsetY'],
  30486. ['shadowColor']
  30487. ]
  30488. );
  30489. var barItemStyle = {
  30490. getBarItemStyle: function (excludes) {
  30491. var style = getBarItemStyle(this, excludes);
  30492. if (this.getBorderLineDash) {
  30493. var lineDash = this.getBorderLineDash();
  30494. lineDash && (style.lineDash = lineDash);
  30495. }
  30496. return style;
  30497. }
  30498. };
  30499. var BAR_BORDER_WIDTH_QUERY = ['itemStyle', 'barBorderWidth'];
  30500. // FIXME
  30501. // Just for compatible with ec2.
  30502. extend(Model.prototype, barItemStyle);
  30503. extendChartView({
  30504. type: 'bar',
  30505. render: function (seriesModel, ecModel, api) {
  30506. var coordinateSystemType = seriesModel.get('coordinateSystem');
  30507. if (coordinateSystemType === 'cartesian2d'
  30508. || coordinateSystemType === 'polar'
  30509. ) {
  30510. this._render(seriesModel, ecModel, api);
  30511. }
  30512. else if (__DEV__) {
  30513. console.warn('Only cartesian2d and polar supported for bar.');
  30514. }
  30515. return this.group;
  30516. },
  30517. dispose: noop,
  30518. _render: function (seriesModel, ecModel, api) {
  30519. var group = this.group;
  30520. var data = seriesModel.getData();
  30521. var oldData = this._data;
  30522. var coord = seriesModel.coordinateSystem;
  30523. var baseAxis = coord.getBaseAxis();
  30524. var isHorizontalOrRadial;
  30525. if (coord.type === 'cartesian2d') {
  30526. isHorizontalOrRadial = baseAxis.isHorizontal();
  30527. }
  30528. else if (coord.type === 'polar') {
  30529. isHorizontalOrRadial = baseAxis.dim === 'angle';
  30530. }
  30531. var animationModel = seriesModel.isAnimationEnabled() ? seriesModel : null;
  30532. data.diff(oldData)
  30533. .add(function (dataIndex) {
  30534. if (!data.hasValue(dataIndex)) {
  30535. return;
  30536. }
  30537. var itemModel = data.getItemModel(dataIndex);
  30538. var layout = getLayout[coord.type](data, dataIndex, itemModel);
  30539. var el = elementCreator[coord.type](
  30540. data, dataIndex, itemModel, layout, isHorizontalOrRadial, animationModel
  30541. );
  30542. data.setItemGraphicEl(dataIndex, el);
  30543. group.add(el);
  30544. updateStyle(
  30545. el, data, dataIndex, itemModel, layout,
  30546. seriesModel, isHorizontalOrRadial, coord.type === 'polar'
  30547. );
  30548. })
  30549. .update(function (newIndex, oldIndex) {
  30550. var el = oldData.getItemGraphicEl(oldIndex);
  30551. if (!data.hasValue(newIndex)) {
  30552. group.remove(el);
  30553. return;
  30554. }
  30555. var itemModel = data.getItemModel(newIndex);
  30556. var layout = getLayout[coord.type](data, newIndex, itemModel);
  30557. if (el) {
  30558. updateProps(el, {shape: layout}, animationModel, newIndex);
  30559. }
  30560. else {
  30561. el = elementCreator[coord.type](
  30562. data, newIndex, itemModel, layout, isHorizontalOrRadial, animationModel, true
  30563. );
  30564. }
  30565. data.setItemGraphicEl(newIndex, el);
  30566. // Add back
  30567. group.add(el);
  30568. updateStyle(
  30569. el, data, newIndex, itemModel, layout,
  30570. seriesModel, isHorizontalOrRadial, coord.type === 'polar'
  30571. );
  30572. })
  30573. .remove(function (dataIndex) {
  30574. var el = oldData.getItemGraphicEl(dataIndex);
  30575. if (coord.type === 'cartesian2d') {
  30576. el && removeRect(dataIndex, animationModel, el);
  30577. }
  30578. else {
  30579. el && removeSector(dataIndex, animationModel, el);
  30580. }
  30581. })
  30582. .execute();
  30583. this._data = data;
  30584. },
  30585. remove: function (ecModel, api) {
  30586. var group = this.group;
  30587. var data = this._data;
  30588. if (ecModel.get('animation')) {
  30589. if (data) {
  30590. data.eachItemGraphicEl(function (el) {
  30591. if (el.type === 'sector') {
  30592. removeSector(el.dataIndex, ecModel, el);
  30593. }
  30594. else {
  30595. removeRect(el.dataIndex, ecModel, el);
  30596. }
  30597. });
  30598. }
  30599. }
  30600. else {
  30601. group.removeAll();
  30602. }
  30603. }
  30604. });
  30605. var elementCreator = {
  30606. cartesian2d: function (
  30607. data, dataIndex, itemModel, layout, isHorizontal,
  30608. animationModel, isUpdate
  30609. ) {
  30610. var rect = new Rect({shape: extend({}, layout)});
  30611. // Animation
  30612. if (animationModel) {
  30613. var rectShape = rect.shape;
  30614. var animateProperty = isHorizontal ? 'height' : 'width';
  30615. var animateTarget = {};
  30616. rectShape[animateProperty] = 0;
  30617. animateTarget[animateProperty] = layout[animateProperty];
  30618. graphic[isUpdate ? 'updateProps' : 'initProps'](rect, {
  30619. shape: animateTarget
  30620. }, animationModel, dataIndex);
  30621. }
  30622. return rect;
  30623. },
  30624. polar: function (
  30625. data, dataIndex, itemModel, layout, isRadial,
  30626. animationModel, isUpdate
  30627. ) {
  30628. // Keep the same logic with bar in catesion: use end value to control
  30629. // direction. Notice that if clockwise is true (by default), the sector
  30630. // will always draw clockwisely, no matter whether endAngle is greater
  30631. // or less than startAngle.
  30632. var clockwise = layout.startAngle < layout.endAngle;
  30633. var sector = new Sector({
  30634. shape: defaults({clockwise: clockwise}, layout)
  30635. });
  30636. // Animation
  30637. if (animationModel) {
  30638. var sectorShape = sector.shape;
  30639. var animateProperty = isRadial ? 'r' : 'endAngle';
  30640. var animateTarget = {};
  30641. sectorShape[animateProperty] = isRadial ? 0 : layout.startAngle;
  30642. animateTarget[animateProperty] = layout[animateProperty];
  30643. graphic[isUpdate ? 'updateProps' : 'initProps'](sector, {
  30644. shape: animateTarget
  30645. }, animationModel, dataIndex);
  30646. }
  30647. return sector;
  30648. }
  30649. };
  30650. function removeRect(dataIndex, animationModel, el) {
  30651. // Not show text when animating
  30652. el.style.text = null;
  30653. updateProps(el, {
  30654. shape: {
  30655. width: 0
  30656. }
  30657. }, animationModel, dataIndex, function () {
  30658. el.parent && el.parent.remove(el);
  30659. });
  30660. }
  30661. function removeSector(dataIndex, animationModel, el) {
  30662. // Not show text when animating
  30663. el.style.text = null;
  30664. updateProps(el, {
  30665. shape: {
  30666. r: el.shape.r0
  30667. }
  30668. }, animationModel, dataIndex, function () {
  30669. el.parent && el.parent.remove(el);
  30670. });
  30671. }
  30672. var getLayout = {
  30673. cartesian2d: function (data, dataIndex, itemModel) {
  30674. var layout = data.getItemLayout(dataIndex);
  30675. var fixedLineWidth = getLineWidth(itemModel, layout);
  30676. // fix layout with lineWidth
  30677. var signX = layout.width > 0 ? 1 : -1;
  30678. var signY = layout.height > 0 ? 1 : -1;
  30679. return {
  30680. x: layout.x + signX * fixedLineWidth / 2,
  30681. y: layout.y + signY * fixedLineWidth / 2,
  30682. width: layout.width - signX * fixedLineWidth,
  30683. height: layout.height - signY * fixedLineWidth
  30684. };
  30685. },
  30686. polar: function (data, dataIndex, itemModel) {
  30687. var layout = data.getItemLayout(dataIndex);
  30688. return {
  30689. cx: layout.cx,
  30690. cy: layout.cy,
  30691. r0: layout.r0,
  30692. r: layout.r,
  30693. startAngle: layout.startAngle,
  30694. endAngle: layout.endAngle
  30695. };
  30696. }
  30697. };
  30698. function updateStyle(
  30699. el, data, dataIndex, itemModel, layout, seriesModel, isHorizontal, isPolar
  30700. ) {
  30701. var color = data.getItemVisual(dataIndex, 'color');
  30702. var opacity = data.getItemVisual(dataIndex, 'opacity');
  30703. var itemStyleModel = itemModel.getModel('itemStyle');
  30704. var hoverStyle = itemModel.getModel('emphasis.itemStyle').getBarItemStyle();
  30705. if (!isPolar) {
  30706. el.setShape('r', itemStyleModel.get('barBorderRadius') || 0);
  30707. }
  30708. el.useStyle(defaults(
  30709. {
  30710. fill: color,
  30711. opacity: opacity
  30712. },
  30713. itemStyleModel.getBarItemStyle()
  30714. ));
  30715. var cursorStyle = itemModel.getShallow('cursor');
  30716. cursorStyle && el.attr('cursor', cursorStyle);
  30717. var labelPositionOutside = isHorizontal
  30718. ? (layout.height > 0 ? 'bottom' : 'top')
  30719. : (layout.width > 0 ? 'left' : 'right');
  30720. if (!isPolar) {
  30721. setLabel(
  30722. el.style, hoverStyle, itemModel, color,
  30723. seriesModel, dataIndex, labelPositionOutside
  30724. );
  30725. }
  30726. setHoverStyle(el, hoverStyle);
  30727. }
  30728. // In case width or height are too small.
  30729. function getLineWidth(itemModel, rawLayout) {
  30730. var lineWidth = itemModel.get(BAR_BORDER_WIDTH_QUERY) || 0;
  30731. return Math.min(lineWidth, Math.abs(rawLayout.width), Math.abs(rawLayout.height));
  30732. }
  30733. // In case developer forget to include grid component
  30734. registerLayout(curry(layout, 'bar'));
  30735. // Visual coding for legend
  30736. registerVisual(function (ecModel) {
  30737. ecModel.eachSeriesByType('bar', function (seriesModel) {
  30738. var data = seriesModel.getData();
  30739. data.setVisual('legendSymbol', 'roundRect');
  30740. });
  30741. });
  30742. /**
  30743. * [Usage]:
  30744. * (1)
  30745. * createListSimply(seriesModel, ['value']);
  30746. * (2)
  30747. * createListSimply(seriesModel, {
  30748. * coordDimensions: ['value'],
  30749. * dimensionsCount: 5
  30750. * });
  30751. *
  30752. * @param {module:echarts/model/Series} seriesModel
  30753. * @param {Object|Array.<string|Object>} opt opt or coordDimensions
  30754. * The options in opt, see `echarts/data/helper/createDimensions`
  30755. * @param {Array.<string>} [nameList]
  30756. * @return {module:echarts/data/List}
  30757. */
  30758. var createListSimply = function (seriesModel, opt, nameList) {
  30759. opt = isArray(opt) && {coordDimensions: opt} || extend({}, opt);
  30760. var source = seriesModel.getSource();
  30761. var dimensionsInfo = createDimensions(source, opt);
  30762. var list = new List(dimensionsInfo, seriesModel);
  30763. list.initData(source, nameList);
  30764. return list;
  30765. };
  30766. /**
  30767. * Data selectable mixin for chart series.
  30768. * To eanble data select, option of series must have `selectedMode`.
  30769. * And each data item will use `selected` to toggle itself selected status
  30770. */
  30771. var dataSelectableMixin = {
  30772. /**
  30773. * @param {Array.<Object>} targetList [{name, value, selected}, ...]
  30774. * If targetList is an array, it should like [{name: ..., value: ...}, ...].
  30775. * If targetList is a "List", it must have coordDim: 'value' dimension and name.
  30776. */
  30777. updateSelectedMap: function (targetList) {
  30778. this._targetList = isArray(targetList) ? targetList.slice() : [];
  30779. this._selectTargetMap = reduce(targetList || [], function (targetMap, target) {
  30780. targetMap.set(target.name, target);
  30781. return targetMap;
  30782. }, createHashMap());
  30783. },
  30784. /**
  30785. * Either name or id should be passed as input here.
  30786. * If both of them are defined, id is used.
  30787. *
  30788. * @param {string|undefined} name name of data
  30789. * @param {number|undefined} id dataIndex of data
  30790. */
  30791. // PENGING If selectedMode is null ?
  30792. select: function (name, id) {
  30793. var target = id != null
  30794. ? this._targetList[id]
  30795. : this._selectTargetMap.get(name);
  30796. var selectedMode = this.get('selectedMode');
  30797. if (selectedMode === 'single') {
  30798. this._selectTargetMap.each(function (target) {
  30799. target.selected = false;
  30800. });
  30801. }
  30802. target && (target.selected = true);
  30803. },
  30804. /**
  30805. * Either name or id should be passed as input here.
  30806. * If both of them are defined, id is used.
  30807. *
  30808. * @param {string|undefined} name name of data
  30809. * @param {number|undefined} id dataIndex of data
  30810. */
  30811. unSelect: function (name, id) {
  30812. var target = id != null
  30813. ? this._targetList[id]
  30814. : this._selectTargetMap.get(name);
  30815. // var selectedMode = this.get('selectedMode');
  30816. // selectedMode !== 'single' && target && (target.selected = false);
  30817. target && (target.selected = false);
  30818. },
  30819. /**
  30820. * Either name or id should be passed as input here.
  30821. * If both of them are defined, id is used.
  30822. *
  30823. * @param {string|undefined} name name of data
  30824. * @param {number|undefined} id dataIndex of data
  30825. */
  30826. toggleSelected: function (name, id) {
  30827. var target = id != null
  30828. ? this._targetList[id]
  30829. : this._selectTargetMap.get(name);
  30830. if (target != null) {
  30831. this[target.selected ? 'unSelect' : 'select'](name, id);
  30832. return target.selected;
  30833. }
  30834. },
  30835. /**
  30836. * Either name or id should be passed as input here.
  30837. * If both of them are defined, id is used.
  30838. *
  30839. * @param {string|undefined} name name of data
  30840. * @param {number|undefined} id dataIndex of data
  30841. */
  30842. isSelected: function (name, id) {
  30843. var target = id != null
  30844. ? this._targetList[id]
  30845. : this._selectTargetMap.get(name);
  30846. return target && target.selected;
  30847. }
  30848. };
  30849. var PieSeries = extendSeriesModel({
  30850. type: 'series.pie',
  30851. // Overwrite
  30852. init: function (option) {
  30853. PieSeries.superApply(this, 'init', arguments);
  30854. // Enable legend selection for each data item
  30855. // Use a function instead of direct access because data reference may changed
  30856. this.legendDataProvider = function () {
  30857. return this.getRawData();
  30858. };
  30859. this.updateSelectedMap(this._createSelectableList());
  30860. this._defaultLabelLine(option);
  30861. },
  30862. // Overwrite
  30863. mergeOption: function (newOption) {
  30864. PieSeries.superCall(this, 'mergeOption', newOption);
  30865. this.updateSelectedMap(this._createSelectableList());
  30866. },
  30867. getInitialData: function (option, ecModel) {
  30868. return createListSimply(this, ['value']);
  30869. },
  30870. _createSelectableList: function () {
  30871. var data = this.getRawData();
  30872. var valueDim = data.mapDimension('value');
  30873. var targetList = [];
  30874. for (var i = 0, len = data.count(); i < len; i++) {
  30875. targetList.push({
  30876. name: data.getName(i),
  30877. value: data.get(valueDim, i),
  30878. selected: retrieveRawAttr(data, i, 'selected')
  30879. });
  30880. }
  30881. return targetList;
  30882. },
  30883. // Overwrite
  30884. getDataParams: function (dataIndex) {
  30885. var data = this.getData();
  30886. var params = PieSeries.superCall(this, 'getDataParams', dataIndex);
  30887. // FIXME toFixed?
  30888. var valueList = [];
  30889. data.each(data.mapDimension('value'), function (value) {
  30890. valueList.push(value);
  30891. });
  30892. params.percent = getPercentWithPrecision(
  30893. valueList,
  30894. dataIndex,
  30895. data.hostModel.get('percentPrecision')
  30896. );
  30897. params.$vars.push('percent');
  30898. return params;
  30899. },
  30900. _defaultLabelLine: function (option) {
  30901. // Extend labelLine emphasis
  30902. defaultEmphasis(option, 'labelLine', ['show']);
  30903. var labelLineNormalOpt = option.labelLine;
  30904. var labelLineEmphasisOpt = option.emphasis.labelLine;
  30905. // Not show label line if `label.normal.show = false`
  30906. labelLineNormalOpt.show = labelLineNormalOpt.show
  30907. && option.label.show;
  30908. labelLineEmphasisOpt.show = labelLineEmphasisOpt.show
  30909. && option.emphasis.label.show;
  30910. },
  30911. defaultOption: {
  30912. zlevel: 0,
  30913. z: 2,
  30914. legendHoverLink: true,
  30915. hoverAnimation: true,
  30916. // 默认全局居中
  30917. center: ['50%', '50%'],
  30918. radius: [0, '75%'],
  30919. // 默认顺时针
  30920. clockwise: true,
  30921. startAngle: 90,
  30922. // 最小角度改为0
  30923. minAngle: 0,
  30924. // 选中时扇区偏移量
  30925. selectedOffset: 10,
  30926. // 高亮扇区偏移量
  30927. hoverOffset: 10,
  30928. // If use strategy to avoid label overlapping
  30929. avoidLabelOverlap: true,
  30930. // 选择模式,默认关闭,可选single,multiple
  30931. // selectedMode: false,
  30932. // 南丁格尔玫瑰图模式,'radius'(半径) | 'area'(面积)
  30933. // roseType: null,
  30934. percentPrecision: 2,
  30935. // If still show when all data zero.
  30936. stillShowZeroSum: true,
  30937. // cursor: null,
  30938. label: {
  30939. // If rotate around circle
  30940. rotate: false,
  30941. show: true,
  30942. // 'outer', 'inside', 'center'
  30943. position: 'outer'
  30944. // formatter: 标签文本格式器,同Tooltip.formatter,不支持异步回调
  30945. // 默认使用全局文本样式,详见TEXTSTYLE
  30946. // distance: 当position为inner时有效,为label位置到圆心的距离与圆半径(环状图为内外半径和)的比例系数
  30947. },
  30948. // Enabled when label.normal.position is 'outer'
  30949. labelLine: {
  30950. show: true,
  30951. // 引导线两段中的第一段长度
  30952. length: 15,
  30953. // 引导线两段中的第二段长度
  30954. length2: 15,
  30955. smooth: false,
  30956. lineStyle: {
  30957. // color: 各异,
  30958. width: 1,
  30959. type: 'solid'
  30960. }
  30961. },
  30962. itemStyle: {
  30963. borderWidth: 1
  30964. },
  30965. // Animation type canbe expansion, scale
  30966. animationType: 'expansion',
  30967. animationEasing: 'cubicOut'
  30968. }
  30969. });
  30970. mixin(PieSeries, dataSelectableMixin);
  30971. /**
  30972. * @param {module:echarts/model/Series} seriesModel
  30973. * @param {boolean} hasAnimation
  30974. * @inner
  30975. */
  30976. function updateDataSelected(uid, seriesModel, hasAnimation, api) {
  30977. var data = seriesModel.getData();
  30978. var dataIndex = this.dataIndex;
  30979. var name = data.getName(dataIndex);
  30980. var selectedOffset = seriesModel.get('selectedOffset');
  30981. api.dispatchAction({
  30982. type: 'pieToggleSelect',
  30983. from: uid,
  30984. name: name,
  30985. seriesId: seriesModel.id
  30986. });
  30987. data.each(function (idx) {
  30988. toggleItemSelected(
  30989. data.getItemGraphicEl(idx),
  30990. data.getItemLayout(idx),
  30991. seriesModel.isSelected(data.getName(idx)),
  30992. selectedOffset,
  30993. hasAnimation
  30994. );
  30995. });
  30996. }
  30997. /**
  30998. * @param {module:zrender/graphic/Sector} el
  30999. * @param {Object} layout
  31000. * @param {boolean} isSelected
  31001. * @param {number} selectedOffset
  31002. * @param {boolean} hasAnimation
  31003. * @inner
  31004. */
  31005. function toggleItemSelected(el, layout, isSelected, selectedOffset, hasAnimation) {
  31006. var midAngle = (layout.startAngle + layout.endAngle) / 2;
  31007. var dx = Math.cos(midAngle);
  31008. var dy = Math.sin(midAngle);
  31009. var offset = isSelected ? selectedOffset : 0;
  31010. var position = [dx * offset, dy * offset];
  31011. hasAnimation
  31012. // animateTo will stop revious animation like update transition
  31013. ? el.animate()
  31014. .when(200, {
  31015. position: position
  31016. })
  31017. .start('bounceOut')
  31018. : el.attr('position', position);
  31019. }
  31020. /**
  31021. * Piece of pie including Sector, Label, LabelLine
  31022. * @constructor
  31023. * @extends {module:zrender/graphic/Group}
  31024. */
  31025. function PiePiece(data, idx) {
  31026. Group.call(this);
  31027. var sector = new Sector({
  31028. z2: 2
  31029. });
  31030. var polyline = new Polyline();
  31031. var text = new Text();
  31032. this.add(sector);
  31033. this.add(polyline);
  31034. this.add(text);
  31035. this.updateData(data, idx, true);
  31036. // Hover to change label and labelLine
  31037. function onEmphasis() {
  31038. polyline.ignore = polyline.hoverIgnore;
  31039. text.ignore = text.hoverIgnore;
  31040. }
  31041. function onNormal() {
  31042. polyline.ignore = polyline.normalIgnore;
  31043. text.ignore = text.normalIgnore;
  31044. }
  31045. this.on('emphasis', onEmphasis)
  31046. .on('normal', onNormal)
  31047. .on('mouseover', onEmphasis)
  31048. .on('mouseout', onNormal);
  31049. }
  31050. var piePieceProto = PiePiece.prototype;
  31051. piePieceProto.updateData = function (data, idx, firstCreate) {
  31052. var sector = this.childAt(0);
  31053. var seriesModel = data.hostModel;
  31054. var itemModel = data.getItemModel(idx);
  31055. var layout = data.getItemLayout(idx);
  31056. var sectorShape = extend({}, layout);
  31057. sectorShape.label = null;
  31058. if (firstCreate) {
  31059. sector.setShape(sectorShape);
  31060. var animationType = seriesModel.getShallow('animationType');
  31061. if (animationType === 'scale') {
  31062. sector.shape.r = layout.r0;
  31063. initProps(sector, {
  31064. shape: {
  31065. r: layout.r
  31066. }
  31067. }, seriesModel, idx);
  31068. }
  31069. // Expansion
  31070. else {
  31071. sector.shape.endAngle = layout.startAngle;
  31072. updateProps(sector, {
  31073. shape: {
  31074. endAngle: layout.endAngle
  31075. }
  31076. }, seriesModel, idx);
  31077. }
  31078. }
  31079. else {
  31080. updateProps(sector, {
  31081. shape: sectorShape
  31082. }, seriesModel, idx);
  31083. }
  31084. // Update common style
  31085. var visualColor = data.getItemVisual(idx, 'color');
  31086. sector.useStyle(
  31087. defaults(
  31088. {
  31089. lineJoin: 'bevel',
  31090. fill: visualColor
  31091. },
  31092. itemModel.getModel('itemStyle').getItemStyle()
  31093. )
  31094. );
  31095. sector.hoverStyle = itemModel.getModel('emphasis.itemStyle').getItemStyle();
  31096. var cursorStyle = itemModel.getShallow('cursor');
  31097. cursorStyle && sector.attr('cursor', cursorStyle);
  31098. // Toggle selected
  31099. toggleItemSelected(
  31100. this,
  31101. data.getItemLayout(idx),
  31102. seriesModel.isSelected(null, idx),
  31103. seriesModel.get('selectedOffset'),
  31104. seriesModel.get('animation')
  31105. );
  31106. function onEmphasis() {
  31107. // Sector may has animation of updating data. Force to move to the last frame
  31108. // Or it may stopped on the wrong shape
  31109. sector.stopAnimation(true);
  31110. sector.animateTo({
  31111. shape: {
  31112. r: layout.r + seriesModel.get('hoverOffset')
  31113. }
  31114. }, 300, 'elasticOut');
  31115. }
  31116. function onNormal() {
  31117. sector.stopAnimation(true);
  31118. sector.animateTo({
  31119. shape: {
  31120. r: layout.r
  31121. }
  31122. }, 300, 'elasticOut');
  31123. }
  31124. sector.off('mouseover').off('mouseout').off('emphasis').off('normal');
  31125. if (itemModel.get('hoverAnimation') && seriesModel.isAnimationEnabled()) {
  31126. sector
  31127. .on('mouseover', onEmphasis)
  31128. .on('mouseout', onNormal)
  31129. .on('emphasis', onEmphasis)
  31130. .on('normal', onNormal);
  31131. }
  31132. this._updateLabel(data, idx);
  31133. setHoverStyle(this);
  31134. };
  31135. piePieceProto._updateLabel = function (data, idx) {
  31136. var labelLine = this.childAt(1);
  31137. var labelText = this.childAt(2);
  31138. var seriesModel = data.hostModel;
  31139. var itemModel = data.getItemModel(idx);
  31140. var layout = data.getItemLayout(idx);
  31141. var labelLayout = layout.label;
  31142. var visualColor = data.getItemVisual(idx, 'color');
  31143. updateProps(labelLine, {
  31144. shape: {
  31145. points: labelLayout.linePoints || [
  31146. [labelLayout.x, labelLayout.y], [labelLayout.x, labelLayout.y], [labelLayout.x, labelLayout.y]
  31147. ]
  31148. }
  31149. }, seriesModel, idx);
  31150. updateProps(labelText, {
  31151. style: {
  31152. x: labelLayout.x,
  31153. y: labelLayout.y
  31154. }
  31155. }, seriesModel, idx);
  31156. labelText.attr({
  31157. rotation: labelLayout.rotation,
  31158. origin: [labelLayout.x, labelLayout.y],
  31159. z2: 10
  31160. });
  31161. var labelModel = itemModel.getModel('label');
  31162. var labelHoverModel = itemModel.getModel('emphasis.label');
  31163. var labelLineModel = itemModel.getModel('labelLine');
  31164. var labelLineHoverModel = itemModel.getModel('emphasis.labelLine');
  31165. var visualColor = data.getItemVisual(idx, 'color');
  31166. setLabelStyle(
  31167. labelText.style, labelText.hoverStyle = {}, labelModel, labelHoverModel,
  31168. {
  31169. labelFetcher: data.hostModel,
  31170. labelDataIndex: idx,
  31171. defaultText: data.getName(idx),
  31172. autoColor: visualColor,
  31173. useInsideStyle: !!labelLayout.inside
  31174. },
  31175. {
  31176. textAlign: labelLayout.textAlign,
  31177. textVerticalAlign: labelLayout.verticalAlign,
  31178. opacity: data.getItemVisual(idx, 'opacity')
  31179. }
  31180. );
  31181. labelText.ignore = labelText.normalIgnore = !labelModel.get('show');
  31182. labelText.hoverIgnore = !labelHoverModel.get('show');
  31183. labelLine.ignore = labelLine.normalIgnore = !labelLineModel.get('show');
  31184. labelLine.hoverIgnore = !labelLineHoverModel.get('show');
  31185. // Default use item visual color
  31186. labelLine.setStyle({
  31187. stroke: visualColor,
  31188. opacity: data.getItemVisual(idx, 'opacity')
  31189. });
  31190. labelLine.setStyle(labelLineModel.getModel('lineStyle').getLineStyle());
  31191. labelLine.hoverStyle = labelLineHoverModel.getModel('lineStyle').getLineStyle();
  31192. var smooth = labelLineModel.get('smooth');
  31193. if (smooth && smooth === true) {
  31194. smooth = 0.4;
  31195. }
  31196. labelLine.setShape({
  31197. smooth: smooth
  31198. });
  31199. };
  31200. inherits(PiePiece, Group);
  31201. // Pie view
  31202. var PieView = Chart.extend({
  31203. type: 'pie',
  31204. init: function () {
  31205. var sectorGroup = new Group();
  31206. this._sectorGroup = sectorGroup;
  31207. },
  31208. render: function (seriesModel, ecModel, api, payload) {
  31209. if (payload && (payload.from === this.uid)) {
  31210. return;
  31211. }
  31212. var data = seriesModel.getData();
  31213. var oldData = this._data;
  31214. var group = this.group;
  31215. var hasAnimation = ecModel.get('animation');
  31216. var isFirstRender = !oldData;
  31217. var animationType = seriesModel.get('animationType');
  31218. var onSectorClick = curry(
  31219. updateDataSelected, this.uid, seriesModel, hasAnimation, api
  31220. );
  31221. var selectedMode = seriesModel.get('selectedMode');
  31222. data.diff(oldData)
  31223. .add(function (idx) {
  31224. var piePiece = new PiePiece(data, idx);
  31225. // Default expansion animation
  31226. if (isFirstRender && animationType !== 'scale') {
  31227. piePiece.eachChild(function (child) {
  31228. child.stopAnimation(true);
  31229. });
  31230. }
  31231. selectedMode && piePiece.on('click', onSectorClick);
  31232. data.setItemGraphicEl(idx, piePiece);
  31233. group.add(piePiece);
  31234. })
  31235. .update(function (newIdx, oldIdx) {
  31236. var piePiece = oldData.getItemGraphicEl(oldIdx);
  31237. piePiece.updateData(data, newIdx);
  31238. piePiece.off('click');
  31239. selectedMode && piePiece.on('click', onSectorClick);
  31240. group.add(piePiece);
  31241. data.setItemGraphicEl(newIdx, piePiece);
  31242. })
  31243. .remove(function (idx) {
  31244. var piePiece = oldData.getItemGraphicEl(idx);
  31245. group.remove(piePiece);
  31246. })
  31247. .execute();
  31248. if (
  31249. hasAnimation && isFirstRender && data.count() > 0
  31250. // Default expansion animation
  31251. && animationType !== 'scale'
  31252. ) {
  31253. var shape = data.getItemLayout(0);
  31254. var r = Math.max(api.getWidth(), api.getHeight()) / 2;
  31255. var removeClipPath = bind(group.removeClipPath, group);
  31256. group.setClipPath(this._createClipPath(
  31257. shape.cx, shape.cy, r, shape.startAngle, shape.clockwise, removeClipPath, seriesModel
  31258. ));
  31259. }
  31260. this._data = data;
  31261. },
  31262. dispose: function () {},
  31263. _createClipPath: function (
  31264. cx, cy, r, startAngle, clockwise, cb, seriesModel
  31265. ) {
  31266. var clipPath = new Sector({
  31267. shape: {
  31268. cx: cx,
  31269. cy: cy,
  31270. r0: 0,
  31271. r: r,
  31272. startAngle: startAngle,
  31273. endAngle: startAngle,
  31274. clockwise: clockwise
  31275. }
  31276. });
  31277. initProps(clipPath, {
  31278. shape: {
  31279. endAngle: startAngle + (clockwise ? 1 : -1) * Math.PI * 2
  31280. }
  31281. }, seriesModel, cb);
  31282. return clipPath;
  31283. },
  31284. /**
  31285. * @implement
  31286. */
  31287. containPoint: function (point, seriesModel) {
  31288. var data = seriesModel.getData();
  31289. var itemLayout = data.getItemLayout(0);
  31290. if (itemLayout) {
  31291. var dx = point[0] - itemLayout.cx;
  31292. var dy = point[1] - itemLayout.cy;
  31293. var radius = Math.sqrt(dx * dx + dy * dy);
  31294. return radius <= itemLayout.r && radius >= itemLayout.r0;
  31295. }
  31296. }
  31297. });
  31298. var createDataSelectAction = function (seriesType, actionInfos) {
  31299. each$1(actionInfos, function (actionInfo) {
  31300. actionInfo.update = 'updateView';
  31301. /**
  31302. * @payload
  31303. * @property {string} seriesName
  31304. * @property {string} name
  31305. */
  31306. registerAction(actionInfo, function (payload, ecModel) {
  31307. var selected = {};
  31308. ecModel.eachComponent(
  31309. {mainType: 'series', subType: seriesType, query: payload},
  31310. function (seriesModel) {
  31311. if (seriesModel[actionInfo.method]) {
  31312. seriesModel[actionInfo.method](
  31313. payload.name,
  31314. payload.dataIndex
  31315. );
  31316. }
  31317. var data = seriesModel.getData();
  31318. // Create selected map
  31319. data.each(function (idx) {
  31320. var name = data.getName(idx);
  31321. selected[name] = seriesModel.isSelected(name)
  31322. || false;
  31323. });
  31324. }
  31325. );
  31326. return {
  31327. name: payload.name,
  31328. selected: selected
  31329. };
  31330. });
  31331. });
  31332. };
  31333. // Pick color from palette for each data item.
  31334. // Applicable for charts that require applying color palette
  31335. // in data level (like pie, funnel, chord).
  31336. var dataColor = function (seriesType) {
  31337. return {
  31338. getTargetSeries: function (ecModel) {
  31339. // Pie and funnel may use diferrent scope
  31340. var paletteScope = {};
  31341. var seiresModelMap = createHashMap();
  31342. ecModel.eachSeriesByType(seriesType, function (seriesModel) {
  31343. seriesModel.__paletteScope = paletteScope;
  31344. seiresModelMap.set(seriesModel.uid, seriesModel);
  31345. });
  31346. return seiresModelMap;
  31347. },
  31348. reset: function (seriesModel, ecModel) {
  31349. var dataAll = seriesModel.getRawData();
  31350. var idxMap = {};
  31351. var data = seriesModel.getData();
  31352. data.each(function (idx) {
  31353. var rawIdx = data.getRawIndex(idx);
  31354. idxMap[rawIdx] = idx;
  31355. });
  31356. dataAll.each(function (rawIdx) {
  31357. var filteredIdx = idxMap[rawIdx];
  31358. // If series.itemStyle.normal.color is a function. itemVisual may be encoded
  31359. var singleDataColor = filteredIdx != null
  31360. && data.getItemVisual(filteredIdx, 'color', true);
  31361. if (!singleDataColor) {
  31362. // FIXME Performance
  31363. var itemModel = dataAll.getItemModel(rawIdx);
  31364. var color = itemModel.get('itemStyle.color')
  31365. || seriesModel.getColorFromPalette(
  31366. dataAll.getName(rawIdx) || (rawIdx + ''), seriesModel.__paletteScope,
  31367. dataAll.count()
  31368. );
  31369. // Legend may use the visual info in data before processed
  31370. dataAll.setItemVisual(rawIdx, 'color', color);
  31371. // Data is not filtered
  31372. if (filteredIdx != null) {
  31373. data.setItemVisual(filteredIdx, 'color', color);
  31374. }
  31375. }
  31376. else {
  31377. // Set data all color for legend
  31378. dataAll.setItemVisual(rawIdx, 'color', singleDataColor);
  31379. }
  31380. });
  31381. }
  31382. };
  31383. };
  31384. // FIXME emphasis label position is not same with normal label position
  31385. function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight) {
  31386. list.sort(function (a, b) {
  31387. return a.y - b.y;
  31388. });
  31389. // 压
  31390. function shiftDown(start, end, delta, dir) {
  31391. for (var j = start; j < end; j++) {
  31392. list[j].y += delta;
  31393. if (j > start
  31394. && j + 1 < end
  31395. && list[j + 1].y > list[j].y + list[j].height
  31396. ) {
  31397. shiftUp(j, delta / 2);
  31398. return;
  31399. }
  31400. }
  31401. shiftUp(end - 1, delta / 2);
  31402. }
  31403. // 弹
  31404. function shiftUp(end, delta) {
  31405. for (var j = end; j >= 0; j--) {
  31406. list[j].y -= delta;
  31407. if (j > 0
  31408. && list[j].y > list[j - 1].y + list[j - 1].height
  31409. ) {
  31410. break;
  31411. }
  31412. }
  31413. }
  31414. function changeX(list, isDownList, cx, cy, r, dir) {
  31415. var lastDeltaX = dir > 0
  31416. ? isDownList // 右侧
  31417. ? Number.MAX_VALUE // 下
  31418. : 0 // 上
  31419. : isDownList // 左侧
  31420. ? Number.MAX_VALUE // 下
  31421. : 0; // 上
  31422. for (var i = 0, l = list.length; i < l; i++) {
  31423. // Not change x for center label
  31424. if (list[i].position === 'center') {
  31425. continue;
  31426. }
  31427. var deltaY = Math.abs(list[i].y - cy);
  31428. var length = list[i].len;
  31429. var length2 = list[i].len2;
  31430. var deltaX = (deltaY < r + length)
  31431. ? Math.sqrt(
  31432. (r + length + length2) * (r + length + length2)
  31433. - deltaY * deltaY
  31434. )
  31435. : Math.abs(list[i].x - cx);
  31436. if (isDownList && deltaX >= lastDeltaX) {
  31437. // 右下,左下
  31438. deltaX = lastDeltaX - 10;
  31439. }
  31440. if (!isDownList && deltaX <= lastDeltaX) {
  31441. // 右上,左上
  31442. deltaX = lastDeltaX + 10;
  31443. }
  31444. list[i].x = cx + deltaX * dir;
  31445. lastDeltaX = deltaX;
  31446. }
  31447. }
  31448. var lastY = 0;
  31449. var delta;
  31450. var len = list.length;
  31451. var upList = [];
  31452. var downList = [];
  31453. for (var i = 0; i < len; i++) {
  31454. delta = list[i].y - lastY;
  31455. if (delta < 0) {
  31456. shiftDown(i, len, -delta, dir);
  31457. }
  31458. lastY = list[i].y + list[i].height;
  31459. }
  31460. if (viewHeight - lastY < 0) {
  31461. shiftUp(len - 1, lastY - viewHeight);
  31462. }
  31463. for (var i = 0; i < len; i++) {
  31464. if (list[i].y >= cy) {
  31465. downList.push(list[i]);
  31466. }
  31467. else {
  31468. upList.push(list[i]);
  31469. }
  31470. }
  31471. changeX(upList, false, cx, cy, r, dir);
  31472. changeX(downList, true, cx, cy, r, dir);
  31473. }
  31474. function avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight) {
  31475. var leftList = [];
  31476. var rightList = [];
  31477. for (var i = 0; i < labelLayoutList.length; i++) {
  31478. if (labelLayoutList[i].x < cx) {
  31479. leftList.push(labelLayoutList[i]);
  31480. }
  31481. else {
  31482. rightList.push(labelLayoutList[i]);
  31483. }
  31484. }
  31485. adjustSingleSide(rightList, cx, cy, r, 1, viewWidth, viewHeight);
  31486. adjustSingleSide(leftList, cx, cy, r, -1, viewWidth, viewHeight);
  31487. for (var i = 0; i < labelLayoutList.length; i++) {
  31488. var linePoints = labelLayoutList[i].linePoints;
  31489. if (linePoints) {
  31490. var dist = linePoints[1][0] - linePoints[2][0];
  31491. if (labelLayoutList[i].x < cx) {
  31492. linePoints[2][0] = labelLayoutList[i].x + 3;
  31493. }
  31494. else {
  31495. linePoints[2][0] = labelLayoutList[i].x - 3;
  31496. }
  31497. linePoints[1][1] = linePoints[2][1] = labelLayoutList[i].y;
  31498. linePoints[1][0] = linePoints[2][0] + dist;
  31499. }
  31500. }
  31501. }
  31502. var labelLayout = function (seriesModel, r, viewWidth, viewHeight) {
  31503. var data = seriesModel.getData();
  31504. var labelLayoutList = [];
  31505. var cx;
  31506. var cy;
  31507. var hasLabelRotate = false;
  31508. data.each(function (idx) {
  31509. var layout = data.getItemLayout(idx);
  31510. var itemModel = data.getItemModel(idx);
  31511. var labelModel = itemModel.getModel('label');
  31512. // Use position in normal or emphasis
  31513. var labelPosition = labelModel.get('position') || itemModel.get('emphasis.label.position');
  31514. var labelLineModel = itemModel.getModel('labelLine');
  31515. var labelLineLen = labelLineModel.get('length');
  31516. var labelLineLen2 = labelLineModel.get('length2');
  31517. var midAngle = (layout.startAngle + layout.endAngle) / 2;
  31518. var dx = Math.cos(midAngle);
  31519. var dy = Math.sin(midAngle);
  31520. var textX;
  31521. var textY;
  31522. var linePoints;
  31523. var textAlign;
  31524. cx = layout.cx;
  31525. cy = layout.cy;
  31526. var isLabelInside = labelPosition === 'inside' || labelPosition === 'inner';
  31527. if (labelPosition === 'center') {
  31528. textX = layout.cx;
  31529. textY = layout.cy;
  31530. textAlign = 'center';
  31531. }
  31532. else {
  31533. var x1 = (isLabelInside ? (layout.r + layout.r0) / 2 * dx : layout.r * dx) + cx;
  31534. var y1 = (isLabelInside ? (layout.r + layout.r0) / 2 * dy : layout.r * dy) + cy;
  31535. textX = x1 + dx * 3;
  31536. textY = y1 + dy * 3;
  31537. if (!isLabelInside) {
  31538. // For roseType
  31539. var x2 = x1 + dx * (labelLineLen + r - layout.r);
  31540. var y2 = y1 + dy * (labelLineLen + r - layout.r);
  31541. var x3 = x2 + ((dx < 0 ? -1 : 1) * labelLineLen2);
  31542. var y3 = y2;
  31543. textX = x3 + (dx < 0 ? -5 : 5);
  31544. textY = y3;
  31545. linePoints = [[x1, y1], [x2, y2], [x3, y3]];
  31546. }
  31547. textAlign = isLabelInside ? 'center' : (dx > 0 ? 'left' : 'right');
  31548. }
  31549. var font = labelModel.getFont();
  31550. var labelRotate = labelModel.get('rotate')
  31551. ? (dx < 0 ? -midAngle + Math.PI : -midAngle) : 0;
  31552. var text = seriesModel.getFormattedLabel(idx, 'normal')
  31553. || data.getName(idx);
  31554. var textRect = getBoundingRect(
  31555. text, font, textAlign, 'top'
  31556. );
  31557. hasLabelRotate = !!labelRotate;
  31558. layout.label = {
  31559. x: textX,
  31560. y: textY,
  31561. position: labelPosition,
  31562. height: textRect.height,
  31563. len: labelLineLen,
  31564. len2: labelLineLen2,
  31565. linePoints: linePoints,
  31566. textAlign: textAlign,
  31567. verticalAlign: 'middle',
  31568. rotation: labelRotate,
  31569. inside: isLabelInside
  31570. };
  31571. // Not layout the inside label
  31572. if (!isLabelInside) {
  31573. labelLayoutList.push(layout.label);
  31574. }
  31575. });
  31576. if (!hasLabelRotate && seriesModel.get('avoidLabelOverlap')) {
  31577. avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight);
  31578. }
  31579. };
  31580. var PI2$4 = Math.PI * 2;
  31581. var RADIAN = Math.PI / 180;
  31582. var pieLayout = function (seriesType, ecModel, api, payload) {
  31583. ecModel.eachSeriesByType(seriesType, function (seriesModel) {
  31584. var data = seriesModel.getData();
  31585. var valueDim = data.mapDimension('value');
  31586. var center = seriesModel.get('center');
  31587. var radius = seriesModel.get('radius');
  31588. if (!isArray(radius)) {
  31589. radius = [0, radius];
  31590. }
  31591. if (!isArray(center)) {
  31592. center = [center, center];
  31593. }
  31594. var width = api.getWidth();
  31595. var height = api.getHeight();
  31596. var size = Math.min(width, height);
  31597. var cx = parsePercent$1(center[0], width);
  31598. var cy = parsePercent$1(center[1], height);
  31599. var r0 = parsePercent$1(radius[0], size / 2);
  31600. var r = parsePercent$1(radius[1], size / 2);
  31601. var startAngle = -seriesModel.get('startAngle') * RADIAN;
  31602. var minAngle = seriesModel.get('minAngle') * RADIAN;
  31603. var validDataCount = 0;
  31604. data.each(valueDim, function (value) {
  31605. !isNaN(value) && validDataCount++;
  31606. });
  31607. var sum = data.getSum(valueDim);
  31608. // Sum may be 0
  31609. var unitRadian = Math.PI / (sum || validDataCount) * 2;
  31610. var clockwise = seriesModel.get('clockwise');
  31611. var roseType = seriesModel.get('roseType');
  31612. var stillShowZeroSum = seriesModel.get('stillShowZeroSum');
  31613. // [0...max]
  31614. var extent = data.getDataExtent(valueDim);
  31615. extent[0] = 0;
  31616. // In the case some sector angle is smaller than minAngle
  31617. var restAngle = PI2$4;
  31618. var valueSumLargerThanMinAngle = 0;
  31619. var currentAngle = startAngle;
  31620. var dir = clockwise ? 1 : -1;
  31621. data.each(valueDim, function (value, idx) {
  31622. var angle;
  31623. if (isNaN(value)) {
  31624. data.setItemLayout(idx, {
  31625. angle: NaN,
  31626. startAngle: NaN,
  31627. endAngle: NaN,
  31628. clockwise: clockwise,
  31629. cx: cx,
  31630. cy: cy,
  31631. r0: r0,
  31632. r: roseType
  31633. ? NaN
  31634. : r
  31635. });
  31636. return;
  31637. }
  31638. // FIXME 兼容 2.0 但是 roseType 是 area 的时候才是这样?
  31639. if (roseType !== 'area') {
  31640. angle = (sum === 0 && stillShowZeroSum)
  31641. ? unitRadian : (value * unitRadian);
  31642. }
  31643. else {
  31644. angle = PI2$4 / validDataCount;
  31645. }
  31646. if (angle < minAngle) {
  31647. angle = minAngle;
  31648. restAngle -= minAngle;
  31649. }
  31650. else {
  31651. valueSumLargerThanMinAngle += value;
  31652. }
  31653. var endAngle = currentAngle + dir * angle;
  31654. data.setItemLayout(idx, {
  31655. angle: angle,
  31656. startAngle: currentAngle,
  31657. endAngle: endAngle,
  31658. clockwise: clockwise,
  31659. cx: cx,
  31660. cy: cy,
  31661. r0: r0,
  31662. r: roseType
  31663. ? linearMap(value, extent, [r0, r])
  31664. : r
  31665. });
  31666. currentAngle = endAngle;
  31667. });
  31668. // Some sector is constrained by minAngle
  31669. // Rest sectors needs recalculate angle
  31670. if (restAngle < PI2$4 && validDataCount) {
  31671. // Average the angle if rest angle is not enough after all angles is
  31672. // Constrained by minAngle
  31673. if (restAngle <= 1e-3) {
  31674. var angle = PI2$4 / validDataCount;
  31675. data.each(valueDim, function (value, idx) {
  31676. if (!isNaN(value)) {
  31677. var layout = data.getItemLayout(idx);
  31678. layout.angle = angle;
  31679. layout.startAngle = startAngle + dir * idx * angle;
  31680. layout.endAngle = startAngle + dir * (idx + 1) * angle;
  31681. }
  31682. });
  31683. }
  31684. else {
  31685. unitRadian = restAngle / valueSumLargerThanMinAngle;
  31686. currentAngle = startAngle;
  31687. data.each(valueDim, function (value, idx) {
  31688. if (!isNaN(value)) {
  31689. var layout = data.getItemLayout(idx);
  31690. var angle = layout.angle === minAngle
  31691. ? minAngle : value * unitRadian;
  31692. layout.startAngle = currentAngle;
  31693. layout.endAngle = currentAngle + dir * angle;
  31694. currentAngle += dir * angle;
  31695. }
  31696. });
  31697. }
  31698. }
  31699. labelLayout(seriesModel, r, width, height);
  31700. });
  31701. };
  31702. var dataFilter = function (seriesType) {
  31703. return {
  31704. seriesType: seriesType,
  31705. reset: function (seriesModel, ecModel) {
  31706. var legendModels = ecModel.findComponents({
  31707. mainType: 'legend'
  31708. });
  31709. if (!legendModels || !legendModels.length) {
  31710. return;
  31711. }
  31712. var data = seriesModel.getData();
  31713. data.filterSelf(function (idx) {
  31714. var name = data.getName(idx);
  31715. // If in any legend component the status is not selected.
  31716. for (var i = 0; i < legendModels.length; i++) {
  31717. if (!legendModels[i].isSelected(name)) {
  31718. return false;
  31719. }
  31720. }
  31721. return true;
  31722. });
  31723. }
  31724. };
  31725. };
  31726. createDataSelectAction('pie', [{
  31727. type: 'pieToggleSelect',
  31728. event: 'pieselectchanged',
  31729. method: 'toggleSelected'
  31730. }, {
  31731. type: 'pieSelect',
  31732. event: 'pieselected',
  31733. method: 'select'
  31734. }, {
  31735. type: 'pieUnSelect',
  31736. event: 'pieunselected',
  31737. method: 'unSelect'
  31738. }]);
  31739. registerVisual(dataColor('pie'));
  31740. registerLayout(curry(pieLayout, 'pie'));
  31741. registerProcessor(dataFilter('pie'));
  31742. exports.version = version;
  31743. exports.dependencies = dependencies;
  31744. exports.PRIORITY = PRIORITY;
  31745. exports.init = init;
  31746. exports.connect = connect;
  31747. exports.disConnect = disConnect;
  31748. exports.disconnect = disconnect;
  31749. exports.dispose = dispose;
  31750. exports.getInstanceByDom = getInstanceByDom;
  31751. exports.getInstanceById = getInstanceById;
  31752. exports.registerTheme = registerTheme;
  31753. exports.registerPreprocessor = registerPreprocessor;
  31754. exports.registerProcessor = registerProcessor;
  31755. exports.registerPostUpdate = registerPostUpdate;
  31756. exports.registerAction = registerAction;
  31757. exports.registerCoordinateSystem = registerCoordinateSystem;
  31758. exports.getCoordinateSystemDimensions = getCoordinateSystemDimensions;
  31759. exports.registerLayout = registerLayout;
  31760. exports.registerVisual = registerVisual;
  31761. exports.registerLoading = registerLoading;
  31762. exports.extendComponentModel = extendComponentModel;
  31763. exports.extendComponentView = extendComponentView;
  31764. exports.extendSeriesModel = extendSeriesModel;
  31765. exports.extendChartView = extendChartView;
  31766. exports.setCanvasCreator = setCanvasCreator;
  31767. exports.registerMap = registerMap;
  31768. exports.getMap = getMap;
  31769. exports.dataTool = dataTool;
  31770. })));