uaestats_fe/lib/presentation/Screens/charts/widgets/chart_widget.dart
2025-04-30 16:03:01 +05:30

4170 lines
161 KiB
Dart

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<int> showingTooltipOnSpots = []; // To store indices of active tooltips
late LineChartBarData tooltipsOnBar; // Needs proper initialization
// Define color lists
final Map<String, List<Color>> 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<String, List<Color>> 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<String, List<Color>> 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<String, List<Color>> colorMapForTwo = {
"ECONOMY": [Color(0xFF648CBA), Color(0xFF90B0D5)],
"SOCIAL": [Color(0xFFD9BB99), Color(0xFF87766E)],
"ENVIRONMENT": [Color(0xFF376C79), Color(0xFF578D9C)],
};
// List<Color> get uniqueColors => colorMap[chartHeader] ?? [Colors.grey]; // Default to grey if header is unknown
// Function to get colors for both English & Arabic keys
List<Color> get uniqueColors {
final Map<String, String> translations = {
'الاقتصاد': 'ECONOMY',
'الاجتماعي': 'SOCIAL',
'البيئة': 'ENVIRONMENT',
};
String key = translations[chartHeader] ?? chartHeader;
return colorMap[key] ?? [Colors.grey];
}
List<Color> get uniqueColorsForTwo {
final Map<String, String> translations = {
'الاقتصاد': 'ECONOMY',
'الاجتماعي': 'SOCIAL',
'البيئة': 'ENVIRONMENT',
};
String key = translations[chartHeader] ?? chartHeader;
return colorMapForTwo[key] ?? [Colors.grey];
}
List<Color> get uniqueColorsForThree {
final Map<String, String> translations = {
'الاقتصاد': 'ECONOMY',
'الاجتماعي': 'SOCIAL',
'البيئة': 'ENVIRONMENT',
};
String key = translations[chartHeader] ?? chartHeader;
return colorMapForThree[key] ?? [Colors.grey];
}
List<Color> get uniqueColorsForFour {
final Map<String, String> 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<ChartData> parseStackedBarChartData(
dynamic chartData,
String groupByKey,
String groupByValue,
) {
return chartData['response']
.where((entry) => entry['ObsKey'][groupByKey] == groupByValue)
.map<ChartData>((entry) {
return ChartData(
x: entry['ObsKey']['TIME_PERIOD'],
y: double.tryParse(entry['ObsValue']['Value']) ?? 0.0,
);
}).toList();
}
List<BarChartGroupData> parseColumnChartData(dynamic chartData) {
return chartData['response'].map<BarChartGroupData>((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<PieChartSectionData> 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<PieChartSectionData>((
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<Widget> 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<Widget>((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<Widget> 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<Widget>((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<Widget> generateIndicatorsBar(
Map<String, dynamic> chartData,
List<String> groupByValues,
) {
List<Widget> 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<String> extractGroupByValues(dynamic chartData, String groupByKey) {
return (chartData['response'] as List<dynamic>).map<String>((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<String> 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<Color> 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: <StackedBarSeries<ChartData, String>>[
// for (int i = 0; i < groupByValues.length; i++)
// StackedBarSeries<ChartData, String>(
// 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<Color> 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: <CartesianSeries<ChartData, String>>[
// for (int i = 0; i < groupByValues.length; i++)
// StackedColumnSeries<ChartData, String>(
// 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<double>((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<int?> 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<int?>(
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<String> groupByValuesList = groupByValues.toList();
int chunkSize = 5;
List<List<String>> 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<String> 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<BarChartRodStackItem> rodStackItems =
rodData.rodStackItems;
List<TextSpan> 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<String> 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<Color> uniqueColorsLine_trend_2 = [
Color(0xFF6097CD),
Color(0xFFD086A7),
Color(0xFF98BCE5),
Color(0xFFA7B5C5),
Color(0xFFBED3EC),
Color(0xFFD4E3F4),
];
Map<String, Color> 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<int> years = (chartData['response'] as List<dynamic>)
.map<int>(
(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<dynamic> filteredData =
(chartData['response'] as List<dynamic>).where((entry) {
int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0;
return year >= minYear && year <= maxYear;
}).toList();
print('LnTrnd2 $filteredData');
// Set<double> uniqueXValues = filteredData
// .map<double>(
// (entry) => double.parse(entry['ObsKey']['TIME_PERIOD']))
// .toSet();
Set<String> uniqueXValues = filteredData.map<String>((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<String, int> 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<String> 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<double> 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<String, String> 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<dynamic, dynamic> to Map<String, String>
Map<String, String> chartBarColrs = Map<String, String>.from(
chartBarColorsRaw,
);
// Now map to Color
Map<String, Color> 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<LineChartBarData> 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<Color> uniqueColorsLine_trend_2 = [
Color(0xFF6097CD),
Color(0xFFD086A7),
];
Set<int> selectedYears = {1970, 1980, 1990, 2000, 2010, 2020};
Map<String, Color> 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<int> years = (chartData['response'] as List<dynamic>)
.map<int>(
(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<dynamic> filteredData =
(chartData['response'] as List<dynamic>).where((entry) {
int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0;
return selectedYears.contains(year);
}).toList();
Set<double> uniqueXValues = filteredData
.map<double>(
(entry) => double.parse(entry['ObsKey']['TIME_PERIOD']),
)
.toSet();
var chartBarColors = chartData['chart_bar_color'] ?? {};
Map<String, String> 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<dynamic, dynamic> to Map<String, String>
// Map<String, String> chartBarColrs =
// Map<String, String>.from(chartBarColorsRaw);
// // Now map to Color
// Map<String, Color> parsedChartBarColors =
// chartBarColrs.map((key, value) {
// return MapEntry(key, _hexToColor(value));
// });
//
List<dynamic> keys = chartBarColors['key'] ?? [];
List<dynamic> colors = chartBarColors['color'] ?? [];
print('RT1keys - $keys');
print('RT1colors - $colors');
// Convert to a map for easy lookup
Map<String, Color> 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<LineChartBarData> 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<Color> uniqueColorsLine_trend_2 = [
// Color(0xFF6097CD),
// Color(0xFFD086A7),
// Color(0xFF98BCE5),
// Color(0xFFA7B5C5),
// Color(0xFFBED3EC),
// Color(0xFFD4E3F4),
// ];
Set<int> selectedYears = {1970, 1980, 1990, 2000, 2010, 2020};
Map<String, Color> 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<int> years = (chartData['response'] as List<dynamic>)
.map<int>(
(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<dynamic> filteredData =
(chartData['response'] as List<dynamic>).where((entry) {
int year = int.tryParse(entry['ObsKey']['TIME_PERIOD'] ?? '0') ?? 0;
return selectedYears.contains(year);
}).toList();
Set<double> uniqueXValues = filteredData
.map<double>(
(entry) => double.parse(entry['ObsKey']['TIME_PERIOD']),
)
.toSet();
print('uniqueXValuesLineTrend2- $uniqueXValues');
var chartBarColors = chartData['chart_bar_color'] ?? {};
Map<String, String> 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<dynamic, dynamic> to Map<String, String>
Map<String, String> chartBarColrs = Map<String, String>.from(
chartBarColorsRaw,
);
// Now map to Color
Map<String, Color> 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<LineChartBarData> 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<String> xAxisData = [];
List<double> yAxisData = [];
List<String> 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<String> xAxisData = [];
List<double> yAxisData = [];
List<Map<String, dynamic>> 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<Map<String, dynamic>> 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<String, String> 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<dynamic, dynamic> to Map<String, String>
Map<String, String> chartBarColrs = Map<String, String>.from(
chartBarColorsRaw,
);
// Now map to Color
Map<String, Color> 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<String, List<String>> 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<String>? 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<String, List<String>> 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<String, String> 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<dynamic, dynamic> to Map<String, String>
// Map<String, String> chartBarColrs =
// Map<String, String>.from(chartBarColorsRaw);
//
// // Now map to Color
// Map<String, Color> parsedChartBarColors =
// chartBarColrs.map((key, value) {
// print('HorizontalLegends1 -$key');
// return MapEntry(key, _hexToColor(value));
// });
List<dynamic> keys = chartBarColors['key'] ?? [];
List<dynamic> colors = chartBarColors['color'] ?? [];
print('RT1keys - $keys');
print('RT1colors - $colors');
// Convert to a map for easy lookup
Map<String, Color> 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<double> 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<LineChartBarData> lineBarsData2(List<dynamic> filteredData) {
List<LineChartBarData> lineBars = [];
// Create a map to store population values by year and gender
Map<int, Map<String, double>> 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<FlSpot> 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<String, int> 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<String> 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<LineChartBarData> lineBarsData(
List<dynamic> filteredData,
Set<String> groupByValues,
String groupByKey,
Map<String, Color> chartBarColors,
) {
List<LineChartBarData> lineBars = [];
print('LineParsedChartBarColors $chartBarColors');
print('filteredData $filteredData');
print('filteredData222 $groupByValues');
// final List<Color> 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<String, Color> 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<FlSpot> spots = filteredData
// .where((entry) => entry['ObsKey'][groupByKey] == group)
// .map<FlSpot>((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<FlSpot> spots = filteredData
// .where((entry) => entry['ObsKey'][groupByKey] == group)
// .map<FlSpot>((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<FlSpot> spots = filteredData
.where((entry) => entry['ObsKey'][groupByKey] == group)
.map<FlSpot>((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<FlSpot> spots = filteredData
// .where((entry) => entry['ObsKey'][groupByKey] == group)
// .map<FlSpot>((entry) {
// String timePeriod = entry['ObsKey']['TIME_PERIOD'];
// List<String> 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<LineBarSpot> 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<LineBarSpot> 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<double> xValues,BuildContext context) {
print('tileDATa1xvalueline2 - $xValues');
double findClosest(double value, Set<double> 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<Color> uniqueColors1 = [
// Color(0xFF648CBA),
// Color(0xFF90B0D5),
// Color(0xFF98BCE5),
// Color(0xFFA7B5C5),
// Color(0xFFBED3EC),
// Color(0xFFD4E3F4),
// ];
//
// final List<Color> uniqueColors2 = [
// Color(0xFFD9BB99),
// Color(0xFF87766E),
// Color(0xFFC6A885),
// Color(0xFFA28565),
// Color(0xFFBFB29B),
// Color(0xFFE8D8BB),
// ];
//
// final List<Color> 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<BarChartGroupData> _generateBarGroups(
BuildContext context,
dynamic chartData,
String groupByKey,
) {
Map<String, Map<String, double>> 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<BarChartGroupData> barGroups = [];
List<String> 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<BarChartRodStackItem> 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<Widget> _generateXTitles(dynamic chartData,BuildContext context) {
Set<String> 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<BarChartGroupData> _buildBarGroups(dynamic chartData) {
// String groupByKey = chartData['group_by'];
// if (groupByKey == null || chartData['response'] == null) {
// return [];
// }
//
// // Extract unique group_by values
// Set<String> groupByValues = extractGroupByValues(chartData, groupByKey);
//
// // Create a map to hold grouped data
// Map<String, List<Map<String, dynamic>>> 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<BarChartGroupData> barGroups = [];
// int groupIndex = 0;
//
// groupedData.forEach((key, values) {
// int colorIndex = 0; // Track the color for each bar within the group
//
// List<BarChartRodData> 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<dynamic> 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<BarChartGroupData> _buildHorizontalRotateBarGroups(
dynamic chartData,
Set<String> 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<dynamic> responseData = chartData['response'];
List<BarChartGroupData> 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<dynamic> groupData = responseData.where((entry) {
return entry['ObsKey'][groupByKeyValueData] == groupValue;
}).toList();
// Create BarChartRodData for each bar in the group
List<BarChartRodData> 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<Color> 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<BarChartGroupData> _buildHorizontalRotateBarGroupsBarColors(
dynamic chartData,
Set<String> groupByValues,
Map<String, Color> 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<dynamic> responseData = chartData['response'];
List<BarChartGroupData> barGroups = [];
print('chartBarColorsRota- $chartBarColors');
// Define two shades for alternating colors
List<Color> 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<dynamic> groupData = responseData.where((entry) {
return entry['ObsKey'][groupByKeyValueData1] == groupValue;
}).toList();
Map<String, String> 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<BarChartRodData> 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<String, List<String>> groupedCrops,
Map<String, Color> parsedChartBarColors,
List<Color> uniqueColors,BuildContext context
) {
// Extract unique crop types
Set<String> 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<BarChartGroupData> 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;
}