Hit-Testing: Canvas vs SVG Interactivity

Both panels show the same 200-point scatter plot. Hover over points to see tooltips. The experience looks the same, but the implementation is fundamentally different. Canvas requires manual distance calculations for every mouse move. SVG gets events for free via the DOM.

Canvas (Manual Hit-Testing)
Hover over points...
SVG (Native DOM Events)
Hover over points...
Key difference: The Canvas panel runs a loop through all 200 points on every mousemove event, calculating the distance from the cursor to each point to find the nearest one. The SVG panel does nothing — each <circle> element has its own mouseenter event, handled natively by the browser's DOM event dispatch system.

Canvas Hit-Testing Code

For Canvas, you must find the nearest point yourself. This is O(n) per mouse move — you check every point:

canvas.addEventListener('mousemove', (event) => {
    const rect = canvas.getBoundingClientRect();
    const mx = event.clientX - rect.left;
    const my = event.clientY - rect.top;

    let nearest = null;
    let minDist = Infinity;

    // O(n) loop — check every point
    for (const point of points) {
        const dx = point.x - mx;
        const dy = point.y - my;
        const dist = Math.sqrt(dx * dx + dy * dy);
        if (dist < minDist && dist < 20) {
            minDist = dist;
            nearest = point;
        }
    }

    if (nearest) {
        showTooltip(nearest);
        highlightPoint(nearest);
    } else {
        hideTooltip();
    }
});

With 200 points, this is fast. With 10,000 points, you would need a spatial index (quadtree) to avoid checking every point on every frame.

SVG Event-Based Code

For SVG, each circle is a DOM element that receives events natively. No distance calculation needed:

// Each circle gets its own event listener — O(1) per event
circles.forEach(circle => {
    circle.addEventListener('mouseenter', (event) => {
        const point = circle.__data__;
        showTooltip(point);
        circle.classList.add('hovered');
    });

    circle.addEventListener('mouseleave', () => {
        hideTooltip();
        circle.classList.remove('hovered');
    });
});

The browser's event dispatch system handles the "which element is under the cursor?" question internally, using a spatial data structure optimized for the DOM tree. You just attach a listener and respond.

Comparison Summary

Aspect Canvas SVG
Detecting hover Manual: loop through all points, compute distance Automatic: mouseenter fires on the element
Complexity per move O(n) naive, O(log n) with quadtree O(1) — browser handles it
Highlighting Redraw the entire canvas with one point in a different color Toggle a CSS class on the element
Click handling Same distance loop in click handler circle.addEventListener('click', ...)
Cursor style Manual: change cursor when nearest point is found CSS: circle { cursor: pointer; }
Scales to 50K points Yes (with spatial index) No (DOM too heavy)
The trade-off: SVG gives you interactivity for free but limits your element count. Canvas scales to huge data volumes but makes you implement interactivity yourself. This is why libraries like Chart.js (Canvas-based) include their own hit-testing systems internally — they absorb the complexity so you do not have to write distance loops.