pie-label.js 15 KB

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