Skip to content

LineChart ​

A responsive SVG line chart with support for multiple series, axis labels, and custom styling.

Data ​

Single series ​

0102030CasesDays0246810
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.

0102030CasesDays (log-ish)01020304050
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.

Multiple series ​

0204060IncidenceWeeks02468
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"
/>

Stacked areas ​

stackAreas (exported from @cfasim-ui/charts) turns layers of values into areas that stack on each other in array order, like Vega-Lite's stacked area mark: compartments of a model, cases by age group. Each layer takes y (its own values), optional x and any Area styling (color, legend, opacity, curve, ...). Layers stack where their x values match (give every layer x, or none), and a layer missing a point counts as 0 there. Positive values stack upward and negative values downward from 0; pass { offset: "normalize" } to stack shares of each x's total instead (the top edge is always 1). The tooltip shows each layer's own value rather than its band, also when normalized, and a chart with only areas puts the tooltip on their x values. Hiding a layer from the legend leaves its gap in the stack.

SusceptibleInfectedRecovered05001,0000246
vue
<script setup>
import { LineChart, stackAreas } from "@cfasim-ui/charts";

const compartments = stackAreas([
  {
    y: [990, 960, 880, 700, 480, 320, 240, 200],
    legend: "Susceptible",
    color: "#3b82f6",
    opacity: 0.7,
  },
  {
    y: [10, 30, 90, 180, 220, 160, 80, 30],
    legend: "Infected",
    color: "#ef4444",
    opacity: 0.7,
  },
  {
    y: [0, 10, 30, 120, 300, 520, 680, 770],
    legend: "Recovered",
    color: "#10b981",
    opacity: 0.7,
  },
]);
</script>

<template>
  <LineChart :areas="compartments" :height="220" legend-toggle />
</template>

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.

10203040% ED visitsJan 5Jan 19Feb 2Feb 16Mar 2
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.

10203040% ED visits2026-01-052026-01-122026-01-192026-01-262026-02-02
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.x is always in the chart's resolved coordinate space, which is epoch-ms when the axis is in date mode. Pass Date.UTC(2026, 0, 15), new Date("2026-01-15").getTime(), or Date.parse("2026-01-15T00:00:00Z"). Passing a small integer like 0 will place the annotation at 1970-01-01 (off-screen).

xLabels on a date axis. When both x: [...date strings] and xLabels are supplied, the date axis wins and xLabels is ignored. Date strings carry their own labeling.

Typed arrays ​

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, with no Array.from(...) copy:

vue
<LineChart :data="outputs.series.column('values')" />
ts
type LineChartData =
  | readonly number[]
  | Float64Array
  | Float32Array
  | Int32Array
  | Uint32Array
  | Int16Array
  | Uint16Array
  | Int8Array
  | Uint8Array
  | Uint8ClampedArray;

Axes ​

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; to reuse a d3 or Vega-Lite format string, wrap it with d3Format / d3TimeFormat (e.g. :y-tick-format="d3Format('.0%')"). On a date axis the units are milliseconds: an interval steps from the first date (7 * 86400000 is a tick every week, on the weekday the data starts), an array holds epoch-ms values, and the default label format follows the interval (days, weeks or months).

0%50%100%CoverageTimeday 0day 7day 14
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
/>

Fixed y range ​

y-min and y-max set the lowest and highest values the y axis must include, so charts of the same quantity share a scale. They only widen the axis: data outside the range still extends it, nothing is clipped.

00.51CoverageWeek0123456
vue
<LineChart
  :data="coverage"
  :y-min="0"
  :y-max="1"
  :height="220"
  x-label="Week"
  y-label="Coverage"
  y-grid
/>

Shared y axis across charts ​

