Radar chart (product comparison)
type: radar plots each category as a spoke; every series draws a polygon connecting its values across the spokes. Shared scale makes two products read against each other directly. Click the legend to isolate one.
A live, editable Svelte 5 data grid example from the SvGrid gallery (Charts). See the SvGrid documentation for the full API.
About this example
A radar chart comparing products in Svelte 5. type radar plots each category, speed, quality, price, durability, ergonomics and warranty, as a spoke, and every series draws a polygon connecting its values across the spokes on a shared scale, so two products read against each other directly. Click the legend to isolate one, and filter the grid to change which products are plotted.
type: 'radar' plots each category as a spoke; each series draws a polygon connecting its values across the spokes. Every series shares the same scale (max across all values), so two products read directly against each other. Toggle a series via the legend to focus.
Imports, features and API used
Imports: @svgrid/grid
Table features registered: rowSortingFeature, columnFilteringFeature
Columns: product (Product), speed (Speed), quality (Quality), price (Price), durability (Durability), ergonomics (Ergonomics), warranty (Warranty)
SvGridApi methods called: api.getDisplayedRows()
Frequently asked questions
How is the scale chosen?
All spokes share one scale from zero to the maximum value across every series, so the polygons are comparable; normalise your columns to the same range first if their units differ.
How are categories ordered?
In the order of the categories array, clockwise from the top; reorder the array to put related attributes next to each other.
How many series are practical?
Three or four polygons stay readable; beyond that use the legend to isolate one at a time, which a click does.
Related documentation
Related articles
- Evaluating SVAR Svelte DataGrid? What Changes if You Pick SvGrid - A fair comparison of SVAR Svelte DataGrid and SvGrid - both MIT, both Svelte-native - covering suite breadth, grouping and pivot, the headless engine, download numbers, and what porting actually costs.
- Bundle Size of Svelte Data Grids - How to Compare - README bundle numbers are nearly useless. Here is how to measure the real delta a data grid adds to your Svelte app, and why feature-gated architectures change the math entirely.
- Porting a React MUI X DataGrid Screen to Svelte - A practical mapping from MUI X DataGrid to SvGrid - columns, cell renderers, server-side data, and the hooks that disappear when you switch to Svelte runes.
Source code (161-chart-radar.svelte)
<!-- Documented in: docs/help/charts/types.md -->
<script lang="ts">
/**
* 161. Radar chart (product comparison)
* --------------------------------------
* `type: 'radar'` plots each `category` as a spoke; each series draws a
* polygon connecting its values across the spokes. Every series shares
* the same scale (max across all values), so two products read directly
* against each other. Toggle a series via the legend to focus.
*/
import {
SvGrid,
SvGridChart,
tableFeatures,
rowSortingFeature,
columnFilteringFeature,
type GridColumns,
type SvGridApi,
type ChartSpec,
} from '@svgrid/grid'
const features = tableFeatures({ rowSortingFeature, columnFilteringFeature })
type Row = { id: number; product: string; speed: number; quality: number; price: number; durability: number; ergonomics: number; warranty: number }
const rows: Row[] = [
{ id: 0, product: 'Aurora X1', speed: 92, quality: 84, price: 70, durability: 65, ergonomics: 88, warranty: 60 },
{ id: 1, product: 'Borealis 9', speed: 75, quality: 90, price: 60, durability: 80, ergonomics: 70, warranty: 85 },
{ id: 2, product: 'Cipher Pro', speed: 88, quality: 72, price: 55, durability: 90, ergonomics: 60, warranty: 75 },
]
const AXES = ['speed', 'quality', 'price', 'durability', 'ergonomics', 'warranty'] as const
const columns: GridColumns<Row> = [
{ field: 'product', header: 'Product', width: 130 },
{ field: 'speed', header: 'Speed', width: 90, align: 'right' },
{ field: 'quality', header: 'Quality', width: 90, align: 'right' },
{ field: 'price', header: 'Price', width: 90, align: 'right' },
{ field: 'durability', header: 'Durability', width: 100, align: 'right' },
{ field: 'ergonomics', header: 'Ergonomics', width: 100, align: 'right' },
{ field: 'warranty', header: 'Warranty', width: 100, align: 'right' },
]
let api = $state<SvGridApi<typeof features, Row> | null>(null)
let displayed = $state<Row[]>(rows)
function sync() { displayed = (api?.getDisplayedRows() as Row[]) ?? rows }
const spec = $derived.by<ChartSpec>(() => ({
type: 'radar',
categories: AXES.map((a) => a.charAt(0).toUpperCase() + a.slice(1)) as string[],
series: displayed.map((r) => ({
label: r.product,
values: AXES.map((a) => r[a] as number),
})),
width: 540,
height: 380,
palette: ['#2563eb', '#16a34a', '#f59e0b'],
}))
/** Pane size, so the chart fills its card rather than a fixed viewBox. */
let paneW = $state(0)
let paneH = $state(0)
</script>
<section class="flex flex-col flex-1 min-h-0 gap-3">
<div class="shrink-0 rounded-lg border px-4 py-3" style="border-color: var(--sg-border); background: var(--sg-header-bg);">
<p class="text-sm font-semibold" style="color: var(--sg-fg);">
Radar: product comparison across 6 attributes
</p>
<p class="mt-0.5 text-xs" style="color: var(--sg-muted);">
Each polygon is one product; each spoke is one attribute (0..100). Click the legend chips
to isolate. Filter or sort the grid - only displayed rows draw polygons.
</p>
</div>
<div class="flex flex-1 min-h-0 gap-3">
<div class="flex-1 min-w-0 min-h-0">
<SvGrid responsive={true}
columnResize
data={rows}
columns={columns}
features={features}
sortable
filterable
selectionMode="none"
rowHeight={32}
containerHeight="100%"
fitColumns={true}
onApiReady={(a) => { api = a; sync() }}
onFiltersChange={sync}
onSortingChange={sync}
/>
</div>
<div class="rounded-lg border p-3" style="flex: 0 1 580px; min-width: 0; min-height: 0; border-color: var(--sg-border); background: var(--sg-bg);">
<!-- Measured box, not the card: its height comes from the parent, so the
chart cannot push the thing it is sized against. -->
<div style="width: 100%; height: 100%; min-height: 0;" bind:clientWidth={paneW} bind:clientHeight={paneH}>
{#if paneW > 40 && paneH > 40}
<SvGridChart {spec} width={paneW} height={paneH} />
{/if}
</div>
</div>
</div>
</section>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.