Skip to content

Commit fbfde1b

Browse files
fix(funnel/waterfall/pie/sunburst): respect textinfo token order
Replaces hardcoded hasFlag() checks with a loop over user-specified parts[] so textinfo tokens render in the order the user requested. Applies to funnel/waterfall (bar/plot.js), pie, and sunburst traces. Fixes #7751 Co-authored-by: Abinesh N <abineshabee2@gmail.com>
1 parent df7ca4b commit fbfde1b

7 files changed

Lines changed: 145 additions & 111 deletions

File tree

src/traces/bar/plot.js

Lines changed: 28 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,52 +1105,43 @@ function calcTextinfo(cd, index, xa, ya) {
11051105
var text = [];
11061106
var tx;
11071107

1108-
var hasFlag = function (flag) {
1108+
function hasFlag(flag) {
11091109
return parts.indexOf(flag) !== -1;
1110-
};
1111-
1112-
if (hasFlag('label')) {
1113-
text.push(formatLabel(cd[index].p));
11141110
}
11151111

1116-
if (hasFlag('text')) {
1117-
tx = Lib.castOption(trace, cdi.i, 'text');
1118-
if (tx === 0 || tx) text.push(tx);
1112+
var nPercent = 0;
1113+
if(isFunnel) {
1114+
if(hasFlag('percent initial')) nPercent++;
1115+
if(hasFlag('percent previous')) nPercent++;
1116+
if(hasFlag('percent total')) nPercent++;
11191117
}
1120-
1121-
if (isWaterfall) {
1122-
var delta = +cdi.rawS || cdi.s;
1123-
var final = cdi.v;
1124-
var initial = final - delta;
1125-
1126-
if (hasFlag('initial')) text.push(formatNumber(initial));
1127-
if (hasFlag('delta')) text.push(formatNumber(delta));
1128-
if (hasFlag('final')) text.push(formatNumber(final));
1129-
}
1130-
1131-
if (isFunnel) {
1132-
if (hasFlag('value')) text.push(formatNumber(cdi.s));
1133-
1134-
var nPercent = 0;
1135-
if (hasFlag('percent initial')) nPercent++;
1136-
if (hasFlag('percent previous')) nPercent++;
1137-
if (hasFlag('percent total')) nPercent++;
1138-
1139-
var hasMultiplePercents = nPercent > 1;
1140-
1141-
if (hasFlag('percent initial')) {
1118+
var hasMultiplePercents = nPercent > 1;
1119+
for(var i in parts) {
1120+
var part = parts[i];
1121+
if(part === 'label') {
1122+
text.push(formatLabel(cdi.p));
1123+
} else if(part === 'text') {
1124+
tx = Lib.castOption(trace, cdi.i, 'text');
1125+
if(tx === 0 || tx) text.push(tx);
1126+
} else if(isWaterfall && part === 'initial') {
1127+
text.push(formatNumber(cdi.v - (+cdi.rawS || cdi.s)));
1128+
} else if(isWaterfall && part === 'delta') {
1129+
text.push(formatNumber(+cdi.rawS || cdi.s));
1130+
} else if(isWaterfall && part === 'final') {
1131+
text.push(formatNumber(cdi.v));
1132+
} else if(isFunnel && part === 'value') {
1133+
text.push(formatNumber(cdi.s));
1134+
} else if(isFunnel && part === 'percent initial') {
11421135
tx = Lib.formatPercent(cdi.begR);
1143-
if (hasMultiplePercents) tx += ' of initial';
1136+
if(hasMultiplePercents) tx += ' of initial';
11441137
text.push(tx);
1145-
}
1146-
if (hasFlag('percent previous')) {
1138+
} else if(isFunnel && part === 'percent previous') {
11471139
tx = Lib.formatPercent(cdi.difR);
1148-
if (hasMultiplePercents) tx += ' of previous';
1140+
if(hasMultiplePercents) tx += ' of previous';
11491141
text.push(tx);
1150-
}
1151-
if (hasFlag('percent total')) {
1142+
} else if(isFunnel && part === 'percent total') {
11521143
tx = Lib.formatPercent(cdi.sumR);
1153-
if (hasMultiplePercents) tx += ' of total';
1144+
if(hasMultiplePercents) tx += ' of total';
11541145
text.push(tx);
11551146
}
11561147
}

src/traces/pie/plot.js

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,24 +1229,22 @@ function formatSliceLabel(gd, pt, cd0) {
12291229
var textinfo = trace.textinfo;
12301230
if (!texttemplate && textinfo && textinfo !== 'none') {
12311231
var parts = textinfo.split('+');
1232-
var hasFlag = function (flag) {
1233-
return parts.indexOf(flag) !== -1;
1234-
};
1235-
var hasLabel = hasFlag('label');
1236-
var hasText = hasFlag('text');
1237-
var hasValue = hasFlag('value');
1238-
var hasPercent = hasFlag('percent');
1239-
12401232
var separators = fullLayout.separators;
1241-
var text;
1242-
1243-
text = hasLabel ? [pt.label] : [];
1244-
if (hasText) {
1245-
var tx = helpers.getFirstFilled(trace.text, pt.pts);
1246-
if (isValidTextValue(tx)) text.push(tx);
1233+
var text = [];
1234+
var tx;
1235+
for(var i in parts) {
1236+
var part = parts[i];
1237+
if(part === 'label') {
1238+
text.push(pt.label);
1239+
} else if(part === 'text') {
1240+
tx = helpers.getFirstFilled(trace.text, pt.pts);
1241+
if(isValidTextValue(tx)) text.push(tx);
1242+
} else if(part === 'value') {
1243+
text.push(helpers.formatPieValue(pt.v, separators));
1244+
} else if(part === 'percent') {
1245+
text.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators));
1246+
}
12471247
}
1248-
if (hasValue) text.push(helpers.formatPieValue(pt.v, separators));
1249-
if (hasPercent) text.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators));
12501248
pt.text = text.join('<br>');
12511249
}
12521250

src/traces/sunburst/plot.js

Lines changed: 27 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -544,58 +544,39 @@ exports.formatSliceLabel = function (pt, entry, trace, cd, fullLayout) {
544544

545545
if (!texttemplate) {
546546
var parts = textinfo.split('+');
547-
var hasFlag = function (flag) {
548-
return parts.indexOf(flag) !== -1;
549-
};
550547
var thisText = [];
551548
var tx;
552549

553-
if (hasFlag('label') && cdi.label) {
554-
thisText.push(cdi.label);
555-
}
556-
557-
if (cdi.hasOwnProperty('v') && hasFlag('value')) {
558-
thisText.push(helpers.formatValue(cdi.v, separators));
550+
var nPercent = 0;
551+
for(var p = 0; p < parts.length; p++) {
552+
var f = parts[p];
553+
if(f === 'percent parent' || f === 'percent entry' || f === 'percent root') nPercent++;
559554
}
560-
561-
if (!isRoot) {
562-
if (hasFlag('current path')) {
555+
var hasMultiplePercents = nPercent > 1;
556+
for(var i in parts) {
557+
var part = parts[i];
558+
if(part === 'label' && cdi.label) {
559+
thisText.push(cdi.label);
560+
} else if(part === 'value' && cdi.hasOwnProperty('v')) {
561+
thisText.push(helpers.formatValue(cdi.v, separators));
562+
} else if(part === 'text') {
563+
tx = Lib.castOption(trace, cdi.i, 'text');
564+
if(Lib.isValidTextValue(tx)) thisText.push(tx);
565+
} else if(!isRoot && part === 'current path') {
563566
thisText.push(helpers.getPath(pt.data));
567+
} else if(!isRoot && part === 'percent parent') {
568+
tx = helpers.formatPercent(val / helpers.getValue(parent), separators);
569+
if(hasMultiplePercents) tx += ' of parent';
570+
thisText.push(tx);
571+
} else if(!isRoot && part === 'percent entry') {
572+
tx = helpers.formatPercent(val / helpers.getValue(entry), separators);
573+
if(hasMultiplePercents) tx += ' of entry';
574+
thisText.push(tx);
575+
} else if(!isRoot && part === 'percent root') {
576+
tx = helpers.formatPercent(val / helpers.getValue(hierarchy), separators);
577+
if(hasMultiplePercents) tx += ' of root';
578+
thisText.push(tx);
564579
}
565-
566-
var nPercent = 0;
567-
if (hasFlag('percent parent')) nPercent++;
568-
if (hasFlag('percent entry')) nPercent++;
569-
if (hasFlag('percent root')) nPercent++;
570-
var hasMultiplePercents = nPercent > 1;
571-
572-
if (nPercent) {
573-
var percent;
574-
var addPercent = function (key) {
575-
tx = helpers.formatPercent(percent, separators);
576-
577-
if (hasMultiplePercents) tx += ' of ' + key;
578-
thisText.push(tx);
579-
};
580-
581-
if (hasFlag('percent parent') && !isRoot) {
582-
percent = val / helpers.getValue(parent);
583-
addPercent('parent');
584-
}
585-
if (hasFlag('percent entry')) {
586-
percent = val / helpers.getValue(entry);
587-
addPercent('entry');
588-
}
589-
if (hasFlag('percent root')) {
590-
percent = val / helpers.getValue(hierarchy);
591-
addPercent('root');
592-
}
593-
}
594-
}
595-
596-
if (hasFlag('text')) {
597-
tx = Lib.castOption(trace, cdi.i, 'text');
598-
if (Lib.isValidTextValue(tx)) thisText.push(tx);
599580
}
600581

601582
return thisText.join('<br>');

test/jasmine/tests/funnel_test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,4 +1757,25 @@ describe('funnel uniformtext', function() {
17571757
}))
17581758
.then(done, done.fail);
17591759
});
1760+
1761+
it('should respect textinfo token order', function(done) {
1762+
Plotly.newPlot(gd, [{
1763+
type: 'funnel',
1764+
y: ['Awareness', 'Interest', 'Action'],
1765+
x: [1000, 700, 400],
1766+
textinfo: 'percent initial+value'
1767+
}], {})
1768+
.then(function() {
1769+
var textEls = gd.querySelectorAll('text.bartext');
1770+
var textContent = Array.from(textEls).map(function(el) {
1771+
return el.textContent;
1772+
});
1773+
expect(textContent.length).toBe(3);
1774+
expect(textContent[0]).toBe('100%1000');
1775+
expect(textContent[1]).toBe('70%700');
1776+
expect(textContent[2]).toBe('40%400');
1777+
})
1778+
.then(done, done.fail);
1779+
});
1780+
17601781
});

test/jasmine/tests/pie_test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,27 @@ describe('Pie traces', function() {
909909
}))
910910
.then(done, done.fail);
911911
});
912+
913+
it('should respect textinfo token order', function(done) {
914+
Plotly.newPlot(gd, [{
915+
type: 'pie',
916+
labels: ['A', 'B', 'C'],
917+
values: [10, 20, 70],
918+
textinfo: 'percent+label',
919+
sort: false
920+
}], {})
921+
.then(function() {
922+
var textEls = gd.querySelectorAll('text.slicetext');
923+
var textContent = Array.from(textEls).map(function(el) {
924+
return el.textContent;
925+
});
926+
expect(textContent.length).toBe(3);
927+
expect(textContent[0]).toBe('10%A');
928+
expect(textContent[1]).toBe('20%B');
929+
expect(textContent[2]).toBe('70%C');
930+
})
931+
.then(done, done.fail);
932+
});
912933
});
913934

