ui: mode-aware chart per device (Temp/Light/Door/Vibration/Magnet)
This commit is contained in:
+75
-14
@@ -30,11 +30,11 @@
|
|||||||
<section class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-8" id="devices"></section>
|
<section class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-8" id="devices"></section>
|
||||||
|
|
||||||
<!-- Chart -->
|
<!-- Chart -->
|
||||||
<section class="bg-white rounded-lg border border-slate-200 mb-8 overflow-hidden">
|
<section id="chart-section" class="bg-white rounded-lg border border-slate-200 mb-8 overflow-hidden">
|
||||||
<div class="px-5 py-4 border-b border-slate-200 flex items-center justify-between">
|
<div class="px-5 py-4 border-b border-slate-200 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-sm font-semibold text-slate-900">History</h2>
|
<h2 class="text-sm font-semibold text-slate-900">History</h2>
|
||||||
<p class="text-xs text-slate-500 mt-0.5">Temperature & humidity over time</p>
|
<p id="chart-subtitle" class="text-xs text-slate-500 mt-0.5">—</p>
|
||||||
</div>
|
</div>
|
||||||
<select id="chart-device" class="text-sm bg-white border border-slate-200 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-2 focus:ring-slate-900/10"></select>
|
<select id="chart-device" class="text-sm bg-white border border-slate-200 rounded-md px-2.5 py-1.5 focus:outline-none focus:ring-2 focus:ring-slate-900/10"></select>
|
||||||
</div>
|
</div>
|
||||||
@@ -191,25 +191,86 @@ function renderMessages() {
|
|||||||
`).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chartPresets = {
|
||||||
|
Temperature: {
|
||||||
|
subtitle: 'Temperature & humidity over time',
|
||||||
|
datasets: (msgs) => [
|
||||||
|
makeDs('Temperature °C', msgs.map(m => m.decoded?.temperature_c ?? null), '#2563eb', 'y'),
|
||||||
|
makeDs('Humidity %', msgs.map(m => m.decoded?.humidity_pct ?? null), '#059669', 'y1'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Light: {
|
||||||
|
subtitle: 'Brightness over time',
|
||||||
|
datasets: (msgs) => [
|
||||||
|
makeDs('Brightness (lux)', msgs.map(m => m.decoded?.brightness_lux ?? null), '#d97706', 'y'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Door: {
|
||||||
|
subtitle: 'Cumulative openings',
|
||||||
|
datasets: (msgs) => [
|
||||||
|
makeDs('Event count', msgs.map(m => m.decoded?.event_count ?? null), '#7c3aed', 'y'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Vibration: {
|
||||||
|
subtitle: 'Cumulative vibration events',
|
||||||
|
datasets: (msgs) => [
|
||||||
|
makeDs('Event count', msgs.map(m => m.decoded?.event_count ?? null), '#e11d48', 'y'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Move: {
|
||||||
|
subtitle: 'Cumulative movement events',
|
||||||
|
datasets: (msgs) => [
|
||||||
|
makeDs('Move events', msgs.map(m => m.decoded?.move_events ?? null), '#e11d48', 'y'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Magnet: {
|
||||||
|
subtitle: 'Cumulative magnet events',
|
||||||
|
datasets: (msgs) => [
|
||||||
|
makeDs('Event count', msgs.map(m => m.decoded?.event_count ?? null), '#7c3aed', 'y'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'Reed Switch': {
|
||||||
|
subtitle: 'Cumulative reed switch events',
|
||||||
|
datasets: (msgs) => [
|
||||||
|
makeDs('Reed events', msgs.map(m => m.decoded?.reed_events ?? null), '#7c3aed', 'y'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function makeDs(label, data, color, yID) {
|
||||||
|
return {
|
||||||
|
label, data, borderColor: color, backgroundColor: color + '14',
|
||||||
|
yAxisID: yID, tension: 0.35, borderWidth: 2, fill: true,
|
||||||
|
spanGaps: true, pointRadius: 2, pointHoverRadius: 4,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function renderChart() {
|
function renderChart() {
|
||||||
const dev = state.selectedDevice || document.getElementById('chart-device').value;
|
const dev = state.selectedDevice || document.getElementById('chart-device').value;
|
||||||
if (!dev) return;
|
const section = document.getElementById('chart-section');
|
||||||
|
const subtitle = document.getElementById('chart-subtitle');
|
||||||
|
if (!dev) { section.style.display = 'none'; return; }
|
||||||
|
|
||||||
const msgs = (state.messagesByDevice[dev] || []).slice().reverse();
|
const msgs = (state.messagesByDevice[dev] || []).slice().reverse();
|
||||||
|
const latestMode = msgs.length ? msgs[msgs.length - 1].decoded?.mode : null;
|
||||||
|
const preset = chartPresets[latestMode];
|
||||||
|
|
||||||
|
if (!preset) {
|
||||||
|
section.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
section.style.display = '';
|
||||||
|
subtitle.textContent = preset.subtitle;
|
||||||
|
|
||||||
const labels = msgs.map(m => fmtShortTime(m.ts));
|
const labels = msgs.map(m => fmtShortTime(m.ts));
|
||||||
const temps = msgs.map(m => m.decoded?.temperature_c ?? null);
|
const datasets = preset.datasets(msgs);
|
||||||
const humi = msgs.map(m => m.decoded?.humidity_pct ?? null);
|
const hasSecondAxis = datasets.some(ds => ds.yAxisID === 'y1');
|
||||||
|
|
||||||
const ctx = document.getElementById('chart').getContext('2d');
|
const ctx = document.getElementById('chart').getContext('2d');
|
||||||
if (state.chart) state.chart.destroy();
|
if (state.chart) state.chart.destroy();
|
||||||
state.chart = new Chart(ctx, {
|
state.chart = new Chart(ctx, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: { labels, datasets },
|
||||||
labels,
|
|
||||||
datasets: [
|
|
||||||
{ label: 'Temperature °C', data: temps, borderColor: '#2563eb', backgroundColor: 'rgba(37,99,235,0.08)', yAxisID: 'y', tension: 0.35, borderWidth: 2, fill: true, spanGaps: true, pointRadius: 2, pointHoverRadius: 4 },
|
|
||||||
{ label: 'Humidity %', data: humi, borderColor: '#059669', backgroundColor: 'rgba(5,150,105,0.08)', yAxisID: 'y1', tension: 0.35, borderWidth: 2, fill: true, spanGaps: true, pointRadius: 2, pointHoverRadius: 4 },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
interaction: { mode: 'index', intersect: false },
|
interaction: { mode: 'index', intersect: false },
|
||||||
@@ -219,8 +280,8 @@ function renderChart() {
|
|||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: { ticks: { color: '#94a3b8', font: { size: 11 } }, grid: { color: '#f1f5f9' } },
|
x: { ticks: { color: '#94a3b8', font: { size: 11 } }, grid: { color: '#f1f5f9' } },
|
||||||
y: { ticks: { color: '#2563eb', font: { size: 11 } }, grid: { color: '#f1f5f9' }, position: 'left', border: { display: false } },
|
y: { ticks: { color: datasets[0].borderColor, font: { size: 11 } }, grid: { color: '#f1f5f9' }, position: 'left', border: { display: false } },
|
||||||
y1: { ticks: { color: '#059669', font: { size: 11 } }, grid: { drawOnChartArea: false }, position: 'right', border: { display: false } },
|
...(hasSecondAxis ? { y1: { ticks: { color: datasets[1].borderColor, font: { size: 11 } }, grid: { drawOnChartArea: false }, position: 'right', border: { display: false } } } : {}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user