Skip to content

Routing

Edge routing determines how connections between nodes are drawn in your diagram. NgDiagram provides flexible routing where each routing algorithm:

  • Calculates the points that define the edge path
  • Draws the SVG path from those points
  • Computes positions along the path for labels and decorations

Routing algorithms automatically calculate optimal paths, with an option to provide predefined waypoints for static edge paths.

NgDiagram includes three routing algorithms:

The simplest routing that connects points with straight line segments. In auto mode, it creates a direct line between source and target. In manual mode, it can create multi-segment paths by connecting user-provided waypoints with straight lines.

Creates paths using only horizontal and vertical segments, ideal for technical diagrams and flowcharts. Supports configurable segment lengths and optional rounded corners.

Produces smooth curved connections using cubic Bézier curves.

To specify a routing algorithm for an edge, set the routing property in the edge data:

60 collapsed lines
import '@angular/compiler';
import { Component } from '@angular/core';
import {
initializeModel,
NgDiagramComponent,
NgDiagramEdgeTemplateMap,
provideNgDiagram,
} from 'ng-diagram';
import { RoutingEdgeComponent } from './routing-edge.component';
@Component({
imports: [NgDiagramComponent],
providers: [provideNgDiagram()],
template: `
<ng-diagram [model]="model" [edgeTemplateMap]="edgeTemplateMap" />
`,
styles: `
:host {
flex: 1;
display: flex;
height: 100%;
}
`,
})
export class Diagram {
edgeTemplateMap = new NgDiagramEdgeTemplateMap([
['routing-edge', RoutingEdgeComponent],
]);
model = initializeModel({
metadata: {
viewport: { x: 0, y: 0, scale: 0.8 },
},
nodes: [
{
id: 'source-node',
position: { x: 150, y: 240 },
data: { label: 'Source' },
rotatable: true,
},
{
id: '2',
position: { x: 600, y: 30 },
data: { label: 'Target 1' },
rotatable: true,
},
{
id: '3',
position: { x: 600, y: 180 },
data: { label: 'Target 2' },
rotatable: true,
},
{
id: '4',
position: { x: 600, y: 330 },
data: { label: 'Target 3' },
rotatable: true,
},
],
edges: [
{
id: '1',
source: 'source-node',
sourcePort: 'port-right',
targetPort: 'port-left',
target: '2',
routing: 'polyline',
type: 'routing-edge',
data: {},
},
{
id: '2',
source: 'source-node',
sourcePort: 'port-right',
targetPort: 'port-left',
target: '3',
routing: 'orthogonal',
type: 'routing-edge',
data: {},
},
{
id: '3',
source: 'source-node',
sourcePort: 'port-right',
targetPort: 'port-left',
target: '4',
routing: 'bezier',
type: 'routing-edge',
data: {},
},
],
});
}

When no routing is specified, the default routing algorithm (polyline) is used. The default can be changed through configuration.

For custom edge components, you can provide routing directly in the template:

<ng-diagram-base-edge
[edge]="edge"
[routing]="'orthogonal'"
stroke="var(--ngd-default-edge-stroke)"
/>

Each edge can operate in one of two routing modes:

In auto mode, the routing algorithm automatically calculates the path between nodes. The list of points is calculated by the routing algorithm and should be treated as read-only. The path updates automatically when nodes move or resize.

const edge = {
id: 'edge1',
source: 'node1',
target: 'node2',
routing: 'orthogonal',
routingMode: 'auto', // or omit for default
data: {},
};

Manual mode gives you full control over the edge path by allowing you to provide your own list of points. The routing algorithm is still used to draw the SVG path from these points and calculate label positions. When nodes move, you control what happens - the path can remain unchanged, or you can programmatically update the points as needed.

Try moving the nodes in the example below - notice how the edge path remains fixed:

model = initializeModel({
17 collapsed lines
metadata: {
viewport: { x: 0, y: 0, scale: 0.9 },
},
nodes: [
{
id: 'node1',
position: { x: 100, y: 150 },
data: { label: 'Move me!' },
rotatable: true,
},
{
id: 'node2',
position: { x: 500, y: 150 },
data: { label: 'Move me too!' },
rotatable: true,
},
],
edges: [
{
id: 'manual-edge',
source: 'node1',
sourcePort: 'port-right',
target: 'node2',
targetPort: 'port-left',
routing: 'orthogonal',
routingMode: 'manual',
points: [
{ x: 285, y: 172 },
{ x: 345, y: 172 },
{ x: 345, y: 100 },
{ x: 445, y: 100 },
{ x: 445, y: 172 },
{ x: 500, y: 172 },
],
data: {},
},
],
});
}

This is useful for creating custom edge behaviors, fixed connection paths, or implementing your own edge update logic.

You can configure routing behavior globally through the diagram’s configuration:

