如何用Flutter Syncfusion SfCartesianChart实现不同长度的多图表系列
问题描述
我有一个已排序的嵌套列表,每个子列表存储特定日期的数据条目,且每个子列表的条目数量不固定。尝试用SfCartesianChart展示这些数据时,用多ColumnSeries的方式达不到预期效果——当前每个日期的柱子会被分散显示,而预期是同一日期的柱子集中在一起,同时要实现包含列序号与日期的二级X轴标签。
解决方案
多ColumnSeries的问题在于它会将不同日期的X轴值视为独立分类,导致柱子分散。正确的思路是合并所有数据为单一数据源,通过X轴分组实现同一日期下的柱子聚合,同时配置双X轴来显示序号和日期。
实现步骤
- 合并所有子列表数据,为每个条目添加日期分组标识和组内序号
- 使用单个ColumnSeries,通过自定义X轴映射实现分组显示
- 配置双X轴:主X轴显示组内序号,二级X轴显示对应日期
完整代码
import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; void main() { runApp(const MyHomePage()); } class MyEvent { final String date; final int value; final String groupDate; // 仅日期部分,用于分组 final int groupIndex; // 组内序号 MyEvent(this.date, this.value, this.groupDate, this.groupIndex); } class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { late List<MyEvent> mergedData; @override void initState() { super.initState(); mergedData = _mergeAndFormatData(); } List<MyEvent> _mergeAndFormatData() { final List<List<MyEvent>> originalList = [ [ MyEvent('2023-07-01T15.22', 11, '', 0), MyEvent('2023-07-01T17.43', 10, '', 0), MyEvent('2023-07-01T19.44', 16, '', 0), MyEvent('2023-07-01T23.34', 13, '', 0), ], [ MyEvent('2023-12-23T08.13', 3, '', 0), MyEvent('2023-12-23T19.55', 4, '', 0), ], [ MyEvent('2023-12-27T06.04', 14, '', 0), MyEvent('2023-12-27T10.13', 12, '', 0), MyEvent('2023-12-27T11.46', 11, '', 0), ], [ MyEvent('2023-12-31T06.04', 5, '', 0), ], ]; final List<MyEvent> result = []; for (var dayList in originalList) { if (dayList.isEmpty) continue; // 提取纯日期部分(如2023-07-01) final groupDate = dayList.first.date.substring(0, 10); for (int i = 0; i < dayList.length; i++) { result.add(MyEvent( dayList[i].date, dayList[i].value, groupDate, i + 1, // 组内序号从1开始 )); } } return result; } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Center( child: SizedBox( height: 400.0, child: SfCartesianChart( // 主X轴:显示组内序号 primaryXAxis: CategoryAxis( axisLabelFormatter: (details) { return ChartAxisLabel( mergedData[details.valueIndex].groupIndex.toString(), const TextStyle(fontSize: 10), ); }, axisLine: const AxisLine(width: 0), // 隐藏主X轴轴线 ), // 二级X轴:显示日期分组 axes: <ChartAxis>[ CategoryAxis( name: 'secondaryX', interval: 1, labelPlacement: LabelPlacement.betweenTicks, axisLabelFormatter: (details) { String currentDate = ''; int groupStartIndex = 0; // 匹配当前索引对应的日期分组 for (int i = 0; i < mergedData.length; i++) { if (mergedData[i].groupDate != currentDate) { currentDate = mergedData[i].groupDate; if (details.valueIndex == groupStartIndex) { return ChartAxisLabel( currentDate.substring(5), // 仅显示月-日 const TextStyle(fontSize: 10), ); } groupStartIndex = i; } } return const ChartAxisLabel('', TextStyle()); }, plotOffset: 20, // 调整二级轴与主X轴的间距 axisLine: const AxisLine(width: 1), ), ], primaryYAxis: NumericAxis(), series: <ChartSeries>[ ColumnSeries<MyEvent, String>( dataSource: mergedData, xValueMapper: (MyEvent event, index) => index.toString(), yValueMapper: (MyEvent event, index) => event.value, // 同一日期的柱子使用相同颜色 pointColorMapper: (MyEvent event, index) { final dateGroups = mergedData.map((e) => e.groupDate).toSet().toList(); final colorIndex = dateGroups.indexOf(event.groupDate); return [Colors.blue, Colors.orange, Colors.green, Colors.purple][colorIndex % 4]; }, spacing: 0.2, // 柱子间距 width: 0.8, // 柱子宽度 ), ], ), ), ), ), ); } }
关键说明
- 数据预处理:将嵌套列表合并为单一列表,给每个条目添加分组信息,确保后续X轴可以按日期聚合。
- 双X轴配置:主X轴负责显示每个柱子的组内序号,二级X轴在下方显示对应的日期,通过
axisLabelFormatter精准控制标签内容。 - 样式优化:通过
pointColorMapper区分不同日期的柱子,调整spacing和width让同一日期的柱子紧密聚集,还原预期效果。
内容的提问来源于stack exchange,提问作者Dmitry
相关产品推荐
相关产品推荐

