328 lines
8.0 KiB
Java
328 lines
8.0 KiB
Java
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; |