Every chart type

Every chart type from one dataset, thirty live thumbnails: bar, horizontal bar, line, area, lollipop, dumbbell, range bar, range area, pareto, radial column, radial bar, nightingale, pie / donut, tree map, sunburst, funnel, waterfall, sankey, chord, radar, heat map, scatter, box plot, histogram, gauge, bullet, calendar, stream, candlestick and OHLC. Click a card for the full-size chart and its variants: classic / hollow / Heikin-Ashi candles, funnel / pyramid / cone, wiggle / silhouette streams, stacking. Every spec is built with the helpers the grid chart panel uses.

A live, editable Svelte 5 data grid example from the SvGrid gallery (Charts). See the SvGrid documentation for the full API.

About this example

Every chart type in the Svelte 5 grid's chart engine, drawn from one dataset as thirty live thumbnails: bar, horizontal bar, line, area, lollipop, dumbbell, range bar, range area, pareto, radial column, radial bar, nightingale, pie and donut, tree map, sunburst, funnel, waterfall, sankey, chord, radar, heat map, scatter, box plot, histogram, gauge, bullet, calendar, stream, candlestick and OHLC. Click a card for the full size chart and its variants: classic, hollow and Heikin-Ashi candles, funnel, pyramid and cone shapes, wiggle and silhouette streams, stacking. Every spec is built with the helpers the grid's chart panel uses. Free, in @svgrid/grid.

One dataset (a year of orders across four regions and three channels), every chart type the engine draws, each thumbnail a live SvChart. Click one to see it full size with its own variants: candle style, funnel shape, stream baseline, donut hole, stacking.

The specs are built with the same helpers the grid's chart panel uses, so what this gallery shows is what charting offers from the picker: rowsToChartSpec for the category types, rowsToHistogramSpec, rowsToRangeSpec, rowsToBoxSpec, specToTreemap / specToSankey for the hierarchy and flow types, and paretoSpec for the pareto.

Free, in @svgrid/grid.

Imports, features and API used

Imports: @svgrid/grid

Frequently asked questions

Do I need a charting library for these?

No. The engine is part of @svgrid/grid and renders SVG with no dependency. Each thumbnail is the same SvChart component the grid panel renders, with a small spec.

How does a histogram get its bins?

rowsToHistogramSpec bins a numeric column with binValues. Pass bins for a fixed count, binWidth for a fixed width, or a method: sturges, fd or sqrt. The panel exposes the count as a Bins input.

What is the difference between a radial column, a radial bar and a nightingale?

A radial column wraps a bar chart round a circle, one angular slot per category with the radius carrying the value. A radial bar draws one ring per category with the arc length carrying the value. A nightingale is a radial column with a square root radius and no gap, the rose Florence Nightingale used.

Related documentation

Related articles

Source code (435-chart-type-gallery.svelte)

