Radar and radial charts
Three phones scored on six attributes as a radar chart with the axis pinned to 0 to 100 and each product switchable on and off, and a year of sales as the radial family: stacked radial columns around a circle, a nightingale where the radius carries the value, and radial bars read against a shared scale. The same categories and series a bar chart takes; only the type changes.
A live, editable Svelte 5 data grid example from the SvGrid gallery (Charts). See the SvGrid documentation for the full API.
About this example
The polar family: a radar chart with one axis per attribute and a polygon per series, the axis pinned through yAxis min and max, series hidden through visible or a legend click; radial-column stacks bars around a circle, nightingale gives every slice the same angle and lets the radius carry the value, and radial-bar draws one ring per category against a shared scale. Every one takes the categories and series a bar chart takes. Free, in @svgrid/grid.
The polar family, on one product-comparison dataset:
- A radar (spider) chart: one axis per attribute, one polygon per product, with the axis pinned to 0..100 so a product that scores low everywhere does not stretch to the rim. Click a legend item to hide a product, double-click to isolate one; the switches below do the same through
visibleon the series. - A radial column chart: the same monthly series as bars around a circle, stacked; a nightingale (polar area) chart, where the radius, not the angle, carries the value; and a radial bar chart, one ring per category read against the scale.
Every one takes the same categories and series a bar chart takes; type is the only difference.
Free, in @svgrid/grid.
Imports, features and API used
Imports: @svgrid/grid, ../shared/ChartDataGrid.svelte, ../shared/ViewSwitch.svelte
Frequently asked questions
How do I pin the radar axis?
Set yAxis min and max on the spec, for instance 0 and 100 for scores, so a series that scores low everywhere does not stretch to the rim.
What is the difference between a radial column and a nightingale?
Both put the categories around a circle. A radial column varies the bar length and keeps the angle; a nightingale keeps every slice the same angle and varies the radius, so the area grows with the square of the value.
How do I hide a series?
Set visible: false on it, or click its legend item; a double-click isolates one series.
Related documentation
Source code (446-chart-radar-radial.svelte)
<!-- Documented in: docs/help/charts/gallery.md -->
<script lang="ts">
/**
* 446. Radar and radial charts
* ----------------------------
* The polar family, on one product-comparison dataset:
*
* - A radar (spider) chart: one axis per attribute, one polygon per
* product, with the axis pinned to 0..100 so a product that scores low
* everywhere does not stretch to the rim. Click a legend item to hide a
* product, double-click to isolate one; the switches below do the same
* through `visible` on the series.
* - A radial column chart: the same monthly series as bars around a
* circle, stacked; a nightingale (polar area) chart, where the radius,
* not the angle, carries the value; and a radial bar chart, one ring
* per category read against the scale.
*
* Every one takes the same `categories` and `series` a bar chart takes;
* `type` is the only difference.
*
* Free, in @svgrid/grid.
*/
import { SvChart, type ChartSpec, type ChartType } from '@svgrid/grid'
import ChartDataGrid from '../shared/ChartDataGrid.svelte'
import ViewSwitch from '../shared/ViewSwitch.svelte'
const attributes = ['Speed', 'Battery', 'Camera', 'Display', 'Build', 'Value']
const products = [
{ label: 'Aster 12', values: [88, 72, 91, 85, 80, 62] },
{ label: 'Birch S', values: [74, 90, 70, 78, 86, 84] },
{ label: 'Cedar One', values: [65, 68, 74, 92, 70, 95] },
]
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
const sales = [
{ label: 'Online', values: [42, 38, 51, 47, 55, 63, 71, 68, 60, 57, 74, 92] },
{ label: 'Retail', values: [30, 28, 33, 35, 38, 41, 46, 44, 39, 42, 55, 70] },
]
let shown = $state<Record<string, boolean>>({ 'Aster 12': true, 'Birch S': true, 'Cedar One': true })
let radialType = $state<Extract<ChartType, 'radial-column' | 'nightingale' | 'radial-bar'>>('radial-column')
const radar = $derived<ChartSpec>({
type: 'radar',
categories: attributes,
series: products.map((p) => ({ ...p, visible: shown[p.label] !== false })),
title: 'Three phones, six attributes',
subtitle: 'Scores out of 100',
yAxis: { min: 0, max: 100 },
height: 360,
})
const radial = $derived<ChartSpec>({
type: radialType,
categories: radialType === 'radial-bar' ? MONTHS.slice(0, 6) : MONTHS,
series: radialType === 'radial-bar' ? [{ label: 'Online', values: sales[0]!.values.slice(0, 6) }] : sales,
stacked: radialType === 'radial-column',
title: radialType === 'radial-column' ? 'Sales around the year' : radialType === 'nightingale' ? 'Sales as a nightingale' : 'First half, one ring per month',
subtitle: radialType === 'radial-column' ? 'Stacked columns on a circle' : radialType === 'nightingale' ? 'The radius carries the value, every slice the same angle' : 'Radial bars read against the shared scale',
valueFormat: 'compact',
height: 360,
})
// Chart | Grid: the same spec as the chart or as the rows behind it.
let view = $state<'chart' | 'grid'>('chart')
</script>
<section class="wrap">
<header class="chrome">
<ViewSwitch bind:value={view} options={[['chart', 'Chart'], ['grid', 'Grid']]} />
{#each products as p (p.label)}
<label class="chk"><input type="checkbox" bind:checked={shown[p.label]} /> {p.label}</label>
{/each}
<label class="ctl">
Radial
<select bind:value={radialType}>
<option value="radial-column">Radial column</option>
<option value="nightingale">Nightingale</option>
<option value="radial-bar">Radial bar</option>
</select>
</label>
<span class="note">
A radar with one axis per attribute and a polygon per product, and the same monthly series as
radial columns, a nightingale and radial bars; only the type changes.
</span>
</header>
<div class="row">
<div class="pane">
{#if view === 'chart'}
<SvChart spec={radar} legend="bottom" autosize />
{:else}
<ChartDataGrid spec={radar} />
{/if}
</div>
<div class="pane">
{#if view === 'chart'}
<SvChart spec={radial} legend="bottom" autosize />
{:else}
<ChartDataGrid spec={radial} />
{/if}
</div>
</div>
</section>
<style>
.wrap {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
gap: 10px;
overflow: auto;
}
.chrome {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
flex: none;
}
.note {
font-size: 12px;
color: var(--sg-muted, #64748b);
}
.chk,
.ctl {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 12px;
color: var(--sg-fg, #0f172a);
white-space: nowrap;
}
.ctl select {
font: inherit;
border: 1px solid var(--sg-border, #e2e8f0);
border-radius: 6px;
background: var(--sg-bg, #fff);
color: inherit;
padding: 2px 6px;
}
.row {
display: flex;
flex: none;
gap: 12px;
flex-wrap: wrap;
}
.pane {
flex: 1 1 380px;
border: 1px solid var(--sg-border, #e2e8f0);
border-radius: 10px;
background: var(--sg-bg, #fff);
padding: 8px 10px;
min-width: 0;
}
</style>More Charts examples
- Chart a selection (context menu) - With integrated charting enabled, the right-click menu gains a Chart selected range item: select a block of cells, right-click, and the chart panel opens scoped to that range - the Excel chart-this gesture, built in. The item appears only when charting is on and is appended to the default context menu automatically.
- Chart view of the grid - The `chart` prop turns the same <SvGrid> into a chart, driven by the grid’s filtered + sorted rows (search + sort flow through). A view of the grid like board and scheduler, but the renderer is free: the grid lazy-loads a built-in view wrapping the standalone SvChart via rowsToChartSpec. Flip Table <-> Chart (bar / line / area) over one source of truth.
- Candlestick / OHLC - Candlestick and OHLC price marks with an ordinal date axis. ChartSeries.ohlc carries the four prices while values keeps the closes, so the CSV export, the screen-reader table and a 10-day moving average all work with no candle-specific code. Toggle the axis: a real time axis opens a gap over every weekend, an ordinal one spaces sessions evenly and still labels them by date. The strip under the plot is the chart brush: drag its window to pan, drag an edge to resize.
- Box plot + error bars - Distribution rather than average: box plots with the 1.5 IQR whisker rule and individual outliers, next to the same data as a bar chart of the means with error bars. two regions with nearly identical means sit side by side in the bars and look nothing alike in the boxes. boxStats() summarises a raw sample, rowsToBoxSpec() does it per group, and ChartSeries.errors annotates any existing mark without changing its type.
- Axes, titles and styling - The chart hands the decisions back: a numeric x axis that spreads payload sizes the way the numbers do, pinned domains with a fixed tick interval and a formatter per axis, grid lines on or off, a shaded budget band, a vertical reference line, per-point markers and colours, a stepped dashed series on the right axis, a title / subtitle / caption, a legend on any side, and a tooltip snippet in single-series mode. Below it, 50,000 monitoring readings decimated to one point per pixel with LTTB or min / max - toggle it off to see the cost.