@livo-build/charts 0.2.1 → 0.2.3
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/README.md +209 -20
- package/dist/core/chart.d.ts +105 -4
- package/dist/core/chart.js +482 -39
- package/dist/core/feed.js +27 -6
- package/dist/core/format.d.ts +13 -1
- package/dist/core/format.js +38 -3
- package/dist/core/indicators.d.ts +50 -0
- package/dist/core/indicators.js +181 -0
- package/dist/core/ohlc.d.ts +10 -0
- package/dist/core/ohlc.js +30 -0
- package/dist/core/polymarket.d.ts +44 -0
- package/dist/core/polymarket.js +92 -0
- package/dist/core/renderer.d.ts +96 -1
- package/dist/core/renderer.js +534 -64
- package/dist/core/signals.d.ts +63 -0
- package/dist/core/signals.js +234 -0
- package/dist/core/theme.d.ts +5 -1
- package/dist/core/theme.js +33 -12
- package/dist/core/types.d.ts +102 -3
- package/dist/index.d.ts +13 -8
- package/dist/index.js +8 -6
- package/dist/react/HyperliquidChart.d.ts +30 -2
- package/dist/react/HyperliquidChart.js +47 -17
- package/dist/react/PolymarketChart.d.ts +40 -0
- package/dist/react/PolymarketChart.js +95 -0
- package/dist/react/PriceChart.d.ts +54 -4
- package/dist/react/PriceChart.js +66 -27
- package/dist/react/SignalsChart.d.ts +37 -0
- package/dist/react/SignalsChart.js +95 -0
- package/dist/react/ui.d.ts +24 -0
- package/dist/react/ui.js +75 -0
- package/dist/react.d.ts +4 -0
- package/dist/react.js +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
# @livo-build/charts (livo-charts)
|
|
2
2
|
|
|
3
|
+
> **Live demos + docs: [charts.livo.build](https://charts.livo.build)** — interactive
|
|
4
|
+
> examples with copy-paste code, plus agent-friendly [llms.txt](https://charts.livo.build/llms.txt)
|
|
5
|
+
> / [llms-full.txt](https://charts.livo.build/llms-full.txt).
|
|
6
|
+
|
|
3
7
|
A lightweight, **dependency-free** canvas charting library. The core renders to a
|
|
4
8
|
`<canvas>` with zero runtime dependencies; a thin, self-styled React wrapper is
|
|
5
9
|
shipped under a separate entry so non-React consumers never pull React into their
|
|
6
10
|
bundle.
|
|
7
11
|
|
|
8
|
-
It ships a **TradingView-style trading chart**: candlesticks
|
|
9
|
-
**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
It ships a **TradingView-style trading chart**: candlesticks, a line, a two-tone
|
|
13
|
+
**baseline** area **or Heikin-Ashi** candles, a **volume panel**, a **volume-by-price
|
|
14
|
+
profile**, a **crosshair with price + time axis labels**, an **OHLCV legend**, independent
|
|
15
|
+
**X and Y zoom** (+ pan, plus touch/pinch), and a toolbar for **intervals, USD/ETH,
|
|
16
|
+
price/market-cap, log scale and fullscreen**. The architecture (a framework-agnostic `Chart`
|
|
12
17
|
controller + a pure `draw()` pass) is built to keep growing — indicators, overlays,
|
|
13
18
|
multiple panes — without touching consumers.
|
|
14
19
|
|
|
@@ -28,8 +33,7 @@ import { PriceChart } from "@livo-build/charts/react";
|
|
|
28
33
|
<PriceChart
|
|
29
34
|
swaps={trades} // [{ t: unixSeconds, p: priceUsd, v: usdVolume }, …]
|
|
30
35
|
ethUsd={1711.81} // enables the USD↔ETH toggle
|
|
31
|
-
|
|
32
|
-
decimals={18}
|
|
36
|
+
supply={1_000_000_000} // enables the Price↔MCap toggle (or tokenAddress to read it)
|
|
33
37
|
symbol="PEPE"
|
|
34
38
|
quote="WETH"
|
|
35
39
|
height={420}
|
|
@@ -37,14 +41,27 @@ import { PriceChart } from "@livo-build/charts/react";
|
|
|
37
41
|
```
|
|
38
42
|
|
|
39
43
|
`swaps` are aggregated into OHLC+volume candles client-side. The toolbar
|
|
40
|
-
(`1s 1m 5m 15m 1h 4h 1d`, candle/line, Price/MCap, USD/ETH, flip, fullscreen) and the
|
|
44
|
+
(`1s 1m 5m 15m 1h 4h 1d`, candle/line, Price/MCap, USD/ETH, flip, log, fullscreen) and the
|
|
41
45
|
OHLCV legend are built in. The canvas is long-lived, so zoom/pan survive prop updates
|
|
42
46
|
(streaming trades won't reset the user's view). The wrapper is styled inline — no CSS
|
|
43
47
|
framework required.
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
`PriceChart` only aggregates the `swaps` you give it — it doesn't fetch more, so panning
|
|
50
|
+
stops at the oldest trade. For **infinite back-history**, pass `onLoadOlder` (fires when the
|
|
51
|
+
user pans/zooms within ~one viewport of the start) and prepend older trades to `swaps`; the
|
|
52
|
+
right-anchored view keeps the position stable. Scope the toolbar to intervals your data
|
|
53
|
+
covers with `intervals={[["1m", 60], ["5m", 300]]}` (a thin series bucketed at `1h`
|
|
54
|
+
collapses into a couple of candles), and react to interval changes with `onIntervalChange`.
|
|
55
|
+
For a turnkey lazy-loading chart, use a feed (`HyperliquidChart` / `connectFeed`) instead.
|
|
56
|
+
|
|
57
|
+
The default **visible span** is `initialBars × interval`. To open on, say, the last 7 days at
|
|
58
|
+
1h candles, give it a week of data and `defaultInterval={3600}` + `options={{ initialBars: 168 }}`
|
|
59
|
+
(168 × 1h = 7 days). The user can still zoom/scroll out from there.
|
|
60
|
+
|
|
61
|
+
Market cap = `price × supply`. Pass `supply` directly, or pass `tokenAddress` (+`decimals`)
|
|
62
|
+
to read `totalSupply()` once via `rpcUrl` (default `https://cloudflare-eth.com`, cached).
|
|
63
|
+
The **Price/MCap toggle only appears when one of those is set** — otherwise it's hidden
|
|
64
|
+
(no dead button), and it falls back to price if the RPC read fails.
|
|
48
65
|
|
|
49
66
|
### Live Hyperliquid chart
|
|
50
67
|
|
|
@@ -68,15 +85,151 @@ import { HyperliquidChart } from "@livo-build/charts/react";
|
|
|
68
85
|
history loads lazily as the user scrolls left (infinite back-scroll) — both with no
|
|
69
86
|
API key. Zoom/pan survive every update.
|
|
70
87
|
|
|
88
|
+
### Live Polymarket & Signal Radar charts
|
|
89
|
+
|
|
90
|
+
Two more turnkey, key-free live charts wrap the framework-agnostic `Chart`:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { PolymarketChart, SignalsChart } from "@livo-build/charts/react";
|
|
94
|
+
|
|
95
|
+
// A prediction-market outcome (probabilities → a % y-axis), from the public CLOB.
|
|
96
|
+
<PolymarketChart tokenId={yesTokenId} bucketSeconds={3600} label="Yes" />
|
|
97
|
+
|
|
98
|
+
// A token tracked by the Livo signals engine ("Signal Radar") — real indexed history.
|
|
99
|
+
<SignalsChart token="PEPE" bucketSeconds={300} indicators={[{ type: "ema", period: 21 }]} />
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Both are `ChartFeed`s under the hood — drive the core directly with `connectFeed`:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { Chart, connectFeed, polymarketFeed, signalsFeed } from "@livo-build/charts";
|
|
106
|
+
|
|
107
|
+
connectFeed(new Chart(el), polymarketFeed({ tokenId, bucketSeconds: 3600 }), { interval: 3600 });
|
|
108
|
+
connectFeed(new Chart(el2), signalsFeed({ token: "PEPE", bucketSeconds: 300 }), { interval: 300 });
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- **`polymarketFeed`** — bucketed OHLC from the public Polymarket CLOB `prices-history`
|
|
112
|
+
endpoint, polled for a building candle. Prices are probabilities in [0, 1] (no volume).
|
|
113
|
+
`fetchPolymarketPriceHistory` is exported for custom fetching.
|
|
114
|
+
- **`signalsFeed`** — real OHLC from the Signal Radar **index**: it queries `/graphql`
|
|
115
|
+
(`allSwaps` for the pool) and buckets the swaps into candles at any interval, paging older
|
|
116
|
+
swaps in as you scroll — so 1m / 5m candles go days deep (the index has no TTL), and 1h/4h/1d
|
|
117
|
+
just zoom out in time. The live candle is polled from `/data` (current price). If `/graphql`
|
|
118
|
+
is unreachable (e.g. cross-origin CORS) or you pass `history: "spark"`, it falls back to the
|
|
119
|
+
snapshot's `spark`. `fetchSignalsSwaps` / `fetchSignalsMarket` / `sparkCandles` are exported.
|
|
120
|
+
(Cross-origin use needs `/graphql` CORS-enabled on the engine; same-origin always works.)
|
|
121
|
+
|
|
71
122
|
### Indicators
|
|
72
123
|
|
|
73
124
|
`PriceChart` and `HyperliquidChart` accept `indicators` — overlays drawn on the price
|
|
74
|
-
pane. Each is `{ type: "sma" | "ema" | "wma" | "vwap", period, color?, source? }`
|
|
75
|
-
(`source` defaults to `close`; `vwap` is cumulative
|
|
125
|
+
pane. Each is `{ type: "sma" | "ema" | "wma" | "vwap" | "bollinger", period, color?, source?, mult? }`
|
|
126
|
+
(`source` defaults to `close`; `vwap` is cumulative; `bollinger` draws a 3-line band at
|
|
127
|
+
`mult` std-devs, default 2).
|
|
76
128
|
Colors fall back to a built-in palette by position. The pure
|
|
77
129
|
`sma`/`ema`/`wma`/`vwap`/`bollingerBands`/`computeIndicator` helpers are exported from
|
|
78
130
|
the core (e.g. feed `bollingerBands(values).{upper,mid,lower}` as three overlays).
|
|
79
131
|
|
|
132
|
+
**Oscillators (RSI / MACD / Stochastic / ATR)** render in their own **stacked sub-panes**
|
|
133
|
+
below the volume panel — pass `oscillators`:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
<PriceChart
|
|
137
|
+
swaps={trades}
|
|
138
|
+
indicators={[{ type: "ema", period: 21 }]} // on the price pane
|
|
139
|
+
oscillators={[
|
|
140
|
+
{ type: "rsi", period: 14 }, // 0–100 band with 70/30 rails
|
|
141
|
+
{ type: "macd", fast: 12, slow: 26, signal: 9 }, // line + signal + histogram
|
|
142
|
+
{ type: "stoch", period: 14, dPeriod: 3, smooth: 3 }, // %K/%D, 80/20 rails
|
|
143
|
+
{ type: "atr", period: 14 }, // auto-scaled volatility (price units)
|
|
144
|
+
]}
|
|
145
|
+
/>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Each pane defaults to 84px (`height` to override). The pure `rsi(values, period)`,
|
|
149
|
+
`macd(values, fast, slow, signal) → { macd, signal, hist }`,
|
|
150
|
+
`stochastic(candles, kPeriod, dPeriod, smooth) → { k, d }` and `atr(candles, period)`
|
|
151
|
+
helpers are exported too.
|
|
152
|
+
|
|
153
|
+
### Chart types: candle · line · baseline · Heikin-Ashi
|
|
154
|
+
|
|
155
|
+
`chartType` (low-level `chart.setChartType(...)`, or the toolbar's ▮▯ / ╱ / ⎓ / HA buttons)
|
|
156
|
+
switches the price series:
|
|
157
|
+
|
|
158
|
+
- **`candle`** — classic OHLC candlesticks.
|
|
159
|
+
- **`line`** — a close line with a soft gradient area fill.
|
|
160
|
+
- **`baseline`** — a two-tone area around a reference price (green above, red below). Set the
|
|
161
|
+
reference with `baselinePrice` (defaults to the first visible close); the core exposes
|
|
162
|
+
`chart.setBaseline(price)`.
|
|
163
|
+
- **`heikin`** — Heikin-Ashi candles: smoothed OHLC that filter noise and make trends easier
|
|
164
|
+
to read. Indicators/oscillators still read the **raw** closes. The pure `heikinAshi(candles)`
|
|
165
|
+
transform is exported.
|
|
166
|
+
|
|
167
|
+
### Volume profile (volume-by-price)
|
|
168
|
+
|
|
169
|
+
A horizontal histogram of volume per price level, drawn over the price pane (peaked at the
|
|
170
|
+
**Point of Control**). Pass `volumeProfile` (or toggle **VP** in the toolbar); the core has
|
|
171
|
+
`chart.setVolumeProfile(...)`:
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
<PriceChart swaps={trades} volumeProfile={{ buckets: 24, width: 0.16 }} />
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
It's computed over the **visible** window, so it tracks zoom/pan. The pure
|
|
178
|
+
`volumeProfile(candles, buckets) → { buckets, maxVol, poc }` helper is exported.
|
|
179
|
+
|
|
180
|
+
### Fitting the container & axis styling
|
|
181
|
+
|
|
182
|
+
By default `PriceChart` / `HyperliquidChart` **fit the container** (`fitContent`) — candles
|
|
183
|
+
spread across the full plot width instead of clustering at a capped slot. Set
|
|
184
|
+
`fitContent={false}` for the tight, right-anchored look (the low-level `Chart` defaults to
|
|
185
|
+
tight; the React wrappers default to fill).
|
|
186
|
+
|
|
187
|
+
The ticks are easy to style — every axis knob is a prop (and a `chart.setAxis({…})` call):
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
<PriceChart
|
|
191
|
+
swaps={trades}
|
|
192
|
+
priceFormat={(v) => `$${v.toLocaleString()}`} // y-axis labels
|
|
193
|
+
timeFormat={(t) => new Date(t * 1000).toLocaleTimeString()}
|
|
194
|
+
priceTicks={6} // horizontal gridlines (default 5)
|
|
195
|
+
timeTicks={8} // x-axis labels (default 7)
|
|
196
|
+
axisFont="11px Inter, sans-serif" // label font (color = theme.axis)
|
|
197
|
+
/>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The **default** price formatter is range-aware: it keeps just enough decimals to tell
|
|
201
|
+
adjacent ticks apart, so a narrow high-value axis (e.g. WETH 1.71K–1.75K) renders
|
|
202
|
+
`1.7350K / 1.7430K` instead of duplicate `1.73K`s. Tick text color comes from `theme.axis`.
|
|
203
|
+
|
|
204
|
+
### Log scale & themes
|
|
205
|
+
|
|
206
|
+
The price axis can be **logarithmic** — equal vertical distance = equal % move, the
|
|
207
|
+
right default for assets that span orders of magnitude. `PriceChart`/`HyperliquidChart`
|
|
208
|
+
expose a **Log** toolbar toggle (or pass `options={{ logScale: true }}`); the core has
|
|
209
|
+
`chart.setLogScale(true)`. Log mode auto-falls back to linear if any visible low is ≤ 0.
|
|
210
|
+
|
|
211
|
+
Two built-in themes ship as `DEFAULT_THEME` (dark) and `LIGHT_THEME`, also addressable
|
|
212
|
+
via `THEME_PRESETS.dark` / `THEME_PRESETS.light`. Pass either (or a partial override) as
|
|
213
|
+
`theme`, or call `chart.setTheme(LIGHT_THEME)`.
|
|
214
|
+
|
|
215
|
+
### Drawing tools (trendlines & horizontal lines)
|
|
216
|
+
|
|
217
|
+
The toolbar arms a one-shot draw: **↗** trendline (drag), **—** horizontal line (click),
|
|
218
|
+
**fib** Fibonacci retracement (drag — levels 0/23.6/38.2/50/61.8/78.6/100% between the two
|
|
219
|
+
prices), and **▭** rectangle (drag). Drawings are anchored in **data space**, so they stay
|
|
220
|
+
pinned to their price/time across pan, zoom and live updates. In the default cursor mode,
|
|
221
|
+
click a drawing to select it, drag to move it, and **double-click it to delete it**; **⌫**
|
|
222
|
+
clears all.
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
<PriceChart swaps={trades} onDrawingsChange={(d) => save(d)} drawings={restored} />
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Drive it from the core too: `chart.setDrawMode("trendline" | "hline" | "none")`,
|
|
229
|
+
`getDrawings()` / `setDrawings()` / `removeDrawing(id)` / `clearDrawings()`, and the
|
|
230
|
+
`onDrawingsChange` callback. Pass `hideDrawingTools` to drop the buttons. The
|
|
231
|
+
`computeProjection(input)` helper exposes the same pixel↔data mapping for custom tools.
|
|
232
|
+
|
|
80
233
|
### Realtime feeds + lazy history (any data source)
|
|
81
234
|
|
|
82
235
|
Connect a chart to a **feed** — a source that serves paged OHLCV history *and* live
|
|
@@ -105,7 +258,10 @@ const feed = {
|
|
|
105
258
|
```
|
|
106
259
|
|
|
107
260
|
The chart's right-anchored view means prepending history keeps the visible window
|
|
108
|
-
stable
|
|
261
|
+
stable. The controller prefetches the next page once the view's left edge comes within
|
|
262
|
+
~one viewport of the oldest loaded candle (see the exported `needsHistory` helper) — so
|
|
263
|
+
**zooming out keeps deepening the window with real data** instead of stalling at the
|
|
264
|
+
oldest bar. It advances one page per data length and stops when the feed runs dry.
|
|
109
265
|
|
|
110
266
|
## Vanilla / framework-agnostic
|
|
111
267
|
|
|
@@ -132,8 +288,16 @@ chart.destroy();
|
|
|
132
288
|
| `new Chart(container, options?)` | Creates a `<canvas>`, wires interaction, observes resize. |
|
|
133
289
|
| `setCandles(candles)` | Replace the series (each candle has `vol`) and redraw. |
|
|
134
290
|
| `setInterval(seconds)` | Bucket interval — drives time-axis label granularity. |
|
|
135
|
-
| `setChartType("candle" \| "line")` | Switch series style. |
|
|
291
|
+
| `setChartType("candle" \| "line" \| "baseline" \| "heikin")` | Switch series style. |
|
|
292
|
+
| `setShowVolume(on)` | Show / hide the volume panel (the price pane reclaims the space). |
|
|
293
|
+
| `setVolumeProfile(config)` | Show/configure (or hide with `false`) the volume-by-price histogram. |
|
|
294
|
+
| `setBaseline(price?)` | Reference price for the `baseline` type (undefined = first visible close). |
|
|
295
|
+
| `setLogScale(on)` | Toggle the logarithmic price axis (auto-falls back to linear if any low ≤ 0). |
|
|
296
|
+
| `setAxis({ fitContent, priceFormat, timeFormat, priceTicks, timeTicks, axisFont })` | Style the axes — only the provided keys change. |
|
|
136
297
|
| `setIndicators(indicators)` | Set the moving-average overlays and redraw. |
|
|
298
|
+
| `setOscillators(oscillators)` | Set the RSI / MACD sub-panes and redraw. |
|
|
299
|
+
| `setDrawMode(mode)` | Arm a drawing tool (`"trendline"` / `"hline"`) or `"none"`. |
|
|
300
|
+
| `setDrawings` / `getDrawings` / `removeDrawing(id)` / `clearDrawings()` | Manage drawings. |
|
|
137
301
|
| `setNeedHistory(cb)` | Callback fired when the view nears the start of loaded data (lazy history). |
|
|
138
302
|
| `setHeight(px)` / `setTheme(partial)` | Resize / merge theme overrides. |
|
|
139
303
|
| `resetView()` | Reset zoom/pan (same as double-click). |
|
|
@@ -142,30 +306,55 @@ chart.destroy();
|
|
|
142
306
|
`ChartOptions`: `height`, `theme`, `initialBars` (120), `minBars` (20),
|
|
143
307
|
`maxBarWidth` (18 — caps candle spacing so sparse series stay tight, right-anchored),
|
|
144
308
|
`volumeRatio` (0.18), `rightPad`/`bottomPad`/`topPad`, `onCrosshair`, `indicators`,
|
|
145
|
-
|
|
309
|
+
`oscillators`, `drawings`, `showVolume` (true), `volumeProfile`, `baselinePrice`,
|
|
310
|
+
`logScale` (false), `fitContent` (false),
|
|
311
|
+
`maxBarWidth` (18) / `maxBodyWidth` (40), `priceFormat`/`timeFormat`, `priceTicks` (5) /
|
|
312
|
+
`timeTicks` (7), `axisFont`, and `onNeedHistory`.
|
|
146
313
|
|
|
147
314
|
### Helpers
|
|
148
315
|
|
|
149
316
|
- `buildOHLC(points, interval, transform?)` — bucket priced trades into OHLC+volume
|
|
150
317
|
candles. `transform` = `{ denom, ethUsd, flip, supply }` (`supply` → market cap).
|
|
151
318
|
- `transformPrice(p, transform?)` — apply the transform to one price.
|
|
152
|
-
- `sma` / `ema` / `wma` / `vwap` / `bollingerBands` / `
|
|
153
|
-
|
|
319
|
+
- `sma` / `ema` / `wma` / `vwap` / `bollingerBands` / `rsi` / `macd` / `stochastic` / `atr`
|
|
320
|
+
/ `volumeProfile` / `heikinAshi` / `computeIndicator` — pure indicator + transform math
|
|
321
|
+
(aligned to input, null until the window fills).
|
|
154
322
|
- `connectFeed(chart, feed, opts)` — wire a `ChartFeed` (paged history + live stream)
|
|
155
323
|
to a chart: latest page, lazy older history, live merge. Returns `{ disconnect }`.
|
|
156
324
|
- `hyperliquidFeed({ coin, interval, testnet })` — a ready `ChartFeed` (REST history +
|
|
157
325
|
WebSocket live). `fetchHyperliquidCandles` / `fetchHlCandleWindow` / `mapHlCandles` /
|
|
158
326
|
`HL_INTERVAL_SECONDS` are exported for custom fetching.
|
|
327
|
+
- `polymarketFeed({ tokenId, bucketSeconds })` — a `ChartFeed` for a Polymarket outcome
|
|
328
|
+
(CLOB price-history + polling). `fetchPolymarketPriceHistory` is exported.
|
|
329
|
+
- `signalsFeed({ token, bucketSeconds })` — a `ChartFeed` for a Signal Radar token: indexed
|
|
330
|
+
`allSwaps` → OHLC (lazy older pages) + a `/data`-polled live candle, spark fallback.
|
|
331
|
+
`fetchSignalsSwaps` / `fetchSignalsMarket` / `sparkCandles` are exported.
|
|
159
332
|
- `draw(input)` — the pure render pass (exported for custom hosts/tests); returns the
|
|
160
333
|
crosshair candle.
|
|
161
|
-
- `DEFAULT_THEME
|
|
334
|
+
- `DEFAULT_THEME` / `LIGHT_THEME` / `THEME_PRESETS`, `formatValue`, `formatAxisValue`
|
|
335
|
+
(range-aware tick labels), `formatVolume`, `formatTime`.
|
|
336
|
+
- `slotWidth(plotW, count, maxBarWidth, fitContent?)` — candle slot-width math (exported
|
|
337
|
+
for custom hosts).
|
|
338
|
+
- `computeProjection(input)` / `priceScale` / `timeScale` / `windowOf` — the pixel↔data
|
|
339
|
+
mapping the renderer and drawing tools share (returns `{ xOfTime, timeOfX, yOfPrice,
|
|
340
|
+
priceOfY, … }`).
|
|
162
341
|
|
|
163
342
|
## Interaction
|
|
164
343
|
|
|
165
344
|
- **Scroll** over the plot → zoom time; over the price axis → zoom price.
|
|
166
|
-
- **
|
|
345
|
+
- **Scroll horizontally** (trackpad swipe / shift+wheel) → pan through time; scrolling
|
|
346
|
+
back into history lazy-loads older candles from the feed.
|
|
347
|
+
- **Drag** the plot → pan; drag the time axis → zoom X; drag the price axis → zoom Y. Panning
|
|
348
|
+
pins the oldest candle to the left edge (no dragging into empty space on the left); on a feed,
|
|
349
|
+
or with `PriceChart`'s `onLoadOlder`, reaching that edge streams in older history instead. You
|
|
350
|
+
*can* scroll past the newest candle into the future — drag it up to halfway across to leave
|
|
351
|
+
right-edge whitespace (so the current price isn't pinned to the edge).
|
|
167
352
|
- **Hover** → crosshair + price/time axis labels + the OHLCV legend.
|
|
168
|
-
- **Double-click** → reset zoom/pan.
|
|
353
|
+
- **Double-click** → reset zoom/pan (or delete a drawing when one is under the cursor).
|
|
354
|
+
- **Touch** → one finger pans (or zooms an axis from its gutter); two-finger pinch
|
|
355
|
+
zooms time (horizontal spread) and price (vertical spread) together.
|
|
356
|
+
- **Drawing tools** → arm trendline/h-line from the toolbar; select + drag to move,
|
|
357
|
+
double-click to delete.
|
|
169
358
|
|
|
170
359
|
## Design notes
|
|
171
360
|
|
package/dist/core/chart.d.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
import type { Candle, ChartOptions, ChartTheme, ChartType, Indicator } from "./types";
|
|
1
|
+
import type { AxisOptions, Candle, ChartOptions, ChartTheme, ChartType, DrawMode, Drawing, Indicator, Oscillator } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Whether a feed should be asked for older candles, given the loaded length and the
|
|
4
|
+
* current view. Returns true when the visible window's left edge is within ~one screen
|
|
5
|
+
* (`max(floor, visibleCount)`) of the start of loaded data — so zooming out / panning
|
|
6
|
+
* left keeps deepening the window (prefetching a viewport of buffer) instead of hitting
|
|
7
|
+
* a wall at the oldest loaded candle and just fattening the bars. Pure + exported so the
|
|
8
|
+
* prefetch trigger is testable without a DOM.
|
|
9
|
+
*/
|
|
10
|
+
export declare function needsHistory(length: number, count: number, offset: number, floor?: number): boolean;
|
|
11
|
+
/** Largest pan offset that still fills the window — pins the OLDEST candle to the left edge
|
|
12
|
+
* so a left-drag can't expose empty space past the start of the data. Pure + exported for
|
|
13
|
+
* testing; the controller clamps every pan/zoom to it. */
|
|
14
|
+
export declare function maxPanOffset(length: number, count: number): number;
|
|
2
15
|
/**
|
|
3
16
|
* Framework-agnostic candlestick/line chart with a volume panel. Creates and owns a
|
|
4
17
|
* `<canvas>` inside the given container and wires the interactions:
|
|
@@ -23,17 +36,42 @@ export declare class Chart {
|
|
|
23
36
|
private readonly ro;
|
|
24
37
|
private readonly pads;
|
|
25
38
|
private readonly minBars;
|
|
26
|
-
private
|
|
39
|
+
private maxBarWidth;
|
|
40
|
+
private maxBodyWidth?;
|
|
27
41
|
private readonly volumeRatio;
|
|
28
42
|
private readonly onCrosshair?;
|
|
29
43
|
private theme;
|
|
30
44
|
private height;
|
|
31
45
|
private candles;
|
|
46
|
+
/** Price-series candles (Heikin-Ashi in "heikin" mode), precomputed on data/type change
|
|
47
|
+
* so the O(n) transform doesn't run every frame. */
|
|
48
|
+
private priceCandles;
|
|
32
49
|
private indicators;
|
|
50
|
+
private oscillators;
|
|
51
|
+
private drawings;
|
|
52
|
+
private drawMode;
|
|
53
|
+
private selectedDrawing;
|
|
54
|
+
/** in-progress trendline (drag from anchor A to B). */
|
|
55
|
+
private draftDrawing;
|
|
56
|
+
/** active move of an existing drawing: its id + the data-space grab anchor + originals. */
|
|
57
|
+
private drawingDrag;
|
|
58
|
+
private onDrawingsChange?;
|
|
59
|
+
private onDrawModeChange?;
|
|
33
60
|
/** Indicator values precomputed on data/indicator change (not per frame). */
|
|
34
61
|
private overlays;
|
|
35
62
|
private interval;
|
|
36
63
|
private type;
|
|
64
|
+
private showVolume;
|
|
65
|
+
private logScale;
|
|
66
|
+
private emptyText?;
|
|
67
|
+
private baselinePrice?;
|
|
68
|
+
private volumeProfile?;
|
|
69
|
+
private fitContent;
|
|
70
|
+
private priceFormat?;
|
|
71
|
+
private timeFormat?;
|
|
72
|
+
private priceTicks?;
|
|
73
|
+
private timeTicks?;
|
|
74
|
+
private axisFont?;
|
|
37
75
|
/** requestAnimationFrame handle coalescing high-frequency redraws (0 = none queued). */
|
|
38
76
|
private raf;
|
|
39
77
|
private onNeedHistory?;
|
|
@@ -44,12 +82,44 @@ export declare class Chart {
|
|
|
44
82
|
private yZoom;
|
|
45
83
|
private hover;
|
|
46
84
|
private drag;
|
|
85
|
+
/** two-finger pinch baseline: finger spreads + view at gesture start. */
|
|
86
|
+
private pinch;
|
|
47
87
|
private lastActive;
|
|
48
88
|
constructor(container: HTMLElement, opts?: ChartOptions);
|
|
49
89
|
setCandles(candles: Candle[]): this;
|
|
50
90
|
setInterval(interval: number): this;
|
|
51
91
|
setChartType(type: ChartType): this;
|
|
92
|
+
/** Show or hide the volume panel (the price pane reclaims the space when hidden). */
|
|
93
|
+
setShowVolume(on: boolean): this;
|
|
94
|
+
/** Toggle the logarithmic price axis (equal pixels = equal % move). */
|
|
95
|
+
setLogScale(on: boolean): this;
|
|
96
|
+
/** Set the empty-state text (e.g. "Loading…" while a feed's first page is in flight; "" hides it). */
|
|
97
|
+
setEmptyText(text: string | undefined): this;
|
|
98
|
+
/** Set the reference price for the `baseline` chart type (undefined = first visible close). */
|
|
99
|
+
setBaseline(price: number | undefined): this;
|
|
100
|
+
/** Show/configure (or hide, with `false`) the volume-by-price histogram. */
|
|
101
|
+
setVolumeProfile(config: ChartOptions["volumeProfile"]): this;
|
|
102
|
+
/** Style the axes: fill-vs-tight candle spacing, custom price/time label formatters,
|
|
103
|
+
* tick counts, and the label font. Only the provided keys change. */
|
|
104
|
+
setAxis(opts: AxisOptions): this;
|
|
52
105
|
setIndicators(indicators: Indicator[]): this;
|
|
106
|
+
/** Set the oscillator sub-panes (RSI / MACD) drawn below the volume panel. */
|
|
107
|
+
setOscillators(oscillators: Oscillator[]): this;
|
|
108
|
+
/** Arm a drawing tool ("trendline" / "hline"), or "none" for pan/zoom + select/move.
|
|
109
|
+
* Drawing tools are one-shot: after one drawing the mode auto-resets to "none". */
|
|
110
|
+
setDrawMode(mode: DrawMode): this;
|
|
111
|
+
/** Replace all drawings (does not fire onDrawingsChange). */
|
|
112
|
+
setDrawings(drawings: Drawing[]): this;
|
|
113
|
+
/** Current drawings (a copy). */
|
|
114
|
+
getDrawings(): Drawing[];
|
|
115
|
+
/** Remove a drawing by id. */
|
|
116
|
+
removeDrawing(id: string): this;
|
|
117
|
+
/** Remove the currently selected drawing, if any. */
|
|
118
|
+
deleteSelected(): this;
|
|
119
|
+
/** Remove all drawings. */
|
|
120
|
+
clearDrawings(): this;
|
|
121
|
+
private emitDrawings;
|
|
122
|
+
private exitDrawMode;
|
|
53
123
|
/** Register a callback fired when the user pans/zooms near the start of loaded data
|
|
54
124
|
* (so a feed can lazily load older candles). See connectFeed. */
|
|
55
125
|
setNeedHistory(cb: (() => void) | undefined): this;
|
|
@@ -60,22 +130,53 @@ export declare class Chart {
|
|
|
60
130
|
private get volH();
|
|
61
131
|
private get plotW();
|
|
62
132
|
private measure;
|
|
133
|
+
/** Largest pan offset that still fills the window — pins the OLDEST candle to the left
|
|
134
|
+
* edge instead of letting a left-drag expose empty space past the start of the data.
|
|
135
|
+
* (When a feed is attached, reaching this edge trips the older-history load, so the
|
|
136
|
+
* data deepens and this max grows; without one, the pan simply stops here.) */
|
|
137
|
+
private maxOffset;
|
|
138
|
+
/** Most-negative offset — how far the view may scroll PAST the newest candle into the
|
|
139
|
+
* future (empty space on the right), so the user can pull the current price toward the
|
|
140
|
+
* centre. Capped at half the visible window. */
|
|
141
|
+
private minOffset;
|
|
63
142
|
private clampView;
|
|
143
|
+
/** Assemble the {@link RenderInput} from current state — shared by render() and projection(). */
|
|
144
|
+
private buildInput;
|
|
145
|
+
/** Pixel↔data projection for the current frame (null when there are no candles). */
|
|
146
|
+
private projection;
|
|
64
147
|
private render;
|
|
65
148
|
/** Coalesce high-frequency redraws (drag/hover/wheel) into one per animation frame. */
|
|
66
149
|
private scheduleRender;
|
|
67
150
|
/** Recompute indicator value-series once when data or config changes (not per frame). */
|
|
68
151
|
private recomputeOverlays;
|
|
69
|
-
/**
|
|
70
|
-
* (
|
|
152
|
+
/** Recompute the price-series candles (Heikin-Ashi transform) on data/type change, so the
|
|
153
|
+
* O(n) pass doesn't run on every redraw or projection lookup. */
|
|
154
|
+
private recomputePriceCandles;
|
|
155
|
+
/** When the view nears the start of loaded data, ask the feed for older candles. Fires
|
|
156
|
+
* at most once per data length, so it advances page-by-page and stops at the end of
|
|
157
|
+
* history. The trigger leads the left edge by ~one viewport (see {@link needsHistory}),
|
|
158
|
+
* so zooming out keeps loading deeper data rather than stalling at the oldest candle. */
|
|
71
159
|
private maybeRequestHistory;
|
|
72
160
|
private region;
|
|
73
161
|
private readonly onWheel;
|
|
162
|
+
/** Pan the time axis by a horizontal pixel delta (wheel/trackpad). Scrolling right
|
|
163
|
+
* (`dx > 0`) moves toward newer candles; scrolling left walks back into history. */
|
|
164
|
+
private panByPixels;
|
|
74
165
|
/** Zoom the time axis by factor `f`, keeping the candle under `cursorX` anchored. */
|
|
75
166
|
private zoomTimeAt;
|
|
167
|
+
/** Topmost drawing under the pointer (within tolerance), or null. */
|
|
168
|
+
private hitTest;
|
|
76
169
|
private readonly onDown;
|
|
77
170
|
private readonly onMove;
|
|
171
|
+
/** Translate the dragged drawing by the pointer's data-space delta from the grab anchor. */
|
|
172
|
+
private applyDrawingDrag;
|
|
173
|
+
/** Apply an in-progress drag (pan or axis-zoom) from the current pointer position.
|
|
174
|
+
* Shared by mouse-move and single-finger touch-move. Returns true if a drag is active. */
|
|
175
|
+
private applyDrag;
|
|
78
176
|
private readonly onUp;
|
|
79
177
|
private readonly onLeave;
|
|
80
178
|
private readonly onDouble;
|
|
179
|
+
private readonly onTouchStart;
|
|
180
|
+
private readonly onTouchMove;
|
|
181
|
+
private readonly onTouchEnd;
|
|
81
182
|
}
|