Compare: replace diverging bar with a labeled day-distribution bar (#71)
The diverging comfort bar read as noise — half the track sat empty and the lighter/saturated tolerance split was unlabeled, so the coloured chunks had no clear meaning. Replace it with one full-width bar that is 100% of the place's days, split into five temperature buckets ordered cold → hot in the site's own temperature colours: too cold, cool (within tolerance), comfortable, warm (within tolerance), too hot. Wide segments print their share inline, and a legend above the cards names every colour. More blue = runs cold, more red = runs hot, with no empty space to decode.
This commit is contained in:
parent
428dfebe83
commit
46c5006ce0
2 changed files with 52 additions and 28 deletions
|
|
@ -331,6 +331,17 @@ const pct = (v) => `${v < 10 ? v.toFixed(1) : Math.round(v)}%`;
|
|||
// Temperature *differences* (avg miss, typical miss) in the active unit.
|
||||
const deg = (v) => fmtDelta(v);
|
||||
|
||||
// Names the five bar colors once, above the ranked cards.
|
||||
const LEGEND =
|
||||
`<div class="cmp-legend" aria-hidden="true">` +
|
||||
`<span><i class="cmp-sw cmp-seg-vcold"></i>Too cold</span>` +
|
||||
`<span><i class="cmp-sw cmp-seg-cool"></i>Cool</span>` +
|
||||
`<span><i class="cmp-sw cmp-seg-comf"></i>Comfortable</span>` +
|
||||
`<span><i class="cmp-sw cmp-seg-warm"></i>Warm</span>` +
|
||||
`<span><i class="cmp-sw cmp-seg-vhot"></i>Too hot</span>` +
|
||||
`<span class="cmp-legend-note">each bar = 100% of days · “cool”/“warm” fall within your tolerance</span>` +
|
||||
`</div>`;
|
||||
|
||||
function renderLocList() {
|
||||
locList.innerHTML = locations.map((l, i) => {
|
||||
let label, cls = "cmp-chip", lead = "";
|
||||
|
|
@ -347,24 +358,29 @@ function renderLocList() {
|
|||
}).join("");
|
||||
}
|
||||
|
||||
// A ranked place: the 0–100 match score up top, then a diverging bar whose colder
|
||||
// days grow left of a fixed center (the comfort zone) and warmer days grow right.
|
||||
// Each side is split into a tolerance band (lighter, nearest the center) and a
|
||||
// beyond-tolerance band (saturated, further out), so how far a place misses — not
|
||||
// just how often — is visible, and a cold place and a hot one mirror each other.
|
||||
// A ranked place: the 0–100 match score up top, then one full-width bar showing how
|
||||
// this place's days split across five temperature buckets, ordered cold → hot using
|
||||
// the site's own temperature colors — too cold, cool (within tolerance), comfortable,
|
||||
// warm (within tolerance), too hot. The whole bar is 100% of the days, so every
|
||||
// segment is a real share and a place that runs cold vs one that runs hot read at a
|
||||
// glance (more blue vs more red). The legend above the cards names each color.
|
||||
function rankCard(loc, s, rank) {
|
||||
const win = rank === 1;
|
||||
const seg = (cls, w) => (w > 0 ? `<span class="cmp-dv-seg ${cls}" style="width:${w}%"></span>` : "");
|
||||
const bar = `<div class="cmp-dv" role="img" aria-label="match score ${Math.round(s.score)} of 100; ${pct(s.belowPct)} colder, ${pct(s.comfortPct)} in comfort, ${pct(s.abovePct)} warmer">` +
|
||||
`<div class="cmp-dv-side cmp-dv-left">${seg("cmp-seg-cold", s.belowOutPct)}${seg("cmp-seg-cool", s.belowTolPct)}</div>` +
|
||||
`<span class="cmp-dv-mid" aria-hidden="true"></span>` +
|
||||
`<div class="cmp-dv-side cmp-dv-right">${seg("cmp-seg-warm", s.aboveTolPct)}${seg("cmp-seg-hot", s.aboveOutPct)}</div></div>`;
|
||||
// Label a segment inline when it's wide enough to fit the number legibly.
|
||||
const seg = (cls, w) => (w > 0
|
||||
? `<span class="cmp-seg ${cls}" style="width:${w}%">${w >= 10 ? `<span class="cmp-seg-n">${Math.round(w)}%</span>` : ""}</span>`
|
||||
: "");
|
||||
const bar = `<div class="cmp-bar" role="img" aria-label="of all days: ${pct(s.belowOutPct)} too cold, ${pct(s.belowTolPct)} cool, ${pct(s.comfortPct)} comfortable, ${pct(s.aboveTolPct)} warm, ${pct(s.aboveOutPct)} too hot">` +
|
||||
seg("cmp-seg-vcold", s.belowOutPct) + seg("cmp-seg-cool", s.belowTolPct) +
|
||||
seg("cmp-seg-comf", s.comfortPct) +
|
||||
seg("cmp-seg-warm", s.aboveTolPct) + seg("cmp-seg-vhot", s.aboveOutPct) +
|
||||
`</div>`;
|
||||
const cap =
|
||||
`<div class="cmp-dv-cap">` +
|
||||
`<span class="cmp-cap cmp-cap-below">${pct(s.belowPct)} colder${s.below ? ` · avg ${deg(s.avgBelow)} under` : ""}</span>` +
|
||||
`<span class="cmp-cap cmp-cap-above">${s.above ? `avg ${deg(s.avgAbove)} over · ` : ""}${pct(s.abovePct)} warmer</span>` +
|
||||
`<div class="cmp-cap-row">` +
|
||||
`<span class="cmp-cap-below">${pct(s.belowPct)} below comfort${s.below ? ` · avg ${deg(s.avgBelow)} colder` : ""}</span>` +
|
||||
`<span class="cmp-cap-above">${s.above ? `avg ${deg(s.avgAbove)} hotter · ` : ""}${pct(s.abovePct)} above comfort</span>` +
|
||||
`</div>`;
|
||||
const bands = `<div class="cmp-bands">${pct(s.comfortPct)} in comfort · ${pct(s.tolPct)} in tolerance · typically off by <b>${deg(s.mad)}</b></div>`;
|
||||
const bands = `<div class="cmp-bands"><b>${pct(s.comfortPct)}</b> comfortable · <b>${pct(s.tolPct)}</b> within tolerance · typically <b>${deg(s.mad)}</b> off</div>`;
|
||||
const sub = win ? `<span class="cmp-best">★ Best match</span> · ${s.n} days` : `${s.n} days`;
|
||||
return `<div class="cmp-card${win ? " cmp-win" : ""}">` +
|
||||
`<div class="cmp-card-top">` +
|
||||
|
|
@ -447,7 +463,7 @@ function renderResults() {
|
|||
`<p class="meta">By ${BASIS_LABEL[basis]} · comfort ${fmtTemp(lo)}–${fmtTemp(hi)}, tolerance ${fmtTemp(tlo)}–${fmtTemp(thi)} · ${span}` +
|
||||
`${loading ? ` · <span class="cmp-loading-inline"><span class="cmp-spinner"></span>loading ${loading} more…</span>` : ""}</p>`;
|
||||
|
||||
results.innerHTML = scored.map((x, i) => rankCard(x.loc, x.s, i + 1)).join("") +
|
||||
results.innerHTML = LEGEND + scored.map((x, i) => rankCard(x.loc, x.s, i + 1)).join("") +
|
||||
(loading ? Array.from({ length: loading }, skeletonRow).join("") : "");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -847,19 +847,26 @@ main { max-width: 1200px; margin: 0 auto; padding: 20px 24px 60px; }
|
|||
/* Diverging comfort bar: a fixed center marks the comfort zone; colder days grow
|
||||
left of it, warmer days grow right. Both halves share one 0–100% scale, so a
|
||||
cold-skewed place and a hot-skewed one mirror each other and compare at a glance. */
|
||||
.cmp-dv { display: flex; align-items: stretch; height: 16px; margin: 14px 0 8px; }
|
||||
.cmp-dv-side { flex: 1 1 0; display: flex; background: var(--surface-2); overflow: hidden; }
|
||||
.cmp-dv-left { border-radius: 8px 0 0 8px; justify-content: flex-end; } /* colder packs toward center */
|
||||
.cmp-dv-right { border-radius: 0 8px 8px 0; justify-content: flex-start; } /* warmer packs toward center */
|
||||
.cmp-dv-seg { height: 100%; flex: 0 0 auto; }
|
||||
/* Nearest the centre = within tolerance (lighter); further out = beyond it (saturated). */
|
||||
.cmp-seg-cool { background: var(--cool); }
|
||||
.cmp-seg-cold { background: var(--cold); }
|
||||
.cmp-seg-warm { background: var(--warm); }
|
||||
.cmp-seg-hot { background: var(--hot); }
|
||||
.cmp-dv-mid { flex: 0 0 4px; background: var(--normal); } /* the comfort-zone marker */
|
||||
/* Legend naming the five bar colors once, above the cards. */
|
||||
.cmp-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; margin: 2px 0 4px; font-size: 12px; color: var(--muted); }
|
||||
.cmp-legend > span { display: inline-flex; align-items: center; gap: 6px; }
|
||||
.cmp-sw { width: 12px; height: 12px; border-radius: 3px; flex: 0 0 auto; }
|
||||
.cmp-legend-note { font-style: italic; opacity: .85; }
|
||||
|
||||
.cmp-dv-cap { display: flex; justify-content: space-between; gap: 10px; font-size: 12px; }
|
||||
/* Full-width day-distribution bar: five temperature buckets, cold → hot, together
|
||||
100% of the days, in the site's own temperature colors. Every segment is a real
|
||||
share, so more blue = runs cold, more red = runs hot — no empty space to decode. */
|
||||
.cmp-bar { display: flex; height: 22px; border-radius: 6px; overflow: hidden; margin: 14px 0 8px; background: var(--surface-2); }
|
||||
.cmp-seg { height: 100%; display: flex; align-items: center; justify-content: center; min-width: 0; }
|
||||
.cmp-seg-n { font-size: 10.5px; font-weight: 700; color: rgba(10, 15, 20, .68); }
|
||||
.cmp-seg-vcold .cmp-seg-n, .cmp-seg-comf .cmp-seg-n, .cmp-seg-vhot .cmp-seg-n { color: rgba(255, 255, 255, .95); }
|
||||
.cmp-seg-vcold { background: var(--very-cold); }
|
||||
.cmp-seg-cool { background: var(--cool); }
|
||||
.cmp-seg-comf { background: var(--normal); }
|
||||
.cmp-seg-warm { background: var(--warm); }
|
||||
.cmp-seg-vhot { background: var(--very-hot); }
|
||||
|
||||
.cmp-cap-row { display: flex; justify-content: space-between; gap: 10px; font-size: 12px; }
|
||||
.cmp-cap-below { color: var(--cold); }
|
||||
.cmp-cap-above { color: var(--hot); text-align: right; }
|
||||
.cmp-bands { margin: 8px 0 0; font-size: 12.5px; color: var(--muted); }
|
||||
|
|
@ -1058,5 +1065,6 @@ main { max-width: 1200px; margin: 0 auto; padding: 20px 24px 60px; }
|
|||
.cmp-bl-row { grid-template-columns: 84px 1fr; gap: 8px; }
|
||||
.cmp-bl-name { font-size: 12px; }
|
||||
.cmp-bl-axis { margin-left: 92px; }
|
||||
.cmp-dv-cap { font-size: 11px; }
|
||||
.cmp-cap-row { font-size: 11px; }
|
||||
.cmp-legend { font-size: 11px; gap: 5px 10px; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue