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.88k stars 1.79k forks source link

Throw exception when set state with length of barGroups property change in BarChart #1062

Closed manhdung98na closed 1 year ago

manhdung98na commented 2 years ago

When i set swapAnimationDuration != 0 and change the length of barGroups property in BarChartData, it will throw exception if new length is less than old length

Versions

https://user-images.githubusercontent.com/81342901/174214548-477a98ce-a438-4e24-866e-bd835c5e188b.mp4

imaNNeo commented 2 years ago

May I ask to you provide a simplified reproducible code (in a main.dart file)? It helps me to find the problem faster.

manhdung98na commented 2 years ago

Here is main file:

// ignore: avoid_web_libraries_in_flutter
import 'dart:html';

// import 'package:cloud_functions/cloud_functions.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:oneres_frontend/ui/page/customerorder/customerpage.dart';
import 'package:oneres_frontend/ui/page/loadingpage.dart';
import 'package:oneres_frontend/ui/page/mainpage.dart';
import 'package:provider/provider.dart';

import 'manager/generalmanager.dart';
import 'ui/page/landingpage.dart';
import 'util/designmanagement.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  String href = window.location.href;
  if (kIsWeb && RegExp(r'/#main').hasMatch(href)) {
    window.location.href = href.substring(0, href.length - 6) + "/#landing";
  }
  await Firebase.initializeApp();
  // FirebaseFunctions.instance.useFunctionsEmulator('localhost', 5001);
  const MaterialColor kPrimaryColor = MaterialColor(
    0xff21202B,
    <int, Color>{
      50: Color.fromARGB(255, 33, 32, 43),
      100: Color.fromARGB(255, 33, 32, 43),
      200: Color.fromARGB(255, 33, 32, 43),
      300: Color.fromARGB(255, 33, 32, 43),
      400: Color.fromARGB(255, 33, 32, 43),
      500: Color.fromARGB(255, 33, 32, 43),
      600: Color.fromARGB(255, 33, 32, 43),
      700: Color.fromARGB(255, 33, 32, 43),
      800: Color.fromARGB(255, 33, 32, 43),
      900: Color.fromARGB(255, 33, 32, 43),
    },
  );
  final GeneralManager generalManager = GeneralManager();
  await generalManager.getLanguageInLocalStorage();
  final Uri uri = Uri.base;
  final bool isOrderFromQRCode = uri.path == "/order";
  final Map<String, String> uriParam = uri.queryParameters;
  runApp(
    ChangeNotifierProvider(
      create: (context) => generalManager,
      child: Consumer<GeneralManager>(
        builder: (context, generalManager, _) {
          precacheImage(
              const AssetImage('assets/img/hotelbackground.jpg'), context);
          return GestureDetector(
            onTap: () {
              GeneralManager().unfocus(context);
            },
            child: MaterialApp(
                routes: {
                  // ignore: prefer_const_constructors, -> for ChangeLanguage feature
                  'landing': (context) => LandingPage(),
                  // ignore: prefer_const_constructors, -> for ChangeLanguage feature
                  'main': (context) => MainPage(),
                  // ignore: prefer_const_constructors, -> for ChangeLanguage feature
                  'loading': (context) => LoadingPage(),
                  // ignore: prefer_const_constructors, -> for ChangeLanguage feature
                  'order': (context) =>
                      CustomerPage(uriParam['res'], uriParam['ta']),
                },
                initialRoute: isOrderFromQRCode ? "order" : 'landing',
                debugShowCheckedModeBanner: false,
                title: 'One Res',
                theme: ThemeData(
                    primarySwatch: kPrimaryColor,
                    visualDensity: VisualDensity.adaptivePlatformDensity,
                    textTheme: const TextTheme(
                      bodyText1: TextStyle(color: Colors.white),
                      bodyText2: TextStyle(color: Colors.white),
                      button: TextStyle(color: Colors.white),
                      caption: TextStyle(color: Colors.white),
                      headline1: TextStyle(color: Colors.white),
                      headline2: TextStyle(color: Colors.white),
                      headline3: TextStyle(color: Colors.white),
                      headline4: TextStyle(color: Colors.white),
                      headline5: TextStyle(color: Colors.white),
                      headline6: TextStyle(color: Colors.white),
                      overline: TextStyle(color: Colors.white),
                      subtitle1:
                          TextStyle(color: ColorManagement.mainBackground),
                      subtitle2: TextStyle(color: Colors.white),
                    ),
                    iconTheme: const IconThemeData(color: Colors.white),
                    primaryIconTheme: const IconThemeData(color: Colors.white),
                    hintColor: const Color(0xffeddcd2),
                    inputDecorationTheme: const InputDecorationTheme(),
                    textSelectionTheme: const TextSelectionThemeData(
                        selectionColor: Color(0xff9799a1))),
                localizationsDelegates: const [
                  GlobalMaterialLocalizations.delegate,
                  GlobalWidgetsLocalizations.delegate,
                  GlobalCupertinoLocalizations.delegate,
                ],
                supportedLocales: const [Locale('en', ''), Locale('vi', '')],
                locale: GeneralManager.locale),
          );
        },
      ),
    ),
  );
}

