save_points_chart 1.9.2
save_points_chart: ^1.9.2 copied to clipboard
Modern Flutter charting library with 17 chart types, Material 3 design, smooth animations, glassmorphism & neumorphism effects, and zero runtime dependencies.
import 'package:flutter/material.dart';
import 'package:save_points_chart/save_points_charts.dart';
void main() => runApp(const ChartsDemoApp());
class ChartsDemoApp extends StatefulWidget {
const ChartsDemoApp({super.key});
@override
State<ChartsDemoApp> createState() => _ChartsDemoAppState();
}
class _ChartsDemoAppState extends State<ChartsDemoApp> {
var _isDark = true;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'save_points_chart Demo',
theme: ThemeData.light(useMaterial3: true),
darkTheme: ThemeData.dark(useMaterial3: true),
themeMode: _isDark ? ThemeMode.dark : ThemeMode.light,
home: ChartsDemoPage(
isDark: _isDark,
onToggleTheme: () => setState(() => _isDark = !_isDark),
),
);
}
}
class ChartsDemoPage extends StatefulWidget {
const ChartsDemoPage({
super.key,
required this.isDark,
required this.onToggleTheme,
});
final bool isDark;
final VoidCallback onToggleTheme;
@override
State<ChartsDemoPage> createState() => _ChartsDemoPageState();
}
class _ChartsDemoPageState extends State<ChartsDemoPage> {
var _showAllCharts = true;
var _chartIndex = 0;
var _style = ChartStyle.gradient;
bool get _isDark => widget.isDark;
/// Applies the currently selected render style to any config.
ChartConfig _styled(ChartConfig config) => config.copyWith(style: _style);
static const _styleLabels = {
ChartStyle.gradient: 'Gradient',
ChartStyle.flat: 'Flat',
ChartStyle.glass: 'Glass',
};
// Chart palette follows the app theme.
ChartTheme get _chartTheme =>
_isDark ? ChartTheme.dark() : ChartTheme.light();
// App chrome colors.
Color get _bg => _isDark ? const Color(0xFF060D1F) : const Color(0xFFF4F6FB);
Color get _surface => _isDark ? const Color(0xFF0B1430) : Colors.white;
Color get _onSurface =>
_isDark ? Colors.white : const Color(0xFF1A1A1A);
Color get _onSurfaceMuted =>
_isDark ? Colors.white70 : const Color(0xFF5B6472);
static const _allChartTitles = [
'Line',
'Bar',
'Pie',
'Donut',
'Area',
'Scatter',
'Radar',
'Gauge',
'Sparkline',
'Stacked',
'Waterfall',
'Funnel',
'Bubble',
];
static const _desktopY = [220.0, 260.0, 240.0, 300.0, 320.0];
static const _mobileY = [140.0, 180.0, 200.0, 210.0, 230.0];
List<ChartPoint> get _monthPointsDesktop => List.generate(
5,
(i) => ChartPoint(x: i.toDouble(), y: _desktopY[i], label: _monthLabel(i)),
);
List<ChartPoint> get _monthPointsMobile => List.generate(
5,
(i) => ChartPoint(x: i.toDouble(), y: _mobileY[i], label: _monthLabel(i)),
);
static String _monthLabel(int i) {
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May'];
return months[i];
}
ChartSeries get _desktopSeries =>
ChartSeries(id: 'desktop', name: 'Desktop', points: _monthPointsDesktop);
ChartSeries get _mobileSeries =>
ChartSeries(id: 'mobile', name: 'Mobile', points: _monthPointsMobile);
ChartConfig _cartesianConfig({
required String title,
required String subtitle,
required List<ChartSeries> series,
}) {
return ChartConfig(
title: title,
subtitle: subtitle,
xAxisTitle: 'Month',
yAxisTitle: 'Users',
series: series,
semanticLabel: title,
);
}
ChartConfig get _barOverviewConfig => _cartesianConfig(
title: 'Traffic overview',
subtitle: 'Desktop vs mobile by month',
series: [_desktopSeries, _mobileSeries],
);
ChartConfig get _lineTrendConfig => _cartesianConfig(
title: 'Traffic trend',
subtitle: 'Jan – May · smooth spline',
series: [_desktopSeries, _mobileSeries],
);
ChartConfig get _areaVolumeConfig => _cartesianConfig(
title: 'Traffic volume',
subtitle: 'Filled areas · last 5 months',
series: [_desktopSeries, _mobileSeries],
);
ChartConfig get _pieDeviceConfig => ChartConfig(
title: 'Traffic by device',
subtitle: 'Share of total sessions',
series: [
ChartSeries(
id: 'devices',
name: 'Devices',
points: const [
ChartPoint(x: 0, y: 60, label: 'Desktop'),
ChartPoint(x: 1, y: 30, label: 'Mobile'),
ChartPoint(x: 2, y: 10, label: 'Tablet'),
],
),
],
);
ChartConfig get _scatterConfig => _lineTrendConfig;
ChartConfig get _radarConfig => ChartConfig(
title: 'Team skills',
subtitle: 'Radar · six dimensions',
xAxisTitle: 'Dimension',
yAxisTitle: 'Score',
series: [
ChartSeries(
id: 'skills',
name: 'Team',
points: const [
ChartPoint(x: 0, y: 80, label: 'Speed'),
ChartPoint(x: 1, y: 65, label: 'Quality'),
ChartPoint(x: 2, y: 90, label: 'Design'),
ChartPoint(x: 3, y: 70, label: 'Support'),
ChartPoint(x: 4, y: 85, label: 'Delivery'),
ChartPoint(x: 5, y: 75, label: 'Cost'),
],
),
],
);
ChartConfig get _gaugeConfig => ChartConfig(
title: 'CPU usage',
subtitle: 'Current utilization',
yAxisTitle: 'Percent',
series: [
ChartSeries(
id: 'cpu',
name: 'CPU',
points: const [ChartPoint(x: 0, y: 72)],
),
],
);
ChartConfig get _waterfallConfig => ChartConfig(
title: 'P&L waterfall',
subtitle: 'Step-by-step breakdown',
xAxisTitle: 'Step',
yAxisTitle: 'Amount',
series: [
ChartSeries(
id: 'pnl',
name: 'P&L',
points: const [
ChartPoint(
x: 0,
y: 100,
label: 'Start',
metadata: {kWaterfallTypeKey: 'absolute'},
),
ChartPoint(x: 1, y: 30, label: 'Revenue'),
ChartPoint(x: 2, y: -15, label: 'Costs'),
ChartPoint(
x: 3,
y: 0,
label: 'Subtotal',
metadata: {kWaterfallTypeKey: 'subtotal'},
),
ChartPoint(x: 4, y: 20, label: 'Tax'),
ChartPoint(
x: 5,
y: 135,
label: 'Total',
metadata: {kWaterfallTypeKey: 'total'},
),
],
),
],
);
ChartConfig get _funnelConfig => ChartConfig(
title: 'Sales funnel',
subtitle: 'Conversion pipeline',
yAxisTitle: 'Count',
series: [
ChartSeries(
id: 'funnel',
name: 'Pipeline',
points: const [
ChartPoint(x: 0, y: 1000, label: 'Visitors'),
ChartPoint(x: 1, y: 600, label: 'Leads'),
ChartPoint(x: 2, y: 300, label: 'Opportunities'),
ChartPoint(x: 3, y: 120, label: 'Customers'),
],
),
],
);
ChartConfig get _bubbleConfig => ChartConfig(
title: 'Regional bubbles',
subtitle: 'Size = volume',
xAxisTitle: 'X',
yAxisTitle: 'Y',
series: [
ChartSeries(
id: 'regions',
name: 'Regions',
points: [
(10, 20, 40),
(25, 35, 80),
(40, 15, 25),
(55, 45, 60),
(70, 30, 50),
].toBubblePoints(),
),
],
);
ChartConfig get _stackedConfig => ChartConfig(
title: 'Stacked products',
subtitle: 'Product mix over time',
xAxisTitle: 'Month',
yAxisTitle: 'Units',
series: [
ChartSeries(
id: 'a',
name: 'Product A',
points: [20, 25, 30, 28, 35].toChartPoints(),
),
ChartSeries(
id: 'b',
name: 'Product B',
points: [15, 18, 22, 20, 25].toChartPoints(),
),
ChartSeries(
id: 'c',
name: 'Product C',
points: [10, 12, 15, 14, 18].toChartPoints(),
),
],
);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _bg,
drawer: _buildDrawer(context),
appBar: AppBar(
title: Text(
_showAllCharts ? _allChartTitles[_chartIndex] : 'save_points_chart',
style: TextStyle(color: _onSurface),
),
backgroundColor: _bg,
foregroundColor: _onSurface,
actions: [
PopupMenuButton<ChartStyle>(
tooltip: 'Render style',
icon: const Icon(Icons.palette_outlined),
initialValue: _style,
onSelected: (s) => setState(() => _style = s),
itemBuilder: (context) => [
for (final s in ChartStyle.values)
PopupMenuItem(
value: s,
child: Row(
children: [
Icon(
_style == s
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 18,
),
const SizedBox(width: 10),
Text(_styleLabels[s]!),
],
),
),
],
),
IconButton(
tooltip: _isDark ? 'Switch to light' : 'Switch to dark',
onPressed: widget.onToggleTheme,
icon: Icon(_isDark ? Icons.light_mode : Icons.dark_mode),
),
TextButton.icon(
onPressed: () => setState(() => _showAllCharts = !_showAllCharts),
icon: Icon(_showAllCharts ? Icons.dashboard : Icons.view_list),
label: Text(_showAllCharts ? 'Dashboard' : 'All charts'),
),
],
),
body: _showAllCharts ? _buildAllCharts() : _buildDashboard(),
);
}
Widget _buildDrawer(BuildContext context) {
final accent = _chartTheme.seriesColor(0);
return Drawer(
backgroundColor: _surface,
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
child: Row(
children: [
Icon(Icons.insert_chart_outlined, color: _onSurface),
const SizedBox(width: 12),
Text(
'All charts',
style: TextStyle(
color: _onSurface,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
],
),
),
Divider(height: 1, color: _onSurfaceMuted.withValues(alpha: 0.3)),
Expanded(
child: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: _allChartTitles.length,
itemBuilder: (context, i) {
final selected = _showAllCharts && i == _chartIndex;
return ListTile(
selected: selected,
selectedTileColor: accent.withValues(alpha: 0.12),
leading: Icon(
_chartIcon(i),
color: selected ? accent : _onSurfaceMuted,
),
title: Text(
_allChartTitles[i],
style: TextStyle(
color: selected ? _onSurface : _onSurfaceMuted,
fontWeight: selected
? FontWeight.w600
: FontWeight.w400,
),
),
onTap: () {
setState(() {
_chartIndex = i;
_showAllCharts = true;
});
Navigator.pop(context);
},
);
},
),
),
],
),
),
);
}
IconData _chartIcon(int index) => switch (index) {
0 => Icons.show_chart, // Line
1 => Icons.bar_chart, // Bar
2 => Icons.pie_chart, // Pie
3 => Icons.donut_large, // Donut
4 => Icons.area_chart, // Area
5 => Icons.scatter_plot, // Scatter
6 => Icons.radar, // Radar
7 => Icons.speed, // Gauge
8 => Icons.timeline, // Sparkline
9 => Icons.stacked_line_chart, // Stacked
10 => Icons.waterfall_chart, // Waterfall
11 => Icons.filter_alt_outlined, // Funnel
12 => Icons.bubble_chart, // Bubble
_ => Icons.insert_chart_outlined,
};
Widget _buildDashboard() {
return Padding(
padding: const EdgeInsets.all(12),
child: Column(
children: [
Expanded(
child: Row(
children: [
Expanded(
child: BarChart(
config: _styled(_barOverviewConfig),
theme: _chartTheme,
),
),
const SizedBox(width: 12),
Expanded(
child: LineChart(
config: _styled(_lineTrendConfig),
theme: _chartTheme,
),
),
],
),
),
const SizedBox(height: 12),
Expanded(
child: Row(
children: [
Expanded(
child: PieChart(
config: _styled(_pieDeviceConfig),
theme: _chartTheme,
),
),
const SizedBox(width: 12),
Expanded(
child: AreaChart(
config: _styled(_areaVolumeConfig),
theme: _chartTheme,
),
),
],
),
),
],
),
);
}
Widget _buildAllCharts() {
return Padding(
padding: const EdgeInsets.all(16),
child: KeyedSubtree(
key: ValueKey(_chartIndex),
child: _buildChartByIndex(_chartIndex),
),
);
}
Widget _buildChartByIndex(int index) {
final theme = _chartTheme;
return switch (index) {
0 => LineChart(config: _styled(_lineTrendConfig), theme: theme),
1 => BarChart(config: _styled(_barOverviewConfig), theme: theme),
2 => PieChart(config: _styled(_pieDeviceConfig), theme: theme),
3 => PieChart(
config: _styled(_pieDeviceConfig),
isDonut: true,
theme: theme,
),
4 => AreaChart(config: _styled(_areaVolumeConfig), theme: theme),
5 => ScatterChart(config: _styled(_scatterConfig), theme: theme),
6 => RadarChart(config: _styled(_radarConfig), theme: theme),
7 => GaugeChart(config: _styled(_gaugeConfig), theme: theme),
8 => SparklineChart(config: _styled(_lineTrendConfig), theme: theme),
9 => StackedAreaChart(config: _styled(_stackedConfig), theme: theme),
10 => WaterfallChart(config: _styled(_waterfallConfig), theme: theme),
11 => FunnelChart(config: _styled(_funnelConfig), theme: theme),
12 => BubbleChart(config: _styled(_bubbleConfig), theme: theme),
_ => LineChart(config: _styled(_lineTrendConfig), theme: theme),
};
}
}