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>
|
||||
|
||||
<!-- 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>
|
||||
<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>
|
||||
<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>
|
||||
@@ -191,25 +191,86 @@ function renderMessages() {
|
||||
`).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() {
|
||||
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 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 temps = msgs.map(m => m.decoded?.temperature_c ?? null);
|
||||
const humi = msgs.map(m => m.decoded?.humidity_pct ?? null);
|
||||
const datasets = preset.datasets(msgs);
|
||||
const hasSecondAxis = datasets.some(ds => ds.yAxisID === 'y1');
|
||||
|
||||
const ctx = document.getElementById('chart').getContext('2d');
|
||||
if (state.chart) state.chart.destroy();
|
||||
state.chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
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 },
|
||||
]
|
||||
},
|
||||
data: { labels, datasets },
|
||||
options: {
|
||||
responsive: true,
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
@@ -219,8 +280,8 @@ function renderChart() {
|
||||
},
|
||||
scales: {
|
||||
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 } },
|
||||
y1: { ticks: { color: '#059669', font: { size: 11 } }, grid: { drawOnChartArea: false }, position: 'right', border: { display: false } },
|
||||
y: { ticks: { color: datasets[0].borderColor, font: { size: 11 } }, grid: { color: '#f1f5f9' }, position: 'left', 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