diff --git a/static/shared.js b/static/shared.js index c8aa2a5..5819927 100644 --- a/static/shared.js +++ b/static/shared.js @@ -2,7 +2,7 @@ // constant and helper here previously existed as a copy in app.js, calendar.js, // day.js, compare.js and/or legend.html; pages import what they need so a tier // color, scale label or formatter has exactly one home. -import { fmtTemp, toUnit } from "./units.js"; +import { fmtTemp } from "./units.js"; // ---- tier colors ---- // style.css's :root custom properties are the source of truth (they're not // re-themed, so reading them once at load is safe). The JS map exists because @@ -113,8 +113,8 @@ export function metricBuckets(days, metric) { }); } -// Format a single metric value / a min–max range in the metric's own unit — temps -// follow the active °C/°F unit; humidity, wind, precip and dry-streak are fixed-unit. +// Format a single metric value in the metric's own unit — temps follow the active +// °C/°F unit; humidity, wind, precip and dry-streak are fixed-unit. function fmtMetricVal(unit, v) { if (v == null || isNaN(v)) return ""; if (unit === "temp") return fmtTemp(v); @@ -124,22 +124,12 @@ function fmtMetricVal(unit, v) { if (unit === "dsr") return `${Math.round(v)}d`; return `${Math.round(v)}`; } -function fmtMetricRange(unit, lo, hi) { - if (lo == null) return ""; - if (lo === hi) return fmtMetricVal(unit, lo); - if (unit === "temp") return `${Math.round(toUnit(lo))}–${Math.round(toUnit(hi))}°`; - if (unit === "humid") return `${Math.round(lo)}–${Math.round(hi)}`; - if (unit === "wind") return `${Math.round(lo)}–${Math.round(hi)} mph`; - if (unit === "precip") return `${lo.toFixed(2)}–${hi.toFixed(2)}″`; - if (unit === "dsr") return `${Math.round(lo)}–${Math.round(hi)}d`; - return `${Math.round(lo)}–${Math.round(hi)}`; -} // Render buckets from metricBuckets() as a connected bar chart, one bar per category -// low→high: the category's average value sits above its bar, the min–max range inside +// low→high: the category's average value sits above its bar, its Max/Min values inside // it, and the share (or raw count when showCount is set) with the category name below. // Bar height encodes frequency within the bucket's scale group (wet/dry/temp scale -// apart), floored so the in-bar range stays legible. Assumes ≥1 day (callers guard). +// apart), floored so the in-bar label stays legible. Assumes ≥1 day (callers guard). export function distStrip(buckets, showCount) { const total = buckets.reduce((s, b) => s + b.n, 0); const groupTotal = {}, groupCount = {}, maxByGroup = {}; @@ -154,7 +144,7 @@ export function distStrip(buckets, showCount) { return n > 0 && r === 0 ? `${p.toFixed(1)}%` : `${r}%`; }; const valText = (n, group) => !n ? "0" : (showCount ? n.toLocaleString() : pctText(n, group)); - const BAR_MIN = 26, BAR_MAX = 74; // px — the floor keeps the in-bar range readable + const BAR_MIN = 30, BAR_MAX = 74; // px — the floor keeps the two-line Max/Min label readable const cols = buckets.map((b) => { let avg = null, lo = null, hi = null; if (b.values && b.values.length) { @@ -164,7 +154,12 @@ export function distStrip(buckets, showCount) { } const h = b.n ? Math.round(BAR_MIN + (BAR_MAX - BAR_MIN) * (b.n / maxByGroup[b.group])) : 0; const bar = h - ? `
${lo != null ? `${fmtMetricRange(b.unit, lo, hi)}` : ""}
` + ? `
${lo != null + ? `` + + `Max${fmtMetricVal(b.unit, hi)}` + + `Min${fmtMetricVal(b.unit, lo)}` + + `` + : ""}
` : `
`; return `
` + diff --git a/static/style.css b/static/style.css index a9369cd..b43af95 100644 --- a/static/style.css +++ b/static/style.css @@ -802,12 +802,16 @@ main { max-width: 1200px; margin: 0 auto; padding: 20px 24px 60px; } display: flex; align-items: center; justify-content: center; } .ct-bar-0 { height: 2px; opacity: .35; } -/* Min–max range inside the bar — a translucent dark pill so it reads on any tier color. */ +/* Max/Min values inside the bar — a translucent dark pill (reads on any tier color) + holding two rows: key on the left, value on the right. */ .ct-range { - max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - font-size: 9.5px; font-weight: 700; line-height: 1; - color: #fff; background: rgba(12, 15, 19, .5); border-radius: 3px; padding: 1px 3px; + display: flex; flex-direction: column; gap: 1px; + max-width: 100%; overflow: hidden; white-space: nowrap; + font-size: 9px; font-weight: 700; line-height: 1.15; + color: #fff; background: rgba(12, 15, 19, .55); border-radius: 3px; padding: 2px 3px; } +.ct-mm { display: flex; justify-content: space-between; gap: 5px; } +.ct-mm-k { font-weight: 600; opacity: .68; } /* Share + category name, below the bar. */ .ct-cap { display: flex; flex-direction: column; align-items: center; gap: 1px; margin-top: 5px; } .ct-num {