# DiagramMinimap

Where you are in something bigger than the window.

- Group: Diagrams
- Import: `import { DiagramMinimap } from '@misoto22/folio/diagrams'`
- Page: https://ui.misoto22.com/components/diagram-minimap/
- Related: diagram-canvas

## When to reach for it

Pairs with DiagramCanvas. The viewport rectangle is derived from the canvas’s own view, never stored — a map that disagrees with its territory is worse than none.

## Anatomy

- **Map frame** (required) — A fixed-width plate — 200px unless width says otherwise — whose height follows the artwork’s own aspect ratio, named as a group so it is not an unlabelled box beside the figure.
- **Miniature** (required) — The children under a CSS scale of width over content.width, anchored top-left, hidden from assistive technology and transparent to the pointer. Drawn only once content has a size — at width 0 the scale would be 1 and the miniature would be the artwork’s top-left corner at full size. It answers "what is there" at a glance and nothing more.
- **Viewport rectangle** (required) — An accent-washed rectangle computed from the canvas’s view and the map’s scale, clipped to the plate so a viewport panned past the artwork stops claiming a map that reaches further, and floored at a few pixels so a deep zoom still leaves something on screen to see. It is derived on every render and never stored.
- **Seek layer** — The transparent layer over the map that turns a press, or a drag that STARTED on it, into a point in CONTENT coordinates and hands it to onSeek. A button pressed elsewhere and dragged across it moves nothing. Nor does the map itself — it has no authority over the view.

## Best practices

### Do

- Feed it the canvas’s own onViewChange. The rectangle is arithmetic on those three numbers, so a map wired that way cannot disagree with the frame it maps — and keeping a second copy of where the viewport is, is exactly how one comes to.
- Measure content at the artwork’s natural CSS size. Everything here is scaled by width over content.width, so a wrong content width scales the miniature and the rectangle by the same wrong factor: the map still looks plausible and points at the wrong part of the picture. A width of 0 draws an empty plate instead, which is what a measurement that has not landed yet should look like.
- Let the canvas say how big the frame is. Every view a DiagramCanvas emits carries the frame it was measured against, so passing onViewChange through is enough — the frame prop is for a frame this component cannot be told about, and a hand-declared one is the single number that makes the rectangle lie.
- Send onSeek straight to the canvas handle’s centerOn. The point arrives in content coordinates, which is the space centerOn already takes, so recentring needs no conversion of your own.

### Don’t

- It is pointer-only and hidden from assistive technology: no tab stop, no keys, and the miniature deliberately publishes nothing because the figure it mirrors already does. The canvas’s arrow keys stay the keyboard’s route to a far corner, so the minimap must never be the only way to reach one.
- The miniature is the same markup shrunk, not a simplified drawing — every label goes down with it, so a wide figure at 200px is a shape rather than a reading copy. It answers where you are; what a node says is the figure’s job.

## Accessibility

- The miniature is aria-hidden. The figure it mirrors already publishes its own summary, and a second copy would read the whole diagram out twice.

## DiagramMinimap

Where you are in something bigger than the window. Two things at once, and both are needed: a miniature of the whole artwork, and a rectangle showing which part of it the frame is currently over. The miniature alone answers "what is there"; the rectangle answers "and where am I", which is the question a reader who has just panned twice actually has. THE RECTANGLE IS DERIVED, never stored. Its position comes out of the canvas's own view — the same three numbers the canvas is already transforming by — divided by the map's scale, and clamped to the plate: a viewport that has been panned past the edge of the artwork is a viewport half over blank paper, and drawing the rectangle out there claims the map extends somewhere it does not. Keeping a second copy of "where the viewport is" is how a minimap comes to disagree with the thing it is a map of, and a map that disagrees is worse than none. NOTHING IS DRAWN UNTIL THE ARTWORK HAS A SIZE. `content` is usually a measurement, and a measurement's first value is zero — so the scale would be 1, the miniature would be the artwork's top-left corner at full size, and the rectangle would sit over it meaning nothing. An empty plate for one frame is the honest version of "not yet". CLICKING RECENTRES rather than jumping. `onSeek` reports a point in CONTENT coordinates, which is what a canvas's `centerOn` takes. The minimap does not move anything itself: it has no authority over the view, it only says where the reader pointed. A drag keeps seeking, and only a drag that STARTED on the map does — a button held down somewhere else and dragged across is not this component's gesture to answer.

