openalgo-charts 1.0.10 → 1.0.12
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 +192 -192
- package/dist/draw/index.d.ts +4 -2
- package/dist/index.d.ts +5 -2
- 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 +4 -1
- package/dist/trade/index.d.ts +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,192 +1,192 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
# OpenAlgo Charts
|
|
4
|
-
|
|
5
|
-
**A from-scratch, dependency-free HTML5-canvas charting engine for OpenAlgo.**
|
|
6
|
-
|
|
7
|
-
Professional interactive charts, indicators, drawing tools, order flow, and on-chart trading — six lazy-loaded tiers, zero runtime dependencies, ~33 KB Brotli for the base engine.
|
|
8
|
-
|
|
9
|
-
[](https://www.npmjs.com/package/openalgo-charts)
|
|
10
|
-
[](./LICENSE)
|
|
11
|
-
[](#size-budget)
|
|
12
|
-
[](#develop)
|
|
13
|
-
[](#principles)
|
|
14
|
-
|
|
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)
|
|
16
|
-
|
|
17
|
-
<img src="docs/architecture-diagram.png" alt="OpenAlgo Charts - layered architecture from public API down to feeds and data, with loadable bundle tiers" width="920" />
|
|
18
|
-
|
|
19
|
-
</div>
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Live OpenAlgo trading terminal
|
|
24
|
-
|
|
25
|
-
Right-click the chart to place market / limit / stop orders, drag the order and TP/SL bracket lines to modify, and watch live P&L on the position line - all on real OpenAlgo history + WebSocket tick data, with an analyzer (sandbox) mode so nothing goes live until you arm it.
|
|
26
|
-
|
|
27
|
-
<p align="center">
|
|
28
|
-
<img src="docs/trading.png" alt="OpenAlgo Charts live trading terminal: RELIANCE 5m candles with order lines, a right-click order menu, a long position with live P&L, and volume" width="920" />
|
|
29
|
-
</p>
|
|
30
|
-
|
|
31
|
-
## Examples gallery
|
|
32
|
-
|
|
33
|
-
Every chart in the [live gallery](https://marketcalls.github.io/openalgo-charts/examples) is the real library running in your browser - switch tabs, hover the crosshair, drag the order lines, place a drawing. What you see is the code that ran.
|
|
34
|
-
|
|
35
|
-
<p align="center">
|
|
36
|
-
<img src="docs/demo1.png" alt="Chart-type switcher, custom themes, data tooltips, and event markers" width="49%" />
|
|
37
|
-
<img src="docs/demo2.png" alt="Range switcher, legend, series compare, and indicators and markers" width="49%" />
|
|
38
|
-
</p>
|
|
39
|
-
<p align="center">
|
|
40
|
-
<img src="docs/demo3.png" alt="More live OpenAlgo Charts examples" width="49%" />
|
|
41
|
-
<img src="docs/demo4.png" alt="More live OpenAlgo Charts examples" width="49%" />
|
|
42
|
-
</p>
|
|
43
|
-
|
|
44
|
-
## Install
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
npm install openalgo-charts
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
```ts
|
|
51
|
-
import { createChart, generateBars } from 'openalgo-charts';
|
|
52
|
-
|
|
53
|
-
const chart = createChart(document.getElementById('chart'));
|
|
54
|
-
chart.addSeries('candlestick').setData(generateBars(1700000000, 200, 3600));
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Tiers
|
|
58
|
-
|
|
59
|
-
Import only what you use. Each tier is a separate bundle that registers into the base engine's registries, so the cost of a feature you don't load is zero.
|
|
60
|
-
|
|
61
|
-
| Import | Contents | Brotli |
|
|
62
|
-
|---|---|---|
|
|
63
|
-
| `openalgo-charts` | Engine, 13 chart types, panes & scales, primitives, registries, chart state, trading overlay, OpenAlgo feeds |
|
|
64
|
-
| `openalgo-charts/indicators` | 18 built-in indicators + the Tier-2 (external-data) contract | 4.5 KB |
|
|
65
|
-
| `openalgo-charts/draw` |
|
|
66
|
-
| `openalgo-charts/transform` | Heikin Ashi, Renko, Range bars, Line Break, Point & Figure, Kagi | 2.7 KB |
|
|
67
|
-
| `openalgo-charts/profile` | Volume Profile, Market Profile (TPO), Footprint, order flow |
|
|
68
|
-
| `openalgo-charts/trade` | Order / position / bracket tools + DOM ladder | 6.6 KB |
|
|
69
|
-
|
|
70
|
-
Everything together is **
|
|
71
|
-
|
|
72
|
-
## What's built
|
|
73
|
-
|
|
74
|
-
### Chart types & transforms
|
|
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
|
-
|
|
77
|
-
### Indicators
|
|
78
|
-
|
|
79
|
-
```ts
|
|
80
|
-
import 'openalgo-charts/indicators';
|
|
81
|
-
|
|
82
|
-
chart.addIndicator('bollinger'); // overlays the price pane
|
|
83
|
-
const macd = chart.addIndicator('macd', { fastPeriod: 8 }); // gets its own pane
|
|
84
|
-
macd.setSettings({ 'macd:width': 2, 'macd:lineStyle': 'dashed' });
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
18 built-ins: SMA, EMA, WMA, VWAP, Bollinger Bands, Supertrend, Parabolic SAR, Ichimoku Cloud, RSI, MACD, Stochastic, ADX/DMI, CCI, MFI, ATR, Volume, OBV, A/D.
|
|
88
|
-
|
|
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
|
-
|
|
91
|
-
### Drawing tools
|
|
92
|
-
|
|
93
|
-
```ts
|
|
94
|
-
import { DrawingController } from 'openalgo-charts/draw';
|
|
95
|
-
|
|
96
|
-
const draw = new DrawingController(chart, { magnet: true });
|
|
97
|
-
draw.setTool('trend-line'); // the next two clicks place it
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
### Panes & legends
|
|
105
|
-
Draggable pane dividers, move / maximize / remove, and TradingView-style pane legends showing one reading per plot in that plot's own colour, with inline show-hide / settings / move / delete controls revealed on hover.
|
|
106
|
-
|
|
107
|
-
### Trading
|
|
108
|
-
Order, position, and bracket lines with live P&L, one-click and drag-to-modify, OCO, validation, an order state machine, analyzer (sandbox) mode, and a depth-of-market ladder (5 to 200 levels).
|
|
109
|
-
|
|
110
|
-
### Profiles & order flow
|
|
111
|
-
Volume Profile, Market Profile (TPO), Footprint, and cumulative delta.
|
|
112
|
-
|
|
113
|
-
### State
|
|
114
|
-
`chart.getState()` / `chart.restoreState()` capture the viewport, grid, panes, price scales, indicator instances, and drawings as one JSON payload — saved layouts and templates with no extra storage plumbing.
|
|
115
|
-
|
|
116
|
-
### Data
|
|
117
|
-
OpenAlgo REST history + WebSocket ticks with auto-reconnect and resubscribe, live candle aggregation, tick/volume bars, a unified `chart.on(...)` event bus, markers and signals, earnings/dividend/expiry event markers, and custom price/time formatters.
|
|
118
|
-
|
|
119
|
-
## Size budget
|
|
120
|
-
|
|
121
|
-
Enforced in CI by [`size-limit`](./.size-limit.json) — nothing is excluded, because there are no runtime dependencies to exclude.
|
|
122
|
-
|
|
123
|
-
| Bundle | Limit | Actual |
|
|
124
|
-
|---|---|---|
|
|
125
|
-
| Base engine | 34 KB | 32.72 KB |
|
|
126
|
-
| Base + trade | 40.5 KB | 39.31 KB |
|
|
127
|
-
| Indicators tier | 9 KB | 4.47 KB |
|
|
128
|
-
| Draw tier | 14 KB | 6.25 KB |
|
|
129
|
-
| Transform tier | 5 KB | 2.66 KB |
|
|
130
|
-
| Profile tier | 8 KB | 5.53 KB |
|
|
131
|
-
| **Everything** | **72 KB** | **58.22 KB** |
|
|
132
|
-
|
|
133
|
-
## Documentation
|
|
134
|
-
|
|
135
|
-
Full docs, the interactive example gallery, and the generated API reference live at:
|
|
136
|
-
|
|
137
|
-
**https://marketcalls.github.io/openalgo-charts/**
|
|
138
|
-
|
|
139
|
-
The site is built with Nextra (in [`website/`](./website)) and statically exported to GitHub Pages on every push. Every code sample on a docs page is a *live* chart running the real library, so what you read is what runs. To run the site locally:
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
npm run build # build the library (dist/) the live demos import
|
|
143
|
-
cd website && npm install && npm run dev # http://localhost:3000/openalgo-charts
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
## Examples
|
|
147
|
-
|
|
148
|
-
Runnable demos in [`examples/`](./examples), including a full **yfinance terminal** ([`examples/yfinance`](./examples/yfinance)) with a TradingView-style shell: symbol search, interval pills, chart-type picker, indicator menu, a vertical drawing rail, a floating properties bar, generated indicator settings, and layout persistence.
|
|
149
|
-
|
|
150
|
-
```bash
|
|
151
|
-
npm run build
|
|
152
|
-
cd examples/yfinance && pip install -r requirements.txt && python server.py
|
|
153
|
-
# → http://127.0.0.1:8000/examples/yfinance/index.html
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
## Develop
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
npm install # install dev toolchain
|
|
160
|
-
npm run typecheck # strict TypeScript check
|
|
161
|
-
npm test # unit tests (vitest) - 468 across 47 files
|
|
162
|
-
npm run build # Rollup -> dist/ (minified ESM per tier + types)
|
|
163
|
-
npm run size # size-limit (Brotli) against the budget
|
|
164
|
-
npm run e2e # Playwright Chromium smoke tests
|
|
165
|
-
npm run verify # typecheck + test + build + size
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## Principles
|
|
169
|
-
|
|
170
|
-
- **Single canvas pipeline** (no SVG, no DOM-per-bar) — small and fast.
|
|
171
|
-
- **Gapless time axis by default** — weekends, holidays, and session breaks collapse.
|
|
172
|
-
- **Registries, not switches** — chart types, indicators, and drawing tools are all descriptors. Adding one is a registration, never a core change.
|
|
173
|
-
- **Zero runtime dependencies** — nothing is excluded from the size budget.
|
|
174
|
-
- **Apache-2.0**, original code.
|
|
175
|
-
|
|
176
|
-
## Status & limitations
|
|
177
|
-
|
|
178
|
-
Version **1.0.8 (published)**. All engine build phases are implemented with 468 unit tests across 47 files.
|
|
179
|
-
|
|
180
|
-
Known gaps, stated plainly:
|
|
181
|
-
|
|
182
|
-
- The **Footprint / order-flow renderer** works but has not had its visual pass — hardcoded colours (not theme-aware), a single display mode, no `setOptions`, and `stackedImbalances` is computed but not drawn.
|
|
183
|
-
- **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. See [`ARCHITECTURE.md`](./ARCHITECTURE.md) §6A.
|
|
184
|
-
- 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.
|
|
185
|
-
- Price-scale **`percentage`** and **`indexed-to-100`** modes, and overlay scales on a shared axis, are not implemented.
|
|
186
|
-
- An optional **DOM chrome package** (toolbar, dialogs, command palette, objects panel) is the next planned piece; today that UI lives in the examples.
|
|
187
|
-
|
|
188
|
-
See [`ARCHITECTURE.md`](./ARCHITECTURE.md) §13a for the full deferred list.
|
|
189
|
-
|
|
190
|
-
## License
|
|
191
|
-
|
|
192
|
-
[Apache-2.0](./LICENSE). See [`NOTICE`](./NOTICE).
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# OpenAlgo Charts
|
|
4
|
+
|
|
5
|
+
**A from-scratch, dependency-free HTML5-canvas charting engine for OpenAlgo.**
|
|
6
|
+
|
|
7
|
+
Professional interactive charts, indicators, drawing tools, order flow, and on-chart trading — six lazy-loaded tiers, zero runtime dependencies, ~33 KB Brotli for the base engine.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/openalgo-charts)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+
[](#size-budget)
|
|
12
|
+
[](#develop)
|
|
13
|
+
[](#principles)
|
|
14
|
+
|
|
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)
|
|
16
|
+
|
|
17
|
+
<img src="docs/architecture-diagram.png" alt="OpenAlgo Charts - layered architecture from public API down to feeds and data, with loadable bundle tiers" width="920" />
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Live OpenAlgo trading terminal
|
|
24
|
+
|
|
25
|
+
Right-click the chart to place market / limit / stop orders, drag the order and TP/SL bracket lines to modify, and watch live P&L on the position line - all on real OpenAlgo history + WebSocket tick data, with an analyzer (sandbox) mode so nothing goes live until you arm it.
|
|
26
|
+
|
|
27
|
+
<p align="center">
|
|
28
|
+
<img src="docs/trading.png" alt="OpenAlgo Charts live trading terminal: RELIANCE 5m candles with order lines, a right-click order menu, a long position with live P&L, and volume" width="920" />
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+
## Examples gallery
|
|
32
|
+
|
|
33
|
+
Every chart in the [live gallery](https://marketcalls.github.io/openalgo-charts/examples) is the real library running in your browser - switch tabs, hover the crosshair, drag the order lines, place a drawing. What you see is the code that ran.
|
|
34
|
+
|
|
35
|
+
<p align="center">
|
|
36
|
+
<img src="docs/demo1.png" alt="Chart-type switcher, custom themes, data tooltips, and event markers" width="49%" />
|
|
37
|
+
<img src="docs/demo2.png" alt="Range switcher, legend, series compare, and indicators and markers" width="49%" />
|
|
38
|
+
</p>
|
|
39
|
+
<p align="center">
|
|
40
|
+
<img src="docs/demo3.png" alt="More live OpenAlgo Charts examples" width="49%" />
|
|
41
|
+
<img src="docs/demo4.png" alt="More live OpenAlgo Charts examples" width="49%" />
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install openalgo-charts
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { createChart, generateBars } from 'openalgo-charts';
|
|
52
|
+
|
|
53
|
+
const chart = createChart(document.getElementById('chart'));
|
|
54
|
+
chart.addSeries('candlestick').setData(generateBars(1700000000, 200, 3600));
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Tiers
|
|
58
|
+
|
|
59
|
+
Import only what you use. Each tier is a separate bundle that registers into the base engine's registries, so the cost of a feature you don't load is zero.
|
|
60
|
+
|
|
61
|
+
| Import | Contents | Brotli |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `openalgo-charts` | Engine, 13 chart types, panes & scales, primitives, registries, chart state, trading overlay, OpenAlgo feeds | 34.6 KB |
|
|
64
|
+
| `openalgo-charts/indicators` | 18 built-in indicators + the Tier-2 (external-data) contract | 4.5 KB |
|
|
65
|
+
| `openalgo-charts/draw` | 34 drawing tools + a headless drawing controller | 8.3 KB |
|
|
66
|
+
| `openalgo-charts/transform` | Heikin Ashi, Renko, Range bars, Line Break, Point & Figure, Kagi | 2.7 KB |
|
|
67
|
+
| `openalgo-charts/profile` | Volume Profile, Market Profile (TPO), Footprint, order flow | 10.1 KB |
|
|
68
|
+
| `openalgo-charts/trade` | Order / position / bracket tools + DOM ladder | 6.6 KB |
|
|
69
|
+
|
|
70
|
+
Everything together is **66.7 KB Brotli**.
|
|
71
|
+
|
|
72
|
+
## What's built
|
|
73
|
+
|
|
74
|
+
### Chart types & transforms
|
|
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
|
+
|
|
77
|
+
### Indicators
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import 'openalgo-charts/indicators';
|
|
81
|
+
|
|
82
|
+
chart.addIndicator('bollinger'); // overlays the price pane
|
|
83
|
+
const macd = chart.addIndicator('macd', { fastPeriod: 8 }); // gets its own pane
|
|
84
|
+
macd.setSettings({ 'macd:width': 2, 'macd:lineStyle': 'dashed' });
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
18 built-ins: SMA, EMA, WMA, VWAP, Bollinger Bands, Supertrend, Parabolic SAR, Ichimoku Cloud, RSI, MACD, Stochastic, ADX/DMI, CCI, MFI, ATR, Volume, OBV, A/D.
|
|
88
|
+
|
|
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
|
+
|
|
91
|
+
### Drawing tools
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { DrawingController } from 'openalgo-charts/draw';
|
|
95
|
+
|
|
96
|
+
const draw = new DrawingController(chart, { magnet: true });
|
|
97
|
+
draw.setTool('trend-line'); // the next two clicks place it
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
34 tools. Lines: trend line, ray, extended line, arrow, horizontal line/ray, vertical line, cross line. Shapes: rectangle, ellipse, circle, triangle. Paths: polyline, arc, curve. Channels: parallel channel, fib channel. Fibonacci: retracement, extension, time zone, speed fan. Gann: fan, box. Forecasting: long/short position (with R:R and risk-based sizing), forecast. Measurers: price range, date range, measure. Arrows: mark up, mark down. Annotation: text, brush, highlighter.
|
|
101
|
+
|
|
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
|
+
### Panes & legends
|
|
105
|
+
Draggable pane dividers, move / maximize / remove, and TradingView-style pane legends showing one reading per plot in that plot's own colour, with inline show-hide / settings / move / delete controls revealed on hover.
|
|
106
|
+
|
|
107
|
+
### Trading
|
|
108
|
+
Order, position, and bracket lines with live P&L, one-click and drag-to-modify, OCO, validation, an order state machine, analyzer (sandbox) mode, and a depth-of-market ladder (5 to 200 levels).
|
|
109
|
+
|
|
110
|
+
### Profiles & order flow
|
|
111
|
+
Volume Profile, Market Profile (TPO), Footprint, and cumulative delta.
|
|
112
|
+
|
|
113
|
+
### State
|
|
114
|
+
`chart.getState()` / `chart.restoreState()` capture the viewport, grid, panes, price scales, indicator instances, and drawings as one JSON payload — saved layouts and templates with no extra storage plumbing.
|
|
115
|
+
|
|
116
|
+
### Data
|
|
117
|
+
OpenAlgo REST history + WebSocket ticks with auto-reconnect and resubscribe, live candle aggregation, tick/volume bars, a unified `chart.on(...)` event bus, markers and signals, earnings/dividend/expiry event markers, and custom price/time formatters.
|
|
118
|
+
|
|
119
|
+
## Size budget
|
|
120
|
+
|
|
121
|
+
Enforced in CI by [`size-limit`](./.size-limit.json) — nothing is excluded, because there are no runtime dependencies to exclude.
|
|
122
|
+
|
|
123
|
+
| Bundle | Limit | Actual |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| Base engine | 34 KB | 32.72 KB |
|
|
126
|
+
| Base + trade | 40.5 KB | 39.31 KB |
|
|
127
|
+
| Indicators tier | 9 KB | 4.47 KB |
|
|
128
|
+
| Draw tier | 14 KB | 6.25 KB |
|
|
129
|
+
| Transform tier | 5 KB | 2.66 KB |
|
|
130
|
+
| Profile tier | 8 KB | 5.53 KB |
|
|
131
|
+
| **Everything** | **72 KB** | **58.22 KB** |
|
|
132
|
+
|
|
133
|
+
## Documentation
|
|
134
|
+
|
|
135
|
+
Full docs, the interactive example gallery, and the generated API reference live at:
|
|
136
|
+
|
|
137
|
+
**https://marketcalls.github.io/openalgo-charts/**
|
|
138
|
+
|
|
139
|
+
The site is built with Nextra (in [`website/`](./website)) and statically exported to GitHub Pages on every push. Every code sample on a docs page is a *live* chart running the real library, so what you read is what runs. To run the site locally:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npm run build # build the library (dist/) the live demos import
|
|
143
|
+
cd website && npm install && npm run dev # http://localhost:3000/openalgo-charts
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Examples
|
|
147
|
+
|
|
148
|
+
Runnable demos in [`examples/`](./examples), including a full **yfinance terminal** ([`examples/yfinance`](./examples/yfinance)) with a TradingView-style shell: symbol search, interval pills, chart-type picker, indicator menu, a vertical drawing rail, a floating properties bar, generated indicator settings, and layout persistence.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npm run build
|
|
152
|
+
cd examples/yfinance && pip install -r requirements.txt && python server.py
|
|
153
|
+
# → http://127.0.0.1:8000/examples/yfinance/index.html
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Develop
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npm install # install dev toolchain
|
|
160
|
+
npm run typecheck # strict TypeScript check
|
|
161
|
+
npm test # unit tests (vitest) - 468 across 47 files
|
|
162
|
+
npm run build # Rollup -> dist/ (minified ESM per tier + types)
|
|
163
|
+
npm run size # size-limit (Brotli) against the budget
|
|
164
|
+
npm run e2e # Playwright Chromium smoke tests
|
|
165
|
+
npm run verify # typecheck + test + build + size
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Principles
|
|
169
|
+
|
|
170
|
+
- **Single canvas pipeline** (no SVG, no DOM-per-bar) — small and fast.
|
|
171
|
+
- **Gapless time axis by default** — weekends, holidays, and session breaks collapse.
|
|
172
|
+
- **Registries, not switches** — chart types, indicators, and drawing tools are all descriptors. Adding one is a registration, never a core change.
|
|
173
|
+
- **Zero runtime dependencies** — nothing is excluded from the size budget.
|
|
174
|
+
- **Apache-2.0**, original code.
|
|
175
|
+
|
|
176
|
+
## Status & limitations
|
|
177
|
+
|
|
178
|
+
Version **1.0.8 (published)**. All engine build phases are implemented with 468 unit tests across 47 files.
|
|
179
|
+
|
|
180
|
+
Known gaps, stated plainly:
|
|
181
|
+
|
|
182
|
+
- The **Footprint / order-flow renderer** works but has not had its visual pass — hardcoded colours (not theme-aware), a single display mode, no `setOptions`, and `stackedImbalances` is computed but not drawn.
|
|
183
|
+
- **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. See [`ARCHITECTURE.md`](./ARCHITECTURE.md) §6A.
|
|
184
|
+
- 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.
|
|
185
|
+
- Price-scale **`percentage`** and **`indexed-to-100`** modes, and overlay scales on a shared axis, are not implemented.
|
|
186
|
+
- An optional **DOM chrome package** (toolbar, dialogs, command palette, objects panel) is the next planned piece; today that UI lives in the examples.
|
|
187
|
+
|
|
188
|
+
See [`ARCHITECTURE.md`](./ARCHITECTURE.md) §13a for the full deferred list.
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
[Apache-2.0](./LICENSE). See [`NOTICE`](./NOTICE).
|
package/dist/draw/index.d.ts
CHANGED
|
@@ -189,7 +189,10 @@ declare class DataLayer {
|
|
|
189
189
|
/** Register a new series; returns its id. */
|
|
190
190
|
createSeries(): SeriesId;
|
|
191
191
|
removeSeries(id: SeriesId): void;
|
|
192
|
-
/**
|
|
192
|
+
/**
|
|
193
|
+
* Bulk-load (full replace) one series' data, then re-merge the time axis.
|
|
194
|
+
* Input is sorted and de-duplicated by time (see {@link sortedUniqueByTime}).
|
|
195
|
+
*/
|
|
193
196
|
setSeriesData(id: SeriesId, bars: readonly Bar[]): void;
|
|
194
197
|
/**
|
|
195
198
|
* Upsert bars into a series by time (used for history paging / backfill /
|
|
@@ -503,7 +506,6 @@ declare const LONG_POSITION: DrawingTool;
|
|
|
503
506
|
declare const SHORT_POSITION: DrawingTool;
|
|
504
507
|
declare const TEXT: DrawingTool;
|
|
505
508
|
declare const PATH: DrawingTool;
|
|
506
|
-
/** Every built-in, in toolbar order. */
|
|
507
509
|
declare const BUILTIN_DRAWING_TOOLS: readonly DrawingTool[];
|
|
508
510
|
/** Register every built-in tool. Idempotent; called on tier import. */
|
|
509
511
|
declare function registerBuiltinDrawingTools(): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Library version string. Matches package.json (published npm release). */
|
|
2
|
-
declare const VERSION = "1.0.
|
|
2
|
+
declare const VERSION = "1.0.12";
|
|
3
3
|
/** Returns the current library version. */
|
|
4
4
|
declare function version(): string;
|
|
5
5
|
|
|
@@ -312,7 +312,10 @@ declare class DataLayer {
|
|
|
312
312
|
/** Register a new series; returns its id. */
|
|
313
313
|
createSeries(): SeriesId;
|
|
314
314
|
removeSeries(id: SeriesId): void;
|
|
315
|
-
/**
|
|
315
|
+
/**
|
|
316
|
+
* Bulk-load (full replace) one series' data, then re-merge the time axis.
|
|
317
|
+
* Input is sorted and de-duplicated by time (see {@link sortedUniqueByTime}).
|
|
318
|
+
*/
|
|
316
319
|
setSeriesData(id: SeriesId, bars: readonly Bar[]): void;
|
|
317
320
|
/**
|
|
318
321
|
* Upsert bars into a series by time (used for history paging / backfill /
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function t(t,e,i,n){const s=n.x-i.x,r=n.y-i.y,o=s*s+r*r;if(0===o)return Math.hypot(t-i.x,e-i.y);let l=((t-i.x)*s+(e-i.y)*r)/o;return l=l<0?0:l>1?1:l,Math.hypot(t-(i.x+l*s),e-(i.y+l*r))}function e(t,e,i,n){const s=n.x-i.x,r=n.y-i.y,o=Math.hypot(s,r);return 0===o?Math.hypot(t-i.x,e-i.y):Math.abs(r*(t-i.x)-s*(e-i.y))/o}function i(t,e){return Math.abs(t-e)}function n(t,e){return Math.abs(t-e)}function s(e,i,n){let s=1/0;for(let r=1;r<n.length;r++){const o=t(e,i,n[r-1],n[r]);o<s&&(s=o)}return s}function r(t,e){return{x0:Math.min(t.x,e.x),y0:Math.min(t.y,e.y),x1:Math.max(t.x,e.x),y1:Math.max(t.y,e.y)}}function o(e,i,n,s,o){const l=r(n,s);if(e>=l.x0&&e<=l.x1&&i>=l.y0&&i<=l.y1&&o)return 0;const a=[{x:l.x0,y:l.y0},{x:l.x1,y:l.y0},{x:l.x1,y:l.y1},{x:l.x0,y:l.y1}];let c=1/0;for(let n=0;n<4;n++){const s=t(e,i,a[n],a[(n+1)%4]);s<c&&(c=s)}return c}function l(t,e,i,n,s){const o=r(i,n),l=(o.x0+o.x1)/2,a=(o.y0+o.y1)/2,c=Math.max(1e-6,(o.x1-o.x0)/2),h=Math.max(1e-6,(o.y1-o.y0)/2),d=(t-l)/c,p=(e-a)/h,u=Math.hypot(d,p);return s&&u<=1?0:Math.abs(u-1)*Math.min(c,h)}function a(t,e,i,n,s){const r=e.x-t.x;if(0===r)return[t,e];const o=(e.y-t.y)/r;return[n?{x:0,y:t.y+(0-t.x)*o}:t,s?{x:i,y:t.y+(i-t.x)*o}:e]}const c=new Map;function h(t){c.set(t.id,t)}function d(t){const e=c.get(t);if(void 0===e)throw new Error(`openalgo-charts: unknown drawing tool "${t}"`);return e}function p(t){return c.has(t)}function u(){return Array.from(c.values())}function x(t){const{ctx:e,rc:i,style:n}=t,s=i.dpr;e.strokeStyle=n.color,e.lineWidth=Math.max(1,Math.round(n.lineWidth*s)),e.lineJoin="round",e.lineCap="round",e.setLineDash("dashed"===n.lineStyle?[6*s,4*s]:"dotted"===n.lineStyle?[1*s,3*s]:[])}function y(t){return t.style.fillColor??t.style.color}function f(t,e){if(!0!==t.style.fill)return;const{ctx:i}=t;i.save(),i.globalAlpha=t.style.fillOpacity??.12,i.fillStyle=y(t),e(),i.restore()}function g(t,e,i,n,s){const{ctx:r,rc:o,style:l}=t,a=(l.fontSize??11)*o.dpr;r.save(),r.setLineDash([]),r.font=`${a}px ui-sans-serif, system-ui, sans-serif`,r.textBaseline="middle",r.textAlign="left";const c=r.measureText(e).width;r.globalAlpha=.85,r.fillStyle=o.theme.background,r.fillRect(i-2*o.dpr,n-.75*a,c+4*o.dpr,1.5*a),r.globalAlpha=1,r.fillStyle=s??l.color,r.fillText(e,i,n),r.restore()}const _=[0,.236,.382,.5,.618,.786,1];function m(i,n,s,r){return{id:i,name:n,points:2,defaultStyle:{extendLeft:s,extendRight:r},draw:t=>{const[e,i]=a(t.pts[0],t.pts[1],t.rc.plotWidth*t.rc.dpr,t.style.extendLeft??s,t.style.extendRight??r);x(t),t.ctx.beginPath(),t.ctx.moveTo(e.x,e.y),t.ctx.lineTo(i.x,i.y),t.ctx.stroke(),t.ctx.setLineDash([])},distance:(i,n,o)=>{const l=o.drawing.style.extendLeft??s,c=o.drawing.style.extendRight??r;if(l&&c)return e(i,n,o.pts[0],o.pts[1]);const[h,d]=a(o.pts[0],o.pts[1],o.rc.plotWidth,l,c);return t(i,n,h,d)}}}const w=m("trend-line","Trend Line",!1,!1),b=m("ray","Ray",!1,!0),v=m("extended-line","Extended Line",!0,!0),M={id:"arrow",name:"Arrow",points:2,draw:t=>{const[e,i]=t.pts;x(t),t.ctx.beginPath(),t.ctx.moveTo(e.x,e.y),t.ctx.lineTo(i.x,i.y),t.ctx.stroke();const n=Math.max(8,5*t.style.lineWidth)*t.rc.dpr,s=Math.atan2(i.y-e.y,i.x-e.x);t.ctx.beginPath(),t.ctx.moveTo(i.x,i.y),t.ctx.lineTo(i.x-n*Math.cos(s-.4),i.y-n*Math.sin(s-.4)),t.ctx.lineTo(i.x-n*Math.cos(s+.4),i.y-n*Math.sin(s+.4)),t.ctx.closePath(),t.ctx.fillStyle=t.style.color,t.ctx.fill(),t.ctx.setLineDash([])},distance:(e,i,n)=>t(e,i,n.pts[0],n.pts[1])},S={id:"horizontal-line",name:"Horizontal Line",points:1,defaultStyle:{showLabels:!0},draw:t=>{const e=Math.round(t.pts[0].y)+.5;x(t),t.ctx.beginPath(),t.ctx.moveTo(0,e),t.ctx.lineTo(t.rc.plotWidth*t.rc.dpr,e),t.ctx.stroke(),t.ctx.setLineDash([]),!1!==t.style.showLabels&&g(t,t.formatPrice(t.drawing.points[0].price),4*t.rc.dpr,e-8*t.rc.dpr)},distance:(t,e,n)=>i(e,n.pts[0].y)},P={id:"horizontal-ray",name:"Horizontal Ray",points:1,defaultStyle:{showLabels:!0},draw:t=>{const e=Math.round(t.pts[0].y)+.5;x(t),t.ctx.beginPath(),t.ctx.moveTo(t.pts[0].x,e),t.ctx.lineTo(t.rc.plotWidth*t.rc.dpr,e),t.ctx.stroke(),t.ctx.setLineDash([]),!1!==t.style.showLabels&&g(t,t.formatPrice(t.drawing.points[0].price),t.pts[0].x+4*t.rc.dpr,e-8*t.rc.dpr)},distance:(t,e,n)=>t<n.pts[0].x?null:i(e,n.pts[0].y)},T={id:"vertical-line",name:"Vertical Line",points:1,draw:t=>{const e=Math.round(t.pts[0].x)+.5;x(t),t.ctx.beginPath(),t.ctx.moveTo(e,0),t.ctx.lineTo(e,t.rc.plotHeight*t.rc.dpr),t.ctx.stroke(),t.ctx.setLineDash([])},distance:(t,e,i)=>n(t,i.pts[0].x)},L={id:"cross-line",name:"Cross Line",points:1,draw:t=>{const e=Math.round(t.pts[0].x)+.5,i=Math.round(t.pts[0].y)+.5;x(t),t.ctx.beginPath(),t.ctx.moveTo(e,0),t.ctx.lineTo(e,t.rc.plotHeight*t.rc.dpr),t.ctx.moveTo(0,i),t.ctx.lineTo(t.rc.plotWidth*t.rc.dpr,i),t.ctx.stroke(),t.ctx.setLineDash([])},distance:(t,e,s)=>Math.min(n(t,s.pts[0].x),i(e,s.pts[0].y))},k={id:"rectangle",name:"Rectangle",points:2,defaultStyle:{fill:!0,fontSize:14,textAlign:"left",textVAlign:"top",textPosition:"inside"},draw:t=>{const e=r(t.pts[0],t.pts[1]);f(t,()=>t.ctx.fillRect(e.x0,e.y0,e.x1-e.x0,e.y1-e.y0)),x(t),t.ctx.strokeRect(e.x0,e.y0,e.x1-e.x0,e.y1-e.y0),t.ctx.setLineDash([]),J(t,e.x0,e.y0,e.x1,e.y1)},distance:(t,e,i)=>o(t,e,i.pts[0],i.pts[1],!0===i.drawing.style.fill)},O={id:"ellipse",name:"Ellipse",points:2,defaultStyle:{fill:!0,fontSize:14,textAlign:"center",textVAlign:"middle",textPosition:"inside"},draw:t=>{const e=r(t.pts[0],t.pts[1]),i=(e.x0+e.x1)/2,n=(e.y0+e.y1)/2,s=Math.max(1,(e.x1-e.x0)/2),o=Math.max(1,(e.y1-e.y0)/2),l=()=>{t.ctx.beginPath(),t.ctx.ellipse(i,n,s,o,0,0,2*Math.PI)};f(t,()=>{l(),t.ctx.fill()}),x(t),l(),t.ctx.stroke(),t.ctx.setLineDash([]),J(t,e.x0,e.y0,e.x1,e.y1)},distance:(t,e,i)=>l(t,e,i.pts[0],i.pts[1],!0===i.drawing.style.fill)},D={id:"parallel-channel",name:"Parallel Channel",points:3,defaultStyle:{fill:!0},draw:t=>{const[e,i,n]=t.pts,s=n.y-(e.y+.5*(i.y-e.y)),r={x:e.x,y:e.y+s},o={x:i.x,y:i.y+s};f(t,()=>{t.ctx.beginPath(),t.ctx.moveTo(e.x,e.y),t.ctx.lineTo(i.x,i.y),t.ctx.lineTo(o.x,o.y),t.ctx.lineTo(r.x,r.y),t.ctx.closePath(),t.ctx.fill()}),x(t),t.ctx.beginPath(),t.ctx.moveTo(e.x,e.y),t.ctx.lineTo(i.x,i.y),t.ctx.moveTo(r.x,r.y),t.ctx.lineTo(o.x,o.y),t.ctx.stroke(),t.ctx.setLineDash([]),J(t,Math.min(e.x,i.x),Math.min(e.y,i.y,r.y,o.y),Math.max(e.x,i.x),Math.max(e.y,i.y,r.y,o.y))},distance:(e,i,n)=>{const[s,r,o]=n.pts,l=o.y-(s.y+.5*(r.y-s.y)),a=t(e,i,s,r),c=t(e,i,{x:s.x,y:s.y+l},{x:r.x,y:r.y+l});return Math.min(a,c)}};function A(t,e,i){return{id:t,name:e,points:i,defaultStyle:{showLabels:!0,levels:[..._],fill:!0,fillOpacity:.06},draw:t=>{const e=t.style.levels??_,n=t.drawing.points,s=2===i?n[0].price:n[2].price,r=n[1].price-n[0].price,o=Math.min(t.pts[0].x,t.pts[i-1].x),l=Math.max(t.pts[0].x,t.pts[i-1].x),a=!0===t.style.extendRight?t.rc.plotWidth*t.rc.dpr:l;x(t);let c=null;for(const i of e){const e=s+r*i,n=Math.round(t.rc.priceScale.priceToY(e)*t.rc.dpr)+.5;!0===t.style.fill&&null!==c&&(t.ctx.save(),t.ctx.globalAlpha=t.style.fillOpacity??.06,t.ctx.fillStyle=y(t),t.ctx.fillRect(o,Math.min(c,n),a-o,Math.abs(n-c)),t.ctx.restore()),c=n,t.ctx.beginPath(),t.ctx.moveTo(o,n),t.ctx.lineTo(a,n),t.ctx.stroke(),!1!==t.style.showLabels&&g(t,`${(100*i).toFixed(1)}% ${t.formatPrice(e)}`,o+4*t.rc.dpr,n-8*t.rc.dpr)}t.ctx.setLineDash([])},distance:(t,e,n)=>{const s=n.drawing.style.levels??_,r=n.drawing.points,o=2===i?r[0].price:r[2].price,l=r[1].price-r[0].price,a=Math.min(n.pts[0].x,n.pts[i-1].x),c=!0===n.drawing.style.extendRight?n.rc.plotWidth:Math.max(n.pts[0].x,n.pts[i-1].x);if(t<a-4||t>c+4)return null;let h=1/0;for(const t of s){const i=Math.abs(e-n.rc.priceScale.priceToY(o+l*t));i<h&&(h=i)}return h}}}const R=A("fib-retracement","Fib Retracement",2),W=A("fib-extension","Fib Extension",3),$={id:"measure",name:"Measure",points:2,defaultStyle:{fill:!0,showLabels:!0,fillOpacity:.1},draw:t=>{const e=r(t.pts[0],t.pts[1]),i=t.drawing.points,n=i[1].price-i[0].price,s=0!==i[0].price?n/i[0].price*100:0,o=n>=0,l=o?"#26a69a":"#ef5350";t.ctx.save(),t.ctx.globalAlpha=t.style.fillOpacity??.1,t.ctx.fillStyle=l,t.ctx.fillRect(e.x0,e.y0,e.x1-e.x0,e.y1-e.y0),t.ctx.restore(),x(t),t.ctx.strokeStyle=l,t.ctx.strokeRect(e.x0,e.y0,e.x1-e.x0,e.y1-e.y0),t.ctx.setLineDash([]);const a=t.rc.dataLayer.timeToIndexFloat(i[0].time),c=t.rc.dataLayer.timeToIndexFloat(i[1].time),h=Math.abs(Math.round(c-a)),d=o?"+":"";g(t,`${d}${t.formatPrice(n)} (${d}${s.toFixed(2)}%) ${h} bars`,e.x0+4*t.rc.dpr,e.y0-10*t.rc.dpr,l)},distance:(t,e,i)=>o(t,e,i.pts[0],i.pts[1],!0)};function I(t,e,i){return{id:t,name:e,points:3,defaultStyle:{showLabels:!0,fillOpacity:.13,accountSize:1e5,risk:1},draw:t=>{const[e,n,s]=t.drawing.points,r=t.rc.dpr,o=Math.min(t.pts[0].x,t.pts[1].x,t.pts[2].x),l=Math.max(t.pts[0].x,t.pts[1].x,t.pts[2].x),a=t.rc.priceScale.priceToY(e.price)*r,c=t.rc.priceScale.priceToY(n.price)*r,h=t.rc.priceScale.priceToY(s.price)*r,d=(e,i,n)=>{t.ctx.save(),t.ctx.globalAlpha=t.style.fillOpacity??.13,t.ctx.fillStyle=n,t.ctx.fillRect(o,Math.min(e,i),l-o,Math.abs(i-e)),t.ctx.restore()};d(a,c,"#26a69a"),d(a,h,"#ef5350"),x(t);for(const e of[a,c,h]){const i=Math.round(e)+.5;t.ctx.beginPath(),t.ctx.moveTo(o,i),t.ctx.lineTo(l,i),t.ctx.stroke()}if(t.ctx.setLineDash([]),!1===t.style.showLabels)return;const p=Math.abs(e.price-s.price),u=Math.abs(n.price-e.price),y=p>0?u/p:0,f=t.style.accountSize??0,_=t.style.risk??0,m=p>0&&f>0?Math.floor(f*_/100/p):0;g(t,`${i?"LONG":"SHORT"} R:R ${y.toFixed(2)}${m>0?` qty ${m}`:""}`,o+4*r,a-10*r)},distance:(t,e,i)=>{const n=Math.min(i.pts[0].x,i.pts[1].x,i.pts[2].x),s=Math.max(i.pts[0].x,i.pts[1].x,i.pts[2].x);if(t<n-4||t>s+4)return null;const r=i.drawing.points.map(t=>i.rc.priceScale.priceToY(t.price)),o=Math.min(...r),l=Math.max(...r);return e>=o&&e<=l?0:Math.min(Math.abs(e-o),Math.abs(e-l))}}}const j=I("long-position","Long Position",!0),z=I("short-position","Short Position",!1),C=1.35,F="ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif";function N(t,e){const i="bold"===t.fontWeight?"700 ":"";return`${"italic"===t.fontStyle?"italic ":""}${i}${e}px ${t.fontFamily??F}`}function U(t,e,i){const n=(e.text??"").split("\n");if(!0!==e.wrap)return n;const s=[];for(const e of n){const n=e.split(/\s+/).filter(t=>""!==t);if(0===n.length){s.push("");continue}let r=n[0];for(let e=1;e<n.length;e++){const o=`${r} ${n[e]}`;t.measureText(o).width>i?(s.push(r),r=n[e]):r=o}s.push(r)}return s}function H(t,e,i){const n=(e.fontSize??13)*i;t.font=N(e,n);const s=U(t,e,(e.wrapWidth??220)*i);let r=0;for(const e of s)r=Math.max(r,t.measureText(e).width);const o=n*C;return{lines:s,width:r+10*i,height:s.length*o+10*i,lineHeight:o}}function J(t,e,i,n,s){const{ctx:r,rc:o,style:l}=t,a=l.text;if(void 0===a||""===a)return;const c=o.dpr,h=(l.fontSize??14)*c,d=6*c;r.save(),r.setLineDash([]),r.font=N(l,h),r.fillStyle=l.fontColor??l.color,r.textBaseline="top";const p=!0===l.wrap?U(r,l,Math.max(20*c,n-e-2*d)):a.split("\n"),u=h*C,x=p.length*u,y=l.textAlign??"left";r.textAlign=y;const f="center"===y?(e+n)/2:"right"===y?n-d:e+d;let g;if("outside"===l.textPosition)g=i-x-d;else{const t=l.textVAlign??"top";g="middle"===t?(i+s-x)/2:"bottom"===t?s-x-d:i+d}for(const t of p)r.fillText(t,f,g),g+=u;r.restore()}const E={id:"text",name:"Text",points:1,defaultStyle:{text:"Text",fontSize:14,fontWeight:"normal",fontStyle:"normal",background:!1,backgroundOpacity:1,border:!1,wrap:!1,wrapWidth:220,textAlign:"left"},draw:t=>{const{ctx:e,rc:i,style:n}=t,s=i.dpr,r=H(e,n,s),o=t.pts[0].x,l=t.pts[0].y;e.save(),e.setLineDash([]),!0===n.background&&(e.globalAlpha=n.backgroundOpacity??1,e.fillStyle=n.backgroundColor??i.theme.background,e.beginPath(),e.roundRect(o,l,r.width,r.height,4*s),e.fill(),e.globalAlpha=1),!0===n.border&&(e.strokeStyle=n.borderColor??n.color,e.lineWidth=Math.max(1,n.lineWidth*s),e.beginPath(),e.roundRect(o,l,r.width,r.height,4*s),e.stroke()),e.font=N(n,(n.fontSize??13)*s),e.fillStyle=n.color,e.textBaseline="top";const a=n.textAlign??"left";e.textAlign=a;const c="center"===a?o+r.width/2:"right"===a?o+r.width-5*s:o+5*s;let h=l+5*s;for(const t of r.lines)e.fillText(t,c,h),h+=r.lineHeight;e.restore()},distance:(t,e,i)=>{const n=i.drawing.style,s=n.fontSize??13,r=i.pts[0],o=function(){if(void 0!==Y)return Y;try{Y="undefined"==typeof document?null:document.createElement("canvas").getContext("2d")}catch{Y=null}return Y}(),l=null===o?{width:(n.text??"").length*s*.6+10,height:s*C+10}:H(o,n,1);return t>=r.x-3&&t<=r.x+l.width+3&&e>=r.y-3&&e<=r.y+l.height+3?0:null}};let Y;const B={id:"path",name:"Path",points:0,draw:t=>{if(!(t.pts.length<2)){x(t),t.ctx.beginPath(),t.ctx.moveTo(t.pts[0].x,t.pts[0].y);for(let e=1;e<t.pts.length;e++)t.ctx.lineTo(t.pts[e].x,t.pts[e].y);t.ctx.stroke(),t.ctx.setLineDash([])}},distance:(t,e,i)=>i.pts.length<2?null:s(t,e,i.pts)},q=[w,b,v,M,S,P,T,L,k,O,D,R,W,j,z,$,E,B];let V=!1;function G(){if(!V){V=!0;for(const t of q)h(t)}}class X{constructor(){Object.defineProperty(this,"_drawings",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"_host",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"_selected",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"_preview",{enumerable:!0,configurable:!0,writable:!0,value:null})}attached(t){this._host=t}detached(){this._host=null}zOrder(){return"top"}autoscaleInfo(){return null}setDrawings(t){this._drawings=t.slice(),this._host?.requestUpdate()}setSelected(t){t!==this._selected&&(this._selected=t,this._host?.requestUpdate())}setPreview(t){this._preview=t,this._host?.requestUpdate()}_project(t,e,i){return{x:t.timeScale.indexToX(t.dataLayer.timeToIndexFloat(e)),y:t.priceScale.priceToY(i)}}_points(t,e){return e.points.map(e=>this._project(t,e.time,e.price))}draw(t,e){const i=null===this._preview?this._drawings:[...this._drawings,this._preview];for(const n of i){if(!1===n.visible||!p(n.tool))continue;const i=d(n.tool);if(n.points.length<Math.max(1,i.points))continue;const s=this._points(e,n);if(!s.some(t=>Number.isFinite(t.x)&&Number.isFinite(t.y)))continue;const r={color:n.style.color??e.theme.lineColor,lineWidth:n.style.lineWidth??1.5,...n.style},o=e.dpr;t.save(),n===this._preview&&(t.globalAlpha=.7),i.draw({ctx:t,rc:e,drawing:n,selected:n.id===this._selected,pts:s.map(t=>({x:t.x*o,y:t.y*o})),style:{...r,color:r.color,lineWidth:r.lineWidth},formatPrice:t=>e.priceScale.format(t)}),t.restore(),n.id===this._selected&&!0!==n.locked&&this._drawHandles(t,e,s)}}_drawHandles(t,e,i){const n=e.dpr;t.save(),t.setLineDash([]),t.lineWidth=Math.max(1,Math.round(1.5*n));for(const s of i)t.beginPath(),t.arc(s.x*n,s.y*n,5*n,0,2*Math.PI),t.fillStyle=e.theme.background,t.fill(),t.strokeStyle=e.theme.lineColor,t.stroke();t.restore()}hitTest(t,e,i){if(null!==this._selected){const n=this._drawings.find(t=>t.id===this._selected);if(void 0!==n&&!0!==n.locked){const s=this._points(i,n);for(let i=0;i<s.length;i++)if(Math.hypot(t-s[i].x,e-s[i].y)<=7)return{externalId:`draw:${n.id}#${i}`,zOrder:"top",distance:0,cursor:"grabbing",draggable:!0}}}let n=null;for(let s=this._drawings.length-1;s>=0;s--){const r=this._drawings[s];if(!1===r.visible||!0===r.locked||!p(r.tool))continue;const o=d(r.tool);if(r.points.length<Math.max(1,o.points))continue;const l=o.distance(t,e,{pts:this._points(i,r),drawing:r,rc:i});null===l||!Number.isFinite(l)||l>6||(null===n||l<n.distance)&&(n={id:r.id,distance:l})}return null===n?null:{externalId:`draw:${n.id}`,zOrder:"top",distance:n.distance,cursor:"move",draggable:!0}}}let K=1;class Q{constructor(t,e={}){Object.defineProperty(this,"_chart",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_opts",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_layers",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"_drawings",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"_tool",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"_pending",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"_pendingPane",{enumerable:!0,configurable:!0,writable:!0,value:0}),Object.defineProperty(this,"_selected",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"_undo",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"_redo",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"_dragStart",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"_off",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"_lastCursor",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"_lastBar",{enumerable:!0,configurable:!0,writable:!0,value:null}),this._chart=t,this._opts={magnet:e.magnet??!1,stayInDrawingMode:e.stayInDrawingMode??!1,historyLimit:e.historyLimit??50,defaultStyle:e.defaultStyle??{}},this._off.push(t.on("click",t=>this._onClick(t))),this._off.push(t.on("crosshair:move",t=>this._onCrosshair(t))),this._off.push(t.on("drag",t=>this._onDrag(t))),this._off.push(t.on("drag:end",()=>this._onDragEnd()));const i=t.drawingState();Array.isArray(i)&&(this._drawings=i.map(t=>({...t}))),this._sync()}setTool(t){if(null!==t&&!p(t))throw new Error(`openalgo-charts: unknown drawing tool "${t}"`);this._tool=t,this._pending=[],this._setPlacementMode(null!==t),this._syncPreview(),this._chart.emit("draw:tool",{tool:t})}_setPlacementMode(t){const e=this._chart;e.setPlacementMode?.(t)}activeTool(){return this._tool}setOptions(t){this._opts={...this._opts,...t,defaultStyle:t.defaultStyle??this._opts.defaultStyle}}drawings(){return this._drawings}get(t){return this._drawings.find(e=>e.id===t)}add(t){this._pushUndo();const e=d(t.tool),i={...t,id:t.id??"d"+K++,style:{...this._opts.defaultStyle,...e.defaultStyle,...t.style}};return this._drawings.push(i),this._sync(),this._chart.emit("draw:add",{drawing:i}),i}update(t,e){const i=this.get(t);return void 0!==i&&(this._pushUndo(),void 0!==e.points&&(i.points=e.points.map(t=>({...t}))),void 0!==e.style&&(i.style={...i.style,...e.style}),void 0!==e.locked&&(i.locked=e.locked),void 0!==e.visible&&(i.visible=e.visible),this._sync(),this._chart.emit("draw:update",{drawing:i}),!0)}remove(t){const e=this._drawings.findIndex(e=>e.id===t);if(e<0)return!1;this._pushUndo();const[i]=this._drawings.splice(e,1);return this._selected===t&&(this._selected=null),this._sync(),this._chart.emit("draw:remove",{drawing:i}),!0}clear(){0!==this._drawings.length&&(this._pushUndo(),this._drawings=[],this._selected=null,this._sync())}select(t){this._selected=t;for(const e of this._layers.values())e.setSelected(t);this._chart.emit("draw:select",{id:t})}selected(){return this._selected}undo(){const t=this._undo.pop();return void 0!==t&&(this._redo.push(JSON.stringify(this._drawings)),this._drawings=JSON.parse(t),this._drawings.some(t=>t.id===this._selected)||(this._selected=null),this._sync(),!0)}redo(){const t=this._redo.pop();return void 0!==t&&(this._undo.push(JSON.stringify(this._drawings)),this._drawings=JSON.parse(t),this._sync(),!0)}canUndo(){return this._undo.length>0}canRedo(){return this._redo.length>0}toJSON(){return this._drawings.map(t=>({...t,points:t.points.map(t=>({...t})),style:{...t.style}}))}fromJSON(t){this._drawings=Array.isArray(t)?t.map(t=>({...t})):[],this._selected=null,this._undo=[],this._redo=[],this._sync()}destroy(){this._setPlacementMode(!1);for(const t of this._off)t();this._off.length=0;for(const[t,e]of this._layers)this._chart.removePrimitive(e);this._layers.clear()}_onCrosshair(t){const e=t.time,i=t.price,n=t.paneIndex??null;this._lastCursor=null===e||null===i||null===n?null:{time:e,price:i,paneIndex:n},this._lastBar=t.bar??null,null!==this._tool&&this._pending.length>0&&this._syncPreview()}_onClick(t){if(null===this._tool)null!==t.id&&t.id.startsWith("draw:")?this.select(t.id.slice(5).split("#")[0]):null===t.id&&this.select(null);else{if(!0===t.viaDrag&&0===this._pending.length)return;if(null===t.price||!Number.isFinite(t.price)||!Number.isFinite(t.time))return;this._placePoint({time:t.time,price:this._snap(t.price,t.paneIndex)},t.paneIndex)}}_placePoint(t,e){0===this._pending.length&&(this._pendingPane=e),this._pending.push(t);const i=d(this._tool);if(i.points>0&&this._pending.length>=i.points){const t=this.add({tool:i.id,points:this._pending,style:{},paneIndex:this._pendingPane});this._pending=[],this._opts.stayInDrawingMode||(this._tool=null,this._setPlacementMode(!1)),this._syncPreview(),this.select(t.id),this._chart.emit("draw:tool",{tool:this._tool})}else this._syncPreview()}_snap(t,e){if(!this._opts.magnet||0!==e)return t;const i=this._lastBar;if(null===i)return t;let n=t,s=1/0;for(const e of[i.open,i.high,i.low,i.close]){const i=Math.abs(e-t);i<s&&(s=i,n=e)}return n}_onDrag(t){if(!t.id.startsWith("draw:"))return;const[e,i]=t.id.slice(5).split("#"),n=this.get(e);if(void 0===n||!0===n.locked)return;const s=void 0===i?null:Number(i);null!==this._dragStart&&this._dragStart.id===e&&this._dragStart.handle===s||(this._pushUndo(),this._dragStart={id:e,handle:s,points:n.points.map(t=>({...t})),from:{time:t.fromTime??t.time,price:t.fromPrice??t.price}});const r=this._dragStart;if(null===s){const e=t.time-r.from.time,i=t.price-r.from.price;n.points=r.points.map(t=>({time:t.time+e,price:t.price+i}))}else s>=0&&s<n.points.length&&(n.points=r.points.map((e,i)=>i===s?{time:t.time,price:t.price}:{...e}));this._sync()}_onDragEnd(){if(null===this._dragStart)return;const t=this.get(this._dragStart.id);this._dragStart=null,void 0!==t&&this._chart.emit("draw:update",{drawing:t})}_layerFor(t){let e=this._layers.get(t);return void 0===e&&(e=new X,this._chart.addPrimitive(e,t),this._layers.set(t,e)),e}_sync(){const t=new Map;for(const e of this._drawings){const i=t.get(e.paneIndex)??[];i.push(e),t.set(e.paneIndex,i)}for(const[e,i]of t)this._layerFor(e).setDrawings(i);for(const[e,i]of this._layers)t.has(e)||i.setDrawings([]),i.setSelected(this._selected);this._chart.setDrawingState(this.toJSON())}_syncPreview(){for(const t of this._layers.values())t.setPreview(null);if(null===this._tool||0===this._pending.length)return;const t=this._lastCursor,e=null===t?this._pending:[...this._pending,{time:t.time,price:t.price}];this._layerFor(this._pendingPane).setPreview({id:"__preview",tool:this._tool,points:e,style:this._opts.defaultStyle,paneIndex:this._pendingPane})}_pushUndo(){this._undo.push(JSON.stringify(this._drawings)),this._undo.length>this._opts.historyLimit&&this._undo.shift(),this._redo=[]}}const Z="draw";G();export{M as ARROW,q as BUILTIN_DRAWING_TOOLS,L as CROSS_LINE,Z as DRAW_TIER,Q as DrawingController,X as DrawingLayer,O as ELLIPSE,v as EXTENDED_LINE,W as FIB_EXTENSION,R as FIB_RETRACEMENT,S as HORIZONTAL_LINE,P as HORIZONTAL_RAY,j as LONG_POSITION,$ as MEASURE,D as PARALLEL_CHANNEL,B as PATH,b as RAY,k as RECTANGLE,z as SHORT_POSITION,E as TEXT,w as TREND_LINE,T as VERTICAL_LINE,l as distToEllipse,e as distToLine,s as distToPolyline,o as distToRect,t as distToSegment,a as extendSegment,d as getDrawingTool,p as hasDrawingTool,r as rectOf,G as registerBuiltinDrawingTools,h as registerDrawingTool,u as registeredDrawingTools};
|
|
1
|
+
function t(t,e,i,s){const n=s.x-i.x,o=s.y-i.y,l=n*n+o*o;if(0===l)return Math.hypot(t-i.x,e-i.y);let r=((t-i.x)*n+(e-i.y)*o)/l;return r=r<0?0:r>1?1:r,Math.hypot(t-(i.x+r*n),e-(i.y+r*o))}function e(t,e,i,s){const n=s.x-i.x,o=s.y-i.y,l=Math.hypot(n,o);return 0===l?Math.hypot(t-i.x,e-i.y):Math.abs(o*(t-i.x)-n*(e-i.y))/l}function i(t,e){return Math.abs(t-e)}function s(t,e){return Math.abs(t-e)}function n(e,i,s){let n=1/0;for(let o=1;o<s.length;o++){const l=t(e,i,s[o-1],s[o]);l<n&&(n=l)}return n}function o(t,e){return{x0:Math.min(t.x,e.x),y0:Math.min(t.y,e.y),x1:Math.max(t.x,e.x),y1:Math.max(t.y,e.y)}}function l(e,i,s,n,l){const r=o(s,n);if(e>=r.x0&&e<=r.x1&&i>=r.y0&&i<=r.y1&&l)return 0;const a=[{x:r.x0,y:r.y0},{x:r.x1,y:r.y0},{x:r.x1,y:r.y1},{x:r.x0,y:r.y1}];let c=1/0;for(let s=0;s<4;s++){const n=t(e,i,a[s],a[(s+1)%4]);n<c&&(c=n)}return c}function r(t,e,i,s,n){const l=o(i,s),r=(l.x0+l.x1)/2,a=(l.y0+l.y1)/2,c=Math.max(1e-6,(l.x1-l.x0)/2),h=Math.max(1e-6,(l.y1-l.y0)/2),d=(t-r)/c,x=(e-a)/h,p=Math.hypot(d,x);return n&&p<=1?0:Math.abs(p-1)*Math.min(c,h)}function a(t,e,i,s,n){const o=e.x-t.x;if(0===o)return[t,e];const l=(e.y-t.y)/o;return[s?{x:0,y:t.y+(0-t.x)*l}:t,n?{x:i,y:t.y+(i-t.x)*l}:e]}const c=new Map;function h(t){c.set(t.id,t)}function d(t){const e=c.get(t);if(void 0===e)throw new Error(`openalgo-charts: unknown drawing tool "${t}"`);return e}function x(t){return c.has(t)}function p(){return Array.from(c.values())}function y(t){const{ctx:e,rc:i,style:s}=t,n=i.dpr;e.strokeStyle=s.color,e.lineWidth=Math.max(1,Math.round(s.lineWidth*n)),e.lineJoin="round",e.lineCap="round",e.setLineDash("dashed"===s.lineStyle?[6*n,4*n]:"dotted"===s.lineStyle?[1*n,3*n]:[])}function u(t){return t.style.fillColor??t.style.color}function f(t,e){if(!0!==t.style.fill)return;const{ctx:i}=t;i.save(),i.globalAlpha=t.style.fillOpacity??.12,i.fillStyle=u(t),e(),i.restore()}function g(t,e,i,s,n){const{ctx:o,rc:l,style:r}=t,a=(r.fontSize??11)*l.dpr;o.save(),o.setLineDash([]),o.font=`${a}px ui-sans-serif, system-ui, sans-serif`,o.textBaseline="middle",o.textAlign="left";const c=o.measureText(e).width;o.globalAlpha=.85,o.fillStyle=l.theme.background,o.fillRect(i-2*l.dpr,s-.75*a,c+4*l.dpr,1.5*a),o.globalAlpha=1,o.fillStyle=n??r.color,o.fillText(e,i,s),o.restore()}const m=[0,.236,.382,.5,.618,.786,1];function b(i,s,n,o){return{id:i,name:s,points:2,defaultStyle:{extendLeft:n,extendRight:o},draw:t=>{const[e,i]=a(t.pts[0],t.pts[1],t.rc.plotWidth*t.rc.dpr,t.style.extendLeft??n,t.style.extendRight??o);y(t),t.ctx.beginPath(),t.ctx.moveTo(e.x,e.y),t.ctx.lineTo(i.x,i.y),t.ctx.stroke(),t.ctx.setLineDash([])},distance:(i,s,l)=>{const r=l.drawing.style.extendLeft??n,c=l.drawing.style.extendRight??o;if(r&&c)return e(i,s,l.pts[0],l.pts[1]);const[h,d]=a(l.pts[0],l.pts[1],l.rc.plotWidth,r,c);return t(i,s,h,d)}}}const _=b("trend-line","Trend Line",!1,!1),w=b("ray","Ray",!1,!0),v=b("extended-line","Extended Line",!0,!0),M={id:"arrow",name:"Arrow",points:2,draw:t=>{const[e,i]=t.pts;y(t),t.ctx.beginPath(),t.ctx.moveTo(e.x,e.y),t.ctx.lineTo(i.x,i.y),t.ctx.stroke();const s=Math.max(8,5*t.style.lineWidth)*t.rc.dpr,n=Math.atan2(i.y-e.y,i.x-e.x);t.ctx.beginPath(),t.ctx.moveTo(i.x,i.y),t.ctx.lineTo(i.x-s*Math.cos(n-.4),i.y-s*Math.sin(n-.4)),t.ctx.lineTo(i.x-s*Math.cos(n+.4),i.y-s*Math.sin(n+.4)),t.ctx.closePath(),t.ctx.fillStyle=t.style.color,t.ctx.fill(),t.ctx.setLineDash([])},distance:(e,i,s)=>t(e,i,s.pts[0],s.pts[1])},T={id:"horizontal-line",name:"Horizontal Line",points:1,defaultStyle:{showLabels:!0},draw:t=>{const e=Math.round(t.pts[0].y)+.5;y(t),t.ctx.beginPath(),t.ctx.moveTo(0,e),t.ctx.lineTo(t.rc.plotWidth*t.rc.dpr,e),t.ctx.stroke(),t.ctx.setLineDash([]),!1!==t.style.showLabels&&g(t,t.formatPrice(t.drawing.points[0].price),4*t.rc.dpr,e-8*t.rc.dpr)},distance:(t,e,s)=>i(e,s.pts[0].y)},S={id:"horizontal-ray",name:"Horizontal Ray",points:1,defaultStyle:{showLabels:!0},draw:t=>{const e=Math.round(t.pts[0].y)+.5;y(t),t.ctx.beginPath(),t.ctx.moveTo(t.pts[0].x,e),t.ctx.lineTo(t.rc.plotWidth*t.rc.dpr,e),t.ctx.stroke(),t.ctx.setLineDash([]),!1!==t.style.showLabels&&g(t,t.formatPrice(t.drawing.points[0].price),t.pts[0].x+4*t.rc.dpr,e-8*t.rc.dpr)},distance:(t,e,s)=>t<s.pts[0].x?null:i(e,s.pts[0].y)},P={id:"vertical-line",name:"Vertical Line",points:1,draw:t=>{const e=Math.round(t.pts[0].x)+.5;y(t),t.ctx.beginPath(),t.ctx.moveTo(e,0),t.ctx.lineTo(e,t.rc.plotHeight*t.rc.dpr),t.ctx.stroke(),t.ctx.setLineDash([])},distance:(t,e,i)=>s(t,i.pts[0].x)},L={id:"cross-line",name:"Cross Line",points:1,draw:t=>{const e=Math.round(t.pts[0].x)+.5,i=Math.round(t.pts[0].y)+.5;y(t),t.ctx.beginPath(),t.ctx.moveTo(e,0),t.ctx.lineTo(e,t.rc.plotHeight*t.rc.dpr),t.ctx.moveTo(0,i),t.ctx.lineTo(t.rc.plotWidth*t.rc.dpr,i),t.ctx.stroke(),t.ctx.setLineDash([])},distance:(t,e,n)=>Math.min(s(t,n.pts[0].x),i(e,n.pts[0].y))},k={id:"rectangle",name:"Rectangle",points:2,defaultStyle:{fill:!0,fontSize:14,textAlign:"left",textVAlign:"top",textPosition:"inside"},draw:t=>{const e=o(t.pts[0],t.pts[1]);f(t,()=>t.ctx.fillRect(e.x0,e.y0,e.x1-e.x0,e.y1-e.y0)),y(t),t.ctx.strokeRect(e.x0,e.y0,e.x1-e.x0,e.y1-e.y0),t.ctx.setLineDash([]),J(t,e.x0,e.y0,e.x1,e.y1)},distance:(t,e,i)=>l(t,e,i.pts[0],i.pts[1],!0===i.drawing.style.fill)},O={id:"ellipse",name:"Ellipse",points:2,defaultStyle:{fill:!0,fontSize:14,textAlign:"center",textVAlign:"middle",textPosition:"inside"},draw:t=>{const e=o(t.pts[0],t.pts[1]),i=(e.x0+e.x1)/2,s=(e.y0+e.y1)/2,n=Math.max(1,(e.x1-e.x0)/2),l=Math.max(1,(e.y1-e.y0)/2),r=()=>{t.ctx.beginPath(),t.ctx.ellipse(i,s,n,l,0,0,2*Math.PI)};f(t,()=>{r(),t.ctx.fill()}),y(t),r(),t.ctx.stroke(),t.ctx.setLineDash([]),J(t,e.x0,e.y0,e.x1,e.y1)},distance:(t,e,i)=>r(t,e,i.pts[0],i.pts[1],!0===i.drawing.style.fill)},D={id:"parallel-channel",name:"Parallel Channel",points:3,defaultStyle:{fill:!0},draw:t=>{const[e,i,s]=t.pts,n=s.y-(e.y+.5*(i.y-e.y)),o={x:e.x,y:e.y+n},l={x:i.x,y:i.y+n};f(t,()=>{t.ctx.beginPath(),t.ctx.moveTo(e.x,e.y),t.ctx.lineTo(i.x,i.y),t.ctx.lineTo(l.x,l.y),t.ctx.lineTo(o.x,o.y),t.ctx.closePath(),t.ctx.fill()}),y(t),t.ctx.beginPath(),t.ctx.moveTo(e.x,e.y),t.ctx.lineTo(i.x,i.y),t.ctx.moveTo(o.x,o.y),t.ctx.lineTo(l.x,l.y),t.ctx.stroke(),t.ctx.setLineDash([]),J(t,Math.min(e.x,i.x),Math.min(e.y,i.y,o.y,l.y),Math.max(e.x,i.x),Math.max(e.y,i.y,o.y,l.y))},distance:(e,i,s)=>{const[n,o,l]=s.pts,r=l.y-(n.y+.5*(o.y-n.y)),a=t(e,i,n,o),c=t(e,i,{x:n.x,y:n.y+r},{x:o.x,y:o.y+r});return Math.min(a,c)}};function $(t,e,i){return{id:t,name:e,points:i,defaultStyle:{showLabels:!0,levels:[...m],fill:!0,fillOpacity:.06},draw:t=>{const e=t.style.levels??m,s=t.drawing.points,n=2===i?s[0].price:s[2].price,o=s[1].price-s[0].price,l=Math.min(t.pts[0].x,t.pts[i-1].x),r=Math.max(t.pts[0].x,t.pts[i-1].x),a=!0===t.style.extendRight?t.rc.plotWidth*t.rc.dpr:r;y(t);let c=null;for(const i of e){const e=n+o*i,s=Math.round(t.rc.priceScale.priceToY(e)*t.rc.dpr)+.5;!0===t.style.fill&&null!==c&&(t.ctx.save(),t.ctx.globalAlpha=t.style.fillOpacity??.06,t.ctx.fillStyle=u(t),t.ctx.fillRect(l,Math.min(c,s),a-l,Math.abs(s-c)),t.ctx.restore()),c=s,t.ctx.beginPath(),t.ctx.moveTo(l,s),t.ctx.lineTo(a,s),t.ctx.stroke(),!1!==t.style.showLabels&&g(t,`${(100*i).toFixed(1)}% ${t.formatPrice(e)}`,l+4*t.rc.dpr,s-8*t.rc.dpr)}t.ctx.setLineDash([])},distance:(t,e,s)=>{const n=s.drawing.style.levels??m,o=s.drawing.points,l=2===i?o[0].price:o[2].price,r=o[1].price-o[0].price,a=Math.min(s.pts[0].x,s.pts[i-1].x),c=!0===s.drawing.style.extendRight?s.rc.plotWidth:Math.max(s.pts[0].x,s.pts[i-1].x);if(t<a-4||t>c+4)return null;let h=1/0;for(const t of n){const i=Math.abs(e-s.rc.priceScale.priceToY(l+r*t));i<h&&(h=i)}return h}}}const A=$("fib-retracement","Fib Retracement",2),W=$("fib-extension","Fib Extension",3),F={id:"measure",name:"Measure",points:2,defaultStyle:{fill:!0,showLabels:!0,fillOpacity:.1},draw:t=>{const e=o(t.pts[0],t.pts[1]),i=t.drawing.points,s=i[1].price-i[0].price,n=0!==i[0].price?s/i[0].price*100:0,l=s>=0,r=l?"#26a69a":"#ef5350";t.ctx.save(),t.ctx.globalAlpha=t.style.fillOpacity??.1,t.ctx.fillStyle=r,t.ctx.fillRect(e.x0,e.y0,e.x1-e.x0,e.y1-e.y0),t.ctx.restore(),y(t),t.ctx.strokeStyle=r,t.ctx.strokeRect(e.x0,e.y0,e.x1-e.x0,e.y1-e.y0),t.ctx.setLineDash([]);const a=t.rc.dataLayer.timeToIndexFloat(i[0].time),c=t.rc.dataLayer.timeToIndexFloat(i[1].time),h=Math.abs(Math.round(c-a)),d=l?"+":"";g(t,`${d}${t.formatPrice(s)} (${d}${n.toFixed(2)}%) ${h} bars`,e.x0+4*t.rc.dpr,e.y0-10*t.rc.dpr,r)},distance:(t,e,i)=>l(t,e,i.pts[0],i.pts[1],!0)};function R(t,e,i){return{id:t,name:e,points:3,defaultStyle:{showLabels:!0,fillOpacity:.13,accountSize:1e5,risk:1},draw:t=>{const[e,s,n]=t.drawing.points,o=t.rc.dpr,l=Math.min(t.pts[0].x,t.pts[1].x,t.pts[2].x),r=Math.max(t.pts[0].x,t.pts[1].x,t.pts[2].x),a=t.rc.priceScale.priceToY(e.price)*o,c=t.rc.priceScale.priceToY(s.price)*o,h=t.rc.priceScale.priceToY(n.price)*o,d=(e,i,s)=>{t.ctx.save(),t.ctx.globalAlpha=t.style.fillOpacity??.13,t.ctx.fillStyle=s,t.ctx.fillRect(l,Math.min(e,i),r-l,Math.abs(i-e)),t.ctx.restore()};d(a,c,"#26a69a"),d(a,h,"#ef5350"),y(t);for(const e of[a,c,h]){const i=Math.round(e)+.5;t.ctx.beginPath(),t.ctx.moveTo(l,i),t.ctx.lineTo(r,i),t.ctx.stroke()}if(t.ctx.setLineDash([]),!1===t.style.showLabels)return;const x=Math.abs(e.price-n.price),p=Math.abs(s.price-e.price),u=x>0?p/x:0,f=t.style.accountSize??0,m=t.style.risk??0,b=x>0&&f>0?Math.floor(f*m/100/x):0;g(t,`${i?"LONG":"SHORT"} R:R ${u.toFixed(2)}${b>0?` qty ${b}`:""}`,l+4*o,a-10*o)},distance:(t,e,i)=>{const s=Math.min(i.pts[0].x,i.pts[1].x,i.pts[2].x),n=Math.max(i.pts[0].x,i.pts[1].x,i.pts[2].x);if(t<s-4||t>n+4)return null;const o=i.drawing.points.map(t=>i.rc.priceScale.priceToY(t.price)),l=Math.min(...o),r=Math.max(...o);return e>=l&&e<=r?0:Math.min(Math.abs(e-l),Math.abs(e-r))}}}const I=R("long-position","Long Position",!0),C=R("short-position","Short Position",!1),j=1.35,z="ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif";function N(t,e){const i="bold"===t.fontWeight?"700 ":"";return`${"italic"===t.fontStyle?"italic ":""}${i}${e}px ${t.fontFamily??z}`}function U(t,e,i){const s=(e.text??"").split("\n");if(!0!==e.wrap)return s;const n=[];for(const e of s){const s=e.split(/\s+/).filter(t=>""!==t);if(0===s.length){n.push("");continue}let o=s[0];for(let e=1;e<s.length;e++){const l=`${o} ${s[e]}`;t.measureText(l).width>i?(n.push(o),o=s[e]):o=l}n.push(o)}return n}function H(t,e,i){const s=(e.fontSize??13)*i;t.font=N(e,s);const n=U(t,e,(e.wrapWidth??220)*i);let o=0;for(const e of n)o=Math.max(o,t.measureText(e).width);const l=s*j;return{lines:n,width:o+10*i,height:n.length*l+10*i,lineHeight:l}}function J(t,e,i,s,n){const{ctx:o,rc:l,style:r}=t,a=r.text;if(void 0===a||""===a)return;const c=l.dpr,h=(r.fontSize??14)*c,d=6*c;o.save(),o.setLineDash([]),o.font=N(r,h),o.fillStyle=r.fontColor??r.color,o.textBaseline="top";const x=!0===r.wrap?U(o,r,Math.max(20*c,s-e-2*d)):a.split("\n"),p=h*j,y=x.length*p,u=r.textAlign??"left";o.textAlign=u;const f="center"===u?(e+s)/2:"right"===u?s-d:e+d;let g;if("outside"===r.textPosition)g=i-y-d;else{const t=r.textVAlign??"top";g="middle"===t?(i+n-y)/2:"bottom"===t?n-y-d:i+d}for(const t of x)o.fillText(t,f,g),g+=p;o.restore()}const E={id:"text",name:"Text",points:1,defaultStyle:{text:"Text",fontSize:14,fontWeight:"normal",fontStyle:"normal",background:!1,backgroundOpacity:1,border:!1,wrap:!1,wrapWidth:220,textAlign:"left"},draw:t=>{const{ctx:e,rc:i,style:s}=t,n=i.dpr,o=H(e,s,n),l=t.pts[0].x,r=t.pts[0].y;e.save(),e.setLineDash([]),!0===s.background&&(e.globalAlpha=s.backgroundOpacity??1,e.fillStyle=s.backgroundColor??i.theme.background,e.beginPath(),e.roundRect(l,r,o.width,o.height,4*n),e.fill(),e.globalAlpha=1),!0===s.border&&(e.strokeStyle=s.borderColor??s.color,e.lineWidth=Math.max(1,s.lineWidth*n),e.beginPath(),e.roundRect(l,r,o.width,o.height,4*n),e.stroke()),e.font=N(s,(s.fontSize??13)*n),e.fillStyle=s.color,e.textBaseline="top";const a=s.textAlign??"left";e.textAlign=a;const c="center"===a?l+o.width/2:"right"===a?l+o.width-5*n:l+5*n;let h=r+5*n;for(const t of o.lines)e.fillText(t,c,h),h+=o.lineHeight;e.restore()},distance:(t,e,i)=>{const s=i.drawing.style,n=s.fontSize??13,o=i.pts[0],l=function(){if(void 0!==B)return B;try{B="undefined"==typeof document?null:document.createElement("canvas").getContext("2d")}catch{B=null}return B}(),r=null===l?{width:(s.text??"").length*n*.6+10,height:n*j+10}:H(l,s,1);return t>=o.x-3&&t<=o.x+r.width+3&&e>=o.y-3&&e<=o.y+r.height+3?0:null}};let B;const Y={id:"path",name:"Path",points:0,draw:t=>{if(!(t.pts.length<2)){y(t),t.ctx.beginPath(),t.ctx.moveTo(t.pts[0].x,t.pts[0].y);for(let e=1;e<t.pts.length;e++)t.ctx.lineTo(t.pts[e].x,t.pts[e].y);t.ctx.stroke(),t.ctx.setLineDash([])}},distance:(t,e,i)=>i.pts.length<2?null:n(t,e,i.pts)},q={id:"price-range",name:"Price Range",points:2,defaultStyle:{fill:!0,showLabels:!0,fillOpacity:.1},draw:t=>{const e=t.rc.dpr,i=t.drawing.points,s=i[1].price-i[0].price,n=0!==i[0].price?s/i[0].price*100:0,o=s>=0,l=o?"#26a69a":"#ef5350",r=Math.min(t.pts[0].x,t.pts[1].x),a=Math.max(t.pts[0].x,t.pts[1].x)+(t.pts[0].x===t.pts[1].x?60*e:0),c=t.pts[0].y,h=t.pts[1].y;t.ctx.save(),t.ctx.globalAlpha=t.style.fillOpacity??.1,t.ctx.fillStyle=l,t.ctx.fillRect(r,Math.min(c,h),a-r,Math.abs(h-c)),t.ctx.restore(),y(t),t.ctx.strokeStyle=l,t.ctx.beginPath();for(const e of[c,h])t.ctx.moveTo(r,Math.round(e)+.5),t.ctx.lineTo(a,Math.round(e)+.5);const d=(r+a)/2;t.ctx.moveTo(d,c),t.ctx.lineTo(d,h);const x=h>c?1:-1;t.ctx.moveTo(d-4*e,h-5*e*x),t.ctx.lineTo(d,h),t.ctx.lineTo(d+4*e,h-5*e*x),t.ctx.moveTo(d-4*e,c+5*e*x),t.ctx.lineTo(d,c),t.ctx.lineTo(d+4*e,c+5*e*x),t.ctx.stroke(),t.ctx.setLineDash([]);const p=o?"+":"";g(t,`${p}${t.formatPrice(s)} (${p}${n.toFixed(2)}%)`,d+6*e,(c+h)/2,l)},distance:(t,e,i)=>l(t,e,i.pts[0],i.pts[1],!0)},V={id:"date-range",name:"Date Range",points:2,defaultStyle:{fill:!0,showLabels:!0,fillOpacity:.1},draw:t=>{const e=t.rc.dpr,i=t.drawing.points,s=t.style.color,n=t.pts[0].x,o=t.pts[1].x,l=Math.min(t.pts[0].y,t.pts[1].y),r=Math.max(t.pts[0].y,t.pts[1].y)+(t.pts[0].y===t.pts[1].y?40*e:0);t.ctx.save(),t.ctx.globalAlpha=t.style.fillOpacity??.1,t.ctx.fillStyle=s,t.ctx.fillRect(Math.min(n,o),l,Math.abs(o-n),r-l),t.ctx.restore(),y(t),t.ctx.beginPath();for(const e of[n,o])t.ctx.moveTo(Math.round(e)+.5,l),t.ctx.lineTo(Math.round(e)+.5,r);const a=(l+r)/2;t.ctx.moveTo(n,a),t.ctx.lineTo(o,a);const c=o>n?1:-1;t.ctx.moveTo(o-5*e*c,a-4*e),t.ctx.lineTo(o,a),t.ctx.lineTo(o-5*e*c,a+4*e),t.ctx.moveTo(n+5*e*c,a-4*e),t.ctx.lineTo(n,a),t.ctx.lineTo(n+5*e*c,a+4*e),t.ctx.stroke(),t.ctx.setLineDash([]);const h=t.rc.dataLayer.timeToIndexFloat(i[0].time),d=t.rc.dataLayer.timeToIndexFloat(i[1].time);g(t,`${Math.abs(Math.round(d-h))} bars`,(n+o)/2,l-10*e,s)},distance:(t,e,i)=>l(t,e,i.pts[0],i.pts[1],!0)},G={id:"forecast",name:"Forecast",points:2,defaultStyle:{fill:!0,showLabels:!0,fillOpacity:.12,lineStyle:"dashed"},draw:t=>{const e=t.rc.dpr,i=t.drawing.points,[s,n]=t.pts,o=i[1].price-i[0].price,l=0!==i[0].price?o/i[0].price*100:0,r=o>=0,a=r?"#26a69a":"#ef5350",c=.35*Math.abs(n.y-s.y)+6*e;t.ctx.save(),t.ctx.globalAlpha=t.style.fillOpacity??.12,t.ctx.fillStyle=a,t.ctx.beginPath(),t.ctx.moveTo(s.x,s.y),t.ctx.lineTo(n.x,n.y-c),t.ctx.lineTo(n.x,n.y+c),t.ctx.closePath(),t.ctx.fill(),t.ctx.restore(),y(t),t.ctx.strokeStyle=a,t.ctx.beginPath(),t.ctx.moveTo(s.x,s.y),t.ctx.lineTo(n.x,n.y),t.ctx.stroke(),t.ctx.setLineDash([]),t.ctx.beginPath(),t.ctx.arc(s.x,s.y,3*e,0,2*Math.PI),t.ctx.fillStyle=a,t.ctx.fill();const h=t.rc.dataLayer.timeToIndexFloat(i[0].time),d=t.rc.dataLayer.timeToIndexFloat(i[1].time),x=Math.abs(Math.round(d-h)),p=r?"+":"";g(t,`${p}${t.formatPrice(o)} (${p}${l.toFixed(2)}%) ${x} bars`,n.x+6*e,n.y,a)},distance:(e,i,s)=>t(e,i,s.pts[0],s.pts[1])},X={id:"circle",name:"Circle",points:2,defaultStyle:{fill:!0},draw:t=>{const[e,i]=t.pts,s=Math.hypot(i.x-e.x,i.y-e.y);t.ctx.beginPath(),t.ctx.arc(e.x,e.y,s,0,2*Math.PI),f(t,()=>t.ctx.fill()),y(t),t.ctx.stroke(),t.ctx.setLineDash([])},distance:(t,e,i)=>{const[s,n]=i.pts,o=Math.hypot(n.x-s.x,n.y-s.y),l=Math.hypot(t-s.x,e-s.y);return!0===i.drawing.style.fill&&l<=o?0:Math.abs(l-o)}},Z={id:"triangle",name:"Triangle",points:3,defaultStyle:{fill:!0},draw:t=>{t.ctx.beginPath(),t.ctx.moveTo(t.pts[0].x,t.pts[0].y),t.ctx.lineTo(t.pts[1].x,t.pts[1].y),t.ctx.lineTo(t.pts[2].x,t.pts[2].y),t.ctx.closePath(),f(t,()=>t.ctx.fill()),y(t),t.ctx.stroke(),t.ctx.setLineDash([])},distance:(t,e,i)=>n(t,e,[...i.pts,i.pts[0]])},K={id:"polyline",name:"Polyline",points:0,defaultStyle:{fill:!1},draw:t=>{if(!(t.pts.length<2)){t.ctx.beginPath(),t.ctx.moveTo(t.pts[0].x,t.pts[0].y);for(let e=1;e<t.pts.length;e++)t.ctx.lineTo(t.pts[e].x,t.pts[e].y);!0===t.style.fill&&(t.ctx.closePath(),f(t,()=>t.ctx.fill())),y(t),t.ctx.stroke(),t.ctx.setLineDash([])}},distance:(t,e,i)=>n(t,e,i.pts)};const Q={id:"arc",name:"Arc",points:3,draw:t=>{const[e,i,s]=t.pts;y(t),t.ctx.beginPath(),t.ctx.moveTo(e.x,e.y),t.ctx.quadraticCurveTo(2*i.x-(e.x+s.x)/2,2*i.y-(e.y+s.y)/2,s.x,s.y),t.ctx.stroke(),t.ctx.setLineDash([])},distance:(t,e,i)=>n(t,e,function(t,e,i){const s=2*e.x-(t.x+i.x)/2,n=2*e.y-(t.y+i.y)/2,o=[];for(let e=0;e<=16;e++){const l=e/16,r=1-l;o.push({x:r*r*t.x+2*r*l*s+l*l*i.x,y:r*r*t.y+2*r*l*n+l*l*i.y})}return o}(i.pts[0],i.pts[1],i.pts[2]))},tt={id:"curve",name:"Curve",points:3,draw:t=>{const[e,i,s]=t.pts;y(t),t.ctx.beginPath(),t.ctx.moveTo(e.x,e.y),t.ctx.quadraticCurveTo(i.x,i.y,s.x,s.y),t.ctx.stroke(),t.ctx.setLineDash([])},distance:(t,e,i)=>{const[s,o,l]=i.pts,r=[];for(let t=0;t<=16;t++){const e=t/16,i=1-e;r.push({x:i*i*s.x+2*i*e*o.x+e*e*l.x,y:i*i*s.y+2*i*e*o.y+e*e*l.y})}return n(t,e,r)}};function et(t,e,i){return{id:t,name:e,points:1,defaultStyle:{fill:!0,fillOpacity:.9},draw:t=>{const e=t.rc.dpr,{x:s,y:n}=t.pts[0],o=16*e,l=6*e,r=i?1:-1,a=n+2*r*e;t.ctx.beginPath(),t.ctx.moveTo(s,a),t.ctx.lineTo(s-l,a+r*l),t.ctx.lineTo(s-l/2.4,a+r*l),t.ctx.lineTo(s-l/2.4,a+r*o),t.ctx.lineTo(s+l/2.4,a+r*o),t.ctx.lineTo(s+l/2.4,a+r*l),t.ctx.lineTo(s+l,a+r*l),t.ctx.closePath(),t.ctx.save(),t.ctx.globalAlpha=t.style.fillOpacity??.9,t.ctx.fillStyle=u(t),t.ctx.fill(),t.ctx.restore(),y(t),t.ctx.stroke(),t.ctx.setLineDash([])},distance:(t,e,s)=>{const n=s.pts[0],o=i?e-n.y:n.y-e;return Math.abs(t-n.x)<=8&&o>=-4&&o<=20?0:null}}}const it=et("arrow-up","Arrow Up",!0),st=et("arrow-down","Arrow Down",!1),nt={id:"highlighter",name:"Highlighter",points:0,defaultStyle:{lineWidth:12,fillOpacity:.28},draw:t=>{if(t.pts.length<2)return;const e=t.rc.dpr;t.ctx.save(),t.ctx.globalAlpha=t.style.fillOpacity??.28,t.ctx.strokeStyle=t.style.color,t.ctx.lineWidth=Math.max(2,(t.style.lineWidth||12)*e),t.ctx.lineCap="round",t.ctx.lineJoin="round",t.ctx.beginPath(),t.ctx.moveTo(t.pts[0].x,t.pts[0].y);for(let e=1;e<t.pts.length;e++)t.ctx.lineTo(t.pts[e].x,t.pts[e].y);t.ctx.stroke(),t.ctx.restore()},distance:(t,e,i)=>{const s=n(t,e,i.pts);return s<=Math.max(6,(i.drawing.style.lineWidth??12)/2)?0:s}},ot={id:"fib-channel",name:"Fib Channel",points:3,defaultStyle:{showLabels:!0,levels:[...m]},draw:t=>{const e=t.style.levels??m,[i,s,n]=t.pts,o=n.x-s.x,l=n.y-s.y;y(t);for(const n of e)t.ctx.beginPath(),t.ctx.moveTo(i.x+o*n,i.y+l*n),t.ctx.lineTo(s.x+o*n,s.y+l*n),t.ctx.stroke(),!1!==t.style.showLabels&&g(t,`${(100*n).toFixed(1)}%`,s.x+o*n+4*t.rc.dpr,s.y+l*n);t.ctx.setLineDash([])},distance:(e,i,s)=>{const n=s.drawing.style.levels??m,[o,l,r]=s.pts;let a=1/0;for(const s of n){const n=t(e,i,{x:o.x+(r.x-l.x)*s,y:o.y+(r.y-l.y)*s},{x:l.x+(r.x-l.x)*s,y:l.y+(r.y-l.y)*s});n<a&&(a=n)}return a}},lt=[0,1,2,3,5,8,13,21,34,55],rt={id:"fib-time-zone",name:"Fib Time Zone",points:2,defaultStyle:{showLabels:!0},draw:t=>{const[e,i]=t.pts,s=i.x-e.x;if(Math.abs(s)<.5)return;const n=t.rc.dpr,o=t.rc.plotWidth*n;y(t);for(const i of lt){const l=e.x+s*i;l<-10||l>o+10||(t.ctx.beginPath(),t.ctx.moveTo(Math.round(l)+.5,0),t.ctx.lineTo(Math.round(l)+.5,t.rc.plotHeight*n),t.ctx.stroke(),!1!==t.style.showLabels&&g(t,String(i),l+3*n,12*n))}t.ctx.setLineDash([])},distance:(t,e,i)=>{const[s,n]=i.pts,o=n.x-s.x;if(Math.abs(o)<.5)return null;let l=1/0;for(const e of lt)l=Math.min(l,Math.abs(t-(s.x+o*e)));return l}},at=[.236,.382,.5,.618,.786,1],ct={id:"fib-fan",name:"Fib Speed Fan",points:2,defaultStyle:{showLabels:!0,levels:[...at]},draw:t=>{const e=t.style.levels??at,[i,s]=t.pts,n=t.rc.dpr,o=t.rc.plotWidth*n;y(t);for(const l of e){const e=a(i,{x:s.x,y:i.y+(s.y-i.y)*l},o,!1,!0)[1];t.ctx.beginPath(),t.ctx.moveTo(i.x,i.y),t.ctx.lineTo(e.x,e.y),t.ctx.stroke(),!1!==t.style.showLabels&&g(t,`${(100*l).toFixed(1)}%`,s.x+4*n,i.y+(s.y-i.y)*l)}t.ctx.setLineDash([])},distance:(e,i,s)=>{const n=s.drawing.style.levels??at,[o,l]=s.pts;let r=1/0;for(const c of n)r=Math.min(r,t(e,i,o,a(o,{x:l.x,y:o.y+(l.y-o.y)*c},s.rc.plotWidth,!1,!0)[1]));return r}},ht=[[1,.125],[1,.25],[1,.5],[1,1],[.5,1],[.25,1],[.125,1]],dt=[_,w,v,M,T,S,P,L,k,O,D,A,W,I,C,G,F,q,V,X,Z,K,Q,tt,it,st,nt,ot,rt,ct,{id:"gann-fan",name:"Gann Fan",points:2,defaultStyle:{showLabels:!0},draw:t=>{const[e,i]=t.pts,s=t.rc.dpr,n=t.rc.plotWidth*s,o=i.x-e.x,l=i.y-e.y;if(!(Math.abs(o)<.5)){y(t);for(const[i,s]of ht){const r=a(e,{x:e.x+o*i,y:e.y+l*s},n,!1,!0)[1];t.ctx.beginPath(),t.ctx.moveTo(e.x,e.y),t.ctx.lineTo(r.x,r.y),t.ctx.stroke()}!1!==t.style.showLabels&&g(t,"1x1",i.x+4*s,i.y),t.ctx.setLineDash([])}},distance:(e,i,s)=>{const[n,o]=s.pts,l=o.x-n.x,r=o.y-n.y;let c=1/0;for(const[o,h]of ht)c=Math.min(c,t(e,i,n,a(n,{x:n.x+l*o,y:n.y+r*h},s.rc.plotWidth,!1,!0)[1]));return c}},{id:"gann-box",name:"Gann Box",points:2,defaultStyle:{fill:!0,fillOpacity:.05},draw:t=>{const e=o(t.pts[0],t.pts[1]);f(t,()=>t.ctx.fillRect(e.x0,e.y0,e.x1-e.x0,e.y1-e.y0)),y(t);const i=e.x1-e.x0,s=e.y1-e.y0;t.ctx.beginPath();for(let n=0;n<=8;n++){const o=Math.round(e.x0+i*n/8)+.5,l=Math.round(e.y0+s*n/8)+.5;t.ctx.moveTo(o,e.y0),t.ctx.lineTo(o,e.y1),t.ctx.moveTo(e.x0,l),t.ctx.lineTo(e.x1,l)}t.ctx.moveTo(e.x0,e.y1),t.ctx.lineTo(e.x1,e.y0),t.ctx.stroke(),t.ctx.setLineDash([])},distance:(t,e,i)=>l(t,e,i.pts[0],i.pts[1],!0===i.drawing.style.fill)},E,Y];let xt=!1;function pt(){if(!xt){xt=!0;for(const t of dt)h(t)}}class yt{constructor(){Object.defineProperty(this,"_drawings",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"_host",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"_selected",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"_preview",{enumerable:!0,configurable:!0,writable:!0,value:null})}attached(t){this._host=t}detached(){this._host=null}zOrder(){return"top"}autoscaleInfo(){return null}setDrawings(t){this._drawings=t.slice(),this._host?.requestUpdate()}setSelected(t){t!==this._selected&&(this._selected=t,this._host?.requestUpdate())}setPreview(t){this._preview=t,this._host?.requestUpdate()}_project(t,e,i){return{x:t.timeScale.indexToX(t.dataLayer.timeToIndexFloat(e)),y:t.priceScale.priceToY(i)}}_points(t,e){return e.points.map(e=>this._project(t,e.time,e.price))}draw(t,e){const i=null===this._preview?this._drawings:[...this._drawings,this._preview];for(const s of i){if(!1===s.visible||!x(s.tool))continue;const i=d(s.tool);if(s.points.length<Math.max(1,i.points))continue;const n=this._points(e,s);if(!n.some(t=>Number.isFinite(t.x)&&Number.isFinite(t.y)))continue;const o={color:s.style.color??e.theme.lineColor,lineWidth:s.style.lineWidth??1.5,...s.style},l=e.dpr;t.save(),s===this._preview&&(t.globalAlpha=.7),i.draw({ctx:t,rc:e,drawing:s,selected:s.id===this._selected,pts:n.map(t=>({x:t.x*l,y:t.y*l})),style:{...o,color:o.color,lineWidth:o.lineWidth},formatPrice:t=>e.priceScale.format(t)}),t.restore(),s.id===this._selected&&!0!==s.locked&&this._drawHandles(t,e,n)}}_drawHandles(t,e,i){const s=e.dpr;t.save(),t.setLineDash([]),t.lineWidth=Math.max(1,Math.round(1.5*s));for(const n of i)t.beginPath(),t.arc(n.x*s,n.y*s,5*s,0,2*Math.PI),t.fillStyle=e.theme.background,t.fill(),t.strokeStyle=e.theme.lineColor,t.stroke();t.restore()}hitTest(t,e,i){if(null!==this._selected){const s=this._drawings.find(t=>t.id===this._selected);if(void 0!==s&&!0!==s.locked){const n=this._points(i,s);for(let i=0;i<n.length;i++)if(Math.hypot(t-n[i].x,e-n[i].y)<=7)return{externalId:`draw:${s.id}#${i}`,zOrder:"top",distance:0,cursor:"grabbing",draggable:!0}}}let s=null;for(let n=this._drawings.length-1;n>=0;n--){const o=this._drawings[n];if(!1===o.visible||!0===o.locked||!x(o.tool))continue;const l=d(o.tool);if(o.points.length<Math.max(1,l.points))continue;const r=l.distance(t,e,{pts:this._points(i,o),drawing:o,rc:i});null===r||!Number.isFinite(r)||r>6||(null===s||r<s.distance)&&(s={id:o.id,distance:r})}return null===s?null:{externalId:`draw:${s.id}`,zOrder:"top",distance:s.distance,cursor:"move",draggable:!0}}}let ut=1;class ft{constructor(t,e={}){Object.defineProperty(this,"_chart",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_opts",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"_layers",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"_drawings",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"_tool",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"_pending",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"_pendingPane",{enumerable:!0,configurable:!0,writable:!0,value:0}),Object.defineProperty(this,"_selected",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"_undo",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"_redo",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"_dragStart",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"_off",{enumerable:!0,configurable:!0,writable:!0,value:[]}),Object.defineProperty(this,"_lastCursor",{enumerable:!0,configurable:!0,writable:!0,value:null}),Object.defineProperty(this,"_lastBar",{enumerable:!0,configurable:!0,writable:!0,value:null}),this._chart=t,this._opts={magnet:e.magnet??!1,stayInDrawingMode:e.stayInDrawingMode??!1,historyLimit:e.historyLimit??50,defaultStyle:e.defaultStyle??{}},this._off.push(t.on("click",t=>this._onClick(t))),this._off.push(t.on("crosshair:move",t=>this._onCrosshair(t))),this._off.push(t.on("drag",t=>this._onDrag(t))),this._off.push(t.on("drag:end",()=>this._onDragEnd()));const i=t.drawingState();Array.isArray(i)&&(this._drawings=i.map(t=>({...t}))),this._sync()}setTool(t){if(null!==t&&!x(t))throw new Error(`openalgo-charts: unknown drawing tool "${t}"`);this._tool=t,this._pending=[],this._setPlacementMode(null!==t),this._syncPreview(),this._chart.emit("draw:tool",{tool:t})}_setPlacementMode(t){const e=this._chart;e.setPlacementMode?.(t)}activeTool(){return this._tool}setOptions(t){this._opts={...this._opts,...t,defaultStyle:t.defaultStyle??this._opts.defaultStyle}}drawings(){return this._drawings}get(t){return this._drawings.find(e=>e.id===t)}add(t){this._pushUndo();const e=d(t.tool),i={...t,id:t.id??"d"+ut++,style:{...this._opts.defaultStyle,...e.defaultStyle,...t.style}};return this._drawings.push(i),this._sync(),this._chart.emit("draw:add",{drawing:i}),i}update(t,e){const i=this.get(t);return void 0!==i&&(this._pushUndo(),void 0!==e.points&&(i.points=e.points.map(t=>({...t}))),void 0!==e.style&&(i.style={...i.style,...e.style}),void 0!==e.locked&&(i.locked=e.locked),void 0!==e.visible&&(i.visible=e.visible),this._sync(),this._chart.emit("draw:update",{drawing:i}),!0)}remove(t){const e=this._drawings.findIndex(e=>e.id===t);if(e<0)return!1;this._pushUndo();const[i]=this._drawings.splice(e,1);return this._selected===t&&(this._selected=null),this._sync(),this._chart.emit("draw:remove",{drawing:i}),!0}clear(){0!==this._drawings.length&&(this._pushUndo(),this._drawings=[],this._selected=null,this._sync())}select(t){this._selected=t;for(const e of this._layers.values())e.setSelected(t);this._chart.emit("draw:select",{id:t})}selected(){return this._selected}undo(){const t=this._undo.pop();return void 0!==t&&(this._redo.push(JSON.stringify(this._drawings)),this._drawings=JSON.parse(t),this._drawings.some(t=>t.id===this._selected)||(this._selected=null),this._sync(),!0)}redo(){const t=this._redo.pop();return void 0!==t&&(this._undo.push(JSON.stringify(this._drawings)),this._drawings=JSON.parse(t),this._sync(),!0)}canUndo(){return this._undo.length>0}canRedo(){return this._redo.length>0}toJSON(){return this._drawings.map(t=>({...t,points:t.points.map(t=>({...t})),style:{...t.style}}))}fromJSON(t){this._drawings=Array.isArray(t)?t.map(t=>({...t})):[],this._selected=null,this._undo=[],this._redo=[],this._sync()}destroy(){this._setPlacementMode(!1);for(const t of this._off)t();this._off.length=0;for(const[t,e]of this._layers)this._chart.removePrimitive(e);this._layers.clear()}_onCrosshair(t){const e=t.time,i=t.price,s=t.paneIndex??null;this._lastCursor=null===e||null===i||null===s?null:{time:e,price:i,paneIndex:s},this._lastBar=t.bar??null,null!==this._tool&&this._pending.length>0&&this._syncPreview()}_onClick(t){if(null===this._tool)null!==t.id&&t.id.startsWith("draw:")?this.select(t.id.slice(5).split("#")[0]):null===t.id&&this.select(null);else{if(!0===t.viaDrag&&0===this._pending.length)return;if(null===t.price||!Number.isFinite(t.price)||!Number.isFinite(t.time))return;this._placePoint({time:t.time,price:this._snap(t.price,t.paneIndex)},t.paneIndex)}}_placePoint(t,e){0===this._pending.length&&(this._pendingPane=e),this._pending.push(t);const i=d(this._tool);if(i.points>0&&this._pending.length>=i.points){const t=this.add({tool:i.id,points:this._pending,style:{},paneIndex:this._pendingPane});this._pending=[],this._opts.stayInDrawingMode||(this._tool=null,this._setPlacementMode(!1)),this._syncPreview(),this.select(t.id),this._chart.emit("draw:tool",{tool:this._tool})}else this._syncPreview()}_snap(t,e){if(!this._opts.magnet||0!==e)return t;const i=this._lastBar;if(null===i)return t;let s=t,n=1/0;for(const e of[i.open,i.high,i.low,i.close]){const i=Math.abs(e-t);i<n&&(n=i,s=e)}return s}_onDrag(t){if(!t.id.startsWith("draw:"))return;const[e,i]=t.id.slice(5).split("#"),s=this.get(e);if(void 0===s||!0===s.locked)return;const n=void 0===i?null:Number(i);null!==this._dragStart&&this._dragStart.id===e&&this._dragStart.handle===n||(this._pushUndo(),this._dragStart={id:e,handle:n,points:s.points.map(t=>({...t})),from:{time:t.fromTime??t.time,price:t.fromPrice??t.price}});const o=this._dragStart;if(null===n){const e=t.time-o.from.time,i=t.price-o.from.price;s.points=o.points.map(t=>({time:t.time+e,price:t.price+i}))}else n>=0&&n<s.points.length&&(s.points=o.points.map((e,i)=>i===n?{time:t.time,price:t.price}:{...e}));this._sync()}_onDragEnd(){if(null===this._dragStart)return;const t=this.get(this._dragStart.id);this._dragStart=null,void 0!==t&&this._chart.emit("draw:update",{drawing:t})}_layerFor(t){let e=this._layers.get(t);return void 0===e&&(e=new yt,this._chart.addPrimitive(e,t),this._layers.set(t,e)),e}_sync(){const t=new Map;for(const e of this._drawings){const i=t.get(e.paneIndex)??[];i.push(e),t.set(e.paneIndex,i)}for(const[e,i]of t)this._layerFor(e).setDrawings(i);for(const[e,i]of this._layers)t.has(e)||i.setDrawings([]),i.setSelected(this._selected);this._chart.setDrawingState(this.toJSON())}_syncPreview(){for(const t of this._layers.values())t.setPreview(null);if(null===this._tool||0===this._pending.length)return;const t=this._lastCursor,e=null===t?this._pending:[...this._pending,{time:t.time,price:t.price}];this._layerFor(this._pendingPane).setPreview({id:"__preview",tool:this._tool,points:e,style:this._opts.defaultStyle,paneIndex:this._pendingPane})}_pushUndo(){this._undo.push(JSON.stringify(this._drawings)),this._undo.length>this._opts.historyLimit&&this._undo.shift(),this._redo=[]}}const gt="draw";pt();export{M as ARROW,dt as BUILTIN_DRAWING_TOOLS,L as CROSS_LINE,gt as DRAW_TIER,ft as DrawingController,yt as DrawingLayer,O as ELLIPSE,v as EXTENDED_LINE,W as FIB_EXTENSION,A as FIB_RETRACEMENT,T as HORIZONTAL_LINE,S as HORIZONTAL_RAY,I as LONG_POSITION,F as MEASURE,D as PARALLEL_CHANNEL,Y as PATH,w as RAY,k as RECTANGLE,C as SHORT_POSITION,E as TEXT,_ as TREND_LINE,P as VERTICAL_LINE,r as distToEllipse,e as distToLine,n as distToPolyline,l as distToRect,t as distToSegment,a as extendSegment,d as getDrawingTool,x as hasDrawingTool,o as rectOf,pt as registerBuiltinDrawingTools,h as registerDrawingTool,p as registeredDrawingTools};
|
|
2
2
|
//# sourceMappingURL=openalgo-charts.draw.mjs.map
|