Merge pull request 'functionality/graph' (#54) from functionality/graph into master
Reviewed-on: #54pull/61/head
						commit
						d3978de92e
					
				| @ -0,0 +1,14 @@ | ||||
| 
 | ||||
| 
 | ||||
| ///class used to provide value for the [DynamicResultChart] to plot the values | ||||
| class DataPoint { | ||||
|   ///values that is displayed on the graph and dot is plotted on this | ||||
|   final double value; | ||||
|   ///label shown on the bottom of the graph | ||||
|   String label; | ||||
| 
 | ||||
|   DataPoint( | ||||
|       {required this.value, | ||||
|         required this.label, | ||||
|       }); | ||||
| } | ||||
| @ -0,0 +1,21 @@ | ||||
| import 'dart:ui' show Color; | ||||
| 
 | ||||
| class ThresholdRange { | ||||
|   final String label; | ||||
|   final double value; | ||||
|   final Color color; | ||||
|   final Color lineColor; | ||||
|   final String? actualValue; | ||||
| 
 | ||||
|   ThresholdRange( | ||||
|       {required this.label, | ||||
|       required this.value, | ||||
|       required this.color, | ||||
|       required this.lineColor, | ||||
|       this.actualValue}); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'ThresholdRange(label: $label, value: $value, color: ${color.value.toRadixString(16)}, lineColor: ${lineColor.value.toRadixString(16)})'; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,287 @@ | ||||
| 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', | ||||
|   ), | ||||
| ]; | ||||
					Loading…
					
					
				
		Reference in New Issue