document.addEventListener('DOMContentLoaded', async function () { const ctx = document.getElementById('phChart'); if (!ctx) return; const NODERED_URL = (window.NODERED_URL || 'https://node-red.aquamonn.online').replace(/\/$/, ''); const chart = new Chart(ctx, { type: 'line', data: { labels: [], datasets: [ { label: 'pH', data: [], borderColor: '#2980b9', backgroundColor: 'rgba(41, 128, 185, 0.15)', tension: 0.3, fill: true, pointRadius: 3, yAxisID: 'yPh' }, { label: 'Suhu (°C)', data: [], borderColor: '#e67e22', backgroundColor: 'rgba(230, 126, 34, 0.10)', tension: 0.3, fill: false, pointRadius: 3, yAxisID: 'ySuhu' }, { label: 'Amonia (ppm)', data: [], borderColor: '#27ae60', backgroundColor: 'rgba(39, 174, 96, 0.10)', tension: 0.3, fill: false, pointRadius: 3, yAxisID: 'yAmonia' }, { label: 'Distance (cm)', data: [], borderColor: '#3b27ae', backgroundColor: 'rgba(59, 39, 174, 0.10)', tension: 0.3, fill: false, pointRadius: 3, yAxisID: 'ydistance' } ] }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, plugins: { legend: { display: true, labels: { color: '#000000', font: { size: 12 } } }, title: { display: true, text: 'Grafik Sensor Realtime', color: '#0b0b0b', font: { size: 14, weight: 'bold' } } }, scales: { x: { ticks: { color: '#ccc', font: { size: 10 }, maxTicksLimit: 10 }, grid: { color: 'rgba(255,255,255,0.08)' } }, yPh: { type: 'linear', position: 'left', title: { display: true, text: 'pH', color: '#2980b9' }, ticks: { color: '#2980b9' }, grid: { color: 'rgba(255,255,255,0.08)' }, min: 0, max: 14 }, ySuhu: { type: 'linear', position: 'right', title: { display: true, text: '°C', color: '#e67e22' }, ticks: { color: '#e67e22' }, grid: { drawOnChartArea: false }, min: 20, max: 40 }, yAmonia: { type: 'linear', position: 'right', title: { display: true, text: 'ppm', color: '#27ae60' }, ticks: { color: '#27ae60' }, grid: { drawOnChartArea: false }, min: 0, max: 10, offset: true }, ydistance: { type: 'linear', position: 'right', title: { display: true, text: 'cm', color: '#3b27ae' }, ticks: { color: '#2927ae' }, grid: { drawOnChartArea: false }, min: 0, max: 30, offset: true } } } }); async function loadChartData() { try { const res = await fetch(NODERED_URL + '/get_history', { cache: 'no-store' }); if (!res.ok) throw new Error('HTTP ' + res.status); const arr = await res.json(); if (!Array.isArray(arr) || arr.length === 0) { chart.options.plugins.title.text = 'Grafik Sensor — Menunggu data…'; chart.update(); return; } const slice = arr.slice(0, 20).reverse(); chart.data.labels = slice.map(r => { if (r.jam) return r.jam.substring(0, 5); const d = new Date(r.timestamp ?? 0); return d.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' }); }); chart.data.datasets[0].data = slice.map(r => parseFloat(r.ph ?? r.pH ?? 0)); chart.data.datasets[1].data = slice.map(r => parseFloat(r.suhu ?? r.temperature ?? 0)); chart.data.datasets[2].data = slice.map(r => parseFloat(r.amonia ?? 0)); chart.data.datasets[3].data = slice.map(r => parseFloat(r.distance ?? 0)); chart.options.plugins.title.text = 'Grafik Sensor Realtime '; chart.update(); } catch(e) { chart.options.plugins.title.text = 'Grafik — Gagal: ' + e.message; chart.update(); console.warn('Chart error:', e.message); } } await loadChartData(); setInterval(loadChartData, 30000); });