Node Graph

Topology graph with force-directed auto-layout and selectable nodes.

Overview

The NodeGraph control visualizes a topology or network: each GraphNode is drawn as a small labeled box and each GraphEdge as a braille line connecting the boxes it references by Id. Positions are computed automatically with a deterministic force-directed layout (Fruchterman–Reingold) — you supply only nodes and edges, never coordinates, and the same data always renders the same picture. Nodes are set as content; edges via the NodeGraph.GraphEdges property element.

<NodeGraph Title="Service topology">
    <GraphNode Id="lb" Name="load balancer" />
    <GraphNode Id="web1" Name="web-1" />
    <GraphNode Id="web2" Name="web-2" />
    <GraphNode Id="api" Name="api" />
    <GraphNode Id="db" Name="postgres" />
    <GraphNode Id="cache" Name="redis" />
    <NodeGraph.GraphEdges>
        <GraphEdge From="lb" To="web1" />
        <GraphEdge From="lb" To="web2" />
        <GraphEdge From="web1" To="api" />
        <GraphEdge From="web2" To="api" />
        <GraphEdge From="api" To="db" />
        <GraphEdge From="api" To="cache" />
    </NodeGraph.GraphEdges>
</NodeGraph>

Properties

PropertyTypeDescription
GraphNodesIList<GraphNode>Inline node collection (content property).
GraphEdgesIList<GraphEdge>Inline edge collection, set via the NodeGraph.GraphEdges property element.
GraphNodesSourceIEnumerableOptional bound node collection; overrides GraphNodes when set.
GraphEdgesSourceIEnumerableOptional bound edge collection; overrides GraphEdges when set.
SelectedIndexintIndex of the selected node (-1 = none). Two-way bindable.
SelectedNodeobjectThe selected node, kept in sync with SelectedIndex. Two-way bindable.
LayoutIterationsintForce-simulation budget, clamped to 1–200 (default 60). The layout stops earlier once it converges.
ShowEdgeArrowsboolDraw a direction marker (▶ ◀ ▲ ▼) at each edge’s target end, in the edge’s own color. Default True.
ZoomdoubleMagnification of the fitted layout, clamped to 1–10 (default 1 = fit-all). Two-way bindable.
PanXdoubleHorizontal offset of the viewport centre from the graph centre, in whole-graph widths (default 0). Clamped to ±(0.5 − 0.5/Zoom). Two-way bindable.
PanYdoubleVertical pan; same units and clamp as PanX.
TitlestringOptional heading drawn along the top edge.

GraphNode fields: Id, Name (the box label; also the text content), Value, Color. GraphEdge fields: From, To (matching a node Id — unknown ids are ignored) and Color (defaults to the axis color). GraphNode.Tag carries an arbitrary payload so a selection leads straight back to the domain object. ShowAxes, ShowGrid and ShowLegend have no effect on this chart.

Data Binding

Bind GraphNodesSource and GraphEdgesSource to ObservableCollections on your ViewModel — adding or removing items re-renders and re-layouts automatically. Data classes and ViewModels must be public or internal so the AOT source generator can bind them.

public sealed class TopologyViewModel : ViewModelBase
{
    public ObservableCollection<GraphNode> Nodes { get; } =
    [
        new GraphNode { Id = "api", Name = "api" },
        new GraphNode { Id = "db", Name = "postgres" },
    ];

    public ObservableCollection<GraphEdge> Edges { get; } =
    [
        new GraphEdge { From = "api", To = "db" },
    ];
}
<NodeGraph GraphNodesSource="{Binding Nodes}"
           GraphEdgesSource="{Binding Edges}" />

Interactivity

The graph is focusable. Give it focus (Tab, or click) and use the arrow keys to move the selection through the nodes in declaration order — Home/End jump to the first and last node — or click any box. The selected box is highlighted (brighter while focused) and SelectedIndex/SelectedNode update two-way, ready to drive a details pane.

<NodeGraph GraphNodesSource="{Binding Nodes}"
           SelectedNode="{Binding Selected, Mode=TwoWay}" />

Zoom & Pan

By default the whole graph is fitted to the control, which turns a large topology into a wall of touching boxes. Zoom magnifies the layout and PanX/PanY choose which part of it the control shows, so a big graph can be explored in place instead of being pre-filtered by the application. A terminal cannot draw a bigger glyph, so zoom does not enlarge the boxes — it spreads the node positions apart and shows fewer of them, which is what makes a crowded graph readable.

Zoom is anchored on the viewport centre: the pan is stored as that centre, so changing the zoom is a pure change of scale and whatever the user is looking at stays put. Pan is clamped so the viewport can never leave the graph, and at Zoom="1" that clamp collapses to zero — fit-all renders exactly as it did before zoom existed, no matter what is assigned to the pan properties.

<NodeGraph GraphNodesSource="{Binding Nodes}"
           Zoom="{Binding Zoom, Mode=TwoWay}"
           PanX="{Binding PanX, Mode=TwoWay}"
           PanY="{Binding PanY, Mode=TwoWay}" />

ResetView() puts all three back to fit-all from code.

Keyboard Shortcuts

KeyAction
↑ / ←Select the previous node.
↓ / →Select the next node.
Home / EndSelect the first / last node.
Left clickSelect the clicked node box.
+ / =Zoom in one step (×1.25), about the viewport centre.
- / _Zoom out one step, down to fit-all.
0Reset the view to fit-all.
Shift+↑ ↓ ← →Pan by a tenth of the viewport. The bare arrows keep moving the selection.
Mouse wheelZoom in / out, about the viewport centre.

Key Concepts