Skip to content

Commit 327fa92

Browse files
authored
Merge pull request #5618 from plotly/fixup-period-hover
Fixup hover on period positioned points
2 parents 84bf2bc + 4f80d95 commit 327fa92

File tree

3 files changed

+264
-21
lines changed

3 files changed

+264
-21
lines changed

src/components/fx/hover.js

+92-19
Original file line numberDiff line numberDiff line change
@@ -640,33 +640,67 @@ function _hover(gd, evt, subplot, noHoverEvent) {
640640

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

643+
// move period positioned points to the end of list
644+
hoverData = orderPeriod(hoverData, hovermode);
645+
643646
// If in compare mode, select every point at position
644647
if(
645648
helpers.isXYhover(mode) &&
646649
hoverData[0].length !== 0 &&
647650
hoverData[0].trace.type !== 'splom' // TODO: add support for splom
648651
) {
649-
var hd = hoverData[0];
650-
var cd0 = hd.cd[hd.index];
651-
var isGrouped = (fullLayout.boxmode === 'group' || fullLayout.violinmode === 'group');
652-
653-
var xVal = hd.xVal;
654-
var ax = hd.xa;
655-
if(ax.type === 'category') xVal = ax._categoriesMap[xVal];
656-
if(ax.type === 'date') xVal = ax.d2c(xVal);
657-
if(cd0 && cd0.t && cd0.t.posLetter === ax._id && isGrouped) {
658-
xVal += cd0.t.dPos;
659-
}
652+
var initLen = hoverData.length;
653+
var winningPoint = hoverData[0];
660654

661-
var yVal = hd.yVal;
662-
ax = hd.ya;
663-
if(ax.type === 'category') yVal = ax._categoriesMap[yVal];
664-
if(ax.type === 'date') yVal = ax.d2c(yVal);
665-
if(cd0 && cd0.t && cd0.t.posLetter === ax._id && isGrouped) {
666-
yVal += cd0.t.dPos;
667-
}
655+
var customXVal = customVal('x', winningPoint, fullLayout);
656+
var customYVal = customVal('y', winningPoint, fullLayout);
657+
658+
findHoverPoints(customXVal, customYVal);
659+
660+
// also find start, middle and end point for period
661+
var axLetter = hovermode.charAt(0);
662+
if(winningPoint.trace[axLetter + 'period']) {
663+
var v = winningPoint[axLetter + 'LabelVal'];
664+
var ax = winningPoint[axLetter + 'a'];
665+
var T = {};
666+
T[axLetter + 'period'] = winningPoint.trace[axLetter + 'period'];
667+
T[axLetter + 'period0'] = winningPoint.trace[axLetter + 'period0'];
668668

669-
findHoverPoints(xVal, yVal);
669+
T[axLetter + 'periodalignment'] = 'start';
670+
var start = alignPeriod(T, ax, axLetter, [v])[0];
671+
672+
T[axLetter + 'periodalignment'] = 'middle';
673+
var middle = alignPeriod(T, ax, axLetter, [v])[0];
674+
675+
T[axLetter + 'periodalignment'] = 'end';
676+
var end = alignPeriod(T, ax, axLetter, [v])[0];
677+
678+
if(axLetter === 'x') {
679+
findHoverPoints(start, customYVal);
680+
findHoverPoints(middle, customYVal);
681+
findHoverPoints(end, customYVal);
682+
} else {
683+
findHoverPoints(customXVal, start);
684+
findHoverPoints(customXVal, middle);
685+
findHoverPoints(customXVal, end);
686+
}
687+
688+
var k;
689+
var seen = {};
690+
for(k = 0; k < initLen; k++) {
691+
seen[hoverData[k].trace.index] = true;
692+
}
693+
694+
// remove non-period aditions and traces that seen before
695+
for(k = hoverData.length - 1; k >= initLen; k--) {
696+
if(
697+
seen[hoverData[k].trace.index] ||
698+
!hoverData[k].trace[axLetter + 'period']
699+
) {
700+
hoverData.splice(k, 1);
701+
}
702+
}
703+
}
670704

671705
// Remove duplicated hoverData points
672706
// note that d3 also filters identical points in the rendering steps
@@ -1889,3 +1923,42 @@ function plainText(s, len) {
18891923
allowedTags: ['br', 'sub', 'sup', 'b', 'i', 'em']
18901924
});
18911925
}
1926+
1927+
function orderPeriod(hoverData, hovermode) {
1928+
var axLetter = hovermode.charAt(0);
1929+
1930+
var first = [];
1931+
var last = [];
1932+
1933+
for(var i = 0; i < hoverData.length; i++) {
1934+
var d = hoverData[i];
1935+
1936+
if(d.trace[axLetter + 'period']) {
1937+
last.push(d);
1938+
} else {
1939+
first.push(d);
1940+
}
1941+
}
1942+
1943+
return first.concat(last);
1944+
}
1945+
1946+
function customVal(axLetter, winningPoint, fullLayout) {
1947+
var ax = winningPoint[axLetter + 'a'];
1948+
var val = winningPoint[axLetter + 'Val'];
1949+
1950+
if(ax.type === 'category') val = ax._categoriesMap[val];
1951+
else if(ax.type === 'date') val = ax.d2c(val);
1952+
1953+
var cd0 = winningPoint.cd[winningPoint.index];
1954+
if(cd0 && cd0.t && cd0.t.posLetter === ax._id) {
1955+
if(
1956+
fullLayout.boxmode === 'group' ||
1957+
fullLayout.violinmode === 'group'
1958+
) {
1959+
val += cd0.t.dPos;
1960+
}
1961+
}
1962+
1963+
return val;
1964+
}

