Frontend Kit
Dark-first chart + UI kit for forecasting work: 6 dependency-free Track-1 Chart.js blocks and 8 Track-2 React components. All color, spacing, radius and type come from the token source of truth (tokens/tokens.css + tokens/chart-preset.js, generated from react/src/tokens.ts).
Track 1 — Chart blocks
Each chart renders live inline with embedded LightGBM demo data. Signatures show the window.FK_* API. Baselines use the shared fkBaseline plugin (Chart.js v4 ignores zeroLineColor, so the kit never relies on it).
Importance — SHAP Importance
Mean |SHAP| horizontal bars, LightGBM / CatBoost demo.
window.FK_Importance.render(el, {rank, importance}, {color})
FK_Importance.render('c-importance',
{ rank: ['promo_depth','price','mech_T'],
importance: { promo_depth: .42, price: .31, mech_T: .18 } },
{ title: 'Importance · lightgbm' });
| Param | Shape | Default |
|---|---|---|
| rank | string[] — feature names, top-first | required |
| importance | Record<feature, mean|SHAP|> | required |
| opts.color | css color for all bars | #4C8DFF |
| opts.title | string — chart title | '' (hidden) |
| opts.height | number — canvas px height | auto |
Beeswarm — SHAP Beeswarm
SHAP distribution per feature (jittered points). Point color is a labeled continuous color scale over c (blue → red gradient), not a binary split.
window.FK_Beeswarm.render(el, {features, points: {x, yIdx, c}})
FK_Beeswarm.render('c-beeswarm',
{ features: ['promo_depth','price'],
points: [{ x: .21, yIdx: 0, c: .9 }, { x: -.12, yIdx: 1, c: .15 }] },
{ title: 'Beeswarm · lightgbm' });
| Param | Shape | Default |
|---|---|---|
| features | string[] — y-axis categories | required |
| points[].x | number — SHAP value | required |
| points[].yIdx | number — index into features | required |
| points[].c | number 0–1 — continuous feature value driving the blue→red gradient | 0.5 |
| opts.title | string | '' |
Dependence — SHAP Dependence
Feature value vs SHAP, colored by mechanic. The mechanism color legend (T / D / F / FD + other) is always visible below the chart.
window.FK_Dependence.render(el, {points: {x, y, m}}, {mechColors})
FK_Dependence.render('c-dependence',
{ points: [{ x: .4, y: .3, m: 'T' }] },
{ title: 'promo_depth · lightgbm' });
| Param | Shape | Default |
|---|---|---|
| points[].x | number|string — feature value (linear axis if all numeric) | required |
| points[].y | number — SHAP value | required |
| points[].m | 'T'|'D'|'F'|'FD'|other — mechanism key | 'other' → #64748D |
| opts.mechColors | Record<mech, css color> | {T:#4C8DFF,D:#2DD4BF,F:#F5A524,FD:#9E7BFF} |
| opts.title | string | '' |
Waterfall — SHAP Waterfall
True cumulative waterfall: floating bars accumulate each contribution from E[f(x)] to f(x). Zero line drawn by the fkBaseline plugin.
window.FK_Waterfall.render(el, {drivers: {feature, value, shap}}, {base})
FK_Waterfall.render('c-waterfall',
{ drivers: [{ feature: 'promo_depth', value: 0.4, shap: .32 },
{ feature: 'price', value: 2.99, shap: -.09 }] },
{ base: 0, title: 'Waterfall · lightgbm' });
| Param | Shape | Default |
|---|---|---|
| drivers[].feature | string — driver name | required |
| drivers[].value | number|string — feature value shown in label | required |
| drivers[].shap | number — signed contribution (bar floats from running total) | required |
| opts.base / data.base | number — E[f(x)] start of accumulation | 0 |
| opts.title | string | '' |
Ladder — Lift Ladder
Lift by mechanic (T / D / F / FD). Dashed line marks 1.0 (no lift) via the fkBaseline plugin.
Dashed line marks 1.0 (no lift) — bars above it are incremental.
window.FK_Ladder.render(el, {ladder: Record})
FK_Ladder.render('c-ladder',
{ ladder: { T: 1.42, D: 1.28, FD: 1.19, F: 1.08 } },
{ title: 'Lift ladder · lightgbm' });
| Param | Shape | Default |
|---|---|---|
| ladder | Record<mechanic, lift> — e.g. {T:1.42} | required |
| opts.title | string | '' |
| baseline | fixed dashed line at y=1.0 (not configurable) | 1.0 |
Quintile — Lift Quintile
Quintile lift curve (bands + lift). Dashed line marks 1.0 (no lift) via the fkBaseline plugin.
Dashed line marks 1.0 (no lift).
window.FK_Quintile.render(el, {bands, lift})
FK_Quintile.render('c-quintile',
{ bands: ['0-10%','10-25%','25-40%','40-55%','55%+'],
lift: [1.05, 1.18, 1.34, 1.52, 1.71] },
{ title: 'Lift by band · lightgbm' });
| Param | Shape | Default |
|---|---|---|
| bands | string[] — x labels | required |
| lift | number[] — same length as bands | required |
| opts.title | string | '' |
| baseline | fixed dashed line at y=1.0 | 1.0 |
Track 2 — React components (static mock states)
Representative static HTML mock states per component in kit styles (no live React here). Sources: react/src/components/*.tsx. All styling from react/src/tokens.ts.
AppLayout
App shell: fixed sidebar + header + centred content column (maxWidth default 1280).
AppLayout({ sidebar: ReactNode; header?: ReactNode; children: ReactNode; maxWidth?: number })
Default — shell with sidebar + header + content
Empty — no header
Sidebar
Dark sidebar nav; active item uses accent.primary; collapsed = icons only.
Sidebar({ sections: {label: string; icon: IconName; href: string; badge?: number}[]; active?: string; collapsed?: boolean; onNavigate?: (href: string) => void })
Default — active Overview
Collapsed — icons only
▦
▤
KpiCard
Value-led KPI card: 24/700 mono value, 12px muted label, sparkline + footnote.
KpiCard({ label: string; value: string; delta?: {v: string; dir: "up" | "down" | "flat"}; spark?: number[]; footnote?: string })
Default — value + delta + sparkline
Promo lift
1.42×
LightGBM · top quintile
Loading — skeleton
ForecastLineChart
Lightweight SVG forecast chart: interval band + actual + amber dashed forecast.
ForecastLineChart({ bands: string[]; actual?: (number | null)[]; forecast: number[]; lower?: number[]; upper?: number[]; onBandClick?: (i: number) => void; title?: string })
Default — band + actual + forecast
Empty — no forecast yet
ShapBar
Horizontal SHAP bars; one component covers importance / ladder / waterfall.
ShapBar({ rows: {label: string; value: number}[]; mode: "importance" | "waterfall" | "ladder"; onSelect?: (label: string) => void; title?: string })
Default — importance mode
Empty — no rows
DataTable
Enterprise table: sticky header option, 25-row pages, mono cells on demand.
DataTable({ columns: {key: string; header: string; mono?: boolean; align?: "left" | "right"; render?: (row: Record<string, unknown>) => ReactNode}[]; rows: Record<string, unknown>[]; pageSize?: number; stickyHeader?: boolean; caption?: string })
Default — 3 rows, mono lift column
| Mechanic | Lift |
|---|---|
| T | 1.42 |
| D | 1.28 |
| FD | 1.19 |
Empty — zero rows
ModelToggle
Pill model toggle (dark restyle of #models button.on); host re-renders on change.
ModelToggle({ value: "lightgbm" | "catboost"; onChange: (v: ModelValue) => void; options?: string[] })
Default — lightgbm selected
Loading — disabled options
Icon
Inline SVG icon set (stroke currentColor), 7 names.
Icon({ name: "promo" | "store" | "trend" | "table" | "model" | "warn" | "info"; size?: number; color?: string; style?: CSSProperties; title?: string })