Vyuh Node Flow

Annotation Components

Widgets and layers for rendering annotations in your node flow

Annotation Components

Image

Annotation Components Overview

Layered diagram showing the annotation rendering architecture: AnnotationLayer containing multiple AnnotationWidgets, each wrapping a custom annotation type (StickyAnnotation, GroupAnnotation, MarkerAnnotation). Shows selection borders, resize handles, and theming integration.

PROTOTYPE PREVIEW

Annotations are visual overlays rendered on top of your node flow. The annotation system consists of several components that work together to provide a seamless experience.

Component Architecture

AnnotationLayer
├── AnnotationWidget (StickyAnnotation)
│   ├── Selection border
│   ├── Resize handles
│   └── Custom content (buildWidget)
├── AnnotationWidget (GroupAnnotation)
│   ├── Selection border
│   └── Custom content (buildWidget)
└── AnnotationWidget (MarkerAnnotation)
    ├── Selection border
    └── Custom content (buildWidget)

AnnotationWidget

The AnnotationWidget is the framework widget that wraps all annotation types with automatic functionality.

Features

FeatureDescription
Reactive positioningUpdates automatically based on annotation position
Visibility controlShows/hides based on annotation.isVisible
Selection feedbackTheme-consistent borders when selected
Gesture handlingTap, double-tap, drag, and context menu events
Resize handlesShown for resizable annotations when selected
Theme integrationUses NodeFlowTheme for consistent styling

Usage

The AnnotationWidget is used internally by AnnotationLayer. You typically don't instantiate it directly:

// Used internally by the framework
AnnotationWidget(
  annotation: stickyNote,
  controller: controller,
  onTap: () => controller.annotations.selectAnnotation(stickyNote.id),
  onDoubleTap: () => _editAnnotation(stickyNote),
  onContextMenu: (position) => _showContextMenu(stickyNote, position),
)

Automatic Features

When you create a custom annotation, the AnnotationWidget automatically provides:

  1. Positioning: Your annotation is positioned on the canvas based on visualPosition
  2. Selection styling: A border appears when the annotation is selected
  3. Resize handles: If isResizable returns true, resize handles appear when selected
  4. Drag handling: Users can drag annotations to move them
  5. Cursor feedback: Cursor changes based on interaction state

Built-in Annotation Types

Image

Sticky Annotation

Yellow sticky note with multi-line text, showing edit mode with text cursor, resize handles on corners and edges when selected.

PROTOTYPE PREVIEW

Free-floating notes for comments and documentation.

final sticky = controller.annotations.createStickyAnnotation(
  id: 'sticky-1',
  position: const Offset(100, 100),
  text: 'Remember to validate inputs!',
  width: 200,
  height: 120,
  color: Colors.yellow.shade200,
);
controller.annotations.addAnnotation(sticky);

Properties:

  • text: The note content (supports multi-line)
  • width / height: Dimensions
  • color: Background color
  • isResizable: Always true for sticky notes
Image

Group Annotation

Blue-tinted group surrounding three nodes with 'Data Processing' header. Shows the group auto-resizing as nodes are moved.

PROTOTYPE PREVIEW

Visual containers that surround related nodes.

// Create group around specific nodes
final group = controller.annotations.createGroupAnnotationAroundNodes(
  id: 'group-1',
  title: 'Data Processing',
  nodeIds: {'node-1', 'node-2', 'node-3'},
  color: Colors.blue.shade200,
  padding: const EdgeInsets.all(30),
);
controller.annotations.addAnnotation(group);

// Or create with explicit position/size
final fixedGroup = controller.annotations.createGroupAnnotation(
  id: 'group-2',
  title: 'Fixed Group',
  position: const Offset(50, 50),
  size: const Size(300, 200),
  color: Colors.green.shade200,
);
controller.annotations.addAnnotation(fixedGroup);

Properties:

  • title: Header label
  • nodeIds: Set of contained node IDs (for auto-sizing)
  • padding: Space around contained nodes
  • color: Header and tint color
Image

Marker Annotation

Small circular markers with icons: timer (clock), warning (triangle), milestone (flag). Each showing tooltip on hover.

PROTOTYPE PREVIEW

Compact icons for status indicators and semantic tags.

final marker = controller.annotations.createMarkerAnnotation(
  id: 'marker-1',
  position: const Offset(200, 80),
  markerType: MarkerType.warning,
  size: 24.0,
  color: Colors.orange,
  tooltip: 'Check prerequisites before proceeding',
);
controller.annotations.addAnnotation(marker);

Marker Types:

  • Status: error, warning, info, risk
  • Tasks: user, script, service, manual
  • Workflow: timer, message, decision, milestone, subprocess, compliance

