7.9 KiB
Node Graph UI with DearImGui — Research
Overview
Three main approaches exist for building a node graph UI with DearImGui:
- Use a standalone library like ImNodes or imgui-node-editor
- Build from scratch using ImGui's draw list API
- Use a hybrid — custom canvas with ImGui widgets
1. Existing Libraries
1.1 imnodes (Nelarius)
- Stars: ~2.4k | License: MIT | Status: Active (last push 2024)
- Files:
imnodes.h,imnodes_internal.h,imnodes.cpp— drop-in, no deps beyond ImGui - API style: Immediate-mode, mirrors ImGui idioms
imnodes::BeginNodeEditor();
imnodes::BeginNode(node_id);
imnodes::BeginNodeTitleBar();
ImGui::Text("Node Name");
imnodes::EndNodeTitleBar();
imnodes::BeginInputAttribute(pin_id);
ImGui::Text("input");
imnodes::EndAttribute();
imnodes::BeginOutputAttribute(pin_id);
ImGui::Text("output");
imnodes::EndAttribute();
imnodes::EndNode();
imnodes::EndNodeEditor();
Strengths:
- Minimal, dependency-free, easy to vendor
- True immediate-mode — user owns all state
- Pins auto-align with embedded ImGui widgets
- Simple
ImNodes::Link(id, from, to)API
Weaknesses:
- Less feature-rich (no built-in minimap, no grouping, limited theming)
- No built-in serialization of layout
- Slower development velocity
Under the hood:
- Uses
ImDrawList::ChannelsSplit()to layer node backgrounds behind UI - Pins are detected via
ImGui::BeginGroupbounding box capture - Link picking uses hierarchical bezier subdivision
1.2 imgui-node-editor (thedmd / Michal Cichon)
- Stars: ~4.4k | License: MIT | Status: Active
- Files:
imgui_node_editor.h/.cpp+imgui_canvas.h/.cpp— also drop-in - API style: retained-state editor context, user draws content
ax::NodeEditor::Begin("Editor");
ax::NodeEditor::BeginNode(node_id);
ax::NodeEditor::BeginPin(pin_id, ax::NodeEditor::PinKind::Input);
ImGui::Text("input");
ax::NodeEditor::EndPin();
ax::NodeEditor::EndNode();
ax::NodeEditor::End();
Strengths:
- Rich feature set: zoom/pan, minimap, selection, context menus, copy/paste
- Blueprint-UE4-inspired default theme
- Bézier curve links with flow animation
- Configurable zoom levels, drag/navigate/select button mapping
ImGuiEx::Canvascan be used independently for custom infinite-workspace UIs- Built-in serialization callbacks (
SaveSettings/LoadSettings)
Weaknesses:
- Heavier than imnodes — more code, larger API surface
- Editor context is a retained object (less "pure" immediate mode)
- Can conflict with ImGui's own ID stack during complex widget embedding
Key API patterns:
| Concern | API |
|---|---|
| Create link | BeginCreate() / QueryNewLink() / AcceptNewItem() / EndCreate() |
| Delete | BeginDelete() / QueryDeletedLink() / AcceptDeletedItem() / EndDelete() |
| Suspend for popups | Suspend() / Resume() — pops out of canvas coordinate space |
| Styling | PushStyleColor() / PushStyleVar() — 20+ style variables |
1.3 ImNodeFlow (Fattorino)
- Stars: Newer (2024–2025) | License: MIT
- Even more feature-packed: node categories, commenting, layout algorithms
- Still maturing; less battle-tested than the two above
Recommendation for Prism: start with imgui-node-editor if we want a polished editor quickly, or imnodes if we want minimal deps and full state control. The custom approach (next section) is best if we have very specific rendering needs.
2. Custom Node Graph from Scratch
Building a node graph manually using ImDrawList gives maximum control but requires handling:
2.1 Canvas / Coordinate System
An infinite-zoom canvas requires:
- An offset (
ImVec2) and scale (float) transform - Conversion functions between screen ↔ canvas space
- Clipping to the parent ImGui window
The imgui-node-editor library includes an ImGuiEx::Canvas utility that handles this standalone — it can be extracted and reused.
2.2 Rendering Nodes (DrawList)
Nodes are typically rendered in layers:
- Background layer: grid dots/lines, selection rectangle
- Node bodies: rounded rectangles (
AddRectFilled) - Node borders: rect strokes, optionally thicker on hover/select
- Pins: small circles or squares on left/right edges
- Links: cubic Bézier curves between pin centers
- UI overlay: selection handles, context menus
Use ImDrawList::ChannelsSplit() for correct z-ordering when mixing drawn shapes with ImGui widgets.
2.3 Interaction Handling
| Interaction | Implementation |
|---|---|
| Pan | Track middle-mouse drag → modify canvas offset |
| Zoom | Mouse wheel → modify scale (clamp to range, center on cursor) |
| Drag node | Hit-test node bodies (invis buttons or rect test), track delta → update node position |
| Select | Rectangular marquee — track shift+drag → compute selection rect → test intersection with node rects |
| Create link | Detect drag from pin, draw preview bezier, test against other pins on release |
| Delete | Keyboard shortcut, query selection, or context menu |
2.4 Link-Picking (Bezier Hit Test)
Cubic Bézier curves require a hierarchical hit test:
- Subdivide curve into N segments
- Find segment closest to mouse cursor
- Recursively subdivide that segment
- Return hit if distance < threshold
3. Architecture Patterns
3.1 Data Model vs. View Separation
From Guillaume Boissé's RogueEngine post:
- Define a data model independent of UI:
PrNode,PrGraph,PrPin - The data model is used both by the runtime (graph evaluation) and the editor (UI rendering)
- This enables easy serialization, undo/redo, and multi-context editing
3.2 Immediate-Mode Node Rendering Loop
For each node in graph:
BeginNode(node.id)
Render node title bar (colored rect + text)
For each input pin:
BeginInputPin(pin.id)
Render ImGui widget (e.g. DragFloat, ColorEdit)
EndInputPin()
For each output pin:
BeginOutputPin(pin.id)
Render ImGui widget
EndOutputPin()
EndNode()
For each link in graph:
DrawBezierLink(from_pos, to_pos, color, thickness)
Positions are stored per-node in user state and updated on drag.
3.3 DrawList Channels (Z-Order)
draw_list->ChannelsSplit(3);
draw_list->ChannelsSetCurrent(0); // Background: grid, selection rect
// ... draw nodes, pins, links
draw_list->ChannelsSetCurrent(1); // UI: ImGui widgets inside nodes
// ... BeginNode/EndNode calls
draw_list->ChannelsSetCurrent(2); // Foreground: tooltips, drag previews
draw_list->ChannelsMerge();
3.4 Undo/Redo
Simplest viable approach (from RogueEngine / @voxagonlabs): serialize the entire project state on every change. Store snapshots in an undo stack. Works well for small-to-medium projects (node graphs are typically small data).
4. Summary Comparison
| Criteria | imnodes | imgui-node-editor | Custom |
|---|---|---|---|
| Integration effort | Copy 3 files | Copy 6-8 files | Full implementation |
| Feature depth | Basic | Rich (minimap, flow, groups, copy/paste) | Whatever you build |
| Immediate mode | Yes | Partial (retained context) | Yes |
| Performance | High | High | Depends on impl |
| Styling control | Minimal | Extensive | Full control |
| Serialization | None | Built-in callbacks | Build your own |
| Community / maturity | Mature, stable | Mature, active | N/A |
5. References
- imnodes: https://github.com/Nelarius/imnodes
- imgui-node-editor: https://github.com/thedmd/imgui-node-editor
- ImNodeFlow: https://github.com/Fattorino/ImNodeFlow
- Blog post — Visual node graph with ImGui: https://gboisse.github.io/posts/node-graph/
- Blog post — Writing imnodes: https://nelari.us/post/imnodes
- ImGui issue #306 (node editor discussion): https://github.com/ocornut/imgui/issues/306
- ImGui useful extensions wiki: https://github.com/ocornut/imgui/wiki/Useful-Extensions
- Voxagon undo/redo: https://blog.voxagon.se/2018/07/10/undo-for-lazy-programmers.html