pie.js 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141
  1. /**
  2. * echarts图表类:饼图
  3. *
  4. * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。
  5. * @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
  6. *
  7. */
  8. define(function (require) {
  9. var ChartBase = require('./base');
  10. // 图形依赖
  11. var TextShape = require('zrender/shape/Text');
  12. var RingShape = require('zrender/shape/Ring');
  13. var CircleShape = require('zrender/shape/Circle');
  14. var SectorShape = require('zrender/shape/Sector');
  15. var PolylineShape = require('zrender/shape/Polyline');
  16. var ecConfig = require('../config');
  17. // 饼图默认参数
  18. ecConfig.pie = {
  19. zlevel: 0, // 一级层叠
  20. z: 2, // 二级层叠
  21. clickable: true,
  22. legendHoverLink: true,
  23. center: ['50%', '50%'], // 默认全局居中
  24. radius: [0, '75%'],
  25. clockWise: true, // 默认顺时针
  26. startAngle: 90,
  27. minAngle: 0, // 最小角度改为0
  28. selectedOffset: 10, // 选中是扇区偏移量
  29. // selectedMode: false, // 选择模式,默认关闭,可选single,multiple
  30. // roseType: null, // 南丁格尔玫瑰图模式,'radius'(半径) | 'area'(面积)
  31. itemStyle: {
  32. normal: {
  33. // color: 各异,
  34. borderColor: 'rgba(0,0,0,0)',
  35. borderWidth: 1,
  36. label: {
  37. show: true,
  38. position: 'outer'
  39. // formatter: 标签文本格式器,同Tooltip.formatter,不支持异步回调
  40. // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE
  41. // distance: 当position为inner时有效,为label位置到圆心的距离与圆半径(环状图为内外半径和)的比例系数
  42. },
  43. labelLine: {
  44. show: true,
  45. length: 20,
  46. lineStyle: {
  47. // color: 各异,
  48. width: 1,
  49. type: 'solid'
  50. }
  51. }
  52. },
  53. emphasis: {
  54. // color: 各异,
  55. borderColor: 'rgba(0,0,0,0)',
  56. borderWidth: 1,
  57. label: {
  58. show: false
  59. // position: 'outer'
  60. // formatter: 标签文本格式器,同Tooltip.formatter,不支持异步回调
  61. // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE
  62. // distance: 当position为inner时有效,为label位置到圆心的距离与圆半径(环状图为内外半径和)的比例系数
  63. },
  64. labelLine: {
  65. show: false,
  66. length: 20,
  67. lineStyle: {
  68. // color: 各异,
  69. width: 1,
  70. type: 'solid'
  71. }
  72. }
  73. }
  74. }
  75. };
  76. var ecData = require('../util/ecData');
  77. var zrUtil = require('zrender/tool/util');
  78. var zrMath = require('zrender/tool/math');
  79. var zrColor = require('zrender/tool/color');
  80. /**
  81. * 构造函数
  82. * @param {Object} messageCenter echart消息中心
  83. * @param {ZRender} zr zrender实例
  84. * @param {Object} series 数据
  85. * @param {Object} component 组件
  86. */
  87. function Pie(ecTheme, messageCenter, zr, option, myChart){
  88. // 图表基类
  89. ChartBase.call(this, ecTheme, messageCenter, zr, option, myChart);
  90. var self = this;
  91. /**
  92. * 输出动态视觉引导线
  93. */
  94. self.shapeHandler.onmouseover = function (param) {
  95. var shape = param.target;
  96. var seriesIndex = ecData.get(shape, 'seriesIndex');
  97. var dataIndex = ecData.get(shape, 'dataIndex');
  98. var percent = ecData.get(shape, 'special');
  99. var center = [shape.style.x, shape.style.y];
  100. var startAngle = shape.style.startAngle;
  101. var endAngle = shape.style.endAngle;
  102. var midAngle = ((endAngle + startAngle) / 2 + 360) % 360; // 中值
  103. var defaultColor = shape.highlightStyle.color;
  104. // 文本标签,需要显示则会有返回
  105. var label = self.getLabel(
  106. seriesIndex, dataIndex, percent,
  107. center, midAngle, defaultColor,
  108. true
  109. );
  110. if (label) {
  111. self.zr.addHoverShape(label);
  112. }
  113. // 文本标签视觉引导线,需要显示则会有返回
  114. var labelLine = self.getLabelLine(
  115. seriesIndex, dataIndex,
  116. center, shape.style.r0, shape.style.r,
  117. midAngle, defaultColor,
  118. true
  119. );
  120. if (labelLine) {
  121. self.zr.addHoverShape(labelLine);
  122. }
  123. };
  124. this.refresh(option);
  125. }
  126. Pie.prototype = {
  127. type: ecConfig.CHART_TYPE_PIE,
  128. /**
  129. * 绘制图形
  130. */
  131. _buildShape: function () {
  132. var series = this.series;
  133. var legend = this.component.legend;
  134. this.selectedMap = {};
  135. this._selected = {};
  136. var center;
  137. var radius;
  138. var pieCase; // 饼图箱子
  139. this._selectedMode = false;
  140. var serieName;
  141. for (var i = 0, l = series.length; i < l; i++) {
  142. if (series[i].type === ecConfig.CHART_TYPE_PIE) {
  143. series[i] = this.reformOption(series[i]);
  144. this.legendHoverLink = series[i].legendHoverLink || this.legendHoverLink;
  145. serieName = series[i].name || '';
  146. // 系列图例开关
  147. this.selectedMap[serieName] = legend ? legend.isSelected(serieName) : true;
  148. if (!this.selectedMap[serieName]) {
  149. continue;
  150. }
  151. center = this.parseCenter(this.zr, series[i].center);
  152. radius = this.parseRadius(this.zr, series[i].radius);
  153. this._selectedMode = this._selectedMode || series[i].selectedMode;
  154. this._selected[i] = [];
  155. if (this.deepQuery([series[i], this.option], 'calculable')) {
  156. pieCase = {
  157. zlevel: series[i].zlevel,
  158. z: series[i].z,
  159. hoverable: false,
  160. style: {
  161. x: center[0], // 圆心横坐标
  162. y: center[1], // 圆心纵坐标
  163. // 圆环内外半径
  164. r0: radius[0] <= 10 ? 0 : radius[0] - 10,
  165. r: radius[1] + 10,
  166. brushType: 'stroke',
  167. lineWidth: 1,
  168. strokeColor: series[i].calculableHolderColor
  169. || this.ecTheme.calculableHolderColor
  170. || ecConfig.calculableHolderColor
  171. }
  172. };
  173. ecData.pack(pieCase, series[i], i, undefined, -1);
  174. this.setCalculable(pieCase);
  175. pieCase = radius[0] <= 10
  176. ? new CircleShape(pieCase)
  177. : new RingShape(pieCase);
  178. this.shapeList.push(pieCase);
  179. }
  180. this._buildSinglePie(i);
  181. this.buildMark(i);
  182. }
  183. }
  184. this.addShapeList();
  185. },
  186. /**
  187. * 构建单个饼图
  188. *
  189. * @param {number} seriesIndex 系列索引
  190. */
  191. _buildSinglePie: function (seriesIndex) {
  192. var series = this.series;
  193. var serie = series[seriesIndex];
  194. var data = serie.data;
  195. var legend = this.component.legend;
  196. var itemName;
  197. var totalSelected = 0; // 迭代累计选中且非0个数
  198. var totalSelectedValue0 = 0; // 迭代累计选中0只个数
  199. var totalValue = 0; // 迭代累计
  200. var maxValue = Number.NEGATIVE_INFINITY;
  201. var singleShapeList = [];
  202. // 计算需要显示的个数和总值
  203. for (var i = 0, l = data.length; i < l; i++) {
  204. itemName = data[i].name;
  205. this.selectedMap[itemName] = legend ? legend.isSelected(itemName) : true;
  206. if (this.selectedMap[itemName] && !isNaN(data[i].value)) {
  207. if (+data[i].value !== 0) {
  208. totalSelected++;
  209. }
  210. else {
  211. totalSelectedValue0++;
  212. }
  213. totalValue += +data[i].value;
  214. maxValue = Math.max(maxValue, +data[i].value);
  215. }
  216. }
  217. if (totalValue === 0) {
  218. return;
  219. }
  220. var percent = 100;
  221. var clockWise = serie.clockWise;
  222. var startAngle = (serie.startAngle.toFixed(2) - 0 + 360) % 360;
  223. var endAngle;
  224. var minAngle = serie.minAngle || 0.01; // #bugfixed
  225. var totalAngle = 360 - (minAngle * totalSelected) - 0.01 * totalSelectedValue0;
  226. var defaultColor;
  227. var roseType = serie.roseType;
  228. var center;
  229. var radius;
  230. var r0; // 扇形内半径
  231. var r1; // 扇形外半径
  232. for (var i = 0, l = data.length; i < l; i++) {
  233. itemName = data[i].name;
  234. if (!this.selectedMap[itemName] || isNaN(data[i].value)) {
  235. continue;
  236. }
  237. // 默认颜色策略,有图例则从图例中获取颜色定义,没有就全局颜色定义
  238. defaultColor = legend ? legend.getColor(itemName) : this.zr.getColor(i);
  239. percent = data[i].value / totalValue;
  240. if (roseType != 'area') {
  241. endAngle = clockWise
  242. ? (startAngle - percent * totalAngle - (percent !== 0 ? minAngle : 0.01))
  243. : (percent * totalAngle + startAngle + (percent !== 0 ? minAngle : 0.01));
  244. }
  245. else {
  246. endAngle = clockWise
  247. ? (startAngle - 360 / l)
  248. : (360 / l + startAngle);
  249. }
  250. endAngle = endAngle.toFixed(2) - 0;
  251. percent = (percent * 100).toFixed(2);
  252. center = this.parseCenter(this.zr, serie.center);
  253. radius = this.parseRadius(this.zr, serie.radius);
  254. r0 = +radius[0];
  255. r1 = +radius[1];
  256. if (roseType === 'radius') {
  257. r1 = data[i].value / maxValue * (r1 - r0) * 0.8 + (r1 - r0) * 0.2 + r0;
  258. }
  259. else if (roseType === 'area') {
  260. r1 = Math.sqrt(data[i].value / maxValue) * (r1 - r0) + r0;
  261. }
  262. if (clockWise) {
  263. var temp;
  264. temp = startAngle;
  265. startAngle = endAngle;
  266. endAngle = temp;
  267. }
  268. this._buildItem(
  269. singleShapeList,
  270. seriesIndex, i, percent,
  271. data[i].selected,
  272. center, r0, r1,
  273. startAngle, endAngle, defaultColor
  274. );
  275. if (!clockWise) {
  276. startAngle = endAngle;
  277. }
  278. }
  279. this._autoLabelLayout(singleShapeList, center, r1);
  280. for (var i = 0, l = singleShapeList.length; i < l; i++) {
  281. this.shapeList.push(singleShapeList[i]);
  282. }
  283. singleShapeList = null;
  284. },
  285. /**
  286. * 构建单个扇形及指标
  287. */
  288. _buildItem: function (
  289. singleShapeList,
  290. seriesIndex, dataIndex, percent,
  291. isSelected,
  292. center, r0, r1,
  293. startAngle, endAngle, defaultColor
  294. ) {
  295. var series = this.series;
  296. var midAngle = ((endAngle + startAngle) / 2 + 360) % 360; // 中值
  297. // 扇形
  298. var sector = this.getSector(
  299. seriesIndex, dataIndex, percent, isSelected,
  300. center, r0, r1,
  301. startAngle, endAngle, defaultColor
  302. );
  303. // 图形需要附加的私有数据
  304. ecData.pack(
  305. sector,
  306. series[seriesIndex], seriesIndex,
  307. series[seriesIndex].data[dataIndex], dataIndex,
  308. series[seriesIndex].data[dataIndex].name,
  309. percent
  310. );
  311. singleShapeList.push(sector);
  312. // 文本标签,需要显示则会有返回
  313. var label = this.getLabel(
  314. seriesIndex, dataIndex, percent,
  315. center, midAngle, defaultColor,
  316. false
  317. );
  318. // 文本标签视觉引导线,需要显示则会有返回
  319. var labelLine = this.getLabelLine(
  320. seriesIndex, dataIndex,
  321. center, r0, r1,
  322. midAngle, defaultColor,
  323. false
  324. );
  325. if (labelLine) {
  326. ecData.pack(
  327. labelLine,
  328. series[seriesIndex], seriesIndex,
  329. series[seriesIndex].data[dataIndex], dataIndex,
  330. series[seriesIndex].data[dataIndex].name,
  331. percent
  332. );
  333. singleShapeList.push(labelLine);
  334. }
  335. if (label) {
  336. ecData.pack(
  337. label,
  338. series[seriesIndex], seriesIndex,
  339. series[seriesIndex].data[dataIndex], dataIndex,
  340. series[seriesIndex].data[dataIndex].name,
  341. percent
  342. );
  343. label._labelLine = labelLine;
  344. singleShapeList.push(label);
  345. }
  346. },
  347. /**
  348. * 构建扇形
  349. */
  350. getSector: function (
  351. seriesIndex, dataIndex, percent, isSelected,
  352. center, r0, r1,
  353. startAngle, endAngle, defaultColor
  354. ) {
  355. var series = this.series;
  356. var serie = series[seriesIndex];
  357. var data = serie.data[dataIndex];
  358. var queryTarget = [data, serie];
  359. // 多级控制
  360. var normal = this.deepMerge(
  361. queryTarget,
  362. 'itemStyle.normal'
  363. ) || {};
  364. var emphasis = this.deepMerge(
  365. queryTarget,
  366. 'itemStyle.emphasis'
  367. ) || {};
  368. var normalColor = this.getItemStyleColor(normal.color, seriesIndex, dataIndex, data)
  369. || defaultColor;
  370. var emphasisColor = this.getItemStyleColor(emphasis.color, seriesIndex, dataIndex, data)
  371. || (typeof normalColor === 'string'
  372. ? zrColor.lift(normalColor, -0.2)
  373. : normalColor
  374. );
  375. var sector = {
  376. zlevel: serie.zlevel,
  377. z: serie.z,
  378. clickable: this.deepQuery(queryTarget, 'clickable'),
  379. style: {
  380. x: center[0], // 圆心横坐标
  381. y: center[1], // 圆心纵坐标
  382. r0: r0, // 圆环内半径
  383. r: r1, // 圆环外半径
  384. startAngle: startAngle,
  385. endAngle: endAngle,
  386. brushType: 'both',
  387. color: normalColor,
  388. lineWidth: normal.borderWidth,
  389. strokeColor: normal.borderColor,
  390. lineJoin: 'round'
  391. },
  392. highlightStyle: {
  393. color: emphasisColor,
  394. lineWidth: emphasis.borderWidth,
  395. strokeColor: emphasis.borderColor,
  396. lineJoin: 'round'
  397. },
  398. _seriesIndex: seriesIndex,
  399. _dataIndex: dataIndex
  400. };
  401. if (isSelected) {
  402. var midAngle =
  403. ((sector.style.startAngle + sector.style.endAngle) / 2).toFixed(2) - 0;
  404. sector.style._hasSelected = true;
  405. sector.style._x = sector.style.x;
  406. sector.style._y = sector.style.y;
  407. var offset = this.query(serie, 'selectedOffset');
  408. sector.style.x += zrMath.cos(midAngle, true) * offset;
  409. sector.style.y -= zrMath.sin(midAngle, true) * offset;
  410. this._selected[seriesIndex][dataIndex] = true;
  411. }
  412. else {
  413. this._selected[seriesIndex][dataIndex] = false;
  414. }
  415. if (this._selectedMode) {
  416. sector.onclick = this.shapeHandler.onclick;
  417. }
  418. if (this.deepQuery([data, serie, this.option], 'calculable')) {
  419. this.setCalculable(sector);
  420. sector.draggable = true;
  421. }
  422. // “emphasis显示”添加事件响应
  423. if (this._needLabel(serie, data, true) // emphasis下显示文本
  424. || this._needLabelLine(serie, data, true) // emphasis下显示引导线
  425. ) {
  426. sector.onmouseover = this.shapeHandler.onmouseover;
  427. }
  428. sector = new SectorShape(sector);
  429. return sector;
  430. },
  431. /**
  432. * 需要显示则会有返回构建好的shape,否则返回undefined
  433. */
  434. getLabel: function (
  435. seriesIndex, dataIndex, percent,
  436. center, midAngle, defaultColor,
  437. isEmphasis
  438. ) {
  439. var series = this.series;
  440. var serie = series[seriesIndex];
  441. var data = serie.data[dataIndex];
  442. // 特定状态下是否需要显示文本标签
  443. if (!this._needLabel(serie, data, isEmphasis)) {
  444. return;
  445. }
  446. var status = isEmphasis ? 'emphasis' : 'normal';
  447. // serie里有默认配置,放心大胆的用!
  448. var itemStyle = zrUtil.merge(
  449. zrUtil.clone(data.itemStyle) || {},
  450. serie.itemStyle
  451. );
  452. // label配置
  453. var labelControl = itemStyle[status].label;
  454. var textStyle = labelControl.textStyle || {};
  455. var centerX = center[0]; // 圆心横坐标
  456. var centerY = center[1]; // 圆心纵坐标
  457. var x;
  458. var y;
  459. var radius = this.parseRadius(this.zr, serie.radius); // 标签位置半径
  460. var textAlign;
  461. var textBaseline = 'middle';
  462. labelControl.position = labelControl.position
  463. || itemStyle.normal.label.position;
  464. if (labelControl.position === 'center') {
  465. // center显示
  466. x = centerX;
  467. y = centerY;
  468. textAlign = 'center';
  469. }
  470. else if (labelControl.position === 'inner' || labelControl.position === 'inside') {
  471. // 内部标签显示, 按外半径比例计算标签位置
  472. radius = (radius[0] + radius[1]) * (labelControl.distance || 0.5);
  473. x = Math.round(centerX + radius * zrMath.cos(midAngle, true));
  474. y = Math.round(centerY - radius * zrMath.sin(midAngle, true));
  475. defaultColor = '#fff';
  476. textAlign = 'center';
  477. }
  478. else {
  479. // 外部显示,默认 labelControl.position === 'outer')
  480. radius = radius[1] - (-itemStyle[status].labelLine.length);
  481. x = Math.round(centerX + radius * zrMath.cos(midAngle, true));
  482. y = Math.round(centerY - radius * zrMath.sin(midAngle, true));
  483. textAlign = (midAngle >= 90 && midAngle <= 270) ? 'right' : 'left';
  484. }
  485. if (labelControl.position != 'center'
  486. && labelControl.position != 'inner'
  487. && labelControl.position != 'inside'
  488. ) {
  489. x += textAlign === 'left' ? 20 : -20;
  490. }
  491. data.__labelX = x - (textAlign === 'left' ? 5 : -5);
  492. data.__labelY = y;
  493. var ts = new TextShape({
  494. zlevel: serie.zlevel,
  495. z: serie.z + 1,
  496. hoverable: false,
  497. style: {
  498. x: x,
  499. y: y,
  500. color: textStyle.color || defaultColor,
  501. text: this.getLabelText(seriesIndex, dataIndex, percent, status),
  502. textAlign: textStyle.align || textAlign,
  503. textBaseline: textStyle.baseline || textBaseline,
  504. textFont: this.getFont(textStyle)
  505. },
  506. highlightStyle: {
  507. brushType: 'fill'
  508. }
  509. });
  510. ts._radius = radius;
  511. ts._labelPosition = labelControl.position || 'outer';
  512. ts._rect = ts.getRect(ts.style);
  513. ts._seriesIndex = seriesIndex;
  514. ts._dataIndex = dataIndex;
  515. return ts;
  516. },
  517. /**
  518. * 根据lable.format计算label text
  519. */
  520. getLabelText: function (seriesIndex, dataIndex, percent, status) {
  521. var series = this.series;
  522. var serie = series[seriesIndex];
  523. var data = serie.data[dataIndex];
  524. var formatter = this.deepQuery(
  525. [data, serie],
  526. 'itemStyle.' + status + '.label.formatter'
  527. );
  528. if (formatter) {
  529. if (typeof formatter === 'function') {
  530. return formatter.call(
  531. this.myChart,
  532. {
  533. seriesIndex: seriesIndex,
  534. seriesName: serie.name || '',
  535. series: serie,
  536. dataIndex: dataIndex,
  537. data: data,
  538. name: data.name,
  539. value: data.value,
  540. percent: percent
  541. }
  542. );
  543. }
  544. else if (typeof formatter === 'string') {
  545. formatter = formatter.replace('{a}','{a0}')
  546. .replace('{b}','{b0}')
  547. .replace('{c}','{c0}')
  548. .replace('{d}','{d0}');
  549. formatter = formatter.replace('{a0}', serie.name)
  550. .replace('{b0}', data.name)
  551. .replace('{c0}', data.value)
  552. .replace('{d0}', percent);
  553. return formatter;
  554. }
  555. }
  556. else {
  557. return data.name;
  558. }
  559. },
  560. /**
  561. * 需要显示则会有返回构建好的shape,否则返回undefined
  562. */
  563. getLabelLine: function (
  564. seriesIndex, dataIndex,
  565. center, r0, r1,
  566. midAngle, defaultColor,
  567. isEmphasis
  568. ) {
  569. var series = this.series;
  570. var serie = series[seriesIndex];
  571. var data = serie.data[dataIndex];
  572. // 特定状态下是否需要显示文本标签
  573. if (this._needLabelLine(serie, data, isEmphasis)) {
  574. var status = isEmphasis ? 'emphasis' : 'normal';
  575. // serie里有默认配置,放心大胆的用!
  576. var itemStyle = zrUtil.merge(
  577. zrUtil.clone(data.itemStyle) || {},
  578. serie.itemStyle
  579. );
  580. // labelLine配置
  581. var labelLineControl = itemStyle[status].labelLine;
  582. var lineStyle = labelLineControl.lineStyle || {};
  583. var centerX = center[0]; // 圆心横坐标
  584. var centerY = center[1]; // 圆心纵坐标
  585. // 视觉引导线起点半径
  586. var minRadius = r1;
  587. // 视觉引导线终点半径
  588. var maxRadius = this.parseRadius(this.zr, serie.radius)[1]
  589. - (-labelLineControl.length);
  590. var cosValue = zrMath.cos(midAngle, true);
  591. var sinValue = zrMath.sin(midAngle, true);
  592. return new PolylineShape({
  593. zlevel: serie.zlevel,
  594. z: serie.z + 1,
  595. hoverable: false,
  596. style: {
  597. pointList: [
  598. [
  599. centerX + minRadius * cosValue,
  600. centerY - minRadius * sinValue
  601. ],
  602. [
  603. centerX + maxRadius * cosValue,
  604. centerY - maxRadius * sinValue
  605. ],
  606. [
  607. data.__labelX,
  608. data.__labelY
  609. ]
  610. ],
  611. //xStart: centerX + minRadius * cosValue,
  612. //yStart: centerY - minRadius * sinValue,
  613. //xEnd: centerX + maxRadius * cosValue,
  614. //yEnd: centerY - maxRadius * sinValue,
  615. strokeColor: lineStyle.color || defaultColor,
  616. lineType: lineStyle.type,
  617. lineWidth: lineStyle.width
  618. },
  619. _seriesIndex: seriesIndex,
  620. _dataIndex: dataIndex
  621. });
  622. }
  623. else {
  624. return;
  625. }
  626. },
  627. /**
  628. * 返回特定状态(normal or emphasis)下是否需要显示label标签文本
  629. * @param {Object} serie
  630. * @param {Object} data
  631. * @param {boolean} isEmphasis true is 'emphasis' and false is 'normal'
  632. */
  633. _needLabel: function (serie, data, isEmphasis) {
  634. return this.deepQuery(
  635. [data, serie],
  636. 'itemStyle.'
  637. + (isEmphasis ? 'emphasis' : 'normal')
  638. + '.label.show'
  639. );
  640. },
  641. /**
  642. * 返回特定状态(normal or emphasis)下是否需要显示labelLine标签视觉引导线
  643. * @param {Object} serie
  644. * @param {Object} data
  645. * @param {boolean} isEmphasis true is 'emphasis' and false is 'normal'
  646. */
  647. _needLabelLine: function (serie, data, isEmphasis) {
  648. return this.deepQuery(
  649. [data, serie],
  650. 'itemStyle.'
  651. + (isEmphasis ? 'emphasis' : 'normal')
  652. +'.labelLine.show'
  653. );
  654. },
  655. /**
  656. * @param {Array.<Object>} sList 单系列图形集合
  657. */
  658. _autoLabelLayout : function (sList, center, r) {
  659. var leftList = [];
  660. var rightList = [];
  661. for (var i = 0, l = sList.length; i < l; i++) {
  662. if (sList[i]._labelPosition === 'outer' || sList[i]._labelPosition === 'outside') {
  663. sList[i]._rect._y = sList[i]._rect.y;
  664. if (sList[i]._rect.x < center[0]) {
  665. leftList.push(sList[i]);
  666. }
  667. else {
  668. rightList.push(sList[i]);
  669. }
  670. }
  671. }
  672. this._layoutCalculate(leftList, center, r, -1);
  673. this._layoutCalculate(rightList, center, r, 1);
  674. },
  675. /**
  676. * @param {Array.<Object>} tList 单系列文本图形集合
  677. * @param {number} direction 水平方向参数,left为-1,right为1
  678. */
  679. _layoutCalculate : function(tList, center, r, direction) {
  680. tList.sort(function(a, b){
  681. return a._rect.y - b._rect.y;
  682. });
  683. // 压
  684. function _changeDown(start, end, delta, direction) {
  685. for (var j = start; j < end; j++) {
  686. tList[j]._rect.y += delta;
  687. tList[j].style.y += delta;
  688. if (tList[j]._labelLine) {
  689. tList[j]._labelLine.style.pointList[1][1] += delta;
  690. tList[j]._labelLine.style.pointList[2][1] += delta;
  691. }
  692. if (j > start
  693. && j + 1 < end
  694. && tList[j + 1]._rect.y > tList[j]._rect.y + tList[j]._rect.height
  695. ) {
  696. _changeUp(j, delta / 2);
  697. return;
  698. }
  699. }
  700. _changeUp(end - 1, delta / 2);
  701. }
  702. // 弹
  703. function _changeUp(end, delta) {
  704. for (var j = end; j >= 0; j--) {
  705. tList[j]._rect.y -= delta;
  706. tList[j].style.y -= delta;
  707. if (tList[j]._labelLine) {
  708. tList[j]._labelLine.style.pointList[1][1] -= delta;
  709. tList[j]._labelLine.style.pointList[2][1] -= delta;
  710. }
  711. if (j > 0
  712. && tList[j]._rect.y > tList[j - 1]._rect.y + tList[j - 1]._rect.height
  713. ) {
  714. break;
  715. }
  716. }
  717. }
  718. function _changeX(sList, isDownList, center, r, direction) {
  719. var x = center[0];
  720. var y = center[1];
  721. var deltaX;
  722. var deltaY;
  723. var length;
  724. var lastDeltaX = direction > 0
  725. ? isDownList // 右侧
  726. ? Number.MAX_VALUE // 下
  727. : 0 // 上
  728. : isDownList // 左侧
  729. ? Number.MAX_VALUE // 下
  730. : 0; // 上
  731. for (var i = 0, l = sList.length; i < l; i++) {
  732. deltaY = Math.abs(sList[i]._rect.y - y);
  733. length = sList[i]._radius - r;
  734. deltaX = (deltaY < r + length)
  735. ? Math.sqrt(
  736. (r + length + 20) * (r + length + 20)
  737. - Math.pow(sList[i]._rect.y - y, 2)
  738. )
  739. : Math.abs(
  740. sList[i]._rect.x + (direction > 0 ? 0 : sList[i]._rect.width) - x
  741. );
  742. if (isDownList && deltaX >= lastDeltaX) {
  743. // 右下,左下
  744. deltaX = lastDeltaX - 10;
  745. }
  746. if (!isDownList && deltaX <= lastDeltaX) {
  747. // 右上,左上
  748. deltaX = lastDeltaX + 10;
  749. }
  750. sList[i]._rect.x = sList[i].style.x = x + deltaX * direction;
  751. if (sList[i]._labelLine) {
  752. sList[i]._labelLine.style.pointList[2][0] = x + (deltaX - 5) * direction;
  753. sList[i]._labelLine.style.pointList[1][0] = x + (deltaX - 20) *direction;
  754. }
  755. lastDeltaX = deltaX;
  756. }
  757. }
  758. var lastY = 0;
  759. var delta;
  760. var len = tList.length;
  761. var upList = [];
  762. var downList = [];
  763. for (var i = 0; i < len; i++) {
  764. delta = tList[i]._rect.y - lastY;
  765. if (delta < 0) {
  766. _changeDown(i, len, -delta, direction);
  767. }
  768. lastY = tList[i]._rect.y + tList[i]._rect.height;
  769. }
  770. if (this.zr.getHeight() - lastY < 0) {
  771. _changeUp(len - 1, lastY - this.zr.getHeight());
  772. }
  773. for (var i = 0; i < len; i++) {
  774. if (tList[i]._rect.y >= center[1]) {
  775. downList.push(tList[i]);
  776. }
  777. else {
  778. upList.push(tList[i]);
  779. }
  780. }
  781. _changeX(downList, true, center, r, direction);
  782. _changeX(upList, false, center, r, direction);
  783. },
  784. /**
  785. * 参数修正&默认值赋值,重载基类方法
  786. * @param {Object} opt 参数
  787. */
  788. reformOption: function (opt) {
  789. // 常用方法快捷方式
  790. var _merge = zrUtil.merge;
  791. opt = _merge(
  792. _merge(
  793. opt || {}, zrUtil.clone(this.ecTheme.pie || {})
  794. ),
  795. zrUtil.clone(ecConfig.pie)
  796. );
  797. // 通用字体设置
  798. opt.itemStyle.normal.label.textStyle = this.getTextStyle(
  799. opt.itemStyle.normal.label.textStyle
  800. );
  801. opt.itemStyle.emphasis.label.textStyle = this.getTextStyle(
  802. opt.itemStyle.emphasis.label.textStyle
  803. );
  804. this.z = opt.z;
  805. this.zlevel = opt.zlevel;
  806. return opt;
  807. },
  808. /**
  809. * 刷新
  810. */
  811. refresh: function (newOption) {
  812. if (newOption) {
  813. this.option = newOption;
  814. this.series = newOption.series;
  815. }
  816. this.backupShapeList();
  817. this._buildShape();
  818. },
  819. /**
  820. * 动态数据增加动画
  821. */
  822. addDataAnimation: function (params, done) {
  823. var series = this.series;
  824. var aniMap = {}; // seriesIndex索引参数
  825. for (var i = 0, l = params.length; i < l; i++) {
  826. aniMap[params[i][0]] = params[i];
  827. }
  828. var aniCount = 0;
  829. function animationDone() {
  830. aniCount--;
  831. if (aniCount === 0) {
  832. done && done();
  833. }
  834. }
  835. // 构建新的饼图匹配差异做动画
  836. var sectorMap = {};
  837. var textMap = {};
  838. var lineMap = {};
  839. var backupShapeList = this.shapeList;
  840. this.shapeList = [];
  841. var seriesIndex;
  842. var isHead;
  843. var dataGrow;
  844. var deltaIdxMap = {}; // 修正新增数据后会对dataIndex产生错位匹配
  845. for (var i = 0, l = params.length; i < l; i++) {
  846. seriesIndex = params[i][0];
  847. isHead = params[i][2];
  848. dataGrow = params[i][3];
  849. if (series[seriesIndex]
  850. && series[seriesIndex].type === ecConfig.CHART_TYPE_PIE
  851. ) {
  852. if (isHead) {
  853. if (!dataGrow) {
  854. sectorMap[
  855. seriesIndex
  856. + '_'
  857. + series[seriesIndex].data.length
  858. ] = 'delete';
  859. }
  860. deltaIdxMap[seriesIndex] = 1;
  861. }
  862. else {
  863. if (!dataGrow) {
  864. sectorMap[seriesIndex + '_-1'] = 'delete';
  865. deltaIdxMap[seriesIndex] = -1;
  866. }
  867. else {
  868. deltaIdxMap[seriesIndex] = 0;
  869. }
  870. }
  871. this._buildSinglePie(seriesIndex);
  872. }
  873. }
  874. var dataIndex;
  875. var key;
  876. for (var i = 0, l = this.shapeList.length; i < l; i++) {
  877. seriesIndex = this.shapeList[i]._seriesIndex;
  878. dataIndex = this.shapeList[i]._dataIndex;
  879. key = seriesIndex + '_' + dataIndex;
  880. // map映射让n*n变n
  881. switch (this.shapeList[i].type) {
  882. case 'sector' :
  883. sectorMap[key] = this.shapeList[i];
  884. break;
  885. case 'text' :
  886. textMap[key] = this.shapeList[i];
  887. break;
  888. case 'polyline' :
  889. lineMap[key] = this.shapeList[i];
  890. break;
  891. }
  892. }
  893. this.shapeList = [];
  894. var targeSector;
  895. for (var i = 0, l = backupShapeList.length; i < l; i++) {
  896. seriesIndex = backupShapeList[i]._seriesIndex;
  897. if (aniMap[seriesIndex]) {
  898. dataIndex = backupShapeList[i]._dataIndex
  899. + deltaIdxMap[seriesIndex];
  900. key = seriesIndex + '_' + dataIndex;
  901. targeSector = sectorMap[key];
  902. if (!targeSector) {
  903. continue;
  904. }
  905. if (backupShapeList[i].type === 'sector') {
  906. if (targeSector != 'delete') {
  907. aniCount++;
  908. // 原有扇形
  909. this.zr.animate(backupShapeList[i].id, 'style')
  910. .when(
  911. 400,
  912. {
  913. startAngle: targeSector.style.startAngle,
  914. endAngle: targeSector.style.endAngle
  915. }
  916. )
  917. .done(animationDone)
  918. .start();
  919. }
  920. else {
  921. aniCount++;
  922. // 删除的扇形
  923. this.zr.animate(backupShapeList[i].id, 'style')
  924. .when(
  925. 400,
  926. deltaIdxMap[seriesIndex] < 0
  927. ? { startAngle: backupShapeList[i].style.startAngle }
  928. : { endAngle: backupShapeList[i].style.endAngle }
  929. )
  930. .done(animationDone)
  931. .start();
  932. }
  933. }
  934. else if (backupShapeList[i].type === 'text'
  935. || backupShapeList[i].type === 'polyline'
  936. ) {
  937. if (targeSector === 'delete') {
  938. // 删除逻辑一样
  939. this.zr.delShape(backupShapeList[i].id);
  940. }
  941. else {
  942. // 懒得新建变量了,借用一下
  943. switch (backupShapeList[i].type) {
  944. case 'text':
  945. aniCount++;
  946. targeSector = textMap[key];
  947. this.zr.animate(backupShapeList[i].id, 'style')
  948. .when(
  949. 400,
  950. {
  951. x :targeSector.style.x,
  952. y :targeSector.style.y
  953. }
  954. )
  955. .done(animationDone)
  956. .start();
  957. break;
  958. case 'polyline':
  959. aniCount++;
  960. targeSector = lineMap[key];
  961. this.zr.animate(backupShapeList[i].id, 'style')
  962. .when(
  963. 400,
  964. {
  965. pointList:targeSector.style.pointList
  966. }
  967. )
  968. .done(animationDone)
  969. .start();
  970. break;
  971. }
  972. }
  973. }
  974. }
  975. }
  976. this.shapeList = backupShapeList;
  977. // 没有动画
  978. if (!aniCount) {
  979. done && done();
  980. }
  981. },
  982. onclick: function (param) {
  983. var series = this.series;
  984. if (!this.isClick || !param.target) {
  985. // 没有在当前实例上发生点击直接返回
  986. return;
  987. }
  988. this.isClick = false;
  989. var offset; // 偏移
  990. var target = param.target;
  991. var style = target.style;
  992. var seriesIndex = ecData.get(target, 'seriesIndex');
  993. var dataIndex = ecData.get(target, 'dataIndex');
  994. for (var i = 0, len = this.shapeList.length; i < len; i++) {
  995. if (this.shapeList[i].id === target.id) {
  996. seriesIndex = ecData.get(target, 'seriesIndex');
  997. dataIndex = ecData.get(target, 'dataIndex');
  998. // 当前点击的
  999. if (!style._hasSelected) {
  1000. var midAngle =
  1001. ((style.startAngle + style.endAngle) / 2)
  1002. .toFixed(2) - 0;
  1003. target.style._hasSelected = true;
  1004. this._selected[seriesIndex][dataIndex] = true;
  1005. target.style._x = target.style.x;
  1006. target.style._y = target.style.y;
  1007. offset = this.query(
  1008. series[seriesIndex],
  1009. 'selectedOffset'
  1010. );
  1011. target.style.x += zrMath.cos(midAngle, true)
  1012. * offset;
  1013. target.style.y -= zrMath.sin(midAngle, true)
  1014. * offset;
  1015. }
  1016. else {
  1017. // 复位
  1018. target.style.x = target.style._x;
  1019. target.style.y = target.style._y;
  1020. target.style._hasSelected = false;
  1021. this._selected[seriesIndex][dataIndex] = false;
  1022. }
  1023. this.zr.modShape(target.id);
  1024. }
  1025. else if (this.shapeList[i].style._hasSelected
  1026. && this._selectedMode === 'single'
  1027. ) {
  1028. seriesIndex = ecData.get(this.shapeList[i], 'seriesIndex');
  1029. dataIndex = ecData.get(this.shapeList[i], 'dataIndex');
  1030. // 单选模式下需要取消其他已经选中的
  1031. this.shapeList[i].style.x = this.shapeList[i].style._x;
  1032. this.shapeList[i].style.y = this.shapeList[i].style._y;
  1033. this.shapeList[i].style._hasSelected = false;
  1034. this._selected[seriesIndex][dataIndex] = false;
  1035. this.zr.modShape(this.shapeList[i].id);
  1036. }
  1037. }
  1038. this.messageCenter.dispatch(
  1039. ecConfig.EVENT.PIE_SELECTED,
  1040. param.event,
  1041. {
  1042. selected: this._selected,
  1043. target: ecData.get(target, 'name')
  1044. },
  1045. this.myChart
  1046. );
  1047. this.zr.refreshNextFrame();
  1048. }
  1049. };
  1050. zrUtil.inherits(Pie, ChartBase);
  1051. // 图表注册
  1052. require('../chart').define('pie', Pie);
  1053. return Pie;
  1054. });