diff --git a/android/app/build.gradle b/android/app/build.gradle index bb4419db..1ca707ed 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -15,12 +15,12 @@ if (localPropertiesFile.exists()) { def flutterVersionCode = localProperties.getProperty("flutter.versionCode") if (flutterVersionCode == null) { - flutterVersionCode = "25" + flutterVersionCode = "26" } def flutterVersionName = localProperties.getProperty("flutter.versionName") if (flutterVersionName == null) { - flutterVersionName = "1.0.24" + flutterVersionName = "1.0.25" } def keystorePropertiesFile = rootProject.file("key.properties") diff --git a/lib/presentation/Screens/charts/screens/chart_screen.dart b/lib/presentation/Screens/charts/screens/chart_screen.dart index 9a302399..2bab742c 100644 --- a/lib/presentation/Screens/charts/screens/chart_screen.dart +++ b/lib/presentation/Screens/charts/screens/chart_screen.dart @@ -155,17 +155,17 @@ class _ChartScreen1State extends ConsumerState { }); // ToastUtil.showSuccessToast("Added to Bookmark."); - // ScaffoldMessenger.of(context).showSnackBar( - // SnackBar( - // content: Text( - // AppLocalizations.of(context)!.added_to_Bookmark, - // style: TextStyle( - // color: Color(0xFF2F692C), fontWeight: FontWeight.w600), - // ), - // backgroundColor: Color(0xFFD6E9C6), - // duration: Duration(seconds: 2), - // ), - // ); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)!.added_to_Bookmark, + style: TextStyle( + color: Color(0xFF2F692C), fontWeight: FontWeight.w600), + ), + backgroundColor: Color(0xFFD6E9C6), + duration: Duration(seconds: 2), + ), + ); print("Bookmark added: ${response.id}"); } catch (e) { @@ -1346,9 +1346,10 @@ class _ChartScreen1State extends ConsumerState { // If chartFilteredData is empty, print the message if (chartFilteredData.isEmpty) { if (kDebugMode) { - print("Selected year: ${selectedFilters.firstWhere((f) => f['filter_key'] == 'TIME_PERIOD', - orElse: () => {'filter_data': ['Unknown']})['filter_data']} - No data for chart: : $chartHeading"); - + print( + "Selected year: ${selectedFilters.firstWhere((f) => f['filter_key'] == 'TIME_PERIOD', orElse: () => { + 'filter_data': ['Unknown'] + })['filter_data']} - No data for chart: : $chartHeading"); } // Add the complete chart data to the filteredData list @@ -1371,7 +1372,6 @@ class _ChartScreen1State extends ConsumerState { addedChartIds .add(chart['chart_heading']); // Track by a unique identifier } - } // List filteredCardData = []; @@ -1800,19 +1800,18 @@ class _ChartScreen1State extends ConsumerState { print("FilteringDAtss START1 - $filterData"); print('TABId1 - $tabId'); - for (var item in filterData) { - if (item['filter_key'] == 'TIME_PERIOD') { - // Convert the values to integers, sort them, and convert back to strings - List timePeriodData = item['filter_data'] - .map((e) => int.parse(e.toString())) // Convert to int - .toList(); - timePeriodData.sort((a, b) => a.compareTo(b)); // Sort numerically - - // Optionally, convert sorted integers back to strings if necessary - item['filter_data'] = timePeriodData.map((e) => e.toString()).toList(); - } - } - + // for (var item in filterData) { + // if (item['filter_key'] == 'TIME_PERIOD') { + // // Convert the values to integers, sort them, and convert back to strings + // List timePeriodData = item['filter_data'] + // .map((e) => int.parse(e.toString())) // Convert to int + // .toList(); + // timePeriodData.sort((a, b) => a.compareTo(b)); // Sort numerically + // + // // Optionally, convert sorted integers back to strings if necessary + // item['filter_data'] = timePeriodData.map((e) => e.toString()).toList(); + // } + // } print('TABId - $tabId'); @@ -1931,11 +1930,33 @@ class _ChartScreen1State extends ConsumerState { double myheight = MediaQuery.of(context).size.height; double mywidth = MediaQuery.of(context).size.width; - int crossAxisCount = cardData.isNotEmpty ? (cardData.length / 2).ceil().clamp(1, 2) : 1; - + int crossAxisCount = + cardData.isNotEmpty ? (cardData.length / 2).ceil().clamp(1, 2) : 1; final color = Color(int.parse(widget.bgColor.replaceFirst('0x', ''), radix: 16)); + // Color bodyColor = Color( + // int.parse( + // (chartScreenData['body_color'] ?? '#898C81') + // .replaceFirst('#', '0xFF'), // Correct format for ARGB in Flutter + // radix: 16, + // ), + // ); + // + // + final bodyColor = Color( + int.parse( + (chartScreenData['body_color'] ?? '#898C81').replaceFirst('#', '0xff'), + ), + ); + + print('ChartScrnBodyColor'); + print(chartScreenData['body_color']); + print( + Color(int.parse((chartScreenData['body_color'] ?? '#898C81') + .replaceFirst('#', '0xff'))), + ); + return PopScope( canPop: false, // Allow back navigation only if not login screen onPopInvokedWithResult: (didPop, result) { @@ -2003,7 +2024,6 @@ class _ChartScreen1State extends ConsumerState { fontWeight: FontWeight.w400, fontFamily: 'Roboto'), ), - ], ), )), @@ -2089,12 +2109,16 @@ class _ChartScreen1State extends ConsumerState { onTap: () async { try { // Define share content - final String shareLink = 'https://fcsc-da580.web.app'; // Your link - final String shareText = 'Check this out!'; - final String svgAssetPath = 'assets/logos/fcsc.png'; // Your SVG asset path + final String shareLink = + 'https://fcsc-da580.web.app'; // Your link + final String shareText = + 'Check this out!'; + final String svgAssetPath = + 'assets/logos/fcsc.png'; // Your SVG asset path // Create a controller to capture the SVG as an image - final screenshotController = ScreenshotController(); + final screenshotController = + ScreenshotController(); // Render the SVG off-screen and capture it as PNG final svgWidget = SvgPicture.asset( @@ -2104,19 +2128,24 @@ class _ChartScreen1State extends ConsumerState { ); // Capture the SVG as a PNG - final Uint8List? capturedImage = await screenshotController.captureFromWidget( + final Uint8List? capturedImage = + await screenshotController + .captureFromWidget( Material( child: svgWidget, ), ); if (capturedImage == null) { - throw Exception('Failed to capture SVG as image'); + throw Exception( + 'Failed to capture SVG as image'); } // Save the captured PNG to a temporary file - final directory = await getTemporaryDirectory(); - final file = File('${directory.path}/shared_image.png'); + final directory = + await getTemporaryDirectory(); + final file = File( + '${directory.path}/shared_image.png'); await file.writeAsBytes(capturedImage); // Share the PNG file with text and link @@ -2130,8 +2159,11 @@ class _ChartScreen1State extends ConsumerState { await file.delete(); } catch (e) { print("Error sharing file: $e"); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Error sharing: $e')), + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: + Text('Error sharing: $e')), ); } }, @@ -2144,43 +2176,41 @@ class _ChartScreen1State extends ConsumerState { size: 24, semanticLabel: 'Share', ), - ), ), + ), ], ), SizedBox( width: 10), // Horizontal space between items Row( children: [ - GestureDetector( onTap: () { showRightSideModal( context, filterData, chartsData); }, - - child: Row( - children: [ - Text( - context.translate( - 'Filter', - 'فلتر', - ), - style: const TextStyle( - fontSize: 16, - color: Colors.white, - ), + child: Row( + children: [ + Text( + context.translate( + 'Filter', + 'فلتر', + ), + style: const TextStyle( + fontSize: 16, + color: Colors.white, + ), + ), + SizedBox(width: 10), + Image.asset( + UaeNumbersAssetPath.filterUae, + color: Colors + .white, // Set color to white + width: 21, + height: 21, + ), + ], ), - - SizedBox(width: 10), - Image.asset( - UaeNumbersAssetPath.filterUae, - color: Colors.white, // Set color to white - width: 21, - height: 21, - ), - ],), - ) // IconButton( // icon: Icon(Icons.filter_alt_outlined, @@ -2258,28 +2288,25 @@ class _ChartScreen1State extends ConsumerState { // nonChartData Cards Padding( padding: const EdgeInsets.all(3.34), - - - - child: GridView.builder( + child: GridView.builder( key: cardKey, itemCount: cardData.length, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount:crossAxisCount, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // crossAxisCount: cardData.length % 2 == 0 ? 2 : 3, - crossAxisSpacing: 2, + crossAxisSpacing: 2, mainAxisSpacing: 5, + childAspectRatio: MediaQuery.of(context) .size .width / (MediaQuery.of(context).size.height / (cardData.length % 2 == 0 ? 2 : 2)), - // childAspectRatio: - // calculateAspectRatio(cardData.length, cardData), ), // Sort and filter the cardData @@ -2303,6 +2330,11 @@ class _ChartScreen1State extends ConsumerState { ? 180.0 : 130.0; + final itemdata = cardData[index]; + bool isLastSingleCard = + (index == cardData.length - 1 && + cardData.length % 2 != 0); + if (response.isEmpty) { return const SizedBox.shrink(); // return Container( @@ -2312,11 +2344,13 @@ class _ChartScreen1State extends ConsumerState { if (response.length == 1) { return Container( margin: const EdgeInsets.all(10), + width: isLastSingleCard + ? double.infinity + : null, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all( - color: Color(int.parse( (chartScreenData[ 'border_color'] ?? @@ -2396,16 +2430,20 @@ class _ChartScreen1State extends ConsumerState { child: FittedBox( child: Text( // '(${response[0]['display_value'] ?? 'NA'})', - RegExp(r'\d').hasMatch(response[0]['display_value'] ?? '') + RegExp(r'\d').hasMatch( + response[0][ + 'display_value'] ?? + '') ? '(${response[0]['display_value'] ?? 'NA'})' : '${response[0]['display_value'] ?? 'NA'}', style: - const TextStyle( - fontSize: 10, - color: Colors.grey, - overflow: TextOverflow.ellipsis, - ), + fontSize: 10, + color: Colors.grey, + overflow: + TextOverflow + .ellipsis, + ), ), ), ), @@ -2420,35 +2458,59 @@ class _ChartScreen1State extends ConsumerState { child: Row( children: [ Text( - response[0]['value'] ?? 'NA', + response[0][ + 'value'] ?? + 'NA', // '${data['roundedAverage'] ?? // 'NA'}', - style: - TextStyle( + style: TextStyle( fontSize: 18, fontWeight: - FontWeight.w800, - color: response[0]['calculation'] == 'different' - ? _getColorFromHex(response[0]['font_color']) - : const Color(0xFF90B0D5), + FontWeight + .w800, + color: response[0] + [ + 'calculation'] == + 'different' + ? _getColorFromHex( + response[0] + [ + 'font_color']) + : bodyColor, // color: Color( // 0xFF90B0D5), ), ), - const SizedBox(width: 1), // Space between text and icon - if (response[0]['calculation'] == 'different') ...[ - if (response[0]['font_color'] == '#D83731') - const Icon(Icons.arrow_downward, color: Colors.red, size: 20) - else if (response[0]['font_color'] == '#11AF22') - const Icon(Icons.arrow_upward, color: Colors.green, size: 20), + const SizedBox( + width: + 1), // Space between text and icon + if (response[0][ + 'calculation'] == + 'different') ...[ + if (response[0][ + 'font_color'] == + '#D83731') + const Icon( + Icons + .arrow_downward, + color: Colors + .red, + size: 20) + else if (response[ + 0][ + 'font_color'] == + '#11AF22') + const Icon( + Icons + .arrow_upward, + color: Colors + .green, + size: 20), ], ], - ), - ), ), - ), ], ), @@ -2457,6 +2519,9 @@ class _ChartScreen1State extends ConsumerState { } else { return Container( margin: const EdgeInsets.all(10), + width: isLastSingleCard + ? double.infinity + : null, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), @@ -2538,14 +2603,19 @@ class _ChartScreen1State extends ConsumerState { Text( // '(${response[0]['display_value'] ?? 'NA'})', - RegExp(r'\d').hasMatch(response[0]['display_value'] ?? '') + RegExp(r'\d').hasMatch(response[ + 0][ + 'display_value'] ?? + '') ? '(${response[0]['display_value'] ?? 'NA'})' : '${response[0]['display_value'] ?? 'NA'}', style: const TextStyle( - fontSize: 10, - color: Colors.grey, - overflow: TextOverflow.ellipsis,), + fontSize: 10, + color: Colors.grey, + overflow: + TextOverflow.ellipsis, + ), ), const SizedBox(height: 1), Flexible( @@ -2560,13 +2630,17 @@ class _ChartScreen1State extends ConsumerState { // data['lastYearValue']), // apiService.formatAmount( // data['lastYearValue']), - style: TextStyle( + style: TextStyle( fontSize: 18, fontWeight: FontWeight.w900, - color: response[0]['calculation'] == 'different' - ? _getColorFromHex(response[0]['font_color']) - : const Color(0xFF90B0D5), + color: response[0][ + 'calculation'] == + 'different' + ? _getColorFromHex( + response[0][ + 'font_color']) + : bodyColor, ), ), ), @@ -2593,26 +2667,33 @@ class _ChartScreen1State extends ConsumerState { fontSize: 14, fontWeight: FontWeight.w600, - - color: response[1]['calculation'] == 'different' - ? _getColorFromHex(response[0]['font_color']) - : const Color(0xFFD83731), - + color: response[1][ + 'calculation'] == + 'different' + ? _getColorFromHex( + response[0][ + 'font_color']) + : const Color( + 0xFFD83731), ), ), ), ), Text( // '(${response[1]['display_value'] ?? 'NA'})', - RegExp(r'\d').hasMatch(response[1]['display_value'] ?? '') + RegExp(r'\d').hasMatch(response[ + 1][ + 'display_value'] ?? + '') ? '(${response[1]['display_value'] ?? 'NA'})' : '${response[1]['display_value'] ?? 'NA'}', style: const TextStyle( - fontSize: 9, - fontWeight: - FontWeight.w500, - color: Colors.grey, - overflow: TextOverflow.ellipsis,), + fontSize: 9, + fontWeight: FontWeight.w500, + color: Colors.grey, + overflow: + TextOverflow.ellipsis, + ), ), ], ), @@ -2656,18 +2737,21 @@ class _ChartScreen1State extends ConsumerState { children: [ // ConstrainedBox Container( - // constraints: const BoxConstraints( - // minHeight: - // 200, // Minimum height - // maxHeight: - // 400, // Maximum height - // ), - height: 350, + height: (chartsData[index] + ['chart_type'] == + 'pie_chart') + ? 500 + : (chartsData[index][ + 'chart_type'] == + 'bar_chart_horizontal') + ? 370 + : 350, // child: buildChart(chartsData[index]), child: ChartWidget( chartData: - chartsData[index])) + chartsData[index], + bodyColor: bodyColor)) ], ), ), @@ -2749,4 +2833,4 @@ Color _getColorFromHex(String hexColor) { hexColor = 'FF$hexColor'; // Add alpha if not provided } return Color(int.parse(hexColor, radix: 16)); -} \ No newline at end of file +} diff --git a/lib/presentation/Screens/charts/widgets/chart_widget.dart b/lib/presentation/Screens/charts/widgets/chart_widget.dart index a075d918..1a8c32c7 100644 --- a/lib/presentation/Screens/charts/widgets/chart_widget.dart +++ b/lib/presentation/Screens/charts/widgets/chart_widget.dart @@ -7,9 +7,10 @@ import 'package:flutter/material.dart'; // import 'package:syncfusion_flutter_charts/charts.dart'; class ChartWidget extends StatelessWidget { + ChartWidget({Key? key, required this.chartData, required this.bodyColor}) + : super(key: key); final dynamic chartData; - - ChartWidget({Key? key, required this.chartData}) : super(key: key); + final Color bodyColor; String capitalizeAndSplit(String input) { return input @@ -18,6 +19,50 @@ class ChartWidget extends StatelessWidget { .join(' '); } + String formatNumber(double value) { + if (value >= 1e12) { + return '${(value / 1e12).toStringAsFixed(2)}T'; + } else if (value >= 1e9) { + return '${(value / 1e9).toStringAsFixed(2)}B'; + } else if (value >= 1e6) { + return '${(value / 1e6).toStringAsFixed(2)}M'; + } else if (value >= 1e3) { + return '${(value / 1e3).toStringAsFixed(2)}K'; + } else { + return value.toStringAsFixed(2); // No decimals for small numbers + } + } + + String formatNumberConversion( + double value, String chartConversion, String numberFormat) { + // Convert the input value to base unit (actual value) + double baseValue = value; + + if (numberFormat == 'M') { + baseValue = value * 1e6; // Convert from Million to base value + } else if (numberFormat == 'B') { + baseValue = value * 1e9; // Convert from Billion to base value + } else if (numberFormat == 'T') { + baseValue = value * 1e12; // Convert from Trillion to base value + } else if (numberFormat == 'K') { + baseValue = value * 1e3; // Convert from Thousand to base value + } + + // Convert base value to the target chartConversion format + if (chartConversion == 'T') { + return '${(baseValue / 1e12).toStringAsFixed(2)}T'; // Convert to Trillion + } else if (chartConversion == 'B') { + return '${(baseValue / 1e9).toStringAsFixed(2)}B'; // Convert to Billion + } else if (chartConversion == 'M') { + return '${(baseValue / 1e6).toStringAsFixed(2)}M'; // Convert to Million + } else if (chartConversion == 'K') { + return '${(baseValue / 1e3).toStringAsFixed(2)}K'; // Convert to Thousand + } else { + return baseValue + .toStringAsFixed(2); // Default: Show value with 2 decimals + } + } + List parseStackedBarChartData( dynamic chartData, String groupByKey, String groupByValue) { return chartData['response'] @@ -53,7 +98,6 @@ class ChartWidget extends StatelessWidget { ) { debugPrint('Chart Data: ${jsonEncode(chartData)}'); - return chartData['response'] .asMap() .entries @@ -79,23 +123,21 @@ class ChartWidget extends StatelessWidget { double percentage = (totalValue > 0) ? (value / totalValue) * 100 : 0.0; bool isTouched = index == touchedIndex; - return PieChartSectionData( - value: value, - color: Colors.primaries[index % Colors.primaries.length], - title: '${percentage.toStringAsFixed(1)}%', - radius: isTouched ? 60 : 50, - - // Increase size when touched - titleStyle: TextStyle( - fontSize: isTouched ? 12 : 10, - fontWeight: FontWeight.bold, - color: Colors.black, - // color: Colors.white, - - ), - titlePositionPercentageOffset: 1.3, - ); + return PieChartSectionData( + value: value, + color: Colors.primaries[index % Colors.primaries.length], + title: '${percentage.toStringAsFixed(1)}%', + radius: isTouched ? 60 : 50, + // Increase size when touched + titleStyle: TextStyle( + fontSize: isTouched ? 12 : 10, + fontWeight: FontWeight.bold, + color: Colors.black, + // color: Colors.white, + ), + titlePositionPercentageOffset: 1.3, + ); }).toList(); } @@ -109,43 +151,47 @@ class ChartWidget extends StatelessWidget { title.length > 10 ? '${title.substring(0, 10)}…' : title; return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 10, - height: 10, - decoration: BoxDecoration( - color: color, - shape: BoxShape.circle, - ), + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 10, + height: 10, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + ), + ), + SizedBox(width: 8), + TooltipTheme( + data: TooltipThemeData( + decoration: BoxDecoration( + color: Colors.blueGrey[800], // Change background color + borderRadius: + BorderRadius.circular(8), // Optional: rounded corners + ), + textStyle: TextStyle(color: Colors.white), // Change text color ), - SizedBox(width: 8), - TooltipTheme( - data: TooltipThemeData( - decoration: BoxDecoration( - color: Colors.blueGrey[800], // Change background color - borderRadius: BorderRadius.circular(8), // Optional: rounded corners - ), - textStyle: TextStyle(color: Colors.white), // Change text color - ), child: Tooltip( message: title, // Full text on hover - child: ConstrainedBox( // Constrain width to allow wrapping - constraints: BoxConstraints(maxWidth: 100), - child: Text( - title, - style: TextStyle(fontSize: 12,), - softWrap: true, - maxLines: 2, - overflow: TextOverflow.ellipsis, // Ensures text doesn't overflow + child: ConstrainedBox( + // Constrain width to allow wrapping + constraints: BoxConstraints(maxWidth: 130), + child: Text( + title, + style: TextStyle( + fontSize: 12, + ), + softWrap: true, + maxLines: 2, + overflow: + TextOverflow.ellipsis, // Ensures text doesn't overflow + ), ), ), - - ),), - SizedBox(width: 5), - ], - ); - + ), + SizedBox(width: 5), + ], + ); }).toList(); } @@ -159,6 +205,7 @@ class ChartWidget extends StatelessWidget { } Widget buildChart(dynamic chartData, BuildContext context) { + // print('bodyColor- $bodyColor'); print('chartDataccccccc $chartData'); // Extract group_by dynamically from the chartData if (chartData == null || chartData['response'] == null) { @@ -257,11 +304,14 @@ class ChartWidget extends StatelessWidget { ), ); case 'pie_chart': - double totalValue = chartData['response'] - .map((entry) { + double totalValue = chartData['response'].map((entry) { var value = entry['ObsValue']['Value']; // print('Processing value: $value, type: ${value.runtimeType}'); - return (value is num) ? value.toDouble() : value is String ? double.tryParse(value) ?? 0.0 : 0.0; + return (value is num) + ? value.toDouble() + : value is String + ? double.tryParse(value) ?? 0.0 + : 0.0; }).fold(0.0, (prev, element) => prev + element); ValueNotifier touchedIndex = ValueNotifier(null); @@ -278,7 +328,6 @@ class ChartWidget extends StatelessWidget { textAlign: TextAlign.center, ), SizedBox(height: 15), - Text( chartData['chart_sub_heading'] ?? '', style: TextStyle( @@ -288,7 +337,6 @@ class ChartWidget extends StatelessWidget { textAlign: TextAlign.center, ), SizedBox(height: 70), - Expanded( child: ValueListenableBuilder( valueListenable: touchedIndex, @@ -304,46 +352,43 @@ class ChartWidget extends StatelessWidget { touchCallback: (FlTouchEvent event, pieTouchResponse) { if (pieTouchResponse?.touchedSection != null && event is! FlTapUpEvent) { - touchedIndex.value = pieTouchResponse! + touchedIndex.value = pieTouchResponse! .touchedSection!.touchedSectionIndex; } else { touchedIndex.value = null; // Reset when not touching } }, - ), ), ); - }, - ), ), - SizedBox(height:105), - Flexible( + SizedBox(height: 105), + Expanded( // child: SingleChildScrollView( // scrollDirection: Axis.horizontal, // + child: SingleChildScrollView( - scrollDirection: Axis.vertical, + scrollDirection: Axis.vertical, child: Container( + width: double.infinity, constraints: BoxConstraints(minHeight: 3), // Allow dynamic height child: Padding( padding: const EdgeInsets.only( - left: 0.0, right: 0.0, bottom: 8.0, top: 20.0), + left: 0.0, right: 0.0, bottom: 8.0, top: 1.0), child: Wrap( - spacing: 12, - runSpacing: 8, + spacing: 2, + runSpacing: 5, children: generateIndicators(chartData, chartData['group_by']), ), ), ), - ), - - // ), + ), ), ], ); @@ -364,8 +409,7 @@ class ChartWidget extends StatelessWidget { return Column( children: [ Text( - chartData['chart_heading'] ?? - 'Pie Chart', // Chart title from data + chartData['chart_heading'] ?? '', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w400, @@ -381,7 +425,7 @@ class ChartWidget extends StatelessWidget { ), textAlign: TextAlign.center, ), - SizedBox(height: 15), + SizedBox(height: 15), AspectRatio( aspectRatio: 1.5, child: BarChart( @@ -393,11 +437,11 @@ class ChartWidget extends StatelessWidget { fitInsideHorizontally: true, fitInsideVertically: true, // tooltipPadding: const EdgeInsets.all(8), - tooltipPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), // Optional + tooltipPadding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 8), // Optional tooltipHorizontalAlignment: FLHorizontalAlignment.left, tooltipMargin: 16, - getTooltipItem: (groupData, groupIndex, rodData, rodIndex) { // Get the list of group names dynamically @@ -555,7 +599,7 @@ class ChartWidget extends StatelessWidget { colorIndex % uniqueColorsLine_trend_2.length]; colorIndex++; } - print('LnTrnd1'); + print('LnTrnd1'); // Extract all years from the chart data List years = (chartData['response'] as List) @@ -592,10 +636,9 @@ class ChartWidget extends StatelessWidget { // (entry) => double.parse(entry['ObsKey']['TIME_PERIOD'])) // .toSet(); - - Set uniqueXValues = filteredData - .map((entry) { - String? timePeriod = entry['ObsKey']['TIME_PERIOD']; // Nullable String + Set uniqueXValues = filteredData.map((entry) { + String? timePeriod = + entry['ObsKey']['TIME_PERIOD']; // Nullable String print('TIME_PERIOD- $timePeriod'); if (timePeriod == null) { @@ -617,8 +660,7 @@ class ChartWidget extends StatelessWidget { print('Unknown format: $timePeriod'); return timePeriod; // Keep it as is } - }) - .toSet(); + }).toSet(); print("Unique X Values: $uniqueXValues"); @@ -627,18 +669,30 @@ class ChartWidget extends StatelessWidget { if (RegExp(r'^\d{4}$').hasMatch(timePeriod)) { return double.parse(timePeriod); // Year-only (2019 → 2019.0) } else if (RegExp(r'^\d{4}-\d{2}$').hasMatch(timePeriod)) { - return double.parse(timePeriod.replaceAll('-', '.')); // Year-Month (2019-11 → 2019.11) + return double.parse(timePeriod.replaceAll( + '-', '.')); // Year-Month (2019-11 → 2019.11) } else if (RegExp(r'^\d{4}-[A-Za-z]{3}$').hasMatch(timePeriod)) { // Year-MonthAbbr (2019-Nov) Map monthMap = { - 'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, - 'May': 5, 'Jun': 6, 'Jul': 7, 'Aug': 8, - 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12 + 'Jan': 1, + 'Feb': 2, + 'Mar': 3, + 'Apr': 4, + 'May': 5, + 'Jun': 6, + 'Jul': 7, + 'Aug': 8, + 'Sep': 9, + 'Oct': 10, + 'Nov': 11, + 'Dec': 12 }; List parts = timePeriod.split('-'); - int month = monthMap[parts[1]] ?? 1; // Default to January if unknown - return double.parse('${parts[0]}.$month'); // Convert to format (2019-Nov → 2019.11) + int month = + monthMap[parts[1]] ?? 1; // Default to January if unknown + return double.parse( + '${parts[0]}.$month'); // Convert to format (2019-Nov → 2019.11) } throw FormatException("Invalid TIME_PERIOD format: $timePeriod"); @@ -650,20 +704,17 @@ class ChartWidget extends StatelessWidget { .toSet(); print('Processed X Values: $uniqueXValuesProcessed'); - - print('LnTrnd2.1'); // Generate line bars for the chart List lineBars = lineBarsData(filteredData, groupByValues, groupByKey); - print('LnTrnd3'); return Column(children: [ Text( - chartData['chart_heading'] ?? '', // Chart title from data + chartData['chart_heading'] ?? '', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w400, @@ -682,7 +733,6 @@ class ChartWidget extends StatelessWidget { SizedBox(height: 10), // Chart Expanded( - child: LineChart(LineChartData( lineTouchData: lineTouchData1(), gridData: gridData(), @@ -692,12 +742,9 @@ class ChartWidget extends StatelessWidget { lineBarsData: lineBars, // minX: uniqueXValues.reduce((a, b) => a < b ? a : b), // maxX: uniqueXValues.reduce((a, b) => a > b ? a : b), - // minX: uniqueXValuesProcessed.reduce((a, b) => a < b ? a : b), - // maxX: uniqueXValuesProcessed.reduce((a, b) => a > b ? a : b), - )) - - - ), + // minX: uniqueXValuesProcessed.reduce((a, b) => a < b ? a : b), + // maxX: uniqueXValuesProcessed.reduce((a, b) => a > b ? a : b), + ))), Padding( padding: const EdgeInsets.all(8.0), child: Wrap( @@ -781,7 +828,7 @@ class ChartWidget extends StatelessWidget { return Column(children: [ Text( - chartData['chart_heading'] ?? '', // Chart title from data + chartData['chart_heading'] ?? '', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w400, @@ -901,7 +948,7 @@ class ChartWidget extends StatelessWidget { return Column(children: [ Text( - chartData['chart_heading'] ?? '', // Chart title from data + chartData['chart_heading'] ?? '', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w400, @@ -910,7 +957,7 @@ class ChartWidget extends StatelessWidget { ), SizedBox(height: 5), Text( - chartData['chart_sub_heading'] ?? '', + '', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w300, @@ -948,35 +995,45 @@ class ChartWidget extends StatelessWidget { List yAxisData = []; List yAxisLabels = []; - var xadditionalgrp= chartData['chart_type_json']['additional_x_group']; + var xadditionalgrp = chartData['chart_type_json']['additional_x_group']; + var chartConversion = chartData['chart_type_json']['conversion']; + var number_format = chartData['chart_type_json']['number_format']; print('xadditionalgrp-$xadditionalgrp'); for (var entry in chartData['response']) { var xValue = entry['ObsKey'][groupByKey]; - var xTimePeriod= entry['ObsKey']['TIME_PERIOD']; + var xTimePeriod = entry['ObsKey']['TIME_PERIOD']; var unitMsr = entry['ObsKey']['UNIT_MEASURE']; var yValue = entry['ObsValue']['Value']; - var xadditionalgroup= chartData['chart_type_json']['additional_x_group']; + var xadditionalgroup = + chartData['chart_type_json']['additional_x_group']; print('chartData response: ${chartData['response']}'); + print('chartConversion: $chartConversion'); + print('number_format: $number_format'); print('xValue-$xValue'); print('Xadditionalt-$xTimePeriod'); print('xadditionalgroup-$xadditionalgroup'); print('yValue-$yValue'); print('unitMsr-$unitMsr'); - if (xadditionalgrp == 'TIME_PERIOD') { chartData['response'].sort((a, b) { // Convert TIME_PERIOD to int for proper sorting - int timeA = int.tryParse(a['ObsKey']['TIME_PERIOD'].toString()) ?? 0; - int timeB = int.tryParse(b['ObsKey']['TIME_PERIOD'].toString()) ?? 0; + int timeA = + int.tryParse(a['ObsKey']['TIME_PERIOD'].toString()) ?? 0; + int timeB = + int.tryParse(b['ObsKey']['TIME_PERIOD'].toString()) ?? 0; // Extract QUARTER and convert "Q1", "Q2", etc. to numeric values - int quarterA = int.tryParse(a['ObsKey']['QUARTER'].toString().replaceAll('Q', '')) ?? 0; - int quarterB = int.tryParse(b['ObsKey']['QUARTER'].toString().replaceAll('Q', '')) ?? 0; + int quarterA = int.tryParse( + a['ObsKey']['QUARTER'].toString().replaceAll('Q', '')) ?? + 0; + int quarterB = int.tryParse( + b['ObsKey']['QUARTER'].toString().replaceAll('Q', '')) ?? + 0; // First, sort by TIME_PERIOD. If equal, sort by QUARTER if (timeA != timeB) { @@ -1001,10 +1058,8 @@ class ChartWidget extends StatelessWidget { xAxisData.add(xLabel); yAxisData.add(double.tryParse(yValue.toString()) ?? 0.0); - yAxisLabels.add(unitMsr ?? ''); - + // yAxisLabels.add(unitMsr ?? ''); } - } return Column(children: [ Text( @@ -1033,26 +1088,54 @@ class ChartWidget extends StatelessWidget { maxY: yAxisData.isNotEmpty ? yAxisData.reduce((a, b) => a > b ? a : b) * 1.2 : 10, + // barTouchData: BarTouchData( + // enabled: true, + // touchCallback: (FlTouchEvent event, barTouchResponse) { + // if (!event.isInterestedForInteractions || barTouchResponse == null || barTouchResponse.spot == null) { + // return; + // } + // }, + // touchTooltipData: BarTouchTooltipData( + // getTooltipColor: (group) => Colors.transparent, + // getTooltipItem: (group, groupIndex, rod, rodIndex) { + // if (rod.toY == 0) return null; // Hide for zero values + // return BarTooltipItem( + // formatNumber(rod.toY), + // TextStyle(color: Colors.black), + // ); + // }, + // ), + // ), - - // barTouchData: BarTouchData(enabled: true), + // barTouchData: BarTouchData(enabled: true), barTouchData: BarTouchData( - enabled: true, - handleBuiltInTouches: true, + enabled: false, + handleBuiltInTouches: false, touchTooltipData: BarTouchTooltipData( + getTooltipColor: (group) => Colors.transparent, // fitInsideHorizontally: true, // fitInsideVertically: true, // tooltipPadding: const EdgeInsets.all(8), - // tooltipMargin: 16, + tooltipMargin: 1, getTooltipItem: (group, groupIndex, rod, rodIndex) { - - String unitMsrLabel = yAxisLabels.isNotEmpty && groupIndex < yAxisLabels.length - ? yAxisLabels[groupIndex] - : ''; + // String unitMsrLabel = yAxisLabels.isNotEmpty && groupIndex < yAxisLabels.length + // ? yAxisLabels[groupIndex] + // : ''; + if (rod.toY == 0) return null; return BarTooltipItem( - '${rod.toY.toStringAsFixed(1)}\n$unitMsrLabel', - const TextStyle(color: Colors.white), + (chartConversion != null && + chartConversion.isNotEmpty && + number_format != null && + chartConversion.isNotEmpty) + ? formatNumberConversion(rod.toY, chartConversion, + number_format) // If condition is true + : formatNumber(rod.toY), + const TextStyle( + color: Colors.black, + fontSize: 12, + fontWeight: FontWeight.w400, + ), ); }, ), @@ -1085,7 +1168,8 @@ class ChartWidget extends StatelessWidget { : title; return Padding( - padding: const EdgeInsets.only(top: 8.0,left: 55.0), + padding: + const EdgeInsets.only(top: 8.0, left: 55.0), child: SizedBox( width: 60, // Limit width to force wrapping child: Transform.rotate( @@ -1093,36 +1177,44 @@ class ChartWidget extends StatelessWidget { angle: -1.5, child: TooltipTheme( - data: TooltipThemeData( - decoration: BoxDecoration( - color: Colors.blueGrey[800], // Change background color - borderRadius: BorderRadius.circular(8), // Optional: rounded corners - ), - textStyle: TextStyle(color: Colors.white), // Change text color + data: TooltipThemeData( + decoration: BoxDecoration( + color: Colors.blueGrey[ + 800], // Change background color + borderRadius: BorderRadius.circular( + 8), // Optional: rounded corners ), - + textStyle: TextStyle( + color: Colors + .white), // Change text color + ), child: TooltipTheme( data: TooltipThemeData( decoration: BoxDecoration( - color: Colors.blueGrey[800], // Change background color - borderRadius: BorderRadius.circular(8), // Optional: rounded corners + color: Colors.blueGrey[ + 800], // Change background color + borderRadius: BorderRadius.circular( + 8), // Optional: rounded corners + ), + textStyle: TextStyle( + color: Colors + .white), // Change text color + ), + child: Tooltip( + message: title, + child: Text( + // title, + displayTitle, + softWrap: true, + textAlign: TextAlign.end, + style: TextStyle( + fontSize: 10, + ), + overflow: TextOverflow.ellipsis, ), - textStyle: TextStyle(color: Colors.white), // Change text color ), - child: Tooltip( - message: title, - - child: Text( - // title, - displayTitle, - softWrap: true, - style: TextStyle(fontSize: 10,), - overflow: TextOverflow.ellipsis, - ),), ), - ), - ))); } return Container(); @@ -1139,7 +1231,13 @@ class ChartWidget extends StatelessWidget { ), ), gridData: FlGridData(show: false), - borderData: FlBorderData(show: false), + borderData: FlBorderData( + show: true, + border: const Border( + // left: BorderSide(color: Colors.grey), + bottom: BorderSide(color: Colors.grey), + ), + ), barGroups: List.generate( xAxisData.length, (index) => BarChartGroupData( @@ -1152,6 +1250,7 @@ class ChartWidget extends StatelessWidget { width: 20, ), ], + showingTooltipIndicators: [0], ), ), ), @@ -1170,124 +1269,146 @@ class ChartWidget extends StatelessWidget { yAxisData.add(double.tryParse(yValue.toString()) ?? 0.0); } } + print("chartData['chart_heading']"); + print(chartData['chart_heading']); int rotationTurns = 1; return Column(children: [ Text( - chartData['chart_heading'] ?? '', // Chart title from data + chartData['chart_heading'] ?? '', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w400, ), textAlign: TextAlign.center, ), - SizedBox(height: 5), - Text( - chartData['chart_sub_heading'] ?? '', - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w300, - ), - textAlign: TextAlign.center, - ), - SizedBox(height: 10), + // SizedBox(height: 5), + // Text( + // chartData['chart_sub_heading'] ?? '', + // style: TextStyle( + // fontSize: 13, + // fontWeight: FontWeight.w300, + // ), + // textAlign: TextAlign.center, + // ), + // SizedBox(height: 5), // Chart - Expanded( - child: BarChart( - BarChartData( - alignment: BarChartAlignment.spaceAround, - maxY: yAxisData.isNotEmpty - ? yAxisData.reduce((a, b) => a > b ? a : b) * 1.2 - : 10, - rotationQuarterTurns: rotationTurns, - barTouchData: BarTouchData( - enabled: true, - handleBuiltInTouches: true, - touchTooltipData: BarTouchTooltipData( - fitInsideHorizontally: true, - fitInsideVertically: true, - tooltipPadding: const EdgeInsets.all(8), - tooltipMargin: 16, - getTooltipItem: (group, groupIndex, rod, rodIndex) { - return BarTooltipItem( - '${rod.toY.toStringAsFixed(1)}', - const TextStyle(color: Colors.white), - ); - }, - ), - ), - titlesData: FlTitlesData( - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: false, - interval: (yAxisData.isNotEmpty - ? yAxisData.reduce((a, b) => a > b ? a : b) / 5 - : 1), - getTitlesWidget: (value, meta) { - return Padding( - padding: const EdgeInsets.only(right: 8.0), - child: Text('${value.toInt()}'), + Flexible( + child: BarChart( + BarChartData( + // alignment: BarChartAlignment.spaceAround, + maxY: yAxisData.isNotEmpty + ? yAxisData.reduce((a, b) => a > b ? a : b) * 1.2 + : 10, + rotationQuarterTurns: rotationTurns, + barTouchData: BarTouchData( + enabled: false, + handleBuiltInTouches: false, + touchTooltipData: BarTouchTooltipData( + fitInsideHorizontally: true, + fitInsideVertically: true, + tooltipPadding: const EdgeInsets.all(8), + tooltipMargin: 16, + getTooltipColor: (group) => Colors.transparent, + getTooltipItem: (group, groupIndex, rod, rodIndex) { + if (rod.toY == 0) return null; + + return BarTooltipItem( + formatNumber(rod.toY), + const TextStyle( + color: Colors.black, + fontSize: 12, + fontWeight: FontWeight.w400, + ), ); }, - reservedSize: 60, ), ), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - getTitlesWidget: (value, meta) { - if (value.toInt() < xAxisData.length) { + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + interval: (yAxisData.isNotEmpty + ? yAxisData.reduce((a, b) => a > b ? a : b) / 5 + : 1), + getTitlesWidget: (value, meta) { return Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Transform.rotate( - angle: -1.58, - // angle: -45 * - // (3.1415927 / 180), // Rotating by -45 degrees - alignment: Alignment.center, - child: Center( - child: SizedBox( - width: 100, - child: Text( - xAxisData[value.toInt()], - style: const TextStyle(fontSize: 12), - softWrap: true, - maxLines: 3, + padding: const EdgeInsets.only(right: 8.0), + child: Text('${value.toInt()}'), + ); + }, + reservedSize: 60, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + if (value.toInt() < xAxisData.length) { + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Transform.rotate( + angle: -1.58, + // angle: -45 * + // (3.1415927 / 180), // Rotating by -45 degrees + alignment: Alignment.center, + child: Center( + child: SizedBox( + width: 100, + child: Text( + xAxisData[value.toInt()], + textAlign: TextAlign.right, + style: const TextStyle( + fontSize: 12, + ), + softWrap: true, + maxLines: 3, + ), ), ), ), - ), - ); - } - return Container(); - }, - reservedSize: 85, + ); + } + return Container(); + }, + reservedSize: 125, + ), + ), + topTitles: AxisTitles( + sideTitles: + SideTitles(showTitles: false), // Hide top titles + ), + rightTitles: AxisTitles( + sideTitles: + SideTitles(showTitles: false), // Hide right titles ), ), - topTitles: AxisTitles( - sideTitles: SideTitles(showTitles: false), // Hide top titles + gridData: FlGridData(show: false), + // borderData: FlBorderData(show: false), + + borderData: FlBorderData( + show: false, ), - rightTitles: AxisTitles( - sideTitles: - SideTitles(showTitles: false), // Hide right titles - ), - ), - gridData: FlGridData(show: false), - borderData: FlBorderData(show: false), - barGroups: List.generate( - xAxisData.length, - (index) => BarChartGroupData( - x: index, - barRods: [ - BarChartRodData( - toY: yAxisData[index], - color: Colors.blueAccent, - borderRadius: BorderRadius.circular(4), - width: 20, - ), - ], + // groupsSpace: 5, + barGroups: List.generate( + xAxisData.length, + (index) => BarChartGroupData( + x: index, + // barsSpace: 2, + barRods: [ + BarChartRodData( + toY: yAxisData[index], + // color: Colors.blueAccent, + color: bodyColor, + borderRadius: BorderRadius.circular(4), + width: 20, + ), + ], + showingTooltipIndicators: [0], + ), ), ), ), - ),), + ), ]); case 'fl_multi_bar': double _calculateChartWidth(dynamic chartData) { @@ -1299,14 +1420,10 @@ class ChartWidget extends StatelessWidget { // Group crops by CROP_TYPE Map> groupedCrops = {}; - int touchedGroupIndex = -1; // Iterate over the chartData to group crops by CROP_TYPE - - for (var item in chartData['response']) { - print("MultiBArRaw item: $item"); String crop = item['ObsKey'][cropKey]; @@ -1314,7 +1431,8 @@ class ChartWidget extends StatelessWidget { // Use a Set to avoid duplicates if (groupedCrops.containsKey(cropType)) { - groupedCrops[cropType] = (groupedCrops[cropType]! + [crop]).toSet().toList(); + groupedCrops[cropType] = + (groupedCrops[cropType]! + [crop]).toSet().toList(); } else { groupedCrops[cropType] = [crop]; } @@ -1336,11 +1454,10 @@ class ChartWidget extends StatelessWidget { // // print('groupedCropsflmutli- $groupedCrops'); - return Center( child: Column(children: [ Text( - chartData['chart_heading'] ?? '', // Chart title from data + chartData['chart_heading'] ?? '', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w400, @@ -1349,7 +1466,7 @@ class ChartWidget extends StatelessWidget { ), SizedBox(height: 5), Text( - chartData['chart_sub_heading'] ?? '', + '', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w300, @@ -1367,8 +1484,8 @@ class ChartWidget extends StatelessWidget { child: BarChart( BarChartData( alignment: BarChartAlignment.spaceAround, - maxY: - _calculateMaxY(chartData), // Dynamically calculate max Y + maxY: _calculateMaxY( + chartData), // Dynamically calculate max Y barGroups: _buildHorizontalRotateBarGroups( chartData, groupByValues), // Build bar groups titlesData: FlTitlesData( @@ -1391,27 +1508,31 @@ class ChartWidget extends StatelessWidget { return Padding( padding: const EdgeInsets.only(top: 8.0), child: SizedBox( - width: 60, // Limit width to force wrapping + width: + 60, // Limit width to force wrapping child: Transform.rotate( angle: -0.5, - child: TooltipTheme( - data: TooltipThemeData( - decoration: BoxDecoration( - color: Colors.blueGrey[800], // Change background color - borderRadius: BorderRadius.circular(8), // Optional: rounded corners - ), - textStyle: TextStyle(color: Colors.white), // Change text color + child: TooltipTheme( + data: TooltipThemeData( + decoration: BoxDecoration( + color: Colors.blueGrey[ + 800], // Change background color + borderRadius: BorderRadius.circular( + 8), // Optional: rounded corners ), - child: Tooltip( - message: title, - child: Text( - displayTitle, - softWrap: true, - // style: TextStyle(backgroundColor: Colors.blueGrey[800]), - overflow: TextOverflow.ellipsis, - )), - + textStyle: TextStyle( + color: Colors + .white), // Change text color ), + child: Tooltip( + message: title, + child: Text( + displayTitle, + softWrap: true, + // style: TextStyle(backgroundColor: Colors.blueGrey[800]), + overflow: TextOverflow.ellipsis, + )), + ), ))); // return Transform.rotate( // angle: @@ -1434,7 +1555,8 @@ class ChartWidget extends StatelessWidget { borderData: FlBorderData(show: false), barTouchData: BarTouchData( touchTooltipData: BarTouchTooltipData( - tooltipHorizontalAlignment: FLHorizontalAlignment.center, + tooltipHorizontalAlignment: + FLHorizontalAlignment.center, tooltipRoundedRadius: 8, fitInsideHorizontally: true, // Ensure it fits within the screen @@ -1452,7 +1574,8 @@ class ChartWidget extends StatelessWidget { groupByValues.elementAt(groupIndex); // Fetch the crop for the current group from groupedCrops - String cropType = groupByValues.elementAt(groupIndex); + String cropType = + groupByValues.elementAt(groupIndex); print('GrpcropType: $cropType'); // String crop = groupedCrops[cropType]![rodIndex]; @@ -1465,7 +1588,9 @@ class ChartWidget extends StatelessWidget { print( 'Crop is null or index out of bounds for cropType: $cropType and rodIndex: $rodIndex'); // crop = cropType; // Fallback to cropType - crop = (crops != null && crops.isNotEmpty) ? crops.first : cropType; + crop = (crops != null && crops.isNotEmpty) + ? crops.first + : cropType; } // print('groupedCrops1: $groupedCrops - $rodIndex'); @@ -1512,7 +1637,8 @@ class ChartWidget extends StatelessWidget { response != null && response.spot != null) { // setState(() { - touchedGroupIndex = response.spot!.touchedBarGroupIndex; + touchedGroupIndex = + response.spot!.touchedBarGroupIndex; // }); } else { // setState(() { @@ -1567,7 +1693,7 @@ class ChartWidget extends StatelessWidget { return Column(children: [ Text( - chartData['chart_heading'] ?? '', // Chart title from data + chartData['chart_heading'] ?? '', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w400, @@ -1577,46 +1703,52 @@ class ChartWidget extends StatelessWidget { SizedBox(height: 5), Text( chartData['chart_sub_heading'] ?? '', - style: TextStyle( fontSize: 13, fontWeight: FontWeight.w300, ), textAlign: TextAlign.center, ), - SizedBox(height: 10), + SizedBox(height: 5), // Chart - Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.vertical, + Flexible( + // child: SingleChildScrollView( + // scrollDirection: Axis.vertical, // child: SingleChildScrollView( // scrollDirection: Axis.horizontal, - child: SizedBox( + child: SizedBox( // width: 500, // width: 1000, height: chartHeight, - child: Stack( - children: [ - BarChart( - BarChartData( - maxY: 400000, - rotationQuarterTurns: rotationTurns, - barTouchData: BarTouchData( - touchTooltipData: BarTouchTooltipData( - tooltipHorizontalAlignment: FLHorizontalAlignment.center, - tooltipRoundedRadius: 8, - fitInsideHorizontally: true, // Ensure it fits within the screen - fitInsideVertically: true, - tooltipPadding: EdgeInsets.all(8), - tooltipMargin: 16, - // Only show tooltip when touched - getTooltipItem: (group, groupIndex, rod, rodIndex) { - if (rod.toY == 0 || touchedGroupIndex == -1) { - return null; // Don't show the tooltip if the value is 0 or there's no touch - } + child: Stack(children: [ + BarChart( + BarChartData( + maxY: 400000, + rotationQuarterTurns: rotationTurns, + barTouchData: BarTouchData( + // enabled: true, + enabled: false, + touchTooltipData: BarTouchTooltipData( + getTooltipColor: (group) => Colors.transparent, + tooltipHorizontalAlignment: + FLHorizontalAlignment.center, + tooltipRoundedRadius: 8, + fitInsideHorizontally: + true, // Ensure it fits within the screen + fitInsideVertically: true, + tooltipPadding: EdgeInsets.all(8), + tooltipMargin: 16, + // Only show tooltip when touched + getTooltipItem: (group, groupIndex, rod, rodIndex) { + // if (rod.toY == 0 || touchedGroupIndex == -1) { + // return null; // Don't show the tooltip if the value is 0 or there's no touch + // } + // Hide tooltips for 0 values + if (rod.toY == 0) + return null; // Hide for zero values - if (groupIndex == touchedGroupIndex) { + // if (groupIndex == touchedGroupIndex) { // print('Group Index: $groupIndex, Group : $group'); // Get the group label dynamically @@ -1642,128 +1774,139 @@ class ChartWidget extends StatelessWidget { } return BarTooltipItem( - '$groupLabel\n$crop', + '$formattedValue', const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, + color: Colors.black, + fontWeight: FontWeight.w400, + fontSize: 12, ), - children: [ - TextSpan( - text: ' Value: $formattedValue', - style: const TextStyle( - color: Colors.yellow, - fontWeight: FontWeight.w500, - ), - ), - ], ); - } - return null; - }, - ), - touchCallback: (event, response) { - if (event.isInterestedForInteractions && - response != null && - response.spot != null) { - // setState(() { - touchedGroupIndex = - response.spot!.touchedBarGroupIndex; - // }); - } else { - // setState(() { - touchedGroupIndex = -1; // Reset if no interaction - // }); - } - }, - ), - titlesData: FlTitlesData( - leftTitles: AxisTitles( - sideTitles: SideTitles( - // showTitles: false, - reservedSize: 40, - interval: 100000, - getTitlesWidget: (value, meta) { - return SizedBox( - width: 50, - child: Text( - value.toInt().toString(), - textAlign: TextAlign.justify, - style: const TextStyle(fontSize: 12,), - ),); + + // return BarTooltipItem( + // '$groupLabel\n$crop', + // const TextStyle( + // color: Colors.white, + // fontWeight: FontWeight.bold, + // ), + // children: [ + // TextSpan( + // text: ' Value: $formattedValue', + // style: const TextStyle( + // color: Colors.yellow, + // fontWeight: FontWeight.w500, + // ), + // ), + // ], + // ); + + // } + // return null; }, ), + // touchCallback: (event, response) { + // if (event.isInterestedForInteractions && + // response != null && + // response.spot != null) { + // // setState(() { + // touchedGroupIndex = + // response.spot!.touchedBarGroupIndex; + // // }); + // } else { + // // setState(() { + // touchedGroupIndex = -1; // Reset if no interaction + // // }); + // } + // }, ), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - reservedSize: 150, // Added space for rotated titles - getTitlesWidget: (value, meta) { - if (value < groupByValues.length) { - String title = - groupByValues.elementAt(value.toInt()); - return Transform.rotate( - angle: - -1.58, // Rotation in radians (~ -30 degrees) - child: Center( - child: SizedBox( - width: 120, - child: Text( - title, - textAlign: TextAlign.end, - style: const TextStyle(fontSize: 12), - softWrap: true, - maxLines: 2, - ), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + // showTitles: false, + reservedSize: 40, + interval: 100000, + getTitlesWidget: (value, meta) { + return SizedBox( + width: 50, + child: Text( + value.toInt().toString(), + textAlign: TextAlign.justify, + style: const TextStyle( + fontSize: 12, ), ), ); - } - return const SizedBox.shrink(); - }, + }, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 150, // Added space for rotated titles + getTitlesWidget: (value, meta) { + if (value < groupByValues.length) { + String title = + groupByValues.elementAt(value.toInt()); + return Transform.rotate( + angle: + -1.58, // Rotation in radians (~ -30 degrees) + child: Center( + child: SizedBox( + width: 120, + child: Text( + title, + textAlign: TextAlign.end, + style: const TextStyle(fontSize: 12), + softWrap: true, + maxLines: 2, + ), + ), + ), + ); + } + return const SizedBox.shrink(); + }, + ), + ), + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false)), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false)), + ), + borderData: FlBorderData( + show: true, + border: const Border( + // left: BorderSide(color: Colors.grey), + bottom: BorderSide(color: Colors.white), ), ), - rightTitles: - AxisTitles(sideTitles: SideTitles(showTitles: false)), - topTitles: - AxisTitles(sideTitles: SideTitles(showTitles: false)), - ), - borderData: FlBorderData( - show: true, - border: const Border( - // left: BorderSide(color: Colors.grey), - bottom: BorderSide(color: Colors.white), + gridData: FlGridData( + show: false, + drawVerticalLine: true, + verticalInterval: 1, + horizontalInterval: 100000, + getDrawingHorizontalLine: (value) { + return FlLine( + color: Colors.grey.withOpacity(0.5), + strokeWidth: 1, + ); + }, + getDrawingVerticalLine: (value) { + return FlLine( + color: Colors.grey.withOpacity(0.5), + strokeWidth: 1, + ); + }, ), + barGroups: _buildHorizontalRotateBarGroups( + chartData, groupByValues), + alignment: BarChartAlignment.spaceAround, ), - gridData: FlGridData( - show: false, - drawVerticalLine: true, - verticalInterval: 1, - horizontalInterval: 100000, - getDrawingHorizontalLine: (value) { - return FlLine( - color: Colors.grey.withOpacity(0.5), - strokeWidth: 1, - ); - }, - getDrawingVerticalLine: (value) { - return FlLine( - color: Colors.grey.withOpacity(0.5), - strokeWidth: 1, - ); - }, - ), - barGroups: _buildHorizontalRotateBarGroups( - chartData, groupByValues), - alignment: BarChartAlignment.spaceAround, ), - ), - - ]) - ), + ])), // ), - )), - + // ) + ), ]); default: @@ -1860,7 +2003,6 @@ class ChartWidget extends StatelessWidget { return lineBars; } - /// Function to parse TIME_PERIOD into a EXACT value double parseTimePeriod(String timePeriod) { // Match formats @@ -1869,13 +2011,23 @@ class ChartWidget extends StatelessWidget { return double.parse(timePeriod); // Year as a double (2020 → 2020.0) } else if (RegExp(r'^\d{4}-\d{2}$').hasMatch(timePeriod)) { // Format: Year-Month (e.g., "2020-10") - return double.parse(timePeriod.replaceAll('-', '.')); // Convert "2020-10" → 2020.10 + return double.parse( + timePeriod.replaceAll('-', '.')); // Convert "2020-10" → 2020.10 } else if (RegExp(r'^\d{4}-[A-Za-z]{3}$').hasMatch(timePeriod)) { // Format: Year-MonthAbbr (e.g., "2020-Oct") Map monthMap = { - 'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, - 'May': 5, 'Jun': 6, 'Jul': 7, 'Aug': 8, - 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12 + 'Jan': 1, + 'Feb': 2, + 'Mar': 3, + 'Apr': 4, + 'May': 5, + 'Jun': 6, + 'Jul': 7, + 'Aug': 8, + 'Sep': 9, + 'Oct': 10, + 'Nov': 11, + 'Dec': 12 }; List parts = timePeriod.split('-'); @@ -1887,7 +2039,6 @@ class ChartWidget extends StatelessWidget { throw FormatException("Invalid TIME_PERIOD format: $timePeriod"); } - List lineBarsData(List filteredData, Set groupByValues, String groupByKey) { List lineBars = []; @@ -1952,9 +2103,6 @@ class ChartWidget extends StatelessWidget { print('spots - $spots'); - - - // List spots = filteredData // .where((entry) => entry['ObsKey'][groupByKey] == group) // .map((entry) { @@ -1970,8 +2118,6 @@ class ChartWidget extends StatelessWidget { // return FlSpot(xValue, yValue); // }).toList(); - - print('lineGrp2'); // Add a line for this group lineBars.add( @@ -1983,7 +2129,6 @@ class ChartWidget extends StatelessWidget { isStrokeCapRound: true, dotData: FlDotData(show: true), belowBarData: BarAreaData(show: false), - ), ); } @@ -2001,6 +2146,29 @@ class ChartWidget extends StatelessWidget { fitInsideVertically: true, tooltipPadding: EdgeInsets.all(8), tooltipMargin: 16, // Adds margin to prevent clipping + getTooltipItems: (List lineBarsSpot) { + return lineBarsSpot.map((lineBarSpot) { + Color lineColor = lineBarSpot.bar is LineChartBarData + ? (lineBarSpot.bar).color ?? Colors.white + : Colors.white; // Extract bar color safely + return LineTooltipItem( + '', + const TextStyle(), + children: [ + TextSpan( + text: '● ', // Unicode circle + style: TextStyle( + color: lineColor), // Set circle color to match bar + ), + TextSpan( + text: formatNumber(lineBarSpot.y), + // text: ' ${lineBarSpot.y}', // Keep the value white + style: const TextStyle(color: Colors.white), + ), + ], + ); + }).toList(); + }, ), handleBuiltInTouches: true, ); @@ -2018,11 +2186,11 @@ class ChartWidget extends StatelessWidget { } FlTitlesData titlesData1(Set xValues) { - print('tileDATa1xvalue - $xValues'); double findClosest(double value, Set values) { - return values.reduce((a, b) => (value - a).abs() < (value - b).abs() ? a : b); + return values + .reduce((a, b) => (value - a).abs() < (value - b).abs() ? a : b); } return FlTitlesData( @@ -2035,18 +2203,16 @@ class ChartWidget extends StatelessWidget { String formattedValue; if (value % 10 == 0) { - // Check if the value is in the millions or thousands range + // Check if the value is in the millions or thousands range if (value >= 1000000000000) { - formattedValue = '${(value / 1000000000000).toStringAsFixed(0)}T'; - } - else if (value >= 1000000000) { + formattedValue = + '${(value / 1000000000000).toStringAsFixed(0)}T'; + } else if (value >= 1000000000) { formattedValue = '${(value / 1000000000).toStringAsFixed(0)}B'; - } - else if (value >= 1000000) { + } else if (value >= 1000000) { formattedValue = '${(value / 1000000).toStringAsFixed(0)}M'; - } - else if (value >= 1000) { + } else if (value >= 1000) { formattedValue = '${(value / 1000).toStringAsFixed(0)}k'; } else { formattedValue = value.toStringAsFixed( @@ -2078,14 +2244,11 @@ class ChartWidget extends StatelessWidget { interval: null, // interval: 10, // Ensure each year is shown only once getTitlesWidget: (value, meta) { - print('BtmTiles :-$value'); print('BtmTilesmeta :-$meta'); // if (xValues.contains(value)) double closestValue = findClosest(value, xValues); - if ((closestValue - value).abs() < 0.15) - { - + if ((closestValue - value).abs() < 0.15) { print("Bottomtiles"); print(value); return Padding( @@ -2206,13 +2369,14 @@ class ChartWidget extends StatelessWidget { x: i, // Index for the x-axis based on the time period barRods: [ BarChartRodData( - toY: currentToY, // Use the accumulated `currentToY` value - rodStackItems: rodStackItems, // Add the stacked items - width: 20, - // width: barWidth, - borderRadius: BorderRadius.zero, - color: Colors.black// This will act as a container for stacked items - ), + toY: currentToY, // Use the accumulated `currentToY` value + rodStackItems: rodStackItems, // Add the stacked items + width: 20, + // width: barWidth, + borderRadius: BorderRadius.zero, + color: + Colors.black // This will act as a container for stacked items + ), ], ); @@ -2407,6 +2571,39 @@ class ChartWidget extends StatelessWidget { } } +class BarValuePainter extends CustomPainter { + BarValuePainter(this.barGroups, this.constraints); + final List barGroups; + final BoxConstraints constraints; + + @override + void paint(Canvas canvas, Size size) { + final textPainter = TextPainter( + textAlign: TextAlign.center, + textDirection: TextDirection.ltr, + ); + + final paint = Paint()..color = Colors.black; + + for (var group in barGroups) { + for (var rod in group.barRods) { + final x = group.x.toDouble() * (size.width / barGroups.length); + final y = (1 - rod.toY / 400000) * size.height; // Normalize height + + textPainter.text = TextSpan( + text: rod.toY.toStringAsFixed(1), + style: TextStyle(color: Colors.black, fontSize: 12), + ); + + textPainter.layout(); + textPainter.paint(canvas, Offset(x - textPainter.width / 2, y - 20)); + } + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} double calculateBarWidth(BuildContext context, int totalBars) { double width = MediaQuery.of(context).size.width; // Chart width @@ -2435,9 +2632,8 @@ double calculateBarWidth(BuildContext context, int totalBars) { } class ChartData { + ChartData({this.x, required this.y, this.xDateTime}); final String? x; final double y; final DateTime? xDateTime; - - ChartData({this.x, required this.y, this.xDateTime}); -} \ No newline at end of file +} diff --git a/pubspec.lock b/pubspec.lock index a90e1d09..5bdff5a3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -70,6 +70,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + boxy: + dependency: "direct main" + description: + name: boxy + sha256: "71af0cd1bf7889c09787f26219a345aa4f38ccb98384c8ec24189e4d8e746005" + url: "https://pub.dev" + source: hosted + version: "2.2.1" build: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 76c44216..c25a09e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: uae_stat description: "View statistics about the UAE from the Federal Center for Statistics and Competitiveness." publish_to: "none" -version: 1.0.24+25 +version: 1.0.25+26 environment: sdk: ">=3.2.3 <4.0.0" @@ -20,7 +20,6 @@ dependencies: injectable: ^2.3.2 riverpod_annotation: ^2.3.3 cupertino_icons: ^1.0.6 - fl_chart: ^0.70.2 iconify_design: ^1.0.1 marquee: ^2.2.3 @@ -66,6 +65,7 @@ dependencies: tutorial_coach_mark: ^1.2.12 package_info_plus: ^8.2.0 flutter_svg: ^2.0.17 + boxy: ^2.2.1 dependency_overrides: fading_edge_scrollview: ^4.1.1