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

FlChart实现X轴AM/PM格式折线图时模拟器无响应问题

问题

尝试使用FlChart绘制过去12小时的折线图,要求X轴标签以「12pm、5am」这类12小时制格式显示,且仅展示首尾标签。但运行代码后模拟器无响应且无报错。

sensorData包含12个按小时存储的DateTime类型对象,相关代码如下:

final Color lineColor;
final List<SpatialGrowSensor> sensorData;

const SensorLineChart({
  Key? key,
  required this.sensorData,
  required this.lineColor,
}) : super(key: key);

LineChartData sampleData1(List<SpatialGrowSensor> sensorData) {
  return LineChartData(
    lineTouchData: lineTouchData1(),
    gridData: gridData(),
    titlesData: titlesData1(),
    borderData: borderData(),
    lineBarsData: lineBarsData1(),
    minY: 0,
    maxY: 10,
  );
}

LineTouchData lineTouchData1() => LineTouchData(
      handleBuiltInTouches: true,
      touchTooltipData: LineTouchTooltipData(
        tooltipBgColor: Colors.blueGrey.withOpacity(0.8),
      ),
    );

FlTitlesData titlesData1() => FlTitlesData(
      bottomTitles: AxisTitles(
        sideTitles: bottomTitles(),
      ),
      rightTitles: AxisTitles(
        sideTitles: SideTitles(showTitles: false),
      ),
      topTitles: AxisTitles(
        sideTitles: SideTitles(showTitles: false),
      ),
      leftTitles: AxisTitles(
        sideTitles: leftTitles(),
      ),
    );

List<LineChartBarData> lineBarsData1() => [
      lineChartBarData1_1(),
    ];

Widget leftTitleWidgets(double value, TitleMeta meta) {
  const style = TextStyle(
    color: Colors.white,
    fontWeight: FontWeight.bold,
    fontSize: 16,
  );

  return Text(value.toString(), style: style, textAlign: TextAlign.center);
}

SideTitles leftTitles() => SideTitles(
      getTitlesWidget: leftTitleWidgets,
      showTitles: true,
      reservedSize: 32,
      interval: 2,
    );

Widget bottomTitleWidgets(double value, TitleMeta meta) {
  const style = TextStyle(
    color: Colors.white,
    fontWeight: FontWeight.bold,
    fontSize: 16,
  );
  // format datetime as '12pm'
  String dateFormated = DateFormat('ha').format(DateTime.fromMillisecondsSinceEpoch(value.toInt(), isUtc: true));
  Widget text = Text(dateFormated, style: style);

  return SideTitleWidget(
    axisSide: meta.axisSide,
    space: 10,
    child: text,
  );
}

SideTitles bottomTitles() => SideTitles(
      showTitles: true,
      reservedSize: 32,
      interval: 6,
      getTitlesWidget: bottomTitleWidgets,
    );

FlGridData gridData() => FlGridData(show: false);

FlBorderData borderData() => FlBorderData(
      show: true,
      border: const Border(
        bottom: BorderSide(color: Color(0xff4e4965), width: 4),
        left: BorderSide(color: Colors.transparent),
        right: BorderSide(color: Colors.transparent),
        top: BorderSide(color: Colors.transparent),
      ),
    );

LineChartBarData lineChartBarData1_1() {
  final flSpotData = sensorData.map((sensor) {
    return FlSpot(sensor.dateTime.millisecondsSinceEpoch.toDouble(), sensor.sensorValue);
  }).toList();
  return LineChartBarData(
    isCurved: true,
    color: lineColor,
    barWidth: 4,
    dotData: FlDotData(show: false),
    belowBarData: BarAreaData(show: false),
    preventCurveOverShooting: true,
    spots: flSpotData,
  );
}

@override
Widget build(BuildContext context) {
  return Padding(
    padding: const EdgeInsets.all(16.0),
    child: Container(
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(8),
        color: HexColor('#0c110a'),
      ),
      child: SizedBox(
        height: 200,
        child: Padding(
          padding: const EdgeInsets.all(32.0),
          child: LineChart(
            sampleData1(sensorData),
          ),
        ),
      ),
    ),
  );
}
解决方案

问题根源

模拟器无响应的核心原因是X轴interval设置错误:你设置了interval: 6,但X轴数值是毫秒级时间戳,6毫秒的间隔会让FlChart尝试生成海量标签,直接耗尽计算资源导致卡死。同时当前逻辑无法实现仅显示首尾标签的需求。

修复代码

修改bottomTitles方法,移除错误的interval参数,通过判断时间戳是否为首尾数据的时间戳,仅渲染首尾标签:

SideTitles bottomTitles() => SideTitles(
      showTitles: true,
      reservedSize: 32,
      getTitlesWidget: (value, meta) {
        // 空数据判断,避免运行报错
        if (sensorData.isEmpty) return const SizedBox.shrink();
        
        // 获取首尾数据的时间戳
        final firstTimestamp = sensorData.first.dateTime.millisecondsSinceEpoch.toDouble();
        final lastTimestamp = sensorData.last.dateTime.millisecondsSinceEpoch.toDouble();
        
        // 仅对首尾时间戳生成标签
        if (value == firstTimestamp || value == lastTimestamp) {
          const style = TextStyle(
            color: Colors.white,
            fontWeight: FontWeight.bold,
            fontSize: 16,
          );
          // 格式化时间为12小时制
          String dateFormated = DateFormat('ha').format(
            DateTime.fromMillisecondsSinceEpoch(value.toInt(), isUtc: true)
          );
          return SideTitleWidget(
            axisSide: meta.axisSide,
            space: 10,
            child: Text(dateFormated, style: style),
          );
        }
        // 其他位置返回空组件,不显示标签
        return const SizedBox.shrink();
      },
    );

额外注意事项

  1. 时区检查:如果sensorData中的DateTime是本地时间而非UTC时间,需要移除isUtc: true参数,否则时间格式化会出现偏差。
  2. 数据有效性:确保sensorData不为空且至少包含2个数据点,否则首尾标签逻辑无法生效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 04:06:25