Skip to content

Fixup hover on period positioned points #5618

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

Merged
merged 14 commits into from
May 13, 2021
Merged
111 changes: 92 additions & 19 deletions src/components/fx/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -640,33 +640,67 @@ function _hover(gd, evt, subplot, noHoverEvent) {

hoverData.sort(function(d1, d2) { return d1.distance - d2.distance; });

// move period positioned points to the end of list
hoverData = orderPeriod(hoverData, hovermode);

// If in compare mode, select every point at position
if(
helpers.isXYhover(mode) &&
hoverData[0].length !== 0 &&
hoverData[0].trace.type !== 'splom' // TODO: add support for splom
) {
var hd = hoverData[0];
var cd0 = hd.cd[hd.index];
var isGrouped = (fullLayout.boxmode === 'group' || fullLayout.violinmode === 'group');

var xVal = hd.xVal;
var ax = hd.xa;
if(ax.type === 'category') xVal = ax._categoriesMap[xVal];
if(ax.type === 'date') xVal = ax.d2c(xVal);
if(cd0 && cd0.t && cd0.t.posLetter === ax._id && isGrouped) {
xVal += cd0.t.dPos;
}
var initLen = hoverData.length;
var winningPoint = hoverData[0];

var yVal = hd.yVal;
ax = hd.ya;
if(ax.type === 'category') yVal = ax._categoriesMap[yVal];
if(ax.type === 'date') yVal = ax.d2c(yVal);
if(cd0 && cd0.t && cd0.t.posLetter === ax._id && isGrouped) {
yVal += cd0.t.dPos;
}
var customXVal = customVal('x', winningPoint, fullLayout);
var customYVal = customVal('y', winningPoint, fullLayout);

findHoverPoints(customXVal, customYVal);

// also find start, middle and end point for period
var axLetter = hovermode.charAt(0);
if(winningPoint.trace[axLetter + 'period']) {
var v = winningPoint[axLetter + 'LabelVal'];
var ax = winningPoint[axLetter + 'a'];
var T = {};
T[axLetter + 'period'] = winningPoint.trace[axLetter + 'period'];
T[axLetter + 'period0'] = winningPoint.trace[axLetter + 'period0'];

findHoverPoints(xVal, yVal);
T[axLetter + 'periodalignment'] = 'start';
var start = alignPeriod(T, ax, axLetter, [v])[0];

T[axLetter + 'periodalignment'] = 'middle';
var middle = alignPeriod(T, ax, axLetter, [v])[0];

T[axLetter + 'periodalignment'] = 'end';
var end = alignPeriod(T, ax, axLetter, [v])[0];

if(axLetter === 'x') {
findHoverPoints(start, customYVal);
findHoverPoints(middle, customYVal);
findHoverPoints(end, customYVal);
} else {
findHoverPoints(customXVal, start);
findHoverPoints(customXVal, middle);
findHoverPoints(customXVal, end);
}

var k;
var seen = {};
for(k = 0; k < initLen; k++) {
seen[hoverData[k].trace.index] = true;
}

// remove non-period aditions and traces that seen before
for(k = hoverData.length - 1; k >= initLen; k--) {
if(
seen[hoverData[k].trace.index] ||
!hoverData[k].trace[axLetter + 'period']
) {
hoverData.splice(k, 1);
}
}
}

// Remove duplicated hoverData points
// note that d3 also filters identical points in the rendering steps
Expand Down Expand Up @@ -1889,3 +1923,42 @@ function plainText(s, len) {
allowedTags: ['br', 'sub', 'sup', 'b', 'i', 'em']
});
}

function orderPeriod(hoverData, hovermode) {
var axLetter = hovermode.charAt(0);

var first = [];
var last = [];

for(var i = 0; i < hoverData.length; i++) {
var d = hoverData[i];

if(d.trace[axLetter + 'period']) {
last.push(d);
} else {
first.push(d);
}
}

return first.concat(last);
}

