openalgo-charts 1.3.0 → 1.4.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/README.md +53 -22
- package/dist/draw/index.d.ts +198 -1
- package/dist/index.d.ts +652 -24
- package/dist/openalgo-charts.draw.mjs +1 -1
- package/dist/openalgo-charts.draw.mjs.map +1 -1
- package/dist/openalgo-charts.mjs +1 -1
- package/dist/openalgo-charts.mjs.map +1 -1
- package/dist/openalgo-charts.standalone.js +1 -1
- package/dist/openalgo-charts.standalone.js.map +1 -1
- package/dist/profile/index.d.ts +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
**A from-scratch, dependency-free HTML5-canvas charting engine for OpenAlgo.**
|
|
6
6
|
|
|
7
|
-
Professional interactive charts, indicators, drawing tools, order flow, market replay, and on-chart trading. Six lazy-loaded tiers, zero runtime dependencies, ~
|
|
7
|
+
Professional interactive charts, indicators, drawing tools, order flow, market replay, linked chart grids, and on-chart trading. Six lazy-loaded tiers, zero runtime dependencies, ~53 KB Brotli for the base engine.
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/openalgo-charts)
|
|
10
10
|
[](./LICENSE)
|
|
11
|
-
[](#size-budget)
|
|
12
|
+
[](#develop)
|
|
13
13
|
[](#principles)
|
|
14
14
|
|
|
15
15
|
[**Documentation**](https://marketcalls.github.io/openalgo-charts/) · [**Live examples**](https://marketcalls.github.io/openalgo-charts/examples) · [**Getting started**](./docs/getting-started.md) · [**Architecture**](./ARCHITECTURE.md)
|
|
@@ -60,19 +60,19 @@ Import only what you use. Each tier is a separate bundle that registers into the
|
|
|
60
60
|
|
|
61
61
|
| Import | Contents | Brotli |
|
|
62
62
|
|---|---|---|
|
|
63
|
-
| `openalgo-charts` | Engine, 13 chart types, panes & scales, primitives, registries, chart state, trading overlay, OpenAlgo feeds |
|
|
63
|
+
| `openalgo-charts` | Engine, 13 chart types, panes & scales, primitives, registries, chart state, chart linking, bar cache, interval registry, trading overlay, OpenAlgo feeds | 52.9 KB |
|
|
64
64
|
| `openalgo-charts/indicators` | 91 built-in indicators + the Tier-2 (external-data) contract | 24.9 KB |
|
|
65
|
-
| `openalgo-charts/draw` | 43 drawing tools + a headless drawing controller |
|
|
65
|
+
| `openalgo-charts/draw` | 43 drawing tools + a headless drawing controller and clipboard | 13.1 KB |
|
|
66
66
|
| `openalgo-charts/transform` | Heikin Ashi, Renko, Range bars, Line Break, Point & Figure, Kagi | 2.7 KB |
|
|
67
67
|
| `openalgo-charts/profile` | Volume Profile, Market Profile (TPO), Footprint, order flow | 10.7 KB |
|
|
68
68
|
| `openalgo-charts/trade` | Order / position / bracket tools + DOM ladder | 6.6 KB |
|
|
69
69
|
|
|
70
|
-
Everything together is **
|
|
70
|
+
Everything together is **110.8 KB Brotli**. Figures are the measured `size-limit` output; the trade tier is a delta over the base, which is why base + trade (59.5 KB) is less than their listed sum.
|
|
71
71
|
|
|
72
72
|
## What's built
|
|
73
73
|
|
|
74
74
|
### Chart types & transforms
|
|
75
|
-
Candles, hollow and volume candles, OHLC bars, high-low, line, line+markers, step, area, HLC-area, baseline, columns, histogram
|
|
75
|
+
Candles, hollow and volume candles, OHLC bars, high-low, line, line+markers, step, area, HLC-area, baseline, columns, histogram, plus Heikin Ashi, Renko, Range bars, Line Break, **Point & Figure** (fixed / percent / ATR box sizing, high-low or close construction), and Kagi.
|
|
76
76
|
|
|
77
77
|
### Indicators
|
|
78
78
|
|
|
@@ -86,7 +86,7 @@ macd.setSettings({ 'macd:width': 2, 'macd:lineStyle': 'dashed' });
|
|
|
86
86
|
|
|
87
87
|
91 built-ins across Trend, Momentum, Volatility and Volume, from the everyday (SMA, EMA, WMA, VWAP, Bollinger Bands, RSI, MACD, Stochastic, ADX/DMI, ATR) through Supertrend, HalfTrend, Ichimoku, Keltner, Donchian, Chandelier Exit and CPR with floor pivots to Connors RSI, Fisher Transform, Woodies CCI, Klinger, Vortex, WaveTrend Pro, Chop Zone and Williams Fractals. Twenty-five of them draw shaded bands, five emit named buy/sell markers, and Seasonality draws a monthly return heatmap as a table over the chart. The full catalogue with ids and defaults is in the docs.
|
|
88
88
|
|
|
89
|
-
The chart owns the whole lifecycle
|
|
89
|
+
The chart owns the whole lifecycle: series, pane placement, reference levels, fixed ranges (RSI 0..100), recompute on data change, teardown. Every plot gets colour, opacity, thickness, and line style for free, generated from the descriptor. Write your own with `registerIndicator`, or use the **Tier-2 contract** for indicators whose data isn't derived from OHLCV (open interest, CVD, any external feed).
|
|
90
90
|
|
|
91
91
|
### Drawing tools
|
|
92
92
|
|
|
@@ -99,7 +99,9 @@ draw.setTool('trend-line'); // the next two clicks place it
|
|
|
99
99
|
|
|
100
100
|
43 tools. Lines: trend line, ray, extended line, arrow, horizontal line/ray, vertical line, cross line. Shapes: rectangle, rotated rectangle, ellipse, circle, triangle. Paths: path, polyline, arc, curve, double curve. Channels: parallel channel, fib channel. Fibonacci: retracement, extension, time zone, speed fan. Gann: fan, box. Cycles: cyclic lines, time cycles, sine line. Forecasting: long/short position (1:1 from one click, with risk/reward and risk-based sizing), forecast. Measurers: price range, date range, measure. Arrows: mark up, mark down. Text and notes: text, price label, callout, flag mark. Brushes: brush, highlighter (freehand).
|
|
101
101
|
|
|
102
|
-
Headless by design
|
|
102
|
+
Headless by design: no toolbar, no dialogs. Placement with live preview, selection, whole-shape and per-anchor dragging, magnet snap to O/H/L/C, undo/redo (a drag is one step), and persistence. Anchors are `{ time, price }`, never pixels, so they survive zoom and resolve inside collapsed session gaps and past the last bar.
|
|
103
|
+
|
|
104
|
+
`draw.copy()`, `draw.cut()` and `draw.paste()` move drawings through the OS clipboard, including between two charts on the page. The payload is JSON under one namespaced key, so foreign text pastes nothing instead of throwing at your Ctrl+V handler, and every field is validated before it reaches the model. A refused clipboard permission does not lose the copy: every write also lands in a shared in-memory clipboard, and a cut deletes only after the write succeeds. A paste is one undo step of fresh objects, nudged two bars and 16 px so it is visibly a second shape. The key bindings stay yours; the engine installs no listeners.
|
|
103
105
|
|
|
104
106
|
### Panes, scales & legends
|
|
105
107
|
Draggable pane dividers, move / maximize / remove, and pane legends showing one reading per plot in that plot's own colour, with inline show-hide / settings / move / delete controls revealed on hover. The status line is switchable field by field (logo, title, market status, OHLC, bar change, volume, last day change, last value) over a host-supplied data source.
|
|
@@ -152,6 +154,22 @@ const bn = addComparison(chart, { symbol: 'BANKNIFTY', bars });
|
|
|
152
154
|
|
|
153
155
|
The comparison rides the pane's hidden overlay scale in its own real prices, the pane rebases to percentage (or indexed-to-100), and the overlay's range is mirrored from the primary's, so equal percentage moves land on equal pixels instead of each line filling the pane. Alignment is by timestamp: a comparison print with no primary bar is dropped, a primary bar with no print becomes a gap.
|
|
154
156
|
|
|
157
|
+
### Linked chart grids
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { createLinkGroup } from 'openalgo-charts';
|
|
161
|
+
|
|
162
|
+
const group = createLinkGroup({ crosshair: true, viewport: true, symbol: false });
|
|
163
|
+
group.add(daily);
|
|
164
|
+
group.add(hourly, { symbol: 'RELIANCE', onSymbol: (s, c) => loadBars(s, c) });
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Hover one chart and the same **instant** is marked on the others; pan or zoom one and the others move to the same wall-clock window. Each channel switches on its own, because mirroring the cursor across four timeframes while keeping each zoom is a different thing from slaving every chart's instrument.
|
|
168
|
+
|
|
169
|
+
Nothing crosses a chart boundary as a logical index. The x axis is a gapless index over each chart's own bars, so index 300 is a different instant on every chart: every value is converted index to time on the sender and time back to index on the receiver, against that chart's own data. A daily chart and an hourly chart with different history depth therefore stay on the same instant, which the naive index copy gets right only when both charts hold the same bars. An instant outside a follower's first or last bar is an absence, not a gap, so it draws nothing; inside its range with no bar there it snaps to the nearest bar in time, or draws nothing under `whenMissing: 'hide'`. The linked crosshair is a vertical line only, at reduced opacity: a mirrored horizontal line would assert a price that belongs to another instrument.
|
|
170
|
+
|
|
171
|
+
Headless like the rest, and the engine has no instrument concept, so symbol sync is a partnership: the host emits `'symbol'` on the chart's bus (or calls `group.setSymbol`) and supplies the per-member `onSymbol` that loads the bars. A member with no `onSymbol` broadcasts but never follows, which is how you pin one chart of a grid.
|
|
172
|
+
|
|
155
173
|
### Settings & context menu
|
|
156
174
|
`chartSettingsSchema(chart)` describes a full settings dialog as tabs of controls, in the same descriptor vocabulary the indicator settings form already uses; `readChartSettings` and `applyChartSettings` are its round trip over flat, JSON-safe keys. Five tabs (Price, Readout, Axes, Appearance, Trading), and a bullish/bearish pair is **one** `colorPair` row carrying its switch and both swatches instead of two stacked rows. Grid, crosshair, scale text, plot margins, status-line fields, the chart timezone, trading colours and the primary series' own style are all real options behind it, so no control in the schema is inert.
|
|
157
175
|
|
|
@@ -163,6 +181,19 @@ Order, position, and bracket lines with live P&L, one-click and drag-to-modi
|
|
|
163
181
|
### Profiles & order flow
|
|
164
182
|
Volume Profile, Market Profile (TPO), Footprint, and cumulative delta.
|
|
165
183
|
|
|
184
|
+
### Warm-load cache & interval registry
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import { withBarCache, registerInterval } from 'openalgo-charts';
|
|
188
|
+
|
|
189
|
+
const feed = withBarCache(new OpenAlgoDataFeed(cfg), { ttlMs: 60_000 });
|
|
190
|
+
registerInterval({ code: '1MO', bucketing: { mode: 'calendar', unit: 'month' } });
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`withBarCache` wraps **any** `DataFeed`, so a custom feed warms up too. One entry per `symbol|exchange|interval` holds the widest range fetched so far and a narrower request is sliced out of it, because keying on the range would miss on every pan. **The forming bar is never stored**: a frozen snapshot of a live candle reaching the last-price line, the header LTP and every indicator computed off that close is worse than no cache at all, so coverage ends at the last closed bar and a hit is short by the one bar a live subscription re-supplies. Freshness is two gates, a TTL and "nothing new can have closed", the second measured on the feed's own bar grid rather than UTC midnight. Bounded LRU on entries and on total bars, in-memory by default with an injectable store if you want localStorage or IndexedDB, and `noCache`, `invalidate()`, `clear()` and `stats()` for the rest.
|
|
194
|
+
|
|
195
|
+
An interval code resolves through a registry whose entry is a **bucketing rule, not a duration**: fixed seconds, a calendar month/quarter/year that opens at local midnight in a named zone, N ticks, or N traded quantity. That is the vocabulary the tick aggregator already used, widened by one case. An unrecognised code now throws `UnknownIntervalError` instead of quietly meaning 60 seconds, so a subscription fails at subscribe time rather than drawing minute bars under someone else's label; `tryResolveInterval` and `isKnownInterval` are the non-throwing probes for validating a picker.
|
|
196
|
+
|
|
166
197
|
### State
|
|
167
198
|
`chart.getState()` / `chart.restoreState()` capture the viewport, grid, panes, price scales, indicator instances, drawings, and the whole settings block (canvas, status line, trading colours, event filters) as one JSON payload: saved layouts and templates with no extra storage plumbing.
|
|
168
199
|
|
|
@@ -171,17 +202,17 @@ OpenAlgo REST history + WebSocket ticks with auto-reconnect and resubscribe, liv
|
|
|
171
202
|
|
|
172
203
|
## Size budget
|
|
173
204
|
|
|
174
|
-
Enforced in CI by [`size-limit`](./.size-limit.json)
|
|
205
|
+
Enforced in CI by [`size-limit`](./.size-limit.json). Nothing is excluded, because there are no runtime dependencies to exclude.
|
|
175
206
|
|
|
176
207
|
| Bundle | Limit | Actual |
|
|
177
208
|
|---|---|---|
|
|
178
|
-
| Base engine | 55 KB |
|
|
179
|
-
| Base + trade | 62 KB |
|
|
209
|
+
| Base engine | 55 KB | 52.88 KB |
|
|
210
|
+
| Base + trade | 62 KB | 59.46 KB |
|
|
180
211
|
| Indicators tier | 27 KB | 24.88 KB |
|
|
181
|
-
| Draw tier | 14 KB | 11
|
|
212
|
+
| Draw tier | 14 KB | 13.11 KB |
|
|
182
213
|
| Transform tier | 5 KB | 2.66 KB |
|
|
183
214
|
| Profile tier | 11 KB | 10.66 KB |
|
|
184
|
-
| **Everything** | **120 KB** | **
|
|
215
|
+
| **Everything** | **120 KB** | **110.76 KB** |
|
|
185
216
|
|
|
186
217
|
## Documentation
|
|
187
218
|
|
|
@@ -204,7 +235,7 @@ Teach your AI coding assistant this library:
|
|
|
204
235
|
npx skills add https://github.com/marketcalls/openalgo-charts
|
|
205
236
|
```
|
|
206
237
|
|
|
207
|
-
Installs six skills from [`.github/skills/`](./.github/skills) - a reference hub with
|
|
238
|
+
Installs six skills from [`.github/skills/`](./.github/skills) - a reference hub with 21 deep-dive files covering the whole API surface and its foot-guns, plus task skills for scaffolding a chart, adding indicators, building a terminal, writing a plugin, and debugging. Works with Claude Code, Cursor, Codex, Copilot, Gemini CLI and the rest of the `skills` CLI's supported agents.
|
|
208
239
|
|
|
209
240
|
## Examples
|
|
210
241
|
|
|
@@ -221,7 +252,7 @@ cd examples/yfinance && pip install -r requirements.txt && python server.py
|
|
|
221
252
|
```bash
|
|
222
253
|
npm install # install dev toolchain
|
|
223
254
|
npm run typecheck # strict TypeScript check
|
|
224
|
-
npm test # unit tests (vitest) -
|
|
255
|
+
npm test # unit tests (vitest) - 1719 across 91 files
|
|
225
256
|
npm run build # Rollup -> dist/ (minified ESM per tier + types)
|
|
226
257
|
npm run size # size-limit (Brotli) against the budget
|
|
227
258
|
npm run e2e # Playwright Chromium smoke tests
|
|
@@ -230,19 +261,19 @@ npm run verify # typecheck + test + build + size
|
|
|
230
261
|
|
|
231
262
|
## Principles
|
|
232
263
|
|
|
233
|
-
- **Single canvas pipeline** (no SVG, no DOM-per-bar)
|
|
234
|
-
- **Gapless time axis by default
|
|
235
|
-
- **Registries, not switches
|
|
236
|
-
- **Zero runtime dependencies
|
|
264
|
+
- **Single canvas pipeline** (no SVG, no DOM-per-bar): small and fast.
|
|
265
|
+
- **Gapless time axis by default**: weekends, holidays, and session breaks collapse.
|
|
266
|
+
- **Registries, not switches**: chart types, indicators, and drawing tools are all descriptors. Adding one is a registration, never a core change.
|
|
267
|
+
- **Zero runtime dependencies**: nothing is excluded from the size budget.
|
|
237
268
|
- **Apache-2.0**, original code.
|
|
238
269
|
|
|
239
270
|
## Status & limitations
|
|
240
271
|
|
|
241
|
-
Version **1.
|
|
272
|
+
Version **1.4.0**. All engine build phases are implemented with 1727 unit tests across 91 files.
|
|
242
273
|
|
|
243
274
|
Known gaps, stated plainly:
|
|
244
275
|
|
|
245
|
-
- **Footprint and order flow need trade-by-trade data classified bid/ask.** OpenAlgo does not store this by default, so it is live-session-only unless you add a tick recorder
|
|
276
|
+
- **Footprint and order flow need trade-by-trade data classified bid/ask.** OpenAlgo does not store this by default, so it is live-session-only unless you add a tick recorder: `FootprintAggregator` is the live path. See [`ARCHITECTURE.md`](./ARCHITECTURE.md) §6A.
|
|
246
277
|
- **Only `Footprint` is theme-aware among the profile primitives.** `VolumeProfile`, `MarketProfile` and `HorizontalProfile` never read `rc.theme`; their defaults are dark-tuned, so a light theme needs explicit colours. `HorizontalProfile` also hardcodes its POC / value-area line colours and has no `setOptions`.
|
|
247
278
|
- The OpenAlgo **WS/trade adapter wire schemas** ship with injectable transports and offline tests, but the exact field names should be verified against your running OpenAlgo build.
|
|
248
279
|
- **A pane has exactly one hidden overlay scale**, so every symbol comparison on a pane shares one baseline. That is right for a single comparison, the common case, but a second one on the same pane is quoted against the first instrument's price; put further instruments on their own pane with `paneIndex` until the overlay scales are keyed.
|
package/dist/draw/index.d.ts
CHANGED
|
@@ -254,6 +254,115 @@ declare class DrawingLayer implements IPrimitive {
|
|
|
254
254
|
hitTest(x: number, y: number, rc: PrimitiveRenderContext): PrimitiveHit | null;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Clipboard transfer for drawings: the layer between the drawing model and the
|
|
259
|
+
* OS clipboard.
|
|
260
|
+
*
|
|
261
|
+
* Two things make this more than `JSON.stringify`:
|
|
262
|
+
*
|
|
263
|
+
* 1. **The OS clipboard is shared with everything else on the machine.** A
|
|
264
|
+
* paste can arrive from a spreadsheet, another charting product, or a
|
|
265
|
+
* hand-edited copy of our own payload. So the payload is written under a
|
|
266
|
+
* namespaced key and everything read back is validated field by field before
|
|
267
|
+
* it can reach the model. Foreign text is *not ours* and is ignored, never an
|
|
268
|
+
* exception the host has to catch on every Ctrl+V.
|
|
269
|
+
* 2. **Clipboard access is async and permission-gated.** `navigator.clipboard`
|
|
270
|
+
* rejects when the document is not focused, the page is not secure, or the
|
|
271
|
+
* user denied the permission. Copy must still work inside the tab, so every
|
|
272
|
+
* write also lands in a module-level in-memory clipboard which is shared by
|
|
273
|
+
* every controller in the page. That is what makes chart-to-chart paste work
|
|
274
|
+
* with the permission denied, and it is why the memory store is a singleton
|
|
275
|
+
* rather than per-controller state.
|
|
276
|
+
*/
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Top-level key of the JSON payload. Namespaced so a paste of arbitrary text,
|
|
280
|
+
* or of JSON belonging to some other application, is recognisable as not ours
|
|
281
|
+
* by looking at one property.
|
|
282
|
+
*/
|
|
283
|
+
declare const DRAWING_CLIPBOARD_KEY = "openalgo-charts/drawings";
|
|
284
|
+
/** Payload format version. Bumped only when the on-clipboard shape changes. */
|
|
285
|
+
declare const DRAWING_CLIPBOARD_VERSION = 1;
|
|
286
|
+
/** The async slice of `navigator.clipboard` this module uses. */
|
|
287
|
+
interface ClipboardPort {
|
|
288
|
+
writeText(text: string): Promise<void>;
|
|
289
|
+
readText(): Promise<string>;
|
|
290
|
+
}
|
|
291
|
+
/** Test seam: drop whatever the in-memory clipboard is holding. */
|
|
292
|
+
declare function clearMemoryClipboard(): void;
|
|
293
|
+
/** `navigator.clipboard` when the browser exposes it, else null. */
|
|
294
|
+
declare function systemClipboard(): ClipboardPort | null;
|
|
295
|
+
/** Serialise drawings into the namespaced payload written to the clipboard. */
|
|
296
|
+
declare function encodeClipboardPayload(drawings: readonly Drawing[]): string;
|
|
297
|
+
/**
|
|
298
|
+
* Validate one entry into a drawing with no id. Returns null for anything that
|
|
299
|
+
* cannot be rendered: an unknown tool would throw inside the controller, and a
|
|
300
|
+
* NaN anchor produces a drawing that can never be drawn or hit-tested again.
|
|
301
|
+
*/
|
|
302
|
+
declare function sanitizeDrawing(value: unknown): Omit<Drawing, 'id'> | null;
|
|
303
|
+
/**
|
|
304
|
+
* Parse clipboard text into drawings, or null when the text is not ours or is
|
|
305
|
+
* not usable. All-or-nothing on purpose: a payload with one corrupt entry is a
|
|
306
|
+
* corrupt payload, and pasting the other nine silently would be worse than
|
|
307
|
+
* pasting none.
|
|
308
|
+
*/
|
|
309
|
+
declare function decodeClipboardPayload(text: unknown): Omit<Drawing, 'id'>[] | null;
|
|
310
|
+
interface DrawingClipboardOptions {
|
|
311
|
+
/** Where to read and write. Defaults to `navigator.clipboard` when present. */
|
|
312
|
+
port?: ClipboardPort | null;
|
|
313
|
+
/**
|
|
314
|
+
* Keep a copy in the shared in-memory clipboard so copy and paste keep
|
|
315
|
+
* working when the OS clipboard is unavailable or the permission is refused.
|
|
316
|
+
* Default true. Set false to make a failed system write a failed copy, which
|
|
317
|
+
* a host that must not silently lose cross-tab transfer may prefer.
|
|
318
|
+
*/
|
|
319
|
+
fallbackToMemory?: boolean;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* The clipboard as the controller sees it: drawings in, drawings out, no
|
|
323
|
+
* exceptions escaping. Every failure mode (no clipboard API, refused
|
|
324
|
+
* permission, foreign text, corrupt payload) reduces to `false` or `null`.
|
|
325
|
+
*/
|
|
326
|
+
declare class DrawingClipboard {
|
|
327
|
+
private _port;
|
|
328
|
+
private _fallback;
|
|
329
|
+
/** Why the last operation did not reach the OS clipboard, if it did not. */
|
|
330
|
+
private _lastError;
|
|
331
|
+
constructor(options?: DrawingClipboardOptions);
|
|
332
|
+
/** Swap the port at runtime (a host granting permission later, or a test). */
|
|
333
|
+
setPort(port: ClipboardPort | null): void;
|
|
334
|
+
/**
|
|
335
|
+
* Turn the in-memory backstop off or on. With it off, a refused system write
|
|
336
|
+
* is a failed copy, which is what a host wants when a cut that cannot reach
|
|
337
|
+
* the OS clipboard must not delete the drawing.
|
|
338
|
+
*/
|
|
339
|
+
setFallbackToMemory(enabled: boolean): void;
|
|
340
|
+
/**
|
|
341
|
+
* Why the OS clipboard was not used by the last call, or null when it was.
|
|
342
|
+
* Set on a successful copy too, when the payload reached memory but not the
|
|
343
|
+
* system clipboard: that copy works in this tab and will not appear in
|
|
344
|
+
* another, which is exactly what a host wants to be able to tell the user.
|
|
345
|
+
*/
|
|
346
|
+
lastError(): string | null;
|
|
347
|
+
/**
|
|
348
|
+
* Write drawings out. Resolves true when the payload is retrievable by a
|
|
349
|
+
* later `read()` (through the system clipboard, or through memory when the
|
|
350
|
+
* fallback is on), false when it is not, so a caller doing a cut knows
|
|
351
|
+
* whether it is safe to delete the original.
|
|
352
|
+
*/
|
|
353
|
+
write(drawings: readonly Drawing[]): Promise<boolean>;
|
|
354
|
+
/**
|
|
355
|
+
* Read drawings back, or null when there is nothing of ours to paste. The
|
|
356
|
+
* system clipboard wins when it holds our payload, so a copy made in another
|
|
357
|
+
* tab beats a stale in-tab one; memory is consulted when the read fails or
|
|
358
|
+
* returns something that is not ours. That last case can paste an older copy
|
|
359
|
+
* after the user has copied unrelated text elsewhere, which is the deliberate
|
|
360
|
+
* trade: losing the copy whenever a browser refuses the read half of the
|
|
361
|
+
* permission would be the worse surprise.
|
|
362
|
+
*/
|
|
363
|
+
read(): Promise<Omit<Drawing, 'id'>[] | null>;
|
|
364
|
+
}
|
|
365
|
+
|
|
257
366
|
/**
|
|
258
367
|
* Drawing controller — the interaction and persistence layer over
|
|
259
368
|
* `DrawingLayer`. It is **headless**: no DOM, no toolbar. A host sets the
|
|
@@ -289,6 +398,21 @@ interface DrawingChartHost {
|
|
|
289
398
|
drawingState(): unknown;
|
|
290
399
|
setDrawingState(state: unknown): void;
|
|
291
400
|
setPlacementMode?(active: boolean): void;
|
|
401
|
+
/**
|
|
402
|
+
* Optional, and used only to offset a paste by a fixed screen distance. Going
|
|
403
|
+
* through pixels rather than adding a price delta keeps the offset the same
|
|
404
|
+
* visible nudge on a log scale as on a linear one. A host without them still
|
|
405
|
+
* gets the time half of the offset.
|
|
406
|
+
*/
|
|
407
|
+
priceToCoordinate?(price: number, paneIndex?: number): number | null;
|
|
408
|
+
coordinateToPrice?(y: number, paneIndex?: number): number | null;
|
|
409
|
+
/**
|
|
410
|
+
* Optional, and used only to keep a paste from a chart with more panes than
|
|
411
|
+
* this one landing on a pane the user cannot see. Adding a primitive creates
|
|
412
|
+
* the pane it names, so without this a drawing copied out of an indicator
|
|
413
|
+
* pane would conjure an empty pane in a single-pane chart.
|
|
414
|
+
*/
|
|
415
|
+
panes?(): readonly unknown[];
|
|
292
416
|
}
|
|
293
417
|
interface DrawingControllerOptions {
|
|
294
418
|
/**
|
|
@@ -302,10 +426,34 @@ interface DrawingControllerOptions {
|
|
|
302
426
|
stayInDrawingMode?: boolean;
|
|
303
427
|
/** Undo depth. Default 50. */
|
|
304
428
|
historyLimit?: number;
|
|
429
|
+
/**
|
|
430
|
+
* Where copy and paste move text. Defaults to `navigator.clipboard`; pass a
|
|
431
|
+
* port to route through a host's own transfer, or `null` to stay in the
|
|
432
|
+
* process-local clipboard entirely.
|
|
433
|
+
*/
|
|
434
|
+
clipboard?: ClipboardPort | null;
|
|
435
|
+
/**
|
|
436
|
+
* Whether a refused or failing clipboard write still lands in the in-process
|
|
437
|
+
* clipboard. Defaults to true, which is what makes copy and paste work between
|
|
438
|
+
* two charts on a page where the browser has denied clipboard permission.
|
|
439
|
+
*
|
|
440
|
+
* Pass false for a host that would rather a failed copy be a failed copy: with
|
|
441
|
+
* it off, `cut` leaves the drawing alone when the write does not land, so a
|
|
442
|
+
* shape is never destroyed for a transfer that did not happen.
|
|
443
|
+
*/
|
|
444
|
+
clipboardFallbackToMemory?: boolean;
|
|
445
|
+
/**
|
|
446
|
+
* How far a pasted copy lands from its original, in bars along time and in
|
|
447
|
+
* screen pixels down the price axis. A paste that lands exactly on top of the
|
|
448
|
+
* original reads as nothing having happened. Defaults: 2 bars, 16 px.
|
|
449
|
+
*/
|
|
450
|
+
pasteOffsetBars?: number;
|
|
451
|
+
pasteOffsetPixels?: number;
|
|
305
452
|
}
|
|
306
453
|
declare class DrawingController {
|
|
307
454
|
private readonly _chart;
|
|
308
455
|
private _opts;
|
|
456
|
+
private readonly _clipboard;
|
|
309
457
|
private readonly _layers;
|
|
310
458
|
private _drawings;
|
|
311
459
|
private _tool;
|
|
@@ -330,6 +478,8 @@ declare class DrawingController {
|
|
|
330
478
|
private _setPlacementMode;
|
|
331
479
|
activeTool(): string | null;
|
|
332
480
|
setOptions(patch: DrawingControllerOptions): void;
|
|
481
|
+
/** The clipboard behind copy / cut / paste, for a host reporting failures. */
|
|
482
|
+
clipboard(): DrawingClipboard;
|
|
333
483
|
/** Every drawing, in creation order. */
|
|
334
484
|
drawings(): readonly Drawing[];
|
|
335
485
|
get(id: string): Drawing | undefined;
|
|
@@ -337,11 +487,52 @@ declare class DrawingController {
|
|
|
337
487
|
add(drawing: Omit<Drawing, 'id'> & {
|
|
338
488
|
id?: string;
|
|
339
489
|
}): Drawing;
|
|
490
|
+
/**
|
|
491
|
+
* Append one drawing without touching history or the layers. Split out so a
|
|
492
|
+
* paste of several drawings is a single undo step rather than one per shape.
|
|
493
|
+
*/
|
|
494
|
+
private _insert;
|
|
340
495
|
update(id: string, patch: Partial<Pick<Drawing, 'points' | 'style' | 'locked' | 'visible'>>): boolean;
|
|
341
496
|
remove(id: string): boolean;
|
|
342
497
|
clear(): void;
|
|
343
498
|
select(id: string | null): void;
|
|
344
499
|
selected(): string | null;
|
|
500
|
+
/**
|
|
501
|
+
* Put drawings on the clipboard. Defaults to the selection; pass an id or a
|
|
502
|
+
* list of ids to copy something else. Resolves false when there was nothing
|
|
503
|
+
* to copy, or when the payload could not be stored anywhere.
|
|
504
|
+
*/
|
|
505
|
+
copy(target?: string | string[] | null): Promise<boolean>;
|
|
506
|
+
/**
|
|
507
|
+
* Copy, then delete. The delete happens **only** after the clipboard write
|
|
508
|
+
* resolves successfully, so a refused write leaves the model exactly as it
|
|
509
|
+
* was rather than destroying a drawing that went nowhere.
|
|
510
|
+
*/
|
|
511
|
+
cut(target?: string | string[] | null): Promise<boolean>;
|
|
512
|
+
/**
|
|
513
|
+
* Paste whatever is on the clipboard into this chart, offset from the
|
|
514
|
+
* original so the copy is visibly a second object. Each pasted drawing is a
|
|
515
|
+
* fresh object with a fresh id, never a second reference to the one copied,
|
|
516
|
+
* so editing the paste cannot alter its source (or the clipboard).
|
|
517
|
+
*
|
|
518
|
+
* Anything that is not our payload (foreign text, a truncated or hand-edited
|
|
519
|
+
* copy, a newer format) pastes nothing and resolves to an empty array: a
|
|
520
|
+
* paste shortcut must not throw at the host because the user last copied a
|
|
521
|
+
* spreadsheet cell.
|
|
522
|
+
*/
|
|
523
|
+
paste(): Promise<Drawing[]>;
|
|
524
|
+
/** Resolve a copy/cut target to live drawings; defaults to the selection. */
|
|
525
|
+
private _targets;
|
|
526
|
+
/** Fold a pane index from another chart onto a pane this one actually has. */
|
|
527
|
+
private _clampPane;
|
|
528
|
+
/** Nudge every anchor so a pasted copy is not hidden under its original. */
|
|
529
|
+
private _offsetPoints;
|
|
530
|
+
/**
|
|
531
|
+
* Move a price down the screen by `px`. Done per anchor rather than as one
|
|
532
|
+
* price delta so the paste is a rigid *screen* translation, which is what the
|
|
533
|
+
* eye expects and what keeps a shape's proportions on a log scale.
|
|
534
|
+
*/
|
|
535
|
+
private _offsetPrice;
|
|
345
536
|
undo(): boolean;
|
|
346
537
|
redo(): boolean;
|
|
347
538
|
canUndo(): boolean;
|
|
@@ -356,6 +547,12 @@ declare class DrawingController {
|
|
|
356
547
|
* build a 1:1 box off one click). Identity for tools without the hook.
|
|
357
548
|
*/
|
|
358
549
|
private _expand;
|
|
550
|
+
/**
|
|
551
|
+
* Bar spacing in seconds, read from the last gap in the data. A one-bar chart
|
|
552
|
+
* has no gap to read, so fall back to a minute rather than answering zero and
|
|
553
|
+
* producing zero-width defaults and invisible paste offsets.
|
|
554
|
+
*/
|
|
555
|
+
private _barSeconds;
|
|
359
556
|
private _isFreehand;
|
|
360
557
|
/**
|
|
361
558
|
* Append one sample to the stroke in progress. Points arriving closer than a
|
|
@@ -426,4 +623,4 @@ declare function extendSegment(a: ScreenPoint, b: ScreenPoint, width: number, le
|
|
|
426
623
|
|
|
427
624
|
declare const DRAW_TIER: "draw";
|
|
428
625
|
|
|
429
|
-
export { ARROW, BUILTIN_DRAWING_TOOLS, CROSS_LINE, DRAW_TIER, type DrawContext, type Drawing, DrawingController, type DrawingControllerOptions, DrawingLayer, type DrawingPoint, type DrawingStyle, type DrawingTool, ELLIPSE, EXTENDED_LINE, FIB_EXTENSION, FIB_RETRACEMENT, HORIZONTAL_LINE, HORIZONTAL_RAY, type HitContext, LONG_POSITION, MEASURE, PARALLEL_CHANNEL, PATH, RAY, RECTANGLE, SHORT_POSITION, type ScreenPoint, type ShortcutEvent, TEXT, TREND_LINE, VERTICAL_LINE, distToEllipse, distToLine, distToPolyline, distToRect, distToSegment, drawingShortcuts, extendSegment, getDrawingTool, hasDrawingTool, matchDrawingShortcut, rectOf, registerBuiltinDrawingTools, registerDrawingTool, registeredDrawingTools };
|
|
626
|
+
export { ARROW, BUILTIN_DRAWING_TOOLS, CROSS_LINE, type ClipboardPort, DRAWING_CLIPBOARD_KEY, DRAWING_CLIPBOARD_VERSION, DRAW_TIER, type DrawContext, type Drawing, DrawingClipboard, type DrawingClipboardOptions, DrawingController, type DrawingControllerOptions, DrawingLayer, type DrawingPoint, type DrawingStyle, type DrawingTool, ELLIPSE, EXTENDED_LINE, FIB_EXTENSION, FIB_RETRACEMENT, HORIZONTAL_LINE, HORIZONTAL_RAY, type HitContext, LONG_POSITION, MEASURE, PARALLEL_CHANNEL, PATH, RAY, RECTANGLE, SHORT_POSITION, type ScreenPoint, type ShortcutEvent, TEXT, TREND_LINE, VERTICAL_LINE, clearMemoryClipboard, decodeClipboardPayload, distToEllipse, distToLine, distToPolyline, distToRect, distToSegment, drawingShortcuts, encodeClipboardPayload, extendSegment, getDrawingTool, hasDrawingTool, matchDrawingShortcut, rectOf, registerBuiltinDrawingTools, registerDrawingTool, registeredDrawingTools, sanitizeDrawing, systemClipboard };
|