@luxalgo/vela 0.7.0 → 0.7.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/dist/{DataProvider-Bu9E5Zh5.d.ts → DataProvider-BALR0qPq.d.ts} +1 -1
- package/dist/{DataProvider-Ck2qyS_9.d.cts → DataProvider-D5uMiJYz.d.cts} +1 -1
- package/dist/{chunk-IFJJPXSV.js → chunk-BFA32GOU.js} +44 -16
- package/dist/{chunk-CZRNTHD6.js → chunk-BKHSQ4YM.js} +129 -125
- package/dist/{chunk-A4G64KVF.js → chunk-PTNO3YK2.js} +311 -111
- package/dist/{chunk-A347YL2P.js → chunk-QBWLINWL.js} +67 -9
- package/dist/{chunk-UHXG5J7C.js → chunk-ZADTVRUO.js} +1 -1
- package/dist/{contributions-BjQ7hyIx.d.ts → contributions-BbhbZd3c.d.ts} +31 -9
- package/dist/{contributions-Cv2Mutvn.d.cts → contributions-CcBFe-91.d.cts} +31 -9
- package/dist/index.cjs +391 -157
- package/dist/index.d.cts +52 -17
- package/dist/index.d.ts +52 -17
- package/dist/index.js +4 -4
- package/dist/{options-ex2_gtKp.d.ts → options-C7b_Qssu.d.cts} +42 -1
- package/dist/{options-ex2_gtKp.d.cts → options-C7b_Qssu.d.ts} +42 -1
- package/dist/{plugin-CiyhU7pi.d.ts → plugin-Cnix5ZBh.d.ts} +6 -4
- package/dist/{plugin-absNH7Yn.d.cts → plugin-D6MlX4--.d.cts} +6 -4
- package/dist/plugin.cjs +39 -15
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +2 -2
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/{statusline-model-BXoU4Kua.d.ts → statusline-model-D8NL7o4L.d.ts} +3 -3
- package/dist/{statusline-model-cRhPBLPO.d.cts → statusline-model-DUKt7_TQ.d.cts} +3 -3
- package/dist/ui.cjs +8 -4
- package/dist/ui.d.cts +10 -7
- package/dist/ui.d.ts +10 -7
- package/dist/ui.js +2 -2
- package/dist/vela.global.js +391 -157
- package/dist/vela.global.min.js +70 -70
- package/dist/widget.cjs +418 -128
- package/dist/widget.d.cts +6 -6
- package/dist/widget.d.ts +6 -6
- package/dist/widget.js +6 -6
- package/dist/workspace.cjs +418 -128
- package/dist/workspace.d.cts +39 -6
- package/dist/workspace.d.ts +39 -6
- package/dist/workspace.js +5 -5
- package/package.json +1 -1
- /package/dist/{chunk-FE6V2YAQ.js → chunk-JU4CWEBF.js} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -75,6 +75,13 @@ function resolveAnimations(animations) {
|
|
|
75
75
|
function isLineLikeSeries(spec) {
|
|
76
76
|
return spec.kind !== "candle" && spec.kind !== "bar" && spec.kind !== "markers";
|
|
77
77
|
}
|
|
78
|
+
function seriesShownOn(spec, surface) {
|
|
79
|
+
if (spec.display) return spec.display[surface] !== false;
|
|
80
|
+
return spec.visible !== false;
|
|
81
|
+
}
|
|
82
|
+
function seriesInScale(spec) {
|
|
83
|
+
return seriesShownOn(spec, "pane") || seriesShownOn(spec, "priceScale");
|
|
84
|
+
}
|
|
78
85
|
|
|
79
86
|
// src/core/events/EventBus.ts
|
|
80
87
|
var TypedEventBus = class {
|
|
@@ -144,6 +151,9 @@ var IndicatorRegistry = class {
|
|
|
144
151
|
};
|
|
145
152
|
|
|
146
153
|
// src/core/native-indicators/NativeIndicator.ts
|
|
154
|
+
function nativeInstanceChannel(type, instanceId) {
|
|
155
|
+
return `${type}#${instanceId}`;
|
|
156
|
+
}
|
|
147
157
|
var REGISTRY = /* @__PURE__ */ new Map();
|
|
148
158
|
function registerNativeIndicator(descriptor) {
|
|
149
159
|
REGISTRY.set(descriptor.type, descriptor);
|
|
@@ -219,6 +229,9 @@ var IndicatorHandleImpl = class {
|
|
|
219
229
|
setProps(values) {
|
|
220
230
|
this.controller.applyProps(this.id, values);
|
|
221
231
|
}
|
|
232
|
+
updateCode(source) {
|
|
233
|
+
this.controller.updateCode(this.id, source);
|
|
234
|
+
}
|
|
222
235
|
setVisible(visible) {
|
|
223
236
|
this.controller.setVisible(this.id, visible);
|
|
224
237
|
}
|
|
@@ -241,6 +254,10 @@ var IndicatorHandleImpl = class {
|
|
|
241
254
|
setPropsSchema(schema) {
|
|
242
255
|
this.propsSchema = schema;
|
|
243
256
|
}
|
|
257
|
+
/** Sync the public `source` once a code update has been prepared and taken over. */
|
|
258
|
+
setSource(source) {
|
|
259
|
+
this.source = source;
|
|
260
|
+
}
|
|
244
261
|
/** Sync the public `visible` getter when the orchestrator changes visibility (API or legend eye). */
|
|
245
262
|
setVisibleState(visible) {
|
|
246
263
|
this.visibleState = visible;
|
|
@@ -2347,16 +2364,18 @@ var Pitchfork = class extends SegmentDrawing {
|
|
|
2347
2364
|
geometry(proj) {
|
|
2348
2365
|
const a = this.anchors[0];
|
|
2349
2366
|
const b = this.anchors[1];
|
|
2350
|
-
|
|
2351
|
-
if (!a || !b || !c) return null;
|
|
2367
|
+
if (!a || !b) return null;
|
|
2352
2368
|
const px = (p) => {
|
|
2353
2369
|
const y = proj.yOf(p.price, this.paneId);
|
|
2354
2370
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
2355
2371
|
};
|
|
2356
2372
|
const P0 = px(a);
|
|
2357
2373
|
const P1 = px(b);
|
|
2374
|
+
if (!P0 || !P1) return null;
|
|
2375
|
+
const c = this.anchors[2];
|
|
2376
|
+
if (!c) return { segments: [[P0[0], P0[1], P1[0], P1[1]]], fill: null };
|
|
2358
2377
|
const P2 = px(c);
|
|
2359
|
-
if (!
|
|
2378
|
+
if (!P2) return null;
|
|
2360
2379
|
const mx = (P1[0] + P2[0]) / 2;
|
|
2361
2380
|
const my = (P1[1] + P2[1]) / 2;
|
|
2362
2381
|
const dx = mx - P0[0];
|
|
@@ -2403,16 +2422,19 @@ var PitchforkVariant = class extends SegmentDrawing {
|
|
|
2403
2422
|
geometry(proj) {
|
|
2404
2423
|
const a = this.anchors[0];
|
|
2405
2424
|
const b = this.anchors[1];
|
|
2406
|
-
|
|
2407
|
-
if (!a || !b || !c) return null;
|
|
2425
|
+
if (!a || !b) return null;
|
|
2408
2426
|
const px = (p) => {
|
|
2409
2427
|
const y = proj.yOf(p.price, this.paneId);
|
|
2410
2428
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
2411
2429
|
};
|
|
2412
|
-
const
|
|
2430
|
+
const P0 = px(a);
|
|
2413
2431
|
const P1 = px(b);
|
|
2432
|
+
if (!P0 || !P1) return null;
|
|
2433
|
+
const c = this.anchors[2];
|
|
2434
|
+
if (!c) return { segments: [[P0[0], P0[1], P1[0], P1[1]]], fill: null };
|
|
2435
|
+
const S2 = px(this.medianStart(a, b, c));
|
|
2414
2436
|
const P2 = px(c);
|
|
2415
|
-
if (!S2 || !
|
|
2437
|
+
if (!S2 || !P2) return null;
|
|
2416
2438
|
const mx = (P1[0] + P2[0]) / 2;
|
|
2417
2439
|
const my = (P1[1] + P2[1]) / 2;
|
|
2418
2440
|
const dx = mx - S2[0];
|
|
@@ -2427,8 +2449,12 @@ var PitchforkVariant = class extends SegmentDrawing {
|
|
|
2427
2449
|
// upper tine
|
|
2428
2450
|
extendRay(P2[0], P2[1], P2[0] + dx, P2[1] + dy, "right", w, h),
|
|
2429
2451
|
// lower tine
|
|
2430
|
-
[P1[0], P1[1], P2[0], P2[1]]
|
|
2452
|
+
[P1[0], P1[1], P2[0], P2[1]],
|
|
2431
2453
|
// base line
|
|
2454
|
+
// The median leaves from the shifted origin, not the pivot — without this construction
|
|
2455
|
+
// line the pivot handle would float unattached to the shape it defines.
|
|
2456
|
+
[P0[0], P0[1], P1[0], P1[1]]
|
|
2457
|
+
// pivot → first tine anchor
|
|
2432
2458
|
],
|
|
2433
2459
|
fill: null
|
|
2434
2460
|
};
|
|
@@ -2485,17 +2511,19 @@ var InsidePitchfork = class extends SegmentDrawing {
|
|
|
2485
2511
|
geometry(proj) {
|
|
2486
2512
|
const a = this.anchors[0];
|
|
2487
2513
|
const b = this.anchors[1];
|
|
2488
|
-
|
|
2489
|
-
if (!a || !b || !c) return null;
|
|
2514
|
+
if (!a || !b) return null;
|
|
2490
2515
|
const px = (p) => {
|
|
2491
2516
|
const y = proj.yOf(p.price, this.paneId);
|
|
2492
2517
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
2493
2518
|
};
|
|
2494
|
-
const B = px({ time: (a.time + b.time) / 2, price: (a.price + b.price) / 2 });
|
|
2495
2519
|
const P0 = px(a);
|
|
2496
2520
|
const P1 = px(b);
|
|
2521
|
+
if (!P0 || !P1) return null;
|
|
2522
|
+
const c = this.anchors[2];
|
|
2523
|
+
if (!c) return { segments: [[P0[0], P0[1], P1[0], P1[1]]], fill: null };
|
|
2524
|
+
const B = px({ time: (a.time + b.time) / 2, price: (a.price + b.price) / 2 });
|
|
2497
2525
|
const P2 = px(c);
|
|
2498
|
-
if (!B || !
|
|
2526
|
+
if (!B || !P2) return null;
|
|
2499
2527
|
const mx = (P1[0] + P2[0]) / 2;
|
|
2500
2528
|
const my = (P1[1] + P2[1]) / 2;
|
|
2501
2529
|
const dx = P2[0] - B[0];
|
|
@@ -2666,16 +2694,18 @@ var Triangle = class extends SegmentDrawing {
|
|
|
2666
2694
|
geometry(proj) {
|
|
2667
2695
|
const a = this.anchors[0];
|
|
2668
2696
|
const b = this.anchors[1];
|
|
2669
|
-
|
|
2670
|
-
if (!a || !b || !c) return null;
|
|
2697
|
+
if (!a || !b) return null;
|
|
2671
2698
|
const px = (p) => {
|
|
2672
2699
|
const y = proj.yOf(p.price, this.paneId);
|
|
2673
2700
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
2674
2701
|
};
|
|
2675
2702
|
const A = px(a);
|
|
2676
2703
|
const B = px(b);
|
|
2704
|
+
if (!A || !B) return null;
|
|
2705
|
+
const c = this.anchors[2];
|
|
2706
|
+
if (!c) return { segments: [[A[0], A[1], B[0], B[1]]], fill: null };
|
|
2677
2707
|
const C = px(c);
|
|
2678
|
-
if (!
|
|
2708
|
+
if (!C) return null;
|
|
2679
2709
|
return {
|
|
2680
2710
|
segments: [
|
|
2681
2711
|
[A[0], A[1], B[0], B[1]],
|
|
@@ -8204,6 +8234,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8204
8234
|
if (identityChanged) {
|
|
8205
8235
|
this.beginLoad(false);
|
|
8206
8236
|
this.setBarSeries([], { clearing: true });
|
|
8237
|
+
this.blankIndicatorVisuals();
|
|
8207
8238
|
if (this.activeEngineStyle) {
|
|
8208
8239
|
this.typeEngines.get(this.activeEngineStyle)?.suspend();
|
|
8209
8240
|
this.renderer.setNativeData?.(this.activeEngineStyle, void 0);
|
|
@@ -8246,7 +8277,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8246
8277
|
restartNativeIndicators() {
|
|
8247
8278
|
for (const record of this.registry.all()) {
|
|
8248
8279
|
if (!record.native) continue;
|
|
8249
|
-
record.native.instance.stop();
|
|
8280
|
+
if (record.native.started) record.native.instance.stop();
|
|
8250
8281
|
record.native.instance = record.native.descriptor.create();
|
|
8251
8282
|
record.native.started = false;
|
|
8252
8283
|
record.pendingStructural = true;
|
|
@@ -8454,6 +8485,23 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8454
8485
|
this.renderer.setBars(this.bars, { preserveView: true });
|
|
8455
8486
|
this.reexecuteIndicators();
|
|
8456
8487
|
}
|
|
8488
|
+
/**
|
|
8489
|
+
* Blank every mounted indicator's painted output — series data, fills, backgrounds,
|
|
8490
|
+
* price lines, drawings, bar colors, trades — while keeping the mount (legend row,
|
|
8491
|
+
* pane, settings) intact, via the same idempotent-by-id remount an input edit uses.
|
|
8492
|
+
* Called on a market identity switch: the fresh models arrive only after the new
|
|
8493
|
+
* bars load, so nothing blanked here is ever restored — each consumer's re-run
|
|
8494
|
+
* remounts a full model over it.
|
|
8495
|
+
*/
|
|
8496
|
+
blankIndicatorVisuals() {
|
|
8497
|
+
for (const record of this.registry.all()) {
|
|
8498
|
+
if (record.hidden || !record.renderHandle || !record.model) continue;
|
|
8499
|
+
record.model = blankedModel(record.model);
|
|
8500
|
+
record.renderHandle = this.renderer.mountIndicator(record.model);
|
|
8501
|
+
record.pendingStructural = true;
|
|
8502
|
+
this.setLoading(record, true);
|
|
8503
|
+
}
|
|
8504
|
+
}
|
|
8457
8505
|
/** Stop + re-run every visible Pine indicator (its input bars changed wholesale). */
|
|
8458
8506
|
reexecuteIndicators() {
|
|
8459
8507
|
for (const record of this.registry.all()) {
|
|
@@ -8698,6 +8746,61 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8698
8746
|
record.session.update(record.inputValues, record.propValues);
|
|
8699
8747
|
this.events.emit("indicator:inputs", { id });
|
|
8700
8748
|
}
|
|
8749
|
+
/**
|
|
8750
|
+
* IndicatorController: replace a SCRIPT indicator's source in place (see
|
|
8751
|
+
* {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
|
|
8752
|
+
* computing until the new source has compiled, so a broken edit costs the user
|
|
8753
|
+
* nothing but an `error` event. Natives have no script — warn and leave them alone.
|
|
8754
|
+
*/
|
|
8755
|
+
updateCode(id, source) {
|
|
8756
|
+
const record = this.registry.get(id);
|
|
8757
|
+
const handle = this.handles.get(id);
|
|
8758
|
+
if (!record || !handle) return;
|
|
8759
|
+
if (record.native) {
|
|
8760
|
+
console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
|
|
8761
|
+
return;
|
|
8762
|
+
}
|
|
8763
|
+
if (source === record.source && record.pendingSource === void 0) return;
|
|
8764
|
+
record.pendingSource = source;
|
|
8765
|
+
void this.swapSource(id, source, handle);
|
|
8766
|
+
}
|
|
8767
|
+
async swapSource(id, source, handle) {
|
|
8768
|
+
const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
|
|
8769
|
+
let prepared;
|
|
8770
|
+
try {
|
|
8771
|
+
prepared = await engine.prepare(source, id);
|
|
8772
|
+
} catch (err) {
|
|
8773
|
+
const record2 = this.registry.get(id);
|
|
8774
|
+
if (record2?.pendingSource === source) {
|
|
8775
|
+
record2.pendingSource = void 0;
|
|
8776
|
+
this.fail(id, handle, err);
|
|
8777
|
+
}
|
|
8778
|
+
return;
|
|
8779
|
+
}
|
|
8780
|
+
const record = this.registry.get(id);
|
|
8781
|
+
if (!record || record.pendingSource !== source) return;
|
|
8782
|
+
record.pendingSource = void 0;
|
|
8783
|
+
record.source = source;
|
|
8784
|
+
record.engine = engine;
|
|
8785
|
+
record.prepared = prepared;
|
|
8786
|
+
handle.setSource(source);
|
|
8787
|
+
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
|
|
8788
|
+
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
|
|
8789
|
+
handle.setSchema(prepared.inputs);
|
|
8790
|
+
handle.setPropsSchema(prepared.props ?? []);
|
|
8791
|
+
record.session?.stop();
|
|
8792
|
+
record.session = void 0;
|
|
8793
|
+
record.pendingStructural = true;
|
|
8794
|
+
record.pendingCause = "code";
|
|
8795
|
+
if (record.hidden) return;
|
|
8796
|
+
if (record.renderHandle) {
|
|
8797
|
+
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
8798
|
+
this.setLoading(record, true);
|
|
8799
|
+
} else {
|
|
8800
|
+
this.mountLoadingPlaceholder(id, record);
|
|
8801
|
+
}
|
|
8802
|
+
this.executeIndicator(id, handle);
|
|
8803
|
+
}
|
|
8701
8804
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
8702
8805
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
8703
8806
|
listIndicators() {
|
|
@@ -8712,7 +8815,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8712
8815
|
const record = this.registry.remove(id);
|
|
8713
8816
|
if (record?.native?.type === "volume") this.volumeOptedOut = true;
|
|
8714
8817
|
record?.session?.stop();
|
|
8715
|
-
record?.native?.instance.stop();
|
|
8818
|
+
if (record?.native?.started) record.native.instance.stop();
|
|
8716
8819
|
this.handles.delete(id);
|
|
8717
8820
|
if (record?.renderHandle) this.renderer.removeIndicator(record.renderHandle);
|
|
8718
8821
|
const paneId = record?.model?.paneId;
|
|
@@ -8738,7 +8841,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8738
8841
|
if (!visible) {
|
|
8739
8842
|
record.session?.stop();
|
|
8740
8843
|
record.session = void 0;
|
|
8741
|
-
record.native?.instance.suspend();
|
|
8844
|
+
if (record.native?.started) record.native.instance.suspend();
|
|
8742
8845
|
if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
|
|
8743
8846
|
else if (record.native) this.mountHiddenNativeRow(id, record);
|
|
8744
8847
|
} else {
|
|
@@ -8819,7 +8922,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8819
8922
|
this.paneActionUnsub?.();
|
|
8820
8923
|
for (const record of this.registry.all()) {
|
|
8821
8924
|
record.session?.stop();
|
|
8822
|
-
record.native?.instance.stop();
|
|
8925
|
+
if (record.native?.started) record.native.instance.stop();
|
|
8823
8926
|
}
|
|
8824
8927
|
for (const engine of this.typeEngines.values()) engine.stop();
|
|
8825
8928
|
this.typeEngines.clear();
|
|
@@ -8843,6 +8946,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8843
8946
|
const engine = this.engineFor(options.language);
|
|
8844
8947
|
record.engine = engine;
|
|
8845
8948
|
const prepared = await engine.prepare(source, id);
|
|
8949
|
+
if (record.source !== source) return;
|
|
8846
8950
|
record.prepared = prepared;
|
|
8847
8951
|
const defaults2 = {};
|
|
8848
8952
|
for (const input of prepared.inputs) defaults2[input.key] = input.defval;
|
|
@@ -8913,7 +9017,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8913
9017
|
await this.readyPromise;
|
|
8914
9018
|
const record = this.registry.get(id);
|
|
8915
9019
|
if (!record?.native || record.hidden) return;
|
|
9020
|
+
const channel = this.nativeChannel(record);
|
|
8916
9021
|
const ctx = {
|
|
9022
|
+
id,
|
|
8917
9023
|
symbol: this.config.market.symbol ?? "TEST",
|
|
8918
9024
|
timeframe: this.config.market.timeframe ?? "60",
|
|
8919
9025
|
live: this.config.live,
|
|
@@ -8921,7 +9027,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8921
9027
|
bars: () => this.bars,
|
|
8922
9028
|
data: this.dataControl,
|
|
8923
9029
|
emit: (out) => this.applyModel(id, this.buildNativeModel(record, out)),
|
|
8924
|
-
pushData: (data) => this.renderer.setNativeData?.(
|
|
9030
|
+
pushData: (data) => this.renderer.setNativeData?.(channel, data),
|
|
8925
9031
|
setStatus: (status) => {
|
|
8926
9032
|
if (record.renderHandle && !record.hidden) this.renderer.setIndicatorStatus?.(record.renderHandle, status);
|
|
8927
9033
|
}
|
|
@@ -8932,16 +9038,26 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8932
9038
|
this.fail(id, handle, err);
|
|
8933
9039
|
}
|
|
8934
9040
|
}
|
|
9041
|
+
/** The renderer-layer channel a native instance's `pushData` lands on: the type itself
|
|
9042
|
+
* for a single-instance type (the layer id doubles as the channel), a per-instance
|
|
9043
|
+
* channel for a `multiInstance` type — otherwise every instance would overwrite the
|
|
9044
|
+
* one layer. Stamped on the model so the renderer mounts a dedicated layer for it. */
|
|
9045
|
+
nativeChannel(record) {
|
|
9046
|
+
const { type, descriptor } = record.native;
|
|
9047
|
+
return descriptor.multiInstance ? nativeInstanceChannel(type, record.id) : type;
|
|
9048
|
+
}
|
|
8935
9049
|
/** Wrap a native indicator's emitted visuals into a full IndicatorModel, tagged `native`. */
|
|
8936
9050
|
buildNativeModel(record, out) {
|
|
8937
9051
|
const d = record.native.descriptor;
|
|
9052
|
+
const type = record.native.type;
|
|
9053
|
+
const channel = this.nativeChannel(record);
|
|
8938
9054
|
return {
|
|
8939
9055
|
id: record.id,
|
|
8940
9056
|
title: record.title,
|
|
8941
9057
|
...d.shortTitle ? { shorttitle: d.shortTitle } : {},
|
|
8942
9058
|
overlay: d.overlay,
|
|
8943
9059
|
paneHint: d.paneHint,
|
|
8944
|
-
native: { type
|
|
9060
|
+
native: { type, ...channel !== type ? { channel } : {} },
|
|
8945
9061
|
...d.legend === false ? { legend: false } : {},
|
|
8946
9062
|
...out.paneAxis != null ? { paneAxis: out.paneAxis } : {},
|
|
8947
9063
|
series: out.series ?? [],
|
|
@@ -9231,6 +9347,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
9231
9347
|
applyModel(id, model) {
|
|
9232
9348
|
const record = this.registry.get(id);
|
|
9233
9349
|
if (!record || record.hidden) return true;
|
|
9350
|
+
if (this.switchingMarket) return false;
|
|
9234
9351
|
if (record.loading && this.bars.length === 0 && !_EngineOrchestrator.modelHasOutput(model)) return false;
|
|
9235
9352
|
const handle = this.handles.get(id);
|
|
9236
9353
|
if (!record.renderHandle) {
|
|
@@ -9248,7 +9365,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
9248
9365
|
if (record.loading && !record.paneLocked) {
|
|
9249
9366
|
const routed = this.routePane(id, model, record.options ?? {});
|
|
9250
9367
|
if (routed !== paneId) {
|
|
9251
|
-
if (paneId !== "price") {
|
|
9368
|
+
if (paneId !== "price" && !this.registry.all().some((r) => r.id !== id && r.model?.paneId === paneId)) {
|
|
9252
9369
|
this.renderer.removePane(paneId);
|
|
9253
9370
|
this.forgetPane(paneId);
|
|
9254
9371
|
}
|
|
@@ -9377,6 +9494,34 @@ function yieldToPaint() {
|
|
|
9377
9494
|
}
|
|
9378
9495
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
9379
9496
|
}
|
|
9497
|
+
function valuesOnSchema(schema, previous) {
|
|
9498
|
+
const out = {};
|
|
9499
|
+
const declared = /* @__PURE__ */ new Set();
|
|
9500
|
+
for (const s of schema) {
|
|
9501
|
+
out[s.key] = s.defval;
|
|
9502
|
+
declared.add(s.key);
|
|
9503
|
+
declared.add(s.title);
|
|
9504
|
+
}
|
|
9505
|
+
for (const [k, v] of Object.entries(previous)) if (declared.has(k)) out[k] = v;
|
|
9506
|
+
return out;
|
|
9507
|
+
}
|
|
9508
|
+
function blankedModel(model) {
|
|
9509
|
+
return {
|
|
9510
|
+
...model,
|
|
9511
|
+
series: model.series.map((s) => s.kind === "candle" || s.kind === "bar" ? { ...s, bars: [] } : s.kind === "markers" ? { ...s, markers: [] } : { ...s, points: [] }),
|
|
9512
|
+
fills: [],
|
|
9513
|
+
backgrounds: [],
|
|
9514
|
+
priceLines: [],
|
|
9515
|
+
lines: [],
|
|
9516
|
+
boxes: [],
|
|
9517
|
+
labels: [],
|
|
9518
|
+
polylines: [],
|
|
9519
|
+
linefills: [],
|
|
9520
|
+
tables: [],
|
|
9521
|
+
barColors: [],
|
|
9522
|
+
trades: []
|
|
9523
|
+
};
|
|
9524
|
+
}
|
|
9380
9525
|
function modelToValuePatch(model) {
|
|
9381
9526
|
const series = [];
|
|
9382
9527
|
let from = Number.POSITIVE_INFINITY;
|
|
@@ -10862,6 +11007,43 @@ function injectStyles(id, css, root = document) {
|
|
|
10862
11007
|
host.appendChild(s);
|
|
10863
11008
|
}
|
|
10864
11009
|
|
|
11010
|
+
// src/ui/tokens.ts
|
|
11011
|
+
var STATIC_ID = "vela-ui-tokens";
|
|
11012
|
+
var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
11013
|
+
var STATIC_CSS = `
|
|
11014
|
+
.vela-ui, .vela-ui-layer {
|
|
11015
|
+
${STATIC_DECLS}
|
|
11016
|
+
font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
|
|
11017
|
+
box-sizing: border-box;
|
|
11018
|
+
/* Chrome text (titles, buttons, menus, readouts) is UI, not copy \u2014 never selectable. */
|
|
11019
|
+
user-select: none;
|
|
11020
|
+
-webkit-user-select: none;
|
|
11021
|
+
}
|
|
11022
|
+
.vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
|
|
11023
|
+
/* Text ENTRY is the one exception: selection is part of editing. */
|
|
11024
|
+
.vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
|
|
11025
|
+
/* iOS Safari zooms the page when a focused field is under 16px, and often
|
|
11026
|
+
keeps that zoom after the field blurs or a dialog closes. The shell's
|
|
11027
|
+
mobile size class is the honest gate \u2014 not a media query \u2014 so an embedded
|
|
11028
|
+
chart on a wide desktop still gets the rule when it is in the phone layout.
|
|
11029
|
+
Scoped to the kit root so a host page's own [data-layout] is never touched. */
|
|
11030
|
+
.vela-ui[data-layout='mobile'] :is(input, textarea, select) { font-size: 16px; }
|
|
11031
|
+
.vela-icon { display: inline-flex; align-items: center; flex: none; }
|
|
11032
|
+
.vela-icon svg { display: block; }
|
|
11033
|
+
`;
|
|
11034
|
+
function applyThemeTokens(el, t) {
|
|
11035
|
+
const tokens = themeTokens(t);
|
|
11036
|
+
for (const key in tokens) el.style.setProperty(key, tokens[key]);
|
|
11037
|
+
}
|
|
11038
|
+
function floatingLayerHost(from, fallback) {
|
|
11039
|
+
return from.closest(".vela-ui") ?? fallback ?? from;
|
|
11040
|
+
}
|
|
11041
|
+
function ensureUIHost(el, theme) {
|
|
11042
|
+
injectStyles(STATIC_ID, STATIC_CSS, el.getRootNode());
|
|
11043
|
+
el.classList.add("vela-ui");
|
|
11044
|
+
if (theme) applyThemeTokens(el, theme);
|
|
11045
|
+
}
|
|
11046
|
+
|
|
10865
11047
|
// src/ui/icons.ts
|
|
10866
11048
|
function iconEl(id, doc = document) {
|
|
10867
11049
|
const span = doc.createElement("span");
|
|
@@ -10895,7 +11077,7 @@ var MENU_CSS = `
|
|
|
10895
11077
|
/* The list is a <ul>: without this, block rows (the separator) paint a ::marker dot. */
|
|
10896
11078
|
list-style: none;
|
|
10897
11079
|
margin: 0;
|
|
10898
|
-
background: var(--vela-surface
|
|
11080
|
+
background: var(--vela-surface);
|
|
10899
11081
|
color: var(--vela-fg);
|
|
10900
11082
|
border: 1px solid var(--vela-border-strong);
|
|
10901
11083
|
border-radius: 6px;
|
|
@@ -11186,7 +11368,7 @@ var Menu = class {
|
|
|
11186
11368
|
const anchor = opts.trigger ?? opts.host ?? document.body;
|
|
11187
11369
|
const doc = anchor.ownerDocument;
|
|
11188
11370
|
injectStyles(MENU_STYLE_ID, MENU_CSS, doc);
|
|
11189
|
-
const host = opts.host ?? anchor
|
|
11371
|
+
const host = floatingLayerHost(opts.host ?? anchor, doc.body);
|
|
11190
11372
|
this.root = new Surface(doc, {
|
|
11191
11373
|
host,
|
|
11192
11374
|
placement: opts.placement,
|
|
@@ -11225,40 +11407,6 @@ var Menu = class {
|
|
|
11225
11407
|
}
|
|
11226
11408
|
};
|
|
11227
11409
|
|
|
11228
|
-
// src/ui/tokens.ts
|
|
11229
|
-
var STATIC_ID = "vela-ui-tokens";
|
|
11230
|
-
var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
11231
|
-
var STATIC_CSS = `
|
|
11232
|
-
.vela-ui, .vela-ui-layer {
|
|
11233
|
-
${STATIC_DECLS}
|
|
11234
|
-
font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
|
|
11235
|
-
box-sizing: border-box;
|
|
11236
|
-
/* Chrome text (titles, buttons, menus, readouts) is UI, not copy \u2014 never selectable. */
|
|
11237
|
-
user-select: none;
|
|
11238
|
-
-webkit-user-select: none;
|
|
11239
|
-
}
|
|
11240
|
-
.vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
|
|
11241
|
-
/* Text ENTRY is the one exception: selection is part of editing. */
|
|
11242
|
-
.vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
|
|
11243
|
-
/* iOS Safari zooms the page when a focused field is under 16px, and often
|
|
11244
|
-
keeps that zoom after the field blurs or a dialog closes. The shell's
|
|
11245
|
-
mobile size class is the honest gate \u2014 not a media query \u2014 so an embedded
|
|
11246
|
-
chart on a wide desktop still gets the rule when it is in the phone layout.
|
|
11247
|
-
Scoped to the kit root so a host page's own [data-layout] is never touched. */
|
|
11248
|
-
.vela-ui[data-layout='mobile'] :is(input, textarea, select) { font-size: 16px; }
|
|
11249
|
-
.vela-icon { display: inline-flex; align-items: center; flex: none; }
|
|
11250
|
-
.vela-icon svg { display: block; }
|
|
11251
|
-
`;
|
|
11252
|
-
function applyThemeTokens(el, t) {
|
|
11253
|
-
const tokens = themeTokens(t);
|
|
11254
|
-
for (const key in tokens) el.style.setProperty(key, tokens[key]);
|
|
11255
|
-
}
|
|
11256
|
-
function ensureUIHost(el, theme) {
|
|
11257
|
-
injectStyles(STATIC_ID, STATIC_CSS, el.getRootNode());
|
|
11258
|
-
el.classList.add("vela-ui");
|
|
11259
|
-
if (theme) applyThemeTokens(el, theme);
|
|
11260
|
-
}
|
|
11261
|
-
|
|
11262
11410
|
// src/ui/components/popover/controller.ts
|
|
11263
11411
|
function insetRect(r, inset) {
|
|
11264
11412
|
return {
|
|
@@ -11365,7 +11513,8 @@ var Popover = class {
|
|
|
11365
11513
|
const doc = opts.trigger.ownerDocument;
|
|
11366
11514
|
injectStyles(POPOVER_STYLE_ID, POPOVER_CSS, doc);
|
|
11367
11515
|
this.trigger = opts.trigger;
|
|
11368
|
-
this.host = opts.host ?? doc.body;
|
|
11516
|
+
this.host = opts.host ?? floatingLayerHost(opts.trigger, doc.body);
|
|
11517
|
+
this.inheritsTokens = !opts.host && this.host !== doc.body;
|
|
11369
11518
|
this.ctrl = popoverController(opts);
|
|
11370
11519
|
this.boundary = opts.boundary ?? "viewport";
|
|
11371
11520
|
this.theme = opts.theme;
|
|
@@ -11397,7 +11546,7 @@ var Popover = class {
|
|
|
11397
11546
|
clearTimeout(this.leaveTimer);
|
|
11398
11547
|
this.leaveTimer = null;
|
|
11399
11548
|
}
|
|
11400
|
-
ensureUIHost(this.el, this.theme);
|
|
11549
|
+
ensureUIHost(this.el, this.inheritsTokens ? void 0 : this.theme);
|
|
11401
11550
|
if (this.fadeMs > 0) {
|
|
11402
11551
|
this.el.style.transition = `opacity ${this.fadeMs}ms ease`;
|
|
11403
11552
|
this.el.style.opacity = "0";
|
|
@@ -15703,33 +15852,32 @@ function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
|
|
|
15703
15852
|
center: (wickLeftDev + wickDev / 2) / dpr
|
|
15704
15853
|
};
|
|
15705
15854
|
}
|
|
15706
|
-
function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
|
|
15855
|
+
function aggregateCandleColumns(bars, i0, i1, xOf, yOf, colorOf2) {
|
|
15707
15856
|
const out = [];
|
|
15708
15857
|
let col = NaN;
|
|
15709
|
-
let
|
|
15858
|
+
let runs = [];
|
|
15710
15859
|
const flush = () => {
|
|
15711
|
-
if (
|
|
15712
|
-
|
|
15713
|
-
|
|
15714
|
-
|
|
15715
|
-
|
|
15716
|
-
|
|
15717
|
-
|
|
15718
|
-
|
|
15719
|
-
|
|
15720
|
-
prev2.headTime = next.headTime;
|
|
15721
|
-
prev2.open = next.open;
|
|
15722
|
-
}
|
|
15723
|
-
if (next.lastIdx > prev2.lastIdx) {
|
|
15724
|
-
prev2.lastIdx = next.lastIdx;
|
|
15725
|
-
prev2.close = next.close;
|
|
15860
|
+
if (runs.length === 0) return;
|
|
15861
|
+
runs.sort((a, b) => a.lo - b.lo);
|
|
15862
|
+
const merged = [];
|
|
15863
|
+
for (const next of runs) {
|
|
15864
|
+
let prev2;
|
|
15865
|
+
for (let k = merged.length - 1; k >= 0; k -= 1) {
|
|
15866
|
+
if (merged[k].color === next.color) {
|
|
15867
|
+
prev2 = merged[k];
|
|
15868
|
+
break;
|
|
15726
15869
|
}
|
|
15870
|
+
}
|
|
15871
|
+
if (prev2 && Math.abs(yOf(prev2.hi) - yOf(next.lo)) < 1) {
|
|
15872
|
+
prev2.hi = next.hi;
|
|
15873
|
+
if (next.lastIdx > prev2.lastIdx) prev2.lastIdx = next.lastIdx;
|
|
15727
15874
|
} else {
|
|
15728
15875
|
merged.push(next);
|
|
15729
15876
|
}
|
|
15730
15877
|
}
|
|
15731
|
-
|
|
15732
|
-
|
|
15878
|
+
merged.sort((a, b) => a.lastIdx - b.lastIdx);
|
|
15879
|
+
for (const r of merged) out.push({ x: col, hi: r.hi, lo: r.lo, color: r.color });
|
|
15880
|
+
runs = [];
|
|
15733
15881
|
};
|
|
15734
15882
|
for (let i = i0; i <= i1; i += 1) {
|
|
15735
15883
|
const b = bars[i];
|
|
@@ -15739,26 +15887,17 @@ function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
|
|
|
15739
15887
|
flush();
|
|
15740
15888
|
col = x;
|
|
15741
15889
|
}
|
|
15890
|
+
const color = colorOf2(b);
|
|
15742
15891
|
let lo = b.low;
|
|
15743
15892
|
let hi = b.high;
|
|
15744
|
-
let
|
|
15745
|
-
|
|
15746
|
-
|
|
15747
|
-
|
|
15748
|
-
|
|
15749
|
-
|
|
15750
|
-
|
|
15751
|
-
|
|
15752
|
-
if (iv.headIdx < headIdx) {
|
|
15753
|
-
headIdx = iv.headIdx;
|
|
15754
|
-
headTime = iv.headTime;
|
|
15755
|
-
open2 = iv.open;
|
|
15756
|
-
}
|
|
15757
|
-
intervals.splice(k, 1);
|
|
15758
|
-
}
|
|
15759
|
-
let insertAt = 0;
|
|
15760
|
-
while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
|
|
15761
|
-
intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open: open2, lastIdx: i, close: b.close });
|
|
15893
|
+
for (let k = runs.length - 1; k >= 0; k -= 1) {
|
|
15894
|
+
const r = runs[k];
|
|
15895
|
+
if (r.color !== color || r.lo > hi || r.hi < lo) continue;
|
|
15896
|
+
if (r.lo < lo) lo = r.lo;
|
|
15897
|
+
if (r.hi > hi) hi = r.hi;
|
|
15898
|
+
runs.splice(k, 1);
|
|
15899
|
+
}
|
|
15900
|
+
runs.push({ lo, hi, color, lastIdx: i });
|
|
15762
15901
|
}
|
|
15763
15902
|
flush();
|
|
15764
15903
|
return out;
|
|
@@ -16733,7 +16872,7 @@ var WebGL2Backend = class {
|
|
|
16733
16872
|
* Routing mirrors the main pass: own series per pane, force_overlay series on the price pane. */
|
|
16734
16873
|
emitGlowSources(b, scene, pane, coords, i0, i1) {
|
|
16735
16874
|
const emitOne = (s, off) => {
|
|
16736
|
-
if (!isLineLikeSeries(s) || s
|
|
16875
|
+
if (!isLineLikeSeries(s) || !seriesShownOn(s, "pane")) return;
|
|
16737
16876
|
if (s.kind === "histogram" || s.kind === "columns") return;
|
|
16738
16877
|
if (s.kind === "circles" || s.kind === "cross") this.emitPointMarkers(b, s, pane, coords, i0, i1, off);
|
|
16739
16878
|
else this.emitPolyline(b, s, pane, coords, i0, i1, s.kind === "step", off);
|
|
@@ -16844,7 +16983,7 @@ var WebGL2Backend = class {
|
|
|
16844
16983
|
const spacing = coords.bodySpacing();
|
|
16845
16984
|
const tier = candleTier(spacing);
|
|
16846
16985
|
if (tier === "aggregate") {
|
|
16847
|
-
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
16986
|
+
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, (bar) => barColors.get(bar.time) ?? (bar.close >= bar.open ? up : down));
|
|
16848
16987
|
return;
|
|
16849
16988
|
}
|
|
16850
16989
|
const tickW = Math.max(1, Math.round(spacing * 0.35));
|
|
@@ -16985,14 +17124,17 @@ var WebGL2Backend = class {
|
|
|
16985
17124
|
const spacing = coords.bodySpacing();
|
|
16986
17125
|
const tier = candleTier(spacing);
|
|
16987
17126
|
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
17127
|
+
const up = paint.up;
|
|
17128
|
+
const down = paint.down;
|
|
17129
|
+
const cs = paint.candle;
|
|
16988
17130
|
if (tier === "aggregate") {
|
|
16989
|
-
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
17131
|
+
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, (bar) => {
|
|
17132
|
+
const isUp = bar.close >= bar.open;
|
|
17133
|
+
return (isUp ? cs.wickUpColor : cs.wickDownColor) ?? barColors.get(bar.time) ?? (isUp ? up : down);
|
|
17134
|
+
});
|
|
16990
17135
|
return;
|
|
16991
17136
|
}
|
|
16992
17137
|
const drawBody = tier === "full";
|
|
16993
|
-
const up = paint.up;
|
|
16994
|
-
const down = paint.down;
|
|
16995
|
-
const cs = paint.candle;
|
|
16996
17138
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
16997
17139
|
for (let i = i0; i <= i1; i += 1) {
|
|
16998
17140
|
const bar = bars[i];
|
|
@@ -17041,24 +17183,26 @@ var WebGL2Backend = class {
|
|
|
17041
17183
|
/**
|
|
17042
17184
|
* Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
|
|
17043
17185
|
* rounded pixel column collapse into high-low sticks — one per contiguous
|
|
17044
|
-
* coverage run (see {@link aggregateCandleColumns}), so a price gap
|
|
17045
|
-
* column stays a void
|
|
17046
|
-
*
|
|
17186
|
+
* SAME-COLOR coverage run (see {@link aggregateCandleColumns}), so a price gap
|
|
17187
|
+
* inside the column stays a void and every bar's extent keeps its own color. Draw
|
|
17188
|
+
* cost stays bounded by screen width, not bar count. `colorOf` is the calling
|
|
17189
|
+
* style's per-bar paint rule (candles and OHLC bars resolve theirs differently).
|
|
17047
17190
|
*/
|
|
17048
|
-
emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
17191
|
+
emitCandlesAggregated(b, bars, i0, i1, coords, pane, colorOf2) {
|
|
17049
17192
|
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
17050
|
-
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
17051
|
-
const c = parseColor(
|
|
17193
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf, colorOf2)) {
|
|
17194
|
+
const c = parseColor(s.color);
|
|
17052
17195
|
const hY = yOf(s.hi);
|
|
17053
17196
|
b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
|
|
17054
17197
|
}
|
|
17055
17198
|
}
|
|
17056
17199
|
emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
17200
|
+
if (!seriesShownOn(spec, "pane")) return;
|
|
17057
17201
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
17058
17202
|
this.emitPlotCandles(b, spec, pane, coords, i0, i1, theme, off);
|
|
17059
17203
|
return;
|
|
17060
17204
|
}
|
|
17061
|
-
if (!isLineLikeSeries(spec)
|
|
17205
|
+
if (!isLineLikeSeries(spec)) return;
|
|
17062
17206
|
switch (spec.kind) {
|
|
17063
17207
|
case "histogram":
|
|
17064
17208
|
case "columns":
|
|
@@ -18722,7 +18866,7 @@ var Canvas2dBackend = class {
|
|
|
18722
18866
|
const spacing = coords.bodySpacing();
|
|
18723
18867
|
const tier = candleTier(spacing);
|
|
18724
18868
|
if (tier === "aggregate") {
|
|
18725
|
-
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
18869
|
+
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, (b) => barColors.get(b.time) ?? (b.close >= b.open ? up : down));
|
|
18726
18870
|
return;
|
|
18727
18871
|
}
|
|
18728
18872
|
const tickW = Math.max(1, Math.round(spacing * 0.35));
|
|
@@ -18898,12 +19042,15 @@ var Canvas2dBackend = class {
|
|
|
18898
19042
|
const spacing = coords.bodySpacing();
|
|
18899
19043
|
const tier = candleTier(spacing);
|
|
18900
19044
|
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
19045
|
+
const cs = paint.candle;
|
|
18901
19046
|
if (tier === "aggregate") {
|
|
18902
|
-
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
19047
|
+
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, (b) => {
|
|
19048
|
+
const up = b.close >= b.open;
|
|
19049
|
+
return (up ? cs.wickUpColor : cs.wickDownColor) ?? barColors.get(b.time) ?? (up ? paint.up : paint.down);
|
|
19050
|
+
});
|
|
18903
19051
|
return;
|
|
18904
19052
|
}
|
|
18905
19053
|
const drawBody = tier === "full";
|
|
18906
|
-
const cs = paint.candle;
|
|
18907
19054
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
18908
19055
|
for (let i = i0; i <= i1; i += 1) {
|
|
18909
19056
|
const b = bars[i];
|
|
@@ -18960,17 +19107,18 @@ var Canvas2dBackend = class {
|
|
|
18960
19107
|
}
|
|
18961
19108
|
/**
|
|
18962
19109
|
* Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
|
|
18963
|
-
* sticks (one per contiguous coverage run — see
|
|
18964
|
-
* so draw cost is bounded by screen width (not
|
|
18965
|
-
*
|
|
18966
|
-
* its
|
|
19110
|
+
* sticks (one per contiguous SAME-COLOR coverage run — see
|
|
19111
|
+
* {@link aggregateCandleColumns}), so draw cost is bounded by screen width (not
|
|
19112
|
+
* bar count) when zoomed far out, a price gap inside the column stays a void, and
|
|
19113
|
+
* every bar's extent keeps its own color. `colorOf` is the calling style's per-bar
|
|
19114
|
+
* paint rule (candles and OHLC bars resolve theirs differently).
|
|
18967
19115
|
*/
|
|
18968
|
-
drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
19116
|
+
drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, colorOf2) {
|
|
18969
19117
|
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
18970
19118
|
ctx.lineWidth = 1;
|
|
18971
|
-
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
19119
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf, colorOf2)) {
|
|
18972
19120
|
const x = s.x + 0.5;
|
|
18973
|
-
ctx.strokeStyle =
|
|
19121
|
+
ctx.strokeStyle = s.color;
|
|
18974
19122
|
ctx.beginPath();
|
|
18975
19123
|
ctx.moveTo(x, yOf(s.hi));
|
|
18976
19124
|
ctx.lineTo(x, yOf(s.lo));
|
|
@@ -18978,11 +19126,12 @@ var Canvas2dBackend = class {
|
|
|
18978
19126
|
}
|
|
18979
19127
|
}
|
|
18980
19128
|
drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
19129
|
+
if (!seriesShownOn(spec, "pane")) return;
|
|
18981
19130
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
18982
19131
|
this.drawPlotCandles(ctx, spec, pane, coords, i0, i1, theme, off);
|
|
18983
19132
|
return;
|
|
18984
19133
|
}
|
|
18985
|
-
if (!isLineLikeSeries(spec)
|
|
19134
|
+
if (!isLineLikeSeries(spec)) return;
|
|
18986
19135
|
switch (spec.kind) {
|
|
18987
19136
|
case "histogram":
|
|
18988
19137
|
case "columns":
|
|
@@ -27930,6 +28079,7 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
27930
28079
|
return { min: min - span * MARGIN_BOTTOM, max: max + span * MARGIN_TOP };
|
|
27931
28080
|
}
|
|
27932
28081
|
function considerSeries(s, i0, i1, off, consider) {
|
|
28082
|
+
if (!seriesInScale(s)) return;
|
|
27933
28083
|
if (s.kind === "candle" || s.kind === "bar") {
|
|
27934
28084
|
for (let i = i0; i <= i1; i += 1) {
|
|
27935
28085
|
const b = s.bars[i - off];
|
|
@@ -28149,6 +28299,22 @@ var MarkPopover = class {
|
|
|
28149
28299
|
this.openKey = null;
|
|
28150
28300
|
/** Bumped per open/close — a lazy content resolving after its popup went away is dropped. */
|
|
28151
28301
|
this.generation = 0;
|
|
28302
|
+
/**
|
|
28303
|
+
* The cluster whose popup was open when the current pointer press began. The anchor is
|
|
28304
|
+
* hit-transparent (the glyph is canvas pixels), so the shell's outside-dismiss closes the
|
|
28305
|
+
* popup on pointerdown; the click the renderer derives from the same pointerup must then
|
|
28306
|
+
* read as "close", not reopen that cluster. Captured on the document ahead of the shell's
|
|
28307
|
+
* (deferred) listener, cleared once the release has dispatched.
|
|
28308
|
+
*/
|
|
28309
|
+
this.pressKey = null;
|
|
28310
|
+
this.onPress = () => {
|
|
28311
|
+
if (this.openKey === null) return;
|
|
28312
|
+
this.pressKey = this.openKey;
|
|
28313
|
+
const doc = this.deps.plot.ownerDocument;
|
|
28314
|
+
const clear = () => void setTimeout(() => this.pressKey = null, 0);
|
|
28315
|
+
doc.addEventListener("pointerup", clear, { capture: true, once: true });
|
|
28316
|
+
doc.addEventListener("pointercancel", clear, { capture: true, once: true });
|
|
28317
|
+
};
|
|
28152
28318
|
const doc = deps.plot.ownerDocument;
|
|
28153
28319
|
injectStyles(CALLOUT_STYLE_ID, CALLOUT_CSS, doc);
|
|
28154
28320
|
injectStyles(MARKS_STYLE_ID, MARKS_CSS, doc);
|
|
@@ -28156,12 +28322,14 @@ var MarkPopover = class {
|
|
|
28156
28322
|
this.anchor.className = "vela-marks-anchor";
|
|
28157
28323
|
Object.assign(this.anchor.style, { position: "absolute", pointerEvents: "none", left: "0", top: "0", width: "0", height: "0" });
|
|
28158
28324
|
deps.plot.appendChild(this.anchor);
|
|
28325
|
+
doc.addEventListener("pointerdown", this.onPress, true);
|
|
28159
28326
|
}
|
|
28160
28327
|
/** The cluster key the open popup belongs to, or null. */
|
|
28161
28328
|
get key() {
|
|
28162
28329
|
return this.openKey;
|
|
28163
28330
|
}
|
|
28164
28331
|
open(cluster, rect) {
|
|
28332
|
+
if (this.pressKey === cluster.key) return;
|
|
28165
28333
|
this.close();
|
|
28166
28334
|
this.place(rect);
|
|
28167
28335
|
const gen = ++this.generation;
|
|
@@ -28209,6 +28377,7 @@ var MarkPopover = class {
|
|
|
28209
28377
|
}
|
|
28210
28378
|
destroy() {
|
|
28211
28379
|
this.close();
|
|
28380
|
+
this.deps.plot.ownerDocument.removeEventListener("pointerdown", this.onPress, true);
|
|
28212
28381
|
this.anchor.remove();
|
|
28213
28382
|
}
|
|
28214
28383
|
place(rect) {
|
|
@@ -29105,6 +29274,13 @@ var NativeRenderer = class {
|
|
|
29105
29274
|
this.backdropRenderer = new BackdropRenderer();
|
|
29106
29275
|
this.volumeRenderer = new VolumeRenderer();
|
|
29107
29276
|
/** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
|
|
29277
|
+
/**
|
|
29278
|
+
* The mounted SDK layers. One BASE entry per registered definition (channel = the layer
|
|
29279
|
+
* id, no owner of its own), plus one DEDICATED entry per mounted native indicator whose
|
|
29280
|
+
* model names its own channel (`native.channel` — an instance of a multi-instance type):
|
|
29281
|
+
* same definition, its own canvas, reading that instance's channel and following that
|
|
29282
|
+
* instance's pane and z key. `channel` is unique across the array.
|
|
29283
|
+
*/
|
|
29108
29284
|
this.extLayers = [];
|
|
29109
29285
|
/** Last applied layer-canvas order (ids below + above the data canvas) — re-slotted only on change. */
|
|
29110
29286
|
this.layerOrderSig = "";
|
|
@@ -30250,11 +30426,7 @@ var NativeRenderer = class {
|
|
|
30250
30426
|
Object.assign(this.overlayRoot.style, { position: "absolute", inset: "0", pointerEvents: "none" });
|
|
30251
30427
|
this.plot = document.createElement("div");
|
|
30252
30428
|
Object.assign(this.plot.style, { position: "absolute", top: "0", right: "0", bottom: "0", left: "0" });
|
|
30253
|
-
this.extLayers = rendererLayers().map((def) => {
|
|
30254
|
-
const canvas = document.createElement("canvas");
|
|
30255
|
-
Object.assign(canvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
30256
|
-
return { def, instance: def.create(), canvas };
|
|
30257
|
-
});
|
|
30429
|
+
this.extLayers = rendererLayers().map((def) => ({ def, instance: def.create(), canvas: this.createLayerCanvas(), channel: def.id, owner: null }));
|
|
30258
30430
|
const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
|
|
30259
30431
|
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
30260
30432
|
this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
|
|
@@ -30271,6 +30443,7 @@ var NativeRenderer = class {
|
|
|
30271
30443
|
this.volumeRenderer.mount(this.volumeCanvas);
|
|
30272
30444
|
this.vpvrRenderer.mount(this.vpvrCanvas);
|
|
30273
30445
|
for (const l of this.extLayers) l.instance.mount(l.canvas);
|
|
30446
|
+
for (const m of this.scene.indicators.values()) this.ensureInstanceLayer(m);
|
|
30274
30447
|
this.chrome.mount(this.chromeCanvas);
|
|
30275
30448
|
this.crosshairLayer.mount(this.cursorCanvas);
|
|
30276
30449
|
this.coords.setViewport(defaultViewport());
|
|
@@ -30863,6 +31036,7 @@ var NativeRenderer = class {
|
|
|
30863
31036
|
this.refreshAnchorOffset(model);
|
|
30864
31037
|
if (model.native && this.extLayers.some((l) => l.def.id === model.native.type)) this.scene.assignIndicatorZTop(model.id);
|
|
30865
31038
|
else this.scene.assignIndicatorZ(model.id);
|
|
31039
|
+
this.ensureInstanceLayer(model);
|
|
30866
31040
|
if (model.legend !== false) {
|
|
30867
31041
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
30868
31042
|
native: !!model.native,
|
|
@@ -30906,6 +31080,7 @@ var NativeRenderer = class {
|
|
|
30906
31080
|
this.scene.vpvrLayer = null;
|
|
30907
31081
|
}
|
|
30908
31082
|
this.scene.indicators.delete(handle.id);
|
|
31083
|
+
this.dropInstanceLayer(handle.id);
|
|
30909
31084
|
this.scene.forgetIndicatorZ(handle.id);
|
|
30910
31085
|
this.scene.forgetAnchorOffset(handle.id);
|
|
30911
31086
|
this.scene.dropIndicatorScale(handle.id);
|
|
@@ -31699,29 +31874,31 @@ var NativeRenderer = class {
|
|
|
31699
31874
|
up: bar.close >= bar.open
|
|
31700
31875
|
};
|
|
31701
31876
|
}
|
|
31702
|
-
/** One group per indicator (name = indicator title), each with a row per
|
|
31877
|
+
/** One group per indicator (name = indicator title), each with a row per plot shown in the data window. */
|
|
31703
31878
|
dataWindowGroups(idx, pricePane) {
|
|
31704
31879
|
const groups = [];
|
|
31705
31880
|
for (const model of this.scene.indicators.values()) {
|
|
31706
|
-
const rows = this.
|
|
31881
|
+
const rows = this.readoutRowsFor(model, idx, pricePane, "dataWindow");
|
|
31707
31882
|
if (rows.length) groups.push({ name: model.title, rows });
|
|
31708
31883
|
}
|
|
31709
31884
|
return groups;
|
|
31710
31885
|
}
|
|
31711
|
-
/** One indicator's readout at bar `idx
|
|
31712
|
-
|
|
31886
|
+
/** One indicator's readout at bar `idx` for one value surface (the legend or the data window):
|
|
31887
|
+
* a row per plot shown on that surface, formatted on its pane's scale. A plot's `display`
|
|
31888
|
+
* decides per surface — a data-window-only plot has a row here and none in the legend. */
|
|
31889
|
+
readoutRowsFor(model, idx, pricePane, surface) {
|
|
31713
31890
|
if (model.native?.type === "volume") return this.volumeReadoutRows(model, idx);
|
|
31714
31891
|
const pane = (model.paneId ? this.scene.panes.get(model.paneId) : null) ?? pricePane;
|
|
31715
31892
|
const off = this.scene.offsetOf(model.id);
|
|
31716
31893
|
const rows = [];
|
|
31717
31894
|
for (const s of model.series) {
|
|
31895
|
+
if (!seriesShownOn(s, surface)) continue;
|
|
31718
31896
|
let value;
|
|
31719
31897
|
let color;
|
|
31720
31898
|
if (s.kind === "candle" || s.kind === "bar") {
|
|
31721
31899
|
value = s.bars[idx - off]?.close;
|
|
31722
31900
|
color = s.style?.up ?? this.theme.upColor;
|
|
31723
31901
|
} else if (isLineLikeSeries(s)) {
|
|
31724
|
-
if (s.visible === false) continue;
|
|
31725
31902
|
value = s.points[idx - off]?.value;
|
|
31726
31903
|
color = s.points[idx - off]?.color ?? s.style.color;
|
|
31727
31904
|
} else {
|
|
@@ -31744,9 +31921,10 @@ var NativeRenderer = class {
|
|
|
31744
31921
|
const color = bar.close >= bar.open ? cfg?.upColor ?? this.theme.upColor : cfg?.downColor ?? this.theme.downColor;
|
|
31745
31922
|
return [{ label: model.title, value: formatVolume(vol), color }];
|
|
31746
31923
|
}
|
|
31747
|
-
/** Refresh the plot values beside every legend title — the same
|
|
31748
|
-
*
|
|
31749
|
-
*
|
|
31924
|
+
/** Refresh the plot values beside every legend title — the same bar the data window reads
|
|
31925
|
+
* (crosshair bar, else the latest bar), pushed per paint, filtered to the plots whose
|
|
31926
|
+
* `display` includes the legend. A hidden indicator has no scene model, so its row is
|
|
31927
|
+
* absent from the map and its readout clears. */
|
|
31750
31928
|
updateLegendValues() {
|
|
31751
31929
|
if (!this.inputsUI) return;
|
|
31752
31930
|
const n = this.bars.length;
|
|
@@ -31755,7 +31933,7 @@ var NativeRenderer = class {
|
|
|
31755
31933
|
const idx = this.hoverLogical != null ? this.hoverLogical : n - 1;
|
|
31756
31934
|
const pricePane = this.dataWindowPricePane();
|
|
31757
31935
|
for (const model of this.scene.indicators.values()) {
|
|
31758
|
-
values.set(model.id, this.
|
|
31936
|
+
values.set(model.id, this.readoutRowsFor(model, idx, pricePane, "legend").map((r) => ({ value: r.value, color: r.color })));
|
|
31759
31937
|
}
|
|
31760
31938
|
}
|
|
31761
31939
|
this.inputsUI.setPlotValues(values);
|
|
@@ -31781,9 +31959,9 @@ var NativeRenderer = class {
|
|
|
31781
31959
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
31782
31960
|
for (const l of this.extLayers) {
|
|
31783
31961
|
if (!l.def.repaintOnCursor) continue;
|
|
31784
|
-
const lp = this.layerPane(l
|
|
31962
|
+
const lp = this.layerPane(l) ?? pane;
|
|
31785
31963
|
if (lp.collapsed) continue;
|
|
31786
|
-
l.instance.render(this.extLayerArgs(l
|
|
31964
|
+
l.instance.render(this.extLayerArgs(l, lp.scale, lp.bounds, nowMs));
|
|
31787
31965
|
if (this.animZoom.on && l.instance.animating?.()) this.animator.start();
|
|
31788
31966
|
}
|
|
31789
31967
|
}
|
|
@@ -31792,13 +31970,15 @@ var NativeRenderer = class {
|
|
|
31792
31970
|
if (canvas.width === 0 || canvas.height === 0) return;
|
|
31793
31971
|
canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
|
|
31794
31972
|
}
|
|
31795
|
-
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths).
|
|
31796
|
-
|
|
31973
|
+
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths).
|
|
31974
|
+
* Data + pending come from the entry's channel (per instance for a dedicated entry);
|
|
31975
|
+
* the settings bag is the TYPE's — a chart type's dialog values, one bag per type. */
|
|
31976
|
+
extLayerArgs(l, scale, bounds, nowMs) {
|
|
31797
31977
|
return {
|
|
31798
31978
|
bars: this.scene.bars,
|
|
31799
|
-
data: this.scene.nativeData.get(
|
|
31800
|
-
settings: this.scene.nativeData.get(`${id}-settings`) ?? {},
|
|
31801
|
-
pending: this.scene.nativePending.get(
|
|
31979
|
+
data: this.scene.nativeData.get(l.channel),
|
|
31980
|
+
settings: this.scene.nativeData.get(`${l.def.id}-settings`) ?? {},
|
|
31981
|
+
pending: this.scene.nativePending.get(l.channel) ?? [],
|
|
31802
31982
|
coords: this.coords,
|
|
31803
31983
|
scale,
|
|
31804
31984
|
bounds,
|
|
@@ -31841,12 +32021,12 @@ var NativeRenderer = class {
|
|
|
31841
32021
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
31842
32022
|
let folded = null;
|
|
31843
32023
|
for (const l of this.extLayers) {
|
|
31844
|
-
const lp = this.layerPane(l
|
|
32024
|
+
const lp = this.layerPane(l) ?? pane;
|
|
31845
32025
|
if (lp.collapsed) {
|
|
31846
32026
|
this.clearLayerCanvas(l.canvas);
|
|
31847
32027
|
continue;
|
|
31848
32028
|
}
|
|
31849
|
-
const args = this.extLayerArgs(l
|
|
32029
|
+
const args = this.extLayerArgs(l, lp.scale, lp.bounds, nowMs);
|
|
31850
32030
|
l.instance.render(args);
|
|
31851
32031
|
if (lp === pane) folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
31852
32032
|
if (this.animZoom.on && l.instance.animating?.()) this.animator.start();
|
|
@@ -32010,7 +32190,7 @@ var NativeRenderer = class {
|
|
|
32010
32190
|
const or = overlaySeriesRange(this.scene.indicators.values(), i0, i1, (id) => this.scene.offsetOf(id));
|
|
32011
32191
|
if (or) dr = dr ? { min: Math.min(dr.min, or.min), max: Math.max(dr.max, or.max) } : or;
|
|
32012
32192
|
}
|
|
32013
|
-
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
32193
|
+
const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels));
|
|
32014
32194
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
32015
32195
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
32016
32196
|
pane.axisFormat = void 0;
|
|
@@ -32129,19 +32309,60 @@ var NativeRenderer = class {
|
|
|
32129
32309
|
}
|
|
32130
32310
|
return null;
|
|
32131
32311
|
}
|
|
32132
|
-
/**
|
|
32133
|
-
|
|
32134
|
-
|
|
32135
|
-
|
|
32136
|
-
|
|
32312
|
+
/** One transparent, pointer-transparent SDK layer canvas covering the plot. */
|
|
32313
|
+
createLayerCanvas() {
|
|
32314
|
+
const canvas = document.createElement("canvas");
|
|
32315
|
+
Object.assign(canvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
32316
|
+
return canvas;
|
|
32317
|
+
}
|
|
32318
|
+
/**
|
|
32319
|
+
* Mount the DEDICATED layer of a native indicator that names its own channel (an
|
|
32320
|
+
* instance of a multi-instance type painting through a registered layer): a fresh
|
|
32321
|
+
* instance of the layer's definition on its own canvas, reading that channel. Idempotent
|
|
32322
|
+
* — a show after hide re-mounts the model, and the layer (kept across the hide, like the
|
|
32323
|
+
* z key) is simply reused. Skipped while the renderer itself is unmounted (`mount()`
|
|
32324
|
+
* catches up from the scene).
|
|
32325
|
+
*/
|
|
32326
|
+
ensureInstanceLayer(model) {
|
|
32327
|
+
const channel = model.native?.channel;
|
|
32328
|
+
if (!channel || !this.plot) return;
|
|
32329
|
+
if (this.extLayers.some((l) => l.owner === model.id)) return;
|
|
32330
|
+
const base = this.extLayers.find((l) => l.owner === null && l.def.id === model.native.type);
|
|
32331
|
+
if (!base) return;
|
|
32332
|
+
const canvas = this.createLayerCanvas();
|
|
32333
|
+
canvas.width = this.dataCanvas.width;
|
|
32334
|
+
canvas.height = this.dataCanvas.height;
|
|
32335
|
+
canvas.style.width = this.dataCanvas.style.width || "100%";
|
|
32336
|
+
canvas.style.height = this.dataCanvas.style.height || "100%";
|
|
32337
|
+
const entry = { def: base.def, instance: base.def.create(), canvas, channel, owner: model.id };
|
|
32338
|
+
this.extLayers.push(entry);
|
|
32339
|
+
this.plot.insertBefore(canvas, this.chromeCanvas);
|
|
32340
|
+
entry.instance.mount(canvas);
|
|
32341
|
+
}
|
|
32342
|
+
/** Tear down the dedicated layer of a removed indicator (a hide keeps it). */
|
|
32343
|
+
dropInstanceLayer(indicatorId) {
|
|
32344
|
+
const i = this.extLayers.findIndex((l2) => l2.owner === indicatorId);
|
|
32345
|
+
if (i < 0) return;
|
|
32346
|
+
const [l] = this.extLayers.splice(i, 1);
|
|
32347
|
+
l.instance.destroy?.();
|
|
32348
|
+
l.canvas.remove();
|
|
32349
|
+
}
|
|
32350
|
+
/** The mounted native indicator that OWNS an SDK layer entry. A dedicated entry is
|
|
32351
|
+
* owned by the instance it was mounted for; the base entry by the mounted indicator
|
|
32352
|
+
* whose type equals the layer id and that has no channel of its own (the id doubles
|
|
32353
|
+
* as the data channel, so the pairing is the SDK's own contract). Null for chart-type
|
|
32354
|
+
* channels and while the owner is hidden (a hidden indicator leaves the scene; its
|
|
32355
|
+
* cleared data channel paints nothing anyway). */
|
|
32356
|
+
layerOwner(l) {
|
|
32357
|
+
if (l.owner !== null) return this.scene.indicators.get(l.owner) ?? null;
|
|
32137
32358
|
for (const m of this.scene.indicators.values()) {
|
|
32138
|
-
if (m.native?.type ===
|
|
32359
|
+
if (m.native?.type === l.def.id && !m.native.channel) return m;
|
|
32139
32360
|
}
|
|
32140
32361
|
return null;
|
|
32141
32362
|
}
|
|
32142
32363
|
/** The pane an SDK layer paints on: its owner's pane, else the price pane. */
|
|
32143
|
-
layerPane(
|
|
32144
|
-
const owner = this.layerOwner(
|
|
32364
|
+
layerPane(l) {
|
|
32365
|
+
const owner = this.layerOwner(l);
|
|
32145
32366
|
const paneId = owner ? owner.paneId ?? PRICE_PANE_ID : PRICE_PANE_ID;
|
|
32146
32367
|
return this.scene.panes.get(paneId) ?? null;
|
|
32147
32368
|
}
|
|
@@ -32149,12 +32370,12 @@ var NativeRenderer = class {
|
|
|
32149
32370
|
* owned layers by their owner's z key against the candles' (an indicator restacked
|
|
32150
32371
|
* below the candles takes its layer canvas along), unowned by declared placement. */
|
|
32151
32372
|
orderedLayerCanvases() {
|
|
32152
|
-
const byId = new Map(this.extLayers.map((l) => [l.
|
|
32373
|
+
const byId = new Map(this.extLayers.map((l) => [l.channel, l.canvas]));
|
|
32153
32374
|
const { below, above } = stackLayers(
|
|
32154
32375
|
this.extLayers.map((l) => {
|
|
32155
|
-
const owner = this.layerOwner(l
|
|
32376
|
+
const owner = this.layerOwner(l);
|
|
32156
32377
|
return {
|
|
32157
|
-
id: l.
|
|
32378
|
+
id: l.channel,
|
|
32158
32379
|
placement: l.def.placement === "below-data" ? "below-data" : "above-data",
|
|
32159
32380
|
ownerZ: owner ? this.scene.zOf(owner.id) : null
|
|
32160
32381
|
};
|
|
@@ -32176,7 +32397,7 @@ var NativeRenderer = class {
|
|
|
32176
32397
|
syncLayerCanvasOrder() {
|
|
32177
32398
|
if (this.extLayers.length === 0 || !this.plot) return;
|
|
32178
32399
|
const { below, above } = this.orderedLayerCanvases();
|
|
32179
|
-
const sig = [...below.map((c) => this.extLayers.find((l) => l.canvas === c).
|
|
32400
|
+
const sig = [...below.map((c) => this.extLayers.find((l) => l.canvas === c).channel), "|", ...above.map((c) => this.extLayers.find((l) => l.canvas === c).channel)].join(",");
|
|
32180
32401
|
if (sig === this.layerOrderSig) return;
|
|
32181
32402
|
this.layerOrderSig = sig;
|
|
32182
32403
|
for (const c of below) this.plot.insertBefore(c, this.dataCanvas);
|
|
@@ -32200,6 +32421,17 @@ var NativeRenderer = class {
|
|
|
32200
32421
|
(m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type)
|
|
32201
32422
|
);
|
|
32202
32423
|
}
|
|
32424
|
+
/**
|
|
32425
|
+
* True when SDK-layer content on the price pane paints at BAR PRICES without
|
|
32426
|
+
* contributing a series: the active chart type's own layer, or an overlay layer native
|
|
32427
|
+
* (a series-less model whose type names a mounted layer). Hiding the candles must then
|
|
32428
|
+
* keep the bars in the price scale — with nothing else on the pane the scale would fall
|
|
32429
|
+
* to the {0,1} placeholder and every such layer would paint off-screen and vanish.
|
|
32430
|
+
*/
|
|
32431
|
+
priceLayersAnchoredToBars(masterModels) {
|
|
32432
|
+
if (this.extLayers.some((l) => l.owner === null && l.def.id === this.scene.priceStyle)) return true;
|
|
32433
|
+
return masterModels.some((m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type));
|
|
32434
|
+
}
|
|
32203
32435
|
/** Per-pane scale state for a host UI (e.g. a price-axis context menu): the pane's pixel
|
|
32204
32436
|
* band (`top`/`height`, so a click y maps to a pane) plus its current axis `mode`/`log`.
|
|
32205
32437
|
* Top-to-bottom order. Every pane is independent — the price pane from the scene setting,
|
|
@@ -35704,6 +35936,8 @@ exports.rendererDefaults = rendererDefaults;
|
|
|
35704
35936
|
exports.rendererLayers = rendererLayers;
|
|
35705
35937
|
exports.resolveEngines = resolveEngines;
|
|
35706
35938
|
exports.resolveTheme = resolveTheme;
|
|
35939
|
+
exports.seriesInScale = seriesInScale;
|
|
35940
|
+
exports.seriesShownOn = seriesShownOn;
|
|
35707
35941
|
exports.settingsRowValueKeys = settingsRowValueKeys;
|
|
35708
35942
|
exports.sharedBarStore = sharedBarStore;
|
|
35709
35943
|
exports.sidePanels = sidePanels;
|