Bounded Voronoi Proximity Picking replaces fragile 8px geometry hit tests with unified 24px mouse and 36px touch capture radii across five heterogeneous graph engines [Fit54] [GB05]. Centralizing pointer arbitration in vis-tooltip.tsx unifies hover, two-step touch tap, 8px deadzone dragging, and 8-sector tooltip repulsion across SVG, Canvas, and WebGL [BOH11].
| Graph Engine | Screen Projection (getScreenNodes) | Native Drag Suppression | Inverse Drag Mapping (onNodeDragMove) |
|---|---|---|---|
d3/engine.ts | d3.zoomTransform(svg).apply([x, y]) [BOH11] | Omitted d3.drag() behavior on <g> nodes | zoomT.invert([sx, sy]) via screenToGraph |
vis-network/engine.ts | network.canvasToDOM(positions[id]) | interaction: { dragNodes: false, hover: false } | network.DOMtoCanvas({ x, y }) + moveNode() |
cytoscape/engine.ts | node.renderedPosition() | autoungrabify: true, autounselectify: true | (s - pan) / zoom via renderedToModel |
sigma/engine.ts | sigma.graphToViewport({ x, y }) | Custom pointer capture overrides camera pan | sigma.viewportToGraph({ x, y }) |
3d-force-graph/engine.ts | graph.graph2ScreenCoords(x, y, z) | .enableNodeDrag(false) + controls.enabled = false | graph.screen2GraphCoords(sx, sy, dragDepth) |
Decoupling target acquisition from renderer-specific hit buffers guarantees identical pointer ergonomics across 2D and 3D network layouts.
Why Do Exact 8px Hit Tests Fail Fitts’s Law on Mouse and Touch?
Exact 8px node hit tests fail Fitts’s Law because tiny visual targets inflate the index of difficulty during rapid cursor movement and finger touch [Fit54]. By evaluating bounded Voronoi cells (24px mouse and 36px touch radii) in findNearest, we unify hover, tap, and drag acquisition [GB05].
Under Fitts’s formulation , an 8px node radius () forces users to decelerate sharply at the end of a ballistic movement, while human fingertips occlude the target completely [Fit54]. Area cursors and the Bubble Cursor [GB05] resolve this bottleneck by expanding the effective target width to the nearest Voronoi cell boundary in screen space (MOUSE_PROXIMITY_RADIUS = 24 and TOUCH_PROXIMITY_RADIUS = 36).
Crucially, early iterations only applied proximity picking to hover previews while leaving node dragging wired to each library’s native 8px geometry picker. That created a jarring interaction mismatch: a user could highlight a node from 20px away, press the mouse button or touch the screen to drag it, and grab empty air because the native drag handler required an exact 8px hit. We eliminated this split by disabling native node dragging across all five engines—omitting d3.drag() in d3/engine.ts [BOH11], setting dragNodes: false in vis-network/engine.ts, setting autoungrabify: true in cytoscape/engine.ts, bypassing camera pan in sigma/engine.ts, and calling .enableNodeDrag(false) in 3d-force-graph/engine.ts—so bindProximityInteraction in vis-tooltip.tsx drives hover, tap, and drag from the exact same findNearest query.
How Does an 8px Drag Deadzone Reconcile Touch Scrolling with Node Dragging?
Mobile browsers abort pointer streams with pointercancel after roughly 5px of vertical movement whenever touch-action: pan-y is enabled for page scrolling. We reconcile native vertical scrolling, two-step touch taps, and single-finger node dragging by combining DRAG_DEADZONE_PX = 8, selective e.preventDefault() on non-passive touchmove, and an 800ms synthetic mouse guard in vis-tooltip.tsx.
Supporting three distinct touch intents on a single canvas requires careful event arbitration. When a finger touches empty canvas outside the 36px Voronoi radius (dragCandidate === null), we never cancel touchmove, allowing the browser’s compositor to scroll the page smoothly via touch-action: pan-y. When a finger lands inside a node’s 36px Voronoi cell, onTouchStart records dragCandidate; if movement remains within DRAG_DEADZONE_PX = 8 on release, handleTouchTap executes our two-step tap flow (first tap reveals the tooltip and ego-network highlight; second tap on the active node or tooltip card navigates to the target URL). Once displacement exceeds 8px with an active dragCandidate, the non-passive touchmove listener calls e.preventDefault() to lock out browser scrolling and streams inverse coordinates to onNodeDragMove:
// src/commons/client/vis-tooltip.tsx — 8px deadzone & selective touch scroll lock
if (Math.hypot(tx - downX, ty - downY) > DRAG_DEADZONE_PX) {
dragMoved = true;
if (dragCandidate && config.onNodeDragStart) {
if (e.cancelable) {
e.preventDefault();
}
if (!customDragActive) {
customDragActive = true;
hideInternal();
config.onActiveNodeChange(dragCandidate.id);
config.onNodeDragStart(dragCandidate, tx, ty);
}
config.onNodeDragMove?.(dragCandidate, tx, ty);
return;
}
}In 3d-force-graph/engine.ts, onNodeDragStart also sets controls.enabled = false so single-finger node dragging does not simultaneously orbit the Three.js camera, restoring controls.enabled = true on onNodeDragEnd. Furthermore, because mobile browsers synthesize compatibility pointerdown, pointermove, and pointerup events (pointerType === 'mouse') hundreds of milliseconds after a touch tap, bindProximityInteraction records lastTouchTime = performance.now() and ignores non-touch events for 800ms so synthetic mouse callbacks never immediately re-trigger navigation.
How Does 8-Sector Angular Repulsion Prevent Tooltip Occlusion?
Static cursor-offset tooltips frequently cover the focused node’s 1-hop neighbors, active ego-network edges, or container controls. Our computeBestPosition solver in vis-tooltip.tsx evaluates eight compass candidate slots (gap = 22px) around the active node, minimizing a composite cost function over viewport boundaries, UI chrome rects, and incident neighbor edge rays.
When showInternal renders the Preact VisNetworkTooltip card, findNearest supplies the screen coordinates of the focused node alongside the projected screen coordinates of all connected 1-hop neighbors. For each of the eight compass candidates with center and direction vector , computeBestPosition accumulates four penalty terms:
- Directional Preference & Viewport Overflow: Assigns a small top-first tie-breaking bias (
0forNup to8forS) and adds2000 + 25 * overflowPxwhenever a candidate box breaches the12pxcontainer padding. - UI Chrome Collision Avoidance: Queries the live bounding rectangles of the domain legend (
.z-20:not(#layout-control-container)) and layout switcher (#layout-control-container), adding1800ifrectsOverlapdetects an intersection. - Edge Midpoint & Neighbor Proximity: Samples three points along each incident 1-hop edge ( with weights
220,220, and420), penalizing both bounding-box overlap (margin = 14px) and radial proximity within130px. - Cosine Angular Alignment: Computes the directional cosine similarity against each incident edge vector and adds whenever .
Selecting the minimum-penalty sector and clamping its coordinates within [pad, clientDim - tooltipDim - pad] anchors the card cleanly in the most open angular sector without cursor-tracking jitter. Finally, animateOpacityTo interpolates tooltip opacity over HIGHLIGHT_TRANSITION_MS = 750 in lockstep with the graph’s ego-network highlight transition, while 3d-force-graph/engine.ts fades the tooltip out immediately if the user orbits the 3D camera—completing the interactive architecture alongside Focus-Aware Hop Attenuation, Five Graph Engines Unified Interaction Physics, 2D Elliptical Bin-Packing Tag Clouds, and our Zero-innerHTML Preact UI Islands across the live D3.js, Vis-Network, and 3D Force-Graph canvases.
References
- [Fit54]Paul Morris Fitts (1954). The information capacity of the human motor system in controlling the amplitude of movement. Journal of Experimental Psychology, 47(6), pp. 381--391. DOI: 10.1037/h0055392.
- [GB05]Tovi Grossman and Ravin Balakrishnan (2005). The bubble cursor: enhancing target acquisition by dynamic resizing of the cursor's activation area. In Proceedings of the SIGCHI Conference on Human Factors in Computing Systems (CHI '05), pp. 281--290. DOI: 10.1145/1054972.1055012.
- [BOH11]Michael Bostock, Vadim Ogievetsky and Jeffrey Heer (2011). D3: Data-Driven Documents. IEEE Transactions on Visualization and Computer Graphics, 17(12), pp. 2301--2309. DOI: 10.1109/TVCG.2011.185.