And here is the method to get data of bar chart in my controller file

List<dynamic> getChartData(int hoveredIndex) {
    if (selectedType ==
        MessageUtil.getMessageByCode(
            MessageCodeUtil.STATISTIC_PROMOTION_BY_ID)) {
      int index = 0;
      Map<String, Map<String, dynamic>> map = getDataPromotionById();
      return map.entries.map((mapEntry) {
        bool isTouched =
            map.keys.toList().indexOf(mapEntry.key) == hoveredIndex;
        num measure;
        if (selectedSubType1 ==
            MessageUtil.getMessageByCode(
                MessageCodeUtil.STATISTIC_USAGE_TIME)) {
          measure = mapEntry.value['num'];
        } else {
          measure = mapEntry.value['total'];
        }
        return BarChartGroupData(
          x: index++,
          barRods: [
            BarChartRodData(
                toY: measure,
                gradient: isTouched ? _hoveredBarsGradient : _barsGradient,
                width: 16,
                borderRadius:
                    const BorderRadius.vertical(top: Radius.circular(6)))
          ],
          showingTooltipIndicators: [if (measure > 0) 0],
        );
      }).toList();
    } else if (selectedType ==
        MessageUtil.getMessageByCode(
            MessageCodeUtil.STATISTIC_EXPENSE_BY_ITEM)) {
      int index = 0;
      SplayTreeMap<String, Map<String, dynamic>> map = getDataExpenseByItem();
      return map.entries.map((mapEntry) {
        bool isTouched =
            map.keys.toList().indexOf(mapEntry.key) == hoveredIndex;
        num measure;
        if (selectedSubType1 ==
            MessageUtil.getMessageByCode(MessageCodeUtil.STATISTIC_AMOUNT)) {
          measure = mapEntry.value['num'];
        } else {
          measure = mapEntry.value['total'];
        }
        return BarChartGroupData(
          x: index++,
          barRods: [
            BarChartRodData(
                toY: measure,
                gradient: isTouched ? _hoveredBarsGradient : _barsGradient,
                width: 16,
                borderRadius:
                    const BorderRadius.vertical(top: Radius.circular(6)))
          ],
          showingTooltipIndicators: [if (measure > 0) 0],
        );
      }).toList();
    } 

    return displayData.map((e) {
      num measure = getMeasure(e);
      int currentIndex = displayData.indexOf(e);
      bool isTouched = currentIndex == hoveredIndex;
      return BarChartGroupData(
        x: num.tryParse(e.dateString),
        barRods: [
          BarChartRodData(
              toY: measure,
              gradient: isTouched ? _hoveredBarsGradient : _barsGradient,
              width: 16,
              borderSide: isTouched
                  ? const BorderSide(color: Colors.yellow, width: 1)
                  : const BorderSide(color: Colors.white, width: 0),
              borderRadius:
                  const BorderRadius.vertical(top: Radius.circular(6)))
        ],
        showingTooltipIndicators: [if (measure > 0) 0],
      );
    }).toList();
  }

SplayTreeMap<String, Map<String, dynamic>> getDataPromotionById() {
    //return {
    //    id_promotion: {
    //        'total': ...,
    //        'num': ...
    //    }
    //}
    SplayTreeMap<String, Map<String, dynamic>> result =
        SplayTreeMap((key1, key2) => key1.compareTo(key2));
    for (var dailyData in displayData) {
      dailyData.promotions.forEach((idItem, value) {
        if (result.containsKey(idItem)) {
          result[idItem]['total'] += value['total'];
          result[idItem]['num'] += value['num'];
        } else {
          result[idItem] = {};
          result[idItem]['total'] = value['total'];
          result[idItem]['num'] = value['num'];
        }
      });
    }
    return result;
  }

Tell me if you need more information. Thank you.

super-lz commented 2 years ago

