|
|
|
|
@ -8,24 +8,33 @@ class DynamicResultChart extends StatelessWidget {
|
|
|
|
|
final List<ThresholdRange> thresholds;
|
|
|
|
|
final double? width;
|
|
|
|
|
final double height;
|
|
|
|
|
final double? maxX;
|
|
|
|
|
final double? maxY;
|
|
|
|
|
|
|
|
|
|
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});
|
|
|
|
|
DynamicResultChart(
|
|
|
|
|
{super.key, required this.dataPoints, required this.thresholds, required this.width, required this.scrollDirection, required this.height, this.maxY, 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();
|
|
|
|
|
}
|
|
|
|
|
// var maxY = 0.0;
|
|
|
|
|
print("the maxY from parameter is $maxY");
|
|
|
|
|
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;
|
|
|
|
|
// else if((maxY??0)>=100) interval = 20;
|
|
|
|
|
print("the interval parameter is $interval");
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
@ -42,10 +51,10 @@ class DynamicResultChart extends StatelessWidget {
|
|
|
|
|
padding: const EdgeInsets.only(top: 8.0, bottom: 8),
|
|
|
|
|
child: LineChart(
|
|
|
|
|
LineChartData(
|
|
|
|
|
minY: minY,
|
|
|
|
|
maxY: maxY,
|
|
|
|
|
minX: dataPoints.first.labelValue - 1,
|
|
|
|
|
maxX: maxX,
|
|
|
|
|
minY: 0,
|
|
|
|
|
maxY: ((maxY?.ceilToDouble()??0.0)+interval ).floorToDouble(),
|
|
|
|
|
// minX: dataPoints.first.labelValue - 1,
|
|
|
|
|
// maxX: maxX,
|
|
|
|
|
lineTouchData: LineTouchData(touchTooltipData: LineTouchTooltipData(getTooltipItems: (touchedSpots) {
|
|
|
|
|
if (touchedSpots.isEmpty) return [];
|
|
|
|
|
|
|
|
|
|
@ -72,32 +81,52 @@ class DynamicResultChart extends StatelessWidget {
|
|
|
|
|
})),
|
|
|
|
|
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) {
|
|
|
|
|
sideTitles : SideTitles(
|
|
|
|
|
showTitles: true,
|
|
|
|
|
reservedSize: 50,
|
|
|
|
|
interval: interval,
|
|
|
|
|
getTitlesWidget: (value, meta) {
|
|
|
|
|
// Skip the last (maxY) label
|
|
|
|
|
if (value == ((maxY?.ceilToDouble()??0.0)+interval ).floorToDouble()) {
|
|
|
|
|
return const SizedBox.shrink();
|
|
|
|
|
}
|
|
|
|
|
return Text(
|
|
|
|
|
actualValue,
|
|
|
|
|
value.toStringAsFixed(1),
|
|
|
|
|
style: const TextStyle(fontSize: 10),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return const SizedBox.shrink();
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
),
|
|
|
|
|
// 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(
|
|
|
|
|
// "${value.toStringAsFixed(0)}",
|
|
|
|
|
// style: const TextStyle(fontSize: 10),
|
|
|
|
|
// );
|
|
|
|
|
// }
|
|
|
|
|
// return const SizedBox.shrink();
|
|
|
|
|
// },
|
|
|
|
|
// ),
|
|
|
|
|
// ),
|
|
|
|
|
bottomTitles: AxisTitles(
|
|
|
|
|
axisNameSize: 60,
|
|
|
|
|
sideTitles: SideTitles(
|
|
|
|
|
@ -128,27 +157,38 @@ class DynamicResultChart extends StatelessWidget {
|
|
|
|
|
topTitles: AxisTitles(),
|
|
|
|
|
rightTitles: AxisTitles(),
|
|
|
|
|
),
|
|
|
|
|
borderData: FlBorderData(show: false),
|
|
|
|
|
borderData: FlBorderData(show: true,border: const Border(
|
|
|
|
|
bottom: BorderSide(
|
|
|
|
|
color: Colors.grey,
|
|
|
|
|
width: 0.5,
|
|
|
|
|
),
|
|
|
|
|
left: BorderSide(
|
|
|
|
|
color: Colors.grey,
|
|
|
|
|
width: .5
|
|
|
|
|
),
|
|
|
|
|
right:BorderSide.none,
|
|
|
|
|
top: BorderSide.none,
|
|
|
|
|
),),
|
|
|
|
|
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),
|
|
|
|
|
),
|
|
|
|
|
gridData: FlGridData(show: true, drawVerticalLine: false, getDrawingHorizontalLine:(value)=> FlLine(color: Colors.grey, strokeWidth: 0.5)),
|
|
|
|
|
// extraLinesData: ExtraLinesData(
|
|
|
|
|
// horizontalLines: [
|
|
|
|
|
// HorizontalLine(
|
|
|
|
|
// color: Colors.black,
|
|
|
|
|
// strokeWidth: 2,
|
|
|
|
|
// dashArray: [5, 5], // optional: dashed line
|
|
|
|
|
// label: HorizontalLineLabel(
|
|
|
|
|
// show: true,
|
|
|
|
|
// alignment: Alignment.centerRight,
|
|
|
|
|
// style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
|
|
|
|
|
// labelResolver: (line) => 'Threshold ${line.y.toInt()}',
|
|
|
|
|
// ),
|
|
|
|
|
// ),
|
|
|
|
|
// ],
|
|
|
|
|
// ),
|
|
|
|
|
// rangeAnnotations: RangeAnnotations(
|
|
|
|
|
// horizontalRangeAnnotations: _buildRangeShades(thresholds),
|
|
|
|
|
// ),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
@ -157,10 +197,11 @@ class DynamicResultChart extends StatelessWidget {
|
|
|
|
|
|
|
|
|
|
List<LineChartBarData> _buildColoredLineSegments(List<DataPoint> dataPoints, List<ThresholdRange> thresholds) {
|
|
|
|
|
List<LineChartBarData> segments = [];
|
|
|
|
|
Color getColor(double value) {
|
|
|
|
|
Color getColor(String value) {
|
|
|
|
|
print("the value to compare is $value");
|
|
|
|
|
for (int i = thresholds.length - 1; i >= 0; i--) {
|
|
|
|
|
if (value >= thresholds[i].value) {
|
|
|
|
|
return thresholds[i].lineColor;
|
|
|
|
|
if (value == thresholds[i].label) {
|
|
|
|
|
return thresholds[i].lineColor;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return Colors.grey;
|
|
|
|
|
@ -173,20 +214,34 @@ class DynamicResultChart extends StatelessWidget {
|
|
|
|
|
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);
|
|
|
|
|
final color1 = getColor(dp1.referenceRangeValue);
|
|
|
|
|
final color2 = getColor(dp2.referenceRangeValue);
|
|
|
|
|
|
|
|
|
|
segments.add(LineChartBarData(
|
|
|
|
|
spots: [spot1, spot2],
|
|
|
|
|
isCurved: false,
|
|
|
|
|
barWidth: 2,
|
|
|
|
|
isCurved: true,
|
|
|
|
|
isStrokeCapRound: true,
|
|
|
|
|
isStrokeJoinRound: true,
|
|
|
|
|
gradient: LinearGradient(
|
|
|
|
|
colors: [color1, color2],
|
|
|
|
|
begin: Alignment.centerLeft,
|
|
|
|
|
end: Alignment.centerRight,
|
|
|
|
|
),
|
|
|
|
|
dotData: FlDotData(show: false), // We'll handle dots separately
|
|
|
|
|
));
|
|
|
|
|
// 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,
|
|
|
|
|
// );
|
|
|
|
|
// },
|
|
|
|
|
// ),
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add dot markers separately so they are on top
|
|
|
|
|
@ -197,16 +252,23 @@ class DynamicResultChart extends StatelessWidget {
|
|
|
|
|
print("the spots are $allSpots");
|
|
|
|
|
|
|
|
|
|
var data = [
|
|
|
|
|
...segments,
|
|
|
|
|
// ...segments
|
|
|
|
|
LineChartBarData(
|
|
|
|
|
spots: allSpots,
|
|
|
|
|
isCurved: false,
|
|
|
|
|
barWidth: 0, // invisible line
|
|
|
|
|
isCurved: true,
|
|
|
|
|
isStrokeCapRound: true,
|
|
|
|
|
isStrokeJoinRound: true,
|
|
|
|
|
barWidth: 2, // invisible line
|
|
|
|
|
gradient: LinearGradient(
|
|
|
|
|
colors: [Color(0xFF5dc36b), Color(0xFF5dc36b)],
|
|
|
|
|
begin: Alignment.centerLeft,
|
|
|
|
|
end: Alignment.centerRight,
|
|
|
|
|
),
|
|
|
|
|
dotData: FlDotData(
|
|
|
|
|
show: true,
|
|
|
|
|
getDotPainter: (spot, _, __, index) {
|
|
|
|
|
final value = spot.y;
|
|
|
|
|
final color = getColor(value);
|
|
|
|
|
final color = Color(0xFF5dc36b);
|
|
|
|
|
return FlDotCirclePainter(
|
|
|
|
|
radius: 4,
|
|
|
|
|
color: color,
|
|
|
|
|
@ -256,8 +318,9 @@ class DataPoint {
|
|
|
|
|
String actualValue;
|
|
|
|
|
final DateTime date;
|
|
|
|
|
bool isStringResource;
|
|
|
|
|
String referenceRangeValue;
|
|
|
|
|
|
|
|
|
|
DataPoint({required this.value, required this.label, required this.date, this.isStringResource = false, this.labelValue = 0.0, this.actualValue = ""});
|
|
|
|
|
DataPoint({required this.value, required this.label, required this.date, this.isStringResource = false, this.labelValue = 0.0, this.actualValue = "", this.referenceRangeValue= ""});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ThresholdRange {
|
|
|
|
|
|