Repository navigation
Cluster hull rendering overshoots (or under-covers) nodes for small/near-collinear clusters in the lock graph #2
Description
Summary
The live lock/blocking graph groups related sessions into visual clusters, drawing a soft colored "hull" (a rounded blob) behind each cluster's nodes. For most clusters this looks correct, but for small clusters (especially exactly 3 nodes) whose force-simulated layout ends up roughly collinear (all three nodes nearly in a line), the hull renders as a dramatically oversized, stretched oval that extends far beyond the actual nodes — sometimes 2-3x wider than it should be.
I'll be honest: I don't fully understand the root cause, and I've tried several fixes that either didn't work or traded this problem for a different one. I'm opening this as a "good first issue" for anyone who enjoys this kind of geometry/rendering puzzle — I'd genuinely appreciate a fresh set of eyes.
Where the bug lives
Relevant files:
web/src/shared/utils/graph/clusterLayout.ts— lays out each cluster's nodes independently viad3-force(a pinned "hub" node + force-directed spokes). For a 3-node component, this can produce a nearly-collinear layout (no strong force pushes 3 points into a non-degenerate 2D spread).web/src/shared/utils/graph/packing.ts— tiles each component's bounding box next to the others (COMPONENT_PADDING = 140), no overlap between components' raw node boxes.web/src/shared/utils/graph/hulls.ts— computes a convex hull (d3-polygon'spolygonHull) around each component's final node positions, then pushes every hull vertex outward from the hull centroid by a fixedHULL_PADDING = 36px.web/src/features/lock-graph/components/ClusterHulls.tsx— takes the padded hull polygon and renders it as one smooth SVG<path>.
What's been tried (all in ClusterHulls.tsx), none fully worked
-
d3-shape'scurveCatmullRomClosed(original/current implementation onmain, still there as the fallback since nothing else has worked better): fits one smooth interpolating spline through all the padded hull points. Looks great for clusters with a reasonable node count in a roughly round/organic arrangement. Fails: for near-collinear point sets, Catmull-Rom's tangent estimation (derived from neighboring point positions) produces large, nearly-parallel tangents, causing the closed spline to massively overshoot in both directions — this is the bug. -
curveCardinalClosedwith a dynamically computedtension(based on a PCA eigenvalue ratio of the hull points, meant to detect "how flat/collinear" the shape is and increase tension — which shrinks tangent magnitude — only for flat shapes, leaving round shapes untouched attension=0, mathematically identical to Catmull-Rom). This was numerically verified against synthetic test coordinates I constructed myself (approximating the screenshot) and looked correct in isolated Node.js testing. When actually deployed and viewed live, it did not visibly fix the problem — the overshoot was still present in the real app. I suspect my synthetic test coordinates didn't accurately represent the real geometry/scale involved, so the tension calibration may be off in a way I couldn't catch without live browser access. -
Local per-corner rounding instead of a global spline (a hand-written function producing a closed path where each polygon corner is rounded independently via a quadratic Bezier anchored at fixed-radius points along each adjacent edge — mathematically guaranteed to never extend beyond the original polygon and never shrink more than the fixed rounding radius, regardless of point count/arrangement). Tried at two different radii (20px, then 50px). Also did not visibly fix the specific 3-node overshoot case when deployed, and at the smaller radius it made larger/organic clusters look visibly worse (sharp, faceted "cut corner" polygon edges instead of the smooth blob look approach Add SQLite-backed persistent history, replay windowing, and 4 new rea… #1 produces for those cases) — a regression a maintainer explicitly rejected after seeing it rendered.
What I genuinely don't understand
- Why approaches Cluster hull rendering overshoots (or under-covers) nodes for small/near-collinear clusters in the lock graph #2 and fix: Use rectangle hull fallback for near-collinear clusters #3, despite being either mathematically guaranteed (fix: Use rectangle hull fallback for near-collinear clusters #3) or numerically verified against synthetic test data (Cluster hull rendering overshoots (or under-covers) nodes for small/near-collinear clusters in the lock graph #2) to fix the overshoot, did not visibly change anything when actually rendered in the live app. This makes me suspect either (a) my synthetic test coordinates don't match the real coordinates closely enough for the fix to have kicked in the way intended, (b) there's a caching/build issue that meant the deployed code wasn't actually what I thought it was, or (c) there's something about the real geometry (packing offsets,
NODE_CENTER_OFFSET, viewport transform inClusterHulls.tsx) that I'm not accounting for correctly. - Whether the actual right fix belongs upstream in
clusterLayout.tsinstead (e.g., preventing 3-node components from ever landing in a near-collinear configuration in the first place, via a minimum-angle constraint on the force simulation) rather than trying to patch the rendering of an already-degenerate hull downstream.
Screenshots
I have several screenshots showing the failure (a 3-node cluster with an oversized stretched-oval hull) across multiple attempted fixes — happy to attach exact ones on request, or a maintainer can reproduce by looking at any 3-node blocking chain where the force layout happens to place the nodes roughly in a line (this seems to happen fairly often with exactly 3 nodes).
What I'd love help with
- A correct diagnosis of why the overshoot happens (my working theory is Catmull-Rom tangent estimation on near-collinear points, but I'm not 100% sure that's the whole story given the fixes above didn't work as expected)
- A fix that (a) doesn't regress the current good-looking behavior for normal/larger/organic clusters, and (b) actually, verifiably fixes the small-cluster case — ideally with a way to verify it that doesn't require me eyeballing screenshots back and forth
- Alternatively, a case for why fixing this upstream (in the force layout, before hull computation) is simpler/more robust than anything downstream in the rendering
Labels: bug, good first issue
Activity
fix: Use rectangle hull fallback for near-collinear clusters