pie-label.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. "use strict";
  2. var Util = require('../util/common');
  3. var _require = require('../graphic/'),
  4. Group = _require.Group;
  5. var DEFAULT_CFG = {
  6. anchorOffset: 5,
  7. // 锚点的偏移量
  8. inflectionOffset: 15,
  9. // 拐点的偏移量
  10. sidePadding: 20,
  11. // 文本距离画布四边的距离
  12. lineHeight: 32,
  13. // 文本的行高
  14. adjustOffset: 15,
  15. // 发生调整时的偏移量
  16. skipOverlapLabels: false,
  17. // 是否不展示重叠的文本
  18. triggerOn: 'touchstart',
  19. // 点击行为触发的时间类型
  20. activeShape: false,
  21. // 当有图形被选中的时候,是否激活图形
  22. activeStyle: {
  23. offset: 1,
  24. appendRadius: 8,
  25. fillOpacity: 0.5
  26. },
  27. label1OffsetY: -1,
  28. label2OffsetY: 1
  29. };
  30. function getEndPoint(center, angle, r) {
  31. return {
  32. x: center.x + r * Math.cos(angle),
  33. y: center.y + r * Math.sin(angle)
  34. };
  35. } // 计算中间角度
  36. function getMiddleAngle(startAngle, endAngle) {
  37. if (endAngle < startAngle) {
  38. endAngle += Math.PI * 2;
  39. }
  40. return (endAngle + startAngle) / 2;
  41. } // 判断两个矩形是否相交
  42. function isOverlap(label1, label2) {
  43. var label1BBox = label1.getBBox();
  44. var label2BBox = label2.getBBox();
  45. return Math.max(label1BBox.minX, label2BBox.minX) <= Math.min(label1BBox.maxX, label2BBox.minX) && Math.max(label1BBox.minY, label2BBox.minY) <= Math.min(label1BBox.maxY, label2BBox.maxY);
  46. }
  47. var controller = /*#__PURE__*/function () {
  48. function controller(cfg) {
  49. var _this = this;
  50. this._handleEvent = function (ev) {
  51. var self = _this;
  52. var chart = self.chart,
  53. drawnLabels = self.drawnLabels,
  54. pieLabelCfg = self.pieLabelCfg;
  55. var onClick = pieLabelCfg.onClick,
  56. activeShape = pieLabelCfg.activeShape;
  57. var canvasEvent = Util.createEvent(ev, chart);
  58. var x = canvasEvent.x,
  59. y = canvasEvent.y; // 查找被点击的 label
  60. var clickedShape;
  61. for (var i = 0, len = drawnLabels.length; i < len; i++) {
  62. var shape = drawnLabels[i];
  63. var bbox = shape.getBBox(); // 通过最小包围盒来判断击中情况
  64. if (x >= bbox.minX && x <= bbox.maxX && y >= bbox.minY && y <= bbox.maxY) {
  65. clickedShape = shape;
  66. break;
  67. }
  68. }
  69. var pieData = chart.getSnapRecords({
  70. x: x,
  71. y: y
  72. });
  73. if (clickedShape) {
  74. canvasEvent.data = clickedShape.get('data');
  75. } else if (pieData.length) {
  76. // 击中饼图扇形区域
  77. canvasEvent.data = pieData[0]._origin;
  78. }
  79. onClick && onClick(canvasEvent);
  80. canvasEvent.data && activeShape && _this._activeShape(canvasEvent.data);
  81. };
  82. Util.mix(this, cfg);
  83. var _chart = this.chart;
  84. this.canvasDom = _chart.get('canvas').get('el');
  85. }
  86. var _proto = controller.prototype;
  87. _proto.renderLabels = function renderLabels() {
  88. var self = this;
  89. var chart = self.chart,
  90. pieLabelCfg = self.pieLabelCfg,
  91. labelGroup = self.labelGroup;
  92. var halves = [[], // left
  93. [] // right
  94. ]; // 存储左右 labels
  95. var geom = chart.get('geoms')[0];
  96. var shapes = geom.get('container').get('children');
  97. var anchorOffset = pieLabelCfg.anchorOffset,
  98. inflectionOffset = pieLabelCfg.inflectionOffset,
  99. label1 = pieLabelCfg.label1,
  100. label2 = pieLabelCfg.label2,
  101. lineHeight = pieLabelCfg.lineHeight,
  102. skipOverlapLabels = pieLabelCfg.skipOverlapLabels,
  103. label1OffsetY = pieLabelCfg.label1OffsetY,
  104. label2OffsetY = pieLabelCfg.label2OffsetY;
  105. var coord = chart.get('coord');
  106. var center = coord.center,
  107. radius = coord.circleRadius;
  108. shapes.forEach(function (shape) {
  109. var _shape$_attrs$attrs = shape._attrs.attrs,
  110. startAngle = _shape$_attrs$attrs.startAngle,
  111. endAngle = _shape$_attrs$attrs.endAngle;
  112. var middleAngle = getMiddleAngle(startAngle, endAngle);
  113. var anchorPoint = getEndPoint(center, middleAngle, radius + anchorOffset);
  114. var inflectionPoint = getEndPoint(center, middleAngle, radius + inflectionOffset);
  115. var origin = shape.get('origin');
  116. var _origin = origin._origin,
  117. color = origin.color;
  118. var label = {
  119. _anchor: anchorPoint,
  120. _inflection: inflectionPoint,
  121. _data: _origin,
  122. x: inflectionPoint.x,
  123. y: inflectionPoint.y,
  124. r: radius + inflectionOffset,
  125. fill: color
  126. };
  127. var textGroup = new Group({
  128. context: chart.get('canvas').get('context'),
  129. // 兼容 node、小程序环境
  130. data: _origin // 存储原始数据
  131. });
  132. var textAttrs = {
  133. x: 0,
  134. y: 0,
  135. fontSize: 12,
  136. lineHeight: 12,
  137. fill: '#808080'
  138. };
  139. if (Util.isFunction(label1)) {
  140. textGroup.addShape('Text', {
  141. attrs: Util.mix({
  142. textBaseline: 'bottom'
  143. }, textAttrs, label1(_origin, color)),
  144. data: _origin,
  145. // 存储原始数据
  146. offsetY: label1OffsetY
  147. });
  148. }
  149. if (Util.isFunction(label2)) {
  150. textGroup.addShape('Text', {
  151. attrs: Util.mix({
  152. textBaseline: 'top'
  153. }, textAttrs, label2(_origin, color)),
  154. data: _origin,
  155. // 存储原始数据
  156. offsetY: label2OffsetY
  157. });
  158. }
  159. label.textGroup = textGroup; // 判断文本的方向
  160. if (anchorPoint.x < center.x) {
  161. label._side = 'left';
  162. halves[0].push(label);
  163. } else {
  164. label._side = 'right';
  165. halves[1].push(label);
  166. }
  167. });
  168. var drawnLabels = [];
  169. if (skipOverlapLabels) {
  170. var lastLabel; // 存储上一个 label 对象,用于检测文本是否重叠
  171. var labels = halves[1].concat(halves[0]); // 顺时针
  172. for (var i = 0, len = labels.length; i < len; i++) {
  173. var label = labels[i];
  174. var textGroup = self._drawLabel(label);
  175. if (lastLabel) {
  176. if (isOverlap(textGroup, lastLabel)) {
  177. // 重叠了就不绘制
  178. continue;
  179. }
  180. }
  181. labelGroup.add(textGroup);
  182. self._drawLabelLine(label);
  183. lastLabel = textGroup;
  184. drawnLabels.push(textGroup);
  185. }
  186. } else {
  187. var height = chart.get('height');
  188. var maxCountForOneSide = parseInt(height / lineHeight, 10);
  189. halves.forEach(function (half) {
  190. if (half.length > maxCountForOneSide) {
  191. half.splice(maxCountForOneSide, half.length - maxCountForOneSide);
  192. }
  193. half.sort(function (a, b) {
  194. return a.y - b.y;
  195. });
  196. var labels = self._antiCollision(half);
  197. drawnLabels = drawnLabels.concat(labels);
  198. });
  199. }
  200. this.drawnLabels = drawnLabels;
  201. };
  202. _proto.bindEvents = function bindEvents() {
  203. var pieLabelCfg = this.pieLabelCfg;
  204. var triggerOn = pieLabelCfg.triggerOn || 'touchstart';
  205. Util.addEventListener(this.canvasDom, triggerOn, this._handleEvent);
  206. };
  207. _proto.unBindEvents = function unBindEvents() {
  208. var pieLabelCfg = this.pieLabelCfg;
  209. var triggerOn = pieLabelCfg.triggerOn || 'touchstart';
  210. Util.removeEventListener(this.canvasDom, triggerOn, this._handleEvent);
  211. };
  212. _proto.clear = function clear() {
  213. this.labelGroup && this.labelGroup.clear();
  214. this.halo && this.halo.remove(true);
  215. this.lastSelectedData = null;
  216. this.drawnLabels = [];
  217. this.unBindEvents();
  218. };
  219. _proto._drawLabel = function _drawLabel(label) {
  220. var pieLabelCfg = this.pieLabelCfg,
  221. chart = this.chart;
  222. var canvasWidth = chart.get('width');
  223. var sidePadding = pieLabelCfg.sidePadding;
  224. var y = label.y,
  225. textGroup = label.textGroup;
  226. var children = textGroup.get('children');
  227. var textAttrs = {
  228. textAlign: label._side === 'left' ? 'left' : 'right',
  229. x: label._side === 'left' ? sidePadding : canvasWidth - sidePadding
  230. };
  231. children.forEach(function (child) {
  232. child.attr(textAttrs);
  233. child.attr('y', y + child.get('offsetY'));
  234. });
  235. return textGroup;
  236. };
  237. _proto._drawLabelLine = function _drawLabelLine(label, maxLabelWidth) {
  238. var chart = this.chart,
  239. pieLabelCfg = this.pieLabelCfg,
  240. labelGroup = this.labelGroup;
  241. var canvasWidth = chart.get('width');
  242. var sidePadding = pieLabelCfg.sidePadding,
  243. adjustOffset = pieLabelCfg.adjustOffset,
  244. lineStyle = pieLabelCfg.lineStyle,
  245. anchorStyle = pieLabelCfg.anchorStyle,
  246. skipOverlapLabels = pieLabelCfg.skipOverlapLabels;
  247. var _anchor = label._anchor,
  248. _inflection = label._inflection,
  249. fill = label.fill,
  250. y = label.y;
  251. var lastPoint = {
  252. x: label._side === 'left' ? sidePadding : canvasWidth - sidePadding,
  253. y: y
  254. };
  255. var points = [_anchor, _inflection, lastPoint];
  256. if (!skipOverlapLabels && _inflection.y !== y) {
  257. // 展示全部文本文本位置做过调整
  258. if (_inflection.y < y) {
  259. // 文本被调整下去了,则添加拐点连接线
  260. var point1 = _inflection;
  261. var point2 = {
  262. x: label._side === 'left' ? lastPoint.x + maxLabelWidth + adjustOffset : lastPoint.x - maxLabelWidth - adjustOffset,
  263. y: _inflection.y
  264. };
  265. var point3 = {
  266. x: label._side === 'left' ? lastPoint.x + maxLabelWidth : lastPoint.x - maxLabelWidth,
  267. y: lastPoint.y
  268. };
  269. points = [_anchor, point1, point2, point3, lastPoint];
  270. if (label._side === 'right' && point2.x < point1.x || label._side === 'left' && point2.x > point1.x) {
  271. points = [_anchor, point3, lastPoint];
  272. }
  273. } else {
  274. points = [_anchor, {
  275. x: _inflection.x,
  276. y: y
  277. }, lastPoint];
  278. }
  279. }
  280. labelGroup.addShape('Polyline', {
  281. attrs: Util.mix({
  282. points: points,
  283. lineWidth: 1,
  284. stroke: fill
  285. }, lineStyle)
  286. }); // 绘制锚点
  287. labelGroup.addShape('Circle', {
  288. attrs: Util.mix({
  289. x: _anchor.x,
  290. y: _anchor.y,
  291. r: 2,
  292. fill: fill
  293. }, anchorStyle)
  294. });
  295. };
  296. _proto._antiCollision = function _antiCollision(half) {
  297. var self = this;
  298. var chart = self.chart,
  299. pieLabelCfg = self.pieLabelCfg;
  300. var coord = chart.get('coord');
  301. var canvasHeight = chart.get('height');
  302. var center = coord.center,
  303. r = coord.circleRadius;
  304. var inflectionOffset = pieLabelCfg.inflectionOffset,
  305. lineHeight = pieLabelCfg.lineHeight;
  306. var startY = center.y - r - inflectionOffset - lineHeight;
  307. var overlapping = true;
  308. var totalH = canvasHeight;
  309. var i;
  310. var maxY = 0;
  311. var minY = Number.MIN_VALUE;
  312. var maxLabelWidth = 0;
  313. var boxes = half.map(function (label) {
  314. var labelY = label.y;
  315. if (labelY > maxY) {
  316. maxY = labelY;
  317. }
  318. if (labelY < minY) {
  319. minY = labelY;
  320. }
  321. var textGroup = label.textGroup;
  322. var labelWidth = textGroup.getBBox().width;
  323. if (labelWidth >= maxLabelWidth) {
  324. maxLabelWidth = labelWidth;
  325. }
  326. return {
  327. size: lineHeight,
  328. targets: [labelY - startY]
  329. };
  330. });
  331. if (maxY - startY > totalH) {
  332. totalH = maxY - startY;
  333. }
  334. var iteratorBoxed = function iteratorBoxed(boxes) {
  335. boxes.forEach(function (box) {
  336. var target = (Math.min.apply(minY, box.targets) + Math.max.apply(minY, box.targets)) / 2;
  337. box.pos = Math.min(Math.max(minY, target - box.size / 2), totalH - box.size);
  338. });
  339. };
  340. while (overlapping) {
  341. iteratorBoxed(boxes); // detect overlapping and join boxes
  342. overlapping = false;
  343. i = boxes.length;
  344. while (i--) {
  345. if (i > 0) {
  346. var previousBox = boxes[i - 1];
  347. var box = boxes[i];
  348. if (previousBox.pos + previousBox.size > box.pos) {
  349. // overlapping
  350. previousBox.size += box.size;
  351. previousBox.targets = previousBox.targets.concat(box.targets); // overflow, shift up
  352. if (previousBox.pos + previousBox.size > totalH) {
  353. previousBox.pos = totalH - previousBox.size;
  354. }
  355. boxes.splice(i, 1); // removing box
  356. overlapping = true;
  357. }
  358. }
  359. }
  360. }
  361. i = 0;
  362. boxes.forEach(function (b) {
  363. var posInCompositeBox = startY; // middle of the label
  364. b.targets.forEach(function () {
  365. half[i].y = b.pos + posInCompositeBox + lineHeight / 2;
  366. posInCompositeBox += lineHeight;
  367. i++;
  368. });
  369. });
  370. var drawnLabels = [];
  371. half.forEach(function (label) {
  372. var textGroup = self._drawLabel(label);
  373. var labelGroup = self.labelGroup;
  374. labelGroup.add(textGroup);
  375. self._drawLabelLine(label, maxLabelWidth);
  376. drawnLabels.push(textGroup);
  377. });
  378. return drawnLabels;
  379. };
  380. _proto._getSelectedShapeByData = function _getSelectedShapeByData(data) {
  381. var selectedShape = null;
  382. var chart = this.chart;
  383. var geom = chart.get('geoms')[0];
  384. var container = geom.get('container');
  385. var children = container.get('children');
  386. Util.each(children, function (child) {
  387. if (child.get('isShape') && child.get('className') === geom.get('type')) {
  388. // get geometry's shape
  389. var shapeData = child.get('origin')._origin;
  390. if (Util.isObjectValueEqual(shapeData, data)) {
  391. selectedShape = child;
  392. return false;
  393. }
  394. }
  395. });
  396. return selectedShape;
  397. };
  398. _proto._activeShape = function _activeShape(data) {
  399. var chart = this.chart,
  400. lastSelectedData = this.lastSelectedData,
  401. pieLabelCfg = this.pieLabelCfg;
  402. if (data === lastSelectedData) {
  403. return;
  404. }
  405. this.lastSelectedData = data;
  406. var activeStyle = pieLabelCfg.activeStyle;
  407. var selectedShape = this._getSelectedShapeByData(data);
  408. var _selectedShape$_attrs = selectedShape._attrs.attrs,
  409. x = _selectedShape$_attrs.x,
  410. y = _selectedShape$_attrs.y,
  411. startAngle = _selectedShape$_attrs.startAngle,
  412. endAngle = _selectedShape$_attrs.endAngle,
  413. r = _selectedShape$_attrs.r,
  414. fill = _selectedShape$_attrs.fill;
  415. var frontPlot = chart.get('frontPlot');
  416. this.halo && this.halo.remove(true);
  417. var halo = frontPlot.addShape('sector', {
  418. attrs: Util.mix({
  419. x: x,
  420. y: y,
  421. r: r + activeStyle.offset + activeStyle.appendRadius,
  422. r0: r + activeStyle.offset,
  423. fill: fill,
  424. startAngle: startAngle,
  425. endAngle: endAngle
  426. }, activeStyle)
  427. });
  428. this.halo = halo;
  429. chart.get('canvas').draw();
  430. };
  431. return controller;
  432. }();
  433. module.exports = {
  434. init: function init(chart) {
  435. var frontPlot = chart.get('frontPlot');
  436. var labelGroup = frontPlot.addGroup({
  437. className: 'pie-label',
  438. zIndex: 0
  439. });
  440. var pieLabelController = new controller({
  441. chart: chart,
  442. labelGroup: labelGroup
  443. });
  444. chart.set('pieLabelController', pieLabelController);
  445. chart.pieLabel = function (cfg) {
  446. cfg = Util.deepMix({}, DEFAULT_CFG, cfg);
  447. pieLabelController.pieLabelCfg = cfg;
  448. return this;
  449. };
  450. },
  451. afterGeomDraw: function afterGeomDraw(chart) {
  452. var controller = chart.get('pieLabelController');
  453. if (controller.pieLabelCfg) {
  454. // 用户配置了饼图文本
  455. controller.renderLabels();
  456. controller.bindEvents(); // 绑定事件
  457. }
  458. },
  459. clearInner: function clearInner(chart) {
  460. var controller = chart.get('pieLabelController');
  461. if (controller.pieLabelCfg) {
  462. // 用户配置了饼图文本
  463. controller.clear();
  464. }
  465. }
  466. };