### Start Node Widget Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/components/node-widget.md A custom widget for rendering a 'start' node. It features a green theme, a play icon, and 'START' text. ```dart class StartNodeWidget extends StatelessWidget { final Node node; const StartNodeWidget({required this.node}); @override Widget build(BuildContext context) { final size = node.size.value; return Container( width: size.width, height: size.height, decoration: BoxDecoration( color: Colors.green[50], borderRadius: BorderRadius.circular(24), border: Border.all(color: Colors.green, width: 2), ), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.play_arrow, color: Colors.green, size: 32), SizedBox(height: 4), Text( 'START', style: TextStyle( color: Colors.green[700], fontWeight: FontWeight.bold, fontSize: 12, ), ), ], ), ), ); } } ``` -------------------------------- ### Complete Node Flow Editor Setup Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/_autodocs/06-node-flow-editor.md Demonstrates a complete setup for a Node Flow Editor within a Flutter application, including controller initialization, basic node configuration, and event handling. ```dart class MyFlowEditor extends StatefulWidget { @override State createState() => _MyFlowEditorState(); } class _MyFlowEditorState extends State { late NodeFlowController controller; @override void initState() { super.initState(); controller = NodeFlowController( nodes: [ Node( id: 'node-1', type: 'input', position: Offset(100, 100), data: MyNodeData(label: 'Start'), ), ], ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Flow Editor')), body: NodeFlowEditor( controller: controller, theme: NodeFlowTheme.defaultTheme, nodeBuilder: (context, node) { return Container( padding: EdgeInsets.all(12), child: Text(node.data.label), ); }, nodeShapeBuilder: (context, node) { if (node.type == 'terminal') { return CircleShape(fillColor: Colors.green); } return null; }, events: NodeFlowEvents( onNodeSelected: (node) { setState(() { if (node != null) { print('Selected: ${node.id}'); } }); }, ), ), ); } @override void dispose() { controller.dispose(); super.dispose(); } } ``` -------------------------------- ### Complete Node Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/api/node.md Demonstrates the creation of multiple nodes, including a start node and a process node, with defined ports and initial Z-index. ```dart final startNode = Node( id: 'start', type: 'trigger', position: Offset(50, 100), size: Size(80, 80), data: WorkflowData(label: 'Start'), outputPorts: [ Port(id: 'start-out', name: 'Begin', position: PortPosition.right), ], initialZIndex: 1, ); final processNode = Node( id: 'process', type: 'action', position: Offset(200, 100), size: Size(150, 80), data: WorkflowData(label: 'Process'), inputPorts: [ Port(id: 'process-in', name: 'Input', position: PortPosition.left), ], outputPorts: [ Port(id: 'process-out', name: 'Output', position: PortPosition.right), ], ); ``` -------------------------------- ### Example Connection with Labels Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/_autodocs/02-connection.md Demonstrates how to create a connection and assign labels to its start, center, and end points using `ConnectionLabel`. ```dart final connection = Connection( id: 'conn-1', sourceNodeId: 'node-a', sourcePortId: 'output-1', targetNodeId: 'node-b', targetPortId: 'input-1', startLabel: ConnectionLabel.start(text: 'Start'), label: ConnectionLabel.center(text: 'Data Flow'), endLabel: ConnectionLabel.end(text: 'End'), ); ``` -------------------------------- ### Install Dependencies Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/start/installation.md Run 'flutter pub get' after adding the dependency to your pubspec.yaml. ```bash flutter pub get ``` -------------------------------- ### PortEvents Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/api/events.md Example demonstrating how to configure PortEvents handlers, including checking port direction and showing tooltips. ```dart PortEvents( onTap: (node, port) { print('Tapped ${port.isOutput ? 'output' : 'input'} port: ${port.id}'); }, onMouseEnter: (node, port) => _showTooltip(port), onMouseLeave: (node, port) => _hideTooltip(), ) ``` -------------------------------- ### Complete Example for a Flow Editor Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/start/quick-start.md This is the full working code for a basic flow editor, combining controller setup and editor building. ```dart import 'package:flutter/material.dart'; import 'package:node_flow/node_flow.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Node Flow Editor', theme: ThemeData( primarySwatch: Colors.blue, ), home: const FlowEditorPage(), ); } } class FlowEditorPage extends StatefulWidget { const FlowEditorPage({super.key}); @override State createState() => _FlowEditorPageState(); } class _FlowEditorPageState extends State { late final NodeFlowController controller; @override void initState() { super.initState(); controller = NodeFlowController( initialNodes: [ Node( id: 'node-1', position: const Offset(0, 0), data: 'Node 1', ), Node( id: 'node-2', position: const Offset(300, 150), data: 'Node 2', ), Node( id: 'node-3', position: const Offset(600, 0), data: 'Node 3', ), ], initialConnections: [ Connection( id: 'conn-1', from: PortId('node-1', 'out'), to: PortId('node-2', 'in'), ), Connection( id: 'conn-2', from: PortId('node-2', 'out'), to: PortId('node-3', 'in'), ), ], ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Node Flow Editor'), ), body: NodeFlowEditor( controller: controller, nodeBuilder: (context, node) => TextNodeWidget( node: node, label: node.data, color: Colors.blue, ports: const [ Port.input(id: 'in'), Port.output(id: 'out'), ], ), ), ); } } class TextNodeWidget extends StatelessWidget { const TextNodeWidget({ super.key, required this.node, required this.label, required this.color, required this.ports, }); final Node node; final String label; final Color color; final List ports; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16.0), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(8.0), border: Border.all(color: Colors.grey.shade300), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( label, style: const TextStyle(color: Colors.white), ), const SizedBox(height: 16.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: ports.map((port) => PortWidget(port: port)).toList(), ), ], ), ); } } ``` -------------------------------- ### GridTheme Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/api/theme.md An example demonstrating how to instantiate a GridTheme with specific properties for a dot-style grid. ```dart GridTheme( style: GridStyles.dots, color: Colors.grey[300]!, size: 20, thickness: 1, ) ``` -------------------------------- ### Verify Installation with a Minimal App Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/start/installation.md A basic Flutter application demonstrating the NodeFlowEditor and NodeFlowController to verify the installation. ```dart import 'package:flutter/material.dart'; import 'package:vyuh_node_flow/vyuh_node_flow.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() => _MyAppState(); } class _MyAppState extends State { late final controller = NodeFlowController(); @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: NodeFlowEditor( controller: controller, theme: NodeFlowTheme.light, nodeBuilder: (context, node) => Center(child: Text(node.data)), ), ), ); } } ``` -------------------------------- ### NodeTheme Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/api/theme.md An example demonstrating how to create a custom NodeTheme instance with specific styling properties for background, borders, and shadows. ```dart NodeTheme( backgroundColor: Colors.white, selectedBackgroundColor: Colors.blue[50]!, borderColor: Colors.grey[300]!, selectedBorderColor: Colors.blue, borderWidth: 1, selectedBorderWidth: 2, borderRadius: BorderRadius.circular(8), shadow: [ BoxShadow( color: Colors.black12, blurRadius: 4, offset: Offset(0, 2), ), ], selectedShadow: [ BoxShadow( color: Colors.blue.withOpacity(0.3), blurRadius: 8, offset: Offset(0, 2), ), ], ) ``` -------------------------------- ### Example: Get Node Bounds Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/api/node.md Demonstrates how to get the node's bounding rectangle and print its dimensions. ```dart final bounds = node.getBounds(); print('Node area: ${bounds.width} x ${bounds.height}'); ``` -------------------------------- ### Install Project Dependencies Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/README.md Install the necessary Dart dependencies for the project after cloning the repository. ```bash dart pub get ``` -------------------------------- ### Create and Populate Graph Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/_autodocs/10-controller.md Demonstrates the initialization of a `NodeFlowController` with initial nodes and connections. ```dart class MyFlowController { late NodeFlowController controller; void initialize() { controller = NodeFlowController( nodes: [ Node( id: 'start', type: 'input', position: Offset(100, 100), data: MyData(label: 'Start'), ports: [Port(id: 'out', name: 'Out', type: PortType.output)], ), Node( id: 'process', type: 'processor', position: Offset(300, 100), data: MyData(label: 'Process'), ports: [ Port(id: 'in', name: 'In', type: PortType.input), Port(id: 'out', name: 'Out', type: PortType.output), ], ), ], connections: [ Connection( id: 'conn-1', sourceNodeId: 'start', sourcePortId: 'out', targetNodeId: 'process', targetPortId: 'in', ), ], ); } void cleanup() { controller.dispose(); } } ``` -------------------------------- ### Create Connection Labels Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/concepts/connections.md Demonstrates creating connection labels at the start, center, and end of a connection path using factory constructors. Includes examples with text and perpendicular offsets. ```dart // Start label (anchor 0.0 - at source) ConnectionLabel.start(text: 'Send') // Center label (anchor 0.5 - at midpoint) ConnectionLabel.center(text: 'Data Flow') // End label (anchor 1.0 - at target) ConnectionLabel.end(text: 'Receive') // With perpendicular offset ConnectionLabel.center(text: 'Flow', offset: 10.0) ``` -------------------------------- ### Custom Port Widget Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/components/port-widget.md Demonstrates how to create a custom node with input and output ports using the PortWidget. This example shows how to position ports, customize their appearance, and check connection status. ```dart class CustomPortNode extends StatelessWidget { final Node node; final NodeFlowController controller; final Rect nodeBounds; const CustomPortNode({ required this.node, required this.controller, required this.nodeBounds, }); @override Widget build(BuildContext context) { final theme = Theme.of(context).plugin()!; final size = node.size.value; return Stack( clipBehavior: Clip.none, children: [ // Node content Container( width: size.width, height: size.height, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.blue), ), child: Center(child: Text(node.data.label)), ), // Input ports on left for (var i = 0; i < node.inputPorts.length; i++) Positioned( left: -6, // Half port size outside node top: _calculatePortOffset(i, node.inputPorts.length, size.height), child: PortWidget( port: node.inputPorts[i], theme: theme.portTheme, controller: controller, nodeId: node.id, isOutput: false, nodeBounds: nodeBounds, isConnected: _isPortConnected(node.inputPorts[i].id), ), ), // Output ports on right for (var i = 0; i < node.outputPorts.length; i++) Positioned( right: -6, // Half port size outside node top: _calculatePortOffset(i, node.outputPorts.length, size.height), child: PortWidget( port: node.outputPorts[i], theme: theme.portTheme, controller: controller, nodeId: node.id, isOutput: true, nodeBounds: nodeBounds, isConnected: _isPortConnected(node.outputPorts[i].id), color: Colors.green, // Custom color for outputs ), ), ], ); } double _calculatePortOffset(int index, int total, double nodeHeight) { final spacing = nodeHeight / (total + 1); return spacing * (index + 1) - 6; // Center port on position } bool _isPortConnected(String portId) { return controller.connections.any( (c) => c.sourcePortId == portId || c.targetPortId == portId, ); } } ``` -------------------------------- ### LabelTheme Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/api/theme.md Illustrates creating a LabelTheme with specific text styling, background, padding, and border. This example shows how to configure a custom label appearance. ```dart LabelTheme( textStyle: TextStyle(fontSize: 12, color: Colors.black87), backgroundColor: Colors.white, padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), borderRadius: BorderRadius.circular(4), border: Border.all(color: Colors.grey[300]!), ) ``` -------------------------------- ### Quick Start Animations Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/advanced/viewport-animations.md Demonstrates common animation methods available on the controller for quick access. ```dart // Animate to center on a node controller.animateToNode('node-123'); // Animate to show multiple nodes controller.animateToNodes(['node-1', 'node-2', 'node-3']); // Animate to a specific position controller.animateToPosition(GraphOffset.fromXY(500, 300)); // Animate to fit all nodes controller.animateToBounds(controller.nodesBounds); // Animate to a zoom level controller.animateToScale(1.5); ``` -------------------------------- ### Basic Node Widget Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/components/node-widget.md A simple node widget that displays text within a styled container. This is a starting point for creating custom node appearances. ```dart NodeFlowEditor( controller: controller, nodeBuilder: (context, node) { return Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.blue), ), child: Text(node.data), ); }, ) ``` -------------------------------- ### Complete Node Flow Editor Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/components/node-flow-editor.md A comprehensive example demonstrating the creation and usage of the Node Flow Editor. It includes node management, event handling, custom node building, and a properties panel. ```dart class MyFlowEditor extends StatefulWidget { @override State createState() => _MyFlowEditorState(); } class _MyFlowEditorState extends State { late final NodeFlowController _controller; Node? _selectedNode; @override void initState() { super.initState(); _controller = NodeFlowController( config: NodeFlowConfig( snapToGrid: true, gridSize: 20.0, ), ); _initializeGraph(); } void _initializeGraph() { final node1 = Node( id: 'node-1', type: 'start', position: Offset(100, 100), size: Size(150, 80), data: MyNodeData(label: 'Start'), outputPorts: [ Port(id: 'node-1-out', name: 'Output'), ], ); _controller.addNode(node1); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Node Flow Editor'), actions: [ IconButton(icon: Icon(Icons.add), onPressed: _addNode), if (_selectedNode != null) IconButton(icon: Icon(Icons.delete), onPressed: _deleteSelectedNode), ], ), body: Row( children: [ Expanded( flex: 3, child: NodeFlowEditor( controller: _controller, theme: NodeFlowTheme.light, behavior: NodeFlowBehavior.design, scrollToZoom: true, showAnnotations: true, nodeBuilder: (context, node) => _buildNode(node), nodeShapeBuilder: (context, node) { if (node.type == 'start') { return CircleShape(fillColor: Colors.green); } return null; }, events: NodeFlowEvents( node: NodeEvents( onSelected: (node) => setState(() => _selectedNode = node), onDoubleTap: (node) => _editNode(node), onContextMenu: (node, pos) => _showNodeMenu(node, pos), ), connection: ConnectionEvents( onCreated: (conn) => _showSnackBar('Connection created'), onDeleted: (conn) => _showSnackBar('Connection deleted'), ), viewport: ViewportEvents( onCanvasTap: (pos) => _controller.clearSelection(), ), onInit: () => _controller.fitToView(), ), ), ), if (_selectedNode != null) SizedBox( width: 300, child: _buildPropertiesPanel(), ), ], ), ); } Widget _buildNode(Node node) { return Container( padding: EdgeInsets.all(12), child: Text( node.data.label, style: TextStyle(fontWeight: FontWeight.bold), ), ); } Widget _buildPropertiesPanel() { return Container( color: Colors.grey[100], padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Properties', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), SizedBox(height: 16), Text('Node ID: ${_selectedNode!.id}'), Text('Type: ${_selectedNode!.type}'), SizedBox(height: 16), ElevatedButton(onPressed: _deleteSelectedNode, child: Text('Delete')), ], ), ); } void _addNode() { final node = Node( id: 'node-${DateTime.now().millisecondsSinceEpoch}', type: 'process', position: Offset(200, 200), size: Size(150, 80), data: MyNodeData(label: 'New Node'), inputPorts: [Port(id: 'in-${DateTime.now().millisecondsSinceEpoch}', name: 'Input')], outputPorts: [Port(id: 'out-${DateTime.now().millisecondsSinceEpoch}', name: 'Output')], ); _controller.addNode(node); } void _deleteSelectedNode() { if (_selectedNode != null) { _controller.removeNode(_selectedNode!.id); setState(() => _selectedNode = null); } } void _editNode(Node node) { /* Show edit dialog */ } void _showNodeMenu(Node node, ScreenPosition pos) { /* Show context menu */ } void _showSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message))); } @override void dispose() { _controller.dispose(); super.dispose(); } } ``` -------------------------------- ### Input Port Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/concepts/ports.md Example of creating an input port, which is used to receive connections from other nodes. Typically positioned on the left side. ```dart Port( id: 'in-1', name: 'Input', position: PortPosition.left, type: PortType.input, ) ``` -------------------------------- ### NodeEvents Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/api/events.md Example demonstrating how to configure various NodeEvents handlers, including asynchronous deletion confirmation. ```dart NodeEvents( onTap: (node) => print('Tapped: ${node.id}'), onDoubleTap: (node) => _editNode(node), onDragStop: (node) => _savePosition(node), onContextMenu: (node, pos) => _showMenu(node, pos), onBeforeDelete: (node) async { return await showDialog( context: context, builder: (ctx) => AlertDialog( title: Text('Delete Node?'), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text('Cancel')), TextButton(onPressed: () => Navigator.pop(ctx, true), child: Text('Delete')), ], ), ) ?? false; }, ) ``` -------------------------------- ### Example Custom Node Widget Implementation Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/concepts/nodes.md A detailed example of a `StatelessWidget` that renders a process node with specific styling, including icons, text, and a decorative border. ```dart class ProcessNodeWidget extends StatelessWidget { final Node node; const ProcessNodeWidget({required this.node}); @override Widget build(BuildContext context) { return Container( width: node.size.value.width, height: node.size.value.height, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.blue, width: 2), boxShadow: const [ BoxShadow( color: Colors.black26, blurRadius: 8, offset: Offset(0, 4), ), ], ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.settings, size: 32, color: Colors.blue), const SizedBox(height: 8), Text( node.data.title, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, ), ), if (node.data.description.isNotEmpty) Text( node.data.description, style: TextStyle( fontSize: 10, color: Colors.grey[600], ), ), ], ), ); } } ``` -------------------------------- ### Theme Examples Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/components/node-flow-viewer.md Demonstrates how to apply visual themes to the NodeFlowViewer using predefined light and dark themes. ```dart theme: NodeFlowTheme.light // or theme: NodeFlowTheme.dark ``` -------------------------------- ### Output Port Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/concepts/ports.md Example of creating an output port, which is used to emit connections to other nodes. Typically positioned on the right side. ```dart Port( id: 'out-1', name: 'Output', position: PortPosition.right, type: PortType.output, ) ``` -------------------------------- ### Basic Node Flow Editor Setup Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/examples/index.md Demonstrates the fundamental setup for a Vyuh Node Flow editor in a Flutter application. This includes initializing the controller, defining a basic node builder, and setting a light theme. ```dart class MyEditor extends StatefulWidget { @override State createState() => _MyEditorState(); } class _MyEditorState extends State { late final NodeFlowController controller; @override void initState() { super.initState(); controller = NodeFlowController(); // Add initial nodes... } @override Widget build(BuildContext context) { return NodeFlowEditor( controller: controller, theme: NodeFlowTheme.light, nodeBuilder: (context, node) => Center( child: Text(node.data), ), ); } @override void dispose() { controller.dispose(); super.dispose(); } } ``` -------------------------------- ### Complete Flow Editor Example with Save/Load Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/advanced/serialization.md A full example of a StatefulWidget that includes saving the graph to SharedPreferences and loading it back. It demonstrates the integration of serialization within a UI. ```dart class FlowEditorWithSaveLoad extends StatefulWidget { @override State createState() => _FlowEditorWithSaveLoadState(); } class _FlowEditorWithSaveLoadState extends State { late final NodeFlowController controller; @override void initState() { super.initState(); controller = NodeFlowController(); } Future _saveGraph() async { try { // Export and serialize graph final graph = controller.exportGraph(); final json = graph.toJson((data) => data.toJson()); final jsonString = jsonEncode(json); // Save to SharedPreferences final prefs = await SharedPreferences.getInstance(); await prefs.setString('saved_graph', jsonString); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Graph saved successfully')), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error saving graph: $e')), ); } } Future _loadGraph() async { try { // Load from SharedPreferences final prefs = await SharedPreferences.getInstance(); final jsonString = prefs.getString('saved_graph'); if (jsonString == null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('No saved graph found')), ); return; } // Deserialize and load final json = jsonDecode(jsonString); final graph = NodeGraph.fromJson( json, (map) => MyNodeData.fromJson(map), ); controller.loadGraph(graph); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Graph loaded successfully')), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error loading graph: $e')), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Flow Editor'), actions: [ IconButton( icon: Icon(Icons.save), onPressed: _saveGraph, tooltip: 'Save', ), IconButton( icon: Icon(Icons.folder_open), onPressed: _loadGraph, tooltip: 'Load', ), ], ), body: NodeFlowEditor( controller: controller, nodeBuilder: (context, node) => MyNodeWidget(node: node), enablePanning: true, enableZooming: true, ), ); } } ``` -------------------------------- ### Dynamic Connection Styling Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/_autodocs/06-node-flow-editor.md This example utilizes connectionStyleBuilder to apply different connection styles based on the connection's data. High-priority connections use bezier curves, while others use smoothstep. ```dart connectionStyleBuilder: (context, conn) { if (conn.data?.priority == Priority.high) { return ConnectionStyles.bezier; } return ConnectionStyles.smoothstep; } ``` -------------------------------- ### ViewportEvents Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/api/events.md Example of how to instantiate ViewportEvents to handle canvas tap, double-tap, context menu, and move events. Canvas positions are in graph coordinates. ```dart ViewportEvents( onCanvasTap: (pos) => controller.clearSelection(), onCanvasDoubleTap: (pos) => _addNodeAt(pos), onCanvasContextMenu: (pos) => _showAddMenu(pos), onMove: (viewport) => _updateMinimap(viewport), ) ``` -------------------------------- ### Custom Node Widget Building Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/_autodocs/01-node.md Example of overriding the `buildWidget` method to provide custom rendering for a node. It checks for an instance-level `widgetBuilder` first. ```dart @override Widget? buildWidget(BuildContext context) { if (widgetBuilder != null) { return widgetBuilder!(context, this); } // Provide subclass-specific rendering return MyCustomWidget(node: this); } ``` -------------------------------- ### NodeBuilder Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/components/node-flow-viewer.md Provides an example of a nodeBuilder function that defines the visual representation for each node in the viewer. ```dart nodeBuilder: (context, node) { return Container( padding: EdgeInsets.all(12), child: Column( children: [ Text(node.data.title, style: TextStyle(fontWeight: FontWeight.bold)), Text(node.data.status), ], ), ); } ``` -------------------------------- ### Node Flow Events Callback Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/_autodocs/06-node-flow-editor.md Provides an example of how to instantiate NodeFlowEvents with callbacks for various user interactions like node selection, double-clicks, connection creation/deletion, and viewport changes. ```dart NodeFlowEvents( onNodeSelected: (node) => print('Selected: ${node?.id}'), onNodeDoubleClicked: (node) => print('Double clicked: ${node.id}'), onConnectionCreated: (conn) => print('Created: ${conn.id}'), onConnectionDeleted: (conn) => print('Deleted: ${conn.id}'), onViewportChanged: (viewport) => print('Zoomed to: ${viewport.zoom}'), ) ``` -------------------------------- ### Complete Example of NodeFlowViewer in Flutter Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/components/node-flow-viewer.md This example demonstrates how to integrate and use the NodeFlowViewer widget within a Flutter application. It includes setting up the controller, loading nodes and connections, and defining custom node builders and tap handlers. Use this for displaying static or interactive node-based workflows. ```dart class WorkflowPreview extends StatefulWidget { final Map> nodes; final List connections; const WorkflowPreview({ required this.nodes, required this.connections, }); @override State createState() => _WorkflowPreviewState(); } class _WorkflowPreviewState extends State { late final NodeFlowController _controller; Node? _selectedNode; @override void initState() { super.initState(); _controller = NodeFlowController(); // Load data for (final node in widget.nodes.values) { _controller.addNode(node); } for (final connection in widget.connections) { _controller.addConnection(connection); } // Fit view after frame WidgetsBinding.instance.addPostFrameCallback((_) { _controller.fitToView(); }); } @override Widget build(BuildContext context) { return Column( children: [ // Info bar if (_selectedNode != null) Container( padding: EdgeInsets.all(12), color: Colors.blue.shade50, child: Row( children: [ Text('Selected: ${_selectedNode!.data.name}'), Spacer(), TextButton( onPressed: () => _controller.centerOnNode(_selectedNode!.id), child: Text('Center'), ), ], ), ), // Viewer Expanded( child: NodeFlowViewer( controller: _controller, theme: NodeFlowTheme.light, nodeBuilder: (context, node) => _buildNode(node), showAnnotations: false, onNodeTap: (node) { if (node != null) { _showNodeDetails(node); } }, onNodeSelected: (node) { setState(() => _selectedNode = node); }, ), ), ], ); } Widget _buildNode(Node node) { final step = node.data; return Container( padding: EdgeInsets.all(12), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(_getIconForType(node.type)), SizedBox(height: 4), Text( step.name, style: TextStyle(fontWeight: FontWeight.bold), ), if (step.status != null) Text( step.status!, style: TextStyle(fontSize: 12, color: Colors.grey), ), ], ), ); } IconData _getIconForType(String type) { switch (type) { case 'trigger': return Icons.play_arrow; case 'action': return Icons.flash_on; case 'condition': return Icons.call_split; default: return Icons.circle; } } void _showNodeDetails(Node node) { showDialog( context: context, builder: (context) => AlertDialog( title: Text(node.data.name), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('ID: ${node.id}'), Text('Type: ${node.type}'), Text('Position: ${node.position.value}'), if (node.data.status != null) Text('Status: ${node.data.status}'), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text('Close'), ), ], ), ); } @override void dispose() { _controller.dispose(); super.dispose(); } } class WorkflowStep { final String name; final String? status; WorkflowStep({required this.name, this.status}); } ``` -------------------------------- ### Node Content Builder Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/_autodocs/06-node-flow-editor.md This example demonstrates how to build custom content for nodes using the nodeBuilder. It displays the node's type and its custom data label. ```dart nodeBuilder: (context, node) { return Container( padding: EdgeInsets.all(12), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(node.type, style: TextStyle(fontSize: 12)), SizedBox(height: 4), Text(node.data.label, style: TextStyle(fontWeight: FontWeight.bold)), ], ), ); } ``` -------------------------------- ### Complete Port Labels Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/theming/port-labels.md This Dart code demonstrates a full example of configuring port labels with various settings, including different positions, shapes, and visibility toggles. It sets up multiple nodes with distinct port configurations within a Flutter application. ```dart import 'package:flutter/material.dart'; import 'package:vyuh_node_flow/vyuh_node_flow.dart'; class PortLabelsExample extends StatefulWidget { const PortLabelsExample({super.key}); @override State createState() => _PortLabelsExampleState(); } class _PortLabelsExampleState extends State { late final NodeFlowController _controller; bool _showLabels = true; @override void initState() { super.initState(); _controller = NodeFlowController(); _setupNodes(); } void _setupNodes() { // Node with all port positions final node1 = Node( id: 'node-1', position: const Offset(100, 100), size: const Size(200, 200), data: 'All Positions', inputPorts: [ Port( id: 'input-left', name: 'Left Input', position: PortPosition.left, showLabel: true, ), Port( id: 'input-top', name: 'Top Input', position: PortPosition.top, showLabel: true, ), ], outputPorts: [ Port( id: 'output-right', name: 'Right Output', position: PortPosition.right, showLabel: true, ), Port( id: 'output-bottom', name: 'Bottom Output', position: PortPosition.bottom, showLabel: true, ), ], ); // Node with different port shapes final node2 = Node( id: 'node-2', position: const Offset(400, 100), size: const Size(180, 180), data: 'Different Shapes', inputPorts: [ Port( id: 'circle-input', name: 'Circle', position: PortPosition.left, shape: MarkerShapes.circle, showLabel: true, ), Port( id: 'rectangle-input', name: 'Rectangle', position: PortPosition.left, offset: const Offset(0, 40), shape: MarkerShapes.rectangle, showLabel: true, ), Port( id: 'diamond-input', name: 'Diamond', position: PortPosition.left, offset: const Offset(0, 80), shape: MarkerShapes.diamond, showLabel: true, ), ], outputPorts: [ Port( id: 'triangle-output', name: 'Triangle', position: PortPosition.right, shape: MarkerShapes.triangle, showLabel: true, ), ], ); // Node with multiple ports on same side final node3 = Node( id: 'node-3', position: const Offset(100, 400), size: const Size(200, 150), data: 'Multiple Ports', inputPorts: List.generate( 3, (i) => Port( id: 'input-$i', name: 'Port ${i + 1}', position: PortPosition.left, offset: Offset(0, (i - 1) * 40), showLabel: true, ), ), outputPorts: List.generate( 3, (i) => Port( id: 'output-$i', name: 'Out ${i + 1}', position: PortPosition.right, offset: Offset(0, (i - 1) * 40), showLabel: true, ), ), ); // Node with mixed labels final node4 = Node( id: 'node-4', position: const Offset(400, 400), size: const Size(180, 150), data: 'Mixed Labels', inputPorts: [ Port( id: 'labeled-input', name: 'With Label', position: PortPosition.left, showLabel: true, // Label enabled ), Port( id: 'unlabeled-input', name: 'No Label', position: PortPosition.left, offset: const Offset(0, 50), showLabel: false, // Label disabled ), ], outputPorts: [ Port( id: 'labeled-output', name: 'With Label', position: PortPosition.right, showLabel: true, ), Port( id: 'unlabeled-output', name: 'No Label', position: PortPosition.right, offset: const Offset(0, 50), showLabel: false, ), ], ); _controller.addNode(node1); _controller.addNode(node2); _controller.addNode(node3); _controller.addNode(node4); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Port Labels Example'), actions: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Row( children: [ const Text('Show Labels: '), Switch( value: _showLabels, onChanged: (value) { setState(() { _showLabels = value; }); }, ) ], ), ) ], ), body: NodeFlow( controller: _controller, showLabels: _showLabels, ), ); } } ``` -------------------------------- ### Example: Add a Single Connection Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/_autodocs/10-controller.md Demonstrates how to create and add a single connection between two nodes. ```dart final connection = Connection( id: 'conn-1', sourceNodeId: 'node-a', sourcePortId: 'out', targetNodeId: 'node-b', targetPortId: 'in', ); controller.addConnection(connection); ``` -------------------------------- ### Usage Example: Simple Comment Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/_autodocs/05-comment-node.md Demonstrates how to create a basic CommentNode instance with essential parameters. ```APIDOC ## Usage Example: Simple Comment ```dart final comment = CommentNode( id: 'comment-1', type: 'comment', position: Offset(200, 50), data: null, text: 'This is a workflow annotation', ); ``` ``` -------------------------------- ### Observable Start Label Property Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/_autodocs/02-connection.md Sets or gets the label displayed at the start of the connection (anchor 0.0). ```dart ConnectionLabel? get startLabel set startLabel(ConnectionLabel? value) ``` -------------------------------- ### Node Initialization and Movement Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/concepts/architecture.md Demonstrates initializing a node with MobX observables and how moving the node triggers automatic UI updates. ```dart final node = Node( position: Offset(100, 100), // Wrapped in Observable internally ); // Moving a node triggers automatic UI update node.position.value = Offset(200, 200); ``` -------------------------------- ### Observable Start Point Property Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/_autodocs/02-connection.md Sets or gets the custom start endpoint marker for the connection. Uses theme default if null. ```dart ConnectionEndPoint? get startPoint set startPoint(ConnectionEndPoint? value) ``` -------------------------------- ### Example: Serialize Node to JSON Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/api/node.md Demonstrates serializing a node to JSON, including its custom data. ```dart final json = node.toJson((data) => data.toJson()); ``` -------------------------------- ### Get Downstream Nodes Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/_autodocs/10-controller.md Retrieves a list of all nodes that are reachable from a given starting node. ```dart List getDownstreamNodes(String nodeId) ``` -------------------------------- ### Basic Node Creation Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/_autodocs/01-node.md Demonstrates how to create a basic node with custom data, ID, type, position, and ports. ```dart class TaskData { final String title; final bool completed; TaskData({required this.title, required this.completed}); } final node = Node( id: 'task-1', type: 'task', position: Offset(100, 100), data: TaskData(title: 'Process Data', completed: false), ports: [ Port(id: 'in-1', name: 'Input', type: PortType.input), Port(id: 'out-1', name: 'Output', type: PortType.output), ], ); ``` -------------------------------- ### Port copyWith Method Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/api/port.md Demonstrates creating a modified copy of an existing port object. ```dart final updatedPort = port.copyWith( name: 'Updated Name', multiConnections: true, ); ``` -------------------------------- ### Basic Connection Creation Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/api/connection.md Example of creating a basic Connection instance with required parameters and adding it to a controller. ```dart final connection = Connection( id: 'conn-1', sourceNodeId: 'node-1', sourcePortId: 'out-1', targetNodeId: 'node-2', targetPortId: 'in-1', ); controller.addConnection(connection); ``` -------------------------------- ### Add Port Example Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/website/docs/api/node.md Adds new input and output ports to an existing node instance. ```dart node.addInputPort(Port(id: 'new-input', name: 'New Input')); node.addOutputPort(Port(id: 'new-output', name: 'New Output')); ``` -------------------------------- ### Basic Node Flow Editor Setup Source: https://github.com/vyuh-tech/vyuh_node_flow/blob/main/packages/vyuh_node_flow/README.md Initializes a NodeFlowEditor with sample input and output nodes and a connection between them. Requires importing necessary packages and defining a NodeFlowController. ```dart import 'package:flutter/material.dart'; import 'package:vyuh_node_flow/vyuh_node_flow.dart'; class FlowEditor extends StatefulWidget { @override State createState() => _FlowEditorState(); } class _FlowEditorState extends State { late final controller = NodeFlowController( nodes: [ Node( id: 'node-1', type: 'input', position: const Offset(100, 100), data: 'Start', outputPorts: const [ Port(id: 'out', name: 'Output', offset: Offset(2, 40)), ], ), Node( id: 'node-2', type: 'output', position: const Offset(400, 100), data: 'End', inputPorts: const [ Port(id: 'in', name: 'Input', offset: Offset(-2, 40)), ], ), ], connections: [ Connection( id: 'conn-1', sourceNodeId: 'node-1', sourcePortId: 'out', targetNodeId: 'node-2', targetPortId: 'in', ), ], ); @override Widget build(BuildContext context) { return NodeFlowEditor( controller: controller, theme: NodeFlowTheme.light, nodeBuilder: (context, node) => Padding( padding: const EdgeInsets.all(16), child: Text(node.data, style: const TextStyle(fontWeight: FontWeight.bold)), ), ); } } ```