<style>
body {
background-color: #1a1a2e; /* Deep dark blue */
color: #ecf0f1; /* Off-white text */
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.container {
width: 80%;
margin: auto;
background-color: #16213e; /* Slightly lighter card background */
border-radius: 15px;
padding: 20px;
box-shadow: 0 4px 15px rgba(0,0,0,0.5);
}
#status {
padding: 8px 15px;
font-weight: bold;
border-radius: 20px;
display: inline-block;
margin-bottom: 20px;
font-size: 0.9em;
text-transform: uppercase;
letter-spacing: 1px;
}
h1 {
text-align: center;
margin-top: 20px;
font-weight: 300;
color: #57E0FF;
}
#forecastChart {
margin-top: 80px; /* Big gap between charts */
}
</style>
End-to-End Temperature Monitoring & ML Prediction
Checking Sensor...
<script>
// Helper function to align data to the master timeline
function alignData(masterLabels, rawData, timeKey, valueKey) {
return masterLabels.map(label => {
const match = rawData.find(d => {
// Formatting date to ISO to ensure string comparison works
return new Date(d[timeKey]).toISOString() === new Date(label).toISOString();
});
return match ? match[valueKey] : null;
});
}
async function loadDashboard() {
// 1. Fetch all data
const [sensorRes, forecastRes, statusRes] = await Promise.all([
fetch('/api/sensor-data'),
fetch('/api/forecast-data'),
fetch('/api/status')
]);
const sensorData = await sensorRes.json();
const forecastData = await forecastRes.json();
const statusData = await statusRes.json();
// 2. Update Status
const statusEl = document.getElementById('status');
statusEl.innerText = statusData.status;
statusEl.style.backgroundColor = statusData.color;
// 3. Create Master Timeline (Synchronization)
// Combine all timestamps, remove duplicates, and sort
const masterLabels = [...new Set([
...sensorData.map(d => d.time),
...forecastData.map(d => d.time)
])].sort((a, b) => new Date(a) - new Date(b));
// 4. Align both datasets to the same timeline
const alignedSensor = alignData(masterLabels, sensorData, 'time', 'temperature');
const alignedForecast = alignData(masterLabels, forecastData, 'time', 'predicted_temp');
// Bulletproof NOW line: Find the last index where we have real sensor data
// This forces the red line to be the "bridge" between reality and prediction
const nowIdx = alignedSensor.reduce((lastIdx, val, idx) => val !== null ? idx : lastIdx, 0);
// 5. Clean the labels for the Chart (MM-DD HH:MM)
const uiLabels = masterLabels.map(label => {
const d = new Date(label);
return d.toISOString().replace('T', ' ').substring(5, 16);
});
// 6. Prepare and Render Chart
const ctx = document.getElementById('tempChart').getContext('2d');
// Safety: Destroy old chart instance if it exists to avoid overlaps
if (window.myChart) { window.myChart.destroy(); }
/////////////////////////////// CHART ///////////////////////////////////////////////////////
// 6. Define custom plugin for "NOW" line
const nowLinePlugin = {
id: 'nowLinePlugin',
beforeDraw: (chart) => {
const {ctx, chartArea: {top, bottom}, scales: {x}} = chart;
const xPos = x.getPixelForValue(nowIdx);
// ctx.save();
// ctx.strokeStyle = '#ff4757'; // Red line for "NOW"
// ctx.lineWidth = 2;
// ctx.setLineDash([5, 5]);
// ctx.beginPath();
// ctx.moveTo(xPos, top);
// ctx.lineTo(xPos, bottom);
// ctx.stroke();
// // Add "NOW" text
// ctx.fillStyle = '#ff4757';
// ctx.font = 'bold 12px Arial';
// ctx.fillText('NOW', xPos + 5, bottom - 10);
// ctx.restore();
}
};
window.myChart = new Chart(ctx, {
type: 'line',
plugins: [nowLinePlugin],
data: {
labels: uiLabels,
datasets: [
{
label: 'Real Temp (°C)',
data: alignedSensor,
borderColor: '#78FF7F',
tension: 0.3, // Smoother lines
fill: false,
spanGaps: false // Line stops when no sensor data exists
},
{
label: 'Predicted Temp (°C)',
data: alignedForecast,
borderColor: '#57E0FF',
tension: 0.3,
borderDash: [5, 5],
fill: false,
spanGaps: true // Connects past predictions to future ones
}
]
},
options: {
responsive: true,
plugins: {
legend: { labels: { color: '#ecf0f1', font: { size: 14 } } }
},
scales: {
x: {
grid: { color: '#334155', borderColor: '#475569' },
ticks: { color: '#94a3b8' }
},
y: {
grid: { color: '#334155', borderColor: '#475569' },
ticks: { color: '#94a3b8' },
beginAtZero: false
}
}
}
});
}
loadDashboard();
</script>