Learn / Edges & routing
Edges & routing
An edge stores intent — connect A to B, orthogonally, avoiding obstacles — and the renderer turns intent into geometry that stays correct as nodes move. Only two fields are required.
The minimal edge, and every field after it
{ source: 'a', target: 'b' } // that's a complete edge
{ id: 'e1',
source: 'a', target: 'b', // node ids
sourceHandle: 'out', // a port id — or a bare side: 'right'
targetHandle: 'left',
type: 'orthogonal', // 'direct' | 'smooth' | 'orthogonal' | 'bezier'
router: 'avoid', // 'straight' | 'orthogonal' | 'manhattan' | 'avoid' | 'elk'
connector: 'rounded', // corner treatment: 'straight' | 'rounded' | 'smooth' | 'bezier'
label: 'retry',
style: { stroke: '#9333ea', strokeWidth: 2, strokeDasharray: '6 3' },
data: { weight: 3 }, // your payload
points: [{ x: 340, y: 180 }] } // explicit waypoints — restores a user-edited route
Omit both handles and the edge attaches to whichever default port faces its partner
as nodes move. Default ports have deterministic ids
(<nodeId>__top … __left), which is why a bare side name
like sourceHandle: 'bottom' resolves. For true perimeter floating —
attaching anywhere along the node's outline rather than at a port — set
metadata: { connectionPoint: 'smart' }.
Type versus router — two different questions
type answers "what shape is the line" (straight segments, one smooth
curve, right angles, a bezier). router answers "which path does it take"
— and that is where the interesting engineering lives:
| Router | Behavior |
|---|---|
straight | direct line, endpoints only |
orthogonal | right-angled path from the port's exit direction |
manhattan | grid-based right-angle routing with turn minimization |
avoid | obstacle avoidance — the path walks around nodes instead of crossing them, and re-routes live as you drag |
elk | edge routes computed by the ELK layout engine, consistent with an ELK-laid-out graph |
Live: every edge type, router and connector →
Labels, parallel edges, crossings
label puts editable text on the path (double-click to edit in place;
drag to reposition — the position persists in the model). When several edges connect
the same pair of nodes, the renderer fans them apart
(rendererConfig: { parallelLinks: true, parallelSpacing: 12 }), and where
unrelated edges cross you can render jump-overs via the renderer config's jump
styles.
Anchors, label placement, bends
A handle can name a point along a side, so several lines leave one tall box at
different heights: 'right@36' is 36 px down the right side,
'bottom@138' 138 px along the bottom, 'left@50%' halfway.
labelPlacement: 'above' | 'below' puts the label just off its line with no
box, and labelStyle gives it its own colour and weight.
waypoints are the bends a line must take — the router keeps them.
When a box moves, its lines move with it. An anchor stays the same point on the box
('right@36' is 36 px down its right side wherever the box goes). A
hand-bent line keeps its bends and re-attaches its ends; on a right-angle line the bend
next to the moved end slides with it, so that run stays square. Give lines
type: 'orthogonal' (Mermaid: linkStyle default interpolate stepBefore)
and a line that was straight between two aligned boxes turns a corner when one moves,
instead of going diagonal. A label placed above or below rides the middle of the line's
longest level run, where you would write it. A text note (shape: { type: 'text' })
is words, not a box: lines pass it rather than detour round it.
{ source: 'api', target: 'wallets', sourceHandle: 'top@170', targetHandle: 'bottom@138',
type: 'orthogonal', waypoints: [{ x: 580, y: 204 }, { x: 850, y: 204 }],
label: 'asks HealthPay to move money', labelPlacement: 'above',
labelStyle: { color: '#5f6b7a', fontSize: 11 } }
The selected node's lines
Turn on highlightConnected and selecting a node brings its lines forward:
the lines coming into it and going out of it are drawn in the page's ink, and every other
line fades back — the way flow editors such as Google's Opal show what a step reads and what
it feeds. A line of the selection that runs across another node is lifted above the cards and
drawn dashed, so it reads as passing over that node, not connecting to it. Grafloria's router
already takes lines around cards, so that is a line bent by hand across one, or one with no
way around. Off by default. It is view state, derived from the selection on every frame:
nothing is written to the model, so it adds no undo step, never syncs to a collaborator, and
an export draws the diagram without it.
createDiagram(el, { highlightConnected: true }); // the selection's lines in ink, the rest at 0.4
render(spec, el, { highlightConnected: { depth: Infinity } }); // trace every path in and out, not just the lines it touches
diagram.setHighlightConnected({ outgoing: 'dashed', dimOpacity: 1, stroke: '#dc2626' }); // live; dashed outgoing = a direction cue
// React and Vue: through the renderer config the wrappers already forward
<GrafloriaFlow rendererConfig={{ highlightConnected: true }} /> // React
<GrafloriaFlow :renderer-config="{ highlightConnected: true }" /> // Vue
<grafloria-diagram-canvas [rendererConfig]="{ highlightConnected: true }" /> // Angular
// the web component: present = on, "trace", a depth, or "false"
<grafloria-flow highlight-connected="trace"></grafloria-flow>
Each line's group carries data-connected="in | out | both | dim", a lifted
line also data-crossing="true", and the classes link-connected,
link-crossing and link-dimmed for your own CSS. A line you selected
keeps the selection's look.
Live: highlight connected lines →
Reconnecting and editing
Drag an edge's endpoint and drop it on another port — validators run again (see
Ports & validation; the candidate carries
link so a rule can treat reconnection differently). Drag the path itself
to bend it — the bend lands in points and round-trips through
serialization. Every one of these edits is a single undoable step.
Where next
- Ports & validation — what edges attach to.
- Layout — edge routes that come from the layout engine.