custom.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. var _config = require("../config");
  20. var __DEV__ = _config.__DEV__;
  21. var zrUtil = require("zrender/lib/core/util");
  22. var graphicUtil = require("../util/graphic");
  23. var _labelHelper = require("./helper/labelHelper");
  24. var getDefaultLabel = _labelHelper.getDefaultLabel;
  25. var createListFromArray = require("./helper/createListFromArray");
  26. var _barGrid = require("../layout/barGrid");
  27. var getLayoutOnAxis = _barGrid.getLayoutOnAxis;
  28. var DataDiffer = require("../data/DataDiffer");
  29. var SeriesModel = require("../model/Series");
  30. var Model = require("../model/Model");
  31. var ChartView = require("../view/Chart");
  32. var _createClipPathFromCoordSys = require("./helper/createClipPathFromCoordSys");
  33. var createClipPath = _createClipPathFromCoordSys.createClipPath;
  34. var prepareCartesian2d = require("../coord/cartesian/prepareCustom");
  35. var prepareGeo = require("../coord/geo/prepareCustom");
  36. var prepareSingleAxis = require("../coord/single/prepareCustom");
  37. var preparePolar = require("../coord/polar/prepareCustom");
  38. var prepareCalendar = require("../coord/calendar/prepareCustom");
  39. /*
  40. * Licensed to the Apache Software Foundation (ASF) under one
  41. * or more contributor license agreements. See the NOTICE file
  42. * distributed with this work for additional information
  43. * regarding copyright ownership. The ASF licenses this file
  44. * to you under the Apache License, Version 2.0 (the
  45. * "License"); you may not use this file except in compliance
  46. * with the License. You may obtain a copy of the License at
  47. *
  48. * http://www.apache.org/licenses/LICENSE-2.0
  49. *
  50. * Unless required by applicable law or agreed to in writing,
  51. * software distributed under the License is distributed on an
  52. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  53. * KIND, either express or implied. See the License for the
  54. * specific language governing permissions and limitations
  55. * under the License.
  56. */
  57. var CACHED_LABEL_STYLE_PROPERTIES = graphicUtil.CACHED_LABEL_STYLE_PROPERTIES;
  58. var ITEM_STYLE_NORMAL_PATH = ['itemStyle'];
  59. var ITEM_STYLE_EMPHASIS_PATH = ['emphasis', 'itemStyle'];
  60. var LABEL_NORMAL = ['label'];
  61. var LABEL_EMPHASIS = ['emphasis', 'label']; // Use prefix to avoid index to be the same as el.name,
  62. // which will cause weird udpate animation.
  63. var GROUP_DIFF_PREFIX = 'e\0\0';
  64. /**
  65. * To reduce total package size of each coordinate systems, the modules `prepareCustom`
  66. * of each coordinate systems are not required by each coordinate systems directly, but
  67. * required by the module `custom`.
  68. *
  69. * prepareInfoForCustomSeries {Function}: optional
  70. * @return {Object} {coordSys: {...}, api: {
  71. * coord: function (data, clamp) {}, // return point in global.
  72. * size: function (dataSize, dataItem) {} // return size of each axis in coordSys.
  73. * }}
  74. */
  75. var prepareCustoms = {
  76. cartesian2d: prepareCartesian2d,
  77. geo: prepareGeo,
  78. singleAxis: prepareSingleAxis,
  79. polar: preparePolar,
  80. calendar: prepareCalendar
  81. }; // ------
  82. // Model
  83. // ------
  84. SeriesModel.extend({
  85. type: 'series.custom',
  86. dependencies: ['grid', 'polar', 'geo', 'singleAxis', 'calendar'],
  87. defaultOption: {
  88. coordinateSystem: 'cartesian2d',
  89. // Can be set as 'none'
  90. zlevel: 0,
  91. z: 2,
  92. legendHoverLink: true,
  93. useTransform: true,
  94. // Custom series will not clip by default.
  95. // Some case will use custom series to draw label
  96. // For example https://echarts.apache.org/examples/en/editor.html?c=custom-gantt-flight
  97. // Only works on polar and cartesian2d coordinate system.
  98. clip: false // Cartesian coordinate system
  99. // xAxisIndex: 0,
  100. // yAxisIndex: 0,
  101. // Polar coordinate system
  102. // polarIndex: 0,
  103. // Geo coordinate system
  104. // geoIndex: 0,
  105. // label: {}
  106. // itemStyle: {}
  107. },
  108. /**
  109. * @override
  110. */
  111. getInitialData: function (option, ecModel) {
  112. return createListFromArray(this.getSource(), this);
  113. },
  114. /**
  115. * @override
  116. */
  117. getDataParams: function (dataIndex, dataType, el) {
  118. var params = SeriesModel.prototype.getDataParams.apply(this, arguments);
  119. el && (params.info = el.info);
  120. return params;
  121. }
  122. }); // -----
  123. // View
  124. // -----
  125. ChartView.extend({
  126. type: 'custom',
  127. /**
  128. * @private
  129. * @type {module:echarts/data/List}
  130. */
  131. _data: null,
  132. /**
  133. * @override
  134. */
  135. render: function (customSeries, ecModel, api, payload) {
  136. var oldData = this._data;
  137. var data = customSeries.getData();
  138. var group = this.group;
  139. var renderItem = makeRenderItem(customSeries, data, ecModel, api); // By default, merge mode is applied. In most cases, custom series is
  140. // used in the scenario that data amount is not large but graphic elements
  141. // is complicated, where merge mode is probably necessary for optimization.
  142. // For example, reuse graphic elements and only update the transform when
  143. // roam or data zoom according to `actionType`.
  144. data.diff(oldData).add(function (newIdx) {
  145. createOrUpdate(null, newIdx, renderItem(newIdx, payload), customSeries, group, data);
  146. }).update(function (newIdx, oldIdx) {
  147. var el = oldData.getItemGraphicEl(oldIdx);
  148. createOrUpdate(el, newIdx, renderItem(newIdx, payload), customSeries, group, data);
  149. }).remove(function (oldIdx) {
  150. var el = oldData.getItemGraphicEl(oldIdx);
  151. el && group.remove(el);
  152. }).execute(); // Do clipping
  153. var clipPath = customSeries.get('clip', true) ? createClipPath(customSeries.coordinateSystem, false, customSeries) : null;
  154. if (clipPath) {
  155. group.setClipPath(clipPath);
  156. } else {
  157. group.removeClipPath();
  158. }
  159. this._data = data;
  160. },
  161. incrementalPrepareRender: function (customSeries, ecModel, api) {
  162. this.group.removeAll();
  163. this._data = null;
  164. },
  165. incrementalRender: function (params, customSeries, ecModel, api, payload) {
  166. var data = customSeries.getData();
  167. var renderItem = makeRenderItem(customSeries, data, ecModel, api);
  168. function setIncrementalAndHoverLayer(el) {
  169. if (!el.isGroup) {
  170. el.incremental = true;
  171. el.useHoverLayer = true;
  172. }
  173. }
  174. for (var idx = params.start; idx < params.end; idx++) {
  175. var el = createOrUpdate(null, idx, renderItem(idx, payload), customSeries, this.group, data);
  176. el.traverse(setIncrementalAndHoverLayer);
  177. }
  178. },
  179. /**
  180. * @override
  181. */
  182. dispose: zrUtil.noop,
  183. /**
  184. * @override
  185. */
  186. filterForExposedEvent: function (eventType, query, targetEl, packedEvent) {
  187. var elementName = query.element;
  188. if (elementName == null || targetEl.name === elementName) {
  189. return true;
  190. } // Enable to give a name on a group made by `renderItem`, and listen
  191. // events that triggerd by its descendents.
  192. while ((targetEl = targetEl.parent) && targetEl !== this.group) {
  193. if (targetEl.name === elementName) {
  194. return true;
  195. }
  196. }
  197. return false;
  198. }
  199. });
  200. function createEl(elOption) {
  201. var graphicType = elOption.type;
  202. var el; // Those graphic elements are not shapes. They should not be
  203. // overwritten by users, so do them first.
  204. if (graphicType === 'path') {
  205. var shape = elOption.shape; // Using pathRect brings convenience to users sacle svg path.
  206. var pathRect = shape.width != null && shape.height != null ? {
  207. x: shape.x || 0,
  208. y: shape.y || 0,
  209. width: shape.width,
  210. height: shape.height
  211. } : null;
  212. var pathData = getPathData(shape); // Path is also used for icon, so layout 'center' by default.
  213. el = graphicUtil.makePath(pathData, null, pathRect, shape.layout || 'center');
  214. el.__customPathData = pathData;
  215. } else if (graphicType === 'image') {
  216. el = new graphicUtil.Image({});
  217. el.__customImagePath = elOption.style.image;
  218. } else if (graphicType === 'text') {
  219. el = new graphicUtil.Text({});
  220. el.__customText = elOption.style.text;
  221. } else if (graphicType === 'group') {
  222. el = new graphicUtil.Group();
  223. } else if (graphicType === 'compoundPath') {
  224. throw new Error('"compoundPath" is not supported yet.');
  225. } else {
  226. var Clz = graphicUtil.getShapeClass(graphicType);
  227. el = new Clz();
  228. }
  229. el.__customGraphicType = graphicType;
  230. el.name = elOption.name;
  231. return el;
  232. }
  233. function updateEl(el, dataIndex, elOption, animatableModel, data, isInit, isRoot) {
  234. var transitionProps = {};
  235. var elOptionStyle = elOption.style || {};
  236. elOption.shape && (transitionProps.shape = zrUtil.clone(elOption.shape));
  237. elOption.position && (transitionProps.position = elOption.position.slice());
  238. elOption.scale && (transitionProps.scale = elOption.scale.slice());
  239. elOption.origin && (transitionProps.origin = elOption.origin.slice());
  240. elOption.rotation && (transitionProps.rotation = elOption.rotation);
  241. if (el.type === 'image' && elOption.style) {
  242. var targetStyle = transitionProps.style = {};
  243. zrUtil.each(['x', 'y', 'width', 'height'], function (prop) {
  244. prepareStyleTransition(prop, targetStyle, elOptionStyle, el.style, isInit);
  245. });
  246. }
  247. if (el.type === 'text' && elOption.style) {
  248. var targetStyle = transitionProps.style = {};
  249. zrUtil.each(['x', 'y'], function (prop) {
  250. prepareStyleTransition(prop, targetStyle, elOptionStyle, el.style, isInit);
  251. }); // Compatible with previous: both support
  252. // textFill and fill, textStroke and stroke in 'text' element.
  253. !elOptionStyle.hasOwnProperty('textFill') && elOptionStyle.fill && (elOptionStyle.textFill = elOptionStyle.fill);
  254. !elOptionStyle.hasOwnProperty('textStroke') && elOptionStyle.stroke && (elOptionStyle.textStroke = elOptionStyle.stroke);
  255. }
  256. if (el.type !== 'group') {
  257. el.useStyle(elOptionStyle); // Init animation.
  258. if (isInit) {
  259. el.style.opacity = 0;
  260. var targetOpacity = elOptionStyle.opacity;
  261. targetOpacity == null && (targetOpacity = 1);
  262. graphicUtil.initProps(el, {
  263. style: {
  264. opacity: targetOpacity
  265. }
  266. }, animatableModel, dataIndex);
  267. }
  268. }
  269. if (isInit) {
  270. el.attr(transitionProps);
  271. } else {
  272. graphicUtil.updateProps(el, transitionProps, animatableModel, dataIndex);
  273. } // Merge by default.
  274. // z2 must not be null/undefined, otherwise sort error may occur.
  275. elOption.hasOwnProperty('z2') && el.attr('z2', elOption.z2 || 0);
  276. elOption.hasOwnProperty('silent') && el.attr('silent', elOption.silent);
  277. elOption.hasOwnProperty('invisible') && el.attr('invisible', elOption.invisible);
  278. elOption.hasOwnProperty('ignore') && el.attr('ignore', elOption.ignore); // `elOption.info` enables user to mount some info on
  279. // elements and use them in event handlers.
  280. // Update them only when user specified, otherwise, remain.
  281. elOption.hasOwnProperty('info') && el.attr('info', elOption.info); // If `elOption.styleEmphasis` is `false`, remove hover style. The
  282. // logic is ensured by `graphicUtil.setElementHoverStyle`.
  283. var styleEmphasis = elOption.styleEmphasis; // hoverStyle should always be set here, because if the hover style
  284. // may already be changed, where the inner cache should be reset.
  285. graphicUtil.setElementHoverStyle(el, styleEmphasis);
  286. if (isRoot) {
  287. graphicUtil.setAsHighDownDispatcher(el, styleEmphasis !== false);
  288. }
  289. }
  290. function prepareStyleTransition(prop, targetStyle, elOptionStyle, oldElStyle, isInit) {
  291. if (elOptionStyle[prop] != null && !isInit) {
  292. targetStyle[prop] = elOptionStyle[prop];
  293. elOptionStyle[prop] = oldElStyle[prop];
  294. }
  295. }
  296. function makeRenderItem(customSeries, data, ecModel, api) {
  297. var renderItem = customSeries.get('renderItem');
  298. var coordSys = customSeries.coordinateSystem;
  299. var prepareResult = {};
  300. if (coordSys) {
  301. prepareResult = coordSys.prepareCustoms ? coordSys.prepareCustoms() : prepareCustoms[coordSys.type](coordSys);
  302. }
  303. var userAPI = zrUtil.defaults({
  304. getWidth: api.getWidth,
  305. getHeight: api.getHeight,
  306. getZr: api.getZr,
  307. getDevicePixelRatio: api.getDevicePixelRatio,
  308. value: value,
  309. style: style,
  310. styleEmphasis: styleEmphasis,
  311. visual: visual,
  312. barLayout: barLayout,
  313. currentSeriesIndices: currentSeriesIndices,
  314. font: font
  315. }, prepareResult.api || {});
  316. var userParams = {
  317. // The life cycle of context: current round of rendering.
  318. // The global life cycle is probably not necessary, because
  319. // user can store global status by themselves.
  320. context: {},
  321. seriesId: customSeries.id,
  322. seriesName: customSeries.name,
  323. seriesIndex: customSeries.seriesIndex,
  324. coordSys: prepareResult.coordSys,
  325. dataInsideLength: data.count(),
  326. encode: wrapEncodeDef(customSeries.getData())
  327. }; // Do not support call `api` asynchronously without dataIndexInside input.
  328. var currDataIndexInside;
  329. var currDirty = true;
  330. var currItemModel;
  331. var currLabelNormalModel;
  332. var currLabelEmphasisModel;
  333. var currVisualColor;
  334. return function (dataIndexInside, payload) {
  335. currDataIndexInside = dataIndexInside;
  336. currDirty = true;
  337. return renderItem && renderItem(zrUtil.defaults({
  338. dataIndexInside: dataIndexInside,
  339. dataIndex: data.getRawIndex(dataIndexInside),
  340. // Can be used for optimization when zoom or roam.
  341. actionType: payload ? payload.type : null
  342. }, userParams), userAPI);
  343. }; // Do not update cache until api called.
  344. function updateCache(dataIndexInside) {
  345. dataIndexInside == null && (dataIndexInside = currDataIndexInside);
  346. if (currDirty) {
  347. currItemModel = data.getItemModel(dataIndexInside);
  348. currLabelNormalModel = currItemModel.getModel(LABEL_NORMAL);
  349. currLabelEmphasisModel = currItemModel.getModel(LABEL_EMPHASIS);
  350. currVisualColor = data.getItemVisual(dataIndexInside, 'color');
  351. currDirty = false;
  352. }
  353. }
  354. /**
  355. * @public
  356. * @param {number|string} dim
  357. * @param {number} [dataIndexInside=currDataIndexInside]
  358. * @return {number|string} value
  359. */
  360. function value(dim, dataIndexInside) {
  361. dataIndexInside == null && (dataIndexInside = currDataIndexInside);
  362. return data.get(data.getDimension(dim || 0), dataIndexInside);
  363. }
  364. /**
  365. * By default, `visual` is applied to style (to support visualMap).
  366. * `visual.color` is applied at `fill`. If user want apply visual.color on `stroke`,
  367. * it can be implemented as:
  368. * `api.style({stroke: api.visual('color'), fill: null})`;
  369. * @public
  370. * @param {Object} [extra]
  371. * @param {number} [dataIndexInside=currDataIndexInside]
  372. */
  373. function style(extra, dataIndexInside) {
  374. dataIndexInside == null && (dataIndexInside = currDataIndexInside);
  375. updateCache(dataIndexInside);
  376. var itemStyle = currItemModel.getModel(ITEM_STYLE_NORMAL_PATH).getItemStyle();
  377. currVisualColor != null && (itemStyle.fill = currVisualColor);
  378. var opacity = data.getItemVisual(dataIndexInside, 'opacity');
  379. opacity != null && (itemStyle.opacity = opacity);
  380. var labelModel = extra ? applyExtraBefore(extra, currLabelNormalModel) : currLabelNormalModel;
  381. graphicUtil.setTextStyle(itemStyle, labelModel, null, {
  382. autoColor: currVisualColor,
  383. isRectText: true
  384. });
  385. itemStyle.text = labelModel.getShallow('show') ? zrUtil.retrieve2(customSeries.getFormattedLabel(dataIndexInside, 'normal'), getDefaultLabel(data, dataIndexInside)) : null;
  386. extra && applyExtraAfter(itemStyle, extra);
  387. return itemStyle;
  388. }
  389. /**
  390. * @public
  391. * @param {Object} [extra]
  392. * @param {number} [dataIndexInside=currDataIndexInside]
  393. */
  394. function styleEmphasis(extra, dataIndexInside) {
  395. dataIndexInside == null && (dataIndexInside = currDataIndexInside);
  396. updateCache(dataIndexInside);
  397. var itemStyle = currItemModel.getModel(ITEM_STYLE_EMPHASIS_PATH).getItemStyle();
  398. var labelModel = extra ? applyExtraBefore(extra, currLabelEmphasisModel) : currLabelEmphasisModel;
  399. graphicUtil.setTextStyle(itemStyle, labelModel, null, {
  400. isRectText: true
  401. }, true);
  402. itemStyle.text = labelModel.getShallow('show') ? zrUtil.retrieve3(customSeries.getFormattedLabel(dataIndexInside, 'emphasis'), customSeries.getFormattedLabel(dataIndexInside, 'normal'), getDefaultLabel(data, dataIndexInside)) : null;
  403. extra && applyExtraAfter(itemStyle, extra);
  404. return itemStyle;
  405. }
  406. /**
  407. * @public
  408. * @param {string} visualType
  409. * @param {number} [dataIndexInside=currDataIndexInside]
  410. */
  411. function visual(visualType, dataIndexInside) {
  412. dataIndexInside == null && (dataIndexInside = currDataIndexInside);
  413. return data.getItemVisual(dataIndexInside, visualType);
  414. }
  415. /**
  416. * @public
  417. * @param {number} opt.count Positive interger.
  418. * @param {number} [opt.barWidth]
  419. * @param {number} [opt.barMaxWidth]
  420. * @param {number} [opt.barMinWidth]
  421. * @param {number} [opt.barGap]
  422. * @param {number} [opt.barCategoryGap]
  423. * @return {Object} {width, offset, offsetCenter} is not support, return undefined.
  424. */
  425. function barLayout(opt) {
  426. if (coordSys.getBaseAxis) {
  427. var baseAxis = coordSys.getBaseAxis();
  428. return getLayoutOnAxis(zrUtil.defaults({
  429. axis: baseAxis
  430. }, opt), api);
  431. }
  432. }
  433. /**
  434. * @public
  435. * @return {Array.<number>}
  436. */
  437. function currentSeriesIndices() {
  438. return ecModel.getCurrentSeriesIndices();
  439. }
  440. /**
  441. * @public
  442. * @param {Object} opt
  443. * @param {string} [opt.fontStyle]
  444. * @param {number} [opt.fontWeight]
  445. * @param {number} [opt.fontSize]
  446. * @param {string} [opt.fontFamily]
  447. * @return {string} font string
  448. */
  449. function font(opt) {
  450. return graphicUtil.getFont(opt, ecModel);
  451. }
  452. }
  453. function wrapEncodeDef(data) {
  454. var encodeDef = {};
  455. zrUtil.each(data.dimensions, function (dimName, dataDimIndex) {
  456. var dimInfo = data.getDimensionInfo(dimName);
  457. if (!dimInfo.isExtraCoord) {
  458. var coordDim = dimInfo.coordDim;
  459. var dataDims = encodeDef[coordDim] = encodeDef[coordDim] || [];
  460. dataDims[dimInfo.coordDimIndex] = dataDimIndex;
  461. }
  462. });
  463. return encodeDef;
  464. }
  465. function createOrUpdate(el, dataIndex, elOption, animatableModel, group, data) {
  466. el = doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data, true);
  467. el && data.setItemGraphicEl(dataIndex, el);
  468. return el;
  469. }
  470. function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data, isRoot) {
  471. // [Rule]
  472. // By default, follow merge mode.
  473. // (It probably brings benifit for performance in some cases of large data, where
  474. // user program can be optimized to that only updated props needed to be re-calculated,
  475. // or according to `actionType` some calculation can be skipped.)
  476. // If `renderItem` returns `null`/`undefined`/`false`, remove the previous el if existing.
  477. // (It seems that violate the "merge" principle, but most of users probably intuitively
  478. // regard "return;" as "show nothing element whatever", so make a exception to meet the
  479. // most cases.)
  480. var simplyRemove = !elOption; // `null`/`undefined`/`false`
  481. elOption = elOption || {};
  482. var elOptionType = elOption.type;
  483. var elOptionShape = elOption.shape;
  484. var elOptionStyle = elOption.style;
  485. if (el && (simplyRemove // || elOption.$merge === false
  486. // If `elOptionType` is `null`, follow the merge principle.
  487. || elOptionType != null && elOptionType !== el.__customGraphicType || elOptionType === 'path' && hasOwnPathData(elOptionShape) && getPathData(elOptionShape) !== el.__customPathData || elOptionType === 'image' && hasOwn(elOptionStyle, 'image') && elOptionStyle.image !== el.__customImagePath // FIXME test and remove this restriction?
  488. || elOptionType === 'text' && hasOwn(elOptionShape, 'text') && elOptionStyle.text !== el.__customText)) {
  489. group.remove(el);
  490. el = null;
  491. } // `elOption.type` is undefined when `renderItem` returns nothing.
  492. if (simplyRemove) {
  493. return;
  494. }
  495. var isInit = !el;
  496. !el && (el = createEl(elOption));
  497. updateEl(el, dataIndex, elOption, animatableModel, data, isInit, isRoot);
  498. if (elOptionType === 'group') {
  499. mergeChildren(el, dataIndex, elOption, animatableModel, data);
  500. } // Always add whatever already added to ensure sequence.
  501. group.add(el);
  502. return el;
  503. } // Usage:
  504. // (1) By default, `elOption.$mergeChildren` is `'byIndex'`, which indicates that
  505. // the existing children will not be removed, and enables the feature that
  506. // update some of the props of some of the children simply by construct
  507. // the returned children of `renderItem` like:
  508. // `var children = group.children = []; children[3] = {opacity: 0.5};`
  509. // (2) If `elOption.$mergeChildren` is `'byName'`, add/update/remove children
  510. // by child.name. But that might be lower performance.
  511. // (3) If `elOption.$mergeChildren` is `false`, the existing children will be
  512. // replaced totally.
  513. // (4) If `!elOption.children`, following the "merge" principle, nothing will happen.
  514. //
  515. // For implementation simpleness, do not provide a direct way to remove sinlge
  516. // child (otherwise the total indicies of the children array have to be modified).
  517. // User can remove a single child by set its `ignore` as `true` or replace
  518. // it by another element, where its `$merge` can be set as `true` if necessary.
  519. function mergeChildren(el, dataIndex, elOption, animatableModel, data) {
  520. var newChildren = elOption.children;
  521. var newLen = newChildren ? newChildren.length : 0;
  522. var mergeChildren = elOption.$mergeChildren; // `diffChildrenByName` has been deprecated.
  523. var byName = mergeChildren === 'byName' || elOption.diffChildrenByName;
  524. var notMerge = mergeChildren === false; // For better performance on roam update, only enter if necessary.
  525. if (!newLen && !byName && !notMerge) {
  526. return;
  527. }
  528. if (byName) {
  529. diffGroupChildren({
  530. oldChildren: el.children() || [],
  531. newChildren: newChildren || [],
  532. dataIndex: dataIndex,
  533. animatableModel: animatableModel,
  534. group: el,
  535. data: data
  536. });
  537. return;
  538. }
  539. notMerge && el.removeAll(); // Mapping children of a group simply by index, which
  540. // might be better performance.
  541. var index = 0;
  542. for (; index < newLen; index++) {
  543. newChildren[index] && doCreateOrUpdate(el.childAt(index), dataIndex, newChildren[index], animatableModel, el, data);
  544. }
  545. }
  546. function diffGroupChildren(context) {
  547. new DataDiffer(context.oldChildren, context.newChildren, getKey, getKey, context).add(processAddUpdate).update(processAddUpdate).remove(processRemove).execute();
  548. }
  549. function getKey(item, idx) {
  550. var name = item && item.name;
  551. return name != null ? name : GROUP_DIFF_PREFIX + idx;
  552. }
  553. function processAddUpdate(newIndex, oldIndex) {
  554. var context = this.context;
  555. var childOption = newIndex != null ? context.newChildren[newIndex] : null;
  556. var child = oldIndex != null ? context.oldChildren[oldIndex] : null;
  557. doCreateOrUpdate(child, context.dataIndex, childOption, context.animatableModel, context.group, context.data);
  558. } // `graphic#applyDefaultTextStyle` will cache
  559. // textFill, textStroke, textStrokeWidth.
  560. // We have to do this trick.
  561. function applyExtraBefore(extra, model) {
  562. var dummyModel = new Model({}, model);
  563. zrUtil.each(CACHED_LABEL_STYLE_PROPERTIES, function (stylePropName, modelPropName) {
  564. if (extra.hasOwnProperty(stylePropName)) {
  565. dummyModel.option[modelPropName] = extra[stylePropName];
  566. }
  567. });
  568. return dummyModel;
  569. }
  570. function applyExtraAfter(itemStyle, extra) {
  571. for (var key in extra) {
  572. if (extra.hasOwnProperty(key) || !CACHED_LABEL_STYLE_PROPERTIES.hasOwnProperty(key)) {
  573. itemStyle[key] = extra[key];
  574. }
  575. }
  576. }
  577. function processRemove(oldIndex) {
  578. var context = this.context;
  579. var child = context.oldChildren[oldIndex];
  580. child && context.group.remove(child);
  581. }
  582. function getPathData(shape) {
  583. // "d" follows the SVG convention.
  584. return shape && (shape.pathData || shape.d);
  585. }
  586. function hasOwnPathData(shape) {
  587. return shape && (shape.hasOwnProperty('pathData') || shape.hasOwnProperty('d'));
  588. }
  589. function hasOwn(host, prop) {
  590. return host && host.hasOwnProperty(prop);
  591. }