Skip to content

Commit

Permalink
Merge pull request #6189 from plotly/improve-box-violin-hoverlabels
Browse files Browse the repository at this point in the history
revise `box` & `violin` hover labels - improve order & handle duplicates
  • Loading branch information
archmoj committed Jun 14, 2022
2 parents 1ec75d5 + 3e25576 commit b2df371
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 88 deletions.
1 change: 1 addition & 0 deletions draftlogs/6189_fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fix undesirable missing hover labels of `box` & `violin` traces [[#6189](https://github.com/plotly/plotly.js/pull/6189)]
49 changes: 33 additions & 16 deletions src/traces/box/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) {
var trace = cd[0].trace;
var t = cd[0].t;
var isViolin = trace.type === 'violin';
var closeBoxData = [];

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

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

if(trace.boxmean || (trace.meanline || {}).visible) {
attrs.push('mean');
}
if(trace.boxpoints || trace.points) {
attrs.push('lf', 'uf');
// labels with equal values (e.g. when min === q1) should still be presented in the order they have when they're unequal
var attrs =
(hasFences && hasMean) ? ['max', 'uf', 'q3', 'med', 'mean', 'q1', 'lf', 'min'] :
(hasFences && !hasMean) ? ['max', 'uf', 'q3', 'med', 'q1', 'lf', 'min'] :
(!hasFences && hasMean) ? ['max', 'q3', 'med', 'mean', 'q1', 'min'] :
['max', 'q3', 'med', 'q1', 'min'];

var rev = vAxis.range[1] < vAxis.range[0];

if(trace.orientation === (rev ? 'v' : 'h')) {
attrs.reverse();
}

var spikeDistance = pointData.spikeDistance;
var spikePosition = pointData[spikePosAttr];

var closeBoxData = [];
for(var i = 0; i < attrs.length; i++) {
var attr = attrs[i];

if(!(attr in di) || (di[attr] in usedVals)) continue;
usedVals[di[attr]] = true;
if(!(attr in di)) continue;

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

// only keep name and spikes on the first item (median)
pointData.name = '';
pointData.spikeDistance = undefined;
pointData[spikePosAttr] = undefined;

// no hovertemplate support yet
pointData2.hovertemplate = false;

closeBoxData.push(pointData2);
}

// only keep name and spikes on the median
pointData.name = '';
pointData.spikeDistance = undefined;
pointData[spikePosAttr] = undefined;
for(var k = 0; k < closeBoxData.length; k++) {
if(closeBoxData[k].attr !== 'med') {
closeBoxData[k].name = '';
closeBoxData[k].spikeDistance = undefined;
closeBoxData[k][spikePosAttr] = undefined;
} else {
closeBoxData[k].spikeDistance = spikeDistance;
closeBoxData[k][spikePosAttr] = spikePosition;
}
}

return closeBoxData;
}

Expand Down
16 changes: 12 additions & 4 deletions src/traces/violin/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,19 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode, opts) {
kdePointData[vLetter + 'Label'] = vLetter + ': ' + Axes.hoverLabelText(vAxis, vVal, trace[vLetter + 'hoverformat']) + ', ' + cd[0].t.labels.kde + ' ' + kdeVal.toFixed(3);

// move the spike to the KDE point
kdePointData.spikeDistance = closeBoxData[0].spikeDistance;
var medId = 0;
for(var k = 0; k < closeBoxData.length; k++) {
if(closeBoxData[k].attr === 'med') {
medId = k;
break;
}
}

kdePointData.spikeDistance = closeBoxData[medId].spikeDistance;
var spikePosAttr = pLetter + 'Spike';
kdePointData[spikePosAttr] = closeBoxData[0][spikePosAttr];
closeBoxData[0].spikeDistance = undefined;
closeBoxData[0][spikePosAttr] = undefined;
kdePointData[spikePosAttr] = closeBoxData[medId][spikePosAttr];
closeBoxData[medId].spikeDistance = undefined;
closeBoxData[medId][spikePosAttr] = undefined;

// no hovertemplate support yet
kdePointData.hovertemplate = false;
Expand Down
79 changes: 51 additions & 28 deletions test/jasmine/tests/box_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -743,8 +743,8 @@ describe('Test box hover:', function() {
fig.layout.hovermode = 'x';
return fig;
},
nums: ['median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7'],
name: ['radishes', '', '', '', ''],
nums: ['median: 0.55', 'min: 0', 'lower fence: 0', 'q1: 0.3', 'q3: 0.6', 'upper fence: 0.7', 'max: 0.7'],
name: ['radishes', '', '', '', '', '', ''],
axis: 'day 1'
}, {
desc: 'with mean',
Expand All @@ -755,8 +755,8 @@ describe('Test box hover:', function() {
fig.layout.hovermode = 'x';
return fig;
},
nums: ['median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7', 'mean: 0.45'],
name: ['radishes', '', '', '', '', ''],
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'],
name: ['radishes', '', '', '', '', '', '', ''],
axis: 'day 1'
}, {
desc: 'with sd',
Expand All @@ -768,10 +768,10 @@ describe('Test box hover:', function() {
return fig;
},
nums: [
'median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7',
'median: 0.55', 'min: 0', 'lower fence: 0', 'q1: 0.3', 'q3: 0.6', 'upper fence: 0.7', 'max: 0.7',
'mean ± σ: 0.45 ± 0.2362908'
],
name: ['radishes', '', '', '', '', ''],
name: ['radishes', '', '', '', '', '', '', ''],
axis: 'day 1'
}, {
desc: 'with boxpoints fences',
Expand All @@ -782,10 +782,11 @@ describe('Test box hover:', function() {
},
pos: [350, 200],
nums: [
'23.25',
'median: 8.15', 'min: 0.75', 'q1: 6.8',
'q3: 10.25', 'max: 23.25', 'lower fence: 5.25', 'upper fence: 12'
],
name: ['', '', '', '', '', '', ''],
name: ['', '', '', '', '', '', '', ''],
axis: 'trace 0'
}, {
desc: 'with overlaid boxes',
Expand All @@ -795,12 +796,22 @@ describe('Test box hover:', function() {
return fig;
},
nums: [
'q1: 0.3', 'median: 0.45', 'q3: 0.6', 'max: 1', 'median: 0.55', 'min: 0', 'q1: 0.1',
'q3: 0.6', 'max: 0.7', 'median: 0.45', 'q1: 0.2', 'q3: 0.6', 'max: 0.9'
'median: 0.45', 'median: 0.45', 'median: 0.55',
'min: 0', 'min: 0.1', 'min: 0.2',
'lower fence: 0', 'lower fence: 0.1', 'lower fence: 0.2',
'q1: 0.1', 'q1: 0.2', 'q1: 0.3',
'q3: 0.6', 'q3: 0.6', 'q3: 0.6',
'upper fence: 0.7', 'upper fence: 0.9', 'upper fence: 1',
'max: 0.7', 'max: 0.9', 'max: 1'
],
name: [
'', 'kale', '', '', 'radishes', '', '',
'', '', 'carrots', '', '', ''
'carrots', 'kale', 'radishes',
'', '', '',
'', '', '',
'', '', '',
'', '', '',
'', '', '',
'', '', ''
],
axis: 'day 1'
}, {
Expand Down Expand Up @@ -841,8 +852,8 @@ describe('Test box hover:', function() {
return fig;
},
pos: [215, 200],
nums: ['median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7'],
name: ['radishes', '', '', '', ''],
nums: ['median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7', 'lower fence: 0', 'upper fence: 0.7'],
name: ['radishes', '', '', '', '', '', ''],
axis: 'day 1'
}, {
desc: 'hoveron boxes+points | hovermode x (box AND closest point)',
Expand All @@ -855,8 +866,8 @@ describe('Test box hover:', function() {
fig.layout.hovermode = 'x';
return fig;
},
nums: ['0.6', 'median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7'],
name: ['radishes', 'radishes', '', '', '', ''],
nums: ['0.6', 'median: 0.55', 'min: 0', 'q1: 0.3', 'q3: 0.6', 'max: 0.7', 'lower fence: 0', 'upper fence: 0.7'],
name: ['radishes', 'radishes', '', '', '', '', '', ''],
axis: 'day 1'
}, {
desc: 'text items on hover',
Expand Down Expand Up @@ -909,20 +920,32 @@ describe('Test box hover:', function() {
},
pos: [430, 130],
nums: [
'max: 1', 'mean ± σ: 0.6833333 ± 0.2409472', 'min: 0.3',
'q1: 0.5', 'q3: 0.9', 'median: 0.7'],
name: ['', '', '', '', '', 'carrots'],
axis: 'day 2',
hOrder: [0, 4, 5, 1, 3, 2]
'median: 0.7',
'min: 0.3',
'q1: 0.5',
'q3: 0.9',
'max: 1',
'lower fence: 0.3',
'upper fence: 1',
'mean ± σ: 0.6833333 ± 0.2409472',
],
name: ['carrots', '', '', '', '', '', '', ''],
axis: 'day 2'
}, {
desc: 'orientation:h | hovermode:closest',
mock: require('@mocks/box_grouped_horz.json'),
pos: [430, 130],
nums: [
'(max: 1, day 2)', '(mean ± σ: 0.6833333 ± 0.2409472, day 2)', '(min: 0.3, day 2)',
'(q1: 0.5, day 2)', '(q3: 0.9, day 2)', '(median: 0.7, day 2)'],
name: ['', '', '', '', '', 'carrots'],
hOrder: [0, 4, 5, 1, 3, 2]
'(median: 0.7, day 2)',
'(min: 0.3, day 2)',
'(q1: 0.5, day 2)',
'(q3: 0.9, day 2)',
'(max: 1, day 2)',
'(lower fence: 0.3, day 2)',
'(upper fence: 1, day 2)',
'(mean ± σ: 0.6833333 ± 0.2409472, day 2)'
],
name: ['carrots', '', '', '', '', '', '', ''],
}, {
desc: 'on boxpoints with numeric positions | hovermode:closest',
mock: {
Expand Down Expand Up @@ -967,8 +990,8 @@ describe('Test box hover:', function() {
}
},
pos: [200, 200],
nums: ['median: 2', 'q1: 1.5', 'q3: 2.5', 'max: 3', 'min: 1'],
name: ['', '', '', '', ''],
nums: ['median: 2', 'q1: 1.5', 'q3: 2.5', 'max: 3', 'min: 1', 'lower fence: 1', 'upper fence: 3'],
name: ['', '', '', '', '', '', ''],
axis: 'trace 0'
}, {
desc: 'q1/median/q3 signature on boxes',
Expand All @@ -987,8 +1010,8 @@ describe('Test box hover:', function() {
}
},
pos: [200, 200],
nums: ['median: 2', 'q1: 1', 'q3: 3'],
name: ['', '', ''],
nums: ['median: 2', 'min: 1', 'q1: 1', 'q3: 3', 'max: 3', 'lower fence: 1', 'upper fence: 3'],
name: ['', '', '', '', '', '', ''],
axis: 'A'
}, {
desc: 'q1/median/q3 signature on points',
Expand Down
33 changes: 27 additions & 6 deletions test/jasmine/tests/hover_label_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2833,21 +2833,25 @@ describe('hover on traces with (x|y)period positioning', function() {
.then(function() { _hover(385, 355); })
.then(function() {
assertHoverLabelContent({
name: ['', '', '', 'box (v)', ''],
name: ['', '', '', 'box (v)', '', '', ''],
nums: [
'(Jan 2001, min: 2)',
'(Jan 2001, lower fence: 2)',
'(Jan 2001, q1: 4)',
'(Jan 2001, q3: 8)',
'(Jan 2001, median: 6)',
'(Jan 2001, max: 10)',
'(Jan 2001, q3: 8)',
'(Jan 2001, upper fence: 10)',
'(Jan 2001, max: 10)'
]
});
})
.then(function() { _hover(475, 120); })
.then(function() {
assertHoverLabelContent({
name: ['', '', '', '', 'box (h)'],
name: ['', '', '', '', '', '', 'box (h)'],
nums: [
'(upper fence: 8, Jan 2004)',
'(lower fence: 0, Jan 2004)',
'(max: 8, Jan 2004)',
'(min: 0, Jan 2004)',
'(q1: 2, Jan 2004)',
Expand Down Expand Up @@ -4673,7 +4677,13 @@ describe('hovermode: (x|y)unified', function() {

assertLabel({title: '3', items: [
'trace 0 : 2',
'min: 1',
'lower fence: 1',
'q1: 1',
'trace 1 : median: 1',
'q3: 1',
'upper fence: 1',
'max: 1',
'trace 3 : 2',
'trace 2 : 2',
'trace 5 : 2',
Expand Down Expand Up @@ -6262,7 +6272,18 @@ describe('hover on traces with (x|y)hoverformat', function() {
{type: 'scattergl', nums: '(02/01/2000, 1.00)'},
{type: 'histogram', nums: '(02/01/2000, 1.00)'},
{type: 'bar', nums: '(02/01/2000, 1.00)'},
{type: 'box', nums: '(02/01/2000, median: 1.00)'},
{type: 'box',
name: ['', '', '', '', '', '', ''],
nums: [
'(02/01/2000, median: 1.00)',
'(02/01/2000, max: 1.00)',
'(02/01/2000, upper fence: 1.00)',
'(02/01/2000, q3: 1.00)',
'(02/01/2000, q1: 1.00)',
'(02/01/2000, lower fence: 1.00)',
'(02/01/2000, min: 1.00)'
]
},
{type: 'ohlc', nums: '02/01/2000\nopen: 4.00\nhigh: 5.00\nlow: 2.00\nclose: 3.00 ▼'},
{type: 'candlestick', nums: '02/01/2000\nopen: 4.00\nhigh: 5.00\nlow: 2.00\nclose: 3.00 ▼'},
{type: 'waterfall', nums: '(02/01/2000, 1.00)\n1.00 ▲\nInitial: 0.00'},
Expand All @@ -6286,7 +6307,7 @@ describe('hover on traces with (x|y)hoverformat', function() {
.then(function() { _hover(200, 200); })
.then(function() {
assertHoverLabelContent({
name: '',
name: t.name ? t.name : '',
nums: t.nums
});
})
Expand Down

0 comments on commit b2df371

Please sign in to comment.