fix(client): paint the DNA trend bars on the shared token scale
DnaBar's gradient stops were percentages of each bar's own total, so the browser rasterized common strands differently per bar (sub-pixel bands flipping visible/merged between steps) and inherited bands read thicker and thinner across the chart. Stops now ride the shared per-token pixel scale (CHART_H/maxTotal) and are byte-identical in every bar; the bar height reuses the same scale. Live-measured on a 99-bar session with the duration curve off: adjacent-bar common-row pixel diffs dropped 589 -> 11, the remainder being genuinely new top-of-bar strands.
This commit is contained in:
1 parent
a4bec7d544
commit
a03e606f08
2 files changed
+15
-11
No files matched your search
@@ -275,10 +275,14 @@ export function makeTrendChart(kit: ViewKit): (props: TrendChartProps) => ReactE
|
||||
const visible: TrendBand[] = []
|
||||
for (const b of props.bands) if (b.tokens > 0) visible.push(b)
|
||||
if (visible.length === 0) return null
|
||||
// Shared per-token pixel scale (CHART_H/maxTotal), not per-bar percentages: the same band's stops are
|
||||
// identical numbers in every bar, so the browser rasterizes common strands identically (per-bar %
|
||||
// stops quantized against each bar's own total and made sub-pixel strands flip visible/merged).
|
||||
const pxPerToken = CHART_H / props.maxTotal
|
||||
const runs: { color: string; from: number; to: number }[] = []
|
||||
for (const b of visible) {
|
||||
const from = Math.round(b.off / props.total * 10000) / 100
|
||||
const to = Math.round((b.off + b.tokens) / props.total * 10000) / 100
|
||||
const from = Math.round(b.off * pxPerToken * 100) / 100
|
||||
const to = Math.round((b.off + b.tokens) * pxPerToken * 100) / 100
|
||||
const last = runs.length > 0 ? runs[runs.length - 1] : null
|
||||
if (last !== null && last.color === b.color && last.to === from) last.to = to
|
||||
else runs.push({ color: b.color, from, to })
|
||||
@@ -299,8 +303,8 @@ export function makeTrendChart(kit: ViewKit): (props: TrendChartProps) => ReactE
|
||||
<div
|
||||
className={'lc-bar-dna' + (props.rise ? RISE_CLASS : '')}
|
||||
style={{
|
||||
height: `${Math.max(1, Math.round(props.total / props.maxTotal * CHART_H))}px`,
|
||||
background: 'linear-gradient(to top, ' + runs.map(r => `${r.color} ${r.from}%, ${r.color} ${r.to}%`).join(', ') + ')',
|
||||
height: `${Math.max(1, Math.round(props.total * pxPerToken))}px`,
|
||||
background: 'linear-gradient(to top, ' + runs.map(r => `${r.color} ${r.from}px, ${r.color} ${r.to}px`).join(', ') + ')',
|
||||
'--lc-i': Math.min(props.enterIndex, STAGGER_CAP),
|
||||
} as CSSProperties}
|
||||
onMouseMove={(e) => { props.onHit(hitAt(e).key) }}
|
||||
|
||||
@@ -52,17 +52,17 @@ describe('TrendChart DNA mode', () => {
|
||||
assert.equal(dnaDivs.length, 2)
|
||||
assert.equal(dnaDivs[0].style.height, `${Math.round(300 / 600 * CHART_H)}px`)
|
||||
assert.equal(dnaDivs[1].style.height, `${CHART_H}px`)
|
||||
// d1: the two adjacent assistant bands coalesce into ONE blue run (50% → 100%).
|
||||
// d1: the two adjacent assistant bands coalesce into ONE blue run (28px → 56px on the shared scale).
|
||||
const g1 = dnaDivs[0].style.background
|
||||
assert.ok(g1.startsWith('linear-gradient(to top, '), g1)
|
||||
assert.ok(g1.includes('var(--color-indigo-500) 0%, var(--color-indigo-500) 33.33%'), g1)
|
||||
assert.ok(g1.includes('var(--color-amber-500) 33.33%, var(--color-amber-500) 50%'), g1)
|
||||
assert.ok(g1.includes('var(--color-blue-500) 50%, var(--color-blue-500) 100%'), g1)
|
||||
assert.ok(g1.includes('var(--color-indigo-500) 0px, var(--color-indigo-500) 18.67px'), g1)
|
||||
assert.ok(g1.includes('var(--color-amber-500) 18.67px, var(--color-amber-500) 28px'), g1)
|
||||
assert.ok(g1.includes('var(--color-blue-500) 28px, var(--color-blue-500) 56px'), g1)
|
||||
// d2: the zero-token user band leaves no run of its own; the teal run starts at its offset.
|
||||
const g2 = dnaDivs[1].style.background
|
||||
assert.ok(g2.includes('var(--color-blue-500) 33.33%, var(--color-blue-500) 46.67%'), g2)
|
||||
assert.ok(g2.includes('var(--color-green-500) 46.67%, var(--color-green-500) 63.33%'), g2)
|
||||
assert.ok(g2.includes('var(--color-teal-500) 63.33%, var(--color-teal-500) 100%'), g2)
|
||||
assert.ok(g2.includes('var(--color-blue-500) 37.33px, var(--color-blue-500) 52.27px'), g2)
|
||||
assert.ok(g2.includes('var(--color-green-500) 52.27px, var(--color-green-500) 70.93px'), g2)
|
||||
assert.ok(g2.includes('var(--color-teal-500) 70.93px, var(--color-teal-500) 112px'), g2)
|
||||
assert.equal(queryAll(m.container, '.lc-bar-stack').length, 0)
|
||||
assert.equal(query(m.container, '.lc-axis-top').textContent, '600')
|
||||
// Without an onPickBand handler a band click is a quiet no-op (the pick guard's undefined arm).
|
||||
|
||||
Reference in new issue
Block a user