# Layout — Incremental

Import these from `@grafloria/engine`.

## Functions

### `affectedRegion`

The nodes allowed to move: the changed set grown by `radius` hops.

This is what "re-run layout only in
the affected region" means concretely — and it is only possible because anchors
are now real: the layout works AROUND the frozen nodes instead of laying out over
them and being corrected afterwards.

```ts
function affectedRegion(
  diagram: DiagramModel,
  changed: string[],
  radius: number
): Set<string>
```

### `alignToPrevious`

The translation that minimises total squared displacement between two layouts.

For a pure translation the optimum is exactly the difference of the centroids of
the shared nodes — no search, no iteration. This is the single highest-value
function in the file: a layered layout is defined only up to translation, so a
new node widening rank 0 can shift the ENTIRE drawing sideways. Every node then
"moves" although the picture is unchanged, the movement budget blows, and a
naive implementation starts fighting its own layout with constraints. Align
first; constrain only what is left.

```ts
function alignToPrevious(next: Positions, previous: Positions): { positions: Positions; shift: { x: number; y: number } }
```

### `constraintsForStrategy`

Note what this does NOT do: it does not emit positions to be clamped afterwards. It emits ANCHORS, which the layered engine honours during coordinate assignment —
the whole reason the four scaffolded strategies never worked.

```ts
function constraintsForStrategy(
  diagram: DiagramModel,
  before: Positions,
  options: IncrementalOptions
): SemanticConstraints
```

### `measureMovement`

Measure how much the mental map was disturbed.

Only nodes that existed BEFORE count: a new node cannot "move", and including it
would flatter the numbers exactly when the layout is at its most disruptive.

```ts
function measureMovement(
  before: Positions,
  after: Positions,
  budget: MovementBudget | undefined,
  savedByAlignment = 0
): MovementReport
```

### `planTween`

```ts
function planTween(before: Positions, after: Positions): TweenPlan
```

## Interfaces

### `IncrementalOptions`

```ts
interface IncrementalOptions
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `strategy?` | `IncrementalStrategy` |  |  |
| `changed?` | `string[]` |  | Node ids the user just added/edited. Everything else is "existing". |
| `radius?` | `number` |  | How far the disturbance is allowed to spread, in graph hops. Default 1. |
| `budget?` | `MovementBudget` |  |  |

### `MovementBudget`

```ts
interface MovementBudget
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `maxPerNode?` | `number` |  | No single node may move further than this. |
| `averagePerNode?` | `number` |  | The mean movement across all nodes that existed before. |

### `MovementReport`

```ts
interface MovementReport
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `total` | `number` |  | Sum of the distances every pre-existing node travelled. |
| `average` | `number` |  |  |
| `max` | `number` |  |  |
| `moved` | `number` |  | How many pre-existing nodes moved at all (beyond a 0.5px epsilon). |
| `unmoved` | `number` |  | Of the pre-existing nodes, how many stayed exactly put. |
| `withinBudget` | `boolean` |  | Did we stay inside the caller's budget? |
| `savedByAlignment` | `number` |  | How much of the movement the centroid re-alignment removed. |

### `TweenPlan`

A tween plan: pure data, so the engine stays free of rAF/DOM/time.

The card asks for "an animated tweened transition from old to new positions
instead of snapping". The engine's job is to say WHERE things go at time t; the
host's job is to drive t. Keeping it that way is what lets the same code run in
a worker, in SSR, and in a test.

```ts
interface TweenPlan
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `movingIds` | `string[]` |  | Nodes that actually move — a host can skip the rest entirely. |

**Members**

- `at(t: number): Positions` — Positions at normalised time t ∈ [0, 1]. Eased.

## Types

### `IncrementalStrategy`

Also has every member of `String`, listed on its own entry.

```ts
type IncrementalStrategy =

  | 'region'

  | 'pin-existing'

  | 'minimal-shift';
```

### `Positions`

```ts
type Positions = Map<string, { x: number; y: number }>;
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `number` |  |  |

**Members**

- `clear(): void`
- `delete(key: K): boolean`
- `forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void` — Executes a provided function once per each key/value pair in the Map, in insertion order.
- `get(key: K): V | undefined` — Returns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map.
- `has(key: K): boolean`
- `set(key: K, value: V): this` — Adds a new element with a specified key and value to the Map. If an element with the same key already exists, the element will be updated.
- `entries(): MapIterator<[K, V]>` — Returns an iterable of key, value pairs for every entry in the map.
- `keys(): MapIterator<K>` — Returns an iterable of keys in the map
- `values(): MapIterator<V>` — Returns an iterable of values in the map
- `[Symbol.iterator](): MapIterator<[K, V]>` — Returns an iterable of entries in the map.
- `readonly [Symbol.toStringTag]: string`
