Skip to content

Commit b2df371

Browse files
authored
Merge pull request #6189 from plotly/improve-box-violin-hoverlabels
revise `box` & `violin` hover labels - improve order & handle duplicates
2 parents 1ec75d5 + 3e25576 commit b2df371

File tree

6 files changed

+174
-88
lines changed

6 files changed

+174
-88
lines changed

draftlogs/6189_fix.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Fix undesirable missing hover labels of `box` & `violin` traces [[#6189](https://github.com/plotly/plotly.js/pull/6189)]

src/traces/box/hover.js

+33-16
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) {
4444
var trace = cd[0].trace;
4545
var t = cd[0].t;
4646
var isViolin = trace.type === 'violin';
47-
var closeBoxData = [];
4847

4948
var pLetter, vLetter, pAxis, vAxis, vVal, pVal, dx, dy, dPos,
5049
hoverPseudoDistance, spikePseudoDistance;
@@ -141,22 +140,30 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) {
141140
pointData.spikeDistance = dxy(di) * spikePseudoDistance / hoverPseudoDistance;
142141
pointData[spikePosAttr] = pAxis.c2p(di.pos, true);
143142

144-
// box plots: each "point" gets many labels
145-
var usedVals = {};
146-
var attrs = ['med', 'q1', 'q3', 'min', 'max'];
143+
var hasMean = trace.boxmean || (trace.meanline || {}).visible;
144+
var hasFences = trace.boxpoints || trace.points;
147145

148-
if(trace.boxmean || (trace.meanline || {}).visible) {
149-
attrs.push('mean');
150-
}
151-
if(trace.boxpoints || trace.points) {
152-
attrs.push('lf', 'uf');
146+
// labels with equal values (e.g. when min === q1) should still be presented in the order they have when they're unequal
147+
var attrs =
148+
(hasFences && hasMean) ? ['max', 'uf', 'q3', 'med', 'mean', 'q1', 'lf', 'min'] :
149+
(hasFences && !hasMean) ? ['max', 'uf', 'q3', 'med', 'q1', 'lf', 'min'] :
150+
(!hasFences && hasMean) ? ['max', 'q3', 'med', 'mean', 'q1', 'min'] :
151+
['max', 'q3', 'med', 'q1', 'min'];
152+
153+
var rev = vAxis.range[1] < vAxis.range[0];
154+
155+
if(trace.orientation === (rev ? 'v' : 'h')) {
156+
attrs.reverse();
153157
}
154158

159+
var spikeDistance = pointData.spikeDistance;
160+
var spikePosition = pointData[spikePosAttr];
161+
162+
var closeBoxData = [];
155163
for(var i = 0; i < attrs.length; i++) {
156164
var attr = attrs[i];
157165

158-
if(!(attr in di) || (di[attr] in usedVals)) continue;
159-
usedVals[di[attr]] = true;
166+
if(!(attr in di)) continue;
160167

161168
// copy out to a new object for each value to label
162169
var val = di[attr];
@@ -176,17 +183,27 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) {
176183
pointData2[vLetter + 'err'] = di.sd;
177184
}
178185

179-
// only keep name and spikes on the first item (median)
180-
pointData.name = '';
181-
pointData.spikeDistance = undefined;
182-
pointData[spikePosAttr] = undefined;
183-
184186
// no hovertemplate support yet
185187
pointData2.hovertemplate = false;
186188

187189
closeBoxData.push(pointData2);
188190
}
189191

192+
// only keep name and spikes on the median
193+
pointData.name = '';
194+
pointData.spikeDistance = undefined;
195+
pointData[spikePosAttr] = undefined;
196+
for(var k = 0; k < closeBoxData.length; k++) {
197+
if(closeBoxData[k].attr !== 'med') {
198+
closeBoxData[k].name = '';
199+
closeBoxData[k].spikeDistance = undefined;
200+
closeBoxData[k][spikePosAttr] = undefined;
201+
} else {
202+
closeBoxData[k].spikeDistance = spikeDistance;
203+
closeBoxData[k][spikePosAttr] = spikePosition;
204+
}
205+
}
206+
190207
return closeBoxData;
191208
}
192209

src/traces/violin/hover.js

+12-4
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,19 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode, opts) {
5656
kdePointData[vLetter + 'Label'] = vLetter + ': ' + Axes.hoverLabelText(vAxis, vVal, trace[vLetter + 'hoverformat']) + ', ' + cd[0].t.labels.kde + ' ' + kdeVal.toFixed(3);
5757

5858
// move the spike to the KDE point
59-
kdePointData.spikeDistance = closeBoxData[0].spikeDistance;
59+
var medId = 0;
60+
for(var k = 0; k < closeBoxData.length; k++) {
61+
if(closeBoxData[k].attr === 'med') {
62+
medId = k;
63+
break;
64+
}
65+
}
66+
67+
kdePointData.spikeDistance = closeBoxData[medId].spikeDistance;
6068
var spikePosAttr = pLetter + 'Spike';
61-
kdePointData[spikePosAttr] = closeBoxData[0][spikePosAttr];
62-
closeBoxData[0].spikeDistance = undefined;
63-
closeBoxData[0][spikePosAttr] = undefined;
69+
kdePointData[spikePosAttr] = closeBoxData[medId][spikePosAttr];
70+
closeBoxData[medId].spikeDistance = undefined;
71+
closeBoxData[medId][spikePosAttr] = undefined;
6472

6573
// no hovertemplate support yet
6674
kdePointData.hovertemplate = false;

test/jasmine/tests/box_test.js

+51-28
Original file line numberDiff line numberDiff line change
@@ -743,8 +743,8 @@ describe('Test box hover:', function() {
743743
fig.layout.hovermode = 'x';
744744
return fig;
745745
},
746-
nums: ['median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7'],
747-
name: ['radishes', '', '', '', ''],
746+
nums: ['median: 0.55', 'min: 0', 'lower fence: 0', 'q1: 0.3', 'q3: 0.6', 'upper fence: 0.7', 'max: 0.7'],
747+
name: ['radishes', '', '', '', '', '', ''],
748748
axis: 'day 1'
749749
}, {
750750
desc: 'with mean',
@@ -755,8 +755,8 @@ describe('Test box hover:', function() {
755755
fig.layout.hovermode = 'x';
756756
return fig;
757757
},
758-
nums: ['median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7', 'mean: 0.45'],
759-
name: ['radishes', '', '', '', '', ''],
758+
nums: ['median: 0.55', 'min: 0', 'lower fence: 0', 'q1: 0.3', 'q3: 0.6', 'upper fence: 0.7', 'max: 0.7', 'mean: 0.45'],
759+
name: ['radishes', '', '', '', '', '', '', ''],
760760
axis: 'day 1'
761761
}, {
762762
desc: 'with sd',
@@ -768,10 +768,10 @@ describe('Test box hover:', function() {
768768
return fig;
769769
},
770770
nums: [
771-
'median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7',
771+
'median: 0.55', 'min: 0', 'lower fence: 0', 'q1: 0.3', 'q3: 0.6', 'upper fence: 0.7', 'max: 0.7',
772772
'mean ± σ: 0.45 ± 0.2362908'
773773
],
774-
name: ['radishes', '', '', '', '', ''],
774+
name: ['radishes', '', '', '', '', '', '', ''],
775775
axis: 'day 1'
776776
}, {
777777
desc: 'with boxpoints fences',
@@ -782,10 +782,11 @@ describe('Test box hover:', function() {
782782
},
783783
pos: [350, 200],
784784
nums: [
785+
'23.25',
785786
'median: 8.15', 'min: 0.75', 'q1: 6.8',
786787
'q3: 10.25', 'max: 23.25', 'lower fence: 5.25', 'upper fence: 12'
787788
],
788-
name: ['', '', '', '', '', '', ''],
789+
name: ['', '', '', '', '', '', '', ''],
789790
axis: 'trace 0'
790791
}, {
791792
desc: 'with overlaid boxes',
@@ -795,12 +796,22 @@ describe('Test box hover:', function() {
795796
return fig;
796797
},
797798
nums: [
798-
'q1: 0.3', 'median: 0.45', 'q3: 0.6', 'max: 1', 'median: 0.55', 'min: 0', 'q1: 0.1',
799-
'q3: 0.6', 'max: 0.7', 'median: 0.45', 'q1: 0.2', 'q3: 0.6', 'max: 0.9'
799+
'median: 0.45', 'median: 0.45', 'median: 0.55',
800+
'min: 0', 'min: 0.1', 'min: 0.2',
801+
'lower fence: 0', 'lower fence: 0.1', 'lower fence: 0.2',
802+
'q1: 0.1', 'q1: 0.2', 'q1: 0.3',
803+
'q3: 0.6', 'q3: 0.6', 'q3: 0.6',
804+
'upper fence: 0.7', 'upper fence: 0.9', 'upper fence: 1',
805+
'max: 0.7', 'max: 0.9', 'max: 1'
800806
],
801807
name: [
802-
'', 'kale', '', '', 'radishes', '', '',
803-
'', '', 'carrots', '', '', ''
808+
'carrots', 'kale', 'radishes',
809+
'', '', '',
810+
'', '', '',
811+
'', '', '',
812+
'', '', '',
813+
'', '', '',
814+
'', '', ''
804815
],
805816
axis: 'day 1'
806817
}, {
@@ -841,8 +852,8 @@ describe('Test box hover:', function() {
841852
return fig;
842853
},
843854
pos: [215, 200],
844-
nums: ['median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7'],
845-
name: ['radishes', '', '', '', ''],
855+
nums: ['median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7', 'lower fence: 0', 'upper fence: 0.7'],
856+
name: ['radishes', '', '', '', '', '', ''],
846857
axis: 'day 1'
847858
}, {
848859
desc: 'hoveron boxes+points | hovermode x (box AND closest point)',
@@ -855,8 +866,8 @@ describe('Test box hover:', function() {
855866
fig.layout.hovermode = 'x';
856867
return fig;
857868
},
858-
nums: ['0.6', 'median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7'],
859-
name: ['radishes', 'radishes', '', '', '', ''],
869+
nums: ['0.6', 'median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7', 'lower fence: 0', 'upper fence: 0.7'],
870+
name: ['radishes', 'radishes', '', '', '', '', '', ''],
860871
axis: 'day 1'
861872
}, {
862873
desc: 'text items on hover',
@@ -909,20 +920,32 @@ describe('Test box hover:', function() {
909920
},
910921
pos: [430, 130],
911922
nums: [
912-
'max: 1', 'mean ± σ: 0.6833333 ± 0.2409472', 'min: 0.3',
913-
'q1: 0.5', 'q3: 0.9', 'median: 0.7'],
914-
name: ['', '', '', '', '', 'carrots'],
915-
axis: 'day 2',
916-
hOrder: [0, 4, 5, 1, 3, 2]
923+
'median: 0.7',
924+
'min: 0.3',
925+
'q1: 0.5',
926+
'q3: 0.9',
927+
'max: 1',
928+
'lower fence: 0.3',
929+
'upper fence: 1',
930+
'mean ± σ: 0.6833333 ± 0.2409472',
931+
],
932+
name: ['carrots', '', '', '', '', '', '', ''],
933+
axis: 'day 2'
917934
}, {
918935
desc: 'orientation:h | hovermode:closest',
919936
mock: require('@mocks/box_grouped_horz.json'),
920937
pos: [430, 130],
921938
nums: [
922-
'(max: 1, day 2)', '(mean ± σ: 0.6833333 ± 0.2409472, day 2)', '(min: 0.3, day 2)',
923-
'(q1: 0.5, day 2)', '(q3: 0.9, day 2)', '(median: 0.7, day 2)'],
924-
name: ['', '', '', '', '', 'carrots'],
925-
hOrder: [0, 4, 5, 1, 3, 2]
939+
'(median: 0.7, day 2)',
940+
'(min: 0.3, day 2)',
941+
'(q1: 0.5, day 2)',
942+
'(q3: 0.9, day 2)',
943+
'(max: 1, day 2)',
944+
'(lower fence: 0.3, day 2)',
945+
'(upper fence: 1, day 2)',
946+
'(mean ± σ: 0.6833333 ± 0.2409472, day 2)'
947+
],
948+
name: ['carrots', '', '', '', '', '', '', ''],
926949
}, {
927950
desc: 'on boxpoints with numeric positions | hovermode:closest',
928951
mock: {
@@ -967,8 +990,8 @@ describe('Test box hover:', function() {
967990
}
968991
},
969992
pos: [200, 200],
970-
nums: ['median: 2', 'q1: 1.5', 'q3: 2.5', 'max: 3', 'min: 1'],
971-
name: ['', '', '', '', ''],
993+
nums: ['median: 2', 'q1: 1.5', 'q3: 2.5', 'max: 3', 'min: 1', 'lower fence: 1', 'upper fence: 3'],
994+
name: ['', '', '', '', '', '', ''],
972995
axis: 'trace 0'
973996
}, {
974997
desc: 'q1/median/q3 signature on boxes',
@@ -987,8 +1010,8 @@ describe('Test box hover:', function() {
9871010
}
9881011
},
9891012
pos: [200, 200],
990-
nums: ['median: 2', 'q1: 1', 'q3: 3'],
991-
name: ['', '', ''],
1013+
nums: ['median: 2', 'min: 1', 'q1: 1', 'q3: 3', 'max: 3', 'lower fence: 1', 'upper fence: 3'],
1014+
name: ['', '', '', '', '', '', ''],
9921015
axis: 'A'
9931016
}, {
9941017
desc: 'q1/median/q3 signature on points',

test/jasmine/tests/hover_label_test.js

+27-6
Original file line numberDiff line numberDiff line change
@@ -2833,21 +2833,25 @@ describe('hover on traces with (x|y)period positioning', function() {
28332833
.then(function() { _hover(385, 355); })
28342834
.then(function() {
28352835
assertHoverLabelContent({
2836-
name: ['', '', '', 'box (v)', ''],
2836+
name: ['', '', '', 'box (v)', '', '', ''],
28372837
nums: [
28382838
'(Jan 2001, min: 2)',
2839+
'(Jan 2001, lower fence: 2)',
28392840
'(Jan 2001, q1: 4)',
2840-
'(Jan 2001, q3: 8)',
28412841
'(Jan 2001, median: 6)',
2842-
'(Jan 2001, max: 10)',
2842+
'(Jan 2001, q3: 8)',
2843+
'(Jan 2001, upper fence: 10)',
2844+
'(Jan 2001, max: 10)'
28432845
]
28442846
});
28452847
})
28462848
.then(function() { _hover(475, 120); })
28472849
.then(function() {
28482850
assertHoverLabelContent({
2849-
name: ['', '', '', '', 'box (h)'],
2851+
name: ['', '', '', '', '', '', 'box (h)'],
28502852
nums: [
2853+
'(upper fence: 8, Jan 2004)',
2854+
'(lower fence: 0, Jan 2004)',
28512855
'(max: 8, Jan 2004)',
28522856
'(min: 0, Jan 2004)',
28532857
'(q1: 2, Jan 2004)',
@@ -4673,7 +4677,13 @@ describe('hovermode: (x|y)unified', function() {
46734677

46744678
assertLabel({title: '3', items: [
46754679
'trace 0 : 2',
4680+
'min: 1',
4681+
'lower fence: 1',
4682+
'q1: 1',
46764683
'trace 1 : median: 1',
4684+
'q3: 1',
4685+
'upper fence: 1',
4686+
'max: 1',
46774687
'trace 3 : 2',
46784688
'trace 2 : 2',
46794689
'trace 5 : 2',
@@ -6262,7 +6272,18 @@ describe('hover on traces with (x|y)hoverformat', function() {
62626272
{type: 'scattergl', nums: '(02/01/2000, 1.00)'},
62636273
{type: 'histogram', nums: '(02/01/2000, 1.00)'},
62646274
{type: 'bar', nums: '(02/01/2000, 1.00)'},
6265-
{type: 'box', nums: '(02/01/2000, median: 1.00)'},
6275+
{type: 'box',
6276+
name: ['', '', '', '', '', '', ''],
6277+
nums: [
6278+
'(02/01/2000, median: 1.00)',
6279+
'(02/01/2000, max: 1.00)',
6280+
'(02/01/2000, upper fence: 1.00)',
6281+
'(02/01/2000, q3: 1.00)',
6282+
'(02/01/2000, q1: 1.00)',
6283+
'(02/01/2000, lower fence: 1.00)',
6284+
'(02/01/2000, min: 1.00)'
6285+
]
6286+
},
62666287
{type: 'ohlc', nums: '02/01/2000\nopen: 4.00\nhigh: 5.00\nlow: 2.00\nclose: 3.00 ▼'},
62676288
{type: 'candlestick', nums: '02/01/2000\nopen: 4.00\nhigh: 5.00\nlow: 2.00\nclose: 3.00 ▼'},
62686289
{type: 'waterfall', nums: '(02/01/2000, 1.00)\n1.00 ▲\nInitial: 0.00'},
@@ -6286,7 +6307,7 @@ describe('hover on traces with (x|y)hoverformat', function() {
62866307
.then(function() { _hover(200, 200); })
62876308
.then(function() {
62886309
assertHoverLabelContent({
6289-
name: '',
6310+
name: t.name ? t.name : '',
62906311
nums: t.nums
62916312
});
62926313
})

0 commit comments

Comments
 (0)