diff --git a/ashvale/dashboard.py b/ashvale/dashboard.py index 5c90eb7..36dafd0 100644 --- a/ashvale/dashboard.py +++ b/ashvale/dashboard.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""The dashboard: five tabs, one viewport, no scrolling. +"""The dashboard: five tabs in the header, one viewport, no scrolling. Layout contract. The page is a fixed three-row grid pinned to the viewport height: header, tab bar, then a content region that takes the @@ -94,10 +94,13 @@ DASHBOARD_HTML = r"""
-
+
-
-
+ +
+
-
+ + +
health @@ -120,13 +131,6 @@ DASHBOARD_HTML = r"""
-
@@ -256,8 +260,8 @@ DASHBOARD_HTML = r""" pressure, last 24 h --
-
-

The only signal that sees past your walls. Its slope drives the forecast.

+
+

Slope, not level, drives the forecast.

@@ -606,7 +610,7 @@ function applyPrecip(p) { // decide which glyph honestly represents it. const wx = wxPick(p.condition, lastDerived.cloud_index, lastDerived.solar_elevation, lastDerived.temperature, p.rain_probability); - el('c-icon').innerHTML = wxSvg(wx[0], 56); + el('c-icon').innerHTML = wxSvg(wx[0], 44); el('c-sky').innerText = wx[1]; el('c-z').innerText = p.zambretti_z!==undefined ? p.zambretti_z : '-'; el('c-trend').innerText = p.pressure_characteristic||'-'; @@ -663,9 +667,24 @@ charts.tend = new Chart(el('tendChart').getContext('2d'), { data:{ datasets:[{ data:[], borderColor:'#a78bfa', backgroundColor:'rgba(167,139,250,.12)', fill:true, borderWidth:1.6, pointRadius:0, tension:.3, parsing:false }] }, options:{ responsive:true, maintainAspectRatio:false, animation:false, - scales:{ x:{ type:'linear', grid:{display:false}, ticks:{ color:'#475569', font:{family:'JetBrains Mono',size:8}, maxTicksLimit:4, - callback:v=>new Date(v*1000).toLocaleTimeString([], {hour:'2-digit'}) } }, - y:{ grid:{color:GRID}, ticks:{ color:'#a78bfa', font:{family:'JetBrains Mono',size:8}, maxTicksLimit:4, callback:v=>v.toFixed(0) } } }, + // Chart.js lays the tick row out inside the canvas. At the old 56 px there + // was not enough of it left, so the labels crowded the edge and the caption + // spilled past the card. The layout padding reserves that strip explicitly. + layout:{ padding:{ bottom:2, left:2, right:4 } }, + scales:{ x:{ type:'linear', grid:{display:false}, + ticks:{ color:'#64748b', font:{family:'JetBrains Mono',size:9}, maxTicksLimit:4, + maxRotation:0, minRotation:0, padding:2, + // Hour-only labels collapse to three identical ticks when the window is + // short, which happens on a young station. Add minutes below six hours. + callback:function (v) { + const sc = this.chart.scales.x; + const span = (sc.max - sc.min) || 0; + const opts = span > 6 * 3600 + ? {hour: '2-digit'} + : {hour: '2-digit', minute: '2-digit'}; + return new Date(v * 1000).toLocaleTimeString([], opts); + } } }, + y:{ grid:{color:GRID}, ticks:{ color:'#a78bfa', font:{family:'JetBrains Mono',size:9}, maxTicksLimit:4, padding:2, callback:v=>v.toFixed(0) } } }, plugins:{ legend:{display:false}, tooltip:{ backgroundColor:'rgba(15,23,42,.95)', titleFont:MONO, callbacks:{ title:i=>tsFmt(i[0].parsed.x) } } } } });