flutter_roadmap_viewer 0.1.6
flutter_roadmap_viewer: ^0.1.6 copied to clipboard
A highly customizable roadmap and skill tree visualization widget with interactive pan/zoom, multiple layout orientations, and progress tracking.
Flutter Roadmap Viewer #
A highly customizable roadmap and skill tree visualization widget for Flutter with interactive pan/zoom, multiple layout orientations, and progress tracking.
Features #
- Hierarchical Layout - Parent-aware positioning algorithm that visually represents dependencies
- Customizable Themes - Pre-built themes (default, dark, light, vibrant) or create your own
- Interactive - Pan, zoom, and tap interactions with fit-to-screen button
- Progress Tracking - Four status types: Not Started, Current, Complete, Skipped
- Multiple Orientations - Horizontal (traditional) or vertical layouts
- Edge Styles - Curved bezier or orthogonal (right-angle) connections
- Custom Rendering - Full control with custom node builders and style providers
- JSON Support - Easy serialization with legacy format migration
Installation #
Add to your pubspec.yaml:
dependencies:
flutter_roadmap_viewer: ^0.1.3
Then run:
flutter pub get
Quick Start #
import 'package:flutter_roadmap_viewer/flutter_roadmap_viewer.dart';
final nodes = [
RoadmapNode(
id: 'flutter-basics',
name: 'Flutter Basics',
nodeType: 'skill',
layer: 0,
parentIds: [],
childIds: ['widgets', 'state'],
progress: ProgressStatus.complete,
),
RoadmapNode(
id: 'widgets',
name: 'Widgets',
nodeType: 'skill',
layer: 1,
parentIds: ['flutter-basics'],
childIds: [],
progress: ProgressStatus.current,
),
RoadmapNode(
id: 'state',
name: 'State Management',
nodeType: 'skill',
layer: 1,
parentIds: ['flutter-basics'],
childIds: [],
progress: ProgressStatus.notStarted,
),
];
RoadmapViewer(
nodes: nodes,
onNodeTap: (node) => print('Tapped: ${node.name}'),
)
Usage Examples #
With Dark Theme #
RoadmapViewer(
nodes: myNodes,
theme: RoadmapTheme.darkTheme(),
edgeStyle: EdgeStyle.curved,
)
With Custom Theme #
RoadmapViewer(
nodes: myNodes,
theme: const RoadmapTheme(
completeNodeColor: Color(0xFF00C853),
currentNodeColor: Color(0xFF2979FF),
notStartedNodeColor: Color(0xFF546E7A),
skippedNodeColor: Color(0xFFFF6D00),
backgroundColor: Colors.white,
edgeWidth: 4.0,
),
)
With Vertical Orientation #
RoadmapViewer(
nodes: myNodes,
orientation: RoadmapOrientation.vertical,
edgeStyle: EdgeStyle.curved,
)
With Custom Node Styling #
RoadmapViewer(
nodes: myNodes,
nodeStyleProvider: (node) {
if (node.progress == ProgressStatus.complete) {
return RoadmapNodeStyle(
width: 150,
height: 70,
borderColor: Colors.green,
borderWidth: 5,
backgroundColor: Colors.green.withOpacity(0.1),
);
}
return RoadmapNodeStyle.skillStyle();
},
)
With Fully Custom Nodes #
RoadmapViewer(
nodes: myNodes,
nodeBuilder: (context, node) {
return Container(
width: 160,
height: 80,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue, Colors.purple],
),
borderRadius: BorderRadius.circular(16),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.star, color: Colors.white),
SizedBox(height: 4),
Text(
node.name ?? node.id,
style: TextStyle(color: Colors.white),
),
],
),
);
},
)
With Custom Data #
final nodes = [
RoadmapNode(
id: 'python',
name: 'Python Programming',
nodeType: 'skill',
layer: 0,
parentIds: [],
childIds: [],
progress: ProgressStatus.complete,
customData: {
'difficulty': 'beginner',
'estimatedHours': 40,
'icon': 'assets/python.png',
'tags': ['programming', 'backend'],
},
),
];
RoadmapViewer(
nodes: nodes,
nodeBuilder: (context, node) {
final difficulty = node.customData?['difficulty'] ?? 'unknown';
final hours = node.customData?['estimatedHours'] ?? 0;
return MyCustomWidget(
title: node.name,
difficulty: difficulty,
hours: hours,
);
},
)
JSON Serialization #
Standard Format #
// Serialize
final json = node.toJson();
// Deserialize
final node = RoadmapNode.fromJson(json);
Legacy Format Support #
If you have existing data with progressPercent (0-100):
final json = {
'id': 'node-1',
'nodeType': 'skill',
'layer': 0,
'parentIds': [],
'childIds': [],
'progressPercent': 85,
};
final node = RoadmapNode.fromJsonLegacy(json);
print(node.progress); // ProgressStatus.complete
Conversion rules:
- 80-100 → ProgressStatus.complete
- 1-79 → ProgressStatus.current
- 0 → ProgressStatus.notStarted
API Reference #
RoadmapViewer #
Main widget for displaying roadmaps.
Required Parameters:
- nodes: List
Optional Parameters:
- width: double? - Widget width (defaults to screen width)
- height: double? - Widget height (defaults to screen height)
- courseLabel: String - Override label for root course nodes
- edgeStyle: EdgeStyle - Edge connection style (default: orthogonal)
- orientation: RoadmapOrientation - Layout direction (default: horizontal)
- nodeWidth: double - Width of nodes (default: 120)
- nodeHeight: double - Height of nodes (default: 60)
- horizontalSpacing: double - Space between nodes horizontally (default: 100)
- verticalSpacing: double - Space between nodes vertically (default: 50)
- onNodeTap: Function(RoadmapNode)? - Callback when node is tapped
- showFitButton: bool - Show fit-to-screen button (default: true)
- minScale: double - Minimum zoom level (default: 0.3)
- maxScale: double - Maximum zoom level (default: 3.0)
- theme: RoadmapTheme? - Theme configuration
- nodeBuilder: Widget Function(BuildContext, RoadmapNode)? - Custom node builder
- nodeStyleProvider: RoadmapNodeStyle Function(RoadmapNode)? - Node style provider
RoadmapNode #
Represents a single node in the roadmap.
Required Fields:
- id: String - Unique identifier
- nodeType: String - Type of node (e.g., 'skill', 'course')
- layer: int - Hierarchy level (0 = root)
- parentIds: List
- childIds: List
Optional Fields:
- name: String? - Display name (defaults to id)
- progress: ProgressStatus - Progress status (default: notStarted)
- customData: Map<String, dynamic>? - Custom metadata
ProgressStatus #
Enum representing node progress:
- ProgressStatus.notStarted - Not yet started
- ProgressStatus.current - Currently in progress
- ProgressStatus.complete - Completed
- ProgressStatus.skipped - Skipped or not applicable
RoadmapTheme #
Theme configuration for the roadmap.
Pre-built Themes:
- RoadmapTheme.defaultTheme - Soft blue-mint gradient
- RoadmapTheme.darkTheme() - Dark background with muted colors
- RoadmapTheme.lightTheme() - White background with bright colors
- RoadmapTheme.vibrantTheme() - Bold, saturated colors
RoadmapNodeStyle #
Style configuration for individual nodes.
Pre-built Styles:
- RoadmapNodeStyle.defaultStyle - Standard style with shadows
- RoadmapNodeStyle.courseStyle() - Pill-shaped (150x70)
- RoadmapNodeStyle.skillStyle() - Rounded rectangle (120x60)
Layout Algorithm #
The package uses a parent-aware layout engine that:
- Groups nodes by layer
- Places root nodes in a grid
- Positions children by averaging parent positions
- Resolves collisions by spreading overlapping nodes
- Centers content within the canvas
Note: Currently all nodes must have the same dimensions for proper layout.
Best Practices #
Node Sizing #
All nodes should use consistent dimensions:
RoadmapViewer(
nodes: nodes,
nodeWidth: 120,
nodeHeight: 60,
nodeStyleProvider: (node) {
return RoadmapNodeStyle(
width: 120, // Same as nodeWidth
height: 60, // Same as nodeHeight
);
},
)
Large Roadmaps #
For roadmaps with 50+ nodes:
- Use smaller node sizes
- Increase spacing
- Provide search/filter functionality
- Consider breaking into multiple views
Performance #
- Avoid rebuilding the entire node list frequently
- Use const constructors where possible
- Consider caching node positions for static roadmaps
Migration from Legacy Format #
If you have data with progressPercent:
final oldNodes = jsonData.map((json) => {
'id': json['id'],
'progressPercent': json['progressPercent'],
});
final newNodes = oldNodes.map((json) =>
RoadmapNode.fromJsonLegacy(json)
).toList();
Troubleshooting #
Nodes Not Appearing #
- Check that all node IDs are unique
- Verify parent/child relationships are valid
- Ensure layer numbers are consecutive
Layout Issues #
- Confirm all nodes have the same width/height
- Check that nodeStyleProvider returns consistent dimensions
- Verify spacing values are reasonable
Theme Not Applying #
- Ensure theme is passed to RoadmapViewer
- Check that theme colors are opaque
- Verify custom node builders aren't overriding theme colors
Examples #
See the example directory for a complete demo app showing:
- Basic roadmap
- Multiple themes
- Custom styling
- Different orientations
- Interactive features
License #
This project is licensed under the MIT License - see the LICENSE file for details.
Changelog #
See CHANGELOG.md for version history.