import 'dart:convert'; import 'dart:math'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:uae_stat/domain/use_cases/language.dart'; // import 'package:syncfusion_flutter_charts/charts.dart'; import 'package:d_chart/d_chart.dart'; class ChartWidget extends StatelessWidget { ChartWidget({ Key? key, required this.chartData, required this.bodyColor, required this.chartHeader, }) : super(key: key); final dynamic chartData; final Color bodyColor; final String chartHeader; List showingTooltipOnSpots = []; // To store indices of active tooltips late LineChartBarData tooltipsOnBar; // Needs proper initialization // Define color lists final Map> colorMap = { "ECONOMY": [ Color(0xFF648CBA), Color(0xFF90B0D5), Color(0xFF98BCE5), Color(0xFFA7B5C5), Color(0xFFBED3EC), Color(0xFFD4E3F4), ], "SOCIAL": [ Color(0xFFD9BB99), Color(0xFF87766E), Color(0xFFC6A885), Color(0xFFA28565), Color(0xFFBFB29B), Color(0xFFE8D8BB), ], "ENVIRONMENT": [ Color(0xFF376C79), Color(0xFF578D9C), Color(0xFF7DAFBC), Color(0xFF86C7D9), // Color(0xFF989898), ], }; final Map> colorMapForFour = { "ECONOMY": [ Color(0xFF648CBA), Color(0xFF90B0D5), Color(0xFF98BCE5), Color(0xFFA7B5C5), Color(0xFFBED3EC), ], "SOCIAL": [ Color(0xFFD9BB99), Color(0xFF87766E), Color(0xFFC6A885), Color(0xFFA28565), Color(0xFFBFB29B), ], "ENVIRONMENT": [ Color(0xFF376C79), Color(0xFF578D9C), Color(0xFF7DAFBC), Color(0xFF86C7D9), ], }; final Map> colorMapForThree = { "ECONOMY": [Color(0xFF648CBA), Color(0xFF90B0D5), Color(0xFF98BCE5)], "SOCIAL": [Color(0xFFD9BB99), Color(0xFF87766E), Color(0xFFC6A885)], "ENVIRONMENT": [Color(0xFF376C79), Color(0xFF578D9C), Color(0xFF7DAFBC)], }; // Define color lists final Map> colorMapForTwo = { "ECONOMY": [Color(0xFF648CBA), Color(0xFF90B0D5)], "SOCIAL": [Color(0xFFD9BB99), Color(0xFF87766E)], "ENVIRONMENT": [Color(0xFF376C79), Color(0xFF578D9C)], }; // List get uniqueColors => colorMap[chartHeader] ?? [Colors.grey]; // Default to grey if header is unknown // Function to get colors for both English & Arabic keys List get uniqueColors { final Map translations = { 'الاقتصاد': 'ECONOMY', 'الاجتماعي': 'SOCIAL', 'البيئة': 'ENVIRONMENT', }; String key = translations[chartHeader] ?? chartHeader; return colorMap[key] ?? [Colors.grey]; } List get uniqueColorsForTwo { final Map translations = { 'الاقتصاد': 'ECONOMY', 'الاجتماعي': 'SOCIAL', 'البيئة': 'ENVIRONMENT', }; String key = translations[chartHeader] ?? chartHeader; return colorMapForTwo[key] ?? [Colors.grey]; } List get uniqueColorsForThree { final Map translations = { 'الاقتصاد': 'ECONOMY', 'الاجتماعي': 'SOCIAL', 'البيئة': 'ENVIRONMENT', }; String key = translations[chartHeader] ?? chartHeader; return colorMapForThree[key] ?? [Colors.grey]; } List get uniqueColorsForFour { final Map translations = { 'اقتصاد': 'ECONOMY', 'اجتماعي': 'SOCIAL', 'بيئة': 'ENVIRONMENT', }; String key = translations[chartHeader] ?? chartHeader; return colorMapForFour[key] ?? [Colors.grey]; } String capitalizeAndSplit(String input) { return input .split('_') .map((word) => word[0].toUpperCase() + word.substring(1)) .join(' '); } Color _hexToColor(String hexColor) { hexColor = hexColor.toUpperCase().replaceAll('#', ''); // Remove # if (hexColor.length == 6) { hexColor = 'FF$hexColor'; // Add alpha if missing } return Color(int.parse(hexColor, radix: 16)); } String formatVerticalText(String text) { return text.split('').join('\n'); // Inserts a newline after each character } 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'] .where((entry) => entry['ObsKey'][groupByKey] == groupByValue) .map((entry) { return ChartData( x: entry['ObsKey']['TIME_PERIOD'], y: double.tryParse(entry['ObsValue']['Value']) ?? 0.0, ); }).toList(); } List parseColumnChartData(dynamic chartData) { return chartData['response'].map((entry) { return BarChartGroupData( x: int.tryParse(entry['ObsKey']['TIME_PERIOD']) ?? 0, barRods: [ BarChartRodData( toY: double.tryParse(entry['ObsValue']['Value']) ?? 0.0, color: Colors.blue, width: 16, borderRadius: BorderRadius.zero, ), ], ); }).toList(); } Color hexToColor(String hex) { hex = hex.replaceFirst('#', ''); // Remove # if (hex.length == 6) { hex = 'FF$hex'; // Add full opacity } return Color(int.parse(hex, radix: 16)); } Color adjustColor(Color baseColor, int index) { final HSLColor hsl = HSLColor.fromColor(baseColor); // Lightness variation (keeping it light, between 0.6 and 0.9) double lightnessFactor = 0.6 + (index % 4) * 0.1; // Varies between 0.6 to 0.8 final HSLColor adjustedHSL = hsl.withLightness( lightnessFactor.clamp(0.6, 0.9), // Ensures light shades only ); return adjustedHSL.toColor(); } Color adjustColorlineTrend(Color baseColor, int index) { final HSLColor hsl = HSLColor.fromColor(baseColor); // Darken the color slightly by reducing lightness double lightnessFactor = hsl.lightness - ((index % 4) * 0.05); lightnessFactor = lightnessFactor.clamp(0.3, 0.7); // Keeps colors darker // Adjust opacity: Making it darker with alpha between 160-230 int alphaFactor = (230 - (index % 4) * 20).clamp( 160, 230, ); // Darker opacity return hsl .withLightness(lightnessFactor) .toColor() .withAlpha(alphaFactor); // Use withAlpha() instead of withOpacity() } List parsePieChartData( dynamic chartData, double totalValue, int? touchedIndex,BuildContext context ) { debugPrint('Chart Data: ${jsonEncode(chartData)}'); var groupByKeyValue; if (chartData['chart_type_json']['chart_type'] == 'bar_chart_horizontal' || chartData['chart_type_json']['chart_type'] == 'horizontal_rotate') { groupByKeyValue = chartData['chart_type_json']['y_group'] ?? ''; } else { groupByKeyValue = chartData['chart_type_json']['x_group'] ?? ''; } return chartData['response'].asMap().entries.map(( entry, ) { int index = entry.key; var data = entry.value; // Handle different types: int, double, and String double value = 0.0; var rawValue = data['ObsValue']['Value']; String title = data['ObsKey'][groupByKeyValue] ?? ''; if (rawValue is String) { value = double.tryParse(rawValue) ?? 0.0; } else if (rawValue is num) { value = rawValue.toDouble(); } else if (rawValue is double) { value = rawValue; } else { print('Unexpected Type for ObsValue[Value]: ${rawValue.runtimeType}'); } // double percentage = (value / totalValue) * 100; double percentage = (totalValue > 0) ? (value / totalValue) * 100 : 0.0; bool isTouched = index == touchedIndex; String displayTitle = title.length > 12 ? '${title.substring(0, 10)}...' : title; return PieChartSectionData( value: value, // color: Colors.primaries[index % Colors.primaries.length], // color: adjustColor(bodyColor, index), // Adjust alpha dynamically color: uniqueColors[index % uniqueColors.length], // title: '${percentage.toStringAsFixed(1)}%', title: percentage >= 0.1 ? '${percentage.toStringAsFixed(1)}%' : '', // title: percentage >= 0.1 ? '$displayTitle\n${percentage.toStringAsFixed(1)}%' : '', radius: isTouched ? 70 : 50, // Increase size when touched titleStyle: TextStyle( fontFamily: context.translate( 'Roboto', 'NotoKufi', ), fontSize: isTouched ? 12 : (percentage < 1 ? 10 : 11), fontWeight: FontWeight.bold, color: Colors.black, // color: Colors.white, ), titlePositionPercentageOffset: 1.4, // titlePositionPercentageOffset: 1.7, ); }).toList(); } // List generateIndicators(dynamic chartData) { // var groupByKeyValue; // if (chartData['chart_type_json']['chart_type'] == 'bar_chart_horizontal' || // chartData['chart_type_json']['chart_type'] == 'horizontal_rotate') { // groupByKeyValue = chartData['chart_type_json']['y_group'] ?? ''; // } else { // groupByKeyValue = chartData['chart_type_json']['x_group'] ?? ''; // } // // return [ // Center( // child: Wrap( // alignment: WrapAlignment.center, // Center-align all items // spacing: 10, // Adjust horizontal spacing // runSpacing: 5, // Adjust vertical spacing for wrapping // children: chartData['response'].asMap().entries.map((entry) { // int index = entry.key; // var data = entry.value; // Color color = Colors.primaries[index % Colors.primaries.length]; // String title = data['ObsKey'][groupByKeyValue] ?? ''; // // return Container( // padding: EdgeInsets.symmetric(vertical: 4), // Add spacing // child: Row( // mainAxisSize: // MainAxisSize.min, // Wrap content without extra space // mainAxisAlignment: // MainAxisAlignment.center, // Align items center // crossAxisAlignment: // CrossAxisAlignment.center, // Align vertically // children: [ // Container( // width: 10, // height: 10, // decoration: BoxDecoration( // color: uniqueColors[index % uniqueColors.length], // shape: BoxShape.circle, // ), // ), // SizedBox(width: 5), // ConstrainedBox( // constraints: BoxConstraints( // maxWidth: 200, // Ensures text wraps within a limit // ), // child: Text( // title, // style: TextStyle(fontSize: 12), // softWrap: true, // Allow text to wrap naturally // textAlign: TextAlign.center, // Center text alignment // ), // ), // ], // ), // ); // }).toList(), // ), // ), // ]; // } List generateIndicators( dynamic chartData, double totalValue, BuildContext context) { var groupByKeyValue; if (chartData['chart_type_json']['chart_type'] == 'bar_chart_horizontal' || chartData['chart_type_json']['chart_type'] == 'horizontal_rotate') { groupByKeyValue = chartData['chart_type_json']['y_group'] ?? ''; } else { groupByKeyValue = chartData['chart_type_json']['x_group'] ?? ''; } return chartData['response'].asMap().entries.map((entry) { int index = entry.key; var data = entry.value; Color color = Colors.primaries[index % Colors.primaries.length]; String title = data['ObsKey'][groupByKeyValue] ?? ''; // String value = data['ObsValue']['Value'].toString(); String shortTitle = title.length > 10 ? '${title.substring(0, 10)}…' : title; // Handle different types: int, double, and String double value = 0.0; var rawValue = data['ObsValue']['Value']; if (rawValue is String) { value = double.tryParse(rawValue) ?? 0.0; } else if (rawValue is num) { value = rawValue.toDouble(); } else if (rawValue is double) { value = rawValue; } else { print('Unexpected Type for ObsValue[Value]: ${rawValue.runtimeType}'); } // double percentage = (value / totalValue) * 100; String percentage = (totalValue > 0) ? ((value / totalValue) * 100).toStringAsFixed(1) : "0.0"; // bool isTouched = index == touchedIndex; // return Wrap( // alignment: WrapAlignment.center, // spacing: 12, // runSpacing: 8, // children: [ // Row( // mainAxisSize: MainAxisSize.min, // children: [ // Container( // width: 10, // height: 10, // decoration: BoxDecoration( // color: uniqueColors[index % uniqueColors.length], // shape: BoxShape.circle, // ), // ), // SizedBox(width: 8), // Flexible( // child: Text( // context.translate( // '$title -- $percentage%', // '$title -- %$percentage', // ), // textAlign: TextAlign.start, // style: TextStyle(fontSize: 12), // softWrap: true, // maxLines: 2, // Allows wrapping within two lines // overflow: TextOverflow.ellipsis, // Prevents overflow // ), // ), // ], // ), // ], // ); return Row( crossAxisAlignment: CrossAxisAlignment.start, // Aligns dot to text start children: [ Container( width: 10, height: 10, margin: EdgeInsets.only(top: 4), // Adjust to align with text decoration: BoxDecoration( color: uniqueColors[index % uniqueColors.length], shape: BoxShape.circle, ), ), SizedBox(width: 8), Expanded( child: TooltipTheme( data: TooltipThemeData( decoration: BoxDecoration( color: Colors.blueGrey[800], // Change background color borderRadius: BorderRadius.circular(8), ), textStyle: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),color: Colors.white), ), child: Tooltip( message: title, // Full text on hover child: Text( context.translate( '$title -- $percentage%', '$title -- %$percentage', ), style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 12), softWrap: true, maxLines: 2, ), ), ), ), ], ); }).toList(); } List generateIndicatorsBar( Map chartData, List groupByValues, ) { List indicators = []; for (int i = 0; i < groupByValues.length; i++) { String groupLabel = groupByValues[i]; indicators.add( Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 12, height: 12, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.blue, // Replace with dynamic color if needed ), ), SizedBox(width: 5), Text( groupLabel, // Displaying groupLabel style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, ), ), ], ), ); } return indicators; } Set extractGroupByValues(dynamic chartData, String groupByKey) { return (chartData['response'] as List).map((entry) { var value = entry['ObsKey'][groupByKey]; return value != null ? value.toString() : ''; // Handle null and ensure conversion to String }).toSet(); } // @override // void initState() { // super.initState(); // tooltipsOnBar = lineBars.first; // Example: Assign the first line bar // } Widget buildChart(dynamic chartData, BuildContext context) { // print('bodyColor- $bodyColor'); print('chartDataccccccc $chartData'); if (chartData == null || chartData['response'] == null) { return Center(child: Text('No chart data available')); } print(chartData['chart_type_json']['TIME_PERIOD']); print(chartData['chart_type_json']['x_group']); String groupByKey = ''; if (chartData['chart_type_json']['chart_type'] == 'bar_chart_horizontal' || chartData['chart_type_json']['chart_type'] == 'horizontal_rotate') { groupByKey = chartData['chart_type_json']['y_group'] ?? ''; } else { groupByKey = chartData['chart_type_json']['x_group'] ?? ''; } print('groupByKey $groupByKey'); Set groupByValues = extractGroupByValues(chartData, groupByKey); print('groupByValues $groupByValues'); switch (chartData['chart_type_json']['chart_type']) { // case 'stacked_bar': // String chartTitle = // capitalizeAndSplit(chartData['chart_heading'] ?? ''); // // Define a list of colors // final List uniqueColors = [ // Color(0xFF648CBA), // Color(0xFF90B0D5), // Color(0xFF98BCE5), // Color(0xFFA7B5C5), // Color(0xFFBED3EC), // Color(0xFFD4E3F4), // ]; // return SfCartesianChart( // primaryXAxis: CategoryAxis(), // title: ChartTitle(text: chartData['chart_heading']), // legend: Legend( // isVisible: true, // position: LegendPosition.bottom, // overflowMode: LegendItemOverflowMode.scroll, // ), // tooltipBehavior: TooltipBehavior( // enable: true, // Enable tooltips // format: 'point.x : point.y', // Custom tooltip format // ), // series: >[ // for (int i = 0; i < groupByValues.length; i++) // StackedBarSeries( // dataSource: parseStackedBarChartData( // chartData, groupByKey, groupByValues.elementAt(i)), // xValueMapper: (ChartData data, _) => data.x ?? '', // yValueMapper: (ChartData data, _) => data.y, // name: groupByValues.elementAt(i), // color: uniqueColors[i % uniqueColors.length], // enableTooltip: true, // ), // ], // ); // case 'stacked_column': // String chartTitle = // capitalizeAndSplit(chartData['chart_heading'] ?? ''); // // Define a list of colors // final List uniqueColors = [ // Color(0xFF648CBA), // Color(0xFF90B0D5), // Color(0xFF98BCE5), // Color(0xFFA7B5C5), // Color(0xFFBED3EC), // Color(0xFFD4E3F4), // ]; // return SfCartesianChart( // primaryXAxis: CategoryAxis(), // title: ChartTitle(text: chartData['chart_heading']), // legend: Legend( // isVisible: true, // position: LegendPosition.bottom, // overflowMode: LegendItemOverflowMode.scroll, // ), // tooltipBehavior: TooltipBehavior( // enable: true, // Enable tooltips // format: 'point.x : point.y', // Custom tooltip format // ), // series: >[ // for (int i = 0; i < groupByValues.length; i++) // StackedColumnSeries( // dataSource: parseStackedBarChartData( // chartData, groupByKey, groupByValues.elementAt(i)), // xValueMapper: (ChartData data, _) => data.x ?? '', // yValueMapper: (ChartData data, _) => data.y, // name: groupByValues.elementAt(i), // color: uniqueColors[i % uniqueColors.length], // enableTooltip: true, // ), // ], // ); case 'column_chart': // Case 3 for fl_chart column chart return BarChart( BarChartData( gridData: FlGridData(show: false), titlesData: FlTitlesData(show: true), borderData: FlBorderData(show: false), barGroups: parseColumnChartData(chartData), ), ); case 'pie_chart': 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; }).fold(0.0, (prev, element) => prev + element); ValueNotifier touchedIndex = ValueNotifier(null); return Column( children: [ Text( chartData['chart_heading'] ?? 'Pie Chart', // Chart title from data style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 14, fontWeight: FontWeight.w400), textAlign: TextAlign.center, ), SizedBox(height: 15), Text( chartData['chart_sub_heading'] ?? '', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 13, fontWeight: FontWeight.w300), textAlign: TextAlign.center, ), SizedBox(height: 70), Expanded( child: ValueListenableBuilder( valueListenable: touchedIndex, builder: (context, value, child) { return PieChart( PieChartData( sections: parsePieChartData( chartData, totalValue, touchedIndex.value,context ), borderData: FlBorderData(show: false), sectionsSpace: 2, centerSpaceRadius: 50, pieTouchData: PieTouchData( touchCallback: (FlTouchEvent event, pieTouchResponse) { if (pieTouchResponse?.touchedSection != null && event is! FlTapUpEvent) { touchedIndex.value = pieTouchResponse! .touchedSection!.touchedSectionIndex; } else { touchedIndex.value = null; // Reset when not touching } }, ), ), ); }, ), ), SizedBox(height: 55), Expanded( // child: SingleChildScrollView( // scrollDirection: Axis.horizontal, // // child: SingleChildScrollView( // scrollDirection: Axis.vertical, child: Center( child: Container( width: double.infinity, constraints: BoxConstraints( minHeight: 3, ), // Allow dynamic height child: Padding( padding: const EdgeInsets.only( left: 2.0, right: 2.0, bottom: 8.0, top: 30, ), child: Column( // Change Row to Column crossAxisAlignment: CrossAxisAlignment.start, children: generateIndicators(chartData, totalValue, context), ), ), ), ), ), // ), ], ); case 'fl_stacked_bar': // Divide groupByValuesList into chunks of 5 items per row List groupByValuesList = groupByValues.toList(); int chunkSize = 5; List> chunkedGroupByValues = []; print('flStackedBar1'); for (int i = 0; i < groupByValuesList.length; i += chunkSize) { chunkedGroupByValues.add( groupByValuesList.sublist( i, i + chunkSize > groupByValuesList.length ? groupByValuesList.length : i + chunkSize, ), ); } return Column( children: [ Text( chartData['chart_heading'] ?? '', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 14, fontWeight: FontWeight.w400), textAlign: TextAlign.center, ), SizedBox(height: 5), Text( chartData['chart_sub_heading'] ?? '', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 13, fontWeight: FontWeight.w300), textAlign: TextAlign.center, ), SizedBox(height: 15), AspectRatio( aspectRatio: 1.5, child: BarChart( BarChartData( alignment: BarChartAlignment.spaceEvenly, barTouchData: BarTouchData( touchTooltipData: BarTouchTooltipData( // tooltipBgColor: Colors.black.withOpacity(0.8), fitInsideHorizontally: true, fitInsideVertically: true, // tooltipPadding: const EdgeInsets.all(8), tooltipPadding: const EdgeInsets.symmetric( horizontal: 4, vertical: 8, ), // Optional tooltipHorizontalAlignment: FLHorizontalAlignment.left, tooltipMargin: 16, getTooltipColor: (colorMap) => Colors.black, getTooltipItem: ( groupData, groupIndex, rodData, rodIndex, ) { // Get the list of group names dynamically List groupNames = []; for (var entry in chartData['response']) { String groupValue = entry['ObsKey'][groupByKey]; if (!groupNames.contains(groupValue)) { groupNames.add(groupValue); } } // Accessing the stacked rod items and calculating the tooltip text List rodStackItems = rodData.rodStackItems; List tooltipTextSpans = []; for (int i = 0; i < rodStackItems.length; i++) { double fromY = rodStackItems[i].fromY; double toY = rodStackItems[i].toY; double value = toY - fromY; String groupName = groupNames[i]; String formattedValue; if (value >= 1000000) { formattedValue = (value / 1000000).toStringAsFixed(1) + 'M'; } else if (value >= 1000) { formattedValue = (value / 1000).toStringAsFixed(1) + 'K'; } else { formattedValue = value.toStringAsFixed( 0, ); // for values smaller than 1000 } // Get the color for the current group Color groupColor = _getColorForGroup( i, ); // Replace with your color logic // Add a TextSpan for the colored circle and the group name with value tooltipTextSpans.addAll([ TextSpan( text: ' ● ', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ), color: groupColor, fontSize: 14, ), // Circle color ), TextSpan( text: '$groupName - $formattedValue\n', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ), color: Colors.white, fontSize: 12, ), ), ]); } return BarTooltipItem( '', TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),color: Colors.white, fontSize: 12), children: tooltipTextSpans, textAlign: TextAlign.left, ); }, ), handleBuiltInTouches: true, // Enable built-in touch events ), titlesData: FlTitlesData( show: true, bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 28, getTitlesWidget: (double value, TitleMeta meta) { return _generateXTitles(chartData,context)[value.toInt()]; }, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 40, getTitlesWidget: _generateLeftTitles, ), ), topTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), ), rightTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), ), ), gridData: FlGridData(show: false), borderData: FlBorderData(show: false), barGroups: _generateBarGroups(context, chartData, groupByKey), ), ), ), // Legend Padding( padding: const EdgeInsets.all(5.0), child: Wrap( spacing: 10, // Horizontal spacing between items runSpacing: 5, // Vertical spacing between rows children: chunkedGroupByValues.expand((chunk) { return chunk.map((group) { List groupByValuesList = groupByValues.toList(); int index = groupByValuesList.indexOf(group); Color groupColor = _getColorForGroup(index); return Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 10, height: 10, decoration: BoxDecoration( color: groupColor, shape: BoxShape.circle, ), ), SizedBox(width: 5), Tooltip( message: group, waitDuration: Duration(milliseconds: 500), showDuration: Duration(seconds: 2), decoration: BoxDecoration( color: Colors.black, // color: Colors.blueGrey[900], borderRadius: BorderRadius.circular(4), ), textStyle: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),color: Colors.white), child: Text( group, // group.length > 10 // ? '${group.substring(0, 10)}...' // : group, // overflow: TextOverflow.ellipsis, style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 11), ), ), ], ); }).toList(); }).toList(), ), ), ], ); case 'line_trend': final List uniqueColorsLine_trend_2 = [ Color(0xFF6097CD), Color(0xFFD086A7), Color(0xFF98BCE5), Color(0xFFA7B5C5), Color(0xFFBED3EC), Color(0xFFD4E3F4), ]; Map groupColorMap = {}; int colorIndex = 0; // for (String group in groupByValues) { // // groupColorMap[group] = uniqueColorsLine_trend_2[ // // colorIndex % uniqueColorsLine_trend_2.length]; // groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; // // groupColorMap[group] = adjustColorlineTrend(bodyColor, colorIndex); // colorIndex++; // } print('LnTrnd1'); // Extract all years from the chart data List years = (chartData['response'] as List) .map( (entry) => int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0, ) .toList(); // Sort in ascending order years.sort(); // Sort in descending order // years.sort((a, b) => b.compareTo(a)); print(years); print('years $years'); if (years.isEmpty) { // Return an empty chart if no data return LineChart( LineChartData( titlesData: FlTitlesData(show: false), lineBarsData: [], ), ); } print('LnTrnd1.2'); // Find the maximum year and calculate the range for the last 5 years int maxYear = years.reduce((a, b) => a > b ? a : b); int minYear = maxYear - 5; // Filter chart data to only include entries within the last 5 years List filteredData = (chartData['response'] as List).where((entry) { int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0; return year >= minYear && year <= maxYear; }).toList(); print('LnTrnd2 $filteredData'); // Set uniqueXValues = filteredData // .map( // (entry) => double.parse(entry['ObsKey']['TIME_PERIOD'])) // .toSet(); Set uniqueXValues = filteredData.map((entry) { String? timePeriod = entry['ObsKey']['TIME_PERIOD']; // Nullable String print('TIME_PERIOD- $timePeriod'); if (timePeriod == null) { print('TIME_PERIOD is null'); return ''; // Or handle the null case appropriately } // Check format using regex if (RegExp(r'^\d{4}$').hasMatch(timePeriod)) { print('Year format detected: $timePeriod'); return timePeriod; // Year only } else if (RegExp(r'^\d{4}-\d{2}$').hasMatch(timePeriod)) { print('Year-Month format detected: $timePeriod'); return timePeriod; // Year-Month } else if (RegExp(r'^\d{4}-[A-Za-z]{3}$').hasMatch(timePeriod)) { print('Year-MonthAbbr format detected: $timePeriod'); return timePeriod; // Year-MonthAbbr } else { print('Unknown format: $timePeriod'); return timePeriod; // Keep it as is } }).toSet(); print("Unique X Values: $uniqueXValues"); double parseTimePeriod(String timePeriod) { // Match formats 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) } 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, }; 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) } throw FormatException("Invalid TIME_PERIOD format: $timePeriod"); } Set uniqueXValuesProcessed = uniqueXValues .where((value) => value.isNotEmpty) // Remove any empty strings .map(parseTimePeriod) .toSet(); print('Processed X Values: $uniqueXValuesProcessed'); print('LnTrnd2.1'); var chartBarColors = chartData['chart_bar_color'] ?? {}; Map barColorsMap = {}; chartBarColors.forEach((key, value) { barColorsMap[key] = value.toString(); // Ensure values are strings }); bool hasBarColors = barColorsMap.isNotEmpty; var chartBarColorsRaw = chartData['chart_bar_color'] ?? {}; // Convert LinkedMap to Map Map chartBarColrs = Map.from( chartBarColorsRaw, ); // Now map to Color Map parsedChartBarColors = chartBarColrs.map(( key, value, ) { return MapEntry(key, _hexToColor(value)); }); print('parsedChartBarColors1 - $parsedChartBarColors'); for (String group in groupByValues) { if (hasBarColors && chartData['dataset'] == 'population') { groupColorMap[group] = parsedChartBarColors[group]!; // Directly assign as Color } else { groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; } colorIndex++; } // Generate line bars for the chart List lineBars = lineBarsData( filteredData, groupByValues, groupByKey, parsedChartBarColors, ); print('LnTrnd3'); return Column( children: [ Text( chartData['chart_heading'] ?? '', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 14, fontWeight: FontWeight.w400), textAlign: TextAlign.center, ), SizedBox(height: 5), Text( chartData['chart_sub_heading'] ?? '', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 13, fontWeight: FontWeight.w300), textAlign: TextAlign.center, ), SizedBox(height: 10), // Chart Expanded( child: LineChart( LineChartData( // showingTooltipIndicators: showingTooltipOnSpots.map((index) { // return ShowingTooltipIndicators([ // LineBarSpot( // tooltipsOnBar, // lineBarsData.indexOf(tooltipsOnBar), // tooltipsOnBar.spots[index], // ), // ]); // }).toList(), lineTouchData: lineTouchData1(context), gridData: gridData(), // titlesData: titlesData1(uniqueXValues), titlesData: titlesData1(uniqueXValuesProcessed,context), borderData: borderData(), lineBarsData: lineBars, // minY: 10000, // 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), ), ), ), Padding( padding: const EdgeInsets.all(8.0), child: Wrap( spacing: 12, runSpacing: 8, children: groupByValues.map((group) { return Row( mainAxisSize: MainAxisSize.max, // Take full width mainAxisAlignment: MainAxisAlignment.start, // Align to left children: [ Container( width: 12, height: 12, color: groupColorMap[group], ), SizedBox(width: 6), Text( group, style: TextStyle( fontSize: 14, fontFamily: context.translate( 'Roboto', 'NotoKufi', ), ), ), ], ); }).toList(), ), ), ], ); case 'line_trend_population': final List uniqueColorsLine_trend_2 = [ Color(0xFF6097CD), Color(0xFFD086A7), ]; Set selectedYears = {1970, 1980, 1990, 2000, 2010, 2020}; Map groupColorMap = {}; int colorIndex = 0; // for (String group in groupByValues) { // // groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; // groupColorMap[group] = uniqueColorsLine_trend_2[ // colorIndex % uniqueColorsLine_trend_2.length]; // colorIndex++; // } // Extract all years from the chart data List years = (chartData['response'] as List) .map( (entry) => int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0, ) .toList(); if (years.isEmpty) { // Return an empty chart if no data return LineChart( LineChartData( titlesData: FlTitlesData(show: false), lineBarsData: [], ), ); } if (years.isNotEmpty) { // Find the latest available year int latestYear = years.reduce((a, b) => a > b ? a : b); selectedYears.add(latestYear); // Include the latest year in selection } // Filter chart data to include only the selected years List filteredData = (chartData['response'] as List).where((entry) { int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0; return selectedYears.contains(year); }).toList(); Set uniqueXValues = filteredData .map( (entry) => double.parse(entry['ObsKey']['TIME_PERIOD']), ) .toSet(); var chartBarColors = chartData['chart_bar_color'] ?? {}; Map barColorsMap = {}; chartBarColors.forEach((key, value) { barColorsMap[key] = value.toString(); // Ensure values are strings }); bool hasBarColors = barColorsMap.isNotEmpty; // var chartBarColorsRaw = chartData['chart_bar_color'] ?? {}; // // Convert LinkedMap to Map // Map chartBarColrs = // Map.from(chartBarColorsRaw); // // Now map to Color // Map parsedChartBarColors = // chartBarColrs.map((key, value) { // return MapEntry(key, _hexToColor(value)); // }); // List keys = chartBarColors['key'] ?? []; List colors = chartBarColors['color'] ?? []; print('RT1keys - $keys'); print('RT1colors - $colors'); // Convert to a map for easy lookup Map parsedChartBarColors = {}; for (int i = 0; i < keys.length; i++) { parsedChartBarColors[keys[i].toString()] = _hexToColor( colors[i].toString(), ); } print('RTLinr1 - $parsedChartBarColors'); for (String group in groupByValues) { if (parsedChartBarColors.containsKey(group)) { groupColorMap[group] = parsedChartBarColors[group]!; // Directly assign as Color } else { groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; } colorIndex++; } // Generate line bars for the chart List lineBars = lineBarsData( filteredData, groupByValues, groupByKey, parsedChartBarColors, ); return Column( children: [ Text( chartData['chart_heading'] ?? '', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 14, fontWeight: FontWeight.w400), textAlign: TextAlign.center, ), SizedBox(height: 5), Text( chartData['chart_sub_heading'] ?? '', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 13, fontWeight: FontWeight.w300), textAlign: TextAlign.center, ), SizedBox(height: 10), Expanded( child: SingleChildScrollView( scrollDirection: Axis.horizontal, // Enable horizontal scrolling padding: const EdgeInsets.only(right: 40, top: 10), child: SizedBox( width: (uniqueXValues.length * 50) + 50, // Adjust width dynamically child: LineChart( LineChartData( lineTouchData: lineTouchData1(context), gridData: gridData(), titlesData: titlesData1(uniqueXValues,context), borderData: borderData(), lineBarsData: lineBars, minX: uniqueXValues.reduce((a, b) => a < b ? a : b), maxX: uniqueXValues.reduce((a, b) => a > b ? a : b), ), ), ), ), ), Padding( padding: const EdgeInsets.all(8.0), child: Wrap( spacing: 12, runSpacing: 8, children: groupByValues.map((group) { return Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 12, height: 12, color: groupColorMap[group], ), SizedBox(width: 6), Text(group, style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 14)), ], ); }).toList(), ), ), // Chart ], ); case 'line_trend_2': // final List uniqueColorsLine_trend_2 = [ // Color(0xFF6097CD), // Color(0xFFD086A7), // Color(0xFF98BCE5), // Color(0xFFA7B5C5), // Color(0xFFBED3EC), // Color(0xFFD4E3F4), // ]; Set selectedYears = {1970, 1980, 1990, 2000, 2010, 2020}; Map groupColorMap = {}; int colorIndex = 0; // for (String group in groupByValues) { // // groupColorMap[group] = uniqueColorsLine_trend_2[ // // colorIndex % uniqueColorsLine_trend_2.length]; // groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; // // groupColorMap[group] = adjustColorlineTrend(bodyColor, colorIndex); // colorIndex++; // } // Extract all years from the chart data List years = (chartData['response'] as List) .map( (entry) => int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0, ) .toList(); if (years.isEmpty) { // Return an empty chart if no data return LineChart( LineChartData( titlesData: FlTitlesData(show: false), lineBarsData: [], ), ); } if (years.isNotEmpty) { // Find the latest available year int latestYear = years.reduce((a, b) => a > b ? a : b); selectedYears.add(latestYear); // Include the latest year in selection } // Filter chart data to include only the selected years List filteredData = (chartData['response'] as List).where((entry) { int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0; return selectedYears.contains(year); }).toList(); Set uniqueXValues = filteredData .map( (entry) => double.parse(entry['ObsKey']['TIME_PERIOD']), ) .toSet(); print('uniqueXValuesLineTrend2- $uniqueXValues'); var chartBarColors = chartData['chart_bar_color'] ?? {}; Map barColorsMap = {}; chartBarColors.forEach((key, value) { barColorsMap[key] = value.toString(); // Ensure values are strings }); bool hasBarColors = barColorsMap.isNotEmpty; var chartBarColorsRaw = chartData['chart_bar_color'] ?? {}; // Convert LinkedMap to Map Map chartBarColrs = Map.from( chartBarColorsRaw, ); // Now map to Color Map parsedChartBarColors = chartBarColrs.map(( key, value, ) { return MapEntry(key, _hexToColor(value)); }); for (String group in groupByValues) { if (parsedChartBarColors.containsKey(group)) { groupColorMap[group] = parsedChartBarColors[group]!; // Directly assign as Color } else { groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; } colorIndex++; } // Generate line bars for the chart List lineBars = lineBarsData2(filteredData); return Column( children: [ Text( chartData['chart_heading'] ?? '', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 14, fontWeight: FontWeight.w400), textAlign: TextAlign.center, ), SizedBox(height: 5), Text( '', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 13, fontWeight: FontWeight.w300), textAlign: TextAlign.center, ), SizedBox(height: 10), // Chart Expanded( child: SingleChildScrollView( scrollDirection: Axis.horizontal, // Enable horizontal scrolling padding: const EdgeInsets.only(right: 40, top: 20), child: SizedBox( width: (uniqueXValues.length * 50) + 50, // Adjust width dynamically child: LineChart( LineChartData( lineTouchData: lineTouchData1(context), gridData: gridData(), titlesData: titlesData1(uniqueXValues,context), borderData: borderData(), lineBarsData: lineBars, minX: uniqueXValues.reduce((a, b) => a < b ? a : b), maxX: uniqueXValues.reduce((a, b) => a > b ? a : b), minY: 0, ), ), ), ), ), ], ); case 'bar_chart': // Extract groupBy values and their corresponding y-axis values List xAxisData = []; List yAxisData = []; List yAxisLabels = []; 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 unitMsr = entry['ObsKey']['UNIT_MEASURE']; var yValue = entry['ObsValue']['Value']; 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'); //Handled For Quarter Chart Case (sort by Year and Quarter) 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; // 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; // First, sort by TIME_PERIOD. If equal, sort by QUARTER if (timeA != timeB) { return timeA.compareTo(timeB); } else { return quarterA.compareTo(quarterB); } }); } print('chartData response sort: ${chartData['response']}'); // if (xValue != null && yValue != null) { // xAxisData.add(xValue.toString()); // yAxisData.add(double.tryParse(yValue.toString()) ?? 0.0); // } // if (xValue != null && yValue != null) { // // Check if additional_x_group is "Timeperiod" and concatenate // String xLabel = xValue.toString(); // if (xadditionalgroup == 'TIME_PERIOD' && xTimePeriod != null) { // xLabel = '$xTimePeriod-$xValue'; // Format: Label (TimePeriod) // } // // xAxisData.add(xLabel); // yAxisData.add(double.tryParse(yValue.toString()) ?? 0.0); // // yAxisLabels.add(unitMsr ?? ''); // } } for (var entry in chartData['response']) { var xValue = entry['ObsKey'][groupByKey]; var xTimePeriod = entry['ObsKey']['TIME_PERIOD']; var yValue = entry['ObsValue']['Value']; var xadditionalgroup = chartData['chart_type_json']['additional_x_group']; if (xValue != null) { String xLabel = xValue.toString(); if (xadditionalgroup == 'TIME_PERIOD' && xTimePeriod != null) { xLabel = '$xTimePeriod-$xValue'; // Format: Label (TimePeriod) } xAxisData.add(xLabel); } if (yValue != null) { yAxisData.add(double.tryParse(yValue.toString()) ?? 0.0); } } print('xAxisData $xAxisData'); print('yAxisData $yAxisData'); return Column( children: [ Text( chartData['chart_heading'] ?? '', // Chart title from data style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 14, fontWeight: FontWeight.w400), textAlign: TextAlign.center, ), SizedBox(height: 5), Text( chartData['chart_sub_heading'] ?? '', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 13, fontWeight: FontWeight.w300), textAlign: TextAlign.center, ), SizedBox(height: 10), // Chart Expanded( child: LayoutBuilder( builder: (context, constraints) { double chartWidth = xAxisData.length * (40 + 10); // Compute chart width double screenWidth = constraints.maxWidth; print('length'); print(xAxisData.length); return SingleChildScrollView( scrollDirection: Axis.horizontal, physics: xAxisData.length == 5 ? const NeverScrollableScrollPhysics() // Disable scrolling : const AlwaysScrollableScrollPhysics(), // Enable scrolling padding: const EdgeInsets.only(top: 10, left: 5), child: ConstrainedBox( constraints: BoxConstraints( minWidth: screenWidth, // Ensure it at least fills available width maxWidth: chartWidth > screenWidth ? chartWidth : screenWidth, // Prevent non-normalized constraints ), child: Center( child: SizedBox( width: xAxisData.length * (40 + 10), // Bar width + manual spacing child: BarChart( BarChartData( alignment: BarChartAlignment.spaceAround, 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: false, handleBuiltInTouches: false, touchTooltipData: BarTouchTooltipData( getTooltipColor: (group) => Colors.transparent, // fitInsideHorizontally: true, // fitInsideVertically: true, // tooltipPadding: const EdgeInsets.all(8), tooltipMargin: 1, getTooltipItem: ( group, groupIndex, rod, rodIndex, ) { // String unitMsrLabel = yAxisLabels.isNotEmpty && groupIndex < yAxisLabels.length // ? yAxisLabels[groupIndex] // : ''; // if (rod.toY == 0) return null; final originalValue = rod.toY == 0.1 ? 0.0 : rod.toY; String displayValue; if (originalValue == 0.0) { displayValue = '0'; // 🔥 clean and simple } else { displayValue = (chartConversion != null && chartConversion.isNotEmpty && number_format != null && chartConversion.isNotEmpty) ? formatNumberConversion( originalValue, chartConversion, number_format, ) : formatNumber(originalValue); } return BarTooltipItem( displayValue, TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ), color: Colors.black, fontSize: 12, fontWeight: FontWeight.w400, ), ); }, ), ), 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()}', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', )), ), ); }, reservedSize: 60, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, interval: 1, getTitlesWidget: (value, meta) { if (value.toInt() < xAxisData.length) { String title = xAxisData[value.toInt()]; String displayTitle = title.length > 10 ? title.substring(0, 10) + '...' : title; print('barChartVALUECHECKTITLE $title'); print( 'barChartVALUECHECK $displayTitle'); return Padding( padding: const EdgeInsets.only( top: 8.0, left: 75.0, ), child: SizedBox( width: 100, // Limit width to force wrapping child: Transform.rotate( // angle: -0.5, angle: -1.5, child: TooltipTheme( data: TooltipThemeData( decoration: BoxDecoration( color: Colors .black, // Change background color // color: Colors.blueGrey[800], // Change background color borderRadius: BorderRadius.circular( 8, ), // Optional: rounded corners ), textStyle: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ), color: Colors.white, ), // Change text color ), child: Tooltip( message: title, child: Text( // title, displayTitle, softWrap: true, textAlign: TextAlign.right, style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ), fontSize: 10, ), overflow: TextOverflow.ellipsis, ), ), ), ), ), ); } return Container(); }, reservedSize: 110, ), ), topTitles: AxisTitles( sideTitles: SideTitles( showTitles: false, ), // Hide top titles ), rightTitles: AxisTitles( sideTitles: SideTitles( showTitles: false, ), // Hide right titles ), ), gridData: FlGridData(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) { final value = double.tryParse(yAxisData[index].toString()) ?? 0; print('GGGGGG $value'); return BarChartGroupData( x: index, barsSpace: 10, barRods: [ BarChartRodData( toY: value == 0 ? 0.1 : value, color: bodyColor, borderRadius: BorderRadius.circular(4), width: 20, ), ], showingTooltipIndicators: [0], ); }, ), // barGroups: List.generate( // xAxisData.length, // (index) => BarChartGroupData( // x: index, // barsSpace: 10, // barRods: [ // BarChartRodData( // // toY: yAxisData[index], // toY: yAxisData[index] == 0 // ? 0.1 // : yAxisData[index], // // toY: yAxisData[index] == 0 // // ? 0.001 // // : yAxisData[ // // index], // gives a slight visible bar // // // color: yAxisData[index] == 0 // // ? Colors.grey // // : bodyColor, // color: bodyColor, // borderRadius: BorderRadius.circular(4), // width: 20, // ), // ], // showingTooltipIndicators: [0], // ), // ), ), ), ), ), ), ); }, ), ), ], ); case 'bar_chart_horizontal': // Extract groupBy values and their corresponding y-axis values List xAxisData = []; List yAxisData = []; List> sortedData = []; var chartConversion = chartData['chart_type_json']['conversion']; var number_format = chartData['chart_type_json']['number_format']; for (var entry in chartData['response']) { String? timePeriod = entry['ObsKey']['TIME_PERIOD']; if (timePeriod != null) { // Check if it contains a month (i.e., has "-" and follows "YYYY-MM" format) bool hasMonth = RegExp(r'^\d{4}-\d{2}$').hasMatch(timePeriod); if (hasMonth) { print('Yes-TIME_PERIOD: $timePeriod'); sortedData.add(entry); } else { print('No-TIME_PERIOD: $timePeriod'); } } } // Sort the data based on year and month sortedData.sort((a, b) { String timeA = a['ObsKey']['TIME_PERIOD']; String timeB = b['ObsKey']['TIME_PERIOD']; return timeA.compareTo( timeB, ); // Lexicographical sorting works for YYYY-MM }); // If no valid TIME_PERIOD with a month is found, return original response List> finalData = sortedData.isNotEmpty ? sortedData : List.from(chartData['response']); for (var entry in finalData) { var xValue = entry['ObsKey'][groupByKey]; var yValue = entry['ObsValue']['Value']; if (xValue != null && yValue != null) { xAxisData.add(xValue.toString()); yAxisData.add(double.tryParse(yValue.toString()) ?? 0.0); } } print("chartData['chart_heading']"); print('RESpns- ${chartData['chart_heading']}'); print(chartData['response']); int rotationTurns = 1; return Column( children: [ Text( chartData['chart_heading'] ?? '', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),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: 5), // Chart Flexible( child: BarChart( BarChartData( // alignment: BarChartAlignment.spaceAround, // maxY: yAxisData.isNotEmpty // ? yAxisData.reduce((a, b) => a > b ? a : b) * 1.2 // : 10, maxY: _calculateMaxY(chartData), rotationQuarterTurns: rotationTurns, barTouchData: BarTouchData( enabled: false, handleBuiltInTouches: false, touchTooltipData: BarTouchTooltipData( fitInsideHorizontally: true, fitInsideVertically: true, tooltipPadding: const EdgeInsets.all(5), tooltipMargin: 12, getTooltipColor: (group) => Colors.transparent, getTooltipItem: (group, groupIndex, rod, rodIndex) { // if (rod.toY == 0) return null; return BarTooltipItem( // formatNumber(rod.toY), (chartConversion != null && chartConversion.isNotEmpty && number_format != null) ? formatNumberConversion( rod.toY, chartConversion, number_format, ) : formatNumber(rod.toY), TextStyle( fontFamily: context.translate( 'Roboto', 'NotoKufi', ), color: Colors.black, fontSize: 12, fontWeight: FontWeight.w400, ), ); }, ), ), 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()}', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', )), ), ); }, 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: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 10), softWrap: true, maxLines: 2, ), ), ), ), ); } return Container(); }, reservedSize: 125, ), ), topTitles: AxisTitles( sideTitles: SideTitles( showTitles: false, ), // Hide top titles ), rightTitles: AxisTitles( sideTitles: SideTitles( showTitles: false, ), // Hide right titles ), ), gridData: FlGridData(show: false), // borderData: FlBorderData(show: false), borderData: FlBorderData(show: false), // groupsSpace: 5, barGroups: List.generate( xAxisData.length, (index) => BarChartGroupData( x: index, barsSpace: 20, barRods: [ BarChartRodData( toY: (yAxisData[index] == 0) ? 0.0001 : yAxisData[index], // color: Colors.blueAccent, color: uniqueColors[1], borderRadius: BorderRadius.circular(4), width: 20, ), ], showingTooltipIndicators: [0], ), ), ), ), ), ], ); case 'fl_multi_bar': double _calculateChartWidth(dynamic chartData) { int totalBars = chartData['response']?.length ?? 0; const double barWidth = 30; // Width for each bar, including spacing return totalBars * barWidth; // Calculate total chart width } var chartConversion = chartData['chart_type_json']['conversion']; var number_format = chartData['chart_type_json']['number_format']; var chartBarColors = chartData['chart_bar_color'] ?? {}; Map barColorsMap = {}; chartBarColors.forEach((key, value) { barColorsMap[key] = value.toString(); // Ensure values are strings }); bool hasBarColors = barColorsMap.isNotEmpty; var chartBarColorsRaw = chartData['chart_bar_color'] ?? {}; // Convert LinkedMap to Map Map chartBarColrs = Map.from( chartBarColorsRaw, ); // Now map to Color Map parsedChartBarColors = chartBarColrs.map(( key, value, ) { return MapEntry(key, _hexToColor(value)); }); if (kDebugMode) { print('Converted barColorsMap: $barColorsMap'); print('hasBarColors: $hasBarColors'); } print('multi_bar'); if (chartData.containsKey('chart_bar_color')) { print('bar_colors - $chartBarColors'); } if (kDebugMode) { print('chartBarColors: $chartBarColors'); print('chartBarColors Type: ${chartBarColors.runtimeType}'); print('chartBarColors keys: ${chartBarColors?.keys}'); print('chartBarColors is null? ${chartBarColors == null}'); print('chartBarColors is empty? ${chartBarColors?.isEmpty}'); } String cropKey = chartData['chart_type_json']['x_sub_group']; // 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]; String cropType = item['ObsKey'][groupByKey]; // Use a Set to avoid duplicates if (groupedCrops.containsKey(cropType)) { groupedCrops[cropType] = (groupedCrops[cropType]! + [crop]).toSet().toList(); } else { groupedCrops[cropType] = [crop]; } } print('Unique groupedCrops: $groupedCrops'); // for (var item in chartData['response']) { // String crop, cropType; // crop = item['ObsKey'][cropKey]; // cropType = item['ObsKey'][groupByKey]; // // if (groupedCrops.containsKey(cropType)) { // groupedCrops[cropType]!.add(crop); // } else { // groupedCrops[cropType] = [crop]; // } // } // // print('groupedCropsflmutli- $groupedCrops'); print('hormulti_bar'); return Center( child: Column( children: [ Text( chartData['chart_heading'] ?? '', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 14, fontWeight: FontWeight.w400), textAlign: TextAlign.center, ), SizedBox(height: 5), Text( '', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 13, fontWeight: FontWeight.w300), textAlign: TextAlign.center, ), SizedBox(height: 10), // Chart Expanded( child: SingleChildScrollView( scrollDirection: Axis.horizontal, // Enable horizontal scrolling child: SizedBox( width: _calculateChartWidth( chartData, ), // Dynamically calculate the chart width child: BarChart( BarChartData( alignment: BarChartAlignment.spaceAround, maxY: _calculateMaxY( chartData, ), // Dynamically calculate max Y // barGroups: _buildHorizontalRotateBarGroups( // chartData, groupByValues), // Build bar groups barGroups: hasBarColors ? _buildHorizontalRotateBarGroupsBarColors( chartData, groupByValues, parsedChartBarColors, ) : _buildHorizontalRotateBarGroups( chartData, groupByValues, ), titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 130, // Added space for rotated titles getTitlesWidget: (value, meta) { if (value < groupByValues.length) { String title = groupByValues.elementAt( value.toInt(), ); String displayTitle = title.length > 10 ? title.substring(0, 10) + '...' : title; return Padding( padding: const EdgeInsets.only( top: 8.0, left: 95.0, ), child: SizedBox( width: 80, // Limit width to force wrapping child: Transform.rotate( angle: -1.5, child: TooltipTheme( data: TooltipThemeData( decoration: BoxDecoration( color: Colors .black, // Change background color // color: Colors.blueGrey[800], // Change background color borderRadius: BorderRadius.circular( 8, ), // Optional: rounded corners ), textStyle: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ), color: Colors.white, ), // Change text color ), child: Tooltip( message: title, child: Text( title, softWrap: true, maxLines: 2, textAlign: TextAlign.right, style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 10), // overflow: TextOverflow.ellipsis, ), ), ), ), ), ); // return Transform.rotate( // angle: // -0.5, // Rotation in radians (~ -30 degrees) // child: Text( // title, // style: const TextStyle(fontSize: 12), // ), // ); } return const SizedBox.shrink(); }, ), ), topTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), ), rightTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), ), ), borderData: FlBorderData(show: false), barTouchData: BarTouchData( enabled: true, touchTooltipData: BarTouchTooltipData( getTooltipColor: (group) => Colors.black, 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 // } if (rod.toY == 0) { return null; // Don't show for zero values } if (groupIndex == touchedGroupIndex) { // Get the group label dynamically String groupLabel = groupByValues.elementAt( groupIndex, ); // Fetch the crop for the current group from groupedCrops String cropType = groupByValues.elementAt( groupIndex, ); print('GrpcropType: $cropType'); // String crop = groupedCrops[cropType]![rodIndex]; // Safely fetch crop with null check List? crops = groupedCrops[cropType]; String crop; if (crops != null && rodIndex < crops.length) { crop = crops[rodIndex]; } else { 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; } // print('groupedCrops1: $groupedCrops - $rodIndex'); // print( // 'Groupcrop: $crop'); double value = rod.toY; return BarTooltipItem( '$groupLabel\n$crop', TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ), color: Colors.white, fontWeight: FontWeight.bold, ), children: [ TextSpan( text: '- ${(chartConversion != null && chartConversion.isNotEmpty && number_format != null) ? formatNumberConversion(rod.toY, chartConversion, number_format) : formatNumber(rod.toY)}', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ), color: Colors.white, 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 // }); } }, ), gridData: FlGridData(show: false), ), ), ), ), ), ], ), ); case 'horizontal_rotate': String cropKey = chartData['chart_type_json'] ['y_sub_group']; // Dynamically get crop key // Group crops by CROP_TYPE Map> groupedCrops = {}; // Iterate over the chartData to group crops by CROP_TYPE for (var item in chartData['response']) { String crop, cropType; // crop = item['ObsKey']['CROP']; crop = item['ObsKey'][cropKey]; cropType = item['ObsKey'][groupByKey]; if (groupedCrops.containsKey(cropType)) { groupedCrops[cropType]!.add(crop); } else { groupedCrops[cropType] = [crop]; } } int touchedGroupIndex = -1; int rotationTurns = 1; print('Grouped Crops: $groupedCrops'); int numberOfBars = groupedCrops.length; // Number of grouped sets (bar groups) double barHeight = 10.0; // Height per individual bar double barSpacing = 1.0; // Space between bar sets double minHeight = 300.0; // Minimum chart height double maxHeight = 500.0; // Maximum chart height // Calculate total height dynamically // double chartHeight = // ((numberOfBars * barHeight) + ((numberOfBars - 1) * barSpacing)) // .clamp(minHeight, maxHeight); var chartBarColors = chartData['chart_bar_color'] ?? {}; Map barColorsMap = {}; // chartBarColors.forEach((key, value) { // barColorsMap[key] = value.toString(); // Ensure values are strings // }); // bool hasBarColors = barColorsMap.isNotEmpty; var chartBarColorsRaw = chartData['chart_bar_color'] ?? {}; // // Convert LinkedMap to Map // Map chartBarColrs = // Map.from(chartBarColorsRaw); // // // Now map to Color // Map parsedChartBarColors = // chartBarColrs.map((key, value) { // print('HorizontalLegends1 -$key'); // return MapEntry(key, _hexToColor(value)); // }); List keys = chartBarColors['key'] ?? []; List colors = chartBarColors['color'] ?? []; print('RT1keys - $keys'); print('RT1colors - $colors'); // Convert to a map for easy lookup Map parsedChartBarColors = {}; for (int i = 0; i < keys.length; i++) { parsedChartBarColors[keys[i].toString()] = _hexToColor( colors[i].toString(), ); } print('RT1 - $parsedChartBarColors'); bool hasBarColors = parsedChartBarColors.isNotEmpty; return Column( children: [ Text( chartData['chart_heading'] ?? '', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 14, fontWeight: FontWeight.w400), textAlign: TextAlign.center, ), SizedBox(height: 5), Text( chartData['chart_sub_heading'] ?? '', style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 13, fontWeight: FontWeight.w300), textAlign: TextAlign.center, ), SizedBox(height: 5), // Chart Flexible( // child: SingleChildScrollView( // scrollDirection: Axis.vertical, // child: SingleChildScrollView( // scrollDirection: Axis.horizontal, child: SizedBox( // width: 500, // width: 1000, // height: chartHeight, child: Stack( children: [ BarChart( BarChartData( // maxY: 400000, maxY: _calculateMaxY(chartData), rotationQuarterTurns: rotationTurns, // barTouchData: BarTouchData( // // enabled: true, // enabled: false, // touchTooltipData: BarTouchTooltipData( // // getTooltipColor: (group) => Colors.black12, // getTooltipColor: (group) => Colors.transparent, // tooltipHorizontalAlignment: FLHorizontalAlignment.center, // tooltipRoundedRadius: 8, // fitInsideHorizontally: // true, // Ensure it fits within the screen // fitInsideVertically: true, // tooltipPadding: const EdgeInsets.all(5), // tooltipMargin: 10, // // // 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) { // // print('Group Index: $groupIndex, Group : $group'); // // // Get the group label dynamically // String groupLabel = groupByValues.elementAt(groupIndex); // // // Fetch the crop for the current group from groupedCrops // String cropType = groupByValues.elementAt(groupIndex); // String crop = groupedCrops[cropType]![rodIndex]; // double value = rod.toY; // // String formattedValue; // if (value >= 1000000) { // formattedValue = // (value / 1000000).toStringAsFixed(1) + 'M'; // } else if (value >= 1000) { // formattedValue = // (value / 1000).toStringAsFixed(1) + 'K'; // } else { // formattedValue = value.toStringAsFixed( // 0); // for values smaller than 1000 // } // // // return BarTooltipItem( // // // '$groupLabel\n$crop', // // '$formattedValue', // // const TextStyle( // // color: Colors.black, // // fontWeight: FontWeight.w400, // // fontSize: 12, // // ), // // ); // // return BarTooltipItem( // ' $formattedValue', // This is your main tooltip text // // textAlign: TextAlign.center, // const TextStyle( // color: Colors.black, // fontWeight: FontWeight.w400, // fontSize: 12, // ), // children: [ // TextSpan( // text: '', // Add extra information here // // text: '\n$crop', // Add extra information here // // style: TextStyle( // color: Colors.grey[ // 700], // Optional: Different color for extra text // fontSize: 8, // ), // ), // ], // ); // // // 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 // // // }); // // } // // }, // ), barTouchData: BarTouchData( enabled: false, // Enable touch to show tooltip handleBuiltInTouches: false, touchTooltipData: BarTouchTooltipData( getTooltipColor: (group) => Colors .transparent, // Light background for visibility tooltipHorizontalAlignment: FLHorizontalAlignment.center, tooltipRoundedRadius: 6, // Softer roundness for tooltips fitInsideHorizontally: true, fitInsideVertically: true, // tooltipPadding: const EdgeInsets.all(5), tooltipPadding: const EdgeInsets.only( top: 8, bottom: 5, left: 5, right: 5), tooltipMargin: 20, // Reduce margin for better alignment getTooltipItem: (group, groupIndex, rod, rodIndex) { // if (rod.toY == 0) return null; // Hide for zero values String cropType = groupByValues.elementAt( groupIndex, ); String crop = groupedCrops[cropType]![rodIndex]; double value = rod.toY; // Show tooltip for zero values if (value == 0) { return BarTooltipItem( '0', // Display "0" instead of hiding textAlign: TextAlign.right, TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ), color: Colors.black, fontSize: 12, ), ); } // Format values dynamically // String formattedValue = value >= 1e6 // ? '${(value / 1e6).toStringAsFixed(1)}M' // : value >= 1e3 // ? '${(value / 1e3).toStringAsFixed(1)}K' // : value.toStringAsFixed(0); String formattedValue; if (value >= 1000000) { formattedValue = (value / 1000000).toStringAsFixed(1) + 'M'; } else if (value >= 1000) { formattedValue = (value / 1000).toStringAsFixed(1) + 'K'; } else { formattedValue = value.toStringAsFixed( 0, ); // for values smaller than 1000 } print('formattedValue00 $formattedValue'); return BarTooltipItem( formattedValue, textAlign: TextAlign.right, TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ), color: Colors.black, fontSize: 12, ), ); }, ), ), 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.center, style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),fontSize: 12), ), ); }, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: (chartData['chart_label_width'] == null || chartData['chart_label_width'] == 0) ? 150 : (double.tryParse( chartData['chart_label_width'] .toString()) ?? 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: (chartData[ 'chart_label_width'] == null || chartData[ 'chart_label_width'] == 0) ? TextAlign.right : TextAlign.center, style: TextStyle(fontFamily: context.translate( 'Roboto', 'NotoKufi', ),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), ), ), 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), barGroups: hasBarColors ? _buildHorizontalRotateBarGroupsBarColors( chartData, groupByValues, parsedChartBarColors, ) : _buildHorizontalRotateBarGroups( chartData, groupByValues, ), alignment: BarChartAlignment.spaceAround, ), ), ], ), ), ), // ), // ) const SizedBox(height: 10), // Space between chart and legend _buildChartLegend(groupedCrops, parsedChartBarColors, uniqueColors,context), ], ); // case 'd_chart_grouped_bar_horizantal': // return DChartBar( // vertical: false, // <--- Important: rotate the chart // barGroupingType: BarGroupingType.grouped, // data: const [ // { // 'id': 'Product A', // 'data': [ // {'domain': 'Jan', 'measure': 30}, // {'domain': 'Feb', 'measure': 50}, // {'domain': 'Mar', 'measure': 40}, // ], // }, // { // 'id': 'Product B', // 'data': [ // {'domain': 'Jan', 'measure': 20}, // {'domain': 'Feb', 'measure': 20}, // {'domain': 'Mar', 'measure': 35}, // ], // }, // { // 'id': 'Product C', // 'data': [ // {'domain': 'Jan', 'measure': 0}, // {'domain': 'Feb', 'measure': 25}, // {'domain': 'Mar', 'measure': 45}, // ], // }, // ], // groupList: const [ // {'id': 'Product A', 'color': Colors.orange}, // {'id': 'Product B', 'color': Colors.deepOrange}, // {'id': 'Product C', 'color': Colors.pinkAccent}, // ], // barValue: (barData, index) => '${barData['measure']}', // barLabelPosition: BarLabelPosition.outside, // domainLabelPaddingToAxisLine: 16, // measureLabelPaddingToAxisLine: 16, // groupSeparatorWidth: 14, // barLabelFontSize: 12, // ); default: return Center(child: Text('Unknown chart type')); } } // Start Of line trend chart FlTitlesData titlesData2(Set xValues) { return FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 40, getTitlesWidget: (value, meta) { // Format values as millions (M) // String formattedValue = (value / 1000000).toStringAsFixed(1) + 'M'; return Text( value.toStringAsFixed(1), style: TextStyle( color: Colors.black, fontSize: 12 ), ); }, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, interval: 1, // Ensure each year is shown only once getTitlesWidget: (value, meta) { if (xValues.contains(value)) { return Text( value.toInt().toString(), style: TextStyle(color: Colors.black, fontSize: 12), ); } else { return SizedBox.shrink(); // Hide non-relevant labels } }, ), ), topTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), // Hide top titles ), rightTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), // Hide right titles ), ); } List lineBarsData2(List filteredData) { List lineBars = []; // Create a map to store population values by year and gender Map> yearGenderMap = {}; for (var entry in filteredData) { int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'].toString()) ?? 0; String gender = entry['ObsKey']['GENDER']; double value = double.tryParse(entry['ObsValue']['Value'].toString()) ?? 0.0; if (!yearGenderMap.containsKey(year)) { yearGenderMap[year] = {'M': 0.0, 'F': 0.0}; } if (gender == 'Male' || gender == 'ذكر') { yearGenderMap[year]!['M'] = value; } else if (gender == 'Female' || gender == 'أنثى') { yearGenderMap[year]!['F'] = value; } } // Calculate the ratio (M/F) * 100 for each year List spots = []; yearGenderMap.forEach((year, genderMap) { if (genderMap['M'] != 0.0 && genderMap['F'] != 0.0) { double ratio = (genderMap['M']! / genderMap['F']!) * 100; spots.add(FlSpot(year.toDouble(), ratio)); } }); // Add a line for the ratio data lineBars.add( LineChartBarData( spots: spots, isCurved: true, // color: bodyColor?? Colors.grey, // Set color for the ratio line color: uniqueColors[1] ?? Colors.grey, barWidth: 3, isStrokeCapRound: true, belowBarData: BarAreaData( show: true, // color: bodyColor , // color: (bodyColor ?? Colors.grey).withAlpha(100), color: (uniqueColors[1] ?? Colors.grey).withAlpha(100), ), ), ); return lineBars; } /// Function to parse TIME_PERIOD into a EXACT value double parseTimePeriod(String timePeriod) { // Match formats if (RegExp(r'^\d{4}$').hasMatch(timePeriod)) { // Format: Year-only (e.g., "2020") 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 } 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, }; List parts = timePeriod.split('-'); int month = monthMap[parts[1]] ?? 1; // Default to January if unknown return double.parse('${parts[0]}.$month'); // Convert "2020-Oct" → 2020.10 } // Throw an error for unsupported formats throw FormatException("Invalid TIME_PERIOD format: $timePeriod"); } List lineBarsData( List filteredData, Set groupByValues, String groupByKey, Map chartBarColors, ) { List lineBars = []; print('LineParsedChartBarColors $chartBarColors'); print('filteredData $filteredData'); print('filteredData222 $groupByValues'); // final List uniqueColors = [ // Color(0xFF6097CD), // Color(0xFFD086A7), // Color(0xFF98BCE5), // Color(0xFFA7B5C5), // Color(0xFFBED3EC), // Color(0xFFD4E3F4), // ]; // Create a map to assign colors to each group in order Map groupColorMap = {}; int colorIndex = 0; // for (String group in groupByValues) { // groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; // // groupColorMap[group] = adjustColorlineTrend(bodyColor, colorIndex); // colorIndex++; // } for (String group in groupByValues) { if (chartBarColors.isNotEmpty && chartBarColors.containsKey(group)) { // Use predefined color from chartBarColors groupColorMap[group] = chartBarColors[group]!; } else { groupColorMap[group] = uniqueColors[colorIndex % uniqueColors.length]; } colorIndex++; } print('Group-Color Map: $groupColorMap'); // Iterate through each group and generate line data for (String group in groupByValues) { print('lineGrp1'); // List spots = filteredData // .where((entry) => entry['ObsKey'][groupByKey] == group) // .map((entry) { // print("FindTimePeriod"); // print(entry['ObsKey'][groupByKey]); // double xValue = double.parse(entry['ObsKey']['TIME_PERIOD']); // double yValue = double.parse(entry['ObsValue']['Value']); // return FlSpot(xValue, yValue); // }).toList(); // List spots = filteredData // .where((entry) => entry['ObsKey'][groupByKey] == group) // .map((entry) { // String timePeriod = entry['ObsKey']['TIME_PERIOD']; // print("FindTimePeriod: $timePeriod"); // // double xValue = parseTimePeriodToDouble(timePeriod); // Convert to double for graph plotting // double yValue = double.parse(entry['ObsValue']['Value']); // print('TimePeriod: $timePeriod, xValue: $xValue, yValue: $yValue'); // // return FlSpot(xValue, yValue); // }).toList(); List spots = filteredData .where((entry) => entry['ObsKey'][groupByKey] == group) .map((entry) { String timePeriod = entry['ObsKey']['TIME_PERIOD']; print("FindTimePeriod: $timePeriod"); // Use the timePeriod directly as a string for x-axis double xValue = parseTimePeriod(timePeriod); print('spotss $xValue'); // double yValue = double.parse(entry['ObsValue']['Value']); double yValue; var value = entry['ObsValue']['Value']; print('LineTrend $value'); if (value is int) { yValue = value.toDouble(); } else if (value is double) { yValue = value; } else if (value is String) { yValue = double.tryParse(value) ?? 0.0; // Handle invalid strings safely } else { throw Exception( "Unexpected value type: ${value.runtimeType}", ); } print('LineTrendX $xValue'); print('LineTrendY $yValue'); return FlSpot(xValue, yValue); }).toList() ..sort((a, b) => a.x.compareTo(b.x)); print('spots - $spots'); // lineChartLabel = spots; // List spots = filteredData // .where((entry) => entry['ObsKey'][groupByKey] == group) // .map((entry) { // String timePeriod = entry['ObsKey']['TIME_PERIOD']; // List parts = timePeriod.split('-'); // Split "2017-01" into ["2017", "01"] // // double year = double.parse(parts[0]); // Convert "2017" to 2017.0 // double month = double.parse(parts[1]) / 12; // Convert "01" to 1/12 // // double xValue = year + month; // E.g., "2017-06" becomes 2017.5 // double yValue = double.parse(entry['ObsValue']['Value']); // // return FlSpot(xValue, yValue); // }).toList(); print('lineGrp2'); // Add a line for this group lineBars.add( LineChartBarData( show: true, spots: spots, isCurved: true, color: groupColorMap[group], barWidth: 3, isStrokeCapRound: true, dotData: FlDotData(show: true), belowBarData: BarAreaData(show: false), ), ); } return lineBars; } // Helper functions for chart styles LineTouchData lineTouchData1(BuildContext context) { var chartConversion = chartData['chart_type_json']['conversion']; var number_format = chartData['chart_type_json']['number_format']; return LineTouchData( touchSpotThreshold: 10, touchTooltipData: LineTouchTooltipData( // tooltipBgColor: Colors.black.withOpacity(0.7), // Tooltip background tooltipRoundedRadius: 8, fitInsideHorizontally: true, // Ensure it fits within the screen fitInsideVertically: true, tooltipPadding: EdgeInsets.all(8), tooltipMargin: 16, // Adds margin to prevent clipping getTooltipColor: (spot) => Colors.black, 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, fontFamily: context.translate( 'Roboto', 'NotoKufi', ), ), // Set circle color to match bar ), TextSpan( // text: formatNumber(lineBarSpot.y), text: (chartConversion != null && chartConversion.isNotEmpty && number_format != null) ? formatNumberConversion( lineBarSpot.y, chartConversion, number_format, ) : formatNumber(lineBarSpot.y), // text: ' ${lineBarSpot.y}', // Keep the value white style: TextStyle( color: Colors.white, fontFamily: context.translate( 'Roboto', 'NotoKufi', ) ), ), ], textAlign: TextAlign.left, ); }).toList(); }, ), handleBuiltInTouches: true, ); } // // LineTouchData lineTouchData1() { // var chartConversion = chartData['chart_type_json']['conversion']; // var number_format = chartData['chart_type_json']['number_format']; // return LineTouchData( // enabled: true, // handleBuiltInTouches: true, // touchCallback: // (FlTouchEvent event, LineTouchResponse? response) { // if (response == null || response.lineBarSpots == null) { // return; // } // // if (event is FlTapUpEvent) { // // final spotIndex = response.lineBarSpots!.first.spotIndex; // // setState(() { // // if (showingTooltipOnSpots.contains(spotIndex)) { // // showingTooltipOnSpots.remove(spotIndex); // // } else { // // showingTooltipOnSpots.add(spotIndex); // // } // // }); // // } // }, // mouseCursorResolver: // (FlTouchEvent event, LineTouchResponse? response) { // if (response == null || response.lineBarSpots == null) { // return SystemMouseCursors.basic; // } // return SystemMouseCursors.click; // }, // touchSpotThreshold: 5, // touchTooltipData: LineTouchTooltipData( // tooltipRoundedRadius: 0, // getTooltipColor: (spot) => Colors.transparent, // fitInsideHorizontally: true, // Ensure it fits within the screen // fitInsideVertically: true, // tooltipPadding: EdgeInsets.all(8), // tooltipMargin: 16, // getTooltipItems: (List touchedSpots) { // return touchedSpots.map((LineBarSpot touchedSpot) { // return LineTooltipItem( // // formatNumber(touchedSpot.y), // (chartConversion != null && // chartConversion.isNotEmpty && // number_format != null) // ? formatNumberConversion(touchedSpot.y, chartConversion, number_format) // : formatNumber(touchedSpot.y), // TextStyle( // color: Colors.black87, // fontWeight: FontWeight.w400, // fontSize: 15, // ), // ); // }).toList(); // }, // ), // getTouchedSpotIndicator: ( // _, // indicators, // ) { // return indicators // .map((int index) => const TouchedSpotIndicatorData( // FlLine(color: Colors.transparent), // FlDotData(show: true), // )) // .toList(); // }, // distanceCalculator: (Offset touchPoint, Offset spotPixelCoordinates) => // (touchPoint - spotPixelCoordinates).distance, // ); // } FlGridData gridData() { return FlGridData( show: false, drawVerticalLine: true, getDrawingHorizontalLine: (value) => FlLine(color: Colors.grey, strokeWidth: 1), getDrawingVerticalLine: (value) => FlLine(color: Colors.grey, strokeWidth: 1), ); } FlTitlesData titlesData1(Set xValues,BuildContext context) { print('tileDATa1xvalueline2 - $xValues'); double findClosest(double value, Set values) { return values.reduce( (a, b) => (value - a).abs() < (value - b).abs() ? a : b, ); } return FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 40, getTitlesWidget: (value, meta) { // Format values as millions (M) // String formattedValue; String? formattedValue; print('LineTrend2val1 - $value '); // if (value % 10 == 0) { // // Check if the value is in the millions or thousands range // if (kDebugMode) { // print('LineTrend2valKmode - $value '); // } // if (value >= 1000000000000) { // formattedValue = // '${(value / 1000000000000).toStringAsFixed(0)}T'; // } else if (value >= 1000000000) { // formattedValue = '${(value / 1000000000).toStringAsFixed(0)}B'; // } else if (value >= 1000000) { // formattedValue = '${(value / 1000000).toStringAsFixed(0)}M'; // } else if (value >= 1000) { // formattedValue = '${(value / 1000).toStringAsFixed(0)}k'; // } else { // formattedValue = value.toStringAsFixed( // 0); // No decimals for values less than 1000 // } // // // Skip rendering if the formatted value is the same as the last one // if (_lastFormattedValue == formattedValue) { // return const SizedBox.shrink(); // Empty widget for duplicates // } // // Update the last formatted value for the next comparison // _lastFormattedValue = formattedValue; // // print('LINETREND_POP- $formattedValue'); // // if (int.parse(formattedValue.replaceAll(RegExp(r'[^0-9]'), '')) % 10 != 0) { // // return const SizedBox.shrink(); // // } // // // // print('formattedValueLine - $formattedValue'); // return Text( // formattedValue, // textAlign: TextAlign.center, // style: TextStyle( // fontSize: 10, // )); // // } // Ensure value is a multiple of 10k, 2M, 100B, or 1T before processing if (value >= 1000000000000) { // Trillions (T) if (value % 1000000000000 == 0) { // Only multiples of 1T formattedValue = '${(value / 1000000000000).toStringAsFixed(0)}T'; } } else if (value >= 1000000000) { // Billions (B) if (value % 100000000000 == 0) { // Only multiples of 100B formattedValue = '${(value / 1000000000).toStringAsFixed(0)}B'; } } else if (value >= 1000000) { // Millions (M) if (value % 2000000 == 0) { // Only multiples of 2M formattedValue = '${(value / 1000000).toStringAsFixed(0)}M'; } } else if (value >= 10000) { // Thousands (k) if (value % 10000 == 0) { // Only multiples of 10k formattedValue = '${(value / 1000).toStringAsFixed(0)}k'; } } print( 'Value: $value | Formatted: ${formattedValue ?? "Hidden"}', ); // Debugging if (formattedValue != null) { return Text( formattedValue, textAlign: TextAlign.center, style: TextStyle( fontSize: 10, fontFamily: context.translate( 'Roboto', 'NotoKufi', ) ), ); } return const SizedBox.shrink(); }, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, 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) { // print("Bottomtiles"); // print(value); return Padding( padding: const EdgeInsets.only(top: 5, left: 20.0), child: SizedBox( width: 40, child: Transform.rotate( // angle: 0.0, angle: -1.5, // Slight rotation to improve readability child: Text( value.toInt().toString(), textAlign: TextAlign.center, style: TextStyle( color: Colors.black, fontSize: 12, fontFamily: context.translate( 'Roboto', 'NotoKufi', ) ), ), ), ), ); } else { return SizedBox.shrink(); // Hide non-relevant labels } }, reservedSize: 40, ), ), topTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), // Hide top titles ), rightTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), // Hide right titles ), ); } FlBorderData borderData() { return FlBorderData( show: true, border: Border( bottom: BorderSide(color: Colors.black, width: 1), // Show bottom border left: BorderSide(color: Colors.black, width: 1), // Show left border top: BorderSide.none, // Hide top border right: BorderSide.none, // Hide right border ), ); } // End Of line trend chart // Define the list of unique colors // final List uniqueColors1 = [ // Color(0xFF648CBA), // Color(0xFF90B0D5), // Color(0xFF98BCE5), // Color(0xFFA7B5C5), // Color(0xFFBED3EC), // Color(0xFFD4E3F4), // ]; // // final List uniqueColors2 = [ // Color(0xFFD9BB99), // Color(0xFF87766E), // Color(0xFFC6A885), // Color(0xFFA28565), // Color(0xFFBFB29B), // Color(0xFFE8D8BB), // ]; // // final List uniqueColors = [ // Color(0xFF376C79), // Color(0xFF578D9C), // Color(0xFF7DAFBC), // Color(0xFF86C7D9), // Color(0xFF989898), // ]; Color _getColorForGroup(int index) { // Use the index to get a color from the uniqueColors list return uniqueColors[index % uniqueColors.length]; } List _generateBarGroups( BuildContext context, dynamic chartData, String groupByKey, ) { Map> groupedData = {}; print('flStackedBar3'); // Group data by `group_by` and `TIME_PERIOD`, accumulating the values for each group and year for (var entry in chartData['response']) { String groupValue = entry['ObsKey'][groupByKey]; String timePeriod = entry['ObsKey']['TIME_PERIOD']; // double value = double.tryParse(entry['ObsValue']['Value']) ?? 0.0; double value; var rawValue = entry['ObsValue']['Value']; if (rawValue is num) { value = rawValue.toDouble(); } else if (rawValue is double) { value = rawValue; } else { value = double.tryParse(rawValue.toString()) ?? 0.0; } print('flStackedBar3.1'); // print('fl_groupValue $groupValue'); // print('fl_timePeriod $timePeriod'); // print('fl_value $value'); if (!groupedData.containsKey(groupValue)) { groupedData[groupValue] = {}; } if (!groupedData[groupValue]!.containsKey(timePeriod)) { groupedData[groupValue]![timePeriod] = 0.0; } groupedData[groupValue]![timePeriod] = groupedData[groupValue]![timePeriod]! + value; } // Create BarChartGroupData for each group List barGroups = []; List timePeriods = groupedData.values.first.keys.toList(); print('timePeriods $timePeriods'); // double barWidth = calculateBarWidth(context, timePeriods.length); // For each time period, generate a BarChartGroupData for (int i = 0; i < timePeriods.length; i++) { // Intermediate variable to track the current toY value as we stack bars double currentToY = 0; List rodStackItems = []; // For each group (e.g., region), create stacked bars int groupIndex = 0; // Track group index for color assignment for (var group in groupedData.keys) { double value = groupedData[group]![timePeriods[i]] ?? 0.0; // Add stacked item with current `toY` value and the incremented one rodStackItems.add( BarChartRodStackItem( currentToY, // The current bottom of the stack currentToY + value, // The new top of the stack _getColorForGroup(groupIndex), // Color based on the index ), ); // Update currentToY after adding the value for this stack currentToY += value; groupIndex++; // Increment group index for the next group } // Create BarChartGroupData with the final stacked value BarChartGroupData barGroup = BarChartGroupData( 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 ), ], ); barGroups.add(barGroup); } return barGroups; } // Function to dynamically generate titles for the x-axis (time periods) List _generateXTitles(dynamic chartData,BuildContext context) { Set timePeriods = {}; for (var entry in chartData['response']) { timePeriods.add(entry['ObsKey']['TIME_PERIOD']); } return timePeriods.map((period) { return Text(period, style: TextStyle( fontSize: 12, fontFamily: context.translate( 'Roboto', 'NotoKufi', ) ) ); }).toList(); } // Function to dynamically set left axis titles (values) String? _lastFormattedValue; Widget _generateLeftTitles(double value, TitleMeta meta) { String formattedValue; if (value % 1000 == 0) { // Check if the value is in the millions or thousands range if (value >= 1000000) { formattedValue = '${(value / 1000000).toStringAsFixed(0)}M'; } else if (value >= 1000) { formattedValue = '${(value / 1000).toStringAsFixed(0)}k'; } else { formattedValue = value.toStringAsFixed( 0, ); // No decimals for values less than 1000 } // Skip rendering if the formatted value is the same as the last one if (_lastFormattedValue == formattedValue) { return const SizedBox.shrink(); // Empty widget for duplicates } // Update the last formatted value for the next _lastFormattedValue = formattedValue; print('formattedValue - $formattedValue'); return Text(formattedValue, style: TextStyle( fontSize: 10, )); } return const SizedBox.shrink(); // return Container(); } /// Start horizontal rotate and fl_multi_bar bar // List _buildBarGroups(dynamic chartData) { // String groupByKey = chartData['group_by']; // if (groupByKey == null || chartData['response'] == null) { // return []; // } // // // Extract unique group_by values // Set groupByValues = extractGroupByValues(chartData, groupByKey); // // // Create a map to hold grouped data // Map>> groupedData = {}; // for (var entry in chartData['response']) { // String groupValue = entry['ObsKey'][groupByKey] ?? ''; // if (groupValue.isNotEmpty) { // groupedData.putIfAbsent(groupValue, () => []).add(entry); // } // } // // // Convert grouped data into BarChartGroupData // List barGroups = []; // int groupIndex = 0; // // groupedData.forEach((key, values) { // int colorIndex = 0; // Track the color for each bar within the group // // List rods = values.map((entry) { // double yValue = double.tryParse(entry['ObsValue']['Value'] ?? '0') ?? 0; // // // Cycle through colors for each bar // final barColor = uniqueColors[colorIndex % uniqueColors.length]; // colorIndex++; // // return BarChartRodData( // fromY: 0, // toY: yValue, // color: barColor, // width: 20, // Adjust bar width for better visibility // borderRadius: BorderRadius.circular(4), // ); // }).toList(); // // barGroups.add(BarChartGroupData( // x: groupIndex, // barRods: rods, // showingTooltipIndicators: [0], // )); // groupIndex++; // }); // // return barGroups; // } double _calculateMaxY(dynamic chartData) { // Dynamically calculate the maximum Y value print('hormulti_bar2'); List response = chartData['response'] ?? []; double maxY = 0; for (var entry in response) { // double value = double.tryParse(entry['ObsValue']['Value'] ?? '0') ?? 0; double value = (entry['ObsValue']['Value'] as num?)?.toDouble() ?? 0; if (value > maxY) { maxY = value; } } // Apply different buffers based on maxY value // return maxY < 500 ? maxY * 3.5 : maxY * 1.5; return maxY * 1.5; // Add 10% buffer for better visualization } List _buildHorizontalRotateBarGroups( dynamic chartData, Set groupByValues, ) { String groupByKeyValueData; if (chartData['chart_type_json']['chart_type'] == 'bar_chart_horizontal' || chartData['chart_type_json']['chart_type'] == 'horizontal_rotate') { groupByKeyValueData = chartData['chart_type_json']['y_group'] ?? ''; } else { groupByKeyValueData = chartData['chart_type_json']['x_group'] ?? ''; } List responseData = chartData['response']; List barGroups = []; int colorIndex = 0; print('hormulti_bar1.1'); // Iterate through groupByValues and populate BarChartGroupData for (int i = 0; i < groupByValues.length; i++) { String groupValue = groupByValues.elementAt(i); // Filter data for the current group (grouping by CROP_TYPE) List groupData = responseData.where((entry) { return entry['ObsKey'][groupByKeyValueData] == groupValue; }).toList(); // Create BarChartRodData for each bar in the group List barRods = groupData.map((data) { // Ensure to retrieve and parse 'ObsValue' value (which should be a double) double value = double.tryParse(data['ObsValue']['Value'].toString()) ?? 0.0; double minBarHeight = 0.1; print('multi_bar1.1'); // final List uniqueColorsForTwo = [ // Color(0xFF648CBA), // Color(0xFF90B0D5), // ]; // Use bodyColor instead of hardcoded colors final barColor; if (chartData['dataset'] == 'general_education' || chartData['dataset'] == 'higher_education' || chartData['dataset'] == 'air_transport' || chartData['dataset'] == 'labour_force' || chartData['dataset'] == 'gdp' || chartData['dataset'] == 'hotels' || chartData['dataset'] == 'hotel_guests' || chartData['dataset'] == 'health_services' || chartData['dataset'] == 'clinics') { barColor = uniqueColorsForTwo[colorIndex % uniqueColorsForTwo.length]; colorIndex++; } else if (chartData['dataset'] == 'cropsk') { barColor = uniqueColorsForThree[colorIndex % uniqueColorsForThree.length]; colorIndex++; } else if (chartData['dataset'] == 'oil_and_gas') { barColor = uniqueColorsForFour[colorIndex % uniqueColorsForFour.length]; colorIndex++; } else { barColor = uniqueColors[colorIndex % uniqueColors.length]; colorIndex++; } return BarChartRodData( // toY: value, toY: value == 0 ? minBarHeight : value, // Use the parsed value color: barColor, // Dynamic color width: 20, // backDrawRodData: BackgroundBarChartRodData( // show: true, // toY: 400000, // color: Colors.grey.shade300, // ), ); }).toList(); // Add BarChartGroupData for the group barGroups.add( BarChartGroupData( x: i, barRods: barRods, showingTooltipIndicators: List.generate( barRods.length, (index) => index, ), ), ); } return barGroups; } List _buildHorizontalRotateBarGroupsBarColors( dynamic chartData, Set groupByValues, Map chartBarColors, ) { String groupByKeyValueData1; if (chartData['chart_type_json']['chart_type'] == 'bar_chart_horizontal' || chartData['chart_type_json']['chart_type'] == 'horizontal_rotate') { groupByKeyValueData1 = chartData['chart_type_json']['y_group'] ?? ''; } else { groupByKeyValueData1 = chartData['chart_type_json']['x_group'] ?? ''; } List responseData = chartData['response']; List barGroups = []; print('chartBarColorsRota- $chartBarColors'); // Define two shades for alternating colors List uniqueColorsForTwo = [ chartBarColors['default'] ?? Colors.blue, // Original color (chartBarColors['default'] ?? Colors.blue).withAlpha( 180, ), // Slightly modified shade ]; int colorIndex = 0; // Iterate through groupByValues and populate BarChartGroupData for (int i = 0; i < groupByValues.length; i++) { String groupValue = groupByValues.elementAt(i); // Filter data for the current group List groupData = responseData.where((entry) { return entry['ObsKey'][groupByKeyValueData1] == groupValue; }).toList(); Map genderMapping = { 'ذكر': 'Male', // Arabic for Male 'أنثى': 'Female', // Arabic for Female }; // Sort data so "Male" appears first, "Female" second groupData.sort((a, b) { String genderA = genderMapping[a['ObsKey']['GENDER']] ?? a['ObsKey']['GENDER'] ?? ""; String genderB = genderMapping[b['ObsKey']['GENDER']] ?? b['ObsKey']['GENDER'] ?? ""; return (genderA == 'Male' ? 0 : 1).compareTo(genderB == 'Male' ? 0 : 1); }); // Create BarChartRodData for each bar in the group List barRods = groupData.map((data) { double value = double.tryParse(data['ObsValue']['Value'].toString()) ?? 0.0; // Get gender-based color String gender = data['ObsKey']['GENDER'] ?? ""; // Convert Arabic gender to English if necessary String normalizedGender = genderMapping[gender] ?? gender; print('horizontalRotateGender - $gender'); // Map Arabic gender values to English equivalents // Determine color dynamically Color barColor; if (chartData['dataset'] == 'health_services') { barColor = uniqueColorsForTwo[ colorIndex % 2]; // Alternate between two colors colorIndex++; // Update index for next bar } else { // barColor = chartBarColors[gender] ?? Colors.grey; // Default gender-based color barColor = chartBarColors[normalizedGender] ?? Colors.grey; } print('GEG- $gender'); return BarChartRodData( toY: value, // Use the parsed value color: barColor, // Gender-based dynamic color width: 20, ); }).toList(); // Add BarChartGroupData for the group barGroups.add( BarChartGroupData( x: i, barRods: barRods, showingTooltipIndicators: List.generate( barRods.length, (index) => index, ), ), ); } return barGroups; } /// End horizontal rotate and fl_multi_bar bar @override Widget build(BuildContext context) { return buildChart(chartData, context); } } Widget _buildChartLegend( Map> groupedCrops, Map parsedChartBarColors, List uniqueColors,BuildContext context ) { // Extract unique crop types Set uniqueCropTypes = groupedCrops.values.expand((list) => list).toSet(); print('HorizontalLegends2 -$uniqueCropTypes'); print('HorizontalLegends -$parsedChartBarColors'); return Wrap( alignment: WrapAlignment.center, spacing: 12, runSpacing: 6, children: uniqueCropTypes.map((cropType) { // Color cropColor = parsedChartBarColors[cropType] ?? Colors.grey; // Fetch color for each crop type int index = uniqueCropTypes.toList().indexOf( cropType, ); // Get index for cycling colors Color cropColor = parsedChartBarColors.isNotEmpty && parsedChartBarColors.containsKey(cropType) ? parsedChartBarColors[cropType]! : uniqueColors[index % uniqueColors.length]; return Row( mainAxisSize: MainAxisSize.min, children: [ CircleAvatar(radius: 6, backgroundColor: cropColor), const SizedBox(width: 4), Text(cropType, style: TextStyle( fontSize: 12, fontFamily: context.translate( 'Roboto', 'NotoKufi', ) )), ], ); }).toList(), ); } 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 double padding = 16.0; // Space between bars double availableWidth = (width - 100); if (totalBars <= 0) { throw ArgumentError("TotalBars must be greater than 0"); } double barWidth = (availableWidth - (padding * (totalBars - 1))) / totalBars; print('barWidth - $barWidth'); // Ensure bar width does not exceed availableWidth if (barWidth > availableWidth) { barWidth = availableWidth; } // Optional: Add a minimum width constraint if needed double minWidth = 10.0; // Example minimum width if (barWidth < minWidth) { barWidth = minWidth; } return barWidth; } class ChartData { ChartData({this.x, required this.y, this.xDateTime}); final String? x; final double y; final DateTime? xDateTime; }