Skip to main content

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โ€‹

ParameterTypeRequiredDefaultDescription
itemsList<T>โœ…-The list of data items to display in the chart
idProviderString Function(T)โœ…-Function that returns a unique ID for each item
toProviderString? Function(T)โœ…-Function that returns the parent ID for each item
toSetterT Function(T, String?)โŒnullFunction to create a copy of item with new parent ID
boxSizeSizeโŒSize(200, 100)Size of each node box
spacingdoubleโŒ20Horizontal spacing between nodes
runSpacingdoubleโŒ50Vertical spacing between levels
orientationGraphOrientationโŒtopToBottomLayout orientation
leafColumnsintโŒ2Number of columns for leaf node arrangement

๐Ÿ”ง Propertiesโ€‹

Read-Only Propertiesโ€‹

PropertyTypeDescription
nodesList<Node<T>>All nodes in the chart
rootsList<Node<T>>Root nodes (nodes without parents)
itemsList<T>All data items
uniqueNodeIdStringGenerates a unique node ID
repaintBoundaryKeyGlobalKeyKey for export functionality

Configurable Propertiesโ€‹

PropertyTypeDescription
orientationGraphOrientationChart orientation (vertical/horizontal)
boxSizeSizeSize of node boxes
spacingdoubleHorizontal spacing between nodes
runSpacingdoubleVertical spacing between levels
leafColumnsintColumn arrangement for leaf nodes
viewerControllerCustomInteractiveViewerController?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:

ActionDescription
unlinkDescendantsMakes descendants root nodes
connectDescendantsToParentConnects descendants to removed node's parent
removeDescendantsRemoves 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 โ†’