Skip to content
Agentmart
Submit a tool

react-flow-canvas-geometry

Compute React Flow (@xyflow) canvas geometry correctly — edge label anchors, bezier control points, handle coordinates, node bounds — so labels and edges land…

Skill by Lichens-Innovation

IndexedMIT
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/lichens-innovation-react-flow-canvas-geometry

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Compute React Flow (@xyflow) canvas geometry correctly — edge label anchors, bezier control points, handle coordinates, node bounds — so labels and edges land where you intend instead of on top of a node. Use when placing or offsetting edge labels, reimplementing getBezierPath maths outside the renderer (e.g. in a seed or layout package), bending an edge through a moved label, deciding where a node ends, or debugging an element that renders in the wrong place on the canvas.

Use cases: design, frontend

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Node.js package

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/react-flow-canvas-geometry

What's inside

Skill name
react-flow-canvas-geometry
Description
Compute React Flow (@xyflow) canvas geometry correctly — edge label anchors, bezier control points, handle coordinates, node bounds — so labels and edges land where you intend instead of on top of a node. Use when placing or offsetting edge labels, reimplementing getBezierPath maths outside the renderer (e.g. in a seed or layout package), bending an edge through a moved label, deciding where a node ends, or debugging an element that renders in the wrong place on the canvas.
License
MIT
Instructions
179 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts

Files (3)

  • README.md
  • SKILL.md
  • check-bezier-model.mjs · scripts/

SKILL.md (first lines)

# React Flow canvas geometry

Getting something to appear at the right place on an `@xyflow/react` canvas means answering three
questions: *where is the handle*, *where does the curve put the label*, and *how big is the node*.
Each has a non-obvious answer, and the first two have an answer that is **wrong in a way that looks
right**.

For checking the result visually, drive the app in a real browser/window rather than trusting a
static read of the coordinates.

## The rule: import the maths, don't retype it

`@xyflow/react` re-exports the geometry helpers. Inside a renderer there is no reason to
reimplement any of this:

```ts
import { getBezierPath, getBezierEdgeCenter, getSmoothStepPath, getStraightPath,
         getEdgeCenter, getNodesBounds } from "@xyflow/react";
```

Reimplement **only** when the code cannot depend on React Flow — a framework-free package that
decides geometry ahead of render, such as a layout or seed package computing `label_offset` values
before anything renders. If you are in that position, go to *Checking a reimplementation* below
before you trust it.

## Where the label goes

`getBezierPath` returns `[path, labelX, labelY, offsetX, offsetY]`. `labelX`/`labelY` are **the
cubic bezier evaluated at t = 0.5, not the midpoint of the drawn curve** — the library says so in
a comment, and calls it "not the actual mid point, but easy to calculate":

```
labelX = 0.125·sourceX + 0.375·sourceControlX + 0.375·targetControlX + 0.125·targetX
labelY = 0.125·sourceY + 0.375·sourceControlY + 0.375·targetControlY + 0.125·targetY
```

That is `(P0 + 3·C1 + 3·C2 + P3) / 8`. Which means you cannot get the label position without the
control points — and the control points are where the trap lives.

### Control points

```ts
function calculateControlOffset(distance, curvature) {
  if (distance >= 0) return 0.5 * distance;      // target is that way
  return curvature * 25 * Math.sqrt(-distance);  // target is behind the handle
}
```

The handle side picks the axis and the sign. **The distance is always measured from the endpoint
that owns the handle, toward the other endpoint** — React Flow calls its helper with `x1/y1` = the
own endpoint and `x2/y2` = the other one, for *both* control points:

| handle side | control point |
|---|---|
| `Left`   | `[x1 - offset(x1 - x2), y1]` |
| `Right`  | `[x1 + offset(x2 - x1), y1]` |
| `Top`    | `[x1, y1 - offset(y1 - y2)]` |
| `Bottom` | `[x1, y1 + offset(y2 - y1)]` |

For the **target** control point, `x1,y1` is the *target* and `x2,y2` is the *source*. Getting that
backwards is the single easiest mistake to make here, because it swaps which branch of
`calculateControlOffset` runs — and the two branches are numerically close over exactly the range
of distances a normal graph uses.

`0.5·d` and `6.25·√d` cross at **d = 156.25**, and stay within a few px of each other for a wide
band around it. Measured over a 4,624-case grid, a sign-flipped model still lands **within 3px of
the library in 51.6% of cases**. In one real graph, a back-route spanning 165px agreed with the
wrong formula to under 1px — while the same model was **34px out** on a 445px span, enough to put a
label inside a node.

**This is why spot-checking two labels in a rendered window proves nothing.** A wrong
reimplementation like this can pass casual review and still ship a real bug.

### Checking a reimplementation

```bash
node .claude/skills/react-flow-canvas-geometry/scripts/check-bezier-model.mjs
```

It loads the real `getBezierPath` out of the pnpm store, runs a grid of every handle-side pair ×
17² source/target offsets, and requires agreement to 0.001px — a reimplementation should be
*exact*, not close. It also reports how many cases a deliberately wrong model would still pass, so
you can see what your manual check was actually worth. Point `model()` at your own function to
check it.

### The other path types

| helper | label anchor | notes |
|---|---|---|
| `getBezierPath` | cubic at t=0.5, needs control points | the one with the trap |
| `getStraightPath` | `getEdgeCenter` — plain midpoint | trivial |
| `getSmoothStepPath` | falls out of `getPoints`, from the routed elbow | there is **no closed formula**: it depends on `offset`, `stepPosition`, an explicit `center`, and which elbow the router chose |

If you need a smooth-step label anchor outside the renderer, don't derive it — call the helper.

## Where the handle is

Edge endpoints are **handle** coordinates, not node origins. For a node box `{x, y, w, h}` with
handles at the default positions:

```
left   → (x,         y + h/2)
right  → (x + w,     y + h/2)
top    → (x + w/2,   y      )
bottom → (x + w/2,   y + h  )
```

A custom `<Handle style={{ top: "50%" }}>` keeps the mid-edge default; if you move a handle with
CSS, this table stops being true and only the DOM knows the answer.

## How big is the node

```ts
width  = node.measured?.width  ?? node.width  ?? node.initialWidth  ?? 0
height = node.measured?.height ?? node.height ?? node.initialHeight ?? 0
```

That is `getNodeDimensions` verbatim. Two consequences:

From Lichens-Innovation/ai-dev-tools, MIT.

More from Lichens-Innovation/ai-dev-tools

Signals

Stars
0
Forks
0
Open issues
6
Last commit (this folder)
Oct 11, 2026 (17 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from Lichens-Innovation

Cite this page

react-flow-canvas-geometry skill. Agent Mart. https://agentmart.dev/skills/lichens-innovation-react-flow-canvas-geometry (last updated 2026-10-24)