# Moving from Highcharts

> One real Highcharts Stock chart ported end to end: what the translator does and what is left by hand.

Section: Commercial. Source: https://ortexcharts.com/docs/from-highcharts

## The Chart Every Research Product Has

This page ports one real Highcharts Stock chart, the one nearly every screener and research
product has: candles, volume on a second y-axis, two moving averages as indicator series,
earnings flags, a target line, a range selector, a navigator and the exporting module, with a
page-wide theme from `Highcharts.setOptions` and live ticks through `addPoint`. The options
object below is the one the port starts from; the [API mapping](https://ortexcharts.com/docs/migration) is the
concept-by-concept table to keep open beside it.

```ts
const options: Highcharts.Options = {
  chart: { height: 520 },
  title: { text: "IBM · NYSE" },
  rangeSelector: { selected: 4, buttons: [{ type: "month", count: 1, text: "1m" }, { type: "year", count: 1, text: "1y" }, { type: "all", text: "All" }] },
  navigator: { enabled: true },
  exporting: { enabled: true },
  yAxis: [
    { title: { text: "Price" }, height: "65%", opposite: true, plotLines: [{ value: 250, color: "#ff9800", dashStyle: "ShortDash", label: { text: "Target 250" } }] },
    { title: { text: "Volume" }, top: "70%", height: "30%", offset: 0, opposite: true },
  ],
  plotOptions: { candlestick: { color: "#ef5350", upColor: "#26a69a" } },
  series: [
    { type: "candlestick", id: "ibm", name: "IBM", data: ohlc },                       // [x, o, h, l, c]
    { type: "column", id: "vol", name: "Volume", data: volume, yAxis: 1 },             // [x, v]
    { type: "sma", linkedTo: "ibm", name: "SMA 20", params: { period: 20 }, color: "#ffb74d" },
    { type: "ema", linkedTo: "ibm", name: "EMA 50", params: { period: 50 }, color: "#4fc3f7" },
    { type: "flags", onSeries: "ibm", name: "Earnings", shape: "circlepin", data: flags }, // { x, title, text }
  ],
};
Highcharts.stockChart("chart", options);
```

Timestamps need no conversion: Highcharts and ORTEX Charts both use milliseconds since the
Unix epoch.

## Step One: The Translator

```bash
npm install @ortex-charts/migrate
```

```ts
import { fromHighcharts } from "@ortex-charts/migrate";

const { chart, series, indicators, markers, unsupported } = fromHighcharts(el, options, { theme: "dark" });
console.info("no counterpart:", unsupported);
```

`options` is the typed `Highcharts.Options` from above, passed as it is; the translator's
input types accept it without a cast. One call produces:

| In the options | On the chart |
|---|---|
| `candlestick` with `[x, o, h, l, c]` rows | The main series, on the right scale of the first pane. |
| `yAxis[1]` with `top: "70%"` and `height: "30%"` | A second pane, 30/65 the height of the first. |
| The `column` on `yAxis: 1` | The volume, in that pane, colored by the candle's direction and formatted as volume. |
| `sma` and `ema` with `params.period` | `addIndicator("sma", { length: 20 })` and `addIndicator("ema", { length: 50 })`, in the series' colors. |
| `flags` with `{ x, title, text }` | Markers above the bar: the title as the text, the text as the tooltip, `circlepin` as a circle. |
| `plotLines` on `yAxis[0]` | One price line at 250, dashed, labelled, on the main series. |
| `title.text` | The watermark. |
| `chart.height` | The chart height. |

And `unsupported` reads:

```text
exporting (the shell offers the equivalent)
navigator (the shell offers the equivalent)
rangeSelector (the shell offers the equivalent)
```

Nothing else is lost, and nothing is emitted for what was: a series the translator cannot
draw leaves no empty legend row behind.

`translateHighcharts(options)` returns the same translation without creating anything:
`chart`, `panes`, `series`, `indicators`, `markers` and `unsupported`, for a test or for a
port that builds the chart itself, as the next step does.

## Step Two: What Is Left by Hand

Three items in `unsupported`, and two that never appear in a Highcharts options object.

**The range selector and the navigator.** These are the shell's period buttons.
`createChartShell` takes everything `createFinancialChart` takes, so the translation can be
poured into it. The buttons default to a day, a week, a month, a quarter, a year, five years
and ten years; `ranges` sets your own. See [the toolbar](https://ortexcharts.com/docs/toolbar#period-buttons).

**Exporting.** There is no export server. The shell's screenshot button and CSV download
cover the two things the module was used for; `downloadPNG` and `rowsToCSV` in
`@ortex-charts/viz` are the same from code. A PDF report is your renderer's job.

**The theme.** `Highcharts.setOptions({ colors, chart: { backgroundColor } })` configured the
page. Here every chart carries a theme object: start from `darkTheme`, set `background`,
`grid`, `text`, `fontFamily` and `palette`, and pass it as `theme`. Indicators take the
palette in order, which is what the `colors` array did. See
[colors, fonts and formatting](https://ortexcharts.com/docs/customization).

**Live ticks.** `series.addPoint(point, false)` on the candle, again on the volume, then
`chart.redraw()` becomes one call: `fc.update(bar)`. A bar whose time equals the last bar
replaces it; a later time appends. The chart schedules its own frame.

**"All" is not `fitContent`.** Highcharts drew ten years in one view by grouping the data
into weekly points. `timeScale.fitContent()` stops at `minBarSpacing`, half a pixel per bar
by default, so 2,500 daily bars do not fit in 1,100 pixels and the view opens on the most
recent bars that do. Lower `timeScale.minBarSpacing`, or open on the last year with
`setVisibleRange`, which is what most readers wanted from "All" anyway.

## Step Three: The Finished Port

The translation plus the shell, in about 30 lines. This is the code the research-product
row of [moving in](https://ortexcharts.com/docs/moving-in) is budgeting for.

```ts
import { createChartShell } from "@ortex-charts/ui";
import { darkTheme } from "@ortex-charts/financial";
import { translateHighcharts } from "@ortex-charts/migrate";
import type { Bar } from "@ortex-charts/math";

const t = translateHighcharts(options);
const candle = t.series.find((s) => s.type === "candlestick")!;
const bars = candle.data as Bar[];

// The volume came through as its own series; the financial chart draws volume from Bar.volume.
const volume = t.series.find((s) => s.volume);
if (volume) {
  const byTime = new Map((volume.data as Array<{ time: number; value: number }>).map((p) => [p.time, p.value]));
  for (const b of bars) b.volume = byTime.get(b.time) ?? 0;
}

const shell = createChartShell(el, {
  ...t.chart,                                    // height, legend, watermark
  theme: { ...darkTheme, name: "brand", background: "#131722", grid: "#1f2535", palette: ["#4fc3f7", "#ffb74d", "#ba68c8"] },
  timeZone: "America/New_York",
  resolution: "1D",
  symbol: "IBM",
  data: bars,
  volume: { heightRatio: t.panes[0]?.heightRatio ?? 0.3 },
  toolbar: { symbol: false, resolution: false },  // the page has its own header
  extrasCollapsed: true,
});
const fc = shell.chart;

for (const l of candle.priceLines) fc.chart.addPriceLine(fc.main.id, { ...l, lineWidth: 1, axisLabelVisible: true, title: l.title ?? "" });
for (const ind of t.indicators) fc.addIndicator(ind.id, ind.inputs, ind.color ? { colors: { [ind.id]: ind.color } } : {});
fc.setMarkers(t.markers);

feed.onBar((bar) => fc.update(bar));           // was addPoint + redraw
```

`fromHighcharts` is this without the shell: the same panes, indicators, markers and price
lines on a core chart, for a page that has its own period buttons and export.

## Column and Pie Charts

The other Highcharts charts on the page move in one call each, with nothing left by hand.

```ts
import { fromHighchartsCategory, fromHighchartsPie } from "@ortex-charts/migrate";

const { chart: column, unsupported } = fromHighchartsCategory(el, columnOptions);   // xAxis.categories, stacking: "normal"
const pie = fromHighchartsPie(el, pieOptions);                                     // innerSize: "60%" is a donut
```

Percent stacking is the one thing reported: normalise the data first. See
[treemaps and heatmaps](https://ortexcharts.com/docs/viz) for what the category and pie charts can do that
Highcharts' cannot.

## Before You Ship

- **Check `unsupported` once**, then delete the log line.
- **A comparison axis** becomes a second series on a percentage or indexed scale:
  `rightPriceScale: { mode: "percentage" }`, or `"indexedTo100"`.
- **A Highcharts indicator with no counterpart** (Ichimoku, VWAP and the rest of the long
  tail) is reported by type. Look in `@ortex-charts/indicators-extra` first, then
  [register your own](https://ortexcharts.com/docs/indicators#writing-your-own).
- **Indicator defaults differ.** A period Highcharts left unset takes the ORTEX Charts
  default, which is 20 for a moving average where Highcharts used 14. Set it explicitly if
  the number matters.
- **Many small charts** in a results table should be [sparklines](https://ortexcharts.com/docs/sparklines), not
  charts.
- **The licence key on the deployed origin**, not just on localhost.
