# Dodge Layouts

`dodgeY` preserves each dot's scaled x position and derives a collision-free y
position in final plot pixels. `dodgeX` transposes the layout: y is preserved
and x is derived.

```ts
import { dodgeY } from '@tanstack/charts/dodge'
import { dot } from '@tanstack/charts/dot'

dot(rows, {
  x: 'value',
  key: 'id',
  r: 4,
  layout: dodgeY({
    anchor: 'middle',
    padding: 1,
  }),
})
```

The layouts and `createDotLayout` are also exported from `@tanstack/charts` and
`@tanstack/charts/universal`.

## Signatures

```ts
function dodgeY(options?: {
  anchor?: 'top' | 'middle' | 'bottom'
  padding?: number
  fit?: 'overflow' | 'compress'
}): DodgeYLayout

function dodgeX(options?: {
  anchor?: 'left' | 'middle' | 'right'
  padding?: number
  fit?: 'overflow' | 'compress'
}): DodgeXLayout

function createDotLayout(options: {
  axis: 'x' | 'y'
  anchor: ChartValue
  resolve(context: {
    chart: ChartBounds
    measuredPositions: readonly number[]
    radii: readonly number[]
  }): readonly number[]
}): DotLayout
```

`dodgeY` defaults to `bottom`; `dodgeX` defaults to `left`. `padding` is the
empty pixel distance between neighboring circle edges and defaults to `1`.
It must be finite and nonnegative.

`fit` defaults to `overflow`, preserving collision-free placement even when
the swarm extends beyond the plot. Use `fit: 'compress'` to fit crowded
offsets inside the plot without changing the measured coordinate or radii.
Compression can make circles overlap. Layouts that already fit are unchanged.
If a circle's diameter exceeds the available plot dimension, compression
throws a `RangeError`; reduce `r` or provide more space. Clipping an overflowing
swarm does not remove its points from keyboard or pointer interaction.

The public type surface also includes `CreateDotLayoutOptions`, `DotLayout`,
`DotLayoutResolveContext`, `DodgeOptions`, `DodgeXAnchor`, `DodgeYAnchor`,
`DodgeXOptions`, and `DodgeYOptions`.

## Custom layouts

Use `createDotLayout` when placement depends on final plot bounds but is not a
dodge. The resolver receives the preserved channel's scaled pixel positions
and the final radii in materialized valid-row order. Return one finite
cross-axis pixel position per materialized row. `dot` validates the result and
retains the authored `anchor` as the derived interaction-point value.

```ts
const rowLayout = createDotLayout({
  axis: 'y',
  anchor: 'rows',
  resolve: ({ chart, measuredPositions, radii }) =>
    measuredPositions.map(
      (_position, index) => chart.y + radii[index] + index * 12,
    ),
})
```

## Scale ownership

The measured channel remains an ordinary semantic chart value. A `dodgeY`
dot contributes only x scale values, so its chart definition needs an x scale
but no y scale. A `dodgeX` dot needs only a y scale.

```ts
defineChart({
  marks: [
    dot(rows, {
      x: 'economy',
      layout: dodgeY({ anchor: 'middle' }),
    }),
  ],
  scales: {
    x: { scale: scaleLinear().domain([5, 50]) },
    y: null,
  },
})
```

Do not configure the generated channel: `y` with `dodgeY`, or `x` with
`dodgeX`. The generated interaction-point value is the logical anchor while
its `x` and `y` fields contain the actual laid-out pixel center.

## Collision and radius

Collision distance is the sum of both final dot radii and `padding`. Configure
`r` and `rScale` once on `dot`; the layout receives the same resolved radii
used for rendering.

Placement is synchronous and deterministic. Edge anchors choose the nearest
valid inward position. Middle anchors choose the valid position with the
smallest absolute displacement. Fixed-radius rows retain source order;
variable-radius rows are placed largest first with source order as the stable
tie-breaker.

## Identity, facets, and grouping

Every rendered point retains its source datum, source index, key, group,
state, and motion policy. Supply `key` when the measured channel contains
duplicates.

Each facet resolves its child dodge layout against that cell's final scales
and bounds. `z` and `color` affect ordinary dot grouping and paint; they do not
create separate collision lanes. Use facets when groups need independent
swarm bounds.