For small multiples (one chart per region, scenario or age group, like Vega-Lite's facet with shared scales), give every chart the same y-min and y-max. sharedExtent (from @cfasim-ui/charts) finds them: pass it every array the charts plot on the y axis (series, area bounds, interval bounds) and it returns their { min, max }, or undefined when there is no finite value yet (hence sharedY?.min). Pass { log: true } for a log axis to skip values at or below 0.

Region A102030400246
Region B102030400246
vue
<script setup>
import { LineChart, sharedExtent } from "@cfasim-ui/charts";

const regionA = [4, 9, 18, 31, 42, 38, 27, 15];
const regionB = [1, 2, 4, 7, 11, 14, 12, 9];
const sharedY = sharedExtent([regionA, regionB]);
</script>

<template>
  <LineChart
    title="Region A"
    :data="regionA"
    :y-min="sharedY?.min"
    :y-max="sharedY?.max"
  />
  <LineChart
    title="Region B"
    :data="regionB"
    :y-min="sharedY?.min"
    :y-max="sharedY?.max"
  />
</template>

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.

1101001,000CasesDay02468
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
/>

Grid lines ​

Set x-grid / y-grid to draw grid lines at each axis' ticks (the custom ticks demo turns both on). Pass a GridStyle object instead of true to change an axis' line weight, color, opacity, or pattern (lineStyle: "dashed" | "dotted", or a raw lineDash). The chart-level grid-style prop supplies defaults for every grid on the chart; a field on the axis prop wins.

0204060IncidenceWeeks02468
vue
<LineChart
  :series="series"
  :height="200"
  x-label="Weeks"
  y-label="Incidence"
  :grid-style="{ color: '#6b7280', opacity: 0.35 }"
  :x-grid="{ lineStyle: 'dotted' }"
  :y-grid="{ lineStyle: 'dashed', lineWidth: 1.5 }"
/>

Titles and text ​

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.

Daily ED visitsInfluenza-like illness, last 11 days0102030CasesDays0246810
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"
/>

Font sizes ​

Every text element on the chart has its own style prop and default size (in px). The text-styling props (axisLabelStyle, tickLabelStyle, legendStyle) take a LabelStyle ({ fontSize?, color?, fontWeight? }), and titleStyle adds lineHeight and align. Unset fields fall back to the defaults below.

Chart partProp / fieldTypeDefault size
TitletitleStyleTitleStyle14
Axis labels (xLabel / yLabel)axisLabelStyleLabelStyle13
Axis tick labels (the numbers on each axis)tickLabelStyleLabelStyle10
Inline legendlegendStyleLabelStyle11
Area section labelareaSection.inlineLabelStyleLabelStyle11
Area section descriptionareaSection.inlineDescriptionStyleLabelStyle11
Annotation textannotation.fontSizenumber13
Interval whiskers (stroke width)intervalStyle.lineWidthnumber1.5

xLabelAlign places the x-axis label centered under the plot ("center") or at its left edge ("left"); when omitted it is centered, except while area-section labels are showing, where it joins their first row at the left. These are single styles applied to both axes: there's no separate x vs y font size. The same props (except the LineChart-only area-section fields) exist on BarChart, since both compose ChartCommonProps.

Daily ED visitsNo interventionsMasking + distancing0204060CasesDays02468
vue
<LineChart
  :series="[
    {
      data: [0, 12, 28, 45, 60, 55, 40, 25, 12],
      color: '#0057b7',
      legend: 'No interventions',
    },
    {
      data: [0, 8, 18, 30, 40, 35, 25, 14, 6],
      color: '#ef4444',
      legend: 'Masking + distancing',
    },
  ]"
  :height="240"
  title="Daily ED visits"
  x-label="Days"
  y-label="Cases"
  :title-style="{ fontSize: 18 }"
  :axis-label-style="{ fontSize: 15, fontWeight: 700 }"
  :tick-label-style="{ fontSize: 13 }"
  :legend-style="{ fontSize: 14 }"
/>

Line styles ​

Dashed baseline ​

Set dashed: true on a series to draw it dashed, for example to set a baseline or counterfactual apart from the scenario it is compared to.

020406002468
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"
/>

Curves ​

curve sets how a series connects its points: "linear" (default), "step", "step-before", "step-after" or "monotone". Steps suit values that hold between observations, such as cumulative counts or a piecewise-constant parameter: "step-after" holds each value until the next x, "step-before" takes the next value early, and "step" switches halfway. "monotone" is a smooth curve that never overshoots the data, so a peak stays at its data point. Set curve on the chart to apply it to every series and area, or on a single series or area. The geometry matches d3-shape and Vega-Lite's interpolate.

Cumulative (step-after)Daily (monotone)10203002468
vue
<LineChart
  :series="[
    {
      data: [2, 5, 9, 14, 22, 30, 33, 35, 36],
      curve: 'step-after',
      color: '#2563eb',
      strokeWidth: 2,
      legend: 'Cumulative (step-after)',
    },
    {
      data: [2, 3, 4, 5, 8, 8, 3, 2, 1],
      curve: 'monotone',
      color: '#fb7e38',
      strokeWidth: 2,
      dots: true,
      legend: 'Daily (monotone)',
    },
  ]"
  :height="220"
/>

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

020406002468
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"
/>

Dots ​

Set dots: true to draw a dot at every point. dotRadius, dotFill and dotStroke style them; line: false keeps the dots and drops the line.

0102030CasesDays0246810
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"
/>

Scatter with per-point size and color ​

dotRadius, dotFill and dotOpacity also take an array (or typed array) parallel to the data, one value per point, so a line: false series becomes a scatter plot that encodes more variables, like Vega-Lite's size, color and opacity channels. Map your data to radii and colors yourself; an entry that is undefined (or NaN in a typed array) falls back to the series default. The chart reserves no room for dots, so give large ones space at the plot edges with chart-padding.

