Pressure tendency chart: own range and change-window controls
Publish Container / publish (push) Successful in 2m28s

Range: same as page / 6h / 24h / 2d / 7d / 30d. Change over: 1h / 3h / 6h / 12h / 24h.
Choices persist in localStorage.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Nate EmmertandClaude Sonnet 5.5 committed 2026-10-15 13:16:05 -04:00
1 parent 74198330d9
commit fc390b0bb1
2 files changed
+77 -16

No files matched your search

+70 -16
View File
@@ -611,6 +611,7 @@ let charts = {};
// ── Formatting helpers ──────────────────────────────────────────────────────
function fmtTime(ts, range) {
const d = new Date(ts);
if (range === '2d') return d.toLocaleDateString('en-US', { weekday: 'short' }) + ' ' + d.toLocaleTimeString('en-US', { hour: 'numeric', hour12: true });
if (['7d','30d','90d','1y','2y','3y'].includes(range)) {
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
@@ -779,6 +780,64 @@ function drawWindRose(canvas, readings) {
})(t0);
}
// ── Pressure tendency (own range + change window) ────────────────────────────
let presTendRange = 'follow', presTendWin = 3;
try { presTendRange = localStorage.getItem('ptRange') || 'follow'; presTendWin = parseInt(localStorage.getItem('ptWin')) || 3; } catch {}
const presTendCache = {};
let presTendLast = { pts: null, range: null };
async function drawPresTend(pagePts, pageRange) {
const canvas = document.getElementById('cPresTend');
if (!canvas) return;
let pts = pagePts, range = pageRange;
if (presTendRange !== 'follow') {
range = presTendRange;
const hit = presTendCache[range];
if (hit && Date.now() - hit.at < 120000) pts = hit.data;
else {
try {
const bucket = ['7d', '30d'].includes(range) ? '&points=720' : '';
const r = await fetch(`/api/station/history?range=${range}${bucket}`);
pts = r.ok ? await r.json() : [];
} catch { pts = []; }
presTendCache[range] = { at: Date.now(), data: pts };
}
if (document.getElementById('cPresTend') !== canvas) return; // charts were re-rendered meanwhile
}
if (pts.length > 900) { const step = Math.ceil(pts.length / 720); pts = pts.filter((_, i) => i % step === 0); }
const winMs = presTendWin * 3600000;
const c = chartColors();
const tend = pts.map(r => {
const t = r.dateutc - winMs;
if (!pts.length || t < pts[0].dateutc) return null;
let lo = 0, hi = pts.length - 1;
while (lo < hi) { const m = (lo + hi) >> 1; if (pts[m].dateutc < t) lo = m + 1; else hi = m; }
const base = pts[lo].baromrelin;
return r.baromrelin != null && base != null ? +(r.baromrelin - base).toFixed(3) : null;
});
const ds = barDataset(`${presTendWin}h change`, tend, c.cool);
ds.backgroundColor = tend.map(v => v == null ? 'transparent' : (v >= 0 ? c.cool + 'bb' : c.accent + 'bb'));
ds.borderWidth = 0;
makeChart('cPresTend', 'bar', [ds], pts.map(r => fmtTime(r.dateutc, range)), `inHg / ${presTendWin}h`);
const title = document.getElementById('ptTitle');
if (title) title.textContent = `Pressure Tendency · ${presTendWin}-hour change (inHg)`;
if (!tend.some(v => v != null) && title) title.textContent += ' — not enough data for this window';
}
function initPresTend(pagePts, pageRange) {
presTendLast = { pts: pagePts, range: pageRange };
const selRange = document.getElementById('ptRange'), selWin = document.getElementById('ptWin');
if (selRange) {
selRange.value = presTendRange;
selRange.addEventListener('change', () => { presTendRange = selRange.value; try { localStorage.setItem('ptRange', presTendRange); } catch {} drawPresTend(presTendLast.pts, presTendLast.range); });
}
if (selWin) {
selWin.value = String(presTendWin);
selWin.addEventListener('change', () => { presTendWin = parseInt(selWin.value); try { localStorage.setItem('ptWin', presTendWin); } catch {} drawPresTend(presTendLast.pts, presTendLast.range); });
}
drawPresTend(pagePts, pageRange);
}
// ── Render charts ────────────────────────────────────────────────────────────
function renderCharts(readings, range, yoy = null) {
const area = document.getElementById('chartsArea');
@@ -835,7 +894,15 @@ function renderCharts(readings, range, yoy = null) {
</div>
<div class="chart-card wide" id="cardPresTend">
<div class="chart-title">Pressure Tendency · 3-hour change (inHg)</div>
<div class="chart-title"><span id="ptTitle">Pressure Tendency (inHg)</span>
<span class="chart-ctl">
<label>Range <select id="ptRange" aria-label="Pressure tendency range">
<option value="follow">Same as page</option><option value="6h">6 hours</option><option value="24h">24 hours</option>
<option value="2d">2 days</option><option value="7d">7 days</option><option value="30d">30 days</option></select></label>
<label>Change over <select id="ptWin" aria-label="Pressure change window">
<option value="1">1 hour</option><option value="3">3 hours</option><option value="6">6 hours</option>
<option value="12">12 hours</option><option value="24">24 hours</option></select></label>
</span></div>
<div class="chart-wrap" style="height:140px"><canvas id="cPresTend"></canvas></div>
</div>
@@ -921,21 +988,8 @@ function renderCharts(readings, range, yoy = null) {
}
makeChart('cSolar', 'line', solarSets, lbls, 'W/m²', { scales: { y: { min: 0 } } });
// Pressure tendency: change vs 3 hours earlier (what forecasters watch)
if (hasPres) {
const tend = pts.map(r => {
const t = r.dateutc - 3 * 3600000;
let lo = 0, hi = pts.length - 1;
if (t < pts[0].dateutc) return null;
while (lo < hi) { const m = (lo + hi) >> 1; if (pts[m].dateutc < t) lo = m + 1; else hi = m; }
const base = pts[lo].baromrelin;
return r.baromrelin != null && base != null ? +(r.baromrelin - base).toFixed(3) : null;
});
const ds = barDataset('3h change', tend, c.cool);
ds.backgroundColor = tend.map(v => v == null ? 'transparent' : (v >= 0 ? c.cool + 'bb' : c.accent + 'bb'));
ds.borderWidth = 0;
makeChart('cPresTend', 'bar', [ds], lbls, 'inHg / 3h');
}
// Pressure tendency: change vs N hours earlier (what forecasters watch); has its own range + window
if (hasPres) initPresTend(pts, range);
// Indoor vs outdoor
if (pts.some(r => r.tempinf != null)) {