Yes, you can use the fl_chart package to implement both Pie and Line charts in Flutter. It’s lightweight, customizable, and supports animation.
dependencies:
fl_chart: ^0.66.0
2. Pie Chart Example
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
class PieChartSample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return PieChart(
PieChartData(
sections: [
PieChartSectionData(
value: 40,
title: '40%',
color: Colors.blue,
radius: 60,
),
PieChartSectionData(
value: 30,
title: '30%',
color: Colors.orange,
radius: 60,
),
PieChartSectionData(
value: 30,
title: '30%',
color: Colors.green,
radius: 60,
),
],
sectionsSpace: 2,
centerSpaceRadius: 30,
),
);
}
}
3. Line Chart Example
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
class LineChartSample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LineChart(
LineChartData(
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(showTitles: true),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(showTitles: true),
),
),
lineBarsData: [
LineChartBarData(
spots: [
FlSpot(0, 1),
FlSpot(1, 1.5),
FlSpot(2, 1.4),
FlSpot(3, 3.4),
FlSpot(4, 2),
FlSpot(5, 2.2),
FlSpot(6, 1.8),
],
isCurved: true,
barWidth: 3,
color: Colors.blue,
dotData: FlDotData(show: true),
),
],
),
);
}
}
If you don't want to use any library,
I have made one custom pie chart and line chart below is the code with output hope it will help you.

