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');