Pooling Explorer

A 2D pooling viz: render an input feature map, slide a poolSize × poolSize window across it in row-major order, and reduce each window with max, avg, or min. The window's contributing cell(s) glow in the input grid; the output matrix fills in one cell at a time as the window advances. Use it to teach CNN pooling layers, downsampling, translation invariance, or how the choice of pooling op shapes the response on a given neighbourhood.

Pooling explorer: 4×4 input, 2×2 Max pool, stride 2.Step 1 of 4. Max pool output (0, 0) = 8.
Input (4×4)
1
0
3
2
0
8
2
1
3
1
0
5
0
2
6
1
Pool
Max · 2×2
Output (2×2)
8
Customize
Pooling
max
2×2
Playback
600

Installation

npx shadcn@latest add https://craftbits.dev/r/pooling-explorer.json

Usage

import { PoolingExplorer } from "@craft-bits/core";
 
const input = [
  [1, 0, 3, 2],
  [0, 8, 2, 1],
  [3, 1, 0, 5],
  [0, 2, 6, 1],
];
 
<PoolingExplorer input={input} poolSize={2} defaultPool="max" defaultPlaying />;

Drive playback and pooling op from outside the component:

const [step, setStep] = useState(0);
const [pool, setPool] = useState<"max" | "avg" | "min">("max");
 
<PoolingExplorer
  input={input}
  poolSize={2}
  pool={pool}
  onPoolChange={setPool}
  currentStep={step}
  onCurrentStepChange={setStep}
/>;

Use overlapping windows with a smaller stride:

<PoolingExplorer input={image} poolSize={3} stride={2} />;

Understanding the component

  1. Row-major slide. currentStep is a single 0-indexed counter that counts output cells. Step 0 is the top-left output, step 1 the next column, etc. The window's position in input space is (step / outW, step % outW) * stride.
  2. Three reductions. max returns the largest value in the window; min the smallest; avg the arithmetic mean (rounded to two decimals). For max and min, the cell(s) matching the extreme value are highlighted as "contributing" — avg treats every cell equally.
  3. Translucent overlay over real cells. The input grid renders real <div> cells (numbered, color-tinted by magnitude). The window position is one motion.div overlaying the grid — sliding it via transform: translate keeps motion on the compositor and avoids reflowing every cell.
  4. SPRINGS.smooth for the slide. The window overlay animates with SPRINGS.smooth from @craft-bits/core/motion — slow enough to read, springy enough to feel like a real slide.
  5. Output fills in cell by cell. Cells before currentStep render their value at full opacity; the current cell highlights in --cb-accent-muted; future cells render dim and show .
  6. Stride aware. stride defaults to poolSize (non-overlapping); set lower for overlapping windows. Output dimensions follow floor((H - poolSize) / stride) + 1.
  7. setInterval autoplay. When playing, the component advances currentStep every playSpeed ms via a single window.setInterval. The interval is cleaned up on unmount, on pause, and whenever the step or dimensions change.
  8. Reduced-motion fallback. With prefers-reduced-motion: reduce, the window overlay snaps to position with no spring, and autoplay is disabled.

Props

PropTypeDefaultDescription
inputreadonly (readonly number[])[]required2D input matrix, rows × cols.
poolSizenumber2Side length of the square pooling window.
stridenumberpoolSizeStep between consecutive window positions.
pool"max" | "avg" | "min"Controlled pooling op.
defaultPool"max" | "avg" | "min""max"Uncontrolled initial pooling op.
onPoolChange(pool) => voidFires when the pooling op changes.
currentStepnumberControlled step (0-indexed).
defaultCurrentStepnumber0Uncontrolled initial step.
onCurrentStepChange(step: number) => voidFires on tick and on external changes.
playingbooleanControlled play state. Pair with onPlayingChange.
defaultPlayingbooleanfalseUncontrolled initial play state.
onPlayingChange(playing: boolean) => voidFires when play / pause flips.
playSpeednumber600Milliseconds between autoplay steps.
showOutputbooleantrueRender the output matrix to the right of the input.
cellSizenumber40Pixel size of an input cell.
transitionTransitionSPRINGS.smoothSpring for the window overlay slide.
classNamestringMerged onto the root <div> via cn().

Accessibility

  • The figure is role="figure" with a visually hidden caption announcing the input dimensions, pooling op, window size, and stride.
  • An aria-live="polite" summary announces the current step, pooling op, and the value of the output cell that just lit up whenever the window advances.
  • Color is never the only signal — every output cell renders its numeric value (or when not yet computed); the window overlay also draws a solid 2px outline in --cb-accent, and contributing input cells get an inset 2px ring so they remain visible at any color scale.
  • prefers-reduced-motion: reduce snaps the window overlay to position and disables autoplay; users can still scrub via the controlled currentStep prop.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/viz/PoolingExplorer.tsx). Stripped the lesson-specific Explore/Predict mode strip, 6-round predict quiz, narration heuristics, hover-to-trace interaction, and hardcoded 6×6 grid. Generalised to a pure pooling primitive: arbitrary input + window size + stride, max/avg/min ops, controlled + uncontrolled state pairs for pool, currentStep, and playing, and a playSpeed knob. Added contributing-cell highlighting so the argmax/argmin link to the input is explicit.