nhance_partner/lib/presentation/themes/charts/stackedBarChart.dart
venbaittech 10e5751367 bug fix
2025-12-24 12:48:16 +05:30

324 lines
11 KiB
Dart

// custom_stacked_bar_chart.dart
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../../core/services/api_service.dart';
import '../../../data/models/bar_data.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../providers/manager_provider.dart';
import 'appChartColors.dart';
class CustomStackedBarChart extends ConsumerStatefulWidget {
final List<String> labels;
final List<String> labelsId;
final List<dynamic> dataList;
final double maxY;
final int initialRotation;
final List<List<List<dynamic>>> productsList;
final List<List<String>>? vehicleTypeNames;
final Map<String, Color>? vehicleColorMap; // Dynamic color mapping
const CustomStackedBarChart({
super.key,
required this.labels,
required this.labelsId,
required this.dataList,
this.maxY = 100.0,
this.initialRotation = 1,
required this.productsList,
this.vehicleTypeNames,
this.vehicleColorMap,
});
@override
ConsumerState<CustomStackedBarChart> createState() =>
_CustomStackedBarChartState();
}
class _CustomStackedBarChartState extends ConsumerState<CustomStackedBarChart> {
late int rotationTurns;
dynamic managerId;
late ApiService apiService;
@override
void initState() {
super.initState();
apiService = ApiService();
rotationTurns = widget.initialRotation;
Future.microtask(() {
managerId = ref.read(managerIdProvider);
});
}
String compactNumber(num value) {
if (value >= 10000000) {
return '${(value / 10000000).toStringAsFixed(1).replaceAll('.0', '')}Cr';
} else if (value >= 1000000) {
return '${(value / 1000000).toStringAsFixed(1).replaceAll('.0', '')}M';
} else if (value >= 1000) {
return '${(value / 1000).toStringAsFixed(1).replaceAll('.0', '')}K';
} else if (value >= 1) {
// 👇 remove .0 ONLY when it's an integer
return value % 1 == 0
? value.toInt().toString()
: value.toStringAsFixed(1);
} else {
// 👇 keep decimals for small values
return value == 0 ? '0' : value.toStringAsFixed(1);
}
}
// else {
// return value.toInt().toString();
// }
@override
Widget build(BuildContext context) {
final maxY = widget.maxY <= 0 ? 100.0 : widget.maxY;
return AspectRatio(
aspectRatio: 4,
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
rotationQuarterTurns: rotationTurns,
// gridData: FlGridData(
// show: true,
// checkToShowHorizontalLine: (value) {
// final step = maxY / 5;
// if (step == 0) return false;
// return (value % step).abs() < 0.0001;
// },
// getDrawingHorizontalLine: (value) =>
// FlLine(color: Colors.grey.withOpacity(0.12), strokeWidth: 0.8),
// drawVerticalLine: false,
//
// ),
gridData: FlGridData(
show: true,
// drawVerticalLine: true,
drawVerticalLine: false,
getDrawingHorizontalLine: (value) => FlLine(
// color: AppColors.gridLinesColor.withValues(alpha: 0.2),
color: Colors.blueGrey.shade100,
// color: Colors.black,
// color: AppColors.borderColor,
strokeWidth: 0.3,
),
),
// borderData: FlBorderData(show: false),
borderData: FlBorderData(
show: true,
border: Border(
bottom: BorderSide(
color: AppColors.contentColorBlack,
width: 0.1,
),
right: BorderSide(color: AppColors.contentColorBlack, width: 0.1),
),
),
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 130,
getTitlesWidget: (value, meta) {
final i = value.toInt();
if (i < 0 || i >= widget.labels.length) {
return const SizedBox();
}
return SideTitleWidget(
meta: meta,
child: Text(
widget.labels[i],
style: GoogleFonts.poppins(
fontSize: 10,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.end,
),
);
},
),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 50,
getTitlesWidget: (value, meta) {
return Transform.rotate(
angle: -3,
child: Padding(
padding: const EdgeInsets.only(right: 3.0),
child: Text(
compactNumber(value),
// value.toInt().toString(),
textAlign: TextAlign.end,
style: GoogleFonts.inter(fontSize: 10),
),
),
);
},
),
),
leftTitles: const AxisTitles(),
topTitles: const AxisTitles(),
),
barTouchData: BarTouchData(
enabled: true,
touchTooltipData: BarTouchTooltipData(
tooltipRoundedRadius: 8,
tooltipPadding: const EdgeInsets.all(8),
tooltipMargin: 8,
fitInsideHorizontally: true,
fitInsideVertically: true,
getTooltipItem: (group, groupIndex, rod, rodIndex) {
// Determine if this is current or previous month
final isCurrentMonth = rodIndex == 0;
final monthLabel = isCurrentMonth ? 'Current' : 'Previous';
// Calculate total for this rod
final total = rod.rodStackItems.isNotEmpty
? rod.rodStackItems
.map((s) => s.toY - s.fromY)
.reduce((a, b) => a + b)
: 0.0;
// Build detailed breakdown
String breakdown = '';
if (rod.rodStackItems.isNotEmpty) {
for (int i = 0; i < rod.rodStackItems.length; i++) {
final item = rod.rodStackItems[i];
final value = item.toY - item.fromY;
if (value > 0) {
// Try to get vehicle type name if provided
String vehicleName = 'Item ${i + 1}';
if (widget.vehicleTypeNames != null &&
groupIndex < widget.vehicleTypeNames!.length &&
i < widget.vehicleTypeNames![groupIndex].length) {
vehicleName = widget.vehicleTypeNames![groupIndex][i];
}
breakdown +=
'\n$vehicleName: ${value.toStringAsFixed(0)}';
}
}
}
return BarTooltipItem(
'$monthLabel Month\nTotal: ${total.toStringAsFixed(0)}$breakdown',
textAlign: TextAlign.start,
GoogleFonts.poppins(
color: Colors.white,
fontSize: 11,
fontWeight: FontWeight.w500,
),
);
},
),
touchCallback: (event, response) async {
if (response == null || response.spot == null) return;
// ✅ ONLY handle clicks
if (event is! FlTapUpEvent) return;
final spot = response.spot!;
final int groupIndex = spot.touchedBarGroupIndex;
final int rodIndex = spot.touchedRodDataIndex;
final int stackIndex = spot.touchedStackItemIndex!;
final label =
widget.vehicleTypeNames?[groupIndex][stackIndex] ?? 'Unknown';
final isCurrentMonth = rodIndex == 0;
final monthLabel = isCurrentMonth ? 'Current' : 'Previous';
final categoryLabel = widget.labels[groupIndex];
final categoryLabelID = widget.labelsId[groupIndex];
debugPrint(
'Clicked stack → $label -> $monthLabel ->$managerId ->$categoryLabel->$categoryLabelID',
);
await apiService.generatePerformanceDashboardExcel(
'StaffProduct',
label,
categoryLabelID,
monthLabel,
managerId,
);
},
),
barGroups: widget.productsList.asMap().entries.map((entry) {
int x = entry.key;
List<List<dynamic>> rods = entry.value;
List<BarChartRodStackItem> currentStack = [];
List<BarChartRodStackItem> previousStack = [];
double cumCurrent = 0;
// double cumPrev = 25000;
double cumPrev = 0;
// Current month
for (var item in rods[0]) {
double value = (item[1] - item[0]).toDouble();
currentStack.add(
BarChartRodStackItem(
cumCurrent,
cumCurrent + value,
item[2] as Color? ?? Colors.grey,
),
);
cumCurrent += value;
}
// Previous month
for (var item in rods[1]) {
double value = (item[1] - item[0]).toDouble();
previousStack.add(
BarChartRodStackItem(
cumPrev,
cumPrev + value,
item[2] as Color? ?? Colors.grey,
),
);
cumPrev += value;
}
return BarChartGroupData(
x: x,
groupVertically: false,
barRods: [
BarChartRodData(
toY: cumCurrent,
width: 16,
rodStackItems: currentStack,
borderRadius: BorderRadius.zero,
),
BarChartRodData(
toY: cumPrev,
width: 16,
rodStackItems: previousStack,
borderRadius: BorderRadius.zero,
),
],
);
}).toList(),
maxY: maxY,
),
),
);
}
}