They also take a function of the point, { value, index, x, seriesIndex, color } (color is the series' resolved color), which suits one style shared across series. For example, keep each series' color but mark points over a threshold:

ts
const dotFill = (p) => (p.value > 100 ? "#dc2626" : p.color);
const series = runs.map((data) => ({ data, dots: true, dotFill }));
20304050Peak cases (%)R₀2345
vue
<LineChart
  :series="[
    {
      x: [1.2, 1.8, 2.1, 2.6, 3.0, 3.4, 3.9, 4.3, 4.8, 5.5],
      data: [12, 18, 15, 26, 30, 24, 38, 35, 44, 52],
      line: false,
      dots: true,
      // one radius and one color per point
      dotRadius: [3, 5, 4, 8, 10, 6, 12, 9, 14, 16],
      dotFill: [
        '#93c5fd',
        '#93c5fd',
        '#93c5fd',
        '#3b82f6',
        '#3b82f6',
        '#3b82f6',
        '#1d4ed8',
        '#1d4ed8',
        '#1e3a8a',
        '#1e3a8a',
      ],
      dotOpacity: 0.8,
    },
  ]"
  :height="220"
  :chart-padding="16"
  x-label="R₀"
  y-label="Peak cases (%)"
/>

Many trajectories with low opacity ​

For an ensemble of model runs, draw every trajectory faintly with the chart-level line-opacity (or a per-series opacity) so the density of the bundle shows where runs agree. To add a median and an interval, see named tooltip rows.

010203040IncidenceDays010203040
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.

Strain AStrain B102030IncidenceDay01020
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"
/>

Legend and tooltip ​

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.

No interventionsSchool closuresMasking + distancingVaccination onlyVaccination + maskingAll interventions combined0204060IncidenceWeeks02468
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.

On touch devices, drag horizontally across the chart to scrub the tooltip between points; a vertical swipe scrolls the page instead of getting captured by the chart.

The tooltip stops at every x value of every series shown in it, so series sampled on different dates (say, weekly data over daily data) need no shared x grid. Each row shows its series' nearest point to the hovered x. The index passed to the slot, used for tooltip-data and emitted by hover, counts positions in the sorted union of those x values, which is the plain point index whenever the series share one x set.

DailyWeekly10203040CasesJan 6Jan 13Jan 20Jan 27
vue
<LineChart
  :series="[
    { legend: 'Daily', x: dailyDates, data: dailyCases, color: '#0057b7' },
    {
      legend: 'Weekly',
      x: weeklyDates,
      data: weeklyCases,
      color: '#fb7e38',
      strokeWidth: 3,
      dots: true,
    },
  ]"
  :height="200"
  y-label="Cases"
  tooltip-trigger="hover"
/>

Named rows ​

Rows are named by the series' legend, or tooltipLabel to name a row without a legend entry. Once any row is named, unnamed series are left out, so ensemble trajectories drop out and the median stays. Series with no value at the hovered x are left out too. Named areas (legend or tooltipLabel) show their range, and intervals use label. tooltip-title formats the heading.

Median90% interval050100InfectionsDay0102030
vue
<script setup>
const series = [
  // One faint line per run: no legend, so no tooltip row.
  ...runs.map((data) => ({ data, color: "#0057b7", opacity: 0.12 })),
  { data: median, legend: "Median", color: "#0057b7", strokeWidth: 2.5 },
];
const areas = [
  { lower: q05, upper: q95, legend: "90% interval", color: "#0057b7" },
];
</script>

<LineChart
  :series="series"
  :areas="areas"
  :height="220"
  x-label="Day"
  y-label="Infections"
  tooltip-trigger="hover"
  :tooltip-title="(label) => `Day ${label}`"
/>

Custom tooltip content ​

The #tooltip slot gets title, values, areas, intervals, index, xLabel, data and format. To extend the default body, render ChartTooltipContent:

vue
<script setup>
import { LineChart, ChartTooltipContent } from "@cfasim-ui/charts";
</script>

<LineChart :series="series" :tooltip-data="notes" tooltip-trigger="hover">
  <template #tooltip="tip">
    <ChartTooltipContent
      :title="tip.title"
      :values="tip.values"
      :areas="tip.areas"
      :intervals="tip.intervals"
      :format="tip.format"
    />
    <p v-if="tip.data">{{ tip.data }}</p>
  </template>
</LineChart>

Toggle series from the legend ​

Set legend-toggle to let readers show and hide marks by clicking their legend item. Every series and area with a legend label becomes toggleable; marks that share a label toggle together. A hidden mark leaves the plot and the tooltip, its legend item dims with a strikethrough, and the y axis rescales to what is still shown. CSV downloads always include every series.

The chart tracks the hidden labels itself. Bind v-model:hidden-series (an array of legend labels) to read the state, set an initial selection, or drive it from your own controls; hidden-series works without legend-toggle too.

BaselineVaccinationVaccination + masks0200400600800CasesDays01020304050
vue
<LineChart
  :series="[
    { data: baseline, legend: 'Baseline', color: '#0057b7' },
    { data: vaccination, legend: 'Vaccination', color: '#fb7e38' },
    { data: combined, legend: 'Vaccination + masks', color: '#2e8540' },
  ]"
  :height="220"
  x-label="Days"
  y-label="Cases"
  tooltip-trigger="hover"
  legend-toggle
  v-model:hidden-series="hidden"
/>

