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.
		
		
		
		
		
			
		
			
				
	
	
		
			288 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Dart
		
	
			
		
		
	
	
			288 lines
		
	
	
		
			9.8 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';
 | |
| ///
 | |
| /// CustomGraph(dataPoints: sampleData, scrollDirection: Axis.horizontal,height: 200,maxY: 100, maxX:2.5,
 | |
| ///               leftLabelFormatter: (value){
 | |
| ///                 Widget buildLabel(String label) {
 | |
| ///                   return Padding(
 | |
| ///                     padding: const EdgeInsets.only(right: 8),
 | |
| ///                     child: Text(
 | |
| ///                       label,
 | |
| ///                       style: TextStyle(
 | |
| ///                           fontSize: 8.fSize, color: AppColors.textColor,
 | |
| ///                       fontFamily:
 | |
| ///                       FontUtils.getFontFamilyForLanguage(false)
 | |
| ///                       ),
 | |
| ///                       textAlign: TextAlign.right,
 | |
| ///                     ),
 | |
| ///                   );
 | |
| ///                 }
 | |
| ///                 switch (value.toInt()) {
 | |
| ///
 | |
| ///                   case 20:
 | |
| ///                     return buildLabel("Critical Low");
 | |
| ///                   case 40:
 | |
| ///                     return buildLabel("Low");
 | |
| ///                   case 60:
 | |
| ///                     return buildLabel("Normal");
 | |
| ///                   case 80:
 | |
| ///                     return buildLabel("High");
 | |
| ///                   case 100:
 | |
| ///                     return buildLabel("Critical High");
 | |
| ///                 }
 | |
| ///                 return const SizedBox.shrink();
 | |
| ///               },
 | |
| ///
 | |
| ///             ),
 | |
| class CustomGraph extends StatelessWidget {
 | |
|   final List<DataPoint> dataPoints;
 | |
|   final double? width;
 | |
|   final double height;
 | |
|   final double? maxY;
 | |
|   final double? maxX;
 | |
|   final Color spotColor;
 | |
|   final Color graphColor;
 | |
|   final Color graphShadowColor;
 | |
|   final Color graphGridColor;
 | |
|   final Color bottomLabelColor;
 | |
|   final double? bottomLabelSize;
 | |
|   final FontWeight? bottomLabelFontWeight;
 | |
| 
 | |
|   ///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 value) leftLabelFormatter;
 | |
| 
 | |
|   final Axis scrollDirection;
 | |
|   final bool showBottomTitleDates;
 | |
|   final bool isFullScreeGraph;
 | |
| 
 | |
|   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,
 | |
|   });
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     // var maxY = 0.0;
 | |
|     double interval = 20;
 | |
|     if ((maxY ?? 0) > 10 && (maxY ?? 0) <= 20) {
 | |
|       interval = 2;
 | |
|     } else if ((maxY ?? 0) > 5 && (maxY ?? 0) <= 10) {
 | |
|       interval = 1;
 | |
|     } else if ((maxY ?? 0) >= 0 && (maxY ?? 0) <= 5) {
 | |
|       interval = .4;
 | |
|     }
 | |
|     return Material(
 | |
|         color: Colors.white,
 | |
|         child: SizedBox(
 | |
|           width: width,
 | |
|           height: height,
 | |
|           child: Padding(
 | |
|             padding: const EdgeInsets.only(top: 8.0, bottom: 8),
 | |
|             child: LineChart(
 | |
|               LineChartData(
 | |
|                 minY: 0,
 | |
|                 maxY:
 | |
|                     ((maxY?.ceilToDouble() ?? 0.0) + interval).floorToDouble(),
 | |
|                 // minX: dataPoints.first.labelValue - 1,
 | |
|                 maxX: maxX,
 | |
|                 minX: -0.2,
 | |
|                 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} ',
 | |
|                             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: 77,
 | |
|                       interval: .1, // Let fl_chart handle it
 | |
|                       getTitlesWidget: (value, _) {
 | |
|                         return leftLabelFormatter(value);
 | |
|                       },
 | |
|                     ),
 | |
|                   ),
 | |
|                   bottomTitles: AxisTitles(
 | |
|                     axisNameSize: 60,
 | |
|                     sideTitles: SideTitles(
 | |
|                       showTitles: showBottomTitleDates,
 | |
|                       reservedSize: 50,
 | |
|                       getTitlesWidget: (value, _) {
 | |
|                         if ((value.toDouble() >= 0) &&
 | |
|                             (value.toDouble() < (maxX ?? dataPoints.length))) {
 | |
|                           var label = dataPoints[value.toInt()].label;
 | |
| 
 | |
|                           return buildBottomLabel(label);
 | |
|                         }
 | |
|                         return const SizedBox.shrink();
 | |
|                       },
 | |
|                       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: 20,
 | |
|                   checkToShowHorizontalLine: (value) =>
 | |
|                       value >= 0 && value <= 100,
 | |
|                   getDrawingHorizontalLine: (value) {
 | |
|                     return FlLine(
 | |
|                       color: AppColors.graphGridColor,
 | |
|                       strokeWidth: 1,
 | |
|                       dashArray: [5, 5],
 | |
|                     );
 | |
|                   },
 | |
|                 ),
 | |
|               ),
 | |
|             ),
 | |
|           ),
 | |
|         ));
 | |
|   }
 | |
| 
 | |
|   List<LineChartBarData> _buildColoredLineSegments(List<DataPoint> dataPoints) {
 | |
|     final List<FlSpot> allSpots = dataPoints.asMap().entries.map((entry) {
 | |
|       return FlSpot(entry.key.toDouble(), entry.value.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,
 | |
|   //     ),
 | |
|   //   );
 | |
|   // }
 | |
| 
 | |
|   Widget buildBottomLabel(String label) {
 | |
|     return Padding(
 | |
|       padding: const EdgeInsets.all(8.0),
 | |
|       child: Text(
 | |
|         label,
 | |
|         style: TextStyle(
 | |
|             fontSize: bottomLabelSize ?? 8.fSize, color: bottomLabelColor),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| }
 | |
| 
 | |
| final List<DataPoint> sampleData = [
 | |
|   DataPoint(
 | |
|     value: 20,
 | |
|     label: 'Jan 2024',
 | |
|   ),
 | |
|   DataPoint(
 | |
|     value: 36,
 | |
|     label: 'Feb 2024',
 | |
|   ),
 | |
|   DataPoint(
 | |
|     value: 80,
 | |
|     label: 'This result',
 | |
|   ),
 | |
| ];
 |