Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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