Skip to main content

OrgChart Widget

The OrgChart widget displays hierarchical organizational structures with single-parent relationships.

Basic Usage

OrgChart<Employee>(
controller: controller,
builder: (details) {
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text(details.item.name, style: TextStyle(fontWeight: FontWeight.bold)),
Text(details.item.position),
if (details.level > 1) // Don't show toggle for CEO/root
IconButton(
icon: Icon(details.nodesHidden ? Icons.add : Icons.remove),
onPressed: () => details.hideNodes(),
),
],
),
),
);
},
)

Constructor Parameters

The OrgChart widget extends BaseGraph and inherits all its common properties, plus:

ParameterTypeDefaultDescription
controllerOrgChartController<E>requiredController managing chart data and layout
onDropFunction(E dragged, E target, bool isTargetSubnode)?nullCallback when a node is dropped onto another

Drag and Drop Operations

The onDrop callback provides three parameters:

OrgChart<Employee>(
controller: controller,
builder: employeeNodeBuilder,
onDrop: (dragged, target, isTargetSubnode) {
if (isTargetSubnode) {
// Prevent circular reference - can't make a manager report to their own subordinate
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Invalid Operation'),
content: Text('Cannot create circular reporting relationship'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('OK'),
),
],
),
);
} else {
// Update the reporting relationship
setState(() {
dragged.managerId = target.id;
controller.calculatePosition();
});
}
},
)

Understanding isTargetSubnode

The isTargetSubnode parameter is critical for preventing circular references in organizational hierarchies:

  • true: The target node is already a subordinate (direct or indirect) of the dragged node
  • false: The target is not a subordinate, making it safe to create a reporting relationship

Collapsing/Expanding Subtrees

OrgChart supports collapsing and expanding subtrees for better navigation of large hierarchies:

builder: (details) {
return Card(
child: Column(
children: [
Text(details.item.name),
// Show toggle button only if the node has children
if (controller.getSubNodes(controller.nodes.firstWhere(
(node) => controller.idProvider(node.data) == controller.idProvider(details.item)
)).isNotEmpty)
IconButton(
icon: Icon(details.nodesHidden ? Icons.add : Icons.remove),
onPressed: () => details.hideNodes(),
tooltip: details.nodesHidden ? 'Expand' : 'Collapse',
),
],
),
);
}

Working with Levels

The level of each node in the hierarchy is provided, making it easy to apply different styling based on depth:

builder: (details) {
// Different background colors based on level
Color backgroundColor = details.level == 1
? Colors.amber[300]! // CEO
: details.level == 2
? Colors.blue[200]! // VP level
: details.level == 3
? Colors.green[100]! // Director level
: Colors.grey[100]!; // Other employees

return Card(
color: backgroundColor,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text(details.item.name),
Text("Level: ${details.level}"),
],
),
),
);
}