NodeFlowController

API reference for the NodeFlowController class

NodeFlowController

The NodeFlowController manages all graph state including nodes, connections, selection, and viewport. It's the central point for programmatic graph manipulation.

Constructor

NodeFlowController<T>({
  GraphViewport? initialViewport,
  NodeFlowConfig? config,
})
ParameterTypeDescription
initialViewportGraphViewport?Initial viewport position and zoom
configNodeFlowConfig?Behavioral configuration (snap-to-grid, zoom limits, etc.)

Node Operations

addNode

Add a node to the graph.

void addNode(Node<T> node)

Example:

final node = Node<MyData>(
  id: 'node-1',
  position: Offset(100, 100),
  size: Size(150, 80),
  data: MyData(label: 'Process'),
  inputPorts: [Port(id: 'in-1', name: 'Input')],
  outputPorts: [Port(id: 'out-1', name: 'Output')],
);
controller.addNode(node);

removeNode

Remove a node and all its connections.

void removeNode(String nodeId)

Removing a node automatically removes all connections to and from that node, and removes the node from any group annotations.

moveNode

Move a node by a delta offset.

void moveNode(String nodeId, Offset delta)

moveSelectedNodes

Move all selected nodes by a delta offset.

void moveSelectedNodes(Offset delta)

setNodeSize

Update a node's size.

void setNodeSize(String nodeId, Size size)

setNodePorts

Replace a node's ports.

void setNodePorts(String nodeId, {
  List<Port>? inputPorts,
  List<Port>? outputPorts,
})

addInputPort / addOutputPort

Add ports to an existing node.

void addInputPort(String nodeId, Port port)
void addOutputPort(String nodeId, Port port)

removePort

Remove a port and all its connections.

void removePort(String nodeId, String portId)

getNode

Get a node by ID.

Node<T>? getNode(String nodeId)

Connection Operations

addConnection

Create a connection between ports.

void addConnection(Connection connection)

Example:

final connection = Connection(
  id: 'conn-1',
  sourceNodeId: 'node-1',
  sourcePortId: 'out-1',
  targetNodeId: 'node-2',
  targetPortId: 'in-1',
);
controller.addConnection(connection);

removeConnection

Remove a connection by ID.

void removeConnection(String connectionId)

getConnectionsForNode

Get all connections for a node.

List<Connection> getConnectionsForNode(String nodeId)

connections

Access all connections (read-only list).

List<Connection> get connections

Control Points

Connections support user-defined control points for custom routing.

addControlPoint

void addControlPoint(String connectionId, Offset position, {int? index})

updateControlPoint

void updateControlPoint(String connectionId, int index, Offset position)

removeControlPoint

void removeControlPoint(String connectionId, int index)

clearControlPoints

void clearControlPoints(String connectionId)

Selection

selectNode

Select a node. Use toggle: true for multi-select behavior.

void selectNode(String nodeId, {bool toggle = false})

selectNodes

Select multiple nodes.

void selectNodes(List<String> nodeIds, {bool toggle = false})

selectConnection

Select a connection.

void selectConnection(String connectionId, {bool toggle = false})

clearSelection

Clear all selections (nodes, connections, annotations).

void clearSelection()

clearNodeSelection / clearConnectionSelection

Clear specific selection types.

void clearNodeSelection()
void clearConnectionSelection()

selectedNodeIds

Get IDs of selected nodes.

Set<String> get selectedNodeIds

hasSelection

Check if anything is selected.

bool get hasSelection

isNodeSelected

Check if a specific node is selected.

bool isNodeSelected(String nodeId)

Viewport

viewport

Current viewport state.

GraphViewport get viewport

Returns GraphViewport with:

  • x, y - Pan offset
  • zoom - Zoom level

setViewport

Set viewport directly.

void setViewport(GraphViewport viewport)

panBy

Pan viewport by a delta.

void panBy(Offset delta)

zoomBy

Zoom by a delta amount (positive = zoom in).

void zoomBy(double delta)

zoomTo

Set zoom to a specific level.

void zoomTo(double zoom)

fitToView

Fit all content in the viewport.

void fitToView()

centerOnNode

Center viewport on a specific node.

void centerOnNode(String nodeId)

Graph Operations

loadGraph

Load a complete graph (nodes, connections, annotations, viewport).

void loadGraph(NodeGraph<T> graph)

exportGraph

Export current graph state.

NodeGraph<T> exportGraph()

Example:

// Save
final graph = controller.exportGraph();
final json = graph.toJson((data) => data.toJson());
await saveToFile(jsonEncode(json));

