3

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.

2
  • Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking. Commented May 24 at 9:48
  • Can you share the screenshot what exactly you want? Commented May 24 at 10:06

2 Answers 2

4

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;
}
Sign up to request clarification or add additional context in comments.

Comments

3

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),
          ],
        ),
      ),
    );
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.