# Route and edit edges

Use an edge's route to express how it travels, then let the mounted diagram keep that route correct as nodes move. This page covers geometry, obstacle avoidance, labels, waypoints, and reconnecting an existing edge in JavaScript, Angular, Qwik, React, and Vue.

## Choose the route and shape

For the [`EdgeSpec`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-instance-edgespec#edgespec) fields `source`, `target`, `type`, and `router`, see [layout and routing](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/layout-and-routing). This page adds connector styling, labels, fixed waypoints, obstacle avoidance, and endpoint reconnection.

```ts
import type { EdgeSpec } from '@grafloria/renderer';

const edges: EdgeSpec[] = [{
  id: 'avoiding-edge', source: 'a', target: 'b', router: 'avoid', connector: 'rounded',
  label: 'retry', labelPlacement: 'above', waypoints: [{ x: 420, y: 180 }],
}];
```

Use `connector: 'straight' | 'rounded' | 'smooth' | 'bezier'` for the connector treatment. A label is editable in the mounted diagram; `labelPlacement: 'above'` or `'below'` places it beside the line, and `labelStyle` controls its appearance.

## Mount an obstacle-avoiding edge

The following data puts a wall between A and B. The edge renders as a right-angled detour and stays outside the wall when the wall moves. Give the host a height: a zero-height host has no canvas to paint.

:::code-group
```js title="JavaScript"
import { render } from '@grafloria/element';
const nodes = [
  { id: 'a', position: { x: 80, y: 260 }, size: { width: 120, height: 60 }, label: 'A' },
  { id: 'b', position: { x: 700, y: 260 }, size: { width: 120, height: 60 }, label: 'B' },
  { id: 'wall', position: { x: 390, y: 205 }, size: { width: 140, height: 170 }, label: 'obstacle' },
];
const edges = [{ id: 'e1', source: 'a', target: 'b', router: 'avoid', connector: 'rounded' }];
const host = document.getElementById('diagram');
if (!host) throw new Error('Missing #diagram');
host.style.height = '100vh';
const api = render({ nodes, edges }, host, { interaction: { enableWaypointEditing: true, showWaypointHandles: true } });
api.renderNow();
```
```ts title="Angular"
import { Component } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import type { EdgeSpec } from '@grafloria/renderer';
@Component({ standalone: true, imports: [DiagramCanvasComponent], template: `<grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges" style="display:block;height:100vh" />` })
export class EdgeGuideComponent {
  nodes = [
    { id: 'a', position: { x: 80, y: 260 }, size: { width: 120, height: 60 }, label: 'A' },
    { id: 'b', position: { x: 700, y: 260 }, size: { width: 120, height: 60 }, label: 'B' },
    { id: 'wall', position: { x: 390, y: 205 }, size: { width: 140, height: 170 }, label: 'obstacle' },
  ];
  edges: EdgeSpec[] = [{ id: 'e1', source: 'a', target: 'b', router: 'avoid', connector: 'rounded' }];
}
```
```tsx title="Qwik"
import { component$ } from '@builder.io/qwik';
import '@grafloria/element';
const nodes = [
  { id: 'a', position: { x: 80, y: 260 }, label: 'A' },
  { id: 'b', position: { x: 700, y: 260 }, label: 'B' },
  { id: 'wall', position: { x: 390, y: 205 }, label: 'obstacle' },
];
const edges = [{ id: 'e1', source: 'a', target: 'b', router: 'avoid', connector: 'rounded' }];
export default component$(() => <div style={{ height: '100vh' }}><grafloria-flow nodes={JSON.stringify(nodes)} edges={JSON.stringify(edges)} /></div>);
```
```tsx title="React"
import { GrafloriaFlow } from '@grafloria/react';
const nodes = [
  { id: 'a', position: { x: 80, y: 260 }, size: { width: 120, height: 60 }, label: 'A' },
  { id: 'b', position: { x: 700, y: 260 }, size: { width: 120, height: 60 }, label: 'B' },
  { id: 'wall', position: { x: 390, y: 205 }, size: { width: 140, height: 170 }, label: 'obstacle' },
];
const edges = [{ id: 'e1', source: 'a', target: 'b', router: 'avoid', connector: 'rounded' }];
export default function EdgeGuide() { return <div style={{ height: '100vh' }}><GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} /></div>; }
```
```vue title="Vue"
<script setup lang="ts">
import { GrafloriaFlow } from '@grafloria/vue';
const nodes = [
  { id: 'a', position: { x: 80, y: 260 }, size: { width: 120, height: 60 }, label: 'A' },
  { id: 'b', position: { x: 700, y: 260 }, size: { width: 120, height: 60 }, label: 'B' },
  { id: 'wall', position: { x: 390, y: 205 }, size: { width: 140, height: 170 }, label: 'obstacle' },
];
const edges = [{ id: 'e1', source: 'a', target: 'b', router: 'avoid', connector: 'rounded' }];
</script>
<template><div style="height:100vh"><GrafloriaFlow :default-nodes="nodes" :default-edges="edges" /></div></template>
```
:::

The JavaScript [`render`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-element-core#render) call returns the live [`DiagramInstance`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-renderer-instance-diagraminstance#diagraminstance). The wrappers mount the same model; moving `wall` makes the obstacle-avoiding route recalculate. See the [edge-routing demo](https://grafloria.com/demos/edges/edge-routing.html).

## Add labels and fixed bends

Put interior points in `waypoints` when a route must pass through known bends. Points use world coordinates; do not include endpoint coordinates. The route keeps the bends while its endpoints reattach as nodes move.

```ts
import type { EdgeSpec } from '@grafloria/renderer';

