Custom Edge Example
This example demonstrates how to create custom edges in ngDiagram with unique visual styles and interactive elements.
import { Component } from '@angular/core';
@Component({ selector: 'circle-arrowhead', template: ` <svg height="0" width="0"> <defs> <marker id="circle-arrowhead" markerWidth="8" markerHeight="8" refX="1" refY="4" orient="auto" > <circle cx="4" cy="4" r="3" fill="red" /> </marker> </defs> </svg> `,})export class CircleArrowheadComponent {}import '@angular/compiler';
import { Component } from '@angular/core';import { initializeModel, NgDiagramBackgroundComponent, NgDiagramComponent, NgDiagramEdgeTemplateMap, NgDiagramNodeTemplateMap, provideNgDiagram, type NgDiagramConfig,} from 'ng-diagram';import { CircleArrowheadComponent } from './circle-arrowhead.component';import { LabeledEdgeComponent } from './labeled-edge.component';import { NodeComponent } from './node/node.component';import { SinusoidEdgeComponent } from './sinusoid-edge.component';
enum NodeTemplateType { CustomNodeType = 'customNodeType',}
@Component({ imports: [ NgDiagramComponent, NgDiagramBackgroundComponent, CircleArrowheadComponent, ], providers: [provideNgDiagram()], template: ` <div class="not-content diagram"> <ng-diagram [model]="model" [edgeTemplateMap]="edgeTemplateMap" [nodeTemplateMap]="nodeTemplateMap" [config]="config" > <ng-diagram-background /> </ng-diagram> <circle-arrowhead /> </div> `, styleUrls: ['./diagram.component.scss'],})export class DiagramComponent { nodeTemplateMap = new NgDiagramNodeTemplateMap([ [NodeTemplateType.CustomNodeType, NodeComponent], ]); edgeTemplateMap = new NgDiagramEdgeTemplateMap([ ['labeled', LabeledEdgeComponent], ['sinusoid', SinusoidEdgeComponent], ]);
config = { zoom: { zoomToFit: { onInit: true, }, }, } satisfies NgDiagramConfig;
model = initializeModel({ nodes: [ { id: '1', position: { x: 200, y: 0 }, data: { label: 'Node 1' }, type: NodeTemplateType.CustomNodeType, }, { id: '2', position: { x: 620, y: 80 }, data: { label: 'Node 2' }, type: NodeTemplateType.CustomNodeType, }, { id: '3', position: { x: 180, y: 230 }, data: { label: 'Node 3' }, type: NodeTemplateType.CustomNodeType, }, { id: '4', position: { x: 430, y: 480 }, data: { label: 'Node 4' }, type: NodeTemplateType.CustomNodeType, }, ], edges: [ { id: '1', source: '1', sourcePort: 'port-bottom', targetPort: 'port-left', target: '2', data: {}, sourceArrowhead: 'circle-arrowhead', }, { id: '2', source: '2', sourcePort: 'port-left', targetPort: 'port-right', target: '3', type: 'labeled', data: {}, }, { id: '3', source: '3', sourcePort: 'port-bottom', targetPort: 'port-left', target: '4', type: 'sinusoid', data: {}, }, ], });}import { Component, computed, input } from '@angular/core';import { NgDiagramBaseEdgeComponent, NgDiagramBaseEdgeLabelComponent, type Edge, type NgDiagramEdgeTemplate,} from 'ng-diagram';
const STROKE_WIDTH_DEFAULT = 2;const STROKE_WIDTH_SELECTED = 4;
@Component({ template: `<ng-diagram-base-edge [edge]="edge()" stroke="var(--ngd-labeled-edge-stroke)" [strokeWidth]="strokeWidth()" > <ng-diagram-base-edge-label id="test-label" [positionOnEdge]="0.5"> <button style="white-space: nowrap; padding: 4px 8px; background: var(--ngd-node-bg-primary-default); border: none;" (click)="onButtonClick()" > Click Me </button> </ng-diagram-base-edge-label> </ng-diagram-base-edge> `, imports: [NgDiagramBaseEdgeComponent, NgDiagramBaseEdgeLabelComponent],})export class LabeledEdgeComponent implements NgDiagramEdgeTemplate { edge = input.required<Edge>(); selected = computed(() => this.edge().selected); strokeWidth = computed(() => this.selected() ? STROKE_WIDTH_SELECTED : STROKE_WIDTH_DEFAULT );
onButtonClick() { const edge = this.edge(); alert(`Edge ID: ${edge.id}`); }}import { Component, input } from '@angular/core';import { NgDiagramBaseNodeTemplateComponent, NgDiagramPortComponent, type NgDiagramNodeTemplate, type Node,} from 'ng-diagram';
@Component({ imports: [NgDiagramPortComponent, NgDiagramBaseNodeTemplateComponent], templateUrl: './node.component.html', styleUrls: ['./node.component.scss'], host: { '[class.ng-diagram-port-hoverable-over-node]': 'true', },})export class NodeComponent implements NgDiagramNodeTemplate { node = input.required<Node>();}import { Component, computed, input } from '@angular/core';import { NgDiagramBaseEdgeComponent, type Edge, type NgDiagramEdgeTemplate, type Point,} from 'ng-diagram';
@Component({ template: `<ng-diagram-base-edge [edge]="customEdge()" stroke="var(--ngd-sinusoid-edge-stroke)" targetArrowhead="ng-diagram-arrow" />`, 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, }; });
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 = 50; const frequency = Math.max(2, distance / 75); const segments = Math.max(20, Math.floor(distance / 2));
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; }}<ng-diagram-base-node-template [node]="node"> <span class="node-label">{{ node().data.label }}</span></ng-diagram-base-node-template><ng-diagram-port id="port-bottom" type="both" side="bottom" /><ng-diagram-port id="port-top" type="both" side="top" />.diagram { display: flex; height: var(--ng-diagram-height); border: var(--ng-diagram-border);
--ngd-labeled-edge-stroke: #ecaf40; --ngd-sinusoid-edge-stroke: #4c75f2;}:host { .node-label { width: 200px; height: 32px; display: flex; align-items: center; justify-content: center; }}Additional Explanation
Section titled “Additional Explanation”Example showcases three different edge types:
-
Default Edge:
- a standard edge parametrized through model data
- enhanced with a custom circular arrowhead marker defined in SVG
-
Labeled Edge:
- a custom edge with adjusted stroke width dynamically changing on selection
- features interactive button label positioned at its midpoint
-
Sinusoid Edge:
- a custom wave-shaped edge with adjusted stroke color
- renders a sinusoidal curve between nodes
Key Concepts
Section titled “Key Concepts”- Creating custom edge components that implement
NgDiagramEdgeTemplate - Adjusting parameters of
NgDiagramBaseEdgeComponentin custom edges - Registering edge types using the
edgeTemplateMap - Defining custom SVG markers for arrowheads
- Adding interactive elements with
NgDiagramBaseEdgeLabelComponent - Generating complex path calculations for non-standard edge shapes