使用 C3/D3 修复饼图标签重叠
Fix Piechart label overlap using C3/D3
根据我原来的问题 (),我试图将最初为 flot 创建的错误修正应用于 C3 饼图。
原则上它似乎有效,检测到碰撞并且正在移动标签但定位似乎不正确。
这是一些示例代码来显示问题
var columns = ['data11', 'data2', 'data347', 'data40098', 'data777'];
var data = [150, 250, 300, 50, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
var chart = c3.generate({
bindto: d3.select('#chart'),
data: {
columns: [
[columns[0]].concat(data[0])
],
type: 'pie',
},
legend: {
position: 'right',
show: true
},
pie: {
label: {
threshold: 0.001,
format: function(value, ratio, id) {
return [id, d3.format(",.0f")(value), "[" + d3.format(",.1%")(ratio) + "]"].join(';');
}
}
},
color: {
pattern: colors
},
onrendered: function() {
redrawLabelBackgrounds();
}
});
function addLabelBackground(index) {
//get label text element
var textLabel = d3.select(".c3-target-" + columns[index] + " > text");
//add rect to parent
var labelNode = textLabel.node();
if (labelNode /*&& labelNode.innerHTML.length > 0*/ ) {
var p = d3.select(labelNode.parentNode).insert("rect", "text")
.style("fill", colors[index]);
}
}
for (var i = 0; i < columns.length; i++) {
if (i > 0) {
setTimeout(function(column) {
chart.load({
columns: [
[columns[column]].concat(data[column]),
]
});
//chart.data.names(columnNames[column])
addLabelBackground(column);
}, (i * 5000 / columns.length), i);
} else {
addLabelBackground(i);
}
}
function redrawLabelBackgrounds() {
function isOverlapping(pos1, pos2) {
/*isOverlapping = (x1min < x2max AND x2min < x1max AND y1min < y2max AND y2min < y1max)
/*
* x1min = pos1[0][0]
* x1max = pos1[0][1]
* y1min = pos1[1][0]
* y1max = pos1[1][1]
*
* x2min = pos2[0][0]
* x2max = pos2[0][1]
* y2min = pos2[1][0]
* y2max = pos2[1][1]
*
* isOverlapping = (pos1[0][0] < pos2[0][1] AND pos2[0][0] < pos1[0][1] AND pos1[1][0] < pos2[1][1] AND pos2[1][0] < pos1[1][1])
*/
return (pos1[0][0] < pos2[0][1] && pos2[0][0] < pos1[0][1] && pos1[1][0] < pos2[1][1] && pos2[1][0] < pos1[1][1]);
}
function getAngle(pos) {
//Q1 && Q4
if ((pos[0] > 0 && pos[1] >= 0) || (pos[0] > 0 && pos[1] > 0)) {
return Math.atan(pos[0] / pos[1]);
}
//Q2
else if (pos[0] < 0 && pos[1] >= 0) {
return Math.atan(pos[0] / pos[1]) + Math.PI;
}
//Q3
else if (pos[0] < 0 && pos[1] <= 0) {
return Math.atan(pos[0] / pos[1]) - Math.PI;
}
// x = 0, y>0
else if (pos[0] === 0 && pos[1] > 0) {
return Math.PI / 2;
}
// x = 0, y<0
else if (pos[0] === 0 && pos[1] < 0) {
return Math.PI / -2;
}
// x= 0, y = 0
else {
return 0;
}
}
//for all label texts drawn yet
var labelSelection = d3.select('#chart').selectAll(".c3-chart-arc > text");
//first put all label nodes in one array
var allLabels = [];
labelSelection.each(function() {
allLabels.push(d3.select(this));
});
//then check and modify labels
labelSelection.each(function(v) {
// get d3 node
var label = d3.select(this);
var labelNode = label.node();
//check if label is drawn
if (labelNode) {
var bbox = labelNode.getBBox();
var labelTextHeight = bbox.height;
if (labelNode.childElementCount === 0 && labelNode.innerHTML.length > 0) {
//build data
var data = labelNode.innerHTML.split(';');
label.text("");
data.forEach(function(i, n) {
label.append("tspan")
.text(i)
.attr("dy", (n === 0) ? 0 : "1.2em")
.attr("x", 0)
.attr("text-anchor", "middle");
}, label);
}
//check if element is visible
if (d3.select(labelNode.parentNode).style("display") !== 'none') {
//get pos of the label text
var labelPos = label.attr("transform").match(/-?\d+(\.\d+)?/g);
if (labelPos && labelPos.length === 2) {
//get surrounding box of the label
bbox = labelNode.getBBox();
// modify the labelPos of the text - check to make sure that the label doesn't overlap one of the other labels
// check to make sure that the label doesn't overlap one of the other labels - 4.3.2014 - from flot user fix pie_label_ratio on github
var newRadius = Math.sqrt(labelPos[0] * labelPos[0] + labelPos[1] * labelPos[1]);
var angle = getAngle(labelPos);
var labelBottom = (labelPos[1] - bbox.height / 2); //
var labelLeft = (labelPos[0] - bbox.width / 2); //
var bCollision = false;
var labelBox = [
[labelLeft, labelLeft + bbox.width],
[labelBottom, labelBottom + bbox.height]
];
var yix = 10; //max label reiterations with collisions
do {
for (var i = allLabels.length - 1; i >= 0; i--) {
if (!labelNode.isEqualNode(allLabels[i].node())) {
var checkLabelBBox = allLabels[i].node().getBBox();
var checkLabelPos = allLabels[i].attr("transform").match(/-?\d+(\.\d+)?/g);
var checkLabelBox = [
[(checkLabelPos[0] - checkLabelBBox.width / 2), (checkLabelPos[0] - checkLabelBBox.width / 2) + checkLabelBBox.width],
[(checkLabelPos[1] - checkLabelBBox.height / 2), (checkLabelPos[1] - checkLabelBBox.height / 2) + checkLabelBBox.height]
];
while (isOverlapping(labelBox, checkLabelBox)) {
newRadius -= 2;
if (newRadius < 0.00) {
break;
}
x = Math.round(Math.cos(angle) * newRadius);
y = Math.round(Math.sin(angle) * newRadius);
labelBottom = (y - bbox.height / 2);
labelLeft = (x - bbox.width / 2);
labelBox[0][0] = labelLeft;
labelBox[0][1] = labelLeft + bbox.width;
labelBox[1][0] = labelBottom;
labelBox[1][1] = labelBottom + bbox.height;
bCollision = true;
}
if (bCollision) break;
}
}
if (bCollision) bCollision = false;
else break;
yix--;
}
while (yix > 0);
//now apply the potentially corrected positions to the label
if (labelPos[0] !== (labelLeft + bbox.width / 2) || labelpos[1] !== (labelBottom + bbox.height / 2)) {
labelPos[0] = labelLeft + bbox.width / 2;
labelPos[1] = labelBottom + bbox.height / 2;
label.attr("transform", "translate(" + labelPos[0] + ',' + labelPos[1] + ")");
}
//now draw and move the rects
d3.select(labelNode.parentNode).select("rect")
.attr("transform", "translate(" + (labelLeft - padding) +
"," + (labelPos[1] - labelTextHeight / 2 - padding) + ")")
.attr("width", bbox.width + 2 * padding)
.attr("height", bbox.height + 2 * padding);
}
}
}
});
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.css" rel="stylesheet" />
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.js"></script>
<div id="chart">
</div>
有人知道这里出了什么问题吗?
基本上它是几件事的结合:
- 计算角度(搞砸了 arctan ;))
- 检查矩形的重叠(不仅是文本框)
- 错误的循环
这现在有效:
var columns = ['data11', 'data2', 'data347', 'data40098', 'data777'];
var data = [150, 250, 300, 50, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
var chart = c3.generate({
bindto: d3.select('#chart'),
data: {
columns: [
[columns[0]].concat(data[0])
],
type: 'pie',
},
legend: {
position: 'right',
show: true
},
pie: {
label: {
threshold: 0.001,
format: function(value, ratio, id) {
return [id, d3.format(",.0f")(value), "[" + d3.format(",.1%")(ratio) + "]"].join(';');
}
}
},
color: {
pattern: colors
},
onrendered: function() {
redrawLabelBackgrounds();
}
});
function addLabelBackground(index) {
//get label text element
var textLabel = d3.select(".c3-target-" + columns[index] + " > text");
//add rect to parent
var labelNode = textLabel.node();
if (labelNode /*&& labelNode.innerHTML.length > 0*/ ) {
var p = d3.select(labelNode.parentNode).insert("rect", "text")
.style("fill", colors[index]);
}
}
for (var i = 0; i < columns.length; i++) {
if (i > 0) {
setTimeout(function(column) {
chart.load({
columns: [
[columns[column]].concat(data[column]),
]
});
//chart.data.names(columnNames[column])
addLabelBackground(column);
}, (i * 5000 / columns.length), i);
} else {
addLabelBackground(i);
}
}
function redrawLabelBackgrounds() {
function isOverlapping(pos1, pos2) {
/*isOverlapping = (x1min < x2max AND x2min < x1max AND y1min < y2max AND y2min < y1max)
/*
* x1min = pos1[0][0]
* x1max = pos1[0][1]
* y1min = pos1[1][0]
* y1max = pos1[1][1]
*
* x2min = pos2[0][0]
* x2max = pos2[0][1]
* y2min = pos2[1][0]
* y2max = pos2[1][1]
*
* isOverlapping = (pos1[0][0] < pos2[0][1] AND pos2[0][0] < pos1[0][1] AND pos1[1][0] < pos2[1][1] AND pos2[1][0] < pos1[1][1])
*/
return (pos1[0][0] < pos2[0][1] && pos2[0][0] < pos1[0][1] && pos1[1][0] < pos2[1][1] && pos2[1][0] < pos1[1][1]);
}
function getAngle(pos) {
//Q1
if ((pos[0] > 0 && pos[1] >= 0)) {
return Math.atan(pos[1] / pos[0]);
}
//Q2 & Q3
else if (pos[0] < 0) {
return Math.atan(pos[1] / pos[0]) + Math.PI;
} //Q4
else if (pos[0] > 0 && pos[1] < 0) {
return Math.atan(pos[1] / pos[0]) + 2 * Math.PI;
}
// x = 0, y>0
else if (pos[0] === 0 && pos[1] > 0) {
return Math.PI / 2;
}
// x = 0, y<0
else if (pos[0] === 0 && pos[1] < 0) {
return Math.PI / -2;
}
// x= 0, y = 0
else {
return 0;
}
}
//for all label texts drawn yet
var labelSelection = d3.select('#chart').selectAll(".c3-chart-arc > text");
//.filter(function(d){return d.style("display") !== 'none'});
//first put all label nodes in one array
var allLabels = [];
labelSelection.each(function() {
allLabels.push(d3.select(this));
});
//padding of the surrounding rect
var rectPadding = 1;
//then check and modify labels
labelSelection.each(function(v, labelIndex) {
// get d3 node
var label = d3.select(this);
var labelNode = label.node();
//check if label is drawn
if (labelNode) {
var bbox = labelNode.getBBox();
var labelTextHeight = bbox.height;
if (labelNode.childElementCount === 0 && labelNode.innerHTML.length > 0) {
//build data
var data = labelNode.innerHTML.split(';');
if (data.length > 1) {
label.html('')
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle");
data.forEach(function(i, n) {
label.append("tspan")
.text(i)
.attr("dy", (n === 0) ? 0 : "1.2em")
.attr("x", 0);
}, label);
}
}
//check if element is visible
if (d3.select(labelNode.parentNode).style("display") !== 'none') {
//get pos of the label text
var labelPos = label.attr("transform").match(/-?\d+(\.\d+)?/g);
if (labelPos && labelPos.length === 2) {
labelPos[0] = parseFloat(labelPos[0]);
labelPos[1] = parseFloat(labelPos[1]);
//get surrounding box of the label
bbox = labelNode.getBBox();
// modify the labelPos of the text - check to make sure that the label doesn't overlap one of the other labels
// check to make sure that the label doesn't overlap one of the other labels - 4.3.2014 - from flot user fix pie_label_ratio on github
var oldRadius = Math.sqrt(labelPos[0] * labelPos[0] + labelPos[1] * labelPos[1]);
var newRadius = oldRadius;
var angle = getAngle(labelPos);
var labelBottom = (labelPos[1] - bbox.height / 2); //
var labelLeft = (labelPos[0] - bbox.width / 2); //
var bCollision = false;
var labelBox = [ [ labelLeft - rectPadding, labelLeft + bbox.width + rectPadding ], [ labelBottom-rectPadding, labelBottom + bbox.height + rectPadding ] ];
var yix = 10; //max label reiterations with collisions
do {
for (var i = labelIndex - 1; i >= 0; i--) {
var checkLabelNode = allLabels[i].node();
var checkLabelBBox = checkLabelNode.getBBox();
var checkLabelPos = allLabels[i].attr("transform").match(/-?\d+(\.\d+)?/g);
if (checkLabelBBox && checkLabelPos) { //element visible
checkLabelPos[0] = parseFloat(checkLabelPos[0]);
checkLabelPos[1] = parseFloat(checkLabelPos[1]);
//box is text bbox + padding from rect
var checkLabelBox = [
[(checkLabelPos[0] - checkLabelBBox.width / 2) - rectPadding, (checkLabelPos[0] + checkLabelBBox.width / 2) + rectPadding],
[(checkLabelPos[1] - checkLabelBBox.height / 2) - rectPadding, (checkLabelPos[1] + checkLabelBBox.height / 2) + rectPadding]
];
while (isOverlapping(labelBox, checkLabelBox)) {
newRadius -= 2;
if (newRadius < 0.00) {
bCollision = true;
break;
}
x = Math.round(Math.cos(angle) * newRadius);
y = Math.round(Math.sin(angle) * newRadius);
labelBottom = (y - bbox.height / 2);
labelLeft = (x - bbox.width / 2);
labelBox[0][0] = labelLeft;
labelBox[0][1] = labelLeft + bbox.width;
labelBox[1][0] = labelBottom;
labelBox[1][1] = labelBottom + bbox.height;
}
if (bCollision) break;
}
}
if (bCollision) bCollision = false;
else break;
yix--;
}
while (yix > 0);
//now apply the potentially corrected positions to the label
if (Math.round(labelPos[0]) !== Math.round(labelLeft + bbox.width / 2) || Math.round(labelPos[1]) !== Math.round(labelBottom + bbox.height / 2)) {
/*console.log("moving label[" + labelIndex + "][" + labelPos[0] + "," + labelPos[1] + "] to [" + (labelLeft + bbox.width / 2) + "," + (labelBottom + bbox.height / 2) + "] Radius " + oldRadius + "=>" + newRadius + " #" + yix);*/
labelPos[0] = labelLeft + bbox.width / 2;
labelPos[1] = labelBottom + bbox.height / 2;
label.attr("transform", "translate(" + labelPos[0] + ',' + labelPos[1] + ")");
}
//now draw and move the rects
d3.select(labelNode.parentNode).select("rect")
.attr("transform", "translate(" + (labelLeft - rectPadding) +
"," + (labelPos[1] - labelTextHeight / 2 - rectPadding) + ")")
.attr("width", bbox.width + 2 * rectPadding)
.attr("height", bbox.height + 2 * rectPadding);
}
}
}
});
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.14/c3.min.css" rel="stylesheet" />
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.14/c3.min.js"></script>
<div id="chart">
</div>
可选待办事项:隐藏最后仍然重叠的标签
根据我原来的问题 (
var columns = ['data11', 'data2', 'data347', 'data40098', 'data777'];
var data = [150, 250, 300, 50, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
var chart = c3.generate({
bindto: d3.select('#chart'),
data: {
columns: [
[columns[0]].concat(data[0])
],
type: 'pie',
},
legend: {
position: 'right',
show: true
},
pie: {
label: {
threshold: 0.001,
format: function(value, ratio, id) {
return [id, d3.format(",.0f")(value), "[" + d3.format(",.1%")(ratio) + "]"].join(';');
}
}
},
color: {
pattern: colors
},
onrendered: function() {
redrawLabelBackgrounds();
}
});
function addLabelBackground(index) {
//get label text element
var textLabel = d3.select(".c3-target-" + columns[index] + " > text");
//add rect to parent
var labelNode = textLabel.node();
if (labelNode /*&& labelNode.innerHTML.length > 0*/ ) {
var p = d3.select(labelNode.parentNode).insert("rect", "text")
.style("fill", colors[index]);
}
}
for (var i = 0; i < columns.length; i++) {
if (i > 0) {
setTimeout(function(column) {
chart.load({
columns: [
[columns[column]].concat(data[column]),
]
});
//chart.data.names(columnNames[column])
addLabelBackground(column);
}, (i * 5000 / columns.length), i);
} else {
addLabelBackground(i);
}
}
function redrawLabelBackgrounds() {
function isOverlapping(pos1, pos2) {
/*isOverlapping = (x1min < x2max AND x2min < x1max AND y1min < y2max AND y2min < y1max)
/*
* x1min = pos1[0][0]
* x1max = pos1[0][1]
* y1min = pos1[1][0]
* y1max = pos1[1][1]
*
* x2min = pos2[0][0]
* x2max = pos2[0][1]
* y2min = pos2[1][0]
* y2max = pos2[1][1]
*
* isOverlapping = (pos1[0][0] < pos2[0][1] AND pos2[0][0] < pos1[0][1] AND pos1[1][0] < pos2[1][1] AND pos2[1][0] < pos1[1][1])
*/
return (pos1[0][0] < pos2[0][1] && pos2[0][0] < pos1[0][1] && pos1[1][0] < pos2[1][1] && pos2[1][0] < pos1[1][1]);
}
function getAngle(pos) {
//Q1 && Q4
if ((pos[0] > 0 && pos[1] >= 0) || (pos[0] > 0 && pos[1] > 0)) {
return Math.atan(pos[0] / pos[1]);
}
//Q2
else if (pos[0] < 0 && pos[1] >= 0) {
return Math.atan(pos[0] / pos[1]) + Math.PI;
}
//Q3
else if (pos[0] < 0 && pos[1] <= 0) {
return Math.atan(pos[0] / pos[1]) - Math.PI;
}
// x = 0, y>0
else if (pos[0] === 0 && pos[1] > 0) {
return Math.PI / 2;
}
// x = 0, y<0
else if (pos[0] === 0 && pos[1] < 0) {
return Math.PI / -2;
}
// x= 0, y = 0
else {
return 0;
}
}
//for all label texts drawn yet
var labelSelection = d3.select('#chart').selectAll(".c3-chart-arc > text");
//first put all label nodes in one array
var allLabels = [];
labelSelection.each(function() {
allLabels.push(d3.select(this));
});
//then check and modify labels
labelSelection.each(function(v) {
// get d3 node
var label = d3.select(this);
var labelNode = label.node();
//check if label is drawn
if (labelNode) {
var bbox = labelNode.getBBox();
var labelTextHeight = bbox.height;
if (labelNode.childElementCount === 0 && labelNode.innerHTML.length > 0) {
//build data
var data = labelNode.innerHTML.split(';');
label.text("");
data.forEach(function(i, n) {
label.append("tspan")
.text(i)
.attr("dy", (n === 0) ? 0 : "1.2em")
.attr("x", 0)
.attr("text-anchor", "middle");
}, label);
}
//check if element is visible
if (d3.select(labelNode.parentNode).style("display") !== 'none') {
//get pos of the label text
var labelPos = label.attr("transform").match(/-?\d+(\.\d+)?/g);
if (labelPos && labelPos.length === 2) {
//get surrounding box of the label
bbox = labelNode.getBBox();
// modify the labelPos of the text - check to make sure that the label doesn't overlap one of the other labels
// check to make sure that the label doesn't overlap one of the other labels - 4.3.2014 - from flot user fix pie_label_ratio on github
var newRadius = Math.sqrt(labelPos[0] * labelPos[0] + labelPos[1] * labelPos[1]);
var angle = getAngle(labelPos);
var labelBottom = (labelPos[1] - bbox.height / 2); //
var labelLeft = (labelPos[0] - bbox.width / 2); //
var bCollision = false;
var labelBox = [
[labelLeft, labelLeft + bbox.width],
[labelBottom, labelBottom + bbox.height]
];
var yix = 10; //max label reiterations with collisions
do {
for (var i = allLabels.length - 1; i >= 0; i--) {
if (!labelNode.isEqualNode(allLabels[i].node())) {
var checkLabelBBox = allLabels[i].node().getBBox();
var checkLabelPos = allLabels[i].attr("transform").match(/-?\d+(\.\d+)?/g);
var checkLabelBox = [
[(checkLabelPos[0] - checkLabelBBox.width / 2), (checkLabelPos[0] - checkLabelBBox.width / 2) + checkLabelBBox.width],
[(checkLabelPos[1] - checkLabelBBox.height / 2), (checkLabelPos[1] - checkLabelBBox.height / 2) + checkLabelBBox.height]
];
while (isOverlapping(labelBox, checkLabelBox)) {
newRadius -= 2;
if (newRadius < 0.00) {
break;
}
x = Math.round(Math.cos(angle) * newRadius);
y = Math.round(Math.sin(angle) * newRadius);
labelBottom = (y - bbox.height / 2);
labelLeft = (x - bbox.width / 2);
labelBox[0][0] = labelLeft;
labelBox[0][1] = labelLeft + bbox.width;
labelBox[1][0] = labelBottom;
labelBox[1][1] = labelBottom + bbox.height;
bCollision = true;
}
if (bCollision) break;
}
}
if (bCollision) bCollision = false;
else break;
yix--;
}
while (yix > 0);
//now apply the potentially corrected positions to the label
if (labelPos[0] !== (labelLeft + bbox.width / 2) || labelpos[1] !== (labelBottom + bbox.height / 2)) {
labelPos[0] = labelLeft + bbox.width / 2;
labelPos[1] = labelBottom + bbox.height / 2;
label.attr("transform", "translate(" + labelPos[0] + ',' + labelPos[1] + ")");
}
//now draw and move the rects
d3.select(labelNode.parentNode).select("rect")
.attr("transform", "translate(" + (labelLeft - padding) +
"," + (labelPos[1] - labelTextHeight / 2 - padding) + ")")
.attr("width", bbox.width + 2 * padding)
.attr("height", bbox.height + 2 * padding);
}
}
}
});
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.css" rel="stylesheet" />
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.js"></script>
<div id="chart">
</div>
有人知道这里出了什么问题吗?
基本上它是几件事的结合: - 计算角度(搞砸了 arctan ;)) - 检查矩形的重叠(不仅是文本框) - 错误的循环
这现在有效:
var columns = ['data11', 'data2', 'data347', 'data40098', 'data777'];
var data = [150, 250, 300, 50, 50];
var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B'];
var padding = 5;
var chart = c3.generate({
bindto: d3.select('#chart'),
data: {
columns: [
[columns[0]].concat(data[0])
],
type: 'pie',
},
legend: {
position: 'right',
show: true
},
pie: {
label: {
threshold: 0.001,
format: function(value, ratio, id) {
return [id, d3.format(",.0f")(value), "[" + d3.format(",.1%")(ratio) + "]"].join(';');
}
}
},
color: {
pattern: colors
},
onrendered: function() {
redrawLabelBackgrounds();
}
});
function addLabelBackground(index) {
//get label text element
var textLabel = d3.select(".c3-target-" + columns[index] + " > text");
//add rect to parent
var labelNode = textLabel.node();
if (labelNode /*&& labelNode.innerHTML.length > 0*/ ) {
var p = d3.select(labelNode.parentNode).insert("rect", "text")
.style("fill", colors[index]);
}
}
for (var i = 0; i < columns.length; i++) {
if (i > 0) {
setTimeout(function(column) {
chart.load({
columns: [
[columns[column]].concat(data[column]),
]
});
//chart.data.names(columnNames[column])
addLabelBackground(column);
}, (i * 5000 / columns.length), i);
} else {
addLabelBackground(i);
}
}
function redrawLabelBackgrounds() {
function isOverlapping(pos1, pos2) {
/*isOverlapping = (x1min < x2max AND x2min < x1max AND y1min < y2max AND y2min < y1max)
/*
* x1min = pos1[0][0]
* x1max = pos1[0][1]
* y1min = pos1[1][0]
* y1max = pos1[1][1]
*
* x2min = pos2[0][0]
* x2max = pos2[0][1]
* y2min = pos2[1][0]
* y2max = pos2[1][1]
*
* isOverlapping = (pos1[0][0] < pos2[0][1] AND pos2[0][0] < pos1[0][1] AND pos1[1][0] < pos2[1][1] AND pos2[1][0] < pos1[1][1])
*/
return (pos1[0][0] < pos2[0][1] && pos2[0][0] < pos1[0][1] && pos1[1][0] < pos2[1][1] && pos2[1][0] < pos1[1][1]);
}
function getAngle(pos) {
//Q1
if ((pos[0] > 0 && pos[1] >= 0)) {
return Math.atan(pos[1] / pos[0]);
}
//Q2 & Q3
else if (pos[0] < 0) {
return Math.atan(pos[1] / pos[0]) + Math.PI;
} //Q4
else if (pos[0] > 0 && pos[1] < 0) {
return Math.atan(pos[1] / pos[0]) + 2 * Math.PI;
}
// x = 0, y>0
else if (pos[0] === 0 && pos[1] > 0) {
return Math.PI / 2;
}
// x = 0, y<0
else if (pos[0] === 0 && pos[1] < 0) {
return Math.PI / -2;
}
// x= 0, y = 0
else {
return 0;
}
}
//for all label texts drawn yet
var labelSelection = d3.select('#chart').selectAll(".c3-chart-arc > text");
//.filter(function(d){return d.style("display") !== 'none'});
//first put all label nodes in one array
var allLabels = [];
labelSelection.each(function() {
allLabels.push(d3.select(this));
});
//padding of the surrounding rect
var rectPadding = 1;
//then check and modify labels
labelSelection.each(function(v, labelIndex) {
// get d3 node
var label = d3.select(this);
var labelNode = label.node();
//check if label is drawn
if (labelNode) {
var bbox = labelNode.getBBox();
var labelTextHeight = bbox.height;
if (labelNode.childElementCount === 0 && labelNode.innerHTML.length > 0) {
//build data
var data = labelNode.innerHTML.split(';');
if (data.length > 1) {
label.html('')
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle");
data.forEach(function(i, n) {
label.append("tspan")
.text(i)
.attr("dy", (n === 0) ? 0 : "1.2em")
.attr("x", 0);
}, label);
}
}
//check if element is visible
if (d3.select(labelNode.parentNode).style("display") !== 'none') {
//get pos of the label text
var labelPos = label.attr("transform").match(/-?\d+(\.\d+)?/g);
if (labelPos && labelPos.length === 2) {
labelPos[0] = parseFloat(labelPos[0]);
labelPos[1] = parseFloat(labelPos[1]);
//get surrounding box of the label
bbox = labelNode.getBBox();
// modify the labelPos of the text - check to make sure that the label doesn't overlap one of the other labels
// check to make sure that the label doesn't overlap one of the other labels - 4.3.2014 - from flot user fix pie_label_ratio on github
var oldRadius = Math.sqrt(labelPos[0] * labelPos[0] + labelPos[1] * labelPos[1]);
var newRadius = oldRadius;
var angle = getAngle(labelPos);
var labelBottom = (labelPos[1] - bbox.height / 2); //
var labelLeft = (labelPos[0] - bbox.width / 2); //
var bCollision = false;
var labelBox = [ [ labelLeft - rectPadding, labelLeft + bbox.width + rectPadding ], [ labelBottom-rectPadding, labelBottom + bbox.height + rectPadding ] ];
var yix = 10; //max label reiterations with collisions
do {
for (var i = labelIndex - 1; i >= 0; i--) {
var checkLabelNode = allLabels[i].node();
var checkLabelBBox = checkLabelNode.getBBox();
var checkLabelPos = allLabels[i].attr("transform").match(/-?\d+(\.\d+)?/g);
if (checkLabelBBox && checkLabelPos) { //element visible
checkLabelPos[0] = parseFloat(checkLabelPos[0]);
checkLabelPos[1] = parseFloat(checkLabelPos[1]);
//box is text bbox + padding from rect
var checkLabelBox = [
[(checkLabelPos[0] - checkLabelBBox.width / 2) - rectPadding, (checkLabelPos[0] + checkLabelBBox.width / 2) + rectPadding],
[(checkLabelPos[1] - checkLabelBBox.height / 2) - rectPadding, (checkLabelPos[1] + checkLabelBBox.height / 2) + rectPadding]
];
while (isOverlapping(labelBox, checkLabelBox)) {
newRadius -= 2;
if (newRadius < 0.00) {
bCollision = true;
break;
}
x = Math.round(Math.cos(angle) * newRadius);
y = Math.round(Math.sin(angle) * newRadius);
labelBottom = (y - bbox.height / 2);
labelLeft = (x - bbox.width / 2);
labelBox[0][0] = labelLeft;
labelBox[0][1] = labelLeft + bbox.width;
labelBox[1][0] = labelBottom;
labelBox[1][1] = labelBottom + bbox.height;
}
if (bCollision) break;
}
}
if (bCollision) bCollision = false;
else break;
yix--;
}
while (yix > 0);
//now apply the potentially corrected positions to the label
if (Math.round(labelPos[0]) !== Math.round(labelLeft + bbox.width / 2) || Math.round(labelPos[1]) !== Math.round(labelBottom + bbox.height / 2)) {
/*console.log("moving label[" + labelIndex + "][" + labelPos[0] + "," + labelPos[1] + "] to [" + (labelLeft + bbox.width / 2) + "," + (labelBottom + bbox.height / 2) + "] Radius " + oldRadius + "=>" + newRadius + " #" + yix);*/
labelPos[0] = labelLeft + bbox.width / 2;
labelPos[1] = labelBottom + bbox.height / 2;
label.attr("transform", "translate(" + labelPos[0] + ',' + labelPos[1] + ")");
}
//now draw and move the rects
d3.select(labelNode.parentNode).select("rect")
.attr("transform", "translate(" + (labelLeft - rectPadding) +
"," + (labelPos[1] - labelTextHeight / 2 - rectPadding) + ")")
.attr("width", bbox.width + 2 * rectPadding)
.attr("height", bbox.height + 2 * rectPadding);
}
}
}
});
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.14/c3.min.css" rel="stylesheet" />
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.14/c3.min.js"></script>
<div id="chart">
</div>
可选待办事项:隐藏最后仍然重叠的标签