|
|
|
|
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<DataPoint> dataPoints;
|
|
|
|
|
final List<DataPoint>? secondaryDataPoints; // For grouped bar charts
|
|
|
|
|
final double? width;
|
|
|
|
|
final double height;
|
|
|
|
|
final double? maxY;
|
|
|
|
|
final double? maxX;
|
|
|
|
|
final double? minX;
|
|
|
|
|
Color? barColor;
|
|
|
|
|
final Color? secondaryBarColor;
|
|
|
|
|
Color? barGridColor;
|
|
|
|
|
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;
|
|
|
|
|
final BarTooltipItem Function(DataPoint)? getTooltipItem;
|
|
|
|
|
|
|
|
|
|
/// Creates the left label and provides it to the chart
|
|
|
|
|
final Widget Function(double) leftLabelFormatter;
|
|
|
|
|
final Widget Function(double, List<DataPoint>) bottomLabelFormatter;
|
|
|
|
|
|
|
|
|
|
final bool showBottomTitleDates;
|
|
|
|
|
final bool isFullScreeGraph;
|
|
|
|
|
final bool makeGraphBasedOnActualValue;
|
|
|
|
|
|
|
|
|
|
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.secondaryBarColor,
|
|
|
|
|
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,
|
|
|
|
|
this.getTooltipItem,
|
|
|
|
|
this.barColor ,
|
|
|
|
|
this.barGridColor ,
|
|
|
|
|
this.bottomLabelColor,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
barColor ??= AppColors.bgGreenColor;
|
|
|
|
|
barGridColor ??= AppColors.graphGridColor;
|
|
|
|
|
bottomLabelColor ??= AppColors.textColor;
|
|
|
|
|
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: (_)=>AppColorsContext.tooltipColor,
|
|
|
|
|
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
|
|
|
|
final dataPoint = dataPoints[groupIndex];
|
|
|
|
|
if(getTooltipItem != null) {
|
|
|
|
|
return getTooltipItem!(dataPoint);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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<BarChartGroupData> _buildBarGroups(List<DataPoint> 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>[
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|