diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 25d5f02f597..dfab610f57c 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1825,9 +1825,16 @@ axes.doTicks = function(gd, axid, skipTitle) { // tick labels - for now just the main labels. // TODO: mirror labels, esp for subplots var tickLabels = container.selectAll('g.' + tcls).data(vals, datafn); - if(!ax.showticklabels || !isNumeric(position)) { + + if(!isNumeric(position)) { + tickLabels.remove(); + drawAxTitle(); + return; + } + if(!ax.showticklabels) { tickLabels.remove(); drawAxTitle(); + calcBoundingBox(); return; } @@ -1993,23 +2000,59 @@ axes.doTicks = function(gd, axid, skipTitle) { } function calcBoundingBox() { - var bBox = container.node().getBoundingClientRect(); - var gdBB = gd.getBoundingClientRect(); - - /* - * the way we're going to use this, the positioning that matters - * is relative to the origin of gd. This is important particularly - * if gd is scrollable, and may have been scrolled between the time - * we calculate this and the time we use it - */ - ax._boundingBox = { - width: bBox.width, - height: bBox.height, - left: bBox.left - gdBB.left, - right: bBox.right - gdBB.left, - top: bBox.top - gdBB.top, - bottom: bBox.bottom - gdBB.top - }; + if(ax.showticklabels) { + var gdBB = gd.getBoundingClientRect(); + var bBox = container.node().getBoundingClientRect(); + + /* + * the way we're going to use this, the positioning that matters + * is relative to the origin of gd. This is important particularly + * if gd is scrollable, and may have been scrolled between the time + * we calculate this and the time we use it + */ + + ax._boundingBox = { + width: bBox.width, + height: bBox.height, + left: bBox.left - gdBB.left, + right: bBox.right - gdBB.left, + top: bBox.top - gdBB.top, + bottom: bBox.bottom - gdBB.top + }; + } else { + var gs = fullLayout._size; + var pos; + + // set dummy bbox for ticklabel-less axes + + if(axLetter === 'x') { + pos = ax.anchor === 'free' ? + gs.t + gs.h * (1 - ax.position) : + gs.t + gs.h * (1 - ax._anchorAxis.domain[{bottom: 0, top: 1}[ax.side]]); + + ax._boundingBox = { + top: pos, + bottom: pos, + left: ax._offset, + rigth: ax._offset + ax._length, + width: ax._length, + height: 0 + }; + } else { + pos = ax.anchor === 'free' ? + gs.l + gs.w * ax.position : + gs.l + gs.w * ax._anchorAxis.domain[{left: 0, right: 1}[ax.side]]; + + ax._boundingBox = { + left: pos, + right: pos, + bottom: ax._offset + ax._length, + top: ax._offset, + height: ax._length, + width: 0 + }; + } + } /* * for spikelines: what's the full domain of positions in the diff --git a/test/jasmine/tests/hover_spikeline_test.js b/test/jasmine/tests/hover_spikeline_test.js index 0b1a1cc1ea8..dad000be93f 100644 --- a/test/jasmine/tests/hover_spikeline_test.js +++ b/test/jasmine/tests/hover_spikeline_test.js @@ -4,40 +4,110 @@ var Plotly = require('@lib/index'); var Fx = require('@src/components/fx'); var Lib = require('@src/lib'); +var fail = require('../assets/fail_test'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); +var customMatchers = require('../assets/custom_matchers'); describe('spikeline', function() { 'use strict'; - var mock = require('@mocks/19.json'); + beforeAll(function() { + jasmine.addMatchers(customMatchers); + }); afterEach(destroyGraphDiv); describe('hover', function() { - var mockCopy = Lib.extendDeep({}, mock); - - mockCopy.layout.xaxis.showspikes = true; - mockCopy.layout.xaxis.spikemode = 'toaxis'; - mockCopy.layout.yaxis.showspikes = true; - mockCopy.layout.yaxis.spikemode = 'toaxis+marker'; - mockCopy.layout.xaxis2.showspikes = true; - mockCopy.layout.xaxis2.spikemode = 'toaxis'; - mockCopy.layout.hovermode = 'closest'; - beforeEach(function(done) { - Plotly.plot(createGraphDiv(), mockCopy.data, mockCopy.layout).then(done); - }); + var gd; + + function makeMock() { + var _mock = Lib.extendDeep({}, require('@mocks/19.json')); + _mock.layout.xaxis.showspikes = true; + _mock.layout.xaxis.spikemode = 'toaxis'; + _mock.layout.yaxis.showspikes = true; + _mock.layout.yaxis.spikemode = 'toaxis+marker'; + _mock.layout.xaxis2.showspikes = true; + _mock.layout.xaxis2.spikemode = 'toaxis'; + _mock.layout.hovermode = 'closest'; + return _mock; + } + + function _hover(evt, subplot) { + Fx.hover(gd, evt, subplot); + delete gd._lastHoverTime; + } + + function _assert(lineExpect, circleExpect) { + var TOL = 5; + var lines = d3.selectAll('line.spikeline'); + var circles = d3.selectAll('circle.spikeline'); + + expect(lines.size()).toBe(lineExpect.length, '# of line nodes'); + expect(circles.size()).toBe(circleExpect.length, '# of circle nodes'); - it('draws lines and markers on enabled axes', function() { - Fx.hover('graph', {xval: 2, yval: 3}, 'xy'); - expect(d3.selectAll('line.spikeline').size()).toEqual(4); - expect(d3.selectAll('circle.spikeline').size()).toEqual(1); + lines.each(function(_, i) { + var sel = d3.select(this); + ['x1', 'y1', 'x2', 'y2'].forEach(function(d, j) { + expect(sel.attr(d)) + .toBeWithin(lineExpect[i][j], TOL, 'line ' + i + ' attr ' + d); + }); + }); + + circles.each(function(_, i) { + var sel = d3.select(this); + ['cx', 'cy'].forEach(function(d, j) { + expect(sel.attr(d)) + .toBeWithin(circleExpect[i][j], TOL, 'circle ' + i + ' attr ' + d); + }); + }); + } + + it('draws lines and markers on enabled axes', function(done) { + gd = createGraphDiv(); + var _mock = makeMock(); + + Plotly.plot(gd, _mock).then(function() { + _hover({xval: 2, yval: 3}, 'xy'); + _assert( + [[80, 250, 557, 250], [80, 250, 557, 250], [557, 401, 557, 250], [557, 401, 557, 250]], + [[83, 250]] + ); + }) + .then(function() { + _hover({xval: 30, yval: 40}, 'x2y2'); + _assert( + [[820, 220, 820, 167], [820, 220, 820, 167]], + [] + ); + }) + .catch(fail) + .then(done); }); - it('doesn\'t draw lines and markers on disabled axes', function() { - Fx.hover('graph', {xval: 30, yval: 40}, 'x2y2'); - expect(d3.selectAll('line.spikeline').size()).toEqual(2); - expect(d3.selectAll('circle.spikeline').size()).toEqual(0); + it('draws lines and markers on enabled axes w/o tick labels', function(done) { + gd = createGraphDiv(); + var _mock = makeMock(); + + _mock.layout.xaxis.showticklabels = false; + _mock.layout.yaxis.showticklabels = false; + + Plotly.plot(gd, _mock).then(function() { + _hover({xval: 2, yval: 3}, 'xy'); + _assert( + [[80, 250, 557, 250], [80, 250, 557, 250], [557, 401, 557, 250], [557, 401, 557, 250]], + [[83, 250]] + ); + }) + .then(function() { + _hover({xval: 30, yval: 40}, 'x2y2'); + _assert( + [[820, 220, 820, 167], [820, 220, 820, 167]], + [] + ); + }) + .catch(fail) + .then(done); }); }); });