@vroomchart/core-wasm 0.1.0 → 0.1.2
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/assets/vroom_core.mjs +1 -1
- package/assets/vroom_core.wasm +0 -0
- package/dist/index.d.ts +55 -14
- package/dist/index.js +113 -460
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,455 +2,6 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
|
|
5
|
-
// src/handle.ts
|
|
6
|
-
var ColorKey = /* @__PURE__ */ ((ColorKey2) => {
|
|
7
|
-
ColorKey2[ColorKey2["Background"] = 0] = "Background";
|
|
8
|
-
ColorKey2[ColorKey2["Bull"] = 1] = "Bull";
|
|
9
|
-
ColorKey2[ColorKey2["Bear"] = 2] = "Bear";
|
|
10
|
-
ColorKey2[ColorKey2["Wick"] = 3] = "Wick";
|
|
11
|
-
ColorKey2[ColorKey2["Grid"] = 4] = "Grid";
|
|
12
|
-
ColorKey2[ColorKey2["AxisText"] = 5] = "AxisText";
|
|
13
|
-
ColorKey2[ColorKey2["Crosshair"] = 6] = "Crosshair";
|
|
14
|
-
ColorKey2[ColorKey2["TooltipBg"] = 7] = "TooltipBg";
|
|
15
|
-
ColorKey2[ColorKey2["TooltipText"] = 8] = "TooltipText";
|
|
16
|
-
ColorKey2[ColorKey2["CrosshairTarget"] = 9] = "CrosshairTarget";
|
|
17
|
-
return ColorKey2;
|
|
18
|
-
})(ColorKey || {});
|
|
19
|
-
|
|
20
|
-
// src/color.ts
|
|
21
|
-
var COLOR_KEYS = {
|
|
22
|
-
background: 0 /* Background */,
|
|
23
|
-
bull: 1 /* Bull */,
|
|
24
|
-
bear: 2 /* Bear */,
|
|
25
|
-
grid: 4 /* Grid */,
|
|
26
|
-
axisText: 5 /* AxisText */,
|
|
27
|
-
crosshair: 6 /* Crosshair */,
|
|
28
|
-
crosshairTarget: 9 /* CrosshairTarget */
|
|
29
|
-
};
|
|
30
|
-
function parseColor(value) {
|
|
31
|
-
if (typeof value === "number") {
|
|
32
|
-
return Number.isFinite(value) ? value >>> 0 : null;
|
|
33
|
-
}
|
|
34
|
-
let s = value.trim();
|
|
35
|
-
if (s.startsWith("#")) s = s.slice(1);
|
|
36
|
-
if (s.length === 6) s = `ff${s}`;
|
|
37
|
-
if (s.length !== 8 || !/^[0-9a-fA-F]{8}$/.test(s)) return null;
|
|
38
|
-
return parseInt(s, 16) >>> 0;
|
|
39
|
-
}
|
|
40
|
-
function applyTheme(handle, theme) {
|
|
41
|
-
Object.keys(COLOR_KEYS).forEach((field) => {
|
|
42
|
-
const value = theme[field];
|
|
43
|
-
if (value == null) return;
|
|
44
|
-
const argb = parseColor(value);
|
|
45
|
-
if (argb == null) return;
|
|
46
|
-
handle.setColor(COLOR_KEYS[field], argb);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
function argbToCss(argb) {
|
|
50
|
-
const a = (argb >>> 24 & 255) / 255;
|
|
51
|
-
const r = argb >>> 16 & 255;
|
|
52
|
-
const g = argb >>> 8 & 255;
|
|
53
|
-
const b = argb & 255;
|
|
54
|
-
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// src/packCandles.ts
|
|
58
|
-
var BYTES_PER_CANDLE = 48;
|
|
59
|
-
function packCandles(candles) {
|
|
60
|
-
const buf = new ArrayBuffer(candles.length * BYTES_PER_CANDLE);
|
|
61
|
-
const view = new DataView(buf);
|
|
62
|
-
for (let i = 0; i < candles.length; i++) {
|
|
63
|
-
const c = candles[i];
|
|
64
|
-
const off = i * BYTES_PER_CANDLE;
|
|
65
|
-
view.setBigInt64(off, BigInt(Math.trunc(c.timeMs)), true);
|
|
66
|
-
view.setFloat64(off + 8, c.open, true);
|
|
67
|
-
view.setFloat64(off + 16, c.high, true);
|
|
68
|
-
view.setFloat64(off + 24, c.low, true);
|
|
69
|
-
view.setFloat64(off + 32, c.close, true);
|
|
70
|
-
view.setFloat64(off + 40, c.volume, true);
|
|
71
|
-
}
|
|
72
|
-
return buf;
|
|
73
|
-
}
|
|
74
|
-
function unpackCandles(buf) {
|
|
75
|
-
const view = new DataView(buf);
|
|
76
|
-
const n = Math.floor(buf.byteLength / BYTES_PER_CANDLE);
|
|
77
|
-
const out = new Array(n);
|
|
78
|
-
for (let i = 0; i < n; i++) {
|
|
79
|
-
const off = i * BYTES_PER_CANDLE;
|
|
80
|
-
out[i] = {
|
|
81
|
-
timeMs: Number(view.getBigInt64(off, true)),
|
|
82
|
-
open: view.getFloat64(off + 8, true),
|
|
83
|
-
high: view.getFloat64(off + 16, true),
|
|
84
|
-
low: view.getFloat64(off + 24, true),
|
|
85
|
-
close: view.getFloat64(off + 32, true),
|
|
86
|
-
volume: view.getFloat64(off + 40, true)
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
return out;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// src/stub/stubModule.ts
|
|
93
|
-
var DEFAULT_VISIBLE = 80;
|
|
94
|
-
var Y_AXIS_W = 58;
|
|
95
|
-
var X_AXIS_H = 22;
|
|
96
|
-
var RIGHT_PAD_CANDLES = 4;
|
|
97
|
-
var DEFAULT_COLORS = {
|
|
98
|
-
[0 /* Background */]: 4279046423,
|
|
99
|
-
[1 /* Bull */]: 4280723098,
|
|
100
|
-
[2 /* Bear */]: 4293874512,
|
|
101
|
-
[3 /* Wick */]: 4286086022,
|
|
102
|
-
[4 /* Grid */]: 4280231472,
|
|
103
|
-
[5 /* AxisText */]: 4287337630,
|
|
104
|
-
[6 /* Crosshair */]: 4291416537,
|
|
105
|
-
[9 /* CrosshairTarget */]: 4291416537
|
|
106
|
-
};
|
|
107
|
-
function median(xs) {
|
|
108
|
-
if (xs.length === 0) return 0;
|
|
109
|
-
const s = [...xs].sort((a, b) => a - b);
|
|
110
|
-
const mid = s.length >> 1;
|
|
111
|
-
return s.length % 2 ? s[mid] : (s[mid - 1] + s[mid]) / 2;
|
|
112
|
-
}
|
|
113
|
-
function niceStep(raw) {
|
|
114
|
-
if (raw <= 0 || !Number.isFinite(raw)) return 1;
|
|
115
|
-
const mag = Math.pow(10, Math.floor(Math.log10(raw)));
|
|
116
|
-
const norm = raw / mag;
|
|
117
|
-
const step = norm <= 1 ? 1 : norm <= 2 ? 2 : norm <= 5 ? 5 : 10;
|
|
118
|
-
return step * mag;
|
|
119
|
-
}
|
|
120
|
-
var StubChart = class {
|
|
121
|
-
constructor(canvas) {
|
|
122
|
-
__publicField(this, "canvas", canvas);
|
|
123
|
-
__publicField(this, "ctx");
|
|
124
|
-
__publicField(this, "candles", []);
|
|
125
|
-
__publicField(this, "durationMs", 6e4);
|
|
126
|
-
__publicField(this, "wCss", 0);
|
|
127
|
-
__publicField(this, "hCss", 0);
|
|
128
|
-
__publicField(this, "dpr", 1);
|
|
129
|
-
__publicField(this, "visStart", 0);
|
|
130
|
-
__publicField(this, "visEnd", 0);
|
|
131
|
-
__publicField(this, "priceMin", 0);
|
|
132
|
-
__publicField(this, "priceMax", 1);
|
|
133
|
-
__publicField(this, "rangeInit", false);
|
|
134
|
-
__publicField(this, "crosshair", false);
|
|
135
|
-
__publicField(this, "chX", 0);
|
|
136
|
-
__publicField(this, "chY", 0);
|
|
137
|
-
__publicField(this, "colors", { ...DEFAULT_COLORS });
|
|
138
|
-
const ctx = canvas.getContext("2d");
|
|
139
|
-
if (!ctx) throw new Error("VroomChart stub: 2D canvas context unavailable");
|
|
140
|
-
this.ctx = ctx;
|
|
141
|
-
}
|
|
142
|
-
// --- geometry helpers ---------------------------------------------------
|
|
143
|
-
get plotW() {
|
|
144
|
-
return Math.max(0, this.wCss - Y_AXIS_W);
|
|
145
|
-
}
|
|
146
|
-
get plotH() {
|
|
147
|
-
return Math.max(0, this.hCss - X_AXIS_H);
|
|
148
|
-
}
|
|
149
|
-
xOf(timeMs) {
|
|
150
|
-
const span = this.visEnd - this.visStart || 1;
|
|
151
|
-
return (timeMs - this.visStart) / span * this.plotW;
|
|
152
|
-
}
|
|
153
|
-
yOf(price) {
|
|
154
|
-
const span = this.priceMax - this.priceMin || 1;
|
|
155
|
-
return (this.priceMax - price) / span * this.plotH;
|
|
156
|
-
}
|
|
157
|
-
color(key) {
|
|
158
|
-
return argbToCss(this.colors[key] ?? DEFAULT_COLORS[key] ?? 4278190080);
|
|
159
|
-
}
|
|
160
|
-
// --- data ---------------------------------------------------------------
|
|
161
|
-
setCandles(packed) {
|
|
162
|
-
this.candles = unpackCandles(packed);
|
|
163
|
-
if (this.candles.length >= 2) {
|
|
164
|
-
const diffs = [];
|
|
165
|
-
for (let i = 1; i < this.candles.length; i++) {
|
|
166
|
-
diffs.push(this.candles[i].timeMs - this.candles[i - 1].timeMs);
|
|
167
|
-
}
|
|
168
|
-
const m = median(diffs);
|
|
169
|
-
if (m > 0) this.durationMs = m;
|
|
170
|
-
}
|
|
171
|
-
if (!this.rangeInit) this.resetWindow();
|
|
172
|
-
this.recomputePriceBounds();
|
|
173
|
-
}
|
|
174
|
-
resetWindow() {
|
|
175
|
-
const n = this.candles.length;
|
|
176
|
-
if (n === 0) {
|
|
177
|
-
this.visStart = 0;
|
|
178
|
-
this.visEnd = 1;
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
const last = this.candles[n - 1].timeMs;
|
|
182
|
-
this.visEnd = last + this.durationMs * RIGHT_PAD_CANDLES;
|
|
183
|
-
this.visStart = this.visEnd - this.durationMs * (DEFAULT_VISIBLE + RIGHT_PAD_CANDLES);
|
|
184
|
-
this.rangeInit = true;
|
|
185
|
-
}
|
|
186
|
-
recomputePriceBounds() {
|
|
187
|
-
let lo = Infinity;
|
|
188
|
-
let hi = -Infinity;
|
|
189
|
-
for (const c of this.candles) {
|
|
190
|
-
if (c.timeMs < this.visStart || c.timeMs > this.visEnd) continue;
|
|
191
|
-
if (c.low < lo) lo = c.low;
|
|
192
|
-
if (c.high > hi) hi = c.high;
|
|
193
|
-
}
|
|
194
|
-
if (!Number.isFinite(lo) || !Number.isFinite(hi) || hi <= lo) {
|
|
195
|
-
if (this.priceMax > this.priceMin) return;
|
|
196
|
-
lo = 0;
|
|
197
|
-
hi = 1;
|
|
198
|
-
}
|
|
199
|
-
const pad = (hi - lo) * 0.08;
|
|
200
|
-
this.priceMin = lo - pad;
|
|
201
|
-
this.priceMax = hi + pad;
|
|
202
|
-
}
|
|
203
|
-
// --- sizing -------------------------------------------------------------
|
|
204
|
-
setSize(width, height, dpr) {
|
|
205
|
-
this.wCss = width;
|
|
206
|
-
this.hCss = height;
|
|
207
|
-
this.dpr = dpr || 1;
|
|
208
|
-
this.canvas.width = Math.max(1, Math.round(width * this.dpr));
|
|
209
|
-
this.canvas.height = Math.max(1, Math.round(height * this.dpr));
|
|
210
|
-
this.ctx.setTransform(this.dpr, 0, 0, this.dpr, 0, 0);
|
|
211
|
-
}
|
|
212
|
-
setColor(key, argb) {
|
|
213
|
-
this.colors[key] = argb >>> 0;
|
|
214
|
-
}
|
|
215
|
-
setVisibleRange(startMs, endMs) {
|
|
216
|
-
if (startMs === 0 && endMs === 0) {
|
|
217
|
-
this.rangeInit = false;
|
|
218
|
-
this.resetWindow();
|
|
219
|
-
} else {
|
|
220
|
-
this.visStart = startMs;
|
|
221
|
-
this.visEnd = endMs;
|
|
222
|
-
this.rangeInit = true;
|
|
223
|
-
}
|
|
224
|
-
this.recomputePriceBounds();
|
|
225
|
-
}
|
|
226
|
-
// --- viewport mutators --------------------------------------------------
|
|
227
|
-
pan(dx, dy) {
|
|
228
|
-
this.translate(dx, dy);
|
|
229
|
-
}
|
|
230
|
-
translate(dx, dy) {
|
|
231
|
-
const span = this.visEnd - this.visStart;
|
|
232
|
-
const dt = dx / (this.plotW || 1) * span;
|
|
233
|
-
this.visStart -= dt;
|
|
234
|
-
this.visEnd -= dt;
|
|
235
|
-
if (dy) {
|
|
236
|
-
const pspan = this.priceMax - this.priceMin;
|
|
237
|
-
const dp = dy / (this.plotH || 1) * pspan;
|
|
238
|
-
this.priceMin += dp;
|
|
239
|
-
this.priceMax += dp;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
zoom(scaleX, scaleY, fx, fy) {
|
|
243
|
-
if (scaleX !== 1) {
|
|
244
|
-
const span = this.visEnd - this.visStart;
|
|
245
|
-
const focalT = this.visStart + fx / (this.plotW || 1) * span;
|
|
246
|
-
const newSpan = span / scaleX;
|
|
247
|
-
this.visStart = focalT - fx / (this.plotW || 1) * newSpan;
|
|
248
|
-
this.visEnd = this.visStart + newSpan;
|
|
249
|
-
}
|
|
250
|
-
if (scaleY !== 1) {
|
|
251
|
-
const pspan = this.priceMax - this.priceMin;
|
|
252
|
-
const focalP = this.priceMax - fy / (this.plotH || 1) * pspan;
|
|
253
|
-
const newSpan = pspan / scaleY;
|
|
254
|
-
this.priceMax = focalP + fy / (this.plotH || 1) * newSpan;
|
|
255
|
-
this.priceMin = this.priceMax - newSpan;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
scalePriceAxis(dy) {
|
|
259
|
-
const factor = Math.max(0.1, 1 + dy / (this.plotH || 1));
|
|
260
|
-
const center = (this.priceMin + this.priceMax) / 2;
|
|
261
|
-
const half = (this.priceMax - this.priceMin) / 2 * factor;
|
|
262
|
-
this.priceMin = center - half;
|
|
263
|
-
this.priceMax = center + half;
|
|
264
|
-
}
|
|
265
|
-
scaleTimeAxis(dx) {
|
|
266
|
-
const factor = Math.max(0.1, 1 + dx / (this.plotW || 1));
|
|
267
|
-
const span = (this.visEnd - this.visStart) * factor;
|
|
268
|
-
this.visStart = this.visEnd - span;
|
|
269
|
-
}
|
|
270
|
-
getAxisMetrics() {
|
|
271
|
-
return { yAxisWidth: Y_AXIS_W, xAxisHeight: X_AXIS_H, indicatorHeight: 0 };
|
|
272
|
-
}
|
|
273
|
-
// --- crosshair ----------------------------------------------------------
|
|
274
|
-
setCrosshair(x, y) {
|
|
275
|
-
this.crosshair = true;
|
|
276
|
-
this.chX = x;
|
|
277
|
-
this.chY = y;
|
|
278
|
-
}
|
|
279
|
-
clearCrosshair() {
|
|
280
|
-
this.crosshair = false;
|
|
281
|
-
}
|
|
282
|
-
snapIndex() {
|
|
283
|
-
if (this.candles.length === 0) return -1;
|
|
284
|
-
const span = this.visEnd - this.visStart || 1;
|
|
285
|
-
const t = this.visStart + this.chX / (this.plotW || 1) * span;
|
|
286
|
-
let best = -1;
|
|
287
|
-
let bestD = Infinity;
|
|
288
|
-
for (let i = 0; i < this.candles.length; i++) {
|
|
289
|
-
const d = Math.abs(this.candles[i].timeMs - t);
|
|
290
|
-
if (d < bestD) {
|
|
291
|
-
bestD = d;
|
|
292
|
-
best = i;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
return best;
|
|
296
|
-
}
|
|
297
|
-
getCrosshairCandle() {
|
|
298
|
-
if (!this.crosshair) return null;
|
|
299
|
-
const i = this.snapIndex();
|
|
300
|
-
if (i < 0) return null;
|
|
301
|
-
const c = this.candles[i];
|
|
302
|
-
return { ...c };
|
|
303
|
-
}
|
|
304
|
-
// --- indicators (accepted, not drawn in the stub) -----------------------
|
|
305
|
-
setRSI() {
|
|
306
|
-
}
|
|
307
|
-
setMACD() {
|
|
308
|
-
}
|
|
309
|
-
setOverlays(_overlays) {
|
|
310
|
-
}
|
|
311
|
-
setVWAP() {
|
|
312
|
-
}
|
|
313
|
-
isAnimating() {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
// --- rendering ----------------------------------------------------------
|
|
317
|
-
present() {
|
|
318
|
-
const ctx = this.ctx;
|
|
319
|
-
const w = this.wCss;
|
|
320
|
-
const h = this.hCss;
|
|
321
|
-
if (w <= 0 || h <= 0) return;
|
|
322
|
-
ctx.clearRect(0, 0, w, h);
|
|
323
|
-
ctx.fillStyle = this.color(0 /* Background */);
|
|
324
|
-
ctx.fillRect(0, 0, w, h);
|
|
325
|
-
this.drawPriceAxis();
|
|
326
|
-
this.drawTimeAxis();
|
|
327
|
-
this.drawCandles();
|
|
328
|
-
if (this.crosshair) this.drawCrosshair();
|
|
329
|
-
}
|
|
330
|
-
priceTicks() {
|
|
331
|
-
const span = this.priceMax - this.priceMin;
|
|
332
|
-
if (span <= 0) return [];
|
|
333
|
-
const step = niceStep(span / 5);
|
|
334
|
-
const first = Math.ceil(this.priceMin / step) * step;
|
|
335
|
-
const out = [];
|
|
336
|
-
for (let p = first; p <= this.priceMax; p += step) out.push(p);
|
|
337
|
-
return out;
|
|
338
|
-
}
|
|
339
|
-
fmtPrice(p) {
|
|
340
|
-
const span = this.priceMax - this.priceMin;
|
|
341
|
-
const decimals = span < 1 ? 4 : span < 100 ? 2 : 0;
|
|
342
|
-
return p.toFixed(decimals);
|
|
343
|
-
}
|
|
344
|
-
drawPriceAxis() {
|
|
345
|
-
const ctx = this.ctx;
|
|
346
|
-
const plotW = this.plotW;
|
|
347
|
-
ctx.lineWidth = 1;
|
|
348
|
-
ctx.strokeStyle = this.color(4 /* Grid */);
|
|
349
|
-
ctx.fillStyle = this.color(5 /* AxisText */);
|
|
350
|
-
ctx.font = '11px -apple-system, system-ui, "Segoe UI", sans-serif';
|
|
351
|
-
ctx.textAlign = "left";
|
|
352
|
-
ctx.textBaseline = "middle";
|
|
353
|
-
for (const p of this.priceTicks()) {
|
|
354
|
-
const y = this.yOf(p);
|
|
355
|
-
if (y < 0 || y > this.plotH) continue;
|
|
356
|
-
ctx.beginPath();
|
|
357
|
-
ctx.moveTo(0, Math.round(y) + 0.5);
|
|
358
|
-
ctx.lineTo(plotW, Math.round(y) + 0.5);
|
|
359
|
-
ctx.stroke();
|
|
360
|
-
ctx.fillText(this.fmtPrice(p), plotW + 6, y);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
fmtTime(timeMs) {
|
|
364
|
-
const d = new Date(timeMs);
|
|
365
|
-
const span = this.visEnd - this.visStart;
|
|
366
|
-
const twoDays = 2 * 24 * 3600 * 1e3;
|
|
367
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
368
|
-
if (span < twoDays) return `${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
369
|
-
return `${d.getMonth() + 1}/${d.getDate()}`;
|
|
370
|
-
}
|
|
371
|
-
drawTimeAxis() {
|
|
372
|
-
const ctx = this.ctx;
|
|
373
|
-
const span = this.visEnd - this.visStart;
|
|
374
|
-
if (span <= 0) return;
|
|
375
|
-
const targetPx = 80;
|
|
376
|
-
const stepMs = niceStep(span / this.plotW * targetPx);
|
|
377
|
-
const first = Math.ceil(this.visStart / stepMs) * stepMs;
|
|
378
|
-
ctx.fillStyle = this.color(5 /* AxisText */);
|
|
379
|
-
ctx.font = '11px -apple-system, system-ui, "Segoe UI", sans-serif';
|
|
380
|
-
ctx.textAlign = "center";
|
|
381
|
-
ctx.textBaseline = "middle";
|
|
382
|
-
const y = this.plotH + X_AXIS_H / 2;
|
|
383
|
-
for (let t = first; t <= this.visEnd; t += stepMs) {
|
|
384
|
-
const x = this.xOf(t);
|
|
385
|
-
if (x < 0 || x > this.plotW) continue;
|
|
386
|
-
ctx.fillText(this.fmtTime(t), x, y);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
drawCandles() {
|
|
390
|
-
const ctx = this.ctx;
|
|
391
|
-
const span = this.visEnd - this.visStart || 1;
|
|
392
|
-
const slotW = this.durationMs / span * this.plotW;
|
|
393
|
-
const bodyW = Math.max(1, slotW * 0.7);
|
|
394
|
-
const bull = this.color(1 /* Bull */);
|
|
395
|
-
const bear = this.color(2 /* Bear */);
|
|
396
|
-
const wick = this.color(3 /* Wick */);
|
|
397
|
-
for (const c of this.candles) {
|
|
398
|
-
const x = this.xOf(c.timeMs);
|
|
399
|
-
if (x < -slotW || x > this.plotW + slotW) continue;
|
|
400
|
-
const up = c.close >= c.open;
|
|
401
|
-
const yHigh = this.yOf(c.high);
|
|
402
|
-
const yLow = this.yOf(c.low);
|
|
403
|
-
const yOpen = this.yOf(c.open);
|
|
404
|
-
const yClose = this.yOf(c.close);
|
|
405
|
-
ctx.strokeStyle = wick;
|
|
406
|
-
ctx.lineWidth = 1;
|
|
407
|
-
ctx.beginPath();
|
|
408
|
-
ctx.moveTo(Math.round(x) + 0.5, yHigh);
|
|
409
|
-
ctx.lineTo(Math.round(x) + 0.5, yLow);
|
|
410
|
-
ctx.stroke();
|
|
411
|
-
ctx.fillStyle = up ? bull : bear;
|
|
412
|
-
const top = Math.min(yOpen, yClose);
|
|
413
|
-
const bodyH = Math.max(1, Math.abs(yClose - yOpen));
|
|
414
|
-
ctx.fillRect(x - bodyW / 2, top, bodyW, bodyH);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
drawCrosshair() {
|
|
418
|
-
const ctx = this.ctx;
|
|
419
|
-
const i = this.snapIndex();
|
|
420
|
-
if (i < 0) return;
|
|
421
|
-
const c = this.candles[i];
|
|
422
|
-
const x = this.xOf(c.timeMs);
|
|
423
|
-
const y = Math.max(0, Math.min(this.plotH, this.chY));
|
|
424
|
-
ctx.save();
|
|
425
|
-
ctx.strokeStyle = this.color(6 /* Crosshair */);
|
|
426
|
-
ctx.lineWidth = 1;
|
|
427
|
-
ctx.setLineDash([4, 4]);
|
|
428
|
-
ctx.beginPath();
|
|
429
|
-
ctx.moveTo(Math.round(x) + 0.5, 0);
|
|
430
|
-
ctx.lineTo(Math.round(x) + 0.5, this.plotH);
|
|
431
|
-
ctx.moveTo(0, Math.round(y) + 0.5);
|
|
432
|
-
ctx.lineTo(this.plotW, Math.round(y) + 0.5);
|
|
433
|
-
ctx.stroke();
|
|
434
|
-
ctx.setLineDash([]);
|
|
435
|
-
const ty = this.yOf(c.close);
|
|
436
|
-
ctx.strokeStyle = this.color(9 /* CrosshairTarget */);
|
|
437
|
-
ctx.beginPath();
|
|
438
|
-
ctx.arc(x, ty, 3.5, 0, Math.PI * 2);
|
|
439
|
-
ctx.stroke();
|
|
440
|
-
ctx.restore();
|
|
441
|
-
}
|
|
442
|
-
destroy() {
|
|
443
|
-
this.candles = [];
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
function createStubModule() {
|
|
447
|
-
return {
|
|
448
|
-
create(canvas) {
|
|
449
|
-
return new StubChart(canvas);
|
|
450
|
-
}
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
|
|
454
5
|
// src/wasm/adapter.ts
|
|
455
6
|
var uid = 0;
|
|
456
7
|
var WasmHandle = class {
|
|
@@ -484,6 +35,12 @@ var WasmHandle = class {
|
|
|
484
35
|
scaleTimeAxis(dx) {
|
|
485
36
|
this.wc.scaleTimeAxis(dx);
|
|
486
37
|
}
|
|
38
|
+
resizeIndicatorPane(dy) {
|
|
39
|
+
this.wc.resizeIndicatorPane(dy);
|
|
40
|
+
}
|
|
41
|
+
scaleIndicatorAxis(y, dy) {
|
|
42
|
+
this.wc.scaleIndicatorAxis(y, dy);
|
|
43
|
+
}
|
|
487
44
|
getAxisMetrics() {
|
|
488
45
|
return this.wc.getAxisMetrics();
|
|
489
46
|
}
|
|
@@ -496,6 +53,9 @@ var WasmHandle = class {
|
|
|
496
53
|
getCrosshairCandle() {
|
|
497
54
|
return this.wc.getCrosshairCandle();
|
|
498
55
|
}
|
|
56
|
+
getCrosshairInfo() {
|
|
57
|
+
return this.wc.getCrosshairInfo();
|
|
58
|
+
}
|
|
499
59
|
setRSI(enabled, period, upperBand, lowerBand, maEnabled, maPeriod) {
|
|
500
60
|
this.wc.setRSI(enabled, period, upperBand, lowerBand, maEnabled, maPeriod);
|
|
501
61
|
}
|
|
@@ -555,6 +115,105 @@ async function loadWasmCore(cfg) {
|
|
|
555
115
|
return makeWasmModule(instance, fontBytes);
|
|
556
116
|
}
|
|
557
117
|
|
|
118
|
+
// src/handle.ts
|
|
119
|
+
var ColorKey = /* @__PURE__ */ ((ColorKey2) => {
|
|
120
|
+
ColorKey2[ColorKey2["Background"] = 0] = "Background";
|
|
121
|
+
ColorKey2[ColorKey2["Bull"] = 1] = "Bull";
|
|
122
|
+
ColorKey2[ColorKey2["Bear"] = 2] = "Bear";
|
|
123
|
+
ColorKey2[ColorKey2["Wick"] = 3] = "Wick";
|
|
124
|
+
ColorKey2[ColorKey2["Grid"] = 4] = "Grid";
|
|
125
|
+
ColorKey2[ColorKey2["AxisText"] = 5] = "AxisText";
|
|
126
|
+
ColorKey2[ColorKey2["Crosshair"] = 6] = "Crosshair";
|
|
127
|
+
ColorKey2[ColorKey2["TooltipBg"] = 7] = "TooltipBg";
|
|
128
|
+
ColorKey2[ColorKey2["TooltipText"] = 8] = "TooltipText";
|
|
129
|
+
ColorKey2[ColorKey2["CrosshairTarget"] = 9] = "CrosshairTarget";
|
|
130
|
+
ColorKey2[ColorKey2["BorderBull"] = 10] = "BorderBull";
|
|
131
|
+
ColorKey2[ColorKey2["BorderBear"] = 11] = "BorderBear";
|
|
132
|
+
ColorKey2[ColorKey2["WickBull"] = 12] = "WickBull";
|
|
133
|
+
ColorKey2[ColorKey2["WickBear"] = 13] = "WickBear";
|
|
134
|
+
ColorKey2[ColorKey2["AccentBull"] = 14] = "AccentBull";
|
|
135
|
+
ColorKey2[ColorKey2["AccentBear"] = 15] = "AccentBear";
|
|
136
|
+
return ColorKey2;
|
|
137
|
+
})(ColorKey || {});
|
|
138
|
+
|
|
139
|
+
// src/packCandles.ts
|
|
140
|
+
var BYTES_PER_CANDLE = 48;
|
|
141
|
+
function packCandles(candles) {
|
|
142
|
+
const buf = new ArrayBuffer(candles.length * BYTES_PER_CANDLE);
|
|
143
|
+
const view = new DataView(buf);
|
|
144
|
+
for (let i = 0; i < candles.length; i++) {
|
|
145
|
+
const c = candles[i];
|
|
146
|
+
const off = i * BYTES_PER_CANDLE;
|
|
147
|
+
view.setBigInt64(off, BigInt(Math.trunc(c.timeMs)), true);
|
|
148
|
+
view.setFloat64(off + 8, c.open, true);
|
|
149
|
+
view.setFloat64(off + 16, c.high, true);
|
|
150
|
+
view.setFloat64(off + 24, c.low, true);
|
|
151
|
+
view.setFloat64(off + 32, c.close, true);
|
|
152
|
+
view.setFloat64(off + 40, c.volume, true);
|
|
153
|
+
}
|
|
154
|
+
return buf;
|
|
155
|
+
}
|
|
156
|
+
function unpackCandles(buf) {
|
|
157
|
+
const view = new DataView(buf);
|
|
158
|
+
const n = Math.floor(buf.byteLength / BYTES_PER_CANDLE);
|
|
159
|
+
const out = new Array(n);
|
|
160
|
+
for (let i = 0; i < n; i++) {
|
|
161
|
+
const off = i * BYTES_PER_CANDLE;
|
|
162
|
+
out[i] = {
|
|
163
|
+
timeMs: Number(view.getBigInt64(off, true)),
|
|
164
|
+
open: view.getFloat64(off + 8, true),
|
|
165
|
+
high: view.getFloat64(off + 16, true),
|
|
166
|
+
low: view.getFloat64(off + 24, true),
|
|
167
|
+
close: view.getFloat64(off + 32, true),
|
|
168
|
+
volume: view.getFloat64(off + 40, true)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/color.ts
|
|
175
|
+
var COLOR_KEYS = {
|
|
176
|
+
background: 0 /* Background */,
|
|
177
|
+
bull: 1 /* Bull */,
|
|
178
|
+
bear: 2 /* Bear */,
|
|
179
|
+
grid: 4 /* Grid */,
|
|
180
|
+
axisText: 5 /* AxisText */,
|
|
181
|
+
crosshair: 6 /* Crosshair */,
|
|
182
|
+
crosshairTarget: 9 /* CrosshairTarget */,
|
|
183
|
+
borderBull: 10 /* BorderBull */,
|
|
184
|
+
borderBear: 11 /* BorderBear */,
|
|
185
|
+
wickBull: 12 /* WickBull */,
|
|
186
|
+
wickBear: 13 /* WickBear */,
|
|
187
|
+
accentBull: 14 /* AccentBull */,
|
|
188
|
+
accentBear: 15 /* AccentBear */
|
|
189
|
+
};
|
|
190
|
+
function parseColor(value) {
|
|
191
|
+
if (typeof value === "number") {
|
|
192
|
+
return Number.isFinite(value) ? value >>> 0 : null;
|
|
193
|
+
}
|
|
194
|
+
let s = value.trim();
|
|
195
|
+
if (s.startsWith("#")) s = s.slice(1);
|
|
196
|
+
if (s.length === 6) s = `ff${s}`;
|
|
197
|
+
if (s.length !== 8 || !/^[0-9a-fA-F]{8}$/.test(s)) return null;
|
|
198
|
+
return parseInt(s, 16) >>> 0;
|
|
199
|
+
}
|
|
200
|
+
function applyTheme(handle, theme) {
|
|
201
|
+
Object.keys(COLOR_KEYS).forEach((field) => {
|
|
202
|
+
const value = theme[field];
|
|
203
|
+
if (value == null) return;
|
|
204
|
+
const argb = parseColor(value);
|
|
205
|
+
if (argb == null) return;
|
|
206
|
+
handle.setColor(COLOR_KEYS[field], argb);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function argbToCss(argb) {
|
|
210
|
+
const a = (argb >>> 24 & 255) / 255;
|
|
211
|
+
const r = argb >>> 16 & 255;
|
|
212
|
+
const g = argb >>> 8 & 255;
|
|
213
|
+
const b = argb & 255;
|
|
214
|
+
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
215
|
+
}
|
|
216
|
+
|
|
558
217
|
// src/index.ts
|
|
559
218
|
function bundledWasmConfig() {
|
|
560
219
|
return {
|
|
@@ -566,17 +225,11 @@ function bundledWasmConfig() {
|
|
|
566
225
|
var modulePromise = null;
|
|
567
226
|
function loadVroom(opts) {
|
|
568
227
|
if (modulePromise) return modulePromise;
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
modulePromise = loadWasmCore(cfg).catch((err) => {
|
|
575
|
-
if (!fallback) throw err;
|
|
576
|
-
console.warn("[vroom] Skia-WASM core failed to load; using Canvas2D stub.", err);
|
|
577
|
-
return createStubModule();
|
|
578
|
-
});
|
|
579
|
-
}
|
|
228
|
+
const cfg = opts?.wasm ?? bundledWasmConfig();
|
|
229
|
+
modulePromise = loadWasmCore(cfg).catch((err) => {
|
|
230
|
+
modulePromise = null;
|
|
231
|
+
throw err;
|
|
232
|
+
});
|
|
580
233
|
return modulePromise;
|
|
581
234
|
}
|
|
582
235
|
function resetVroomForTesting() {
|