Uncertainty ​

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). Set legend on an Area to include the band in the inline legend; use showInLegend: false to hide a legend label without removing the field from shared config. Set blendMode on an Area to apply a CSS mix-blend-mode to its fill so overlapping bands combine their colors (e.g. "multiply") instead of the later one obscuring the earlier.

95% interval0102030CasesDays0246810
vue
<LineChart
  :data="mean"
  :areas="[
    {
      upper: ci95Hi,
      lower: ci95Lo,
      color: '#0057b7',
      opacity: 0.15,
      legend: '95% interval',
    },
  ]"
  :height="220"
  x-label="Days"
  y-label="Cases"
  tooltip-trigger="hover"
/>

Intervals ​

Draw a whisker at any x with intervals: a line from lower to upper with end caps, and a dot at value when one is given. Intervals are independent of the series, which makes them a good fit for calibration targets: observed ranges a bundle of simulated trajectories should pass through. x is in the chart's x-space (so it respects xMin, explicit x values, and epoch-ms on a date axis), and the y axis extends to cover every interval.

010203040IncidenceDays0102030
vue
<LineChart
  :series="trajectories"
  :intervals="[
    { x: 5, lower: 1, upper: 6, value: 3 },
    { x: 12, lower: 14, upper: 26, value: 20 },
    { x: 19, lower: 32, upper: 46, value: 39 },
    { x: 30, lower: 8, upper: 18, value: 13 },
  ]"
  :interval-style="{ color: '#c2410c', lineWidth: 2, capWidth: 10 }"
  :height="260"
  :line-opacity="0.15"
  x-label="Days"
  y-label="Incidence"
/>

intervalStyle sets defaults for every whisker on the chart; any field set on an individual interval wins. The style fields are color, lineWidth, capWidth (0 hides the caps), opacity, lineDash, outlineWidth / outlineColor (a page-colored halo for legibility over dense series, off by default), dot / dotRadius / dotFill / dotStroke / dotStrokeWidth for the value marker, and blendMode.

For one whisker per data point, put parallel lower and upper arrays on a Series. They draw in the series color under its line and dots, follow its legend toggle, show up in the tooltip as a range, and export as extra _lower / _upper CSV columns. Style them with the series' intervalStyle.

ObservedModel102030CasesWeek0123456
vue
<LineChart
  :series="[
    {
      data: observed,
      lower: observedLo,
      upper: observedHi,
      color: '#0057b7',
      dots: true,
      legend: 'Observed',
      intervalStyle: { capWidth: 6, opacity: 0.8 },
    },
    { data: model, color: '#ef4444', dashed: true, legend: 'Model' },
  ]"
  :height="240"
  x-label="Week"
  y-label="Cases"
  tooltip-trigger="hover"
/>

Intervals vs. areas. areas fills a continuous band between two series and suits a model's own uncertainty envelope. intervals marks discrete ranges at specific positions and suits observations.

Highlights and annotations ​

Area sections ​

Highlight a range of a series line by filling the area between the line and the x-axis. A labeled section's fill continues past the axis to a faint bar, with its label and description beneath, left-aligned at the section start. Labels of sections that overlap in time stack into rows, and the x-axis label moves to the left of the first row (a section label that would overlap it drops a row). Set legend: "inline" to list a section in the legend strip instead. Tune the bar and row spacing with area-section-layout (see AreaSectionLayout); per section, lineColor and strokeWidth style the highlighted line (or, without seriesIndex, the boundary lines) and barColor, barWidth, and barOpacity style the bar. Move the sections below with the sliders to see the labels stack and unstack; with x-label-align="center" the x-axis label moves beneath the rows instead.

No interventionsInterventions050100150Cumulative countDays0246810Day 2–7Rapid growth phaseDay 3–9Mitigation period
Growth phase (days)
010
Mitigation period (days)
010
vue
<script setup>
import { ref, computed } from "vue";

const growthRange = ref([2, 7]);
const mitigationRange = ref([3, 9]);
const sections = computed(() => [
  {
    startIndex: growthRange.value[0],
    endIndex: growthRange.value[1],
    color: "#6366f1",
    strokeWidth: 0,
    label: `Day ${growthRange.value[0]}–${growthRange.value[1]}`,
    description: "Rapid growth phase",
  },
  {
    seriesIndex: 0,
    startIndex: mitigationRange.value[0],
    endIndex: mitigationRange.value[1],
    color: "#f43f5e",
    label: `Day ${mitigationRange.value[0]}–${mitigationRange.value[1]}`,
    description: "Mitigation period",
  },
]);
</script>

<LineChart
  :series="series"
  :area-sections="sections"
  :height="250"
  x-label="Days"
  y-label="Cumulative count"
/>
<NumberInput
  v-model:range="growthRange"
  label="Growth phase (days)"
  :min="0"
  :max="10"
  number-type="integer"
/>
<NumberInput
  v-model:range="mitigationRange"
  label="Mitigation period (days)"
  :min="0"
  :max="10"
  number-type="integer"
/>

Without a below-chart label (no label/description, or legend: "inline") the fill stays inside the plot and nothing is drawn under the axis.

