import 'package:flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:fl_chart/fl_chart.dart'; class GraphScreen extends StatefulWidget { const GraphScreen({super.key}); @override State createState() => _GraphScreenState(); } class _GraphScreenState extends State { final DatabaseReference historyRef = FirebaseDatabase.instance.ref("hydroponic/history"); List phSpots = []; List ppmSpots = []; double latestPh = 0; double latestPpm = 0; String latestTime = "-"; @override void initState() { super.initState(); loadGraph(); } void loadGraph() { historyRef.onValue.listen((event) { if (!event.snapshot.exists) return; final data = Map.from(event.snapshot.value as Map); List> entries = data.entries.toList(); entries.sort( (a, b) => int.parse(a.key.toString()) .compareTo(int.parse(b.key.toString())), ); // Ambil 20 data terakhir if (entries.length > 20) { entries = entries.sublist(entries.length - 20); } List tempPh = []; List tempPpm = []; for (int i = 0; i < entries.length; i++) { final item = Map.from(entries[i].value); final ph = double.tryParse(item["ph"].toString()) ?? 0; final ppm = double.tryParse(item["ppm"].toString()) ?? 0; tempPh.add( FlSpot(i.toDouble(), ph), ); tempPpm.add( FlSpot(i.toDouble(), ppm), ); } final latestItem = Map.from(entries.last.value); setState(() { phSpots = tempPh; ppmSpots = tempPpm; latestPh = double.tryParse(latestItem["ph"].toString()) ?? 0; latestPpm = double.tryParse(latestItem["ppm"].toString()) ?? 0; latestTime = latestItem["waktu"]?.toString() ?? "-"; }); }); } Widget buildChart( String title, List spots, bool isPhChart, ) { if (spots.isEmpty) { return const SizedBox(); } double minY = 0; double maxY = isPhChart ? 9 : 1200; return Card( elevation: 4, margin: const EdgeInsets.all(12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Text( title, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 20), SizedBox( height: 350, child: LineChart( LineChartData( minY: minY, maxY: maxY, lineTouchData: LineTouchData( enabled: true, ), gridData: FlGridData( show: true, drawVerticalLine: false, ), borderData: FlBorderData( show: true, ), titlesData: FlTitlesData( topTitles: AxisTitles( sideTitles: SideTitles( showTitles: false, ), ), rightTitles: AxisTitles( sideTitles: SideTitles( showTitles: false, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 40, interval: isPhChart ? 1 : 200, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, interval: 5, getTitlesWidget: (value, meta) { return Padding( padding: const EdgeInsets.only( top: 8, ), child: Text( value.toInt().toString(), style: const TextStyle( fontSize: 10, ), ), ); }, ), ), ), lineBarsData: [ LineChartBarData( spots: spots, isCurved: true, barWidth: 4, color: isPhChart ? Colors.green : Colors.blue, dotData: FlDotData( show: true, ), belowBarData: BarAreaData( show: true, color: (isPhChart ? Colors.green : Colors.blue) .withOpacity(0.15), ), ), ], ), ), ), ], ), ), ); } Widget infoCard( String title, String value, IconData icon, ) { return Expanded( child: Card( elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Icon( icon, size: 32, ), const SizedBox(height: 8), Text( title, style: const TextStyle( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Text( value, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, ), ), ], ), ), ), ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey.shade100, appBar: AppBar( title: const Text("Grafik Monitoring"), centerTitle: true, backgroundColor: Colors.green, ), body: SingleChildScrollView( child: Column( children: [ const SizedBox(height: 10), Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: [ infoCard( "pH Terbaru", latestPh.toStringAsFixed(2), Icons.science, ), const SizedBox(width: 10), infoCard( "PPM Terbaru", latestPpm.toStringAsFixed(0), Icons.water_drop, ), ], ), ), Card( margin: const EdgeInsets.all(12), elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.all(14), child: Row( children: [ const Icon( Icons.access_time, color: Colors.green, ), const SizedBox(width: 10), Expanded( child: Text( "Update Terakhir : $latestTime", style: const TextStyle( fontWeight: FontWeight.w500, ), ), ), ], ), ), ), buildChart( "Grafik pH (20 Data Terakhir)", phSpots, true, ), buildChart( "Grafik PPM (20 Data Terakhir)", ppmSpots, false, ), const SizedBox(height: 20), ], ), ), ); } }