Rule off the column groups and mark their subtotals
Prior, plan and forecast each carry twelve months and a total, so scanning across is thirty-odd columns of identical-looking numbers with nothing to say where one domain ends and the next begins -- annual figures read as just another month. Each group's first column now takes a left rule and each group's subtotal a tint and a heavier weight. Both are derived from the cell's column path: the deepest path is a leaf, so anything shorter is an aggregate of the levels below it, which is what makes a subtotal a subtotal. Through regular_table's style listener rather than CSS, because the DOM cells are recycled across columns as you scroll -- a stylesheet would paint the wrong ones the moment the grid virtualised. The styles go into the grid's own shadow root, since a page stylesheet cannot reach it, and use currentColor so the rule follows the theme instead of disappearing against Pro Dark. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cdb40e7368
commit
54d49ebc75
@ -866,6 +866,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
||||
viewer.addEventListener('perspective-select', viewer._pspSelect)
|
||||
|
||||
gridRef.current = await viewer.getPlugin()
|
||||
applyGroupRules()
|
||||
setLargeDataset(false)
|
||||
|
||||
} catch (err) {
|
||||
@ -929,6 +930,67 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
||||
}
|
||||
}
|
||||
|
||||
// Rule off the column groups, and mark each group's subtotal.
|
||||
//
|
||||
// Scanning across "prior · plan · forecast, each with twelve months and a
|
||||
// total" is twelve columns of identical-looking numbers with nothing to say
|
||||
// where one domain ends and the next begins. The annual figures and the
|
||||
// monthly ones read as one run.
|
||||
//
|
||||
// Done through regular_table's style listener rather than CSS: which column
|
||||
// starts a group, and which one is a group's subtotal, are facts about the
|
||||
// data that only the cell metadata knows. The listener runs on every draw, so
|
||||
// it survives scrolling and virtualisation -- a stylesheet cannot, since the
|
||||
// DOM cells are recycled across columns as you scroll.
|
||||
function applyGroupRules() {
|
||||
const grid = gridRef.current
|
||||
const table = grid?.regular_table
|
||||
if (!table || table._pfGroupRules) return
|
||||
table._pfGroupRules = true
|
||||
|
||||
// The grid lives in a shadow root, so a stylesheet on the page cannot reach
|
||||
// these cells. Inject into whichever root actually contains the table.
|
||||
// currentColor rather than a fixed grey, so the rule follows the theme
|
||||
// instead of vanishing against Pro Dark.
|
||||
const root = table.getRootNode() || document
|
||||
if (!root.querySelector('#pf-group-rules')) {
|
||||
const style = document.createElement('style')
|
||||
style.id = 'pf-group-rules'
|
||||
style.textContent = `
|
||||
td.pf-group-start { border-left: 2px solid currentColor; opacity: 1; }
|
||||
td.pf-subtotal { font-weight: 600; background: color-mix(in srgb, currentColor 7%, transparent); }
|
||||
`
|
||||
;(root.head || root).appendChild(style)
|
||||
}
|
||||
|
||||
table.addStyleListener(() => {
|
||||
const body = table.querySelectorAll('tbody td')
|
||||
// The deepest column path is a leaf; anything shorter is an aggregate of
|
||||
// the levels below it, which is what makes a subtotal a subtotal.
|
||||
let depth = 0
|
||||
const metas = []
|
||||
for (const td of body) {
|
||||
let meta
|
||||
try { meta = table.getMeta(td) } catch { meta = null }
|
||||
metas.push([td, meta])
|
||||
const path = meta?.column_header
|
||||
if (Array.isArray(path)) depth = Math.max(depth, path.filter(v => v != null && v !== '').length)
|
||||
}
|
||||
|
||||
let prevGroup = null
|
||||
for (const [td, meta] of metas) {
|
||||
td.classList.remove('pf-group-start', 'pf-subtotal')
|
||||
const path = meta?.column_header
|
||||
if (!Array.isArray(path) || !path.length) { prevGroup = null; continue }
|
||||
const named = path.filter(v => v != null && v !== '')
|
||||
const group = named[0]
|
||||
if (group !== prevGroup) { td.classList.add('pf-group-start'); prevGroup = group }
|
||||
if (named.length < depth) td.classList.add('pf-subtotal')
|
||||
}
|
||||
})
|
||||
table.draw()
|
||||
}
|
||||
|
||||
// Size every column to its contents.
|
||||
//
|
||||
// Values fit on their own: draw() calls regular_table.resetAutoSize(), which
|
||||
|
||||
Loading…
Reference in New Issue
Block a user