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 雷达图

相关推荐
problc4 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
lqj_本人12 小时前
鸿蒙next选择 Flutter 开发跨平台应用的原因
flutter·华为·harmonyos
lqj_本人15 小时前
Flutter&鸿蒙next 状态管理框架对比分析
flutter·华为·harmonyos
起司锅仔19 小时前
Flutter启动流程(2)
flutter
hello world smile1 天前
最全的Flutter中pubspec.yaml及其yaml 语法的使用说明
android·前端·javascript·flutter·dart·yaml·pubspec.yaml
lqj_本人1 天前
Flutter 的 Widget 概述与常用 Widgets 与鸿蒙 Next 的对比
flutter·harmonyos
iFlyCai1 天前
极简实现酷炫动效:Flutter隐式动画指南第二篇之一些酷炫的隐式动画效果
flutter
lqj_本人1 天前
Flutter&鸿蒙next 中使用 MobX 进行状态管理
flutter·华为·harmonyos
lqj_本人1 天前
Flutter&鸿蒙next 中的 setState 使用场景与最佳实践
flutter·华为·harmonyos
hello world smile1 天前
Flutter常用命令整理
android·flutter·移动开发·android studio·安卓