Radar / Spider Chart

Free

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

</>sv-radar-chart chartradarcomparison
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 demo

More examples

Filled with dots

example.htmlAngular
<sv-radar-chart [series]="series" [axes]="axes" [filled]="true" [showDots]="true" [levels]="5" />

Per-axis scale + events

example.htmlAngular
<sv-radar-chart [series]="series" [axes]="axes" [maxValue]="100" (pointSelect)="onSelect($event)" />

Mixed units, one spoke at a time

example.htmlAngular
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

NameTypeDefaultDescription
loadingbooleanfalseTrue while the series is being fetched.
errorSvDataError | string | nullnullLoad failure. Use `toSvDataError()` from `@styloviz/core` to normalize an `HttpErrorResponse`.
emptyMessagestring'No data to plot'Copy for the no-data state.
filteredbooleanfalseTrue when the emptiness is caused by a filter rather than an empty set.
axesreadonly RadarAxis[][]The radial axes (one spoke per axis label).
seriesreadonly RadarSeries[][]Series to plot; each value array aligns with `axes`.
sizenumber300Chart diameter in pixels.
levelsnumber5Number of concentric grid levels.
maxValuenumber100Global maximum used to normalise values (per-axis `max` overrides).
showDotsbooleantrueShow point markers at each value.
filledbooleantrueFill the series area (vs stroke-only).
titlestring''Chart title.
ariaLabelstring''Accessible name override for the chart. Falls back to a generated summary.
customClassstring''Extra CSS classes applied to the root wrapper.

Outputs

EventPayload typeDescription
retryvoidThe host should re-run the failed load.
pointHoverRadarPointEventEmitted when a vertex is hovered or keyboard-focused.
pointSelectRadarPointEventEmitted 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

NameTypeDefaultDescription
loadingbooleanfalseTrue while the series is being fetched.
errorSvDataError | string | nullnullLoad failure. Use `toSvDataError()` from `@styloviz/core` to normalize an `HttpErrorResponse`.
emptyMessagestring'No data to plot'Copy for the no-data state.
filteredbooleanfalseTrue when the emptiness is caused by a filter rather than an empty set.
axesreadonly RadarAxis[][]The radial axes (one spoke per axis label).
seriesreadonly RadarSeries[][]Series to plot; each value array aligns with `axes`.
sizenumber300Chart diameter in pixels.
levelsnumber5Number of concentric grid levels.
maxValuenumber100Global maximum used to normalise values (per-axis `max` overrides).
showDotsbooleantrueShow point markers at each value.
filledbooleantrueFill the series area (vs stroke-only).
titlestring''Chart title.
ariaLabelstring''Accessible name override for the chart. Falls back to a generated summary.
customClassstring''Extra CSS classes applied to the root wrapper.

Outputs

EventPayload typeDescription
retryvoidThe host should re-run the failed load.
pointHoverRadarPointEventEmitted when a vertex is hovered or keyboard-focused.
pointSelectRadarPointEventEmitted 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.

usage.htmlAngular
<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.

host.component.tsTypeScript
// 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.

Loads giscus.app and GitHub — nothing is fetched until you click.