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.

pub package License: Apache 2.0

🌐 Live Demo

Try it out in your browser: Live Demo β†’

πŸŽ₯ Demo

![Showcase Coach Preview]

Image Image Image

InfoGraphic

Image

🎯 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 ChartTheme directly. The included ThemeProvider uses Flutter's built-in InheritedWidget for 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 CustomPainter implementations for full control
  • Built-in state management - ThemeProvider uses Flutter's InheritedWidget (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.

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

  1. Minimal Rebuilds: Charts only rebuild when data changes
  2. Cached Styling: Theme objects are cached and reused
  3. Efficient Rendering: Optimized paint operations
  4. Lightweight Animations: Smooth 60fps animations
  5. 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:

  1. Create a new widget in lib/widgets/
  2. Follow the existing pattern
  3. Use ChartContainer for consistent styling
  4. Support ChartTheme for theme awareness

πŸ› Recent Improvements

Latest Features

Enhanced Customization (Latest)

  • Added height property to all chart widgets for customizable sizing
  • Added header and footer properties 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 RangeError that occurred when accessing data in bubble chart callbacks
  • Added proper bounds checking for datasetIndex and pointIndex in 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
widgets/chart_context_menu/action_item
widgets/chart_context_menu/chart_context_menu_helper
widgets/chart_context_menu/chart_context_menu_widget
widgets/chart_context_menu/color_scheme
widgets/chart_context_menu/web_action_button
widgets/chart_context_menu/web_close_button
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