OrgChartController
The OrgChartController
is the brain of your organizational chart. It manages the data, handles updates, and provides methods for interacting with the chart programmatically.
๐ Constructorโ
OrgChartController<T>({
required List<T> items,
required String Function(T) idProvider,
required String? Function(T) toProvider,
T Function(T, String?)? toSetter,
Size boxSize = const Size(200, 100),
double spacing = 20,
double runSpacing = 50,
GraphOrientation orientation = GraphOrientation.topToBottom,
int leafColumns = 2,
})
Constructor Parametersโ
Parameter | Type | Required | Default | Description |
---|---|---|---|---|
items | List<T> | โ | - | The list of data items to display in the chart |
idProvider | String Function(T) | โ | - | Function that returns a unique ID for each item |
toProvider | String? Function(T) | โ | - | Function that returns the parent ID for each item |
toSetter | T Function(T, String?) | โ | null | Function to create a copy of item with new parent ID |
boxSize | Size | โ | Size(200, 100) | Size of each node box |
spacing | double | โ | 20 | Horizontal spacing between nodes |
runSpacing | double | โ | 50 | Vertical spacing between levels |
orientation | GraphOrientation | โ | topToBottom | Layout orientation |
leafColumns | int | โ | 2 | Number of columns for leaf node arrangement |
๐ง Propertiesโ
Read-Only Propertiesโ
Property | Type | Description |
---|---|---|
nodes | List<Node<T>> | All nodes in the chart |
roots | List<Node<T>> | Root nodes (nodes without parents) |
items | List<T> | All data items |
uniqueNodeId | String | Generates a unique node ID |
repaintBoundaryKey | GlobalKey | Key for export functionality |
Configurable Propertiesโ
Property | Type | Description |
---|---|---|
orientation | GraphOrientation | Chart orientation (vertical/horizontal) |
boxSize | Size | Size of node boxes |
spacing | double | Horizontal spacing between nodes |
runSpacing | double | Vertical spacing between levels |
leafColumns | int | Column arrangement for leaf nodes |
viewerController | CustomInteractiveViewerController? | Controller for zoom/pan |
๐ Methodsโ
Data Managementโ
addItem
โ
Adds a single item to the chart.
void addItem(
T item, {
bool recalculatePosition = true,
bool centerGraph = false,
})
Example:
controller.addItem(
Employee(id: '10', name: 'New Employee', managerId: '2'),
centerGraph: true,
);
addItems
โ
Adds multiple items to the chart.
void addItems(
List<T> items, {
bool recalculatePosition = true,
bool centerGraph = false,
})
removeItem
โ
Removes an item and handles its descendants.
void removeItem(
String? id,
ActionOnNodeRemoval action, {
bool recalculatePosition = true,
bool centerGraph = false,
})
ActionOnNodeRemoval Options:
Action | Description |
---|---|
unlinkDescendants | Makes descendants root nodes |
connectDescendantsToParent | Connects descendants to removed node's parent |
removeDescendants | Removes node and all descendants |
Example:
// Remove manager and promote subordinates to their manager's level
controller.removeItem(
'5',
ActionOnNodeRemoval.connectDescendantsToParent,
);
// Remove entire department
controller.removeItem(
'3',
ActionOnNodeRemoval.removeDescendants,
);
updateItem
โ
Updates an existing item in the chart.
void updateItem(
T item, {
bool recalculatePosition = true,
bool centerGraph = false,
})
replaceAll
โ
Replaces all items in the chart.
void replaceAll(
List<T> items, {
bool recalculatePosition = true,
bool centerGraph = false,
})
clearItems
โ
Removes all items from the chart.
void clearItems({
bool recalculatePosition = true,
bool centerGraph = false,
})
Layout and Positioningโ
calculatePosition
โ
Recalculates node positions.
void calculatePosition({bool center = true})
switchOrientation
โ
Switches between vertical and horizontal layouts.
void switchOrientation({
GraphOrientation? orientation,
bool center = true,
})
Example:
// Toggle orientation
controller.switchOrientation();
// Set specific orientation
controller.switchOrientation(
orientation: GraphOrientation.leftToRight,
);
centerNode
โ
Centers the view on a specific node.
Future<void> centerNode(
String nodeId, {
double? scale,
bool animate = true,
Duration duration = const Duration(milliseconds: 300),
Curve curve = Curves.easeInOut,
})
Example:
// Center on CEO with zoom
await controller.centerNode(
'ceo-001',
scale: 1.5,
animate: true,
);
Node Queriesโ
getSubNodes
โ
Gets direct children of a node.
List<Node<T>> getSubNodes(Node<T> node)
getLevel
โ
Gets the hierarchical level of a node.
int getLevel(Node<T> node)
Example:
final node = controller.nodes.first;
final level = controller.getLevel(node); // 1 for root, 2 for children, etc.
isNodeHidden
โ
Checks if a node is currently hidden.
bool isNodeHidden(Node<T> node)
isSubNode
โ
Checks if one node is a descendant of another.
bool isSubNode(Node<T> child, Node<T> parent)
getOverlapping
โ
Gets nodes that overlap with a given node.
List<Node<T>> getOverlapping(Node<T> node)
Export Functionsโ
exportAsImage
โ
Exports the chart as a PNG image.
Future<Uint8List?> exportAsImage()
Example:
final imageBytes = await controller.exportAsImage();
if (imageBytes != null) {
// Save to file or share
await saveImageToGallery(imageBytes);
}
exportAsPdf
โ
Exports the chart as a PDF document.
Future<pw.Document?> exportAsPdf()
๐ฏ Complete Exampleโ
Here's a comprehensive example showing the controller in action:
class OrgChartManager extends StatefulWidget {
_OrgChartManagerState createState() => _OrgChartManagerState();
}
class _OrgChartManagerState extends State<OrgChartManager> {
late OrgChartController<Employee> controller;
final viewerController = CustomInteractiveViewerController();
void initState() {
super.initState();
// Initialize controller
controller = OrgChartController<Employee>(
items: loadEmployees(),
idProvider: (emp) => emp.id,
toProvider: (emp) => emp.managerId,
toSetter: (emp, newManagerId) => emp.copyWith(
managerId: newManagerId,
),
boxSize: Size(220, 120),
spacing: 40,
runSpacing: 80,
orientation: GraphOrientation.topToBottom,
leafColumns: 3,
);
// Set viewer controller for zoom/pan
controller.setViewerController(viewerController);
}
// Add new employee
void hireEmployee(Employee newEmployee) {
setState(() {
controller.addItem(newEmployee, centerGraph: true);
});
}
// Remove employee with options
void fireEmployee(String employeeId) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Remove Employee'),
content: Text('What should happen to their reports?'),
actions: [
TextButton(
onPressed: () {
controller.removeItem(
employeeId,
ActionOnNodeRemoval.connectDescendantsToParent,
);
Navigator.pop(context);
},
child: Text('Promote to Same Level'),
),
TextButton(
onPressed: () {
controller.removeItem(
employeeId,
ActionOnNodeRemoval.removeDescendants,
);
Navigator.pop(context);
},
child: Text('Remove Entire Team'),
),
],
),
);
}
// Reorganize employee
void changeManager(Employee employee, String newManagerId) {
final updated = employee.copyWith(managerId: newManagerId);
controller.updateItem(updated);
}
// Search and focus
void searchEmployee(String query) async {
final employee = controller.items.firstWhere(
(emp) => emp.name.toLowerCase().contains(query.toLowerCase()),
);
if (employee != null) {
await controller.centerNode(
employee.id,
scale: 2.0,
animate: true,
);
}
}
// Export functions
void exportChart() async {
final action = await showDialog<String>(
context: context,
builder: (context) => SimpleDialog(
title: Text('Export Chart'),
children: [
SimpleDialogOption(
onPressed: () => Navigator.pop(context, 'image'),
child: Text('Export as Image'),
),
SimpleDialogOption(
onPressed: () => Navigator.pop(context, 'pdf'),
child: Text('Export as PDF'),
),
],
),
);
if (action == 'image') {
final bytes = await controller.exportAsImage();
// Handle image bytes
} else if (action == 'pdf') {
final pdf = await controller.exportAsPdf();
// Handle PDF document
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Organization Chart'),
actions: [
IconButton(
icon: Icon(Icons.rotate_90_degrees_ccw),
onPressed: () => controller.switchOrientation(),
),
IconButton(
icon: Icon(Icons.download),
onPressed: exportChart,
),
],
),
body: OrgChart<Employee>(
controller: controller,
viewerController: viewerController,
// ... rest of configuration
),
);
}
}
๐ก Best Practicesโ
1. Efficient Updatesโ
// Bad: Multiple individual updates
employees.forEach((emp) => controller.addItem(emp));
// Good: Batch update
controller.addItems(employees);
3. Performance Optimizationโ
// Disable recalculation for batch operations
controller.addItem(item1, recalculatePosition: false);
controller.addItem(item2, recalculatePosition: false);
controller.addItem(item3, recalculatePosition: false);
controller.calculatePosition(); // Calculate once at the end
Dynamic Data Loadingโ
Load data on-demand as nodes expand:
class LazyOrgChartController<T> extends OrgChartController<T> {
final Future<List<T>> Function(String parentId) loadChildren;
Future<void> expandNode(String nodeId) async {
final children = await loadChildren(nodeId);
addItems(children);
}
}
Next: Learn about the OrgChart Widget โ