From 0315ec1d31ddd044212203ab5b67da63778e34de Mon Sep 17 00:00:00 2001 From: kleponijo <158453666+kleponijo@users.noreply.github.com> Date: Wed, 13 May 2026 10:57:54 +0700 Subject: [PATCH] pembaruan evaporasi grafik dan overflowed tanggal --- lib/main.dart | 3 - .../views/widgets/evaporasi_chart_widget.dart | 151 +++++++++++++----- .../widgets/evaporasi_period_selector.dart | 36 +++-- .../lib/src/firebase_monitoring_repo.dart | 1 - 4 files changed, 135 insertions(+), 56 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index f0093ed..bd3e7d5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:klimatologiot/simple_bloc_observer.dart'; import 'package:klimatologiot/app.dart'; import 'package:user_repository/user_repository.dart'; import 'package:monitoring_repository/monitoring_repository.dart'; @@ -14,7 +12,6 @@ void main() async { options: DefaultFirebaseOptions.currentPlatform, ); await initializeDateFormatting('id_ID', null); - Bloc.observer = SimpleBlocObserver(); runApp(MyApp( FirebaseUserRepo(), FirebaseMonitoringRepo(), diff --git a/lib/screens/monitoring/evaporasi/views/widgets/evaporasi_chart_widget.dart b/lib/screens/monitoring/evaporasi/views/widgets/evaporasi_chart_widget.dart index 209b0a4..d1e1a93 100644 --- a/lib/screens/monitoring/evaporasi/views/widgets/evaporasi_chart_widget.dart +++ b/lib/screens/monitoring/evaporasi/views/widgets/evaporasi_chart_widget.dart @@ -1,11 +1,7 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; -const _tooltipBgColor = Colors.black87; - - class EvaporasiChartWidget extends StatelessWidget { - final List dailyValues; final List dailyTemperatures; final String period; @@ -114,7 +110,7 @@ class EvaporasiChartWidget extends StatelessWidget { // Evaporasi mm biasanya >= 0, kita pakai min 0 agar estetik. final evapMin = 0.0; - final evapMax = _clampDouble(evapMaxRaw * 1.3, 10.0, 100.0); + final evapMax = _clampDouble(evapMaxRaw * 1.15, 8.0, 50.0); // Suhu bisa saja 0 jika data kosong; tetap aman. final tempMin = tempMinRaw; @@ -128,9 +124,8 @@ class EvaporasiChartWidget extends StatelessWidget { evapByX[s.x.toInt()] = s.y; } - final dedupEvapSpots = evapByX.entries - .toList() - ..sort((a, b) => a.key.compareTo(b.key)); + final dedupEvapSpots = evapByX.entries.toList() + ..sort((a, b) => a.key.compareTo(b.key)); final Map tempByX = {}; for (final entry in dailyTemperatures.asMap().entries) { @@ -149,9 +144,8 @@ class EvaporasiChartWidget extends StatelessWidget { return FlSpot(x, y); }).toList(); - final evapSpots = dedupEvapSpots - .map((e) => FlSpot(e.key.toDouble(), e.value)) - .toList(); + final evapSpots = + dedupEvapSpots.map((e) => FlSpot(e.key.toDouble(), e.value)).toList(); if (evapSpots.isEmpty && tempSpots.isEmpty) { return const SizedBox.shrink(); @@ -167,19 +161,32 @@ class EvaporasiChartWidget extends StatelessWidget { ); } - final chart = LineChart( LineChartData( minY: evapMin, maxY: evapMax, - gridData: FlGridData(show: false), + gridData: FlGridData( + show: true, + drawVerticalLine: false, + horizontalInterval: (evapMax - evapMin) / 4, + getDrawingHorizontalLine: (value) { + return FlLine( + color: Colors.grey.withOpacity(0.15), + strokeWidth: 1, + ); + }, + ), borderData: FlBorderData(show: false), titlesData: FlTitlesData( bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 32, - interval: 1, + interval: period == 'Hari Ini' + ? 3 + : period == 'Minggu Ini' + ? 1 + : 5, getTitlesWidget: (value, meta) { final index = value.toInt(); return Padding( @@ -228,21 +235,35 @@ class EvaporasiChartWidget extends StatelessWidget { handleBuiltInTouches: true, touchTooltipData: LineTouchTooltipData( getTooltipItems: (spots) { - - return spots.map((spot) { - // fl_chart tidak mengekspos warna ke tooltip spot pada tipe LineBarSpot - // jadi kita tampilkan dua informasi sekaligus untuk memudahkan dosen. - final temp = _evapScaleToTemp( - yEvap: spot.y, - evapMin: evapMin, - evapMax: evapMax, - tempMin: tempMin, - tempMax: tempMax, - ); + final isTemp = spot.bar.color == Colors.orange.shade700; + + if (isTemp) { + final temp = _evapScaleToTemp( + yEvap: spot.y, + evapMin: evapMin, + evapMax: evapMax, + tempMin: tempMin, + tempMax: tempMax, + ); + + return LineTooltipItem( + '🌡 Suhu\n${temp.toStringAsFixed(1)} °C', + const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ); + } + return LineTooltipItem( - 'Evap: ${spot.y.toStringAsFixed(1)} mm\nSuhu: ${temp.toStringAsFixed(1)} °C', - const TextStyle(color: Colors.white, fontSize: 12), + '💧 Evaporasi\n${spot.y.toStringAsFixed(1)} mm', + const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w600, + ), ); }).toList(); }, @@ -253,9 +274,15 @@ class EvaporasiChartWidget extends StatelessWidget { LineChartBarData( spots: evapSpots, isCurved: true, + curveSmoothness: 0.35, + isStrokeCapRound: true, color: Colors.blue.shade700, barWidth: 3, - dotData: FlDotData(show: false), + showingIndicators: [0], + dotData: FlDotData( + show: false, + checkToShowDot: (spot, barData) => false, + ), belowBarData: BarAreaData( show: true, gradient: LinearGradient( @@ -272,12 +299,20 @@ class EvaporasiChartWidget extends StatelessWidget { LineChartBarData( spots: tempSpots, isCurved: true, + curveSmoothness: 0.35, + isStrokeCapRound: true, color: Colors.orange.shade700, barWidth: 3, - dotData: FlDotData(show: false), + showingIndicators: [0], + dotData: FlDotData( + show: false, + checkToShowDot: (spot, barData) => false, + ), ), ], ), + duration: const Duration(milliseconds: 350), + curve: Curves.easeOutCubic, ); return Container( @@ -297,25 +332,70 @@ class EvaporasiChartWidget extends StatelessWidget { child: Column( children: [ // Legend - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + Wrap( + spacing: 16, + runSpacing: 8, + alignment: WrapAlignment.center, children: [ Row( + mainAxisSize: MainAxisSize.min, children: [ - Container(width: 10, height: 10, decoration: BoxDecoration(color: Colors.blue.shade700, shape: BoxShape.circle)), + Container( + width: 10, + height: 10, + decoration: BoxDecoration( + color: Colors.blue.shade700, + shape: BoxShape.circle, + ), + ), const SizedBox(width: 6), - const Text('Evaporasi (mm)', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.blueGrey)), + const Text( + 'Evaporasi (mm)', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.blueGrey, + ), + ), ], ), Row( + mainAxisSize: MainAxisSize.min, children: [ - Container(width: 10, height: 10, decoration: BoxDecoration(color: Colors.orange.shade700, shape: BoxShape.circle)), + Container( + width: 10, + height: 10, + decoration: BoxDecoration( + color: Colors.orange.shade700, + shape: BoxShape.circle, + ), + ), const SizedBox(width: 6), - const Text('Suhu (°C)', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.brown)), + const Text( + 'Suhu (°C)', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.brown, + ), + ), ], ), ], ), + + Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Text( + 'Monitoring Evaporasi & Suhu', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.blueGrey.shade700, + ), + ), + ), + const SizedBox(height: 8), Expanded( child: ClipRRect( @@ -328,4 +408,3 @@ class EvaporasiChartWidget extends StatelessWidget { ); } } - diff --git a/lib/screens/monitoring/evaporasi/views/widgets/evaporasi_period_selector.dart b/lib/screens/monitoring/evaporasi/views/widgets/evaporasi_period_selector.dart index 2d5e17c..0b3d742 100644 --- a/lib/screens/monitoring/evaporasi/views/widgets/evaporasi_period_selector.dart +++ b/lib/screens/monitoring/evaporasi/views/widgets/evaporasi_period_selector.dart @@ -19,21 +19,26 @@ class EvaporasiPeriodSelector extends StatelessWidget { color: Colors.white, borderRadius: BorderRadius.circular(15), ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - _buildTab(context, "Hari Ini", selectedPeriod, viewMode), - _buildTab(context, "Minggu Ini", selectedPeriod, viewMode), - _buildTab(context, "Bulan Ini", selectedPeriod, viewMode), - const SizedBox(width: 8), - // Date picker button - _buildDatePickerButton(context, viewMode, selectedDate), - ], + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + physics: const BouncingScrollPhysics(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + _buildTab(context, "Hari Ini", selectedPeriod, viewMode), + _buildTab(context, "Minggu Ini", selectedPeriod, viewMode), + _buildTab(context, "Bulan Ini", selectedPeriod, viewMode), + const SizedBox(width: 8), + // Date picker button + _buildDatePickerButton(context, viewMode, selectedDate), + ], + ), ), ); } - Widget _buildTab(BuildContext context, String label, String current, EvaporasiViewMode viewMode) { + Widget _buildTab(BuildContext context, String label, String current, + EvaporasiViewMode viewMode) { // If in customDate mode, show period tabs as inactive bool isActive = viewMode == EvaporasiViewMode.period && label == current; @@ -59,7 +64,8 @@ class EvaporasiPeriodSelector extends StatelessWidget { ); } -Widget _buildDatePickerButton(BuildContext context, EvaporasiViewMode viewMode, DateTime? selectedDate) { + Widget _buildDatePickerButton(BuildContext context, + EvaporasiViewMode viewMode, DateTime? selectedDate) { final isActive = viewMode == EvaporasiViewMode.customDate; return GestureDetector( @@ -67,8 +73,8 @@ Widget _buildDatePickerButton(BuildContext context, EvaporasiViewMode viewMode, // Set mode ke customDate SEBELUM membuka date picker final bloc = context.read(); bloc.add( - const EvaporasiViewModeChanged(EvaporasiViewMode.customDate), - ); + const EvaporasiViewModeChanged(EvaporasiViewMode.customDate), + ); showModalBottomSheet( context: context, @@ -79,7 +85,6 @@ Widget _buildDatePickerButton(BuildContext context, EvaporasiViewMode viewMode, child: const EvaporasiDatePicker(), ), ); - }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), @@ -116,4 +121,3 @@ Widget _buildDatePickerButton(BuildContext context, EvaporasiViewMode viewMode, return "${date.day}/${date.month}"; } } - diff --git a/packages/monitoring_repository/lib/src/firebase_monitoring_repo.dart b/packages/monitoring_repository/lib/src/firebase_monitoring_repo.dart index 060d254..f7cf78c 100644 --- a/packages/monitoring_repository/lib/src/firebase_monitoring_repo.dart +++ b/packages/monitoring_repository/lib/src/firebase_monitoring_repo.dart @@ -52,7 +52,6 @@ class FirebaseMonitoringRepo implements MonitoringRepository { final Map data = snapshot.value as Map; final list = data.values.map((item) { - print(item); return mapper(item as Map); }).toList();