Scope calendar strip percentages to their bar group (#59)
Following the per-group bar heights, the percentage labels now match: a multi-bar group (rain intensities on precip, streak lengths on dry streak) shows each bar's share within that group, so its bars read as a distribution. The lone opposing column (Dry on precip, Rain day on dry streak) keeps its share of all shown days, which stays meaningful rather than trivially 100%.
This commit is contained in:
parent
79d755c16e
commit
f4109f9ede
1 changed files with 18 additions and 4 deletions
|
|
@ -558,10 +558,24 @@ function renderTotals(visible) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const total = buckets.reduce((n, b) => n + b[2], 0);
|
const total = buckets.reduce((n, b) => n + b[2], 0);
|
||||||
const pctText = (n) => { const p = 100 * n / total, r = Math.round(p); return n > 0 && r === 0 ? `${p.toFixed(1)}%` : `${r}%`; };
|
// Per-group subtotals + membership, so a bar's share can be scoped to its own group.
|
||||||
|
const groupTotal = {}, groupCount = {};
|
||||||
|
for (const b of buckets) {
|
||||||
|
groupTotal[b[3]] = (groupTotal[b[3]] || 0) + b[2];
|
||||||
|
groupCount[b[3]] = (groupCount[b[3]] || 0) + 1;
|
||||||
|
}
|
||||||
|
// A multi-bar group (rain intensities on precip, streak lengths on dry streak) shows
|
||||||
|
// each bar's share *within that group* — matching the per-group bar heights. A lone
|
||||||
|
// opposing column (Dry on precip, Rain day on dry streak) stays a share of ALL shown
|
||||||
|
// days, since a group-relative percentage there would trivially read 100%.
|
||||||
|
const pctText = (n, group) => {
|
||||||
|
const d = groupCount[group] > 1 ? (groupTotal[group] || 1) : total;
|
||||||
|
const p = 100 * n / d, r = Math.round(p);
|
||||||
|
return n > 0 && r === 0 ? `${p.toFixed(1)}%` : `${r}%`;
|
||||||
|
};
|
||||||
// The strip prints either each category's share (default) or its raw day count,
|
// The strip prints either each category's share (default) or its raw day count,
|
||||||
// toggled by the "Show count" checkbox rendered in the header (state: showCount).
|
// toggled by the "Show count" checkbox rendered in the header (state: showCount).
|
||||||
const valText = (n) => !n ? "0" : (showCount ? n.toLocaleString() : pctText(n));
|
const valText = (n, group) => !n ? "0" : (showCount ? n.toLocaleString() : pctText(n, group));
|
||||||
const head = wActive
|
const head = wActive
|
||||||
? `${days.length.toLocaleString()} of ${visible.length.toLocaleString()} shown days match the weather filter — by ${title}:`
|
? `${days.length.toLocaleString()} of ${visible.length.toLocaleString()} shown days match the weather filter — by ${title}:`
|
||||||
: `${visible.length.toLocaleString()} days shown — by ${title}:`;
|
: `${visible.length.toLocaleString()} days shown — by ${title}:`;
|
||||||
|
|
@ -592,9 +606,9 @@ function renderTotals(visible) {
|
||||||
// Caption stacks the category name over its value; the name reserves two lines
|
// Caption stacks the category name over its value; the name reserves two lines
|
||||||
// so the value figures stay aligned across columns whether a name wraps or not.
|
// so the value figures stay aligned across columns whether a name wraps or not.
|
||||||
return `<div class="ct-col" style="--c:${color}" ` +
|
return `<div class="ct-col" style="--c:${color}" ` +
|
||||||
`title="${label} · ${pctText(n)} (${n})">` +
|
`title="${label} · ${pctText(n, group)} (${n})">` +
|
||||||
`<span class="ct-cap"><span class="ct-label">${label}</span>` +
|
`<span class="ct-cap"><span class="ct-label">${label}</span>` +
|
||||||
`<span class="ct-num${n ? "" : " ct-num-zero"}">${valText(n)}</span></span>` +
|
`<span class="ct-num${n ? "" : " ct-num-zero"}">${valText(n, group)}</span></span>` +
|
||||||
`${line}</div>`;
|
`${line}</div>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
totEl.innerHTML = `${header}
|
totEl.innerHTML = `${header}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue