ktsu.ImGui.NodeEditor
3.26.0
Prefix Reserved
dotnet add package ktsu.ImGui.NodeEditor --version 3.26.0
NuGet\Install-Package ktsu.ImGui.NodeEditor -Version 3.26.0
<PackageReference Include="ktsu.ImGui.NodeEditor" Version="3.26.0" />
<PackageVersion Include="ktsu.ImGui.NodeEditor" Version="3.26.0" />
<PackageReference Include="ktsu.ImGui.NodeEditor" />
paket add ktsu.ImGui.NodeEditor --version 3.26.0
#r "nuget: ktsu.ImGui.NodeEditor, 3.26.0"
#:package ktsu.ImGui.NodeEditor@3.26.0
#addin nuget:?package=ktsu.ImGui.NodeEditor&version=3.26.0
#tool nuget:?package=ktsu.ImGui.NodeEditor&version=3.26.0
ktsu.ImGui.NodeEditor
ImGui.NodeEditor is a visual node editor built on ImNodes, with the graph itself kept away from the drawing. NodeEditorEngine owns nodes, links and the physics that lays them out and knows nothing about ImGui; NodeEditorRenderer draws whatever the engine holds; NodeEditorInputHandler turns a frame's interactions into requests the engine can accept or refuse. Nodes can be declared as ordinary types decorated with ktsu.NodeGraph attributes and instantiated by reflection.
Features
- Separation of concerns: business logic (
NodeEditorEngine), rendering (NodeEditorRenderer), and input (NodeEditorInputHandler) are separate objects, so the graph can be built and tested without a renderer - Tuning panel:
PhysicsSettingsPaneldraws every layout setting, grouped and captioned, so a graph can be tuned while it is on screen - Attribute-based nodes:
AttributeBasedNodeFactoryreadsktsu.NodeGraphattributes off a type — or every decorated type in an assembly — and creates nodes with the right pins - Physics is opt-in: the simulation does nothing until
PhysicsSettings.Enabledis set, so a host that positions nodes itself pays nothing for it - Type-aware connections:
TryCreateLinkreturns a result with a message rather than throwing, and pin compatibility comes from the same rules the metadata declares - Physics-based layout: nodes repel, links pull, and the graph settles; powered by
ktsu.ForceDirectedLayout, with per-frame stability and energy readings for debug overlays - Drag-aware: nodes being dragged are excluded from the simulation, and the renderer reports position and size changes back to the engine
Installation
Package Manager Console
Install-Package ktsu.ImGui.NodeEditor
.NET CLI
dotnet add package ktsu.ImGui.NodeEditor
Package Reference
<PackageReference Include="ktsu.ImGui.NodeEditor" Version="x.y.z" />
ImNodes must be initialized before the editor draws. ktsu.ImGui.App detects and sets up the extension automatically; in a host that does not, initialize ImNodes yourself as its bindings document.
Usage Examples
Basic Example
using System.Numerics;
using Hexa.NET.ImGui;
using ktsu.ImGui.NodeEditor;
private readonly NodeEditorEngine engine = new();
private readonly NodeEditorRenderer renderer = new();
private readonly NodeEditorInputHandler input = new();
// Build a graph
Node source = engine.CreateNode(new Vector2(200, 200), "Source", [], ["Value"]);
Node target = engine.CreateNode(new Vector2(500, 200), "Target", ["Source.Value"], []);
engine.TryCreateLink(source.OutputPins[0].Id, target.InputPins[0].Id);
// Draw it, once per frame
void DrawGraph(float deltaTime)
{
renderer.Render(engine, ImGui.GetContentRegionAvail());
// The renderer measures what ImNodes actually laid out; hand that back to the engine
foreach ((int id, Vector2 position) in renderer.GetNodePositionUpdates(engine))
{
engine.UpdateNodePosition(id, position);
}
foreach ((int id, Vector2 dimensions) in renderer.GetNodeDimensionUpdates(engine))
{
engine.UpdateNodeDimensions(id, dimensions);
}
// Apply what the user did this frame
InputEvents events = input.ProcessInput();
foreach (LinkCreationRequest request in events.LinkCreationRequests)
{
engine.TryCreateLink(request.FromPinId, request.ToPinId);
}
foreach (int linkId in events.LinkDeletionRequests)
{
engine.RemoveLink(linkId);
}
engine.SetDraggedNodes(renderer.CurrentlyDraggedNodes);
engine.UpdatePhysics(deltaTime);
}
Nodes from decorated types
using ktsu.NodeGraph;
[Node("Add")]
public class AddNode
{
[InputPin("A")] public double A { get; set; }
[InputPin("B")] public double B { get; set; }
[OutputPin("Sum")] public double Sum { get; private set; }
[NodeExecute]
public void Execute() => Sum = A + B;
}
AttributeBasedNodeFactory factory = new(engine);
factory.RegisterNodeType<AddNode>();
factory.RegisterNodeTypesFromAssembly(typeof(AddNode).Assembly);
Node node = factory.CreateNode<AddNode>(new Vector2(100, 100));
GetAllNodeDefinitions() returns the registered definitions, which is what a "add node" menu is built from: each one carries the display name, category, tags, execution mode, deprecation state and pin list read off the attributes.
Two things to know about registration. A class node also gets an Instance output pin (and input pins for its constructor's parameters), so it can be chained onward. And RegisterNodeTypesFromAssembly skips abstract types — which in IL includes every static class — so a [Node] method parked on a static holder class has to be registered by naming that holder: factory.RegisterNodeType(typeof(MathNodes)).
Tuning the layout
engine.UpdatePhysicsSettings(new PhysicsSettings
{
Enabled = true,
RepulsionStrength = 1_200_000.0,
LinkSpringStrength = 0.5,
RestLinkLength = 225.0,
});
// Readings worth putting behind a debug toggle
float energy = engine.TotalSystemEnergy;
(int substeps, float substepDelta) = engine.LastPhysicsStepInfo;
renderer.RenderDebugOverlays(engine, editorPosition, editorSize, showDebug: true);
API Reference
NodeEditorEngine
The graph and its physics. No ImGui calls.
| Name | Return Type | Description |
|---|---|---|
Nodes |
IReadOnlyList<Node> |
Every node |
Links |
IReadOnlyList<Link> |
Every link |
GravityCenter |
Vector2 |
The point the layout pulls toward |
TotalSystemEnergy |
float |
Current energy, for stability readouts |
LastPhysicsStepInfo |
(int SubstepCount, float SubstepDeltaTime) |
What the last UpdatePhysics actually ran |
CreateNode(Vector2, string, int, int) |
Node |
Creates a node with a number of unnamed pins |
CreateNode(Vector2, string, List<string>, List<string>) |
Node |
Creates a node with named pins |
TryCreateLink(int, int) |
LinkCreationResult |
Attempts a connection; the result carries success, a message, and the link |
RemoveLink(int) / RemoveNode(int) |
bool |
Removes a link or node |
UpdateNodePosition(int, Vector2) / UpdateNodeDimensions(int, Vector2) |
void |
Feeds measured layout back in |
SetDraggedNodes(IReadOnlySet<int>) |
void |
Excludes dragged nodes from the simulation |
UpdatePhysicsSettings(PhysicsSettings) |
void |
Replaces the physics settings |
UpdatePhysics(float) |
void |
Advances the layout by a frame delta |
NodeEditorRenderer
| Name | Return Type | Description |
|---|---|---|
Render(NodeEditorEngine, Vector2) |
void |
Draws every node and link through ImNodes |
GetNodePositionUpdates(NodeEditorEngine) |
Dictionary<int, Vector2> |
Positions ImNodes moved since the last frame |
GetNodeDimensionUpdates(NodeEditorEngine) |
Dictionary<int, Vector2> |
Sizes ImNodes measured |
RenderDebugOverlays(...) |
void |
Force and stability overlays |
CurrentlyDraggedNodes |
IReadOnlySet<int> |
Nodes the user is dragging this frame |
PhysicsSettingsPanel
| Name | Return Type | Description |
|---|---|---|
Draw(ref PhysicsSettings) |
bool |
Draws every tunable the simulation has, grouped by force and captioned; true when the user changed one |
DrawDiagnostics(NodeEditorEngine) |
void |
Energy, whether it has settled, substep count and rate |
The forces interact, so none can be judged alone: raising repulsion changes what the spring's rest
length means, and levelling links only works in the room repulsion made. The panel therefore exposes
the whole of PhysicsSettings rather than a chosen subset — a setting that is not on it is one
nobody can reach without recompiling. Every control marks itself with ktsu.ImGui.Probes, so a UI
test can address it by the label the user sees.
PhysicsSettings settings = engine.PhysicsSettings;
if (PhysicsSettingsPanel.Draw(ref settings))
{
engine.UpdatePhysicsSettings(settings);
}
PhysicsSettingsPanel.DrawDiagnostics(engine);
NodeEditorInputHandler
ProcessInput() returns InputEvents, holding LinkCreationRequests (LinkCreationRequest(FromPinId, ToPinId)) and LinkDeletionRequests.
AttributeBasedNodeFactory
| Name | Return Type | Description |
|---|---|---|
RegisterNodeType<T>() / RegisterNodeType(Type) |
void |
Registers a decorated type |
RegisterNodeTypesFromAssembly(Assembly) |
void |
Registers every decorated type in an assembly |
CreateNode<T>(Vector2) / CreateNode(Type, Vector2) |
Node |
Creates a node from a registered type |
CreateMethodNode(MethodInfo, Vector2) |
Node |
Creates a node from a decorated method |
GetNodeDefinition(Type) / GetNodeDefinition(MethodInfo) |
NodeDefinition? |
The metadata read off a registration |
GetAllNodeDefinitions() |
IEnumerable<NodeDefinition> |
Every registration, for building menus |
Domain models
Node(Id, Position, Name, InputPins, OutputPins, Dimensions, Velocity, Force, IsPinned), Link(Id, OutputPinId, InputPinId) and Pin(Id, Direction, Name, DisplayName) are records; PinDirection is Input or Output.
Acknowledgments
- Dear ImGui - The immediate mode GUI library the editor draws into
- Hexa.NET.ImGui - The .NET bindings for Dear ImGui, and for
Hexa.NET.ImNodes, the node editor extension this renders through - ktsu.Semantics -
ktsu.Semantics.Quantitiesfor typed quantities in the physics settings
ktsu.NodeGraph supplies the node metadata and ktsu.ForceDirectedLayout the physics; both ship from this repository.
Contributing
Contributions are welcome! For feature requests, bug reports, or questions, please open an issue on the GitHub repository. If you would like to contribute code, please open a pull request with your changes.
License
ImGui.NodeEditor is licensed under the MIT License. See LICENSE.md for more information.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Hexa.NET.ImGui (>= 2.2.9)
- Hexa.NET.ImNodes (>= 2.2.9)
- ktsu.ForceDirectedLayout (>= 3.26.0)
- ktsu.ImGui.Probes (>= 3.26.0)
- ktsu.NodeGraph (>= 1.0.0)
- ktsu.Semantics.Quantities (>= 3.2.6)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on ktsu.ImGui.NodeEditor:
| Package | Downloads |
|---|---|
|
ktsu.Coder.Graph
A flexible and extensible .NET library for representing code as language-agnostic Abstract Syntax Trees, serializing them to human-readable YAML for round-trip storage and version control, and generating source in C#, Python, JavaScript and C++. Provides strongly-typed AST nodes for classes, functions, parameters, statements, expressions and typed literals, with a plugin-based architecture for adding target languages, full deep-cloning support and custom metadata on any node. Ships an ImGui node-graph editor over the same AST, with a force-directed layout, an inspector for every node's properties, undo/redo, and a desktop application built on it. |
GitHub repositories
This package is not used by any popular GitHub repositories.
## v3.26.0 (minor)
Changes since v3.25.0:
- Use Ensure.NotNull for the panel's argument guards ([@Claude](https://github.com/Claude))
- [minor] Put the whole layout tuning surface on a reusable panel ([@Claude](https://github.com/Claude))