@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 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 **or** a line series, a
9
- **volume panel**, a **crosshair with price + time axis labels**, an **OHLCV legend**,
10
- independent **X and Y zoom** (+ pan), and a toolbar for **intervals, USD/ETH,
11
- price/market-cap and fullscreen**. The architecture (a framework-agnostic `Chart`
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
- tokenAddress="0x…" // enables the Price↔MCap toggle (reads totalSupply())
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
- Market cap = `price × totalSupply()`, fetched once via `rpcUrl` (default
46
- `https://cloudflare-eth.com`) and cached; it silently falls back to price if the RPC
47
- is unavailable.
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 and ignores period/source).
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, and the controller only asks for more when you scroll near the start.
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
- and `onNeedHistory`.
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` / `computeIndicator` pure
153
- indicator math (aligned to input, null until the window fills).
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`, `formatValue`, `formatVolume`, `formatTime`.
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
- - **Drag** the plot → pan; drag the time axis → zoom X; drag the price axis → zoom Y.
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
 
@@ -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 readonly maxBarWidth;
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
- /** When the view nears the start of loaded data, ask the feed for older candles
70
- * (once per data length, so it stops at the end of history). */
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
  }