Created
May 14, 2020 09:02
-
-
Save aligos/e3ea9be630eae4fd639bf7044fe8a312 to your computer and use it in GitHub Desktop.
ES6 d3-tip source https://bl.ocks.org/bytesbysophie/0311395c1e082f98e67efaf2c7f9555b
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint no-plusplus: "off" */ | |
import * as d3 from 'd3'; | |
const d3Functor = function functor(v) { | |
return typeof v === 'function' ? v : () => v; | |
}; | |
export default () => { | |
let svg = null; | |
let point = null; | |
let target = null; | |
let direction = 'n'; | |
let offset = [0, 0]; | |
let html = ' '; | |
function initNode() { | |
const divNode = d3.select(document.createElement('div')); | |
divNode | |
.style('position', 'absolute') | |
.style('top', '0') | |
.style('opacity', '0') | |
.style('pointer-events', 'none') | |
.style('box-sizing', 'border-box'); | |
return divNode.node(); | |
} | |
let node = initNode(); | |
function getNodeEl() { | |
if (node === null) { | |
node = initNode(); | |
// re-add node to DOM | |
document.body.appendChild(node); | |
} | |
return d3.select(node); | |
} | |
function getSVGNode(element) { | |
const el = element.node(); | |
if (el.tagName.toLowerCase() === 'svg') return el; | |
return el.ownerSVGElement; | |
} | |
// Returns an Object {n, s, e, w, nw, sw, ne, se} | |
function getScreenBBox() { | |
let targetel = target || d3.event.target; | |
while (typeof targetel.getScreenCTM === 'undefined' && targetel.parentNode === 'undefined') { | |
targetel = targetel.parentNode; | |
} | |
const bbox = {}; | |
const matrix = targetel.getScreenCTM(); | |
const tbbox = targetel.getBBox(); | |
const { width, height } = tbbox; | |
const { x, y } = tbbox; | |
point.x = x; | |
point.y = y; | |
bbox.nw = point.matrixTransform(matrix); | |
point.x += width; | |
bbox.ne = point.matrixTransform(matrix); | |
point.y += height; | |
bbox.se = point.matrixTransform(matrix); | |
point.x -= width; | |
bbox.sw = point.matrixTransform(matrix); | |
point.y -= height / 2; | |
bbox.w = point.matrixTransform(matrix); | |
point.x += width; | |
bbox.e = point.matrixTransform(matrix); | |
point.x -= width / 2; | |
point.y -= height / 2; | |
bbox.n = point.matrixTransform(matrix); | |
point.y += height; | |
bbox.s = point.matrixTransform(matrix); | |
return bbox; | |
} | |
function directionN() { | |
const bbox = getScreenBBox(); | |
return { | |
top: bbox.n.y - node.offsetHeight, | |
left: bbox.n.x - node.offsetWidth / 2, | |
}; | |
} | |
function directionS() { | |
const bbox = getScreenBBox(); | |
return { | |
top: bbox.s.y, | |
left: bbox.s.x - node.offsetWidth / 2, | |
}; | |
} | |
function directionE() { | |
const bbox = getScreenBBox(); | |
return { | |
top: bbox.e.y - node.offsetHeight / 2, | |
left: bbox.e.x, | |
}; | |
} | |
function directionW() { | |
const bbox = getScreenBBox(); | |
return { | |
top: bbox.w.y - node.offsetHeight / 2, | |
left: bbox.w.x - node.offsetWidth, | |
}; | |
} | |
function directionNW() { | |
const bbox = getScreenBBox(); | |
return { | |
top: bbox.nw.y - node.offsetHeight, | |
left: bbox.nw.x - node.offsetWidth, | |
}; | |
} | |
function directionNE() { | |
const bbox = getScreenBBox(); | |
return { | |
top: bbox.ne.y - node.offsetHeight, | |
left: bbox.ne.x, | |
}; | |
} | |
function directionSW() { | |
const bbox = getScreenBBox(); | |
return { | |
top: bbox.sw.y, | |
left: bbox.sw.x - node.offsetWidth, | |
}; | |
} | |
function directionSE() { | |
const bbox = getScreenBBox(); | |
return { | |
top: bbox.se.y, | |
left: bbox.e.x, | |
}; | |
} | |
const directionCallbacks = { | |
n: directionN, | |
s: directionS, | |
e: directionE, | |
w: directionW, | |
nw: directionNW, | |
ne: directionNE, | |
sw: directionSW, | |
se: directionSE, | |
}; | |
const directions = Object.keys(directionCallbacks); | |
function tip(vis) { | |
svg = getSVGNode(vis); | |
point = svg.createSVGPoint(); | |
document.body.appendChild(node); | |
} | |
// Public - show the tooltip on the screen | |
// | |
// Returns a tip | |
tip.show = (...rest) => { | |
const args = Array.prototype.slice.call(rest); | |
if (args[args.length - 1] instanceof SVGElement) target = args.pop(); | |
const content = html.apply(this, args); | |
const poffset = offset.apply(this, args); | |
const dir = direction.apply(this, args); | |
const nodel = getNodeEl(); | |
let i = directions.length; | |
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; | |
const scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; | |
nodel | |
.html(content) | |
.style('position', 'absolute') | |
.style('opacity', 1) | |
.style('pointer-events', 'all'); | |
while (i--) nodel.classed(directions[i], false); | |
const coords = directionCallbacks[dir].apply(this); | |
nodel | |
.classed(dir, true) | |
.style('top', `${coords.top + poffset[0] + scrollTop}px`) | |
.style('left', `${coords.left + poffset[1] + scrollLeft}px`); | |
return tip; | |
}; | |
// Public - hide the tooltip | |
// | |
// Returns a tip | |
tip.hide = () => { | |
const nodel = getNodeEl(); | |
nodel.style('opacity', 0).style('pointer-events', 'none'); | |
return tip; | |
}; | |
// Public: Proxy attr calls to the d3 tip container. Sets or gets attribute value. | |
// | |
// n - name of the attribute | |
// v - value of the attribute | |
// | |
// Returns tip or attribute value | |
tip.attr = (n, ...rest) => { | |
if (rest.length < 2 && typeof n === 'string') { | |
return getNodeEl().attr(n); | |
} | |
const args = Array.prototype.slice.call(rest); | |
d3.selection.prototype.attr.apply(getNodeEl(), args); | |
return tip; | |
}; | |
// Public: Proxy style calls to the d3 tip container. Sets or gets a style value. | |
// | |
// n - name of the property | |
// v - value of the property | |
// | |
// Returns tip or style property value | |
tip.style = (n, ...rest) => { | |
// debugger; | |
if (rest.length < 2 && typeof n === 'string') { | |
return getNodeEl().style(n); | |
} | |
const args = Array.prototype.slice.call(rest); | |
if (args.length === 1) { | |
const styles = args[0]; | |
Object.keys(styles).forEach((key) => | |
d3.selection.prototype.style.apply(getNodeEl(), [key, styles[key]]) | |
); | |
} | |
return tip; | |
}; | |
// Public: Set or get the direction of the tooltip | |
// | |
// v - One of n(north), s(south), e(east), or w(west), nw(northwest), | |
// sw(southwest), ne(northeast) or se(southeast) | |
// | |
// Returns tip or direction | |
tip.direction = (v, ...rest) => { | |
if (!rest.length) return direction; | |
direction = v == null ? v : d3Functor(v); | |
return tip; | |
}; | |
// Public: Sets or gets the offset of the tip | |
// | |
// v - Array of [x, y] offset | |
// | |
// Returns offset or | |
tip.offset = (v, ...rest) => { | |
if (!rest.length) return offset; | |
offset = v == null ? v : d3Functor(v); | |
return tip; | |
}; | |
// Public: sets or gets the html value of the tooltip | |
// | |
// v - String value of the tip | |
// | |
// Returns html value or tip | |
tip.html = (v, ...rest) => { | |
if (!rest.length) return html; | |
html = v == null ? v : d3Functor(v); | |
return tip; | |
}; | |
// Public: destroys the tooltip and removes it from the DOM | |
// | |
// Returns a tip | |
tip.destroy = () => { | |
if (node) { | |
getNodeEl().remove(); | |
node = null; | |
} | |
return tip; | |
}; | |
return tip; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment