import 'package:easy_localization/easy_localization.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/common_models/data_points.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; /// A customizable bar chart widget using `fl_chart`. /// /// Displays a bar chart with configurable axis labels, colors, and data points. /// Useful for visualizing comparative data, categories, or grouped values. /// /// **Parameters:** /// - [dataPoints]: List of `DataPoint` objects to plot. /// - [secondaryDataPoints]: Optional list for grouped bars (e.g., comparison data). /// - [leftLabelFormatter]: Function to build left axis labels. /// - [bottomLabelFormatter]: Function to build bottom axis labels. /// - [width]: Optional width of the chart. /// - [height]: Required height of the chart. /// - [maxY], [maxX], [minX]: Axis bounds. /// - [barColor]: Color of the bars. /// - [secondaryBarColor]: Color of the secondary bars. /// - [barRadius]: Border radius for bar corners. /// - [barWidth]: Width of each bar. /// - [bottomLabelColor]: Color of bottom axis labels. /// - [bottomLabelSize]: Font size for bottom axis labels. /// - [bottomLabelFontWeight]: Font weight for bottom axis labels. /// - [leftLabelInterval]: Interval between left axis labels. /// - [leftLabelReservedSize]: Reserved space for left axis labels. /// - [showBottomTitleDates]: Whether to show bottom axis labels. /// - [isFullScreeGraph]: Whether the graph is fullscreen. /// - [makeGraphBasedOnActualValue]: Use `actualValue` for plotting. /// /// Example usage: /// ```dart /// CustomBarChart( /// dataPoints: sampleData, /// leftLabelFormatter: (value) => ..., /// bottomLabelFormatter: (value, dataPoints) => ..., /// height: 300, /// maxY: 100, /// ) class CustomBarChart extends StatelessWidget { final List dataPoints; final List? secondaryDataPoints; // For grouped bar charts final double? width; final double height; final double? maxY; final double? maxX; final double? minX; final Color barColor; final Color? secondaryBarColor; final Color barGridColor; final Color bottomLabelColor; final double? bottomLabelSize; final FontWeight? bottomLabelFontWeight; final double? leftLabelInterval; final double? leftLabelReservedSize; final double? bottomLabelReservedSize; final bool? showGridLines; final GetDrawingGridLine? getDrawingVerticalLine; final double? verticalInterval; final double? minY; final BorderRadius? barRadius; final double barWidth; /// Creates the left label and provides it to the chart final Widget Function(double) leftLabelFormatter; final Widget Function(double, List) bottomLabelFormatter; final bool showBottomTitleDates; final bool isFullScreeGraph; final bool makeGraphBasedOnActualValue; const CustomBarChart( {super.key, required this.dataPoints, this.secondaryDataPoints, required this.leftLabelFormatter, this.width, required this.height, this.maxY, this.maxX, this.showBottomTitleDates = true, this.isFullScreeGraph = false, this.barColor = AppColors.bgGreenColor, this.secondaryBarColor, this.barGridColor = AppColors.graphGridColor, this.bottomLabelColor = AppColors.textColor, this.bottomLabelFontWeight = FontWeight.w500, this.bottomLabelSize, this.leftLabelInterval, this.leftLabelReservedSize, this.bottomLabelReservedSize, this.makeGraphBasedOnActualValue = false, required this.bottomLabelFormatter, this.minX, this.showGridLines = false, this.getDrawingVerticalLine, this.verticalInterval, this.minY, this.barRadius, this.barWidth = 16}); @override Widget build(BuildContext context) { return Material( color: Colors.white, child: SizedBox( width: width, height: height, child: BarChart( BarChartData( minY: minY ?? 0, maxY: maxY, barTouchData: BarTouchData( handleBuiltInTouches: true, touchCallback: (FlTouchEvent event, BarTouchResponse? touchResponse) { // Let fl_chart handle the touch }, touchTooltipData: BarTouchTooltipData( getTooltipColor: (_)=>AppColors.tooltipColor, getTooltipItem: (group, groupIndex, rod, rodIndex) { final dataPoint = dataPoints[groupIndex]; return BarTooltipItem( '${dataPoint.actualValue} ${dataPoint.unitOfMeasurement ?? ""}\n${DateFormat('dd MMM, yyyy').format(dataPoint.time)}', TextStyle( color: Colors.black, fontSize: 12.f, fontWeight: FontWeight.w500, ), ); }, ), enabled: true, ), titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: leftLabelReservedSize ?? 80, interval: leftLabelInterval ?? .1, getTitlesWidget: (value, _) { return leftLabelFormatter(value); }, ), ), bottomTitles: AxisTitles( axisNameSize: 20, sideTitles: SideTitles( showTitles: showBottomTitleDates, reservedSize: bottomLabelReservedSize ?? 30, getTitlesWidget: (value, _) { return bottomLabelFormatter(value, dataPoints); }, interval: 1, ), ), topTitles: AxisTitles(), rightTitles: AxisTitles(), ), borderData: FlBorderData( show: true, border: const Border( bottom: BorderSide.none, left: BorderSide(color: Colors.grey, width: .5), right: BorderSide.none, top: BorderSide.none, ), ), barGroups: _buildBarGroups(dataPoints), gridData: FlGridData( show: showGridLines ?? true, drawHorizontalLine: false, verticalInterval: verticalInterval, getDrawingVerticalLine: getDrawingVerticalLine ?? (value) { return FlLine( color: barGridColor, strokeWidth: 1, dashArray: [5, 5], ); }, )), ), ), ); } /// Builds bar chart groups from data points List _buildBarGroups(List dataPoints) { return dataPoints.asMap().entries.map((entry) { final index = entry.key; final dataPoint = entry.value; double value = (makeGraphBasedOnActualValue) ? double.tryParse(dataPoint.actualValue) ?? 0.0 : dataPoint.value; final barRods = [ BarChartRodData( toY: value, color: barColor, width: barWidth, borderRadius: barRadius ?? BorderRadius.circular(6), // backDrawRodData: BackgroundBarChartRodData( // show: true, // toY: maxY, // color: Colors.grey[100], // ), ), ]; // Add secondary bar if provided (for grouped bar charts) if (secondaryDataPoints != null && index < secondaryDataPoints!.length) { final secondaryDataPoint = secondaryDataPoints![index]; double secondaryValue = (makeGraphBasedOnActualValue) ? double.tryParse(secondaryDataPoint.actualValue) ?? 0.0 : secondaryDataPoint.value; barRods.add( BarChartRodData( toY: secondaryValue, color: secondaryBarColor ?? AppColors.blueColor, width: barWidth, borderRadius: barRadius ?? BorderRadius.circular(6), ), ); } return BarChartGroupData( x: index, barRods: barRods, barsSpace: 8.w ); }).toList(); } }