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

# Radar & radial

export const GraphEmbed = ({src, story, aspectRatio, borderRadius = '14px', args}) => {
  const STORYBOOK_BASE_URL = typeof window !== 'undefined' && window.location.hostname === 'localhost' ? 'http://localhost:6006' : 'https://storybook-sdk.vercel.app';
  function buildArgsString(args) {
    if (!args) return '';
    const encodeValue = value => {
      if (value === null) return '!null';
      if (value === undefined) return '!undefined';
      if (typeof value === 'boolean') return `!${value}`;
      if (typeof value === 'number') return String(value);
      return encodeURIComponent(String(value));
    };
    const properties = Object.entries(args).map(([key, value]) => `${key}:${encodeValue(value)}`).join(';');
    return `&args=${properties}`;
  }
  const resolvedSrc = story ? `${STORYBOOK_BASE_URL}/iframe.html?id=${story}&viewMode=story&embed=1&globals=mode:readonly${buildArgsString(args)}` : src;
  const resolvedAspectRatio = aspectRatio ?? (story ? '16 / 9' : '10 / 6');
  return <iframe src={resolvedSrc} loading="lazy" allowfullscreen="true" style={{
    width: '100%',
    aspectRatio: resolvedAspectRatio,
    border: 'none',
    borderRadius,
    colorScheme: 'light'
  }} />;
};

Radial charts bend the plane into a circle with `coord.polar`, just like [pie & donut](/sdk-next/graph-types/pie) — but here the *category* sweeps around the circle instead of the value.

With `theta: 'x'`, each category takes a spoke and its value grows outward along the radius. The geom then sets the shape: `geom.line()` traces a radar, `geom.area()` fills it, `geom.bar()` draws a rose.

With `theta: 'y'`, the value sweeps the angle instead, and each category becomes its own concentric track — a radial bar.

They read best for a small, fixed set of categories compared across a few series — a skills profile, a weekly cycle, activity by hour.

## Radar

A radar (or spider) chart is a `geom.line()` under `coord.polar({ theta: 'x' })`. Map the axis category to `x`, the value to `y`, and a series column to `color`.

Two scale settings carry the chart. A discrete `x` scale gives each category an evenly-spaced spoke. `domainMin: 0` on `y` is effectively required for a line: it anchors the centre of the circle at zero, so the radius stays proportional to the value — without it the domain starts at the data minimum, which lands at the centre and exaggerates the differences arbitrarily. An area or bar holds zero on its own. A non-interactive `geom.point()` layer marks each vertex:

<GraphEmbed story="chart-types-radar-graph--spider" />

```tsx theme={null}
import { useMemo } from 'react';
import {
  GraphProvider,
  GraphRenderer,
  createSpec,
  pipe,
  geom,
  scale,
  coord,
} from '@graphysdk/react';

const data = {
  columns: [
    { key: 'skill', label: 'Skill' },
    { key: 'player', label: 'Player' },
    { key: 'score', label: 'Score' },
  ],
  rows: [
    { skill: 'Speed', player: 'Alice', score: 8 },
    { skill: 'Power', player: 'Alice', score: 6 },
    { skill: 'Defense', player: 'Alice', score: 7 },
    // ...
    { skill: 'Speed', player: 'Bob', score: 6 },
    { skill: 'Power', player: 'Bob', score: 9 },
    { skill: 'Defense', player: 'Bob', score: 5 },
    // ...
  ],
};

export function SkillsRadar() {
  const spec = useMemo(
    () =>
      pipe(
        createSpec({ x: 'skill', y: 'score', color: 'player' }),
        geom.line(),
        geom.point({ interactive: false }),
        coord.polar({ theta: 'x' }),
        scale.x.discrete(),
        scale.y({ domainMin: 0 }),
        scale.color.palette()
      ),
    []
  );

  return (
    <GraphProvider spec={spec} data={data}>
      <GraphRenderer />
    </GraphProvider>
  );
}
```

### Time around the rim

