Make the compare page unit-aware so the °F/°C toggle works there (#55)
compare.js never imported units.js, so the header unit toggle was never built on the compare page and every temperature was stuck in °F. Import units.js (which builds the toggle) and route the comfort target, comfort band, and the colder/warmer/typical-miss figures through the unit formatters, re-rendering on unit change. State stays in °F (the API's unit); only the displayed numbers convert. Add fmtDelta for degree *differences* (band, average miss), which scale by 5/9 without the 32° offset that absolute temperatures use.
This commit is contained in:
parent
94dde1ebc6
commit
29302624a7
2 changed files with 29 additions and 10 deletions
|
|
@ -16,6 +16,7 @@ import { getJSON, TTL } from "./cache.js";
|
|||
import { initFindButton } from "./mappicker.js";
|
||||
import { MONTHS, pad, isoOfDate, monthStart, monthEnd, buildChunks,
|
||||
clickOpensPicker } from "./shared.js";
|
||||
import { fmtTemp, fmtDelta, onUnitChange } from "./units.js";
|
||||
|
||||
const CMP = {
|
||||
comfort: "thermograph:cmpComfort",
|
||||
|
|
@ -243,7 +244,8 @@ function computeStats(series) {
|
|||
|
||||
// ---- render ----
|
||||
const pct = (v) => `${v < 10 ? v.toFixed(1) : Math.round(v)}%`;
|
||||
const deg = (v) => `${Math.round(v)}°`;
|
||||
// Temperature *differences* (avg miss, typical miss) in the active unit.
|
||||
const deg = (v) => fmtDelta(v);
|
||||
|
||||
function renderLocList() {
|
||||
locList.innerHTML = locations.map((l, i) => {
|
||||
|
|
@ -269,7 +271,7 @@ function rankCard(loc, s, rank) {
|
|||
const stats =
|
||||
`<div class="cmp-stats">` +
|
||||
`<div class="cmp-stat cmp-s-below"><span class="cmp-k">Colder</span><span class="cmp-v">${pct(s.belowPct)}</span><span class="cmp-d">${s.below ? `avg ${deg(s.avgBelow)} below` : "—"}</span></div>` +
|
||||
`<div class="cmp-stat cmp-s-comfort"><span class="cmp-k">In comfort</span><span class="cmp-v">${pct(s.comfortPct)}</span><span class="cmp-d">±${tol}° of ${comfort}°</span></div>` +
|
||||
`<div class="cmp-stat cmp-s-comfort"><span class="cmp-k">In comfort</span><span class="cmp-v">${pct(s.comfortPct)}</span><span class="cmp-d">±${fmtDelta(tol)} of ${fmtTemp(comfort)}</span></div>` +
|
||||
`<div class="cmp-stat cmp-s-above"><span class="cmp-k">Warmer</span><span class="cmp-v">${pct(s.abovePct)}</span><span class="cmp-d">${s.above ? `avg ${deg(s.avgAbove)} above` : "—"}</span></div>` +
|
||||
`</div>`;
|
||||
return `<div class="cmp-card">` +
|
||||
|
|
@ -301,8 +303,8 @@ function renderResults() {
|
|||
const win = scored[0];
|
||||
const span = `${monthLabel(range.start)} – ${monthLabel(range.end)}`;
|
||||
const lead = scored.length > 1
|
||||
? `<b>${win.loc.name}</b> best matches ${comfort}° — ${pct(win.s.comfortPct)} of days within ±${tol}°`
|
||||
: `<b>${win.loc.name}</b>: ${pct(win.s.comfortPct)} of days within ±${tol}° of ${comfort}°`;
|
||||
? `<b>${win.loc.name}</b> best matches ${fmtTemp(comfort)} — ${pct(win.s.comfortPct)} of days within ±${fmtDelta(tol)}`
|
||||
: `<b>${win.loc.name}</b>: ${pct(win.s.comfortPct)} of days within ±${fmtDelta(tol)} of ${fmtTemp(comfort)}`;
|
||||
head.hidden = false;
|
||||
head.innerHTML = `<h2>${lead}</h2>` +
|
||||
`<p class="meta">By ${BASIS_LABEL[basis]} · ${span}${loading ? ` · loading ${loading} more…` : ""}</p>`;
|
||||
|
|
@ -335,18 +337,27 @@ locList.addEventListener("click", (e) => {
|
|||
// Comfort / band / basis: instant re-rank over data already in hand — no refetch.
|
||||
comfortInput.addEventListener("input", () => {
|
||||
comfort = +comfortInput.value;
|
||||
comfortVal.textContent = `${comfort}°`;
|
||||
comfortVal.textContent = fmtTemp(comfort);
|
||||
lsSet(CMP.comfort, String(comfort));
|
||||
writeHashState();
|
||||
renderResults();
|
||||
});
|
||||
tolInput.addEventListener("input", () => {
|
||||
tol = +tolInput.value;
|
||||
tolVal.textContent = `±${tol}°`;
|
||||
tolVal.textContent = `±${fmtDelta(tol)}`;
|
||||
lsSet(CMP.tol, String(tol));
|
||||
writeHashState();
|
||||
renderResults();
|
||||
});
|
||||
|
||||
// The °F/°C toggle only flips what's shown — comfort/tol/series all stay in °F
|
||||
// (the API's unit), so there's nothing to refetch or re-rank, just re-render the
|
||||
// numbers: the two slider labels and every card.
|
||||
onUnitChange(() => {
|
||||
comfortVal.textContent = fmtTemp(comfort);
|
||||
tolVal.textContent = `±${fmtDelta(tol)}`;
|
||||
renderResults();
|
||||
});
|
||||
basisToggle.addEventListener("click", (e) => {
|
||||
const btn = e.target.closest("button[data-basis]");
|
||||
if (!btn) return;
|
||||
|
|
@ -373,8 +384,8 @@ clickOpensPicker(startInput, endInput); // whole-field tap opens the month pic
|
|||
if (hash.start && hash.end) range = { start: hash.start, end: hash.end };
|
||||
}
|
||||
|
||||
comfortInput.value = comfort; comfortVal.textContent = `${comfort}°`;
|
||||
tolInput.value = tol; tolVal.textContent = `±${tol}°`;
|
||||
comfortInput.value = comfort; comfortVal.textContent = fmtTemp(comfort);
|
||||
tolInput.value = tol; tolVal.textContent = `±${fmtDelta(tol)}`;
|
||||
basisToggle.querySelectorAll("button").forEach((b) => b.classList.toggle("active", b.dataset.basis === basis));
|
||||
startInput.value = range.start; endInput.value = range.end;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,15 @@ export function fmtTemp(vF, withLetter) {
|
|||
return `${Math.round(toUnit(vF))}°${withLetter ? _unit : ""}`;
|
||||
}
|
||||
|
||||
// A Fahrenheit-degree *difference* (a span or tolerance, not an absolute
|
||||
// reading) formatted in the active unit. Differences scale by 5/9 with no 32°
|
||||
// offset, so they can't go through toUnit/fmtTemp.
|
||||
export function fmtDelta(dF, withLetter) {
|
||||
if (dF == null || isNaN(dF)) return "—";
|
||||
const d = _unit === "C" ? dF * 5 / 9 : dF;
|
||||
return `${Math.round(d)}°${withLetter ? _unit : ""}`;
|
||||
}
|
||||
|
||||
export function onUnitChange(cb) { _unitCbs.push(cb); }
|
||||
|
||||
export function setUnit(u) {
|
||||
|
|
@ -39,8 +48,7 @@ function syncUnitToggle(wrap) {
|
|||
}
|
||||
|
||||
// Drop a segmented °F/°C control into the header's top-right, ahead of the view
|
||||
// switcher. A page opts out with data-no-unit-toggle on <body> (Compare does:
|
||||
// its comfort slider is inherently °F-based).
|
||||
// switcher. A page opts out with data-no-unit-toggle on <body>.
|
||||
(function buildUnitToggle() {
|
||||
const brand = document.querySelector(".brand");
|
||||
if (!brand || brand.querySelector(".unit-toggle")) return;
|
||||
|
|
|
|||
Loading…
Reference in a new issue