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).

Tokens: tokens/tokens.css · tokens/chart-preset.js  ·  bg #0C111B · card #151C2B · text #E8EDF5/#9AA7BE · accent #4C8DFF

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' });
Parameters
ParamShapeDefault
rankstring[] — feature names, top-firstrequired
importanceRecord<feature, mean|SHAP|>required
opts.colorcss color for all bars#4C8DFF
opts.titlestring — chart title'' (hidden)
opts.heightnumber — canvas px heightauto

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.

low chigh ccontinuous scale
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' });
Parameters
ParamShapeDefault
featuresstring[] — y-axis categoriesrequired
points[].xnumber — SHAP valuerequired
points[].yIdxnumber — index into featuresrequired
points[].cnumber 0–1 — continuous feature value driving the blue→red gradient0.5
opts.titlestring''

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.

TDFFDother
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' });
Parameters
ParamShapeDefault
points[].xnumber|string — feature value (linear axis if all numeric)required
points[].ynumber — SHAP valuerequired
points[].m'T'|'D'|'F'|'FD'|other — mechanism key'other' → #64748D
opts.mechColorsRecord<mech, css color>{T:#4C8DFF,D:#2DD4BF,F:#F5A524,FD:#9E7BFF}
opts.titlestring''

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' });
Parameters
ParamShapeDefault
drivers[].featurestring — driver namerequired
drivers[].valuenumber|string — feature value shown in labelrequired
drivers[].shapnumber — signed contribution (bar floats from running total)required
opts.base / data.basenumber — E[f(x)] start of accumulation0
opts.titlestring''

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' });
Parameters
ParamShapeDefault
ladderRecord<mechanic, lift> — e.g. {T:1.42}required
opts.titlestring''
baselinefixed 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' });
Parameters
ParamShapeDefault
bandsstring[] — x labelsrequired
liftnumber[] — same length as bandsrequired
opts.titlestring''
baselinefixed dashed line at y=1.01.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

nav
· Overview
· Models
Header
content · maxWidth 1280

Empty — no header

AppLayout without header: sidebar + content only

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

◉ Overview
▦ Models 3
▤ Tables

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×

▲ +0.08 vs last week

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

— actual · - - forecast - - · band = interval

Empty — no forecast yet

No forecast rows — run a model to populate bands

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

promo_depth
.42
price
.31
mech_T
.18

Empty — no rows

No SHAP rows for this mode

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

MechanicLift
T1.42
D1.28
FD1.19

Empty — zero rows

No rows match the current filter

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 })

Default — 7 icons

promo store trend table model warn info

Empty — unknown name fallback

Unknown icon name renders nothing (no crash)