Rectangle Trick
A self-contained <svg> histogram with a candidate-rectangle overlay. Pass heights as a numeric array (one bar per entry) and a rectangle describing the currently-evaluated span — { left, right, height } — and the component draws every bar baseline-aligned plus a dashed translucent rectangle covering bars left..right at height. The classic teaching tool for the largest-rectangle-in-histogram problem: every monotonic-stack pop produces one of these candidate rectangles, and the answer is the maximum-area one.
Purely visual — no clicks, no internal state. Drive it from a step controller or any reducer that produces a rolling best-rectangle guess.
Installation
npx shadcn@latest add https://craftbits.dev/r/rectangle-trick.jsonUsage
import { RectangleTrick } from "@craft-bits/core";
<RectangleTrick
heights={[2, 1, 5, 6, 2, 3]}
rectangle={{ left: 2, right: 3, height: 5 }}
rectangleLabel="area = 10"
/>Bare histogram (no overlay):
<RectangleTrick heights={[2, 1, 5, 6, 2, 3]} />Drive the overlay from a monotonic-stack stepper — when a bar at index r pops the entry at index p, the resulting candidate spans (stackTop + 1)..(r - 1) at heights[p]:
<RectangleTrick
heights={heights}
rectangle={{ left: candidateLeft, right: candidateRight, height: candidateHeight }}
rectangleLabel={`area = ${candidateArea}`}
tone={candidateArea === best ? "success" : "accent"}
/>Tall thin bars, success tone for the running best:
<RectangleTrick
heights={heights}
rectangle={best}
rectangleLabel={`best area = ${bestArea}`}
barWidth={20}
plotHeight={200}
tone="success"
/>Understanding the component
- Self-contained SVG. One
<svg>sized to fit every bar plus the value / rectangle labels. The caller positions it; notop/left/transform: translateis set internally. - Inclusive span semantics.
rectangleis{ left, right, height }withrightinclusive — the rectangle coversright - left + 1bars. Out-of-range indices are clamped silently so the overlay never escapes the chart bounds. - Auto-scaled y-axis.
maxHeightdefaults to the tallest bar inheights, so the overlay always fills the available vertical space. PassmaxHeightexplicitly when you want stable scaling across step-to-step renders. - Five tones.
defaultreads as "neutral candidate",accentas "currently evaluating",successas "this is the best so far",warningas "watch this rectangle",erroras "rejected". Bars inside the overlay pick up the tone; bars outside stay neutral. - Bar values. Each bar carries its integer height above the baseline by default. Pass
showValues={false}for a cleaner static chart. - Spring transitions. Bar heights, rectangle position, and rectangle dimensions all animate with
SPRINGS.smooth. The label slides under the overlay rather than blinking, so scrub controls feel continuous. - Reduced motion. Bar enter animation, value-change transition, and overlay morph all collapse to instant under
prefers-reduced-motion: reduce.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
heights | number[] | required | Bar heights, left to right. Clamped to [0, maxHeight] at render. |
rectangle | { left, right, height } | — | Currently-evaluated rectangle overlay. Omit for the bare histogram. |
rectangleLabel | string | — | Optional label rendered under the overlay (e.g. "area = 10"). |
maxHeight | number | max(heights) | Maximum bar value. Falls back to the tallest height. |
tone | "default" | "accent" | "success" | "warning" | "error" | "accent" | Overlay palette. |
barWidth | number | 36 | Bar width in pixels. |
barGap | number | 6 | Gap between bars in pixels. |
plotHeight | number | 140 | Plot area height in pixels. |
showValues | boolean | true | Render the integer height above each bar. |
transition | Transition | SPRINGS.smooth | Override bar / overlay transitions. Reduced-motion users snap regardless. |
className | string | — | Merged onto the <svg> root via cn(). |
Accessibility
- The outer
<svg>isrole="img"with a<title>summarising the bar heights and the current rectangle span / height. Screen readers hear both the chart and the candidate without parsing SVG geometry. - The root exposes
data-has-rectangle(true/false) anddata-toneso consumer apps can hook custom styles or assistive tooling. - Every bar
<g>exposesdata-indexanddata-state(in-rectangle/rest) so step controllers can target specific bars via the DOM. - Tone is never the only signal — the overlay carries a dashed stroke and an optional area label, so colourblind users see the rectangle even when the highlight colour is hard to discriminate.
- Motion respects
prefers-reduced-motion: reduce— the enter animation and the candidate-morph spring collapse to instant.
Credits
- Extracted from:
algoflashcards(src/lessons/primitives/observation/RectangleTrick.tsx). The library extract reframes the primitive around the classic largest-rectangle-in-histogram visual — aheights[]row with one candidate overlay — and lets the caller compose any reducer-driven scoring on top.