> ## 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.

# Coordinate systems

A **coordinate system** decides the plane a geom is drawn in. The same [geom](/sdk-next/concepts/geoms) and [scales](/sdk-next/concepts/scales) produce very different charts depending on the coord: a stacked bar is a stacked column in cartesian coordinates, a set of horizontal bars when the axes are flipped, and a pie when the plane is bent into a circle.

Set the coordinate system by piping a `coord.*()` part into the spec. When you don't, the chart is cartesian.

## Cartesian

The standard x–y plane, and the default.

```tsx theme={null}
pipe(
  createSpec({ x: 'month', y: 'revenue' }),
  geom.bar(),
  scale.x(),
  scale.y(),
  coord.cartesian() // optional — this is the default
);
```

## Flip

`coord.flip()` swaps the x and y axes. Its main use is turning vertical columns into horizontal bars — you keep the natural mapping (category on `x`, value on `y`) and the flip lays it on its side.

```tsx theme={null}
pipe(
  createSpec({ x: 'country', y: 'population' }),
  geom.bar(),
  scale.x(),
  scale.y(),
  coord.flip() // horizontal bars
);
```

## Polar

`coord.polar()` bends the plane into a circle — this is how pie, donut, rose and radial charts are made. It takes:

<ParamField path="theta" type="'x' | 'y'" default="x">
  Which aesthetic maps to the angle. `theta: 'y'` sweeps the *value* around the
  circle (pie/donut); `theta: 'x'` sweeps the *category* around it, with the
  value as radius (rose / coxcomb). A bare `coord.polar()` gives you the rose
  form — pass `theta: 'y'` for a pie.
</ParamField>

<ParamField path="innerRadius" type="number" default="0">
  Inner radius as a fraction `0–1`. `0` is a full pie; a value like `0.5` cuts
  out the centre for a donut.
</ParamField>

<ParamField path="startAngle" type="number" default="0">
  Starting angle in degrees.
</ParamField>

A pie chart is a single stacked-fill bar wrapped around `theta: 'y'`. The `'fill'` position normalises the stack to 100%, which is what closes the circle:

```tsx theme={null}
pipe(
  createSpec({ x: '', y: 'revenue', color: 'region' }),
  geom.bar({ position: 'fill' }),
  coord.polar({ theta: 'y' }),
  scale.x(),
  scale.y(),
  scale.color.palette()
);
```

The rest of the polar family follows from the same two knobs:

| Chart | Recipe |
| - | - |
| Pie | `geom.bar({ position: 'fill' })` + `coord.polar({ theta: 'y' })` |
| Donut | the same, plus `innerRadius: 0.5` |
| Radar | `geom.line()` or `geom.area()` + `coord.polar({ theta: 'x' })` |
| Rose | `geom.bar()` + `coord.polar({ theta: 'x' })` |

See [Pie & donut](/sdk-next/graph-types/pie) and [Radar & radial](/sdk-next/graph-types/radial) for worked examples.

Polar coordinates also flatten the `padding` on every discrete [scale](/sdk-next/concepts/scales) to `0`, so wedges meet edge to edge and a `scale.x.discrete({ padding: 0.2 })` has no visible effect inside a pie.

### Time around the circle

Mapping time to the angle turns a chart into a clock or a calendar: a day of tide heights, a year of temperatures. Pair `theta: 'x'` with a datetime or continuous x-scale:

```tsx theme={null}
pipe(
  createSpec({ x: 'recordedAt', y: 'tideHeight' }),
  geom.line(),
  coord.polar({ theta: 'x' }),
  scale.x.datetime(),
  scale.y()
);
```

Each reading takes its own slot, and the turn closes one step past the last one so it never lands back on the first. The step is the median spacing, so twelve monthly readings close on the following January and each month keeps its true width. See [Radar & radial](/sdk-next/graph-types/radial#time-around-the-rim) for a rendered example.

A numeric axis works the same way: a wind rose binned every 10 degrees gives 350 its own slot before the turn wraps back to 0. A `log` or `sqrt` x-scale keeps its data domain, since an even step there is not an even slot.

When the circle stands for a fixed period, name its bounds, so a missing reading cannot shift the others. The inferred step only knows the readings it is given:

```tsx theme={null}
scale.x.continuous({ domainMin: 0, domainMax: 360 }); // wind direction
scale.x.datetime({ domainMin: Date.UTC(2025, 5, 1), domainMax: Date.UTC(2025, 5, 2) }); // one day, midnight to midnight
```

## One coord per spec

Only one coordinate system is in play at a time, and piping a second `coord.*()` replaces the first outright rather than merging into it:

```tsx theme={null}
// theta is gone — the second call is the whole coord.
pipe(spec, coord.polar({ theta: 'y' }), coord.polar({ innerRadius: 0.5 }));

// Pass both params in one call instead.
pipe(spec, coord.polar({ theta: 'y', innerRadius: 0.5 }));
```

## Axis limits

Every coord accepts optional `xLimits` / `yLimits` as `[min, max]` to clip the drawing region; both are `null` unless you set them. For most charts you'll control the visible range through [scale](/sdk-next/concepts/scales) `domainMin` / `domainMax` instead — limits operate on the coordinate space rather than the data domain.

## Next

* [Chart types](/sdk-next/graph-types/index) — coords combined with geoms and positions
* [Pie & donut](/sdk-next/graph-types/pie) — the `theta: 'y'` polar recipes in full
* [Radar & radial](/sdk-next/graph-types/radial) — the `theta: 'x'` polar family


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