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

429 lines
16 KiB
Dart

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<BarData> dataList;
final List<String> labels;
final List<String>? 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<CustomBarChart> createState() => _CustomBarChartState();
ConsumerState<CustomBarChart> createState() => _CustomBarChartState();
}
class _CustomBarChartState extends ConsumerState<CustomBarChart> {
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);
}
}