# Series types

> Candlesticks, bars, line, area, baseline, histogram, Heikin-Ashi and your own.

Section: Charts. Source: https://ortexcharts.com/docs/series

## The Six Built-In Types

Every series is created through `chart.addSeries(type, options)` and returns a `SeriesModel`.
Three of the types hold OHLCV bars and three hold single values; the difference matters,
because a value series cannot be redrawn as candles.

| Type | Kind | Data | Draws |
|---|---|---|---|
| `candlestick` | bars | `Bar[]` | Body between open and close, wicks to high and low. Hollow and Heikin-Ashi are options on this type, not separate types. |
| `bar` | bars | `Bar[]` | A vertical high-low line with an open tick left and a close tick right. |
| `line` | values | points | A polyline, optionally curved, optionally with point markers. |
| `area` | values | points | A line with a vertical gradient fill beneath it. |
| `baseline` | values | points | A line whose halves above and below a base value are colored and filled separately. |
| `histogram` | values | points | Columns from a base value, colored fixed, by sign, or by the main series direction. |

```ts
const price = chart.addSeries("candlestick", { title: "AAPL" });
price.setData(bars);

const ma = chart.addSeries("line", { title: "EMA 21", color: "#F5A524", lineWidth: 1.5 });
ma.setData(emaPoints);
```

