Skip to main content

OrgChart Widget

The OrgChart widget is the visual component that renders your organizational hierarchy. It provides extensive customization options for appearance, behavior, and interactions.

Widget Constructorโ€‹

OrgChart<T>({
Key? key,
required OrgChartController<T> controller,
required Widget Function(NodeBuilderDetails<T>) builder,
bool isDraggable = false,
Curve curve = Curves.linear,
Duration duration = const Duration(milliseconds: 300),
Paint? linePaint,
double cornerRadius = 0,
GraphArrowStyle arrowStyle = const SolidGraphArrow(),
LineEndingType lineEndingType = LineEndingType.arrow,
Widget Function(BuildContext, T)? optionsBuilder,
void Function(T)? onOptionSelect,
void Function(T dragged, T target, bool isTargetSubnode)? onDrop,
CustomInteractiveViewerController? viewerController,
InteractionConfig? interactionConfig,
KeyboardConfig? keyboardConfig,
ZoomConfig? zoomConfig,
FocusNode? focusNode,
})

Widget Parametersโ€‹

Required Parametersโ€‹

ParameterTypeDescription
controllerOrgChartController<T>Controls data and layout
builderWidget Function(NodeBuilderDetails<T>)Builds each node widget

Optional Parametersโ€‹

ParameterTypeDefaultDescription
isDraggableboolfalseEnable drag and drop
curveCurveCurves.linearAnimation curve
durationDuration300msAnimation duration
linePaintPaint?nullEdge line styling
cornerRadiusdouble0Corner radius for edges
arrowStyleGraphArrowStyleSolidGraphArrow()Arrow/line style
lineEndingTypeLineEndingType.arrowLine ending decoration
optionsBuilderFunction?nullContext menu builder
onOptionSelectFunction?nullContext menu handler
onDropFunction?nullDrag and drop handler
viewerControllerController?nullZoom/pan controller
interactionConfigConfig?nullInteraction settings
keyboardConfigConfig?nullKeyboard shortcuts
zoomConfigConfig?nullZoom settings
focusNodeFocusNode?nullKeyboard focus node

Node Builderโ€‹

The builder function receives a NodeBuilderDetails<T> object with context about each node:

class NodeBuilderDetails<T> {
final T item; // Your data object
final int level; // Hierarchy level (1 = root)
final Function hideNodes; // Toggle children visibility
final bool nodesHidden; // Are children hidden?
final bool isBeingDragged; // Is node being dragged?
final bool isOverlapped; // Is another node over this?
}

Basic Node Builderโ€‹

OrgChart<Employee>(
controller: controller,
builder: (NodeBuilderDetails<Employee> details) {
return Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(details.item.name),
),
);
},
)

Advanced Node Builderโ€‹

builder: (NodeBuilderDetails<Employee> details) {
final employee = details.item;

return AnimatedContainer(
duration: Duration(milliseconds: 200),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: details.level == 1
? [Colors.purple, Colors.blue]
: [Colors.blue, Colors.cyan],
),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: details.isOverlapped
? Colors.green
: Colors.transparent,
width: 3,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(
details.isBeingDragged ? 0.3 : 0.1
),
blurRadius: details.isBeingDragged ? 15 : 5,
offset: Offset(0, details.isBeingDragged ? 8 : 3),
),
],
),
child: Stack(
children: [
// Main content
Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
backgroundImage: NetworkImage(employee.imageUrl),
radius: 30,
),
SizedBox(height: 8),
Text(
employee.name,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text(
employee.position,
style: TextStyle(
color: Colors.white70,
fontSize: 12,
),
),
if (employee.teamSize > 0)
Container(
margin: EdgeInsets.only(top: 8),
padding: EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: Text(
'${employee.teamSize} team members',
style: TextStyle(
color: Colors.white,
fontSize: 11,
),
),
),
],
),
),

// Collapse/Expand button
if (hasSubordinates(employee))
Positioned(
bottom: -10,
left: 0,
right: 0,
child: Center(
child: GestureDetector(
onTap: () => details.hideNodes(),
child: Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 5,
),
],
),
child: Icon(
details.nodesHidden
? Icons.add
: Icons.remove,
size: 20,
),
),
),
),
),

// Level badge
if (details.level == 1)
Positioned(
top: 0,
right: 0,
child: Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.only(
topRight: Radius.circular(12),
bottomLeft: Radius.circular(12),
),
),
child: Icon(
Icons.star,
color: Colors.white,
size: 16,
),
),
),
],
),
);
}

Edge Stylingโ€‹

Line Paint Configurationโ€‹

OrgChart(
linePaint: Paint()
..color = Colors.blue.shade300
..strokeWidth = 2.5
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round
..style = PaintingStyle.stroke,
)

Arrow Stylesโ€‹

OrgChart(
arrowStyle: SolidGraphArrow(),
lineEndingType: LineEndingType.arrow,
)

Drag and Dropโ€‹

Enable drag and drop with the isDraggable parameter and handle drops with onDrop:

OrgChart<Employee>(
controller: controller,
isDraggable: true,
onDrop: (Employee dragged, Employee target, bool isTargetSubnode) {
// Validate the move
if (!canReassignEmployee(dragged, target)) {
showSnackBar('Cannot move ${dragged.name} to ${target.name}');
return;
}

// Update your data model
setState(() {
dragged.managerId = isTargetSubnode
? target.managerId // Same level as target
: target.id; // Under target

// Refresh the chart
controller.updateItem(dragged);
});
},
builder: (details) {
// Visual feedback during drag
return Opacity(
opacity: details.isBeingDragged ? 0.7 : 1.0,
child: Transform.scale(
scale: details.isBeingDragged ? 1.05 : 1.0,
child: buildNode(details),
),
);
},
)

