Vyuh Node Flow is built with a layered architecture that separates concerns and provides a flexible, extensible foundation for building node-based editors.
High-Level Overview
The NodeFlowEditor is the core widget that is used to manipulate the entire node graph. Its companion object is the NodeFlowController, providing the API and infrastructure for manipulating the node graph.
Core Components
1. NodeFlowEditor
The main widget that renders the interactive flow editor.
- Manages the visual rendering of the graph
- Handles user interactions (panning, zooming, selecting)
- Delegates to specialized layers for rendering different aspects
- Provides callbacks for user actions
2. NodeFlowController
The controller manages the graph state and provides APIs for manipulation:
class NodeFlowController<T, C> {
// Core graph data
final Map<String, Node<T>> nodes;
final List<Connection> connections;
final GraphViewport viewport;
// APIs
void addNode(Node<T> node);
void removeNode(String nodeId);
void addConnection(Connection connection);
void removeConnection(String connectionId);
// Selection
Set<String> get selectedNodeIds;
void selectNode(String nodeId, {bool toggle = false});
// Viewport management
void setViewport(GraphViewport viewport);
void fitToView();
void centerOn(GraphOffset position);
// And many more...
}Key responsibilities:
- State Management: Uses MobX observables for reactive updates
- Graph Operations: CRUD operations on nodes and connections
- Selection Management: Multi-select support
- Keyboard Shortcuts: Extensible keyboard shortcuts
- Viewport Control: Panning and zooming
3. NodeGraph
An immutable data class for graph serialization and deserialization. All state management is handled by the NodeFlowController:
class NodeGraph<T, dynamic> {
/// All nodes including GroupNode and CommentNode
final List<Node<T>> nodes;
final List<Connection> connections;
final GraphViewport viewport;
final Map<String, dynamic> metadata;
}4. Node
Represents a single node in the graph with reactive state management:
class Node<T> {
final String id;
final String type;
final T data;
final NodeRenderLayer layer; // background, middle, foreground
final bool locked; // Prevents dragging via UI
final bool selectable; // Participates in marquee selection
// Observable properties
final Observable<Offset> position;
final Observable<Offset> visualPosition;
final Observable<Size> size;
final Observable<int> zIndex;
final Observable<bool> selected;
final Observable<bool> dragging;
// Port lists
final ObservableList<Port> inputPorts;
final ObservableList<Port> outputPorts;
// Computed properties
bool get isVisible;
bool get isEditing;
bool get isResizable; // true for nodes with ResizableMixin
}Key Features:
- Generic data type
Tfor custom node information - MobX observables for reactive position, size, z-index, and state
- Separate
position(logical) andvisualPosition(for snap-to-grid) - Observable lists for dynamic port management
- Support for shaped nodes via
NodeShape lockedflag to prevent accidental dragginglayerfor rendering order (GroupNode=background, regular=middle, CommentNode=foreground)selectablecontrols participation in marquee selection
5. Port
Connection points on nodes with extensive customization:
class Port {
final String id;
final String name;
final PortPosition position; // left, right, top, bottom
final PortType type; // input or output (inferred from position if not specified)
final Offset offset;
final bool multiConnections;
final int? maxConnections;
final MarkerShape? shape; // null = use theme default
final Size? size; // null = use theme default
final String? tooltip;
final bool isConnectable;
final bool showLabel;
// Observable state
final Observable<bool> highlighted; // Set during connection drag
}Key Features:
- Configurable as input or output port type (auto-inferred from position)
- Custom shapes via
MarkerShape(default: capsule half from theme) - Multi-connection support with optional max limit
- Tooltips and connectable state
- Position offset specifies where the CENTER of the port should be
- Observable highlight state for connection drag feedback
6. Connection
Links between ports with reactive styling and labels:
class Connection {
final String id;
final String sourceNodeId;
final String sourcePortId;
final String targetNodeId;
final String targetPortId;
final Map<String, dynamic>? data;
final bool locked; // Prevents deletion via UI
// Optional customization
final ConnectionStyle? style;
final ConnectionEndPoint? startPoint;
final ConnectionEndPoint? endPoint;
final double? startGap;
final double? endGap;
// Observable properties
Observable<bool> animated;
Observable<bool> selected;
Observable<ConnectionLabel?> startLabel; // anchor 0.0
Observable<ConnectionLabel?> label; // anchor 0.5
Observable<ConnectionLabel?> endLabel; // anchor 1.0
Observable<ConnectionEffect?> animationEffect;
ObservableList<Offset> controlPoints; // For editable path connections
}Key Features:
- Three label positions: start, center (0.5 anchor), and end
- Observable labels and animation state for reactive updates
- Custom styles and endpoint markers
- Arbitrary data attachment via
datamap - Animation effects support (FlowingDash, Particle, GradientFlow, Pulse)
lockedflag to prevent accidental deletion- Control points for user-editable connection paths
Rendering Pipeline
The editor uses a multi-layer rendering approach:
- Grid Layer: Background grid (dots or lines)
- Connections Layer: All connections between nodes
- Connection Labels Layer: Labels on connections
- Nodes Layer: Node widgets via custom builder
- Interaction Layer: Selection rectangles, temporary connections
Each layer is optimized independently for performance.
State Management
Vyuh Node Flow uses MobX for reactive state management:
- All mutable state is wrapped in
Observable - UI automatically updates when observables change
- No need for manual state synchronization
- Efficient change detection
Example:
final node = Node<MyData>(
position: Offset(100, 100), // Wrapped in Observable internally
);
// Moving a node triggers automatic UI update
node.position.value = Offset(200, 200);Event Flow
User interactions flow through the system like this:
User Action → Interaction Layer → Controller → Graph Model → MobX Reactions → UI UpdateExample: Moving a node
- User drags a node
- Interaction layer detects drag
- Calls
controller.moveNode() - Controller updates node position (Observable)
- MobX triggers reaction
- UI re-renders affected nodes
Extensibility Points
The architecture provides several extension points:
Core Customization
- Custom Node Data: Use any type for your node's
datafield - Node Builders: Provide custom node widgets for building the content as well as the container
- Special Nodes: Use
GroupNodefor visual grouping andCommentNodefor annotations - Validators: Add connection validation logic
Theming
- Themes: Customize all visual aspects
- Connection Styles: Implement custom connection renderers like bezier, smoothstep, step, and straight
- Connection Effects: Add effects like flowing dashed lines, pulsing lines, moving arrows, particles, etc.
- Port Shapes: Leverage custom shapes to create endpoints and ports
- Grid Styles: Create custom grid styles. By default, you get dots, grid, hierarchical-grid, lines, and cross styles
- Node Shapes: Use built-in or custom shapes for node containers
🖼️ Customization Points Overview Grid layout showing customization
examples: (1) Connection Styles - bezier, step, smoothstep, straight paths; (2) Connection Effects - flowing dashes, particles, gradients, pulse; (3) Port Shapes - circle, square, diamond, triangle, capsule; (4) Grid Styles - dots, lines, cross, hierarchical; (5) Node Shapes - rectangle, circle, diamond, hexagon with ports. :::
Design Principles
- Separation of Concerns: Clear boundaries between layers
- Reactive: Automatic UI updates via MobX
- Type Safety: Generic types for compile-time safety
- Extensibility: Multiple extension points
- Performance: Optimized for large graphs
- Flutter-native: Built with Flutter best practices
Next Steps
- Learn about Nodes
- Explore Ports
- Understand Connections
- Deep dive into the Controller