Flutter 绘制探索 | 饼状图的绘制与事件


theme: cyanosis

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 11 天,点击查看活动详情


前言

上一篇 《Flutter 绘制探索 | 扇形区域与点击校验》 中,我们已经实现了 扇形区域路径 的生成,和校验点击手势的功能:

image.png

本篇,将根据扇形区域,实现最基本的饼图绘制效果,以及简单的点触激活效果:

2022年10月27日18-51-06.gif

2022年10月27日20-44-08.gif


1. 饼图基础绘制

一个 SectorShape 对象对应着界面上的一个扇形区域。如下所示,是 startAngle-90° ,扫描 108° 的图形:

image.png

SectorShape shape = SectorShape( center: Offset(size.width / 2, size.height / 2), innerRadius: innerRadius, outRadius: 80, startAngle: -pi/2, sweepAngle: 0.3* 2 * pi, );


对于一个饼图而言,就是若干个不同颜色的扇形区域依次排放的效果。而 数据 决定扇形区域个数和扫描角度。接下来实现一下根据数值列表,渲染对于的饼图:

image.png

dart final List<double> data = [0.3,0.3,0.15,0.1,0.15];


下面是通过数据列表映射出 SectorShape 列表的方法,主要逻辑是遍历数据,根据是数据决定其实角度和扫描角度:

dart List<SectorShape> mapShapeByData(List<double> data, Size size) { List<SectorShape> shapes = []; double cursor = 0; final Offset center = Offset(size.width / 2, size.height / 2); for (int i = 0; i < data.length; i++) { SectorShape shape = SectorShape( center: center, innerRadius: 40, outRadius: 80, startAngle: -pi / 2 + cursor * 2*pi, sweepAngle: data[i] * 2 * pi, ); shapes.add(shape); cursor += data[i]; } return shapes; }


然后在绘制是遍历绘制即可,这样不同的数据就能有不同的展示效果,这就是数据的可视化展示。

image.png

```dart final List data = [0.2, 0.3, 0.15, 0.2, 0.15];

List shapes = mapShapeByData(data, size); for (int i = 0; i < shapes.length; i++) { paint.color = colors[i]; canvas.drawPath(shapes[i].formPath(), paint); } ```

在创建 SectorShape 对象时,可以通过 innerRadius 的大小确定空洞的大小,比如下面是 innerRadius = 0 的效果,即实心饼图:

image.png


2. 对一组数据的处理

上面的 data 数据必须是表示的还是占比,必须之和为 1 。那如何根据一组数据,来展示饼图呢?比如下面是七本小册的销量,想看一下占比情况:

final List<double> data = [944, 1141, 910, 1094, 1589,1423,2956];

这道题应该小学生都会做,就是算比例呗。只要将数据与处理一下即可:

dart List<double> preprocess(List<num> data){ num sum =0; data.forEach((e) => sum+=e); return data.map((e) => e/sum).toList(); }

这样就可以展示出七本小册的销量情况,但是目前图中无法反应哪本是哪个部分。所以我们需要处理一下图例。

image.png


3. 图例处理

在处理图例之前,先封装一下数据,如下 SaleData 中存储名称和销量数据:

``` class SaleData { final String name; final int value;

const SaleData({ required this.name, required this.value, }); } ```

然后准备一下测试数据:

static const List<SaleData> test = [ SaleData(name: "Flutter 语言基础 - 梦始之地",value: 944), SaleData(name: "Flutter 渲染机制 - 聚沙成塔",value: 910), SaleData(name: "Flutter 布局探索 - 薪火相传",value: 1094), SaleData(name: "Flutter 滑动探索 - 珠联璧合",value: 1141), SaleData(name: "Flutter 动画探索 - 流光幻影",value: 1589), SaleData(name: "Flutter 手势探索 - 执掌天下",value: 1423), SaleData(name: "Flutter 绘制指南 - 妙笔生花",value: 2956), ];


需要实现的效果如下:这里右侧的图例暂时通过 Flutter 组件实现。遍历数据,通过 Wrap 组件竖向包裹 LegendItem 即可。

image.png

```dart class LegendItem extends StatelessWidget { final SaleData data; final Color color;

const LegendItem({Key? key,required this.data,required this.color}) : super(key: key);

@override Widget build(BuildContext context) { return Wrap( crossAxisAlignment: WrapCrossAlignment.center, children: [ CircleAvatar( backgroundColor: color, radius: 6, ), const SizedBox(width: 6,), Text(data.name,), ], ); } } ```


4. 饼图的点击事件

如下所示,点击扇形区域时,对应的扇形会 沿角平分线 移动,达到 弹出 的效果。

2022年10月27日18-51-06.gif

有了上一篇 SectorShape 中实现的 contains 方法校验点是否在矩形区域内,点击激活的效果就很容易实现。如下所示,在生成 SectorShape 时,校验触点是否在区域内,如果在,只要沿角平分线将 center 值平移即可:

dart List<SectorShape> mapShapeByData(List<double> data, Size size) { _activeIndex = -1; //略同... double rad = shape.startAngle + data[i] * 2 * pi / 2; if (shape.contains(p)) { _activeIndex = i; shape.center = shape.center.translate(5 * cos(rad), 5 * sin(rad)); } //略同... } return shapes; }


点击时,也可以让对应扇形区域的大圆半径增加,达到 凸出 的效果,如下图所示:

2022年10月27日20-44-08.gif

dart int _activeIndex = -1; List<SectorShape> mapShapeByData(List<double> data, Size size) { _activeIndex = -1; //略同... double rad = shape.startAngle + data[i] * 2 * pi / 2; if (shape.contains(p)) { _activeIndex = i; shape.outRadius += 5; } //略同... } return shapes; }

这里用 _activeIndex 记录了激活索引,根据索引在绘制时可以根据路径来绘制阴影:

dart if (i == _activeIndex) { canvas.drawShadow(path, Colors.grey, 2, false); }


到这里,饼图 的基本绘制和点击事件就完成了。但这只是最基础的东西,另外还有很多细节需要考虑,比如动画、图例的事件、结构的封装等,想要做好一个通用的图表库,还有很长的路要走。现在把基础的逻辑打通,也有利于之后的整合。下一篇,将看一下 饼图 中动画的实现,那本文就到这里,谢谢观看 ~

更多 Flutter 绘制技巧,欢迎关注 《Flutter 绘制探索》 专栏。

  • @张风捷特烈 2022.10.28 未允禁转
  • 我的 公众号: 编程之王
  • 我的 github 主页 :  toly1994328
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值