Hide the grand-total column group in rollup mode

Rollup mode emits a grand-total column group alongside the subtotals. The
subtotals are the point -- they are what per-branch collapse needs -- but
the grand total sums across the split, and when the split is Prior Year /
Plan / Forecast that sum is meaningless while looking exactly like a real
figure to anyone scanning the sheet.

The engine cannot separate the two: t_totals is { BEFORE, HIDDEN, AFTER },
and in a rollup the grand total *is* the root of the hierarchy that produces
the subtotals. Turning it off turns the subtotals off with it. So it is
hidden rather than suppressed -- the view still computes the column, it is
simply not painted, and collapsed to zero width so there is no gap where it
was.

Targeted by psp-split-total, excluding psp-split-subtotal, which is how the
datagrid already distinguishes them. That survives adding a measure or
rearranging the pivot; hiding by column position would not -- the group
spans one column per measure in `columns`.

Injected into the plugin's shadow root, since a document stylesheet cannot
reach inside it, and re-asserted on config updates because the plugin
element is replaced when the plugin changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-09-17 13:03:46 -04:00
parent 4d2af589b9
commit 484798cac5

View File

@ -659,6 +659,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
// the named table in time. // the named table in time.
await viewer.load(tableRef.current) await viewer.load(tableRef.current)
viewer.setAttribute('theme', dark ? 'Pro Dark' : 'Pro Light') viewer.setAttribute('theme', dark ? 'Pro Dark' : 'Pro Light')
hideSplitTotal()
// restore last-used layout or build default // restore last-used layout or build default
// Strip cfg.table table is already loaded by reference above; a stale name // Strip cfg.table table is already loaded by reference above; a stale name
@ -705,6 +706,8 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
const live = await viewer.save() const live = await viewer.save()
adoptSplit(live.split_by || [], adoptSplit(live.split_by || [],
live.split_by_depth != null ? live.split_by_depth - 1 : null) live.split_by_depth != null ? live.split_by_depth - 1 : null)
// the plugin element is replaced when the plugin changes, so re-assert
hideSplitTotal()
const cfg = await captureConfig() const cfg = await captureConfig()
if (cfg) await persistLayout(vid, cfg) if (cfg) await persistLayout(vid, cfg)
} catch {} } catch {}
@ -809,6 +812,46 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
} }
} }
// In rollup mode the datagrid emits a grand-total column group as well as the
// subtotals. The subtotals are the point -- they are what per-branch collapse
// needs -- but the grand total sums across the split, and when the split is
// Prior Year / Plan / Forecast that sum is meaningless and reads as a real
// figure to anyone scanning the sheet.
//
// The engine cannot separate them: t_totals is { BEFORE, HIDDEN, AFTER }, and in
// a rollup the grand total *is* the root of the hierarchy that produces the
// subtotals. So it is hidden rather than suppressed -- the view still computes
// it, it is simply not painted.
//
// psp-split-total marks it and psp-split-subtotal marks the subtotals, so this
// survives adding measures or rearranging the pivot, unlike hiding by position.
// The rule has to live inside the plugin's shadow root; a document stylesheet
// cannot reach it.
const HIDE_SPLIT_TOTAL_CSS = `
th.psp-split-total:not(.psp-split-subtotal),
td.psp-split-total:not(.psp-split-subtotal) {
visibility: hidden !important;
width: 0 !important;
min-width: 0 !important;
max-width: 0 !important;
padding: 0 !important;
border-left-width: 0 !important;
border-right-width: 0 !important;
overflow: hidden !important;
}
`
function hideSplitTotal() {
const viewer = viewerRef.current
const root = viewer?.shadowRoot
?.querySelector('perspective-viewer-datagrid')?.shadowRoot
if (!root || root.getElementById?.('pf-hide-split-total')) return
const style = document.createElement('style')
style.id = 'pf-hide-split-total'
style.textContent = HIDE_SPLIT_TOTAL_CSS
root.appendChild(style)
}
// Row depth is a ViewConfig field, so it is set through restore() rather than // Row depth is a ViewConfig field, so it is set through restore() rather than
// by calling set_depth() on the view. That is the whole difference: the config // by calling set_depth() on the view. That is the whole difference: the config
// is what the viewer rebuilds its view *from*, so the depth survives every // is what the viewer rebuilds its view *from*, so the depth survives every