const labelledEdge: EdgeSpec = {
  id: 'labelled', source: 'a', target: 'b', router: 'orthogonal', connector: 'rounded',
  waypoints: [{ x: 360, y: 160 }, { x: 620, y: 160 }], label: 'asks for approval',
  labelPlacement: 'above', labelStyle: { color: '#5f6b7a', fontSize: 11 },
};
```

For interactive editing, enable `enableWaypointEditing` and `showWaypointHandles` in the interaction configuration. Select an edge, click its path to add one interior waypoint, then drag the handle. Each bend changes the rendered route and is one undoable user edit. See the [editable-edge demo](https://grafloria.com/demos/edges/editable-edge.html).

:::code-group
```js title="JavaScript"
import { render } from '@grafloria/element';
const nodes = [{ id: 'a', position: { x: 80, y: 180 }, label: 'A' }, { id: 'b', position: { x: 620, y: 180 }, label: 'B' }];
const host = document.getElementById('diagram');
if (!host) throw new Error('Missing #diagram');
host.style.height = '100vh';
const editable = render({ nodes, edges: [{ id: 'e1', source: 'a', target: 'b', type: 'direct' }] }, host, { interaction: { enableWaypointEditing: true, showWaypointHandles: true } });
editable.renderNow();
```
```ts title="Angular"
import { AfterViewInit, Component, viewChild } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
@Component({ standalone: true, imports: [DiagramCanvasComponent], template: `<grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges" style="display:block;height:100vh" />` })
export class EditableEdgeComponent implements AfterViewInit {
  readonly canvas = viewChild.required(DiagramCanvasComponent);
  nodes = [{ id: 'a', position: { x: 80, y: 180 }, label: 'A' }, { id: 'b', position: { x: 620, y: 180 }, label: 'B' }];
  edges = [{ id: 'e1', source: 'a', target: 'b', type: 'direct' as const }];
  ngAfterViewInit(): void { this.canvas().activeEngine()?.setInteractionConfig({ enableWaypointEditing: true, showWaypointHandles: true }); }
}
```
```tsx title="Qwik"
import { component$ } from '@builder.io/qwik';
import '@grafloria/element';
const nodes = [{ id: 'a', position: { x: 80, y: 180 }, label: 'A' }, { id: 'b', position: { x: 620, y: 180 }, label: 'B' }];
const edges = [{ id: 'e1', source: 'a', target: 'b', type: 'direct' as const }];
export default component$(() => <div style={{ height: '100vh' }}><grafloria-flow nodes={JSON.stringify(nodes)} edges={JSON.stringify(edges)} interaction={JSON.stringify({ enableWaypointEditing: true, showWaypointHandles: true })} /></div>);
```
```tsx title="React"
import { GrafloriaFlow } from '@grafloria/react';
const nodes = [{ id: 'a', position: { x: 80, y: 180 }, label: 'A' }, { id: 'b', position: { x: 620, y: 180 }, label: 'B' }];
const edges = [{ id: 'e1', source: 'a', target: 'b', type: 'direct' as const }];
export default function EditableEdge() { return <div style={{ height: '100vh' }}><GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} interaction={{ enableWaypointEditing: true, showWaypointHandles: true }} /></div>; }
```
```vue title="Vue"
<script setup lang="ts">
import { GrafloriaFlow } from '@grafloria/vue';
const nodes = [{ id: 'a', position: { x: 80, y: 180 }, label: 'A' }, { id: 'b', position: { x: 620, y: 180 }, label: 'B' }];
const edges = [{ id: 'e1', source: 'a', target: 'b', type: 'direct' as const }];
</script>
<template><div style="height:100vh"><GrafloriaFlow :default-nodes="nodes" :default-edges="edges" :interaction="{ enableWaypointEditing: true, showWaypointHandles: true }" /></div></template>
```
:::

## Reconnect an existing edge

Pin an endpoint to a side or port when the connection must land there:

```ts
import type { EdgeSpec } from '@grafloria/renderer';

