flutter 雷达图

通过CustomPainter自定义雷达图

效果如下

主要代码

Dart 复制代码
import 'package:flutter/material.dart';
import 'dart:math';
import 'dash_painter.dart';
import 'model/charts_model.dart';

class RadarChart extends StatelessWidget {
  final List<ChartModel> list;
  final double maxValue;
  final Color radarColor;
  final Color dataColor;

  const RadarChart({
    super.key,
    required this.list,
    this.maxValue = 100,
    this.radarColor = Colors.grey,
    this.dataColor = Colors.green,
  });

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: RadarChartPainter(
        list: list,
        maxValue: maxValue,
        radarColor: radarColor,
        dataColor: dataColor,
        numLayers: 4,
      ),
    );
  }
}

class RadarChartPainter extends CustomPainter {
  final int numLayers;
  final List<ChartModel> list;
  final double maxValue;
  final Color radarColor;
  final Color dataColor;
  List<Offset> startList = []; //存放第一层的点
  List<Offset> endList = []; //存放最外层的点
  List<Offset> textOffsetList = [];
  final int _offsetDy = 20;
  final int _offsetDx = 5;

  RadarChartPainter({
    required this.numLayers,
    required this.list,
    required this.maxValue,
    required this.radarColor,
    required this.dataColor,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final radius = size.width / 2;
    final dataPoints = <Offset>[];
    final radarPaint = Paint()
      ..color = radarColor
      ..style = PaintingStyle.stroke;
    final bgPaint = Paint()
      ..color = const Color(0xFFCAD0E8).withOpacity(.4)
      ..style = PaintingStyle.fill;
    //画背景颜色
    for (var layer = 1; layer <= numLayers; layer++) {
      if (layer == 3) {
        final layerRadius = radius * (layer / numLayers);
        final radarPath = Path();
        for (var i = 0; i < 6; i++) {
          //每一层的6个点
          final angle = (2 * pi / 6) * i - (pi / 2);
          final x = center.dx + layerRadius * cos(angle);
          final y = center.dy + layerRadius * sin(angle);
          final point = Offset(x, y);
          if (i == 0) {
            radarPath.moveTo(point.dx, point.dy);
          } else {
            radarPath.lineTo(point.dx, point.dy);
          }
        }
        radarPath.close();
        canvas.drawPath(radarPath, bgPaint);
      }
    }
    //连接每一层的6个点和文字
    for (var layer = 1; layer <= numLayers; layer++) {
      final layerRadius = radius * (layer / numLayers);
      final radarPath = Path();
      for (var i = 0; i < 6; i++) {
        final angle = (2 * pi / 6) * i - (pi / 2);
        final x = center.dx + layerRadius * cos(angle);
        final y = center.dy + layerRadius * sin(angle);
        final point = Offset(x, y);
        if (i == 0) {
          radarPath.moveTo(point.dx, point.dy);
        } else {
          radarPath.lineTo(point.dx, point.dy);
        }
        //保存第一层的点
        if (layer == 1) {
          startList.add(point);
        }
        //保存最外层的点,用于后面画第一层的最外层点的连线
        if (layer == 4) {
          endList.add(point);
          //画label文字
          const textStyle = TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: Color(0XFF999999));
          final textSpan = TextSpan(text: list[i].label, style: textStyle);
          final textPainter = TextPainter(
            text: textSpan,
            textDirection: TextDirection.ltr,
          );
          textPainter.layout();
          double w = textPainter.width;
          Offset off = const Offset(0, 0);
          if (i == 0) {
            off = Offset(point.dx - w * 0.5, point.dy - _offsetDy);
          } else if (i == 1 || i == 2) {
            off = Offset(point.dx + _offsetDx, point.dy);
          } else if (i == 4 || i == 5) {
            off = Offset(point.dx - _offsetDx - w, point.dy);
          } else if (i == 3) {
            off = Offset(point.dx - w * 0.5, point.dy);
          }
          textPainter.paint(canvas, off);
        }
      }
      radarPath.close();
      const DashPainter(span: 3, step: 3).paint(canvas, radarPath, radarPaint);
    }
    //画第一层的点到最外层的点的连线
    for (var i = 0; i < 6; i++) {
      final path = Path();
      path.moveTo(startList[i].dx, startList[i].dy);
      path.lineTo(endList[i].dx, endList[i].dy);
      const DashPainter(span: 4, step: 9).paint(canvas, path, radarPaint);
    }

    //画数据区域
    final dataPaint = Paint()
      ..color = dataColor
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2.0;
    final dataFillPaint = Paint()
      ..color = dataColor.withOpacity(.3)
      ..style = PaintingStyle.fill
      ..strokeWidth = 2.0;

    final dataPath = Path();
    for (var i = 0; i < 6; i++) {
      final angle = (2 * pi / 6) * i - (pi / 2);
      final value = list[i].y;
      final normalizedValue = value / maxValue;
      final dataRadius = radius * 0.75 * normalizedValue + radius * 0.25;
      final x = center.dx + dataRadius * cos(angle);
      final y = center.dy + dataRadius * sin(angle);
      final point = Offset(x, y);

      if (i == 0) {
        dataPath.moveTo(point.dx, point.dy);
      } else {
        dataPath.lineTo(point.dx, point.dy);
      }

      dataPoints.add(point);
    }
    dataPath.close();
    canvas.drawPath(dataPath, dataFillPaint);
    canvas.drawPath(dataPath, dataPaint);

    final dataPointPaint = Paint()..color = dataColor;
    for (var point in dataPoints) {
      canvas.drawCircle(point, 4.0, dataPointPaint);
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

项目地址 : flutter_radar: flutter 雷达图

相关推荐
科昂41 分钟前
Dart 单线程异步模型:从原理到工程实践的系统化解析
android·flutter·dart
fundroid1 小时前
2025 跨平台技术如何选:KMP 与 Flutter 的核心差异
flutter·kotlin·kmp
耳東陈4 小时前
Flutter开箱即用一站式解决方案-新增企业级日志
flutter
顾林海4 小时前
Flutter 图片组件全面解析:从基础加载到高级应用
android·前端·flutter
眼镜会飞4 小时前
Flutter window和Mac中webview2使用Cef替代
windows·flutter·mac
淡写成灰4 小时前
Flutter自定义带有Badger组件组
flutter
好的佩奇5 小时前
Dart 之任务
android·flutter·dart
豪冷啊18 小时前
Flutter Invalid constant value.
flutter
顾林海1 天前
Flutter容器组件深度解析
android·前端·flutter
xq95271 天前
mac os flutter 配置环境变量
flutter