diff --git a/src/kibana/components/vislib/components/tooltip/_position_tooltip.js b/src/kibana/components/vislib/components/tooltip/_position_tooltip.js index eefc01afdc28..30c53032622a 100644 --- a/src/kibana/components/vislib/components/tooltip/_position_tooltip.js +++ b/src/kibana/components/vislib/components/tooltip/_position_tooltip.js @@ -5,13 +5,13 @@ define(function (require) { var OFFSET = 10; var $clone; - function positionTooltip($window, $chart, $tooltip, event) { + function positionTooltip($window, $chart, $tooltip, $sizer, event) { $chart = $($chart); $tooltip = $($tooltip); if (!$chart.size() || !$tooltip.size()) return; - var size = getTtSize($tooltip); + var size = getTtSize($tooltip, $sizer); var pos = getBasePosition(size, event); var overflow = getOverflow(size, pos, [$chart, $window]); @@ -19,25 +19,15 @@ define(function (require) { return placeToAvoidOverflow(pos, overflow); } - function getTtSize($tooltip) { - if (!$clone || $clone.html() !== $tooltip.html()) { - $clone && $clone.remove(); - - $clone = $tooltip - .clone() - .addClass('vis-tooltip-sizing-clone') - .css({ - visibility: 'hidden', - position: 'absolute', - top: -500, - left: -500 - }) - .appendTo('body'); + function getTtSize($tooltip, $sizer) { + var ttHtml = $tooltip.html(); + if ($sizer.html() !== ttHtml) { + $sizer.html(ttHtml); } var size = { - width: $clone.outerWidth(), - height: $clone.outerHeight() + width: $sizer.outerWidth(), + height: $sizer.outerHeight() }; return size; diff --git a/src/kibana/components/vislib/components/tooltip/tooltip.js b/src/kibana/components/vislib/components/tooltip/tooltip.js index 54ac6aff5b70..b0aeeb5bc907 100644 --- a/src/kibana/components/vislib/components/tooltip/tooltip.js +++ b/src/kibana/components/vislib/components/tooltip/tooltip.js @@ -1,5 +1,6 @@ define(function (require) { return function TooltipFactory(d3, Private) { + var _ = require('lodash'); var $ = require('jquery'); require('css!components/vislib/styles/main'); @@ -20,13 +21,26 @@ define(function (require) { this.el = el; this.formatter = formatter; this.events = events; - this.tooltipClass = 'vis-tooltip'; this.containerClass = 'vis-wrapper'; + this.tooltipClass = 'vis-tooltip'; + this.tooltipSizerClass = 'vis-tooltip-sizing-clone'; this.$window = $(window); this.$chart = $(el).find('.' + this.containerClass); } + Tooltip.prototype.$get = _.once(function () { + return $('
').addClass(this.tooltipClass).appendTo(document.body); + }); + + Tooltip.prototype.$getSizer = _.once(function () { + return this.$get() + .clone() + .removeClass(this.tooltipClass) + .addClass(this.tooltipSizerClass) + .appendTo(document.body); + }); + /** * Calculates values for the tooltip placement * @@ -47,17 +61,14 @@ define(function (require) { var tooltipFormatter = this.formatter; return function (selection) { - - if (d3.select('body').select('.' + self.tooltipClass)[0][0] === null) { - d3.select('body').append('div').attr('class', self.tooltipClass); - } + var $tooltip = self.$get(); + var $sizer = self.$getSizer(); + var tooltipSelection = d3.select($tooltip.get(0)); if (self.container === undefined || self.container !== d3.select(self.el).select('.' + self.containerClass)) { self.container = d3.select(self.el).select('.' + self.containerClass); } - var tooltipDiv = d3.select('.' + self.tooltipClass); - selection.each(function (d, i) { var element = d3.select(this); @@ -66,7 +77,8 @@ define(function (require) { var placement = self.getTooltipPlacement( self.$window, self.$chart, - $('.' + self.tooltipClass), + $tooltip, + $sizer, d3.event ); if (!placement) return; @@ -74,14 +86,14 @@ define(function (require) { var events = self.events ? self.events.eventResponse(d, i) : d; // return text and position for tooltip - return tooltipDiv.datum(events) + return tooltipSelection.datum(events) .html(tooltipFormatter) .style('visibility', 'visible') .style('left', placement.left + 'px') .style('top', placement.top + 'px'); }) .on('mouseout.tip', function () { - return tooltipDiv.style('visibility', 'hidden') + return tooltipSelection.style('visibility', 'hidden') .style('left', '-500px') .style('top', '-500px'); }); diff --git a/src/kibana/components/vislib/styles/_tooltip.less b/src/kibana/components/vislib/styles/_tooltip.less index 20d6b1a43475..a16a321bbb7f 100644 --- a/src/kibana/components/vislib/styles/_tooltip.less +++ b/src/kibana/components/vislib/styles/_tooltip.less @@ -1,6 +1,7 @@ @import (reference) "../../../styles/main"; -.vis-tooltip { +.vis-tooltip, +.vis-tooltip-sizing-clone { visibility: hidden; line-height: 1.1; font-size: 12px; @@ -34,3 +35,10 @@ } } } + +.vis-tooltip-sizing-clone { + visibility: hidden; + position: fixed; + top: -500px; + left: -500px; +} \ No newline at end of file diff --git a/test/unit/specs/vislib/components/tooltip/positioning.js b/test/unit/specs/vislib/components/tooltip/positioning.js index 15d028e78a34..7d43dbbbd511 100644 --- a/test/unit/specs/vislib/components/tooltip/positioning.js +++ b/test/unit/specs/vislib/components/tooltip/positioning.js @@ -11,6 +11,7 @@ define(function (require) { var $window; var $chart; var $tooltip; + var $sizer; function testEl(width, height, $children) { var $el = $('
'); @@ -42,11 +43,13 @@ define(function (require) { $tooltip = testEl([50, 100], [35, 75]) ) ); + + $sizer = $tooltip.clone().appendTo($window); }); afterEach(function () { $window.remove(); - $window = $chart = $tooltip = null; + $window = $chart = $tooltip = $sizer = null; posTT.removeClone(); }); @@ -67,7 +70,7 @@ define(function (require) { var w = sinon.spy($.fn, 'outerWidth'); var h = sinon.spy($.fn, 'outerHeight'); - posTT.getTtSize($tooltip); + posTT.getTtSize($tooltip, $sizer); [w, h].forEach(function (spy) { expect(spy).to.have.property('callCount', 1); @@ -81,7 +84,7 @@ define(function (require) { describe('getBasePosition()', function () { it('calculates the offset values for the four positions', function () { - var size = posTT.getTtSize($tooltip); + var size = posTT.getTtSize($tooltip, $sizer); var pos = posTT.getBasePosition(size, makeEvent()); positions.forEach(function (p) { @@ -110,7 +113,8 @@ define(function (require) { it('determines how much the base placement overflows the containing bounds in each direction', function () { // size the tooltip very small so it won't collide with the edges $tooltip.css({ width: 15, height: 15 }); - var size = posTT.getTtSize($tooltip); + $sizer.css({ width: 15, height: 15 }); + var size = posTT.getTtSize($tooltip, $sizer); expect(size).to.have.property('width', 15); expect(size).to.have.property('height', 15); @@ -127,7 +131,7 @@ define(function (require) { }); it('identifies an overflow with a positive value in that direction', function () { - var size = posTT.getTtSize($tooltip); + var size = posTT.getTtSize($tooltip, $sizer); // position the element based on a mouse that is in the bottom right hand courner of the chart var pos = posTT.getBasePosition(size, makeEvent(0.99, 0.99)); @@ -155,7 +159,7 @@ define(function (require) { function check(xPercent, yPercent/*, directions... */) { var directions = _.rest(arguments, 2); var event = makeEvent(xPercent, yPercent); - var placement = posTT($window, $chart, $tooltip, event); + var placement = posTT($window, $chart, $tooltip, $sizer, event); expect(placement).to.have.property('top').and.property('left');