[vislib/tooltip] prevent creating unneeded tooltip elements

This commit is contained in:
Spencer Alger 2014-11-05 20:48:53 -07:00
parent 129c10fb78
commit 245b317c4f
4 changed files with 49 additions and 35 deletions

View file

@ -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;

View file

@ -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 $('<div>').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');
});

View file

@ -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;
}

View file

@ -11,6 +11,7 @@ define(function (require) {
var $window;
var $chart;
var $tooltip;
var $sizer;
function testEl(width, height, $children) {
var $el = $('<div>');
@ -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');