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; 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 _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(); 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 _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)})'; } }