Skip to content

Commit 624d5f6

Browse files
authored
Merge pull request #1980 from plotly/spikes-when-no-ticklabels
Make hover spikes work when no tick labels are present
2 parents b1368f6 + e45142d commit 624d5f6

File tree

2 files changed

+152
-39
lines changed

2 files changed

+152
-39
lines changed

src/plots/cartesian/axes.js

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,9 +1827,16 @@ axes.doTicks = function(gd, axid, skipTitle) {
18271827
// tick labels - for now just the main labels.
18281828
// TODO: mirror labels, esp for subplots
18291829
var tickLabels = container.selectAll('g.' + tcls).data(vals, datafn);
1830-
if(!ax.showticklabels || !isNumeric(position)) {
1830+
1831+
if(!isNumeric(position)) {
1832+
tickLabels.remove();
1833+
drawAxTitle();
1834+
return;
1835+
}
1836+
if(!ax.showticklabels) {
18311837
tickLabels.remove();
18321838
drawAxTitle();
1839+
calcBoundingBox();
18331840
return;
18341841
}
18351842

@@ -1995,23 +2002,59 @@ axes.doTicks = function(gd, axid, skipTitle) {
19952002
}
19962003

19972004
function calcBoundingBox() {
1998-
var bBox = container.node().getBoundingClientRect();
1999-
var gdBB = gd.getBoundingClientRect();
2000-
2001-
/*
2002-
* the way we're going to use this, the positioning that matters
2003-
* is relative to the origin of gd. This is important particularly
2004-
* if gd is scrollable, and may have been scrolled between the time
2005-
* we calculate this and the time we use it
2006-
*/
2007-
ax._boundingBox = {
2008-
width: bBox.width,
2009-
height: bBox.height,
2010-
left: bBox.left - gdBB.left,
2011-
right: bBox.right - gdBB.left,
2012-
top: bBox.top - gdBB.top,
2013-
bottom: bBox.bottom - gdBB.top
2014-
};
2005+
if(ax.showticklabels) {
2006+
var gdBB = gd.getBoundingClientRect();
2007+
var bBox = container.node().getBoundingClientRect();
2008+
2009+
/*
2010+
* the way we're going to use this, the positioning that matters
2011+
* is relative to the origin of gd. This is important particularly
2012+
* if gd is scrollable, and may have been scrolled between the time
2013+
* we calculate this and the time we use it
2014+
*/
2015+
2016+
ax._boundingBox = {
2017+
width: bBox.width,
2018+
height: bBox.height,
2019+
left: bBox.left - gdBB.left,
2020+
right: bBox.right - gdBB.left,
2021+
top: bBox.top - gdBB.top,
2022+
bottom: bBox.bottom - gdBB.top
2023+
};
2024+
} else {
2025+
var gs = fullLayout._size;
2026+
var pos;
2027+
2028+
// set dummy bbox for ticklabel-less axes
2029+
2030+
if(axLetter === 'x') {
2031+
pos = ax.anchor === 'free' ?
2032+
gs.t + gs.h * (1 - ax.position) :
2033+
gs.t + gs.h * (1 - ax._anchorAxis.domain[{bottom: 0, top: 1}[ax.side]]);
2034+
2035+
ax._boundingBox = {
2036+
top: pos,
2037+
bottom: pos,
2038+
left: ax._offset,
2039+
rigth: ax._offset + ax._length,
2040+
width: ax._length,
2041+
height: 0
2042+
};
2043+
} else {
2044+
pos = ax.anchor === 'free' ?
2045+
gs.l + gs.w * ax.position :
2046+
gs.l + gs.w * ax._anchorAxis.domain[{left: 0, right: 1}[ax.side]];
2047+
2048+
ax._boundingBox = {
2049+
left: pos,
2050+
right: pos,
2051+
bottom: ax._offset + ax._length,
2052+
top: ax._offset,
2053+
height: ax._length,
2054+
width: 0
2055+
};
2056+
}
2057+
}
20152058

20162059
/*
20172060
* for spikelines: what's the full domain of positions in the

test/jasmine/tests/hover_spikeline_test.js

Lines changed: 91 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,110 @@ var Plotly = require('@lib/index');
44
var Fx = require('@src/components/fx');
55
var Lib = require('@src/lib');
66

7+
var fail = require('../assets/fail_test');
78
var createGraphDiv = require('../assets/create_graph_div');
89
var destroyGraphDiv = require('../assets/destroy_graph_div');
10+
var customMatchers = require('../assets/custom_matchers');
911

1012
describe('spikeline', function() {
1113
'use strict';
1214

13-
var mock = require('@mocks/19.json');
15+
beforeAll(function() {
16+
jasmine.addMatchers(customMatchers);
17+
});
1418

1519
afterEach(destroyGraphDiv);
1620

1721
describe('hover', function() {
18-
var mockCopy = Lib.extendDeep({}, mock);
19-
20-
mockCopy.layout.xaxis.showspikes = true;
21-
mockCopy.layout.xaxis.spikemode = 'toaxis';
22-
mockCopy.layout.yaxis.showspikes = true;
23-
mockCopy.layout.yaxis.spikemode = 'toaxis+marker';
24-
mockCopy.layout.xaxis2.showspikes = true;
25-
mockCopy.layout.xaxis2.spikemode = 'toaxis';
26-
mockCopy.layout.hovermode = 'closest';
27-
beforeEach(function(done) {
28-
Plotly.plot(createGraphDiv(), mockCopy.data, mockCopy.layout).then(done);
29-
});
22+
var gd;
23+
24+
function makeMock() {
25+
var _mock = Lib.extendDeep({}, require('@mocks/19.json'));
26+
_mock.layout.xaxis.showspikes = true;
27+
_mock.layout.xaxis.spikemode = 'toaxis';
28+
_mock.layout.yaxis.showspikes = true;
29+
_mock.layout.yaxis.spikemode = 'toaxis+marker';
30+
_mock.layout.xaxis2.showspikes = true;
31+
_mock.layout.xaxis2.spikemode = 'toaxis';
32+
_mock.layout.hovermode = 'closest';
33+
return _mock;
34+
}
35+
36+
function _hover(evt, subplot) {
37+
Fx.hover(gd, evt, subplot);
38+
delete gd._lastHoverTime;
39+
}
40+
41+
function _assert(lineExpect, circleExpect) {
42+
var TOL = 5;
43+
var lines = d3.selectAll('line.spikeline');
44+
var circles = d3.selectAll('circle.spikeline');
45+
46+
expect(lines.size()).toBe(lineExpect.length, '# of line nodes');
47+
expect(circles.size()).toBe(circleExpect.length, '# of circle nodes');
3048

31-
it('draws lines and markers on enabled axes', function() {
32-
Fx.hover('graph', {xval: 2, yval: 3}, 'xy');
33-
expect(d3.selectAll('line.spikeline').size()).toEqual(4);
34-
expect(d3.selectAll('circle.spikeline').size()).toEqual(1);
49+
lines.each(function(_, i) {
50+
var sel = d3.select(this);
51+
['x1', 'y1', 'x2', 'y2'].forEach(function(d, j) {
52+
expect(sel.attr(d))
53+
.toBeWithin(lineExpect[i][j], TOL, 'line ' + i + ' attr ' + d);
54+
});
55+
});
56+
57+
circles.each(function(_, i) {
58+
var sel = d3.select(this);
59+
['cx', 'cy'].forEach(function(d, j) {
60+
expect(sel.attr(d))
61+
.toBeWithin(circleExpect[i][j], TOL, 'circle ' + i + ' attr ' + d);
62+
});
63+
});
64+
}
65+
66+
it('draws lines and markers on enabled axes', function(done) {
67+
gd = createGraphDiv();
68+
var _mock = makeMock();
69+
70+
Plotly.plot(gd, _mock).then(function() {
71+
_hover({xval: 2, yval: 3}, 'xy');
72+
_assert(
73+
[[80, 250, 557, 250], [80, 250, 557, 250], [557, 401, 557, 250], [557, 401, 557, 250]],
74+
[[83, 250]]
75+
);
76+
})
77+
.then(function() {
78+
_hover({xval: 30, yval: 40}, 'x2y2');
79+
_assert(
80+
[[820, 220, 820, 167], [820, 220, 820, 167]],
81+
[]
82+
);
83+
})
84+
.catch(fail)
85+
.then(done);
3586
});
3687

37-
it('doesn\'t draw lines and markers on disabled axes', function() {
38-
Fx.hover('graph', {xval: 30, yval: 40}, 'x2y2');
39-
expect(d3.selectAll('line.spikeline').size()).toEqual(2);
40-
expect(d3.selectAll('circle.spikeline').size()).toEqual(0);
88+
it('draws lines and markers on enabled axes w/o tick labels', function(done) {
89+
gd = createGraphDiv();
90+
var _mock = makeMock();
91+
92+
_mock.layout.xaxis.showticklabels = false;
93+
_mock.layout.yaxis.showticklabels = false;
94+
95+
Plotly.plot(gd, _mock).then(function() {
96+
_hover({xval: 2, yval: 3}, 'xy');
97+
_assert(
98+
[[80, 250, 557, 250], [80, 250, 557, 250], [557, 401, 557, 250], [557, 401, 557, 250]],
99+
[[83, 250]]
100+
);
101+
})
102+
.then(function() {
103+
_hover({xval: 30, yval: 40}, 'x2y2');
104+
_assert(
105+
[[820, 220, 820, 167], [820, 220, 820, 167]],
106+
[]
107+
);
108+
})
109+
.catch(fail)
110+
.then(done);
41111
});
42112
});
43113
});

0 commit comments

Comments
 (0)