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
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.
About
Use cases: design, frontend
Reviews
Write a reviewNo 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
- 0.570score
claude-light
Lichens-Innovation
Applies a lightweight Claude Code configuration to this project's .claude/settings.local.json: optional auto-compact window, connectors, workflows, bundled…
SkillAFreshness: AClaude Code +9 - 0.570score
setup-claude-auto-lint-format
Lichens-Innovation
Installs a PostToolUse hook that runs the project's linter and formatter on every file Claude Code or one of its subagents writes or edits, and speaks up only…
SkillAFreshness: AClaude Code +9 - 0.570score
setup-claude-just-usage
Lichens-Innovation
Reads the justfiles of a repository and, for each level that has one (root, apps/frontend, packages/ui, ...), writes a .claude/rules/use-just-commands.md…
SkillAFreshness: AClaude Code +9 - 0.570score
chromatic-init
Lichens-Innovation
Adds Chromatic visual testing to a Storybook project.
SkillAFreshness: AClaude Code +9 - 0.570score
design-loop
Lichens-Innovation
Runs the Claude Design -> React implementation loop: takes an approved component or screen proposal (or the palette card) from a Claude Design project…
SkillAFreshness: AClaude Code +9 - 0.570score
design-palette
Lichens-Innovation
Creates or migrates the canonical CSS theme palette: light/dark inputs, generated scales, semantic tokens shared by web and React Native, hand-authored…
SkillAFreshness: AClaude Code +9 - 0.570score
design-refresh
Lichens-Innovation
The one skill for updating Claude Design from the code: checks whether the synced components are behind their code, rebuilds stale screen mockups, offers…
SkillAFreshness: AClaude Code +9 - 0.570score
react-coding-standards
Lichens-Innovation
Enforces internal React and TypeScript coding standards using avoid/prefer rules.
SkillAFreshness: AClaude Code +9 - 0.566score
storybook-init
Lichens-Innovation
Installs and configures Storybook in a React or React Native project.
SkillAFreshness: AClaude Code +9 - 0.566score
setup-claude-guardrails
Lichens-Innovation
Installs guardrails in this project's .claude/ (and its git worktrees) that keep Claude Code from reading, writing or running commands on paths outside a…
SkillAFreshness: AClaude Code +9 - 0.565score
react-hook-form
Lichens-Innovation
Guide for setting up react-hook-form with Zod validation in React.
SkillAFreshness: AClaude Code +9 - 0.565score
generate-pr-description
Lichens-Innovation
Generates pull request descriptions by comparing current branch with parent branch.
SkillAFreshness: AClaude Code +9
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
- Source
- GitHubgithub-graphql
More from Lichens-Innovation
- 0.570score
setup-claude-auto-lint-format
Lichens-Innovation
Installs a PostToolUse hook that runs the project's linter and formatter on every file Claude Code or one of its subagents writes or edits, and speaks up only…
SkillAFreshness: AClaude Code +9 - 0.570score
design-loop
Lichens-Innovation
Runs the Claude Design -> React implementation loop: takes an approved component or screen proposal (or the palette card) from a Claude Design project…
SkillAFreshness: AClaude Code +9 - 0.570score
setup-claude-just-usage
Lichens-Innovation
Reads the justfiles of a repository and, for each level that has one (root, apps/frontend, packages/ui, ...), writes a .claude/rules/use-just-commands.md…
SkillAFreshness: AClaude Code +9 - 0.570score
chromatic-init
Lichens-Innovation
Adds Chromatic visual testing to a Storybook project.
SkillAFreshness: AClaude Code +9 - 0.570score
claude-light
Lichens-Innovation
Applies a lightweight Claude Code configuration to this project's .claude/settings.local.json: optional auto-compact window, connectors, workflows, bundled…
SkillAFreshness: AClaude Code +9 - 0.570score
design-palette
Lichens-Innovation
Creates or migrates the canonical CSS theme palette: light/dark inputs, generated scales, semantic tokens shared by web and React Native, hand-authored…
SkillAFreshness: AClaude Code +9
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)