Skip to content

fix(edges): align arrow markers with the stroke they terminate - #75

Open
Ken-vdE wants to merge 2 commits into
Vrun-design:mainfrom
Ken-vdE:fix/edge-arrow-marker-alignment
Open

fix(edges): align arrow markers with the stroke they terminate#75
Ken-vdE wants to merge 2 commits into
Vrun-design:mainfrom
Ken-vdE:fix/edge-arrow-marker-alignment

Conversation

@Ken-vdE

@Ken-vdE Ken-vdE commented Aug 24, 2026

Copy link
Copy Markdown

Arrowheads pointed somewhere the edge did not, and the stroke poked out of the triangle's side. Two independent causes, both about the path tangent an orient="auto" SVG marker reads at its vertex.

  1. getBezierPath whips into the target handle's normal over the final few pixels: on a real template edge the tangent turns 42 degrees within the last 12px. The marker takes the exact endpoint tangent, and refX puts the tip on the endpoint, so the whole triangle sits over curve running a different way. Reserve a straight lead as long as the arrow marker at both endpoints, so the stroke stops where the arrowhead begins and both share one direction. The leads are tangent-continuous with the curve because getBezierPath already leaves and enters along the same handle normals. Skipped on edges shorter than 48px.

  2. buildCurvedPath anchors smooth splines by duplicating the first and last point, which makes d3 emit zero-length commands and cubics whose control points collapse onto the endpoint. The tangent is then zero and browsers fall back to 0 degrees, so ELK-routed and manual-waypoint edges pointed right no matter which way they ran. normalizeMarkerTangents strips the degenerate commands and restores a readable tangent; a fully collapsed cubic traces its own chord, so it becomes a lineTo. Endpoints are untouched and basis output now matches d3's un-anchored tail.

Before
image
After
image

Ken van der Eerden added 2 commits August 24, 2026 11:43
Arrowheads pointed somewhere the edge did not, and the stroke poked out of
the triangle's side. Two independent causes, both about the path tangent an
`orient="auto"` SVG marker reads at its vertex.

1. `getBezierPath` whips into the target handle's normal over the final few
   pixels: on a real template edge the tangent turns 42 degrees within the
   last 12px. The marker takes the exact endpoint tangent, and `refX` puts
   the tip on the endpoint, so the whole triangle sits over curve running a
   different way. Reserve a straight lead as long as the arrow marker at both
   endpoints, so the stroke stops where the arrowhead begins and both share
   one direction. The leads are tangent-continuous with the curve because
   `getBezierPath` already leaves and enters along the same handle normals.
   Skipped on edges shorter than 48px.

2. `buildCurvedPath` anchors smooth splines by duplicating the first and last
   point, which makes d3 emit zero-length commands and cubics whose control
   points collapse onto the endpoint. The tangent is then zero and browsers
   fall back to 0 degrees, so ELK-routed and manual-waypoint edges pointed
   right no matter which way they ran. `normalizeMarkerTangents` strips the
   degenerate commands and restores a readable tangent; a fully collapsed
   cubic traces its own chord, so it becomes a `lineTo`. Endpoints are
   untouched and `basis` output now matches d3's un-anchored tail.
- move test-only tangent readers to a testHelpers module
- rebuild plain bezier when the lead splice finds no cubic, so the
  fallback path still touches the real handles
- correct ARROW_LEAD_PX comment: marker lengths vary, 12 approximates
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.

1 participant