Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

revise box & violin hover labels - improve order & handle duplicates #6189

Merged
merged 12 commits into from
Jun 14, 2022
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)]
52 changes: 36 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,33 @@ 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 euqual values (e.g. when min === q1) should be presented in certain order
archmoj marked this conversation as resolved.
Show resolved Hide resolved
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(
(!rev && trace.orientation === 'h') ||
(rev && trace.orientation === 'v')
) {
archmoj marked this conversation as resolved.
Show resolved Hide resolved
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 +186,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 first item (median)
archmoj marked this conversation as resolved.
Show resolved Hide resolved
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