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 { initFindButton } from "./mappicker.js";
|
||||||
import { MONTHS, pad, isoOfDate, monthStart, monthEnd, buildChunks,
|
import { MONTHS, pad, isoOfDate, monthStart, monthEnd, buildChunks,
|
||||||
clickOpensPicker } from "./shared.js";
|
clickOpensPicker } from "./shared.js";
|
||||||
|
import { fmtTemp, fmtDelta, onUnitChange } from "./units.js";
|
||||||
|
|
||||||
const CMP = {
|
const CMP = {
|
||||||
comfort: "thermograph:cmpComfort",
|
comfort: "thermograph:cmpComfort",
|
||||||
|
|
@ -243,7 +244,8 @@ function computeStats(series) {
|
||||||
|
|
||||||
// ---- render ----
|
// ---- render ----
|
||||||
const pct = (v) => `${v < 10 ? v.toFixed(1) : Math.round(v)}%`;
|
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() {
|
function renderLocList() {
|
||||||
locList.innerHTML = locations.map((l, i) => {
|
locList.innerHTML = locations.map((l, i) => {
|
||||||
|
|
@ -269,7 +271,7 @@ function rankCard(loc, s, rank) {
|
||||||
const stats =
|
const stats =
|
||||||
`<div class="cmp-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-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 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>`;
|
`</div>`;
|
||||||
return `<div class="cmp-card">` +
|
return `<div class="cmp-card">` +
|
||||||
|
|
@ -301,8 +303,8 @@ function renderResults() {
|
||||||
const win = scored[0];
|
const win = scored[0];
|
||||||
const span = `${monthLabel(range.start)} – ${monthLabel(range.end)}`;
|
const span = `${monthLabel(range.start)} – ${monthLabel(range.end)}`;
|
||||||
const lead = scored.length > 1
|
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> 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 ±${tol}° of ${comfort}°`;
|
: `<b>${win.loc.name}</b>: ${pct(win.s.comfortPct)} of days within ±${fmtDelta(tol)} of ${fmtTemp(comfort)}`;
|
||||||
head.hidden = false;
|
head.hidden = false;
|
||||||
head.innerHTML = `<h2>${lead}</h2>` +
|
head.innerHTML = `<h2>${lead}</h2>` +
|
||||||
`<p class="meta">By ${BASIS_LABEL[basis]} · ${span}${loading ? ` · loading ${loading} more…` : ""}</p>`;
|
`<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.
|
// Comfort / band / basis: instant re-rank over data already in hand — no refetch.
|
||||||
comfortInput.addEventListener("input", () => {
|
comfortInput.addEventListener("input", () => {
|
||||||
comfort = +comfortInput.value;
|
comfort = +comfortInput.value;
|
||||||
comfortVal.textContent = `${comfort}°`;
|
comfortVal.textContent = fmtTemp(comfort);
|
||||||
lsSet(CMP.comfort, String(comfort));
|
lsSet(CMP.comfort, String(comfort));
|
||||||
writeHashState();
|
writeHashState();
|
||||||
renderResults();
|
renderResults();
|
||||||
});
|
});
|
||||||
tolInput.addEventListener("input", () => {
|
tolInput.addEventListener("input", () => {
|
||||||
tol = +tolInput.value;
|
tol = +tolInput.value;
|
||||||
tolVal.textContent = `±${tol}°`;
|
tolVal.textContent = `±${fmtDelta(tol)}`;
|
||||||
lsSet(CMP.tol, String(tol));
|
lsSet(CMP.tol, String(tol));
|
||||||
writeHashState();
|
writeHashState();
|
||||||
renderResults();
|
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) => {
|
basisToggle.addEventListener("click", (e) => {
|
||||||
const btn = e.target.closest("button[data-basis]");
|
const btn = e.target.closest("button[data-basis]");
|
||||||
if (!btn) return;
|
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 };
|
if (hash.start && hash.end) range = { start: hash.start, end: hash.end };
|
||||||
}
|
}
|
||||||
|
|
||||||
comfortInput.value = comfort; comfortVal.textContent = `${comfort}°`;
|
comfortInput.value = comfort; comfortVal.textContent = fmtTemp(comfort);
|
||||||
tolInput.value = tol; tolVal.textContent = `±${tol}°`;
|
tolInput.value = tol; tolVal.textContent = `±${fmtDelta(tol)}`;
|
||||||
basisToggle.querySelectorAll("button").forEach((b) => b.classList.toggle("active", b.dataset.basis === basis));
|
basisToggle.querySelectorAll("button").forEach((b) => b.classList.toggle("active", b.dataset.basis === basis));
|
||||||
startInput.value = range.start; endInput.value = range.end;
|
startInput.value = range.start; endInput.value = range.end;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,15 @@ export function fmtTemp(vF, withLetter) {
|
||||||
return `${Math.round(toUnit(vF))}°${withLetter ? _unit : ""}`;
|
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 onUnitChange(cb) { _unitCbs.push(cb); }
|
||||||
|
|
||||||
export function setUnit(u) {
|
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
|
// 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:
|
// switcher. A page opts out with data-no-unit-toggle on <body>.
|
||||||
// its comfort slider is inherently °F-based).
|
|
||||||
(function buildUnitToggle() {
|
(function buildUnitToggle() {
|
||||||
const brand = document.querySelector(".brand");
|
const brand = document.querySelector(".brand");
|
||||||
if (!brand || brand.querySelector(".unit-toggle")) return;
|
if (!brand || brand.querySelector(".unit-toggle")) return;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue