Efficient tooltip positioning in D3.js chart
I was just scribbling on paper without a specific goal. Suddenly, a little ah-ha moment brought me in a little exploration.

The proof-of-concept #
Apparently, Iām all about Proof-Of-Concept these days.
This time is about positioning a graphic element (letās say, a tooltip) next to the mouse but using the most efficient area in a given canvas in order to avoid the element to be cut by the boundary of the canvas.
Disclaimer #
I didnāt make any research on the topic, I bet I wouldnāt be astonished if there were, at least, a couple of Ph.D. out there on efficient tooltips positioning algorithms.
I donāt know whether this technique has been already used somewhere. In this case, I would love to hear more about them.
The rules #
They are simple and can be outlined as the following:
- construct four lines between the canvas edge corners and the mouse position
- find the longest one
- find a point along the longest line that is far enough from the mouse
- use that position to move the centroid of the element we want to position efficiently
A very efficient way to show how it works and whatās going on behind the scene might be this progressive visualization:

Here the relevant javascript code that uses a couple of essential native functions to accomplish this feature. It was extrapolated by a D3.js script but it can be adapted to other contexts quite easily. This script requires to be within a mousemove listener:
// finding the longest line
var maxL
var maxV = 0
lines.each(function (d, i) {
if (d.l >= maxV) {
maxL = this
maxV = d.l
}
})
// get the final point
var l = maxL.getTotalLength()
var p = maxL.getPointAtLength(l - 60)
// position the element
legend.attr('transform', `translate(${p.x}, ${p.y})`)
Here the interactive version (click to toggle the chart visibility and⦠desktop only):
Conclusion #
I donāt know whether Iām going to use this technique in the future. Nevertheless, itās always good to figure out whatās going on behind the curtain, it canāt hurt.
PS: Iāve also learned something on SVG 2; the getTotalLength() function will be deprecated on some SVG element (i.e. line, text), this is why my implementation uses path instead line elements.
Source code here.