<!-- Documented in: docs/help/charts/types.md -->
<script lang="ts">
  /**
   * 435. Every chart type
   * ---------------------
   * One dataset (a year of orders across four regions and three channels),
   * every chart type the engine draws, each thumbnail a live `SvChart`. Click
   * one to see it full size with its own variants: candle style, funnel
   * shape, stream baseline, donut hole, stacking.
   *
   * The specs are built with the same helpers the grid's chart panel uses,
   * so what this gallery shows is what `charting` offers from the picker:
   * `rowsToChartSpec` for the category types, `rowsToHistogramSpec`,
   * `rowsToRangeSpec`, `rowsToBoxSpec`, `specToTreemap` / `specToSankey` for
   * the hierarchy and flow types, and `paretoSpec` for the pareto.
   *
   * Free, in @svgrid/grid.
   */
  import {
    SvChart,
    rowsToChartSpec,
    rowsToHistogramSpec,
    rowsToRangeSpec,
    rowsToBoxSpec,
    rowsToScatterSpec,
    specToTreemap,
    specToSankey,
    specToCalendar,
    type ChartSpec,
    type ChartType,
  } from '@svgrid/grid'

  type Order = { id: number; day: string; region: string; channel: string; revenue: number; cost: number; units: number }
  const rows: Order[] = (() => {
    let seed = 11
    const rnd = () => ((seed = (seed * 1103515245 + 12345) % 2147483648) / 2147483648)
    const regions = ['North', 'South', 'East', 'West']
    const channels = ['Web', 'Retail', 'Partner']
    const out: Order[] = []
    const start = Date.UTC(2026, 0, 1)
    for (let i = 0; i < 400; i += 1) {
      const day = new Date(start + Math.floor(rnd() * 365) * 86_400_000).toISOString().slice(0, 10)
      const region = regions[Math.floor(rnd() * 4)]!
      const channel = channels[Math.floor(rnd() * 3)]!
      const units = 1 + Math.floor(rnd() * 12)
      const price = 40 + rnd() * 160
      const revenue = Math.round(units * price)
      out.push({ id: i + 1, day, region, channel, revenue, cost: Math.round(revenue * (0.45 + rnd() * 0.3)), units })
    }
    return out
  })()

  // ---- Variants the full-size view can toggle -------------------------------
  let candleStyle = $state<'classic' | 'hollow' | 'heikin-ashi'>('classic')
  let funnelShape = $state<'trapezoid' | 'pyramid' | 'cone'>('trapezoid')
  let stackOffset = $state<'wiggle' | 'silhouette' | 'zero'>('wiggle')
  let stacked = $state(false)

  const byRegion = rowsToChartSpec(rows, { type: 'bar', category: 'region', value: 'revenue' })
  const byRegionChannel = rowsToChartSpec(rows, { type: 'bar', category: 'region', value: 'revenue', series: 'channel' })
  const byMonth = rowsToChartSpec(rows, { type: 'line', category: 'day', value: 'revenue', bucket: 'month' })
  const byMonthChannel = rowsToChartSpec(rows, { type: 'area', category: 'day', value: 'revenue', series: 'channel', bucket: 'month' })
  const twoMeasures = rowsToChartSpec(rows, { type: 'bar', category: 'region', value: ['cost', 'revenue'] })
  const monthlyOhlc = (() => {
    // A price series from the monthly revenue, so the candles have something to say.
    const cats = byMonth.categories
    const v = byMonth.series[0]!.values
    const ohlc = v.map((c, i) => {
      const o = i ? v[i - 1]! : c * 0.95
      return { o, c, h: Math.max(o, c) * 1.04, l: Math.min(o, c) * 0.96 }
    })
    return { categories: cats, ohlc, closes: v }
  })()

  type Card = { type: ChartType; label: string; group: string; spec: () => ChartSpec }
  const cards: Card[] = [
    { type: 'bar', label: 'Bar', group: 'Compare', spec: () => ({ ...byRegionChannel, type: 'bar', stacked }) },
    { type: 'bar', label: 'Horizontal bar', group: 'Compare', spec: () => ({ ...byRegion, type: 'bar', orientation: 'horizontal' }) },
    { type: 'line', label: 'Line', group: 'Compare', spec: () => ({ ...byMonthChannel, type: 'line' }) },
    { type: 'area', label: 'Area', group: 'Compare', spec: () => ({ ...byMonthChannel, type: 'area', stacked }) },
    { type: 'lollipop', label: 'Lollipop', group: 'Compare', spec: () => ({ ...byRegion, type: 'lollipop' }) },
    { type: 'dumbbell', label: 'Dumbbell', group: 'Compare', spec: () => ({ ...rowsToRangeSpec(twoMeasures.categories.map((c, i) => ({ region: c, cost: twoMeasures.series[0]!.values[i]!, revenue: twoMeasures.series[1]!.values[i]! })), { category: 'region', low: 'cost', high: 'revenue', type: 'range-bar' }), type: 'dumbbell' }) },
    { type: 'range-bar', label: 'Range bar', group: 'Compare', spec: () => rowsToRangeSpec(twoMeasures.categories.map((c, i) => ({ region: c, cost: twoMeasures.series[0]!.values[i]!, revenue: twoMeasures.series[1]!.values[i]! })), { category: 'region', low: 'cost', high: 'revenue' }) },
    { type: 'range-area', label: 'Range area', group: 'Compare', spec: () => ({ ...byMonth, type: 'range-area', series: [{ label: 'Revenue band', values: byMonth.series[0]!.values.map((v) => v * 1.15), lowValues: byMonth.series[0]!.values.map((v) => v * 0.85) }] }) },
    { type: 'pareto', label: 'Pareto', group: 'Compare', spec: () => ({ ...rowsToChartSpec(rows, { type: 'bar', category: 'channel', value: 'units' }), type: 'pareto' }) },
    { type: 'radial-column', label: 'Radial column', group: 'Compare', spec: () => ({ ...byRegionChannel, type: 'radial-column', stacked }) },
    { type: 'radial-bar', label: 'Radial bar', group: 'Compare', spec: () => ({ ...byRegion, type: 'radial-bar' }) },
    { type: 'nightingale', label: 'Nightingale', group: 'Compare', spec: () => ({ ...rowsToChartSpec(rows, { type: 'bar', category: 'day', value: 'units', bucket: 'month' }), type: 'nightingale', categories: byMonth.categories.map((c) => new Date(c).toLocaleDateString(undefined, { month: 'short' })) }) },
    { type: 'pie', label: 'Pie / donut', group: 'Part of a whole', spec: () => ({ ...byRegion, type: 'pie', innerRadius: 0.55 }) },
    { type: 'treemap', label: 'Tree map', group: 'Part of a whole', spec: () => ({ ...byRegionChannel, type: 'treemap', treemap: specToTreemap(byRegionChannel) }) },
    { type: 'sunburst', label: 'Sunburst', group: 'Part of a whole', spec: () => ({ ...byRegionChannel, type: 'sunburst', tree: specToTreemap(byRegionChannel) }) },
    { type: 'funnel', label: 'Funnel', group: 'Flow', spec: () => ({ type: 'funnel', categories: ['Visits', 'Sign-ups', 'Trials', 'Paid'], series: [{ label: 'Users', values: [12400, 5100, 2300, 880] }], funnelShape }) },
    { type: 'waterfall', label: 'Waterfall', group: 'Flow', spec: () => ({ type: 'waterfall', categories: ['Start', 'Web', 'Retail', 'Partner', 'Returns', 'End'], series: [{ label: 'P&L', values: [120, 48, 31, 22, -19, 0] }], waterfallTotals: [true, false, false, false, false, true] }) },
    { type: 'sankey', label: 'Sankey', group: 'Flow', spec: () => { const f = specToSankey(byRegionChannel); return { ...byRegionChannel, type: 'sankey', sankeyNodes: f.nodes, sankeyLinks: f.links } } },
    { type: 'chord', label: 'Chord', group: 'Flow', spec: () => { const f = specToSankey(byRegionChannel); return { ...byRegionChannel, type: 'chord', sankeyNodes: f.nodes, sankeyLinks: f.links } } },
    { type: 'radar', label: 'Radar', group: 'Distribution', spec: () => ({ ...byRegionChannel, type: 'radar' }) },
    { type: 'heatmap', label: 'Heat map', group: 'Distribution', spec: () => ({ ...byRegionChannel, type: 'heatmap' }) },
    { type: 'scatter', label: 'Scatter', group: 'Distribution', spec: () => rowsToScatterSpec(rows.slice(0, 120), { x: 'cost', y: 'revenue', series: 'channel', r: 'units' }) },
    { type: 'boxplot', label: 'Box plot', group: 'Distribution', spec: () => rowsToBoxSpec(rows, { category: 'region', value: 'revenue' }) },
    { type: 'histogram', label: 'Histogram', group: 'Distribution', spec: () => rowsToHistogramSpec(rows, { value: 'revenue', bins: 12 }) },
    { type: 'gauge', label: 'Gauge', group: 'Single value', spec: () => ({ type: 'gauge', categories: [], series: [], gaugeValue: 72, gaugeMin: 0, gaugeMax: 100, gaugeTarget: 80, gaugeUnit: '%', gaugeRanges: [{ from: 0, to: 50, color: '#ef4444' }, { from: 50, to: 75, color: '#f59e0b' }, { from: 75, to: 100, color: '#16a34a' }] }) },
    { type: 'bullet', label: 'Bullet', group: 'Single value', spec: () => ({ type: 'bullet', categories: byRegion.categories, series: [{ label: 'Revenue', values: byRegion.series[0]!.values, targets: byRegion.series[0]!.values.map((v, i) => v * (0.9 + (i % 3) * 0.1)) }], bulletRanges: [{ from: 0, to: 12000, color: 'rgba(148,163,184,0.35)' }, { from: 12000, to: 20000, color: 'rgba(148,163,184,0.22)' }, { from: 20000, to: 32000, color: 'rgba(148,163,184,0.12)' }] }) },
    { type: 'calendar', label: 'Calendar', group: 'Over time', spec: () => { const daily = rowsToChartSpec(rows, { type: 'bar', category: 'day', value: 'units' }); return { ...daily, type: 'calendar', calendarValues: specToCalendar(daily) } } },
    { type: 'stream', label: 'Stream', group: 'Over time', spec: () => ({ ...byMonthChannel, type: 'stream', stacked: true, stackOffset }) },
    { type: 'candlestick', label: 'Candlestick', group: 'Over time', spec: () => ({ type: 'candlestick', categories: monthlyOhlc.categories, series: [{ label: 'Revenue', values: monthlyOhlc.closes, ohlc: monthlyOhlc.ohlc, overlay: 'sma:3' }], xType: 'ordinal-time', candleStyle }) },
    { type: 'ohlc', label: 'OHLC', group: 'Over time', spec: () => ({ type: 'ohlc', categories: monthlyOhlc.categories, series: [{ label: 'Revenue', values: monthlyOhlc.closes, ohlc: monthlyOhlc.ohlc }], xType: 'ordinal-time' }) },
  ]
  const groups = [...new Set(cards.map((c) => c.group))]

  let selected = $state(0)
  const card = $derived(cards[selected]!)
  const fullSpec = $derived<ChartSpec>({ ...card.spec(), title: card.label, height: 320 })
  const thumb = (c: Card): ChartSpec => {
    const s = c.spec()
    // Thumbnails: small, quiet, and never more than a few categories wide.
    return { ...s, width: 160, height: 100, title: undefined, yAxisTitle: undefined, xAxisTitle: undefined, referenceLines: undefined, xAxis: { ...(s.xAxis ?? {}), labels: false, title: undefined }, yAxis: { ...(s.yAxis ?? {}), labels: false, title: undefined }, y2Axis: { ...(s.y2Axis ?? {}), labels: false, title: undefined } }
  }
