From c09170b5060b7025019f16a8ae9f79b9c1eeb5ab Mon Sep 17 00:00:00 2001 From: Syed Taha Alam Date: Tue, 12 Aug 2025 18:25:47 +0300 Subject: [PATCH] Wd: Lab result graph completed. --- lib/core/service/medical/labs_service.dart | 84 ++++++++++++++++++- .../viewModels/medical/labs_view_model.dart | 9 +- lib/uitl/translations_delegate_base.dart | 10 +++ .../medical/LabResult/lab_result_graph.dart | 70 +++++++++++----- 4 files changed, 148 insertions(+), 25 deletions(-) diff --git a/lib/core/service/medical/labs_service.dart b/lib/core/service/medical/labs_service.dart index 083aca8d..e5ddfb91 100644 --- a/lib/core/service/medical/labs_service.dart +++ b/lib/core/service/medical/labs_service.dart @@ -559,7 +559,7 @@ class LabsService extends BaseService { if (!normaHasValue) { normal = 0; - if (criticalLowHasValue && (normale != 0 || normal != -1)) { + if (criticalLowHasValue && (normal != 0 || normal != -1)) { normal = criticalLow! + step * 2; } if (lowHasValue) { normal = low! + step; @@ -720,4 +720,86 @@ class LabsService extends BaseService { results[i].resultValue = values[i]?.toStringAsFixed(2) ?? '0.00'; } } + + double transformValueInRange(double inputValue, String flag) { + // Define range boundaries + double rangeStart, rangeEnd; + + switch (flag) { + case 'CL': + rangeStart = 0.0; + rangeEnd = 19.0; + break; + case 'L': + rangeStart = 20.0; + rangeEnd = 39.0; + break; + case 'N': + rangeStart = 40.0; + rangeEnd = 59.0; + break; + case 'H': + rangeStart = 60.0; + rangeEnd = 79.0; + break; + case 'CH': + rangeStart = 80.0; + rangeEnd = 100.0; + break; + default: + throw ArgumentError('Invalid flag: $flag'); + } + + // Clamp input value to 0-100 and map it to the range bounds + final clampedValue = inputValue.clamp(0.0, 100.0); + final normalizedValue = clampedValue / 100.0; // Normalize input to 0-1 + + // Map the normalized value to the target range bounds + final transformedValue = rangeStart + (normalizedValue * (rangeEnd - rangeStart)); + + return transformedValue; + } + + + + List getThresholdValue() { + return [ + ThresholdRange( + label: 'criticalLow', + value: 0, + color: Color(0xffffffff), + lineColor: Color(0xFFe9a2a4)), + ThresholdRange( + label: 'low', + value: 20, + color: Color(0xFFf2fbf5), + lineColor: Color(0xFFeecd94)), + ThresholdRange( + label: 'normal', + value: 40, + color: Color(0xFFf2fbf5), + lineColor: Color(0xFF5dc36b)), + ThresholdRange( + label: 'high', + value: 60, + color: Color(0xffffffff), + lineColor: Color(0xFFeecd94)), + ThresholdRange( + label: 'criticalHigh', + value: 80, + color: Color(0xffffffff), + lineColor: Color(0xFFe9a2a4)), + ]; + } + + } + + + + + + + + + diff --git a/lib/core/viewModels/medical/labs_view_model.dart b/lib/core/viewModels/medical/labs_view_model.dart index 5c6ae2bb..aa493726 100644 --- a/lib/core/viewModels/medical/labs_view_model.dart +++ b/lib/core/viewModels/medical/labs_view_model.dart @@ -177,17 +177,18 @@ class LabsViewModel extends BaseViewModel { print("the sorted list is ${sortedResponse}"); var recentThree = _labsService.getMostRecentThree(sortedResponse); print("the recentThree list is ${recentThree}"); - var mappedToThresholdValue = _labsService.buildThresholdList(recentThree); - _labsService.adjustLabOrderResults(sortedResponse); + var mappedToThresholdValue = _labsService.getThresholdValue(); + // var mappedToThresholdValue = _labsService.buildThresholdList(recentThree); + // _labsService.adjustLabOrderResults(sortedResponse); print("the recentThree list is ${mappedToThresholdValue}"); threshold = mappedToThresholdValue; List months = ['Jan','Feb','Mar','April','May','Jun','July','Aug','Sep','Oct','Nov','Dec']; double counter = 1; - recentThree.forEach((element) { + recentThree.reversed.forEach((element) { try { var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!); - timeSeries.add(DataPoint( labelValue: counter,value : double.parse(element.resultValue!), label: "${months[dateTime.month]} ${dateTime.year}", date: dateTime)); + timeSeries.add(DataPoint( labelValue: counter,value : _labsService.transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""), label: "${months[dateTime.month]} ${dateTime.year}", date: dateTime)); counter++; } catch (e) {} }); diff --git a/lib/uitl/translations_delegate_base.dart b/lib/uitl/translations_delegate_base.dart index 6a8ee35c..0c2d4eaa 100644 --- a/lib/uitl/translations_delegate_base.dart +++ b/lib/uitl/translations_delegate_base.dart @@ -4750,6 +4750,16 @@ class TranslationBase { switch (label) { case "thisResult": return thisResult; + case "high": + return high; + case "criticalLow": + return criticalLow; + case "criticalHigh": + return criticalHigh; + case "low": + return low; + case "normal": + return normal; default: return ""; } diff --git a/lib/widgets/data_display/medical/LabResult/lab_result_graph.dart b/lib/widgets/data_display/medical/LabResult/lab_result_graph.dart index 96e7ef80..39139ca9 100644 --- a/lib/widgets/data_display/medical/LabResult/lab_result_graph.dart +++ b/lib/widgets/data_display/medical/LabResult/lab_result_graph.dart @@ -19,7 +19,7 @@ class DynamicResultChart extends StatelessWidget { return FlSpot(entry.key.toDouble(), entry.value.value); }).toList(); final widthPerPoint = 30.0; // Customize as needed - final chartWidth = (dataPoints.length+1) * widthPerPoint; + final chartWidth = (dataPoints.length + 1) * widthPerPoint; return Material( color: Colors.white, @@ -28,20 +28,37 @@ class DynamicResultChart extends StatelessWidget { child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: SizedBox( - width: MediaQuery.sizeOf(context).width, + width: MediaQuery.sizeOf(context).width - 77, child: Padding( - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.only(top: 8.0, bottom: 8), child: LineChart( LineChartData( minY: minY, maxY: maxY, - minX : dataPoints.first.labelValue - 1 , - maxX : dataPoints.first.labelValue + 1 , + minX: dataPoints.first.labelValue - 1, + maxX: dataPoints.first.labelValue + 1, + lineTouchData: + LineTouchData(touchTooltipData: LineTouchTooltipData( + getTooltipItems: (touchedSpots) { + if (touchedSpots.isEmpty) return []; + + // Only show tooltip for the first touched spot, hide others + return touchedSpots.map((spot) { + if (spot == touchedSpots.first) { + return LineTooltipItem( + '${spot.y}', + const TextStyle(color: Colors.white), + ); + } + return null; // hides the rest + }).toList(); + } + )), titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - reservedSize: 70, + reservedSize: 77, interval: .1, // Let fl_chart handle it getTitlesWidget: (value, _) { // print("the value is ======== ${value}"); @@ -58,7 +75,10 @@ class DynamicResultChart extends StatelessWidget { lineColor: Colors.transparent), ); - var actualValue = (matchingThreshold.actualValue != null )?"'${matchingThreshold.label} (${matchingThreshold.actualValue})'":'${matchingThreshold.label}'; + var actualValue = (matchingThreshold.actualValue != + null) + ? "${TranslationBase.of(context).getTranslation(matchingThreshold.label)} (${matchingThreshold.actualValue})" + : '${TranslationBase.of(context).getTranslation(matchingThreshold.label)}'; if (matchingThreshold.label.isNotEmpty) { return Text( actualValue, @@ -70,20 +90,26 @@ class DynamicResultChart extends StatelessWidget { ), ), bottomTitles: AxisTitles( + axisNameSize: 60, sideTitles: SideTitles( showTitles: true, + reservedSize: 50, getTitlesWidget: (value, _) { if (value.toInt() >= 0 && value.toInt() < dataPoints.length) { var label = dataPoints[value.toInt()].label; - if(dataPoints[value.toInt()].isStringResource) - { - label = TranslationBase.of(context).getTranslation(dataPoints[value.toInt()].label); - } + if (dataPoints[value.toInt()].isStringResource) { + label = TranslationBase.of(context) + .getTranslation( + dataPoints[value.toInt()].label); + } - return Text( - label, - style: const TextStyle(fontSize: 12), + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + label, + style: const TextStyle(fontSize: 12), + ), ); } return const SizedBox.shrink(); @@ -163,6 +189,7 @@ class DynamicResultChart extends StatelessWidget { final List allSpots = dataPoints.asMap().entries.map((entry) { return FlSpot(entry.key.toDouble(), entry.value.value); }).toList(); + print("the spots are $allSpots"); return [ ...segments, @@ -236,12 +263,17 @@ final List thresholdLevels = [ class DataPoint { final double value; - double labelValue; - String label; // e.g., "This Result" + double labelValue; + String label; // e.g., "This Result" final DateTime date; bool isStringResource; - DataPoint({required this.value, required this.label, required this.date, this.isStringResource = false, this.labelValue = 0.0}); + DataPoint( + {required this.value, + required this.label, + required this.date, + this.isStringResource = false, + this.labelValue = 0.0}); } class ThresholdRange { @@ -256,9 +288,7 @@ class ThresholdRange { required this.value, required this.color, required this.lineColor, - this.actualValue - }); - + this.actualValue}); @override String toString() {