Node graph
@xui/node-graphZoom and framing buttons for the enclosing canvas.
Install
pnpm add @xui/node-graph Add the barrel to a standalone component's imports:
import { XuiNodeGraphImports } from '@xui/node-graph';
@Component({
imports: [XuiNodeGraphImports],
// …
})Examples
Shader graph
Source only — this one needs a component of its own to run, so there is nothing to render here.
<xui-story-graph [nodes]="nodes" [initialEdges]="edges" [portTypes]="types" collapsible />Schematic
Source only — this one needs a component of its own to run, so there is nothing to render here.
<xui-story-graph
[nodes]="nodes"
[initialEdges]="edges"
routing="orthogonal"
background="grid"
[gridSize]="20"
[snapToGrid]="true"
portShape="pin"
portLayout="columns"
/>Groups
Source only — this one needs a component of its own to run, so there is nothing to render here.
<xui-story-graph
[nodes]="nodes"
[groups]="groups"
[initialEdges]="edges"
[portTypes]="types"
[adoptOnDrop]="true"
/>Create on drop
Source only — this one needs a component of its own to run, so there is nothing to render here.
<xui-story-graph [nodes]="nodes" [catalog]="catalog" [portTypes]="types" />Routing
<div class="grid grid-cols-2 gap-4">
@for (mode of modes; track mode) {
<div>
<p class="text-foreground-muted mb-1 text-xs font-medium">{{ mode }}</p>
<xui-node-graph
class="border-border h-56 rounded-lg border"
[routing]="mode"
background="none"
[edges]="[{ id: 'e', source: { nodeId: 'a', portId: 'out' }, target: { nodeId: 'b', portId: 'in' } }]"
>
<xui-graph-node nodeId="a" label="Source" [position]="{ x: 30, y: 30 }" [width]="120">
<xui-graph-port portId="out" direction="output" label="Out" />
</xui-graph-node>
<xui-graph-node nodeId="b" label="Target" [position]="{ x: 200, y: 120 }" [width]="120">
<xui-graph-port portId="in" direction="input" label="In" />
</xui-graph-node>
</xui-node-graph>
</div>
}
</div>Port shapes
<xui-node-graph class="border-border h-72 rounded-lg border" background="none">
<xui-graph-node nodeId="shapes" label="Port shapes" [position]="{ x: 60, y: 40 }" [width]="200">
@for (shape of shapes; track shape) {
<xui-graph-port [portId]="shape" [label]="shape" direction="input" [shape]="shape" />
}
</xui-graph-node>
</xui-node-graph>Inline widgets
<xui-node-graph class="border-border h-80 rounded-lg border">
<xui-graph-node nodeId="noise" label="Noise" accent="var(--color-chart-2)"
[position]="{ x: 80, y: 60 }" [width]="240">
<xui-graph-node-preview class="bg-muted h-16" />
<xui-graph-port portId="scale" direction="input" label="Scale">
<xui-slider xuiGraphNoDrag class="ml-auto w-24" [max]="10" [value]="4"
[labelRenderer]="false" aria-label="Scale" />
</xui-graph-port>
<xui-graph-port portId="octaves" direction="input" label="Octaves">
<xui-numeric-input xuiGraphNoDrag size="sm" class="ml-auto w-20" value="4"
[min]="1" [max]="8" aria-label="Octaves" />
</xui-graph-port>
<xui-graph-port portId="seed" direction="input" label="Seed" />
<xui-graph-port portId="out" direction="output" label="Value" />
</xui-graph-node>
</xui-node-graph>Column layout
Source only — this one needs a component of its own to run, so there is nothing to render here.
<xui-story-graph [nodes]="nodes" [initialEdges]="edges" [portTypes]="types" portLayout="columns" />Edge styles
<xui-node-graph class="border-border h-96 rounded-lg border" [edges]="edges">
<xui-graph-node nodeId="a" label="Out" [position]="{ x: 60, y: 60 }" [width]="140">
<xui-graph-port portId="p1" direction="output" label="label" />
<xui-graph-port portId="p2" direction="output" label="animated" />
<xui-graph-port portId="p3" direction="output" label="dashed" />
<xui-graph-port portId="p4" direction="output" label="marker" />
</xui-graph-node>
<xui-graph-node nodeId="b" label="In" [position]="{ x: 420, y: 60 }" [width]="140">
<xui-graph-port portId="p1" direction="input" />
<xui-graph-port portId="p2" direction="input" />
<xui-graph-port portId="p3" direction="input" />
<xui-graph-port portId="p4" direction="input" />
</xui-graph-node>
</xui-node-graph>API
Zoom and framing buttons for the enclosing canvas.
Inputs
classClassValueExtra classes, merged into the component's own rather than replacing them.''showFitbooleanShow the fit-to-view button.trueshowZoomLevelbooleanShow the current zoom as a percentage between the zoom buttons.trueorientation'horizontal' | 'vertical'Lay the buttons out in a column (default) or a row.'vertical'A frame around a set of nodes that moves them all together.
Inputs
groupIdstringMatches the group input on the nodes that belong to this frame.classClassValueExtra classes, merged into the component's own rather than replacing them.''labelstringThe group's caption. An empty label drops the header entirely, and with it the vertical offset it adds to the group's contents.''colorstringBorder and title colour; a tint of it fills the frame. Any CSS colour.'var(--color-border-strong)'paddingnumberBlank margin between the members' bounding box and the frame.24selectablebooleanLet the group be selected by clicking it or by a marquee.truedraggablebooleanLet the group be dragged, taking the nodes inside it along. A locked group, or a locked graph, overrides this.truelockedbooleanPins the frame and everything in it.falsedragHandle'group' | 'header'group drags from anywhere on the frame, as a node editor's frames do. header restricts it to the title bar, which leaves the frame's interior free for panning and marquee selection.'group'Methods
ngOnInit(): voidAn overview of the whole graph with the current viewport drawn on it.
Inputs
classClassValueExtra classes, merged into the component's own rather than replacing them.''paddingnumberBlank margin around the content, in graph units.40Replaces a node's default header. Use it when the title needs an icon, a badge, or anything beyond the label input.
Inputs
classClassValueExtra classes, merged into the component's own rather than replacing them.''Trailing controls in a node header — a menu button, a toggle, a status dot.
Inputs
classClassValueExtra classes, merged into the component's own rather than replacing them.''A full-bleed block between the header and the ports — a thumbnail, a preview render, a waveform. Sits above the port rows, as it does in a VFX graph.
Inputs
classClassValueExtra classes, merged into the component's own rather than replacing them.''Furniture layered over the canvas — a legend, a toolbar, a context menu.
Inputs
classClassValueExtra classes, merged into the component's own rather than replacing them.''Marks a subtree inside a node as not draggable, so pointer gestures on it belong to the control rather than moving the node.
A card on the canvas: a header, an optional body, and a set of ports.
Variants
selectedfalselockedfalsedimmedfalseInputs
nodeIdstringIdentifies the node to edges and to the selection. Must be stable and unique.classClassValueExtra classes, merged into the component's own rather than replacing them.''positionXuiGraphPointTop-left corner in graph space. Two-way bound so the host owns the value.{ x: 0, y: 0 }labelstringThe node's caption, shown in its header.''widthnumber | undefinedFixed width in graph units. Leave unset to size to content.undefinedaccentstringAccent colour for the header's top rule — the usual way a node editor shows which category a node belongs to. Any CSS colour; prefer a theme token.groupstringId of the <xui-graph-group> this node belongs to. Membership lives on the node rather than in the frame, so a node never has to move in the template to join or leave one.portLayoutXuiGraphPortLayoutArrangement of the port rows.'stacked'collapsedbooleanHides the body and pulls every connector onto the header, so a finished subtree can be folded away without breaking its wiring.falsecollapsiblebooleanAdd a header toggle that folds the node's body away, leaving its ports and edges intact.falseselectablebooleanLet the node be selected. Also what gives it role="option" and aria-selected; an unselectable node has neither.truedraggablebooleanLet the node be dragged. A locked node, or a locked graph, overrides this.truelockedbooleanPins the node in place while leaving it selectable.falsedragHandle'node' | 'header'Restrict dragging to the header, as a window manager does.'node'dimmedbooleanRenders the node faded — for a disabled branch or a bypassed effect.falseOutputs
activatedvoidDouble-click on the header. The usual gesture for entering a subgraph.Methods
ngOnInit(): voidmoveTo(point: XuiGraphPoint): voidA connector on a node, together with the row of content that belongs to it.
Inputs
portIdstringUnique within the owning node — the node id supplies the rest of the identity.classClassValueExtra classes, merged into the component's own rather than replacing them.''directionXuiGraphPortDirectionWhich way data flows. inout covers terminals that are neither source nor sink — a component lead in a schematic, where either end of a wire may be drawn first.'input'labelstringThe port's caption. Also its accessible name, falling back to the port id when empty.''dataTypestringThe compatibility key. Two ports may only be wired together when their data types match, and the type also picks the connector's colour — see provideXuiNodeGraphConfig({ portTypes }). Leaving it unset makes the port a wildcard that connects to anything.colorstringOverrides the colour inherited from {@link dataType}.disabledbooleanBlock connections to and from this port. It stays visible and keeps its existing edges.falsesideXuiGraphPortSideWhich node edge the connector sits on. Leave unset to take it from {@link direction} — inputs on the left, outputs on the right.shapeXuiGraphPortShapeOverride the port's glyph. Falls back to the shape declared for its type, then to the graph's default.maxConnectionsnumberHow many wires may terminate here. Defaults to 1 for an input — the usual rule that a value has exactly one producer — and unlimited for outputs and inout terminals, which fan out freely.NaNMethods
ngOnInit(): voidA pannable, zoomable canvas for node-based editors and schematics.
Inputs
classClassValueExtra classes, merged into the component's own rather than replacing them.''edgesreadonly XuiGraphEdge[]The wires to draw. Never mutated — see connect.[]viewportXuiGraphViewportPan and zoom. Two-way bindable, so a host can save and restore the view.{ x: 0, y: 0, zoom: 1 }routingXuiGraphRoutingHow edges are drawn between ports: curved (bezier), right-angled (orthogonal), rounded right angles (smoothstep), or direct (straight).this.config.routingbackgroundXuiGraphBackgroundThe canvas pattern behind the graph, which scales with the zoom.this.config.backgroundmarkerXuiGraphMarkerDefault end marker on an edge. An edge may override it.this.config.markergridSizenumberSpacing of the background pattern, and the step nodes snap to when snapToGrid is on.this.config.gridSizesnapToGridbooleanRound node positions to gridSize as they are dragged. Holding Shift while nudging with the arrow keys does the same.this.config.snapToGridminZoomnumberHow far the canvas can zoom out, as a scale factor.this.config.minZoommaxZoomnumberHow far the canvas can zoom in, as a scale factor.this.config.maxZoomedgeWidthnumberDefault stroke width for edges, in graph units. An edge may override it.this.config.edgeWidthportSizenumberDefault port glyph size, in graph units.this.config.portSizeportShapeXuiGraphPortShapeDefault port glyph. A port type, or the port itself, may override it.this.config.portShapeportColorstringDefault port colour. A port type, or the port itself, may override it.this.config.portColorportTypesRecord<string, XuiGraphPortType>Data-type registry: colour, glyph and label per dataType.this.config.portTypesallowSelfConnectionbooleanLet an edge run from a node back to itself.this.config.allowSelfConnectionenforcePortTypesbooleanRefuse a connection between ports whose declared types do not match. Off by default, which leaves validation to the host.this.config.enforcePortTypesisValidConnectionXuiGraphConnectionValidatorLast say on whether a connection may be made. Runs after the built-in rules.lockedbooleanRead-only canvas: pan and zoom still work, editing does not.falsepanOnDragbooleanDragging empty canvas pans. Turn it off to make dragging a marquee select instead.truezoomActivation'wheel' | 'ctrl-wheel'wheel zooms on a bare wheel, as node editors do. ctrl-wheel reserves the bare wheel for panning and zooms only with the modifier, as design tools do.'wheel'zoomOnScrollbooleanZoom on wheel. Turn it off inside a scrolling page, where the wheel should scroll past the graph rather than into it.trueOutputs
connectXuiGraphConnectionA connection that passed every rule. The host decides whether to add it.connectionDropXuiGraphConnectionDropA wire dropped on empty canvas — the hook for "drag out to create a node".edgeClickXuiGraphEdgeEmits the edge that was clicked.nodeMoveXuiGraphNodeMoveFired once per drag, when the pointer is released.selectionChange{ nodes: string[]; edges: string[] }Emits the selected node and edge ids whenever the selection changes.deleteSelection{ nodes: string[]; edges: string[] }Delete or Backspace over the canvas. The host performs the removal.canvasContextMenu{ event: MouseEvent; position: XuiGraphPoint }Emits a right-click on empty canvas, with the position in graph coordinates — the hook for a "new node here" menu.Methods
fitView(padding?: number): voidFrame every node, leaving padding screen pixels of margin.zoomIn(): voidzoomOut(): voidresetZoom(): voidReturn to 1:1 without moving the centre of the view.