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 dataPoints; final List thresholds; const DynamicResultChart( {super.key, required this.dataPoints, required this.thresholds}); @override Widget build(BuildContext context) { final minY = thresholds.first.value; final maxY = thresholds.last.value + 1; 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( height: MediaQuery.sizeOf(context).height / 4, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: SizedBox( width: MediaQuery.sizeOf(context).width, child: Padding( padding: const EdgeInsets.all(16.0), child: LineChart( LineChartData( minY: minY, maxY: maxY, minX : dataPoints.first.labelValue - 1 , maxX : dataPoints.first.labelValue + 1 , titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 70, 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 )?"'${matchingThreshold.label} (${matchingThreshold.actualValue})'":'${matchingThreshold.label}'; if (matchingThreshold.label.isNotEmpty) { return Text( actualValue, style: const TextStyle(fontSize: 10), ); } return const SizedBox.shrink(); }, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, 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 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 _buildColoredLineSegments( List dataPoints, List thresholds) { List 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 allSpots = dataPoints.asMap().entries.map((entry) { return FlSpot(entry.key.toDouble(), entry.value.value); }).toList(); return [ ...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, ); }, ), ) ]; } List _buildRangeShades( List thresholds) { List 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 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 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)})'; } }