Skip to content

d2cycle: route "shape: cycle" edges as circular arcs clipped at shape borders - #2808

Open
kimotostudio wants to merge 1 commit into
d2lang:masterfrom
kimotostudio:cycle-work
Open

d2cycle: route "shape: cycle" edges as circular arcs clipped at shape borders#2808
kimotostudio wants to merge 1 commit into
d2lang:masterfrom
kimotostudio:cycle-work

Conversation

@kimotostudio

Copy link
Copy Markdown

Fixes #1578

/claim #1578

Builds on the approach in #2362: shape: cycle arranges objects in a circle and routes edges as perfectly circular arcs — and makes the arcs start/stop exactly on the shape borders while staying smooth. No renderer changes; measured worst endpoint-to-border distance 0.001px, anchor-radius spread 0.000px.

How

Instead of sampling the arc as a polyline and clipping it afterwards (which fights the renderer's cubic-Bézier interpretation of IsCurve routes), the route is generated analytically:

  • The exact angle where the layout circle crosses each shape's border is found with a chord scan + bisection (~0.001px accuracy). Rectangular shapes clip at their bounding box (they define no Perimeter(), matching shape.TraceToShapeBorder semantics); non-rectangular shapes (circle, hexagon, ...) clip at their visible Perimeter().
  • The clipped arc is emitted as standard cubic Bézier segments (k = 4/3·tan(Δθ/4), one segment per quarter turn), i.e. a [P0, C1, C2, P1, ...] route — so d2svg.pathData renders it as-is and no renderer changes are needed.
  • Node positions are snapped to integer coordinates so the export-time int() truncation cannot shift boxes off the (float) route endpoints.

Also guards the degenerate cases: single-node cycles no longer produce an infinite radius, and clipping falls back to the center-to-center arc if no border crossing is found.

Testing

  • TA=1 ./ci/test.sh ./e2etests -run TestE2E/txtar/cycle-diagram -v (the command from the bounty comment) generates the fixture; dagre + elk both pass. Measured from the generated boards: worst endpoint-to-border distance 0.001px, anchor-radius spread 0.000px (perfectly circular).
  • New package tests in d2layouts/d2cycle: border contact, circularity, non-rectangular perimeter clipping, single-node guard.
  • CI=1 go test ./... passes with the repo's pinned toolchain (go1.25.0).
  • Rendered output matches the target look in the issue (see the cycle-diagram e2e fixture SVGs in this diff).

Known limitations (happy to follow up): nested containers inside a cycle node keep their default layout (same as #2362), and outside labels/icons are not yet obstacles for the arc.

Disclosure: implemented with AI assistance (Claude), reviewed and submitted by me.

… borders

Fixes d2lang#1578

Generates cycle-layout edge routes analytically as cubic Bezier arc
segments that start/stop exactly on shape borders (bounding box for
rectangular shapes, Perimeter() for circle/hexagon/etc), so no renderer
changes are needed. Snaps node positions to integers so export-time
truncation cannot shift boxes off the float route endpoints. Guards
single-node radius divergence and missing border crossings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

shape: cycle

1 participant