Radar Chart
Compare series across multiple axes that share a single radial domain.
Overview
RadarChart is the styled wrapper over recharts' RadarChart, composed on the shadcn chart primitive (ChartContainer). Each data row (xKey) becomes a spoke on the polar grid; each entry in config becomes a closed polygon that shares one radial maximum. Use it when you have three to seven dimensions that should be evaluated together - skill profiles, satisfaction scores, capability gap analysis.
Reach for RadarChart when no single dimension is the headline and the shape of the polygon carries meaning across the whole set. For comparing one ordered dimension, LineChart reads faster. For ranking categories on a single metric, BarChart is more legible.
The React edition (@gremorie/rx-data) renders with recharts inside the shadcn chart primitive; the Angular edition (@gremorie/ng-data) renders headless d3 geometry inside a styled SVG. The rendering engine differs; the public surface is identical input-for-input.
Anatomy
<radar-chart>
└─ figure (role="img") card surface, aspect-square
├─ svg
│ ├─ polar grid (gridType: polygon | circle)
│ ├─ angle axis (spokes from xKey)
│ └─ radar polygons, one per config key
├─ tooltip overlay (optional via tooltip)
├─ legend (when config has >1 series)
└─ sr-only data tableThe React edition composes this surface through the shadcn chart primitive:
RadarChart
└─ ChartContainer responsive square frame + injects --color-<key>
└─ RadarChart (recharts) polar plot area over the angular axis (xKey)
├─ ChartTooltip hover card (ChartTooltipContent)
├─ PolarAngleAxis spoke labels (xKey)
├─ PolarGrid polygon or circular grid (gridType)
└─ Radar one polygon per seriesPreview
- Desktop
- Mobile
| month | Desktop | Mobile |
|---|---|---|
| Jan | 186 | 80 |
| Feb | 305 | 200 |
| Mar | 237 | 120 |
| Apr | 173 | 190 |
| May | 209 | 130 |
| Jun | 214 | 140 |
'use client';import { RadarChart, type ChartConfig, type ChartDatum,} from '@gremorie/rx-data';const monthlyData: ChartDatum[] = [ { month: 'Jan', desktop: 186, mobile: 80 }, { month: 'Feb', desktop: 305, mobile: 200 }, { month: 'Mar', desktop: 237, mobile: 120 }, { month: 'Apr', desktop: 173, mobile: 190 }, { month: 'May', desktop: 209, mobile: 130 }, { month: 'Jun', desktop: 214, mobile: 140 },];const monthlyConfig: ChartConfig = { desktop: { label: 'Desktop', color: 'var(--chart-1)' }, mobile: { label: 'Mobile', color: 'var(--chart-2)' },};export function RadarChartPreview() { return <RadarChart data={monthlyData} config={monthlyConfig} xKey="month" />;}Installation
bash npx gremorie@latest add rx-radar-chart bash pnpm dlx gremorie@latest add rx-radar-chart
bash yarn dlx gremorie@latest add rx-radar-chart
bash bunx --bun gremorie@latest add rx-radar-chart
Usage
import { RadarChart } from "@gremorie/rx-data";
import type { ChartConfig, ChartDatum } from "@gremorie/rx-data";
const data: ChartDatum[] = [
{ skill: "Speed", a: 90, b: 60 },
{ skill: "Power", a: 70, b: 80 },
{ skill: "Stamina", a: 85, b: 70 },
{ skill: "Agility", a: 75, b: 90 },
{ skill: "Skill", a: 95, b: 65 },
];
const config: ChartConfig = {
a: { label: "Player A", color: "var(--chart-1)" },
b: { label: "Player B", color: "var(--chart-2)" },
};
export function PlayerComparison() {
return <RadarChart data={data} config={config} xKey="skill" />;
}import { Component } from '@angular/core';
import { RadarChart } from '@gremorie/ng-data';
import type { ChartConfig, ChartDatum } from '@gremorie/ng-data';
@Component({
selector: 'app-player-comparison',
imports: [RadarChart],
template: `<radar-chart [data]="data" [config]="config" xKey="skill" />`,
})
export class PlayerComparisonComponent {
readonly data: ChartDatum[] = [
{ skill: 'Speed', a: 90, b: 60 },
{ skill: 'Power', a: 70, b: 80 },
{ skill: 'Stamina', a: 85, b: 70 },
{ skill: 'Agility', a: 75, b: 90 },
{ skill: 'Skill', a: 95, b: 65 },
];
readonly config: ChartConfig = {
a: { label: 'Player A', color: 'var(--chart-1)' },
b: { label: 'Player B', color: 'var(--chart-2)' },
};
}
API
<RadarChart>
The styled wrapper over recharts' RadarChart, composed on ChartContainer. Renders the responsive frame, a PolarGrid, the PolarAngleAxis (spoke labels), an optional tooltip, and one <Radar> per config key colored var(--color-<key>).
| Prop | Type | Default | Description |
|---|---|---|---|
data | ChartDatum[] | - | Tabular data. One row per spoke. Each row carries the spoke label at xKey plus one numeric value per config key. |
config | ChartConfig | - | Maps each series key to { label, color }. The color is any CSS color or token, typically "var(--chart-1)" through "var(--chart-5)". ChartContainer injects each as a var(--color-<key>) CSS variable. |
xKey | string | - | Name of the spoke (angle) field on each row. Drives the PolarAngleAxis labels around the perimeter. |
gridType | "polygon" | "circle" | "polygon" | Shape of the PolarGrid rings - straight-edged polygon (default) or concentric circles. |
tooltip | boolean | true | Toggles the hover tooltip. |
className | string | - | Merged onto the ChartContainer. The bundled style centers the chart with a square aspect. |
Composing with the chart primitive
For full control, compose recharts directly inside ChartContainer using the exported primitives. This mirrors the official shadcn radar block.
| Export | Role |
|---|---|
ChartContainer | Responsive frame; injects per-series --color-<key> vars from config. |
ChartTooltip | recharts Tooltip re-export; pair with ChartTooltipContent. |
ChartTooltipContent | Styled tooltip body. |
ChartLegend | recharts Legend re-export; pair with ChartLegendContent. |
ChartLegendContent | Styled legend body. |
useChart | Hook to read the active config inside a custom child. |
Types
| Type | Shape |
|---|---|
ChartDatum | Record<string, string | number> & { fill?: string } - one row of chart data. |
ChartConfig | Record<string, { label?: ReactNode; color?: string }> - per-series label and color map. |
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
type ChartDatum,
} from '@gremorie/rx-data';
import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from 'recharts';
const config = {
a: { label: 'Player A', color: 'var(--chart-1)' },
b: { label: 'Player B', color: 'var(--chart-2)' },
} satisfies ChartConfig;
export function Example({ data }: { data: ChartDatum[] }) {
return (
<ChartContainer
config={config}
className="mx-auto aspect-square max-h-[250px]"
>
<RadarChart accessibilityLayer data={data}>
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
<PolarAngleAxis dataKey="skill" />
<PolarGrid gridType="polygon" />
<Radar
dataKey="a"
fill="var(--color-a)"
fillOpacity={0}
stroke="var(--color-a)"
strokeWidth={2}
/>
<Radar
dataKey="b"
fill="var(--color-b)"
fillOpacity={0}
stroke="var(--color-b)"
strokeWidth={2}
/>
</RadarChart>
</ChartContainer>
);
}Drop down to this level for dashed strokes, gradient fills, marker dots at spoke intersections, or a custom legend via ChartLegend + ChartLegendContent.
Composition
- Pick
xKey- the spoke label field. Three to seven spokes is the sweet spot; fewer and you get a triangle that hides shape, more and the axes crowd. - Author
configwith at most three series. Polygons stay legible when stroked; four or more become hard to read. - Order spokes consistently across charts in the same surface so users can compare shapes by muscle memory.
- Choose
gridType-polygon(default) follows the spoke geometry;circlereads as a target/dartboard when exact spoke alignment matters less.
The chart auto-sizes to its parent through ChartContainer's responsive frame, with a square aspect so the polygons stay symmetric.
Variations
Multi-series comparison
- Desktop
- Mobile
| month | Desktop | Mobile |
|---|---|---|
| Jan | 186 | 80 |
| Feb | 305 | 200 |
| Mar | 237 | 120 |
| Apr | 173 | 190 |
| May | 209 | 130 |
| Jun | 214 | 140 |
'use client';import { RadarChart, type ChartConfig, type ChartDatum,} from '@gremorie/rx-data';const monthlyData: ChartDatum[] = [ { month: 'Jan', desktop: 186, mobile: 80 }, { month: 'Feb', desktop: 305, mobile: 200 }, { month: 'Mar', desktop: 237, mobile: 120 }, { month: 'Apr', desktop: 173, mobile: 190 }, { month: 'May', desktop: 209, mobile: 130 }, { month: 'Jun', desktop: 214, mobile: 140 },];const monthlyConfig: ChartConfig = { desktop: { label: 'Desktop', color: 'var(--chart-1)' }, mobile: { label: 'Mobile', color: 'var(--chart-2)' },};export function RadarChartPreview() { return <RadarChart data={monthlyData} config={monthlyConfig} xKey="month" />;}const config: ChartConfig = {
a: { label: 'Player A', color: 'var(--chart-1)' },
b: { label: 'Player B', color: 'var(--chart-2)' },
};
<RadarChart data={data} config={config} xKey="skill" />;<radar-chart [data]="data" [config]="config" xKey="skill" />The most common shape. Two stroked series let the user compare polygon footprints at a glance. Keep the count at two or three before the overlapping polygons defeat the comparison.
Single series profile
<RadarChart
data={data}
config={{ score: { label: 'Score', color: 'var(--chart-1)' } }}
xKey="dimension"
/><radar-chart [data]="data" [config]="config" xKey="dimension" />For single-subject profiles - one player, one product, one team. A single filled polygon reads cleanly even with five or six spokes.
Polygon grid
The default gridType. Straight-edged polygon rings track the spokes, emphasizing the per-spoke geometry.
<RadarChart data={data} config={config} xKey="skill" gridType="polygon" /><radar-chart [data]="data" [config]="config" xKey="skill" gridType="polygon" />Circle grid
Set gridType to "circle" for concentric circles that read like a target or dartboard, keeping the rings as a neutral radial scale when exact spoke alignment matters less.
<RadarChart data={data} config={config} xKey="skill" gridType="circle" /><radar-chart [data]="data" [config]="config" xKey="skill" gridType="circle" />Accessibility
- recharts'
accessibilityLayeradds keyboard navigation and screen-reader announcements for the plotted series. --chart-1through--chart-5ship with WCAG AA contrast against the card background in both light and dark themes.- Spoke labels: each
PolarAngleAxistick is rendered as SVG<text>around the polygon, keeping the color-to-series mapping legible alongside the tooltip. - Limit series: keep the count at two or three. Four or more overlapping polygons defeat the visual goal of shape comparison.
Related
- LineChart - reach for a line when one ordered dimension is the headline.
- BarChart - reach for bars when ranking categories on a single metric.
- RadialChart - sibling polar layout when categories should be shown as concentric arcs.