Flutter使用fl_chart的总结思考

3,039 阅读6分钟
SDK版本
Flutter3.10.5
fl_chart0.61.0

最近在项目中,我们需要用折线图展示24小时内连续数据的需求,大致如下:

  1. X轴是时间轴,最长24小时,显示整点即可;Y轴显示的是正常数值;
  2. 当数据低于20,需要用灰色线条并映射区域颜色;当数值在20~40之间用绿色线条并映射区域颜色;当大于40以后使用红色线条并映射区域颜色;
  3. 支持点击查看图表上的数据点,显示详细信息;
  4. 首次展示图表最新时间的数据,即滑动到最右边;

大概图表长这样:

Pasted Graphic 1.png

在筛选了一波相关的flutter图表库后,最终选择fl_chart去实现,其示例图表中有类似的DEMO。在尝试接入过程中,遇到了一些问题:

问题思考:

  1. 在手机等小屏设备中,如果X轴有大量数据,图表需要支持水平滑动查看
  2. X轴上的时间点所在的位置都需要自己实现,而不是直接传时间数组就解决了
  3. 所有的数据在图表中的x位置也都需要根据时间点去计算位置
  4. 不同数值用不同的颜色去展示,fl_chart的颜色和渐变效果都是用百分比去实现,数据密集程度和起始时间都是影响百分比的关键因素

解决方案

一、滑动问题

  • 方案a:外套scrollview,让图表在内部滚动,需要根据数据量计算图表最大宽度
    • 优点:方便实现,易于理解
    • 弊端:体验欠佳,因为在滑至最右边时左侧的Y轴会被遮挡。可能需要额外写Y轴固定在最左侧;体验不佳
  • 方案b:在摸索过程中,发现可以通过设置minX和maxX,图表外套GestureDetector组件监听手势滑动实现,github上issue里已经有参考方案
    • 优点:滑动效果优于方案a,无需重写图表的Y轴
    • 弊端:实现难度大,边界处理

最终还是选择了方案b,目前体验下来没有遇到崩溃的问题。

二、X轴坐标问题

其实2、3点问题,不算难,只是觉得有点麻烦,本以为图表库可以轻松解决,但最终还是多了些工作量。

  • X轴上的整点需要根据接口数据动态得出所有的时间点,利用set存储整点数据
  • 接口数据的时间戳以秒为最小单位,得出在X轴上的偏移值大概思路:
    • 首先接口数据是按照时间asc排序的
    • 其次可以算出X轴上整点的set集合
    • 最后可以算出所有数据的X偏移位置,大致计算方法:(data.minute*60+data.second)/3600+{集合里的索引}。

举个例子:假如数据第一个时间点是12:30,X轴对应的第一个整点是12:00,索引就是0,那么该数据的X偏移值就是(30*60+0)/3600+0,以此类推,13点区间的数据就是索引值变成了1;

三、颜色映射问题

这个问题也是最麻烦的问题,起初在尝试使用少量切平均时间点(每10分钟一条)的数据中,未发现这个问题,我的问题方案是每条数据都在颜色数组里添加一条颜色数值,1:1的关系,多少数据就有多少个颜色数值。后面因为数据点可能在几秒钟、几十秒甚至1分钟都有可能产生,最终导致颜色映射的有问题。仔细查看官网示例,发现颜色的设定参数其实都是百分比来的,并不是1对1的关系。这个时候就要用到另外个参数stops。

在LinearGradient类中,colors数组定义了渐变中使用的颜色,而stops数组定义了这些颜色在渐变中的具体位置。如果stops是null,则假定颜色均匀分布,即颜色平均分布在渐变路径上。如果提供stops数组,它将指定每种颜色的准确位置

例如:如果传入的颜色数组是[灰色,绿色,红色],那么这3个颜色均匀显示在图表中,自灰色渐变到绿色再到红色,各占比33%左右。

stops: 定义每个颜色对应的渐变位置,必须在[0, 1]的范围内并且数组长度要和colors长度一致。

  • 例如:[0.25, 0.75] 表示灰色将开始于渐变路径的25%,而绿色将结束于渐变路径的75%。

精确计算出颜色覆盖区域,你需要考虑这些值以及它们相对于LinearGradient使用的开始和结束点的位置。

同时还有一个问题,如果按照官方stops的传值,你会发现颜色之间会存在个渐变混合色的产生,它并不是在颜色边界出立即由灰色变成绿色,中间会存在灰色和绿色的混合色产生,再到绿色,这导致图表看起来有点奇怪。当然如果产品不介意可忽略。

图片仅供参考: image.png

这里我的思路就是把stops的每个边界值都给一个范围,而不是单个值决定2个颜色的边界。

如果灰色值的占比是0~0.24999,那么数组的前2位就是[0,0.24999]
如果绿色的范围是0.25~0.75,那么数组就是[0,0.24999,0.25,0.75]
最后数组就是[0,0.24999,0.25,0.75,0.759999,1]

如果能得出上面的数组基本解决颜色渐变过程中的混色问题和颜色stops位置问题。

如何精确算出每个点对应的stops范围

原来我的想法是根据已经生成的x坐标位置去计算,也就是利用当前点的x坐标除以最后一个点x的值得出结果,但是在实际过程中,这种会有问题。这里我就直接给出示例代码:


void calculateStopsForGradient(List<FlSpot> spots) {
  // 确定x轴上的最大值和最小值,默认数组已经按照asc排序
  double minX = spots.first.x;
  double maxX = spots.last.x;
  double rangeX = maxX - minX;
  
  List<double> stops = [];
  List<Color> colors = [];
   for (int i = 0; i < dataLength; i++) {
      Color color = getChartDataColor();
      // 首次先添加颜色
      if (colors.isEmpty) {
        colors.add(color);
        stops.add(i / dataLength);
        continue;
      }
      if (colors[colors.length - 1] != color) {
        // 添加颜色之前,检查是否和前一个颜色重复
        // 如果重复了1次,则添加一个颜色
        colors.add(colors.last);
        stops.add((spots[i - 1].x - minX) / rangeX);
        colors.add(color);
        stops.add((spots[i].x - minX) / rangeX);
      }
    }
    // 如果末尾不是1,则补偿一下
    if (stops.last != 1) {
      colors.add(colors.last);
      stops.add(1);
    }
}

基本这里colors 和 stops的计算已经解决了。

记录下自己思考的过程。