diff --git a/cmd/ingestd/web/app.js b/cmd/ingestd/web/app.js index 43971cb..82770fd 100644 --- a/cmd/ingestd/web/app.js +++ b/cmd/ingestd/web/app.js @@ -17,6 +17,8 @@ const colors = { uvi: "#f4d35e", light: "#b8f2e6", precip: "#4ea8de", + rain: "#4ea8de", + rainStart: "#f77f00", }; function formatNumber(value, digits) { @@ -74,6 +76,69 @@ function safeDate(value) { return Number.isNaN(dt.getTime()) ? null : dt; } +function hourBucketMs(ts, tz) { + const d = new Date(ts); + if (Number.isNaN(d.getTime())) return null; + if (tz === "utc") { + return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours()); + } + return new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours()).getTime(); +} + +function computeRainIncrements(points) { + const out = []; + let prev = null; + for (const p of points) { + const t = new Date(p.ts).getTime(); + if (Number.isNaN(t)) continue; + if (p.rain_mm === null || p.rain_mm === undefined) { + out.push({ x: t, y: null }); + continue; + } + let delta = null; + if (prev !== null) { + delta = p.rain_mm - prev; + if (delta < 0) delta = 0; + } + prev = p.rain_mm; + out.push({ x: t, y: delta }); + } + return out; +} + +function computeRollingSum(points, windowMs) { + const out = []; + let sum = 0; + let j = 0; + const values = points.map((p) => ({ + x: p.x, + y: p.y == null ? 0 : p.y, + })); + for (let i = 0; i < values.length; i++) { + const cur = values[i]; + sum += cur.y; + while (values[j].x < cur.x - windowMs) { + sum -= values[j].y; + j += 1; + } + out.push({ x: cur.x, y: sum }); + } + return out; +} + +function computeHourlySums(points, tz) { + const buckets = new Map(); + for (const p of points) { + if (p.y == null) continue; + const bucket = hourBucketMs(p.x, tz); + if (bucket === null) continue; + buckets.set(bucket, (buckets.get(bucket) || 0) + p.y); + } + return Array.from(buckets.entries()) + .sort((a, b) => a[0] - b[0]) + .map(([x, y]) => ({ x, y })); +} + function startOfDay(date, tz) { if (tz === "utc") { return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())); @@ -476,6 +541,38 @@ function renderDashboard(data) { }; upsertChart("chart-power", powerChart); + const rainOptions = baseOptions(range); + rainOptions.scales.y1 = { + position: "right", + ticks: { color: "#a4c4c4" }, + grid: { drawOnChartArea: false }, + }; + + const rainIncs = computeRainIncrements(obsFiltered); + const rainRolling = computeRollingSum(rainIncs, 24 * 60 * 60 * 1000); + const rainHourly = computeHourlySums(rainIncs, state.tz); + const rainTitle = document.getElementById("chart-rain-title"); + const isHourly = state.range === "6h"; + if (rainTitle) { + rainTitle.textContent = isHourly ? "Rain (hourly sums)" : "Rain (24h rolling)"; + } + + const rainChart = { + type: "line", + data: { + datasets: [ + { + label: isHourly ? "rain hourly sum (mm)" : "rain rolling 24h (mm)", + data: isHourly ? rainHourly : rainRolling, + borderColor: colors.rain, + yAxisID: "y", + }, + ], + }, + options: rainOptions, + }; + upsertChart("chart-rain", rainChart); + const precipChart = { type: "bar", data: { diff --git a/cmd/ingestd/web/index.html b/cmd/ingestd/web/index.html index 319e0b0..0eecf39 100644 --- a/cmd/ingestd/web/index.html +++ b/cmd/ingestd/web/index.html @@ -146,6 +146,15 @@ +