Data Mixture
A four-state training-mixture card: one composition bar per training stage split by data domain, with per-domain epochs so upsampling reads as the gap between unique tokens and tokens seen.
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/chart-data-mixture.jsonPrompt
Build a React + TypeScript + Tailwind "ChartDataMixture" card with zod. No chart
library: each stage is one horizontal composition bar laid out with flex
percentages, so text lands in HTML rather than in SVG.
Contract
- One zod schema is the single source of truth:
{ status: "loading" | "empty" | "error" | "ready"; title: string;
defaultView?: "unique" | "seen";
stages: { id, label, totalTokens >= 0,
domains: { id, label, tokens >= 0, epochs > 0 }[] }[] }.
- Props = z.infer of that schema plus precision? (decimals on every share,
clamped 0-2, default 1), onRetry?: () => void, className and the rest of the
div props spread on the root. No hand-written parallel interface.
- tokens and epochs stay separate numbers on purpose: tokens seen =
tokens * epochs, and the whole card is about the gap between the two.
- totalTokens is a check figure, not a denominator. Shares are apportioned over
the domains actually listed; a stage whose declared total is more than 0.5%
away from its domains is called out in the footnote.
Behavior
- Four first-class branches inside one bg-card panel:
- loading: a fixed silhouette of four label rows plus segmented tracks, the
same shape the real bars take, aria-hidden, with an sr-only role=status.
- empty: two hollow bars plus a line explaining what a mixture payload needs.
A "ready" payload with no stages, or with no positive tokens anywhere,
renders this branch rather than a blank card.
- error: message plus a "Try again" button rendered only when onRetry exists.
- ready: header, one bar per stage, the domain key, a readout line, a footnote
and an sr-only table.
- A radiogroup in the header switches the measure between "unique" (partition
the corpus) and "seen" (partition tokens * epochs). Roving tabindex, arrow
keys, Home and End; only handled keys call preventDefault. Every bar, every
label and every stage total re-apportions with it. defaultView seeds it once,
then the control owns the choice.
- Pointing at a segment, or focusing a key row, lights that domain in every
stage; activating the key row pins it (aria-pressed) and Escape clears both
the pin and the focus highlight, so the footnote's keyboard instruction is
true. Unselected segments mix their own token toward the card rather than
dropping opacity, so the highlight outline and the label halo stay at full
strength. The readout line shows the pinned domain stage by stage, otherwise
names the domain whose share grows most under replay, and when no share moves
reads off the replay factor instead — a run where every domain shares one
epoch count is not a single-pass run.
- Derived in-component, never passed in: tokens seen per domain and per stage,
the replay factor (seen / unique), how many domains a stage passes over more
than once, the run-wide token-weighted mean epochs per domain, and the shift
in points between a domain's two shares.
- Degenerate input is absorbed, not thrown: a non-finite or negative token count
reads as 0, a non-positive epoch count reads as 1, both are counted and
reported in the footnote, and a domain repeated inside one stage merges rather
than drawing two identically coloured segments.
Rendering & styling
- Shares are apportioned by largest remainder on a 10^precision grid, so each
bar totals exactly 100 in the labels and in the geometry — every segment is
laid out with the same rounded number it prints.
- Colour comes only from the chart tokens: domain i uses
var(--chart-{(i % 5) + 1}) for its segment, its key swatch and its highlight,
where i is the domain's position in the run-wide order, so a domain keeps its
colour across stages. Past the fifth domain a stripe and then a crosshatch
built from color-mix with var(--card) takes over instead of repeating a hue.
- In-bar labels appear only when the segment has the room: name plus share above
64px, share alone above 40px, nothing below that. Width comes from a
ResizeObserver on the stage column, disconnected on unmount and before every
rebuild. Text on a fill carries a text-shadow ring in var(--card), the HTML
equivalent of paint-order:stroke, which is what makes one text colour legal on
all five fills in both themes.
- Panel is rounded-xl border bg-card; every number is tabular-nums; segment
width transitions are wrapped in motion-reduce:transition-none, and the
skeleton pulse in motion-reduce:animate-none.
- Each bar is role="img" with a label naming every domain and share; the exact
numbers live in an sr-only table (stage, domain, unique tokens, epochs, tokens
seen, both shares) whose sr-only class sits on the wrapper, never the table.
Customization levers
- Density: BAR height (36px) and the two label thresholds decide how much lands
inside the bars; raise them for a compact card, drop the in-bar labels
entirely and let the key carry every number.
- Sub-blocks: the domain key, the readout line and the footnote are independent
— a model card usually keeps the key, a slide usually keeps only the bars.
- Measure: the two views are a literal union; add a third (for example "tokens
seen per parameter") by extending VIEWS and the shareOf/amountOf pair.
- Precision: precision 0 gives whole percents on a crowded bar, 2 keeps a 0.05%
domain distinguishable; the largest-remainder grid follows it.
- Palette: replace domainInk to pin a fixed token per domain id (brand colours
for in-house corpora) instead of cycling by position; the texture tiers are
what keep a mixture past five domains apart.
- Units: formatTokens scales to T / B / M / k — swap it for documents, hours or
bytes and the whole card follows, since nothing else formats a magnitude.Concepts
- Unique against seen — the same mixture partitioned twice: once over the corpus as it exists, once over
tokens × epochsas the optimiser reads it. Upsampling is invisible in the first view and is the entire story in the second, so the card refuses to pick one and makes the switch a first-class control. - Replay factor — tokens seen divided by unique tokens, per stage and for the run. It is the one number that says how much of a training budget was spent re-reading data rather than reading new data.
- Largest-remainder apportionment — every share gets its floor first and the leftover units go to the biggest fractional parts, so a bar totals exactly 100 in both views. Rounding each share on its own is what makes a full bar print 99.9% and leaves a gap at the end of the geometry.
- Position from the printed value — a segment is laid out with the same rounded share it prints and the key repeats, so nothing on the card is measured against a number the reader cannot see.
- Cross-stage domain highlight — a domain keeps its colour, its palette slot and its texture tier across every stage, so pinning it draws one continuous reading of how its share moves from pretrain to preference.
- Check figure, not denominator — the declared stage total is reconciled against the domains listed rather than trusted, because a mixture table that does not add up is the most common defect in a real training report.
Agent Trajectory
A four-state agent-run timeline — every step a measured segment on the run's own clock, coloured by kind, retries nested inside the step they re-attempt, tokens generated per step on a second track, and a wall-time-versus-generation mix apportioned by largest remainder so the shares add to exactly 100.
GPU Utilization
A four-state cluster-utilisation card: a device-by-time SM occupancy strip, one memory line per device group against the HBM ceiling, and idle stalls drawn as bubbles sized by their duration — with the idle share, the longest stall and a wall-clock budget that totals exactly 100% derived in-component.