Line charts
Twenty-four months of sign-ups for three plans on a real time axis, plus a six-month forecast: smooth or stepped lines, markers per series, a gap where two months of data are missing or a bridge across it (connectNulls), a dashed forecast series that meets the actuals at a reference line for today, a shaded outage band, series names at the line ends, crosshair pills, and a log scale for plans an order of magnitude apart.
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 line chart with the switches a line chart usually needs: xType time spaces the points by date, smooth, step and marker are per series, NaN values draw a gap or are bridged with connectNulls, a forecast runs dashed past a reference line for today, a reference band shades an outage, seriesLabels name each line at its end, crosshairLabels read the hovered month and value off the axes, and yScale log handles plans an order of magnitude apart. Free, in @svgrid/grid.
Twenty-four months of sign-ups for three plans, plus a six-month forecast, and the switches a line chart usually needs:
xType: 'time'spaces the points by date and labels the axis with real months, not category strings.smooth,stepandmarkerare per series; the switches set them on all three.- The forecast is its own series: it repeats the last actual value so the two lines meet, then runs dashed (
dash) past the reference line that marks today. Everything before that point is NaN in the forecast and after it in the actuals, and the chart draws a gap there rather than a dive to zero (nullAs: 'gap'). - Two readings are missing on purpose (an outage);
connectNullson the Team series bridges them, the others show the hole. seriesLabelsnames each line at its end so the legend is optional, andcrosshairLabelsreads the hovered month and value off the axes.yScale: 'log'when the plans are an order of magnitude apart.
Free, in @svgrid/grid.
Imports, features and API used
Imports: @svgrid/grid, ../shared/ChartDataGrid.svelte, ../shared/ViewSwitch.svelte
Frequently asked questions
How do I show missing data as a gap?
Put NaN (or null) in the values. With nullAs gap, the default, the line breaks there and no marker is drawn; connectNulls: true on a series bridges its gaps instead, and nullAs zero plots them as 0.
How do I draw a forecast as a dashed line?
Make the forecast its own series with dash set, NaN before the point where it starts, and the last actual value repeated at that point so the two lines meet. A reference line with axis x marks where the actuals end.
How do I put the series names at the end of the lines?
Set seriesLabels: true on the spec, or pass a formatter to rename or drop some. The chart reserves a gutter on the right and pushes overlapping labels apart.
Related documentation
Related articles
- Inside SvGrid: The Inline Editing Engine - How SvGrid handles inline cell editing - typed editors, an event-based commit model, undo/redo, and why the grid never touches your data directly.
- Inline Editing with Validation in SvGrid - How to wire up typed cell editors, reject bad input before it reaches your data, and keep per-cell error state without a form library.
Source code (439-chart-line.svelte)
<!-- Documented in: docs/help/charts/gallery.md -->
<script lang="ts">
/**
* 439. Line charts
* ----------------
* Twenty-four months of sign-ups for three plans, plus a six-month forecast,
* and the switches a line chart usually needs:
*
* - `xType: 'time'` spaces the points by date and labels the axis with real
* months, not category strings.
* - `smooth`, `step` and `marker` are per series; the switches set them on
* all three.
* - The forecast is its own series: it repeats the last actual value so the
* two lines meet, then runs dashed (`dash`) past the reference line that
* marks today. Everything before that point is NaN in the forecast and
* after it in the actuals, and the chart draws a gap there rather than a
* dive to zero (`nullAs: 'gap'`).
* - Two readings are missing on purpose (an outage); `connectNulls` on the
* Team series bridges them, the others show the hole.
* - `seriesLabels` names each line at its end so the legend is optional, and
* `crosshairLabels` reads the hovered month and value off the axes.
* - `yScale: 'log'` when the plans are an order of magnitude apart.
*
* Free, in @svgrid/grid.
*/
import { SvChart, type ChartSpec } from '@svgrid/grid'
import ChartDataGrid from '../shared/ChartDataGrid.svelte'
import ViewSwitch from '../shared/ViewSwitch.svelte'
let seed = 7
const rnd = () => ((seed = (seed * 1103515245 + 12345) % 2147483648) / 2147483648)
const months: string[] = []
for (let i = 0; i < 30; i += 1) months.push(new Date(Date.UTC(2024, 3 + i, 1)).toISOString().slice(0, 10))
const ACTUAL = 24
// NaN is a gap: the chart draws nothing there (`nullAs: 'gap'`).
const grow = (start: number, rate: number, noise: number) =>
months.map((_, i) => (i < ACTUAL ? Math.round(start * Math.pow(1 + rate, i) * (1 + (rnd() - 0.5) * noise)) : Number.NaN))
const free = grow(1800, 0.045, 0.14)
const team = grow(420, 0.07, 0.18)
const enterprise = grow(38, 0.09, 0.22)
// The outage: two months nobody could sign up for Team or Enterprise.
team[13] = Number.NaN
team[14] = Number.NaN
enterprise[13] = Number.NaN
enterprise[14] = Number.NaN
const forecast = (actual: number[], rate: number) => {
const last = actual[ACTUAL - 1]!
return months.map((_, i) => (i < ACTUAL - 1 ? Number.NaN : Math.round(last * Math.pow(1 + rate, i - (ACTUAL - 1)))))
}
let smooth = $state(true)
let step = $state(false)
let markers = $state(false)
let log = $state(false)
let bridge = $state(true)
const spec = $derived<ChartSpec>({
type: 'line',
xType: 'time',
categories: months,
series: [
{ label: 'Free', values: free, smooth, step: step ? 'after' : undefined, marker: markers ? 'circle' : 'none', color: '#2563eb' },
{ label: 'Team', values: team, smooth, step: step ? 'after' : undefined, marker: markers ? 'diamond' : 'none', color: '#16a34a', connectNulls: bridge },
{ label: 'Enterprise', values: enterprise, smooth, step: step ? 'after' : undefined, marker: markers ? 'square' : 'none', color: '#f59e0b' },
{ label: 'Free forecast', values: forecast(free, 0.04), smooth, dash: '6 4', marker: 'none', color: '#2563eb', opacity: 0.7 },
{ label: 'Team forecast', values: forecast(team, 0.06), smooth, dash: '6 4', marker: 'none', color: '#16a34a', opacity: 0.7 },
],
nullAs: 'gap',
yScale: log ? 'log' : 'linear',
title: 'Sign-ups by plan',
subtitle: 'Monthly, with a six-month forecast',
yAxis: { title: 'Sign-ups', gridLines: true, format: 'compact' },
referenceLines: [{ value: months[ACTUAL - 1]!, axis: 'x', label: 'Today', dashed: true }],
referenceBands: [{ from: months[13]!, to: months[14]!, axis: 'x', label: 'Outage', color: '#dc2626', opacity: 0.12 }],
seriesLabels: { formatter: (s) => (s.endsWith('forecast') ? '' : s) },
height: 380,
})
// 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']]} />
<label class="chk"><input type="checkbox" bind:checked={smooth} /> Smooth</label>
<label class="chk"><input type="checkbox" bind:checked={step} /> Step</label>
<label class="chk"><input type="checkbox" bind:checked={markers} /> Markers</label>
<label class="chk"><input type="checkbox" bind:checked={bridge} /> Bridge the outage on Team</label>
<label class="chk"><input type="checkbox" bind:checked={log} /> Log scale</label>
<span class="note">
A time axis, smooth or stepped lines, markers per series, a gap where the data is missing (or a
bridge across it), a dashed forecast past a reference line, labels at the line ends and a log scale.
</span>
</header>
<div class="pane">
{#if view === 'chart'}
<SvChart {spec} legend={false} zoomable brush crosshairLabels autosize />
{:else}
<ChartDataGrid spec={spec} />
{/if}
</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 {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 12px;
color: var(--sg-fg, #0f172a);
white-space: nowrap;
}
.pane {
flex: none;
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.