Distribution bars: split the in-bar range into Max/Min rows (#84)
The min–max range inside each distribution bar packed onto one line (e.g. -20–-16°), where the range dash collides with the minus signs and becomes unreadable for negative temperatures. Split it into two labelled rows — Max on top, Min below — so each value stands alone. The per-tier average above each bar is unchanged. Shared by the Compare distribution and the Calendar totals strip. Bump the bar-height floor 26→30px so the two-line label never clips.
This commit is contained in:
parent
de02ebcf19
commit
3fbaffd876
2 changed files with 20 additions and 21 deletions
|
|
@ -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
|
||||
? `<div class="ct-bar" style="height:${h}px">${lo != null ? `<span class="ct-range">${fmtMetricRange(b.unit, lo, hi)}</span>` : ""}</div>`
|
||||
? `<div class="ct-bar" style="height:${h}px">${lo != null
|
||||
? `<span class="ct-range">` +
|
||||
`<span class="ct-mm"><span class="ct-mm-k">Max</span>${fmtMetricVal(b.unit, hi)}</span>` +
|
||||
`<span class="ct-mm"><span class="ct-mm-k">Min</span>${fmtMetricVal(b.unit, lo)}</span>` +
|
||||
`</span>`
|
||||
: ""}</div>`
|
||||
: `<div class="ct-bar ct-bar-0"></div>`;
|
||||
return `<div class="ct-col${b.cls ? ` ct-${b.cls}` : ""}" style="--c:${b.color}" ` +
|
||||
`title="${b.label} · ${pctText(b.n, b.group)} (${b.n})${avg != null ? ` · avg ${fmtMetricVal(b.unit, avg)}` : ""}">` +
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue