Flutter基于charts_flutter开发时间序列图,如何实现选中刻度后展示关联数据
实现选中时间刻度展示关联数据的修改方案
核心修改逻辑(基于现有代码)
1. 开启图表的domain选中交互支持
在charts.TimeSeriesChart配置中补充选中相关参数,让坐标轴刻度可响应点击:
- 开启信息类选中模型
- 配置渲染器支持按域值就近选中(点击刻度自动匹配对应时间)
- 绑定选中变化监听回调
2. 状态管理改造
把原无状态的EndPointsAxisTimeSeriesChart改为有状态组件,保存当前选中的时间与对应数值,触发UI刷新。
3. 选中事件匹配逻辑
从选中模型中取出当前选中的时间域值,遍历两个时间序列的数据源,匹配对应时间的业务数值,处理时间精度对齐(避免因毫秒差导致匹配失败)。
4. 展示层适配
在原有标题区域下方、图表上方的文本区域,新增选中数据的展示,复用原有UI风格,无选中时保留默认统计值。
具体代码修改示例
(1)改造EndPointsAxisTimeSeriesChart为有状态组件,新增选中逻辑
// 替换原有无状态类,新增选中回调参数(选中域值时触发,传入选中时间、两个系列的对应数值) class EndPointsAxisTimeSeriesChart extends StatefulWidget { final List<charts.Series<TimeSeriesSales, DateTime>> seriesList; final bool animate; final void Function(DateTime? selectedTime, double? series1Value, double? series2Value)? onDomainSelected; const EndPointsAxisTimeSeriesChart( this.seriesList, { super.key, this.animate = false, this.onDomainSelected, }); @override State<EndPointsAxisTimeSeriesChart> createState() => _EndPointsAxisTimeSeriesChartState(); } class _EndPointsAxisTimeSeriesChartState extends State<EndPointsAxisTimeSeriesChart> { DateTime? _selectedTime; @override Widget build(BuildContext context) { return charts.TimeSeriesChart( widget.seriesList, animate: widget.animate, // 保留原有配置 domainAxis: const charts.DateTimeAxisSpec(), dateTimeFactory: const charts.LocalDateTimeFactory(), primaryMeasureAxis: charts.NumericAxisSpec( tickProviderSpec: const charts.BasicNumericTickProviderSpec(desiredTickCount: 4), ), // 新增:选中交互配置 selectionModelType: charts.SelectionModelType.info, // 开启信息选中模型 onSelectionChanged: _handleSelectionChange, // 绑定选中变化回调 defaultRenderer: charts.LineRendererConfig( includePoints: false, includeArea: false, // 新增:点击任意位置(含坐标轴刻度)自动匹配最近的时间域值 selectNearestByDomain: true, ), ); } // 新增:选中事件处理逻辑 void _handleSelectionChange(charts.SelectionModel model) { final selectedDomain = model.selectedDomain; if (selectedDomain == null) { setState(() => _selectedTime = null); widget.onDomainSelected?.call(null, null, null); return; } // 裁剪时间精度,避免毫秒差导致匹配失败(可根据业务粒度调整裁剪规则) final targetTime = DateTime( selectedDomain.year, selectedDomain.month, selectedDomain.day, selectedDomain.hour, selectedDomain.minute, ); // 匹配第一个系列(Consumos)的对应值 final series1Data = widget.seriesList.firstWhere((s) => s.id == 'Consumos').data; double? value1; for (final item in series1Data) { final itemTime = DateTime( item.time.year, item.time.month, item.time.day, item.time.hour, item.time.minute, ); if (itemTime == targetTime) { value1 = item.sales; break; } } // 匹配第二个系列(Consumos2)的对应值 final series2Data = widget.seriesList.firstWhere((s) => s.id == 'Consumos2').data; double? value2; for (final item in series2Data) { final itemTime = DateTime( item.time.year, item.time.month, item.time.day, item.time.hour, item.time.minute, ); if (itemTime == targetTime) { value2 = item.sales; break; } } // 更新状态并触发上层回调 setState(() => _selectedTime = selectedDomain); widget.onDomainSelected?.call(targetTime, value1, value2); } }
(2)适配上层graficoLinealDoble方法,展示选中数据
修改原有上层UI的文本区域,监听选中态变化,替换原有默认统计值:
Widget graficoLinealDoble(BuildContext context, List<DateTime> listOfFechas, List<double> listOfCantidades, List<DateTime> listOfFechas2, List<double> listOfCantidades2,String titulo, String subtitulo, String medidaSubtitulo, [String color = '#E0B872', String color2 = '#E5E5E9']) { // 用ValueNotifier管理选中态,兼容现有函数式写法(也可抽成独立Stateful组件管理) final selectedValueNotifier = ValueNotifier<(DateTime?, double?, double?)>((null, null, null)); return Column( children: <Widget>[ const SizedBox(height: 8.0), Text( titulo, maxLines: 1, overflow: TextOverflow.fade, softWrap: false, style: TextStyle( fontSize: 18.0, color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold, ), ), // 改造原有数值展示区域,监听选中态变化 ValueListenableBuilder( valueListenable: selectedValueNotifier, builder: (context, selectedData, _) { final selectedTime = selectedData.$1; final value1 = selectedData.$2; final value2 = selectedData.$3; // 有选中时展示对应数据,无选中时展示原有默认值 final displayText = selectedTime == null ? subtitulo // 可自定义选中时间的格式化展示,比如仅显示日期/时分 : '${value1?.toStringAsFixed(1) ?? '-'} / ${value2?.toStringAsFixed(1) ?? '-'}'; final displayUnit = selectedTime == null ? medidaSubtitulo : medidaSubtitulo; return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( displayText, style: const TextStyle(fontSize: 26.0, color: Colors.grey), ), Text( displayUnit, style: TextStyle( fontSize: 16.0, color: Theme.of(context).primaryColor, ), strutStyle: const StrutStyle(fontSize: 26.0), ), ], ); }, ), Flexible( child: EndPointsAxisTimeSeriesChart( _crearLinea( listOfFechas, listOfCantidades, color, listOfFechas2, listOfCantidades2, color2, ), animate: true, // 绑定选中回调,更新选中态 onDomainSelected: (time, v1, v2) { selectedValueNotifier.value = (time, v1, v2); }, ), ), ], ); }
补充说明
- 时间匹配的精度裁剪规则可根据实际业务调整:如果时间粒度按天,可去掉比对时、分部分;如果是按小时,可去掉分钟比对。
- 可新增刻度选中高亮效果:在
domainAxis的renderSpec中配置TickRendererSpec的labelStyle,结合_selectedTime动态修改选中刻度的颜色或背景。 - 封装的
onDomainSelected回调可直接复用在不同业务页面,无需修改图表内部逻辑。
内容的提问来源于stack exchange,提问作者David L
相关产品推荐
相关产品推荐

