Radar / Spider Chart
FreeMulti-series radar/spider chart with configurable axes, fill/stroke mode, dot markers, level grid and auto-positioned labels. States under the plot when the spokes are on different scales, and that the shape depends on the order of the axes. Pure SVG.
import { SvRadarChartComponent } from '@styloviz/charts';Demo & usage
Basic usage
Usage example — open the gallery for the full interactive demo.
An interactive demo for this component lives in the gallery.
Open live demoMore examples
Filled with dots
<sv-radar-chart [series]="series" [axes]="axes" [filled]="true" [showDots]="true" [levels]="5" />
Per-axis scale + events
<sv-radar-chart [series]="series" [axes]="axes" [maxValue]="100" (pointSelect)="onSelect($event)" />
Mixed units, one spoke at a time
axes = [{ label: 'Latency (ms)', max: 400 }, { label: 'Accuracy' }];When to use
- Comparing a few items across several shared dimensions (skills, product specs).
- You want a quick visual "shape" of strengths and weaknesses.
- For exact values, a grouped Bar Chart is more precise.
Async states
- loading
- empty
- error
- retry
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
| loading | boolean | false | True while the series is being fetched. |
| error | SvDataError | string | null | null | Load failure. Use `toSvDataError()` from `@styloviz/core` to normalize an `HttpErrorResponse`. |
| emptyMessage | string | 'No data to plot' | Copy for the no-data state. |
| filtered | boolean | false | True when the emptiness is caused by a filter rather than an empty set. |
| axes | readonly RadarAxis[] | [] | The radial axes (one spoke per axis label). |
| series | readonly RadarSeries[] | [] | Series to plot; each value array aligns with `axes`. |
| size | number | 300 | Chart diameter in pixels. |
| levels | number | 5 | Number of concentric grid levels. |
| maxValue | number | 100 | Global maximum used to normalise values (per-axis `max` overrides). |
| showDots | boolean | true | Show point markers at each value. |
| filled | boolean | true | Fill the series area (vs stroke-only). |
| title | string | '' | Chart title. |
| ariaLabel | string | '' | Accessible name override for the chart. Falls back to a generated summary. |
| customClass | string | '' | Extra CSS classes applied to the root wrapper. |
Outputs
| Event | Payload type | Description |
|---|---|---|
| retry | void | The host should re-run the failed load. |
| pointHover | RadarPointEvent | Emitted when a vertex is hovered or keyboard-focused. |
| pointSelect | RadarPointEvent | Emitted when a vertex is clicked or activated via keyboard (Enter/Space). |
<sv-radar-chart-pro>
Also in this family — SvRadarChartProComponent from @styloviz-pro/charts.
Async states
- loading
- empty
- error
- retry
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
| loading | boolean | false | True while the series is being fetched. |
| error | SvDataError | string | null | null | Load failure. Use `toSvDataError()` from `@styloviz/core` to normalize an `HttpErrorResponse`. |
| emptyMessage | string | 'No data to plot' | Copy for the no-data state. |
| filtered | boolean | false | True when the emptiness is caused by a filter rather than an empty set. |
| axes | readonly RadarAxis[] | [] | The radial axes (one spoke per axis label). |
| series | readonly RadarSeries[] | [] | Series to plot; each value array aligns with `axes`. |
| size | number | 300 | Chart diameter in pixels. |
| levels | number | 5 | Number of concentric grid levels. |
| maxValue | number | 100 | Global maximum used to normalise values (per-axis `max` overrides). |
| showDots | boolean | true | Show point markers at each value. |
| filled | boolean | true | Fill the series area (vs stroke-only). |
| title | string | '' | Chart title. |
| ariaLabel | string | '' | Accessible name override for the chart. Falls back to a generated summary. |
| customClass | string | '' | Extra CSS classes applied to the root wrapper. |
Outputs
| Event | Payload type | Description |
|---|---|---|
| retry | void | The host should re-run the failed load. |
| pointHover | RadarPointEvent | Emitted when a vertex is hovered or keyboard-focused. |
| pointSelect | RadarPointEvent | Emitted when a vertex is clicked or activated via keyboard (Enter/Space). |
Wire it to your API
This component is headless about transport: it renders what you give it and reports what the user asks for. Nothing below requires @styloviz/data — drive it from whatever you already use.
Template
Bound to the names this component actually exposes.
<sv-radar-chart [series]="data.items()" [loading]="data.loading()" [error]="data.error()" [filtered]="data.filtered()" (retry)="data.reload()" />
Host class
Drive the three async inputs from your own fetch.
// This component reports its state but does not emit a query, so drive // it from your own fetch and bind the three async inputs. readonly items = signal<Row[]>([]); readonly loading = signal(false); readonly error = signal<SvDataError | null>(null); async load(): Promise<void> { this.loading.set(true); this.error.set(null); try { this.items.set(await this.api.fetchRows()); } catch (e) { this.error.set(toSvDataError(e)); } finally { this.loading.set(false); } }
- This component does not emit a query, so it renders what you give it. Fetch on your own trigger and bind `series`, `loading` and `error`.
- Set `filtered` when a search or filter is active. It is the difference between "you have no orders" and "no orders match this filter" — telling a customer with 4,000 orders the first one reads as a bug.
- Normalize failures with `toSvDataError()` from `@styloviz/core`. It derives `retryable` from the status, so a 404 does not get a "Try again" button that can only fail again.
Full walkthrough: Connecting to a backend.
Customization & theming
- Configurable axes and grid levels.
- Per-axis `max` or a global `maxValue` for normalisation, with the differing maxima named under the plot when they disagree.
- Fill or stroke-only series with markers.
- Auto-positioned axis labels.
- Responsive: the SVG scales down to fit narrow containers (`max-w-full h-auto`) while keeping its aspect ratio, instead of overflowing at its fixed `size`.
Best practices
- Do: Keep series to 2–3; overlapping radars get unreadable fast.
- Do: Use a consistent scale across axes so the shape is meaningful.
- Don't: Do not read the enclosed AREA as a magnitude. The polygon's shape and its area are artefacts of the order the axes happen to be written in — swap two spokes and the same numbers draw a different figure, a spiky one becoming a smooth one, with nothing changed in the data. Compare vertices along a spoke, not outlines. The chart says so under the plot whenever there are three or more axes.
- Don't: Do not compare distance from the centre across spokes on different maxima. A per-axis max normalises that spoke on its own, so 80 out of 200 is drawn INSIDE 60 out of 100 — the larger number looks smaller, which is the opposite of what a reader given the bare figures would conclude. The differing maxima are named under the plot and carried in the summary table.
- Tip: A per-axis `max` is what lets a radar mix units at all. It is a real capability with a real cost, so use it deliberately rather than because one axis happened to need a bigger number.
- Tip: Stroke-only (filled=false) reads better when comparing multiple series.
Accessibility
- The SVG is a `role="group"` with a generated summary as its name (override via `ariaLabel`) — `group`, not `img`, because the vertices are tab stops and an `img`'s subtree is pruned out of the accessibility tree with them in it.
- A radar encodes value as distance from the centre, which is the hardest quantity on the chart to read off — and impossible to compare across axes whose maxima differ. `sv-data-summary-table` publishes one row per axis and one column per series, plus an "Axis maximum" column when the axes are on mixed scales: without it a reader is told 80 and 40 with no way to know they are the same share of their own axis.
- Both caveats are stated in the rendered output, not only in the table — the reader misled by a mixed scale or by an ordering artefact is the one looking straight at the web, and a table is the alternative for someone who cannot see it. There is no input to hide the line, on the same rule as the bullet chart's scale note.
- Each vertex is keyboard-focusable with an `aria-label`; focus/hover shows a tooltip and Enter/Space emits `pointSelect`. Legend distinguishes series beyond color.
Feedback on Radar / Spider Chart
Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.