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.
diplomatic-quarter/lib/widgets/data_display/medical/LabResult/lab_result_graph.dart

314 lines
10 KiB
Dart

import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart';
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:intl/intl.dart';
class DynamicResultChart extends StatelessWidget {
final List<DataPoint> dataPoints;
final List<ThresholdRange> thresholds;
final double? width;
final double height;
final double? maxX;
final Axis scrollDirection;
final bool isFullScreenGraph;
const DynamicResultChart(
{super.key, required this.dataPoints, required this.thresholds,required this.width,required this.scrollDirection, required this.height, this.maxX, this.isFullScreenGraph = false});
@override
Widget build(BuildContext context) {
var minY = 0.0;
var maxY = 0.0;
if(thresholds.isNotEmpty) {
print("the thresholds are $thresholds");
minY = thresholds.first.value;
maxY = thresholds.last.value + 1;
}else{
return SizedBox.shrink();
}
final spots = dataPoints.asMap().entries.map((entry) {
return FlSpot(entry.key.toDouble(), entry.value.value);
}).toList();
final widthPerPoint = 30.0; // Customize as needed
final chartWidth = (dataPoints.length + 1) * widthPerPoint;
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: minY,
maxY: maxY,
minX: dataPoints.first.labelValue-1,
maxX: maxX,
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) {
final dataPoint = dataPoints[spot.x.toInt()];
return LineTooltipItem(
'${dataPoint.label} ${spot.y.toStringAsFixed(2)}',
const TextStyle(color: Colors.white),
);
}
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, _) {
// print("the value is ======== ${value}");
// Compare with 2-decimal precision to avoid close duplicates
final matchingThreshold = thresholds.firstWhere(
(t) =>
t.value.toStringAsFixed(1) ==
value.toStringAsFixed(1),
orElse: () => ThresholdRange(
label: '',
value: 0,
color: Colors.transparent,
lineColor: Colors.transparent),
);
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,
style: const TextStyle(fontSize: 10),
);
}
return const SizedBox.shrink();
},
),
),
bottomTitles: AxisTitles(
axisNameSize: 60,
sideTitles: SideTitles(
showTitles: !isFullScreenGraph,
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);
}
return Padding(
padding: EdgeInsetsDirectional.only(top: 8.0,),
child: Text(
label,
style: const TextStyle(fontSize: 12),
),
);
}
return const SizedBox.shrink();
},
interval: 1, // ensures 1:1 mapping with spots
),
),
topTitles: AxisTitles(),
rightTitles: AxisTitles(),
),
borderData: FlBorderData(show: false),
lineBarsData:
_buildColoredLineSegments(dataPoints, thresholds),
gridData: FlGridData(show: false),
extraLinesData: ExtraLinesData(
horizontalLines: thresholds.map((t) {
return HorizontalLine(
y: t.value,
color: t.lineColor,
strokeWidth: 2,
// label: HorizontalLineLabel(
// show: true,
// alignment: Alignment.centerLeft,
// style: const TextStyle(fontSize: 10),
// labelResolver: (_) => '${t.label} (${t.value.toStringAsFixed(2)})',
// ),
);
}).toList(),
),
rangeAnnotations: RangeAnnotations(
horizontalRangeAnnotations: _buildRangeShades(thresholds),
),
),
),
),
));
}
List<LineChartBarData> _buildColoredLineSegments(
List<DataPoint> dataPoints, List<ThresholdRange> thresholds) {
List<LineChartBarData> segments = [];
Color getColor(double value) {
for (int i = thresholds.length - 1; i >= 0; i--) {
if (value >= thresholds[i].value) {
return thresholds[i].lineColor;
}
}
return Colors.grey;
}
for (int i = 0; i < dataPoints.length - 1; i++) {
final dp1 = dataPoints[i];
final dp2 = dataPoints[i + 1];
final spot1 = FlSpot(i.toDouble(), dp1.value);
final spot2 = FlSpot((i + 1).toDouble(), dp2.value);
final color1 = getColor(dp1.value);
final color2 = getColor(dp2.value);
segments.add(LineChartBarData(
spots: [spot1, spot2],
isCurved: false,
barWidth: 2,
gradient: LinearGradient(
colors: [color1, color2],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
dotData: FlDotData(show: false), // We'll handle dots separately
));
}
// Add dot markers separately so they are on top
final List<FlSpot> allSpots = dataPoints.asMap().entries.map((entry) {
return FlSpot(entry.key.toDouble(), entry.value.value);
}).toList();
print("the spots are $allSpots");
var data = [
...segments,
LineChartBarData(
spots: allSpots,
isCurved: false,
barWidth: 0, // invisible line
dotData: FlDotData(
show: true,
getDotPainter: (spot, _, __, index) {
final value = spot.y;
final color = getColor(value);
return FlDotCirclePainter(
radius: 4,
color: color,
strokeWidth: 1,
strokeColor: Colors.white,
);
},
),
)
];
var max = LineChartHelper.calculateMaxAxisValues(data).maxX;
print("the maxX is the -------> $max");
return data;
}
List<HorizontalRangeAnnotation> _buildRangeShades(
List<ThresholdRange> thresholds) {
List<HorizontalRangeAnnotation> ranges = [];
for (int i = 0; i < thresholds.length - 1; i++) {
ranges.add(HorizontalRangeAnnotation(
y1: thresholds[i].value,
y2: thresholds[i + 1].value,
color: thresholds[i].color,
));
}
return ranges;
}
}
final List<DataPoint> sampleData = [
DataPoint(value: 1.4, label: 'Jan 2024', date: DateTime(2024, 1, 1)),
DataPoint(value: 3.6, label: 'Feb 2024', date: DateTime(2024, 2, 1)),
DataPoint(value: 1.96, label: 'This result', date: DateTime(2024, 3, 1)),
];
final List<ThresholdRange> thresholdLevels = [
ThresholdRange(
label: 'Critical Low',
value: 1.4,
color: Color(0xfff6e9e9),
lineColor: Color(0xFFe9a2a4)),
ThresholdRange(
label: 'Low',
value: 3.6,
color: Color(0xFFf2fbf5),
lineColor: Color(0xFFeecd94)),
ThresholdRange(
label: 'Normal',
value: 5.96,
color: Color(0xFFf2fbf5),
lineColor: Color(0xFF5dc36b)),
ThresholdRange(
label: 'High',
value: 7.15,
color: Color(0xfff6e9e9),
lineColor: Color(0xFFeecd94)),
ThresholdRange(
label: 'Critical High',
value: 10.15,
color: Color(0xfff6e9e9),
lineColor: Color(0xFFe9a2a4)),
];
class DataPoint {
final double value;
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});
}
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)})';
}
}