This commit is contained in:
sebseb7
2025-12-21 22:48:26 +01:00
parent c90477aa52
commit bd6b20e6ed
8 changed files with 1209 additions and 142 deletions

View File

@@ -177,7 +177,7 @@
<!-- Brightness Chart -->
<div id="chartContainer"
style="margin-top: 40px; background: var(--card-bg); padding: 20px; border-radius: 8px; display: none;">
<h3>Brightness Levels</h3>
<h3 id="chartTitle">Brightness Levels</h3>
<canvas id="brightnessChart" style="width: 100%; height: 200px;"></canvas>
</div>
</div>
@@ -193,7 +193,8 @@
const state = {
cameras: [],
selectedCamera: localStorage.getItem('lastCamera') || '',
selectedDate: localStorage.getItem('lastDate') || ''
selectedDate: localStorage.getItem('lastDate') || '',
currentSettings: {}
};
// DOM Elements
@@ -258,6 +259,7 @@
if (state.selectedCamera) {
loadDates(state.selectedCamera);
loadSettings(state.selectedCamera);
}
} catch (err) {
console.error(err);
@@ -299,6 +301,17 @@
}
}
async function loadSettings(cameraId) {
try {
const res = await fetch(`settings/${cameraId}`, { headers: getHeaders() });
const data = await res.json();
state.currentSettings = data.settings || {};
} catch (err) {
console.error('Failed to load settings', err);
state.currentSettings = {};
}
}
async function loadImages(isAutoUpdate = false) {
const cameraId = cameraSelect.value;
const dateStr = dateSelect.value; // YYYY-MM-DD
@@ -439,16 +452,49 @@
const width = canvas.width;
const height = canvas.height;
const padding = { top: 20, right: 20, bottom: 30, left: 40 };
const padding = { top: 20, right: 30, bottom: 30, left: 45 };
ctx.clearRect(0, 0, width, height);
// Sort stats by timestamp just in case
stats.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
// Determine data source and range
// If any entry has 'ocr_val', prefer that.
const hasOcr = stats.some(s => s.ocr_val !== undefined && s.ocr_val !== null);
const valueKey = hasOcr ? 'ocr_val' : 'brightness';
// Clean data (filter nulls)
const validPoints = stats
.filter(s => s[valueKey] !== undefined && s[valueKey] !== null)
.map(s => ({
t: s.timestamp,
v: Number(s[valueKey])
}));
if (validPoints.length === 0) return;
let minY = Math.min(...validPoints.map(p => p.v));
let maxY = Math.max(...validPoints.map(p => p.v));
// Add some padding to Y range
const range = maxY - minY;
if (range === 0) {
minY -= 10;
maxY += 10;
} else {
minY -= range * 0.1;
maxY += range * 0.1;
}
// For brightness, clamp to 0-255 if not OCR?
if (!hasOcr) {
minY = 0;
maxY = 255;
}
// Helper to parsing time to minutes from start of day (local time)
const getMinutes = (iso) => {
// Parse UCT timestamp to Date object
// Parse UTC timestamp to Date object
const parts = iso.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2})[-:](\d{2})[-:](\d{2})/);
let d;
if (parts) {
@@ -460,14 +506,15 @@
return d.getHours() * 60 + d.getMinutes();
};
const points = stats.map(s => ({
x: getMinutes(s.timestamp),
y: s.brightness
const points = validPoints.map(p => ({
x: getMinutes(p.t),
y: p.v
}));
// Scales
const mapX = (minutes) => padding.left + (minutes / 1440) * (width - padding.left - padding.right);
const mapY = (val) => height - padding.bottom - (val / 255) * (height - padding.top - padding.bottom);
// Invert Y (0 at bottom)
const mapY = (val) => height - padding.bottom - ((val - minY) / (maxY - minY)) * (height - padding.top - padding.bottom);
// Draw Axes
ctx.strokeStyle = '#555';
@@ -486,7 +533,7 @@
ctx.stroke();
// Draw Line
ctx.strokeStyle = '#3b82f6';
ctx.strokeStyle = hasOcr ? '#10b981' : '#3b82f6'; // Green for OCR, Blue for Brightness
ctx.lineWidth = 2;
ctx.beginPath();
@@ -498,6 +545,14 @@
}
ctx.stroke();
// Draw Dots
ctx.fillStyle = hasOcr ? '#10b981' : '#3b82f6';
for (const p of points) {
ctx.beginPath();
ctx.arc(mapX(p.x), mapY(p.y), 3, 0, Math.PI * 2);
ctx.fill();
}
// Draw Labels (Time)
ctx.fillStyle = '#ccc';
ctx.font = '12px sans-serif';
@@ -505,16 +560,35 @@
for (let h = 0; h <= 24; h += 4) {
const x = mapX(h * 60);
ctx.fillText(`${h}:00`, x, height - 10);
if (x > padding.left) {
ctx.fillText(`${h}:00`, x, height - 10);
}
}
// Draw Labels (Brightness)
// Draw Labels (Value)
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
for (let b = 0; b <= 255; b += 64) {
const y = mapY(b);
ctx.fillText(Math.round(b), padding.left - 5, y);
// Draw 5 ticks
for (let i = 0; i <= 4; i++) {
const val = minY + (i / 4) * (maxY - minY);
const y = mapY(val);
ctx.fillText(Math.round(val), padding.left - 5, y);
}
// Title
ctx.save();
ctx.translate(15, height / 2);
ctx.rotate(-Math.PI / 2);
ctx.textAlign = 'center';
ctx.textAlign = 'center';
const label = state.currentSettings.chartLabel || (hasOcr ? "Value" : "Brightness");
ctx.fillText(label, 0, 0);
// Update Header too
const titleEl = document.getElementById('chartTitle');
if (titleEl) titleEl.textContent = label;
ctx.restore();
}
function getPrettyTime(ts) {
@@ -541,8 +615,10 @@
// Listeners
cameraSelect.addEventListener('change', (e) => {
state.selectedCamera = e.target.value;
if (state.selectedCamera) loadDates(state.selectedCamera);
else dateSelect.disabled = true;
if (state.selectedCamera) {
loadDates(state.selectedCamera);
loadSettings(state.selectedCamera);
} else dateSelect.disabled = true;
});
dateSelect.addEventListener('change', (e) => {