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:

RouterBehavior
straightdirect line, endpoints only
orthogonalright-angled path from the port's exit direction
manhattangrid-based right-angle routing with turn minimization
avoidobstacle avoidance — the path walks around nodes instead of crossing them, and re-routes live as you drag
elkedge routes computed by the ELK layout engine, consistent with an ELK-laid-out graph
Obstacle-avoiding routing is the feature most diagram libraries sell as a paid add-on or leave as an exercise. Here it is one word on the edge spec. The comparison table sources this cell by cell.

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