Imbalance Scales

A static balance-scale visualization for binary class imbalance. Two pans hang from a horizontal beam pivoting at a central fulcrum; the beam tilts toward whichever class has more samples. Use it to motivate class weighting, oversampling, and balanced metrics (F1, balanced accuracy) on skewed datasets.

Imbalance scales: 1,000 positive vs 100 negative samples. Imbalance ratio 9.1%.
Customize
Counts
1000
100
Display

Installation

npx shadcn@latest add https://craftbits.dev/r/imbalance-scales.json

Usage

import { ImbalanceScales } from "@craft-bits/core";
 
<ImbalanceScales positiveCount={1000} negativeCount={100} />

Hide the raw pos : neg caption while keeping the imbalance percentage:

<ImbalanceScales
  positiveCount={1000}
  negativeCount={100}
  showRatio={false}
/>

Show only the scale with no captions at all:

<ImbalanceScales
  positiveCount={1000}
  negativeCount={100}
  showRatio={false}
  showImbalanceRatio={false}
/>

Understanding the component

  1. Two pans, one beam. The left pan is the positive class (cb-accent tone); the right pan is the negative class (cb-fg-muted tone). Both hang from chain lines anchored at the ends of a horizontal beam, with a fulcrum triangle at the centre.
  2. Tilt is proportional, not absolute. Beam rotation is atan2(negative − positive, max(positive, negative)) clamped to ±22°. Using atan2 over the heavier pan keeps the visual readable: a 1000 : 1 split tilts almost exactly the same as a 10 : 1 split (both pin to the clamp), while finer ratios in the 1 : 1 to 5 : 1 range get the full sweep.
  3. Pan contents stay upright. The pans translate with the rotating beam, but their inner content (sample-stack rectangle, count, class label) counter-rotates by −tilt so numbers never read sideways at extreme imbalances.
  4. One spring drives everything. Beam tilt animates on SPRINGS.smooth. Pan content inherits via the counter-rotation. Reduced-motion users get a { duration: 0 } transition — the scale snaps to its target tilt.
  5. Imbalance ratio is min / (min + max). A balanced split reads 50.0%; a 1000 : 100 split reads 9.1% — the lower the percentage, the more skewed the dataset.

Props

PropTypeDefaultDescription
positiveCountnumberSample count for the positive class. Negative values are clamped to 0.
negativeCountnumberSample count for the negative class. Negative values are clamped to 0.
showRatiobooleantrueRender the raw pos : neg caption underneath the scale.
showImbalanceRatiobooleantrueRender the min / (min + max) percentage caption.
classNamestringMerged onto the root <div> via cn().

Accessibility

  • The root <div> is role="figure" with a visually hidden summary of the two counts and the imbalance ratio.
  • The SVG carries role="img" and is labelled by the same hidden summary.
  • All decorative geometry is marked aria-hidden.
  • Motion respects prefers-reduced-motion: reduce — beam tilt collapses to an instant update.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/nn/ImbalanceScales.tsx). The source was a full lesson primitive with a drag-the-class-weight slider, ×99 balance celebration, phase narration, gradient-contribution bars, and a live equation readout. The library extract is the bare imbalance visual — pass two counts, get a tilted scale.