Skip to content

Getting Started

One package, one namespace and one stylesheet - no services to register. Then a BlowCanvas in a container that has a height is a working node editor over your own List<BlowNode> and List<BlowEdge>.

Install

The package runs on .NET 8, 9 and 10, in Blazor WebAssembly, Blazor Server, a Blazor Web App and Blazor Hybrid. In a Web App it belongs in the project that holds the pages with a canvas - the .Client project, for pages rendered with InteractiveWebAssembly.

dotnet add package Bit.Blow

Add the namespace to _Imports.razor. Everything in the library - the components, the model, layout, history and the clipboard - lives in it:

@using Bit.Blow

Link the stylesheet

From the host page - Components/App.razor in a Blazor Web App, wwwroot/index.html in a standalone WebAssembly or a Hybrid app. The link is required: the library does not inject its stylesheet, so leaving it out gives an unstyled canvas rather than an error. Put your own overrides after it.

<link rel="stylesheet" href="_content/Bit.Blow/bit-blow.css" />

On .NET 9 and later, href="@Assets["_content/Bit.Blow/bit-blow.css"]" in App.razor serves the fingerprinted copy. The script module, _content/Bit.Blow/bit-blow.js, needs no tag: the component imports it the first time it needs it.

A first flow

The canvas fills its container (width: 100%; height: 100%), so the container needs a height - or give the canvas one directly with Style="height: 600px". The graph is plain C#: the canvas edits the lists you hand it in place as the user works.

<div style="height: 400px">
    <BlowCanvas @ref="_flow" Nodes="_nodes" Edges="_edges" FitViewOnInit="true">
        <BlowBackground />
        <BlowControls />
    </BlowCanvas>
</div>

@code {
    private BlowCanvas? _flow;

    private List<BlowNode> _nodes =
    [
        new BlowNode { Id = "1", Type = "input", Label = "Order placed", Position = new(0, 0) },
        new BlowNode { Id = "2", Label = "Payment", Position = new(0, 100) },
        new BlowNode { Id = "3", Type = "output", Label = "Shipped", Position = new(0, 200) },
    ];

    private List<BlowEdge> _edges =
    [
        new BlowEdge { Id = "e1-2", Source = "1", Target = "2" },
        new BlowEdge { Id = "e2-3", Source = "2", Target = "3", Animated = true },
    ];
}
Live
Order placed
Payment
Shipped
bit Blow

That is a working editor. Nodes drag; dragging from a handle to another node connects them; clicking selects, Shift+click adds to the selection and Shift+drag on the background draws a selection box; Delete removes the selection; the wheel zooms toward the cursor, dragging the background pans and two fingers pinch. Tab reaches every node and edge, Enter selects, the arrow keys move the selection and Escape clears it.

Reacting to edits

Your state is always the graph on screen. What the canvas raises tells you when something worth knowing has happened:

  • NodesChanged and EdgesChanged once an edit is complete - a drag released, a resize ended, a connection made, something deleted or pasted. Not on every pointer move.
  • OnNodeDrag, OnNodeResize and OnMove while a gesture runs, for live readouts.
  • OnSelectionChange when the selected set actually changes.
  • OnConnect, OnReconnect and OnDelete for the specific edits. A connection adds its own edge; OnConnect is for side effects.

When your own code changes the graph outside a render of your component - a timer, a message from a server - call _flow.Refresh(remeasure: true), or InvokeAsync(StateHasChanged) on your component.

Calling the canvas from code

Capture the canvas with @ref for the imperative API - the counterpart of React Flow's useReactFlow():

_flow.FitView();
await _flow.SetCenterAsync(x: 400, y: 200, zoom: 1.5, durationMs: 300);
BlowXYPosition point = _flow.ScreenToFlowPosition(e.ClientX, e.ClientY);

_flow.AddNodes(node);
await _flow.DeleteElementsAsync(nodes: [node]);
BlowNode? found = _flow.GetNode("2");

BlowSnapshot snapshot = _flow.ToObject();
await _flow.RestoreAsync(snapshot);

Where to go next

An unhandled error has occurred. Reload 🗙