No interventionsDay 3–7 Rapid growth phase050100150Cumulative countDays0246810
vue
<LineChart
  :series="[
    {
      data: [0, 2, 5, 12, 25, 45, 70, 100, 130, 155, 170],
      color: '#000',
      strokeWidth: 1,
      legend: 'No interventions',
    },
  ]"
  :area-sections="[
    {
      seriesIndex: 0,
      startIndex: 3,
      endIndex: 7,
      color: '#6366f1',
      legend: 'inline',
      label: 'Day 3–7',
      description: 'Rapid growth phase',
    },
  ]"
  :height="250"
  x-label="Days"
  y-label="Cumulative count"
/>

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. A pointer line connects the anchor to the label: pointer picks "curved" (default), "straight" or "none", and arrow caps the anchor end. Text supports \n for line breaks, **bold** and _italic_ runs (they compose: **_both_**). Multi-line callouts attach to whichever edge of the text faces the anchor, so the pointer never cuts through the lines. For a horizontal or vertical reference line, see Reference lines.

The label and line each get a legibility outline in the page background color: outlineColor / outlineWidth style the line's halo and are the defaults for the text's textOutlineColor / textOutlineWidth. blendMode applies a CSS mix-blend-mode to the whole annotation so it combines with the series under it instead of covering them. Try the options below.

0102030CasesDays0510ClusterDay 3
Pointer
Label position
Outline
Blend mode
vue
<LineChart
  :data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
  :annotations="[
    {
      x: 3,
      y: 15,
      offset: { x: 50, y: -36 },
      text: '**Cluster**\n_Day 3_',
      pointer: 'curved',
      arrow: true,
      outlineWidth: 3,
      textOutlineWidth: 3,
      blendMode: 'normal',
    },
  ]"
  :chart-padding="{ top: 50, right: 70, bottom: 16, left: 70 }"
  :height="280"
  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.

Reference lines ​

Draw a reference line (a horizontal or vertical rule), such as a threshold or an event date, with an annotation whose pointer is "ruleY" (horizontal, at y) or "ruleX" (vertical, at x). The line spans the whole plot; the other coordinate only places the label. Pass text: "" for a line without a label.

0102030CasesDays0510CapacityIntervention
vue
<LineChart
  :data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
  :annotations="[
    {
      x: 0,
      y: 20,
      offset: { x: 4, y: -6 },
      text: 'Capacity',
      pointer: 'ruleY',
      lineDash: '4 3',
    },
    {
      x: 4,
      y: 2,
      offset: { x: 6, y: 0 },
      text: 'Intervention',
      pointer: 'ruleX',
    },
  ]"
  :chart-padding="{ top: 24, right: 24 }"
  :height="240"
  x-label="Days"
  y-label="Cases"
/>

All the rule values replace the connector with a straight line through the anchor:

  • "ruleX": vertical line spanning the plot height at the annotation's x.
  • "ruleY": horizontal line spanning the plot width at the annotation's y.
  • "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, and the label is positioned from the anchor as usual via offset. rotate turns the label about its position, in degrees clockwise: -90 reads bottom to top beside a vertical rule. Rotated text keeps to the side of its position facing away from the anchor, so a small offset.x of either sign puts the label just beside the rule. Set align explicitly for rotated text, since it runs along the rotated baseline ("left" extends away from the position in reading direction).

0102030CasesDays0510Peak day
Rule
Label side
Label rotation
Line dash
Outline
Blend mode
vue
<LineChart
  :data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
  :annotations="[
    {
      x: 5,
      y: 14,
      offset: { x: -6, y: 0 },
      text: 'Peak day',
      pointer: 'ruleX',
      align: 'left',
      rotate: -90,
      lineDash: '4 3',
      outlineWidth: 0,
      textOutlineWidth: 3,
    },
  ]"
  :chart-padding="{ top: 24, right: 24 }"
  :height="260"
  x-label="Days"
  y-label="Cases"
/>

Draggable markers ​

Use markers to draw vertical lines spanning the full plot height, each with a label just above the top of the line. Markers are draggable along the x-axis by default; bind with v-model:markers to receive position updates while dragging (plus marker-drag / marker-drag-end events). Labels are painted above all series lines with a background-colored stroke so they stay legible, and labels that would collide move down to the next row.

Each marker can drive its own variable: assemble the markers array from your refs in a writable computed and split drag updates back out in its setter, as below. (Alternatively, listen to marker-drag and dispatch on payload.index or payload.marker.label.)

Drag the lines below (or focus one and use the arrow keys). While a marker is dragged it shows its current x just above the x axis, formatted like the x axis or through valueFormat and styled with valueStyle, valueOutlineColor / valueOutlineWidth and valueBlendMode; set showValueOnDrag: false to hide it. The tooltip stays up over a marker's line and hides while one is dragged:

InfectionsHospitalizations50100CountDays010203040IsolationRecovery

Isolation: day 12.0 · Recovery: day 32.0

vue
<script setup>
import { ref, computed } from "vue";

