You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter折线图X轴添加事件标记的实现方案咨询

在Flutter心率折线图X轴标记单次/持续事件的实现方案

针对你需要的X轴事件标记需求,推荐使用charts_flutter(Google官方维护的图表库)来实现,以下是具体步骤:

一、单次事件(如运动开始)的标记

通过图表的注释组件在指定时间点添加自定义符号,核心是用SeriesAnnotation配合自定义SymbolRenderer:

1. 定义数据模型

import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/material.dart';

// 心率数据模型
class HeartRateData {
  final DateTime time;
  final int rate;
  HeartRateData(this.time, this.rate);
}

// 单次事件模型
class SingleEvent {
  final DateTime time;
  final String label;
  SingleEvent(this.time, this.label);
}

2. 实现图表与标记渲染

class HeartRateChart extends StatelessWidget {
  final List<HeartRateData> heartRateData;
  final List<SingleEvent> singleEvents;

  HeartRateChart({required this.heartRateData, required this.singleEvents});

  @override
  Widget build(BuildContext context) {
    final seriesList = [
      // 心率折线系列
      charts.Series<HeartRateData, DateTime>(
        id: 'Heart Rate',
        colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
        domainFn: (data, _) => data.time,
        measureFn: (data, _) => data.rate,
        data: heartRateData,
      ),
      // 单次事件标记系列
      charts.Series<SingleEvent, DateTime>(
        id: 'Events',
        domainFn: (event, _) => event.time,
        measureFn: (_, __) => 0, // 固定在X轴附近
        data: singleEvents,
        annotations: singleEvents.map((event) => charts.SeriesAnnotation<DateTime>(
          domain: event.time,
          measure: 0,
          renderer: charts.PointAnnotationRenderer(
            symbolRenderer: _CustomEventSymbol(event.label),
          ),
        )).toList(),
      ),
    ];

    return charts.TimeSeriesChart(
      seriesList,
      animate: false,
      domainAxis: charts.DateTimeAxisSpec(
        renderSpec: charts.SmallTickRendererSpec(labelRotation: 45),
      ),
    );
  }
}

// 自定义符号渲染器:画红色圆点+标签
class _CustomEventSymbol extends charts.SymbolRenderer {
  final String label;
  _CustomEventSymbol(this.label);

  @override
  void paint(charts.ChartCanvas canvas, Rectangle<num> bounds,
      {charts.Color? fillColor, charts.Color? strokeColor, double? strokeWidth}) {
    // 绘制圆点标记
    canvas.drawCircle(
      Point(bounds.centerLeft.x + bounds.width/2, bounds.top + bounds.height/2),
      bounds.width/2,
      charts.MaterialPalette.red.shadeDefault,
      fill: true,
    );
    // 绘制事件标签
    canvas.drawText(
      charts.TextElement(
        label,
        style: charts.TextStyleSpec(fontSize: 10, color: charts.MaterialPalette.black),
      ),
      bounds.centerLeft.x.round(),
      (bounds.top - 15).round(),
      alignment: charts.TextAnchor.middle,
    );
  }

  @override
  Size get preferredSize => const Size(16, 16);
}

二、持续事件(如睡眠)的标记

利用图表的RangeAnnotation组件,在X轴时间区间上绘制背景色块,直观展示持续时长:

1. 添加持续事件模型

// 持续事件模型
class DurationEvent {
  final DateTime startTime;
  final DateTime endTime;
  final String label;
  DurationEvent(this.startTime, this.endTime, this.label);
}

2. 整合到图表中

修改HeartRateChart的参数和build方法,添加RangeAnnotation行为:

class HeartRateChart extends StatelessWidget {
  final List<HeartRateData> heartRateData;
  final List<SingleEvent> singleEvents;
  final List<DurationEvent> durationEvents; // 新增参数

  HeartRateChart({
    required this.heartRateData,
    required this.singleEvents,
    required this.durationEvents,
  });

  @override
  Widget build(BuildContext context) {
    // ... 保留之前的seriesList代码

    return charts.TimeSeriesChart(
      seriesList,
      animate: false,
      domainAxis: charts.DateTimeAxisSpec(
        renderSpec: charts.SmallTickRendererSpec(labelRotation: 45),
      ),
      // 添加持续事件标记
      behaviors: [
        charts.RangeAnnotation(
          durationEvents.map((event) => charts.RangeAnnotationSegment(
            event.startTime,
            event.endTime,
            charts.RangeAnnotationAxisType.domain,
            color: charts.MaterialPalette.green.shadeDefault.lighter,
            label: charts.RangeAnnotationLabel(
              event.label,
              position: charts.AnnotationLabelPosition.margin,
            ),
          )).toList(),
        ),
      ],
    );
  }
}

三、使用示例

// 模拟测试数据
final testHeartRate = [
  HeartRateData(DateTime(2024,5,20,8),70),
  HeartRateData(DateTime(2024,5,20,9),72),
  HeartRateData(DateTime(2024,5,20,10),105),
  HeartRateData(DateTime(2024,5,20,11),85),
  HeartRateData(DateTime(2024,5,20,22),65),
  HeartRateData(DateTime(2024,5,21,6),68),
];

final testSingleEvents = [SingleEvent(DateTime(2024,5,20,9,30), '运动开始')];
final testDurationEvents = [DurationEvent(DateTime(2024,5,20,22), DateTime(2024,5,21,6), '睡眠')];

// 在页面中使用图表
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Padding(
      padding: const EdgeInsets.all(16.0),
      child: HeartRateChart(
        heartRateData: testHeartRate,
        singleEvents: testSingleEvents,
        durationEvents: testDurationEvents,
      ),
    ),
  );
}

替代方案说明

如果使用syncfusion_flutter_charts,思路一致:

  • 单次事件:用TextAnnotation或ShapeAnnotation定位到指定时间点
  • 持续事件:用StripLine设置start和end时间,作为区间标记

内容的提问来源于stack exchange,提问作者pravin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 05:53:24