const reconnectable: EdgeSpec = {
  id: 'e1', source: 'a', target: 'b', sourceHandle: 'right', targetHandle: 'left', type: 'direct',
};
```

With waypoint editing off, select the edge, press its endpoint handle, drag to another valid port, and release. The model changes from A→B to A→C; validators run for the new target. Subscribe to the instance's `reconnect` event when application state needs the new endpoint:

```ts
import { render } from '@grafloria/element';

const nodes = [
  { id: 'a', position: { x: 80, y: 260 }, label: 'A' },
  { id: 'b', position: { x: 660, y: 110 }, label: 'B' },
  { id: 'c', position: { x: 660, y: 430 }, label: 'C' },
];
const host = document.getElementById('diagram');
if (!host) throw new Error('Missing #diagram');
host.style.height = '100vh';
const api = render({ nodes, edges: [{ id: 'e1', source: 'a', target: 'b', sourceHandle: 'right', targetHandle: 'left', type: 'direct' }] }, host);
const stop = api.on('reconnect', () => {
  const link = api.getModel().getLink('e1');
  if (link) console.log(link.targetNodeId);
});

// Call stop() when the host unmounts.
```

The same gesture is built into the bindings shown here; no custom pointer handler is required. Keep endpoint handles pinned in a test or demo when you need the handle to sit directly on the port. See the [reconnect-edge demo](https://grafloria.com/demos/edges/reconnect-edge.html).

## Pitfalls

- `type` chooses the line shape; `router` chooses the path. Set both when you need both guarantees.
- `waypoints` contains interior points only. The router supplies the endpoints.
- Waypoint editing is opt-in. Without `enableWaypointEditing`, clicking the path does not add a bend.
- A mounted canvas needs a resolved height. See [edit Mermaid diagrams](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/edit-mermaid-diagrams) for the canvas sizing warning.
- Reconnecting an endpoint invokes connection validation again. See [ports and connection rules](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/ports-and-connection-rules).

## Related

- [Ports and connection rules](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/ports-and-connection-rules)
- [Commands and shared history](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/commands-and-shared-history)
- [Events and interaction](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/events-and-interaction)
- [The DiagramInstance](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/instance-and-lifecycle)
