The charting library an AI agent can read
Lighter, faster, smarter
A canvas charting engine with no dependencies. Put it on your site and your users can ask about a chart — the agent reads what is on screen and draws the answer back onto it.
import { createChart, AreaSeries } from '@arincen/charts';
const chart = createChart(el, { autoSize: true });
chart.addSeries(AreaSeries).setData(data);
chart.toText(); // and a model can read it- 25.8KB
- gzipped, light build
- 0
- dependencies
- 701
- tests, every one of them run on this build
- 3
- calls to read, picture and annotate a chart
The part nobody else has
Your users' eye and hand on the chart
Every charting library draws for eyes. Everything else — a model, a screen reader, an alerting job — has to dig through series internals and write the same summary again, badly, in every project. This one says what it is showing, in words, and takes an answer back as markers, levels and shaded regions.
Runs here, on this page. No key, no request, nothing leaves your browser.
chart.toText()
chart.annotate()
// 1. the chart says what it is showing
const description = chart.toText();
// 2. your model reads it
const answer = await ask(`${description}
Name the periods worth a second look. Reply as JSON:
[{ "from": <unix>, "to": <unix>, "text": "<six words>" }]`);
// 3. the answer goes back on the chart
chart.annotate(JSON.parse(answer));Your application already has the data. This is not about the data.
You have the rows, and you have a model of your own — ask it what happened last March and it will tell you. What neither of them has is the chart: where the reader is pointing, which readings are on screen right now, and a way to put the answer back where they are looking. That is the part somebody has to write per chart, per project, and it is the part this does.
- "Why did it spike here?"
- Your database cannot say where "here" is. Pixel coordinates become a timestamp and a price through a scale that changes on every zoom.
chart.pointer() - "Zoom into the March crash."
- That is not a sentence to answer, it is an action to take. The agent moves the view the reader is looking at.
chart.timeScale().setVisibleRange() - "Draw the trend line between these two lows."
- A model can work out the levels. Somebody still has to put a line on a canvas at the right coordinates — and take that one line off again when the next sentence asks.
chart.annotate()
It usually costs less, too: a description of what is on screen is a few dozen tokens, and the ten thousand readings behind it are not. The model decides what it actually needs and asks for those rows. Not a universal claim — arithmetic over thousands of readings needs the readings — but the nine thousand candles nobody asked about need never be sent.
Every one of those, running on this page
Four charts, four calls. Point at one, move the view of another, draw on a third and take a single line back off it, and let the fourth find every chart on this page the way an agent would.
chart.pointer()point at the chartchart.timeScale().setVisibleRange()press a buttonchart.annotate()press a buttonwindow.arincenChartspress a buttonIt states, it does not interpret
No "bullish", no "resistance", no trend call. A conclusion about somebody else's money is theirs to draw — and a test fails our build if any of those words appear in the output.
It describes what is on screen
And says so. A model told the high is 150 about a chart holding five years will say something wrong about the other four, so the window is stated outright.
The same call serves a screen reader
It is the plainest text description of a chart there is, which is the other reason to have it. Full keyboard navigation and a live region ship in the same build.
Twenty-one charts, all running
Everything it draws, drawing right now
Not screenshots. Every square below is a live chart from the same file this page loaded, and every one of them is the smallest honest demonstration of one thing.
Candlesticks
Rounded bodies, wicks drawn separately, and our green and red rather than anybody else's.
Area, with the fill carried into the axes
The gutters take the same gradient, so the chart reads as one object instead of a coloured middle between two grey strips.
Lines, as many as you like
A price and its ten-period average. Hover one and the others fade back.
Baseline
Green above the level you set, red below it, filled both ways.
OHLC bars
The oldest notation on a price chart, and still the densest.
Volume
A histogram coloured per bar, formatted in thousands and millions without asking.
Two panes, one time axis
Price above, volume below, sharing a crosshair. Drag the divider — it has a grip.
Price on the right, volume on the left
Two series on two scales, each measured on its own numbers.
Two instruments, one scale
Percentage mode, so a 40 dollar stock and a 4000 dollar one can be read against each other.
A period switcher
Press one. The data is replaced and the view refits — no rebuild, no flicker, no second chart.
Buy, sell and a target
Entries, exits and the level you are watching. Markers are outlined in the page colour so they stay legible over a candle.
Your name on it
A watermark of your own text, behind the series and in front of nothing.
A chart type we do not ship
A rolling high-low band, drawn by a renderer of your own. Custom series replace the drawing entirely.
A tooltip, without writing one
Hover it. Nobody else gives you this — every project hand-rolls the same floating box.
The hours the market was shut
Three intraday days. The shading comes from opening hours you supply and the platform's own timezone database.
Annotations, from wherever
A region and a point, drawn from the shape a model returns — or from your own analysis.
A light chart on a dark page
One word: theme. Nine values across five branches of the options tree, including the one everybody misses.
Nothing to draw, and it says so
Rather than labelling an axis 0.00 to 1.00 over an empty grid, which is a chart stating prices it does not have.
Three sparklines in a table
One call each. Green if it ended higher than it started, red if it did not, no axes, nothing to drag.
A live feed
A reading a second through update(), with the last price pulsing — unless the reader has asked for less movement.
The numbers behind it
toCSV() gives you the readings, toImage() gives you the picture. The first two rows are printed on the chart.
Each of these is a handful of lines. The documentation carries the code for all of them, and a page you can edit them on.
Feature by feature
Some of what you only get here
Every row is something the charting libraries we checked have no API for — including the one most teams are migrating from, whose published type declarations we read rather than trusted our memory of. Anywhere else, each of these is yours to build.
A chart a model can read and draw on
toText(), toImage() and annotate(). No provider, no key, no bundled client.
Export the picture and the numbers
A PNG of what is on screen, and a CSV of the readings behind it, in one call each.
A tooltip that follows the crosshair
They have no tooltip API at all, so every project hand-rolls the same one.
Keyboard and screen-reader support
Arrows, page keys, Home and End, with every move announced. On by default, because a chart nobody can reach without a mouse has its prices locked behind one.
Dark mode in one word
By hand it is nine values across five branches of the options tree, and the one everybody misses turns the price under the pointer into dark text on a dark tag.
A chart that says it has no data
Instead of labelling an axis 0.00 to 1.00 over an empty grid, which is a chart stating prices it does not have.
It tells you what is wrong with your data
Out of order, duplicated, a NaN, a price quoted as a string — counted and named, rather than drawn short in silence.
Plugin failures are reported, not swallowed
A primitive that throws loses its own drawing and says which hook failed, instead of vanishing with an empty console.
The hours the market was shut
Shaded from hours you supply and the platform's own timezone database, so summer time needs nothing from anyone.
A sparkline in one call
Six chart options and three series options have to be turned off before a chart becomes a shape in a table cell. Or one function.
It respects reduced motion
The pulsing last price and the glide after a flick stand down when the reader has asked for less movement.
Right-to-left text, without mirroring the chart
Labels follow the locale; time still runs left to right, because a reversed axis puts the newest bar where every reader looks for the oldest.
And this is only some of them. The rest — a tooltip that writes its own contents, primitives, custom series, right-to-left labels, conflation for a hundred thousand bars — is in the documentation.
Two builds, one codebase
The features you do not use are not there
Most of what a charting library carries is structural, and most pages never use it. A bundler cannot remove it — the core references it, and nothing can prove your page has one pane. So it is removed earlier, when the library is built.
Switch the build and watch what the chart can still do.
- ✓Line, area, baseline, candlestick, bar and histogram series
- ✓Crosshair, markers, price lines and primitives
- ✓Pan, zoom, pinch and axis drag
- ✓Panes
- ✓Logarithmic, percentage and indexed-to-100 price scales
- ✓Left and overlay price scales
- ✓Custom series
- ✓Watermarks, touch tracking and kinetic scroll
Not disabled. Absent — the method is undefined and the code is not in the file.
The case for it
Why this one
Something other than eyes can read it
One call hands a model exactly what is on screen, in words. Another draws its answer back as regions, points and levels. No key, no bundled client, no provider to be tied to — and the same call is the plainest description a screen reader has ever been given of a chart.
Small enough to stop thinking about
Charts are usually the heaviest thing on a financial page. This one is smaller than most icon sets.
Nothing to audit
No dependencies at all. Nothing to update, nothing to have a vulnerability filed against, nothing pulling in a transitive tree.
Already carrying real traffic
Every chart on Arincen is drawn by it, on both domains — instrument pages, portfolios, articles, the mobile app and the embeddable widgets.
Extend it without forking
Primitives draw over a chart that already works. Custom series replace the drawing entirely. Both take plain objects, so the thing you need next is yours to write rather than ours to approve.
Get going
Start here
One command, or a script tag and no build step at all.
Works with
import { createChart, AreaSeries } from '@arincen/charts';
const chart = createChart(document.getElementById('chart'), {
autoSize: true,
});
const series = chart.addSeries(AreaSeries, {
lineColor: '#db2777',
});
series.setData(data);
chart.timeScale().fitContent();