@rioaxyz/sonrchart 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +268 -0
- package/dist/index.cjs +19660 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1116 -0
- package/dist/index.d.ts +1116 -0
- package/dist/index.js +19548 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rioa, LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# sonrchart
|
|
2
|
+
|
|
3
|
+
**Own the glass, not the ocean.** A themed, data-source-agnostic React charting
|
|
4
|
+
component for financial candlestick charts, built on
|
|
5
|
+
[KLineChart](https://github.com/klinecharts/KLineChart) (MIT). Extracted from the
|
|
6
|
+
Sonr trading app so the chart engine can be developed, versioned, and tested on
|
|
7
|
+
its own.
|
|
8
|
+
|
|
9
|
+
> New here? Read the [lore](LORE.md) for the why. MIT-licensed — see
|
|
10
|
+
> [LICENSE](LICENSE).
|
|
11
|
+
|
|
12
|
+
The design goal: KLineChart gives us candles, ~50+ indicators, drawing tools,
|
|
13
|
+
and sub-panes for free; sonrchart wraps it with the Sonr look, a clean React
|
|
14
|
+
API, and one integration seam — a `DataSource` — so the host app owns where
|
|
15
|
+
bars come from.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- **One React component, one data seam.** `<SonrChart>` renders from a single
|
|
20
|
+
`DataSource` you implement (REST, websocket, anything) — the library never
|
|
21
|
+
knows where bars come from.
|
|
22
|
+
- **Themed out of the box.** Sonr dark/light palette baked in; override any
|
|
23
|
+
KLineChart style.
|
|
24
|
+
- **Candle styles.** Solid, hollow, hollow-up, OHLC/HLC bars, area, and Heikin
|
|
25
|
+
Ashi (a built-in data transform).
|
|
26
|
+
- **Indicators.** All of KLineChart's ~50+ built-ins, placed on the price pane
|
|
27
|
+
or their own sub-pane, with per-pane heights — plus a drop-in `IndicatorMenu`
|
|
28
|
+
to toggle them.
|
|
29
|
+
- **Sonr custom indicators.** Session tinting (pre/after-hours, ET, DST-aware)
|
|
30
|
+
and pattern-context markers.
|
|
31
|
+
- **Trade plan on the chart.** Static `priceLines` (entry/stop/target) and
|
|
32
|
+
**draggable** `orderLines` with an `onMove` callback.
|
|
33
|
+
- **Compare / multi-symbol overlay.** Overlay other symbols, ratio-scaled to
|
|
34
|
+
share a starting price.
|
|
35
|
+
- **Drawing tools.** Curated trend/ray/Fibonacci/channel/note tools with a
|
|
36
|
+
`DrawingToolbar`, magnet-to-OHLC snapping, and **undo/redo**.
|
|
37
|
+
- **Price scale modes.** Normal / percentage / logarithmic, invert, labels
|
|
38
|
+
inside.
|
|
39
|
+
- **Persistence & templates.** Export/import chart state (drawings + indicators)
|
|
40
|
+
and save/apply named indicator sets.
|
|
41
|
+
- **Snapshots.** Capture the chart to a PNG data URL (or trigger a download).
|
|
42
|
+
- **Composable chrome.** `ChartToolbar` bundles intervals + indicators +
|
|
43
|
+
drawing tools + snapshot; add your own buttons via its children slot.
|
|
44
|
+
- **Fast & dependency-light.** The only runtime dependency is KLineChart;
|
|
45
|
+
React is a peer. The per-frame hot paths are tuned (see
|
|
46
|
+
[PERFORMANCE.md](PERFORMANCE.md)).
|
|
47
|
+
|
|
48
|
+
See [`docs/charting-feature-inventory.md`](docs/charting-feature-inventory.md)
|
|
49
|
+
for the full roadmap and what KLineChart provides vs. what this library adds.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install sonrchart klinecharts react react-dom
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { SonrChart, type DataSource } from "sonrchart";
|
|
61
|
+
|
|
62
|
+
// You implement this — REST, websocket, anything. `time` is epoch ms.
|
|
63
|
+
const source: DataSource = {
|
|
64
|
+
async getBars(symbol, interval, count) {
|
|
65
|
+
const r = await fetch(`/bars?symbol=${symbol}&interval=${interval}&limit=${count}`);
|
|
66
|
+
return (await r.json()).bars; // [{ time, open, high, low, close, volume }]
|
|
67
|
+
},
|
|
68
|
+
subscribe(symbol, interval, onBar) {
|
|
69
|
+
const ws = new WebSocket(`/stream?symbol=${symbol}`);
|
|
70
|
+
ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.bar) onBar(m.bar); };
|
|
71
|
+
return () => ws.close();
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export function Chart() {
|
|
76
|
+
return (
|
|
77
|
+
<div style={{ height: 480 }}>
|
|
78
|
+
<SonrChart
|
|
79
|
+
symbol="AAPL"
|
|
80
|
+
interval="1"
|
|
81
|
+
dataSource={source}
|
|
82
|
+
theme="dark"
|
|
83
|
+
indicators={["VOL"]}
|
|
84
|
+
priceLines={[
|
|
85
|
+
{ price: 101.5, color: "#35c98d", label: "Target", style: "dashed" },
|
|
86
|
+
{ price: 100.0, color: "#7d93e8", label: "Entry" },
|
|
87
|
+
{ price: 98.8, color: "#ff6a76", label: "Stop", style: "dashed" },
|
|
88
|
+
]}
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The component fills its parent, so give the parent a height.
|
|
96
|
+
|
|
97
|
+
## Integrating into an app
|
|
98
|
+
|
|
99
|
+
1. **Install** the package and its peers:
|
|
100
|
+
`npm install sonrchart klinecharts react react-dom`.
|
|
101
|
+
2. **Implement a `DataSource`.** This is the whole integration — one object with
|
|
102
|
+
`getBars(symbol, interval, count)` returning `[{ time, open, high, low,
|
|
103
|
+
close, volume }]` (`time` in epoch ms), and an optional `subscribe(symbol,
|
|
104
|
+
interval, onBar)` for live updates that returns an unsubscribe function.
|
|
105
|
+
Point them at your own API/feed.
|
|
106
|
+
3. **Render `<SonrChart>`** inside a sized container (it fills its parent),
|
|
107
|
+
passing `symbol`, `interval`, and your `dataSource`. That's a working chart.
|
|
108
|
+
4. **Turn on features with props** as you need them — `indicators`, `sessions`,
|
|
109
|
+
`patterns`, `priceLines`, `orderLines`, `compareSeries`, `priceScale`,
|
|
110
|
+
`candleStyle`, `theme`. Each is independent and optional.
|
|
111
|
+
5. **Add chrome (optional).** Grab a `ref<SonrChartHandle>` and drop in
|
|
112
|
+
`<ChartToolbar chartRef={ref} … />` (or the individual `IndicatorMenu` /
|
|
113
|
+
`DrawingToolbar`) for interval buttons, indicator toggles, drawing tools, and
|
|
114
|
+
a snapshot button. Add your own controls via the toolbar's children slot.
|
|
115
|
+
6. **Drive it imperatively (optional)** through the same `ref` —
|
|
116
|
+
`startDrawing`, `undo`/`redo`, `snapshot`, `exportState`/`importState`,
|
|
117
|
+
`applyIndicatorSet`, etc. (full list under [Props](#props)).
|
|
118
|
+
7. **Style it.** The chart uses your `theme`; all toolbar/button classes are
|
|
119
|
+
`sonr-*-btn`, so your app's CSS controls the chrome.
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { useRef } from "react";
|
|
123
|
+
import { SonrChart, ChartToolbar, type SonrChartHandle, type DataSource } from "sonrchart";
|
|
124
|
+
|
|
125
|
+
function TradingChart({ source }: { source: DataSource }) {
|
|
126
|
+
const ref = useRef<SonrChartHandle>(null);
|
|
127
|
+
return (
|
|
128
|
+
<div style={{ display: "flex", flexDirection: "column", height: 520 }}>
|
|
129
|
+
<ChartToolbar chartRef={ref} intervals={["1", "5", "60", "D"]} interval="1" onIntervalChange={/* ... */ () => {}} />
|
|
130
|
+
<div style={{ flex: 1, minHeight: 0 }}>
|
|
131
|
+
<SonrChart ref={ref} symbol="AAPL" interval="1" dataSource={source} theme="dark" sessions indicators={["VOL"]} />
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
> **Bundler note:** sonrchart ships ESM + CJS with types, and bundles KLineChart
|
|
139
|
+
> in. It's browser-only (canvas), so if you server-render, load it client-side.
|
|
140
|
+
|
|
141
|
+
## Props
|
|
142
|
+
|
|
143
|
+
| Prop | Type | Notes |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `symbol` | `string` | Ticker to chart. |
|
|
146
|
+
| `interval` | `Interval` | `"10S" \| "30S" \| "1" \| "5" \| "15" \| "30" \| "60" \| "D"` (or any string your DataSource understands). Default `"1"`. |
|
|
147
|
+
| `dataSource` | `DataSource` | `getBars(symbol, interval, count)` + optional `subscribe(...)`. The only integration seam. |
|
|
148
|
+
| `theme` | `"dark" \| "light"` | Default `"dark"` (the Sonr console palette). |
|
|
149
|
+
| `styles` | `DeepPartial<Styles>` | KLineChart style overrides merged over the theme. |
|
|
150
|
+
| `indicators` | `(string \| IndicatorSpec)[]` | Indicators to show — a name (`"VOL"`) or a spec (`{ name, pane: "main" \| "new", calcParams }`). Default `["VOL"]`. |
|
|
151
|
+
| `paneHeights` | `Record<string, number>` | Sub-pane pixel heights, keyed by indicator name. |
|
|
152
|
+
| `sessions` | `boolean \| { premarket?, afterhours? }` | Tint pre/after-hours session backgrounds (ET). |
|
|
153
|
+
| `patterns` | `PatternMarker[]` | Setup markers (`{ time, kind, label }`) drawn on the price pane. |
|
|
154
|
+
| `priceLines` | `PriceLine[]` | Static horizontal reference lines (entry / stop / target). |
|
|
155
|
+
| `orderLines` | `OrderLine[]` | Draggable order/position lines; `onMove(price, phase)` fires as the user drags. |
|
|
156
|
+
| `priceScale` | `{ type?, invert?, inside? }` | Scale mode — `"normal" \| "percentage" \| "logarithm"`, plus invert / labels-inside. |
|
|
157
|
+
| `magnet` | `boolean \| "weak" \| "strong"` | Snap drawing points to nearby OHLC. |
|
|
158
|
+
| `candleStyle` | `"candle" \| "hollow" \| "hollow_up" \| "ohlc" \| "area" \| "heikin_ashi"` | Candle rendering style (Heikin Ashi transforms the data). |
|
|
159
|
+
| `compareSeries` | `{ symbol, color? }[]` | Overlay other symbols on the main pane, ratio-scaled to share a start price. |
|
|
160
|
+
| `barCount` | `number` | Bars requested on load. Default 500. |
|
|
161
|
+
| `onReady` | `(chart) => void` | The raw KLineChart instance, for advanced use. |
|
|
162
|
+
| `onSymbolError` | `(symbol) => void` | Fired when `dataSource.resolveSymbol` returns `null`. |
|
|
163
|
+
|
|
164
|
+
A `ref` exposes the full control surface:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
handle.addIndicator(spec); handle.removeIndicator(name); handle.listIndicators();
|
|
168
|
+
handle.getIndicatorSet(); handle.applyIndicatorSet(specs); // study templates
|
|
169
|
+
handle.startDrawing(tool); handle.clearDrawings(); // drawing tools
|
|
170
|
+
handle.undo(); handle.redo(); handle.canUndo(); handle.canRedo(); // drawing history
|
|
171
|
+
handle.snapshot({ type: "png" }); // -> data URL
|
|
172
|
+
handle.exportState(); handle.importState(state); // save / load
|
|
173
|
+
handle.chart(); // raw KLineChart
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Custom header controls: drop `ToolbarButton`s (or any element) into
|
|
177
|
+
`<ChartToolbar>`'s children slot; they inherit the `sonr-*-btn` classes so the
|
|
178
|
+
host styles all chrome through its own CSS.
|
|
179
|
+
|
|
180
|
+
### Chrome components
|
|
181
|
+
|
|
182
|
+
Drop-in, ref-driven UI you can use as-is or replace:
|
|
183
|
+
|
|
184
|
+
- **`<IndicatorMenu chartRef={ref} />`** — toggle indicators.
|
|
185
|
+
- **`<DrawingToolbar chartRef={ref} />`** — the curated drawing tools + Clear.
|
|
186
|
+
- **`<ChartToolbar chartRef={ref} intervals=… />`** — composes the interval
|
|
187
|
+
buttons, indicator menu, drawing tools, and a Snapshot button into one bar.
|
|
188
|
+
- **`downloadSnapshot(handle, "chart.png")`** — save the chart as an image.
|
|
189
|
+
|
|
190
|
+
### Symbol resolution
|
|
191
|
+
|
|
192
|
+
Add `resolveSymbol(symbol)` to your `DataSource` to validate/enrich a ticker
|
|
193
|
+
before it loads — return a `SymbolInfo` (name, price/volume precision) or `null`
|
|
194
|
+
for an unknown symbol (the chart then fires `onSymbolError` and skips loading).
|
|
195
|
+
|
|
196
|
+
### Persistence
|
|
197
|
+
|
|
198
|
+
`exportState()` returns a plain-JSON `ChartState` (user drawings + indicators +
|
|
199
|
+
scale) for the host to store; `importState(state)` restores the drawings and
|
|
200
|
+
indicators.
|
|
201
|
+
|
|
202
|
+
### Indicator config UI
|
|
203
|
+
|
|
204
|
+
`IndicatorMenu` is a drop-in control that drives a chart's `ref` — toggling a
|
|
205
|
+
button adds/removes that indicator live:
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
import { SonrChart, IndicatorMenu, type SonrChartHandle } from "sonrchart";
|
|
209
|
+
const ref = useRef<SonrChartHandle>(null);
|
|
210
|
+
// ...
|
|
211
|
+
<IndicatorMenu chartRef={ref} />
|
|
212
|
+
<SonrChart ref={ref} symbol="AAPL" dataSource={source} />
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Sonr custom indicators
|
|
216
|
+
|
|
217
|
+
`sessions` and `patterns` are Sonr's own custom KLineChart indicators
|
|
218
|
+
(registered automatically). Session tinting shades pre-market (04:00–09:30 ET)
|
|
219
|
+
and after-hours (16:00–20:00 ET); pattern markers draw a labeled triangle at
|
|
220
|
+
each setup's bar (green up for longs, red down for shorts).
|
|
221
|
+
|
|
222
|
+
## Develop
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
npm install
|
|
226
|
+
npm run dev # live demo at http://localhost:5300 (synthetic data, no backend)
|
|
227
|
+
npm run build # tsup -> dist/ (ESM + CJS + d.ts)
|
|
228
|
+
npm run typecheck
|
|
229
|
+
npm test # vitest run
|
|
230
|
+
npm run coverage # vitest run --coverage
|
|
231
|
+
npm run check # typecheck + test + build (what CI runs)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The demo (`demo/`) drives the chart from a synthetic random-walk source, so it
|
|
235
|
+
runs with no backend — the same shape a real host adapter implements.
|
|
236
|
+
|
|
237
|
+
## Testing
|
|
238
|
+
|
|
239
|
+
Unit + integration tests run under [Vitest](https://vitest.dev) (jsdom) and
|
|
240
|
+
gate every push/PR via GitHub Actions (`.github/workflows/ci.yml`):
|
|
241
|
+
|
|
242
|
+
- **Pure logic** (`test/internal.test.ts`, `test/session.test.ts`) — interval→
|
|
243
|
+
period mapping, bar conversion, style merge, and the ET session classifier
|
|
244
|
+
including its DST behavior.
|
|
245
|
+
- **Feature wiring** (`test/SonrChart.test.tsx`) — KLineChart is mocked so each
|
|
246
|
+
feature is asserted at the integration boundary: data-loader plumbing, the
|
|
247
|
+
indicator API + specs (#4), session-indicator create/override/remove (#10),
|
|
248
|
+
pattern-indicator lifecycle (#5), and static vs. draggable overlays with the
|
|
249
|
+
`onMove` drag callback (#19).
|
|
250
|
+
- **Custom-indicator drawing** (`test/indicatorsDraw.test.ts`) — the canvas
|
|
251
|
+
`draw` callbacks run against a fake 2D context: session bands fill pre/after-
|
|
252
|
+
hours only, pattern markers draw a triangle + label colored by direction.
|
|
253
|
+
- **Config UI** (`test/IndicatorMenu.test.tsx`) and the demo data source.
|
|
254
|
+
|
|
255
|
+
Coverage sits near 98% of `src/`. The one path not unit-tested is real
|
|
256
|
+
pixel rendering, which the demo harness (`npm run dev`) exercises end to end.
|
|
257
|
+
|
|
258
|
+
## Performance
|
|
259
|
+
|
|
260
|
+
The features that run in the render hot path (session/pattern tinting) are
|
|
261
|
+
tuned so they cost near-zero per frame — no added dependencies. The ET session
|
|
262
|
+
classifier alone is **~80× faster** than a naive `Intl`-per-bar approach and
|
|
263
|
+
stays DST-correct. See [PERFORMANCE.md](PERFORMANCE.md); reproduce with
|
|
264
|
+
`npm run bench`.
|
|
265
|
+
|
|
266
|
+
## License
|
|
267
|
+
|
|
268
|
+
[MIT](LICENSE) © Rioa, LLC
|