Architecture

Understanding the architecture and design of Vyuh Node Flow

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 extends NodeData> {
  // Core graph data
  final Graph<T> graph;
  final Viewport 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 multiSelect = false});

  // Viewport management
  void panTo(Offset position);
  void zoomTo(double zoom);
  void fitToScreen();

  // 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> {
  final List<Node<T>> nodes;
  final List<Connection> connections;
  final List<Annotation> annotations;
  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;

  // 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;

}

Key Features:

  • Generic data type T for custom node information
  • MobX observables for reactive position, size, z-index, and state
  • Separate position (logical) and visualPosition (for snap-to-grid)
  • Observable lists for dynamic port management
  • Support for shaped nodes via NodeShape

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; // source, target, both
  final Offset offset;
  final bool multiConnections;
  final int? maxConnections;
  final PortShape shape;
  final double size;
  final String? tooltip;
  final bool isConnectable;

  // Computed properties
  bool get isSource;
  bool get isTarget;
}

Key Features:

  • Configurable as source (output), target (input), or bidirectional
  • Custom shapes via PortShape (default: capsule half)
  • Multi-connection support with optional max limit
  • Tooltips and connectable state
  • Position offset for fine-tuned placement

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;

  // Optional customization
  final ConnectionStyle? style;
  final ConnectionEndPoint? startPoint;
  final ConnectionEndPoint? endPoint;

  // 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<ConnectionAnimationEffect?> animationEffect;

}

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 data map
  • Animation effects support

Rendering Pipeline

The editor uses a multi-layer rendering approach:

  1. Grid Layer: Background grid (dots or lines)
  2. Connections Layer: All connections between nodes
  3. Connection Labels Layer: Labels on connections
  4. Nodes Layer: Node widgets via custom builder
  5. 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 Update

Example: Moving a node

  1. User drags a node
  2. Interaction layer detects drag
  3. Calls controller.moveNode()
  4. Controller updates node position (Observable)
  5. MobX triggers reaction
  6. UI re-renders affected nodes

Extensibility Points

The architecture provides several extension points:

Core Customization

  1. Custom Node Data: Extend NodeData for your domain
  2. Node Builders: Provide custom node widgets for building the content as well as the container
  3. Annotations: Add custom overlays and markers
  4. Validators: Add connection validation logic

Theming

  1. Themes: Customize all visual aspects
  2. Connection Styles: Implement custom connection renderers like bezier, smoothstep, step, and straight
  3. Connection Effects: Add effects like flowing dashed lines, pulsing lines, moving arrows, particles, etc.
  4. Port Shapes: Leverage custom shapes to create endpoints and ports
  5. Grid Styles: Create custom grid styles. By default, you get dots, grid, hierarchical-grid, lines, and cross styles
  6. Node Shapes: Use built-in or custom shapes for node containers

Design Principles

  1. Separation of Concerns: Clear boundaries between layers
  2. Reactive: Automatic UI updates via MobX
  3. Type Safety: Generic types for compile-time safety
  4. Extensibility: Multiple extension points
  5. Performance: Optimized for large graphs
  6. Flutter-native: Built with Flutter best practices

Next Steps

On this page