var Util = require('../../util'); var PolarLabels = require('./polar-labels'); var PathUtil = require('../util/path'); var Global = require('../../global'); var MARGIN = 5; function getEndPoint(center, angle, r) { return { x: center.x + r * Math.cos(angle), y: center.y + r * Math.sin(angle) }; } function antiCollision(labels, lineHeight, plotRange, center, isRight) { // adjust y position of labels to avoid overlapping var overlapping = true; var start = plotRange.start; var end = plotRange.end; var startY = Math.min(start.y, end.y); var totalHeight = Math.abs(start.y - end.y); var i; var maxY = 0; var minY = Number.MIN_VALUE; var boxes = labels.map(function (label) { if (label.y > maxY) { maxY = label.y; } if (label.y < minY) { minY = label.y; } return { size: lineHeight, targets: [label.y - startY] }; }); minY -= startY; if (maxY - startY > totalHeight) { totalHeight = maxY - startY; } while (overlapping) { /* eslint no-loop-func: 0 */ boxes.forEach(function (box) { var target = (Math.min.apply(minY, box.targets) + Math.max.apply(minY, box.targets)) / 2; box.pos = Math.min(Math.max(minY, target - box.size / 2), totalHeight - box.size); // box.pos = Math.max(0, target - box.size / 2); }); // detect overlapping and join boxes overlapping = false; i = boxes.length; while (i--) { if (i > 0) { var previousBox = boxes[i - 1]; var box = boxes[i]; if (previousBox.pos + previousBox.size > box.pos) { // overlapping previousBox.size += box.size; previousBox.targets = previousBox.targets.concat(box.targets); // overflow, shift up if (previousBox.pos + previousBox.size > totalHeight) { previousBox.pos = totalHeight - previousBox.size; } boxes.splice(i, 1); // removing box overlapping = true; } } } } i = 0; // step 4: normalize y and adjust x boxes.forEach(function (b) { var posInCompositeBox = startY + lineHeight / 2; // middle of the label b.targets.forEach(function () { labels[i].y = b.pos + posInCompositeBox; posInCompositeBox += lineHeight; i++; }); }); // (x - cx)^2 + (y - cy)^2 = totalR^2 labels.forEach(function (label) { var rPow2 = label.r * label.r; var dyPow2 = Math.pow(Math.abs(label.y - center.y), 2); if (rPow2 < dyPow2) { label.x = center.x; } else { var dx = Math.sqrt(rPow2 - dyPow2); if (!isRight) { // left label.x = center.x - dx; } else { // right label.x = center.x + dx; } } }); } var PieLabels = function PieLabels(cfg) { PieLabels.superclass.constructor.call(this, cfg); }; Util.extend(PieLabels, PolarLabels); Util.augment(PieLabels, { getDefaultCfg: function getDefaultCfg() { return { label: Global.thetaLabels }; }, getDefaultOffset: function getDefaultOffset(point) { return point.offset || 0; }, /** * @protected * to avoid overlapping * @param {Array} items labels to be placed * @return {Array} items */ adjustItems: function adjustItems(items) { var self = this; var offset = items[0] ? items[0].offset : 0; if (offset > 0) { items = self._distribute(items, offset); } return PieLabels.superclass.adjustItems.call(this, items); }, /** * @private * distribute labels * @param {Array} labels labels * @param {Number} offset offset * @return {Array} labels */ _distribute: function _distribute(labels, offset) { var self = this; var coord = self.get('coord'); var radius = coord.getRadius(); var lineHeight = self.get('label').labelHeight; var center = coord.getCenter(); var totalR = radius + offset; var totalHeight = totalR * 2 + lineHeight * 2; var plotRange = { start: coord.start, end: coord.end }; var geom = self.get('geom'); if (geom) { var view = geom.get('view'); plotRange = view.getViewRegion(); } // step 1: separate labels var halves = [[], // left [] // right ]; labels.forEach(function (label) { if (!label) { return; } if (label.textAlign === 'right') { // left halves[0].push(label); } else { // right or center will be put on the right side halves[1].push(label); } }); halves.forEach(function (half, index) { // step 2: reduce labels var maxLabelsCountForOneSide = parseInt(totalHeight / lineHeight, 10); if (half.length > maxLabelsCountForOneSide) { half.sort(function (a, b) { // sort by percentage DESC return b['..percent'] - a['..percent']; }); half.splice(maxLabelsCountForOneSide, half.length - maxLabelsCountForOneSide); } // step 3: distribute position (x and y) half.sort(function (a, b) { // sort by y ASC return a.y - b.y; }); antiCollision(half, lineHeight, plotRange, center, index); }); return halves[0].concat(halves[1]); }, // 连接线 lineToLabel: function lineToLabel(label) { var self = this; var coord = self.get('coord'); var r = coord.getRadius(); var distance = label.offset; var angle = label.orignAngle || label.angle; var center = coord.getCenter(); var start = getEndPoint(center, angle, r + MARGIN / 2); var inner = getEndPoint(center, angle, r + distance / 2); if (!label.labelLine) { label.labelLine = self.get('label').labelLine || {}; } label.labelLine.path = ['M' + start.x, start.y + ' Q' + inner.x, inner.y + ' ' + label.x, label.y].join(','); }, /** * @protected * get rotation for label * @param {Number} angle angle * @param {Number} offset offset * @return {Number} rotate */ getLabelRotate: function getLabelRotate(angle, offset) { var rotate; if (offset < 0) { rotate = angle * 180 / Math.PI; if (rotate > 90) { rotate = rotate - 180; } if (rotate < -90) { rotate = rotate + 180; } } return rotate / 180 * Math.PI; }, /** * @protected * get text align for label * @param {Object} point point * @return {String} align */ getLabelAlign: function getLabelAlign(point) { var self = this; var coord = self.get('coord'); var center = coord.getCenter(); var align; if (point.angle <= Math.PI / 2 && point.x >= center.x) { align = 'left'; } else { align = 'right'; } var offset = self.getDefaultOffset(point); if (offset <= 0) { if (align === 'right') { align = 'left'; } else { align = 'right'; } } return align; }, getArcPoint: function getArcPoint(point) { return point; }, getPointAngle: function getPointAngle(point) { var self = this; var coord = self.get('coord'); var startPoint = { x: Util.isArray(point.x) ? point.x[0] : point.x, y: point.y[0] }; self.transLabelPoint(startPoint); // 转换到画布坐标,如果坐标系发生改变 var endPoint = { x: Util.isArray(point.x) ? point.x[1] : point.x, y: point.y[1] }; self.transLabelPoint(endPoint); // 转换到画布坐标,如果坐标系发生改变 var angle; var startAngle = PathUtil.getPointAngle(coord, startPoint); if (point.points && point.points[0].y === point.points[1].y) { angle = startAngle; } else { var endAngle = PathUtil.getPointAngle(coord, endPoint); if (startAngle >= endAngle) { // 100% pie slice endAngle = endAngle + Math.PI * 2; } angle = startAngle + (endAngle - startAngle) / 2; } return angle; }, getCirclePoint: function getCirclePoint(angle, offset) { var self = this; var coord = self.get('coord'); var center = coord.getCenter(); var r = coord.getRadius() + offset; var point = getEndPoint(center, angle, r); point.angle = angle; point.r = r; return point; } }); module.exports = PieLabels;