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:
bowenliang123 committed 2026-10-17 09:35:33 +08:00
1 parent a4bec7d544
commit a03e606f08
2 files changed
+15 -11

No files matched your search

+8 -4
View File
@@ -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).