imaNNeo / fl_chart

FL Chart is a highly customizable Flutter chart library that supports Line Chart, Bar Chart, Pie Chart, Scatter Chart, and Radar Chart.
https://flchart.dev
MIT License
6.86k stars 1.78k forks source link

When using line charts, stuttering and crashing occurs #1740

Open wls0 opened 2 months ago

wls0 commented 2 months ago

Describe the bug A clear and concise description of what the bug is.

When using linear charts, it hangs or crashes. When there is a lot of data, the simulator (iphone15 max pro) turns off and it stutters even when there are 4 data

To Reproduce

//main.dart

import 'package:shared_account_app/src/views/read/folder/a.viewmodel.dart';
import 'package:provider/provider.dart';
import 'package:flutter/rendering.dart';
import 'package:intl/date_symbol_data_local.dart';

import 'src/views/read/folder/a.view.dart';

void main() {
  debugPaintSizeEnabled = false;

  initializeDateFormatting();
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider<AViewModel>(
          create: (context) => AViewModel(),
        ),
      ],
      child: const App(),
    ),
  );
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'main',
      home: MainPage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class MainPage extends StatelessWidget {
  const MainPage({super.key});

  @override
  Widget build(BuildContext context) {
    return const _MainPageState();
  }
}

class _MainPageState extends StatelessWidget {
  const _MainPageState({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: AView(),
    );
  }
}

//model.dart
class A {
  final DateTime date;
  final int amount;

  A({
    required this.date,
    required this.amount,
  });

  factory A.fromJson(Map<String, dynamic> json) {
    return A(
      date: DateTime.parse(json['date']),
      amount: int.parse(json['amount'].toString()),
    );
  }
}

//service.dart
import 'dart:convert';
import 'dart:io';

import 'a.model.dart';

class AService {
  Future<List<A>> getAData(DateTime startDate, DateTime endDate) async {
    var file = File(
        '/Users/jin0/Desktop/project2/shared_account_app/mock/annual_trend_item.mock.json');
    var res = file.readAsStringSync();
    var json = jsonDecode(res);

    List<A> annualTrendReport = (json['body'] as List)
        .map((e) => A.fromJson(e as Map<String, dynamic>))
        .toList();

    return annualTrendReport;
  }
}

//view.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_account_app/src/views/read/folder/a.viewmodel.dart';
import 'package:fl_chart/fl_chart.dart';

class AView extends StatefulWidget {
  const AView({super.key});

  @override
  State<AView> createState() => _AViewState();
}

class _AViewState extends State<AView> {
  DateTime startDate = DateTime.now();
  DateTime endDate = DateTime.now();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      _initializeDates();
      _loadInitialData();
    });
  }

  void _initializeDates() {
    final now = DateTime.now();
    startDate = DateTime(now.year, now.month, 1);
    endDate = DateTime(now.year, now.month + 1, 0);
  }

  Future<void> _loadInitialData() async {
    final aViewModel = Provider.of<AViewModel>(context, listen: false);
    aViewModel.cleanReportViewDatas();
    await aViewModel.getAnnualTrendReportDatas(startDate, endDate);
  }

  List<Color> gradientColors = [
    Colors.brown,
    Colors.black,
  ];

  bool showAvg = false;

  List<FlSpot> spots = [];
  double minY = 0;
  double maxY = 0;

  void _updateChartData(List<Map<String, dynamic>> annualTrendItems,
      double maxAmount, double minAmount) {
    print(annualTrendItems);
    spots.clear();
    double x = 0;
    minY = minAmount.toDouble();
    maxY = maxAmount.toDouble();

    spots = annualTrendItems.map((e) {
      FlSpot spot = FlSpot(x, (e['amount']));
      x++;
      return spot;
    }).toList();
  }

  @override
  Widget build(BuildContext context) {
    return Consumer<AViewModel>(
      builder: (context, aViewModel, child) {
        return Stack(
          children: <Widget>[
            AspectRatio(
              aspectRatio: 1.5,
              child: Padding(
                padding: const EdgeInsets.only(
                  right: 18,
                  left: 12,
                  top: 24,
                  bottom: 12,
                ),
                child: LineChart(
                  mainData(aViewModel),
                ),
              ),
            ),
            SizedBox(
              width: 60,
              height: 34,
              child: TextButton(
                onPressed: () {
                  setState(() {
                    showAvg = !showAvg;
                  });
                },
                child: Text(
                  'avg',
                  style: TextStyle(
                    fontSize: 12,
                    color:
                        showAvg ? Colors.white.withOpacity(0.5) : Colors.white,
                  ),
                ),
              ),
            ),
          ],
        );
      },
    );
  }

  LineChartData mainData(AViewModel aViewModel) {
    _updateChartData(
      aViewModel.aTrendItems,
      aViewModel.maxAmount,
      aViewModel.minAmount,
    );

    return 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.purple,
            strokeWidth: 1,
          );
        },
      ),
      titlesData: const FlTitlesData(
        show: true,
        rightTitles: AxisTitles(
          sideTitles: SideTitles(showTitles: false),
        ),
        topTitles: AxisTitles(
          sideTitles: SideTitles(showTitles: false),
        ),
        bottomTitles: AxisTitles(
          sideTitles: SideTitles(
            showTitles: false,
            reservedSize: 30,
            interval: 1,
          ),
        ),
        leftTitles: AxisTitles(
          sideTitles: SideTitles(
            showTitles: false,
            interval: 1,
            reservedSize: 70,
          ),
        ),
      ),
      borderData: FlBorderData(
        show: false,
      ),
      minX: 0,
      maxX: spots.length.toDouble(),
      minY: minY,
      maxY: maxY,
      lineBarsData: [
        LineChartBarData(
          spots: spots,
          isCurved: true,
          gradient: LinearGradient(
            colors: gradientColors,
          ),
          barWidth: 5,
          isStrokeCapRound: true,
          dotData: const FlDotData(
            show: false,
          ),
          belowBarData: BarAreaData(
            show: false,
            gradient: LinearGradient(
              colors: gradientColors
                  .map((color) => color.withOpacity(0.1))
                  .toList(),
            ),
          ),
        ),
      ],
      lineTouchData: LineTouchData(
        enabled: false,
        touchTooltipData: LineTouchTooltipData(
          getTooltipColor: (spot) => Colors.white,
        ),
      ),
    );
  }
}

//a.viewmodel.dart
import 'package:flutter/material.dart';

import 'a.model.dart';
import 'a.service.dart';

class AViewModel extends ChangeNotifier {
  final AService _aService = AService();

  void cleanReportViewDatas() {
    _aTrendItems.clear();
    notifyListeners();
  }

  final List<Map<String, dynamic>> _aTrendItems = [];

  List<Map<String, dynamic>> get aTrendItems => _aTrendItems;
  double _maxAmount = 0;
  double _minAmount = 0;
  double get maxAmount => _maxAmount;
  double get minAmount => _minAmount;

  Future<void> getAnnualTrendReportDatas(
      DateTime startDate, DateTime endDate) async {
    List<A> aTrend = await _aService.getAData(startDate, endDate);
    double sumAmount = 0;
    _aTrendItems.clear();
    _aTrendItems.addAll(aTrend.map((e) {
      double amount = sumAmount + e.amount;
      sumAmount = amount;

      return {
        'date': e.date.toString(),
        'amount': amount,
      };
    }));
    _maxAmount = _aTrendItems.last['amount'];
    _minAmount = _aTrendItems.first['amount'];

    notifyListeners();
  }
}

Screenshots If applicable, add screenshots, or videoshots to help explain your problem.

Versions