Flutter实时刷新折线图实现

背景:

最近有个项目需求,需要以图表的方式展示实时采集的脑神经数据。数据比较多,以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画线的功能。

arduino 复制代码
void drawPoints(PointMode pointMode, List<Offset> points, Paint paint);

enum PointMode {
  points,
  lines,
  polygon,
}

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

scala 复制代码
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轴的功能。

代码简洁,功能也挺简单,后续有对应的刷新实时要求高的图表都可以用这种方式实现。性能会好很多。

相关推荐
恋猫de小郭43 分钟前
Android 禁止侧载将正式实施,需要等待 24 小时冷静期
android·flutter·harmonyos
FFF-X1 小时前
解决 Flutter Gradle 下载报错:修改默认 distributionUrl
flutter
程序员Ctrl喵20 小时前
异步编程:Event Loop 与 Isolate 的深层博弈
开发语言·flutter
前端不太难1 天前
Flutter 如何设计可长期维护的模块边界?
flutter
小蜜蜂嗡嗡1 天前
flutter列表中实现置顶动画
flutter
始持1 天前
第十二讲 风格与主题统一
前端·flutter
始持1 天前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持1 天前
第十三讲 异步操作与异步构建
前端·flutter
新镜1 天前
【Flutter】 视频视频源横向、竖向问题
flutter
黄林晴1 天前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter