# Route and edit edges

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](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-instance-edgespec#edgespec) 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](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/build-runnable-workflows); the examples here add routing and edge-editing behavior.

:::code-group
```js title="JavaScript"
import { 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();
```
```ts title="Angular"
import { 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,
  }));
}
```
```tsx title="React"
import { 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 title="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](https://grafloria.com/demos/edges/routing-algorithms.html) or [view its source](https://github.com/grafloria/grafloria/blob/6538c1506102712e4265e25bd8ffbb240b3e5038/demos/edges/routing-algorithms.html).

## 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.

:::code-group
```js title="JavaScript"
import { 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();
```
```ts title="Angular"
import { 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',
  }];
}
```
```tsx title="React"
import { 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 title="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](https://grafloria.com/demos/edges/reconnect-edge.html) or [view its source](https://github.com/grafloria/grafloria/blob/6538c1506102712e4265e25bd8ffbb240b3e5038/demos/edges/reconnect-edge.html).

## 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`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-element-core#render). Its returned [`DiagramInstance`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-instance-diagraminstance#diagraminstance) is the live diagram instance.

For the basic mounted two-node pattern, see [How Grafloria works](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/how-grafloria-works); the Vue sample below keeps its setup runnable while adding waypoint editing on a diagonal edge.

:::code-group
```js title="JavaScript"
import { 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();
```
```ts title="Angular"
import { 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,
    });
  }
}
```
```tsx title="React"
import { 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 title="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](https://grafloria.com/demos/edges/editable-edge.html) or [view its source](https://github.com/grafloria/grafloria/blob/6538c1506102712e4265e25bd8ffbb240b3e5038/demos/edges/editable-edge.html).

## 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. |

- `type` and `router` answer 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 `sourceHandle` and `targetHandle`; a hand-bent route stores its waypoints in the edge's `points` after editing.

For port compatibility and connection rules, see [Configure ports](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/configure-ports) and [Validate connections](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/validate-connections). For undoing edits, see [Command history](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/command-history).
