# Adding a chart() Method to Tala

> I added a chart() method to tala, my TypeScript technical analysis library, so you can see its indicators against the price. It also gained a server mode that re-runs the indicators when you edit the data in the browser.

- Date: 2026-05-30
- Tags: typescript, opensource, showdev, tradingview
- Also on dev.to: [https://dev.to/jimzandueta/building-chart-for-tala-from-raw-indicator-data-to-something-you-can-actually-inspect-hg9](https://dev.to/jimzandueta/building-chart-for-tala-from-raw-indicator-data-to-something-you-can-actually-inspect-hg9)

<p align="center">
  <img src="/blog/building-chart-for-tala-from-raw-indicator-data-to-something-you-can-actually-inspect/1.png" alt="Tala logo" width="160" />
</p>

I added a `chart()` method to `tala`, my TypeScript library for technical analysis, because its results were not that satisfying to look at as raw objects. Technical analysis means running formulas called indicators, like moving averages, over past prices. You give `tala` an asset's price history, chain the indicators you want, and get the data back with those indicators added.

```ts
const result = tala()
  .sma(14)
  .rsi(14)
  .macd()
  .run(history)
```

After using it for a while, staring at `rsi14`, `macdHistogram` or `sma20` in a JSON result didn't give me the full picture. A moving average is useful because of where it sits next to the candles. RSI matters because of how it moves over time, and MACD is easier to follow when you can see its lines cross. That's why `tala` needed a way to turn its indicators into a chart:

```ts
.chart()
```

![Tala chart output with candlesticks, indicator overlays, oscillator panes and sidebar controls.](/blog/building-chart-for-tala-from-raw-indicator-data-to-something-you-can-actually-inspect/2.png)

## Where chart() lives

My first thought was a separate package:

```txt
tala
tala-viz
```

As two packages, the API felt awkward. If someone already has this:

```ts
tala()
  .sma(14)
  .rsi(14)
  .macd()
```

then making them move to a second package just to see the result is clumsy. Ending the chain with the chart was simpler:

```ts
await tala()
  .sma(14)
  .rsi(14)
  .macd()
  .chart(history)
```

It reads as "calculate these indicators, then show them to me". I dropped the separate package and made `chart()` part of the main `tala` API.

For the drawing I picked TradingView Lightweight Charts. It's made for financial charts and it's open source. It also let me show candles, overlays and oscillators on one page without building a dashboard first.

## Panes and layout

Overlay indicators like SMA, EMA, Bollinger Bands and VWAP use the same units as the price, so they sit on the candlestick chart. Oscillators swing within their own range of values instead. RSI, MACD, Stochastic, CCI, Williams %R and ADX live in different ranges, and forced into one pane they are technically complete but unreadable. The design went from one pane to two, and ended with three oscillator panes grouped by range:

```txt
0–100:
RSI, ADX, Stochastic

Around zero:
MACD, Williams %R, Fisher Transform

Wide range:
CCI
```

The interface changed a lot too. The sidebar started as a slide-out panel. Then it became always visible, moved to the left, and finally worked more like a hover overlay so the charts stayed usable. The toggles became checkboxes and then went back to toggles. A controls button existed for a while and then disappeared.

A table also lets you edit the OHLCV data, which is the open, high, low, close and volume of each candle. My first attempt put it inline with the chart, and it took too much space and made the page feel cramped. I moved it into a modal, so you open it only when you need it.

## The bugs

Tooltips were slightly off at first. I positioned them with the chart point that Lightweight Charts returns, but that point is relative to the chart's drawing area, not the page. Using the mouse event's viewport coordinates fixed it.

Keeping the panes in step was similar. I first synced them with logical ranges, which count bars, and the panes never lined up. Syncing on visible time ranges made it better.

The most painful part was the generated HTML. The chart output is a self-contained HTML page, so TypeScript generates the HTML, and the HTML contains JavaScript. At one point I was writing TypeScript template literals that produced JavaScript strings inside an HTML document. Escaping was tricky, and one missing brace could leave the whole page blank. I ended up writing small scripts just to check that every brace in the generated output had its pair.

## Server mode

The first version wrote an HTML file. Then the question came up of what happens when someone edits the data. A static page can redraw the candles, but it can't re-run the indicators, because `tala` runs in Node.js. I added server mode to fix that:

```ts
await tala()
  .sma(14)
  .rsi(14)
  .chart(history, {
    format: 'server',
    port: 3000,
  })
```

It starts a local HTTP server with a recalculation endpoint. You edit the OHLCV data in the browser, click Recalculate, and the server re-runs the same indicator chain.

The browser wants chart data oldest first, and `tala` expects history newest first:

```ts
history[0] === most recent candle
```

To handle that, recalculation reverses the data before sending it to the server, then reverses the response again for display. Without that, everything still draws, but `tala` calculates the indicators in the wrong order.

## What shipped

The feature shipped in `@jimzandueta/tala@2.2.0`, with both static HTML output and local server mode:

```ts
await tala()
  .sma(14)
  .ema(26)
  .bb(20, 2)
  .macd()
  .rsi(14)
  .chart(history, { format: 'html' })
```

```ts
const chart = await tala()
  .sma(14)
  .rsi(14)
  .chart(history, {
    format: 'server',
    port: 3000,
  })

chart.close()
```

The release ended with these test numbers:

```txt
243 passing tests
37 test suites
29 new visualization tests
```

It also kept the parts of `tala` I did not want to give up:

```txt
Zero runtime dependencies for normal usage
TypeScript strict mode
Dual CJS/ESM output
Tree-shakeable build
```

Tree-shakeable means a bundler can drop the parts you don't use. The charting code only loads when you call `chart()`, so people who only want the calculations still get the light library they had before. You still call it the same way, at the end of the chain:

```ts
.chart()
```

## Try it

You can install `tala` from npm:

```bash
npm install @jimzandueta/tala
```

Then generate a chart:

```ts
import { tala } from '@jimzandueta/tala'

await tala()
  .sma(14)
  .ema(26)
  .bb(20, 2)
  .macd()
  .rsi(14)
  .chart(history, { format: 'html' })
```

Links:

- GitHub repo: https://github.com/jimzandueta/tala
- npm package: https://www.npmjs.com/package/@jimzandueta/tala
- Docs: https://jimzandueta.github.io/tala/docs/

If you try `chart()`, I'd like to hear which indicators or chart interactions you want next.
