# Colors, fonts and formatting

> Theme tokens, per-series colors, number and date formatting, and localization.

Section: Customization. Source: https://ortexcharts.com/docs/customization

## Three Levels of Color

Colors are resolved in one direction, from general to specific, and knowing the order saves
a lot of guessing.

1. **The theme** is a flat token object. Every color the engine paints, when nothing more
   specific is set, comes from here.
2. **Series options** override the theme for one series: `color`, `upColor`, `topColor` and
   so on.
3. **Per-value coloring** overrides both, where the type supports it: a histogram in
   `colorMode: "sign"`, candles in up and down colors, a baseline series above and below its
   base.

The rule to internalize is that **a series left with unset colors follows the theme**. That
is what makes `chart.setTheme("light")` repaint an entire chart, indicators included,
without touching a single series. Setting explicit colors opts that series out.

_Live example: [theme-tokens](https://ortexcharts.com/examples/theme-tokens)_

Throughout this page `chart` is the core `Chart` — `setTheme`, `applyOptions`, `priceScale`
and `theme` live there. If you started from `createFinancialChart` or `createChartShell`,
reach it as `fc.chart` or `shell.chart.chart`.

## Theme Tokens

`darkTheme` and `lightTheme` are exported objects, and a theme is just such an object. Every
token is required, so start from one of the built-ins rather than from an empty object.

| Token | Type | What it colors |
|---|---|---|
| `name` | `string` | An identifier for your own bookkeeping. |
| `background` | color | The plot area of every pane. |
| `axisBackground` | color | The price and time axis strips. |
| `axisBorder` | color | The line between the axes and the plot. |
| `grid` | color | Grid lines. |
| `paneSeparator` | color | The divider between panes. |
| `paneSeparatorHover` | color | The same divider while it is being hovered or dragged. |
| `text` | color | Axis labels and general text. |
| `textMuted` | color | Secondary text. |
| `fontFamily` | CSS font stack | Every string the canvas draws. |
| `fontSize` | number | Base size in pixels; labels derive from it. |
| `crosshair` | color | The crosshair lines. |
| `crosshairLabelBackground` | color | The axis tags at the crosshair. |
| `crosshairLabelText` | color | Text inside those tags. |
| `up` | color | Rising candle bodies and bars. |
| `down` | color | Falling candle bodies and bars. |
| `upWick` | color | Rising candle wicks. |
| `downWick` | color | Falling candle wicks. |
| `volumeUp` | color | Volume columns on a rising bar. |
| `volumeDown` | color | Volume columns on a falling bar. |
| `line` | color | A line or area series with no color of its own. |
| `areaTop` | color | Top of the area gradient. |
| `areaBottom` | color | Bottom of the area gradient. |
| `palette` | `string[]` | Handed to successive overlays and indicator outputs in order. |
| `watermark` | color | The watermark text. |
| `lastValueText` | color | Text inside the last-value axis label. |
| `legendBackground` | color | Behind the legend; transparent in both built-in themes. |
| `legendText` | color | Legend text. |
| `selection` | color | Selected drawings and selection rectangles. |
| `tooltipBackground` | color | Hover tooltips. |
| `tooltipText` | color | Tooltip text. |
| `tooltipBorder` | color | Tooltip border. |

Colors accept `#rgb`, `#rrggbb`, `#rrggbbaa`, `rgb()` and `rgba()`.

### The Palette

`palette` is the one token that is not a single color, and it does more work than it looks.
Every series added without an explicit `color` takes the next entry, cycling when it runs
out. So does every indicator output whose definition asks for `theme.palette[n]`. Setting a
brand palette therefore recolors indicators and host overlays at once:

```ts
import { darkTheme } from "@ortex-charts/financial";

chart.setTheme({
  ...darkTheme,
  name: "brand-dark",
  palette: ["#FF7A00", "#7C5CE6", "#00B8D9", "#36B37E", "#FFAB00", "#FF5630"],
});
```

### Colour-Blind Safe Themes

About one man in twelve cannot separate the red and green a candle chart has used since the
1980s. Two themes ship for that, and the toolbar has a switch:

```ts
import { createFinancialChart } from "@ortex-charts/financial";

createFinancialChart(el, { theme: "dark-colorblind", data: bars });
```

| Theme | Up | Down |
|---|---|---|
| `dark-colorblind` | `#56B4E9` | `#D55E00` |
| `light-colorblind` | `#0072B2` | `#E69F00` |

Both pairs come from the Okabe-Ito palette, which is chosen to stay apart under deuteranopia,
protanopia and tritanopia, and both differ in **luminance** as well as hue, so direction
survives a greyscale print, a monochrome display and total colour blindness. Overlay series
use the rest of that palette, exported as `OKABE_ITO`.

Only the colours that carry meaning change: direction, volume, the overlay palette and the
single-series line. The background, grid and text keep the theme they came from, so a
colour-blind chart still looks like the rest of your application.

Your own theme can have the same treatment:

```ts
import { colorBlindSafe } from "@ortex-charts/core";

const acmeSafe = colorBlindSafe(acmeTheme);   // name becomes "acme-colorblind"
```

In [the shell](https://ortexcharts.com/docs/shell), the `colorBlind` toolbar item is a switch beside the light and
dark one, and the two are independent: turning the palette on keeps you in dark, and switching
to light keeps the palette.

### A Complete Custom Theme

```ts
import { darkTheme, type Theme } from "@ortex-charts/financial";

export const brandDark: Theme = {
  ...darkTheme,
  name: "brand-dark",
  background: "#0B0E14",
  axisBackground: "#0B0E14",
  axisBorder: "#1E2531",
  grid: "#141A23",
  text: "#8A96A6",
  textMuted: "#5A6675",
  fontFamily: '"Inter", -apple-system, "Segoe UI", Roboto, sans-serif',
  fontSize: 11,
  up: "#00C08B",
  down: "#FF4D5E",
  upWick: "#00C08B",
  downWick: "#FF4D5E",
  volumeUp: "rgba(0, 192, 139, 0.4)",
  volumeDown: "rgba(255, 77, 94, 0.4)",
  line: "#4C8DFF",
  areaTop: "rgba(76, 141, 255, 0.28)",
  areaBottom: "rgba(76, 141, 255, 0.02)",
  palette: ["#FFAB00", "#A78BFA", "#F472B6", "#34D399", "#FB923C", "#60A5FA"],
};

createFinancialChart(el, { theme: brandDark, data: bars });
```

### Switching at Runtime

```ts
chart.setTheme("light");
chart.setTheme(brandDark);
chart.theme();                 // the resolved Theme object
```

A theme change is a repaint, never a rebuild. Series, indicators, drawings, primitives, the
visible range and the crosshair position all survive it. Indicator colors are functions of
the theme, so they follow too.

### Color Helpers

```ts
import { parseColor, withAlpha, contrastText } from "@ortex-charts/financial";

parseColor("#22C08A");                  // [34, 192, 138, 1]
withAlpha(theme.up, 0.15);              // "rgba(34, 192, 138, 0.15)"
contrastText("#22C08A");                // "#111418" or "#FFFFFF", whichever reads
```

`contrastText` is what the engine uses for text on colored badges, and it is the right tool
for a host drawing its own labels on a theme color.

## Chart Chrome

Everything below is a chart option, applied at creation or with `applyOptions`, which is
deeply partial so you write only the keys you are changing.

### Grid, Crosshair and Watermark

```ts
chart.applyOptions({
  grid: { vertical: true, horizontal: true },
  crosshair: {
    mode: "magnet",                       // "magnet" | "free" | "hidden"
    vertLine: { visible: true, width: 1, style: "dashed", labelVisible: true, color: "#6F7D91" },
    horzLine: { visible: true, width: 1, style: "dashed", labelVisible: true },
  },
  watermark: { text: "AAPL", visible: true, fontSize: 44 },
});
```

`magnet` snaps the crosshair to the nearest bar value; `free` follows the pointer exactly;
`hidden` removes it while leaving hover events intact.

The watermark draws your own artwork instead of the text when you give it one, centred
behind the series in the first pane:

```ts
chart.applyOptions({
  watermark: {
    visible: true,
    logo: "https://cdn.example.com/mark.svg",  // or "ortex" for the ORTEX wordmark
    logoScale: 0.25,                           // height as a fraction of the pane, default 0.25
    logoHeight: 120,                           // exact height in pixels, wins over logoScale
    opacity: 0.08,                             // default 0.08
  },
});
```

Keep the opacity low enough that a candle wick still reads over it; the width is capped at
80% of the plot so a wide mark cannot run under the price scale. This is your mark on your
chart and is unrelated to the [ORTEX mark](https://ortexcharts.com/docs/branding), which a license removes.

### The Legend

```ts
chart.applyOptions({
  legend: {
    visible: true,
    showValues: true,          // OHLC or value at the crosshair, not just titles
    logo: "https://logos.example.com/aapl.png",
    symbol: "NASDAQ:AAPL",     // printed before the first series title
    paneControls: true,        // the collapse toggle on pane legends
    collapsed: false,          // show only the first row of every pane legend
    collapsible: true,         // the reader's own toggle on that first row
  },
});
```

`logo` is drawn before the first series title and is set automatically by the
[datafeed binding](https://ortexcharts.com/docs/data) from `SymbolInfo.logo`. A missing image falls back to the
color dot silently. `symbol` sits between the logo and the title, which is where an
exchange and ticker belong; the [shell](https://ortexcharts.com/docs/shell) fills it in from the current symbol
unless you turn that off.

A chart carrying a dozen overlays has a legend to match, and a reader who knows what is on
it wants the chart back. `collapsed` shows the first row only, and `collapsible` puts a
toggle on that row so the reader decides. Both the toggle and the option go through the
same three methods, so a host can drive it from its own button and store the answer:

```ts
chart.setLegendCollapsed(true);
chart.isLegendCollapsed();                                    // → true
const off = chart.onLegendCollapse((e) => save(e.collapsed)); // fires on either route
```

The shell has the button already: add `"legend"` to its `toolbar` items, and the state
travels in the layout as `legendCollapsed`.

### Price Scales

Each scale is configured independently, per pane.

```ts
chart.priceScale("right").applyOptions({
  mode: "logarithmic",                   // "normal" | "logarithmic" | "percentage" | "indexedTo100"
  autoScale: true,
  visible: true,
  scaleMargins: { top: 0.1, bottom: 0.1 },
  borderVisible: true,
  ticksVisible: true,
  minimumWidth: 64,
  invert: false,
});

chart.priceScale("right", volumePane.id).applyOptions({ scaleMargins: { top: 0.8, bottom: 0 } });
```

`scaleMargins` are fractions of the pane height left empty above and below the data, which
is how a volume pane keeps its columns in the lower fifth. `invert` flips the axis, which is
occasionally right for a yield or a rate.

### The Time Scale

Covered on [Time, sessions and resolutions](https://ortexcharts.com/docs/time); the options are `barSpacing`,
`minBarSpacing`, `maxBarSpacing`, `rightOffsetPx`, `fixLeftEdge`, `fixRightEdge`,
`followRealtime`, `borderVisible` and `visible`.

### Interaction and Layout

```ts
chart.applyOptions({
  handleScroll: true,
  handleScale: true,
  kineticScroll: true,       // momentum after a fast drag or flick
  autoSize: true,            // follow the container with a ResizeObserver
  width: 900,                // only when autoSize is false
  height: 480,
  timeAxisHeight: 26,
  priceLabelGap: 36,         // minimum pixels between price labels
  timeLabelGap: 72,          // minimum pixels between time labels
});
```

Raising `priceLabelGap` and `timeLabelGap` thins the labels, which is the fix for a small
chart whose axes look crowded. Lowering them packs more in.

## Number Formatting

Formatting is per series, through `priceFormat`.

```ts
series.applyOptions({ priceFormat: { type: "price", precision: 2, minMove: 0.01 } });
series.applyOptions({ priceFormat: { type: "volume", precision: 0, minMove: 1 } });
series.applyOptions({ priceFormat: { type: "percent", precision: 2, minMove: 0.01 } });
series.applyOptions({
  priceFormat: {
    type: "custom",
    precision: 2,
    minMove: 0.01,
    formatter: (v) => `${v >= 0 ? "+" : ""}${v.toFixed(2)} bp`,
  },
});
```

| `type` | Renders | Used for |
|---|---|---|
| `price` | `1,234.56` with `precision` decimals | Prices and most values. |
| `volume` | `1.23M`, `4.56B` | Volume and share counts. |
| `percent` | `12.34%` | Ratios already expressed in percent. |
| `custom` | Whatever `formatter` returns | Anything else: basis points, currencies, ratings. |

`minMove` is the smallest increment the instrument trades in. It matters because the axis
adds decimals when the visible range is narrow, and `minMove` is the floor it will not go
below. A `minMove` of `0.01` on a stock and `0.0001` on a currency pair is the usual pairing
with `precision` 2 and 4.

`formatter` wins over everything else, and it is used for the axis labels, the crosshair
tags, the last-value label and the legend, so one function covers them all.

The formatting helpers are exported for host-drawn labels:

```ts
import { formatPrice, formatPercent, formatCompact, formatVolume, precisionFromMinMove } from "@ortex-charts/math";
import { formatValue } from "@ortex-charts/financial";

formatPrice(1234.5678, { precision: 2 });        // "1,234.57"
formatPercent(0.1234, { precision: 2 });         // percent formatting
formatCompact(1234567, { precision: 2 });        // "1.23M"
formatVolume(1234567);                           // volume shorthand
precisionFromMinMove(0.0001);                    // 4
formatValue(1234.5, series.options.priceFormat); // exactly what the chart would print
```

## Dates, Locales and Language

Dates and numbers go through `Intl`, so the chart's `locale` option controls month names,
date ordering and grouping separators.

```ts
createFinancialChart(el, { locale: "de-DE", timeZone: "Europe/Berlin" });
```

There is a limit worth stating plainly: **the interface strings are English only.** The
toolbar labels, menu entries, dialog titles and tooltips in `@ortex-charts/ui` are not
translated and there is no string table to supply. Dates and numbers localize; words do not.
A host that needs another language today replaces the chrome with its own controls and
drives the chart through the shell methods, which are all public.

## Per-Series Overrides at a Glance

| Series type | The options that override the theme |
|---|---|
| `candlestick` | `upColor`, `downColor`, `wickUpColor`, `wickDownColor`, `borderVisible`, `hollowUp`, `bodyWidthRatio` |
| `bar` | `upColor`, `downColor`, `thin`, `openVisible` |
| `line` | `color`, `lineWidth`, `lineStyle`, `curve`, `pointMarkers`, `pointRadius` |
| `area` | `color`, `topColor`, `bottomColor`, `baseValue`, `curve` |
| `baseline` | `baseValue`, `topLineColor`, `bottomLineColor`, `topFillColor`, `bottomFillColor` |
| `histogram` | `color`, `upColor`, `downColor`, `colorMode`, `base`, `widthRatio` |

See [Series types](https://ortexcharts.com/docs/series) for what each one does.

## Styling the Chrome

The toolbar and drawing rail in `@ortex-charts/ui` are ordinary DOM. They are styled with
CSS custom properties derived from the chart theme, injected once per document, so a theme
switch restyles them along with the canvas.

```ts
import { applyThemeVars, UI_CSS, ensureStyles } from "@ortex-charts/ui";
```

`ensureStyles(document)` injects the stylesheet if it is not already there, and
`applyThemeVars(element, theme)` writes the custom properties onto an element. The shell
does both for you; the exports exist so a host can style its own controls to match, or
override the properties in its own stylesheet.

Class names are prefixed `oc-ui-`, which makes them stable targets for a host stylesheet
without fighting a CSS-in-JS runtime.

## A Practical Recipe: Two Themes and a Toggle

```ts
import { createFinancialChart, darkTheme, lightTheme, type Theme } from "@ortex-charts/financial";

const brand = (base: Theme, name: string): Theme => ({
  ...base,
  name,
  fontFamily: '"Inter", system-ui, sans-serif',
  palette: ["#FF7A00", "#7C5CE6", "#00B8D9", "#36B37E", "#FFAB00", "#FF5630"],
});

const themes = { dark: brand(darkTheme, "brand-dark"), light: brand(lightTheme, "brand-light") };

const chart = createFinancialChart(el, { theme: themes.dark, data: bars });

matchMedia("(prefers-color-scheme: light)").addEventListener("change", (e) => {
  chart.chart.setTheme(e.matches ? themes.light : themes.dark);
});
```

Deriving both themes from one function keeps the pair consistent, and the switch is a
repaint, so it can be wired to a media query without any care about timing.
