uaestats_fe/lib/presentation/Screens/charts/widgets/chart_widget.dart
2025-02-28 09:01:52 +05:30

2740 lines
102 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:syncfusion_flutter_charts/charts.dart';
class ChartWidget extends StatelessWidget {
ChartWidget({Key? key, required this.chartData, required this.bodyColor})
: super(key: key);
final dynamic chartData;
final Color bodyColor;
String capitalizeAndSplit(String input) {
return input
.split('_')
.map((word) => word[0].toUpperCase() + word.substring(1))
.join(' ');
}
String formatNumber(double value) {
if (value >= 1e12) {
return '${(value / 1e12).toStringAsFixed(2)}T';
} else if (value >= 1e9) {
return '${(value / 1e9).toStringAsFixed(2)}B';
} else if (value >= 1e6) {
return '${(value / 1e6).toStringAsFixed(2)}M';
} else if (value >= 1e3) {
return '${(value / 1e3).toStringAsFixed(2)}K';
} else {
return value.toStringAsFixed(2); // No decimals for small numbers
}
}
String formatNumberConversion(
double value, String chartConversion, String numberFormat,) {
// Convert the input value to base unit (actual value)
double baseValue = value;
if (numberFormat == 'M') {
baseValue = value * 1e6; // Convert from Million to base value
} else if (numberFormat == 'B') {
baseValue = value * 1e9; // Convert from Billion to base value
} else if (numberFormat == 'T') {
baseValue = value * 1e12; // Convert from Trillion to base value
} else if (numberFormat == 'K') {
baseValue = value * 1e3; // Convert from Thousand to base value
}
// Convert base value to the target chartConversion format
if (chartConversion == 'T') {
return '${(baseValue / 1e12).toStringAsFixed(2)}T'; // Convert to Trillion
} else if (chartConversion == 'B') {
return '${(baseValue / 1e9).toStringAsFixed(2)}B'; // Convert to Billion
} else if (chartConversion == 'M') {
return '${(baseValue / 1e6).toStringAsFixed(2)}M'; // Convert to Million
} else if (chartConversion == 'K') {
return '${(baseValue / 1e3).toStringAsFixed(2)}K'; // Convert to Thousand
} else {
return baseValue
.toStringAsFixed(2); // Default: Show value with 2 decimals
}
}
List<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 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();
}
List<PieChartSectionData> parsePieChartData(
dynamic chartData,
double totalValue,
int? touchedIndex,
) {
debugPrint('Chart Data: ${jsonEncode(chartData)}');
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'];
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;
return PieChartSectionData(
value: value,
// color: Colors.primaries[index % Colors.primaries.length],
color: adjustColor(bodyColor, index), // Adjust alpha dynamically
title: '${percentage.toStringAsFixed(1)}%',
radius: isTouched ? 60 : 50,
// Increase size when touched
titleStyle: TextStyle(
fontSize: isTouched ? 12 : 10,
fontWeight: FontWeight.bold,
color: Colors.black,
// color: Colors.white,
),
titlePositionPercentageOffset: 1.3,
);
}).toList();
}
List<Widget> generateIndicators(dynamic chartData, String groupByValue) {
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'][groupByValue] ?? '';
String shortTitle =
title.length > 10 ? '${title.substring(0, 10)}' : title;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: adjustColor(bodyColor, index),
shape: BoxShape.circle,
),
),
SizedBox(width: 8),
TooltipTheme(
data: TooltipThemeData(
decoration: BoxDecoration(
color: Colors.blueGrey[800], // Change background color
borderRadius:
BorderRadius.circular(8), // Optional: rounded corners
),
textStyle: TextStyle(color: Colors.white), // Change text color
),
child: Tooltip(
message: title, // Full text on hover
child: ConstrainedBox(
// Constrain width to allow wrapping
constraints: BoxConstraints(maxWidth: 200),
child: Text(
title,
style: TextStyle(
fontSize: 12,
),
// softWrap: true,
// maxLines: 2,
overflow: TextOverflow.ellipsis, // Ensures text doesn't overflow
),
),
),
),
SizedBox(width: 5),
],
);
}).toList();
}
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();
}
Widget buildChart(dynamic chartData, BuildContext context) {
// print('bodyColor- $bodyColor');
print('chartDataccccccc $chartData');
// Extract group_by dynamically from the chartData
if (chartData == null || chartData['response'] == null) {
return Center(child: Text('No chart data available'));
}
print(chartData['chart_type_json']['TIME_PERIOD']);
String groupByKey = chartData['group_by'] ?? '';
print('groupByKey $groupByKey');
Set<String> groupByValues = extractGroupByValues(chartData, groupByKey);
print('groupByValues $groupByValues');
switch (chartData['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(
fontSize: 14,
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
SizedBox(height: 15),
Text(
chartData['chart_sub_heading'] ?? '',
style: TextStyle(
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),
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: 45),
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: 1.0),
child: Column( // Change Row to Column
crossAxisAlignment: CrossAxisAlignment.start,
children: generateIndicators(chartData, chartData['group_by']),
),
// child: Wrap(
// spacing: 2,
// runSpacing: 5,
// children:
// generateIndicators(chartData, chartData['group_by']),
// ),
),
),
),
),
// ),
],
);
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(
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: 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,
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(
color: groupColor,
fontSize: 14), // Circle color
),
TextSpan(
text: '$groupName - $formattedValue\n',
style:
TextStyle(color: Colors.white, fontSize: 12),
),
]);
}
return BarTooltipItem(
'',
TextStyle(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)[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.blueGrey[900],
borderRadius: BorderRadius.circular(4),
),
textStyle: TextStyle(color: Colors.white),
child: Text(
group,
// group.length > 10
// ? '${group.substring(0, 10)}...'
// : group,
// overflow: TextOverflow.ellipsis,
style: TextStyle(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];
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();
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');
// 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');
// Generate line bars for the chart
List<LineChartBarData> lineBars =
lineBarsData(filteredData, groupByValues, groupByKey);
print('LnTrnd3');
return Column(children: [
Text(
chartData['chart_heading'] ?? '',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
SizedBox(height: 5),
Text(
chartData['chart_sub_heading'] ?? '',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w300,
),
textAlign: TextAlign.center,
),
SizedBox(height: 10),
// Chart
Expanded(
child: LineChart(LineChartData(
lineTouchData: lineTouchData1(),
gridData: gridData(),
// titlesData: titlesData1(uniqueXValues),
titlesData: titlesData1(uniqueXValuesProcessed),
borderData: borderData(),
lineBarsData: lineBars,
// minX: uniqueXValues.reduce((a, b) => a < b ? a : b),
// maxX: uniqueXValues.reduce((a, b) => a > b ? a : b),
// minX: uniqueXValuesProcessed.reduce((a, b) => a < b ? a : b),
// maxX: uniqueXValuesProcessed.reduce((a, b) => a > b ? a : b),
))),
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(fontSize: 14),
),
],
);
}).toList(),
),
),
]);
case 'line_trend_population':
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];
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();
// Generate line bars for the chart
List<LineChartBarData> lineBars =
lineBarsData(filteredData, groupByValues, groupByKey);
return Column(children: [
Text(
chartData['chart_heading'] ?? '',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
SizedBox(height: 5),
Text(
chartData['chart_sub_heading'] ?? '',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w300,
),
textAlign: TextAlign.center,
),
SizedBox(height: 10),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal, // Enable horizontal scrolling
padding: const EdgeInsets.only(right: 40),
child: SizedBox(
width: (uniqueXValues.length * 50) +
50, // Adjust width dynamically
child: LineChart(LineChartData(
lineTouchData: lineTouchData1(),
gridData: gridData(),
titlesData: titlesData1(uniqueXValues),
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(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];
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');
// Generate line bars for the chart
List<LineChartBarData> lineBars = lineBarsData2(filteredData);
return Column(children: [
Text(
chartData['chart_heading'] ?? '',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
SizedBox(height: 5),
Text(
'',
style: TextStyle(
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(),
gridData: gridData(),
titlesData: titlesData1(uniqueXValues),
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');
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);
}
});
}
// 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 ?? '');
}
}
return Column(children: [
Text(
chartData['chart_heading'] ?? '', // Chart title from data
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
SizedBox(height: 5),
Text(
chartData['chart_sub_heading'] ?? '',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w300,
),
textAlign: TextAlign.center,
),
SizedBox(height: 10),
// Chart
Expanded(
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;
return BarTooltipItem(
(chartConversion != null &&
chartConversion.isNotEmpty &&
number_format != null &&
chartConversion.isNotEmpty)
? formatNumberConversion(rod.toY, chartConversion,
number_format) // If condition is true
: formatNumber(rod.toY),
const TextStyle(
color: Colors.black,
fontSize: 12,
fontWeight: FontWeight.w400,
),
);
},
),
),
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()}'),
);
},
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;
return Padding(
padding:
const EdgeInsets.only(top: 8.0, left: 55.0),
child: SizedBox(
width: 60, // Limit width to force wrapping
child: Transform.rotate(
// angle: -0.5,
angle: -1.5,
child: TooltipTheme(
data: TooltipThemeData(
decoration: BoxDecoration(
color: Colors.blueGrey[
800], // Change background color
borderRadius: BorderRadius.circular(
8), // Optional: rounded corners
),
textStyle: TextStyle(
color: Colors
.white), // Change text color
),
child: TooltipTheme(
data: TooltipThemeData(
decoration: BoxDecoration(
color: Colors.blueGrey[
800], // Change background color
borderRadius: BorderRadius.circular(
8), // Optional: rounded corners
),
textStyle: TextStyle(
color: Colors
.white), // Change text color
),
child: Tooltip(
message: title,
child: Text(
// title,
displayTitle,
softWrap: true,
textAlign: TextAlign.end,
style: TextStyle(
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) => BarChartGroupData(
x: index,
barRods: [
BarChartRodData(
toY: yAxisData[index],
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 = [];
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(
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,
rotationQuarterTurns: rotationTurns,
barTouchData: BarTouchData(
enabled: false,
handleBuiltInTouches: false,
touchTooltipData: BarTouchTooltipData(
fitInsideHorizontally: true,
fitInsideVertically: true,
tooltipPadding: const EdgeInsets.all(8),
tooltipMargin: 16,
getTooltipColor: (group) => Colors.transparent,
getTooltipItem: (group, groupIndex, rod, rodIndex) {
if (rod.toY == 0) return null;
return BarTooltipItem(
formatNumber(rod.toY),
const TextStyle(
color: Colors.black,
fontSize: 12,
fontWeight: FontWeight.w400,
),
);
},
),
),
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()}'),
);
},
reservedSize: 60,
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
if (value.toInt() < xAxisData.length) {
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Transform.rotate(
angle: -1.58,
// angle: -45 *
// (3.1415927 / 180), // Rotating by -45 degrees
alignment: Alignment.center,
child: Center(
child: SizedBox(
width: 100,
child: Text(
xAxisData[value.toInt()],
textAlign: TextAlign.right,
style: const TextStyle(
fontSize: 12,
),
softWrap: true,
maxLines: 3,
),
),
),
),
);
}
return Container();
},
reservedSize: 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: 2,
barRods: [
BarChartRodData(
toY: yAxisData[index],
// color: Colors.blueAccent,
color: bodyColor,
borderRadius: BorderRadius.circular(4),
width: 20,
),
],
showingTooltipIndicators: [0],
),
),
),
),
),
]);
case 'fl_multi_bar':
double _calculateChartWidth(dynamic chartData) {
int totalBars = chartData['response']?.length ?? 0;
const double barWidth = 30; // Width for each bar, including spacing
return totalBars * barWidth; // Calculate total chart width
}
print('multi_bar');
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(
fontSize: 14,
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
SizedBox(height: 5),
Text(
'',
style: TextStyle(
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
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 80, // 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: 30.0),
child: SizedBox(
width: 60, // Limit width to force wrapping
child: Transform.rotate(
angle: -1.5,
child: TooltipTheme(
data: TooltipThemeData(
decoration: BoxDecoration(
color: Colors.blueGrey[
800], // Change background color
borderRadius: BorderRadius.circular(
8), // Optional: rounded corners
),
textStyle: TextStyle(
color: Colors.white), // Change text color
),
child: Tooltip(
message: title,
child: Text(
displayTitle,
softWrap: true,
maxLines:2 ,
style: TextStyle(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(
touchTooltipData: BarTouchTooltipData(
tooltipHorizontalAlignment:
FLHorizontalAlignment.center,
tooltipRoundedRadius: 8,
fitInsideHorizontally:
true, // Ensure it fits within the screen
fitInsideVertically: true,
tooltipPadding: EdgeInsets.all(8),
tooltipMargin: 16,
// Only show tooltip when touched
getTooltipItem: (group, groupIndex, rod, rodIndex) {
if (rod.toY == 0 || touchedGroupIndex == -1) {
return null; // Don't show the tooltip if the value is 0 or there's no touch
}
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;
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('Cropvaluevalue: $groupLabel\n$crop $value $formattedValue');
return BarTooltipItem(
'$groupLabel\n$crop',
const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
children: [
TextSpan(
// text: ' Value: ${rod.toY}',
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
// });
}
},
),
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);
return Column(children: [
Text(
chartData['chart_heading'] ?? '',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
SizedBox(height: 5),
Text(
chartData['chart_sub_heading'] ?? '',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w300,
),
textAlign: TextAlign.center,
),
SizedBox(height: 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,
rotationQuarterTurns: rotationTurns,
barTouchData: BarTouchData(
// enabled: true,
enabled: false,
touchTooltipData: BarTouchTooltipData(
getTooltipColor: (group) => Colors.transparent,
tooltipHorizontalAlignment:
FLHorizontalAlignment.center,
tooltipRoundedRadius: 8,
fitInsideHorizontally:
true, // Ensure it fits within the screen
fitInsideVertically: true,
tooltipPadding: EdgeInsets.all(8),
tooltipMargin: 16,
// Only show tooltip when touched
getTooltipItem: (group, groupIndex, rod, rodIndex) {
// if (rod.toY == 0 || touchedGroupIndex == -1) {
// return null; // Don't show the tooltip if the value is 0 or there's no touch
// }
// Hide tooltips for 0 values
if (rod.toY == 0)
return null; // Hide for zero values
// if (groupIndex == touchedGroupIndex) {
// 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(
'$formattedValue',
const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w400,
fontSize: 12,
),
);
// return BarTooltipItem(
// '$groupLabel\n$crop',
// const TextStyle(
// color: Colors.white,
// fontWeight: FontWeight.bold,
// ),
// children: [
// TextSpan(
// text: ' Value: $formattedValue',
// style: const TextStyle(
// color: Colors.yellow,
// fontWeight: FontWeight.w500,
// ),
// ),
// ],
// );
// }
// return null;
},
),
// touchCallback: (event, response) {
// if (event.isInterestedForInteractions &&
// response != null &&
// response.spot != null) {
// // setState(() {
// touchedGroupIndex =
// response.spot!.touchedBarGroupIndex;
// // });
// } else {
// // setState(() {
// touchedGroupIndex = -1; // Reset if no interaction
// // });
// }
// },
),
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(
// showTitles: false,
reservedSize: 40,
interval: 100000,
getTitlesWidget: (value, meta) {
return SizedBox(
width: 50,
child: Text(
value.toInt().toString(),
textAlign: TextAlign.justify,
style: const TextStyle(
fontSize: 12,
),
),
);
},
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 150, // Added space for rotated titles
getTitlesWidget: (value, meta) {
if (value < groupByValues.length) {
String title =
groupByValues.elementAt(value.toInt());
return Transform.rotate(
angle:
-1.58, // Rotation in radians (~ -30 degrees)
child: Center(
child: SizedBox(
width: 120,
child: Text(
title,
textAlign: TextAlign.end,
style: const TextStyle(fontSize: 12),
softWrap: true,
maxLines: 2,
),
),
),
);
}
return const SizedBox.shrink();
},
),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false)),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false)),
),
borderData: FlBorderData(
show: true,
border: const Border(
// left: BorderSide(color: Colors.grey),
bottom: BorderSide(color: Colors.white),
),
),
gridData: FlGridData(
show: false,
drawVerticalLine: true,
verticalInterval: 1,
horizontalInterval: 100000,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.withOpacity(0.5),
strokeWidth: 1,
);
},
getDrawingVerticalLine: (value) {
return FlLine(
color: Colors.grey.withOpacity(0.5),
strokeWidth: 1,
);
},
),
barGroups: _buildHorizontalRotateBarGroups(
chartData, groupByValues),
alignment: BarChartAlignment.spaceAround,
),
),
])),
// ),
// )
),
]);
default:
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: Colors.blue, // Set color for the ratio line
barWidth: 3,
isStrokeCapRound: true,
belowBarData: BarAreaData(show: true),
),
);
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) {
List<LineChartBarData> lineBars = [];
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];
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');
// double yValue = double.parse(entry['ObsValue']['Value']);
double yValue;
var value = entry['ObsValue']['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}");
}
return FlSpot(xValue, yValue);
}).toList();
print('spots - $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(
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() {
return LineTouchData(
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
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), // Set circle color to match bar
),
TextSpan(
text: formatNumber(lineBarSpot.y),
// text: ' ${lineBarSpot.y}', // Keep the value white
style: const TextStyle(color: Colors.white),
),
],
);
}).toList();
},
),
handleBuiltInTouches: true,
);
}
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) {
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;
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('formattedValueLine - $formattedValue');
return Text(formattedValue, textAlign: TextAlign.center,
style: TextStyle(
fontSize: 10,
));
}
return const SizedBox.shrink();
// return Text(
// 'LINETRENTD2',
// style: TextStyle(color: Colors.black, fontSize: 12),
// );
},
),
),
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: 1.1),
child: Transform.rotate(
angle: 0.0,
// angle: -0.5, // Slight rotation to improve readability
child: 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
),
);
}
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> uniqueColors = [
Color(0xFF648CBA),
Color(0xFF90B0D5),
Color(0xFF98BCE5),
Color(0xFFA7B5C5),
Color(0xFFBED3EC),
Color(0xFFD4E3F4),
];
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) {
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),
);
}).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 comparison
_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;
}
}
return maxY * 1.1; // Add 10% buffer for better visualization
}
List<BarChartGroupData> _buildHorizontalRotateBarGroups(
dynamic chartData, Set<String> groupByValues) {
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'][chartData['group_by']] == 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;
print('multi_bar1.1');
final List<Color> uniqueColorsForTwo = [
Color(0xFF648CBA),
Color(0xFF90B0D5),
];
final barColor;
if (chartData['dataset'] == 'general_education' ||
chartData['dataset'] == 'higher_education' ||
chartData['dataset'] == 'air_transport' ||
chartData['dataset'] == 'labour_force' ||
chartData['dataset'] == 'gdp' ||
chartData['dataset'] == 'clinics') {
barColor = uniqueColorsForTwo[colorIndex % uniqueColorsForTwo.length];
colorIndex++;
} else {
barColor = uniqueColors[colorIndex % uniqueColors.length];
colorIndex++;
}
return BarChartRodData(
toY: 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;
}
/// End horizontal rotate and fl_multi_bar bar
@override
Widget build(BuildContext context) {
return buildChart(chartData, context);
}
}
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;
}