</script>

<section class="wrap">
  <header class="chrome">
    <span class="note">{cards.length} live thumbnails from one dataset. Click a card; the variants under the big chart apply to it.</span>
  </header>
  <div class="body">
    <div class="gallery-col">
      {#each groups as g (g)}
        <div class="group-label">{g}</div>
        <div class="gallery">
          {#each cards as c, i (i)}
            {#if c.group === g}
              <button type="button" class="thumb" class:is-selected={selected === i} aria-pressed={selected === i} title={c.label} onclick={() => (selected = i)}>
                <div class="thumb-chart"><SvChart spec={thumb(c)} interactive={false} legend={false} toolbar={false} /></div>
                <span class="thumb-label">{c.label}</span>
              </button>
            {/if}
          {/each}
        </div>
      {/each}
    </div>
    <div class="pane">
      <SvChart spec={fullSpec} legend={card.type === 'pie' || card.type === 'radial-bar' || card.type === 'nightingale' ? 'right' : true} zoomable={['line', 'area', 'candlestick', 'ohlc', 'stream', 'range-area'].includes(card.type)} />
      <div class="variants">
        {#if card.type === 'candlestick'}
          <label class="ctl">Candles
            <select bind:value={candleStyle}><option value="classic">Classic</option><option value="hollow">Hollow</option><option value="heikin-ashi">Heikin-Ashi</option></select>
          </label>
        {:else if card.type === 'funnel'}
          <label class="ctl">Shape
            <select bind:value={funnelShape}><option value="trapezoid">Funnel</option><option value="pyramid">Pyramid</option><option value="cone">Cone</option></select>
          </label>
        {:else if card.type === 'stream'}
          <label class="ctl">Baseline
            <select bind:value={stackOffset}><option value="wiggle">Wiggle</option><option value="silhouette">Silhouette</option><option value="zero">Zero (stacked area)</option></select>
          </label>
        {:else if card.type === 'bar' || card.type === 'area' || card.type === 'radial-column'}
          <label class="chk"><input type="checkbox" bind:checked={stacked} /> Stacked</label>
        {:else}
          <span class="note">No variants for this type. The type name is the only thing that changed in the spec.</span>
        {/if}
      </div>
    </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);
  }
  .body {
    display: flex;
    gap: 12px;
    flex: 1;
    min-height: 0;
    align-items: flex-start;
  }
  .gallery-col {
    flex: 0 0 520px;
    max-height: 100%;
    overflow: auto;
    padding-right: 4px;
  }
  .group-label {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--sg-muted, #64748b);
    margin: 6px 0 4px;
  }
  .gallery {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 6px;
  }
  .thumb {
    border: 1.5px solid var(--sg-border, #e2e8f0);
    border-radius: 8px;
    background: var(--sg-bg, #fff);
    padding: 4px 4px 2px;
    cursor: pointer;
    text-align: left;
    color: var(--sg-fg, #0f172a);
    font: inherit;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
  }
  .thumb:hover {
    border-color: var(--sg-accent, #2563eb);
  }
  .thumb.is-selected {
    border-color: var(--sg-accent, #2563eb);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--sg-accent, #2563eb) 30%, transparent);
  }
  .thumb-chart {
    pointer-events: none;
    line-height: 0;
  }
  .thumb-label {
    display: block;
    font-size: 11px;
    font-weight: 600;
    padding: 2px 2px 0;
  }
  .pane {
    flex: 1;
    min-width: 0;
    border: 1px solid var(--sg-border, #e2e8f0);
    border-radius: 10px;
    background: var(--sg-bg, #fff);
    padding: 8px 10px;
  }
  .variants {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-top: 6px;
    min-height: 26px;
  }
  .chk,
  .ctl {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 12px;
    color: var(--sg-fg, #0f172a);
  }
  .ctl select {
    font: inherit;
    border: 1px solid var(--sg-border, #e2e8f0);
    border-radius: 6px;
    background: var(--sg-bg, #fff);
    color: inherit;
    padding: 2px 6px;
  }
  @media (max-width: 860px) {
    .body {
      flex-direction: column;
    }
    .gallery-col {
      flex: none;
      width: 100%;
      max-height: none;
    }
  }
</style>

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