22 collapsed lines
import '@angular/compiler';
import { Component } from '@angular/core';
import {
initializeModel,
NgDiagramComponent,
provideNgDiagram,
type NgDiagramConfig,
} from 'ng-diagram';
@Component({
imports: [NgDiagramComponent],
providers: [provideNgDiagram()],
template: `<ng-diagram [model]="model" [config]="config" />`,
styles: `
:host {
flex: 1;
display: flex;
height: 100%;
}
`,
})
export class ConfiguredDiagram {
config: NgDiagramConfig = {
edgeRouting: {
defaultRouting: 'orthogonal', // Set default routing
orthogonal: {
firstLastSegmentLength: 30, // Length of first and last segments
maxCornerRadius: 8, // Maximum radius for rounded corners
},
bezier: {
bezierControlOffset: 150, // Distance of control points from nodes
},
},
};
44 collapsed lines
model = initializeModel({
metadata: {
viewport: { x: 0, y: 0, scale: 0.8 },
},
nodes: [
{
id: 'node1',
position: { x: 100, y: 100 },
data: { label: 'Source' },
},
{
id: 'node2',
position: { x: 400, y: 100 },
data: { label: 'Target 1' },
},
{
id: 'node3',
position: { x: 400, y: 250 },
data: { label: 'Target 2' },
},
],
edges: [
{
id: 'edge1',
source: 'node1',
sourcePort: 'port-right',
target: 'node2',
targetPort: 'port-left',
// No routing specified - uses default 'orthogonal' from config
data: {},
},
{
id: 'edge2',
source: 'node1',
sourcePort: 'port-rigth',
target: 'node3',
targetPort: 'port-left',
routing: 'bezier', // Explicitly set to bezier
data: {},
},
],
});
}

NgDiagram’s routing system is extensible. You can create custom routing algorithms by implementing the EdgeRouting interface, or by creating custom edge components that generate their own paths using manual mode.

You can create dynamic custom paths by computing points in your edge component and using manual routing mode. Here’s an example of a sinusoid edge that updates its path dynamically as nodes move:

8 collapsed lines
import { Component, computed, input } from '@angular/core';
import {
NgDiagramBaseEdgeComponent,
type Edge,
type NgDiagramEdgeTemplate,
type Point,
} from 'ng-diagram';
@Component({
selector: 'sinusoid-edge',
template: `<ng-diagram-base-edge
[edge]="customEdge()"
stroke="var(--ngd-default-edge-stroke)"
[targetArrowhead]="'ng-diagram-arrow'"
></ng-diagram-base-edge>`,
styleUrl: './sinusoid-edge.component.scss',
imports: [NgDiagramBaseEdgeComponent],
})
export class SinusoidEdgeComponent implements NgDiagramEdgeTemplate {
edge = input.required<Edge>();
customEdge = computed(() => {
const edge = this.edge();
const { sourcePosition, targetPosition } = edge;
if (!sourcePosition || !targetPosition) {
return edge;
}
const points = this.generateSinusoidPoints(sourcePosition, targetPosition);
return {
...edge,
points,
routing: 'polyline',
routingMode: 'manual' as const,
};
});
47 collapsed lines
private generateSinusoidPoints(sourcePosition: Point, targetPosition: Point) {
const startX = sourcePosition.x;
const startY = sourcePosition.y;
const endX = targetPosition.x;
const endY = targetPosition.y;
// Calculate distance and angle between points
const distance = Math.sqrt(
Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2)
);
const angle = Math.atan2(endY - startY, endX - startX);
// Sinusoidal wave parameters
const amplitude = 20;
const frequency = Math.max(2, distance / 100);
const segments = Math.max(20, Math.floor(distance / 5));
const points = [{ x: startX, y: startY }];
// Generate sinusoidal curve points
for (let i = 1; i < segments; i++) {
const t = i / segments;
// Base position along the straight line
const baseX = startX + (endX - startX) * t;
const baseY = startY + (endY - startY) * t;
// Calculate perpendicular offset using sine wave
// Fade out amplitude as we approach the end to ensure smooth connection
const fadeFactor = i < segments - 5 ? 1 : (segments - i) / 5;
const sineOffset =
Math.sin(t * 2 * Math.PI * frequency) * amplitude * fadeFactor;
const perpAngle = angle + Math.PI / 2;
const x = baseX + Math.cos(perpAngle) * sineOffset;
const y = baseY + Math.sin(perpAngle) * sineOffset;
points.push({ x, y });
}
// Always end exactly at the target position
points.push({ x: endX, y: endY });
return points;
}
}

This approach is ideal when you want the path to update automatically based on node positions and when you just need to compute points - the SVG path can be computed by a built-in routing algorithm (in this case, polyline was sufficient).

For more control and reusability across different edge types, you can create a custom routing algorithm by implementing the EdgeRouting interface:

import { EdgeRouting, EdgeRoutingContext, Point } from 'ng-diagram';
export class CustomRouting implements EdgeRouting {
name = 'custom';
computePoints(context: EdgeRoutingContext): Point[] {
const { sourcePoint, targetPoint, edge, sourceNode, targetNode } = context;
// Your custom routing logic here
// Return array of points defining the path
return [sourcePoint /* ...waypoints */, , targetPoint];
}
computeSvgPath(points: Point[]): string {
// Convert points to SVG path string
return `M ${points[0].x},${points[0].y} ...`;
}
computePointOnPath(points: Point[], percentage: number): Point {
// Calculate point at given percentage along path
// Used for label positioning
return { x: 0, y: 0 };
}
}

Register your custom routing:

import { NgDiagramService } from 'ng-diagram';
constructor(private diagramService: NgDiagramService) {
this.diagramService.registerRouting(new CustomRouting());
}

Then use it in your edges:

const edge = {
id: 'edge1',
source: 'node1',
target: 'node2',
routing: 'custom', // Your custom routing name
data: {},
};