import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import '../../../core/services/api_service.dart'; import '../../../data/models/bar_data.dart'; import '../../providers/manager_provider.dart'; import 'appChartColors.dart'; class CustomBarChart extends ConsumerStatefulWidget { final List dataList; final List labels; final List? shortName; final String title; final String? btnName; final double maxY; final int initialRotation; const CustomBarChart({ super.key, required this.dataList, required this.labels, this.shortName, this.btnName, this.title = "Horizontal Bar Chart", this.maxY = 20, this.initialRotation = 1, }); final shadowColor = const Color(0xFFCCCCCC); @override // State createState() => _CustomBarChartState(); ConsumerState createState() => _CustomBarChartState(); } class _CustomBarChartState extends ConsumerState { int touchedGroupIndex = -1; late int rotationTurns; dynamic managerId; late ApiService apiService; @override void initState() { super.initState(); apiService = ApiService(); rotationTurns = widget.initialRotation; Future.microtask(() { managerId = ref.read(managerIdProvider); }); } final indianFormatter = NumberFormat('#,##,##0', 'en_IN'); BarChartGroupData generateBarGroup( int x, Color color, double value1, double value2, double shadowValue, ) { return BarChartGroupData( x: x, groupVertically: false, // showingTooltipIndicators: [x], showingTooltipIndicators: const [1, 2], // barsSpace: 30, barRods: [ BarChartRodData(toY: 0, color: Colors.transparent, width: 10), BarChartRodData( toY: value1, color: const Color(0xFF34a9a8), // color: Color(0xFF0FB9B1), width: 20, borderRadius: BorderRadius.circular(0), ), // BarChartRodData(toY: 2, color: Colors.orange.shade300, width: 6), BarChartRodData( toY: value2, color: const Color(0xFFA5E4E1), // color: const Color(0xFF64748B), // color: Colors.orange.shade300, // color: Color(0xFF2F3640), width: 20, borderRadius: BorderRadius.circular(0), ), ], // showingTooltipIndicators: touchedGroupIndex == x ? [0] : [], ); } 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); } } String compactNumberToolTip(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 { return value.toInt().toString(); } } // Widget build(BuildContext context) { // return AspectRatio( // aspectRatio: 1.4, // // ); // } double getChartWidth() { const double barWidth = 40; // bar + spacing double minWidth = MediaQuery.of(context).size.width * 0.9; // minimum chart width final calculatedWidth = widget.dataList.length * barWidth; return calculatedWidth < minWidth ? minWidth : calculatedWidth; } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { return SingleChildScrollView( scrollDirection: Axis.horizontal, child: SizedBox( // width: constraints.maxWidth, // height: constraints.maxHeight, width: getChartWidth(), // 👈 IMPORTANT height: constraints.maxHeight, child: BarChart( BarChartData( // alignment: BarChartAlignment.spaceBetween, alignment: BarChartAlignment.start, // verticalAxis: Axis.horizontal, // rotationQuarterTurns: rotationTurns, barTouchData: BarTouchData( enabled: true, handleBuiltInTouches: true, touchTooltipData: BarTouchTooltipData( tooltipMargin: 20, tooltipRoundedRadius: 6, getTooltipColor: (group) { return Colors.transparent; // 👈 tooltip background color }, tooltipPadding: const EdgeInsets.symmetric( horizontal: 7, vertical: 2, ), // rotateAngle: -85, // ask fl_chart to fit tooltip inside available space fitInsideVertically: true, // if your fl_chart version supports it fitInsideHorizontally: true, // if your fl_chart version supports it getTooltipItem: ( BarChartGroupData group, int groupIndex, BarChartRodData rod, int rodIndex, ) { if (rod.toY == 0) { return null; } final textColor = rodIndex == 1 ? Color(0xFF34a9a8) : Color(0xFF020050); // final textColor = Colors.black87; // final textColor = Color(0xFF020050); // final textColor =; final textStyle = GoogleFonts.poppins( color: textColor, fontSize: 8, fontWeight: rodIndex == 1 ? FontWeight.w600 : FontWeight.w400, ); // final formattedValue = indianFormatter.format( // rod.toY.round(), // ); final formattedValue = compactNumberToolTip(rod.toY); return BarTooltipItem(formattedValue, textStyle); // return BarTooltipItem('${rod.toY.round()}', textStyle); }, ), touchCallback: (FlTouchEvent e, BarTouchResponse? r) async { // optional: change state to highlight tapped group if (r == null || r.spot == null) { setState(() => touchedGroupIndex = -1); return; } setState( () => touchedGroupIndex = r.spot!.touchedBarGroupIndex, ); final spot = r.spot!; final groupIndex = spot.touchedBarGroupIndex; final rodIndex = spot.touchedRodDataIndex; final barData = widget.dataList[groupIndex]; final name = barData.chartName; final val = barData.id; final month = rodIndex == 1 ? 'current' : 'previous'; print('name - $name'); print('val - $val'); print('month - $month'); print('managerId - $managerId'); if (e is FlTapUpEvent) { debugPrint( 'Tapped value → ${rodIndex == 1 ? barData.value1 : barData.value2} -> ${barData.id}-' ' ${rodIndex == 1 ? 'current' : 'previous'} -> ${barData.chartName}', ); if (managerId != null) { print('managerId - $managerId'); await apiService.generateChartExcel( name, val, month, managerId, ); } } }, ), borderData: FlBorderData( show: true, border: Border( bottom: BorderSide( color: AppColors.contentColorBlack, width: 0.1, ), left: BorderSide( color: AppColors.contentColorBlack, width: 0.1, ), ), ), 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, ), ), titlesData: FlTitlesData( bottomTitles: AxisTitles( axisNameWidget: widget.btnName != null ? Text(widget.btnName!) : Text(''), axisNameSize: 10, sideTitles: SideTitles( reservedSize: 30, showTitles: true, getTitlesWidget: (value, meta) { if (value == 0) { return const SizedBox.shrink(); // 👈 hide 0 } final i = value.toInt(); if (i >= widget.labels.length) return const SizedBox(); final hasShortName = widget.shortName != null && widget.shortName!.length > i && widget.shortName![i].trim().isNotEmpty; return SideTitleWidget( meta: meta, child: hasShortName ? Tooltip( message: widget.shortName![i], child: Transform.rotate( angle: -1, child: Padding( padding: const EdgeInsets.only( right: 3.0, ), child: Text( truncate(widget.labels[i], max: 5), // widget.labels[i], style: GoogleFonts.poppins( fontSize: 9, fontWeight: FontWeight.w500, color: Color(0xFF1E293B), ), textAlign: TextAlign.end, ), ), ), ) : Tooltip( message: widget.labels[i], child: Transform.rotate( angle: -1, child: Padding( padding: const EdgeInsets.only( right: 3.0, ), child: Text( truncate(widget.labels[i], max: 5), // widget.[i], style: GoogleFonts.poppins( fontSize: 9, fontWeight: FontWeight.w400, color: Color(0xFF1E293B), ), textAlign: TextAlign.end, ), ), ), ), ); }, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, // interval: 10, getTitlesWidget: (value, meta) { // print("valuevalue - $value"); final text = value.toInt().toString(); final bool isLast = value == meta.max; // if (isLast) { // return const SizedBox.shrink(); // 👈 hide last label // } // print('value: $value'); // print('text: $text'); // print('length: ${text.length}'); // print('isLast: $isLast'); return Transform.rotate( // angle: -3, angle: 0, child: Padding( padding: const EdgeInsets.only(right: 3.0), child: Text( // isLast ? '0' : value.toInt().toString(), isLast ? '0' : compactNumber(value), textAlign: TextAlign.end, style: GoogleFonts.inter( fontSize: 9, color: isLast ? Colors.white : Colors.black, fontWeight: isLast ? FontWeight.w100 : FontWeight.w300, ), ), ), ); }, reservedSize: 45, ), ), rightTitles: const AxisTitles(), topTitles: const AxisTitles(), ), barGroups: widget.dataList.asMap().entries.map((e) { final index = e.key; final data = e.value; print('generateBarGroupBAR - $data'); return generateBarGroup( index, data.color, data.value1, data.value2, data.shadowValue, ); }).toList(), groupsSpace: 12, // maxY: widget.maxY, maxY: (widget.dataList != null && widget.dataList!.isNotEmpty) ? widget.dataList! .map( (e) => e.value1 > e.value2 ? e.value1 : e.value2, ) .reduce((a, b) => a > b ? a : b) * 1.15 : 20, ), ), ), ); }, ); } String truncate(String text, {int max = 6}) { if (text.length <= max) return text; return text.substring(0, max); } }