// Each marker drives its own variable...
const isolation = ref(12);
const recovery = ref(32);

// ...mapped through a writable computed: the getter assembles the
// markers array, the setter splits drag updates back out.
const markers = computed({
  get: () => [
    { x: isolation.value, label: "Isolation" },
    { x: recovery.value, label: "Recovery" },
  ],
  set: ([a, b]) => {
    isolation.value = a.x;
    recovery.value = b.x;
  },
});
</script>

<LineChart
  v-model:markers="markers"
  :series="[
    {
      data: infections,
      color: '#1a1a1a',
      strokeWidth: 2,
      legend: 'Infections',
    },
    {
      data: hospitalizations,
      color: '#0057b7',
      strokeWidth: 2,
      legend: 'Hospitalizations',
    },
  ]"
  :height="260"
  x-label="Days"
  y-label="Count"
  tooltip-trigger="hover"
/>
<p>
  Isolation: day {{ isolation.toFixed(1) }} · Recovery: day
  {{ recovery.toFixed(1) }}
</p>

Drag positions are continuous floats; round in the setter (isolation.value = Math.round(a.x)) if the variable should be whole days; the marker then snaps as you drag. If you don't need separate variables, a plain ref holding the array works too: const markers = ref([{ x: 12, label: "Isolation" }]) with v-model:markers="markers".

Labels sit marker-label-gap pixels above the top of the lines (default 7). Increase it for more clearance; the chart reserves the extra top padding automatically (the gap plus the tallest label font size), so nothing clips.

The line's style is configurable per marker: color, strokeWidth, dashed: false for a solid line (or dash for a custom pattern), and outline: true for a page-colored stroke behind the line. The label's text styles via labelStyle and its legibility stroke via labelOutlineColor / labelOutlineWidth (set 0 to disable). Set labelBackground: true to draw the label on a pill in the marker color (or pass a color), flat where it sits on the line and rounded on its other corners, padded by labelPadding (a number or { x, y }, default 6 by 5); the text turns white or near-black to suit the fill unless labelStyle.color says otherwise, and a draggable marker's pill darkens on hover and keyboard focus. Set draggable: false to pin a marker in place.

50100CountDays010203040InterventionPeak
vue
<LineChart
  v-model:markers="markers"
  :series="[...]"
  :height="240"
  x-label="Days"
  y-label="Count"
/>

<script setup>
const markers = ref([
  {
    x: 10,
    label: "Intervention",
    color: "#ef4444",
    strokeWidth: 2,
    dashed: false,
    outline: true,
    labelStyle: { color: "#ef4444", fontSize: 13 },
    labelOutlineWidth: 4,
  },
  {
    x: 22,
    label: "Peak",
    color: "#0057b7",
    dash: "2 6",
    labelBackground: true,
  },
]);
</script>

On a date axis, set x to a Date, a date string, or epoch-ms; drag updates then emit Date values, and the drag readout formats them like the axis ticks. On a numeric axis x is in the same display space as the axis (it respects xMin). snap: true snaps a dragged marker to the series' x positions (whole days below) and a number snaps to multiples of that step (7 * 86400000 for weeks on a date axis, 5 for every fifth unit on a numeric one); arrow keys then step between snap positions.

50100InfectionsJan 12Jan 26Feb 9IsolationRecovery
vue
<LineChart v-model:markers="markers" :x="dates" :y="infections" :height="240" />

<script setup>
const dates = Array.from(
  { length: 45 },
  (_, t) => new Date(Date.UTC(2026, 0, 1 + t)),
);
const markers = ref([
  { x: "2026-01-13", label: "Isolation", snap: true },
  { x: "2026-02-02", label: "Recovery", labelBackground: true, snap: true },
]);
</script>
ts
interface ChartMarker {
  x: number | string | Date; // position in data coords (Date/string on a date axis)
  label?: string; // text above the top of the line
  color?: string; // line color (default: #999)
  strokeWidth?: number; // line width (default: 1.5)
  dashed?: boolean; // default: true; false = solid
  dash?: string | number | readonly number[]; // custom stroke-dasharray (wins over dashed)
  outline?: boolean; // page-colored stroke behind the line
  outlineColor?: string; // default: var(--color-bg-0, #fff)
  outlineWidth?: number; // extra width added to strokeWidth (default: 4)
  labelStyle?: LabelStyle; // { fontSize?, color?, fontWeight? }, defaults 12 / currentColor / 600
  labelOutlineColor?: string; // label legibility stroke (default: var(--color-bg-0, #fff))
  labelOutlineWidth?: number; // default: 3 (0 with labelBackground); 0 disables
  labelBackground?: boolean | string; // pill behind the label: true = marker color
  labelPadding?: number | { x?: number; y?: number }; // pill padding (default: { x: 6, y: 5 })
  draggable?: boolean; // default: true
  snap?: boolean | number; // true = series x positions; number = step in axis units
  showValueOnDrag?: boolean; // current x above the x axis while dragging (default: true)
  valueFormat?: (x: number | Date) => string; // formats that value (default: like the x axis)
  valueStyle?: LabelStyle; // drag value text style (defaults: label font size / currentColor / 400)
  valueOutlineColor?: string; // drag value halo (default: labelOutlineColor)
  valueOutlineWidth?: number; // default: 3; 0 disables
  valueBlendMode?: BlendMode; // mix-blend-mode for the drag value text
}

Each draggable marker is exposed as a horizontal slider (role="slider"): it's keyboard-focusable, and ←/→ move it one step (hold Shift for ×10). Note that when the chart has a title or ariaLabel, the <svg> defaults to role="img", which hides the sliders from assistive tech; pass role="figure" if screen-reader users should reach them.

Interaction and export ​

Zoom ​

Set zoom to let readers click and drag on the plot to zoom. One gesture covers two kinds of zoom:

  • Along x: a roughly straight horizontal drag selects a full-height band. The chart zooms to that x-range and the y axis rescales to the data visible in it.
  • Box: once the drag also covers about 24px vertically, the selection becomes a box and the chart zooms both axes to it.

Pass zoom="x" to allow only the x-range zoom, whatever the drag's height. While zoomed, marks outside the window are clipped at the edges the window cuts through; an edge that is the visible data's own boundary (the top and bottom of a rescaled y axis, or an x edge at the first or last point) leaves the dots and whisker caps sitting on it whole. A reset button appears at the top right; double-clicking the plot also resets, and Esc cancels a drag in progress. Repeated drags zoom further in, up to 10,000× per axis.

Drag-to-zoom is a mouse gesture. On touch screens (and with a pen) a drag keeps scrubbing the tooltip and scrolling the page, so the chart stays at its full extent there.

0100200300400CasesDays020406080100
vue
<LineChart
  :data="cases"
  :height="220"
  x-label="Days"
  y-label="Cases"
  tooltip-trigger="hover"
  zoom
/>

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.

10203040CasesWeek0123Download weekly cases (CSV)
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)" />

Accessibility ​

The chart's <svg> is exposed as a single labeled image so screen readers announce one accessible name instead of wandering through the individual marks. When the chart has a title, the <svg> gets role="img" and an aria-label set to the title. The menu and download controls live outside the <svg>, so they stay reachable regardless.

Set ariaLabel to give screen readers a fuller summary than the visible title (it overrides title for the accessible name only):

vue
<LineChart
  :data="cases"
  title="Daily ED visits"
  aria-label="Daily ED visits over 11 days, rising from 12 to a peak of 48"
/>

Pass role to override the default "img" (e.g. role="figure", or a role of your own).

With legend-toggle, each toggleable legend item is a real <button> laid over the legend, outside the <svg>. The buttons sit in a group named "Show or hide series", are reachable with Tab, activate with Enter or Space, and report whether the mark is shown through aria-pressed. They stay in place while the chart is expanded.

Props ​

PropTypeRequiredDefault
widthnumberNo—
heightnumberNo—
titlestringNo—
titleStyleTitleStyleNo—
rolestringNo—
ariaLabelstringNo—
axisLabelStyleLabelStyleNo—
tickLabelStyleLabelStyleNo—
legendStyleLabelStyleNo—
legendTogglebooleanNo—
hiddenSeriesstring[]No—
xLabelstringNo—
yLabelstringNo—
xLabelAlign"left" | "center"No—
debouncenumberNo—
menuboolean | stringNotrue
tooltipDataArrayLike&lt;unknown&gt;No—
tooltipTrigger"hover" | "click"No—
tooltipClamp"none" | "chart" | "window"No"window"
tooltipValueFormatNumberFormatNo—
tooltipTitle(label: string, index: number) =&gt; stringNo—
csvstring | (() =&gt; string)No—
filenamestringNo—
downloadLinkboolean | stringNo—
downloadButtonboolean | stringNo—
fullscreenTargetstring | HTMLElementNo—
zoomboolean | "x"No—
annotationsreadonly ChartAnnotation[]No—
intervalsreadonly ChartInterval[]No—
intervalStyleIntervalStyleNo—
gridStyleGridStyleNo—
chartPaddingChartPaddingNo—
yLineChartDataNo—
dataLineChartDataNo—
xLineChartXInputNo—
seriesSeries[]No—
areasArea[]No—
areaSectionsAreaSection[]No—
areaSectionLayoutAreaSectionLayoutNo—
markersChartMarker[]No—
markerLabelGapnumberNo7
lineOpacitynumberNo1
curveLineCurveNo—
yMinnumberNo—
yMaxnumberNo—
yScaleType"linear" | "log"No"linear"
xMinnumberNo—
xTicksnumber | number[]No—
yTicksnumber | number[]No—
xTickFormatNumberFormat | ((value: number, index: number) =&gt; string) | DateFormatNo—
timezoneDateTimezoneNo—
yTickFormatNumberFormatNo—
xLabelsstring[]No—
xGridboolean | GridStyleNo—
yGridboolean | GridStyleNo—

Series ​

ts
interface Series {
  y?: LineChartData; // y-values (one of y / data must be set; y wins)
  data?: LineChartData; // y-values (alternative name)
  x?: LineChartXInput; // optional parallel x-values; dates switch on a date axis
  color?: string;
  strokeWidth?: number;
  dashed?: boolean;
  curve?: LineCurve; // "linear" | "step" | "step-before" | "step-after" | "monotone"
  opacity?: number;
  lineOpacity?: number; // overrides opacity for the line only
  dotOpacity?: PerPoint<number>; // overrides opacity for the dots only
  blendMode?: BlendMode; // CSS mix-blend-mode for the line and dots
  line?: boolean; // default: true
  outline?: boolean; // page-colored stroke drawn behind the line
  outlineColor?: string; // default: var(--color-bg-0, #fff)
  outlineWidth?: number; // extra width added to strokeWidth (default: 4)
  dots?: boolean;
  dotRadius?: PerPoint<number>; // one value, or one per point
  dotFill?: PerPoint<string>;
  dotStroke?: string;
  legend?: string; // inline legend label, also names the tooltip row
  showInLegend?: boolean; // default: true when legend is set
  showInTooltip?: boolean; // default: true
  tooltipLabel?: string; // names the tooltip row without a legend entry
  lower?: LineChartData; // per-point whisker bounds, parallel to y
  upper?: LineChartData;
  intervalStyle?: IntervalStyle; // styles those whiskers
}

PerPoint ​

ts
// One value for every dot, one per point (array or typed array, parallel
// to the data), or a function of the point. undefined / NaN falls back to
// the default.
type PerPoint<T> = T | ArrayLike<T> | ((point: PointContext) => T | undefined);

interface PointContext {
  value: number; // the point's value
  index: number; // its index in the series' data
  x: number; // data units; epoch ms on a date axis
  seriesIndex: number;
  color: string; // the series' resolved color
}

Area ​

ts
interface Area {
  upper: LineChartData;
  lower: LineChartData;
  x?: LineChartXInput; // optional parallel x-values
  curve?: LineCurve; // both edges; defaults to the chart's curve
  value?: LineChartData; // shown in the tooltip instead of the range (stackAreas sets it)
  color?: string;
  opacity?: number;
  legend?: string; // inline legend label, also names the tooltip row
  showInLegend?: boolean; // default: true when legend is set
  showInTooltip?: boolean; // default: true for a named area
  tooltipLabel?: string; // names the tooltip row without a legend entry
  blendMode?: BlendMode; // CSS mix-blend-mode for the fill
}

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; // highlighted line segment, or the boundary lines of a full-height section; default: 2, 0 hides
  lineColor?: string; // same line; default: color
  dashed?: boolean;
  barColor?: string; // bar under a labeled section's fill; default: color
  barWidth?: number; // default: 1
  barOpacity?: number; // default: 0.5
  legend?: "inline" | "below" | false; // default: "below"
  inlineLabelStyle?: LabelStyle;
  inlineDescriptionStyle?: LabelStyle;
}

AreaSectionLayout ​

Chart-level spacing of the below-chart section labels, passed as area-section-layout. The bar and label rows are shared by every section, so the spacing is set once per chart.

ts
interface AreaSectionLayout {
  barOffset?: number; // plot bottom to the bar; default: 26
  labelGap?: number; // bar to the first label row; default: 6
  rowHeight?: number; // each stacked label row; default: 32
}

ChartInterval ​

ts
interface ChartInterval extends IntervalStyle {
  x: number; // data-space x (epoch ms on a date axis)
  lower: number;
  upper: number;
  value?: number; // point estimate, drawn as a dot
  seriesIndex?: number; // tie to a series: hides with it; LineChart takes its color, BarChart centers on its bar
}

interface IntervalStyle {
  color?: string; // default: currentColor (LineChart series whiskers: the series color)
  lineWidth?: number; // default: 1.5
  capWidth?: number; // default: 8; 0 hides the caps
  opacity?: number; // default: 1
  lineDash?: string | number | number[]; // whisker only; caps stay solid
  outlineWidth?: number; // default: 0 (no halo)
  outlineColor?: string; // default: var(--color-bg-0, #fff)
  dot?: boolean; // default: true for `intervals`, false for series whiskers
  dotRadius?: number; // default: 3
  dotFill?: string; // default: color
  dotStroke?: string;
  dotStrokeWidth?: number; // default: 1
  blendMode?: BlendMode;
}

The tooltip slot receives intervals (standalone intervals whose x is the hovered x) alongside values, and each series value carries lower / upper when the series has whiskers.

GridStyle ​

Accepted by xGrid / yGrid (and BarChart's valueGrid) in place of true, and by the chart-level gridStyle prop as defaults.

ts
interface GridStyle {
  color?: string; // default: currentColor
  opacity?: number; // default: 0.1
  lineWidth?: number; // default: 1
  lineStyle?: "solid" | "dashed" | "dotted"; // default: solid; scales with lineWidth
  lineDash?: string | number | number[]; // raw stroke-dasharray; overrides lineStyle
}