Use an edge's router to choose the path, reconnect an endpoint by dragging it to another node, and enable waypoint editing when readers need to shape a route by hand. An edge spec stores the connection and routing intent; the mounted diagram renders that intent as geometry.
Choose a route
type describes the line's shape; router selects the path-finding behavior. The examples below put three routers on the same A-to-B corridor, with a wall in each lane. Drag a wall and compare the route.
router | Behavior |
|---|---|
orthogonal | Right-angled path from the port's exit direction. |
manhattan | Grid-based right-angle routing with turn minimization. |
avoid | Routes around nodes and recalculates as nodes move. |
elk | Uses ELK edge routes, consistent with an ELK-laid-out graph. |
straight | Direct line between endpoints. |
If router is unset, it is derived from type. The orthogonal, manhattan, and elk lanes below follow the routing-algorithms demo; the single-obstacle demo also shows an orthogonal route bending around a wall.
For the shared framework setup and typed node and edge specs, see Build runnable workflows; the examples here add routing and edge-editing behavior.
jsimport { render } from '@grafloria/element';
const host = document.createElement('div');
host.style.height = '600px';
document.body.append(host);
const lanes = ['orthogonal', 'manhattan', 'elk'];
const nodes = lanes.flatMap((router, index) => {
const y = 110 + index * 150;
return [
{ id: `a${index}`, position: { x: 70, y: y - 24 }, size: { width: 108, height: 48 }, label: 'A' },
{ id: `b${index}`, position: { x: 760, y: y - 24 }, size: { width: 108, height: 48 }, label: 'B' },
{ id: `wall${index}`, position: { x: 410, y: y - 42 }, size: { width: 100, height: 84 }, label: router },
];
});
const edges = lanes.map((router, index) => ({
id: `edge${index}`,
source: `a${index}`,
target: `b${index}`,
router,
}));
const instance = render({ nodes, edges }, host);
instance.fitView();
tsimport { Component } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
const lanes: NonNullable<EdgeSpec['router']>[] = ['orthogonal', 'manhattan', 'elk'];
@Component({
standalone: true,
imports: [DiagramCanvasComponent],
template: `
<grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges"
style="display:block; height:600px" />
`,
})
export class RoutingExampleComponent {
nodes: NodeSpec[] = lanes.flatMap((router, index) => {
const y = 110 + index * 150;
return [
{ id: `a${index}`, position: { x: 70, y: y - 24 }, size: { width: 108, height: 48 }, label: 'A' },
{ id: `b${index}`, position: { x: 760, y: y - 24 }, size: { width: 108, height: 48 }, label: 'B' },
{ id: `wall${index}`, position: { x: 410, y: y - 42 }, size: { width: 100, height: 84 }, label: router },
];
});
edges: EdgeSpec[] = lanes.map((router, index) => ({
id: `edge${index}`,
source: `a${index}`,
target: `b${index}`,
router,
}));
}
tsximport { GrafloriaFlow } from '@grafloria/react';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
const lanes: NonNullable<EdgeSpec['router']>[] = ['orthogonal', 'manhattan', 'elk'];
const nodes: NodeSpec[] = lanes.flatMap((router, index) => {
const y = 110 + index * 150;
return [
{ id: `a${index}`, position: { x: 70, y: y - 24 }, size: { width: 108, height: 48 }, label: 'A' },
{ id: `b${index}`, position: { x: 760, y: y - 24 }, size: { width: 108, height: 48 }, label: 'B' },
{ id: `wall${index}`, position: { x: 410, y: y - 42 }, size: { width: 100, height: 84 }, label: router },
];
});
const edges: EdgeSpec[] = lanes.map((router, index) => ({
id: `edge${index}`,
source: `a${index}`,
target: `b${index}`,
router,
}));
export function RoutingExample() {
return (
<div style={{ height: '600px' }}>
<GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} fitView />
</div>
);
}
vue<script setup lang="ts"> import { GrafloriaFlow } from '@grafloria/vue'; import type { EdgeSpec, NodeSpec } from '@grafloria/renderer'; const lanes: NonNullable<EdgeSpec['router']>[] = ['orthogonal', 'manhattan', 'elk']; const nodes: NodeSpec[] = lanes.flatMap((router, index) => { const y = 110 + index * 150; return [ { id: `a${index}`, position: { x: 70, y: y - 24 }, size: { width: 108, height: 48 }, label: 'A' }, { id: `b${index}`, position: { x: 760, y: y - 24 }, size: { width: 108, height: 48 }, label: 'B' }, { id: `wall${index}`, position: { x: 410, y: y - 42 }, size: { width: 100, height: 84 }, label: router }, ]; }); const edges: EdgeSpec[] = lanes.map((router, index) => ({ id: `edge${index}`, source: `a${index}`, target: `b${index}`, router, })); </script> <template> <div style="height:600px"> <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" :fit-view="true" /> </div> </template>
The canvas shows three A-to-B lanes, with a labelled wall in each. Move a wall to see the route respond to the new geometry.
Open the live routing-algorithms demo or view its source.
Reconnect an endpoint
Select the edge, then drag its endpoint handle onto a valid port on another node. The following examples pin the endpoints to named sides so the handles begin on the shown node edges.
jsimport { render } from '@grafloria/element';
const host = document.createElement('div');
host.style.height = '600px';
document.body.append(host);
const nodes = [
{ id: 'a', position: { x: 80, y: 260 }, size: { width: 120, height: 60 }, label: 'A' },
{ id: 'b', position: { x: 660, y: 110 }, size: { width: 120, height: 60 }, label: 'B' },
{ id: 'c', position: { x: 660, y: 430 }, size: { width: 120, height: 60 }, label: 'C' },
];
const edges = [{ id: 'e1', source: 'a', target: 'b', sourceHandle: 'right', targetHandle: 'left', type: 'direct' }];
const instance = render({ nodes, edges }, host);
instance.fitView();
tsimport { Component } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
@Component({
standalone: true,
imports: [DiagramCanvasComponent],
template: `
<grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges"
style="display:block; height:600px" />
`,
})
export class ReconnectExampleComponent {
nodes: NodeSpec[] = [
{ id: 'a', position: { x: 80, y: 260 }, size: { width: 120, height: 60 }, label: 'A' },
{ id: 'b', position: { x: 660, y: 110 }, size: { width: 120, height: 60 }, label: 'B' },
{ id: 'c', position: { x: 660, y: 430 }, size: { width: 120, height: 60 }, label: 'C' },
];
edges: EdgeSpec[] = [{
id: 'e1', source: 'a', target: 'b', sourceHandle: 'right', targetHandle: 'left', type: 'direct',
}];
}
tsximport { GrafloriaFlow } from '@grafloria/react';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
const nodes: NodeSpec[] = [
{ id: 'a', position: { x: 80, y: 260 }, size: { width: 120, height: 60 }, label: 'A' },
{ id: 'b', position: { x: 660, y: 110 }, size: { width: 120, height: 60 }, label: 'B' },
{ id: 'c', position: { x: 660, y: 430 }, size: { width: 120, height: 60 }, label: 'C' },
];
const edges: EdgeSpec[] = [{
id: 'e1', source: 'a', target: 'b', sourceHandle: 'right', targetHandle: 'left', type: 'direct',
}];
export function ReconnectExample() {
return (
<div style={{ height: '600px' }}>
<GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} fitView />
</div>
);
}
vue<script setup lang="ts"> import { GrafloriaFlow } from '@grafloria/vue'; import type { EdgeSpec, NodeSpec } from '@grafloria/renderer'; const nodes: NodeSpec[] = [ { id: 'a', position: { x: 80, y: 260 }, size: { width: 120, height: 60 }, label: 'A' }, { id: 'b', position: { x: 660, y: 110 }, size: { width: 120, height: 60 }, label: 'B' }, { id: 'c', position: { x: 660, y: 430 }, size: { width: 120, height: 60 }, label: 'C' }, ]; const edges: EdgeSpec[] = [{ id: 'e1', source: 'a', target: 'b', sourceHandle: 'right', targetHandle: 'left', type: 'direct', }]; </script> <template> <div style="height:600px"> <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" :fit-view="true" /> </div> </template>
After you select the wire between A and B, its endpoint handles appear. Drag the handle at B to C: the link now ends at C. A drop without a valid target leaves the original connection in place.
Open the live reconnect-edge demo or view its source.
Add and move waypoints
Waypoint editing is opt-in. Enable it on the diagram, select an edge, click its path to add a bend, and drag that waypoint. Its interior path changes while the connection remains attached to both nodes.
The JavaScript sample mounts the same two-node direct edge using render. Its returned DiagramInstance is the live diagram instance.
For the basic mounted two-node pattern, see How Grafloria works; the Vue sample below keeps its setup runnable while adding waypoint editing on a diagonal edge.
jsimport { render } from '@grafloria/element';
const host = document.createElement('div');
host.style.height = '400px';
document.body.append(host);
const nodes = [
{ id: 'a', position: { x: 120, y: 180 }, size: { width: 150, height: 70 }, label: 'A' },
{ id: 'b', position: { x: 620, y: 180 }, size: { width: 150, height: 70 }, label: 'B' },
];
const edges = [{ id: 'e1', source: 'a', target: 'b', type: 'direct' }];
const instance = render({ nodes, edges }, host, {
interaction: { enableWaypointEditing: true, showWaypointHandles: true },
});
instance.fitView();
tsimport { AfterViewInit, Component, viewChild } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
@Component({
standalone: true,
imports: [DiagramCanvasComponent],
template: `
<grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges"
style="display:block; height:400px" />
`,
})
export class EditableEdgeExampleComponent implements AfterViewInit {
canvas = viewChild.required(DiagramCanvasComponent);
nodes: NodeSpec[] = [
{ id: 'a', position: { x: 120, y: 180 }, size: { width: 150, height: 70 }, label: 'A' },
{ id: 'b', position: { x: 620, y: 180 }, size: { width: 150, height: 70 }, label: 'B' },
];
edges: EdgeSpec[] = [{ id: 'e1', source: 'a', target: 'b', type: 'direct' }];
ngAfterViewInit(): void {
this.canvas().activeEngine()?.setInteractionConfig({
enableWaypointEditing: true,
showWaypointHandles: true,
});
}
}
tsximport { GrafloriaFlow } from '@grafloria/react';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
const nodes: NodeSpec[] = [
{ id: 'a', position: { x: 120, y: 180 }, size: { width: 150, height: 70 }, label: 'A' },
{ id: 'b', position: { x: 620, y: 180 }, size: { width: 150, height: 70 }, label: 'B' },
];
const edges: EdgeSpec[] = [{ id: 'e1', source: 'a', target: 'b', type: 'direct' }];
export function EditableEdgeExample() {
return (
<div style={{ height: '400px' }}>
<GrafloriaFlow
defaultNodes={nodes}
defaultEdges={edges}
interaction={{ enableWaypointEditing: true, showWaypointHandles: true }}
fitView
/>
</div>
);
}
vue<script setup lang="ts"> import { GrafloriaFlow } from '@grafloria/vue'; import type { EdgeSpec, NodeSpec } from '@grafloria/renderer'; const nodes: NodeSpec[] = [ { id: 'draft', position: { x: 100, y: 70 }, size: { width: 150, height: 70 }, label: 'Draft' }, { id: 'review', position: { x: 540, y: 250 }, size: { width: 150, height: 70 }, label: 'Review' }, ]; const edges: EdgeSpec[] = [{ id: 'review-route', source: 'draft', target: 'review', type: 'direct' }]; </script> <template> <div style="height:400px"> <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" :interaction="{ enableWaypointEditing: true, showWaypointHandles: true }" :fit-view="true" /> </div> </template>
The initial wire is straight. Once selected, clicking along its path adds a waypoint at that location; dragging the waypoint bends the line without moving either endpoint. A second path click adds another bend. Angular needs the interaction settings applied to the mounted canvas's engine; the other bindings accept them through their interaction prop.
Open the live editable-edge demo or view its source.
Options and pitfalls
| Option | Type | Default | What it does |
|---|---|---|---|
router | 'straight' | 'orthogonal' | 'manhattan' | 'avoid' | 'elk' or a registered router name | Derived from type when omitted | Chooses the edge's route. |
type | 'direct' | 'smooth' | 'orthogonal' | 'bezier' | — | Chooses the edge's line shape. |
sourceHandle, targetHandle | string | Not specified | Pin an endpoint to a port or side; omit them for port-facing attachment. |
interaction.enableWaypointEditing | boolean | false | Allows adding, moving, or removing waypoints interactively. |
interaction.showWaypointHandles | boolean | true | Shows waypoint handles on selected edges. |
typeandrouteranswer different questions: line shape versus path selection. A connector controls corner drawing and is separate from both.- Waypoint editing is disabled by default. Without enabling it, clicking the path does not add a bend.
- Endpoint reconnection drops onto a port. If the target is missing or invalid, the existing connection is restored.
- An edge-handles route is pinned when you give it
sourceHandleandtargetHandle; a hand-bent route stores its waypoints in the edge'spointsafter editing.
For port compatibility and connection rules, see Configure ports and Validate connections. For undoing edits, see Command history.
Was this page helpful?