A datetime `x` scale in place of the discrete one turns the radar into a clock or a calendar. See [Coordinate systems](/sdk-next/concepts/coordinate-systems#time-around-the-circle) for how the turn is divided:

<GraphEmbed story="chart-types-radar-graph--seasonal-cycle" />

```tsx theme={null}
const spec = pipe(
  createSpec({ x: 'month', y: 'temperature' }),
  geom.line(),
  geom.point({ interactive: false }),
  coord.polar({ theta: 'x' }),
  scale.x.datetime(),
  scale.y()
);
```

## Filled radar

Swap the line for `geom.area({ position: 'identity' })` to fill each series polygon. Keep `position: 'identity'` so the areas overlap at their true values rather than stacking; the vertex points still sit on top:

<GraphEmbed story="chart-types-radar-graph--filled" />

```tsx theme={null}
const spec = pipe(
  createSpec({ x: 'skill', y: 'score', color: 'player' }),
  geom.area({ position: 'identity' }),
  geom.point({ interactive: false }),
  coord.polar({ theta: 'x' }),
  scale.x.discrete(),
  scale.y(),
  scale.color.palette()
);
```

Area fills draw at `alpha: 0.3`. With more than two or three series they still obscure each other — reach for the outline radar above, or take the fill down further through the [stylesheet](/sdk-next/config/styling):

```tsx theme={null}
styles({ defaults: [style.geom.area({ fillAlpha: 0.15 })] });
```

The vertex dots draw at the built-in `size: 8`, which is heavy for a radar. Give the point layer an `id` and scope a style entry to it:

```tsx theme={null}
const spec = pipe(
  createSpec({ x: 'skill', y: 'score', color: 'player' }),
  geom.area({ position: 'identity' }),
  geom.point({ id: 'vertices', interactive: false }),
  coord.polar({ theta: 'x' }),
  scale.x.discrete(),
  scale.y(),
  scale.color.palette(),
  styles({
    defaults: [
      style.geom.area({ fillAlpha: 0.15 }),
      style.geom.point({ size: 5 }, { layer: 'vertices' }),
    ],
  })
);
```

## Rose / coxcomb

A rose (or coxcomb) chart keeps `theta: 'x'` but draws bars instead of a line: each category is an angular wedge and the value sets how far the wedge reaches. Map a series to `color` and pick a [position](/sdk-next/graph-types/index#position-modes) — `dodge` splits each wedge into side-by-side petals, `stack` grows the series outward along the radius:

<GraphEmbed story="chart-types-polar-bar-graph--rose" />

```tsx theme={null}
const spec = pipe(
  createSpec({ x: 'day', y: 'signups', color: 'channel' }),
  geom.bar({ position: 'dodge' }), // 'stack' to stack the series along the radius
  coord.polar({ theta: 'x' }),
  scale.x.discrete(),
  scale.y(),
  scale.color.palette()
);
```

`params.width` sets a petal's angular thickness as a fraction of its category band, in `(0, 1]` and defaulting to `0.7` — drop it to open a gap between wedges, raise it to `1` for a solid ring of petals.

## Radial bar

A radial bar (or race-track) chart flips the angle back to the value with `theta: 'y'`: each category becomes its own concentric ring and the value sweeps around it. An `innerRadius` opens up the centre so the innermost track isn't crushed into a point. Stack a series to read each ring as consecutive coloured segments:

<GraphEmbed story="chart-types-polar-bar-graph--radial-bar" />

```tsx theme={null}
const spec = pipe(
  createSpec({ x: 'day', y: 'signups', color: 'channel' }),
  geom.bar({ position: 'stack' }),
  coord.polar({ theta: 'y', innerRadius: 0.15 }),
  scale.x.discrete(),
  scale.y(),
  scale.color.palette()
);
```

Here `params.width` is the radial thickness of each track. What gives a racetrack its capsule ends is `cornerRadius: 'full'`, which rounds an arc to half its radial span:

```tsx theme={null}
styles({ defaults: [style.geom.bar({ cornerRadius: 'full' })] });
```

On a stacked ring only the outer corners of the whole arc round, so the segments inside it stay flush. A border in the background color separates them instead:

```tsx theme={null}
styles({
  defaults: [style.geom.bar({ stroke: '#ffffff', strokeWidth: 2 })],
});
```

## Where the first spoke lands

`coord.polar` takes a `startAngle` in degrees, defaulting to `0`. On a rose it decides which spoke sits at the top; on a racetrack it decides where every arc begins sweeping:

```tsx theme={null}
coord.polar({ theta: 'x', startAngle: 45 });
```

## Axes

Unlike pie and donut, these charts keep a real categorical `x` mapping, so both axes render by default. Turn the category axis off when the legend already names the spokes:

```tsx theme={null}
config({ axes: { x: { isVisible: false } } });
```

## Related

* [Styling](/sdk-next/config/styling) — fill opacity, marker size, corner radius and per-layer scoping
* [Coordinate systems](/sdk-next/concepts/coordinate-systems) — the polar plane, `theta`, `startAngle` and `innerRadius`
* [Pie & donut](/sdk-next/graph-types/pie) — the `theta: 'y'` parts-of-a-whole family
* [Chart types overview](/sdk-next/graph-types/index) — geoms, positions and the full recipe list


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