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
		
	
| 
											1 month ago
										 | 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', | ||
|  |   ), | ||
|  | ]; |