TreeView.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  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 zrUtil = require("zrender/lib/core/util");
  20. var graphic = require("../../util/graphic");
  21. var SymbolClz = require("../helper/Symbol");
  22. var _layoutHelper = require("./layoutHelper");
  23. var radialCoordinate = _layoutHelper.radialCoordinate;
  24. var echarts = require("../../echarts");
  25. var bbox = require("zrender/lib/core/bbox");
  26. var View = require("../../coord/View");
  27. var roamHelper = require("../../component/helper/roamHelper");
  28. var RoamController = require("../../component/helper/RoamController");
  29. var _cursorHelper = require("../../component/helper/cursorHelper");
  30. var onIrrelevantElement = _cursorHelper.onIrrelevantElement;
  31. /*
  32. * Licensed to the Apache Software Foundation (ASF) under one
  33. * or more contributor license agreements. See the NOTICE file
  34. * distributed with this work for additional information
  35. * regarding copyright ownership. The ASF licenses this file
  36. * to you under the Apache License, Version 2.0 (the
  37. * "License"); you may not use this file except in compliance
  38. * with the License. You may obtain a copy of the License at
  39. *
  40. * http://www.apache.org/licenses/LICENSE-2.0
  41. *
  42. * Unless required by applicable law or agreed to in writing,
  43. * software distributed under the License is distributed on an
  44. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  45. * KIND, either express or implied. See the License for the
  46. * specific language governing permissions and limitations
  47. * under the License.
  48. */
  49. var _default = echarts.extendChartView({
  50. type: 'tree',
  51. /**
  52. * Init the chart
  53. * @override
  54. * @param {module:echarts/model/Global} ecModel
  55. * @param {module:echarts/ExtensionAPI} api
  56. */
  57. init: function (ecModel, api) {
  58. /**
  59. * @private
  60. * @type {module:echarts/data/Tree}
  61. */
  62. this._oldTree;
  63. /**
  64. * @private
  65. * @type {module:zrender/container/Group}
  66. */
  67. this._mainGroup = new graphic.Group();
  68. /**
  69. * @private
  70. * @type {module:echarts/componet/helper/RoamController}
  71. */
  72. this._controller = new RoamController(api.getZr());
  73. this._controllerHost = {
  74. target: this.group
  75. };
  76. this.group.add(this._mainGroup);
  77. },
  78. render: function (seriesModel, ecModel, api, payload) {
  79. var data = seriesModel.getData();
  80. var layoutInfo = seriesModel.layoutInfo;
  81. var group = this._mainGroup;
  82. var layout = seriesModel.get('layout');
  83. if (layout === 'radial') {
  84. group.attr('position', [layoutInfo.x + layoutInfo.width / 2, layoutInfo.y + layoutInfo.height / 2]);
  85. } else {
  86. group.attr('position', [layoutInfo.x, layoutInfo.y]);
  87. }
  88. this._updateViewCoordSys(seriesModel, layoutInfo, layout);
  89. this._updateController(seriesModel, ecModel, api);
  90. var oldData = this._data;
  91. var seriesScope = {
  92. expandAndCollapse: seriesModel.get('expandAndCollapse'),
  93. layout: layout,
  94. orient: seriesModel.getOrient(),
  95. curvature: seriesModel.get('lineStyle.curveness'),
  96. symbolRotate: seriesModel.get('symbolRotate'),
  97. symbolOffset: seriesModel.get('symbolOffset'),
  98. hoverAnimation: seriesModel.get('hoverAnimation'),
  99. useNameLabel: true,
  100. fadeIn: true
  101. };
  102. data.diff(oldData).add(function (newIdx) {
  103. if (symbolNeedsDraw(data, newIdx)) {
  104. // Create node and edge
  105. updateNode(data, newIdx, null, group, seriesModel, seriesScope);
  106. }
  107. }).update(function (newIdx, oldIdx) {
  108. var symbolEl = oldData.getItemGraphicEl(oldIdx);
  109. if (!symbolNeedsDraw(data, newIdx)) {
  110. symbolEl && removeNode(oldData, oldIdx, symbolEl, group, seriesModel, seriesScope);
  111. return;
  112. } // Update node and edge
  113. updateNode(data, newIdx, symbolEl, group, seriesModel, seriesScope);
  114. }).remove(function (oldIdx) {
  115. var symbolEl = oldData.getItemGraphicEl(oldIdx); // When remove a collapsed node of subtree, since the collapsed
  116. // node haven't been initialized with a symbol element,
  117. // you can't found it's symbol element through index.
  118. // so if we want to remove the symbol element we should insure
  119. // that the symbol element is not null.
  120. if (symbolEl) {
  121. removeNode(oldData, oldIdx, symbolEl, group, seriesModel, seriesScope);
  122. }
  123. }).execute();
  124. this._nodeScaleRatio = seriesModel.get('nodeScaleRatio');
  125. this._updateNodeAndLinkScale(seriesModel);
  126. if (seriesScope.expandAndCollapse === true) {
  127. data.eachItemGraphicEl(function (el, dataIndex) {
  128. el.off('click').on('click', function () {
  129. api.dispatchAction({
  130. type: 'treeExpandAndCollapse',
  131. seriesId: seriesModel.id,
  132. dataIndex: dataIndex
  133. });
  134. });
  135. });
  136. }
  137. this._data = data;
  138. },
  139. _updateViewCoordSys: function (seriesModel) {
  140. var data = seriesModel.getData();
  141. var points = [];
  142. data.each(function (idx) {
  143. var layout = data.getItemLayout(idx);
  144. if (layout && !isNaN(layout.x) && !isNaN(layout.y)) {
  145. points.push([+layout.x, +layout.y]);
  146. }
  147. });
  148. var min = [];
  149. var max = [];
  150. bbox.fromPoints(points, min, max); // If don't Store min max when collapse the root node after roam,
  151. // the root node will disappear.
  152. var oldMin = this._min;
  153. var oldMax = this._max; // If width or height is 0
  154. if (max[0] - min[0] === 0) {
  155. min[0] = oldMin ? oldMin[0] : min[0] - 1;
  156. max[0] = oldMax ? oldMax[0] : max[0] + 1;
  157. }
  158. if (max[1] - min[1] === 0) {
  159. min[1] = oldMin ? oldMin[1] : min[1] - 1;
  160. max[1] = oldMax ? oldMax[1] : max[1] + 1;
  161. }
  162. var viewCoordSys = seriesModel.coordinateSystem = new View();
  163. viewCoordSys.zoomLimit = seriesModel.get('scaleLimit');
  164. viewCoordSys.setBoundingRect(min[0], min[1], max[0] - min[0], max[1] - min[1]);
  165. viewCoordSys.setCenter(seriesModel.get('center'));
  166. viewCoordSys.setZoom(seriesModel.get('zoom')); // Here we use viewCoordSys just for computing the 'position' and 'scale' of the group
  167. this.group.attr({
  168. position: viewCoordSys.position,
  169. scale: viewCoordSys.scale
  170. });
  171. this._viewCoordSys = viewCoordSys;
  172. this._min = min;
  173. this._max = max;
  174. },
  175. _updateController: function (seriesModel, ecModel, api) {
  176. var controller = this._controller;
  177. var controllerHost = this._controllerHost;
  178. var group = this.group;
  179. controller.setPointerChecker(function (e, x, y) {
  180. var rect = group.getBoundingRect();
  181. rect.applyTransform(group.transform);
  182. return rect.contain(x, y) && !onIrrelevantElement(e, api, seriesModel);
  183. });
  184. controller.enable(seriesModel.get('roam'));
  185. controllerHost.zoomLimit = seriesModel.get('scaleLimit');
  186. controllerHost.zoom = seriesModel.coordinateSystem.getZoom();
  187. controller.off('pan').off('zoom').on('pan', function (e) {
  188. roamHelper.updateViewOnPan(controllerHost, e.dx, e.dy);
  189. api.dispatchAction({
  190. seriesId: seriesModel.id,
  191. type: 'treeRoam',
  192. dx: e.dx,
  193. dy: e.dy
  194. });
  195. }, this).on('zoom', function (e) {
  196. roamHelper.updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY);
  197. api.dispatchAction({
  198. seriesId: seriesModel.id,
  199. type: 'treeRoam',
  200. zoom: e.scale,
  201. originX: e.originX,
  202. originY: e.originY
  203. });
  204. this._updateNodeAndLinkScale(seriesModel);
  205. }, this);
  206. },
  207. _updateNodeAndLinkScale: function (seriesModel) {
  208. var data = seriesModel.getData();
  209. var nodeScale = this._getNodeGlobalScale(seriesModel);
  210. var invScale = [nodeScale, nodeScale];
  211. data.eachItemGraphicEl(function (el, idx) {
  212. el.attr('scale', invScale);
  213. });
  214. },
  215. _getNodeGlobalScale: function (seriesModel) {
  216. var coordSys = seriesModel.coordinateSystem;
  217. if (coordSys.type !== 'view') {
  218. return 1;
  219. }
  220. var nodeScaleRatio = this._nodeScaleRatio;
  221. var groupScale = coordSys.scale;
  222. var groupZoom = groupScale && groupScale[0] || 1; // Scale node when zoom changes
  223. var roamZoom = coordSys.getZoom();
  224. var nodeScale = (roamZoom - 1) * nodeScaleRatio + 1;
  225. return nodeScale / groupZoom;
  226. },
  227. dispose: function () {
  228. this._controller && this._controller.dispose();
  229. this._controllerHost = {};
  230. },
  231. remove: function () {
  232. this._mainGroup.removeAll();
  233. this._data = null;
  234. }
  235. });
  236. function symbolNeedsDraw(data, dataIndex) {
  237. var layout = data.getItemLayout(dataIndex);
  238. return layout && !isNaN(layout.x) && !isNaN(layout.y) && data.getItemVisual(dataIndex, 'symbol') !== 'none';
  239. }
  240. function getTreeNodeStyle(node, itemModel, seriesScope) {
  241. seriesScope.itemModel = itemModel;
  242. seriesScope.itemStyle = itemModel.getModel('itemStyle').getItemStyle();
  243. seriesScope.hoverItemStyle = itemModel.getModel('emphasis.itemStyle').getItemStyle();
  244. seriesScope.lineStyle = itemModel.getModel('lineStyle').getLineStyle();
  245. seriesScope.labelModel = itemModel.getModel('label');
  246. seriesScope.hoverLabelModel = itemModel.getModel('emphasis.label');
  247. if (node.isExpand === false && node.children.length !== 0) {
  248. seriesScope.symbolInnerColor = seriesScope.itemStyle.fill;
  249. } else {
  250. seriesScope.symbolInnerColor = '#fff';
  251. }
  252. return seriesScope;
  253. }
  254. function updateNode(data, dataIndex, symbolEl, group, seriesModel, seriesScope) {
  255. var isInit = !symbolEl;
  256. var node = data.tree.getNodeByDataIndex(dataIndex);
  257. var itemModel = node.getModel();
  258. var seriesScope = getTreeNodeStyle(node, itemModel, seriesScope);
  259. var virtualRoot = data.tree.root;
  260. var source = node.parentNode === virtualRoot ? node : node.parentNode || node;
  261. var sourceSymbolEl = data.getItemGraphicEl(source.dataIndex);
  262. var sourceLayout = source.getLayout();
  263. var sourceOldLayout = sourceSymbolEl ? {
  264. x: sourceSymbolEl.position[0],
  265. y: sourceSymbolEl.position[1],
  266. rawX: sourceSymbolEl.__radialOldRawX,
  267. rawY: sourceSymbolEl.__radialOldRawY
  268. } : sourceLayout;
  269. var targetLayout = node.getLayout();
  270. if (isInit) {
  271. symbolEl = new SymbolClz(data, dataIndex, seriesScope);
  272. symbolEl.attr('position', [sourceOldLayout.x, sourceOldLayout.y]);
  273. } else {
  274. symbolEl.updateData(data, dataIndex, seriesScope);
  275. }
  276. symbolEl.__radialOldRawX = symbolEl.__radialRawX;
  277. symbolEl.__radialOldRawY = symbolEl.__radialRawY;
  278. symbolEl.__radialRawX = targetLayout.rawX;
  279. symbolEl.__radialRawY = targetLayout.rawY;
  280. group.add(symbolEl);
  281. data.setItemGraphicEl(dataIndex, symbolEl);
  282. graphic.updateProps(symbolEl, {
  283. position: [targetLayout.x, targetLayout.y]
  284. }, seriesModel);
  285. var symbolPath = symbolEl.getSymbolPath();
  286. if (seriesScope.layout === 'radial') {
  287. var realRoot = virtualRoot.children[0];
  288. var rootLayout = realRoot.getLayout();
  289. var length = realRoot.children.length;
  290. var rad;
  291. var isLeft;
  292. if (targetLayout.x === rootLayout.x && node.isExpand === true) {
  293. var center = {};
  294. center.x = (realRoot.children[0].getLayout().x + realRoot.children[length - 1].getLayout().x) / 2;
  295. center.y = (realRoot.children[0].getLayout().y + realRoot.children[length - 1].getLayout().y) / 2;
  296. rad = Math.atan2(center.y - rootLayout.y, center.x - rootLayout.x);
  297. if (rad < 0) {
  298. rad = Math.PI * 2 + rad;
  299. }
  300. isLeft = center.x < rootLayout.x;
  301. if (isLeft) {
  302. rad = rad - Math.PI;
  303. }
  304. } else {
  305. rad = Math.atan2(targetLayout.y - rootLayout.y, targetLayout.x - rootLayout.x);
  306. if (rad < 0) {
  307. rad = Math.PI * 2 + rad;
  308. }
  309. if (node.children.length === 0 || node.children.length !== 0 && node.isExpand === false) {
  310. isLeft = targetLayout.x < rootLayout.x;
  311. if (isLeft) {
  312. rad = rad - Math.PI;
  313. }
  314. } else {
  315. isLeft = targetLayout.x > rootLayout.x;
  316. if (!isLeft) {
  317. rad = rad - Math.PI;
  318. }
  319. }
  320. }
  321. var textPosition = isLeft ? 'left' : 'right';
  322. var rotate = seriesScope.labelModel.get('rotate');
  323. var labelRotateRadian = rotate * (Math.PI / 180);
  324. symbolPath.setStyle({
  325. textPosition: seriesScope.labelModel.get('position') || textPosition,
  326. textRotation: rotate == null ? -rad : labelRotateRadian,
  327. textOrigin: 'center',
  328. verticalAlign: 'middle'
  329. });
  330. }
  331. if (node.parentNode && node.parentNode !== virtualRoot) {
  332. var edge = symbolEl.__edge;
  333. if (!edge) {
  334. edge = symbolEl.__edge = new graphic.BezierCurve({
  335. shape: getEdgeShape(seriesScope, sourceOldLayout, sourceOldLayout),
  336. style: zrUtil.defaults({
  337. opacity: 0,
  338. strokeNoScale: true
  339. }, seriesScope.lineStyle)
  340. });
  341. }
  342. graphic.updateProps(edge, {
  343. shape: getEdgeShape(seriesScope, sourceLayout, targetLayout),
  344. style: {
  345. opacity: 1
  346. }
  347. }, seriesModel);
  348. group.add(edge);
  349. }
  350. }
  351. function removeNode(data, dataIndex, symbolEl, group, seriesModel, seriesScope) {
  352. var node = data.tree.getNodeByDataIndex(dataIndex);
  353. var virtualRoot = data.tree.root;
  354. var itemModel = node.getModel();
  355. var seriesScope = getTreeNodeStyle(node, itemModel, seriesScope);
  356. var source = node.parentNode === virtualRoot ? node : node.parentNode || node;
  357. var sourceLayout;
  358. while (sourceLayout = source.getLayout(), sourceLayout == null) {
  359. source = source.parentNode === virtualRoot ? source : source.parentNode || source;
  360. }
  361. graphic.updateProps(symbolEl, {
  362. position: [sourceLayout.x + 1, sourceLayout.y + 1]
  363. }, seriesModel, function () {
  364. group.remove(symbolEl);
  365. data.setItemGraphicEl(dataIndex, null);
  366. });
  367. symbolEl.fadeOut(null, {
  368. keepLabel: true
  369. });
  370. var edge = symbolEl.__edge;
  371. if (edge) {
  372. graphic.updateProps(edge, {
  373. shape: getEdgeShape(seriesScope, sourceLayout, sourceLayout),
  374. style: {
  375. opacity: 0
  376. }
  377. }, seriesModel, function () {
  378. group.remove(edge);
  379. });
  380. }
  381. }
  382. function getEdgeShape(seriesScope, sourceLayout, targetLayout) {
  383. var cpx1;
  384. var cpy1;
  385. var cpx2;
  386. var cpy2;
  387. var orient = seriesScope.orient;
  388. var x1;
  389. var x2;
  390. var y1;
  391. var y2;
  392. if (seriesScope.layout === 'radial') {
  393. x1 = sourceLayout.rawX;
  394. y1 = sourceLayout.rawY;
  395. x2 = targetLayout.rawX;
  396. y2 = targetLayout.rawY;
  397. var radialCoor1 = radialCoordinate(x1, y1);
  398. var radialCoor2 = radialCoordinate(x1, y1 + (y2 - y1) * seriesScope.curvature);
  399. var radialCoor3 = radialCoordinate(x2, y2 + (y1 - y2) * seriesScope.curvature);
  400. var radialCoor4 = radialCoordinate(x2, y2);
  401. return {
  402. x1: radialCoor1.x,
  403. y1: radialCoor1.y,
  404. x2: radialCoor4.x,
  405. y2: radialCoor4.y,
  406. cpx1: radialCoor2.x,
  407. cpy1: radialCoor2.y,
  408. cpx2: radialCoor3.x,
  409. cpy2: radialCoor3.y
  410. };
  411. } else {
  412. x1 = sourceLayout.x;
  413. y1 = sourceLayout.y;
  414. x2 = targetLayout.x;
  415. y2 = targetLayout.y;
  416. if (orient === 'LR' || orient === 'RL') {
  417. cpx1 = x1 + (x2 - x1) * seriesScope.curvature;
  418. cpy1 = y1;
  419. cpx2 = x2 + (x1 - x2) * seriesScope.curvature;
  420. cpy2 = y2;
  421. }
  422. if (orient === 'TB' || orient === 'BT') {
  423. cpx1 = x1;
  424. cpy1 = y1 + (y2 - y1) * seriesScope.curvature;
  425. cpx2 = x2;
  426. cpy2 = y2 + (y1 - y2) * seriesScope.curvature;
  427. }
  428. }
  429. return {
  430. x1: x1,
  431. y1: y1,
  432. x2: x2,
  433. y2: y2,
  434. cpx1: cpx1,
  435. cpy1: cpy1,
  436. cpx2: cpx2,
  437. cpy2: cpy2
  438. };
  439. }
  440. module.exports = _default;