From c1b19e7c9e5eb30d175998eba15d0751a66af3a3 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Mon, 26 Nov 2018 14:12:54 -0500 Subject: [PATCH] Generate unique _id for each tooltip This lets allows multiple tooltips around nested elements. The previous code did a bunch of .selectAll('.tooltip') that would break if it matched multiple tooltips. --- modules/util/tooltip.js | 373 ++++++++++++++++++++-------------------- 1 file changed, 190 insertions(+), 183 deletions(-) diff --git a/modules/util/tooltip.js b/modules/util/tooltip.js index fefdf266b..fc7ada3fb 100644 --- a/modules/util/tooltip.js +++ b/modules/util/tooltip.js @@ -1,192 +1,199 @@ import { select as d3_select } from 'd3-selection'; import { utilFunctor } from './index'; +var _tooltipID = 0; export function tooltip() { - var tooltip = function(selection) { - selection.each(setup); - }, - animation = utilFunctor(false), - html = utilFunctor(false), - title = function() { - var title = this.getAttribute('data-original-title'); - if (title) { + var _id = _tooltipID++; + var tooltip = function(selection) { + selection.each(setup); + }; + var _animation = utilFunctor(false); + var _title = function() { + var title = this.getAttribute('data-original-title'); + if (title) { + return title; + } else { + title = this.getAttribute('title'); + this.removeAttribute('title'); + this.setAttribute('data-original-title', title); + } return title; - } else { - title = this.getAttribute('title'); - this.removeAttribute('title'); - this.setAttribute('data-original-title', title); - } - return title; - }, - over = 'mouseenter.tooltip', - out = 'mouseleave.tooltip', - placement = utilFunctor('top'); - - - tooltip.title = function(_) { - if (arguments.length) { - title = utilFunctor(_); - return tooltip; - } else { - return title; - } - }; - - - tooltip.html = function(_) { - if (arguments.length) { - html = utilFunctor(_); - return tooltip; - } else { - return html; - } - }; - - - tooltip.placement = function(_) { - if (arguments.length) { - placement = utilFunctor(_); - return tooltip; - } else { - return placement; - } - }; - - - tooltip.show = function(selection) { - selection.each(show); - }; - - - tooltip.hide = function(selection) { - selection.each(hide); - }; - - - tooltip.toggle = function(selection) { - selection.each(toggle); - }; - - - tooltip.destroy = function(selection) { - selection - .on(over, null) - .on(out, null) - .attr('title', function() { - return this.getAttribute('data-original-title') || this.getAttribute('title'); - }) - .attr('data-original-title', null) - .selectAll('.tooltip') - .remove(); - }; - - - function setup() { - var root = d3_select(this), - animate = animation.apply(this, arguments), - tip = root.selectAll('.tooltip').data([0]); - - var enter = tip.enter() - .append('div') - .attr('class', 'tooltip'); - - enter - .append('div') - .attr('class', 'tooltip-arrow'); - - enter - .append('div') - .attr('class', 'tooltip-inner'); - - tip = enter - .merge(tip); - - if (animate) { - tip.classed('fade', true); - } - - var place = placement.apply(this, arguments); - tip.classed(place, true); - - root.on(over, show); - root.on(out, hide); - } - - - function show() { - var root = d3_select(this), - content = title.apply(this, arguments), - tip = root.selectAll('.tooltip') - .classed('in', true), - markup = html.apply(this, arguments); - - tip.selectAll('.tooltip-inner')[markup ? 'html' : 'text'](content); - var place = placement.apply(this, arguments), - outer = getPosition(root.node()), - inner = getPosition(tip.node()), - pos; - - switch (place) { - case 'top': - pos = {x: outer.x + (outer.w - inner.w) / 2, y: outer.y - inner.h}; - break; - case 'right': - pos = {x: outer.x + outer.w, y: outer.y + (outer.h - inner.h) / 2}; - break; - case 'left': - pos = {x: outer.x - inner.w, y: outer.y + (outer.h - inner.h) / 2}; - break; - case 'bottom': - pos = {x: Math.max(0, outer.x + (outer.w - inner.w) / 2), y: outer.y + outer.h}; - break; - } - - if (pos) { - tip.style('left', ~~pos.x + 'px').style('top', ~~pos.y + 'px'); - } else { - tip.style('left', null).style('top', null); - } - - this.tooltipVisible = true; - } - - - function hide() { - d3_select(this).selectAll('.tooltip') - .classed('in', false); - - this.tooltipVisible = false; - } - - - function toggle() { - if (this.tooltipVisible) { - hide.apply(this, arguments); - } else { - show.apply(this, arguments); - } - } - - return tooltip; -} - - -function getPosition(node) { - var mode = d3_select(node).style('position'); - if (mode === 'absolute' || mode === 'static') { - return { - x: node.offsetLeft, - y: node.offsetTop, - w: node.offsetWidth, - h: node.offsetHeight }; - } else { - return { - x: 0, - y: 0, - w: node.offsetWidth, - h: node.offsetHeight + var _html = utilFunctor(false); + var _placement = utilFunctor('top'); + + + tooltip.title = function(val) { + if (arguments.length) { + _title = utilFunctor(val); + return tooltip; + } else { + return _title; + } }; - } + + + tooltip.html = function(val) { + if (arguments.length) { + _html = utilFunctor(val); + return tooltip; + } else { + return _html; + } + }; + + + tooltip.placement = function(val) { + if (arguments.length) { + _placement = utilFunctor(val); + return tooltip; + } else { + return _placement; + } + }; + + + tooltip.show = function(selection) { + selection.each(show); + }; + + + tooltip.hide = function(selection) { + selection.each(hide); + }; + + + tooltip.toggle = function(selection) { + selection.each(toggle); + }; + + + tooltip.destroy = function(selection) { + selection + .on('mouseenter.tooltip', null) + .on('mouseleave.tooltip', null) + .attr('title', function() { + return this.getAttribute('data-original-title') || this.getAttribute('title'); + }) + .attr('data-original-title', null) + .selectAll('.tooltip-' + _id) + .remove(); + }; + + + function setup() { + var root = d3_select(this); + var animate = _animation.apply(this, arguments); + var tip = root.selectAll('.tooltip-' + _id) + .data([0]); + + var enter = tip.enter() + .append('div') + .attr('class', 'tooltip tooltip-' + _id); + + enter + .append('div') + .attr('class', 'tooltip-arrow'); + + enter + .append('div') + .attr('class', 'tooltip-inner'); + + tip = enter + .merge(tip); + + if (animate) { + tip.classed('fade', true); + } + + var place = _placement.apply(this, arguments); + tip.classed(place, true); + + root.on('mouseenter.tooltip', show); + root.on('mouseleave.tooltip', hide); + } + + + function show() { + var root = d3_select(this); + var content = _title.apply(this, arguments); + var tip = root.selectAll('.tooltip-' + _id); + + if (tip.empty()) { // tooltip was removed somehow, put it back + root.call(tooltip.destroy); + root.each(setup); + tip = root.selectAll('.tooltip-' + _id); + } + + tip.classed('in', true); + var markup = _html.apply(this, arguments); + + tip.selectAll('.tooltip-inner')[markup ? 'html' : 'text'](content); + var place = _placement.apply(this, arguments); + var outer = getPosition(root.node()); + var inner = getPosition(tip.node()); + var pos; + + switch (place) { + case 'top': + pos = { x: outer.x + (outer.w - inner.w) / 2, y: outer.y - inner.h }; + break; + case 'right': + pos = { x: outer.x + outer.w, y: outer.y + (outer.h - inner.h) / 2 }; + break; + case 'left': + pos = { x: outer.x - inner.w, y: outer.y + (outer.h - inner.h) / 2 }; + break; + case 'bottom': + pos = { x: Math.max(0, outer.x + (outer.w - inner.w) / 2), y: outer.y + outer.h }; + break; + } + + if (pos) { + tip.style('left', ~~pos.x + 'px').style('top', ~~pos.y + 'px'); + } else { + tip.style('left', null).style('top', null); + } + + this.tooltipVisible = true; + + + function getPosition(node) { + var mode = d3_select(node).style('position'); + if (mode === 'absolute' || mode === 'static') { + return { + x: node.offsetLeft, + y: node.offsetTop, + w: node.offsetWidth, + h: node.offsetHeight + }; + } else { + return { + x: 0, + y: 0, + w: node.offsetWidth, + h: node.offsetHeight + }; + } + } + } + + + function hide() { + d3_select(this).selectAll('.tooltip-' + _id).classed('in', false); + this.tooltipVisible = false; + } + + + function toggle() { + if (this.tooltipVisible) { + hide.apply(this, arguments); + } else { + show.apply(this, arguments); + } + } + + + return tooltip; }