40 · 图表与数据可视化
一句话定位:图表选型是"数据形状 × 交互需求 × 性能预算"的三角决策,对应前端 ECharts / Recharts 的选型。能源 App 满是曲线、饼图、柱状图,本篇给出 Flutter 侧的完整选型矩阵与落地套路。
开篇速览
| 问题 | 答案 |
|---|---|
| 前端对应物 | ECharts / Recharts / D3;Canvas 自绘 |
| 三条路线 | 库(fl_chart)、自绘(CustomPainter)、WebView 内嵌 ECharts |
| 能源典型图 | 发电曲线、负荷曲线、电量构成饼图、设备统计柱状图 |
| 一句话红线 | "图表库画不出来"多半是选型错,不是能力不足;先按"静态 / 交互 / 大屏 / 高性能"四条线定路线 |
代码来源说明:HaierEnergy 已有自研图表组件(
HeLineChart、HeChartPanel),基于fl_chart封装并扩展了 tooltip 与横屏展开。 本篇讲选型框架,落地实现对照 11 · 组件与主题 与 28 · 自定义渲染实战。
一、四条路线怎么选
| 需求 | 推荐 | 优点 | 代价 |
|---|---|---|---|
| 常规折线/柱状/饼图,标准交互 | fl_chart | 成熟、类型丰富、主题可控 | 复杂交互要自己扩展 |
| 极简/性能敏感(大量点、实时刷新) | CustomPainter 自绘 | 零依赖、帧率高、尺寸自由 | 无现成交互,全手写 |
| 超复杂图表(桑基、3D、GIS) | WebView + ECharts | 前端生态全覆盖 | 桥接成本、体验割裂、首屏慢 |
| 设计高度定制的品牌图表 | fl_chart 二次封装 | 兼顾效率与定制 | 要建组件层 |
二、fl_chart:80% 场景的默认解
2.1 基本用法
dart
LineChart(
LineChartData(
minX: 0,
maxX: 23,
minY: 0,
maxY: 100,
lineBarsData: [
LineChartBarData(
spots: [for (var i = 0; i < 24; i++) FlSpot(i.toDouble(), power[i])],
color: AppColors.primary,
isCurved: true,
barWidth: 2,
),
],
titlesData: ..., // X/Y 轴标题
gridData: ..., // 网格线
lineTouchData: LineTouchData(enabled: true), // 触摸 tooltip
),
)2.2 交互三件套
- tooltip:
lineTouchData返回触摸位置,自绘气泡(项目HeLineChart已做); - 横屏展开:图表在全屏时用
RotatedBox伪横屏,禁止全局系统横屏(见 11 篇 的相关说明); - 空态/加载态:图表数据为空时要给占位,不能画一条假线。
三、CustomPainter:什么时候自绘
当"每帧数据量大"或"要完全控制视觉"时自绘:
dart
class _AreaChartPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final path = Path();
// 根据数据点构造曲线路径
for (var i = 0; i < points.length; i++) {
final dx = i / (points.length - 1) * size.width;
final dy = size.height - points[i] * size.height;
i == 0 ? path.moveTo(dx, dy) : path.lineTo(dx, dy);
}
// 填充面积 + 描边
canvas.drawPath(path, areaPaint);
canvas.drawPath(path, linePaint);
}
@override
bool shouldRepaint(covariant _AreaChartPainter old) =>
old.points != points;
}自绘红线:shouldRepaint 必须基于数据比较返回,否则每帧全量重绘会卡;动画用 AnimationController 驱动渐变进度,别在 paint 里开循环。
四、WebView + ECharts:什么时候才用
以下情况才值得付出桥接成本:
- 图表类型 fl_chart 没有(桑基/3D/地图);
- 已有大屏 H5 图表可直接复用;
- 需要成熟的数据缩放、联动等复杂交互。
代价清单:WebView 内存大、首屏慢、与 Flutter 主题不一致、双向通信要维护(JavascriptChannel)。能源 App 的常规统计图不要走这条路。
五、主题与设计系统统一
图表不是独立存在的,要接入 AppTokens(见 11 篇):
- 颜色:直接用设计 token 的图表系列色(项目已有
defaultChartPalette); - 字号:坐标轴、图例、tooltip 用小字号 token;
- 暗色模式:图表配色要随主题切换,不能硬编码。
六、性能与测试
6.1 性能
| 场景 | 手段 |
|---|---|
| 大量数据点 | 降采样(抽点/平滑),超过阈值减少显示点 |
| 实时刷新 | 用 CustomPainter 增量重绘,避免重建 Widget 树 |
| 滚动页面里的图表 | 延迟加载(VisibilityDetector),不可见不绘制 |
| 横屏全屏 | 只加载一个图表实例,离开即销毁 |
6.2 测试
图表重点测数据映射(点坐标、颜色、空态),不是测像素。用 Widget test 断言渲染不抛异常 + 空态显示;视觉回归用 golden(见 19 篇 · 4.4 Golden)。
动手练习
- 用 fl_chart 画一个 24 点发电曲线(含 tooltip + 空态占位);
- 用 CustomPainter 画一个面积图,验证
shouldRepaint只按数据变化触发; - 给图表接 AppTokens 配色,写一个"深色模式下颜色切换"的 Widget test。
自检清单
- [ ] 能按"静态 / 交互 / 大屏 / 高性能"四线选型
- [ ] 会配 fl_chart 的坐标轴、网格、tooltip
- [ ] 知道 CustomPainter 自绘的性能红线(shouldRepaint、避免 paint 循环)
- [ ] 知道 WebView + ECharts 的真实代价,不滥用
- [ ] 会让图表接设计 token 与暗色模式
常见误区
- "所有图表都上 WebView ECharts":桥接成本与体验损失远超收益,常规图用 fl_chart;
- "fl_chart 画不了就手绘":先确认是不是缺配置,手绘前要有明确性能/定制理由;
- "shouldRepaint 返回 true 就行":会导致滚动时每帧全量重绘,卡顿元凶;
- "图表配色单独一套":不接 AppTokens,暗色/改版会漏改。
相关文档
- 00 · 学习路线图 —— 阅读顺序
- 28 · 自定义渲染实战 —— CustomPainter 基础
- 11 · 组件与主题 —— 图表组件与 AppTokens
- 19 · 测试实战 —— Golden 截图测试
- 41 · 自定义 Shader 与 GLSL —— 图表特效的上限