Column Blender

An interactive picture of the column view of Ax. Two column vectors sit at the origin of a Cartesian plane; sliders scale each one; dashed ghost arrows show the weighted columns; a green result arrow sums them. An optional row-view panel cross-checks the same product via the dot-product formula so the learner sees that the two algorithms — sum the columns, or dot the rows — produce the same vector.

col₀col₁Ax

Recipe

x[0] = 2
x[1] = 2

2 · [1, 3] + 2 · [2, 4] = [6, 14]

Customize
Canvas
320 px
±4
Matrix A (column-major)
1
3
2
4
Initial x
2
2
Story

Installation

npx shadcn@latest add https://craftbits.dev/r/column-blender.json

Usage

import { ColumnBlender } from "@craft-bits/viz/column-blender";
 
<ColumnBlender
  matrixA={[1, 3, 2, 4]}
  initialX={[2, 2]}
  presets={[{ label: "Load x = [5, 6]", w0: 5, w1: 6 }]}
  showRowView
  onWeightsChange={([w0, w1]) => console.log(w0, w1)}
/>

The matrix is column-major: [col0.x, col0.y, col1.x, col1.y] corresponds to

A = | col0.x  col1.x |
    | col0.y  col1.y |

Strip everything down to the bare visualizer — no presets, no row-view:

<ColumnBlender matrixA={[2, 0, 0, 2]} initialX={[1, 1]} />

Stack multiple presets to walk through a small recipe deck:

<ColumnBlender
  matrixA={[1, 3, 2, 4]}
  presets={[
    { label: "x = [1, 0]", w0: 1, w1: 0 },
    { label: "x = [0, 1]", w0: 0, w1: 1 },
    { label: "x = [5, 6]", w0: 5, w1: 6 },
  ]}
/>

Understanding the component

  1. Two base arrows. Column 0 paints in the accent token, column 1 in the warning token. Both are anchored at the origin and persist regardless of the slider values — they are the vocabulary of the matrix.
  2. Two weighted ghost arrows. Whenever the weighted column has non-trivial magnitude the component draws a dashed arrow of that column scaled by its weight. Ghosts share the colour of their base column so the eye tracks them automatically.
  3. The result arrow. A solid success-coloured arrow runs from the origin to w0·col0 + w1·col1. Two short dashed construction lines connect the ghost tips to the result tip to make the parallelogram closure visible.
  4. Lerp on preset load. Pressing a preset pill smoothly eases w0 and w1 to the target over ~300ms (cubic ease-out) so the learner sees the recipe being assembled. Dragging a slider cancels the lerp.
  5. Row-view cross-check. When showRowView is set, a "Verify with row view" button reveals row₀ · x and row₁ · x written out and confirms they equal the result coordinates — the visual claim that the two algorithms agree.
  6. Reduced motion. Under prefers-reduced-motion: reduce the preset lerp is replaced by an instant snap; everything else is static.

Props

PropTypeDefaultDescription
matrixA[number, number, number, number][1, 3, 2, 4]The 2×2 matrix in column-major order.
initialX[number, number][2, 2]Initial weights applied to the two columns.
weightMinnumber-6Slider lower bound for each weight.
weightMaxnumber6Slider upper bound for each weight.
weightStepnumber0.5Slider step.
rangenumber4Half-range of the visible axis.
sizenumber320Pixel size of the square SVG canvas.
presetsColumnBlenderPreset[]Optional weight presets rendered as pill buttons. Loading a preset smoothly lerps the weights.
showRowViewbooleanfalseWhen true, reveals a "Verify with row view" panel.
onWeightsChange([w0, w1]) => voidCalled whenever either weight changes.
classNamestringMerged onto the root via cn().

Accessibility

  • The SVG canvas is role="img" with an aria-label that describes both columns and the current weights/result so screen readers can read the visual state at a glance.
  • Each slider has a dedicated aria-label ("Weight for column 0" / "Weight for column 1").
  • Colour is reinforced with position and label text: weights show next to slider tracks, the formula readout repeats w0/w1/result in their respective token colours, and the row-view panel duplicates the result digits.
  • Motion respects prefers-reduced-motion: reduce — preset loading snaps instantly instead of lerping; slider drags are unaffected either way.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/math/ColumnBlender.tsx). The source was a hand-rolled lesson primitive with built-in ModeStrip/ChallengeBtn chrome, a narration banner driven by a phase machine, a hard-coded Load x = [5, 6] preset, and a project-specific accent/warn/success/ink palette. The viz extract drops the lesson chrome, generalises the preset list and slider range, lifts the "Verify with row view" toggle to a simple boolean prop, repaints onto the craft-bits accent/warning/success/fg tokens, and exposes onWeightsChange for callers that want to mirror state outside the component.