Cap the row-label width, and never narrow a column

Each group_by level is its own row-header column, so fitting the first to
its longest label pushes the second to start after it — which reads as the
deeper level being indented past the end of the shallower one, and sends the
rest of the pivot off to the right when a label is long.

Capped at 260px, which trades a rare truncation for a sheet that stays
legible. And taking the max with the current width means Fit only ever
widens, so a width set by dragging is not undone by pressing it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-09-17 15:05:36 -04:00
parent 9ccaabd4c1
commit ec5d9e1dc3

View File

@ -1120,11 +1120,25 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
}
if (widest.size === 0) return
// Capped, and never narrowed.
//
// Each group_by level is its own row-header column, so widening the first to
// its longest label pushes the second to start after it which reads as the
// deeper level being indented past the end of the shallower one. Fitting the
// widest label exactly is therefore right for the label and wrong for the
// sheet, and a long customer name would push the rest of the pivot off to the
// right.
//
// The cap trades a rare truncation for a layout that stays legible. Taking
// the max with the current width means Fit never makes a column narrower
// than it already is, so a width set by dragging survives.
const MAX_ROW_LABEL_W = 260
const sizes = { ...grid.saveColumnSizes() }
for (const [idx, w] of widest) {
// A few px over: canvas metrics and rendered text differ slightly with font
// fallback and letter-spacing.
sizes[idx] = Math.ceil(w + padding + 8)
const fitted = Math.min(Math.ceil(w + padding + 8), MAX_ROW_LABEL_W)
sizes[idx] = Math.max(fitted, sizes[idx] ?? 0)
}
grid.restoreColumnSizes(sizes)
await grid.draw({ invalid_columns: true })