Skip to content

Commit 6ee556c

Browse files
committed
use spikedistance in bar spikes
1 parent 7e0d980 commit 6ee556c

File tree

2 files changed

+59
-11
lines changed

2 files changed

+59
-11
lines changed

src/traces/bar/hover.js

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ function hoverOnBars(pointData, xval, yval, hovermode) {
3131
var isClosest = (hovermode === 'closest');
3232
var isWaterfall = (trace.type === 'waterfall');
3333
var maxHoverDistance = pointData.maxHoverDistance;
34+
var maxSpikeDistance = pointData.maxSpikeDistance;
3435

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

@@ -61,40 +62,63 @@ function hoverOnBars(pointData, xval, yval, hovermode) {
6162
return Math.max(thisBarMaxPos(di), di.p + t.bardelta / 2);
6263
};
6364

64-
function _positionFn(_minPos, _maxPos) {
65+
function hoverPositionFn(_minPos, _maxPos) {
6566
// add a little to the pseudo-distance for wider bars, so that like scatter,
6667
// if you are over two overlapping bars, the narrower one wins.
6768
return Fx.inbox(_minPos - posVal, _maxPos - posVal,
6869
maxHoverDistance + Math.min(1, Math.abs(_maxPos - _minPos) / pRangeCalc) - 1);
6970
}
7071

72+
function spikePositionFn(_minPos, _maxPos) {
73+
// add a little to the pseudo-distance for wider bars, so that like scatter,
74+
// if you are over two overlapping bars, the narrower one wins.
75+
return Fx.inbox(_minPos - posVal, _maxPos - posVal,
76+
maxSpikeDistance + Math.min(1, Math.abs(_maxPos - _minPos) / pRangeCalc) - 1);
77+
}
78+
7179
function positionFn(di) {
72-
return _positionFn(minPos(di), maxPos(di));
80+
return hoverPositionFn(minPos(di), maxPos(di));
7381
}
7482

7583
function thisBarPositionFn(di) {
76-
return _positionFn(thisBarMinPos(di), thisBarMaxPos(di));
84+
return spikePositionFn(thisBarMinPos(di), thisBarMaxPos(di));
7785
}
7886

79-
function sizeFn(di) {
80-
var v = sizeVal;
81-
var b = di.b;
87+
function getSize(di) {
8288
var s = di[sizeLetter];
8389

8490
if(isWaterfall) {
8591
var rawS = Math.abs(di.rawS) || 0;
86-
if(v > 0) {
92+
if(sizeVal > 0) {
8793
s += rawS;
88-
} else if(v < 0) {
94+
} else if(sizeVal < 0) {
8995
s -= rawS;
9096
}
9197
}
9298

99+
return s;
100+
}
101+
102+
function sizeFn(di) {
103+
var v = sizeVal;
104+
var b = di.b;
105+
var s = getSize(di);
106+
93107
// add a gradient so hovering near the end of a
94108
// bar makes it a little closer match
95109
return Fx.inbox(b - v, s - v, maxHoverDistance + (s - v) / (s - b) - 1);
96110
}
97111

112+
function thisBarSizeFn(di) {
113+
var v = sizeVal;
114+
var b = di.b;
115+
var s = getSize(di);
116+
117+
// add a gradient so hovering near the end of a
118+
// bar makes it a little closer match
119+
return Fx.inbox(b - v, s - v, maxSpikeDistance + (s - v) / (s - b) - 1);
120+
}
121+
98122
if(trace.orientation === 'h') {
99123
posVal = yval;
100124
sizeVal = xval;
@@ -158,7 +182,7 @@ function hoverOnBars(pointData, xval, yval, hovermode) {
158182
pointData.baseLabel = hoverLabelText(sa, di.b);
159183

160184
// spikelines always want "closest" distance regardless of hovermode
161-
pointData.spikeDistance = (sizeFn(di) + thisBarPositionFn(di)) / 2 - maxHoverDistance;
185+
pointData.spikeDistance = (thisBarSizeFn(di) + thisBarPositionFn(di)) / 2;
162186
// they also want to point to the data value, regardless of where the label goes
163187
// in case of bars shifted within groups
164188
pointData[posLetter + 'Spike'] = pa.c2p(di.p, true);

test/jasmine/tests/hover_spikeline_test.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,12 +419,13 @@ describe('spikeline hover', function() {
419419
.then(done, done.fail);
420420
});
421421

422-
it('correctly select the closest bar even when setting spikedistance to -1', function(done) {
422+
it('correctly select the closest bar even when setting spikedistance to -1 (case of x hovermode)', function(done) {
423423
var mock = require('@mocks/bar_stack-with-gaps');
424424
var mockCopy = Lib.extendDeep({}, mock);
425425
mockCopy.layout.xaxis.showspikes = true;
426426
mockCopy.layout.yaxis.showspikes = true;
427427
mockCopy.layout.spikedistance = -1;
428+
mockCopy.layout.hovermode = 'x';
428429

429430
Plotly.newPlot(gd, mockCopy)
430431
.then(function() {
@@ -433,10 +434,33 @@ describe('spikeline hover', function() {
433434
expect(lines.size()).toBe(4);
434435
expect(lines[0][1].getAttribute('stroke')).toBe('#2ca02c');
435436

436-
_hover({xpx: 600, ypx: 200});
437+
_hover({xpx: 600, ypx: 100});
437438
lines = d3SelectAll('line.spikeline');
438439
expect(lines.size()).toBe(4);
440+
expect(lines[0][1].getAttribute('stroke')).toBe('#2ca02c');
441+
})
442+
.then(done, done.fail);
443+
});
444+
445+
it('correctly select the closest bar even when setting spikedistance to -1 (case of closest hovermode)', function(done) {
446+
var mock = require('@mocks/bar_stack-with-gaps');
447+
var mockCopy = Lib.extendDeep({}, mock);
448+
mockCopy.layout.xaxis.showspikes = true;
449+
mockCopy.layout.yaxis.showspikes = true;
450+
mockCopy.layout.spikedistance = -1;
451+
mockCopy.layout.hovermode = 'closest';
452+
453+
Plotly.newPlot(gd, mockCopy)
454+
.then(function() {
455+
_hover({xpx: 600, ypx: 400});
456+
var lines = d3SelectAll('line.spikeline');
457+
expect(lines.size()).toBe(4);
439458
expect(lines[0][1].getAttribute('stroke')).toBe('#1f77b4');
459+
460+
_hover({xpx: 600, ypx: 100});
461+
lines = d3SelectAll('line.spikeline');
462+
expect(lines.size()).toBe(4);
463+
expect(lines[0][1].getAttribute('stroke')).toBe('#2ca02c');
440464
})
441465
.then(done, done.fail);
442466
});

0 commit comments

Comments
 (0)