@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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, DEFAULT_DRAWING_COLOR, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, magnifierTimeframeLabel, lineSegmentIntersection, MAGNIFIER_TIMEFRAME_OPTIONS, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS, stableSeriesId } from './chunk-
|
|
2
|
-
import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, CALLOUT_STYLE_ID, CALLOUT_CSS, Popover, blendOver, splitColor, CalloutBubble, Menu, TextArea, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-
|
|
1
|
+
import { deserializeDrawing, chartTypes, rendererLayers, isLineLikeSeries, seriesShownOn, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, nativeInstanceChannel, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, DEFAULT_DRAWING_COLOR, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, magnifierTimeframeLabel, lineSegmentIntersection, MAGNIFIER_TIMEFRAME_OPTIONS, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, seriesInScale, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS, stableSeriesId } from './chunk-BFA32GOU.js';
|
|
2
|
+
import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, CALLOUT_STYLE_ID, CALLOUT_CSS, Popover, blendOver, splitColor, CalloutBubble, Menu, TextArea, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-BKHSQ4YM.js';
|
|
3
3
|
import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BULLISH, CATEGORICAL, SERIES_LINE, BEARISH, isDarkColor, INFO, injectStyles, CHIP_PLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, iconMarkup, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, SLATE, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-CAFCLMPF.js';
|
|
4
4
|
|
|
5
5
|
// src/core/events/EventBus.ts
|
|
@@ -3170,11 +3170,6 @@ function resolveAnimations(animations) {
|
|
|
3170
3170
|
};
|
|
3171
3171
|
}
|
|
3172
3172
|
|
|
3173
|
-
// src/core/model/series.ts
|
|
3174
|
-
function isLineLikeSeries(spec) {
|
|
3175
|
-
return spec.kind !== "candle" && spec.kind !== "bar" && spec.kind !== "markers";
|
|
3176
|
-
}
|
|
3177
|
-
|
|
3178
3173
|
// src/renderers/shared/IndicatorInputsDialog.ts
|
|
3179
3174
|
var PROPS_TAB = "Properties";
|
|
3180
3175
|
function bagOf(row, decl) {
|
|
@@ -5255,33 +5250,32 @@ function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
|
|
|
5255
5250
|
center: (wickLeftDev + wickDev / 2) / dpr
|
|
5256
5251
|
};
|
|
5257
5252
|
}
|
|
5258
|
-
function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
|
|
5253
|
+
function aggregateCandleColumns(bars, i0, i1, xOf, yOf, colorOf2) {
|
|
5259
5254
|
const out = [];
|
|
5260
5255
|
let col = NaN;
|
|
5261
|
-
let
|
|
5256
|
+
let runs = [];
|
|
5262
5257
|
const flush = () => {
|
|
5263
|
-
if (
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
prev2.headTime = next.headTime;
|
|
5273
|
-
prev2.open = next.open;
|
|
5274
|
-
}
|
|
5275
|
-
if (next.lastIdx > prev2.lastIdx) {
|
|
5276
|
-
prev2.lastIdx = next.lastIdx;
|
|
5277
|
-
prev2.close = next.close;
|
|
5258
|
+
if (runs.length === 0) return;
|
|
5259
|
+
runs.sort((a, b) => a.lo - b.lo);
|
|
5260
|
+
const merged = [];
|
|
5261
|
+
for (const next of runs) {
|
|
5262
|
+
let prev2;
|
|
5263
|
+
for (let k = merged.length - 1; k >= 0; k -= 1) {
|
|
5264
|
+
if (merged[k].color === next.color) {
|
|
5265
|
+
prev2 = merged[k];
|
|
5266
|
+
break;
|
|
5278
5267
|
}
|
|
5268
|
+
}
|
|
5269
|
+
if (prev2 && Math.abs(yOf(prev2.hi) - yOf(next.lo)) < 1) {
|
|
5270
|
+
prev2.hi = next.hi;
|
|
5271
|
+
if (next.lastIdx > prev2.lastIdx) prev2.lastIdx = next.lastIdx;
|
|
5279
5272
|
} else {
|
|
5280
5273
|
merged.push(next);
|
|
5281
5274
|
}
|
|
5282
5275
|
}
|
|
5283
|
-
|
|
5284
|
-
|
|
5276
|
+
merged.sort((a, b) => a.lastIdx - b.lastIdx);
|
|
5277
|
+
for (const r of merged) out.push({ x: col, hi: r.hi, lo: r.lo, color: r.color });
|
|
5278
|
+
runs = [];
|
|
5285
5279
|
};
|
|
5286
5280
|
for (let i = i0; i <= i1; i += 1) {
|
|
5287
5281
|
const b = bars[i];
|
|
@@ -5291,26 +5285,17 @@ function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
|
|
|
5291
5285
|
flush();
|
|
5292
5286
|
col = x;
|
|
5293
5287
|
}
|
|
5288
|
+
const color = colorOf2(b);
|
|
5294
5289
|
let lo = b.low;
|
|
5295
5290
|
let hi = b.high;
|
|
5296
|
-
let
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
if (iv.headIdx < headIdx) {
|
|
5305
|
-
headIdx = iv.headIdx;
|
|
5306
|
-
headTime = iv.headTime;
|
|
5307
|
-
open = iv.open;
|
|
5308
|
-
}
|
|
5309
|
-
intervals.splice(k, 1);
|
|
5310
|
-
}
|
|
5311
|
-
let insertAt = 0;
|
|
5312
|
-
while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
|
|
5313
|
-
intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open, lastIdx: i, close: b.close });
|
|
5291
|
+
for (let k = runs.length - 1; k >= 0; k -= 1) {
|
|
5292
|
+
const r = runs[k];
|
|
5293
|
+
if (r.color !== color || r.lo > hi || r.hi < lo) continue;
|
|
5294
|
+
if (r.lo < lo) lo = r.lo;
|
|
5295
|
+
if (r.hi > hi) hi = r.hi;
|
|
5296
|
+
runs.splice(k, 1);
|
|
5297
|
+
}
|
|
5298
|
+
runs.push({ lo, hi, color, lastIdx: i });
|
|
5314
5299
|
}
|
|
5315
5300
|
flush();
|
|
5316
5301
|
return out;
|
|
@@ -6285,7 +6270,7 @@ var WebGL2Backend = class {
|
|
|
6285
6270
|
* Routing mirrors the main pass: own series per pane, force_overlay series on the price pane. */
|
|
6286
6271
|
emitGlowSources(b, scene, pane, coords, i0, i1) {
|
|
6287
6272
|
const emitOne = (s, off) => {
|
|
6288
|
-
if (!isLineLikeSeries(s) || s
|
|
6273
|
+
if (!isLineLikeSeries(s) || !seriesShownOn(s, "pane")) return;
|
|
6289
6274
|
if (s.kind === "histogram" || s.kind === "columns") return;
|
|
6290
6275
|
if (s.kind === "circles" || s.kind === "cross") this.emitPointMarkers(b, s, pane, coords, i0, i1, off);
|
|
6291
6276
|
else this.emitPolyline(b, s, pane, coords, i0, i1, s.kind === "step", off);
|
|
@@ -6396,7 +6381,7 @@ var WebGL2Backend = class {
|
|
|
6396
6381
|
const spacing = coords.bodySpacing();
|
|
6397
6382
|
const tier = candleTier(spacing);
|
|
6398
6383
|
if (tier === "aggregate") {
|
|
6399
|
-
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
6384
|
+
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, (bar) => barColors.get(bar.time) ?? (bar.close >= bar.open ? up : down));
|
|
6400
6385
|
return;
|
|
6401
6386
|
}
|
|
6402
6387
|
const tickW = Math.max(1, Math.round(spacing * 0.35));
|
|
@@ -6537,14 +6522,17 @@ var WebGL2Backend = class {
|
|
|
6537
6522
|
const spacing = coords.bodySpacing();
|
|
6538
6523
|
const tier = candleTier(spacing);
|
|
6539
6524
|
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
6525
|
+
const up = paint.up;
|
|
6526
|
+
const down = paint.down;
|
|
6527
|
+
const cs = paint.candle;
|
|
6540
6528
|
if (tier === "aggregate") {
|
|
6541
|
-
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
6529
|
+
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, (bar) => {
|
|
6530
|
+
const isUp = bar.close >= bar.open;
|
|
6531
|
+
return (isUp ? cs.wickUpColor : cs.wickDownColor) ?? barColors.get(bar.time) ?? (isUp ? up : down);
|
|
6532
|
+
});
|
|
6542
6533
|
return;
|
|
6543
6534
|
}
|
|
6544
6535
|
const drawBody = tier === "full";
|
|
6545
|
-
const up = paint.up;
|
|
6546
|
-
const down = paint.down;
|
|
6547
|
-
const cs = paint.candle;
|
|
6548
6536
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
6549
6537
|
for (let i = i0; i <= i1; i += 1) {
|
|
6550
6538
|
const bar = bars[i];
|
|
@@ -6593,24 +6581,26 @@ var WebGL2Backend = class {
|
|
|
6593
6581
|
/**
|
|
6594
6582
|
* Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
|
|
6595
6583
|
* rounded pixel column collapse into high-low sticks — one per contiguous
|
|
6596
|
-
* coverage run (see {@link aggregateCandleColumns}), so a price gap
|
|
6597
|
-
* column stays a void
|
|
6598
|
-
*
|
|
6584
|
+
* SAME-COLOR coverage run (see {@link aggregateCandleColumns}), so a price gap
|
|
6585
|
+
* inside the column stays a void and every bar's extent keeps its own color. Draw
|
|
6586
|
+
* cost stays bounded by screen width, not bar count. `colorOf` is the calling
|
|
6587
|
+
* style's per-bar paint rule (candles and OHLC bars resolve theirs differently).
|
|
6599
6588
|
*/
|
|
6600
|
-
emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
6589
|
+
emitCandlesAggregated(b, bars, i0, i1, coords, pane, colorOf2) {
|
|
6601
6590
|
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
6602
|
-
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
6603
|
-
const c = parseColor(
|
|
6591
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf, colorOf2)) {
|
|
6592
|
+
const c = parseColor(s.color);
|
|
6604
6593
|
const hY = yOf(s.hi);
|
|
6605
6594
|
b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
|
|
6606
6595
|
}
|
|
6607
6596
|
}
|
|
6608
6597
|
emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
6598
|
+
if (!seriesShownOn(spec, "pane")) return;
|
|
6609
6599
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
6610
6600
|
this.emitPlotCandles(b, spec, pane, coords, i0, i1, theme, off);
|
|
6611
6601
|
return;
|
|
6612
6602
|
}
|
|
6613
|
-
if (!isLineLikeSeries(spec)
|
|
6603
|
+
if (!isLineLikeSeries(spec)) return;
|
|
6614
6604
|
switch (spec.kind) {
|
|
6615
6605
|
case "histogram":
|
|
6616
6606
|
case "columns":
|
|
@@ -8274,7 +8264,7 @@ var Canvas2dBackend = class {
|
|
|
8274
8264
|
const spacing = coords.bodySpacing();
|
|
8275
8265
|
const tier = candleTier(spacing);
|
|
8276
8266
|
if (tier === "aggregate") {
|
|
8277
|
-
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
8267
|
+
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, (b) => barColors.get(b.time) ?? (b.close >= b.open ? up : down));
|
|
8278
8268
|
return;
|
|
8279
8269
|
}
|
|
8280
8270
|
const tickW = Math.max(1, Math.round(spacing * 0.35));
|
|
@@ -8450,12 +8440,15 @@ var Canvas2dBackend = class {
|
|
|
8450
8440
|
const spacing = coords.bodySpacing();
|
|
8451
8441
|
const tier = candleTier(spacing);
|
|
8452
8442
|
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
8443
|
+
const cs = paint.candle;
|
|
8453
8444
|
if (tier === "aggregate") {
|
|
8454
|
-
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
8445
|
+
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, (b) => {
|
|
8446
|
+
const up = b.close >= b.open;
|
|
8447
|
+
return (up ? cs.wickUpColor : cs.wickDownColor) ?? barColors.get(b.time) ?? (up ? paint.up : paint.down);
|
|
8448
|
+
});
|
|
8455
8449
|
return;
|
|
8456
8450
|
}
|
|
8457
8451
|
const drawBody = tier === "full";
|
|
8458
|
-
const cs = paint.candle;
|
|
8459
8452
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
8460
8453
|
for (let i = i0; i <= i1; i += 1) {
|
|
8461
8454
|
const b = bars[i];
|
|
@@ -8512,17 +8505,18 @@ var Canvas2dBackend = class {
|
|
|
8512
8505
|
}
|
|
8513
8506
|
/**
|
|
8514
8507
|
* Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
|
|
8515
|
-
* sticks (one per contiguous coverage run — see
|
|
8516
|
-
* so draw cost is bounded by screen width (not
|
|
8517
|
-
*
|
|
8518
|
-
* its
|
|
8508
|
+
* sticks (one per contiguous SAME-COLOR coverage run — see
|
|
8509
|
+
* {@link aggregateCandleColumns}), so draw cost is bounded by screen width (not
|
|
8510
|
+
* bar count) when zoomed far out, a price gap inside the column stays a void, and
|
|
8511
|
+
* every bar's extent keeps its own color. `colorOf` is the calling style's per-bar
|
|
8512
|
+
* paint rule (candles and OHLC bars resolve theirs differently).
|
|
8519
8513
|
*/
|
|
8520
|
-
drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
8514
|
+
drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, colorOf2) {
|
|
8521
8515
|
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
8522
8516
|
ctx.lineWidth = 1;
|
|
8523
|
-
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
8517
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf, colorOf2)) {
|
|
8524
8518
|
const x = s.x + 0.5;
|
|
8525
|
-
ctx.strokeStyle =
|
|
8519
|
+
ctx.strokeStyle = s.color;
|
|
8526
8520
|
ctx.beginPath();
|
|
8527
8521
|
ctx.moveTo(x, yOf(s.hi));
|
|
8528
8522
|
ctx.lineTo(x, yOf(s.lo));
|
|
@@ -8530,11 +8524,12 @@ var Canvas2dBackend = class {
|
|
|
8530
8524
|
}
|
|
8531
8525
|
}
|
|
8532
8526
|
drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
8527
|
+
if (!seriesShownOn(spec, "pane")) return;
|
|
8533
8528
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
8534
8529
|
this.drawPlotCandles(ctx, spec, pane, coords, i0, i1, theme, off);
|
|
8535
8530
|
return;
|
|
8536
8531
|
}
|
|
8537
|
-
if (!isLineLikeSeries(spec)
|
|
8532
|
+
if (!isLineLikeSeries(spec)) return;
|
|
8538
8533
|
switch (spec.kind) {
|
|
8539
8534
|
case "histogram":
|
|
8540
8535
|
case "columns":
|
|
@@ -16772,6 +16767,7 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
16772
16767
|
return { min: min - span * MARGIN_BOTTOM, max: max + span * MARGIN_TOP };
|
|
16773
16768
|
}
|
|
16774
16769
|
function considerSeries(s, i0, i1, off, consider) {
|
|
16770
|
+
if (!seriesInScale(s)) return;
|
|
16775
16771
|
if (s.kind === "candle" || s.kind === "bar") {
|
|
16776
16772
|
for (let i = i0; i <= i1; i += 1) {
|
|
16777
16773
|
const b = s.bars[i - off];
|
|
@@ -16991,6 +16987,22 @@ var MarkPopover = class {
|
|
|
16991
16987
|
this.openKey = null;
|
|
16992
16988
|
/** Bumped per open/close — a lazy content resolving after its popup went away is dropped. */
|
|
16993
16989
|
this.generation = 0;
|
|
16990
|
+
/**
|
|
16991
|
+
* The cluster whose popup was open when the current pointer press began. The anchor is
|
|
16992
|
+
* hit-transparent (the glyph is canvas pixels), so the shell's outside-dismiss closes the
|
|
16993
|
+
* popup on pointerdown; the click the renderer derives from the same pointerup must then
|
|
16994
|
+
* read as "close", not reopen that cluster. Captured on the document ahead of the shell's
|
|
16995
|
+
* (deferred) listener, cleared once the release has dispatched.
|
|
16996
|
+
*/
|
|
16997
|
+
this.pressKey = null;
|
|
16998
|
+
this.onPress = () => {
|
|
16999
|
+
if (this.openKey === null) return;
|
|
17000
|
+
this.pressKey = this.openKey;
|
|
17001
|
+
const doc = this.deps.plot.ownerDocument;
|
|
17002
|
+
const clear = () => void setTimeout(() => this.pressKey = null, 0);
|
|
17003
|
+
doc.addEventListener("pointerup", clear, { capture: true, once: true });
|
|
17004
|
+
doc.addEventListener("pointercancel", clear, { capture: true, once: true });
|
|
17005
|
+
};
|
|
16994
17006
|
const doc = deps.plot.ownerDocument;
|
|
16995
17007
|
injectStyles(CALLOUT_STYLE_ID, CALLOUT_CSS, doc);
|
|
16996
17008
|
injectStyles(MARKS_STYLE_ID, MARKS_CSS, doc);
|
|
@@ -16998,12 +17010,14 @@ var MarkPopover = class {
|
|
|
16998
17010
|
this.anchor.className = "vela-marks-anchor";
|
|
16999
17011
|
Object.assign(this.anchor.style, { position: "absolute", pointerEvents: "none", left: "0", top: "0", width: "0", height: "0" });
|
|
17000
17012
|
deps.plot.appendChild(this.anchor);
|
|
17013
|
+
doc.addEventListener("pointerdown", this.onPress, true);
|
|
17001
17014
|
}
|
|
17002
17015
|
/** The cluster key the open popup belongs to, or null. */
|
|
17003
17016
|
get key() {
|
|
17004
17017
|
return this.openKey;
|
|
17005
17018
|
}
|
|
17006
17019
|
open(cluster, rect) {
|
|
17020
|
+
if (this.pressKey === cluster.key) return;
|
|
17007
17021
|
this.close();
|
|
17008
17022
|
this.place(rect);
|
|
17009
17023
|
const gen = ++this.generation;
|
|
@@ -17051,6 +17065,7 @@ var MarkPopover = class {
|
|
|
17051
17065
|
}
|
|
17052
17066
|
destroy() {
|
|
17053
17067
|
this.close();
|
|
17068
|
+
this.deps.plot.ownerDocument.removeEventListener("pointerdown", this.onPress, true);
|
|
17054
17069
|
this.anchor.remove();
|
|
17055
17070
|
}
|
|
17056
17071
|
place(rect) {
|
|
@@ -17921,6 +17936,13 @@ var NativeRenderer = class {
|
|
|
17921
17936
|
this.backdropRenderer = new BackdropRenderer();
|
|
17922
17937
|
this.volumeRenderer = new VolumeRenderer();
|
|
17923
17938
|
/** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
|
|
17939
|
+
/**
|
|
17940
|
+
* The mounted SDK layers. One BASE entry per registered definition (channel = the layer
|
|
17941
|
+
* id, no owner of its own), plus one DEDICATED entry per mounted native indicator whose
|
|
17942
|
+
* model names its own channel (`native.channel` — an instance of a multi-instance type):
|
|
17943
|
+
* same definition, its own canvas, reading that instance's channel and following that
|
|
17944
|
+
* instance's pane and z key. `channel` is unique across the array.
|
|
17945
|
+
*/
|
|
17924
17946
|
this.extLayers = [];
|
|
17925
17947
|
/** Last applied layer-canvas order (ids below + above the data canvas) — re-slotted only on change. */
|
|
17926
17948
|
this.layerOrderSig = "";
|
|
@@ -19066,11 +19088,7 @@ var NativeRenderer = class {
|
|
|
19066
19088
|
Object.assign(this.overlayRoot.style, { position: "absolute", inset: "0", pointerEvents: "none" });
|
|
19067
19089
|
this.plot = document.createElement("div");
|
|
19068
19090
|
Object.assign(this.plot.style, { position: "absolute", top: "0", right: "0", bottom: "0", left: "0" });
|
|
19069
|
-
this.extLayers = rendererLayers().map((def) => {
|
|
19070
|
-
const canvas = document.createElement("canvas");
|
|
19071
|
-
Object.assign(canvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
19072
|
-
return { def, instance: def.create(), canvas };
|
|
19073
|
-
});
|
|
19091
|
+
this.extLayers = rendererLayers().map((def) => ({ def, instance: def.create(), canvas: this.createLayerCanvas(), channel: def.id, owner: null }));
|
|
19074
19092
|
const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
|
|
19075
19093
|
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
19076
19094
|
this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
|
|
@@ -19087,6 +19105,7 @@ var NativeRenderer = class {
|
|
|
19087
19105
|
this.volumeRenderer.mount(this.volumeCanvas);
|
|
19088
19106
|
this.vpvrRenderer.mount(this.vpvrCanvas);
|
|
19089
19107
|
for (const l of this.extLayers) l.instance.mount(l.canvas);
|
|
19108
|
+
for (const m of this.scene.indicators.values()) this.ensureInstanceLayer(m);
|
|
19090
19109
|
this.chrome.mount(this.chromeCanvas);
|
|
19091
19110
|
this.crosshairLayer.mount(this.cursorCanvas);
|
|
19092
19111
|
this.coords.setViewport(defaultViewport());
|
|
@@ -19679,6 +19698,7 @@ var NativeRenderer = class {
|
|
|
19679
19698
|
this.refreshAnchorOffset(model);
|
|
19680
19699
|
if (model.native && this.extLayers.some((l) => l.def.id === model.native.type)) this.scene.assignIndicatorZTop(model.id);
|
|
19681
19700
|
else this.scene.assignIndicatorZ(model.id);
|
|
19701
|
+
this.ensureInstanceLayer(model);
|
|
19682
19702
|
if (model.legend !== false) {
|
|
19683
19703
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
19684
19704
|
native: !!model.native,
|
|
@@ -19722,6 +19742,7 @@ var NativeRenderer = class {
|
|
|
19722
19742
|
this.scene.vpvrLayer = null;
|
|
19723
19743
|
}
|
|
19724
19744
|
this.scene.indicators.delete(handle.id);
|
|
19745
|
+
this.dropInstanceLayer(handle.id);
|
|
19725
19746
|
this.scene.forgetIndicatorZ(handle.id);
|
|
19726
19747
|
this.scene.forgetAnchorOffset(handle.id);
|
|
19727
19748
|
this.scene.dropIndicatorScale(handle.id);
|
|
@@ -20515,29 +20536,31 @@ var NativeRenderer = class {
|
|
|
20515
20536
|
up: bar.close >= bar.open
|
|
20516
20537
|
};
|
|
20517
20538
|
}
|
|
20518
|
-
/** One group per indicator (name = indicator title), each with a row per
|
|
20539
|
+
/** One group per indicator (name = indicator title), each with a row per plot shown in the data window. */
|
|
20519
20540
|
dataWindowGroups(idx, pricePane) {
|
|
20520
20541
|
const groups = [];
|
|
20521
20542
|
for (const model of this.scene.indicators.values()) {
|
|
20522
|
-
const rows = this.
|
|
20543
|
+
const rows = this.readoutRowsFor(model, idx, pricePane, "dataWindow");
|
|
20523
20544
|
if (rows.length) groups.push({ name: model.title, rows });
|
|
20524
20545
|
}
|
|
20525
20546
|
return groups;
|
|
20526
20547
|
}
|
|
20527
|
-
/** One indicator's readout at bar `idx
|
|
20528
|
-
|
|
20548
|
+
/** One indicator's readout at bar `idx` for one value surface (the legend or the data window):
|
|
20549
|
+
* a row per plot shown on that surface, formatted on its pane's scale. A plot's `display`
|
|
20550
|
+
* decides per surface — a data-window-only plot has a row here and none in the legend. */
|
|
20551
|
+
readoutRowsFor(model, idx, pricePane, surface) {
|
|
20529
20552
|
if (model.native?.type === "volume") return this.volumeReadoutRows(model, idx);
|
|
20530
20553
|
const pane = (model.paneId ? this.scene.panes.get(model.paneId) : null) ?? pricePane;
|
|
20531
20554
|
const off = this.scene.offsetOf(model.id);
|
|
20532
20555
|
const rows = [];
|
|
20533
20556
|
for (const s of model.series) {
|
|
20557
|
+
if (!seriesShownOn(s, surface)) continue;
|
|
20534
20558
|
let value;
|
|
20535
20559
|
let color;
|
|
20536
20560
|
if (s.kind === "candle" || s.kind === "bar") {
|
|
20537
20561
|
value = s.bars[idx - off]?.close;
|
|
20538
20562
|
color = s.style?.up ?? this.theme.upColor;
|
|
20539
20563
|
} else if (isLineLikeSeries(s)) {
|
|
20540
|
-
if (s.visible === false) continue;
|
|
20541
20564
|
value = s.points[idx - off]?.value;
|
|
20542
20565
|
color = s.points[idx - off]?.color ?? s.style.color;
|
|
20543
20566
|
} else {
|
|
@@ -20560,9 +20583,10 @@ var NativeRenderer = class {
|
|
|
20560
20583
|
const color = bar.close >= bar.open ? cfg?.upColor ?? this.theme.upColor : cfg?.downColor ?? this.theme.downColor;
|
|
20561
20584
|
return [{ label: model.title, value: formatVolume(vol), color }];
|
|
20562
20585
|
}
|
|
20563
|
-
/** Refresh the plot values beside every legend title — the same
|
|
20564
|
-
*
|
|
20565
|
-
*
|
|
20586
|
+
/** Refresh the plot values beside every legend title — the same bar the data window reads
|
|
20587
|
+
* (crosshair bar, else the latest bar), pushed per paint, filtered to the plots whose
|
|
20588
|
+
* `display` includes the legend. A hidden indicator has no scene model, so its row is
|
|
20589
|
+
* absent from the map and its readout clears. */
|
|
20566
20590
|
updateLegendValues() {
|
|
20567
20591
|
if (!this.inputsUI) return;
|
|
20568
20592
|
const n = this.bars.length;
|
|
@@ -20571,7 +20595,7 @@ var NativeRenderer = class {
|
|
|
20571
20595
|
const idx = this.hoverLogical != null ? this.hoverLogical : n - 1;
|
|
20572
20596
|
const pricePane = this.dataWindowPricePane();
|
|
20573
20597
|
for (const model of this.scene.indicators.values()) {
|
|
20574
|
-
values.set(model.id, this.
|
|
20598
|
+
values.set(model.id, this.readoutRowsFor(model, idx, pricePane, "legend").map((r) => ({ value: r.value, color: r.color })));
|
|
20575
20599
|
}
|
|
20576
20600
|
}
|
|
20577
20601
|
this.inputsUI.setPlotValues(values);
|
|
@@ -20597,9 +20621,9 @@ var NativeRenderer = class {
|
|
|
20597
20621
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
20598
20622
|
for (const l of this.extLayers) {
|
|
20599
20623
|
if (!l.def.repaintOnCursor) continue;
|
|
20600
|
-
const lp = this.layerPane(l
|
|
20624
|
+
const lp = this.layerPane(l) ?? pane;
|
|
20601
20625
|
if (lp.collapsed) continue;
|
|
20602
|
-
l.instance.render(this.extLayerArgs(l
|
|
20626
|
+
l.instance.render(this.extLayerArgs(l, lp.scale, lp.bounds, nowMs));
|
|
20603
20627
|
if (this.animZoom.on && l.instance.animating?.()) this.animator.start();
|
|
20604
20628
|
}
|
|
20605
20629
|
}
|
|
@@ -20608,13 +20632,15 @@ var NativeRenderer = class {
|
|
|
20608
20632
|
if (canvas.width === 0 || canvas.height === 0) return;
|
|
20609
20633
|
canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
|
|
20610
20634
|
}
|
|
20611
|
-
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths).
|
|
20612
|
-
|
|
20635
|
+
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths).
|
|
20636
|
+
* Data + pending come from the entry's channel (per instance for a dedicated entry);
|
|
20637
|
+
* the settings bag is the TYPE's — a chart type's dialog values, one bag per type. */
|
|
20638
|
+
extLayerArgs(l, scale, bounds, nowMs) {
|
|
20613
20639
|
return {
|
|
20614
20640
|
bars: this.scene.bars,
|
|
20615
|
-
data: this.scene.nativeData.get(
|
|
20616
|
-
settings: this.scene.nativeData.get(`${id}-settings`) ?? {},
|
|
20617
|
-
pending: this.scene.nativePending.get(
|
|
20641
|
+
data: this.scene.nativeData.get(l.channel),
|
|
20642
|
+
settings: this.scene.nativeData.get(`${l.def.id}-settings`) ?? {},
|
|
20643
|
+
pending: this.scene.nativePending.get(l.channel) ?? [],
|
|
20618
20644
|
coords: this.coords,
|
|
20619
20645
|
scale,
|
|
20620
20646
|
bounds,
|
|
@@ -20657,12 +20683,12 @@ var NativeRenderer = class {
|
|
|
20657
20683
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
20658
20684
|
let folded = null;
|
|
20659
20685
|
for (const l of this.extLayers) {
|
|
20660
|
-
const lp = this.layerPane(l
|
|
20686
|
+
const lp = this.layerPane(l) ?? pane;
|
|
20661
20687
|
if (lp.collapsed) {
|
|
20662
20688
|
this.clearLayerCanvas(l.canvas);
|
|
20663
20689
|
continue;
|
|
20664
20690
|
}
|
|
20665
|
-
const args = this.extLayerArgs(l
|
|
20691
|
+
const args = this.extLayerArgs(l, lp.scale, lp.bounds, nowMs);
|
|
20666
20692
|
l.instance.render(args);
|
|
20667
20693
|
if (lp === pane) folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
20668
20694
|
if (this.animZoom.on && l.instance.animating?.()) this.animator.start();
|
|
@@ -20826,7 +20852,7 @@ var NativeRenderer = class {
|
|
|
20826
20852
|
const or = overlaySeriesRange(this.scene.indicators.values(), i0, i1, (id) => this.scene.offsetOf(id));
|
|
20827
20853
|
if (or) dr = dr ? { min: Math.min(dr.min, or.min), max: Math.max(dr.max, or.max) } : or;
|
|
20828
20854
|
}
|
|
20829
|
-
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
20855
|
+
const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels));
|
|
20830
20856
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
20831
20857
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
20832
20858
|
pane.axisFormat = void 0;
|
|
@@ -20945,19 +20971,60 @@ var NativeRenderer = class {
|
|
|
20945
20971
|
}
|
|
20946
20972
|
return null;
|
|
20947
20973
|
}
|
|
20948
|
-
/**
|
|
20949
|
-
|
|
20950
|
-
|
|
20951
|
-
|
|
20952
|
-
|
|
20974
|
+
/** One transparent, pointer-transparent SDK layer canvas covering the plot. */
|
|
20975
|
+
createLayerCanvas() {
|
|
20976
|
+
const canvas = document.createElement("canvas");
|
|
20977
|
+
Object.assign(canvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
20978
|
+
return canvas;
|
|
20979
|
+
}
|
|
20980
|
+
/**
|
|
20981
|
+
* Mount the DEDICATED layer of a native indicator that names its own channel (an
|
|
20982
|
+
* instance of a multi-instance type painting through a registered layer): a fresh
|
|
20983
|
+
* instance of the layer's definition on its own canvas, reading that channel. Idempotent
|
|
20984
|
+
* — a show after hide re-mounts the model, and the layer (kept across the hide, like the
|
|
20985
|
+
* z key) is simply reused. Skipped while the renderer itself is unmounted (`mount()`
|
|
20986
|
+
* catches up from the scene).
|
|
20987
|
+
*/
|
|
20988
|
+
ensureInstanceLayer(model) {
|
|
20989
|
+
const channel = model.native?.channel;
|
|
20990
|
+
if (!channel || !this.plot) return;
|
|
20991
|
+
if (this.extLayers.some((l) => l.owner === model.id)) return;
|
|
20992
|
+
const base = this.extLayers.find((l) => l.owner === null && l.def.id === model.native.type);
|
|
20993
|
+
if (!base) return;
|
|
20994
|
+
const canvas = this.createLayerCanvas();
|
|
20995
|
+
canvas.width = this.dataCanvas.width;
|
|
20996
|
+
canvas.height = this.dataCanvas.height;
|
|
20997
|
+
canvas.style.width = this.dataCanvas.style.width || "100%";
|
|
20998
|
+
canvas.style.height = this.dataCanvas.style.height || "100%";
|
|
20999
|
+
const entry = { def: base.def, instance: base.def.create(), canvas, channel, owner: model.id };
|
|
21000
|
+
this.extLayers.push(entry);
|
|
21001
|
+
this.plot.insertBefore(canvas, this.chromeCanvas);
|
|
21002
|
+
entry.instance.mount(canvas);
|
|
21003
|
+
}
|
|
21004
|
+
/** Tear down the dedicated layer of a removed indicator (a hide keeps it). */
|
|
21005
|
+
dropInstanceLayer(indicatorId) {
|
|
21006
|
+
const i = this.extLayers.findIndex((l2) => l2.owner === indicatorId);
|
|
21007
|
+
if (i < 0) return;
|
|
21008
|
+
const [l] = this.extLayers.splice(i, 1);
|
|
21009
|
+
l.instance.destroy?.();
|
|
21010
|
+
l.canvas.remove();
|
|
21011
|
+
}
|
|
21012
|
+
/** The mounted native indicator that OWNS an SDK layer entry. A dedicated entry is
|
|
21013
|
+
* owned by the instance it was mounted for; the base entry by the mounted indicator
|
|
21014
|
+
* whose type equals the layer id and that has no channel of its own (the id doubles
|
|
21015
|
+
* as the data channel, so the pairing is the SDK's own contract). Null for chart-type
|
|
21016
|
+
* channels and while the owner is hidden (a hidden indicator leaves the scene; its
|
|
21017
|
+
* cleared data channel paints nothing anyway). */
|
|
21018
|
+
layerOwner(l) {
|
|
21019
|
+
if (l.owner !== null) return this.scene.indicators.get(l.owner) ?? null;
|
|
20953
21020
|
for (const m of this.scene.indicators.values()) {
|
|
20954
|
-
if (m.native?.type ===
|
|
21021
|
+
if (m.native?.type === l.def.id && !m.native.channel) return m;
|
|
20955
21022
|
}
|
|
20956
21023
|
return null;
|
|
20957
21024
|
}
|
|
20958
21025
|
/** The pane an SDK layer paints on: its owner's pane, else the price pane. */
|
|
20959
|
-
layerPane(
|
|
20960
|
-
const owner = this.layerOwner(
|
|
21026
|
+
layerPane(l) {
|
|
21027
|
+
const owner = this.layerOwner(l);
|
|
20961
21028
|
const paneId = owner ? owner.paneId ?? PRICE_PANE_ID : PRICE_PANE_ID;
|
|
20962
21029
|
return this.scene.panes.get(paneId) ?? null;
|
|
20963
21030
|
}
|
|
@@ -20965,12 +21032,12 @@ var NativeRenderer = class {
|
|
|
20965
21032
|
* owned layers by their owner's z key against the candles' (an indicator restacked
|
|
20966
21033
|
* below the candles takes its layer canvas along), unowned by declared placement. */
|
|
20967
21034
|
orderedLayerCanvases() {
|
|
20968
|
-
const byId = new Map(this.extLayers.map((l) => [l.
|
|
21035
|
+
const byId = new Map(this.extLayers.map((l) => [l.channel, l.canvas]));
|
|
20969
21036
|
const { below, above } = stackLayers(
|
|
20970
21037
|
this.extLayers.map((l) => {
|
|
20971
|
-
const owner = this.layerOwner(l
|
|
21038
|
+
const owner = this.layerOwner(l);
|
|
20972
21039
|
return {
|
|
20973
|
-
id: l.
|
|
21040
|
+
id: l.channel,
|
|
20974
21041
|
placement: l.def.placement === "below-data" ? "below-data" : "above-data",
|
|
20975
21042
|
ownerZ: owner ? this.scene.zOf(owner.id) : null
|
|
20976
21043
|
};
|
|
@@ -20992,7 +21059,7 @@ var NativeRenderer = class {
|
|
|
20992
21059
|
syncLayerCanvasOrder() {
|
|
20993
21060
|
if (this.extLayers.length === 0 || !this.plot) return;
|
|
20994
21061
|
const { below, above } = this.orderedLayerCanvases();
|
|
20995
|
-
const sig = [...below.map((c) => this.extLayers.find((l) => l.canvas === c).
|
|
21062
|
+
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(",");
|
|
20996
21063
|
if (sig === this.layerOrderSig) return;
|
|
20997
21064
|
this.layerOrderSig = sig;
|
|
20998
21065
|
for (const c of below) this.plot.insertBefore(c, this.dataCanvas);
|
|
@@ -21016,6 +21083,17 @@ var NativeRenderer = class {
|
|
|
21016
21083
|
(m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type)
|
|
21017
21084
|
);
|
|
21018
21085
|
}
|
|
21086
|
+
/**
|
|
21087
|
+
* True when SDK-layer content on the price pane paints at BAR PRICES without
|
|
21088
|
+
* contributing a series: the active chart type's own layer, or an overlay layer native
|
|
21089
|
+
* (a series-less model whose type names a mounted layer). Hiding the candles must then
|
|
21090
|
+
* keep the bars in the price scale — with nothing else on the pane the scale would fall
|
|
21091
|
+
* to the {0,1} placeholder and every such layer would paint off-screen and vanish.
|
|
21092
|
+
*/
|
|
21093
|
+
priceLayersAnchoredToBars(masterModels) {
|
|
21094
|
+
if (this.extLayers.some((l) => l.owner === null && l.def.id === this.scene.priceStyle)) return true;
|
|
21095
|
+
return masterModels.some((m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type));
|
|
21096
|
+
}
|
|
21019
21097
|
/** Per-pane scale state for a host UI (e.g. a price-axis context menu): the pane's pixel
|
|
21020
21098
|
* band (`top`/`height`, so a click y maps to a pane) plus its current axis `mode`/`log`.
|
|
21021
21099
|
* Top-to-bottom order. Every pane is independent — the price pane from the scene setting,
|
|
@@ -21512,6 +21590,9 @@ var IndicatorHandleImpl = class {
|
|
|
21512
21590
|
setProps(values) {
|
|
21513
21591
|
this.controller.applyProps(this.id, values);
|
|
21514
21592
|
}
|
|
21593
|
+
updateCode(source) {
|
|
21594
|
+
this.controller.updateCode(this.id, source);
|
|
21595
|
+
}
|
|
21515
21596
|
setVisible(visible) {
|
|
21516
21597
|
this.controller.setVisible(this.id, visible);
|
|
21517
21598
|
}
|
|
@@ -21534,6 +21615,10 @@ var IndicatorHandleImpl = class {
|
|
|
21534
21615
|
setPropsSchema(schema) {
|
|
21535
21616
|
this.propsSchema = schema;
|
|
21536
21617
|
}
|
|
21618
|
+
/** Sync the public `source` once a code update has been prepared and taken over. */
|
|
21619
|
+
setSource(source) {
|
|
21620
|
+
this.source = source;
|
|
21621
|
+
}
|
|
21537
21622
|
/** Sync the public `visible` getter when the orchestrator changes visibility (API or legend eye). */
|
|
21538
21623
|
setVisibleState(visible) {
|
|
21539
21624
|
this.visibleState = visible;
|
|
@@ -22342,6 +22427,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22342
22427
|
if (identityChanged) {
|
|
22343
22428
|
this.beginLoad(false);
|
|
22344
22429
|
this.setBarSeries([], { clearing: true });
|
|
22430
|
+
this.blankIndicatorVisuals();
|
|
22345
22431
|
if (this.activeEngineStyle) {
|
|
22346
22432
|
this.typeEngines.get(this.activeEngineStyle)?.suspend();
|
|
22347
22433
|
this.renderer.setNativeData?.(this.activeEngineStyle, void 0);
|
|
@@ -22384,7 +22470,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22384
22470
|
restartNativeIndicators() {
|
|
22385
22471
|
for (const record of this.registry.all()) {
|
|
22386
22472
|
if (!record.native) continue;
|
|
22387
|
-
record.native.instance.stop();
|
|
22473
|
+
if (record.native.started) record.native.instance.stop();
|
|
22388
22474
|
record.native.instance = record.native.descriptor.create();
|
|
22389
22475
|
record.native.started = false;
|
|
22390
22476
|
record.pendingStructural = true;
|
|
@@ -22592,6 +22678,23 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22592
22678
|
this.renderer.setBars(this.bars, { preserveView: true });
|
|
22593
22679
|
this.reexecuteIndicators();
|
|
22594
22680
|
}
|
|
22681
|
+
/**
|
|
22682
|
+
* Blank every mounted indicator's painted output — series data, fills, backgrounds,
|
|
22683
|
+
* price lines, drawings, bar colors, trades — while keeping the mount (legend row,
|
|
22684
|
+
* pane, settings) intact, via the same idempotent-by-id remount an input edit uses.
|
|
22685
|
+
* Called on a market identity switch: the fresh models arrive only after the new
|
|
22686
|
+
* bars load, so nothing blanked here is ever restored — each consumer's re-run
|
|
22687
|
+
* remounts a full model over it.
|
|
22688
|
+
*/
|
|
22689
|
+
blankIndicatorVisuals() {
|
|
22690
|
+
for (const record of this.registry.all()) {
|
|
22691
|
+
if (record.hidden || !record.renderHandle || !record.model) continue;
|
|
22692
|
+
record.model = blankedModel(record.model);
|
|
22693
|
+
record.renderHandle = this.renderer.mountIndicator(record.model);
|
|
22694
|
+
record.pendingStructural = true;
|
|
22695
|
+
this.setLoading(record, true);
|
|
22696
|
+
}
|
|
22697
|
+
}
|
|
22595
22698
|
/** Stop + re-run every visible Pine indicator (its input bars changed wholesale). */
|
|
22596
22699
|
reexecuteIndicators() {
|
|
22597
22700
|
for (const record of this.registry.all()) {
|
|
@@ -22836,6 +22939,61 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22836
22939
|
record.session.update(record.inputValues, record.propValues);
|
|
22837
22940
|
this.events.emit("indicator:inputs", { id });
|
|
22838
22941
|
}
|
|
22942
|
+
/**
|
|
22943
|
+
* IndicatorController: replace a SCRIPT indicator's source in place (see
|
|
22944
|
+
* {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
|
|
22945
|
+
* computing until the new source has compiled, so a broken edit costs the user
|
|
22946
|
+
* nothing but an `error` event. Natives have no script — warn and leave them alone.
|
|
22947
|
+
*/
|
|
22948
|
+
updateCode(id, source) {
|
|
22949
|
+
const record = this.registry.get(id);
|
|
22950
|
+
const handle = this.handles.get(id);
|
|
22951
|
+
if (!record || !handle) return;
|
|
22952
|
+
if (record.native) {
|
|
22953
|
+
console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
|
|
22954
|
+
return;
|
|
22955
|
+
}
|
|
22956
|
+
if (source === record.source && record.pendingSource === void 0) return;
|
|
22957
|
+
record.pendingSource = source;
|
|
22958
|
+
void this.swapSource(id, source, handle);
|
|
22959
|
+
}
|
|
22960
|
+
async swapSource(id, source, handle) {
|
|
22961
|
+
const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
|
|
22962
|
+
let prepared;
|
|
22963
|
+
try {
|
|
22964
|
+
prepared = await engine.prepare(source, id);
|
|
22965
|
+
} catch (err) {
|
|
22966
|
+
const record2 = this.registry.get(id);
|
|
22967
|
+
if (record2?.pendingSource === source) {
|
|
22968
|
+
record2.pendingSource = void 0;
|
|
22969
|
+
this.fail(id, handle, err);
|
|
22970
|
+
}
|
|
22971
|
+
return;
|
|
22972
|
+
}
|
|
22973
|
+
const record = this.registry.get(id);
|
|
22974
|
+
if (!record || record.pendingSource !== source) return;
|
|
22975
|
+
record.pendingSource = void 0;
|
|
22976
|
+
record.source = source;
|
|
22977
|
+
record.engine = engine;
|
|
22978
|
+
record.prepared = prepared;
|
|
22979
|
+
handle.setSource(source);
|
|
22980
|
+
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
|
|
22981
|
+
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
|
|
22982
|
+
handle.setSchema(prepared.inputs);
|
|
22983
|
+
handle.setPropsSchema(prepared.props ?? []);
|
|
22984
|
+
record.session?.stop();
|
|
22985
|
+
record.session = void 0;
|
|
22986
|
+
record.pendingStructural = true;
|
|
22987
|
+
record.pendingCause = "code";
|
|
22988
|
+
if (record.hidden) return;
|
|
22989
|
+
if (record.renderHandle) {
|
|
22990
|
+
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
22991
|
+
this.setLoading(record, true);
|
|
22992
|
+
} else {
|
|
22993
|
+
this.mountLoadingPlaceholder(id, record);
|
|
22994
|
+
}
|
|
22995
|
+
this.executeIndicator(id, handle);
|
|
22996
|
+
}
|
|
22839
22997
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
22840
22998
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
22841
22999
|
listIndicators() {
|
|
@@ -22850,7 +23008,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22850
23008
|
const record = this.registry.remove(id);
|
|
22851
23009
|
if (record?.native?.type === "volume") this.volumeOptedOut = true;
|
|
22852
23010
|
record?.session?.stop();
|
|
22853
|
-
record?.native?.instance.stop();
|
|
23011
|
+
if (record?.native?.started) record.native.instance.stop();
|
|
22854
23012
|
this.handles.delete(id);
|
|
22855
23013
|
if (record?.renderHandle) this.renderer.removeIndicator(record.renderHandle);
|
|
22856
23014
|
const paneId = record?.model?.paneId;
|
|
@@ -22876,7 +23034,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22876
23034
|
if (!visible) {
|
|
22877
23035
|
record.session?.stop();
|
|
22878
23036
|
record.session = void 0;
|
|
22879
|
-
record.native?.instance.suspend();
|
|
23037
|
+
if (record.native?.started) record.native.instance.suspend();
|
|
22880
23038
|
if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
|
|
22881
23039
|
else if (record.native) this.mountHiddenNativeRow(id, record);
|
|
22882
23040
|
} else {
|
|
@@ -22957,7 +23115,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22957
23115
|
this.paneActionUnsub?.();
|
|
22958
23116
|
for (const record of this.registry.all()) {
|
|
22959
23117
|
record.session?.stop();
|
|
22960
|
-
record.native?.instance.stop();
|
|
23118
|
+
if (record.native?.started) record.native.instance.stop();
|
|
22961
23119
|
}
|
|
22962
23120
|
for (const engine of this.typeEngines.values()) engine.stop();
|
|
22963
23121
|
this.typeEngines.clear();
|
|
@@ -22981,6 +23139,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22981
23139
|
const engine = this.engineFor(options.language);
|
|
22982
23140
|
record.engine = engine;
|
|
22983
23141
|
const prepared = await engine.prepare(source, id);
|
|
23142
|
+
if (record.source !== source) return;
|
|
22984
23143
|
record.prepared = prepared;
|
|
22985
23144
|
const defaults = {};
|
|
22986
23145
|
for (const input of prepared.inputs) defaults[input.key] = input.defval;
|
|
@@ -23051,7 +23210,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
23051
23210
|
await this.readyPromise;
|
|
23052
23211
|
const record = this.registry.get(id);
|
|
23053
23212
|
if (!record?.native || record.hidden) return;
|
|
23213
|
+
const channel = this.nativeChannel(record);
|
|
23054
23214
|
const ctx = {
|
|
23215
|
+
id,
|
|
23055
23216
|
symbol: this.config.market.symbol ?? "TEST",
|
|
23056
23217
|
timeframe: this.config.market.timeframe ?? "60",
|
|
23057
23218
|
live: this.config.live,
|
|
@@ -23059,7 +23220,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
23059
23220
|
bars: () => this.bars,
|
|
23060
23221
|
data: this.dataControl,
|
|
23061
23222
|
emit: (out) => this.applyModel(id, this.buildNativeModel(record, out)),
|
|
23062
|
-
pushData: (data) => this.renderer.setNativeData?.(
|
|
23223
|
+
pushData: (data) => this.renderer.setNativeData?.(channel, data),
|
|
23063
23224
|
setStatus: (status) => {
|
|
23064
23225
|
if (record.renderHandle && !record.hidden) this.renderer.setIndicatorStatus?.(record.renderHandle, status);
|
|
23065
23226
|
}
|
|
@@ -23070,16 +23231,26 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
23070
23231
|
this.fail(id, handle, err);
|
|
23071
23232
|
}
|
|
23072
23233
|
}
|
|
23234
|
+
/** The renderer-layer channel a native instance's `pushData` lands on: the type itself
|
|
23235
|
+
* for a single-instance type (the layer id doubles as the channel), a per-instance
|
|
23236
|
+
* channel for a `multiInstance` type — otherwise every instance would overwrite the
|
|
23237
|
+
* one layer. Stamped on the model so the renderer mounts a dedicated layer for it. */
|
|
23238
|
+
nativeChannel(record) {
|
|
23239
|
+
const { type, descriptor } = record.native;
|
|
23240
|
+
return descriptor.multiInstance ? nativeInstanceChannel(type, record.id) : type;
|
|
23241
|
+
}
|
|
23073
23242
|
/** Wrap a native indicator's emitted visuals into a full IndicatorModel, tagged `native`. */
|
|
23074
23243
|
buildNativeModel(record, out) {
|
|
23075
23244
|
const d = record.native.descriptor;
|
|
23245
|
+
const type = record.native.type;
|
|
23246
|
+
const channel = this.nativeChannel(record);
|
|
23076
23247
|
return {
|
|
23077
23248
|
id: record.id,
|
|
23078
23249
|
title: record.title,
|
|
23079
23250
|
...d.shortTitle ? { shorttitle: d.shortTitle } : {},
|
|
23080
23251
|
overlay: d.overlay,
|
|
23081
23252
|
paneHint: d.paneHint,
|
|
23082
|
-
native: { type
|
|
23253
|
+
native: { type, ...channel !== type ? { channel } : {} },
|
|
23083
23254
|
...d.legend === false ? { legend: false } : {},
|
|
23084
23255
|
...out.paneAxis != null ? { paneAxis: out.paneAxis } : {},
|
|
23085
23256
|
series: out.series ?? [],
|
|
@@ -23369,6 +23540,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
23369
23540
|
applyModel(id, model) {
|
|
23370
23541
|
const record = this.registry.get(id);
|
|
23371
23542
|
if (!record || record.hidden) return true;
|
|
23543
|
+
if (this.switchingMarket) return false;
|
|
23372
23544
|
if (record.loading && this.bars.length === 0 && !_EngineOrchestrator.modelHasOutput(model)) return false;
|
|
23373
23545
|
const handle = this.handles.get(id);
|
|
23374
23546
|
if (!record.renderHandle) {
|
|
@@ -23386,7 +23558,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
23386
23558
|
if (record.loading && !record.paneLocked) {
|
|
23387
23559
|
const routed = this.routePane(id, model, record.options ?? {});
|
|
23388
23560
|
if (routed !== paneId) {
|
|
23389
|
-
if (paneId !== "price") {
|
|
23561
|
+
if (paneId !== "price" && !this.registry.all().some((r) => r.id !== id && r.model?.paneId === paneId)) {
|
|
23390
23562
|
this.renderer.removePane(paneId);
|
|
23391
23563
|
this.forgetPane(paneId);
|
|
23392
23564
|
}
|
|
@@ -23515,6 +23687,34 @@ function yieldToPaint() {
|
|
|
23515
23687
|
}
|
|
23516
23688
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
23517
23689
|
}
|
|
23690
|
+
function valuesOnSchema(schema, previous) {
|
|
23691
|
+
const out = {};
|
|
23692
|
+
const declared = /* @__PURE__ */ new Set();
|
|
23693
|
+
for (const s of schema) {
|
|
23694
|
+
out[s.key] = s.defval;
|
|
23695
|
+
declared.add(s.key);
|
|
23696
|
+
declared.add(s.title);
|
|
23697
|
+
}
|
|
23698
|
+
for (const [k, v] of Object.entries(previous)) if (declared.has(k)) out[k] = v;
|
|
23699
|
+
return out;
|
|
23700
|
+
}
|
|
23701
|
+
function blankedModel(model) {
|
|
23702
|
+
return {
|
|
23703
|
+
...model,
|
|
23704
|
+
series: model.series.map((s) => s.kind === "candle" || s.kind === "bar" ? { ...s, bars: [] } : s.kind === "markers" ? { ...s, markers: [] } : { ...s, points: [] }),
|
|
23705
|
+
fills: [],
|
|
23706
|
+
backgrounds: [],
|
|
23707
|
+
priceLines: [],
|
|
23708
|
+
lines: [],
|
|
23709
|
+
boxes: [],
|
|
23710
|
+
labels: [],
|
|
23711
|
+
polylines: [],
|
|
23712
|
+
linefills: [],
|
|
23713
|
+
tables: [],
|
|
23714
|
+
barColors: [],
|
|
23715
|
+
trades: []
|
|
23716
|
+
};
|
|
23717
|
+
}
|
|
23518
23718
|
function modelToValuePatch(model) {
|
|
23519
23719
|
const series = [];
|
|
23520
23720
|
let from = Number.POSITIVE_INFINITY;
|