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:
Parameter | Type | Default | Description |
---|---|---|---|
controller | OrgChartController<E> | required | Controller managing chart data and layout |
onDrop | Function(E dragged, E target, bool isTargetSubnode)? | null | Callback 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 nodefalse
: 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}"),
],
),
),
);
}