| SDK | 版本 |
|---|---|
| Flutter | 3.10.5 |
| fl_chart | 0.61.0 |
最近在项目中,我们需要用折线图展示24小时内连续数据的需求,大致如下:
- X轴是时间轴,最长24小时,显示整点即可;Y轴显示的是正常数值;
- 当数据低于20,需要用灰色线条并映射区域颜色;当数值在20~40之间用绿色线条并映射区域颜色;当大于40以后使用红色线条并映射区域颜色;
- 支持点击查看图表上的数据点,显示详细信息;
- 首次展示图表最新时间的数据,即滑动到最右边;
大概图表长这样:
在筛选了一波相关的flutter图表库后,最终选择fl_chart去实现,其示例图表中有类似的DEMO。在尝试接入过程中,遇到了一些问题:
问题思考:
- 在手机等小屏设备中,如果X轴有大量数据,图表需要支持水平滑动查看
- X轴上的时间点所在的位置都需要自己实现,而不是直接传时间数组就解决了
- 所有的数据在图表中的x位置也都需要根据时间点去计算位置
- 不同数值用不同的颜色去展示,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的传值,你会发现颜色之间会存在个渐变混合色的产生,它并不是在颜色边界出立即由灰色变成绿色,中间会存在灰色和绿色的混合色产生,再到绿色,这导致图表看起来有点奇怪。当然如果产品不介意可忽略。
图片仅供参考:
这里我的思路就是把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的计算已经解决了。
记录下自己思考的过程。