bd3c98407c43865a850e8257bcec5b39899b7e4b.svn-base 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. /**
  2. * echarts图表类:树图
  3. *
  4. * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。
  5. * @author Loutongbing (娄同兵, loutongbing@126.com)
  6. */
  7. define(function (require) {
  8. var ChartBase = require('./base');
  9. var GOLDEN_SECTION = 0.618;
  10. // 图形依赖
  11. var IconShape = require('../util/shape/Icon');
  12. var ImageShape = require('zrender/shape/Image');
  13. var LineShape = require('zrender/shape/Line');
  14. var BezierCurveShape = require('zrender/shape/BezierCurve');
  15. // 布局依赖
  16. var TreeLayout = require('../layout/Tree');
  17. // 数据依赖
  18. var TreeData = require('../data/Tree');
  19. var ecConfig = require('../config');
  20. // 默认参数
  21. ecConfig.tree = {
  22. zlevel: 1, // 一级层叠
  23. z: 2, // 二级层叠
  24. calculable: false,
  25. clickable: true,
  26. rootLocation: {},
  27. orient: 'vertical',
  28. symbol: 'circle',
  29. symbolSize: 20,
  30. nodePadding: 30,
  31. layerPadding: 100,
  32. /*rootLocation: {
  33. x: 'center' | 'left' | 'right' | 'x%' | {number},
  34. y: 'center' | 'top' | 'bottom' | 'y%' | {number}
  35. },*/
  36. itemStyle: {
  37. normal: {
  38. // color: 各异,
  39. label: {
  40. show: true
  41. },
  42. lineStyle: {
  43. width: 1,
  44. color: '#777',
  45. type: 'curve' // curve
  46. }
  47. },
  48. emphasis: {
  49. }
  50. }
  51. };
  52. var ecData = require('../util/ecData');
  53. var zrConfig = require('zrender/config');
  54. var zrEvent = require('zrender/tool/event');
  55. var zrUtil = require('zrender/tool/util');
  56. /**
  57. * 构造函数
  58. * @param {Object} messageCenter echart消息中心
  59. * @param {ZRender} zr zrender实例
  60. * @param {Object} series 数据
  61. * @param {Object} component 组件
  62. * @constructor
  63. * @exports Tree
  64. */
  65. function Tree(ecTheme, messageCenter, zr, option, myChart) {
  66. // 图表基类
  67. ChartBase.call(this, ecTheme, messageCenter, zr, option, myChart);
  68. this.refresh(option);
  69. }
  70. Tree.prototype = {
  71. type : ecConfig.CHART_TYPE_TREE,
  72. /**
  73. * 构建单个
  74. *
  75. * @param {Object} data 数据
  76. */
  77. _buildShape : function (series, seriesIndex) {
  78. var data = series.data[0];
  79. this.tree = TreeData.fromOptionData(data.name, data.children);
  80. // 添加root的data
  81. this.tree.root.data = data;
  82. // 根据root坐标 方向 对每个节点的坐标进行映射
  83. this._setTreeShape(series);
  84. // 递归画出树节点与连接线
  85. this.tree.traverse(
  86. function (treeNode) {
  87. this._buildItem(
  88. treeNode,
  89. series,
  90. seriesIndex
  91. );
  92. // 画连接线
  93. if (treeNode.children.length > 0) {
  94. this._buildLink(
  95. treeNode,
  96. series
  97. );
  98. }
  99. },
  100. this
  101. );
  102. var panable = series.roam === true || series.roam === 'move';
  103. var zoomable = series.roam === true || series.roam === 'scale';
  104. // Enable pan and zooom
  105. this.zr.modLayer(this.getZlevelBase(), {
  106. panable: panable,
  107. zoomable: zoomable
  108. });
  109. if (
  110. this.query('markPoint.effect.show')
  111. || this.query('markLine.effect.show')
  112. ) {
  113. this.zr.modLayer(ecConfig.EFFECT_ZLEVEL, {
  114. panable: panable,
  115. zoomable: zoomable
  116. });
  117. }
  118. this.addShapeList();
  119. },
  120. /**
  121. * 构建单个item
  122. */
  123. _buildItem : function (
  124. treeNode,
  125. serie,
  126. seriesIndex
  127. ) {
  128. var queryTarget = [treeNode.data, serie];
  129. var symbol = this.deepQuery(queryTarget, 'symbol');
  130. // 多级控制
  131. var normal = this.deepMerge(
  132. queryTarget,
  133. 'itemStyle.normal'
  134. ) || {};
  135. var emphasis = this.deepMerge(
  136. queryTarget,
  137. 'itemStyle.emphasis'
  138. ) || {};
  139. var normalColor = normal.color || this.zr.getColor();
  140. var emphasisColor = emphasis.color || this.zr.getColor();
  141. var angle = -treeNode.layout.angle || 0;
  142. // 根节点不旋转
  143. if (treeNode.id === this.tree.root.id) {
  144. angle = 0;
  145. }
  146. var textPosition = 'right';
  147. if (Math.abs(angle) >= Math.PI / 2 && Math.abs(angle) < Math.PI * 3 / 2) {
  148. angle += Math.PI;
  149. textPosition = 'left';
  150. }
  151. var rotation = [
  152. angle,
  153. treeNode.layout.position[0],
  154. treeNode.layout.position[1]
  155. ];
  156. var shape = new IconShape({
  157. zlevel: this.getZlevelBase(),
  158. z: this.getZBase() + 1,
  159. rotation: rotation,
  160. clickable: this.deepQuery(queryTarget, 'clickable'),
  161. style: {
  162. x: treeNode.layout.position[0] - treeNode.layout.width * 0.5,
  163. y: treeNode.layout.position[1] - treeNode.layout.height * 0.5,
  164. width: treeNode.layout.width,
  165. height: treeNode.layout.height,
  166. iconType: symbol,
  167. color: normalColor,
  168. brushType: 'both',
  169. lineWidth: normal.borderWidth,
  170. strokeColor: normal.borderColor
  171. },
  172. highlightStyle: {
  173. color: emphasisColor,
  174. lineWidth: emphasis.borderWidth,
  175. strokeColor: emphasis.borderColor
  176. }
  177. });
  178. if (shape.style.iconType.match('image')) {
  179. shape.style.image = shape.style.iconType.replace(
  180. new RegExp('^image:\\/\\/'), ''
  181. );
  182. shape = new ImageShape({
  183. rotation: rotation,
  184. style: shape.style,
  185. highlightStyle: shape.highlightStyle,
  186. clickable: shape.clickable,
  187. zlevel: this.getZlevelBase(),
  188. z: this.getZBase()
  189. });
  190. }
  191. // 节点标签样式
  192. if (this.deepQuery(queryTarget, 'itemStyle.normal.label.show')) {
  193. shape.style.text = treeNode.data.label == null ? treeNode.id : treeNode.data.label;
  194. shape.style.textPosition = this.deepQuery(
  195. queryTarget, 'itemStyle.normal.label.position'
  196. );
  197. // 极坐标另外计算 时钟哪个侧面
  198. if (serie.orient === 'radial' && shape.style.textPosition !== 'inside') {
  199. shape.style.textPosition = textPosition;
  200. }
  201. shape.style.textColor = this.deepQuery(
  202. queryTarget, 'itemStyle.normal.label.textStyle.color'
  203. );
  204. shape.style.textFont = this.getFont(this.deepQuery(
  205. queryTarget, 'itemStyle.normal.label.textStyle'
  206. ) || {});
  207. }
  208. if (this.deepQuery(queryTarget, 'itemStyle.emphasis.label.show')) {
  209. shape.highlightStyle.textPosition = this.deepQuery(
  210. queryTarget, 'itemStyle.emphasis.label.position'
  211. );
  212. shape.highlightStyle.textColor = this.deepQuery(
  213. queryTarget, 'itemStyle.emphasis.label.textStyle.color'
  214. );
  215. shape.highlightStyle.textFont = this.getFont(this.deepQuery(
  216. queryTarget, 'itemStyle.emphasis.label.textStyle'
  217. ) || {});
  218. }
  219. // todo
  220. ecData.pack(
  221. shape,
  222. serie, seriesIndex,
  223. treeNode.data, 0,
  224. treeNode.id
  225. );
  226. this.shapeList.push(shape);
  227. },
  228. _buildLink : function (
  229. parentNode,
  230. serie
  231. ) {
  232. var lineStyle = serie.itemStyle.normal.lineStyle;
  233. // 折线另外计算
  234. if (lineStyle.type === 'broken') {
  235. this._buildBrokenLine(
  236. parentNode,
  237. lineStyle,
  238. serie
  239. );
  240. return;
  241. }
  242. for (var i = 0; i < parentNode.children.length; i++) {
  243. var xStart = parentNode.layout.position[0];
  244. var yStart = parentNode.layout.position[1];
  245. var xEnd = parentNode.children[i].layout.position[0];
  246. var yEnd = parentNode.children[i].layout.position[1];
  247. switch (lineStyle.type) {
  248. case 'curve':
  249. this._buildBezierCurve(
  250. parentNode,
  251. parentNode.children[i],
  252. lineStyle,
  253. serie
  254. );
  255. break;
  256. // 折线
  257. case 'broken':
  258. break;
  259. // default画直线
  260. default:
  261. var shape = this._getLine(
  262. xStart,
  263. yStart,
  264. xEnd,
  265. yEnd,
  266. lineStyle
  267. );
  268. this.shapeList.push(shape);
  269. }
  270. }
  271. },
  272. _buildBrokenLine: function (
  273. parentNode,
  274. lineStyle,
  275. serie
  276. ) {
  277. // 引用_getLine需要把type改为solid
  278. var solidLineStyle = zrUtil.clone(lineStyle);
  279. solidLineStyle.type = 'solid';
  280. var shapes = [];
  281. var xStart = parentNode.layout.position[0];
  282. var yStart = parentNode.layout.position[1];
  283. var orient = serie.orient;
  284. // 子节点的y
  285. var yEnd = parentNode.children[0].layout.position[1];
  286. // 中点x y
  287. var xMiddle = xStart;
  288. var yMiddle = yStart + (yEnd - yStart) * (1 - GOLDEN_SECTION);
  289. // 中线的起始
  290. var xMiddleStart = parentNode.children[0].layout.position[0];
  291. var yMiddleStart = yMiddle;
  292. var xMiddleEnd = parentNode.children[parentNode.children.length - 1].layout.position[0];
  293. var yMiddleEnd = yMiddle;
  294. // 水平状态
  295. if (orient === 'horizontal') {
  296. var xEnd = parentNode.children[0].layout.position[0];
  297. xMiddle = xStart + (xEnd - xStart) * (1 - GOLDEN_SECTION);
  298. yMiddle = yStart;
  299. xMiddleStart = xMiddle;
  300. yMiddleStart = parentNode.children[0].layout.position[1];
  301. xMiddleEnd = xMiddle;
  302. yMiddleEnd = parentNode.children[parentNode.children.length - 1].layout.position[1];
  303. }
  304. // 第一条 从根节点垂直向下
  305. shapes.push(
  306. this._getLine(
  307. xStart,
  308. yStart,
  309. xMiddle,
  310. yMiddle,
  311. solidLineStyle
  312. )
  313. );
  314. // 第二条 横向
  315. shapes.push(
  316. this._getLine(
  317. xMiddleStart,
  318. yMiddleStart,
  319. xMiddleEnd,
  320. yMiddleEnd,
  321. solidLineStyle
  322. )
  323. );
  324. // 第三条 垂直向下到子节点
  325. for (var i = 0; i < parentNode.children.length; i++) {
  326. xEnd = parentNode.children[i].layout.position[0];
  327. yEnd = parentNode.children[i].layout.position[1];
  328. // 水平状态
  329. if (orient === 'horizontal') {
  330. yMiddleStart = yEnd;
  331. }
  332. else {
  333. xMiddleStart = xEnd;
  334. }
  335. shapes.push(
  336. this._getLine(
  337. xMiddleStart,
  338. yMiddleStart,
  339. xEnd,
  340. yEnd,
  341. solidLineStyle
  342. )
  343. );
  344. }
  345. this.shapeList = this.shapeList.concat(shapes);
  346. },
  347. _getLine: function (
  348. xStart,
  349. yStart,
  350. xEnd,
  351. yEnd,
  352. lineStyle
  353. ) {
  354. if (xStart === xEnd) {
  355. xStart = xEnd = this.subPixelOptimize(xStart, lineStyle.width);
  356. }
  357. if (yStart === yEnd) {
  358. yStart = yEnd = this.subPixelOptimize(yStart, lineStyle.width);
  359. }
  360. return new LineShape({
  361. zlevel: this.getZlevelBase(),
  362. hoverable: false,
  363. style: zrUtil.merge(
  364. {
  365. xStart: xStart,
  366. yStart: yStart,
  367. xEnd: xEnd,
  368. yEnd: yEnd,
  369. lineType: lineStyle.type,
  370. strokeColor: lineStyle.color,
  371. lineWidth: lineStyle.width
  372. },
  373. lineStyle,
  374. true
  375. )
  376. });
  377. },
  378. _buildBezierCurve: function (
  379. parentNode,
  380. treeNode,
  381. lineStyle,
  382. serie
  383. ) {
  384. var offsetRatio = GOLDEN_SECTION;
  385. var orient = serie.orient;
  386. var xStart = parentNode.layout.position[0];
  387. var yStart = parentNode.layout.position[1];
  388. var xEnd = treeNode.layout.position[0];
  389. var yEnd = treeNode.layout.position[1];
  390. var cpX1 = xStart;
  391. var cpY1 = (yEnd - yStart) * offsetRatio + yStart;
  392. var cpX2 = xEnd;
  393. var cpY2 = (yEnd - yStart) * (1 - offsetRatio) + yStart;
  394. if (orient === 'horizontal') {
  395. cpX1 = (xEnd - xStart) * offsetRatio + xStart;
  396. cpY1 = yStart;
  397. cpX2 = (xEnd - xStart) * (1 - offsetRatio) + xStart;
  398. cpY2 = yEnd;
  399. }
  400. else if (orient === 'radial') {
  401. // 根节点 画直线
  402. if (parentNode.id === this.tree.root.id) {
  403. cpX1 = (xEnd - xStart) * offsetRatio + xStart;
  404. cpY1 = (yEnd - yStart) * offsetRatio + yStart;
  405. cpX2 = (xEnd - xStart) * (1 - offsetRatio) + xStart;
  406. cpY2 = (yEnd - yStart) * (1 - offsetRatio) + yStart;
  407. }
  408. else {
  409. var xStartOrigin = parentNode.layout.originPosition[0];
  410. var yStartOrigin = parentNode.layout.originPosition[1];
  411. var xEndOrigin = treeNode.layout.originPosition[0];
  412. var yEndOrigin = treeNode.layout.originPosition[1];
  413. var rootX = this.tree.root.layout.position[0];
  414. var rootY = this.tree.root.layout.position[1];
  415. cpX1 = xStartOrigin;
  416. cpY1 = (yEndOrigin - yStartOrigin) * offsetRatio + yStartOrigin;
  417. cpX2 = xEndOrigin;
  418. cpY2 = (yEndOrigin - yStartOrigin) * (1 - offsetRatio) + yStartOrigin;
  419. var rad = (cpX1 - this.minX) / this.width * Math.PI * 2;
  420. cpX1 = cpY1 * Math.cos(rad) + rootX;
  421. cpY1 = cpY1 * Math.sin(rad) + rootY;
  422. rad = (cpX2 - this.minX) / this.width * Math.PI * 2;
  423. cpX2 = cpY2 * Math.cos(rad) + rootX;
  424. cpY2 = cpY2 * Math.sin(rad) + rootY;
  425. }
  426. }
  427. var shape = new BezierCurveShape({
  428. zlevel: this.getZlevelBase(),
  429. hoverable: false,
  430. style: zrUtil.merge(
  431. {
  432. xStart: xStart,
  433. yStart: yStart,
  434. cpX1: cpX1,
  435. cpY1: cpY1,
  436. cpX2: cpX2,
  437. cpY2: cpY2,
  438. xEnd: xEnd,
  439. yEnd: yEnd,
  440. strokeColor: lineStyle.color,
  441. lineWidth: lineStyle.width
  442. },
  443. lineStyle,
  444. true
  445. )
  446. });
  447. this.shapeList.push(shape);
  448. },
  449. _setTreeShape : function (serie) {
  450. // 跑出来树的layout
  451. var treeLayout = new TreeLayout(
  452. {
  453. nodePadding: serie.nodePadding,
  454. layerPadding: serie.layerPadding
  455. }
  456. );
  457. this.tree.traverse(
  458. function (treeNode) {
  459. var queryTarget = [treeNode.data, serie];
  460. var symbolSize = this.deepQuery(queryTarget, 'symbolSize');
  461. if (typeof symbolSize === 'number') {
  462. symbolSize = [symbolSize, symbolSize];
  463. }
  464. treeNode.layout = {
  465. width: symbolSize[0], // 节点大小
  466. height: symbolSize[1]
  467. };
  468. },
  469. this
  470. );
  471. treeLayout.run(this.tree);
  472. // 树的方向
  473. var orient = serie.orient;
  474. var rootX = serie.rootLocation.x;
  475. var rootY = serie.rootLocation.y;
  476. var zrWidth = this.zr.getWidth();
  477. var zrHeight = this.zr.getHeight();
  478. if (rootX === 'center') {
  479. rootX = zrWidth * 0.5;
  480. }
  481. else {
  482. rootX = this.parsePercent(rootX, zrWidth);
  483. }
  484. if (rootY === 'center') {
  485. rootY = zrHeight * 0.5;
  486. }
  487. else {
  488. rootY = this.parsePercent(rootY, zrHeight);
  489. }
  490. rootY = this.parsePercent(rootY, zrHeight);
  491. // 水平树
  492. if (orient === 'horizontal') {
  493. rootX = isNaN(rootX) ? 10 : rootX;
  494. rootY = isNaN(rootY) ? zrHeight * 0.5 : rootY;
  495. }
  496. // 极坐标
  497. if (orient === 'radial') {
  498. rootX = isNaN(rootX) ? zrWidth * 0.5 : rootX;
  499. rootY = isNaN(rootY) ? zrHeight * 0.5 : rootY;
  500. }
  501. // 纵向树
  502. else {
  503. rootX = isNaN(rootX) ? zrWidth * 0.5 : rootX;
  504. rootY = isNaN(rootY) ? 10 : rootY;
  505. }
  506. // tree layout自动算出来的root的坐标
  507. var originRootX = this.tree.root.layout.position[0];
  508. // 若是极坐标,则求最大最小值
  509. if (orient === 'radial') {
  510. var minX = Infinity;
  511. var maxX = 0;
  512. var maxWidth = 0;
  513. this.tree.traverse(
  514. function (treeNode) {
  515. maxX = Math.max(maxX, treeNode.layout.position[0]);
  516. minX = Math.min(minX, treeNode.layout.position[0]);
  517. maxWidth = Math.max(maxWidth, treeNode.layout.width);
  518. }
  519. );
  520. // 2 * maxWidth 还不大好但至少保证绝不会重叠 todo
  521. this.width = maxX - minX + 2 * maxWidth;
  522. this.minX = minX;
  523. }
  524. this.tree.traverse(
  525. function (treeNode) {
  526. var x;
  527. var y;
  528. if (orient === 'vertical' && serie.direction === 'inverse') {
  529. x = treeNode.layout.position[0] - originRootX + rootX;
  530. y = rootY - treeNode.layout.position[1];
  531. }
  532. else if (orient === 'vertical') {
  533. x = treeNode.layout.position[0] - originRootX + rootX;
  534. y = treeNode.layout.position[1] + rootY;
  535. }
  536. else if (orient === 'horizontal' && serie.direction === 'inverse') {
  537. y = treeNode.layout.position[0] - originRootX + rootY;
  538. x = rootX - treeNode.layout.position[1];
  539. }
  540. else if (orient === 'horizontal') {
  541. y = treeNode.layout.position[0] - originRootX + rootY;
  542. x = treeNode.layout.position[1] + rootX;
  543. }
  544. // 极坐标
  545. else {
  546. x = treeNode.layout.position[0];
  547. y = treeNode.layout.position[1];
  548. // 记录原始坐标,以后计算贝塞尔曲线的控制点
  549. treeNode.layout.originPosition = [x, y];
  550. var r = y;
  551. var angle = (x - minX) / this.width * Math.PI * 2;
  552. x = r * Math.cos(angle) + rootX;
  553. y = r * Math.sin(angle) + rootY;
  554. treeNode.layout.angle = angle;
  555. }
  556. treeNode.layout.position[0] = x;
  557. treeNode.layout.position[1] = y;
  558. },
  559. this
  560. );
  561. },
  562. /*
  563. * 刷新
  564. */
  565. /* refresh: function (newOption) {
  566. this.clear();
  567. if (newOption) {
  568. this.option = newOption;
  569. this.series = newOption.series;
  570. }
  571. this._buildShape();
  572. }*/
  573. refresh: function (newOption) {
  574. this.clear();
  575. if (newOption) {
  576. this.option = newOption;
  577. this.series = this.option.series;
  578. }
  579. // Map storing all trees of series
  580. var series = this.series;
  581. var legend = this.component.legend;
  582. for (var i = 0; i < series.length; i++) {
  583. if (series[i].type === ecConfig.CHART_TYPE_TREE) {
  584. series[i] = this.reformOption(series[i]);
  585. var seriesName = series[i].name || '';
  586. this.selectedMap[seriesName] =
  587. legend ? legend.isSelected(seriesName) : true;
  588. if (!this.selectedMap[seriesName]) {
  589. continue;
  590. }
  591. this._buildSeries(series[i], i);
  592. }
  593. }
  594. },
  595. _buildSeries: function (series, seriesIndex) {
  596. /*var tree = Tree.fromOptionData('root', series.data);
  597. this._treesMap[seriesIndex] = tree;*/
  598. // this._buildTreemap(tree.root, seriesIndex);
  599. this._buildShape(series, seriesIndex);
  600. }
  601. };
  602. zrUtil.inherits(Tree, ChartBase);
  603. // 图表注册
  604. require('../chart').define('tree', Tree);
  605. return Tree;
  606. });