// Load
final json = jsonDecode(await loadFromFile());
final graph = NodeGraph.fromJson(json, (map) => MyData.fromJson(map));
controller.loadGraph(graph);

clearGraph

Remove all nodes, connections, and annotations.

void clearGraph()

Alignment & Distribution

alignNodes

Align nodes to a specific edge or center.

void alignNodes(List<String> nodeIds, NodeAlignment alignment)
AlignmentDescription
NodeAlignment.leftAlign to left edge
NodeAlignment.rightAlign to right edge
NodeAlignment.topAlign to top edge
NodeAlignment.bottomAlign to bottom edge
NodeAlignment.horizontalCenterCenter horizontally
NodeAlignment.verticalCenterCenter vertically

distributeNodesHorizontally / distributeNodesVertically

Distribute nodes evenly.

void distributeNodesHorizontally(List<String> nodeIds)
void distributeNodesVertically(List<String> nodeIds)

Annotations

addAnnotation / removeAnnotation

void addAnnotation(Annotation annotation)
void removeAnnotation(String annotationId)

getAnnotation

Annotation? getAnnotation(String annotationId)

selectAnnotation / clearAnnotationSelection

void selectAnnotation(String annotationId, {bool toggle = false})
void clearAnnotationSelection()

Factory Methods

Convenience methods for creating common annotation types:

StickyAnnotation createStickyNote({
  required Offset position,
  required String text,
  String? id,
  double width = 200.0,
  double height = 100.0,
  Color? color,
})

GroupAnnotation createGroupAnnotation({
  required List<String> nodeIds,
  String? id,
  String? label,
  Color? color,
})

MarkerAnnotation createMarker({
  required Offset position,
  required String label,
  String? id,
  Color? color,
})

Lifecycle

dispose

Dispose the controller and release resources.

void dispose()

Always call dispose() when the controller is no longer needed to prevent memory leaks.

Complete Example

class WorkflowEditor extends StatefulWidget {
  @override
  State<WorkflowEditor> createState() => _WorkflowEditorState();
}

class _WorkflowEditorState extends State<WorkflowEditor> {
  late final NodeFlowController<WorkflowData> controller;

  @override
  void initState() {
    super.initState();
    controller = NodeFlowController<WorkflowData>();
    _setupGraph();
  }

  void _setupGraph() {
    controller.addNode(Node(
      id: 'start',
      position: Offset(100, 100),
      size: Size(120, 60),
      data: WorkflowData(label: 'Start', type: 'trigger'),
      outputPorts: [Port(id: 'start-out', name: 'Next')],
    ));

    controller.addNode(Node(
      id: 'process',
      position: Offset(300, 100),
      size: Size(120, 60),
      data: WorkflowData(label: 'Process', type: 'action'),
      inputPorts: [Port(id: 'process-in', name: 'Input')],
      outputPorts: [Port(id: 'process-out', name: 'Output')],
    ));

    controller.addConnection(Connection(
      id: 'conn-1',
      sourceNodeId: 'start',
      sourcePortId: 'start-out',
      targetNodeId: 'process',
      targetPortId: 'process-in',
    ));

    WidgetsBinding.instance.addPostFrameCallback((_) {
      controller.fitToView();
    });
  }

  void _addNode() {
    final id = 'node-${DateTime.now().millisecondsSinceEpoch}';
    controller.addNode(Node(
      id: id,
      position: Offset(200, 200),
      size: Size(120, 60),
      data: WorkflowData(label: 'New Node', type: 'action'),
      inputPorts: [Port(id: '$id-in', name: 'Input')],
      outputPorts: [Port(id: '$id-out', name: 'Output')],
    ));
    controller.selectNode(id);
  }

  void _deleteSelected() {
    for (final nodeId in controller.selectedNodeIds.toList()) {
      controller.removeNode(nodeId);
    }
  }

  void _saveGraph() async {
    final graph = controller.exportGraph();
    final json = graph.toJson((data) => data.toJson());
    // Save to file or API
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Workflow Editor'),
        actions: [
          IconButton(icon: Icon(Icons.add), onPressed: _addNode),
          IconButton(icon: Icon(Icons.delete), onPressed: _deleteSelected),
          IconButton(icon: Icon(Icons.fit_screen), onPressed: controller.fitToView),
          IconButton(icon: Icon(Icons.save), onPressed: _saveGraph),
        ],
      ),
      body: NodeFlowEditor<WorkflowData>(
        controller: controller,
        theme: NodeFlowTheme.light,
        nodeBuilder: (context, node) => Center(child: Text(node.data.label)),
      ),
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}

On this page