diff --git a/WeatherPredictor/web_page.h b/WeatherPredictor/web_page.h index 8d5980f..d44ed7d 100644 --- a/WeatherPredictor/web_page.h +++ b/WeatherPredictor/web_page.h @@ -90,7 +90,15 @@ static const char INDEX_HTML[] PROGMEM = R"HTML( .head h2{font-family:var(--serif); font-weight:600; font-size:1.15rem; margin:0} .legend{display:flex; gap:16px; font-size:.76rem; color:var(--muted)} .legend i{display:inline-block; width:20px; height:2px; vertical-align:middle; margin-right:6px} - #chart{width:100%; height:200px} + .baro{position:relative; padding:0 46px} + #chart{width:100%; height:200px; display:block} + .ax{position:absolute; top:0; height:200px; display:flex; flex-direction:column; + justify-content:space-between; font-family:var(--mono); font-variant-numeric:tabular-nums; + font-size:.7rem; pointer-events:none} + .ax-l{left:0; align-items:flex-start; color:var(--change)} + .ax-r{right:0; align-items:flex-end; color:var(--fair)} + .axx{display:flex; justify-content:space-between; padding:0 46px; margin-top:6px; + font-family:var(--mono); font-variant-numeric:tabular-nums; font-size:.7rem; color:var(--muted)} .hint{color:var(--muted); font-size:.78rem; margin:10px 0 0} /* Settings */ @@ -104,11 +112,12 @@ static const char INDEX_HTML[] PROGMEM = R"HTML( .grid2{display:grid; grid-template-columns:1fr 1fr; gap:14px} @media(max-width:520px){.grid2{grid-template-columns:1fr}} label{display:block; font-size:.72rem; letter-spacing:.14em; text-transform:uppercase; color:var(--muted); margin:0 0 6px} - input{ + input,select{ width:100%; padding:11px 12px; background:var(--ink2); color:var(--text); border:1px solid var(--line); border-radius:10px; font-family:var(--mono); font-size:1rem; } - input:focus{outline:none; border-color:var(--brass); box-shadow:0 0 0 3px rgba(198,161,91,.18)} + select{appearance:none; cursor:pointer} + input:focus,select:focus{outline:none; border-color:var(--brass); box-shadow:0 0 0 3px rgba(198,161,91,.18)} .actions{display:flex; align-items:center; gap:16px; margin-top:18px} button{ padding:11px 22px; border:0; border-radius:10px; cursor:pointer; @@ -162,7 +171,12 @@ static const char INDEX_HTML[] PROGMEM = R"HTML( Temperature - +
+
+ +
+
+

Collecting data…

@@ -173,7 +187,7 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(
-
+
@@ -302,34 +316,48 @@ async function refresh(){ await drawChart(); } +function fmtTime(ep){var d=new Date(ep*1000);function p(x){return(x<10?"0":"")+x;}return p(d.getUTCHours())+":"+p(d.getUTCMinutes());} +function fillAxis(id,mx,mn,deg){ + var box=document.getElementById(id); box.innerHTML=""; + [mx,(mx+mn)/2,mn].forEach(function(v){ + var s=document.createElement("span"); + s.textContent=deg?(Math.round(v)+"°"):Math.round(v); + box.appendChild(s); + }); +} async function drawChart(){ var data; try{ data=await (await fetch("/api/history")).json(); }catch(e){ return; } lastHistory=data||[]; var svg=document.getElementById("chart"), W=640,H=200,pad=6; while(svg.firstChild) svg.removeChild(svg.firstChild); - if(!data.length){ document.getElementById("hint").textContent="Collecting data…"; updateSetHand(); return; } + if(!data.length){ + document.getElementById("hint").textContent="Collecting data…"; + document.getElementById("axL").innerHTML=""; document.getElementById("axR").innerHTML=""; + document.getElementById("axT0").textContent="—"; document.getElementById("axT1").textContent="—"; + updateSetHand(); return; + } var ps=data.map(function(d){return d.msl;}), ts=data.map(function(d){return d.temp;}), n=data.length; - function line(vals,color,fillId){ - var mn=Math.min.apply(null,vals), mx=Math.max.apply(null,vals), rng=(mx-mn)||1, d=""; - for(var i=0;i1?i/(n-1):0); - var y=H-pad-(H-2*pad)*((vals[i]-mn)/rng); - d+=(i?"L":"M")+x.toFixed(1)+" "+y.toFixed(1); - } - if(fillId){ - var area=d+"L"+(W-pad)+" "+(H-pad)+"L"+pad+" "+(H-pad)+"Z"; - svg.appendChild(el("path",{d:area,fill:color,opacity:.10})); - } + var pmn=Math.min.apply(null,ps), pmx=Math.max.apply(null,ps); + var tmn=Math.min.apply(null,ts), tmx=Math.max.apply(null,ts); + [0,0.5,1].forEach(function(f){var y=(pad+(H-2*pad)*f).toFixed(1); + svg.appendChild(el("line",{x1:0,y1:y,x2:W,y2:y,stroke:"#22314F","stroke-width":1}));}); + function line(vals,mn,mx,color,fill){ + var rng=(mx-mn)||1,d=""; + for(var i=0;i1?i/(n-1):0); + var y=H-pad-(H-2*pad)*((vals[i]-mn)/rng); d+=(i?"L":"M")+x.toFixed(1)+" "+y.toFixed(1);} + if(fill){var area=d+"L"+(W-pad)+" "+(H-pad)+"L"+pad+" "+(H-pad)+"Z"; + svg.appendChild(el("path",{d:area,fill:color,opacity:.10}));} svg.appendChild(el("path",{d:d,fill:"none",stroke:color,"stroke-width":2,"stroke-linejoin":"round"})); } - // baseline - svg.appendChild(el("line",{x1:0,y1:H-1,x2:W,y2:H-1,stroke:"#2A3A5A","stroke-width":1})); - line(ps,"#79C2D0",true); - line(ts,"#E4B658",false); + line(ps,pmn,pmx,"#79C2D0",true); + line(ts,tmn,tmx,"#E4B658",false); + fillAxis("axL",pmx,pmn,false); + fillAxis("axR",tmx,tmn,true); + document.getElementById("axT0").textContent=fmtTime(data[0].t); + document.getElementById("axT1").textContent=fmtTime(data[n-1].t); var hrs=((data[n-1].t-data[0].t)/3600); - document.getElementById("hint").textContent=n+" readings over "+hrs.toFixed(1)+" h · "+ - Math.min.apply(null,ps).toFixed(0)+"–"+Math.max.apply(null,ps).toFixed(0)+" hPa"; + document.getElementById("hint").textContent=n+" readings over "+hrs.toFixed(1)+" h · pressure in hPa, temperature in °C"; updateSetHand(); } @@ -351,11 +379,15 @@ function updateSetHand(){ document.getElementById("deltas").textContent="hPa · "+word; } +function tzLabel(min){var s=min<0?"-":"+";var a=Math.abs(min);var h=Math.floor(a/60);var m=a%60;return "UTC"+s+h+(m?(":"+(m<10?"0":"")+m):"");} +function buildTz(){var sel=document.getElementById("s-tz");for(var m=-720;m<=840;m+=30){var o=document.createElement("option");o.value=m;o.textContent=tzLabel(m);sel.appendChild(o);}} + async function loadSettings(){ try{ var s=await (await fetch("/api/settings")).json(); document.getElementById("s-alt").value=s.altitude; - document.getElementById("s-tz").value=s.tz; + var tz=document.getElementById("s-tz"); tz.value=String(s.tz); + if(parseInt(tz.value,10)!==s.tz){var o=document.createElement("option");o.value=s.tz;o.textContent=tzLabel(s.tz);tz.appendChild(o);tz.value=String(s.tz);} document.getElementById("s-lat").value=s.lat; document.getElementById("s-lon").value=s.lon; document.getElementById("coords").innerHTML=Number(s.lat).toFixed(2)+"°N, "+Number(s.lon).toFixed(2)+"°E"; @@ -380,6 +412,7 @@ document.getElementById("save").addEventListener("click", async function(){ }); buildGauge(); +buildTz(); loadSettings(); refresh(); setInterval(refresh,15000);