@luxalgo/vela 0.6.9 → 0.6.10
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-8Z95Q-RJ.d.cts → DataProvider-CWmp31dA.d.ts} +49 -1
- package/dist/{DataProvider-DExJrfut.d.ts → DataProvider-l_eLMly_.d.cts} +49 -1
- package/dist/{chunk-EQCHJZOT.js → chunk-4KZVZ7QQ.js} +1 -1
- package/dist/{chunk-6WDDVMBJ.js → chunk-FFOP37FQ.js} +577 -321
- package/dist/{chunk-62SVGONC.js → chunk-G77Y7LK2.js} +2 -2
- package/dist/{chunk-WFSZBX3R.js → chunk-KG4YT3TI.js} +3 -1
- package/dist/{chunk-FCVIG7JG.js → chunk-MHY7MVXH.js} +5 -1
- package/dist/{chunk-STHSKXOR.js → chunk-NMTQXNT4.js} +591 -141
- package/dist/{contributions-D7PVZO2i.d.ts → contributions-BCz6Dr6a.d.ts} +6 -6
- package/dist/{contributions-C1U2Krwg.d.cts → contributions-D9vTzm5p.d.cts} +6 -6
- package/dist/index.cjs +580 -318
- package/dist/index.d.cts +31 -10
- package/dist/index.d.ts +31 -10
- package/dist/index.js +4 -4
- package/dist/{options-FM0peknS.d.ts → options-DSqHsQyN.d.cts} +6 -6
- package/dist/{options-FM0peknS.d.cts → options-DSqHsQyN.d.ts} +6 -6
- package/dist/{plugin-DfVqBz9p.d.cts → plugin-Bt8hLR8Z.d.cts} +3 -3
- package/dist/{plugin-7bkF32Rk.d.ts → plugin-CH7pfMrE.d.ts} +3 -3
- package/dist/plugin.cjs +3 -1
- 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-zdF4eZLr.d.ts → statusline-5K7y4Ya2.d.ts} +3 -3
- package/dist/{statusline-DOPiT6I6.d.cts → statusline-TYLQmoeh.d.cts} +3 -3
- package/dist/ui.cjs +7 -1
- package/dist/ui.d.cts +3 -1
- package/dist/ui.d.ts +3 -1
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +580 -318
- package/dist/vela.global.min.js +50 -48
- package/dist/widget.cjs +1052 -340
- package/dist/widget.d.cts +17 -6
- package/dist/widget.d.ts +17 -6
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +1052 -340
- package/dist/workspace.d.cts +89 -5
- package/dist/workspace.d.ts +89 -5
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, 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 } from './chunk-
|
|
2
|
-
import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, blendOver, splitColor, CalloutBubble, Menu, TextArea, Popover, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-
|
|
3
|
-
import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BEARISH, BULLISH, isDarkColor, SERIES_LINE, SLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-
|
|
1
|
+
import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, 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 } from './chunk-4KZVZ7QQ.js';
|
|
2
|
+
import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, blendOver, splitColor, CalloutBubble, Menu, TextArea, Popover, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-MHY7MVXH.js';
|
|
3
|
+
import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BEARISH, BULLISH, isDarkColor, SERIES_LINE, SLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-KG4YT3TI.js';
|
|
4
4
|
|
|
5
5
|
// src/core/events/EventBus.ts
|
|
6
6
|
var TypedEventBus = class {
|
|
@@ -696,6 +696,23 @@ var BarStore = class {
|
|
|
696
696
|
};
|
|
697
697
|
var sharedBarStore = new BarStore();
|
|
698
698
|
|
|
699
|
+
// src/data/symbol-groups.ts
|
|
700
|
+
function isGroupRow(d) {
|
|
701
|
+
return d.group != null && d.ticker === d.group;
|
|
702
|
+
}
|
|
703
|
+
function groupKeyOf(d) {
|
|
704
|
+
return `${(d.prefix ?? d.provider ?? "").toLowerCase()}:${(d.group ?? "").toUpperCase()}`;
|
|
705
|
+
}
|
|
706
|
+
function groupMembers(pool, groupRow) {
|
|
707
|
+
const key = groupKeyOf(groupRow);
|
|
708
|
+
return pool.filter((s) => s.group != null && !isGroupRow(s) && groupKeyOf(s) === key);
|
|
709
|
+
}
|
|
710
|
+
function defaultMemberOf(pool, groupRow) {
|
|
711
|
+
const members = groupMembers(pool, groupRow);
|
|
712
|
+
const defaults = members.filter((m) => m.default);
|
|
713
|
+
return (defaults.length === 1 ? defaults[0] : members[0]) ?? null;
|
|
714
|
+
}
|
|
715
|
+
|
|
699
716
|
// src/data/ProviderRegistry.ts
|
|
700
717
|
var normName = (n) => n.trim().toLowerCase();
|
|
701
718
|
var normTicker = (t) => t.trim().toUpperCase();
|
|
@@ -785,17 +802,24 @@ var ProviderRegistry = class {
|
|
|
785
802
|
resolve(raw, opts = {}) {
|
|
786
803
|
const { provider, ticker } = this.parse(raw);
|
|
787
804
|
if (provider) {
|
|
788
|
-
if (this.entries.has(provider))
|
|
805
|
+
if (this.entries.has(provider)) {
|
|
806
|
+
const d = this.entries.get(provider).byTicker?.get(normTicker(ticker));
|
|
807
|
+
return { provider, ticker: d != null && isGroupRow(d) ? this.loadableTicker(provider, d) : ticker };
|
|
808
|
+
}
|
|
789
809
|
const key = `${provider}:${normTicker(ticker)}`;
|
|
790
810
|
for (const name of this.candidateOrder(opts.default)) {
|
|
791
811
|
const d = this.entries.get(name).prefixIndex?.get(key);
|
|
792
|
-
if (d) return { provider: name, ticker:
|
|
812
|
+
if (d) return { provider: name, ticker: this.loadableTicker(name, d) };
|
|
793
813
|
}
|
|
794
814
|
return null;
|
|
795
815
|
}
|
|
796
816
|
const norm = normTicker(ticker);
|
|
797
817
|
for (const name of this.candidateOrder(opts.default)) {
|
|
798
|
-
|
|
818
|
+
const e = this.entries.get(name);
|
|
819
|
+
if (e.index?.has(norm)) {
|
|
820
|
+
const d = e.byTicker?.get(norm);
|
|
821
|
+
return { provider: name, ticker: d != null && isGroupRow(d) ? this.loadableTicker(name, d) : ticker };
|
|
822
|
+
}
|
|
799
823
|
}
|
|
800
824
|
if (opts.lenient && this.entries.size === 1) {
|
|
801
825
|
const only = this.names()[0];
|
|
@@ -804,6 +828,13 @@ var ProviderRegistry = class {
|
|
|
804
828
|
}
|
|
805
829
|
return null;
|
|
806
830
|
}
|
|
831
|
+
/** What a resolved descriptor LOADS: itself — unless it is a GROUP row (listed, never
|
|
832
|
+
* served), which loads its default member (single `default`, else first listed). A
|
|
833
|
+
* memberless group keeps its own ticker and fails downstream like any unknown symbol. */
|
|
834
|
+
loadableTicker(name, d) {
|
|
835
|
+
if (!isGroupRow(d)) return d.ticker;
|
|
836
|
+
return defaultMemberOf(this.entries.get(name)?.descriptors ?? [], d)?.ticker ?? d.ticker;
|
|
837
|
+
}
|
|
807
838
|
/**
|
|
808
839
|
* Resolve now if possible, else resolve later — re-attempting after each provider
|
|
809
840
|
* settles. The promise stays pending until some registered provider can serve the
|
|
@@ -947,6 +978,28 @@ var CachingDataFeed = class {
|
|
|
947
978
|
this.store.merge(key, dropForming(bars));
|
|
948
979
|
return bars;
|
|
949
980
|
}
|
|
981
|
+
/**
|
|
982
|
+
* Progressive twin of {@link load}: a COLD load streams through the inner feed's
|
|
983
|
+
* progressive path — batches forwarded verbatim, the FINAL answer cached exactly as
|
|
984
|
+
* `load` caches — while a cache-covered load answers once through `load` itself (a
|
|
985
|
+
* warm chart has nothing to stream). Falls back to `load` wholesale when the inner
|
|
986
|
+
* feed lacks the capability, so callers may prefer this method unconditionally.
|
|
987
|
+
*/
|
|
988
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
989
|
+
if (cfg.data && cfg.data.length > 0) return this.inner.load(cfg);
|
|
990
|
+
if (!this.inner.loadProgressive) return null;
|
|
991
|
+
const symbol = cfg.symbol ?? "TEST";
|
|
992
|
+
const key = cacheKey(symbol, cfg.timeframe ?? "60", cfg.session);
|
|
993
|
+
const cached = this.store.get(key);
|
|
994
|
+
const n = cfg.bars ?? 500;
|
|
995
|
+
const lastCached = cached?.[cached.length - 1];
|
|
996
|
+
if (cached && lastCached && cached.length >= n - 1 && this.inner.loadRange) return this.load(cfg);
|
|
997
|
+
this.store.retainSymbol(symbol);
|
|
998
|
+
const bars = await this.inner.loadProgressive(cfg, onBatch, opts);
|
|
999
|
+
if (bars == null) return null;
|
|
1000
|
+
this.store.merge(key, dropForming(bars));
|
|
1001
|
+
return bars;
|
|
1002
|
+
}
|
|
950
1003
|
/**
|
|
951
1004
|
* Cache-backed ranged fetch — the gateway engines use for secondary series
|
|
952
1005
|
* (`request.security` HTF/LTF/cross-symbol) and the orchestrator's backward
|
|
@@ -1192,6 +1245,17 @@ var MultiProviderFeed = class {
|
|
|
1192
1245
|
this.prefetchSymbolInfo(resolved);
|
|
1193
1246
|
return this.cache.load(canonical(cfg, resolved));
|
|
1194
1247
|
}
|
|
1248
|
+
/** Progressive twin of {@link load} — same resolution, the cache streams the batches. */
|
|
1249
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
1250
|
+
if (cfg.data && cfg.data.length > 0) {
|
|
1251
|
+
this.liveBars = cfg.data.map((b) => ({ ...b }));
|
|
1252
|
+
return cfg.data;
|
|
1253
|
+
}
|
|
1254
|
+
const resolved = await this.registry.whenResolvable(rawSymbol(cfg), { default: this.primaryProvider });
|
|
1255
|
+
this.primaryProvider = resolved.provider;
|
|
1256
|
+
this.prefetchSymbolInfo(resolved);
|
|
1257
|
+
return this.cache.loadProgressive(canonical(cfg, resolved), onBatch, opts);
|
|
1258
|
+
}
|
|
1195
1259
|
/**
|
|
1196
1260
|
* Synchronous per-symbol metadata for engines (Pine `syminfo.*`), served from the cache
|
|
1197
1261
|
* warmed by load(). Undefined until the prefetch lands (the engine then synthesizes a
|
|
@@ -1279,6 +1343,18 @@ var RegistryFetchFeed = class {
|
|
|
1279
1343
|
if (!provider) return Promise.resolve([]);
|
|
1280
1344
|
return safeBars(provider, ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session });
|
|
1281
1345
|
}
|
|
1346
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
1347
|
+
const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
|
|
1348
|
+
const provider = this.registry.get(name ?? "");
|
|
1349
|
+
if (!provider) return [];
|
|
1350
|
+
if (!provider.getBarsProgressive) return null;
|
|
1351
|
+
try {
|
|
1352
|
+
return await provider.getBarsProgressive(ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session }, onBatch, opts);
|
|
1353
|
+
} catch (e) {
|
|
1354
|
+
console.warn(`[vela] progressive fetch failed for ${ticker} ${cfg.timeframe ?? "60"} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
1355
|
+
return [];
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1282
1358
|
loadRange(cfg, range) {
|
|
1283
1359
|
const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
|
|
1284
1360
|
const provider = this.registry.get(name ?? "");
|
|
@@ -2426,7 +2502,7 @@ var DrawingToolbar = class {
|
|
|
2426
2502
|
this.root.replaceChildren();
|
|
2427
2503
|
this.groupCells.clear();
|
|
2428
2504
|
this.groupIcons.clear();
|
|
2429
|
-
this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.
|
|
2505
|
+
this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.onCursorClick());
|
|
2430
2506
|
this.root.appendChild(this.cursorBtn);
|
|
2431
2507
|
if (this.def.groups.length > 0) this.root.appendChild(this.divider());
|
|
2432
2508
|
for (const g of this.def.groups) {
|
|
@@ -2530,6 +2606,14 @@ var DrawingToolbar = class {
|
|
|
2530
2606
|
this.magnetIcon = icon2;
|
|
2531
2607
|
return cell;
|
|
2532
2608
|
}
|
|
2609
|
+
/** Cursor returns to select/idle: an active measure/eraser mode exits through its own
|
|
2610
|
+
* toggle callback (disarming a tool via `onArm(null)` alone can't — the host treats a
|
|
2611
|
+
* null arm as a no-op side effect of entering those modes), then the tool disarms. */
|
|
2612
|
+
onCursorClick() {
|
|
2613
|
+
if (this.measureActive) this.onMeasure();
|
|
2614
|
+
if (this.eraserActive) this.onEraser();
|
|
2615
|
+
this.onArm(null);
|
|
2616
|
+
}
|
|
2533
2617
|
/** Clicking the icon arms the group's last-used tool (it does NOT open the flyout). */
|
|
2534
2618
|
onGroupIconClick(group) {
|
|
2535
2619
|
const type = this.lastUsed.get(group.id) ?? group.tools[0]?.type;
|
|
@@ -3819,6 +3903,9 @@ var CLOSE_SVG = iconAt("close", LEGEND_ICON_PX2);
|
|
|
3819
3903
|
var FOLD_SVG = iconAt("chevron-up", LEGEND_ICON_PX2);
|
|
3820
3904
|
var UNFOLD_SVG = iconAt("chevron-down", LEGEND_ICON_PX2);
|
|
3821
3905
|
var OVERVIEW_SVG = iconAt("objects", LEGEND_ICON_PX2);
|
|
3906
|
+
function legendCalloutsDisplay(open, hasCallouts) {
|
|
3907
|
+
return !open && hasCallouts ? "inline-flex" : "none";
|
|
3908
|
+
}
|
|
3822
3909
|
var InputsUI = class {
|
|
3823
3910
|
constructor(container, theme, paneBoundsOf) {
|
|
3824
3911
|
this.container = container;
|
|
@@ -4022,7 +4109,7 @@ var InputsUI = class {
|
|
|
4022
4109
|
row.callouts = [];
|
|
4023
4110
|
row.calloutsEl.replaceChildren();
|
|
4024
4111
|
const views = this.legendCallouts?.(row.id) ?? [];
|
|
4025
|
-
row.calloutsEl.style.display = views.length > 0
|
|
4112
|
+
row.calloutsEl.style.display = legendCalloutsDisplay(row.highlighted, views.length > 0);
|
|
4026
4113
|
for (const view of views) {
|
|
4027
4114
|
const bubble = new CalloutBubble({
|
|
4028
4115
|
icon: view.icon,
|
|
@@ -4506,11 +4593,11 @@ var InputsUI = class {
|
|
|
4506
4593
|
row.controlsEl.style.display = open || row.hidden ? "inline-flex" : "none";
|
|
4507
4594
|
if (open) {
|
|
4508
4595
|
row.el.appendChild(row.statusEl);
|
|
4509
|
-
row.
|
|
4596
|
+
for (const bubble of row.callouts) bubble.hidePanel();
|
|
4510
4597
|
} else {
|
|
4511
4598
|
row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
4512
|
-
row.el.insertBefore(row.calloutsEl, row.statusEl);
|
|
4513
4599
|
}
|
|
4600
|
+
row.calloutsEl.style.display = legendCalloutsDisplay(open, row.callouts.length > 0);
|
|
4514
4601
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
4515
4602
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
4516
4603
|
if (child === row.extrasEl) {
|
|
@@ -4600,7 +4687,7 @@ var ICONS = {
|
|
|
4600
4687
|
};
|
|
4601
4688
|
var STYLE_ID2 = "vela-pane-controls";
|
|
4602
4689
|
var ICON_PX = 12;
|
|
4603
|
-
var CLUSTER_PILL = "rgba(0,0,0,0.
|
|
4690
|
+
var CLUSTER_PILL = "rgba(0,0,0,0.65)";
|
|
4604
4691
|
function ensureStyles2() {
|
|
4605
4692
|
if (typeof document === "undefined" || document.getElementById(STYLE_ID2)) return;
|
|
4606
4693
|
const st = document.createElement("style");
|
|
@@ -4620,8 +4707,12 @@ var PaneControls = class {
|
|
|
4620
4707
|
this.deps = deps;
|
|
4621
4708
|
this.clusters = /* @__PURE__ */ new Map();
|
|
4622
4709
|
this.hoverPaneId = null;
|
|
4710
|
+
/** Mobile: hover clusters are meaningless without a cursor — suppressed; a
|
|
4711
|
+
* collapsed pane's standalone expand chip stays (the only way back up). */
|
|
4712
|
+
this.suspended = false;
|
|
4623
4713
|
/** Reveal the cluster for the pane under the cursor, resolved from the pointer's y in the plot. */
|
|
4624
4714
|
this.onPlotMove = (e) => {
|
|
4715
|
+
if (this.suspended) return;
|
|
4625
4716
|
const rect = this.plot.getBoundingClientRect();
|
|
4626
4717
|
const y = e.clientY - rect.top;
|
|
4627
4718
|
let hit = null;
|
|
@@ -4693,7 +4784,12 @@ var PaneControls = class {
|
|
|
4693
4784
|
}
|
|
4694
4785
|
if (p.count > 1) {
|
|
4695
4786
|
cluster.appendChild(
|
|
4696
|
-
this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
|
|
4787
|
+
this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
|
|
4788
|
+
role: "maximize",
|
|
4789
|
+
// Same inverse-chip treatment as the collapsed pane's expand toggle: the
|
|
4790
|
+
// maximized state must read as an active state, not just a swapped glyph.
|
|
4791
|
+
selected: p.maximized
|
|
4792
|
+
})
|
|
4697
4793
|
);
|
|
4698
4794
|
}
|
|
4699
4795
|
}
|
|
@@ -4730,17 +4826,18 @@ var PaneControls = class {
|
|
|
4730
4826
|
}
|
|
4731
4827
|
const hovered = id === this.hoverPaneId;
|
|
4732
4828
|
const hasButtons = cluster.children.length > 0;
|
|
4733
|
-
const
|
|
4829
|
+
const stateChipRole = p.collapsed ? "collapse" : !this.suspended && p.maximized ? "maximize" : null;
|
|
4830
|
+
const visible = hasButtons && (hovered || stateChipRole != null) && p.height > 8;
|
|
4734
4831
|
cluster.style.right = `${rightPx}px`;
|
|
4735
4832
|
cluster.style.top = p.collapsed ? `${p.top + Math.max(1, Math.round((p.height - 24) / 2))}px` : `${p.top + 4}px`;
|
|
4736
4833
|
cluster.style.display = visible ? "flex" : "none";
|
|
4737
4834
|
if (!visible) continue;
|
|
4738
|
-
const
|
|
4739
|
-
cluster.style.background =
|
|
4835
|
+
const soloChip = stateChipRole != null && !hovered;
|
|
4836
|
+
cluster.style.background = soloChip ? "transparent" : CLUSTER_PILL;
|
|
4740
4837
|
for (const child of cluster.children) {
|
|
4741
4838
|
const btn2 = child;
|
|
4742
4839
|
btn2.style.display = "inline-flex";
|
|
4743
|
-
btn2.style.visibility =
|
|
4840
|
+
btn2.style.visibility = soloChip && btn2.dataset.role !== stateChipRole ? "hidden" : "visible";
|
|
4744
4841
|
}
|
|
4745
4842
|
}
|
|
4746
4843
|
}
|
|
@@ -4750,6 +4847,14 @@ var PaneControls = class {
|
|
|
4750
4847
|
this.hoverPaneId = paneId;
|
|
4751
4848
|
this.reposition();
|
|
4752
4849
|
}
|
|
4850
|
+
/** Mobile suppression: no hover clusters (touch has no cursor; the shell's own
|
|
4851
|
+
* chrome covers maximize), while collapsed panes keep their expand chips. */
|
|
4852
|
+
setSuspended(on) {
|
|
4853
|
+
if (on === this.suspended) return;
|
|
4854
|
+
this.suspended = on;
|
|
4855
|
+
if (on) this.hoverPaneId = null;
|
|
4856
|
+
this.reposition();
|
|
4857
|
+
}
|
|
4753
4858
|
destroy() {
|
|
4754
4859
|
this.plot.removeEventListener("pointermove", this.onPlotMove);
|
|
4755
4860
|
this.plot.removeEventListener("pointerleave", this.onPlotLeave);
|
|
@@ -4900,160 +5005,6 @@ var AxisScaleButtons = class {
|
|
|
4900
5005
|
}
|
|
4901
5006
|
};
|
|
4902
5007
|
|
|
4903
|
-
// src/renderers/shared/TableOverlay.ts
|
|
4904
|
-
var SIZE_PX = {
|
|
4905
|
-
auto: 13,
|
|
4906
|
-
tiny: 10,
|
|
4907
|
-
small: 11,
|
|
4908
|
-
normal: 13,
|
|
4909
|
-
large: 16,
|
|
4910
|
-
huge: 20
|
|
4911
|
-
};
|
|
4912
|
-
function fontPxOf(size) {
|
|
4913
|
-
if (typeof size === "number") return size > 0 ? size : SIZE_PX.auto;
|
|
4914
|
-
return SIZE_PX[size] ?? SIZE_PX.auto;
|
|
4915
|
-
}
|
|
4916
|
-
function tableHasContent(t) {
|
|
4917
|
-
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
4918
|
-
}
|
|
4919
|
-
function mergeRenderPlan(t) {
|
|
4920
|
-
const span = /* @__PURE__ */ new Map();
|
|
4921
|
-
const omit = /* @__PURE__ */ new Set();
|
|
4922
|
-
for (const m of t.merges) {
|
|
4923
|
-
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
4924
|
-
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
4925
|
-
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
4926
|
-
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
4927
|
-
}
|
|
4928
|
-
}
|
|
4929
|
-
}
|
|
4930
|
-
for (let r = 0; r < t.rows; r += 1) {
|
|
4931
|
-
for (let c = 0; c < t.columns; c += 1) {
|
|
4932
|
-
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
4933
|
-
}
|
|
4934
|
-
}
|
|
4935
|
-
for (const key of span.keys()) omit.delete(key);
|
|
4936
|
-
return { span, omit };
|
|
4937
|
-
}
|
|
4938
|
-
var TableOverlay = class {
|
|
4939
|
-
constructor(container, theme, paneBounds) {
|
|
4940
|
-
this.container = container;
|
|
4941
|
-
this.theme = theme;
|
|
4942
|
-
this.paneBounds = paneBounds;
|
|
4943
|
-
this.lastTables = [];
|
|
4944
|
-
if (getComputedStyle(container).position === "static") container.style.position = "relative";
|
|
4945
|
-
this.root = document.createElement("div");
|
|
4946
|
-
Object.assign(this.root.style, {
|
|
4947
|
-
position: "absolute",
|
|
4948
|
-
inset: "0",
|
|
4949
|
-
pointerEvents: "none",
|
|
4950
|
-
overflow: "hidden",
|
|
4951
|
-
zIndex: "3"
|
|
4952
|
-
});
|
|
4953
|
-
container.appendChild(this.root);
|
|
4954
|
-
}
|
|
4955
|
-
update(tables) {
|
|
4956
|
-
this.lastTables = tables;
|
|
4957
|
-
this.root.replaceChildren();
|
|
4958
|
-
for (const t of tables) {
|
|
4959
|
-
if (tableHasContent(t)) this.root.appendChild(this.renderTable(t));
|
|
4960
|
-
}
|
|
4961
|
-
}
|
|
4962
|
-
/** Re-render at the current pane geometry — after layout settles or on resize. */
|
|
4963
|
-
reposition() {
|
|
4964
|
-
if (this.root.isConnected) this.update(this.lastTables);
|
|
4965
|
-
}
|
|
4966
|
-
/** Show/hide the whole overlay. Tables anchor to pane corners, not to bars, so unlike the
|
|
4967
|
-
* series content they DON'T vanish with an emptied chart — the loading state hides them. */
|
|
4968
|
-
setVisible(visible) {
|
|
4969
|
-
this.root.style.display = visible ? "" : "none";
|
|
4970
|
-
}
|
|
4971
|
-
destroy() {
|
|
4972
|
-
this.root.remove();
|
|
4973
|
-
}
|
|
4974
|
-
renderTable(t) {
|
|
4975
|
-
const b = this.paneBounds(t.paneId);
|
|
4976
|
-
const wrap = document.createElement("div");
|
|
4977
|
-
wrap.style.position = "absolute";
|
|
4978
|
-
if (t.frameColor && t.frameWidth > 0) wrap.style.border = `${t.frameWidth}px solid ${t.frameColor}`;
|
|
4979
|
-
this.anchor(wrap, t.position, b);
|
|
4980
|
-
const table = document.createElement("table");
|
|
4981
|
-
Object.assign(table.style, {
|
|
4982
|
-
borderCollapse: "collapse",
|
|
4983
|
-
background: t.bgColor ?? "transparent",
|
|
4984
|
-
fontFamily: this.theme.fontFamily || "sans-serif",
|
|
4985
|
-
border: "none",
|
|
4986
|
-
tableLayout: "auto",
|
|
4987
|
-
// Re-enable pointer events on the table only (root is none) so cell tooltips work.
|
|
4988
|
-
pointerEvents: "auto"
|
|
4989
|
-
});
|
|
4990
|
-
const { span, omit } = mergeRenderPlan(t);
|
|
4991
|
-
const plotW = Math.max(0, (this.root.clientWidth || this.container.clientWidth) - b.rightAxis);
|
|
4992
|
-
const paneH = b.height;
|
|
4993
|
-
const cellBorder = t.borderColor && t.borderWidth > 0 ? `${t.borderWidth}px solid ${t.borderColor}` : "none";
|
|
4994
|
-
for (let r = 0; r < t.rows; r += 1) {
|
|
4995
|
-
const tr = document.createElement("tr");
|
|
4996
|
-
for (let c = 0; c < t.columns; c += 1) {
|
|
4997
|
-
if (omit.has(`${r}:${c}`)) continue;
|
|
4998
|
-
const cell = t.cells[r]?.[c] ?? null;
|
|
4999
|
-
const td = document.createElement("td");
|
|
5000
|
-
if (cell === null) {
|
|
5001
|
-
Object.assign(td.style, { padding: "0", border: "none" });
|
|
5002
|
-
tr.appendChild(td);
|
|
5003
|
-
continue;
|
|
5004
|
-
}
|
|
5005
|
-
const sp = span.get(`${r}:${c}`);
|
|
5006
|
-
if (sp) {
|
|
5007
|
-
if (sp.cs > 1) td.colSpan = sp.cs;
|
|
5008
|
-
if (sp.rs > 1) td.rowSpan = sp.rs;
|
|
5009
|
-
}
|
|
5010
|
-
Object.assign(td.style, {
|
|
5011
|
-
border: cellBorder,
|
|
5012
|
-
padding: "2px 6px",
|
|
5013
|
-
background: cell.bgColor ?? "transparent",
|
|
5014
|
-
color: cell.textColor ?? this.theme.textColor,
|
|
5015
|
-
textAlign: cell.hAlign,
|
|
5016
|
-
verticalAlign: cell.vAlign === "top" ? "top" : cell.vAlign === "bottom" ? "bottom" : "middle",
|
|
5017
|
-
fontSize: `${fontPxOf(cell.textSize)}px`,
|
|
5018
|
-
fontFamily: cell.fontFamily === "monospace" ? "monospace" : "inherit",
|
|
5019
|
-
fontWeight: cell.bold ? "bold" : "normal",
|
|
5020
|
-
fontStyle: cell.italic ? "italic" : "normal",
|
|
5021
|
-
// Pine cell text never wraps; `\n` still breaks lines. Wrapping
|
|
5022
|
-
// used to collapse unicode sparklines and ━━━ dividers.
|
|
5023
|
-
whiteSpace: "pre"
|
|
5024
|
-
});
|
|
5025
|
-
if (cell.width) td.style.width = `${cell.width / 100 * plotW}px`;
|
|
5026
|
-
if (cell.height) td.style.height = `${cell.height / 100 * paneH}px`;
|
|
5027
|
-
if (cell.tooltip) td.title = cell.tooltip;
|
|
5028
|
-
td.textContent = cell.text ?? "";
|
|
5029
|
-
tr.appendChild(td);
|
|
5030
|
-
}
|
|
5031
|
-
table.appendChild(tr);
|
|
5032
|
-
}
|
|
5033
|
-
wrap.appendChild(table);
|
|
5034
|
-
return wrap;
|
|
5035
|
-
}
|
|
5036
|
-
/**
|
|
5037
|
-
* Position the wrapper at a Pine `position.*` corner/edge of the table's PANE
|
|
5038
|
-
* (not the whole chart), inset past the right price axis so it never overlaps
|
|
5039
|
-
* the Y-axis labels.
|
|
5040
|
-
*/
|
|
5041
|
-
anchor(el, position, b) {
|
|
5042
|
-
const m = 6;
|
|
5043
|
-
const containerH = this.root.clientHeight || this.container.clientHeight;
|
|
5044
|
-
const paneBottom = b.top + b.height;
|
|
5045
|
-
if (position.startsWith("top")) el.style.top = `${b.top + m}px`;
|
|
5046
|
-
else if (position.startsWith("bottom")) el.style.bottom = `${Math.max(0, containerH - paneBottom) + m}px`;
|
|
5047
|
-
else el.style.top = `${b.top + b.height / 2}px`;
|
|
5048
|
-
if (position.endsWith("left")) el.style.left = `${m}px`;
|
|
5049
|
-
else if (position.endsWith("right")) el.style.right = `${b.rightAxis + m}px`;
|
|
5050
|
-
else el.style.left = `calc(50% - ${b.rightAxis / 2}px)`;
|
|
5051
|
-
const tx = position.endsWith("center") ? "-50%" : "0";
|
|
5052
|
-
const ty = position.startsWith("middle") ? "-50%" : "0";
|
|
5053
|
-
if (tx !== "0" || ty !== "0") el.style.transform = `translate(${tx}, ${ty})`;
|
|
5054
|
-
}
|
|
5055
|
-
};
|
|
5056
|
-
|
|
5057
5008
|
// src/renderers/native/capabilities.ts
|
|
5058
5009
|
var NATIVE_CAPABILITIES = {
|
|
5059
5010
|
panes: true,
|
|
@@ -5074,7 +5025,7 @@ var NATIVE_CAPABILITIES = {
|
|
|
5074
5025
|
drawingDepth: true,
|
|
5075
5026
|
// drawings share the series' z space (backend-composited interleave layers)
|
|
5076
5027
|
tables: true,
|
|
5077
|
-
//
|
|
5028
|
+
// canvas-painted into the owning indicator's interleave slice
|
|
5078
5029
|
trades: true,
|
|
5079
5030
|
// strategy order-fill markers (arrows + labels + fill-price ticks)
|
|
5080
5031
|
inputsUI: true
|
|
@@ -5105,6 +5056,9 @@ function candleTier(spacing) {
|
|
|
5105
5056
|
if (spacing < CANDLE_BODY_MIN_SPACING) return "wick";
|
|
5106
5057
|
return "full";
|
|
5107
5058
|
}
|
|
5059
|
+
function snapY(yCss, dpr) {
|
|
5060
|
+
return Math.round(yCss * dpr) / dpr;
|
|
5061
|
+
}
|
|
5108
5062
|
function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
|
|
5109
5063
|
const wickDev = Math.max(1, Math.round(wickWidth(spacing) * dpr));
|
|
5110
5064
|
const wickLeftDev = Math.round(xCss * dpr - wickDev / 2);
|
|
@@ -5911,11 +5865,9 @@ var WebGL2Backend = class {
|
|
|
5911
5865
|
};
|
|
5912
5866
|
b.alpha = this.modelAlpha;
|
|
5913
5867
|
for (const m of models) for (const bgSpan of m.backgrounds) if (bgSpan.overlay !== true) this.emitBackground(b, bgSpan, pane, coords);
|
|
5914
|
-
for (const m of models) for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
5915
5868
|
if (isPrice) {
|
|
5916
5869
|
for (const m of scene.indicators.values()) {
|
|
5917
5870
|
for (const bgSpan of m.backgrounds) if (bgSpan.overlay === true) this.emitBackground(b, bgSpan, pane, coords);
|
|
5918
|
-
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
5919
5871
|
}
|
|
5920
5872
|
}
|
|
5921
5873
|
const drawCandles = isPrice && !scene.candlesHidden;
|
|
@@ -5931,6 +5883,7 @@ var WebGL2Backend = class {
|
|
|
5931
5883
|
b.alpha = this.modelAlpha;
|
|
5932
5884
|
const off = scene.offsetOf(m.id);
|
|
5933
5885
|
const mp = effPane(m);
|
|
5886
|
+
for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, mp, coords, i0, i1, off);
|
|
5934
5887
|
for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
5935
5888
|
}
|
|
5936
5889
|
if (drawCandles && !candleDrawn) {
|
|
@@ -5938,14 +5891,18 @@ var WebGL2Backend = class {
|
|
|
5938
5891
|
b.alpha = this.candleStructureAlpha;
|
|
5939
5892
|
this.emitPriceSeries(b, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
5940
5893
|
}
|
|
5941
|
-
drawSlicesUpTo(Infinity);
|
|
5942
5894
|
b.alpha = this.modelAlpha;
|
|
5943
5895
|
if (isPrice) {
|
|
5896
|
+
for (const m of scene.indicators.values()) {
|
|
5897
|
+
const off = scene.offsetOf(m.id);
|
|
5898
|
+
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, off);
|
|
5899
|
+
}
|
|
5944
5900
|
for (const m of scene.indicators.values()) {
|
|
5945
5901
|
const off = scene.offsetOf(m.id);
|
|
5946
5902
|
for (const s of m.series) if (s.overlay === true) this.emitSeries(b, s, pane, coords, i0, i1, theme, off);
|
|
5947
5903
|
}
|
|
5948
5904
|
}
|
|
5905
|
+
drawSlicesUpTo(Infinity);
|
|
5949
5906
|
for (const m of models) {
|
|
5950
5907
|
const mp = effPane(m);
|
|
5951
5908
|
for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
|
|
@@ -6340,12 +6297,12 @@ var WebGL2Backend = class {
|
|
|
6340
6297
|
if (drawBody) {
|
|
6341
6298
|
const oY = coords.priceToY(bar.open, pane.scale, pane.bounds);
|
|
6342
6299
|
const cY = coords.priceToY(bar.close, pane.scale, pane.bounds);
|
|
6343
|
-
bodyTop = Math.min(oY, cY);
|
|
6344
|
-
bodyH = Math.max(1, Math.
|
|
6300
|
+
bodyTop = snapY(Math.min(oY, cY), coords.dpr);
|
|
6301
|
+
bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - bodyTop);
|
|
6345
6302
|
}
|
|
6346
6303
|
if (cs.wickVisible) {
|
|
6347
|
-
const hY = coords.priceToY(bar.high, pane.scale, pane.bounds);
|
|
6348
|
-
const lY = coords.priceToY(bar.low, pane.scale, pane.bounds);
|
|
6304
|
+
const hY = snapY(coords.priceToY(bar.high, pane.scale, pane.bounds), coords.dpr);
|
|
6305
|
+
const lY = snapY(coords.priceToY(bar.low, pane.scale, pane.bounds), coords.dpr);
|
|
6349
6306
|
b.alpha = this.candleStructureAlpha;
|
|
6350
6307
|
const wCol = parseColor((isUp ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : bodyColorStr));
|
|
6351
6308
|
if (drawBody) {
|
|
@@ -7678,9 +7635,10 @@ var SceneGraph = class {
|
|
|
7678
7635
|
* so each indicator arrives behind the candles (and behind older indicators);
|
|
7679
7636
|
* `setIndicatorZ`/`bringToFront`/`sendToBack` change it. */
|
|
7680
7637
|
this.seriesZ = /* @__PURE__ */ new Map();
|
|
7681
|
-
/** Per-pane raster layers of
|
|
7638
|
+
/** Per-pane raster layers of drawings interleaved into the series stack (each
|
|
7639
|
+
* indicator's Pine drawings at its model's z, plus in-stack user drawings) — each a
|
|
7682
7640
|
* prepainted canvas the backend composites just before the series carrying `beforeZ`.
|
|
7683
|
-
* Rebuilt by the renderer per data frame
|
|
7641
|
+
* Rebuilt by the renderer per data frame. */
|
|
7684
7642
|
this.drawingSlices = /* @__PURE__ */ new Map();
|
|
7685
7643
|
/** Per-model index offset: the chart bar index of the model's `anchorTime` — its
|
|
7686
7644
|
* index-aligned payloads (dense series arrays, `bar_index` drawings) count from that
|
|
@@ -7915,11 +7873,9 @@ var Canvas2dBackend = class {
|
|
|
7915
7873
|
};
|
|
7916
7874
|
ctx.globalAlpha = this.modelAlpha;
|
|
7917
7875
|
for (const m of models) for (const bg of m.backgrounds) if (bg.overlay !== true) this.drawBackground(ctx, bg, pane, coords);
|
|
7918
|
-
for (const m of models) for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
7919
7876
|
if (isPrice) {
|
|
7920
7877
|
for (const m of scene.indicators.values()) {
|
|
7921
7878
|
for (const bg of m.backgrounds) if (bg.overlay === true) this.drawBackground(ctx, bg, pane, coords);
|
|
7922
|
-
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
7923
7879
|
}
|
|
7924
7880
|
}
|
|
7925
7881
|
const slices = scene.drawingSlices.get(pane.id) ?? [];
|
|
@@ -7943,6 +7899,7 @@ var Canvas2dBackend = class {
|
|
|
7943
7899
|
ctx.globalAlpha = this.modelAlpha;
|
|
7944
7900
|
const off = scene.offsetOf(m.id);
|
|
7945
7901
|
const mp = effPane(m);
|
|
7902
|
+
for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, mp, coords, i0, i1, off);
|
|
7946
7903
|
for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
7947
7904
|
}
|
|
7948
7905
|
if (drawCandles && !candleDrawn) {
|
|
@@ -7950,14 +7907,18 @@ var Canvas2dBackend = class {
|
|
|
7950
7907
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
7951
7908
|
this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
7952
7909
|
}
|
|
7953
|
-
drawSlicesUpTo(Infinity);
|
|
7954
7910
|
if (isPrice) {
|
|
7955
7911
|
ctx.globalAlpha = this.modelAlpha;
|
|
7912
|
+
for (const m of scene.indicators.values()) {
|
|
7913
|
+
const off = scene.offsetOf(m.id);
|
|
7914
|
+
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, off);
|
|
7915
|
+
}
|
|
7956
7916
|
for (const m of scene.indicators.values()) {
|
|
7957
7917
|
const off = scene.offsetOf(m.id);
|
|
7958
7918
|
for (const s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, i1, theme, off);
|
|
7959
7919
|
}
|
|
7960
7920
|
}
|
|
7921
|
+
drawSlicesUpTo(Infinity);
|
|
7961
7922
|
ctx.globalAlpha = this.modelAlpha;
|
|
7962
7923
|
for (const m of models) {
|
|
7963
7924
|
const mp = effPane(m);
|
|
@@ -8201,13 +8162,13 @@ var Canvas2dBackend = class {
|
|
|
8201
8162
|
if (drawBody) {
|
|
8202
8163
|
const oY = coords.priceToY(b.open, pane.scale, pane.bounds);
|
|
8203
8164
|
const cY = coords.priceToY(b.close, pane.scale, pane.bounds);
|
|
8204
|
-
top = Math.min(oY, cY);
|
|
8205
|
-
bodyH = Math.max(1, Math.
|
|
8165
|
+
top = snapY(Math.min(oY, cY), coords.dpr);
|
|
8166
|
+
bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - top);
|
|
8206
8167
|
}
|
|
8207
8168
|
if (cs.wickVisible) {
|
|
8208
8169
|
const wick = (up ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : color);
|
|
8209
|
-
const hY = coords.priceToY(b.high, pane.scale, pane.bounds);
|
|
8210
|
-
const lY = coords.priceToY(b.low, pane.scale, pane.bounds);
|
|
8170
|
+
const hY = snapY(coords.priceToY(b.high, pane.scale, pane.bounds), coords.dpr);
|
|
8171
|
+
const lY = snapY(coords.priceToY(b.low, pane.scale, pane.bounds), coords.dpr);
|
|
8211
8172
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
8212
8173
|
ctx.strokeStyle = wick;
|
|
8213
8174
|
ctx.lineWidth = g.wickW;
|
|
@@ -8654,6 +8615,19 @@ function autoFontSize(lines, boxW, boxH, bold) {
|
|
|
8654
8615
|
|
|
8655
8616
|
// src/renderers/shared/DrawingSceneRenderer.ts
|
|
8656
8617
|
var EMPTY_DRAWING_SET = { lines: [], boxes: [], labels: [], polylines: [], linefills: [] };
|
|
8618
|
+
function modelDrawingSet(m, overlay) {
|
|
8619
|
+
const want = (d) => Boolean(d.overlay) === overlay;
|
|
8620
|
+
return {
|
|
8621
|
+
lines: (m.lines ?? []).filter(want),
|
|
8622
|
+
boxes: (m.boxes ?? []).filter(want),
|
|
8623
|
+
labels: (m.labels ?? []).filter(want),
|
|
8624
|
+
polylines: (m.polylines ?? []).filter(want),
|
|
8625
|
+
linefills: (m.linefills ?? []).filter(want)
|
|
8626
|
+
};
|
|
8627
|
+
}
|
|
8628
|
+
function drawingSetEmpty(s) {
|
|
8629
|
+
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
8630
|
+
}
|
|
8657
8631
|
function fontSizePx(size) {
|
|
8658
8632
|
return size === "auto" ? 12 : namedFontSize(size);
|
|
8659
8633
|
}
|
|
@@ -9506,10 +9480,8 @@ var ChromeRenderer = class {
|
|
|
9506
9480
|
this.ctx = null;
|
|
9507
9481
|
// The color for axis tick labels — the host-passed surface text, set each frame in render().
|
|
9508
9482
|
this.axisTextColor = DARK_THEME.textColor;
|
|
9509
|
-
// Shared Pine-drawing renderer
|
|
9483
|
+
// Shared Pine-drawing renderer, used here for autoscale geometry only; widthCache persists.
|
|
9510
9484
|
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
9511
|
-
// Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per render).
|
|
9512
|
-
this.labelTips = [];
|
|
9513
9485
|
}
|
|
9514
9486
|
mount(canvas) {
|
|
9515
9487
|
this.canvas = canvas;
|
|
@@ -9533,8 +9505,8 @@ var ChromeRenderer = class {
|
|
|
9533
9505
|
*/
|
|
9534
9506
|
paneDrawingsRange(ownModels, scene, isPricePane, vr) {
|
|
9535
9507
|
let dr = null;
|
|
9536
|
-
for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(
|
|
9537
|
-
if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(
|
|
9508
|
+
for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, false), vr, scene.offsetOf(m.id)));
|
|
9509
|
+
if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, true), vr, scene.offsetOf(m.id)));
|
|
9538
9510
|
return dr;
|
|
9539
9511
|
}
|
|
9540
9512
|
/** Clear the chrome canvas and draw drawings + axes + current-price line.
|
|
@@ -9551,7 +9523,6 @@ var ChromeRenderer = class {
|
|
|
9551
9523
|
const dataW = coords.width;
|
|
9552
9524
|
const dataH = coords.height;
|
|
9553
9525
|
this.axisTextColor = surface?.textColor ?? theme.textColor;
|
|
9554
|
-
this.labelTips = [];
|
|
9555
9526
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
9556
9527
|
ctx.clearRect(0, 0, fullW, fullH);
|
|
9557
9528
|
if (surface && (fullW > dataW || fullH > dataH)) {
|
|
@@ -9567,17 +9538,6 @@ var ChromeRenderer = class {
|
|
|
9567
9538
|
return;
|
|
9568
9539
|
}
|
|
9569
9540
|
const pricePane = panes.find((p) => p.kind === "price") ?? null;
|
|
9570
|
-
for (const pane of panes) {
|
|
9571
|
-
if (pane.collapsed) continue;
|
|
9572
|
-
for (const m of scene.indicatorsForPane(pane.id)) {
|
|
9573
|
-
const sc = scene.scaleFor(m, pane);
|
|
9574
|
-
const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
9575
|
-
this.renderDrawings(ctx, coords, this.ownDrawings(m), mp, dataW, scene.offsetOf(m.id));
|
|
9576
|
-
}
|
|
9577
|
-
}
|
|
9578
|
-
if (pricePane) {
|
|
9579
|
-
for (const m of scene.indicators.values()) this.renderDrawings(ctx, coords, this.overlayDrawings(m), pricePane, dataW, scene.offsetOf(m.id));
|
|
9580
|
-
}
|
|
9581
9541
|
if (pricePane && !pricePane.collapsed && scene.tradeMarkers.visible) {
|
|
9582
9542
|
for (const m of scene.indicators.values()) {
|
|
9583
9543
|
if (m.trades?.length) this.renderTrades(ctx, coords, scene, theme, m.trades, pricePane, dataW);
|
|
@@ -9593,25 +9553,6 @@ var ChromeRenderer = class {
|
|
|
9593
9553
|
this.canvas = null;
|
|
9594
9554
|
this.ctx = null;
|
|
9595
9555
|
}
|
|
9596
|
-
// ── Pine-drawing helpers (own vs force_overlay routing) ──
|
|
9597
|
-
ownDrawings(m) {
|
|
9598
|
-
return {
|
|
9599
|
-
lines: (m.lines ?? []).filter((d) => !d.overlay),
|
|
9600
|
-
boxes: (m.boxes ?? []).filter((d) => !d.overlay),
|
|
9601
|
-
labels: (m.labels ?? []).filter((d) => !d.overlay),
|
|
9602
|
-
polylines: (m.polylines ?? []).filter((d) => !d.overlay),
|
|
9603
|
-
linefills: (m.linefills ?? []).filter((d) => !d.overlay)
|
|
9604
|
-
};
|
|
9605
|
-
}
|
|
9606
|
-
overlayDrawings(m) {
|
|
9607
|
-
return {
|
|
9608
|
-
lines: (m.lines ?? []).filter((d) => d.overlay),
|
|
9609
|
-
boxes: (m.boxes ?? []).filter((d) => d.overlay),
|
|
9610
|
-
labels: (m.labels ?? []).filter((d) => d.overlay),
|
|
9611
|
-
polylines: (m.polylines ?? []).filter((d) => d.overlay),
|
|
9612
|
-
linefills: (m.linefills ?? []).filter((d) => d.overlay)
|
|
9613
|
-
};
|
|
9614
|
-
}
|
|
9615
9556
|
drawingsRange(set, vr, indexOffset = 0) {
|
|
9616
9557
|
this.drawScene.setSet(set, indexOffset);
|
|
9617
9558
|
if (this.drawScene.isEmpty()) return null;
|
|
@@ -9645,34 +9586,6 @@ var ChromeRenderer = class {
|
|
|
9645
9586
|
);
|
|
9646
9587
|
ctx.restore();
|
|
9647
9588
|
}
|
|
9648
|
-
renderDrawings(ctx, coords, set, pane, dataW, indexOffset = 0) {
|
|
9649
|
-
this.drawScene.setSet(set, indexOffset);
|
|
9650
|
-
if (this.drawScene.isEmpty()) return;
|
|
9651
|
-
ctx.save();
|
|
9652
|
-
ctx.translate(0, pane.bounds.top);
|
|
9653
|
-
ctx.beginPath();
|
|
9654
|
-
ctx.rect(0, 0, dataW, pane.bounds.height);
|
|
9655
|
-
ctx.clip();
|
|
9656
|
-
this.drawScene.render(
|
|
9657
|
-
ctx,
|
|
9658
|
-
dataW,
|
|
9659
|
-
pane.bounds.height,
|
|
9660
|
-
(l) => coords.logicalToX(l),
|
|
9661
|
-
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
9662
|
-
);
|
|
9663
|
-
ctx.restore();
|
|
9664
|
-
for (const r of this.drawScene.labelTipRegions()) {
|
|
9665
|
-
this.labelTips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
9666
|
-
}
|
|
9667
|
-
}
|
|
9668
|
-
/** Tooltip of the topmost label under a plot-space point, or null. Fed by the last render. */
|
|
9669
|
-
labelTooltipAt(x, y) {
|
|
9670
|
-
for (let i = this.labelTips.length - 1; i >= 0; i -= 1) {
|
|
9671
|
-
const r = this.labelTips[i];
|
|
9672
|
-
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
9673
|
-
}
|
|
9674
|
-
return null;
|
|
9675
|
-
}
|
|
9676
9589
|
// ── axes ──
|
|
9677
9590
|
drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
|
|
9678
9591
|
ctx.strokeStyle = scene.style.borderColor ?? theme.borderColor;
|
|
@@ -13991,17 +13904,17 @@ function glyphIcon(glyph) {
|
|
|
13991
13904
|
return textGlyph(String(glyph), 15);
|
|
13992
13905
|
}
|
|
13993
13906
|
function stampSizeIcon(size) {
|
|
13994
|
-
return textGlyph("\u25CF", (
|
|
13907
|
+
return textGlyph("\u25CF", (SIZE_PX[String(size)] ?? 13) + 4);
|
|
13995
13908
|
}
|
|
13996
13909
|
function sizeIcon(size) {
|
|
13997
13910
|
return textGlyph(String(size).charAt(0).toUpperCase(), 15);
|
|
13998
13911
|
}
|
|
13999
|
-
var
|
|
13912
|
+
var SIZE_PX = { small: 10, normal: 13, large: 16, huge: 20 };
|
|
14000
13913
|
function numbersSizeIcon(size) {
|
|
14001
|
-
return textGlyph("12", (
|
|
13914
|
+
return textGlyph("12", (SIZE_PX[String(size)] ?? 13) - 1, 16.5, 'font-weight="600"');
|
|
14002
13915
|
}
|
|
14003
13916
|
function labelSizeIcon(size) {
|
|
14004
|
-
return textGlyph("T", (
|
|
13917
|
+
return textGlyph("T", (SIZE_PX[String(size)] ?? 13) + 2);
|
|
14005
13918
|
}
|
|
14006
13919
|
function capitalize(s) {
|
|
14007
13920
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
@@ -14976,6 +14889,315 @@ var UserDrawingController = class {
|
|
|
14976
14889
|
}
|
|
14977
14890
|
};
|
|
14978
14891
|
|
|
14892
|
+
// src/renderers/shared/TableOverlay.ts
|
|
14893
|
+
var SIZE_PX2 = {
|
|
14894
|
+
auto: 13,
|
|
14895
|
+
tiny: 10,
|
|
14896
|
+
small: 11,
|
|
14897
|
+
normal: 13,
|
|
14898
|
+
large: 16,
|
|
14899
|
+
huge: 20
|
|
14900
|
+
};
|
|
14901
|
+
function fontPxOf(size) {
|
|
14902
|
+
if (typeof size === "number") return size > 0 ? size : SIZE_PX2.auto;
|
|
14903
|
+
return SIZE_PX2[size] ?? SIZE_PX2.auto;
|
|
14904
|
+
}
|
|
14905
|
+
function tableHasContent(t) {
|
|
14906
|
+
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
14907
|
+
}
|
|
14908
|
+
function mergeRenderPlan(t) {
|
|
14909
|
+
const span = /* @__PURE__ */ new Map();
|
|
14910
|
+
const omit = /* @__PURE__ */ new Set();
|
|
14911
|
+
for (const m of t.merges) {
|
|
14912
|
+
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
14913
|
+
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
14914
|
+
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
14915
|
+
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
14916
|
+
}
|
|
14917
|
+
}
|
|
14918
|
+
}
|
|
14919
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
14920
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
14921
|
+
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
14922
|
+
}
|
|
14923
|
+
}
|
|
14924
|
+
for (const key of span.keys()) omit.delete(key);
|
|
14925
|
+
return { span, omit };
|
|
14926
|
+
}
|
|
14927
|
+
|
|
14928
|
+
// src/renderers/shared/TableCanvasRenderer.ts
|
|
14929
|
+
var PAD_X = 6;
|
|
14930
|
+
var PAD_Y = 2;
|
|
14931
|
+
var MARGIN = 6;
|
|
14932
|
+
var LINE_HEIGHT = 1.2;
|
|
14933
|
+
function paintTable(ctx, t, args, tips) {
|
|
14934
|
+
if (!tableHasContent(t)) return;
|
|
14935
|
+
const layout = layoutTable(ctx, t, args);
|
|
14936
|
+
if (!layout || layout.w <= 0 || layout.h <= 0) return;
|
|
14937
|
+
const fw = t.frameColor && t.frameWidth > 0 ? t.frameWidth : 0;
|
|
14938
|
+
const { x, y } = anchorOrigin(t.position, layout.w + 2 * fw, layout.h + 2 * fw, args);
|
|
14939
|
+
const x0 = x + fw;
|
|
14940
|
+
const y0 = y + fw;
|
|
14941
|
+
if (t.bgColor) {
|
|
14942
|
+
ctx.fillStyle = t.bgColor;
|
|
14943
|
+
ctx.fillRect(x0, y0, layout.w, layout.h);
|
|
14944
|
+
}
|
|
14945
|
+
if (fw > 0 && t.frameColor) {
|
|
14946
|
+
ctx.strokeStyle = t.frameColor;
|
|
14947
|
+
ctx.lineWidth = fw;
|
|
14948
|
+
ctx.strokeRect(x + fw / 2, y + fw / 2, layout.w + fw, layout.h + fw);
|
|
14949
|
+
}
|
|
14950
|
+
const colX = [0];
|
|
14951
|
+
for (const w of layout.colW) colX.push(colX[colX.length - 1] + w);
|
|
14952
|
+
const rowY = [0];
|
|
14953
|
+
for (const h of layout.rowH) rowY.push(rowY[rowY.length - 1] + h);
|
|
14954
|
+
const prevBaseline = ctx.textBaseline;
|
|
14955
|
+
const prevAlign = ctx.textAlign;
|
|
14956
|
+
ctx.textBaseline = "middle";
|
|
14957
|
+
for (const box of layout.boxes) {
|
|
14958
|
+
const rx = x0 + colX[box.c];
|
|
14959
|
+
const ry = y0 + rowY[box.r];
|
|
14960
|
+
const rw = colX[box.c + box.cs] - colX[box.c];
|
|
14961
|
+
const rh = rowY[box.r + box.rs] - rowY[box.r];
|
|
14962
|
+
const cell = box.cell;
|
|
14963
|
+
if (cell.bgColor) {
|
|
14964
|
+
ctx.fillStyle = cell.bgColor;
|
|
14965
|
+
ctx.fillRect(rx, ry, rw, rh);
|
|
14966
|
+
}
|
|
14967
|
+
const text = cell.text ?? "";
|
|
14968
|
+
if (text.length > 0) {
|
|
14969
|
+
const px = fontPxOf(cell.textSize);
|
|
14970
|
+
ctx.font = cellFont(cell, px, args.theme);
|
|
14971
|
+
ctx.fillStyle = cell.textColor ?? args.theme.textColor;
|
|
14972
|
+
const lines = text.split("\n");
|
|
14973
|
+
const blockH = lines.length * px * LINE_HEIGHT;
|
|
14974
|
+
const blockTop = cell.vAlign === "top" ? ry + PAD_Y : cell.vAlign === "bottom" ? ry + rh - PAD_Y - blockH : ry + (rh - blockH) / 2;
|
|
14975
|
+
const tx = cell.hAlign === "left" ? rx + PAD_X : cell.hAlign === "right" ? rx + rw - PAD_X : rx + rw / 2;
|
|
14976
|
+
ctx.textAlign = cell.hAlign;
|
|
14977
|
+
lines.forEach((line, i) => ctx.fillText(line, tx, blockTop + (i + 0.5) * px * LINE_HEIGHT));
|
|
14978
|
+
}
|
|
14979
|
+
if (cell.tooltip) tips.push({ left: rx, top: ry, right: rx + rw, bottom: ry + rh, text: cell.tooltip });
|
|
14980
|
+
}
|
|
14981
|
+
ctx.textBaseline = prevBaseline;
|
|
14982
|
+
ctx.textAlign = prevAlign;
|
|
14983
|
+
if (t.borderColor && t.borderWidth > 0) {
|
|
14984
|
+
ctx.strokeStyle = t.borderColor;
|
|
14985
|
+
ctx.lineWidth = t.borderWidth;
|
|
14986
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14987
|
+
ctx.beginPath();
|
|
14988
|
+
const edge = (ax, ay, bx, by) => {
|
|
14989
|
+
const key = `${ax},${ay},${bx},${by}`;
|
|
14990
|
+
if (seen.has(key)) return;
|
|
14991
|
+
seen.add(key);
|
|
14992
|
+
ctx.moveTo(ax, ay);
|
|
14993
|
+
ctx.lineTo(bx, by);
|
|
14994
|
+
};
|
|
14995
|
+
for (const box of layout.boxes) {
|
|
14996
|
+
const l = Math.round(x0 + colX[box.c]);
|
|
14997
|
+
const r = Math.round(x0 + colX[box.c + box.cs]);
|
|
14998
|
+
const tp = Math.round(y0 + rowY[box.r]);
|
|
14999
|
+
const bt = Math.round(y0 + rowY[box.r + box.rs]);
|
|
15000
|
+
edge(l, tp, r, tp);
|
|
15001
|
+
edge(l, bt, r, bt);
|
|
15002
|
+
edge(l, tp, l, bt);
|
|
15003
|
+
edge(r, tp, r, bt);
|
|
15004
|
+
}
|
|
15005
|
+
ctx.stroke();
|
|
15006
|
+
}
|
|
15007
|
+
}
|
|
15008
|
+
function layoutTable(ctx, t, args) {
|
|
15009
|
+
const { span, omit } = mergeRenderPlan(t);
|
|
15010
|
+
const colW = new Array(t.columns).fill(0);
|
|
15011
|
+
const rowH = new Array(t.rows).fill(0);
|
|
15012
|
+
const boxes = [];
|
|
15013
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
15014
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
15015
|
+
if (omit.has(`${r}:${c}`)) continue;
|
|
15016
|
+
const cell = t.cells[r]?.[c];
|
|
15017
|
+
if (cell == null) continue;
|
|
15018
|
+
const sp = span.get(`${r}:${c}`);
|
|
15019
|
+
boxes.push({ cell, r, c, cs: Math.min(sp?.cs ?? 1, t.columns - c), rs: Math.min(sp?.rs ?? 1, t.rows - r) });
|
|
15020
|
+
}
|
|
15021
|
+
}
|
|
15022
|
+
if (boxes.length === 0) return null;
|
|
15023
|
+
const sizeOf = (cell) => {
|
|
15024
|
+
const px = fontPxOf(cell.textSize);
|
|
15025
|
+
ctx.font = cellFont(cell, px, args.theme);
|
|
15026
|
+
const lines = (cell.text ?? "").split("\n");
|
|
15027
|
+
let maxW = 0;
|
|
15028
|
+
for (const line of lines) maxW = Math.max(maxW, ctx.measureText(line).width);
|
|
15029
|
+
let w2 = Math.ceil(maxW) + 2 * PAD_X;
|
|
15030
|
+
let h2 = Math.ceil(lines.length * px * LINE_HEIGHT) + 2 * PAD_Y;
|
|
15031
|
+
if (cell.width) w2 = Math.max(w2, cell.width / 100 * args.plotWidth);
|
|
15032
|
+
if (cell.height) h2 = Math.max(h2, cell.height / 100 * args.paneHeight);
|
|
15033
|
+
return { w: w2, h: h2 };
|
|
15034
|
+
};
|
|
15035
|
+
const spanning = [];
|
|
15036
|
+
for (const box of boxes) {
|
|
15037
|
+
const { w: w2, h: h2 } = sizeOf(box.cell);
|
|
15038
|
+
if (box.cs === 1) colW[box.c] = Math.max(colW[box.c], w2);
|
|
15039
|
+
if (box.rs === 1) rowH[box.r] = Math.max(rowH[box.r], h2);
|
|
15040
|
+
if (box.cs > 1 || box.rs > 1) spanning.push({ box, w: w2, h: h2 });
|
|
15041
|
+
}
|
|
15042
|
+
for (const { box, w: w2, h: h2 } of spanning) {
|
|
15043
|
+
if (box.cs > 1) {
|
|
15044
|
+
let sum = 0;
|
|
15045
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
|
|
15046
|
+
if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
|
|
15047
|
+
}
|
|
15048
|
+
if (box.rs > 1) {
|
|
15049
|
+
let sum = 0;
|
|
15050
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
|
|
15051
|
+
if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
|
|
15052
|
+
}
|
|
15053
|
+
}
|
|
15054
|
+
let w = 0;
|
|
15055
|
+
for (const cw of colW) w += cw;
|
|
15056
|
+
let h = 0;
|
|
15057
|
+
for (const rh of rowH) h += rh;
|
|
15058
|
+
return { colW, rowH, w, h, boxes };
|
|
15059
|
+
}
|
|
15060
|
+
function anchorOrigin(position, totalW, totalH, args) {
|
|
15061
|
+
let y;
|
|
15062
|
+
if (position.startsWith("top")) y = MARGIN;
|
|
15063
|
+
else if (position.startsWith("bottom")) y = args.paneHeight - MARGIN - totalH;
|
|
15064
|
+
else y = args.paneHeight / 2 - totalH / 2;
|
|
15065
|
+
let x;
|
|
15066
|
+
if (position.endsWith("left")) x = MARGIN;
|
|
15067
|
+
else if (position.endsWith("right")) x = args.plotWidth - MARGIN - totalW;
|
|
15068
|
+
else x = args.plotWidth / 2 - totalW / 2;
|
|
15069
|
+
return { x, y };
|
|
15070
|
+
}
|
|
15071
|
+
function cellFont(cell, px, theme) {
|
|
15072
|
+
const family = cell.fontFamily === "monospace" ? "monospace" : theme.fontFamily || "sans-serif";
|
|
15073
|
+
return `${cell.italic ? "italic " : ""}${cell.bold ? "bold " : ""}${px}px ${family}`;
|
|
15074
|
+
}
|
|
15075
|
+
|
|
15076
|
+
// src/renderers/native/drawings/IndicatorDrawingSlices.ts
|
|
15077
|
+
function indicatorSliceKey(z, boundaries) {
|
|
15078
|
+
return boundaries.find((b) => b > z) ?? Infinity;
|
|
15079
|
+
}
|
|
15080
|
+
var IndicatorDrawingSlices = class {
|
|
15081
|
+
constructor() {
|
|
15082
|
+
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
15083
|
+
/** Slice canvas cache, keyed `paneId|beforeZ` — same lifecycle as the user-drawing cache. */
|
|
15084
|
+
this.sliceCache = /* @__PURE__ */ new Map();
|
|
15085
|
+
/** Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per prepare). */
|
|
15086
|
+
this.tips = [];
|
|
15087
|
+
}
|
|
15088
|
+
/**
|
|
15089
|
+
* Rebuild the per-indicator drawing slices for this data frame. `ref` is the data
|
|
15090
|
+
* canvas the slices must match pixel-for-pixel (the backend composites them 1:1).
|
|
15091
|
+
* Runs from the renderer's data paint, just before the backend composites the scene.
|
|
15092
|
+
*/
|
|
15093
|
+
prepare(scene, coords, theme, ref) {
|
|
15094
|
+
this.tips = [];
|
|
15095
|
+
const out = /* @__PURE__ */ new Map();
|
|
15096
|
+
if (ref.width === 0 || ref.height === 0) {
|
|
15097
|
+
this.sliceCache.clear();
|
|
15098
|
+
return out;
|
|
15099
|
+
}
|
|
15100
|
+
this.drawScene.setDeps({
|
|
15101
|
+
timeToLogical: (ms) => coords.timeToLogical(ms),
|
|
15102
|
+
barAt: (logical) => {
|
|
15103
|
+
const b = scene.bars[Math.round(logical)];
|
|
15104
|
+
return b ? { high: b.high, low: b.low } : null;
|
|
15105
|
+
},
|
|
15106
|
+
theme
|
|
15107
|
+
});
|
|
15108
|
+
const dpr = coords.dpr;
|
|
15109
|
+
const dataW = coords.width;
|
|
15110
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
15111
|
+
const add = (paneId, beforeZ, entry) => {
|
|
15112
|
+
const key = `${paneId}|${beforeZ}`;
|
|
15113
|
+
const bucket = buckets.get(key);
|
|
15114
|
+
if (bucket) bucket.entries.push(entry);
|
|
15115
|
+
else buckets.set(key, { paneId, beforeZ, entries: [entry] });
|
|
15116
|
+
};
|
|
15117
|
+
for (const pane of scene.orderedPanes()) {
|
|
15118
|
+
if (pane.collapsed) continue;
|
|
15119
|
+
const boundaries = scene.seriesBoundaries(pane.id);
|
|
15120
|
+
for (const m of scene.orderedIndicatorsForPane(pane.id)) {
|
|
15121
|
+
const set = modelDrawingSet(m, false);
|
|
15122
|
+
const tables = (m.tables ?? []).filter((t) => !t.overlay);
|
|
15123
|
+
if (drawingSetEmpty(set) && tables.length === 0) continue;
|
|
15124
|
+
const sc = scene.scaleFor(m, pane);
|
|
15125
|
+
const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
15126
|
+
const beforeZ = indicatorSliceKey(scene.zOf(m.id), boundaries);
|
|
15127
|
+
add(pane.id, beforeZ, { set, tables, pane: mp, indexOffset: scene.offsetOf(m.id) });
|
|
15128
|
+
}
|
|
15129
|
+
if (pane.kind === "price") {
|
|
15130
|
+
for (const m of scene.indicators.values()) {
|
|
15131
|
+
const set = modelDrawingSet(m, true);
|
|
15132
|
+
const tables = (m.tables ?? []).filter((t) => t.overlay === true);
|
|
15133
|
+
if (drawingSetEmpty(set) && tables.length === 0) continue;
|
|
15134
|
+
add(pane.id, Infinity, { set, tables, pane, indexOffset: scene.offsetOf(m.id) });
|
|
15135
|
+
}
|
|
15136
|
+
}
|
|
15137
|
+
}
|
|
15138
|
+
for (const [key, { paneId, beforeZ, entries }] of buckets) {
|
|
15139
|
+
let canvas = this.sliceCache.get(key);
|
|
15140
|
+
if (!canvas) {
|
|
15141
|
+
canvas = document.createElement("canvas");
|
|
15142
|
+
this.sliceCache.set(key, canvas);
|
|
15143
|
+
}
|
|
15144
|
+
if (canvas.width !== ref.width || canvas.height !== ref.height) {
|
|
15145
|
+
canvas.width = ref.width;
|
|
15146
|
+
canvas.height = ref.height;
|
|
15147
|
+
}
|
|
15148
|
+
const ctx = canvas.getContext("2d");
|
|
15149
|
+
if (!ctx) continue;
|
|
15150
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
15151
|
+
ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
|
|
15152
|
+
for (const e of entries) this.paintEntry(ctx, e, coords, dataW, theme);
|
|
15153
|
+
const slices = out.get(paneId) ?? [];
|
|
15154
|
+
slices.push({ beforeZ, canvas });
|
|
15155
|
+
out.set(paneId, slices);
|
|
15156
|
+
}
|
|
15157
|
+
for (const key of [...this.sliceCache.keys()]) if (!buckets.has(key)) this.sliceCache.delete(key);
|
|
15158
|
+
for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
|
|
15159
|
+
return out;
|
|
15160
|
+
}
|
|
15161
|
+
paintEntry(ctx, e, coords, dataW, theme) {
|
|
15162
|
+
const { pane } = e;
|
|
15163
|
+
const paneTips = [];
|
|
15164
|
+
ctx.save();
|
|
15165
|
+
ctx.translate(0, pane.bounds.top);
|
|
15166
|
+
ctx.beginPath();
|
|
15167
|
+
ctx.rect(0, 0, dataW, pane.bounds.height);
|
|
15168
|
+
ctx.clip();
|
|
15169
|
+
this.drawScene.setSet(e.set, e.indexOffset);
|
|
15170
|
+
this.drawScene.render(
|
|
15171
|
+
ctx,
|
|
15172
|
+
dataW,
|
|
15173
|
+
pane.bounds.height,
|
|
15174
|
+
(l) => coords.logicalToX(l),
|
|
15175
|
+
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
15176
|
+
);
|
|
15177
|
+
paneTips.push(...this.drawScene.labelTipRegions());
|
|
15178
|
+
for (const t of e.tables) paintTable(ctx, t, { paneHeight: pane.bounds.height, plotWidth: dataW, theme }, paneTips);
|
|
15179
|
+
ctx.restore();
|
|
15180
|
+
for (const r of paneTips) {
|
|
15181
|
+
this.tips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
15182
|
+
}
|
|
15183
|
+
}
|
|
15184
|
+
/** Tooltip of the topmost label or table cell under a plot-space point, or null. Fed by the last prepare. */
|
|
15185
|
+
labelTooltipAt(x, y) {
|
|
15186
|
+
for (let i = this.tips.length - 1; i >= 0; i -= 1) {
|
|
15187
|
+
const r = this.tips[i];
|
|
15188
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
15189
|
+
}
|
|
15190
|
+
return null;
|
|
15191
|
+
}
|
|
15192
|
+
};
|
|
15193
|
+
function mergeSlices(indicator, user) {
|
|
15194
|
+
const out = /* @__PURE__ */ new Map();
|
|
15195
|
+
for (const [paneId, slices] of indicator) out.set(paneId, [...slices]);
|
|
15196
|
+
for (const [paneId, slices] of user) out.set(paneId, [...out.get(paneId) ?? [], ...slices]);
|
|
15197
|
+
for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
|
|
15198
|
+
return out;
|
|
15199
|
+
}
|
|
15200
|
+
|
|
14979
15201
|
// src/renderers/native/drawings/Projector.ts
|
|
14980
15202
|
function createProjector(coords, paneOf, paneIdAtY, barsInRange) {
|
|
14981
15203
|
return {
|
|
@@ -15829,6 +16051,11 @@ var NativeRenderer = class {
|
|
|
15829
16051
|
this.vpvrRenderer = new VpvrRenderer();
|
|
15830
16052
|
this.resizeObserver = null;
|
|
15831
16053
|
this.dprMedia = null;
|
|
16054
|
+
/** Plot size in INTEGER device px, as last reported by the resize observer's
|
|
16055
|
+
* device-pixel-content-box — the browser's own statement of how many device pixels
|
|
16056
|
+
* it paints the plot into. `null` until the first report or where the box type is
|
|
16057
|
+
* unsupported (WebKit); syncSize then falls back to rounding the client rect. */
|
|
16058
|
+
this.plotDeviceSize = null;
|
|
15832
16059
|
this.coords = new CoordinateSystem();
|
|
15833
16060
|
this.scene = new SceneGraph();
|
|
15834
16061
|
// chosen at mount (WebGL2 if available, else canvas2d)
|
|
@@ -15836,6 +16063,8 @@ var NativeRenderer = class {
|
|
|
15836
16063
|
this.glowAmount = 0;
|
|
15837
16064
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
15838
16065
|
this.chrome = new ChromeRenderer();
|
|
16066
|
+
/** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
|
|
16067
|
+
this.indicatorSlices = new IndicatorDrawingSlices();
|
|
15839
16068
|
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
15840
16069
|
this.labelTooltip = null;
|
|
15841
16070
|
this.crosshairLayer = new CrosshairRenderer();
|
|
@@ -15984,7 +16213,6 @@ var NativeRenderer = class {
|
|
|
15984
16213
|
this.toggleVisibleCbs = /* @__PURE__ */ new Set();
|
|
15985
16214
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
15986
16215
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
15987
|
-
this.tableOverlays = /* @__PURE__ */ new Map();
|
|
15988
16216
|
this.name = "native";
|
|
15989
16217
|
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
|
|
15990
16218
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
@@ -16298,7 +16526,6 @@ var NativeRenderer = class {
|
|
|
16298
16526
|
* hidden) on clear so a re-show picks up the current theme.
|
|
16299
16527
|
*/
|
|
16300
16528
|
setLoading(loading) {
|
|
16301
|
-
for (const overlay of this.tableOverlays.values()) overlay.setVisible(!loading);
|
|
16302
16529
|
if (!loading || !this.wrapper) {
|
|
16303
16530
|
this.loadingEl?.remove();
|
|
16304
16531
|
this.loadingEl = null;
|
|
@@ -16960,7 +17187,7 @@ var NativeRenderer = class {
|
|
|
16960
17187
|
this.plot.addEventListener("pointerleave", this.onScrollProximityLeave);
|
|
16961
17188
|
this.labelTooltip = new LabelTooltip(this.plot, {
|
|
16962
17189
|
theme: () => this.chromeTheme(),
|
|
16963
|
-
lookup: (x, y) => this.
|
|
17190
|
+
lookup: (x, y) => this.indicatorSlices.labelTooltipAt(x, y)
|
|
16964
17191
|
});
|
|
16965
17192
|
this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
|
|
16966
17193
|
projector: () => this.drawingProjector(),
|
|
@@ -17045,6 +17272,7 @@ var NativeRenderer = class {
|
|
|
17045
17272
|
this.emitPaneAction({ type: "maximize", paneId, maximized });
|
|
17046
17273
|
}
|
|
17047
17274
|
});
|
|
17275
|
+
this.paneControls.setSuspended(this.layoutMode === "mobile");
|
|
17048
17276
|
this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
|
|
17049
17277
|
panes: () => this.axisScaleViews(),
|
|
17050
17278
|
rightAxis: () => this.rightAxisW,
|
|
@@ -17054,8 +17282,19 @@ var NativeRenderer = class {
|
|
|
17054
17282
|
if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
|
|
17055
17283
|
}
|
|
17056
17284
|
});
|
|
17057
|
-
this.resizeObserver = new ResizeObserver(() =>
|
|
17285
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
17286
|
+
for (const e of entries) {
|
|
17287
|
+
if (e.target !== this.plot) continue;
|
|
17288
|
+
const s = e.devicePixelContentBoxSize?.[0];
|
|
17289
|
+
if (s) this.plotDeviceSize = { width: s.inlineSize, height: s.blockSize };
|
|
17290
|
+
}
|
|
17291
|
+
this.resize();
|
|
17292
|
+
});
|
|
17058
17293
|
this.resizeObserver.observe(this.wrapper);
|
|
17294
|
+
try {
|
|
17295
|
+
this.resizeObserver.observe(this.plot, { box: "device-pixel-content-box" });
|
|
17296
|
+
} catch {
|
|
17297
|
+
}
|
|
17059
17298
|
this.watchDpr();
|
|
17060
17299
|
this.syncSize();
|
|
17061
17300
|
}
|
|
@@ -17180,8 +17419,6 @@ var NativeRenderer = class {
|
|
|
17180
17419
|
this.inputsUI?.destroy();
|
|
17181
17420
|
this.paneControls?.destroy();
|
|
17182
17421
|
this.axisScaleButtons?.destroy();
|
|
17183
|
-
for (const overlay of this.tableOverlays.values()) overlay.destroy();
|
|
17184
|
-
this.tableOverlays.clear();
|
|
17185
17422
|
this.resizeObserver?.disconnect();
|
|
17186
17423
|
this.resizeObserver = null;
|
|
17187
17424
|
this.dprMedia?.removeEventListener("change", this.onDprChange);
|
|
@@ -17210,6 +17447,7 @@ var NativeRenderer = class {
|
|
|
17210
17447
|
this.attributionEl = null;
|
|
17211
17448
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
17212
17449
|
this.mountContainer?.style.removeProperty("--vela-scale-gutter");
|
|
17450
|
+
this.mountContainer?.style.removeProperty("--vela-bottom-gutter");
|
|
17213
17451
|
this.mountContainer?.style.removeProperty("--vela-price-pane-top");
|
|
17214
17452
|
this.mountContainer?.style.removeProperty("--vela-price-pane-bottom");
|
|
17215
17453
|
this.mountContainer = null;
|
|
@@ -17302,7 +17540,6 @@ var NativeRenderer = class {
|
|
|
17302
17540
|
ensurePane(pane) {
|
|
17303
17541
|
this.scene.ensurePane(pane.id, pane.kind, pane.order, pane.heightWeight ?? (pane.kind === "price" ? 3 : 1));
|
|
17304
17542
|
this.layoutPanes();
|
|
17305
|
-
this.repositionTables();
|
|
17306
17543
|
this.paneControls?.refresh();
|
|
17307
17544
|
this.scheduler.invalidate(4 /* Full */);
|
|
17308
17545
|
}
|
|
@@ -17324,17 +17561,14 @@ var NativeRenderer = class {
|
|
|
17324
17561
|
if (!model.ownScale) this.scene.dropIndicatorScale(handle.id);
|
|
17325
17562
|
this.inputsUI.setPane(handle.id, paneId);
|
|
17326
17563
|
this.refreshAnchorOffset(model);
|
|
17327
|
-
this.syncTables(model);
|
|
17328
17564
|
this.refreshAxisWidth();
|
|
17329
17565
|
this.layoutPanes();
|
|
17330
|
-
this.repositionTables();
|
|
17331
17566
|
this.paneControls?.refresh();
|
|
17332
17567
|
this.scheduler.invalidate(4 /* Full */);
|
|
17333
17568
|
}
|
|
17334
17569
|
orderPanes(orderedIds) {
|
|
17335
17570
|
this.scene.orderPanes(orderedIds);
|
|
17336
17571
|
this.layoutPanes();
|
|
17337
|
-
this.repositionTables();
|
|
17338
17572
|
this.paneControls?.refresh();
|
|
17339
17573
|
this.scheduler.invalidate(4 /* Full */);
|
|
17340
17574
|
}
|
|
@@ -17343,7 +17577,6 @@ var NativeRenderer = class {
|
|
|
17343
17577
|
if (!pane || pane.collapsed === collapsed) return;
|
|
17344
17578
|
pane.collapsed = collapsed;
|
|
17345
17579
|
this.layoutPanes();
|
|
17346
|
-
this.repositionTables();
|
|
17347
17580
|
this.paneControls?.refresh();
|
|
17348
17581
|
this.scheduler.invalidate(4 /* Full */);
|
|
17349
17582
|
}
|
|
@@ -17351,7 +17584,6 @@ var NativeRenderer = class {
|
|
|
17351
17584
|
if (paneId !== null && !this.scene.panes.has(paneId)) paneId = null;
|
|
17352
17585
|
this.maximizedPaneId = paneId;
|
|
17353
17586
|
this.layoutPanes();
|
|
17354
|
-
this.repositionTables();
|
|
17355
17587
|
this.paneControls?.refresh();
|
|
17356
17588
|
this.scheduler.invalidate(4 /* Full */);
|
|
17357
17589
|
}
|
|
@@ -17438,7 +17670,6 @@ var NativeRenderer = class {
|
|
|
17438
17670
|
native: !!model.native,
|
|
17439
17671
|
...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
|
|
17440
17672
|
});
|
|
17441
|
-
this.syncTables(model);
|
|
17442
17673
|
if (model.native?.type === "volume") {
|
|
17443
17674
|
this.volumeActive = true;
|
|
17444
17675
|
this.volumeHidden = false;
|
|
@@ -17462,7 +17693,6 @@ var NativeRenderer = class {
|
|
|
17462
17693
|
}
|
|
17463
17694
|
}
|
|
17464
17695
|
applyPatch(model, patch);
|
|
17465
|
-
this.syncTables(model);
|
|
17466
17696
|
this.scheduler.invalidate(3 /* Light */);
|
|
17467
17697
|
}
|
|
17468
17698
|
removeIndicator(handle) {
|
|
@@ -17481,8 +17711,6 @@ var NativeRenderer = class {
|
|
|
17481
17711
|
this.scene.forgetAnchorOffset(handle.id);
|
|
17482
17712
|
this.scene.dropIndicatorScale(handle.id);
|
|
17483
17713
|
this.inputsUI.remove(handle.id);
|
|
17484
|
-
this.tableOverlays.get(handle.id)?.destroy();
|
|
17485
|
-
this.tableOverlays.delete(handle.id);
|
|
17486
17714
|
this.refreshAxisWidth();
|
|
17487
17715
|
this.paneControls?.refresh();
|
|
17488
17716
|
this.scheduler.invalidate(4 /* Full */);
|
|
@@ -17526,8 +17754,6 @@ var NativeRenderer = class {
|
|
|
17526
17754
|
}
|
|
17527
17755
|
if (!visible) {
|
|
17528
17756
|
this.scene.indicators.delete(handle.id);
|
|
17529
|
-
this.tableOverlays.get(handle.id)?.destroy();
|
|
17530
|
-
this.tableOverlays.delete(handle.id);
|
|
17531
17757
|
}
|
|
17532
17758
|
this.inputsUI.setVisible(handle.id, visible);
|
|
17533
17759
|
this.scheduler.invalidate(4 /* Full */);
|
|
@@ -17573,6 +17799,7 @@ var NativeRenderer = class {
|
|
|
17573
17799
|
this.userDrawings?.setLayoutMode(mode);
|
|
17574
17800
|
this.settingsDialog?.setLayoutMode(mode);
|
|
17575
17801
|
this.inputsUI?.setLayoutMode(mode);
|
|
17802
|
+
this.paneControls?.setSuspended(mode === "mobile");
|
|
17576
17803
|
if (this.scrollButton) {
|
|
17577
17804
|
const px = mode === "mobile" ? SCROLL_BTN_SIZE_TOUCH : SCROLL_BTN_SIZE;
|
|
17578
17805
|
this.scrollButton.style.width = `${px}px`;
|
|
@@ -17968,7 +18195,6 @@ var NativeRenderer = class {
|
|
|
17968
18195
|
/** Relayout + repaint + refresh the hover buttons after a collapse/maximize/order change. */
|
|
17969
18196
|
afterPaneLayoutChange() {
|
|
17970
18197
|
this.layoutPanes();
|
|
17971
|
-
this.repositionTables();
|
|
17972
18198
|
this.paneControls?.refresh();
|
|
17973
18199
|
this.scheduler.invalidate(4 /* Full */);
|
|
17974
18200
|
}
|
|
@@ -18049,7 +18275,6 @@ var NativeRenderer = class {
|
|
|
18049
18275
|
above.heightWeight = next.above;
|
|
18050
18276
|
below.heightWeight = next.below;
|
|
18051
18277
|
this.layoutPanes();
|
|
18052
|
-
this.repositionTables();
|
|
18053
18278
|
this.scheduler.invalidate(4 /* Full */);
|
|
18054
18279
|
}
|
|
18055
18280
|
/** Double-click a separator → split the two adjacent panes evenly (each gets half of
|
|
@@ -18064,7 +18289,6 @@ var NativeRenderer = class {
|
|
|
18064
18289
|
above.heightWeight = half;
|
|
18065
18290
|
below.heightWeight = half;
|
|
18066
18291
|
this.layoutPanes();
|
|
18067
|
-
this.repositionTables();
|
|
18068
18292
|
this.scheduler.invalidate(4 /* Full */);
|
|
18069
18293
|
}
|
|
18070
18294
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -18343,7 +18567,10 @@ var NativeRenderer = class {
|
|
|
18343
18567
|
const liveActual = li >= 0 ? this.bars[li] : void 0;
|
|
18344
18568
|
const easeLive = !!liveActual && this.liveEaseTime === liveActual.time && (liveActual.high !== this.liveEaseHigh || liveActual.low !== this.liveEaseLow || liveActual.close !== this.liveEaseClose);
|
|
18345
18569
|
if (easeLive && liveActual) this.bars[li] = { ...liveActual, high: this.liveEaseHigh, low: this.liveEaseLow, close: this.liveEaseClose };
|
|
18346
|
-
this.scene.drawingSlices =
|
|
18570
|
+
this.scene.drawingSlices = mergeSlices(
|
|
18571
|
+
this.indicatorSlices.prepare(this.scene, this.coords, this.theme, this.dataCanvas),
|
|
18572
|
+
this.userDrawings?.prepareSlices(this.scene.orderedPanes().map((p) => p.id)) ?? /* @__PURE__ */ new Map()
|
|
18573
|
+
);
|
|
18347
18574
|
this.backdropRenderer.render(this.scene, this.coords, this.theme, gridAlpha);
|
|
18348
18575
|
this.backend.render(this.scene, this.coords, this.theme);
|
|
18349
18576
|
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
@@ -18781,27 +19008,6 @@ var NativeRenderer = class {
|
|
|
18781
19008
|
}
|
|
18782
19009
|
return maxVol;
|
|
18783
19010
|
}
|
|
18784
|
-
/** Create/update/destroy an indicator's DOM table overlay (anchored off real pane geometry). */
|
|
18785
|
-
syncTables(model) {
|
|
18786
|
-
const tables = model.tables ?? [];
|
|
18787
|
-
let overlay = this.tableOverlays.get(model.id);
|
|
18788
|
-
if (tables.length === 0) {
|
|
18789
|
-
if (overlay) {
|
|
18790
|
-
overlay.destroy();
|
|
18791
|
-
this.tableOverlays.delete(model.id);
|
|
18792
|
-
}
|
|
18793
|
-
return;
|
|
18794
|
-
}
|
|
18795
|
-
if (!overlay) {
|
|
18796
|
-
overlay = new TableOverlay(this.plot, this.theme, (id) => this.paneBoundsFor(id));
|
|
18797
|
-
overlay.setVisible(this.loadingEl === null);
|
|
18798
|
-
this.tableOverlays.set(model.id, overlay);
|
|
18799
|
-
}
|
|
18800
|
-
overlay.update(tables);
|
|
18801
|
-
}
|
|
18802
|
-
repositionTables() {
|
|
18803
|
-
for (const overlay of this.tableOverlays.values()) overlay.reposition();
|
|
18804
|
-
}
|
|
18805
19011
|
layoutPanes() {
|
|
18806
19012
|
const panes = this.scene.orderedPanes();
|
|
18807
19013
|
const dataHeight = this.coords.height;
|
|
@@ -18852,6 +19058,7 @@ var NativeRenderer = class {
|
|
|
18852
19058
|
const visible = maxPane ? [maxPane] : this.scene.orderedPanes().filter((p) => !p.collapsed);
|
|
18853
19059
|
const paneBottom = visible.length ? Math.max(...visible.map((p) => p.bounds.top + p.bounds.height)) : dataHeight;
|
|
18854
19060
|
this.scrollBtnBottomPx = SCROLL_BTN_BOTTOM + Math.max(0, dataHeight - paneBottom);
|
|
19061
|
+
this.mountContainer?.style.setProperty("--vela-bottom-gutter", `${TIME_AXIS_H + Math.max(0, dataHeight - paneBottom)}px`);
|
|
18855
19062
|
this.scrollBtnRightPx = this.rightAxisW + SCROLL_BTN_RIGHT_INSET;
|
|
18856
19063
|
if (this.scrollButton) {
|
|
18857
19064
|
this.scrollButton.style.bottom = `${this.scrollBtnBottomPx}px`;
|
|
@@ -18944,30 +19151,33 @@ var NativeRenderer = class {
|
|
|
18944
19151
|
if (w <= 0 || h <= 0) return;
|
|
18945
19152
|
const dpr = window.devicePixelRatio || 1;
|
|
18946
19153
|
this.plot.style.left = `${this.toolbarGutter}px`;
|
|
18947
|
-
const
|
|
18948
|
-
|
|
18949
|
-
|
|
18950
|
-
|
|
18951
|
-
|
|
18952
|
-
|
|
18953
|
-
|
|
18954
|
-
|
|
18955
|
-
|
|
18956
|
-
|
|
18957
|
-
|
|
18958
|
-
|
|
18959
|
-
|
|
18960
|
-
|
|
18961
|
-
|
|
18962
|
-
|
|
18963
|
-
this.
|
|
18964
|
-
this.
|
|
18965
|
-
this.
|
|
18966
|
-
|
|
19154
|
+
const rect = this.plot.getBoundingClientRect();
|
|
19155
|
+
let bw = Math.max(1, Math.round(rect.width * dpr));
|
|
19156
|
+
let bh = Math.max(1, Math.round(rect.height * dpr));
|
|
19157
|
+
const dev = this.plotDeviceSize;
|
|
19158
|
+
if (dev && Math.abs(dev.width - rect.width * dpr) <= 1 && Math.abs(dev.height - rect.height * dpr) <= 1) {
|
|
19159
|
+
bw = Math.max(1, dev.width);
|
|
19160
|
+
bh = Math.max(1, dev.height);
|
|
19161
|
+
}
|
|
19162
|
+
const pw = bw / dpr;
|
|
19163
|
+
const ph = bh / dpr;
|
|
19164
|
+
const size = (canvas) => {
|
|
19165
|
+
canvas.width = bw;
|
|
19166
|
+
canvas.height = bh;
|
|
19167
|
+
canvas.style.width = `${pw}px`;
|
|
19168
|
+
canvas.style.height = `${ph}px`;
|
|
19169
|
+
};
|
|
19170
|
+
size(this.dataCanvas);
|
|
19171
|
+
size(this.backdropCanvas);
|
|
19172
|
+
size(this.volumeCanvas);
|
|
19173
|
+
for (const l of this.extLayers) size(l.canvas);
|
|
19174
|
+
size(this.vpvrCanvas);
|
|
19175
|
+
size(this.chromeCanvas);
|
|
19176
|
+
size(this.drawingsCanvas);
|
|
19177
|
+
size(this.cursorCanvas);
|
|
18967
19178
|
this.coords.setSize(Math.max(1, pw - this.rightAxisW), Math.max(1, ph - TIME_AXIS_H), dpr);
|
|
18968
19179
|
this.scene.crosshair = null;
|
|
18969
19180
|
this.layoutPanes();
|
|
18970
|
-
this.repositionTables();
|
|
18971
19181
|
this.userDrawings?.onResize();
|
|
18972
19182
|
if (!this.didInitialFit && this.coords.barCount > 0) {
|
|
18973
19183
|
this.fitContent();
|
|
@@ -19359,6 +19569,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19359
19569
|
/** Invalidates detached async work (backfill loops, in-flight loads, gap heals):
|
|
19360
19570
|
* bumped by init(), setMarket() and destroy(). */
|
|
19361
19571
|
this.generation = 0;
|
|
19572
|
+
/** Aborts the in-flight PROGRESSIVE load's source polling on supersession — an
|
|
19573
|
+
* abandoned stream left polling to its own budget starves the browser's per-host
|
|
19574
|
+
* connection pool, and the NEXT symbol's very first fetch with it (measured). */
|
|
19575
|
+
this.progressiveAbort = null;
|
|
19362
19576
|
/** Awaiters racing a superseded load (setMarket callers) — released on every bump so they never hang. */
|
|
19363
19577
|
this.supersedeWaiters = [];
|
|
19364
19578
|
/** `history:complete` fired for the CURRENT load. Each market load re-arms the cycle
|
|
@@ -19501,6 +19715,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19501
19715
|
* superseded setMarket awaiters so their promises resolve instead of hanging. */
|
|
19502
19716
|
bumpGeneration() {
|
|
19503
19717
|
const gen = ++this.generation;
|
|
19718
|
+
this.progressiveAbort?.abort();
|
|
19719
|
+
this.progressiveAbort = null;
|
|
19504
19720
|
for (const w of this.supersedeWaiters.splice(0)) w();
|
|
19505
19721
|
return gen;
|
|
19506
19722
|
}
|
|
@@ -19554,7 +19770,47 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19554
19770
|
const requested = market.bars ?? 500;
|
|
19555
19771
|
const initialRange = market.visibleRange;
|
|
19556
19772
|
const deep = !market.data?.length && initialRange == null && requested > SINGLE_LOAD_BARS;
|
|
19557
|
-
|
|
19773
|
+
let progressiveServed = false;
|
|
19774
|
+
if (!market.data?.length && initialRange == null && this.feed.loadProgressive) {
|
|
19775
|
+
let painted = false;
|
|
19776
|
+
const paint = (bars, final) => {
|
|
19777
|
+
if (this.generation !== gen || !final && bars.length === 0) return;
|
|
19778
|
+
this.setBarSeries(bars, painted ? { preserveView: true } : void 0);
|
|
19779
|
+
if (!painted && bars.length > 0) {
|
|
19780
|
+
painted = true;
|
|
19781
|
+
if (opts.firstLoad) this.activateBarLayers();
|
|
19782
|
+
if (!final) this.historyState = "backfill";
|
|
19783
|
+
}
|
|
19784
|
+
};
|
|
19785
|
+
const abort = new AbortController();
|
|
19786
|
+
this.progressiveAbort = abort;
|
|
19787
|
+
progressiveServed = await new Promise((firstPaint) => {
|
|
19788
|
+
let signaled = false;
|
|
19789
|
+
const signal = (served) => {
|
|
19790
|
+
if (!signaled) {
|
|
19791
|
+
signaled = true;
|
|
19792
|
+
firstPaint(served);
|
|
19793
|
+
}
|
|
19794
|
+
};
|
|
19795
|
+
abort.signal.addEventListener("abort", () => signal(true), { once: true });
|
|
19796
|
+
this.feed.loadProgressive(market, (bars) => {
|
|
19797
|
+
paint(bars, false);
|
|
19798
|
+
if (painted) signal(true);
|
|
19799
|
+
}, { signal: abort.signal }).then((full) => {
|
|
19800
|
+
if (this.progressiveAbort === abort) this.progressiveAbort = null;
|
|
19801
|
+
if (full == null) return signal(false);
|
|
19802
|
+
if (this.generation !== gen) return signal(true);
|
|
19803
|
+
paint(full, true);
|
|
19804
|
+
this.completeHistory(full.length >= requested ? "depth" : "genesis");
|
|
19805
|
+
signal(true);
|
|
19806
|
+
}).catch(() => {
|
|
19807
|
+
if (this.progressiveAbort === abort) this.progressiveAbort = null;
|
|
19808
|
+
if (this.generation === gen) this.completeHistory("aborted");
|
|
19809
|
+
signal(true);
|
|
19810
|
+
});
|
|
19811
|
+
});
|
|
19812
|
+
}
|
|
19813
|
+
if (progressiveServed) ; else if (deep && this.feed.loadRange) {
|
|
19558
19814
|
const head = await this.feed.load({ ...market, bars: Math.min(requested, CHUNK_BARS) });
|
|
19559
19815
|
if (this.generation !== gen) return;
|
|
19560
19816
|
this.setBarSeries(head);
|
|
@@ -21374,4 +21630,4 @@ function resolveElement(container) {
|
|
|
21374
21630
|
return element;
|
|
21375
21631
|
}
|
|
21376
21632
|
|
|
21377
|
-
export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MultiProviderFeed, NativeRenderer, RendererControl, TIMEZONES, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultToolbar, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, sharedBarStore, timeframeToMs, tzButtonLabel, tzMenuLabel, tzOffset };
|
|
21633
|
+
export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MultiProviderFeed, NativeRenderer, RendererControl, TIMEZONES, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultMemberOf, defaultToolbar, groupKeyOf, groupMembers, isGroupRow, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, sharedBarStore, timeframeToMs, tzButtonLabel, tzMenuLabel, tzOffset };
|