布景:

最近有个项目需求,需要以图表的方法展现实时收集的脑神经数据。数据比较多,以1k的采样率来算,乘以收集通道数,大致需要每秒改写万级以上的数据点。

正常这种图表一般都是在matlab上展现剖析的。不过为了一些其它功用吧,需要在pad上也完成这个功用。之前在ipad上运用三方库有完成过差不多的需求功用,不过这次的项目要求是在android pad上运用。自己不喜爱android原生开发,但对flutter还是比较喜爱的,自然就采用了flutter开发。

东西调研

欲先成其事,必先利其器。磨刀不误砍柴功,遂先找了找flutter的图表库。寻了一遍,flutter图表库用的比较多的着实比较少,只要两个fl_chart和graphic,还有个syncfusion_officechart,不过这个是付费版的(也能免费用,但是条件限制很严苛)。剩下还有一些用的人不太多的库,没有太多研讨。

直接上手模拟生成了一堆数据,然后丢进了fl_chart。然后图是出来了,在50ms改写频率,上万数据点的显现下,图表卡成了幻灯片。一看fps在10左右徜徉。换成graphic,作用略微好了些,but依旧很卡,看了下cpu耗费。大多数cpu都耗费在了x轴取title的操作上,这里因为graphic数据格式界说的关系,当数据多了还有个功用bug。比如1w个点,就要做1w次循环从1w的数据里找title。直接迸裂,做个map,从map里取应该会提高许多吧。不过我的需求比较简单,不需要显现这么杂乱的x轴title。所以直接改库源码,换成index作为x title。功用提高到了25左右的fps。看着满载的cpu和raster负荷。我陷入了沉思。

自己着手锦衣玉食

我的需求只需要展现出数据变化的曲线,而交互什么的在这么高的频率改写下是没有需求的。所以决议自己画个图表。

看了下flutter的canvas绘制,貌似还挺简单。甚至供给了直接经过points画线的功用。

void drawPoints(PointMode pointMode, List<Offset> points, Paint paint);
enum PointMode {
 points,
 lines,
 polygon,
}

所以一切都简单了,改吧改吧,一个简单的手写版折线图就出来了。 大致贴个代码

class WSChartView extends StatelessWidget {
 const WSChartView(this.config, this.points, {super.key});
 final List<Offset> points;
 final ChartConfig config;
 @override
 Widget build(BuildContext context) {
  return Stack(
   children: [
    if (config.yTitles != null)
     SizedBox(
      width: 32,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: config.yTitles!.map((e) => Text(e)).toList()),
     ),
    if (config.yTitles != null)
     SizedBox.expand(
       child: RepaintBoundary(
         child: CustomPaint(
      painter: ChartXYPainter(ChartXYConfig(yTitles: config.yTitles)),
     ))),
    SizedBox.expand(
      child: Container(
        padding: EdgeInsets.fromLTRB(
          config.yTitles != null ? 32 : 8, 8, 8, 8),
        child: RepaintBoundary(
         child: CustomPaint(
          painter: _WSPainter(points, config),
         ),
        )))
   ],
  );
 }
}
class _WSPainter extends CustomPainter {
 _WSPainter(this.points, this.config) {
  _paint = Paint();
  _paint.strokeWidth = 1;
  _paint.color = config.color;
 }
 final List<Offset> points;
 final ChartConfig config;
 late Paint _paint;
 @override
 void paint(Canvas canvas, Size size) {
  var length = points.length;
  var data = <Offset>[];
  final lastX = points.last.dx;
  var xPercent = size.width / lastX;
  var yPercent = size.height / 2 / config.yMax;
  for (var i = 0; i < length; i++) {
   var point = points[i];
   data.add(
     Offset(point.dx * xPercent, (config.yMax - point.dy) * yPercent));
  }
  canvas.drawPoints(PointMode.polygon, data, _paint);
 }
 @override
 bool shouldRepaint(covariant _WSPainter oldDelegate) {
  return true;
 }
}
class ChartXYPainter extends CustomPainter {
 ChartXYPainter(this.config) {
  _paint = Paint();
  _paint.strokeWidth = 2;
  _paint.color = Colors.black;
 }
 late Paint _paint;
 final ChartXYConfig config;
 @override
 void paint(Canvas canvas, Size size) {
  final edge = (
   8.0,
   8.0,
   config.xTitles != null ? 32.0 : 8.0,
   config.yTitles != null ? 32.0 : 8.0
  );
  canvas.drawLine(Offset(edge.$4, size.height - edge.$3),
    Offset(size.width - edge.$2, size.height - edge.$3), _paint);
  canvas.drawLine(Offset(edge.$4, size.height - edge.$3),
    Offset(edge.$4, edge.$1), _paint);
 }
 @override
 bool shouldRepaint(covariant ChartXYPainter oldDelegate) {
  return false;
 }
}

ChartXYPainter 对应的是画xy轴的功用。

Flutter实时改写折线图完成

Flutter实时改写折线图完成

代码简练,功用也挺简单,后续有对应的改写实时要求高的图表都可以用这种方法完成。功用会好许多。