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

Flutter fl_chart LineChart跨4天以上数据时渲染报错

Flutter fl_chart LineChart iOS GPU内存错误解决方案

问题

使用fl_chart的LineChart组件时,iOS物理设备上时间跨度超过4天会触发GPU内存错误:

Execution of the command buffer was aborted due to an error during execution. Too much geometry to support memoryless render pass attachments. (0000000d:kIOGPUCommandBufferCallbackErrorOutOfMemoryForParameterBuffer)

测试场景:minx设为6天前时间戳时触发,4天内正常,有时需热重载1-2次复现。最小测试代码如下:

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:intl/intl.dart';

class Playground extends HookConsumerWidget {

  @override
  Widget build(BuildContext context, WidgetRef ref) {

    List<Color> gradientColors = [
      Theme.of(context).colorScheme.primary.withOpacity(.5),
      Theme.of(context).colorScheme.primary,
    ];


    List<FlSpot> test_spots = [
      // FlSpot(
      //   Timestamp.fromDate(DateTime.now().subtract(const Duration(days: 4))).millisecondsSinceEpoch.toDouble() / 1000,
      //   167.0
      // )
    ];

    double minx = Timestamp.fromDate(DateTime.now().subtract(const Duration(days: 6))).millisecondsSinceEpoch.toDouble() / 1000;
    double maxx = Timestamp.fromDate(DateTime.now()).millisecondsSinceEpoch.toDouble() / 1000;

    double miny = 150;
    double maxy = 200;

    return Padding(
      padding: const EdgeInsets.fromLTRB(0, 100, 0, 0),
      child: Stack(
      children: <Widget>[
        AspectRatio(
          aspectRatio: 1.70,
          child: Padding(
            padding: const EdgeInsets.only(
              right: 18,
              left: 12,
              top: 24,
              bottom: 12,
            ),
            child: LineChart(
              LineChartData(
                gridData: FlGridData(
                  show: true,
                  drawVerticalLine: true,
                  horizontalInterval: 1,
                  verticalInterval: 1,
                  getDrawingHorizontalLine: (value) {
                    return const FlLine(
                      color: Colors.white,
                      strokeWidth: 1,
                    );
                  },
                  getDrawingVerticalLine: (value) {
                    return const FlLine(
                      color: Colors.white,
                      strokeWidth: 1,
                    );
                  },
                ),
                titlesData: const FlTitlesData(
                  show: true,
                  rightTitles: AxisTitles(
                    sideTitles: SideTitles(showTitles: false),
                  ),
                  topTitles: AxisTitles(
                    sideTitles: SideTitles(showTitles: false),
                  ),
                  bottomTitles: AxisTitles(
                    sideTitles: SideTitles(
                      showTitles: true,
                      reservedSize: 30,
                      getTitlesWidget: bottomTitleWidgets,
                    ),
                  ),
                  leftTitles: AxisTitles(
                    sideTitles: SideTitles(
                      showTitles: true,
                      getTitlesWidget: leftTitleWidgets,
                      reservedSize: 42,
                    ),
                  ),
                ),
                borderData: FlBorderData(
                  show: true,
                  border: Border.all(color: Theme.of(context).colorScheme.tertiary),
                ),
                minX: minx,
                maxX: maxx,
                minY: miny,
                maxY: maxy,
                lineBarsData: [
                  LineChartBarData(
                    spots: test_spots,
                    isCurved: false,
                    gradient: LinearGradient(
                      colors: gradientColors,
                    ),
                    barWidth: 3,
                    isStrokeCapRound: true,
                    dotData: const FlDotData(
                      show: false,
                    ),
                    belowBarData: BarAreaData(
                      show: true,
                      gradient: LinearGradient(
                        colors: gradientColors
                            .map((color) => color.withOpacity(0.3))
                            .toList(),
                      ),
                    ),
                  ),
                ],
              )
            ),
          ),
        ),
      ],
      )
    );
  }
}

Widget bottomTitleWidgets(double value, TitleMeta meta) {
  if(value == meta.min || value == meta.max){
    return const Text("");
  }
  Widget text = Text(
    DateFormat('Md').format(DateTime.fromMillisecondsSinceEpoch((value * 1000).round())), 
    style: TextStyle(
      color: Colors.grey[600],
      fontSize: 10,
    )
  );
  return SideTitleWidget(
    axisSide: meta.axisSide,
    child: text,
  );
}

Widget leftTitleWidgets(double value, TitleMeta meta) {
  return Text(
    value.toInt().toString(), 
    style: TextStyle(
      color: Colors.grey[600],
      fontSize: 15,
    ),
    textAlign: TextAlign.center
  );
}

原因

你的猜测正确:verticalInterval: 1设置下,X轴单位是秒(时间戳除以1000),6天跨度对应518400个单位,组件会渲染50多万条垂直线,直接耗尽GPU内存。

解决方案

无需更换图表库,调整以下参数即可解决:

1. 调整网格线垂直间隔

将verticalInterval改为合理的时间单位(比如1天=86400秒),减少垂直线数量:

gridData: FlGridData(
  show: true,
  drawVerticalLine: true,
  horizontalInterval: 1,
  verticalInterval: 86400, // 按天间隔
  // ...其他配置
),

2. 转换为相对时间戳缩小数值范围

不用原始时间戳,改用相对于起始时间的差值作为X轴数值,大幅降低数值跨度:

// 定义起始时间
final startDate = DateTime.now().subtract(const Duration(days: 6));
// 计算相对秒数
double minx = 0;
double maxx = Timestamp.fromDate(DateTime.now()).millisecondsSinceEpoch.toDouble() / 1000 -
              Timestamp.fromDate(startDate).millisecondsSinceEpoch.toDouble() / 1000;

同时更新bottomTitleWidgets,把相对时间转回原始日期:

// 在Playground组件中定义startDate,或者通过闭包传递
final startDate = DateTime.now().subtract(const Duration(days: 6));

Widget bottomTitleWidgets(double value, TitleMeta meta) {
  if(value == meta.min || value == meta.max){
    return const Text("");
  }
  // 相对秒数转原始日期
  final date = startDate.add(Duration(seconds: value.round()));
  return SideTitleWidget(
    axisSide: meta.axisSide,
    child: Text(
      DateFormat('Md').format(date), 
      style: TextStyle(
        color: Colors.grey[600],
        fontSize: 10,
      )
    ),
  );
}

3. 可选:关闭垂直网格线

如果不需要垂直网格线,直接禁用:

gridData: FlGridData(
  drawVerticalLine: false,
  // ...其他配置
),

验证

修改后,X轴网格线数量被控制在合理范围,不会再触发GPU内存错误。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 00:09:52