403 lines
15 KiB
Dart
403 lines
15 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 CustomBarChartHorizontal 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 CustomBarChartHorizontal({
|
|
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<CustomBarChartHorizontal> createState() => _CustomBarChartState();
|
|
ConsumerState<CustomBarChartHorizontal> createState() =>
|
|
_CustomBarChartState();
|
|
}
|
|
|
|
class _CustomBarChartState extends ConsumerState<CustomBarChartHorizontal> {
|
|
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: Colors.cyan.shade300,
|
|
width: 10,
|
|
borderRadius: const BorderRadius.only(
|
|
topLeft: Radius.circular(5),
|
|
topRight: Radius.circular(5),
|
|
),
|
|
// borderRadius: BorderRadius.circular(5),
|
|
),
|
|
// BarChartRodData(toY: 2, color: Colors.orange.shade300, width: 6),
|
|
BarChartRodData(
|
|
toY: value2,
|
|
// toY: value2,
|
|
color: const Color(0xFFA5E4E1),
|
|
// color: Colors.orange.shade300,
|
|
width: 10,
|
|
borderRadius: const BorderRadius.only(
|
|
topLeft: Radius.circular(5),
|
|
topRight: Radius.circular(5),
|
|
),
|
|
// borderRadius: BorderRadius.circular(5),
|
|
),
|
|
],
|
|
// 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 {
|
|
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 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: 6,
|
|
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
|
|
// ? Colors.cyan.shade500
|
|
// : Colors.orange.shade500;
|
|
|
|
// final textColor = Colors.black;
|
|
|
|
final textColor = rodIndex == 1
|
|
? Color(0xFF34a9a8)
|
|
: Color(0xFF020050);
|
|
final textStyle = GoogleFonts.poppins(
|
|
color: textColor,
|
|
fontSize: 8,
|
|
fontWeight: rodIndex == 1
|
|
? FontWeight.w600
|
|
: FontWeight.w400,
|
|
);
|
|
|
|
// final formattedValue = indianFormatter.format(
|
|
// rod.toY.round(),
|
|
// );
|
|
|
|
final formattedValue = compactNumber(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.gridLinesColor,
|
|
// color: AppColors.contentColorBlack,
|
|
width: 0.1,
|
|
),
|
|
right: BorderSide(
|
|
color: AppColors.gridLinesColor,
|
|
// 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: 90,
|
|
reservedSize: MediaQuery.of(context).size.width * 0.1,
|
|
showTitles: true,
|
|
getTitlesWidget: (value, meta) {
|
|
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: 0,
|
|
child: Text(
|
|
// widget.labels[i],
|
|
truncate(widget.labels[i], max: 5),
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 9,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF1E293B),
|
|
),
|
|
textAlign: TextAlign.end,
|
|
),
|
|
),
|
|
)
|
|
: Transform.rotate(
|
|
angle: 0,
|
|
child: Text(
|
|
widget.labels[i],
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 9,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF1E293B),
|
|
),
|
|
textAlign: TextAlign.end,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
|
|
rightTitles: 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,
|
|
color: Colors.white,
|
|
fontWeight: isLast
|
|
? FontWeight.w100
|
|
: FontWeight.w400,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
reservedSize: 5,
|
|
),
|
|
),
|
|
leftTitles: 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);
|
|
}
|
|
}
|