You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flutter中实现Pie Chart与Line Chart以实现数据可视化

Flutter饼图与折线图实现指南

一、使用第三方图表库(推荐)

第三方库是最省心的方案,推荐fl_chart,它轻量、定制性强,性能表现稳定,支持饼图、折线图等多种图表类型。

1. 饼图实现

先在pubspec.yaml中添加依赖:

dependencies:
  fl_chart: ^0.65.0

示例代码:

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

class PieChartSample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 300,
      child: PieChart(
        PieChartData(
          sections: [
            PieChartSectionData(
              color: Colors.blue,
              value: 30,
              title: '30%',
              radius: 80,
            ),
            PieChartSectionData(
              color: Colors.green,
              value: 40,
              title: '40%',
              radius: 80,
            ),
            PieChartSectionData(
              color: Colors.orange,
              value: 30,
              title: '30%',
              radius: 80,
            ),
          ],
          centerSpaceRadius: 40, // 中间空白区域半径
        ),
      ),
    );
  }
}

通过PieChartSectionData可自定义扇区颜色、大小、标签,还能添加点击事件、过渡动画等效果。

2. 折线图实现

同样基于fl_chart的示例代码:

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

class LineChartSample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 300,
      child: LineChart(
        LineChartData(
          gridData: FlGridData(show: true), // 显示网格线
          titlesData: FlTitlesData(
            leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: true)), // 左侧刻度标签
            bottomTitles: AxisTitles(sideTitles: SideTitles(showTitles: true)), // 底部刻度标签
          ),
          borderData: FlBorderData(show: true), // 显示图表边框
          lineBarsData: [
            LineChartBarData(
              spots: [
                FlSpot(0, 5),
                FlSpot(1, 10),
                FlSpot(2, 7),
                FlSpot(3, 12),
                FlSpot(4, 9),
              ],
              isCurved: true, // 开启曲线平滑效果
              color: Colors.blue,
              barWidth: 3,
              dotData: FlDotData(show: true), // 显示数据点
            ),
          ],
        ),
      ),
    );
  }
}

支持自定义曲线弧度、数据点样式、坐标轴标签,还能添加多条折线、区域填充等进阶效果。

二、CustomPaint手动绘制

如果需要高度定制化或不想依赖第三方库,可通过CustomPaint手动实现。

1. 饼图手动绘制

核心逻辑是计算每个扇区的角度,再用Canvas绘制圆弧:

class CustomPieChart extends StatelessWidget {
  final List<double> values = [30, 40, 30];
  final List<Color> colors = [Colors.blue, Colors.green, Colors.orange];

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 300,
      width: 300,
      child: CustomPaint(
        painter: PiePainter(values, colors),
      ),
    );
  }
}

class PiePainter extends CustomPainter {
  final List<double> values;
  final List<Color> colors;
  double total = 0;

  PiePainter(this.values, this.colors) {
    total = values.reduce((a, b) => a + b);
  }

  @override
  void paint(Canvas canvas, Size size) {
    double startAngle = 0;
    final center = Offset(size.width / 2, size.height / 2);
    final radius = size.width / 2 - 20;

    for (int i = 0; i < values.length; i++) {
      double sweepAngle = (values[i] / total) * 2 * 3.14159;
      final paint = Paint()..color = colors[i];
      canvas.drawArc(
        Rect.fromCircle(center: center, radius: radius),
        startAngle,
        sweepAngle,
        true,
        paint,
      );
      startAngle += sweepAngle;
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

可扩展添加扇区标签、点击交互、角度动画等效果,比如结合AnimationController实现加载动画。

2. 折线图手动绘制

核心是将数据映射为坐标点,再用Canvas绘制线条和数据点:

class CustomLineChart extends StatelessWidget {
  final List<double> data = [5, 10, 7, 12, 9];

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 300,
      width: double.infinity,
      child: CustomPaint(
        painter: LinePainter(data),
      ),
    );
  }
}

class LinePainter extends CustomPainter {
  final List<double> data;

  LinePainter(this.data);

  @override
  void paint(Canvas canvas, Size size) {
    final padding = 20.0;
    final chartWidth = size.width - 2 * padding;
    final chartHeight = size.height - 2 * padding;
    final maxValue = data.reduce((a, b) => a > b ? a : b);

    // 绘制网格线
    final gridPaint = Paint()
      ..color = Colors.grey.withOpacity(0.3)
      ..strokeWidth = 1;
    for (int i = 0; i <= 5; i++) {
      double y = padding + (chartHeight / 5) * i;
      canvas.drawLine(Offset(padding, y), Offset(size.width - padding, y), gridPaint);
    }

    // 计算数据点坐标
    List<Offset> points = [];
    for (int i = 0; i < data.length; i++) {
      double x = padding + (chartWidth / (data.length - 1)) * i;
      double y = padding + chartHeight - (data[i] / maxValue) * chartHeight;
      points.add(Offset(x, y));
    }

    // 绘制折线
    final linePaint = Paint()
      ..color = Colors.blue
      ..strokeWidth = 3
      ..style = PaintingStyle.stroke;
    canvas.drawPoints(PointMode.polygon, points, linePaint);

    // 绘制数据点
    final dotPaint = Paint()..color = Colors.blue;
    for (var point in points) {
      canvas.drawCircle(point, 5, dotPaint);
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

可扩展添加坐标轴标签、数值提示框、渐变填充等效果。

三、性能优化小技巧

  • 使用第三方库时,避免频繁触发图表刷新,比如用const修饰无状态组件,或仅在数据变化时调用setState。
  • 手动绘制时,缓存计算好的坐标、路径等数据,避免每次paint都重复计算。
  • 处理大数据量时,优先选择支持数据采样的库,或手动对数据进行降采样处理。

内容的提问来源于stack exchange,提问作者Navya Narayanan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 02:22:02