Appearance
LineChart
A responsive SVG line chart with support for multiple series, axis labels, and custom styling.
Examples
Single series
vue
<LineChart
:data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
:height="200"
x-label="Days"
y-label="Cases"
tooltip-trigger="hover"
/>x/y
Pass paired x and y arrays to plot points at specific x positions. y is equivalent to data (both names are accepted), and they both take typed arrays. For multi-series charts, set x and y (or data) on each Series.
vue
<LineChart
:x="[0, 1, 2, 5, 10, 20, 50]"
:y="[0, 2, 5, 12, 22, 30, 38]"
:height="200"
x-label="Days"
y-label="Cases"
tooltip-trigger="hover"
/>When x is omitted, y/data values are plotted at indices 0, 1, 2, etc.
Dates
Pass ISO date strings (YYYY-MM-DD) or Date objects as x and the chart auto-detects a date axis: ticks anchor to year / month / week / day / hour boundaries depending on the visible range, and labels format themselves accordingly. Numeric x arrays keep their existing behavior — auto-detection is content-driven and never promotes plain numbers.
vue
<LineChart
:x="[
'2026-01-05',
'2026-01-12',
'2026-01-19',
'2026-01-26',
'2026-02-02',
'2026-02-09',
'2026-02-16',
'2026-02-23',
'2026-03-02',
'2026-03-09',
]"
:y="[4, 9, 18, 32, 41, 38, 27, 18, 11, 6]"
:height="220"
y-label="% ED visits"
/>Override the format via x-tick-format. Presets: "iso", "iso-datetime", "year", "month-year", "month-day", "day", "time", "datetime", "medium". You can also pass an Intl.DateTimeFormatOptions literal, or a function (ms, unit?) => string for full control.
vue
<LineChart
:x="['2026-01-05', '2026-01-12', '2026-01-19', '2026-01-26', '2026-02-02']"
:y="[4, 9, 18, 32, 41]"
x-tick-format="iso"
:height="220"
y-label="% ED visits"
/>By default the chart parses bare YYYY-MM-DD (and offset-less YYYY-MM-DDTHH:mm:ss) as UTC and renders tick labels in UTC. Set timezone="local" to interpret offset-less strings in the browser's timezone instead. Strings that include an explicit offset (Z or +05:00) are always parsed exactly as written.
Annotations on a date axis.
ChartAnnotation.xis always in the chart's resolved coordinate space — that's epoch-ms when the axis is in date mode. PassDate.UTC(2026, 0, 15),new Date("2026-01-15").getTime(), orDate.parse("2026-01-15T00:00:00Z"). Passing a small integer like0will place the annotation at 1970-01-01 (off-screen).
xLabelson a date axis. When bothx: [...date strings]andxLabelsare supplied, the date axis wins —xLabelsis ignored. Date strings carry their own labeling.
Title
The title prop accepts \n to break across lines. Use titleStyle to override fontSize, lineHeight, color, fontWeight, and align ("left" | "center" | "right", default "left"). Top padding grows automatically for additional lines.
vue
<LineChart
:data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
:height="220"
:title="'Daily ED visits\nInfluenza-like illness, last 11 days'"
:title-style="{ fontSize: 16, lineHeight: 20, color: '#0057b7' }"
x-label="Days"
y-label="Cases"
/>Multiple series
vue
<LineChart
:series="[
{
data: [0, 10, 25, 45, 60, 55, 40, 20, 8],
color: '#fb7e38',
strokeWidth: 3,
},
{
x: [0, 1, 3, 4, 6, 7, 8],
y: [0, 5, 20, 28, 18, 10, 4],
color: '#0057b7',
strokeWidth: 3,
},
]"
:height="200"
x-label="Weeks"
y-label="Incidence"
/>Legend
Set legend on a series to add an entry to the inline legend strip above the plot. When the items don't all fit on one row at the chart's current width, the legend wraps to additional rows and the plot area shrinks to keep the legend clear of the data. Use showInLegend: false to keep a series off the legend (e.g. when its legend string is reused as a CSV column header) and showInTooltip: false to omit it from the hover tooltip.
vue
<LineChart
:series="[
{ data: scenarioA, color: '#0057b7', legend: 'No interventions' },
{ data: scenarioB, color: '#fb7e38', legend: 'School closures' },
{ data: scenarioC, color: '#ef4444', legend: 'Masking + distancing' },
{ data: scenarioD, color: '#10b981', legend: 'Vaccination only' },
{ data: scenarioE, color: '#6366f1', legend: 'Vaccination + masking' },
{ data: scenarioF, color: '#a855f7', legend: 'All interventions combined' },
]"
:height="240"
x-label="Weeks"
y-label="Incidence"
/>Tooltip
Hover over the chart to see a tooltip with values at each data point. Set tooltip-trigger="hover" to enable the built-in tooltip with crosshair and highlight dots. Use the #tooltip slot for custom content. Pass tooltip-value-format to control how numeric values render (e.g. percentages, currency); it falls back to y-tick-format when omitted.
vue
<LineChart
:series="[
{
data: [0, 10, 25, 45, 60, 55, 40, 20, 8],
color: '#fb7e38',
strokeWidth: 3,
},
{
data: [0, 5, 12, 20, 28, 25, 18, 10, 4],
color: '#0057b7',
strokeWidth: 3,
},
]"
:x-tick-format="(_, i) => months[i]"
:height="200"
x-label="Month"
y-label="Incidence"
tooltip-trigger="hover"
/>Custom axis ticks
Control tick placement with x-ticks and y-ticks. Pass a number for a fixed interval (in data units, respecting xMin) or an array of explicit values. Use x-tick-format / y-tick-format to customize labels.
vue
<LineChart
:data="coverage"
:x-ticks="7"
:y-ticks="[0, 0.5, 1]"
:y-tick-format="(v) => `${(v * 100).toFixed(0)}%`"
:x-tick-format="(v) => `day ${v}`"
:height="220"
x-label="Time"
y-label="Coverage"
x-grid
y-grid
/>Logarithmic y-axis
Set y-scale-type="log" to switch the y axis to base-10 log scaling. Useful when data spans several orders of magnitude (e.g. epidemic case counts in early exponential growth). Non-positive values collapse to the axis floor, and yMin <= 0 is ignored.
vue
<LineChart
:data="[1, 3, 8, 22, 60, 165, 450, 1230, 3350]"
y-scale-type="log"
:height="220"
x-label="Day"
y-label="Cases"
y-grid
/>Dashed baseline
vue
<LineChart
:series="[
{
data: [0, 10, 25, 45, 60, 55, 40, 20, 8],
color: '#999',
dashed: true,
strokeWidth: 2,
},
{
data: [0, 5, 12, 20, 28, 25, 18, 10, 4],
color: '#2563eb',
strokeWidth: 2,
},
]"
:height="200"
/>Outline
Set outline: true on a series to draw a page-colored stroke behind the line. This is useful when lines overlap, cross dense areas, or sit on a busy background — the outline keeps each series visually distinct.
Override the outline's color with outlineColor (any CSS color; defaults to the page background var(--color-bg-0, #fff)) and its thickness with outlineWidth (extra width added to the line's strokeWidth, split evenly per side; defaults to 4).
vue
<LineChart
:series="[
{
data: [0, 10, 25, 45, 60, 55, 40, 20, 8],
color: '#fb7e38',
strokeWidth: 3,
outline: true,
},
{
data: [0, 12, 22, 40, 58, 58, 42, 22, 10],
color: '#0057b7',
strokeWidth: 3,
outline: true,
},
]"
:height="200"
/>Many trajectories with low opacity
vue
<LineChart
:series="trajectories"
:height="250"
:line-opacity="0.15"
x-label="Days"
y-label="Incidence"
/>Blend mode
Set blendMode on a Series to apply a CSS mix-blend-mode to that series' line (and dots, if enabled). Useful when two thick or high-contrast lines overlap — "multiply" darkens the crossing point on light backgrounds, "screen" lightens on dark, "difference" inverts. The white outline, if any, is unaffected.
vue
<LineChart
:series="[
{
data: strainA,
color: '#ef4444',
strokeWidth: 3,
blendMode: 'multiply',
legend: 'Strain A',
},
{
data: strainB,
color: '#3b82f6',
strokeWidth: 3,
blendMode: 'multiply',
legend: 'Strain B',
},
]"
:height="240"
x-label="Day"
y-label="Incidence"
/>Grid lines
vue
<LineChart
:series="[
{
data: [0, 10, 25, 45, 60, 55, 40, 20, 8],
color: '#fb7e38',
strokeWidth: 3,
},
{
data: [0, 5, 12, 20, 28, 25, 18, 10, 4],
color: '#0057b7',
strokeWidth: 3,
},
]"
:height="200"
x-label="Weeks"
y-label="Incidence"
x-grid
y-grid
/>Dots
vue
<LineChart
:series="[
{
data: [0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2],
color: '#0057b7',
strokeWidth: 2,
dots: true,
dotRadius: 4,
dotFill: '#fff',
dotStroke: '#0057b7',
},
]"
:height="200"
x-label="Days"
y-label="Cases"
/>Confidence band
Use the areas prop to fill a band between two y-series — useful for confidence intervals or min/max envelopes around a mean line. Each Area takes parallel upper and lower arrays (and an optional x array, just like Series).
vue
<LineChart
:data="mean"
:areas="[
{
upper: ci95Hi,
lower: ci95Lo,
color: '#0057b7',
opacity: 0.15,
},
]"
:height="220"
x-label="Days"
y-label="Cases"
tooltip-trigger="hover"
/>Area sections
Highlight a range of a series line by filling the area between the line and the x-axis. Labels are rendered below the chart and automatically stack when they overlap.
vue
<LineChart
:series="[
{
data: [0, 2, 5, 12, 25, 45, 70, 100, 130, 155, 170],
color: '#000',
strokeWidth: 1,
legend: 'No interventions',
},
{
data: [0, 0, 0, 2, 8, 20, 40, 65, 90, 110, 120],
color: '#999',
strokeWidth: 1,
dashed: true,
legend: 'Interventions',
},
]"
:area-sections="[
{
startIndex: 2,
endIndex: 7,
color: '#6366f1',
strokeWidth: 0,
legend: 'inline',
label: 'Day 2–7',
description: 'Rapid growth phase',
},
{
seriesIndex: 0,
startIndex: 5,
endIndex: 9,
color: '#f43f5e',
label: 'Day 5–9',
description: 'Mitigation period',
},
]"
:height="250"
x-label="Days"
y-label="Cumulative count"
tooltip-trigger="hover"
/>Annotations
Pin callouts to data points with annotations. Each annotation anchors at (x, y) in data coordinates (the same x-space as the chart axis, so it respects xMin and explicit x values), with a pixel offset: { x, y } for the label position. Text supports \n for line breaks. A curved pointer line connects the anchor to the label, and the label gets an outline stroke matching the background so it stays legible over series lines.
vue
<LineChart
:data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
:annotations="[
{ x: 5, y: 30, offset: { x: 24, y: -28 }, text: 'Peak\nDay 5' },
{ x: 0, y: 0, offset: { x: 28, y: -22 }, text: 'Onset' },
]"
:chart-padding="{ top: 40, right: 24 }"
:height="240"
x-label="Days"
y-label="Cases"
/>Use chart-padding to reserve room outside the plot so annotations (or any other overlay) don't clip the data area. It accepts a number (same on all sides) or an object with top, right, bottom, left.
Annotation text supports a small set of inline markers:
**bold**— bold_italic_— italic\n— line break
Markers compose (**_bold italic_**).
vue
<LineChart
:data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
:annotations="[
{ x: 5, y: 30, offset: { x: 24, y: -28 }, text: '**Peak**\n_Day 5_' },
]"
:chart-padding="{ top: 40, right: 24 }"
:height="240"
x-label="Days"
y-label="Cases"
/>ts
interface ChartAnnotation {
x: number; // anchor in data coords (x-axis)
y: number; // anchor in data coords (y-axis)
text: string; // label text; \n produces line breaks
offset: { x: number; y: number }; // pixel offset from anchor to label
color?: string; // text / pointer color (default: currentColor)
fontSize?: number; // default: 13 (matches axis labels)
fontWeight?: string | number; // default: "normal"
outlineColor?: string; // background-matched outline (default: var(--color-bg-0, #fff))
outlineWidth?: number; // default: 3
align?: "left" | "center" | "right"; // default: derived from offset[0] sign
lineColor?: string; // connector-line color override (default: color)
lineWidth?: number; // default: 1
lineDash?: string | number | readonly number[]; // SVG stroke-dasharray
// default: "curved"
// "ruleX" / "ruleY" span the full plot on the named axis;
// "ruleUp" / "ruleDown" / "ruleFromLeft" / "ruleFromRight" run from an edge to the anchor.
pointer?:
| "curved"
| "straight"
| "none"
| "ruleX"
| "ruleY"
| "ruleUp"
| "ruleDown"
| "ruleFromLeft"
| "ruleFromRight";
arrow?: boolean; // triangle marker at the anchor end (default: true)
}Rules
Set pointer to one of the rule values to replace the curved / straight connector with a straight line through the anchor:
"ruleX"— vertical line spanning the plot height at the annotation'sx."ruleY"— horizontal line spanning the plot width at the annotation'sy."ruleUp"— vertical from the bottom edge up to the anchor."ruleDown"— vertical from the top edge down to the anchor."ruleFromLeft"— horizontal from the left edge in to the anchor."ruleFromRight"— horizontal from the right edge in to the anchor.
lineColor, lineWidth, and lineDash style the line. The label is positioned from the anchor as usual via offset.
vue
<LineChart
:data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
:annotations="[
{
x: 5,
y: 30,
offset: { x: 8, y: 14 },
text: 'Peak',
pointer: 'ruleX',
lineDash: '4 3',
},
{
x: 5,
y: 30,
offset: { x: -8, y: -6 },
text: 'Max',
align: 'right',
pointer: 'ruleFromLeft',
lineDash: '4 3',
},
]"
:chart-padding="{ top: 24, right: 24 }"
:height="240"
x-label="Days"
y-label="Cases"
/>Chart menu
The chart menu (top-right) leads with Fullscreen and then Save as SVG, Save as PNG, and Download CSV. Fullscreen grows the chart to fill the browser window so the plot has more room; while expanded the menu trigger is replaced by a close (✕) button — click it or press Esc to return to the inline size. Set menu="false" to hide the trigger entirely.
While expanded the chart is teleported to <body> so it reliably covers the viewport even when an ancestor uses transform, filter, contain, or establishes a stacking context. If your app doesn't mount at the document root (e.g. inside a shadow root or a dedicated overlay container), point fullscreenTarget at a CSS selector or element to teleport there instead.
Custom CSV download
By default, the Download CSV menu item exports the chart series as CSV. Use the csv prop to supply your own content (for example, to include original dates, categorical labels, or extra columns that aren't plotted). Use filename to control the download filename (shared by SVG, PNG and CSV).
Pass download-link to also render a plain text link below the chart — set it to true for the default label, or pass a string to customize it. Use download-button instead to render a styled <button> (with the class line-chart-download-button, available for custom CSS) in place of the link.
vue
<LineChart
:data="[10, 22, 35, 48]"
:height="200"
filename="weekly-cases"
:csv="`week,cases
2024-W01,10
2024-W02,22
2024-W03,35
2024-W04,48`"
x-label="Week"
y-label="Cases"
download-link="Download weekly cases (CSV)"
/>csv also accepts a function, which is useful for deferring serialization until the user clicks Download:
vue
<LineChart :data="cases" :csv="() => buildCsv(cases, dates)" />Props
| Prop | Type | Required | Default |
|---|---|---|---|
width | number | No | — |
height | number | No | — |
title | string | No | — |
titleStyle | TitleStyle | No | — |
axisLabelStyle | LabelStyle | No | — |
tickLabelStyle | LabelStyle | No | — |
legendStyle | LabelStyle | No | — |
xLabel | string | No | — |
yLabel | string | No | — |
debounce | number | No | — |
menu | boolean | string | No | true |
tooltipData | ArrayLike<unknown> | No | — |
tooltipTrigger | "hover" | "click" | No | — |
tooltipClamp | "none" | "chart" | "window" | No | "chart" |
tooltipValueFormat | NumberFormat | No | — |
csv | string | (() => string) | No | — |
filename | string | No | — |
downloadLink | boolean | string | No | — |
downloadButton | boolean | string | No | — |
fullscreenTarget | string | HTMLElement | No | — |
annotations | readonly ChartAnnotation[] | No | — |
chartPadding | ChartPadding | No | — |
y | LineChartData | No | — |
data | LineChartData | No | — |
x | LineChartXInput | No | — |
series | Series[] | No | — |
areas | Area[] | No | — |
areaSections | AreaSection[] | No | — |
lineOpacity | number | No | 1 |
yMin | number | No | — |
yScaleType | "linear" | "log" | No | "linear" |
xMin | number | No | — |
xTicks | number | number[] | No | — |
yTicks | number | number[] | No | — |
xTickFormat | `| NumberFormat |
\| ((value: number, index: number) => string)
\| DateFormat` | No | — |
| timezone | DateTimezone | No | — | | yTickFormat | NumberFormat | No | — | | xLabels | string[] | No | — | | xGrid | boolean | No | — | | yGrid | boolean | No | — |
Data
data, series[].data, and areas[].upper/lower accept a plain number[] or any standard numeric typed array (Float64Array, Int32Array, etc.). This lets you pass the output of ModelOutput.column() directly — no Array.from(...) copy is needed:
vue
<LineChart :data="outputs.series.column('values')" />ts
type LineChartData =
| readonly number[]
| Float64Array
| Float32Array
| Int32Array
| Uint32Array
| Int16Array
| Uint16Array
| Int8Array
| Uint8Array
| Uint8ClampedArray;Series
ts
interface Series {
y?: LineChartData; // y-values (preferred)
data?: LineChartData; // y-values (alternative name; one of y/data must be set)
x?: LineChartData; // optional parallel x-values
color?: string;
dashed?: boolean;
strokeWidth?: number;
opacity?: number;
line?: boolean;
outline?: boolean; // page-colored stroke drawn behind the line
outlineColor?: string; // outline stroke color (default: var(--color-bg-0, #fff))
outlineWidth?: number; // extra width added to strokeWidth (default: 4)
dots?: boolean;
dotRadius?: number;
dotFill?: string;
dotStroke?: string;
}Area
ts
interface Area {
upper: LineChartData;
lower: LineChartData;
x?: LineChartData; // optional parallel x-values
color?: string;
opacity?: number;
}AreaSection
ts
interface AreaSection {
seriesIndex?: number; // omit for full-height fill
startIndex: number;
endIndex: number;
color?: string;
opacity?: number; // default: 0.15
label?: string;
description?: string;
strokeWidth?: number; // default: 2
dashed?: boolean;
legend?: "inline" | "below" | false; // default: "below"
}