### Props

- `content` (required) — `{ width: number; height: number }`. The whole artwork, at its natural size in CSS pixels.
- `frame` — `{ width: number; height: number }`. The frame the artwork is being looked at through, in CSS pixels. Optional because a `DiagramCanvas` now reports its own frame on every view it emits: wire `onViewChange` straight through and this is already right. Pass it only for a frame this component cannot be told about.
- `view` (required) — `CanvasView`. Where that frame currently sits — a `DiagramCanvas`'s `onViewChange`.
- `children` — `ReactNode`. A miniature of the artwork. Usually the same figure, rendered again.
- `onSeek` — `(x: number, y: number) => void`. Called with a point in CONTENT coordinates when the reader picks one.
- `width` — `number` default `200`. How wide the map is, in CSS pixels. Height follows the aspect ratio.
- `className` — `string`.
- `label` — `string` default `'Diagram overview'`.

## Example — default

```tsx
<div className="flex flex-col gap-3">
  <DiagramCanvas ref={canvas} height="14rem" label="Request path" onViewChange={setView}>
    <ArchitectureFigure spec={SPEC} heading={false} legend="hidden" cards={false} />
  </DiagramCanvas>
  <DiagramMinimap
    content={{ width: 900, height: 340 }}
    frame={{ width: 640, height: 224 }}
    view={view}
    width={180}
    onSeek={(x, y) => canvas.current?.centerOn(x, y)}
  >
    <ArchitectureFigure spec={SPEC} heading={false} legend="hidden" cards={false} />
  </DiagramMinimap>
</div>
```

## Example — what the rectangle is

```tsx
import { ArchitectureFigure, DiagramMinimap } from '@misoto22/folio/diagrams'

<div className="flex flex-wrap items-start gap-6">
  {STATES.map((state) => (
    <div key={state.caption} className="flex flex-col gap-2">
      <DiagramMinimap
        content={CONTENT}
        frame={FRAME}
        view={state.view}
        width={150}
        label={`Overview, ${state.caption.toLowerCase()}`}
      >
        <ArchitectureFigure spec={SPEC} heading={false} legend="hidden" cards={false} />
      </DiagramMinimap>
      <span className="mono-meta text-(--ink-3-aa)">{state.caption}</span>
    </div>
  ))}
</div>
```

## Example — when a minimap earns its place

```tsx
import { ArchitectureFigure, DiagramMinimap } from '@misoto22/folio/diagrams'

<div className="flex flex-wrap items-start gap-8">
  <div className="flex flex-col gap-2">
    <DiagramMinimap
      content={CONTENT}
      frame={{ width: 400, height: 190 }}
      view={{ scale: 1, x: -240, y: -60 }}
      width={150}
      label="Overview, a window on the diagram"
    >
      <ArchitectureFigure spec={SPEC} heading={false} legend="hidden" cards={false} />
    </DiagramMinimap>
    <span className="mono-meta text-(--ink-3-aa)">400 × 190 of 900 × 340</span>
  </div>
  <div className="flex flex-col gap-2">
    <DiagramMinimap
      content={CONTENT}
      frame={CONTENT}
      view={{ scale: 1, x: 0, y: 0 }}
      width={150}
      label="Overview, the whole diagram in frame"
    >
      <ArchitectureFigure spec={SPEC} heading={false} legend="hidden" cards={false} />
    </DiagramMinimap>
    <span className="mono-meta text-(--ink-3-aa)">900 × 340 of 900 × 340</span>
  </div>
</div>
```
