FlowCanvas
The main canvas component for rendering and managing flow graphs. This is the primary container for your node editor.
Overview
FlowCanvas provides the interactive surface where nodes are displayed, connected, and manipulated. It handles panning, zooming, node selection, and all user interactions with the graph.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| Graph | FlowGraph | required | The graph instance to display |
| BackgroundContent | RenderFragment | required | Background/grid content |
| Panels | RenderFragment | null | Overlay UI panels (zoom controls, etc.) |
| Height | string | “100%” | Canvas height (CSS value) |
| Width | string | “100%” | Canvas width (CSS value) |
| Style | string | ”” | Custom inline CSS styles |
| GridStyle | string | ”” | Background grid inline CSS |
| Class | string | “flow-canvas” | CSS class for canvas element |
| SelectionRectangleClass | string | “flow-selection-rectangle” | CSS class for rectangle selection |
| NodeSelectionClass | string | “selected” | CSS class applied to selected nodes |
| ExecutingEdgeClass | string | “edge-executing” | CSS class for edges during execution |
| IsReadOnly | bool | false | Read-only mode (pan/zoom only, no editing) |
| Zoom | double | 1.0 | Initial zoom level |
| MinZoom | double | 0.2 | Minimum zoom level |
| MaxZoom | double | 2.0 | Maximum zoom level |
| PanKey | string | “alt” | Modifier key for panning: “shift”, “ctrl”, “alt”, “meta” |
| AutoUpdateSocketColors | bool | true | Automatically update socket colors based on connections |
| EdgeShouldMatchDataType | bool | true | Validate type compatibility when connecting sockets |
| JsEdgePathFunctionName | string | null | Custom JavaScript function name for edge rendering |
| ScrollSpeed | float | 1 | How fast zoom happens in the canvas viewport |
Properties
ElementRef
Gets the underlying DOM element reference for the canvas container <div>. Useful for custom JavaScript interop.
Type: ElementReference
var canvasEl = canvas.ElementRef;
await JS.InvokeVoidAsync("myFunction", canvasEl);
Methods
SetZoomAsync
Sets the canvas zoom level.
Signature: ValueTask SetZoomAsync(double zoom)
await canvas.SetZoomAsync(1.5); // 150% zoom
SetOffsetAsync
Sets the canvas pan offset.
Signature: ValueTask SetOffsetAsync(double offsetX, double offsetY)
await canvas.SetOffsetAsync(100, 50);
SetReadOnlyAsync
Enables or disables read-only mode.
Signature: ValueTask SetReadOnlyAsync(bool isReadOnly)
await canvas.SetReadOnlyAsync(true); // Lock canvas
SetCanvasModeAsync
Sets the canvas interaction mode.
Signature: ValueTask SetCanvasModeAsync(CanvasMode mode)
// CanvasMode.Select - Click to select nodes, Alt+drag to pan
await canvas.SetCanvasModeAsync(CanvasMode.Select);
// CanvasMode.Pan - Always pan mode
await canvas.SetCanvasModeAsync(CanvasMode.Pan);
GetViewportPropertiesAsync
Gets the current viewport state.
Signature: ValueTask<CanvasProperties> GetViewportPropertiesAsync()
var props = await canvas.GetViewportPropertiesAsync();
Console.WriteLine($"Zoom: {props.Zoom}, Offset: ({props.OffsetX}, {props.OffsetY})");
SetViewportPropertiesAsync
Sets viewport properties (zoom, offset, etc.).
Signature: ValueTask SetViewportPropertiesAsync(CanvasProperties props)
await canvas.SetViewportPropertiesAsync(new CanvasProperties
{
Zoom = 1.0,
OffsetX = 0,
OffsetY = 0,
MinZoom = 0.2,
MaxZoom = 2.0,
IsReadOnly = false
});
ClearAsync
Clears all nodes and edges, resets viewport.
Signature: ValueTask ClearAsync()
await canvas.ClearAsync();
SelectNodesAsync
Selects nodes by their IDs.
Signature: ValueTask SelectNodesAsync(params string[] nodeIds)
await canvas.SelectNodesAsync("node1", "node2", "node3");
SelectAllNodesAsync
Selects all nodes currently in the graph.
Signature: ValueTask SelectAllNodesAsync()
await canvas.SelectAllNodesAsync();
ClearNodeSelectionAsync
Clears the current node selection.
Signature: ValueTask ClearNodeSelectionAsync()
await canvas.ClearNodeSelectionAsync();
GetSelectedNodesAsync
Gets IDs of currently selected nodes.
Signature: ValueTask<string[]> GetSelectedNodesAsync()
var selectedIds = await canvas.GetSelectedNodesAsync();
Refresh
Triggers a component re-render.
Signature: void Refresh()
canvas.Refresh();
ArrangeAsync
Arranges all nodes in a left-to-right layered layout based on the dependency graph. Nodes with no dependencies (roots) are placed in the leftmost column; each subsequent column contains nodes that depend on the previous column.
There are two overloads:
Fixed-spacing overload
Uses constant column width and row height regardless of node size.
Signature: ValueTask ArrangeAsync(double startX = 50, double startY = 50, double horizontalSpacing = 250, double verticalSpacing = 120)
| Parameter | Type | Default | Description |
|---|---|---|---|
| startX | double | 50 | Canvas X coordinate for the first column |
| startY | double | 50 | Canvas Y coordinate for the topmost node |
| horizontalSpacing | double | 250 | Horizontal gap between columns in pixels |
| verticalSpacing | double | 120 | Vertical gap between nodes in a column in pixels |
await canvas.ArrangeAsync();
await canvas.ArrangeAsync(startX: 100, startY: 80, horizontalSpacing: 300, verticalSpacing: 150);
DOM-aware overload
Reads each node’s actual rendered width and height from the DOM so columns are sized to their widest node and rows are sized to their tallest node. gapX and gapY control the whitespace between nodes. Set useDom: false to fall back to fixed spacing.
Signature: ValueTask ArrangeAsync(double x = 50, double y = 50, double gapX = 60, double gapY = 40, bool useDom = true)
| Parameter | Type | Default | Description |
|---|---|---|---|
| x | double | 50 | Canvas X coordinate for the first column |
| y | double | 50 | Canvas Y coordinate for the topmost node |
| gapX | double | 60 | Horizontal whitespace between columns in pixels |
| gapY | double | 40 | Vertical whitespace between nodes in a column in pixels |
| useDom | bool | true | When true, reads rendered node sizes from the DOM |
// DOM-aware layout with default gaps
await canvas.ArrangeAsync(x: 50, y: 50, gapX: 60, gapY: 40, useDom: true);
// Tighter gaps
await canvas.ArrangeAsync(x: 20, y: 20, gapX: 30, gapY: 20, useDom: true);
// Fall back to fixed spacing
await canvas.ArrangeAsync(x: 50, y: 50, gapX: 60, gapY: 40, useDom: false);
Events
OnCanvasLoaded
Fired when the canvas finishes loading.
Type: EventCallback<CanvasLoadedEventArgs>
<FlowCanvas OnCanvasLoaded="HandleLoaded" ...>
@code {
void HandleLoaded(CanvasLoadedEventArgs e)
{
Console.WriteLine($"Canvas loaded with zoom: {e.Zoom}");
}
}
OnPanned
Fired when the canvas is panned.
Type: EventCallback<PanEventArgs>
void HandlePanned(PanEventArgs e)
{
Console.WriteLine($"Panned to: ({e.OffsetX}, {e.OffsetY})");
}
OnZoomed
Fired when the zoom level changes.
Type: EventCallback<ZoomEventArgs>
void HandleZoomed(ZoomEventArgs e)
{
Console.WriteLine($"Zoomed to: {e.Zoom}");
}
OnNodeMoved
Fired when a node is moved.
Type: EventCallback<NodeMovedArgs>
void HandleNodeMoved(NodeMovedArgs e)
{
Console.WriteLine($"Node {e.NodeId} moved to ({e.X}, {e.Y})");
}
OnNodeSelected
Fired when a node is selected.
Type: EventCallback<NodeSelectedEventArgs>
void HandleNodeSelected(NodeSelectedEventArgs e)
{
Console.WriteLine($"Node selected: {e.NodeId}");
}
OnNodeDeselected
Fired when a node is deselected.
Type: EventCallback<NodeDeselectedEventArgs>
void HandleNodeDeselected(NodeDeselectedEventArgs e)
{
Console.WriteLine($"Node deselected: {e.NodeId}");
}
OnSelectionChanged
Fired when the selection changes (multiple nodes).
Type: EventCallback<SelectionChangedEventArgs>
void HandleSelectionChanged(SelectionChangedEventArgs e)
{
Console.WriteLine($"Selected {e.SelectedNodeIds.Length} nodes");
}
OnNotifyNodesCleared
Fired when all nodes are cleared from the canvas.
Type: EventCallback<NodesClearedEventArgs>
void HandleNodesCleared(NodesClearedEventArgs e)
{
Console.WriteLine($"Cleared {e.ClearedCount} nodes");
}
OnEdgeConnectRequest
Fired when an edge connection is requested. Can be handled to customize connection logic.
Type: EventCallback<ConnectRequestArgs>
async Task HandleConnectRequest(ConnectRequestArgs e)
{
// Custom validation
if (ShouldAllowConnection(e.FromSocket, e.ToSocket))
{
// Let it proceed (don't set Handled)
}
else
{
e.Handled = true; // Block the connection
}
}
OnSocketLongPress
Fired when a socket is long-pressed (held for 1 second).
Type: EventCallback<SocketLongPressEventArgs>
void HandleSocketLongPress(SocketLongPressEventArgs e)
{
Console.WriteLine($"Long press on socket: {e.Socket.Name}");
}
OnContextMenu
Fired when the canvas is right-clicked.
Type: EventCallback<CanvasContextMenuEventArgs>
async Task HandleContextMenu(CanvasContextMenuEventArgs e)
{
// Show context menu
await contextMenu.ShowAsync(e.ClientX, e.ClientY, e.X, e.Y);
}
OnNodeAdded
Fired when a node is added to the canvas.
Type: EventCallback<NodeAddedEventArgs>
void HandleNodeAdded(NodeAddedEventArgs e)
{
Console.WriteLine($"Node added: {e.NodeId}");
}
OnEdgeAdded
Fired when an edge is added to the canvas.
Type: EventCallback<EdgeAddedEventArgs>
void HandleEdgeAdded(EdgeAddedEventArgs e)
{
Console.WriteLine($"Edge added: {e.EdgeId}");
}
OnNodeRemoved
Fired when a node is removed from the canvas.
Type: EventCallback<NodeRemovedEventArgs>
void HandleNodeRemoved(NodeRemovedEventArgs e)
{
Console.WriteLine($"Node removed: {e.NodeId}");
}
OnEdgeRemoved
Fired when an edge is removed from the canvas.
Type: EventCallback<EdgeRemovedEventArgs>
void HandleEdgeRemoved(EdgeRemovedEventArgs e)
{
Console.WriteLine($"Edge removed: {e.EdgeId}");
}
OnAllNodesCleared
Fired when all nodes are cleared from the canvas.
Type: EventCallback
void HandleAllNodesCleared()
{
Console.WriteLine("All nodes cleared");
}
Complete Example
@using FlowState.Components
@using FlowState.Models
@using FlowState.Models.Events
<FlowCanvas @ref="canvas"
Graph="graph"
Height="100vh"
Width="100vw"
MinZoom="0.5"
MaxZoom="3.0"
Zoom="1.0"
PanKey="alt"
IsReadOnly="false"
OnCanvasLoaded="OnLoaded"
OnNodeMoved="OnNodeMoved"
OnContextMenu="ShowContextMenu">
<BackgroundContent>
<FlowBackground class="custom-grid"/>
</BackgroundContent>
<Panels>
<FlowPanels>
<button @onclick="ZoomIn">+</button>
<button @onclick="ZoomOut">-</button>
</FlowPanels>
</Panels>
</FlowCanvas>
@code {
FlowCanvas? canvas;
FlowGraph graph = new();
async Task OnLoaded(CanvasLoadedEventArgs e)
{
// Canvas is ready
await graph.CreateNodeAsync<MyNode>(100, 100, []);
}
void OnNodeMoved(NodeMovedArgs e)
{
Console.WriteLine($"Node moved to ({e.X}, {e.Y})");
}
async Task ShowContextMenu(CanvasContextMenuEventArgs e)
{
// Show your context menu
}
async Task ZoomIn()
{
var props = await canvas!.GetViewportPropertiesAsync();
await canvas.SetZoomAsync(Math.Min(props.Zoom + 0.1, props.MaxZoom));
}
async Task ZoomOut()
{
var props = await canvas!.GetViewportPropertiesAsync();
await canvas.SetZoomAsync(Math.Max(props.Zoom - 0.1, props.MinZoom));
}
}
See Also
- FlowBackground - Background customization
- FlowPanels - Overlay UI panels
- FlowGraph - Graph management
- Getting Started - Complete working example