914935
describe('Pie texttemplate:', function() {

test/jasmine/tests/sunburst_test.js

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,7 +1162,7 @@ describe('Test sunburst restyle:', function () {
11621162
}
11631163

11641164
Plotly.newPlot(gd, mock)
1165-
.then(_assert('base', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3']))
1165+
.then(_assert('base', ['node0\nRoot', 'node2\nB', 'node1\nA', 'node3\nb']))
11661166
.then(function () {
11671167
spyOn(Plots, 'doCalcdata').and.callThrough();
11681168
})
@@ -1173,32 +1173,54 @@ describe('Test sunburst restyle:', function () {
11731173
.then(_restyle({ textinfo: 'none' }))
11741174
.then(_assert('no textinfo', ['', '', '', '']))
11751175
.then(_restyle({ textinfo: 'label+text+value' }))
1176-
.then(_assert('show everything', ['Root\n0\nnode0', 'B\n2\nnode2', 'A\n1\nnode1', 'b\n3\nnode3']))
1176+
.then(_assert('show everything', ['Root\nnode0\n0', 'B\nnode2\n2', 'A\nnode1\n1', 'b\nnode3\n3']))
11771177
.then(_restyle({ textinfo: null }))
1178-
.then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3']))
1178+
.then(_assert('back to dflt', ['node0\nRoot', 'node2\nB', 'node1\nA', 'node3\nb']))
11791179
// now change insidetextorientation to 'horizontal'
11801180
.then(_restyle({ insidetextorientation: 'horizontal' }))
1181-
.then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3']))
1181+
.then(_assert('back to dflt', ['node0\nRoot', 'node2\nB', 'node1\nA', 'node3\nb']))
11821182
.then(_restyle({ textinfo: 'none' }))
11831183
.then(_assert('no textinfo', ['', '', '', '']))
11841184
.then(_restyle({ textinfo: null }))
1185-
.then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3']))
1185+
.then(_assert('back to dflt', ['node0\nRoot', 'node2\nB', 'node1\nA', 'node3\nb']))
11861186
// now change insidetextorientation to 'tangential'
11871187
.then(_restyle({ insidetextorientation: 'tangential' }))
1188-
.then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3']))
1188+
.then(_assert('back to dflt', ['node0\nRoot', 'node2\nB', 'node1\nA', 'node3\nb']))
11891189
.then(_restyle({ textinfo: 'none' }))
11901190
.then(_assert('no textinfo', ['', '', '', '']))
11911191
.then(_restyle({ textinfo: null }))
1192-
.then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3']))
1192+
.then(_assert('back to dflt', ['node0\nRoot', 'node2\nB', 'node1\nA', 'node3\nb']))
11931193
// now change insidetextorientation to 'radial'
11941194
.then(_restyle({ insidetextorientation: 'radial' }))
1195-
.then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3']))
1195+
.then(_assert('back to dflt', ['node0\nRoot', 'node2\nB', 'node1\nA', 'node3\nb']))
11961196
.then(_restyle({ textinfo: 'none' }))
11971197
.then(_assert('no textinfo', ['', '', '', '']))
11981198
.then(_restyle({ textinfo: null }))
1199-
.then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3']))
1199+
.then(_assert('back to dflt', ['node0\nRoot', 'node2\nB', 'node1\nA', 'node3\nb']))
12001200
.then(done, done.fail);
12011201
});
1202+
1203+
it('should respect textinfo token order', function(done) {
1204+
Plotly.newPlot(gd, [{
1205+
type: 'sunburst',
1206+
labels: ['Root', 'A', 'B'],
1207+
parents: ['', 'Root', 'Root'],
1208+
values: [0, 10, 20],
1209+
textinfo: 'percent root+value+label'
1210+
}], {})
1211+
.then(function() {
1212+
var layer = d3Select(gd).select('.sunburstlayer');
1213+
var textContent = [];
1214+
layer.selectAll('text.slicetext').each(function() {
1215+
textContent.push(this.textContent);
1216+
});
1217+
expect(textContent.length).toBe(3);
1218+
expect(textContent[0]).toBe('0Root');
1219+
expect(textContent[1]).toBe('67%20B');
1220+
expect(textContent[2]).toBe('33%10A');
1221+
})
1222+
.then(done, done.fail);
1223+
});
12021224
});
12031225

12041226
describe('Test sunburst tweening:', function () {

test/jasmine/tests/treemap_test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,7 +1431,7 @@ describe('Test treemap restyle:', function () {
14311431
}
14321432

14331433
Plotly.newPlot(gd, mock)
1434-
.then(_assert('base', ['Root', 'B', 'A\nnode1', 'b\nnode3']))
1434+
.then(_assert('base', ['Root', 'B', 'node1\nA', 'node3\nb']))
14351435
.then(function () {
14361436
spyOn(Plots, 'doCalcdata').and.callThrough();
14371437
})
@@ -1442,9 +1442,9 @@ describe('Test treemap restyle:', function () {
14421442
.then(_restyle({ textinfo: 'none' }))
14431443
.then(_assert('no textinfo', ['Root', 'B', ' ', ' '])) // use one space character instead of a blank string to avoid jumps during transition
14441444
.then(_restyle({ textinfo: 'label+text+value' }))
1445-
.then(_assert('show everything', ['Root', 'B', 'A\n1\nnode1', 'b\n3\nnode3']))
1445+
.then(_assert('show everything', ['Root', 'B', 'A\nnode1\n1', 'b\nnode3\n3']))
14461446
.then(_restyle({ textinfo: null }))
1447-
.then(_assert('back to dflt', ['Root', 'B', 'A\nnode1', 'b\nnode3']))
1447+
.then(_assert('back to dflt', ['Root', 'B', 'node1\nA', 'node3\nb']))
14481448
.then(done, done.fail);
14491449
});
14501450

0 commit comments

Comments
 (0)