# RadarChart

A profile across several named dimensions.

- Group: Charts
- Import: `import { RadarChart } from '@misoto22/folio/charts'`
- Page: https://ui.misoto22.com/components/radar-chart/
- Related: line-chart, pie-chart

## When to reach for it

Recognising a silhouette. The area a radar encloses depends on the order its spokes happen to be in, so it is the wrong chart for comparing magnitudes.

## Anatomy

- **Figure frame** (required) — ChartFigure’s <figure>, with the polar plot inside one ChartContainer.
- **Polygons** (required) — <RadarChart.Radar>. variant="filled" is the default and paints at 2.2 times --chart-fill, because a radar’s fill IS the mark rather than a wash under a line and has to hold its shape where two of them overlap.
- **Spoke labels** — <RadarChart.PolarAngleAxis>, the names around the perimeter. They are the only thing that says what a corner of the silhouette measures.
- **Radial scale** — <RadarChart.PolarRadiusAxis>, and it is opt-in. Leave it out and the rings carry no numbers at all: the reader has a shape and no idea what one ring is worth.
- **Grid** — <RadarChart.PolarGrid>, polygonal rather than circular by default, so the rings line up with the polygon the data draws over them.
- **Legend** — <RadarChart.Legend>. Two overlapping outlines two steps apart on the grey ramp name nothing, and there is no axis here to name them instead.

## Best practices

### Do

- Fix the spoke ORDER and keep it fixed across every radar on the page. The area a polygon encloses is a function of the order the dimensions happen to sit in, so re-ordering the spokes changes the silhouette without changing one number.
- Put every dimension on a comparable scale first — a percentile, a score out of ten, an index. One radius serves all the spokes, so a spoke in milliseconds beside one in percent draws a spike that means nothing.
- Switch to variant="lines" past two series. Filled polygons overlap, and judging areas through two layers of translucency is precisely what this form is worst at.

### Don’t

- Do not read magnitude off it. A radar is for recognising a silhouette — the same profile before and after — and which of two is bigger is a question a BarChart answers and this one only appears to.
- Do not skip <RadarChart.PolarRadiusAxis> and call the chart finished. It renders, the rings render, and nothing on screen says whether the outer ring is 100 or 1,000; the sr-only table still has the figures, the sighted reader does not.

## Accessibility

- title is required; the rows are also rendered as a visually hidden table.
- Two or three series at most: filled polygons overlap, and judging areas through two layers of translucency is what a radar is worst at. Past that, variant="lines".

## RadarChart

A profile across several named dimensions — the shape for "what is this thing strong and weak at". It reads a SHAPE, not a set of values: the area a radar encloses depends on the order the spokes happen to be in, so it is the wrong chart for comparing magnitudes and the right one for recognising a silhouette. Two or three series at most.

### Props

- `config` (required) — `TConfig & ValidateKeys<TData, TConfig>`.
- `data` (required) — `TData[]`. The rows the chart draws. One entry per point, bar or category.
- `title` (required) — `string`. What the chart shows, in a sentence a reader could act on. Required, and announced to a screen reader even when it is not printed.
- `showTitle` — `boolean`. Prints the title above the plot instead of hiding it from sight.
- `description` — `ReactNode`. A line under the title — the unit, the window, the caveat.
- `children` (required) — `ReactNode`. The composed parts — axes, grid, tooltip, legend, and the marks themselves.
- `className` — `string`. Merged onto the figure, last, so a call site can size or space it.
- `chartProps` — `ComponentProps<typeof RechartsRadarChart>`. Escape hatch onto the raw Recharts chart element.
- `defaultSelectedDataKey` — `string | null` default `null`. The series lit on first render, when the chart keeps its own selection.
- `selectedDataKey` — `string | null`. The selected series, driven from outside. Give this and the chart follows it; leave it undefined and the chart keeps its own, starting from `defaultSelectedDataKey`.
- `onSelectionChange` — `(selectedDataKey: string | null) => void`. Fires when the selection changes, and with null when it is cleared.
- `isLoading` — `boolean` default `false`. Swaps the marks for an animated skeleton, keeping the measured height so the page does not jump when the data lands.
- `loadingPoints` — `number`. How many points the skeleton draws.
- `angleDataKey` — `keyof TData & string`. The row field naming each spoke. Used by the table view.
- `hideDataTable` — `boolean` default `false`. Drops the hidden table view. Only correct when the page prints the data itself.
- `empty` — `ChartEmptyProps | false`. What the chart shows when it has nothing to draw. `false` keeps the empty plot, for a chart whose emptiness is itself the reading.

## Example — default

```tsx
import { RadarChart, type ChartConfig } from '@misoto22/folio/charts'

<RadarChart title="Team profile" config={config} data={data} angleDataKey="skill">
  <RadarChart.PolarGrid />
  <RadarChart.PolarAngleAxis dataKey="skill" />
  <RadarChart.Tooltip />
  <RadarChart.Radar dataKey="current">
    <RadarChart.Dot variant="border" />
  </RadarChart.Radar>
</RadarChart>
```

## Example — variant and grid

```tsx
import { ToggleGroup, ToggleGroupItem } from '@misoto22/folio'
import { RadarChart, type ChartConfig, type RadarVariant } from '@misoto22/folio/charts'

<div className="flex w-full flex-col gap-4">
  <div className="flex flex-wrap gap-3">
    <ToggleGroup
      type="single"
      value={variant}
      onValueChange={(next) => next && setVariant(next as RadarVariant)}
      aria-label="Radar fill"
    >
      {VARIANTS.map((option) => (
        <ToggleGroupItem key={option} value={option}>
          {option}
        </ToggleGroupItem>
      ))}
    </ToggleGroup>

    <ToggleGroup
      type="single"
      value={gridType}
      onValueChange={(next) => next && setGridType(next as (typeof GRIDS)[number])}
      aria-label="Grid"
    >
      {GRIDS.map((option) => (
        <ToggleGroupItem key={option} value={option}>
          {option}
        </ToggleGroupItem>
      ))}
    </ToggleGroup>
  </div>

  <RadarChart title="Team profile" config={config} data={data} angleDataKey="skill">
    <RadarChart.PolarGrid gridType={gridType} />
    <RadarChart.PolarAngleAxis dataKey="skill" />
    <RadarChart.Legend isClickable />
    <RadarChart.Tooltip />
    <RadarChart.Radar dataKey="current" variant={variant} isClickable />
    <RadarChart.Radar dataKey="target" variant={variant} isClickable />
  </RadarChart>
</div>
```

## Example — loading

```tsx
import { RadarChart, type ChartConfig } from '@misoto22/folio/charts'

<RadarChart title="Team profile" config={config} data={[]} isLoading>
  <RadarChart.PolarGrid />
  <RadarChart.Radar dataKey="current" />
</RadarChart>
```
