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. Open the interactive demo or read the documentation.

What this example shows

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

Source code (161-chart-radar.svelte)

<!-- Documented in: docs/help/charts.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 ColumnDef,
    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: ColumnDef<typeof features, 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'],
  }))
</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-h-0">
      <SvGrid responsive={true}
        data={rows}
        columns={columns}
        features={features}
        sortable
        filterable
        selectionMode="none"
        enableRowSummaries={false}
        rowHeight={32}
        containerHeight="100%"
        fitColumns={true}
        onApiReady={(a) => { api = a; sync() }}
        onFiltersChange={sync}
        onSortingChange={sync}
      />
    </div>
    <div class="shrink-0 rounded-lg border p-3" style="width: 580px; border-color: var(--sg-border); background: var(--sg-bg);">
      <SvGridChart {spec} />
    </div>
  </div>
</section>

View this example on GitHub

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.
  • Integrated charts (no deps) - Chart the grid data with no external charting library. SvGridChart renders a ChartSpec; rowsToChartSpec aggregates the grid current (filtered/sorted) rows into one. Bar, line, area, pie - plus 100% stacked, top-N + Other, an average reference line, and double-click-to-isolate a series. Filter the grid and the chart re-aggregates live.
  • Scatter / bubble chart - A scatter plot maps two numeric measures (x vs y); a bubble chart adds a third via dot radius. type: scatter with series points [{ x, y, r }]. Spend vs revenue, sized by deals, coloured by region, with an average-revenue reference line. Filter the grid and the cloud re-plots.
  • Time-series chart (date axis) - xType: time spaces points by ACTUAL time - irregular date gaps render proportionally - and shows real date ticks. A referenceLines target/SLA line spans the plot; toggle 100% stacked to read each day as a share of its total. Line, stacked area, or stacked bar.