import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: ChartScreen(),
debugShowCheckedModeBanner: false,
);
}
}
class ChartScreen extends StatefulWidget {
const ChartScreen({super.key});
@override
State<ChartScreen> createState() => _ChartScreenState();
}
class _ChartScreenState extends State<ChartScreen> with SingleTickerProviderStateMixin {
late final AnimationController _controller;
final List<PieChartItem> pieItems = [
PieChartItem(value: 40, color: Colors.blue),
PieChartItem(value: 30, color: Colors.red),
PieChartItem(value: 20, color: Colors.green),
PieChartItem(value: 10, color: Colors.orange),
];
final List<LineChartPoint> lineData = [
LineChartPoint(x: 0, y: 20),
LineChartPoint(x: 20, y: 40),
LineChartPoint(x: 40, y: 10),
LineChartPoint(x: 60, y: 70),
LineChartPoint(x: 80, y: 30),
LineChartPoint(x: 100, y: 90),
];
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(seconds: 2));
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Pie & Line Chart Example')),
body: SingleChildScrollView(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Card(
elevation: 6,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: SizedBox(
width: 200,
height: 200,
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
painter: PieChartPainter(pieItems, _controller.value),
child: Container(),
);
},
),
),
),
const SizedBox(height: 30),
Card(
elevation: 6,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: SizedBox(
width: 200,
height: 200,
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
painter: LineChartPainter(lineData, _controller.value),
child: Container(),
);
},
),
),
),
const SizedBox(height: 30),
],
),
),
),
);
}
}
// Data class for Pie
class PieChartItem {
final double value;
final Color color;
PieChartItem({required this.value, required this.color});
}
// Painter for Pie Chart
class PieChartPainter extends CustomPainter {
final List<PieChartItem> items;
final double animationPercent;
PieChartPainter(this.items, this.animationPercent);
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = min(size.width, size.height) / 2 * 0.9;
final total = items.fold<double>(0, (sum, item) => sum + item.value);
double startAngle = -pi / 2;
final textPainter = TextPainter(textAlign: TextAlign.center, textDirection: TextDirection.ltr);
for (var item in items) {
final sweepAngle = (item.value / total) * 2 * pi * animationPercent;
final paint = Paint()..color = item.color;
// Draw slice
canvas.drawArc(Rect.fromCircle(center: center, radius: radius),
startAngle, sweepAngle, true, paint);
// Draw percentage text if sweepAngle > 0 (visible)
if (sweepAngle > 0) {
final middleAngle = startAngle + sweepAngle / 2;
final labelRadius = radius * 0.6;
final labelX = center.dx + labelRadius * cos(middleAngle);
final labelY = center.dy + labelRadius * sin(middleAngle);
final percent = ((item.value / total) * 100).toStringAsFixed(1) + '%';
final textSpan = TextSpan(
text: percent,
style: TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.bold,
shadows: [Shadow(blurRadius: 2, color: Colors.black.withOpacity(0.7))],
),
);
textPainter.text = textSpan;
textPainter.layout();
final offset = Offset(labelX - textPainter.width / 2, labelY - textPainter.height / 2);
textPainter.paint(canvas, offset);
}
startAngle += sweepAngle;
}
}
@override
bool shouldRepaint(covariant PieChartPainter oldDelegate) =>
oldDelegate.animationPercent != animationPercent || oldDelegate.items != items;
}
// Data class for Line Chart
class LineChartPoint {
final double x;
final double y;
LineChartPoint({required this.x, required this.y});
}
// Painter for Line Chart
class LineChartPainter extends CustomPainter {
final List<LineChartPoint> data;
final double animationPercent;
LineChartPainter(this.data, this.animationPercent);
@override
void paint(Canvas canvas, Size size) {
final paintLine = Paint()
..color = Colors.blue
..strokeWidth = 2
..style = PaintingStyle.stroke;
final paintDot = Paint()
..color = Colors.red
..style = PaintingStyle.fill;
const paddingLeft = 40.0;
const paddingBottom = 40.0;
const paddingTop = 20.0;
const paddingRight = 20.0;
final chartWidth = size.width - paddingLeft - paddingRight;
final chartHeight = size.height - paddingTop - paddingBottom;
final path = Path();
for (int i = 0; i < data.length; i++) {
final x = paddingLeft + (chartWidth * (data[i].x / 100));
final y = paddingTop + chartHeight - (chartHeight * (data[i].y / 100));
if (i == 0) {
path.moveTo(x, y);
} else {
// Only draw lines up to animationPercent
if ((i / (data.length - 1)) <= animationPercent) {
path.lineTo(x, y);
}
}
}
canvas.drawPath(path, paintLine);
// Draw dots for visible points
for (int i = 0; i < data.length; i++) {
if (data.length == 1 || (i / (data.length - 1)) <= animationPercent) {
final x = paddingLeft + (chartWidth * (data[i].x / 100));
final y = paddingTop + chartHeight - (chartHeight * (data[i].y / 100));
canvas.drawCircle(Offset(x, y), 4, paintDot);
}
}
// Draw X and Y axis
final axisPaint = Paint()
..color = Colors.black54
..strokeWidth = 1;
// Y axis
canvas.drawLine(Offset(paddingLeft, paddingTop),
Offset(paddingLeft, size.height - paddingBottom), axisPaint);
// X axis
canvas.drawLine(Offset(paddingLeft, size.height - paddingBottom),
Offset(size.width - paddingRight, size.height - paddingBottom), axisPaint);
// Draw Y labels (0, 50, 100)
final textPainter = TextPainter(textAlign: TextAlign.right, textDirection: TextDirection.ltr);
for (int i = 0; i <= 2; i++) {
final yVal = i * 50;
final y = paddingTop + chartHeight - (chartHeight * (yVal / 100));
final textSpan = TextSpan(
text: yVal.toString(),
style: const TextStyle(fontSize: 12, color: Colors.black54),
);
textPainter.text = textSpan;
textPainter.layout();
textPainter.paint(canvas, Offset(paddingLeft - 8 - textPainter.width, y - textPainter.height / 2));
}
// Draw X labels (0, 50, 100)
for (int i = 0; i <= 2; i++) {
final xVal = i * 50;
final x = paddingLeft + chartWidth * (xVal / 100);
final textSpan = TextSpan(
text: xVal.toString(),
style: const TextStyle(fontSize: 12, color: Colors.black54),
);
textPainter.text = textSpan;
textPainter.layout();
textPainter.paint(canvas, Offset(x - textPainter.width / 2, size.height - paddingBottom + 4));
}
}
@override
bool shouldRepaint(covariant LineChartPainter oldDelegate) =>
oldDelegate.animationPercent != animationPercent || oldDelegate.data != data;
}