_Live example: [stock-chart](https://ortexcharts.com/examples/stock-chart)_

## Options Every Series Has

These come from `SeriesOptionsBase` and apply to all six types and to any type you register
yourself.

| Option | Type | Default | What it does |
|---|---|---|---|
| `id` | `string` | generated | Stable identity, useful when you restore a saved chart. |
| `title` | `string` | `""` | Name in the legend and in the layout. Placement is keyed by it. |
| `pane` | `string` | first pane | Which pane the series lives on. |
| `priceScaleId` | `string` | `"right"` | `right`, `left`, or any other id for an undrawn overlay scale. |
| `visible` | `boolean` | `true` | Hides the series without removing it; it keeps updating. |
| `color` | `string` | next palette color | Line, area and histogram color. |
| `lineWidth` | `number` | `2` | Stroke width in CSS pixels. |
| `lineStyle` | `"solid" \| "dashed" \| "dotted"` | `"solid"` | Stroke pattern. |
| `priceFormat` | `PriceFormat` | price, 2 decimals | Number formatting for the axis, legend and labels. |
| `lastValueVisible` | `boolean` | `true` | Value label on the price axis. |
| `priceLineVisible` | `boolean` | `false` | Dashed horizontal line at the last value. The `bar` and `candlestick` types default it to `true`. |
| `align` | `AlignMode` | `"forwardFill"` | How timestamps map onto the main timeline. |
| `zIndex` | `number` | `0` | Draw order within a pane. |
| `legendVisible` | `boolean` | `true` | Whether the series appears in the legend. |
| `crosshairMarkerVisible` | `boolean` | `true` | Dot on the series at the crosshair index. |

`color` left unset takes the next color from the theme palette, so several overlays added in
a row are distinguishable without any color management on your side.

## Candlesticks

```ts
chart.addSeries("candlestick", {
  title: "AAPL",
  upColor: "#22C08A",
  downColor: "#EF4E5A",
  wickUpColor: "#22C08A",
  wickDownColor: "#EF4E5A",
  borderVisible: true,
  hollowUp: false,
  heikinAshi: false,
  bodyWidthRatio: 0.7,
});
```

`hollowUp` draws up candles with a transparent body, which is the classic hollow-candle
style. `bodyWidthRatio` is the fraction of the bar spacing a body occupies; bodies never
grow wider than the gap allows and never shrink below one pixel, so a chart at maximum zoom
out still reads as candles rather than as mush.

Colors left unset fall back to the theme `up`, `down`, `upWick` and `downWick` tokens, which
is what makes a theme switch repaint candles correctly without touching the series.

### Heikin-Ashi

Heikin-Ashi is an option rather than a type, so it can be toggled without recreating the
series or losing the indicators computed from it.

```ts
price.applyOptions({ heikinAshi: true });
```

The transform runs over the raw bars whenever the data or the flag changes. Note the
limitation: on a live chart the transform currently re-runs over all bars on every tick,
which is measurable on very long intraday histories. Indicators still read the raw columns,
so an EMA over Heikin-Ashi candles is an EMA of the real closes.

## OHLC Bars

```ts
chart.addSeries("bar", { title: "AAPL", thin: false, openVisible: true });
```

`thin` drops the open and close ticks and draws only the high-low line, which is what most
people want below about four pixels of bar spacing. `openVisible: false` keeps the close
tick and drops the open one.

## Line

```ts
chart.addSeries("line", {
  title: "Cost to borrow",
  curve: "linear",        // "linear" | "step" | "monotone" | "smooth"
  pointMarkers: false,
  pointRadius: 2.5,
  align: "forwardFill",
});
```

`curve: "step"` is the right choice for anything that holds a value until it changes, such
as a rating, a threshold or a daily statistic drawn on an intraday chart. `monotone` is a
shape-preserving spline; `smooth` is a basis spline that does not pass through its points,
so avoid it where the exact values matter.

Line series decimate when they are denser than the pixels available: one minimum and one
maximum per pixel column. The visual result is identical and the cost stops growing with the
data.

## Area

```ts
chart.addSeries("area", {
  title: "Estimated Short Interest",
  color: "#7C5CE6",
  topColor: "rgba(124, 92, 230, 0.30)",
  bottomColor: "rgba(124, 92, 230, 0.02)",
  baseValue: 0,
});
```

`topColor` and `bottomColor` are the ends of a vertical gradient; unset, they come from the
theme `areaTop` and `areaBottom` tokens. `baseValue` fills down to a value instead of to the
bottom of the pane, which is what you want for a series that can go negative.

## Baseline

```ts
chart.addSeries("baseline", {
  title: "Relative performance",
  baseValue: 0,              // or "first" for the first visible value
  topLineColor: "#22C08A",
  bottomLineColor: "#EF4E5A",
  topFillColor: "rgba(34, 192, 138, 0.18)",
  bottomFillColor: "rgba(239, 78, 90, 0.18)",
});
```

`baseValue: "first"` re-bases on every pan, which turns the series into "performance since
the left edge of the view" without any recomputation on your side.

## Histogram

```ts
chart.addSeries("histogram", {
  title: "Volume",
  colorMode: "mainDirection",     // "fixed" | "sign" | "mainDirection"
  base: 0,
  widthRatio: 0.7,
  upColor: "rgba(34, 192, 138, 0.45)",
  downColor: "rgba(239, 78, 90, 0.45)",
  priceFormat: { type: "volume", precision: 0, minMove: 1 },
});
```

The three color modes cover the three things histograms are used for. `fixed` is one color
for a count. `sign` colors above and below `base` differently, which is what a MACD histogram
or a delta pane wants. `mainDirection` colors each column by whether the main series bar at
the same index closed up or down, which is what a volume pane wants — and it is why
`createFinancialChart` does not need you to precompute colored volume rows.

## Setting and Updating Data

`setData` replaces everything. `update` handles one row and is the path live data takes.

```ts
series.setData(rows);

const appended = series.update({ time, open, high, low, close, volume });
// true when a new bar was appended, false when the last bar was replaced in place
```

The rules `update` follows are worth knowing, because the fast paths depend on them:

- A `time` equal to the last bar replaces that bar in place.
- A later `time` appends into a capacity-doubling backing store, so no column is copied.
- An earlier `time` takes a slower rebuild path; feeds that emit out of order pay for it.

`series.holdUpdates()` and `series.releaseUpdates()` queue updates and apply them in order
later. [Bar replay](https://ortexcharts.com/docs/replay) uses this so a live feed can keep running while the user
replays history.

## Switching Type Without Losing State

```ts
price.setType("line", { lineWidth: 2 });
```

`setType` keeps the data, the id, the pane, the scale, the title, the alignment and every
base option, and swaps only the renderer and its type-specific defaults. Indicators, markers
and drawings attached to the chart are untouched. This is what the toolbar's chart-type
buttons call, and it is why switching from candles to a line does not flash.

A value series cannot become a bar series, because it has no open, high or low. The call
throws with a message that says exactly that.

## Reading Values

```ts
series.valueAt(index);       // { open, high, low, close, volume } or { value } or null
series.primaryValue(index);  // the number the axis label and price line use
series.lastIndex();          // index of the last finite value, or -1
series.rawLength;            // rows as given, before alignment
series.raw;                  // the struct-of-arrays form, or null before setData
series.columns;              // aligned columns — a view, never hold it across a data change
```

## Price Lines

A price line is a horizontal line at a fixed value on the series' scale, with an axis label.
It belongs to the series, so it moves with the scale and disappears with it.

```ts
const id = chart.addPriceLine(series.id, {
  price: 182.5,
  title: "Entry",
  color: "#F5A524",
  lineStyle: "dashed",
  lineWidth: 1,
  axisLabelVisible: true,
});

chart.removePriceLine(series.id, id);
```

For lines the user can drag, and for crossing notifications, use
[alerts](https://ortexcharts.com/docs/replay) instead.

## Writing Your Own Series Type

A series type is four functions and a defaults object. Register it once and
`chart.addSeries("your-type", …)` works, including in saved layouts and the legend.

```ts
import { registerSeriesType, pixelColumns, type SeriesOptionsBase, type SeriesTypeDef } from "@ortex-charts/core";
import { valueExtent } from "@ortex-charts/math";

interface DotOptions extends SeriesOptionsBase {
  radius: number;
}

const dotSeries: SeriesTypeDef<DotOptions> = {
  kind: "values",
  defaults: { radius: 3 },
  draw(ctx, view) {
    const { xs, ys } = pixelColumns(view, "value");
    ctx.fillStyle = view.options.color;
    for (let i = 0; i < xs.length; i++) {
      if (ys[i] !== ys[i]) continue;                   // NaN is a gap
      ctx.beginPath();
      ctx.arc(xs[i], ys[i], view.options.radius, 0, Math.PI * 2);
      ctx.fill();
    }
  },
  extent: (columns, from, to) => valueExtent(columns.value, from, to),
  valueAt: (columns, i) => (columns.value[i] === columns.value[i] ? { value: columns.value[i] } : null),
  primaryValue: (columns, i) => columns.value[i],
};

registerSeriesType("dot", dotSeries);

chart.addSeries<DotOptions>("dot", { title: "Signals", color: "#F472B6", radius: 4 }).setData(points);
```

The `SeriesView` handed to `draw` carries aligned `columns`, the visible index bounds `from`
and `to`, the mappings `x(index)` and `y(value)`, the bar spacing, the resolved theme, the
device pixel ratio, the plot rectangle, the hovered index and the main series columns. Draw
only between `from` and `to`; the engine has already clipped to the pane.

Four rules keep a custom type well behaved:

- Return `null` from `extent` when the series should not affect autoscale.
- Return `null` from `valueAt` where there is no data, so the legend shows nothing rather
  than `NaN`.
- Treat `NaN` as a gap in every column rather than as a zero.
- Add an optional `colorAt` when the series is drawn in more than one color, so the
  last-value label matches what is on screen.

## What Is Not Here

Renko, Kagi, Point and Figure, line break, step-line bars, scatter and HLC area are not
built in. They are transforms plus a renderer on the same interfaces shown above, and they
are on the roadmap, but today writing one is your work rather than a configuration flag.