AnnotationLayer

The AnnotationLayer is responsible for rendering all annotations. It's automatically included in NodeFlowEditor.

// The layer is created internally by NodeFlowEditor
// You don't need to create it yourself

NodeFlowEditor<MyData>(
  controller: controller,
  nodeBuilder: (context, node) => MyNodeWidget(node: node),
  // Annotations are automatically rendered
)

Layer Ordering

Annotations are rendered based on their z-index:

// Send group to back (behind nodes)
controller.annotations.sendAnnotationToBack(groupId);

// Bring sticky note to front
controller.annotations.bringAnnotationToFront(stickyId);

Groups typically use negative z-index values to render behind nodes, while sticky notes and markers render on top.

AnnotationTheme

Customize annotation appearance through the theme:

NodeFlowTheme(
  annotationTheme: AnnotationTheme(
    selectionBorderColor: Colors.blue,
    selectionBorderWidth: 2.0,
    selectionBackgroundColor: Colors.blue.withOpacity(0.05),
    borderRadius: BorderRadius.circular(4),
  ),
)
PropertyTypeDescription
selectionBorderColorColorBorder color when selected
selectionBorderWidthdoubleBorder width when selected
selectionBackgroundColorColor?Background tint when selected
borderRadiusBorderRadiusCorner radius for selection border

Creating Custom Annotations

Extend the Annotation base class to create custom annotation types:

class BadgeAnnotation extends Annotation {
  final String label;
  final Color badgeColor;

  BadgeAnnotation({
    required super.id,
    required Offset position,
    required this.label,
    this.badgeColor = Colors.purple,
  }) : super(
         type: 'badge',
         initialPosition: position,
       );

  @override
  Size get size => const Size(60, 60);

  @override
  bool get isResizable => false;

  @override
  Widget buildWidget(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: badgeColor,
        shape: BoxShape.circle,
        boxShadow: [
          BoxShadow(
            color: badgeColor.withOpacity(0.3),
            blurRadius: 8,
            spreadRadius: 2,
          ),
        ],
      ),
      child: Center(
        child: Text(
          label,
          style: const TextStyle(
            color: Colors.white,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    );
  }

  @override
  Map<String, dynamic> toJson() => {
    'id': id,
    'type': type,
    'x': currentPosition.dx,
    'y': currentPosition.dy,
    'label': label,
    'badgeColor': badgeColor.value,
  };

  @override
  void fromJson(Map<String, dynamic> json) {
    setPosition(Offset(
      (json['x'] as num).toDouble(),
      (json['y'] as num).toDouble(),
    ));
  }
}

Required Overrides

MethodPurpose
Size get sizeDimensions for hit testing and layout
Widget buildWidget(BuildContext)Visual representation
Map<String, dynamic> toJson()Serialization
void fromJson(Map<String, dynamic>)Deserialization

Optional Overrides

PropertyDefaultPurpose
isResizablefalseEnable resize handles
isInteractivetrueEnable user interaction
isVisibletrueShow/hide annotation

Annotation Events

Handle annotation interactions through the events API:

NodeFlowEditor<MyData>(
  controller: controller,
  events: NodeFlowEvents(
    annotation: AnnotationEvents(
      onTap: (annotation) => _selectAnnotation(annotation),
      onDoubleTap: (annotation) => _editAnnotation(annotation),
      onContextMenu: (annotation, position) {
        _showContextMenu(annotation, position);
      },
      onCreated: (annotation) => _saveAnnotation(annotation),
      onDeleted: (annotation) => _deleteAnnotation(annotation),
      onMoved: (annotation, newPosition) {
        print('Moved to $newPosition');
      },
    ),
  ),
)

Node-Following Annotations

Annotations can follow nodes, moving automatically when the node moves:

// Create annotation
final note = controller.annotations.createStickyAnnotation(
  id: 'linked-note',
  position: Offset.zero, // Will be overridden
  text: 'Always visible next to this node',
  offset: const Offset(80, 50), // Offset from node center
);
controller.annotations.addAnnotation(note);

// Link to a node
controller.annotations.addNodeDependency(note.id, 'target-node-id');

// Now when the node moves, the annotation follows

Best Practices

  1. Use groups sparingly - Too many overlapping groups create visual clutter
  2. Match markers to semantics - Use consistent marker types for consistent meanings
  3. Keep notes concise - Sticky notes work best for short reminders
  4. Layer thoughtfully - Groups behind nodes, markers at same level, notes on top
  5. Consider interactivity - Set isInteractive: false for decorative annotations

See Also

On this page