I also encountered this problem. When switching to the second histogram, using animation will report an error. Such as RangeError (index): Index out of range: index should be less than 2: 2.

manhdung98na commented 2 years ago

Here is main file: // ignore: avoid_web_libraries_in_flutter import 'dart:html';

// import 'package:cloud_functions/cloud_functions.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:oneres_frontend/ui/page/customerorder/customerpage.dart'; import 'package:oneres_frontend/ui/page/loadingpage.dart'; import 'package:oneres_frontend/ui/page/mainpage.dart'; import 'package:provider/provider.dart';

import 'manager/generalmanager.dart'; import 'ui/page/landingpage.dart'; import 'util/designmanagement.dart';

void main() async { WidgetsFlutterBinding.ensureInitialized(); String href = window.location.href; if (kIsWeb && RegExp(r'/#main').hasMatch(href)) { window.location.href = href.substring(0, href.length - 6) + "/#landing"; } await Firebase.initializeApp(); // FirebaseFunctions.instance.useFunctionsEmulator('localhost', 5001); const MaterialColor kPrimaryColor = MaterialColor( 0xff21202B, <int, Color>{ 50: Color.fromARGB(255, 33, 32, 43), 100: Color.fromARGB(255, 33, 32, 43), 200: Color.fromARGB(255, 33, 32, 43), 300: Color.fromARGB(255, 33, 32, 43), 400: Color.fromARGB(255, 33, 32, 43), 500: Color.fromARGB(255, 33, 32, 43), 600: Color.fromARGB(255, 33, 32, 43), 700: Color.fromARGB(255, 33, 32, 43), 800: Color.fromARGB(255, 33, 32, 43), 900: Color.fromARGB(255, 33, 32, 43), }, ); final GeneralManager generalManager = GeneralManager(); await generalManager.getLanguageInLocalStorage(); final Uri uri = Uri.base; final bool isOrderFromQRCode = uri.path == "/order"; final Map<String, String> uriParam = uri.queryParameters; runApp( ChangeNotifierProvider( create: (context) => generalManager, child: Consumer( builder: (context, generalManager, _) { precacheImage( const AssetImage('assets/img/hotelbackground.jpg'), context); return GestureDetector( onTap: () { GeneralManager().unfocus(context); }, child: MaterialApp( routes: { // ignore: prefer_const_constructors, -> for ChangeLanguage feature 'landing': (context) => LandingPage(), // ignore: prefer_const_constructors, -> for ChangeLanguage feature 'main': (context) => MainPage(), // ignore: prefer_const_constructors, -> for ChangeLanguage feature 'loading': (context) => LoadingPage(), // ignore: prefer_const_constructors, -> for ChangeLanguage feature 'order': (context) => CustomerPage(uriParam['res'], uriParam['ta']), }, initialRoute: isOrderFromQRCode ? "order" : 'landing', debugShowCheckedModeBanner: false, title: 'One Res', theme: ThemeData( primarySwatch: kPrimaryColor, visualDensity: VisualDensity.adaptivePlatformDensity, textTheme: const TextTheme( bodyText1: TextStyle(color: Colors.white), bodyText2: TextStyle(color: Colors.white), button: TextStyle(color: Colors.white), caption: TextStyle(color: Colors.white), headline1: TextStyle(color: Colors.white), headline2: TextStyle(color: Colors.white), headline3: TextStyle(color: Colors.white), headline4: TextStyle(color: Colors.white), headline5: TextStyle(color: Colors.white), headline6: TextStyle(color: Colors.white), overline: TextStyle(color: Colors.white), subtitle1: TextStyle(color: ColorManagement.mainBackground), subtitle2: TextStyle(color: Colors.white), ), iconTheme: const IconThemeData(color: Colors.white), primaryIconTheme: const IconThemeData(color: Colors. white), hintColor: const Color(0xffeddcd2), inputDecorationTheme: const InputDecorationTheme(), textSelectionTheme: const TextSelectionThemeData( selectionColor: Color(0xff9799a1))), localizationsDelegates: const [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [Locale('en', ''), Locale('vi', '' )], locale: GeneralManager.locale), ); }, ), ), ); }

And here is the method to get data of bar chart in my controller file List getChartData(int hoveredIndex) { if (selectedType == MessageUtil.getMessageByCode( MessageCodeUtil.STATISTIC_PROMOTION_BY_ID)) { int index = 0; Map<String, Map<String, dynamic>> map = getDataPromotionById(); return map.entries.map((mapEntry) { bool isTouched = map.keys.toList().indexOf(mapEntry.key) == hoveredIndex; num measure; if (selectedSubType1 == MessageUtil.getMessageByCode( MessageCodeUtil.STATISTIC_USAGE_TIME)) { measure = mapEntry.value['num']; } else { measure = mapEntry.value['total']; } return BarChartGroupData( x: index++, barRods: [ BarChartRodData( toY: measure, gradient: isTouched ? _hoveredBarsGradient : _barsGradient, width: 16, borderRadius: const BorderRadius.vertical(top: Radius.circular(6))) ], showingTooltipIndicators: [if (measure > 0) 0], ); }).toList(); } else if (selectedType == MessageUtil.getMessageByCode( MessageCodeUtil.STATISTIC_EXPENSE_BY_ITEM)) { int index = 0; SplayTreeMap<String, Map<String, dynamic>> map = getDataExpenseByItem (); return map.entries.map((mapEntry) { bool isTouched = map.keys.toList().indexOf(mapEntry.key) == hoveredIndex; num measure; if (selectedSubType1 == MessageUtil.getMessageByCode(MessageCodeUtil.STATISTIC_AMOUNT)) { measure = mapEntry.value['num']; } else { measure = mapEntry.value['total']; } return BarChartGroupData( x: index++, barRods: [ BarChartRodData( toY: measure, gradient: isTouched ? _hoveredBarsGradient : _barsGradient, width: 16, borderRadius: const BorderRadius.vertical(top: Radius.circular(6))) ], showingTooltipIndicators: [if (measure > 0) 0], ); }).toList(); }

return displayData.map((e) {
  num measure = getMeasure(e);
  int currentIndex = displayData.indexOf(e);
  bool isTouched = currentIndex == hoveredIndex;
  return BarChartGroupData(
    x: num.tryParse(e.dateString),
    barRods: [
      BarChartRodData(
          toY: measure,
          gradient: isTouched ? _hoveredBarsGradient : _barsGradient,
          width: 16,
          borderSide: isTouched
              ? const BorderSide(color: Colors.yellow, width: 1)
              : const BorderSide(color: Colors.white, width: 0),
          borderRadius:
              const BorderRadius.vertical(top: Radius.circular(6)))
    ],
    showingTooltipIndicators: [if (measure > 0) 0],
  );
}).toList();

}

SplayTreeMap<String, Map<String, dynamic>> getDataPromotionById() { //return { // id_promotion: { // 'total': ..., // 'num': ... // } //} SplayTreeMap<String, Map<String, dynamic>> result = SplayTreeMap((key1, key2) => key1.compareTo(key2)); for (var dailyData in displayData) { dailyData.promotions.forEach((idItem, value) { if (result.containsKey(idItem)) { result[idItem]['total'] += value['total']; result[idItem]['num'] += value['num']; } else { result[idItem] = {}; result[idItem]['total'] = value['total']; result[idItem]['num'] = value['num']; } }); } return result; }

Tell me if you need more information. Thank you.

Vào Th 6, 17 thg 6, 2022 vào lúc 22:57 Iman khoshabi < @.***> đã viết:

May I ask to you provide a simplified reproducible code (in a main.dart file)? It helps me to find the problem faster.

— Reply to this email directly, view it on GitHub https://github.com/imaNNeoFighT/fl_chart/issues/1062#issuecomment-1159016185, or unsubscribe https://github.com/notifications/unsubscribe-auth/ATMTDNJYRX2RBZJCKCJZ6BLVPSN5BANCNFSM5ZAZ6JOQ . You are receiving this because you authored the thread.Message ID: @.***>

keesus commented 1 year ago

i am having a same trouble. any solutions guys?

krispypen commented 1 year ago

a work around is disabling the animations: swapAnimationDuration: Duration.zero,

imaNNeo commented 1 year ago

Here is main file:


// ignore: avoid_web_libraries_in_flutter
import 'dart:html';

// import 'package:cloud_functions/cloud_functions.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:oneres_frontend/ui/page/customerorder/customerpage.dart';
import 'package:oneres_frontend/ui/page/loadingpage.dart';
import 'package:oneres_frontend/ui/page/mainpage.dart';
import 'package:provider/provider.dart';
....
Tell me if you need more information. Thank you.

It does not compile. Please provide me a valid reproducible code (without any need to import lots of packages and lots of changes)

josepcapotorres commented 1 year ago

The solution that @krispypen said worked to me. Thanks @krispypen!