Use routing, labels and endpoint markers when a diagram needs to distinguish a connection from a crossing, or several relationships between the same nodes. The example below draws a right-angle detour around an obstacle, two crossing wires with a jump-over, three separate parallel lanes, and a self-loop outside its node.
An edge stores intent; the renderer turns that intent into geometry as nodes move. Describe connections with EdgeSpec rather than calculating SVG paths yourself.
1. Describe the routes and their labels
Create edge-data.ts in your browser application. The NodeSpec arrays provide actual endpoints and an obstacle. Every edge has a stable id so you can identify the corresponding live link later.
The first edge pins its endpoints halfway down the right and left sides. Its router chooses the route; its connector rounds the bends. The label sits above the line, the source carries a circle, and the target carries an arrow.
tsimport type { EdgeSpec, NodeSpec, SVGRendererConfig } from '@grafloria/renderer';
export const nodes: NodeSpec[] = [
{ id: 'a', label: 'A', position: { x: 40, y: 100 }, size: { width: 110, height: 60 } },
{ id: 'b', label: 'B', position: { x: 750, y: 100 }, size: { width: 110, height: 60 } },
{ id: 'wall', label: 'Obstacle', position: { x: 400, y: 60 }, size: { width: 120, height: 140 } },
{ id: 'c', label: 'C', position: { x: 40, y: 270 }, size: { width: 110, height: 44 } },
{ id: 'd', label: 'D', position: { x: 750, y: 270 }, size: { width: 110, height: 44 } },
{ id: 'e', label: 'E', position: { x: 40, y: 430 }, size: { width: 110, height: 44 } },
{ id: 'f', label: 'F', position: { x: 750, y: 430 }, size: { width: 110, height: 44 } },
{ id: 'g', label: 'G', position: { x: 40, y: 560 }, size: { width: 110, height: 60 } },
{ id: 'h', label: 'H', position: { x: 480, y: 560 }, size: { width: 110, height: 60 } },
{ id: 'self', label: 'Self', position: { x: 750, y: 560 }, size: { width: 110, height: 60 } },
];
export const edges: EdgeSpec[] = [
{
id: 'detour', source: 'a', target: 'b',
sourceHandle: 'right@50%', targetHandle: 'left@50%',
type: 'orthogonal', router: 'orthogonal', connector: 'rounded',
label: 'depends on', labelPlacement: 'above',
labelStyle: { color: '#15803d', fontSize: 12 },
style: {
stroke: '#15803d', strokeWidth: 2,
arrowTail: { type: 'circle', size: 6, filled: true },
arrowHead: { type: 'arrow', size: 10, filled: true },
},
},
{
id: 'cf', source: 'c', target: 'f', type: 'direct',
sourceHandle: 'right', targetHandle: 'left',
style: { jumpPoints: { enabled: true, size: 10, detectMode: 'all' } },
},
{
id: 'ed', source: 'e', target: 'd', type: 'direct',
sourceHandle: 'right', targetHandle: 'left',
style: { jumpPoints: { enabled: true, size: 10, detectMode: 'all' } },
},
{ id: 'p1', source: 'g', target: 'h', type: 'direct', label: 'request' },
{ id: 'p2', source: 'g', target: 'h', type: 'direct', label: 'response' },
{ id: 'p3', source: 'g', target: 'h', type: 'direct', label: 'audit' },
{
id: 'loop', source: 'self', target: 'self', label: 'retry',
style: { selfLoop: { side: 'top', size: 40 } },
},
];
export const rendererConfig: Partial<SVGRendererConfig> = {
parallelLinks: true,
parallelSpacing: 24,
jumpOwnership: 'single',
};
SVGRendererConfig controls the whole diagram's lane spacing and crossing ownership. jumpOwnership: 'single' gives each crossing one hop even though both crossing edges enable jump points.
2. Mount the diagram
Choose your framework's tab and place its file beside edge-data.ts. Install that tab's packages in your application:
bash# JavaScript
npm install @grafloria/element @grafloria/engine @grafloria/renderer
# Angular
npm install @grafloria/angular @angular/common @angular/core @angular/forms @angular/platform-browser @grafloria/engine @grafloria/renderer rxjs @grafloria/element
# Qwik
npm install @grafloria/qwik @grafloria/engine @grafloria/renderer @builder.io/qwik @grafloria/element
# React
npm install @grafloria/react @grafloria/engine @grafloria/renderer react react-dom @grafloria/element
# Vue
npm install @grafloria/vue @grafloria/engine @grafloria/renderer vue @grafloria/element
For JavaScript, render mounts the spec and returns a live DiagramInstance. The JavaScript tab uses TypeScript in main.ts and creates its container in the browser.
For Angular, render DiagramCanvasComponent in app.component.ts; its two-way bindings return node and edge edits to your arrays. For Qwik, React and Vue, render the binding's component: GrafloriaFlow, GrafloriaFlow, or GrafloriaFlow, respectively. These tabs seed an uncontrolled instance with defaultNodes and defaultEdges.
tsimport { render } from '@grafloria/element';
import { nodes, edges, rendererConfig } from './edge-data';
const container = document.createElement('div');
container.style.height = '700px';
document.body.append(container);
const instance = render({ nodes, edges }, container, {
renderer: rendererConfig,
});
instance.fitView();
tsimport { AfterViewInit, Component, ViewChild } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import type { NodeSpec, EdgeSpec } from '@grafloria/renderer';
import { nodes, edges, rendererConfig } from './edge-data';
@Component({
selector: 'app-root',
standalone: true,
imports: [DiagramCanvasComponent],
template: `
<grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges"
[rendererConfig]="rendererConfig"
style="display:block; height:700px" />
`,
})
export class AppComponent implements AfterViewInit {
nodes: NodeSpec[] = nodes;
edges: EdgeSpec[] = edges;
rendererConfig = rendererConfig;
@ViewChild(DiagramCanvasComponent) canvas?: DiagramCanvasComponent;
ngAfterViewInit(): void {
requestAnimationFrame(() => this.canvas?.fitToContent());
}
}
tsximport { component$ } from '@builder.io/qwik';
import { GrafloriaFlow } from '@grafloria/qwik';
import { nodes, edges, rendererConfig } from './edge-data';
export default component$(() => (
<div style={{ height: '700px' }}>
<GrafloriaFlow defaultNodes={nodes} defaultEdges={edges}
rendererConfig={rendererConfig} fitView />
</div>
));
tsximport { GrafloriaFlow } from '@grafloria/react';
import { nodes, edges, rendererConfig } from './edge-data';
export default function App() {
return (
<div style={{ height: '700px' }}>
<GrafloriaFlow defaultNodes={nodes} defaultEdges={edges}
rendererConfig={rendererConfig} fitView />
</div>
);
}
vue<script setup lang="ts"> import { GrafloriaFlow } from '@grafloria/vue'; import { nodes, edges, rendererConfig } from './edge-data'; </script> <template> <div style="height:700px"> <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" :renderer-config="rendererConfig" fit-view /> </div> </template>
Angular draws the same connections in its canvas.
Qwik renders the seeded edge specs.
React renders the same edge geometry.
Vue renders the routes and labels from the shared data.
Follow A → B around the obstacle, then inspect the middle crossing: the hop distinguishes two unrelated wires from a junction. At the bottom, G → H has three individually selectable lanes, and Self → Self leaves the node body before returning. Drag the obstacle or an endpoint node to see the routes update.
Choose the geometry
type is a shorthand for the line's shape. Explicit router and connector fields let you choose its path and its drawing independently.
type | Router when omitted | Connector when omitted and no explicit router is set |
|---|---|---|
direct | straight | straight |
smooth | straight | smooth |
orthogonal | orthogonal | rounded |
bezier | straight | bezier |
An explicit orthogonal, manhattan, avoid or elk router implies a rounded connector unless you name a connector yourself. Use straight for sharp polyline bends, rounded for rounded corners, or smooth / bezier for curved drawing.
| Router | What you get |
|---|---|
straight | A direct route between endpoints. |
orthogonal | Right-angle segments respecting port exit directions; the sample detours around the obstacle. |
manhattan | Grid-based right-angle routing with turn minimization. |
avoid | Obstacle-avoiding routing through the built-in A* router. |
elk | The synchronous renderer currently substitutes orthogonal. |
Known issue:
router: 'elk'does not produce ELK routing through the mounted renderer: ELK routing is async-only, and this rendering path substitutesorthogonal. Until it is fixed, requestrouter: 'orthogonal'explicitly, as the sample does; userouter: 'avoid'when you want the built-in A* obstacle router.
For node placement by ELK, see Lay out a diagram.
Choose attachments, labels and markers
Use handles when a connection must stay at a particular port or position on a box. Omit both handles to let the attachment follow the real port on the side facing its partner. For true perimeter floating, set the edge's metadata to { connectionPoint: 'smart' }; this permits attachment along the outline rather than only at a port.
| Option | Type | Default | What it does |
|---|---|---|---|
sourceHandle, targetHandle | string | Port-facing when neither is named | Pin to a port id, side name, or point along a side. right@36 is 36 px down the right side; bottom@138 is 138 px from its left end; left@50% is halfway down. |
waypoints | Point array | No supplied bends | Supply interior bends in world coordinates; endpoints stay attached to ports. |
labelPlacement | 'on' | 'above' | 'below' | 'on' | Draw a label chip on the line, or text off the line without a box unless its style requests a background. |
labelStyle | Label style | No override | Set the label's own color, font size, weight, family or background. |
style.arrowHead | Arrow style | Filled arrow, size 10 | Choose the target marker. Supply type, size and filled. |
style.arrowTail | Arrow style | No source marker | Choose the source marker with the same fields. |
parallelLinks | boolean | true | Fan links between the same unordered pair of nodes into separate lanes, including reverse-direction links. |
parallelSpacing | number | 16 | Set adjacent parallel-lane spacing in pixels. |
jumpOwnership | 'both' | 'single' | 'both' | Choose whether both jump-enabled links or one link draws a crossing hop. |
style.jumpPoints.enabled | boolean | Not enabled without configuration | Enable crossing decorations on that edge. |
style.jumpPoints.size | number | 10 | Set the jump size in pixels. |
style.selfLoop.size | number | 40 | Set how far a self-loop bulges outside its node. |
style.selfLoop.side | 'auto' | 'top' | 'right' | 'bottom' | 'left' | 'auto' | Choose the loop's side; automatic uses its source port's side. |
Endpoint markers include arrow, circle, square and diamond, plus ER markers such as crow-foot and zero-or-many, and UML markers such as generalization and hollow-diamond. Set type: 'none' to suppress an endpoint marker. Use the shipped shapes before registering your own geometry.
For more than one label on a link, obtain its live LinkModel through instance.getModel().getLink(id). addLabel({ text: 'condition', position: 0.25 }) adds text a quarter of the way along its path; call instance.renderNow() after setup mutations to repaint. A fractional label position follows the route instead of remaining at an absolute canvas coordinate.
Pitfalls
- A connection-point strategy that accepts the edge owns both endpoints before per-end
metadata.sourceAnchor/metadata.targetAnchorare considered. Do not combine a floating strategy with per-end anchors expecting the anchors to take precedence. aboveandbeloware relative to the run's direction: on a vertical run,aboveplaces text to its left.- Changing a live link's router through
setRouter()clears its cached points and manual-waypoint flag. ChangingsetConnector()leaves routed points intact. Choose the router before adding bends you need to keep. - Jump points do not replace a two-point smooth or bezier curve with a chord-based hop. Use direct or right-angle crossing wires, as above, when you need jump-overs.
- For user-facing edits that belong in undo history, follow Commands and history, rather than treating setup model mutations as commands.
Live demos and related tasks
- Edge routing: drag or remove the obstacle and inspect the detour.
- Edge labels: drag a label along its edge, then move an endpoint.
- Parallel links and self-loops: inspect separate lanes and the outside loop.
- Jump-overs: move the crossing and watch its hop follow.
See Validate port connections for allowed connections, Configure editing gestures for interaction settings, and Save and restore documents for preserving the live document.
Was this page helpful?