Skip to content

Line Jumps Example

since v1.4.0

This example shows line jumps: where two edges with this routing cross, the horizontal edge jumps over the vertical one. This makes it clear which lines are connected and which only cross.

Move the nodes or draw new edges - the jumps follow the edges. A new edge gets its jumps when you drop it. Use the Arc and Gap buttons to switch between a bridge over the crossed line and a simple break in the line.


import '@angular/compiler';
import { Component, computed, inject } from '@angular/core';
import {
initializeModel,
NgDiagramBackgroundComponent,
NgDiagramComponent,
NgDiagramModelService,
NgDiagramService,
provideNgDiagram,
type Edge,
type Node,
type NgDiagramConfig,
} from 'ng-diagram';
import { LineJumpsRouting, type LineJumpStyle } from './line-jumps-routing';
@Component({
imports: [NgDiagramComponent, NgDiagramBackgroundComponent],
providers: [provideNgDiagram()],
template: `
<div class="not-content diagram">
<div class="controls">
<button
type="button"
[class.active]="jumpStyle() === 'arc'"
[attr.aria-pressed]="jumpStyle() === 'arc'"
[disabled]="!isInitialized()"
(click)="setJumpStyle('arc')"
>
Arc
</button>
<button
type="button"
[class.active]="jumpStyle() === 'gap'"
[attr.aria-pressed]="jumpStyle() === 'gap'"
[disabled]="!isInitialized()"
(click)="setJumpStyle('gap')"
>
Gap
</button>
</div>
<ng-diagram
[model]="model"
[config]="config"
(diagramInit)="onDiagramInit()"
>
<ng-diagram-background />
</ng-diagram>
</div>
`,
styleUrls: ['./diagram.component.scss'],
})
export class DiagramComponent {
private readonly diagramService = inject(NgDiagramService);
private readonly modelService = inject(NgDiagramModelService);
private readonly routing = new LineJumpsRouting(
this.modelService.edges,
this.diagramService.config
);
// The buttons show the style from the config, so there is one source of truth
readonly jumpStyle = computed(() => this.routing.options().style);
// updateConfig() needs the diagram, so the buttons wait for it
readonly isInitialized = this.diagramService.isInitialized;
config = {
zoom: {
zoomToFit: {
onInit: true,
padding: 60,
},
},
edgeRouting: {
'line-jumps': { size: 16, style: 'arc' },
},
} satisfies NgDiagramConfig;
model = initializeModel({
nodes: [
device('sensor-a', 'Sensor A', 100, 0),
device('sensor-b', 'Sensor B', 440, 0),
device('gateway', 'Gateway', 0, 150),
device('router', 'Router', 0, 260),
device('cloud', 'Cloud', 840, 150),
device('backup', 'Backup', 840, 260),
device('panel-a', 'Panel A', 400, 410),
device('panel-b', 'Panel B', 740, 410),
],
});
// The routing must be registered before the edges are routed,
// so the edges are added after diagramInit.
onDiagramInit() {
this.diagramService.registerRouting(this.routing);
// Edges without `routing`, also the ones drawn by the user, use the jumps
this.diagramService.setDefaultRouting('line-jumps');
this.modelService.addEdges(edges);
}
// 'arc' draws a bridge over the crossed line, 'gap' only breaks the line
setJumpStyle(style: LineJumpStyle) {
this.diagramService.updateConfig({
edgeRouting: { 'line-jumps': { style } },
});
}
}
const edges: Edge[] = [
connect('sensor-a', 'panel-a'),
connect('sensor-b', 'panel-b'),
connect('gateway', 'cloud'),
connect('router', 'backup'),
];
function device(id: string, label: string, x: number, y: number): Node {
return { id, position: { x, y }, data: { label } };
}
function connect(source: string, target: string): Edge {
return {
id: `${source}-${target}`,
source,
sourcePort: 'port-right',
target,
targetPort: 'port-left',
targetArrowhead: 'ng-diagram-arrow',
data: {},
};
}

The jumps come from a custom routing, LineJumpsRouting:

  • computePoints returns simple orthogonal points between the ports. The line leaves and enters each port with a short straight segment. The points have no jumps, so labels, relink handles and the crossing search work on clean lines.
  • computeSvgPath draws the path from these points. For each horizontal segment it finds the vertical segments of other edges with this routing that cross it, and adds a jump at each crossing. Edges with another routing are skipped, so a crossing with them has no jump. Crossings that are too close for separate jumps share one wider jump.
  • computePointOnPath places labels along the segments of the path.
  • The routing gets the edges signal from NgDiagramModelService and the config signal from NgDiagramService. In this example the edge reads these signals while it draws its path, so the jumps update when any edge moves or when the options change.

The implementation supports large diagrams and scales well: the vertical segments of all edges are sorted once per model change, and each edge checks only the segments close to its horizontal segments.

The points in this example are kept simple and do not go around other nodes. To use jumps with your own paths, keep the computeSvgPath part and compute your own orthogonal points.

Set the options in the config under the routing name:

edgeRouting: {
'line-jumps': { size: 16, style: 'arc' },
},
  • size - the width of a jump in pixels.
  • style - 'arc' draws an arc over the other line, 'gap' breaks the line instead.

To change the options at runtime, call updateConfig(), as the buttons in this example do. The config argument of computeSvgPath does not cause a redraw, so the routing reads the options from the config signal.

Learn more: Routing guide →