Save Points Chart π
A modern, high-performance Flutter charting library with full theme support, featuring 17 chart types, Material 3 design, smooth animations, and interactive context menus with visual border highlighting.
π Live Demo
Try it out in your browser: Live Demo β
π₯ Demo
![Showcase Coach Preview]
InfoGraphic
π― Features
- 17 Chart Types: Line, Bar, Area, Stacked Area, Pie, Donut, Radial, Sparkline, Scatter, Bubble, Radar, Gauge, Spline, Step Line, Stacked Column, Pyramid, and Funnel charts
- Zero Dependencies: No external packages required - uses only Flutter SDK
- Modern Design: Material 3, Neumorphism, and Glassmorphism effects
- Full Theme Support: Automatic light/dark theme adaptation with InheritedWidget
- Interactive Context Menus: Awesome context menus on tap with actions
- Click Interaction: All charts support click interaction with visual border highlighting
- Visual Border Highlighting: White borders (3-4px) appear on selected elements for clear feedback
- Haptic Feedback: Tactile feedback on all chart interactions for better UX
- Hover Support: Mouse hover effects on Line, Bar, Area, Scatter, Bubble, and Radial charts
- High Performance: Optimized rendering with cached calculations and minimal rebuilds
- Smooth Animations: Beautiful entrance animations for all chart types
- Clean Architecture: Modular, reusable, and maintainable code
- Highly Customizable: Extensive configuration options
π¦ Installation
Add this to your package's pubspec.yaml file:
dependencies:
save_points_chart: ^1.7.7
Then run:
flutter pub get
Note: This package has zero external dependencies! Charts work perfectly without any state management - just pass a
ChartThemedirectly. The includedThemeProvideruses Flutter's built-inInheritedWidgetfor theme management.
π Quick Start
import 'package:save_points_chart/save_points_chart.dart';
LineChartWidget(
dataSets: [
ChartDataSet(
label: 'Day 1',
color: Colors.blue,
dataPoint: ChartDataPoint(x: 0, y: 10),
),
ChartDataSet(
label: 'Day 2',
color: Colors.blue,
dataPoint: ChartDataPoint(x: 1, y: 20),
),
ChartDataSet(
label: 'Day 3',
color: Colors.blue,
dataPoint: ChartDataPoint(x: 2, y: 15),
),
],
theme: ChartTheme.light(),
title: 'Sales Trend',
subtitle: 'Last 3 months',
)
The demo showcases all 17 chart types with interactive features, click interactions, border highlighting, animations, and theme switching.
The video is also included in the published package on pub.dev.
π¦ Dependencies
- Zero external dependencies! - Uses only Flutter SDK
- No external charting library - Uses custom
CustomPainterimplementations for full control - Built-in state management -
ThemeProvideruses Flutter'sInheritedWidget(no provider package needed)
ποΈ Architecture
lib/
βββ models/ # Data models (ChartDataPoint, PieData, ChartDataSet)
βββ theme/ # Theme and config (ChartTheme, ChartsConfig)
βββ painters/ # Custom painters (BaseChartPainter, LineChartPainter, etc.)
βββ widgets/ # Chart widgets (Line, Bar, Area, Stacked Area, Pie, Donut, Radial, Sparkline, Scatter, Bubble, Radar, Gauge, Spline, Step Line, Stacked Column, Pyramid, Funnel)
βββ providers/ # Theme provider for state management
βββ data/ # Sample data generators
βββ screens/ # Demo screens
π¨ Design Decisions
Chart Implementation: Custom CustomPainter
Why Custom Implementation?
- β Zero external dependencies - No charting library required
- β Full control - Complete customization of every aspect
- β Lightweight - No unnecessary features or bloat
- β High performance - Optimized rendering with direct canvas access
- β Theme-aware - Built from the ground up with theme support
- β Maintainable - Simple, understandable code structure
Architecture:
BaseChartPainter- Common utilities (grid, axes, labels)- Specialized painters for each chart type
- Efficient rendering with minimal repaints
- Smooth animations through Flutter's animation system
Theme System
- Adaptive Colors: Automatically adjusts based on light/dark mode
- Material 3: Uses Material Design 3 principles
- Gradient Support: Modern gradient fills for visual appeal
- Shadow System: Configurable elevation and shadows
- Glassmorphism: Optional frosted glass effect
- Neumorphism: Optional soft shadow effect
ChartsConfig (shared configuration)
ChartsConfig lets you set theme, visual effects, empty/error UI, and shadows in one place. Pass it to any chart via the config parameter; config values override the chartβs own parameters when provided. All fields are optional.
- theme β ChartTheme for colors and styling
- useGlassmorphism / useNeumorphism β Container effects
- emptyWidget / emptyMessage β Custom empty state when thereβs no data
- errorWidget / errorMessage β Custom error state
- boxShadow β Container shadows
Use one config instance across multiple charts for consistent look and behavior. See Using ChartsConfig for a full example and parameter table; lib/theme/charts_config.dart for the full API and dartdoc.
π Usage
Basic Example
import 'package:flutter/material.dart';
import 'package:save_points_chart/save_points_chart.dart';
LineChartWidget(
dataSets: [
ChartDataSet(
label: 'Day 1',
color: Colors.blue,
dataPoint: ChartDataPoint(x: 0, y: 10),
),
ChartDataSet(
label: 'Day 2',
color: Colors.blue,
dataPoint: ChartDataPoint(x: 1, y: 20),
),
ChartDataSet(
label: 'Day 3',
color: Colors.blue,
dataPoint: ChartDataPoint(x: 2, y: 15),
),
],
theme: ChartTheme.light(),
title: 'Sales Trend',
subtitle: 'Last 3 months',
)
Using ChartsConfig
ChartsConfig lets you set theme, visual effects, empty/error UI, and shadows in one place. Pass it to any chart via the config parameter; config values override the chartβs own parameters when provided. All fields are optional. Use one config instance across multiple charts for consistent look and behavior.
import 'package:flutter/material.dart';
import 'package:save_points_chart/save_points_chart.dart';
final config = ChartsConfig(
theme: ChartTheme.light(),
useGlassmorphism: true,
emptyMessage: 'No data yet',
errorMessage: 'Something went wrong',
);
LineChartWidget(
dataSets: dataSets,
config: config,
title: 'Sales Trend',
subtitle: 'Last 3 months',
)
| Parameter | Description |
|---|---|
theme |
ChartTheme for colors and styling |
useGlassmorphism / useNeumorphism |
Container effects (frosted glass / soft shadow) |
emptyWidget / emptyMessage |
Custom empty state when thereβs no data |
errorWidget / errorMessage |
Custom error state |
boxShadow |
Container shadows |
With Theme Provider (Optional)
If you want to use the included ThemeProvider for automatic theme switching, wrap your app with it:
Complete App Setup:
import 'package:flutter/material.dart';
import 'package:save_points_chart/save_points_chart.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ThemeProvider(
child: _MaterialAppWithTheme(),
);
}
}
class _MaterialAppWithTheme extends StatelessWidget {
const _MaterialAppWithTheme();
@override
Widget build(BuildContext context) {
final themeProvider = ThemeProvider.of(context);
return MaterialApp(
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: themeProvider.themeMode,
home: MyHomePage(),
);
}
}
Using ThemeProvider in Widgets:
// Get theme provider anywhere in your widget tree
final themeProvider = ThemeProvider.of(context);
// Access chart theme
LineChartWidget(
dataSets: dataSets,
theme: themeProvider.chartTheme,
useGlassmorphism: true,
)
// Toggle theme
IconButton(
icon: Icon(themeProvider.isDarkMode ? Icons.light_mode : Icons.dark_mode),
onPressed: () => themeProvider.toggleTheme(),
)
Note: ThemeProvider uses Flutter's built-in InheritedWidget, so no external dependencies are required! The widget automatically rebuilds when the theme changes.
Click Interaction & Visual Feedback
All charts support click interaction with visual border highlighting. When you tap on chart elements (points, bars, segments), they show a prominent white border (3-4px) for clear visual feedback:
LineChartWidget(
dataSets: dataSets,
theme: chartTheme,
onPointTap: (point, datasetIndex, pointIndex, position) {
// Selected point will show white border automatically
// Handle tap event
},
)
Interactive Context Menu
All charts support interactive context menus on tap with haptic feedback:
LineChartWidget(
dataSets: dataSets,
theme: chartTheme,
onPointTap: (point, datasetIndex, pointIndex, position) {
ChartContextMenuHelper.show(
context,
point: point,
position: position,
datasetIndex: datasetIndex,
elementIndex: pointIndex,
datasetLabel: 'Sales',
theme: chartTheme,
onViewDetails: () {
// Handle view details
},
onExport: () {
// Handle export
},
onShare: () {
// Handle share
},
);
},
)
Custom Height
All charts support customizable height for flexible layouts:
LineChartWidget(
dataSets: dataSets,
theme: chartTheme,
height: 400.0, // Custom height in pixels
title: 'Sales Trend',
)
If height is not specified, charts use default heights optimized for each chart type.
Pie & Donut chart layout (row or column)
PieChartWidget and DonutChartWidget support a legendLayout parameter to show the chart and legend in a row or column:
// Row (default): chart left, legend right
PieChartWidget(data: pieData)
// Column: chart on top, legend below β good for narrow screens
PieChartWidget(
data: pieData,
legendLayout: .vertical,
)
DonutChartWidget(
data: donutData,
legendLayout: .vertical,
)
Using ChartsConfig
Use ChartsConfig to share theme, effects, and empty/error messages across charts:
final config = ChartsConfig(
theme: ChartTheme.light(),
useGlassmorphism: true,
emptyMessage: 'No data yet',
errorMessage: 'Something went wrong',
);
LineChartWidget(dataSets: dataSets, config: config)
PieChartWidget(data: pieData, config: config)
Omitted config fields fall back to each chartβs own parameters or defaults. See lib/theme/charts_config.dart for all options.
Header and Footer
All charts support optional header and footer widgets for additional content:
LineChartWidget(
dataSets: dataSets,
theme: chartTheme,
title: 'Sales Trend',
header: Container(
padding: EdgeInsets.all(8.0),
child: Text('Additional info above chart'),
),
footer: Container(
padding: EdgeInsets.all(8.0),
child: Text('Additional info below chart'),
),
)
The header appears below the subtitle (if provided), and the footer appears below the chart. Both are optional and can contain any Flutter widget.
Hover Support
Line, Bar, Area, Scatter, Bubble, and Radial charts support mouse hover with visual feedback:
LineChartWidget(
dataSets: dataSets,
theme: chartTheme,
onPointHover: (point, datasetIndex, pointIndex) {
// Handle hover - point is null when mouse exits
if (point != null) {
print('Hovering over: ${point.y}');
}
},
)
BarChartWidget(
dataSets: dataSets,
theme: chartTheme,
onBarHover: (point, datasetIndex, barIndex) {
// Handle bar hover
if (point != null) {
print('Hovering over bar: ${point.y}');
}
},
)
BubbleChartWidget(
dataSets: dataSets,
theme: chartTheme,
onBubbleHover: (point, datasetIndex, pointIndex) {
// Handle bubble hover
if (point != null) {
print('Hovering over bubble: ${point.y}');
}
},
)
Bubble Chart Example
Bubble charts visualize three-dimensional data where x, y represent position and size represents a third dimension:
// Store dataSets to reuse in callbacks (important!)
final bubbleDataSets = [
BubbleDataSet(
label: 'Region A',
color: Colors.blue,
dataPoints: [
BubbleDataPoint(x: 10, y: 20, size: 50, label: 'Point 1'),
BubbleDataPoint(x: 15, y: 30, size: 75, label: 'Point 2'),
BubbleDataPoint(x: 20, y: 25, size: 60, label: 'Point 3'),
],
),
BubbleDataSet(
label: 'Region B',
color: Colors.pink,
dataPoints: [
BubbleDataPoint(x: 12, y: 22, size: 55, label: 'Point 1'),
BubbleDataPoint(x: 18, y: 35, size: 80, label: 'Point 2'),
],
),
];
BubbleChartWidget(
dataSets: bubbleDataSets,
theme: chartTheme,
title: 'Regional Performance',
subtitle: 'Bubble chart with size dimension',
minBubbleSize: 5.0,
maxBubbleSize: 30.0,
onBubbleTap: (point, datasetIndex, pointIndex, position) {
// Always validate indices to prevent RangeError
if (datasetIndex < 0 || datasetIndex >= bubbleDataSets.length) {
return;
}
final dataSet = bubbleDataSets[datasetIndex];
if (pointIndex < 0 || pointIndex >= dataSet.dataPoints.length) {
return;
}
final bubblePoint = dataSet.dataPoints[pointIndex];
print('Tapped: ${bubblePoint.label} - Size: ${bubblePoint.size}');
// Show context menu
ChartContextMenuHelper.show(
context,
point: point,
position: position,
datasetIndex: datasetIndex,
elementIndex: pointIndex,
datasetLabel: dataSet.label,
theme: chartTheme,
onViewDetails: () {
// Handle view details
},
);
},
)
Important: Always store your dataSets in a variable and reuse it in callbacks. Don't regenerate data in callbacks (e.g., SampleData.generateBubbleData()[datasetIndex]) as this can cause RangeError if the data structure changes or indices are invalid.
ποΈ Customization Options
All charts support extensive customization:
- Colors: Theme-aware adaptive colors
- Gradients: Linear gradients for fills and backgrounds
- Line Thickness: Configurable stroke width
- Border Radius: Rounded corners
- Shadows: Elevation and shadow effects
- Axis Styling: Customizable axis appearance
- Grid Lines: Toggle grid visibility
- Legends: Show/hide legends
- Tooltips: Interactive tooltips
- Padding & Spacing: Configurable spacing
- Height: Customizable chart height for flexible layouts
- Header & Footer: Optional header and footer widgets for additional content
β‘ Performance Optimizations
- Minimal Rebuilds: Charts only rebuild when data changes
- Cached Styling: Theme objects are cached and reused
- Efficient Rendering: Optimized paint operations
- Lightweight Animations: Smooth 60fps animations
- Lazy Loading: Data processed only when needed
π Theme Switching
The ThemeProvider supports automatic theme switching between:
- Light mode - Always use light theme
- Dark mode - Always use dark theme
- System mode - Follows device system settings
The theme automatically updates all charts and widgets that use ThemeProvider.of(context).chartTheme.
π± Example App
Try the live demo: https://save-points-charts.netlify.app/
Or check out the example app in the repository to see all chart types in action.
π Chart Types
Line Chart
- Smooth curves
- Gradient area fills
- Interactive tooltips with haptic feedback
- Mouse hover support with visual feedback
- Multiple datasets
Bar Chart
- Grouped or stacked bars
- Rounded corners
- Gradient fills
- Customizable spacing
- Mouse hover support with elevation effects
- Haptic feedback on tap
Area Chart
- Filled areas with gradients
- Smooth curves
- Multiple datasets overlay
- Interactive point tapping
Stacked Area Chart
- Cumulative multi-series visualization
- Stacked layers for trend comparison
- Multiple datasets required
- Smooth gradient fills
- Interactive point tapping
Pie Chart
- Percentage labels
- Customizable colors
- Legend support
- Smooth animations
- Row or column layout:
legendLayoutβ chart and legend in a row (default) or column (.vertical)
Donut Chart
- Center value display
- Similar to pie with center space
- Modern donut design
- Row or column layout:
legendLayoutβ chart and legend in a row (default) or column (.vertical)
Radial Chart
- Multi-dimensional data
- Radar/spider chart
- Performance metrics visualization
- Mouse hover support with glow effects
- Haptic feedback on tap
Sparkline Chart
- Compact inline charts
- Positive/negative color coding
- Trend visualization
Scatter Chart
- Relationship visualization
- Correlation analysis
- Multiple data series support
- Interactive point tapping
- Mouse hover support
Bubble Chart
- Three-dimensional data visualization (x, y position + size dimension)
- Size-based encoding for third variable
- Multiple data series support with distinct colors
- Interactive bubble tapping with haptic feedback
- Mouse hover support with visual feedback
- Customizable bubble size range (minBubbleSize, maxBubbleSize)
- Context menu support on tap
- Visual border highlighting on selection
- Smooth entrance animations
Radar Chart
- Multi-dimensional data comparison
- Spider/web chart visualization
- Multiple series overlay
- Customizable grid levels
- Performance metrics display
Gauge Chart
- Single metric visualization
- KPI and progress indicators
- Customizable segments
- Semi-circular or circular gauge
- Center label and unit display
- Interactive chart tapping
Spline Chart
- Smooth bezier curves
- Gradient area fills
- Interactive point tapping with border highlighting
- Multiple datasets support
Step Line Chart
- Step function visualization
- Horizontal and vertical segments
- Interactive point tapping with border highlighting
- Area fill support
Stacked Column Chart
- Multiple datasets stacked vertically
- Interactive bar tapping with border highlighting
- Gradient fills per segment
- Customizable bar width
Pyramid Chart
- Hierarchical data visualization
- Largest to smallest segments
- Interactive segment tapping with border highlighting
- Gradient fills
Funnel Chart
- Sales funnel and conversion tracking
- Top to bottom narrowing
- Interactive segment tapping with border highlighting
- Gradient fills
π¨ Design Effects
Glassmorphism
Enable with useGlassmorphism: true for a frosted glass effect with backdrop blur.
Neumorphism
Enable with useNeumorphism: true for soft shadows and embossed appearance.
π Example Screens
The demo screen includes:
- Drawer navigation for chart type selection
- Theme toggle button
- Design effect selector (Glassmorphism/Neumorphism)
- Multiple chart examples per type
- Responsive layout
β Best Practices
Data Handling in Callbacks
Always store your dataSets and reuse them in callbacks. Don't regenerate data in callbacks as this can cause RangeError exceptions.
β Wrong:
BubbleChartWidget(
dataSets: SampleData.generateBubbleData(),
onBubbleTap: (point, datasetIndex, pointIndex, position) {
// This can cause RangeError if data structure changes!
final dataSet = SampleData.generateBubbleData()[datasetIndex];
final bubblePoint = dataSet.dataPoints[pointIndex];
},
)
β Correct:
final bubbleDataSets = SampleData.generateBubbleData();
BubbleChartWidget(
dataSets: bubbleDataSets,
onBubbleTap: (point, datasetIndex, pointIndex, position) {
// Validate indices to prevent RangeError
if (datasetIndex < 0 || datasetIndex >= bubbleDataSets.length) {
return;
}
final dataSet = bubbleDataSets[datasetIndex];
if (pointIndex < 0 || pointIndex >= dataSet.dataPoints.length) {
return;
}
final bubblePoint = dataSet.dataPoints[pointIndex];
// Safe to use bubblePoint now
},
)
Index Validation
Always validate indices in callbacks before accessing array elements. This prevents RangeError exceptions and makes your app more robust:
onBubbleTap: (point, datasetIndex, pointIndex, position) {
// Validate datasetIndex
if (datasetIndex < 0 || datasetIndex >= dataSets.length) {
return; // Invalid dataset index
}
// Validate pointIndex
final dataSet = dataSets[datasetIndex];
if (pointIndex < 0 || pointIndex >= dataSet.dataPoints.length) {
return; // Invalid point index
}
// Now safe to access
final point = dataSet.dataPoints[pointIndex];
// ... handle tap
}
This pattern applies to all chart types with interactive callbacks (Line, Bar, Area, Scatter, Bubble, Radial, etc.).
π§ Extending
To add new chart types:
- Create a new widget in
lib/widgets/ - Follow the existing pattern
- Use
ChartContainerfor consistent styling - Support
ChartThemefor theme awareness
π Recent Improvements
Latest Features
Enhanced Customization (Latest)
- Added
heightproperty to all chart widgets for customizable sizing - Added
headerandfooterproperties to all chart widgets for additional content - Enhanced padding and clipping in chart painters for better visualization
- Improved X-axis padding calculation to prevent point clipping
- Refactored data handling with labels directly on data points for better clarity
Impact: Charts now offer more flexible layouts and customization options, making it easier to integrate charts into various UI designs.
Bug Fixes
Fixed RangeError in Bubble Chart Callbacks
- Fixed
RangeErrorthat occurred when accessing data in bubble chart callbacks - Added proper bounds checking for
datasetIndexandpointIndexin all examples - Improved data handling by storing dataSets and reusing them in callbacks instead of regenerating
- This fix prevents crashes when invalid indices are passed to callbacks
Impact: All bubble chart implementations now include proper index validation, making the library more robust and preventing runtime exceptions.
π License
This project is open source and available for use.
π€ Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
π§ Support
For issues, questions, or suggestions, please open an issue on GitHub.
Libraries
- models/bubble_data_point
- models/bubble_data_set
- models/chart_data
- models/chart_data_point
- models/chart_data_set
- models/chart_interaction
- models/pie_data
- models/radar_data_point
- models/radar_data_set
- painters/bar_chart_painter
- painters/base_chart_painter
- painters/bubble_chart_painter
- painters/funnel_chart_painter
- painters/gauge_chart_painter
- painters/line_chart_painter
- painters/pie_chart_painter
- painters/pyramid_chart_painter
- painters/radar_chart_painter
- painters/radial_chart_painter
- painters/scatter_chart_painter
- painters/spline_chart_painter
- painters/stacked_area_chart_painter
- painters/stacked_column_chart_painter
- painters/step_line_chart_painter
- providers/theme_provider
- save_points_chart
- A modern, high-performance Flutter charting library with full theme support.
- theme/chart_theme
- theme/charts_config
- utils/chart_interaction_helper
- widgets/area_chart_widget
- widgets/bar_chart_widget
- widgets/bubble_chart_widget
- widgets/chart_container
- widgets/chart_context_menu/action_item
- widgets/chart_context_menu/color_scheme
- widgets/chart_empty_state
- widgets/donut_chart_widget
- widgets/funnel_chart_widget
- widgets/gauge_chart_widget
- widgets/line_chart_widget
- widgets/pie_chart_widget
- widgets/pyramid_chart_widget
- widgets/radar_chart_widget
- widgets/radial_chart_widget
- widgets/scatter_chart_widget
- widgets/show_empty_or_widget
- widgets/sparkline_chart_widget
- widgets/spline_chart_widget
- widgets/stacked_area_chart_widget
- widgets/stacked_column_chart_widget
- widgets/step_line_chart_widget