src/traces/bar/hover.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,22 @@ function hoverOnBars(pointData, xval, yval, hovermode) {
3535

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

38-
function thisBarMinPos(di) { return di[posLetter] - di.w / 2; }
39-
function thisBarMaxPos(di) { return di[posLetter] + di.w / 2; }
38+
function thisBarMinPos(di) { return thisBarExtPos(di, -1); }
39+
function thisBarMaxPos(di) { return thisBarExtPos(di, 1); }
40+
41+
function thisBarExtPos(di, sgn) {
42+
var w = di.w;
43+
var delta = sgn * w;
44+
if(trace[posLetter + 'period']) {
45+
var alignment = trace[posLetter + 'periodalignment'];
46+
if(alignment === 'start') {
47+
delta = (sgn === -1) ? 0 : w;
48+
} else if(alignment === 'end') {
49+
delta = (sgn === -1) ? -w : 0;
50+
}
51+
}
52+
return di[posLetter] + delta / 2;
53+
}
4054

4155
var minPos = isClosest ?
4256
thisBarMinPos :

test/jasmine/tests/hover_label_test.js

+156
Original file line numberDiff line numberDiff line change
@@ -4978,6 +4978,162 @@ describe('hovermode: (x|y)unified', function() {
49784978
.then(done, done.fail);
49794979
});
49804980

4981+
[{
4982+
xperiod: 0,
4983+
desc: 'non-period scatter points and period bars'
4984+
}, {
4985+
xperiod: 24 * 3600 * 1000,
4986+
desc: 'period scatter points and period bars'
4987+
}].forEach(function(t) {
4988+
it(t.desc, function(done) {
4989+
var fig = {
4990+
data: [
4991+
{
4992+
name: 'bar',
4993+
type: 'bar',
4994+
x: ['1999-12', '2000-01', '2000-02'],
4995+
y: [2, 1, 3],
4996+
xhoverformat: '%b',
4997+
xperiod: 'M1'
4998+
},
4999+
{
5000+
xperiod: t.xperiod,
5001+
name: 'scatter',
5002+
type: 'scatter',
5003+
x: [
5004+
'2000-01-01', '2000-01-06', '2000-01-11', '2000-01-16', '2000-01-21', '2000-01-26',
5005+
'2000-02-01', '2000-02-06', '2000-02-11', '2000-02-16', '2000-02-21', '2000-02-26',
5006+
'2000-03-01', '2000-03-06', '2000-03-11', '2000-03-16', '2000-03-21', '2000-03-26'
5007+
],
5008+
y: [
5009+
1.1, 1.2, 1.3, 1.4, 1.5, 1.6,
5010+
2.1, 2.2, 2.3, 2.4, 2.5, 2.6,
5011+
3.1, 3.2, 3.3, 3.4, 3.5, 3.6,
5012+
]
5013+
}
5014+
],
5015+
layout: {
5016+
showlegend: false,
5017+
width: 600,
5018+
height: 400,
5019+
hovermode: 'x unified'
5020+
}
5021+
};
5022+
5023+
Plotly.newPlot(gd, fig)
5024+
.then(function(gd) {
5025+
_hover(gd, { xpx: 50, ypx: 200 });
5026+
assertLabel({title: 'Dec', items: [
5027+
'bar : 2'
5028+
]});
5029+
5030+
_hover(gd, { xpx: 100, ypx: 200 });
5031+
assertLabel({title: 'Jan 1, 2000', items: [
5032+
'scatter : 1.1'
5033+
]});
5034+
5035+
_hover(gd, { xpx: 150, ypx: 200 });
5036+
assertLabel({title: 'Jan 11, 2000', items: [
5037+
'bar : (Jan, 1)',
5038+
'scatter : 1.3'
5039+
]});
5040+
5041+
_hover(gd, { xpx: 200, ypx: 200 });
5042+
assertLabel({title: 'Jan 26, 2000', items: [
5043+
'bar : (Jan, 1)',
5044+
'scatter : 1.6'
5045+
]});
5046+
5047+
_hover(gd, { xpx: 250, ypx: 200 });
5048+
assertLabel({title: 'Feb 11, 2000', items: [
5049+
'bar : (Feb, 3)',
5050+
'scatter : 2.3'
5051+
]});
5052+
5053+
_hover(gd, { xpx: 300, ypx: 200 });
5054+
assertLabel({title: 'Feb 21, 2000', items: [
5055+
'bar : (Feb, 3)',
5056+
'scatter : 2.5'
5057+
]});
5058+
5059+
_hover(gd, { xpx: 350, ypx: 200 });
5060+
assertLabel({title: 'Mar 6, 2000', items: [
5061+
'scatter : 3.2'
5062+
]});
5063+
})
5064+
.then(done, done.fail);
5065+
});
5066+
});
5067+
5068+
it('period points alignments', function(done) {
5069+
Plotly.newPlot(gd, {
5070+
data: [
5071+
{
5072+
name: 'bar',
5073+
type: 'bar',
5074+
x: ['2000-01', '2000-02'],
5075+
y: [1, 2],
5076+
xhoverfrmat: '%b',
5077+
xperiod: 'M1'
5078+
},
5079+
{
5080+
name: 'start',
5081+
type: 'scatter',
5082+
x: ['2000-01', '2000-02'],
5083+
y: [1, 2],
5084+
xhoverformat: '%b',
5085+
xperiod: 'M1',
5086+
xperiodalignment: 'start'
5087+
},
5088+
{
5089+
name: 'end',
5090+
type: 'scatter',
5091+
x: ['2000-01', '2000-02'],
5092+
y: [1, 2],
5093+
xhoverformat: '%b',
5094+
xperiod: 'M1',
5095+
xperiodalignment: 'end'
5096+
},
5097+
],
5098+
layout: {
5099+
showlegend: false,
5100+
width: 600,
5101+
height: 400,
5102+
hovermode: 'x unified'
5103+
}
5104+
})
5105+
.then(function(gd) {
5106+
_hover(gd, { xpx: 40, ypx: 200 });
5107+
assertLabel({title: 'Jan', items: [
5108+
'bar : (Jan 1, 2000, 1)',
5109+
'start : 1',
5110+
'end : 1'
5111+
]});
5112+
5113+
_hover(gd, { xpx: 100, ypx: 200 });
5114+
assertLabel({title: 'Jan 1, 2000', items: [
5115+
'bar : 1',
5116+
'start : (Jan, 1)',
5117+
'end : (Jan, 1)'
5118+
]});
5119+
5120+
_hover(gd, { xpx: 360, ypx: 200 });
5121+
assertLabel({title: 'Feb 1, 2000', items: [
5122+
'bar : 2',
5123+
'start : (Feb, 2)',
5124+
'end : (Feb, 2)'
5125+
]});
5126+
5127+
_hover(gd, { xpx: 400, ypx: 200 });
5128+
assertLabel({title: 'Feb', items: [
5129+
'bar : (Feb 1, 2000, 2)',
5130+
'start : 2',
5131+
'end : 2'
5132+
]});
5133+
})
5134+
.then(done, done.fail);
5135+
});
5136+
49815137
it('should have the same traceorder as the legend', function(done) {
49825138
var mock = require('@mocks/stacked_area.json');
49835139
var mockCopy = Lib.extendDeep({}, mock);

0 commit comments

Comments
 (0)