tala started as a technical analysis library.
The idea was simple: give it asset price history, chain the indicators you want, and get enriched data back.
const result = tala()
.sma(14)
.rsi(14)
.macd()
.run(history)
That worked.
But after using it for a while, one thing became obvious:
computed indicators are not that satisfying to look at as raw objects.
You can stare at rsi14, macdHistogram, or sma20 in a JSON result, but it does not give you the full picture. These values are meant to be seen against price movement.
A moving average is useful because of where it sits relative to the candles.
RSI matters because of how it moves over time.
MACD is easier to understand when you can see the crossovers and histogram shifts.
So the next step for tala became clear.
It needed a way to turn calculated indicators into a chart.
That became:
.chart()
The First Version Was Not the Right Version
My first thought was to build charting as a separate package.
Something like:
tala
tala-viz
On paper, that looked clean.
The calculation library stays separate. The visualization package does its own thing. Everyone is happy.
Except the API did not feel right.
If a user already has this:
tala()
.sma(14)
.rsi(14)
.macd()
then making them move to a separate package just to see the result feels awkward.
The better version was much simpler:
await tala()
.sma(14)
.rsi(14)
.macd()
.chart(history)
That felt like the natural ending of the chain.
Not a separate tool.
Not a second workflow.
Just: calculate these indicators, then show them to me.
So the separate package idea was dropped, and chart() became part of the main tala API.
Picking the Charting Layer
The next question was which charting library to use.
I wanted something that was already good at financial charts. Not a general-purpose charting package that could technically render candles if you configured enough things.
That led to TradingView Lightweight Charts.
It was open-source, TypeScript-friendly, and built for exactly this kind of use case.
More importantly, it matched the kind of output I wanted tala to generate: a clean chart page where the user could inspect candles, overlays, and oscillators without needing to build a dashboard first.
The goal was not to turn tala into a charting platform.
The goal was to give users a fast way to see what their indicator chain produced.
The Feature Slowly Became More Than “Show a Chart”
At the start, I imagined this as a fairly small feature.
Render candles.
Render indicators.
Done.
That was optimistic.
Once the first chart existed, the missing pieces became obvious.
Overlay indicators like SMA, EMA, Bollinger Bands, and VWAP belonged on the candlestick chart.
But oscillators were different.
RSI, MACD, Stochastic, CCI, Williams %R, and ADX cannot all share one pane nicely. They live in different value ranges, and if you force them together, the chart becomes technically complete but practically unreadable.
So the design evolved.
First one pane.
Then two.
Eventually three oscillator panes grouped by range:
0–100:
RSI, ADX, Stochastic
Around zero:
MACD, Williams %R, Fisher Transform
Wide range:
CCI
That was one of the first reminders that visualization work is not just about displaying data.
It is about deciding how the data should be read.
The UI Changed a Lot
The sidebar went through several versions.
At one point, it was a slide-out panel.
Then it became always visible.
Then it moved to the left.
Then it changed again so it could behave more like a hover overlay while keeping the charts usable.
The toggles changed too.
They started one way, became checkboxes, then went back to toggles.
The controls button existed for a while, then disappeared.
The editable data table also changed direction.
My first attempt placed it inline with the chart. That did not work well. It took too much space and made the whole page feel cramped.
So it became a modal.
That was a better fit. The chart stayed focused, and editing OHLCV data became something you opened only when needed.
None of these changes were individually huge.
But together, they were the difference between “the feature technically works” and “the feature feels usable.”
The Bugs Were Mostly Small, Annoying, and Educational
Some of the hardest parts were not the big architecture decisions.
They were small details that were easy to underestimate.
Tooltips were one example.
At first, I positioned them using the chart point returned by Lightweight Charts.
That sounded right.
It was not.
The tooltip was slightly offset because the point was relative to the chart drawing area, not the full page layout.
The fix was to use the mouse event’s viewport coordinates instead.
Small issue, but very visible.
Time scale syncing had a similar path.
The first version synced panes using logical ranges. It mostly worked, but the charts still did not feel perfectly aligned.
Switching to visible time ranges made the behavior better.
Again, not a glamorous fix.
But when you are building chart interactions, “almost right” is still wrong.
The Most Painful Part Was Generated HTML
The chart output is a self-contained HTML page.
That means TypeScript generates HTML, and that HTML contains JavaScript.
So at some point I was writing TypeScript template literals that generated JavaScript strings inside an HTML document.
That is exactly as fun as it sounds.
Escaping became painful.
One missing brace could make the whole page blank.
Some bugs were not really logic bugs. They were “the generated JavaScript is invalid because one character disappeared three layers ago” bugs.
I ended up using small scripts just to check brace balance in the generated output.
Not fancy.
But it worked.
This was probably the least elegant part of the journey, but also one of the most real parts. Sometimes the work is not a clean architectural breakthrough. Sometimes it is just finding the missing }.
Server Mode Changed the Feature
The first version could write an HTML file.
That was useful.
But then the question came up:
What happens if the user edits the data?
In a static HTML file, the browser can update the candle chart, but it cannot re-run tala indicators because the library runs in Node.js.
That led to server mode.
await tala()
.sma(14)
.rsi(14)
.chart(history, {
format: 'server',
port: 3000,
})
Server mode starts a local HTTP server with a recalculation endpoint.
The user can edit OHLCV data in the browser, click Recalculate, and the server re-runs the same indicator chain.
That made the feature feel less like a static export and more like a small inspection environment.
It also introduced one of the easiest bugs to miss: data ordering.
The browser wants chart data oldest-first.
tala expects history newest-first:
history[0] === most recent candle
So recalculation had to reverse the data before sending it to the server, then reverse the response back for display.
Without that, everything could still render, but the indicators would be calculated in the wrong order.
That is the kind of bug that looks fine until you actually trust the output.
What Shipped
The feature shipped in @jimzandueta/tala@2.2.0.
The final API supports both static HTML output and local server mode:
await tala()
.sma(14)
.ema(26)
.bb(20, 2)
.macd()
.rsi(14)
.chart(history, { format: 'html' })
const chart = await tala()
.sma(14)
.rsi(14)
.chart(history, {
format: 'server',
port: 3000,
})
chart.close()
The release ended with:
243 passing tests
37 test suites
29 new visualization tests
It also kept the parts of tala I did not want to compromise:
Zero runtime dependencies for normal usage
TypeScript strict mode
Dual CJS/ESM output
Tree-shakeable build
The charting code only loads when chart() is called.
So users who only want calculations still get the lightweight library they expected.
What I Learned
The main lesson from this feature is that visualization is not just output.
It is interpretation.
The calculations were already there. But turning them into something useful meant making decisions about layout, grouping, scales, tooltips, editing, recalculation, and safety.
The method looks small:
.chart()
But the journey behind it was not.
It went from a simple “can we show the indicators?” idea into a feature with HTML output, server mode, editable data, synchronized panes, review fixes, and a release.
That is the kind of feature I like building.
Small API.
Clear purpose.
A lot of hidden work to make the user experience feel straightforward.
Try It
If you work with price history data and want a TypeScript-first way to calculate technical indicators, you can try tala here:
npm install @jimzandueta/tala
Then generate a chart:
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 the new chart() method, I would be interested to hear what feels useful, what feels missing, and which indicators or chart interactions would make the workflow better.


Top comments (0)