Skip to content
Draft
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
17 changes: 15 additions & 2 deletions src/components/fx/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,17 @@ exports.loneHover = function loneHover(hoverItems, opts) {
return multiHover ? hoverLabel : hoverLabel.node();
};

// can either axis of this trace draw a spike to a point that is not hovered?
// 'hovered data' spikes snap to the hover label, so they need hoverData to exist.
function canSpikeToClosest(pointData) {
var xa = pointData.xa;
var ya = pointData.ya;
return Boolean(
(xa && xa.showspikes && xa.spikesnap !== 'hovered data') ||
(ya && ya.showspikes && ya.spikesnap !== 'hovered data')
);
}

// The actual implementation is here:
function _hover(gd, evt, subplot, noHoverEvent, eventTarget) {
if (!subplot) subplot = 'xy';
Expand Down Expand Up @@ -659,8 +670,10 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) {
}

// Now if there is range to look in, find the points to draw the spikelines
// Do it only if there is no hoverData
if (hasCartesian && spikedistance !== 0) {
// Do it only if there is no hoverData, and only if one of this trace's axes
// can actually draw a spike to a non-hovered point - the search below is
// unbounded when spikedistance is -1 (the default), so it is expensive.
if (hasCartesian && spikedistance !== 0 && canSpikeToClosest(pointData)) {
if (hoverData.length === 0) {
pointData.distance = spikedistance;
pointData.index = false;
Expand Down
70 changes: 70 additions & 0 deletions test/jasmine/tests/hover_spikeline_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -856,3 +856,73 @@ describe('spikeline hover', function() {
});
});
});

describe('spikeline search', function() {
'use strict';

var gd;

beforeEach(function() {
gd = createGraphDiv();
});

afterEach(destroyGraphDiv);

// the search for a point to spike to is unbounded when spikedistance is -1
// (the default), so it must not run when no spike can come out of it
function countHoverPointsCalls(layout) {
return Plotly.newPlot(gd, [{
x: [1, 2, 3],
y: [1, 2, 3],
mode: 'markers'
}], Lib.extendFlat({width: 400, height: 400}, layout))
.then(function() {
var spy = spyOn(gd.calcdata[0][0].trace._module, 'hoverPoints').and.callThrough();

Lib.clearThrottle();
// hover on empty space, so there is no hoverData
Fx.hover(gd, {xpx: 40, ypx: 40}, 'xy');

return spy.calls.count();
});
}

it('does not look for spike points when no axis shows spikes', function(done) {
countHoverPointsCalls({})
.then(function(count) {
expect(count).toBe(1);
})
.then(done, done.fail);
});

it('does not look for spike points when spikesnap is "hovered data"', function(done) {
countHoverPointsCalls({
xaxis: {showspikes: true, spikesnap: 'hovered data'},
yaxis: {showspikes: true, spikesnap: 'hovered data'}
})
.then(function(count) {
expect(count).toBe(1);
})
.then(done, done.fail);
});

it('looks for spike points when an axis snaps spikes to data', function(done) {
countHoverPointsCalls({
xaxis: {showspikes: true, spikesnap: 'data'}
})
.then(function(count) {
expect(count).toBe(2);
})
.then(done, done.fail);
});

it('looks for spike points when an axis snaps spikes to the cursor', function(done) {
countHoverPointsCalls({
yaxis: {showspikes: true, spikesnap: 'cursor'}
})
.then(function(count) {
expect(count).toBe(2);
})
.then(done, done.fail);
});
});
Loading