# Node Graph UI with DearImGui — Research ## Overview Three main approaches exist for building a node graph UI with DearImGui: 1. Use a standalone library like **ImNodes** or **imgui-node-editor** 2. Build from scratch using ImGui's draw list API 3. 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 ```cpp 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::BeginGroup` bounding 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 ```cpp 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::Canvas` can 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: 1. **Background layer**: grid dots/lines, selection rectangle 2. **Node bodies**: rounded rectangles (`AddRectFilled`) 3. **Node borders**: rect strokes, optionally thicker on hover/select 4. **Pins**: small circles or squares on left/right edges 5. **Links**: cubic Bézier curves between pin centers 6. **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: 1. Subdivide curve into N segments 2. Find segment closest to mouse cursor 3. Recursively subdivide that segment 4. 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) ```cpp 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