# Charts

> What the charts share, the data they take, and how to format, size and colour them.

There are 84 pieces on [Charts](https://obsydeui.com/charts), in nine groups: Trends, Comparison, Composition, Distribution, Radial, Calendars, Relationships, Metrics and Utilities. The Utilities group holds five working references rather than charts: [Chart Tooltip](https://obsydeui.com/charts/chart-tooltip), [Chart Legend](https://obsydeui.com/charts/chart-legend), [Chart Patterns](https://obsydeui.com/charts/chart-patterns), [Chart Annotations](https://obsydeui.com/charts/chart-annotations) and [Chart States](https://obsydeui.com/charts/chart-states).

## What the charts share

Every chart is built on one kit, `ember/viz-kit`, which installs once beside `ember/shared` and is reused by every chart after that. From it, the full-size charts get:

- **A tooltip** that follows the pointer, a finger or the keyboard. Most radial charts read out in their centre instead.
- **A legend** wired to the chart both ways: hover a row and its series stands out, hover a series and its row lights.
- **Loading and empty states** on the same instance, through one `status` prop.
- **Axes, ticks and number formatters** from `viz-kit/scale.ts`.
- **Keyboard and touch.** Tab focuses the plot and the arrow keys scrub it. On a phone, a tap leaves the reading up after the finger lifts.
- **An `accent`** and reduced motion, the same as every other piece.

Compact pieces such as Sparkline and Metric keep only what fits their size: no tooltip or legend, and quieter states.

The examples on this page use the [Line Chart](https://obsydeui.com/charts/line-chart). The table on each chart's page lists the controls of its live demo; the component's full props are in its `Props` interface in the source.

## Data

Charts take their data in one of two layouts.

- **Series first:** a `series` array, each item with its `values` and a name, plus one `labels` array for the x axis. Line, step and stream charts work this way. The Line Chart names a series with `name`; most newer charts take an `id` and a `label`.
- **Rows first:** a `data` array with one item per row or category. The Bar Chart takes `{ label, values }` per row, with the series names in `series`. The Donut Chart takes `{ label, value }` per slice.

Values are plain numbers. The x position is the index: `values[i]` belongs to `labels[i]`, so every series has the same length, oldest first.

```tsx
import { LineChart } from '@/components/obsyde/ember/line-chart/LineChart';

const labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];

export function Requests() {
  return (
    <LineChart
      label="Requests per day"
      labels={labels}
      series={[
        { name: 'North', values: [120, 132, 101, 134, 90, 230, 210] },
        { name: 'South', values: [220, 182, 191, 234, 290, 330, 310] },
      ]}
    />
  );
}
```

The Line Chart draws up to six series. Without `labels`, the rows are numbered 01, 02 and so on.

A few charts take other shapes:

- The Area Chart takes numbers, `{ x, y }` points, or any objects, read through its `x` and `y` accessor props.
- The Live Line takes `{ t, v }` samples, with `t` a timestamp in ms. Timeline Bars take events with `start` and `end` in ms.
- Flows and networks such as Sankey and Network Graph take `nodes` and `links`.
- Gauge, Dial Gauge and Metric take a single `value`. Kiln Gauge takes `units`, an array.

To update a chart, pass new arrays. Lines, areas and bars morph to the new values over 500ms. The Line Chart also leaves a faint ghost of the old line, and when the new values continue the old ones (points appended, or a window that slid left), it grows from its tip instead.

## Formatting and axes

Values go through a formatter, a function `(v: number) => string`. On the Line Chart and the other early charts it is `formatValue`; most of the newer charts call it `format`. On the Line Chart it formats the y axis, the tooltip and the legend, and without one the chart picks a plain number format that fits its ticks. Other charts differ: some keep a fixed axis format, and some default to their own unit (Budget Columns to currency). Check the chart's source when the axis matters.

X labels are strings, so format dates before you pass them. `formatX` on the Line Chart reformats each label at render, with the label and its index.

The kit's formatters are in `viz-kit/scale.ts`, and you can import them yourself:

```tsx
import { fmtCurrency, fmtDate } from '@/components/obsyde/ember/viz-kit/scale';

<LineChart
  label="Revenue"
  labels={days.map((day) => fmtDate(day, 'weekday'))}
  series={series}
  formatValue={(v) => fmtCurrency(v, { compact: true })}
  yTicks={5}
  yDomain={[0, 4000]}
/>
```

There are also `fmtNumber`, `fmtCompact` (1500 becomes "1.5k"), `fmtPercent` and `fmtDuration`. All of them use a true minus sign.

Axis options on the Line Chart:

- `yTicks`: about how many y ticks (default 4). Ticks land on round steps.
- `yDomain`: a fixed `[min, max]`. Without it, the range fits the visible series and eases to its new fit when you hide one.
- `showAxis`: turn both axes off for a bare plot.
- `background`: `'grid'` (default), `'dots'`, `'hatch'` or `'none'`.

There are no axis lines: the grid carries the structure. X labels thin out so they never collide, and keep the first and last. While you scrub, a date pill rides the x axis and the labels near it step aside.

## Tooltip and legend

Most charts take `tooltip`: usually `'panel'` and `'none'`, on many line and area charts `'corner'` (a fixed readout above the plot), and on Lollipop, Range Bars and Pyramid `'pill'`. Defaults vary: most use `'panel'`, Lollipop and Range Bars `'pill'`, Area Chart and Streamgraph `'none'`. The chart builds the content itself, usually one row per visible series formatted with your formatter; some charts add their own rows, such as open, high, low and close on Candlestick. The panel places itself where it covers nothing you are reading: the hovered point, its neighbours and the axis pills stay clear.

On the Line Chart and the other charts that share its plot navigation, Tab focuses the plot, then:

| Key | Action |
| --- | --- |
| ← / → | Step one row |
| Shift+← / → | Jump to the next marked row: the peak, the low, a threshold crossing |
| PageUp / PageDown | Move 10% of the rows |
| Home / End | First or last row |
| Esc | Clear, and unlock an isolated series |

Each step is read out by screen readers as a sentence, such as "Wed, North 101, down 23% on the row before". On the Line Chart, ↑ and ↓ move focus between series instead. Charts with a different shape have their own keys: Heat Grid selects rectangles with Shift+arrows, Sunburst walks its hierarchy, and Metric's sparkline has no keyboard reading.

Series legends work the same way on every chart:

- **Hover** a row and that series stands out while the others fade, in every chart bound to the legend.
- **Click** a row to hide or show its series. The axis re-fits.
- **Shift+click** locks the isolation, so you can move the pointer away and keep reading. Click any row or the legend background, or press Esc, to unlock.
- **Keyboard:** the arrow keys and Home/End move between rows, Enter or Space toggles, Shift+Enter isolates.

Press-and-hold to lock is opt-in: the kit's `<Legend>` takes `hold`, and no chart turns it on. Level legends (heat steps) only respond to hover and focus, and gradient legends are a key to read, not a control.

The Line Chart's legend defaults to `'focus'`: a click lights that series in the accent instead of hiding it. Use `legend="toggle"` for the hide-and-show click, `'list'` for a side legend whose values roll as you scrub, or `'none'`. It shows with two or more series.

Charts that take `syncGroup` share one crosshair and focus with every other chart on the same key. They sync by row index, so give them the same rows.

## Loading and empty states

Every chart takes `status`: `'ready'` (the default), `'loading'` or `'empty'`.

- **Loading** shows a shimmering placeholder in the chart's own shape, with a "Loading…" chip.
- **Empty** shows a faint baseline and a short message.
- **Ready** fades the placeholder out while the data draws in.

On the full-size charts it is the same instance throughout: nothing remounts, so the chart keeps its size and the switch is a crossfade. Compact charts such as Sparkline show the placeholder without the chip or message. The crosshair, dots and pills stay hidden until it is ready. The messages are set in each chart's source; change them there.

Set `status="empty"` yourself when there is nothing to show. Some charts also switch to empty on their own when given no data, but not all of them do.

## Size and responsiveness

Most charts fill the width of their container and redraw when that width changes, without replaying the entry animation. Sparkline is a fixed 96px wide unless you set `width="fill"`, and Radial Stack keeps a minimum of 260px. The marks draw once the chart has measured its container in the browser.

Height depends on the kind of chart:

- **Most charts** take `height` in px. On the Line Chart it is the plot plus the x-label row (default 240); the legend sits above it.
- **Radial charts** (Donut, Radar, Sunburst, Gauge and the like) take `size`: the largest diameter in px. They draw at that size until they have measured their container, then shrink to fit a narrower one.
- **Row charts** (Lollipop, Range Bars, Dumbbell, Pyramid, Bar Race, Horizon) take `rowHeight` and grow with the number of rows.

On Scatter Plot and Hexbin, `size` is the size of a mark, not of the chart.

Charts don't block page scrolling: on touch screens a vertical swipe still scrolls the page. The exceptions own the gesture on purpose: Network Graph pans with it, and an interactive Gauge drags its needle.

## Accent and colour

Series are monochrome. They take a bone-to-grey ladder by position, from `--viz-1` to `--viz-6` in `viz-kit/viz-kit.css`. Past six, most charts carry the ladder on with in-between tones; a few (Streamgraph and some radial charts) repeat it.

The `accent` (`ember`, `volt` or `arc`) is kept for the one thing that matters: the focused series, a stretch past a threshold, the live tip, the selected bar, the cursor dot when there is one series. Changing it changes all of those at once.

```tsx
<LineChart label="Requests" labels={labels} series={series} accent="volt" defaultFocused={0} />
```

Here the first series starts focused and runs in volt while the others stay grey. Use `focused` and `onFocusChange` to control it.

The Line Chart has no per-series colour prop: colour follows position. A few charts differ:

- The Step Line takes a `color` on each series.
- Heat Grid, Horizon Chart and Correlation Matrix take `palette="heat"`, and Hexbin takes `ramp="heat"`, for a blackbody ramp that follows the accent. Their default is grey.
- The Candlestick defaults to green and red (`colors="market"`). Set `colors="mono"` to keep it monochrome.

To change the accent's colours, see [Theming](https://obsydeui.com/docs/theming).

## Performance

Most charts are SVG. Charts with thousands of marks draw them on a canvas: Hexbin, Ember Dust, Network Graph, the dot field of Radial Clock and the dither of Density Plot. Axes, tooltips and labels stay SVG and HTML on top.

Hover, scrubbing and animation mostly skip React: the charts write to the DOM directly from frame loops that sleep when nothing moves. Network Graph also pauses when it is off screen or the tab is hidden.

For long series, the Line Chart's `brush` adds an overview strip under the plot: drag to zoom the x range, double-click to reset. For thousands of scattered points, use Hexbin or Ember Dust rather than Scatter Plot.
