# LineChart

Several series compared over a continuous axis.

- Group: Charts
- Import: `import { LineChart } from '@misoto22/folio/charts'`
- Page: https://ui.misoto22.com/components/line-chart/
- Related: area-chart, composed-chart

## When to reach for it

The reader is comparing series against each other. When the area under one line is the point, fill it — that is an AreaChart.

## Anatomy

- **Figure frame** (required) — ChartFigure’s <figure> and its sr-only caption. description is announced with the title and printed only under showTitle, which is where a note about a clipped axis belongs.
- **Lines** (required) — <LineChart.Line>, one per series at a 1.6px stroke. isClickable adds a second, fully transparent 15px line underneath the visible one, because a hairline is not a pointer target.
- **Point markers** — <LineChart.Dot> and <LineChart.ActiveDot>, both slots and both off by default. The resting dot shares the intro wipe mask so it arrives with its own line; the active dot is never masked, because it exists only on hover, long after the wipe has finished.
- **Buffer segment** — buffer draws the last leg dashed by measuring the real path with getPointAtLength, so a projection reads as a different kind of fact at any curve type. Fewer than two drawable points and it falls back to a plain curve.
- **Sonification control** — <LineChart.Sonify>, a real <button> above the plot that plays the visible rows as pitch. Sound never starts from an effect, only from that click, and it reads the brushed window rather than the whole series.
- **Hidden data table** — The sr-only table of the full data. Recharts’ accessibilityLayer gives a keyboard cursor that announces one point at a time, which is navigation; this is the figures.

## Best practices

### Do

- This is the one chart in the family that survives a truncated value axis. A line encodes by SLOPE, so clipping the domain to the data’s own range is often what makes a two percent move visible at all — state the range in description when you do it.
- Reach for buffer on a period still open rather than dropping it. A part-month plotted solid reads as a crash; plotted as a dashed final leg it reads as what it is, which is incomplete.
- Compose <LineChart.Dot> when the series is sparse. With dots off — the default — five points are four segments, and the reader cannot tell a measured value from a bend in the interpolation.

### Don’t

- A single row draws nothing at all: one point has no segment, dot is false unless composed, and the empty state does not fire because a row exists. Guard the one-row case at the call site.
- Eight lines in one frame is a hairball, and a 1.6px stroke over an eight-step grey ramp makes it a worse one than a chromatic chart would. Past about five series the answer is Facet, not a ninth ramp slot — SERIES_SLOTS is 8 and there is no ninth.
- connectNulls turns a gap into a straight segment that looks measured. It costs more here than on an area, because the reader reads the slope of that invented segment as a rate.

## Accessibility

- title is required; the rows are also rendered as a visually hidden table.
- A clickable line gets a 15px transparent line underneath it, because a 1.6px stroke is not a pointer target.
- buffer draws the last segment dashed by measuring the real path length, so a projection is visibly a different kind of fact at any curve type.

## LineChart

Several series compared over a continuous axis — the shape for "which of these is going where". The difference from `<AreaChart>` is what the reader is asked to do: an area says "read the magnitude under this", a line says "compare these against each other". Filling four overlapping series makes the second question unanswerable, which is why a line chart has no fill variant to offer.

### Props

- `config` (required) — `TConfig & ValidateKeys<TData, TConfig>`.
- `data` (required) — `TData[]`. The rows the chart draws. One entry per point, bar or category.
- `title` (required) — `string`. What the chart shows, in a sentence a reader could act on. Required, and announced to a screen reader even when it is not printed.
- `showTitle` — `boolean`. Prints the title above the plot instead of hiding it from sight.
- `description` — `ReactNode`. A line under the title — the unit, the window, the caveat.
- `children` (required) — `ReactNode`. The composed parts — axes, grid, tooltip, legend, and the marks themselves.
- `className` — `string`. Merged onto the figure, last, so a call site can size or space it.
- `chartProps` — `ComponentProps<typeof RechartsLineChart>`. Escape hatch onto the raw Recharts chart element.
- `curveType` — `ChartCurveType` default `'linear'`. How the line between two points is interpolated. Every mark inherits it unless it says otherwise.
- `animationType` — `ChartRevealType` default `'forward'`.
- `defaultSelectedDataKey` — `string | null` default `null`. The series lit on first render, when the chart keeps its own selection.
- `selectedDataKey` — `string | null`. The selected series, driven from outside. Give this and the chart follows it; leave it undefined and the chart keeps its own, starting from `defaultSelectedDataKey`.
- `onSelectionChange` — `(selectedDataKey: string | null) => void`. Fires when the selection changes, and with null when it is cleared.
- `isLoading` — `boolean` default `false`. Swaps the marks for an animated skeleton, keeping the measured height so the page does not jump when the data lands.
- `loadingPoints` — `number`. How many points the skeleton draws.
- `xDataKey` — `keyof TData & string`. The row field on the category axis. Needed by the brush and by the table view.
- `hideDataTable` — `boolean` default `false`. Drops the hidden table view. Only correct when the page prints the data itself.
- `empty` — `ChartEmptyProps | false`. What the chart shows when it has nothing to draw. Rendered instead of the plot whenever `data` is empty and the chart is not loading — the state a real dashboard reaches within a week, and the one an empty pair of axes is indistinguishable from a failed load. `false` keeps the axes, for a chart whose emptiness is itself the reading.

## Example — default

```tsx
import { LineChart, type ChartConfig } from '@misoto22/folio/charts'

<LineChart title="Visitors per month" config={config} data={data} xDataKey="month">
  <LineChart.Grid />
  <LineChart.XAxis dataKey="month" />
  <LineChart.YAxis />
  <LineChart.Legend />
  <LineChart.Tooltip />
  <LineChart.Line dataKey="desktop" />
  <LineChart.Line dataKey="mobile" strokeVariant="dashed" />
</LineChart>
```

## Example — stroke dot curve

```tsx
import { ToggleGroup, ToggleGroupItem } from '@misoto22/folio'

<div className="flex w-full flex-col gap-4">
  <div className="flex flex-wrap gap-3">
    <ToggleGroup
      type="single"
      value={stroke}
      onValueChange={(next) => next && setStroke(next as LineStrokeVariant)}
      aria-label="Stroke"
    >
      {STROKES.map((option) => (
        <ToggleGroupItem key={option} value={option}>
          {option}
        </ToggleGroupItem>
      ))}
    </ToggleGroup>

    <ToggleGroup
      type="single"
      value={dot}
      onValueChange={(next) => next && setDot(next as ChartDotVariant)}
      aria-label="Dot"
    >
      {DOTS.map((option) => (
        <ToggleGroupItem key={option} value={option}>
          {option}
        </ToggleGroupItem>
      ))}
    </ToggleGroup>

    <ToggleGroup
      type="single"
      value={String(curve)}
      onValueChange={(next) => next && setCurve(next as ChartCurveType)}
      aria-label="Curve"
    >
      {CURVES.map((option) => (
        <ToggleGroupItem key={option} value={option}>
          {option}
        </ToggleGroupItem>
      ))}
    </ToggleGroup>
  </div>

  <LineChart title="Visitors per month" config={config} data={data} curveType={curve}>
    <LineChart.Grid />
    <LineChart.XAxis dataKey="month" />
    <LineChart.Tooltip />
    <LineChart.Line dataKey="desktop" strokeVariant={stroke}>
      <LineChart.Dot variant={dot} />
      <LineChart.ActiveDot variant="colored-border" />
    </LineChart.Line>
  </LineChart>
</div>
```

## Example — legend variants

```tsx
import { ToggleGroup, ToggleGroupItem } from '@misoto22/folio'
import { LineChart, type ChartConfig, type ChartLegendVariant } from '@misoto22/folio/charts'

<div className="flex w-full flex-col gap-4">
  <ToggleGroup
    type="single"
    value={variant}
    onValueChange={(next) => next && setVariant(next as ChartLegendVariant)}
    aria-label="Legend swatch"
  >
    {VARIANTS.map((option) => (
      <ToggleGroupItem key={option} value={option}>
        {option}
      </ToggleGroupItem>
    ))}
  </ToggleGroup>

  <LineChart title="Visitors per month" config={config} data={data}>
    <LineChart.Grid />
    <LineChart.XAxis dataKey="month" />
    <LineChart.Legend variant={variant} />
    <LineChart.Tooltip />
    <LineChart.Line dataKey="desktop" />
    <LineChart.Line dataKey="mobile" strokeVariant="dashed" />
  </LineChart>
</div>
```

## Example — backgrounds

```tsx
import { ToggleGroup, ToggleGroupItem } from '@misoto22/folio'
import { LineChart, type ChartBackgroundVariant, type ChartConfig } from '@misoto22/folio/charts'

<div className="flex w-full flex-col gap-4">
  <ToggleGroup
    type="single"
    value={variant}
    onValueChange={(next) => next && setVariant(next as ChartBackgroundVariant)}
    aria-label="Background plate"
    className="flex-wrap"
  >
    {VARIANTS.map((option) => (
      <ToggleGroupItem key={option} value={option}>
        {option}
      </ToggleGroupItem>
    ))}
  </ToggleGroup>

  <LineChart title={`Visitors per month — ${variant} plate`} config={config} data={data}>
    <LineChart.Background variant={variant} />
    <LineChart.XAxis dataKey="month" />
    <LineChart.Tooltip />
    <LineChart.Line dataKey="desktop" />
  </LineChart>
</div>
```

## Example — emphasis

```tsx
import { LineChart, type ChartConfig } from '@misoto22/folio/charts'

<div className="grid w-full gap-8 lg:grid-cols-2">
  <LineChart title="Glowing — one series is the point" showTitle config={config} data={data}>
    <LineChart.Grid />
    <LineChart.XAxis dataKey="month" />
    <LineChart.Line dataKey="desktop" glowing />
    <LineChart.Line dataKey="mobile" strokeVariant="dashed" />
  </LineChart>

  {/* The last segment is dashed by measuring the real path length, so the
      split lands exactly on the second-to-last point at any curve type —
      which an arithmetic dasharray cannot do. */}
  <LineChart title="Buffer — the last leg is a projection" showTitle config={config} data={data}>
    <LineChart.Grid />
    <LineChart.XAxis dataKey="month" />
    <LineChart.Line dataKey="desktop" buffer />
  </LineChart>
</div>
```

## Example — brush

```tsx
import { LineChart, type ChartConfig } from '@misoto22/folio/charts'

<LineChart title="Visitors per day" config={config} data={data} xDataKey="day">
  <LineChart.Grid />
  <LineChart.XAxis dataKey="day" />
  <LineChart.Legend />
  <LineChart.Tooltip />
  <LineChart.Line dataKey="desktop" />
  <LineChart.Line dataKey="mobile" strokeVariant="dashed" />
  <LineChart.Brush height={56} />
</LineChart>
```

## Example — loading

```tsx
import { LineChart, type ChartConfig } from '@misoto22/folio/charts'

<LineChart title="Visitors per month" config={config} data={[]} isLoading>
  <LineChart.Grid />
  <LineChart.Line dataKey="desktop" />
</LineChart>
```

## Example — axis labels and format

```tsx
import { LineChart, formatNumber, type ChartConfig } from '@misoto22/folio/charts'

<div className="grid w-full gap-8 lg:grid-cols-2">
  <LineChart title="p95 latency" showTitle config={latency} data={data}>
    <LineChart.Grid />
    <LineChart.XAxis dataKey="week" />
    <LineChart.YAxis
      label="Latency"
      tickFormatter={formatNumber({ style: 'duration' })}
      width={56}
    />
    <LineChart.Tooltip />
    <LineChart.Line dataKey="p95" />
  </LineChart>

  <LineChart title="Ad spend" showTitle config={spend} data={data}>
    <LineChart.Grid />
    <LineChart.XAxis dataKey="week" />
    <LineChart.YAxis
      tickFormatter={formatNumber({ style: 'currency', currency: 'AUD', fractionDigits: 0 })}
      width={72}
    />
    <LineChart.Tooltip />
    <LineChart.Line dataKey="spend" strokeVariant="dashed" />
  </LineChart>
</div>
```

## Example — sonify

```tsx
import { LineChart, type ChartConfig } from '@misoto22/folio/charts'

<LineChart title="Latency per week, milliseconds" config={config} data={data} xDataKey="week">
  <LineChart.Sonify align="start" noteMs={180} />
  <LineChart.Grid />
  <LineChart.XAxis dataKey="week" />
  <LineChart.YAxis label="ms" />
  <LineChart.Legend />
  <LineChart.Tooltip />
  <LineChart.Line dataKey="p95" />
  <LineChart.Line dataKey="p50" strokeVariant="dashed" />
</LineChart>
```

## Example — toolbar

```tsx
import { LineChart, type ChartConfig } from '@misoto22/folio/charts'

<LineChart title="Visitors per day" config={config} data={data} xDataKey="day">
  <LineChart.Toolbar exports={['png', 'csv']} />
  <LineChart.Grid />
  <LineChart.XAxis dataKey="day" />
  <LineChart.YAxis label="Visitors" />
  <LineChart.Legend />
  <LineChart.Tooltip />
  <LineChart.Line dataKey="desktop" />
  <LineChart.Line dataKey="mobile" strokeVariant="dashed" />
  <LineChart.Brush height={48} />
</LineChart>
```
