TKK_E32232338/assets/js/chart.js

147 lines
4.3 KiB
JavaScript

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);
});