Skip to content

Cluster hull rendering overshoots (or under-covers) nodes for small/near-collinear clusters in the lock graph #2

Description

@Fayupable

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 via d3-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's polygonHull) around each component's final node positions, then pushes every hull vertex outward from the hull centroid by a fixed HULL_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

  1. d3-shape's curveCatmullRomClosed (original/current implementation on main, 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.

  2. curveCardinalClosed with a dynamically computed tension (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 at tension=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.

  3. 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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggood first issueGood for newcomers

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions