Integrated charts

SvGrid can chart its own data with no external charting library. Two pieces:

Feed it api.getDisplayedRows() and the chart reflects the grid's current, filtered, sorted data - the "chart from the grid" enterprise feature.

The grid's filtered and sorted rows flow through rowsToChartSpec into SvGridChart, which re-renders whenever the grid's filters or sorting change.

The examples on this page import from @svgrid/grid:

<script lang="ts">
  import { SvGrid, SvGridChart } from '@svgrid/grid'
</script>
<script lang="ts">
  import { SvGrid, SvGridChart, rowsToChartSpec, type SvGridApi } from '@svgrid/grid'

  let api: SvGridApi<F, Row> | null = null
  let displayed = $state<Row[]>(rows)
  const sync = () => (displayed = (api?.getDisplayedRows() as Row[]) ?? rows)

  const spec = $derived(
    rowsToChartSpec(displayed, { type: 'bar', category: 'region', value: 'revenue', reduce: 'sum' }),
  )
</script>

<SvGrid {data} {columns} {features} sortable filterable
  onApiReady={(a) => { api = a; sync() }}
  onFiltersChange={sync} onSortingChange={sync} />

<SvGridChart {spec} />

rowsToChartSpec

Option Meaning
type 'bar' | 'line' | 'area' | 'pie' | 'scatter'
category Field whose distinct values become the x-axis / slices.
value Numeric field, or an array of fields (one series each).
series Pivot field: one series per distinct value of it.
reduce 'sum' (default), 'avg', or 'count'.
stacked Stack the series instead of grouping them.
stacked100 Stack to 100% - each category normalized to its total.
sort 'value-desc' | 'value-asc' | 'category' | 'none'.
topN Keep the top N categories, bucket the rest into "Other".
otherLabel Label for the bucket (default 'Other').
width / height SVG viewBox size.

Three multi-series shapes:

rowsToChartSpec(rows, { type: 'bar', category: 'region', value: 'revenue' })            // 1 series
rowsToChartSpec(rows, { type: 'bar', category: 'region', value: ['revenue', 'cost'] })  // 2 series
rowsToChartSpec(rows, { type: 'bar', category: 'region', value: 'sales', series: 'product' }) // pivot

Building a spec yourself

SvGridChart takes any ChartSpec. Per-series type and axis give you combo charts and a secondary Y axis; stacked stacks bars/areas; innerRadius turns a pie into a donut; yAxisTitle / y2AxisTitle / xAxisTitle label the axes. Negative values drop below a zero baseline automatically, and null / NaN values break the line (a gap) instead of dropping it to zero.

const spec: ChartSpec = {
  type: 'bar',
  stacked: false,
  categories: ['Q1', 'Q2', 'Q3', 'Q4'],
  series: [
    { label: 'Revenue', values: [120, 140, 90, 180] },                 // bars, left axis
    { label: 'Margin %', values: [0.31, 0.28, 0.22, 0.35], type: 'line', axis: 'right' }, // line, right axis
  ],
}
// donut:  { type: 'pie', innerRadius: 0.6, categories, series: [...] }

The geometry helper buildChart(spec) is exported too, if you want the raw SVG primitives for a custom renderer.

Reference / target lines

referenceLines draws horizontal goal / average / SLA lines across the plot. Each entry stretches the axis domain so the line is always in view:

const spec: ChartSpec = {
  type: 'bar', categories: ['Q1', 'Q2', 'Q3', 'Q4'],
  series: [{ label: 'Revenue', values: [120, 140, 90, 180] }],
  referenceLines: [{ value: 150, label: 'Target', axis: 'left', color: '#ef4444', dashed: true }],
}

Number format, currency and locale

valueFormat picks the shape of every number the chart draws - the value axis, tooltips, data labels and reference lines:

{ valueFormat: 'currency' }   // 'number' | 'currency' | 'percent' | 'compact'

By itself that formats in the chart's own compact style, and currency means a $. Add currency (an ISO 4217 code) and locale (BCP-47) to format through Intl.NumberFormat instead:

{ valueFormat: 'currency', currency: 'EUR', locale: 'de-DE' }

Setting either one switches the whole chart over, so separators, the decimal mark and the compact suffixes follow the locale rather than English. Leaving both unset keeps the original output exactly, which is deliberate: Intl's compact form is not the same string even for en-US (1.2K, capitalised), so formatting everything through it would restyle every axis already drawn.

A grid inherits this. localization.locale is the chart's default locale, so a grid that is already localized gets a localized chart without repeating itself, and charting.locale overrides it when the two really should differ:

<SvGrid
  {data} {columns}
  localization={{ locale: 'de-DE' }}
  charting={{ valueFormat: 'currency', currency: 'EUR' }}
/>

Currency has no such default, because only the application knows what the numbers are denominated in. Ask for valueFormat: 'currency' without one and the chart falls back to USD rather than refusing to draw.

100% stacked

stacked100: true (implies stacked) normalizes each category to its own total, so the axis runs 0..100% and every column fills the plot height - ideal for reading composition / share. Tooltips and labels still show the original values.

Scatter / bubble

type: 'scatter' plots two numeric measures against each other. Each series carries points: [{ x, y, r?, label? }]; an optional r becomes the bubble radius (scaled across the data). One series per group colours the points.

const spec: ChartSpec = {
  type: 'scatter', categories: [],
  xAxisTitle: 'Spend', yAxisTitle: 'Revenue',
  series: [
    { label: 'EMEA', values: [], points: [{ x: 12_000, y: 80_000, r: 18, label: 'Ada' }] },
    { label: 'APAC', values: [], points: [{ x: 30_000, y: 140_000, r: 33, label: 'Grace' }] },
  ],
}

Horizontal bars

orientation: 'horizontal' swaps the axes: categories run down the left, bars grow rightward. It suits long category labels (rep names, product names) that would otherwise crowd / rotate on a vertical x-axis. Grouped, stacked, 100%, data labels, and reference lines (which become vertical) all work. Only applies when every series is a bar - combo / line / area fall back to vertical.

const spec: ChartSpec = {
  type: 'bar', orientation: 'horizontal',
  categories: ['Ada', 'Grace', 'Margaret', 'Linus'],
  series: [{ label: 'Revenue', values: [120, 90, 140, 80] }],
  referenceLines: [{ value: 110, label: 'Avg' }],   // drawn as a vertical line
}

Time axis

xType: 'time' treats categories as dates: x positions are spaced by actual time (irregular gaps render proportionally, not evenly) and the axis shows real date ticks. Works with line / area / bar.

rowsToChartSpec(rows, { type: 'line', category: 'date', value: 'sessions', series: 'channel' })
// then: spec.xType = 'time'

Ordinal dates, for data with gaps

xType: 'ordinal-time' also reads categories as dates, but spaces them evenly and uses the dates only for the tick labels.

Use it whenever the gaps in your data are not meaningful. On a true time axis a series of trading sessions or business days opens a hole over every weekend three times as wide as a working day, which tells the reader nothing except that Saturday exists.

spec.xType = 'ordinal-time'

Candlestick and OHLC

Set ohlc on the series alongside values:

const spec: ChartSpec = {
  type: 'candlestick',            // or 'ohlc' for tick bars
  xType: 'ordinal-time',          // skip the days the market was shut
  categories: rows.map((r) => r.date),
  series: [{
    label: 'ACME',
    values: rows.map((r) => r.c), // the CLOSES - see below
    ohlc: rows.map((r) => ({ o: r.o, h: r.h, l: r.l, c: r.c })),
    overlay: 'sma:10',            // a moving average, for free
  }],
}

A rising bar draws hollow and a falling one filled, so direction reads from the shape as well as the colour. candleColors overrides the pair. A null entry in ohlc is a day with no session and draws nothing.

Put the closing prices in values too. Everything that reads a series generically reads values: the tooltip, the CSV export, the screen-reader table, and overlay. Filling it in is what lets a price series carry a moving average or export to CSV without a line of candle-specific code.

Give volume its own pane rather than the price chart's right axis. Both axes share the full plot height, so a right-axis volume series climbs up through the candles: technically correct, and unreadable. A second short chart over the same categories and the same xType is what a trading screen actually does, and the two stay in register because they share the axis mode.

Candlesticks are cartesian, so zoom, the brush mini-map, the crosshair tooltip and keyboard navigation all work. The price axis is not pinned to zero the way a bar chart's is, so a series trading between 180 and 195 uses the whole plot instead of a fifth of it.

Open the live example: Candlestick / OHLC (Charts)

The other chart types

Everything below reads the same categories + series shape as a bar chart, so you can switch type and keep the rest of the spec. They are all reachable from the built-in panel too, where the picker offers whichever ones your current columns can actually feed.

Waterfall

Running total, with bars that step up and down from where the last one left off. waterfallTotals marks the bars that are subtotals: those reset the running sum and span from zero.

{ type: 'waterfall', categories, series: [{ label: 'P&L', values }],
  waterfallTotals: [false, false, false, true] }

Only the first series is drawn: a waterfall is one running sequence, so a split-by has nothing to add.

Open the live example: Waterfall (signed P&L) (Charts)

Funnel

Stages narrowing to a conclusion. Each segment carries its own conversion and drop-off, so the chart answers "where did they go" rather than just "how many". Sort descending unless the data is already in stage order.

Open the live example: Funnel chart (signup conversion) (Charts)

Radar

One spoke per category, one polygon per series. Good for comparing a handful of things across the same handful of measures; poor above about eight spokes, where the shape stops being readable.

Open the live example: Radar chart (product comparison) (Charts)

Heat map

Series become rows, categories become columns, and each cell is coloured by value. colorScale picks the ramp: 'sequential' runs one hue from min to max, 'diverging' runs cold to warm around zero, or pass your own array of two or more hex colours.

A heat map needs a split-by: without one it has a single row.

Open the live example: Heatmap chart (Charts)

Tree map

Nested rectangles sized by value, laid out squarified so the shapes stay close to square and remain comparable. Pass treemap as a hierarchy, or let the panel build one from a group-by plus an optional split-by.

Only positive values have an area, so zero and negative leaves are dropped.

Open the live example: Tree-map (sales by region·category) (Charts)

Sankey

Flows between nodes. sankeyNodes names them and sankeyLinks carries { source, target, value }. From the panel, group-by is the source and split-by is the target.

Open the live example: Sankey diagram (user flow) (Charts)

Calendar

A year of days, GitHub-style: one cell per day, coloured by value. calendarValues takes { date: 'YYYY-MM-DD', value } samples and missing days render blank. calendarStart / calendarEnd set the window; by default it spans the data.

Open the live example: Calendar heatmap (year of days) (Charts)

Gauge

A single number on a semicircle dial.

{ type: 'gauge', categories: [], series: [],
  gaugeValue: 99.82, gaugeMin: 0, gaugeMax: 100, gaugeUnit: '%',
  gaugeTarget: 99.9,
  gaugeRanges: [
    { from: 0,  to: 70, color: '#ef4444' },
    { from: 70, to: 99.9, color: '#f59e0b' },
    { from: 99.9, to: 100, color: '#16a34a' },
  ] }

Bands are half-open [from, to), so a value sitting exactly on a boundary belongs to the band that boundary opens. They are drawn as a thin reference ring inside the dial rather than on the value arc: the arc is the reading, the bands only say what the scale means, and a band covering most of the scale should not look like a full dial.

Open the live example: Gauge dial (KPI dashboard) (Charts)

Box plot

The one chart here that answers "how spread out" rather than "how much", which is the question an average hides. ChartSeries.boxes carries a five-number summary per category, and values holds the medians alongside it, so tooltips, CSV export and overlays keep working with no box-specific code.

import { boxStats, rowsToBoxSpec } from '@svgrid/grid'

// One sample -> one summary, with the usual 1.5 IQR whisker rule.
boxStats([120, 130, 140, 150, 900])
// { min: 120, q1: 130, median: 140, q3: 150, max: 150, outliers: [900] }

// Per group, straight from rows. This is what the chart panel calls.
const spec = rowsToBoxSpec(rows, { category: 'region', value: 'ms' })

min and max are the whisker ends, not the extremes of the sample: whiskers stop at the last observation inside the fence, and anything past it comes back in outliers to be drawn as individual points. A category with no observations returns null and is left as a gap rather than a box at zero.

Pass series for side-by-side boxes per group. In the grid's own panel, Box plot is the one type that ignores the Aggregate control, because it does its own reduction - sum | avg | count has nothing to say about a distribution.

Open the live example: Box plot + error bars (Charts)

Error bars

Not a chart type. ChartSeries.errors annotates whatever mark the series already draws, so a bar, line, area or scatter series grows whiskers without changing its type:

{
  type: 'bar',
  categories: ['eu-west', 'us-east'],
  series: [{
    label: 'Mean',
    values: [120, 140],
    // A number is a symmetric +/- margin.
    errors: [12, 34],
    // Or set both ends explicitly: errors: [{ lo: 108, hi: 132 }, ...]
  }],
}

null skips one entry and still draws its mark. The value axis stretches to cover the whiskers, so a bar with a wide interval cannot clip at the top of the plot.

Drawing your own marks

Two snippets let you draw into the chart's own coordinate space. underlay paints beneath the built-in marks, overlay above them:

<script lang="ts">
  import { SvChart, type ChartSpec } from '@svgrid/grid'

  const spec: ChartSpec = {
    type: 'line',
    categories: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
    series: [{ label: 'Latency', values: [120, 180, 140, 260, 150] }],
  }
  const budget = 200
</script>

<SvChart {spec}>
  {#snippet underlay({ geo, yOf })}
    <!-- Everything over budget, shaded. -->
    {#if yOf}
      <rect
        x={geo.plot.x}
        y={geo.plot.y}
        width={geo.plot.w}
        height={Math.max(0, yOf(budget) - geo.plot.y)}
        fill="tomato"
        fill-opacity="0.08"
      />
    {/if}
  {/snippet}

  {#snippet overlay({ xOf, yOf })}
    <!-- A ring around the worst day. -->
    {#if xOf && yOf}
      <circle cx={xOf(3)} cy={yOf(260)} r="7" fill="none" stroke="tomato" stroke-width="2" />
    {/if}
  {/snippet}
</SvChart>

This is the extension point instead of a registry of custom series types: a chart mark is markup, so drawing one should be markup too.

Both snippets receive { geo, xOf, yOf, scales }. geo is the full ChartGeometry - every laid-out bar, line point, tick and plot rectangle. xOf(i) is the pixel x at the centre of category i (fractional indices interpolate), and yOf(value, axis?) is the pixel y for a value on the left or right axis.

Take the scales from here rather than recomputing them. The domain a chart was drawn against is nice-rounded, stretched to include zero for bar charts and to cover any reference lines - reproducing that from the raw data is guesswork, and being a few pixels out reads as a rendering bug. chartScales(geo) is exported if you need the same functions outside a snippet:

import { buildChart, chartScales } from '@svgrid/grid'

const geo = buildChart(spec)
const sc = chartScales(geo) // null for pie / gauge / treemap / sankey / radar / funnel / calendar
sc?.xInvert(240) // which category is at x=240
sc?.yInvert(80)  // what value is at y=80

xOf and yOf are null on the types with no cartesian axes, which is also how you can tell whether plot coordinates mean anything for the current type.

The overlay is drawn below the crosshair and the hit layer on purpose, so whatever you add cannot swallow tooltips, keyboard navigation or drill clicks.

Large series

A chart with more categories than the plot has pixels adapts on its own, with nothing to configure. Below about 4px per category:

Measured on a 20,000-point line chart in Chromium: 63,024 DOM nodes and 4,711 ms before, 3,072 nodes and 617 ms after. The engine was never the bottleneck - buildChart handles 100,000 points in about 140 ms - it was four separate things that emitted one node per category.

Bar charts are not thinned: a line still shows its shape without dots, but bars are the data, so dropping them would draw an empty chart. Several thousand sub-pixel bars still render correctly and still merge into a solid block, which is a good sign you want topN, a coarser group-by, or zoomable.

Charting from the grid, without a spec

charting puts the whole thing behind one prop: the grid grows a Chart button and a panel that derives its spec from the displayed rows.

<SvGrid {data} {columns} charting />
<SvGrid {data} {columns} charting={{ position: 'right', crossFilter: true }} />

The panel lets a reader pick the chart type, the group-by, an optional split-by, the measure, the aggregate and the number format, plus stacking, 100%, horizontal bars, donut, data labels, a log axis, a date axis and the series palette. Everything it offers is a ChartingConfig field, so anything a reader can reach you can also preset.

Cross-filtering

crossFilter: true turns a click on a chart category into a grid filter, and the panel grows a Clear filter button. It applies to the types where a clicked mark maps back to exactly one value of the charted dimension, so it is off for gauge, scatter and sankey - on a sankey a clicked target would otherwise filter the source column and empty the grid.

Open the live example: Built-in charting (one prop) (Charts)

Interactivity

SvGridChart is interactive by default:

<SvGridChart {spec}
  dataLabels                                  // value labels on each element
  formatValue={(v) => `$${compact(v)}`}       // tooltips, labels, AND Y-axis ticks
  onSelect={(s) => api.setFacetFilter('region', [s.category])} // drill the grid
  legend={true}                               // clickable legend; default true
  interactive={false}                         // opt out of tooltips + toggling
/>

formatValue is applied to tooltips, data labels, and the Y-axis ticks, so they stay consistent - keep it compact (e.g. $2M, not $2,000,000).

Letting readers pin their own notes

annotations in the spec are yours. annotatable hands the same thing to the reader: it adds an Annotate toggle to the toolbar, and while that is on, picking a category fires onAnnotate and clicking an existing marker fires onAnnotationRemove.

The chart does not store them. It owns the gesture - which you cannot build from outside, because drag is already zoom and click is already drill - and hands you back the data point. Where the notes live and whether they outlive a reload is yours to decide:

<script lang="ts">
  import { SvChart, type ChartAnnotation, type ChartSpec } from '@svgrid/grid'

  let notes = $state<ChartAnnotation[]>([])
  const base: ChartSpec = {
    type: 'line',
    categories: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
    series: [{ label: 'Errors', values: [4, 6, 31, 8, 5] }],
  }
  const spec = $derived({ ...base, annotations: notes })
</script>

<SvChart
  {spec}
  annotatable
  onAnnotate={(at) => (notes = [...notes, { at: { category: at.category }, label: `${at.value}` }])}
  onAnnotationRemove={(i) => (notes = notes.filter((_, n) => n !== i))}
/>

What comes back is a data point, not a pixel: { category, index, value }. A note anchored to a category stays on its data when the chart is re-sorted, re-filtered or zoomed, which is the whole reason not to hand back coordinates.

It works from the keyboard for the same reason. Annotate mode takes over the category gesture rather than adding one of its own, and the category hit zones already navigate with arrow keys - so pinning a note is Tab, arrow, Enter. A marker is focusable while the mode is on, and Enter removes it.

Export

Download the rendered chart as a standalone SVG or a PNG. Pass the chart's wrapper element (or its <svg>):

<div bind:this={chartEl}><SvGridChart {spec} /></div>

<button onclick={() => downloadChartSvg(chartEl, 'chart.svg')}>SVG</button>
<button onclick={() => downloadChartPng(chartEl, 'chart.png', { scale: 2 })}>PNG</button>

chartToSvgString / chartToPngBlob return the data if you want to upload it instead. The export inlines the live theme colors, so it matches what's on screen.

CSV

chartSpecToCsv(spec) writes the data behind the picture: one row per category, one column per series. A series carrying more than one number per category widens rather than losing them:

Series shape Columns
plain values <label>
ohlc <label> Open, High, Low, Close
boxes <label> Min, Q1, Median, Q3, Max, Outliers
errors <label>, <label> Low, <label> High

Scatter has no categories, so it exports one row per point instead (Series, X, Y, Size, Label).

chartCsvExportable(spec) answers whether there is anything to write, without building it - which is what the panel's CSV menu item is disabled on. Sankey, treemap, gauge and calendar have no rectangular data and still export nothing.

Notes

See the live Integrated charts demo, or the Chart wizard panel - a pick-a-chart dialog whose type-gallery thumbnails are themselves live SvGridChart previews.

More examples

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.

Open the live example: Scatter / bubble chart (Charts)

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.

Open the live example: Time-series chart (date axis) (Charts)

Chart zoom + brush mini-map

Drag a rectangle over a 180-day series to zoom in; double-click resets. A compact brush below shows the full range with a draggable window - drag the body to pan, edges to resize. Pairs with the crosshair tooltip + PNG/SVG export.

Open the live example: Chart zoom + brush mini-map (Charts)

Heatmap chart

type: heatmap renders a colored grid (one cell per row/column) from the same categories + series shape. Choose a sequential or diverging color ramp; cell text auto-contrasts black/white. Filter the grid Channel column and the heatmap re-renders.

Open the live example: Heatmap chart (Charts)

Analytics: trend, log, drill

Four story-telling chart features at once: overlay (SMA/EMA/linear regression), annotations pinned at named events, yScale: log for wide-range data, and onDrill that filters the grid to the rowIds of the clicked category. Click any day to drill.

Open the live example: Analytics: trend, log, drill (Charts)

Color-blind-safe pattern fills

patternFallback: true layers a texture (stripe / crosshatch / dots / diagonal) on every series so two series with similar hues still read as distinct in grayscale or for readers with color-vision deficiency. Works on bars and area stacks.

Open the live example: Color-blind-safe pattern fills (Charts)

Forecast: smooth + confidence band

smooth: true bends the polyline into a monotone cubic curve that still passes through every point but flows between them. upperValues + lowerValues shade a translucent envelope around the forecast for at-a-glance uncertainty. 12 weeks actuals + 8 weeks forecast.

Open the live example: Forecast: smooth + confidence band (Charts)

Waterfall (signed P&L)

type: waterfall renders each bar starting where the previous one ended. waterfallTotals marks subtotal/total bars that span from 0. Bars colour-code by sign (green / red); total bars get a neutral slate. Connector lines link bar tops so the cumulative trend reads at a glance.

Open the live example: Waterfall (signed P&L) (Charts)

Streaming chart (rolling window)

Hit Start - prices stream in at 4 Hz. The buffer holds the last 60 ticks; older points drop off the left as new ones appear on the right. Re-aggregates via rowsToChartSpec so smoothing, brush, zoom all stay in play.

Open the live example: Streaming chart (rolling window) (Charts)

Funnel chart (signup conversion)

type: funnel renders strictly-decreasing values as a stack of trapezoids. Each segment shows conversion vs. the top of the funnel inline; hover for the step drop-off. Click a segment to record a drill selection.

Open the live example: Funnel chart (signup conversion) (Charts)

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.

Open the live example: Radar chart (product comparison) (Charts)

Calendar heatmap (year of days)

type: calendar renders a GitHub-commit-style 7-row x ~53-column grid. Each cell shaded by its value; days with no value render as outlined blanks so missing data reads as missing. Filter the grid Type column and the heatmap re-aggregates.

Open the live example: Calendar heatmap (year of days) (Charts)

Tree-map (sales by region·category)

The canonical BI tree-map: revenue broken down hierarchically into nested rectangles - bigger value = bigger rectangle. The squarified algorithm keeps every cell close to a square so labels stay readable. Switch the drill order (Region → Category vs. Category → Region) to compare the same data two ways.

Open the live example: Tree-map (sales by region·category) (Charts)

Sankey diagram (user flow)

type: sankey lays nodes out in columns by longest-path depth and renders flow links as bezier ribbons whose width = link value in pixels. User journey from acquisition channel through onboarding to outcome. Hover any ribbon for the source -> target value.

Open the live example: Sankey diagram (user flow) (Charts)

Live examples

  • 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.
  • Waterfall (signed P&L) - type: waterfall renders each bar starting where the previous one ended. waterfallTotals marks subtotal/total bars that span from 0. Bars colour-code by sign (green / red); total bars get a neutral slate. Connector lines link bar tops so the cumulative trend reads at a glance.
  • Funnel chart (signup conversion) - type: funnel renders strictly-decreasing values as a stack of trapezoids. Each segment shows conversion vs. the top of the funnel inline; hover for the step drop-off. Click a segment to record a drill selection.
  • 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.
  • Heatmap chart - type: heatmap renders a colored grid (one cell per row/column) from the same categories + series shape. Choose a sequential or diverging color ramp; cell text auto-contrasts black/white. Filter the grid Channel column and the heatmap re-renders.
  • Tree-map (sales by region·category) - The canonical BI tree-map: revenue broken down hierarchically into nested rectangles - bigger value = bigger rectangle. The squarified algorithm keeps every cell close to a square so labels stay readable. Switch the drill order (Region → Category vs. Category → Region) to compare the same data two ways.
  • Sankey diagram (user flow) - type: sankey lays nodes out in columns by longest-path depth and renders flow links as bezier ribbons whose width = link value in pixels. User journey from acquisition channel through onboarding to outcome. Hover any ribbon for the source -> target value.
  • Calendar heatmap (year of days) - type: calendar renders a GitHub-commit-style 7-row x ~53-column grid. Each cell shaded by its value; days with no value render as outlined blanks so missing data reads as missing. Filter the grid Type column and the heatmap re-aggregates.
  • Gauge dial (KPI dashboard) - type: gauge renders a semicircle with track + value arcs, optional red/amber/green range bands, and a target tick. Click any row in the KPI grid to drive the dial; bands auto-flip direction based on whether higher or lower is better.
  • 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.
  • Built-in charting (one prop) - Turn on the built-in Chart panel with a single charting prop - no external library. Pick Group by / Value, choose a type, filter a column or click a bar, and the chart re-aggregates over the grid's current (filtered / sorted) rows 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.
  • Chart zoom + brush mini-map - Drag a rectangle over a 180-day series to zoom in; double-click resets. A compact brush below shows the full range with a draggable window - drag the body to pan, edges to resize. Pairs with the crosshair tooltip + PNG/SVG export.
  • Analytics: trend, log, drill - Four story-telling chart features at once: overlay (SMA/EMA/linear regression), annotations pinned at named events, yScale: log for wide-range data, and onDrill that filters the grid to the rowIds of the clicked category. Click any day to drill.
  • Color-blind-safe pattern fills - patternFallback: true layers a texture (stripe / crosshatch / dots / diagonal) on every series so two series with similar hues still read as distinct in grayscale or for readers with color-vision deficiency. Works on bars and area stacks.
  • Forecast: smooth + confidence band - smooth: true bends the polyline into a monotone cubic curve that still passes through every point but flows between them. upperValues + lowerValues shade a translucent envelope around the forecast for at-a-glance uncertainty. 12 weeks actuals + 8 weeks forecast.
  • Streaming chart (rolling window) - Hit Start - prices stream in at 4 Hz. The buffer holds the last 60 ticks; older points drop off the left as new ones appear on the right. Re-aggregates via rowsToChartSpec so smoothing, brush, zoom all stay in play.
  • 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.
  • Chart wizard (from the grid) - Grid on the left, chart wizard on the right. The wizard builds a chart from the grid rows via rowsToChartSpec: pick the type from a gallery (whose thumbnails are themselves live mini SvGridChart instances), choose group-by + measure + aggregation, and a palette. Column, Bar (horizontal), Line, Area, Pie with stacked / 100% variants. Drag a cell range or tick row checkboxes to chart just the selection; otherwise the whole filtered / sorted set re-aggregates live.

Related articles