Skip to content

Commit bdccf2b

Browse files
authored
Merge pull request #352 from xychang/pie_chart_fix
Pervent overlapping external labels in pie chart
2 parents a4f68e8 + c8471b1 commit bdccf2b

2 files changed

Lines changed: 65 additions & 13 deletions

File tree

examples/TestPieChart.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,18 @@ pie:
115115
hideLabelLessThan: 0.03
116116
showExtLabelOnlyIfNoLabel: true
117117
```
118+
119+
When there are multiple external labels, make sure they won't overlap with each other
120+
``` tracker
121+
searchType: task.done, task.notdone
122+
searchTarget: Say I love you, Say I love you
123+
datasetName: Done, NotDone
124+
pie:
125+
label: '{{0.5/11*100}}%, B {{0.4/11*100}}%, C {{0.1/11*100}}%, D {{8/11*100}}%, E {{9.7/11*100}}%, F {{0.3/28.5*100}}'
126+
extLabel: 'A {{0.5/11*100}}%, B {{0.4/11*100}}%, C {{0.1/11*100}}%, D {{8/11*100}}%, E {{9.7/11*100}}, F {{0.3/11*100}}%'
127+
data: '0.5, 0.4, 0.1, 8, 9.7, 0.3'
128+
dataColor: '#4daf4a,#377eb8,#ff7f00,#984ea3,#e41a1c,#aaaaaa'
129+
ratioInnerRadius: 0.4
130+
hideLabelLessThan: 0.03
131+
showExtLabelOnlyIfNoLabel: true
132+
```

src/pie.ts

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ function renderPie(
467467

468468
let pie = d3.pie();
469469
let pieValues = pie(values);
470+
pieValues.forEach(function (value: any, i: number) {value.input_index = i})
470471

471472
let sectors = sectorsGroup
472473
.selectAll("sector")
@@ -526,36 +527,72 @@ function renderPie(
526527
return arcObj.startAngle + (arcObj.endAngle - arcObj.startAngle) / 2;
527528
}
528529

530+
function externalLabelText(arcObj: any, i: number) {
531+
if (showExtLabelOnlyIfNoLabel) {
532+
if (labels[i] === "" || isLabelHidden(arcObj)) {
533+
return extLabels[i];
534+
}
535+
return "";
536+
} else {
537+
return extLabels[i];
538+
}
539+
}
540+
529541
// external label elements
542+
let prevBB : DOMRect = null;
543+
let extlabelPos : any = {};
530544
let extLabelElements = sectorsGroup
531545
.selectAll("extLabel")
532546
.data(pieValues)
533547
.enter()
534548
.append("text")
549+
// Sort external labels based on y value such that we can move down overlapping labels
550+
.sort(function (arcObj1: any, arcObj2: any) {
551+
return Math.cos(getMidAngle(arcObj2)) - Math.cos(getMidAngle(arcObj1));
552+
})
535553
.text(function (arcObj: any, i: number) {
536-
if (showExtLabelOnlyIfNoLabel) {
537-
if (labels[i] === "" || isLabelHidden(arcObj)) {
538-
return extLabels[i];
539-
}
540-
return "";
541-
} else {
542-
return extLabels[i];
543-
}
554+
i = arcObj.input_index;
555+
return externalLabelText(arcObj, i)
544556
})
545557
.attr("transform", function (arcObj: any, i: number) {
558+
i = arcObj.input_index;
559+
// If external label is empty, directly return.
560+
if (externalLabelText(arcObj, i).length == 0) {
561+
return;
562+
}
546563
let posLabel = hiddenArc.centroid(arcObj);
547564
let midAngle = getMidAngle(arcObj);
548565

549566
posLabel[0] =
550567
(radius * 0.99 - extLabelSizes[i].width) *
551568
(midAngle < Math.PI ? 1 : -1);
552-
return "translate(" + posLabel[0] + "," + posLabel[1] + ")";
569+
570+
var yshift = 0;
571+
let thisBB = new DOMRect(posLabel[0], posLabel[1], extLabelSizes[i].width, extLabelSizes[i].height);
572+
573+
if (prevBB !== null) {
574+
// Check whether there are overlaps
575+
if (!(thisBB.right < prevBB.left || prevBB.right < thisBB.left
576+
|| prevBB.bottom < thisBB.top)) {
577+
// Since y is sorted from low to high, we expect to shift this item further down
578+
yshift = prevBB.bottom - thisBB.top;
579+
// console.log("has overlap", yshift);
580+
}
581+
}
582+
if (yshift != 0) {
583+
thisBB = new DOMRect(posLabel[0], posLabel[1] + yshift, extLabelSizes[i].width, extLabelSizes[i].height);
584+
}
585+
prevBB = thisBB;
586+
// Save external label position for connection line plotting
587+
extlabelPos[i] = [posLabel[0], posLabel[1] + yshift]
588+
return "translate(" + posLabel[0] + "," + (posLabel[1] + yshift) + ")";
553589
})
554590
.style("text-anchor", function (arcObj: any) {
555591
let midAngle = getMidAngle(arcObj);
556592
return midAngle < Math.PI ? "start" : "end";
557593
})
558594
.attr("class", "tracker-pie-label");
595+
559596

560597
function getPointsForConnectionLines(arcObj: any, i: number) {
561598
let labelWidth = labelSizes[i].width;
@@ -565,7 +602,8 @@ function renderPie(
565602

566603
let posLabel = arc.centroid(arcObj); // line insertion in the slice
567604
let posMiddle = hiddenArc.centroid(arcObj); // line break: we use the other arc generator that has been built only for that
568-
let posExtLabel = hiddenArc.centroid(arcObj); // Label position = almost the same as posB
605+
let posExtLabel = extlabelPos[i] || hiddenArc.centroid(arcObj); // Label position = almost the same as posB
606+
posMiddle[1] = posExtLabel[1];
569607
// console.log(labels[i]);
570608
// console.log(`label/middle/extLabel: ${posLabel}/${posMiddle}/${posExtLabel}`);
571609

@@ -574,7 +612,7 @@ function renderPie(
574612
(posMiddle[1] - posLabel[1]) ** 2
575613
);
576614

577-
if (labels[i] !== "") {
615+
if (labels[i] !== "" && !labelHidden) {
578616
// shift posLabel, toward the middle point
579617
posLabel[0] =
580618
posLabel[0] +
@@ -584,8 +622,7 @@ function renderPie(
584622
((posMiddle[1] - posLabel[1]) * labelWidth) / distMiddleToLabel;
585623

586624
// shift posExtLabel
587-
posExtLabel[0] =
588-
(radius * 0.99 - extLabelWidth - 3) *
625+
posExtLabel[0] =posExtLabel[0] + (- 3) *
589626
(midAngle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left
590627
}
591628

0 commit comments

Comments
 (0)