> ## Documentation Index
> Fetch the complete documentation index at: https://docs.graphy.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Geoms & layers

A **geom** is what a chart draws for its data — a line, a bar, a point. Each geom is a **layer** in the spec; add several and they stack into one chart. Geoms are the visible half of the grammar: [mappings](/sdk-next/concepts/mappings) and [scales](/sdk-next/concepts/scales) decide *where* things go, geoms decide *what* is drawn there.

## The built-in geoms

| Geom | Draws | Reads |
| - | - | - |
| `geom.point()` | A point per observation | `x`, `y`, `color`, `size`, `alpha`, `label` |
| `geom.line()` | A path connecting observations | `x`, `y`, `color`, `strokeWidth`, `lineType`, `label` |
| `geom.area()` | A filled band under a line | `x`, `y`, `color`, `alpha`, `label` |
| `geom.bar()` | A rectangle (or arc, in polar) per observation | `x`, `y`, `color`, `alpha`, `label` |
| `geom.rule()` | A single reference line across the panel | `x` or `y` |
| `geom.tile()` | A cell filling its band on both axes | `x`, `y`, `color` (required), `label` |

Each is a factory that returns a layer. Call it with options to configure the layer, and geom-specific `params` for its geometry and behavior knobs — paint (colors, widths, opacities) lives in the [stylesheet](/sdk-next/config/styling) instead:

```tsx theme={null}
geom.line({ params: { curve: 'smooth', missingValues: 'connect' } });
geom.bar({ position: 'stack', params: { width: 0.8 } });
```

## Params and paint, geom by geom

`params` are geometry and behavior; the `style.*` target beside them is where that geom's paint is authored.

| Geom | Params | Paint target |
| - | - | - |
| `point` | none | `style.geom.point({ size, stroke, strokeWidth })` |
| `line` | `curve: 'linear' \| 'smooth'` (default `'linear'`), `missingValues: 'zero' \| 'gap' \| 'connect'` (default `'gap'`) | `style.geom.line({ strokeWidth, lineType, fillAlpha })` |
| `area` | the same two, but `missingValues` defaults to `'zero'` — an area can't hold a gap mid-stack | `style.geom.area({ alpha, strokeWidth, lineType, strokeAlpha })` |
| `bar` | `width` (default `0.7`) — the fraction of the category's band the bar fills, in `(0, 1]` | `style.geom.bar({ cornerRadius, stroke, strokeWidth })` |
| `rule` | `label` (optional text), `labelPosition: 'start' \| 'end'` (default `'start'`) | `style.geom.rule({ stroke, strokeWidth, lineType })`, and `style.geom.rule.label({ … })` for its text |
| `tile` | none — a cell's geometry is the two bands it sits in | `style.geom.tile({ cornerRadius, stroke, strokeWidth })` |

Every target also takes the shared `alpha` and `saturation`, and its paint word: `fill` on a bar, an area, a point and a tile, `stroke` on a line and a rule. Two things catch people out: a bar's `cornerRadius` is a **token** (`'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full'`), not a pixel count — a tile's is a pixel number, like `style.graph`; and a `defaults` entry loses to a mapped aesthetic while an `overrides` entry beats it. [Styling](/sdk-next/config/styling) has the full cascade.

<Note>
  Params merge without validation, so a key a geom doesn't declare is accepted
  and quietly ignored.
</Note>

## Layers

Every `geom.*()` you pipe in adds a layer, drawn in order — later layers sit on top. Layers share the spec-level [mapping](/sdk-next/concepts/mappings) unless one overrides it. This is how decorated and combo charts are built:

```tsx theme={null}
pipe(
  createSpec({ x: 'month', y: 'revenue' }),
  geom.line(), // the trend
  geom.point(), // a dot on every vertex, on top
  scale.x(),
  scale.y()
);
```

## Common layer options

Beyond `params`, every geom accepts these options:

| Option | Purpose |
| - | - |
| `id` | A stable name for the layer. It's the handle `style.*(…, { layer: id })`, highlights and annotation anchors point at — set one to style a single series of a multi-layer chart |
| `aes` | A [layer-local mapping](/sdk-next/concepts/mappings#where-a-mapping-lives), merged over the spec's |
| `position` | How overlapping observations arrange — see below |
| `stat` | A [statistical transform](/sdk-next/advanced/statistics) for this layer — `stat.count()`, `stat.sum()`, `stat.mean()`, `stat.smooth()`; the default is `stat.identity()` |
| `yScaleType` | Bind the layer to the `'primary'` or `'secondary'` y-axis |
| `dataLabels` | Show [value labels](/sdk-next/config/data-labels) on each observation |
| `transforms` | [Transforms](/sdk-next/advanced/transforms) applied to this layer's view of the data |
| `interactive` | Whether the layer takes part in hover hit-testing. `true` everywhere except `geom.rule()`, which stays out of it by default |

## Position modes

When several observations share the same x (because `color` or `group` splits the data), a layer's `position` decides how they arrange:

| Position | Effect |
| - | - |
| `'identity'` | Draw at the raw value; observations may overlap |
| `'stack'` | Stack observations end to end |
| `'fill'` | Stack, then normalise each stack to 100% |
| `'dodge'` | Place observations side by side within the band |

Each geom picks its own default: `geom.bar()` is `'dodge'`, `geom.area()` is `'stack'`, and `geom.point()`, `geom.line()`, `geom.rule()` and `geom.tile()` are `'identity'`. So a bar chart with a `color` mapping arrives grouped and an area chart arrives stacked, with no `position` of your own.

```tsx theme={null}
geom.bar({ position: 'stack' }); // stacked columns instead of grouped
geom.bar({ position: 'fill' }); // 100% stacked columns
geom.area({ position: 'identity' }); // overlapping bands instead of stacked
```

Dodge works the same way for every geom: each group gets a share of the band and stands at its center. A bar spreads across its share, and a point or a line vertex stands in the middle of it. Layers that dodge line up, so dodge the points over a grouped bar chart and each point lands on its own bar:

```tsx theme={null}
geom.bar(); // grouped: 'dodge' by default
geom.point({ position: 'dodge' }); // each group's point on its own bar
```

A dodged line moves with its points, so a line and its points stay together. Dodge needs a band, so it only moves anything on a categorical axis. On a continuous x axis a dodged layer stays at its values.

## Next

* [Scales](/sdk-next/concepts/scales) — map values to positions, colors and sizes
* [Styling](/sdk-next/config/styling) — the stylesheet that paints every geom
* [Statistics](/sdk-next/advanced/statistics) — summarise a layer before drawing it
* [Chart types](/sdk-next/graph-types/index) — geoms assembled into recipes
* [Heatmap & waffle](/sdk-next/graph-types/heatmap) — the tile geom end to end


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.