Context Menusโ€‹

Add right-click or long-press context menus:

OrgChart<Employee>(
optionsBuilder: (BuildContext context, Employee employee) {
return Card(
elevation: 8,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(Icons.edit),
title: Text('Edit'),
onTap: () => editEmployee(employee),
),
ListTile(
leading: Icon(Icons.person_add),
title: Text('Add Report'),
onTap: () => addSubordinate(employee),
),
ListTile(
leading: Icon(Icons.delete),
title: Text('Remove'),
onTap: () => removeEmployee(employee),
),
Divider(),
ListTile(
leading: Icon(Icons.info),
title: Text('Details'),
onTap: () => showEmployeeDetails(employee),
),
],
),
);
},
onOptionSelect: (Employee employee) {
// Optional: Handle selection
Navigator.pop(context);
},
)

Zoom and Pan Configurationโ€‹

Basic Setupโ€‹

final viewerController = CustomInteractiveViewerController();

OrgChart(
viewerController: viewerController,
interactionConfig: InteractionConfig(
enableZoom: true,
enablePan: true,
enableDoubleTapZoom: true,
),
zoomConfig: ZoomConfig(
minScale: 0.5,
maxScale: 3.0,
initialScale: 1.0,
),
)

Programmatic Controlโ€‹

// Zoom to specific level
viewerController.setScale(1.5);

// Pan to position
viewerController.panTo(Offset(100, 200));

// Reset view
viewerController.reset();

// Center on specific node
controller.centerNode(
'employee-123',
scale: 2.0,
animate: true,
);

Keyboard Navigationโ€‹

OrgChart(
focusNode: FocusNode(),
keyboardConfig: KeyboardConfig(
enableKeyboardControls: true,
keyboardPanDistance: 50.0,
keyboardZoomFactor: 1.1,
enableKeyRepeat: true,
keyRepeatInitialDelay: Duration(milliseconds: 500),
keyRepeatInterval: Duration(milliseconds: 50),
animateKeyboardTransitions: true,
keyboardAnimationDuration: Duration(milliseconds: 200),
keyboardAnimationCurve: Curves.easeOutCubic,
invertArrowKeyDirection: false,
),
)

// Or use predefined configurations:
keyboardConfig: KeyboardConfig.fast(), // Fast, responsive controls
keyboardConfig: KeyboardConfig.disabled(), // Disable keyboard controls

Animation Configurationโ€‹

OrgChart(
// Smooth animations
duration: Duration(milliseconds: 500),
curve: Curves.easeInOutCubic,

// Or instant updates
duration: Duration.zero,

// Or bouncy animations
duration: Duration(milliseconds: 800),
curve: Curves.elasticOut,
)

Complete Exampleโ€‹

Here's a full-featured OrgChart implementation:

class AdvancedOrgChart extends StatefulWidget {

_AdvancedOrgChartState createState() => _AdvancedOrgChartState();
}

class _AdvancedOrgChartState extends State<AdvancedOrgChart> {
late OrgChartController<Employee> controller;
final viewerController = CustomInteractiveViewerController();
final focusNode = FocusNode();


void initState() {
super.initState();
controller = OrgChartController<Employee>(
items: loadEmployees(),
idProvider: (e) => e.id,
toProvider: (e) => e.managerId,
toSetter: (e, newId) => e.copyWith(managerId: newId),
boxSize: Size(250, 140),
spacing: 50,
runSpacing: 100,
);
controller.setViewerController(viewerController);
}


Widget build(BuildContext context) {
return Scaffold(
body: OrgChart<Employee>(
controller: controller,
viewerController: viewerController,
focusNode: focusNode,

// Visual configuration
duration: Duration(milliseconds: 500),
curve: Curves.easeInOutCubic,
linePaint: Paint()
..color = Theme.of(context).primaryColor.withOpacity(0.5)
..strokeWidth = 2.0
..strokeCap = StrokeCap.round,
arrowStyle: SolidGraphArrow(),
cornerRadius: 10,
lineEndingType: LineEndingType.arrow,

// Interactions
isDraggable: true,
interactionConfig: InteractionConfig(
enableZoom: true,
enablePan: true,
enableDoubleTapZoom: true,
),
zoomConfig: ZoomConfig(
minScale: 0.25,
maxScale: 4.0,
initialScale: 1.0,
),
keyboardConfig: KeyboardConfig(
enableKeyboardControls: true,
keyboardPanDistance: 50.0,
keyboardZoomFactor: 1.1,
),

// Event handlers
onDrop: handleEmployeeDrop,
optionsBuilder: buildContextMenu,
onOptionSelect: handleOptionSelect,

// Node builder
builder: (details) => buildAdvancedNode(details),
),
);
}
}

Performance Tipsโ€‹

  1. Use RepaintBoundary: Wrap complex nodes in RepaintBoundary
  2. Optimize Images: Use cached network images
  3. Limit Animations: Reduce animation duration for large charts
  4. Lazy Loading: Load nodes on-demand for huge datasets
  5. Simplify Nodes: Use simpler designs for better performance

Next Stepsโ€‹