function customVal(axLetter, winningPoint, fullLayout) {
var ax = winningPoint[axLetter + 'a'];
var val = winningPoint[axLetter + 'Val'];

if(ax.type === 'category') val = ax._categoriesMap[val];
else if(ax.type === 'date') val = ax.d2c(val);

var cd0 = winningPoint.cd[winningPoint.index];
if(cd0 && cd0.t && cd0.t.posLetter === ax._id) {
if(
fullLayout.boxmode === 'group' ||
fullLayout.violinmode === 'group'
) {
val += cd0.t.dPos;
}
}

return val;
}
18 changes: 16 additions & 2 deletions src/traces/bar/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,22 @@ function hoverOnBars(pointData, xval, yval, hovermode) {

var posVal, sizeVal, posLetter, sizeLetter, dx, dy, pRangeCalc;

function thisBarMinPos(di) { return di[posLetter] - di.w / 2; }
function thisBarMaxPos(di) { return di[posLetter] + di.w / 2; }
function thisBarMinPos(di) { return thisBarExtPos(di, -1); }
function thisBarMaxPos(di) { return thisBarExtPos(di, 1); }

function thisBarExtPos(di, sgn) {
var w = di.w;
var delta = sgn * w;
if(trace[posLetter + 'period']) {
var alignment = trace[posLetter + 'periodalignment'];
if(alignment === 'start') {
delta = (sgn === -1) ? 0 : w;
} else if(alignment === 'end') {
delta = (sgn === -1) ? -w : 0;
}
}
return di[posLetter] + delta / 2;
}

var minPos = isClosest ?
thisBarMinPos :
Expand Down
156 changes: 156 additions & 0 deletions test/jasmine/tests/hover_label_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4998,6 +4998,162 @@ describe('hovermode: (x|y)unified', function() {
.then(done, done.fail);
});

[{
xperiod: 0,
desc: 'non-period scatter points and period bars'
}, {
xperiod: 24 * 3600 * 1000,
desc: 'period scatter points and period bars'
}].forEach(function(t) {
it(t.desc, function(done) {
var fig = {
data: [
{
name: 'bar',
type: 'bar',
x: ['1999-12', '2000-01', '2000-02'],
y: [2, 1, 3],
xhoverformat: '%b',
xperiod: 'M1'
},
{
xperiod: t.xperiod,
name: 'scatter',
type: 'scatter',
x: [
'2000-01-01', '2000-01-06', '2000-01-11', '2000-01-16', '2000-01-21', '2000-01-26',
'2000-02-01', '2000-02-06', '2000-02-11', '2000-02-16', '2000-02-21', '2000-02-26',
'2000-03-01', '2000-03-06', '2000-03-11', '2000-03-16', '2000-03-21', '2000-03-26'
],
y: [
1.1, 1.2, 1.3, 1.4, 1.5, 1.6,
2.1, 2.2, 2.3, 2.4, 2.5, 2.6,
3.1, 3.2, 3.3, 3.4, 3.5, 3.6,
]
}
],
layout: {
showlegend: false,
width: 600,
height: 400,
hovermode: 'x unified'
}
};

Plotly.newPlot(gd, fig)
.then(function(gd) {
_hover(gd, { xpx: 50, ypx: 200 });
assertLabel({title: 'Dec', items: [
'bar : 2'
]});

_hover(gd, { xpx: 100, ypx: 200 });
assertLabel({title: 'Jan 1, 2000', items: [
'scatter : 1.1'
]});

_hover(gd, { xpx: 150, ypx: 200 });
assertLabel({title: 'Jan 11, 2000', items: [
'bar : (Jan, 1)',
'scatter : 1.3'
]});

_hover(gd, { xpx: 200, ypx: 200 });
assertLabel({title: 'Jan 26, 2000', items: [
'bar : (Jan, 1)',
'scatter : 1.6'
]});

_hover(gd, { xpx: 250, ypx: 200 });
assertLabel({title: 'Feb 11, 2000', items: [
'bar : (Feb, 3)',
'scatter : 2.3'
]});

_hover(gd, { xpx: 300, ypx: 200 });
assertLabel({title: 'Feb 21, 2000', items: [
'bar : (Feb, 3)',
'scatter : 2.5'
]});

_hover(gd, { xpx: 350, ypx: 200 });
assertLabel({title: 'Mar 6, 2000', items: [
'scatter : 3.2'
]});
})
.then(done, done.fail);
});
});

it('period points alignments', function(done) {
Plotly.newPlot(gd, {
data: [
{
name: 'bar',
type: 'bar',
x: ['2000-01', '2000-02'],
y: [1, 2],
xhoverfrmat: '%b',
xperiod: 'M1'
},
{
name: 'start',
type: 'scatter',
x: ['2000-01', '2000-02'],
y: [1, 2],
xhoverformat: '%b',
xperiod: 'M1',
xperiodalignment: 'start'
},
{
name: 'end',
type: 'scatter',
x: ['2000-01', '2000-02'],
y: [1, 2],
xhoverformat: '%b',
xperiod: 'M1',
xperiodalignment: 'end'
},
],
layout: {
showlegend: false,
width: 600,
height: 400,
hovermode: 'x unified'
}
})
.then(function(gd) {
_hover(gd, { xpx: 40, ypx: 200 });
assertLabel({title: 'Jan', items: [
'bar : (Jan 1, 2000, 1)',
'start : 1',
'end : 1'
]});

_hover(gd, { xpx: 100, ypx: 200 });
assertLabel({title: 'Jan 1, 2000', items: [
'bar : 1',
'start : (Jan, 1)',
'end : (Jan, 1)'
]});

_hover(gd, { xpx: 360, ypx: 200 });
assertLabel({title: 'Feb 1, 2000', items: [
'bar : 2',
'start : (Feb, 2)',
'end : (Feb, 2)'
]});

_hover(gd, { xpx: 400, ypx: 200 });
assertLabel({title: 'Feb', items: [
'bar : (Feb 1, 2000, 2)',
'start : 2',
'end : 2'
]});
})
.then(done, done.fail);
});

it('should have the same traceorder as the legend', function(done) {
var mock = require('@mocks/stacked_area.json');
var mockCopy = Lib.extendDeep({}, mock);
Expand Down