Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I implement Pie and Line Charts in Flutter for visualizing data?

I'm developing a Flutter app where I need to visually display data using charts. Specifically, I want to implement:

A Pie Chart to represent proportions or percentages.

A Line Chart to display trends over time or continuous data.

I'm looking for guidance on how to implement these charts in Flutter. I'm open to any of the following approaches:

Using a reliable Flutter charting library (lightweight and customizable).

Drawing charts manually using CustomPaint, if necessary.

Any other practical and performance-friendly solution.

like image 418
Navya Narayanan Avatar asked Dec 21 '25 22:12

Navya Narayanan


2 Answers

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.

enter image description here

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;
}
like image 107
ABV Avatar answered Dec 24 '25 22:12

ABV


See if the following example helps. It makes use of fl_chart package:

Get it with:

flutter pub add fl_chart

Code:


import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';

void main() {
  runApp(
    const MaterialApp(home: FlChartsPage(), debugShowCheckedModeBanner: false),
  );
}

class FlChartsPage extends StatelessWidget {
  const FlChartsPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Pie and Line Charts with fl_chart')),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text('Pie Chart', style: Theme.of(context).textTheme.bodyMedium),
            const SizedBox(height: 16),
            SizedBox(
              height: 250,
              child: PieChart(
                PieChartData(
                  sectionsSpace: 2,
                  centerSpaceRadius: 0, // Full pie (no donut hole)
                  sections: [
                    PieChartSectionData(
                      value: 40,
                      color: Colors.blue,
                      title: 'A 40%',
                      radius: 100,
                      titleStyle: const TextStyle(
                        fontSize: 14,
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                    ),
                    PieChartSectionData(
                      value: 30,
                      color: Colors.red,
                      title: 'B 30%',
                      radius: 100,
                      titleStyle: const TextStyle(
                        fontSize: 14,
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                    ),
                    PieChartSectionData(
                      value: 20,
                      color: Colors.green,
                      title: 'C 20%',
                      radius: 100,
                      titleStyle: const TextStyle(
                        fontSize: 14,
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                    ),
                    PieChartSectionData(
                      value: 10,
                      color: Colors.orange,
                      title: 'D 10%',
                      radius: 100,
                      titleStyle: const TextStyle(
                        fontSize: 14,
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 32),
            Text('Line Chart', style: Theme.of(context).textTheme.bodyMedium),
            const SizedBox(height: 16),
            AspectRatio(
              aspectRatio: 1.5,
              child: LineChart(
                LineChartData(
                  lineBarsData: [
                    LineChartBarData(
                      isCurved: true,
                      spots: const [
                        FlSpot(0, 1),
                        FlSpot(1, 3),
                        FlSpot(2, 2),
                        FlSpot(3, 5),
                        FlSpot(4, 3),
                        FlSpot(5, 4),
                      ],
                      barWidth: 3,
                      color: Colors.blue,
                      dotData: FlDotData(show: true),
                    ),
                  ],
                  titlesData: FlTitlesData(
                    bottomTitles: AxisTitles(
                      sideTitles: SideTitles(
                        showTitles: true,
                        reservedSize: 32,
                      ),
                    ),
                    leftTitles: AxisTitles(
                      sideTitles: SideTitles(showTitles: true),
                    ),
                  ),
                  gridData: FlGridData(show: true),
                  borderData: FlBorderData(show: true),
                ),
              ),
            ),
            const SizedBox(height: 32),
          ],
        ),
      ),
    );
  }
}

like image 43
rusty Avatar answered Dec 24 '25 21:12

rusty



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!