With data sorted along the x axis, we can find the closest point even faster.
var [px, py] = d3.mouse(this)
var index = bisect.left(data, px)
var minPoint = null
var minDist = Infinity
var lxDist = 0
var rxDist = 0
var i = 0
while (lxDist < minDist && rxDist < minDist){
lxDist = checkPoint(data[index - i])
rxDist = checkPoint(data[index + i])
i++
}
function checkPoint(d){
if (!d) return Infinity
var dx = d.px - px
var dy = d.py - py
var dist = Math.sqrt(dx*dx + dy*dy)
if (dist < minDist){
minDist = dist
minPoint = d
}
return Math.abs(px - d.px)
}
var ttSel = d3.select('body').selectAppend('div.tooltip.tooltip-hidden')
var sel = d3.select('#graph').html('')
var c = d3.conventions({
sel,
margin: {left: 30},
layers: 'cs',
})
var [ctx, svg] = c.layers
d3.drawAxis(c)
var colorScale = d3.scaleLinear().range(['#f0f', '#0f0'])
var n = 1000000
var data = d3.range(n).map(i => {
var r = i/n
var px = c.x(r)
var py = c.y(Math.random())
var color = colorScale(r + Math.random()/5)
ctx.fillStyle = color
ctx.fillRect(px, py, 1, 1)
return {px, py, color}
})
var highlightCircle = svg.append('circle')
.at({r: 10, fill: 'none'})
.st({pointerEvents: 'none'})
var bisect = d3.bisector(d => d.px)
svg.append('rect')
.at({width: c.width, height: c.height, fillOpacity: 0})
.call(d3.attachTooltip)
.on('mousemove', function(){
var startT = performance.now()
var [px, py] = d3.mouse(this)
var index = bisect.left(data, px)
var minPoint = null
var minDist = Infinity
var lxDist = 0
var rxDist = 0
var i = 0
while (lxDist < minDist && rxDist < minDist){
lxDist = checkPoint(data[index - i])
rxDist = checkPoint(data[index + i])
i++
}
function checkPoint(d){
if (!d) return Infinity
var dx = d.px - px
var dy = d.py - py
var dist = Math.sqrt(dx*dx + dy*dy)
if (dist < minDist){
minDist = dist
minPoint = d
}
return Math.abs(px - d.px)
}
var timeDelta = performance.now() - startT
ttSel.text(' found in ' + d3.format('.3f')(timeDelta) + 'ms with ' + d3.format('05')(i*2) + ' comparisons')
highlightCircle
.translate([minPoint.px, minPoint.py])
.at({stroke: minPoint.color, strokeWidth: 4})
})
.on('mouseout', function(){
highlightCircle.at({strokeWidth: 0})
})
<!DOCTYPE html>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
<div id='graph'></div>
<script src='d3_.js'></script>
<script type="text/javascript">
</script>
<script src='_script.js'></script>
body{
font-family: menlo, Consolas, 'Lucida Console', monospace;
margin: 0px;
}
.tooltip {
top: -1000px;
position: fixed;
padding: 10px;
background: rgba(255, 255, 255, .90);
border: 1px solid lightgray;
pointer-events: none;
}
.tooltip-hidden{
opacity: 0;
transition: all .3s;
transition-delay: .1s;
}
@media (max-width: 590px){
div.tooltip{
bottom: -1px;
width: calc(100%);
left: -1px !important;
right: -1px !important;
top: auto !important;
width: auto !important;
}
}
svg{
overflow: visible;
}
.domain{
display: none;
}
text{
pointer-events: none;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}