Basic Usage
Learn how to create your first organizational chart or family tree in just 5 minutes!
๐ Quick Startโ
Step 1: Import the Packageโ
import 'package:org_chart/org_chart.dart';
import 'package:flutter/material.dart';
Step 2: Define Your Data Modelโ
- For OrgChart
- For Genogram
// Define your data model
class Employee {
final String id;
final String name;
final String? managerId;
final String position;
final String department;
final String imageUrl;
Employee({
required this.id,
required this.name,
this.managerId,
required this.position,
required this.department,
required this.imageUrl,
});
}
// Create sample data
final employees = [
Employee(
id: '1',
name: 'Jane Smith',
managerId: null,
position: 'CEO',
department: 'Executive',
imageUrl: 'https://i.pravatar.cc/150?img=1',
),
Employee(
id: '2',
name: 'John Doe',
managerId: '1',
position: 'CTO',
department: 'Technology',
imageUrl: 'https://i.pravatar.cc/150?img=2',
),
Employee(
id: '3',
name: 'Sarah Johnson',
managerId: '1',
position: 'CFO',
department: 'Finance',
imageUrl: 'https://i.pravatar.cc/150?img=3',
),
Employee(
id: '4',
name: 'Mike Wilson',
managerId: '2',
position: 'Lead Developer',
department: 'Technology',
imageUrl: 'https://i.pravatar.cc/150?img=4',
),
Employee(
id: '5',
name: 'Emily Brown',
managerId: '2',
position: 'QA Manager',
department: 'Technology',
imageUrl: 'https://i.pravatar.cc/150?img=5',
),
];
// Define your data model
class Person {
final String id;
final String name;
final String? fatherId;
final String? motherId;
final List<String> spouseIds;
final int gender; // 0 = male, 1 = female
final DateTime birthDate;
final bool isAlive;
Person({
required this.id,
required this.name,
this.fatherId,
this.motherId,
this.spouseIds = const [],
required this.gender,
required this.birthDate,
this.isAlive = true,
});
}
// Create sample family data
final familyMembers = [
// Grandparents
Person(
id: '1',
name: 'Robert Smith',
gender: 0,
birthDate: DateTime(1940, 5, 15),
spouseIds: ['2'],
),
Person(
id: '2',
name: 'Mary Smith',
gender: 1,
birthDate: DateTime(1942, 8, 20),
spouseIds: ['1'],
),
// Parents
Person(
id: '3',
name: 'James Smith',
fatherId: '1',
motherId: '2',
gender: 0,
birthDate: DateTime(1965, 3, 10),
spouseIds: ['4'],
),
Person(
id: '4',
name: 'Linda Johnson',
gender: 1,
birthDate: DateTime(1967, 11, 25),
spouseIds: ['3'],
),
// Children
Person(
id: '5',
name: 'Tom Smith',
fatherId: '3',
motherId: '4',
gender: 0,
birthDate: DateTime(1990, 7, 8),
),
Person(
id: '6',
name: 'Emma Smith',
fatherId: '3',
motherId: '4',
gender: 1,
birthDate: DateTime(1993, 4, 15),
),
];
Step 3: Create the Controllerโ
- OrgChartController
- GenogramController
class MyOrgChartScreen extends StatefulWidget {
_MyOrgChartScreenState createState() => _MyOrgChartScreenState();
}
class _MyOrgChartScreenState extends State<MyOrgChartScreen> {
late OrgChartController<Employee> controller;
void initState() {
super.initState();
// Initialize the controller
controller = OrgChartController<Employee>(
items: employees,
// Required: Provide unique ID for each item
idProvider: (employee) => employee.id,
// Required: Provide parent ID (manager for org chart)
toProvider: (employee) => employee.managerId,
// Optional: Update parent when reorganizing
toSetter: (employee, newManagerId) => Employee(
id: employee.id,
name: employee.name,
managerId: newManagerId,
position: employee.position,
department: employee.department,
imageUrl: employee.imageUrl,
),
// Optional: Customize box size
boxSize: Size(220, 120),
// Optional: Customize spacing
spacing: 40,
runSpacing: 80,
// Optional: Set orientation
orientation: GraphOrientation.topToBottom,
// Optional: Arrange leaf nodes in columns
leafColumns: 3,
);
}
Widget build(BuildContext context) {
// Continue to Step 4...
}
}
class MyGenogramScreen extends StatefulWidget {
_MyGenogramScreenState createState() => _MyGenogramScreenState();
}
class _MyGenogramScreenState extends State<MyGenogramScreen> {
late GenogramController<Person> controller;
void initState() {
super.initState();
// Initialize the controller
controller = GenogramController<Person>(
items: familyMembers,
// Required: Provide unique ID for each person
idProvider: (person) => person.id,
// Required: Provide father's ID
fatherProvider: (person) => person.fatherId,
// Required: Provide mother's ID
motherProvider: (person) => person.motherId,
// Required: Provide list of spouse IDs
spousesProvider: (person) => person.spouseIds,
// Required: Provide gender (0=male, 1=female)
genderProvider: (person) => person.gender,
// Optional: Customize box size
boxSize: Size(150, 150),
// Optional: Customize spacing
spacing: 30,
runSpacing: 60,
// Optional: Set orientation
orientation: GraphOrientation.topToBottom,
);
}
Widget build(BuildContext context) {
// Continue to Step 4...
}
}
Step 4: Build the Chart Widgetโ
- OrgChart Widget
- Genogram Widget
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Company Organization'),
actions: [
IconButton(
icon: Icon(Icons.swap_horiz),
onPressed: () {
// Switch orientation
controller.switchOrientation();
},
),
],
),
body: OrgChart<Employee>(
controller: controller,
// Build each node
builder: (NodeBuilderDetails<Employee> details) {
return Card(
elevation: details.isBeingDragged ? 8 : 4,
color: details.level == 1
? Colors.blue.shade100
: Colors.white,
child: Container(
padding: EdgeInsets.all(12),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Employee photo
CircleAvatar(
radius: 25,
backgroundImage: NetworkImage(details.item.imageUrl),
),
SizedBox(height: 8),
// Name
Text(
details.item.name,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
// Position
Text(
details.item.position,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
// Department badge
Container(
margin: EdgeInsets.only(top: 4),
padding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(10),
),
child: Text(
details.item.department,
style: TextStyle(fontSize: 10),
),
),
// Expand/Collapse button if has children
if (hasSubordinates(details.item))
IconButton(
icon: Icon(
details.nodesHidden
? Icons.expand_more
: Icons.expand_less,
size: 20,
),
onPressed: () {
details.hideNodes();
},
),
],
),
),
);
},
// Optional: Enable drag and drop
isDraggable: true,
// Optional: Handle drop events
onDrop: (draggedEmployee, targetEmployee, isSubordinate) {
setState(() {
// Update your data model
updateEmployeeManager(
draggedEmployee,
targetEmployee,
);
// Refresh the chart
controller.replaceAll(updatedEmployees);
});
},
// Optional: Customize animations
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
// Optional: Customize edge appearance
linePaint: Paint()
..color = Colors.grey.shade400
..strokeWidth = 2.0,
arrowStyle: SolidGraphArrow(),
cornerRadius: 10,
lineEndingType: LineEndingType.arrow,
),
);
}
// Helper method to check if employee has subordinates
bool hasSubordinates(Employee employee) {
return employees.any((e) => e.managerId == employee.id);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Family Tree'),
actions: [
IconButton(
icon: Icon(Icons.swap_horiz),
onPressed: () {
// Switch orientation
controller.switchOrientation();
},
),
],
),
body: Genogram<Person>(
controller: controller,
// Build each node
builder: (NodeBuilderDetails<Person> details) {
final person = details.item;
final age = DateTime.now().year - person.birthDate.year;
return Container(
width: 140,
height: 140,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: person.gender == 0
? Colors.blue.shade100
: Colors.pink.shade100,
border: Border.all(
color: person.gender == 0
? Colors.blue
: Colors.pink,
width: 3,
),
boxShadow: [
if (details.isBeingDragged)
BoxShadow(
color: Colors.black26,
blurRadius: 8,
offset: Offset(0, 4),
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Gender icon
Icon(
person.gender == 0
? Icons.male
: Icons.female,
size: 30,
color: person.gender == 0
? Colors.blue
: Colors.pink,
),
SizedBox(height: 4),
// Name
Text(
person.name,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
),
textAlign: TextAlign.center,
),
// Birth year
Text(
'${person.birthDate.year}',
style: TextStyle(
fontSize: 10,
color: Colors.grey[600],
),
),
// Age
if (person.isAlive)
Text(
'Age: $age',
style: TextStyle(
fontSize: 10,
color: Colors.grey[600],
),
),
// Deceased indicator
if (!person.isAlive)
Text(
'โ',
style: TextStyle(
fontSize: 16,
color: Colors.grey[700],
),
),
],
),
);
},
// Configure marriage styles
edgeConfig: GenogramEdgeConfig(
defaultMarriageStyle: MarriageStyle(
lineStyle: MarriageLineStyle.solid(
color: Colors.black,
strokeWidth: 2.0,
),
),
divorcedMarriageStyle: MarriageStyle(
lineStyle: MarriageLineStyle.dashed(
color: Colors.red,
strokeWidth: 2.0,
),
decorator: DivorceDecorator(
color: Colors.red,
slashLength: 12.0,
),
),
childStrokeWidth: 1.5,
marriageColors: [
Colors.blue,
Colors.green,
Colors.orange,
],
),
// Optional: Provide marriage status
marriageStatusProvider: (person, spouse) {
// Implement your logic
return MarriageStatus.married;
},
// Optional: Enable drag and drop
isDraggable: true,
// Optional: Customize animations
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
),
);
}
๐จ Complete Working Exampleโ
Here's a complete, runnable example you can copy and paste:
main.dart
import 'package:flutter/material.dart';
import 'package:org_chart/org_chart.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Org Chart Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: BasicOrgChartDemo(),
);
}
}
class BasicOrgChartDemo extends StatefulWidget {
_BasicOrgChartDemoState createState() => _BasicOrgChartDemoState();
}
class _BasicOrgChartDemoState extends State<BasicOrgChartDemo> {
late OrgChartController<Map<String, dynamic>> controller;
final List<Map<String, dynamic>> employees = [
{'id': '1', 'name': 'Jane CEO', 'title': 'Chief Executive', 'managerId': null},
{'id': '2', 'name': 'John CTO', 'title': 'Chief Technology', 'managerId': '1'},
{'id': '3', 'name': 'Sarah CFO', 'title': 'Chief Finance', 'managerId': '1'},
{'id': '4', 'name': 'Mike Dev', 'title': 'Lead Developer', 'managerId': '2'},
{'id': '5', 'name': 'Emily QA', 'title': 'QA Manager', 'managerId': '2'},
{'id': '6', 'name': 'Tom Jr Dev', 'title': 'Junior Developer', 'managerId': '4'},
{'id': '7', 'name': 'Lisa Analyst', 'title': 'Financial Analyst', 'managerId': '3'},
];
void initState() {
super.initState();
controller = OrgChartController(
items: employees,
idProvider: (item) => item['id'],
toProvider: (item) => item['managerId'],
boxSize: Size(180, 100),
spacing: 30,
runSpacing: 60,
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Organization Chart'),
centerTitle: true,
actions: [
IconButton(
icon: Icon(Icons.rotate_90_degrees_ccw),
tooltip: 'Switch Orientation',
onPressed: () => controller.switchOrientation(),
),
],
),
body: OrgChart<Map<String, dynamic>>(
controller: controller,
builder: (details) {
final item = details.item;
return Card(
elevation: details.isBeingDragged ? 8 : 3,
color: details.level == 1
? Theme.of(context).primaryColor.withOpacity(0.1)
: Colors.white,
child: Container(
padding: EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.person,
size: 30,
color: Theme.of(context).primaryColor,
),
SizedBox(height: 8),
Text(
item['name'],
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
Text(
item['title'],
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
),
);
},
isDraggable: true,
linePaint: Paint()
..color = Colors.grey.shade400
..strokeWidth = 2.0,
arrowStyle: SolidGraphArrow(),
cornerRadius: 10,
),
);
}
}
๐ฏ What's Next?โ
Congratulations! You've created your first chart. Now explore more advanced features:
๐ก Pro Tip: Start simple with basic nodes, then gradually add features like drag & drop, animations, and custom styling as you become more comfortable with the API.