You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
HMG_Patient_App_New/lib/widgets/graph/custom_graph.dart

280 lines
9.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.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 line graph widget using `fl_chart`.
///
/// Displays a line chart with configurable axis labels, colors, and data points.
/// Useful for visualizing time series or other sequential data.
///
/// **Parameters:**
/// - [dataPoints]: List of `DataPoint` objects to plot.
/// - [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.
/// - [spotColor]: Color of the touched spot marker.
/// - [graphColor]: Color of the line.
/// - [graphShadowColor]: Color of the area below the line.
/// - [graphGridColor]: Color of the grid lines.
/// - [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.
/// - [scrollDirection]: Axis direction for scrolling.
/// - [showBottomTitleDates]: Whether to show bottom axis labels.
/// - [isFullScreeGraph]: Whether the graph is fullscreen.
/// - [makeGraphBasedOnActualValue]: Use `actualValue` for plotting.
///
/// Example usage:
/// ```dart
/// CustomGraph(
/// dataPoints: sampleData,
/// leftLabelFormatter: (value) => ...,
/// bottomLabelFormatter: (value, dataPoints) => ...,
/// height: 200,
/// scrollDirection: Axis.horizontal,
/// maxY: 100,
/// maxX: 2.5,
/// )
class CustomGraph extends StatelessWidget {
final List<DataPoint> dataPoints;
final double? width;
final double height;
final double? maxY;
final double? maxX;
final double? minX;
final Color spotColor;
final Color graphColor;
final Color graphShadowColor;
final Color graphGridColor;
final Color bottomLabelColor;
final double? bottomLabelSize;
final FontWeight? bottomLabelFontWeight;
final double? leftLabelInterval;
final double? leftLabelReservedSize;
///creates the left label and provide it to the chart as it will be used by other part of the application so the label will be different for every chart
final Widget Function(double) leftLabelFormatter;
final Widget Function(double , List<DataPoint>) bottomLabelFormatter;
final Axis scrollDirection;
final bool showBottomTitleDates;
final bool isFullScreeGraph;
final bool makeGraphBasedOnActualValue;
const CustomGraph({
super.key,
required this.dataPoints,
required this.leftLabelFormatter,
this.width,
required this.scrollDirection,
required this.height,
this.maxY,
this.maxX,
this.showBottomTitleDates = true,
this.isFullScreeGraph = false,
this.spotColor = AppColors.bgGreenColor,
this.graphColor = AppColors.bgGreenColor,
this.graphShadowColor = AppColors.graphGridColor,
this.graphGridColor = AppColors.graphGridColor,
this.bottomLabelColor = AppColors.textColor,
this.bottomLabelFontWeight = FontWeight.w500,
this.bottomLabelSize,
this.leftLabelInterval,
this.leftLabelReservedSize,
this.makeGraphBasedOnActualValue = false,
required this.bottomLabelFormatter,
this.minX,
});
@override
Widget build(BuildContext context) {
// var maxY = 0.0;
double interval = 20;
return Material(
color: Colors.white,
child: SizedBox(
width: width,
height: height,
child: LineChart(
LineChartData(
minY: 0,
// maxY: ((maxY?.ceilToDouble() ?? 0.0) + interval).floorToDouble(),
maxY: maxY,
// minX: dataPoints.first.labelValue - 1,
maxX: maxX,
minX: minX ,
lineTouchData: LineTouchData(
getTouchLineEnd: (_, __) => 0,
getTouchedSpotIndicator: (barData, indicators) {
// Only show custom marker for touched spot
return indicators.map((int index) {
return TouchedSpotIndicatorData(
FlLine(color: Colors.transparent),
FlDotData(
show: true,
getDotPainter: (spot, percent, barData, idx) {
return FlDotCirclePainter(
radius: 8,
color: spotColor,
strokeWidth: 2,
strokeColor: Colors.white,
);
},
),
);
}).toList();
},
enabled: true,
touchTooltipData: LineTouchTooltipData(
getTooltipColor: (_) => Colors.white,
getTooltipItems: (touchedSpots) {
if (touchedSpots.isEmpty) return [];
// Only show tooltip for the first touched spot, hide others
return touchedSpots.map((spot) {
if (spot == touchedSpots.first) {
final dataPoint = dataPoints[spot.x.toInt()];
return LineTooltipItem(
// '${dataPoint.label} ${spot.y.toStringAsFixed(2)}',
'${dataPoint.value} - ${dataPoint.actualValue} - ${dataPoint.displayTime}',
TextStyle(
color: Colors.black,
fontSize: 12.fSize,
fontWeight: FontWeight.w500),
);
}
return null; // hides the rest
}).toList();
},
),
),
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: leftLabelReservedSize??80,
interval: leftLabelInterval ?? .1, // Let fl_chart handle it
getTitlesWidget: (value, _) {
return leftLabelFormatter(value);
},
),
),
bottomTitles: AxisTitles(
axisNameSize: 20,
sideTitles: SideTitles(
showTitles: showBottomTitleDates,
reservedSize: 20,
getTitlesWidget: (value, _) {
return bottomLabelFormatter(value, dataPoints, );
},
interval: 1, // ensures 1:1 mapping with spots
),
),
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,
),
),
lineBarsData: _buildColoredLineSegments(dataPoints),
gridData: FlGridData(
show: true,
drawVerticalLine: false,
// horizontalInterval: 40,
checkToShowHorizontalLine: (value) =>
value >= 0 && value <= 100,
getDrawingHorizontalLine: (value) {
return FlLine(
color: graphGridColor,
strokeWidth: 1,
dashArray: [5, 5],
);
},
),
),
),
));
}
List<LineChartBarData> _buildColoredLineSegments(List<DataPoint> dataPoints) {
final List<FlSpot> allSpots = dataPoints.asMap().entries.map((entry) {
double value = (makeGraphBasedOnActualValue)?double.tryParse(entry.value.actualValue)??0.0:entry.value.value;
debugPrint("the value is $value");
return FlSpot(entry.key.toDouble(), value);
}).toList();
var data = [
LineChartBarData(
spots: allSpots,
isCurved: true,
isStrokeCapRound: true,
isStrokeJoinRound: true,
barWidth: 4,
gradient: LinearGradient(
colors: [graphColor, graphColor],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: [
graphShadowColor,
Colors.transparent,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
)
];
return data;
}
// Widget buildLabel(String label) {
// return Padding(
// padding: const EdgeInsets.only(right: 8),
// child: Text(
// label,
// style: TextStyle(
// fontSize: leftLabelSize ?? 8.fSize, color: leftLabelColor),
// textAlign: TextAlign.right,
// ),
// );
// }
}
// final List<DataPoint> sampleData = [
// DataPoint(
// value: 20,
// label: 'Jan 2024',
// ),
// DataPoint(
// value: 36,
// label: 'Feb 2024',
// ),
// DataPoint(
// value: 80,
// label: 'This result',
// ),
// ];