acttrader-charts 1.0.21 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/{MACD-DGFPNwy9.d.cts → MACD-Dqi682ei.d.cts} +81 -5
- package/dist/{MACD-DGFPNwy9.d.ts → MACD-Dqi682ei.d.ts} +81 -5
- package/dist/index.cjs +1752 -556
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +180 -75
- package/dist/index.d.ts +180 -75
- package/dist/index.js +1750 -554
- package/dist/index.js.map +1 -1
- package/dist/indicators/index.d.cts +2 -2
- package/dist/indicators/index.d.ts +2 -2
- package/dist/webview/chart.html +67 -67
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -186,9 +186,20 @@ function deepMergeTheme(base, override) {
|
|
|
186
186
|
bottomBar: { ...base.bottomBar, ...override.bottomBar },
|
|
187
187
|
indicatorOverlay: { ...base.indicatorOverlay, ...override.indicatorOverlay },
|
|
188
188
|
tradeLevels: { ...base.tradeLevels, ...override.tradeLevels },
|
|
189
|
-
tradePanel: { ...base.tradePanel, ...override.tradePanel }
|
|
189
|
+
tradePanel: { ...base.tradePanel, ...override.tradePanel },
|
|
190
|
+
drawing: mergeDrawingTheme(base.drawing, override.drawing)
|
|
190
191
|
};
|
|
191
192
|
}
|
|
193
|
+
function mergeDrawingTheme(base, override) {
|
|
194
|
+
if (!override) return base;
|
|
195
|
+
const result = { ...base };
|
|
196
|
+
for (const key of Object.keys(override)) {
|
|
197
|
+
const o = override[key];
|
|
198
|
+
if (!o) continue;
|
|
199
|
+
result[key] = { ...base[key], ...o };
|
|
200
|
+
}
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
192
203
|
function resolveCssVarsInTheme(theme, container) {
|
|
193
204
|
const tmp = document.createElement("div");
|
|
194
205
|
tmp.style.display = "none";
|
|
@@ -287,11 +298,157 @@ function resolveCssVarsInTheme(theme, container) {
|
|
|
287
298
|
rowHoverBg: resolve(theme.tradePanel.rowHoverBg),
|
|
288
299
|
rowText: resolve(theme.tradePanel.rowText),
|
|
289
300
|
rowSubText: resolve(theme.tradePanel.rowSubText)
|
|
290
|
-
}
|
|
301
|
+
},
|
|
302
|
+
drawing: resolveDrawingThemeVars(theme.drawing, resolve)
|
|
291
303
|
};
|
|
292
304
|
container.removeChild(tmp);
|
|
293
305
|
return result;
|
|
294
306
|
}
|
|
307
|
+
function resolveDrawingThemeVars(drawing, resolve) {
|
|
308
|
+
const out = {};
|
|
309
|
+
for (const key of Object.keys(drawing)) {
|
|
310
|
+
const entry = drawing[key];
|
|
311
|
+
const resolved = { ...entry };
|
|
312
|
+
if (resolved.strokeColor) resolved.strokeColor = resolve(resolved.strokeColor);
|
|
313
|
+
if (resolved.fillColor) resolved.fillColor = resolve(resolved.fillColor);
|
|
314
|
+
if ("textColor" in resolved && resolved.textColor) {
|
|
315
|
+
resolved.textColor = resolve(resolved.textColor);
|
|
316
|
+
}
|
|
317
|
+
if ("levels" in resolved && resolved.levels) {
|
|
318
|
+
const levels = {};
|
|
319
|
+
for (const lk of Object.keys(resolved.levels)) {
|
|
320
|
+
const lv = resolved.levels[lk];
|
|
321
|
+
levels[lk] = { ...lv, color: resolve(lv.color) };
|
|
322
|
+
}
|
|
323
|
+
resolved.levels = levels;
|
|
324
|
+
}
|
|
325
|
+
out[key] = resolved;
|
|
326
|
+
}
|
|
327
|
+
return out;
|
|
328
|
+
}
|
|
329
|
+
var DEFAULT_DRAWING_THEME = {
|
|
330
|
+
// ── Lines ────────────────────────────────────────────────────────────────
|
|
331
|
+
horizontalLine: { strokeColor: "#e8b84b", lineWidth: 1, lineStyle: "dashed" },
|
|
332
|
+
verticalLine: { strokeColor: "#e8b84b", lineWidth: 1, lineStyle: "dashed" },
|
|
333
|
+
trendLine: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
334
|
+
crossLine: { strokeColor: "#78909c", lineWidth: 1, lineStyle: "dashed" },
|
|
335
|
+
trendAngle: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
336
|
+
// ── Channels ────────────────────────────────────────────────────────────
|
|
337
|
+
flatChannel: { strokeColor: "#ab47bc", lineWidth: 1, lineStyle: "solid", fillColor: "#ab47bc", fillOpacity: 0.08 },
|
|
338
|
+
parallelChannel: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid", fillColor: "#26c6da", fillOpacity: 0.08 },
|
|
339
|
+
disjointChannel: { strokeColor: "#ef9a9a", lineWidth: 1, lineStyle: "solid" },
|
|
340
|
+
// ── Pitchfork family ────────────────────────────────────────────────────
|
|
341
|
+
pitchfork: { strokeColor: "#ffa726", lineWidth: 1, lineStyle: "solid" },
|
|
342
|
+
schiffPitchfork: { strokeColor: "#ff7043", lineWidth: 1, lineStyle: "solid" },
|
|
343
|
+
modifiedSchiff: { strokeColor: "#ef5350", lineWidth: 1, lineStyle: "solid" },
|
|
344
|
+
insidePitchfork: { strokeColor: "#ab47bc", lineWidth: 1, lineStyle: "solid" },
|
|
345
|
+
// ── Fib family (multi-level) ────────────────────────────────────────────
|
|
346
|
+
fibRetracement: {
|
|
347
|
+
lineWidth: 1,
|
|
348
|
+
lineStyle: "dashed",
|
|
349
|
+
levels: {
|
|
350
|
+
"0": { visible: true, color: "#ef5350" },
|
|
351
|
+
"0.236": { visible: true, color: "#e8b84b" },
|
|
352
|
+
"0.382": { visible: true, color: "#26a69a" },
|
|
353
|
+
"0.5": { visible: true, color: "#2196f3" },
|
|
354
|
+
"0.618": { visible: true, color: "#26a69a" },
|
|
355
|
+
"0.786": { visible: true, color: "#e8b84b" },
|
|
356
|
+
"1": { visible: true, color: "#ef5350" }
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
fibExtension: {
|
|
360
|
+
lineWidth: 1,
|
|
361
|
+
lineStyle: "dashed",
|
|
362
|
+
levels: {
|
|
363
|
+
"0": { visible: true, color: "#888888" },
|
|
364
|
+
"0.618": { visible: true, color: "#26a69a" },
|
|
365
|
+
"1": { visible: true, color: "#888888" },
|
|
366
|
+
"1.272": { visible: true, color: "#2196f3" },
|
|
367
|
+
"1.618": { visible: true, color: "#e8b84b" },
|
|
368
|
+
"2": { visible: true, color: "#ef5350" },
|
|
369
|
+
"2.618": { visible: true, color: "#ef5350" }
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
fibProjection: {
|
|
373
|
+
lineWidth: 1,
|
|
374
|
+
lineStyle: "dashed",
|
|
375
|
+
levels: {
|
|
376
|
+
"0": { visible: true, color: "#ef5350" },
|
|
377
|
+
"0.618": { visible: true, color: "#26a69a" },
|
|
378
|
+
"1": { visible: true, color: "#2196f3" },
|
|
379
|
+
"1.272": { visible: true, color: "#26a69a" },
|
|
380
|
+
"1.618": { visible: true, color: "#e8b84b" },
|
|
381
|
+
"2": { visible: true, color: "#ef5350" },
|
|
382
|
+
"2.618": { visible: true, color: "#ef5350" }
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
fibChannel: { strokeColor: "#888888", lineWidth: 1, lineStyle: "dashed" },
|
|
386
|
+
fibFan: { strokeColor: "#888888", lineWidth: 1, lineStyle: "dashed" },
|
|
387
|
+
fibCircles: { strokeColor: "#e8b84b", lineWidth: 1, lineStyle: "dashed" },
|
|
388
|
+
fibSpiral: { strokeColor: "#e8b84b", lineWidth: 1, lineStyle: "solid" },
|
|
389
|
+
fibTimezone: { strokeColor: "#e8b84b", lineWidth: 1, lineStyle: "dashed" },
|
|
390
|
+
// ── Geometric shapes ────────────────────────────────────────────────────
|
|
391
|
+
rectangle: { strokeColor: "#9b59b6", lineWidth: 1, lineStyle: "solid", fillColor: "#9b59b6", fillOpacity: 0.09 },
|
|
392
|
+
rotatedRectangle: { strokeColor: "#9c27b0", lineWidth: 1, lineStyle: "solid", fillColor: "#9c27b0", fillOpacity: 0.09 },
|
|
393
|
+
ellipse: { strokeColor: "#2196f3", lineWidth: 1, lineStyle: "solid", fillColor: "#2196f3", fillOpacity: 0.08 },
|
|
394
|
+
circle: { strokeColor: "#4caf50", lineWidth: 1, lineStyle: "solid", fillColor: "#4caf50", fillOpacity: 0.08 },
|
|
395
|
+
triangle: { strokeColor: "#ff9800", lineWidth: 1, lineStyle: "solid", fillColor: "#ff9800", fillOpacity: 0.08 },
|
|
396
|
+
arc: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
397
|
+
sineLine: { strokeColor: "#ce93d8", lineWidth: 1, lineStyle: "solid" },
|
|
398
|
+
path: { strokeColor: "#ec407a", lineWidth: 1, lineStyle: "solid" },
|
|
399
|
+
polyline: { strokeColor: "#ff7043", lineWidth: 1, lineStyle: "solid" },
|
|
400
|
+
// ── Brush / Highlighter ─────────────────────────────────────────────────
|
|
401
|
+
brush: { strokeColor: "#ff5722", lineWidth: 2, lineStyle: "solid" },
|
|
402
|
+
highlighter: { strokeColor: "#ffeb3b", lineWidth: 8, lineStyle: "solid", fillOpacity: 0.4 },
|
|
403
|
+
// ── Text-bearing ────────────────────────────────────────────────────────
|
|
404
|
+
text: { textColor: "#f5f5f5", fontSize: 13, strokeColor: "#2196f3" },
|
|
405
|
+
callout: { textColor: "#f5f5f5", fontSize: 13, strokeColor: "#546e7a", fillColor: "#546e7a", fillOpacity: 0.18 },
|
|
406
|
+
priceLabel: { textColor: "#ffffff", fontSize: 11, strokeColor: "#26a69a", fillColor: "#26a69a", fillOpacity: 1 },
|
|
407
|
+
priceNote: { textColor: "#f5f5f5", fontSize: 12, strokeColor: "#ffa726" },
|
|
408
|
+
anchoredNote: { textColor: "#f5f5f5", fontSize: 12, strokeColor: "#42a5f5" },
|
|
409
|
+
// ── Arrows / Markers ────────────────────────────────────────────────────
|
|
410
|
+
arrowUp: { strokeColor: "#26a69a", lineWidth: 1.5, fillColor: "#26a69a", fillOpacity: 1 },
|
|
411
|
+
arrowDown: { strokeColor: "#ef5350", lineWidth: 1.5, fillColor: "#ef5350", fillOpacity: 1 },
|
|
412
|
+
arrowMarker: { strokeColor: "#ef5350", lineWidth: 1.5, fillColor: "#ef5350", fillOpacity: 1 },
|
|
413
|
+
flag: { strokeColor: "#ef5350", lineWidth: 1, fillColor: "#ef5350", fillOpacity: 0.85 },
|
|
414
|
+
// ── Volume profile ──────────────────────────────────────────────────────
|
|
415
|
+
volumeProfile: { strokeColor: "#26c6da", lineWidth: 1, fillColor: "#26c6da", fillOpacity: 0.35 },
|
|
416
|
+
anchoredVP: { strokeColor: "#42a5f5", lineWidth: 1, fillColor: "#42a5f5", fillOpacity: 0.35 },
|
|
417
|
+
fixedRangeVP: { strokeColor: "#7e57c2", lineWidth: 1, fillColor: "#7e57c2", fillOpacity: 0.35 },
|
|
418
|
+
// ── Harmonic patterns ───────────────────────────────────────────────────
|
|
419
|
+
abcdPattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
420
|
+
gartleyPattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
421
|
+
batPattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
422
|
+
butterflyPattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
423
|
+
crabPattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
424
|
+
sharkPattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
425
|
+
cypherPattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
426
|
+
headShoulders: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
427
|
+
trianglePattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
428
|
+
threeDrives: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
429
|
+
// ── Elliott waves ───────────────────────────────────────────────────────
|
|
430
|
+
elliottImpulse: { strokeColor: "#42a5f5", lineWidth: 1, lineStyle: "solid" },
|
|
431
|
+
elliottCorrective: { strokeColor: "#42a5f5", lineWidth: 1, lineStyle: "solid" },
|
|
432
|
+
elliottTriangle: { strokeColor: "#42a5f5", lineWidth: 1, lineStyle: "solid" },
|
|
433
|
+
elliottWxy: { strokeColor: "#42a5f5", lineWidth: 1, lineStyle: "solid" },
|
|
434
|
+
elliottCombination: { strokeColor: "#42a5f5", lineWidth: 1, lineStyle: "solid" },
|
|
435
|
+
// ── Gann ────────────────────────────────────────────────────────────────
|
|
436
|
+
gannFan: { strokeColor: "#ffa726", lineWidth: 1, lineStyle: "solid" },
|
|
437
|
+
gannSquare: { strokeColor: "#ab47bc", lineWidth: 1, lineStyle: "solid" },
|
|
438
|
+
gannBox: { strokeColor: "#7e57c2", lineWidth: 1, lineStyle: "solid" },
|
|
439
|
+
// ── Measurement ─────────────────────────────────────────────────────────
|
|
440
|
+
priceRange: { strokeColor: "#66bb6a", lineWidth: 1, fillColor: "#66bb6a", fillOpacity: 0.07 },
|
|
441
|
+
dateRange: { strokeColor: "#66bb6a", lineWidth: 1, fillColor: "#66bb6a", fillOpacity: 0.07 },
|
|
442
|
+
datePriceRange: { strokeColor: "#66bb6a", lineWidth: 1, fillColor: "#66bb6a", fillOpacity: 0.07 },
|
|
443
|
+
ruler: { strokeColor: "#90a4ae", lineWidth: 1, lineStyle: "dashed" },
|
|
444
|
+
// ── Advanced ────────────────────────────────────────────────────────────
|
|
445
|
+
regressionTrend: { strokeColor: "#42a5f5", lineWidth: 1, lineStyle: "solid" },
|
|
446
|
+
projection: { strokeColor: "#9575cd", lineWidth: 1, lineStyle: "dashed" },
|
|
447
|
+
cyclicLines: { strokeColor: "#80cbc4", lineWidth: 1, lineStyle: "dashed" },
|
|
448
|
+
timeCycles: { strokeColor: "#80cbc4", lineWidth: 1, lineStyle: "dashed", fillColor: "#80cbc4", fillOpacity: 0.06 },
|
|
449
|
+
priceProjection: { strokeColor: "#ef9a9a", lineWidth: 1, lineStyle: "dashed", fillColor: "#ef9a9a", fillOpacity: 0.09 },
|
|
450
|
+
ghostFeed: { strokeColor: "#9e9e9e", lineWidth: 1, lineStyle: "dotted" }
|
|
451
|
+
};
|
|
295
452
|
var DARK_THEME = {
|
|
296
453
|
background: "#141d22",
|
|
297
454
|
grid: "#243344",
|
|
@@ -365,7 +522,8 @@ var DARK_THEME = {
|
|
|
365
522
|
rowHoverBg: "rgba(255,255,255,0.04)",
|
|
366
523
|
rowText: "#e8eef2",
|
|
367
524
|
rowSubText: "#9ca3af"
|
|
368
|
-
}
|
|
525
|
+
},
|
|
526
|
+
drawing: DEFAULT_DRAWING_THEME
|
|
369
527
|
};
|
|
370
528
|
var LIGHT_THEME = {
|
|
371
529
|
background: "#ffffff",
|
|
@@ -439,7 +597,8 @@ var LIGHT_THEME = {
|
|
|
439
597
|
rowHoverBg: "rgba(0,0,0,0.03)",
|
|
440
598
|
rowText: "#333333",
|
|
441
599
|
rowSubText: "#666666"
|
|
442
|
-
}
|
|
600
|
+
},
|
|
601
|
+
drawing: DEFAULT_DRAWING_THEME
|
|
443
602
|
};
|
|
444
603
|
|
|
445
604
|
// src/core/uiConfig.ts
|
|
@@ -1751,7 +1910,7 @@ function cascadeBracketAwayFromEntry(entry, preferredBoxY, boxH, direction, same
|
|
|
1751
1910
|
}
|
|
1752
1911
|
return boxY;
|
|
1753
1912
|
}
|
|
1754
|
-
function getLevelDisplayText(level, price, precision) {
|
|
1913
|
+
function getLevelDisplayText(level, price, precision, omitLots = false) {
|
|
1755
1914
|
if (level.data?._isDraft) {
|
|
1756
1915
|
const p = level;
|
|
1757
1916
|
const sideLabel = p.side.charAt(0).toUpperCase() + p.side.slice(1);
|
|
@@ -1760,13 +1919,13 @@ function getLevelDisplayText(level, price, precision) {
|
|
|
1760
1919
|
}
|
|
1761
1920
|
if (level.type === "pending") {
|
|
1762
1921
|
const p = level;
|
|
1763
|
-
const lotsStr = p.lots !== void 0 ? ` ${p.lots}` : "";
|
|
1922
|
+
const lotsStr = !omitLots && p.lots !== void 0 ? ` ${p.lots}` : "";
|
|
1764
1923
|
return level.text ?? `${p.side.toUpperCase()}${lotsStr} @${price.toFixed(precision)}`;
|
|
1765
1924
|
}
|
|
1766
1925
|
if (level.type === "position" && level.entryPriceEditable) {
|
|
1767
1926
|
const pos = level;
|
|
1768
1927
|
const sideStr = pos.side ? pos.side.toUpperCase() : "";
|
|
1769
|
-
const lotsStr = pos.lots !== void 0 ? ` ${pos.lots}` : "";
|
|
1928
|
+
const lotsStr = !omitLots && pos.lots !== void 0 ? ` ${pos.lots}` : "";
|
|
1770
1929
|
return level.text ?? `${sideStr}${lotsStr} @${price.toFixed(precision)}`;
|
|
1771
1930
|
}
|
|
1772
1931
|
if (level.type === "position") return level.text ?? "";
|
|
@@ -1871,7 +2030,7 @@ function isDraggable(level) {
|
|
|
1871
2030
|
return !!level.entryPriceEditable;
|
|
1872
2031
|
return false;
|
|
1873
2032
|
}
|
|
1874
|
-
function renderTradeLevels(ctx, levels, scale, priceRange, theme, chartW, priceH, precision, hoveredLabel = null, draggingLabel = null, dragNewPrice = null, selectedLabel = null, pendingLabels = /* @__PURE__ */ new Set(), positionStyle = "line", hideConfirmCancel = false, draftOrderLabel = null, draftBracketPnl = {}, expandedClusters = /* @__PURE__ */ new Set(), clusterThresholdPx = 0, showQuantityField = false, dragNewSLPrice = null, dragNewTPPrice = null, buttonScale = 1) {
|
|
2033
|
+
function renderTradeLevels(ctx, levels, scale, priceRange, theme, chartW, priceH, precision, hoveredLabel = null, draggingLabel = null, dragNewPrice = null, selectedLabel = null, pendingLabels = /* @__PURE__ */ new Set(), positionStyle = "line", hideConfirmCancel = false, draftOrderLabel = null, draftBracketPnl = {}, expandedClusters = /* @__PURE__ */ new Set(), clusterThresholdPx = 0, showQuantityField = false, dragNewSLPrice = null, dragNewTPPrice = null, buttonScale = 1, alwaysShowBrackets = false) {
|
|
1875
2034
|
if (levels.length === 0)
|
|
1876
2035
|
return {
|
|
1877
2036
|
hitAreas: [],
|
|
@@ -1911,9 +2070,7 @@ function renderTradeLevels(ctx, levels, scale, priceRange, theme, chartW, priceH
|
|
|
1911
2070
|
}
|
|
1912
2071
|
const pnlText = hasPnl(level) ? level.pnlText : null;
|
|
1913
2072
|
const hasPending = pendingLabels.has(level.label);
|
|
1914
|
-
const displayText = getLevelDisplayText(level, price, precision);
|
|
1915
2073
|
const pnlW = pnlText ? ctx.measureText(pnlText).width + PNL_GAP : 0;
|
|
1916
|
-
const labelW = ctx.measureText(displayText).width;
|
|
1917
2074
|
const effectivePending = hasPending && !hideConfirmCancel;
|
|
1918
2075
|
const confirmW = effectivePending ? (confirmR * 2 + confirmGap) * 2 : 0;
|
|
1919
2076
|
const closeW = effectivePending || hideConfirmCancel ? 0 : editR * 2 + editGap + closeR * 2 + BOX_PAD_X / 2;
|
|
@@ -1923,6 +2080,8 @@ function renderTradeLevels(ctx, levels, scale, priceRange, theme, chartW, priceH
|
|
|
1923
2080
|
const supportsQtyField = level.type === "pending" || isEntryOrderPosition;
|
|
1924
2081
|
const levelLots = level.lots;
|
|
1925
2082
|
const qtyPillText = showQuantityField && (isDraftLevel || isEditingLevel) && supportsQtyField && levelLots !== void 0 ? String(levelLots) : null;
|
|
2083
|
+
const displayText = getLevelDisplayText(level, price, precision, qtyPillText !== null);
|
|
2084
|
+
const labelW = ctx.measureText(displayText).width;
|
|
1926
2085
|
const pillW = qtyPillText ? PILL_PAD_X + ctx.measureText(qtyPillText).width + PILL_PAD_X + PILL_GAP : 0;
|
|
1927
2086
|
const bW = BOX_PAD_X + pillW + pnlW + labelW + BOX_PAD_X + confirmW + closeW;
|
|
1928
2087
|
measured.push({ level, price, y, boxW: bW, boxH });
|
|
@@ -2082,7 +2241,8 @@ function renderTradeLevels(ctx, levels, scale, priceRange, theme, chartW, priceH
|
|
|
2082
2241
|
dragNewSLPrice,
|
|
2083
2242
|
dragNewTPPrice,
|
|
2084
2243
|
buttonScale,
|
|
2085
|
-
bracketBoxYMap
|
|
2244
|
+
bracketBoxYMap,
|
|
2245
|
+
alwaysShowBrackets
|
|
2086
2246
|
);
|
|
2087
2247
|
const ec = labelToExpandedCluster.get(level.label);
|
|
2088
2248
|
if (ec && layout.boxY !== layout.y - boxH / 2) {
|
|
@@ -2319,10 +2479,12 @@ function drawDotHoverBox(ctx, level, y, dotX, dotR, theme, precision) {
|
|
|
2319
2479
|
});
|
|
2320
2480
|
ctx.restore();
|
|
2321
2481
|
}
|
|
2322
|
-
function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered, selected, scale, priceRange, priceH, draggingLabel, dragNewPrice, bracketLineXMap, hasPendingChanges, positionStyle, hitAreas, editAreas, hoverAreas, dragAreas, confirmAreas, addBracketAreas, hideConfirmCancel = false, draftBracketPnl, showQuantityField = false, draftQtyPillAreas, dragNewSLPrice = null, dragNewTPPrice = null, buttonScale = 1, bracketBoxYMap) {
|
|
2482
|
+
function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered, selected, scale, priceRange, priceH, draggingLabel, dragNewPrice, bracketLineXMap, hasPendingChanges, positionStyle, hitAreas, editAreas, hoverAreas, dragAreas, confirmAreas, addBracketAreas, hideConfirmCancel = false, draftBracketPnl, showQuantityField = false, draftQtyPillAreas, dragNewSLPrice = null, dragNewTPPrice = null, buttonScale = 1, bracketBoxYMap, alwaysShowBrackets = false) {
|
|
2323
2483
|
const { y, boxY, boxW, boxH } = layout;
|
|
2324
2484
|
const color = levelColor(level, colors, theme);
|
|
2325
2485
|
const active = hovered || selected;
|
|
2486
|
+
const passiveBracketShow = alwaysShowBrackets && !active;
|
|
2487
|
+
const effectiveHideConfirmCancel = hideConfirmCancel || passiveBracketShow;
|
|
2326
2488
|
const s = Math.min(Math.max(buttonScale, 1), 3);
|
|
2327
2489
|
const closeR = CLOSE_R * s;
|
|
2328
2490
|
const editR = EDIT_R * s;
|
|
@@ -2366,7 +2528,7 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2366
2528
|
const gripX = draggable ? groupX : 0;
|
|
2367
2529
|
const boxX = draggable ? groupX + GRIP_W + 2 : groupX;
|
|
2368
2530
|
const bracketHost = level;
|
|
2369
|
-
if (active || isMarketDraft) {
|
|
2531
|
+
if (active || isMarketDraft || passiveBracketShow) {
|
|
2370
2532
|
const slDragKey = `${level.label}:stopLoss`;
|
|
2371
2533
|
const tpDragKey = `${level.label}:takeProfit`;
|
|
2372
2534
|
const mainDragActive = draggingLabel === level.label;
|
|
@@ -2408,7 +2570,7 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2408
2570
|
dragNewPrice,
|
|
2409
2571
|
hitAreas,
|
|
2410
2572
|
dragAreas,
|
|
2411
|
-
|
|
2573
|
+
effectiveHideConfirmCancel,
|
|
2412
2574
|
bracketHost._slOrderLabel,
|
|
2413
2575
|
draftBracketPnl?.sl,
|
|
2414
2576
|
buttonScale,
|
|
@@ -2432,7 +2594,7 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2432
2594
|
dragNewPrice,
|
|
2433
2595
|
hitAreas,
|
|
2434
2596
|
dragAreas,
|
|
2435
|
-
|
|
2597
|
+
effectiveHideConfirmCancel,
|
|
2436
2598
|
void 0,
|
|
2437
2599
|
draftBracketPnl?.sl,
|
|
2438
2600
|
buttonScale
|
|
@@ -2456,7 +2618,7 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2456
2618
|
dragNewPrice,
|
|
2457
2619
|
hitAreas,
|
|
2458
2620
|
dragAreas,
|
|
2459
|
-
|
|
2621
|
+
effectiveHideConfirmCancel,
|
|
2460
2622
|
bracketHost._tpOrderLabel,
|
|
2461
2623
|
draftBracketPnl?.tp,
|
|
2462
2624
|
buttonScale,
|
|
@@ -2480,7 +2642,7 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2480
2642
|
dragNewPrice,
|
|
2481
2643
|
hitAreas,
|
|
2482
2644
|
dragAreas,
|
|
2483
|
-
|
|
2645
|
+
effectiveHideConfirmCancel,
|
|
2484
2646
|
void 0,
|
|
2485
2647
|
draftBracketPnl?.tp,
|
|
2486
2648
|
buttonScale
|
|
@@ -2544,7 +2706,8 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2544
2706
|
const isDraftLevel = level.data?._isDraft === true;
|
|
2545
2707
|
const supportsQtyField = level.type === "pending" || isEntryOrderPos;
|
|
2546
2708
|
const levelLots = level.lots;
|
|
2547
|
-
|
|
2709
|
+
const showQtyPill = showQuantityField && (isDraftLevel || selected || hasPendingChanges) && supportsQtyField && levelLots !== void 0;
|
|
2710
|
+
if (showQtyPill) {
|
|
2548
2711
|
const qtyText = String(levelLots);
|
|
2549
2712
|
ctx.font = FONT_MAIN;
|
|
2550
2713
|
const pillContentW = ctx.measureText(qtyText).width;
|
|
@@ -2575,7 +2738,7 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2575
2738
|
ctx.fillText(pnlText, textX, midY);
|
|
2576
2739
|
textX += ctx.measureText(pnlText).width + PNL_GAP;
|
|
2577
2740
|
}
|
|
2578
|
-
const displayText = getLevelDisplayText(level, layout.price, precision);
|
|
2741
|
+
const displayText = getLevelDisplayText(level, layout.price, precision, showQtyPill);
|
|
2579
2742
|
ctx.font = FONT_MAIN;
|
|
2580
2743
|
ctx.fillStyle = monoColor(theme);
|
|
2581
2744
|
ctx.textAlign = "left";
|
|
@@ -3264,6 +3427,20 @@ function renderCandleCountdown(ctx, lastBarTime, intervalMs, lastBarIndex, viewp
|
|
|
3264
3427
|
ctx.textBaseline = "alphabetic";
|
|
3265
3428
|
ctx.restore();
|
|
3266
3429
|
}
|
|
3430
|
+
function renderPriceAxisCountdown(ctx, lastBarTime, intervalMs, theme, boxX, priceTagY, tagH, boxW) {
|
|
3431
|
+
const remaining = Math.max(0, lastBarTime + intervalMs - Date.now());
|
|
3432
|
+
const totalSec = Math.ceil(remaining / 1e3);
|
|
3433
|
+
const label = formatRemaining(totalSec);
|
|
3434
|
+
const boxY = priceTagY + tagH;
|
|
3435
|
+
ctx.save();
|
|
3436
|
+
ctx.fillStyle = theme.axisText;
|
|
3437
|
+
ctx.fillRect(boxX, boxY, boxW, tagH);
|
|
3438
|
+
ctx.fillStyle = theme.background;
|
|
3439
|
+
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
3440
|
+
ctx.textAlign = "left";
|
|
3441
|
+
ctx.fillText(label, boxX + 4, boxY + tagH - 4);
|
|
3442
|
+
ctx.restore();
|
|
3443
|
+
}
|
|
3267
3444
|
|
|
3268
3445
|
// src/axis/PriceAxis.ts
|
|
3269
3446
|
function renderPriceAxis(ctx, priceRange, scale, theme, chartWidth, chartHeight, axisWidth, gridLines, pricePrecision = 2, cfg = DEFAULT_PRICE_AXIS_CONFIG) {
|
|
@@ -5576,6 +5753,50 @@ var LeftBar = class {
|
|
|
5576
5753
|
}
|
|
5577
5754
|
};
|
|
5578
5755
|
|
|
5756
|
+
// src/drawings/styleUtils.ts
|
|
5757
|
+
function resolveStyle(type, instanceStyle, theme) {
|
|
5758
|
+
const themeEntry = theme?.drawing?.[type] ?? {};
|
|
5759
|
+
const merged = { ...themeEntry, ...instanceStyle ?? {} };
|
|
5760
|
+
const themeLevels = themeEntry.levels;
|
|
5761
|
+
const instLevels = instanceStyle?.levels;
|
|
5762
|
+
if (themeLevels || instLevels) {
|
|
5763
|
+
const out = { ...themeLevels ?? {} };
|
|
5764
|
+
if (instLevels) {
|
|
5765
|
+
for (const k of Object.keys(instLevels)) {
|
|
5766
|
+
out[k] = { ...out[k] ?? { visible: true, color: "#888888" }, ...instLevels[k] };
|
|
5767
|
+
}
|
|
5768
|
+
}
|
|
5769
|
+
merged.levels = out;
|
|
5770
|
+
}
|
|
5771
|
+
return {
|
|
5772
|
+
strokeColor: merged.strokeColor ?? "#888888",
|
|
5773
|
+
lineWidth: merged.lineWidth ?? 1,
|
|
5774
|
+
lineStyle: merged.lineStyle ?? "solid",
|
|
5775
|
+
fillColor: merged.fillColor ?? merged.strokeColor ?? "#888888",
|
|
5776
|
+
fillOpacity: merged.fillOpacity ?? 0.1,
|
|
5777
|
+
...merged.textColor !== void 0 && { textColor: merged.textColor },
|
|
5778
|
+
...merged.fontSize !== void 0 && { fontSize: merged.fontSize },
|
|
5779
|
+
...merged.levels !== void 0 && { levels: merged.levels }
|
|
5780
|
+
};
|
|
5781
|
+
}
|
|
5782
|
+
function applyLineStyle(ctx, style, width) {
|
|
5783
|
+
const w = Math.max(1, width);
|
|
5784
|
+
if (style === "dashed") ctx.setLineDash([6 * w, 3 * w]);
|
|
5785
|
+
else if (style === "dotted") ctx.setLineDash([2 * w, 3 * w]);
|
|
5786
|
+
else ctx.setLineDash([]);
|
|
5787
|
+
}
|
|
5788
|
+
function hexWithAlpha(hex, alpha) {
|
|
5789
|
+
if (!hex.startsWith("#")) return hex;
|
|
5790
|
+
const a = Math.max(0, Math.min(1, alpha));
|
|
5791
|
+
const aHex = Math.round(a * 255).toString(16).padStart(2, "0");
|
|
5792
|
+
let body = hex.slice(1);
|
|
5793
|
+
if (body.length === 3) body = body.split("").map((c) => c + c).join("");
|
|
5794
|
+
else if (body.length === 4) body = body.slice(0, 3).split("").map((c) => c + c).join("");
|
|
5795
|
+
else if (body.length === 8) body = body.slice(0, 6);
|
|
5796
|
+
if (body.length !== 6) return hex;
|
|
5797
|
+
return `#${body}${aHex}`;
|
|
5798
|
+
}
|
|
5799
|
+
|
|
5579
5800
|
// src/drawings/HorizontalLine.ts
|
|
5580
5801
|
var HIT_TOLERANCE = 6;
|
|
5581
5802
|
var HorizontalLine = class {
|
|
@@ -5585,13 +5806,14 @@ var HorizontalLine = class {
|
|
|
5585
5806
|
this.points = [point];
|
|
5586
5807
|
this.complete = true;
|
|
5587
5808
|
}
|
|
5588
|
-
render(ctx, scale, _viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
5809
|
+
render(ctx, scale, _viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
5589
5810
|
const price = this.points[0].price;
|
|
5590
5811
|
const y = scale.yToPixel(price, priceRange, chartH);
|
|
5812
|
+
const s = resolveStyle("horizontalLine", this.style, theme);
|
|
5591
5813
|
ctx.save();
|
|
5592
|
-
ctx.strokeStyle =
|
|
5593
|
-
ctx.lineWidth = isSelected ?
|
|
5594
|
-
|
|
5814
|
+
ctx.strokeStyle = s.strokeColor;
|
|
5815
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
5816
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
5595
5817
|
ctx.beginPath();
|
|
5596
5818
|
ctx.moveTo(0, y);
|
|
5597
5819
|
ctx.lineTo(chartW, y);
|
|
@@ -5600,7 +5822,7 @@ var HorizontalLine = class {
|
|
|
5600
5822
|
const label = price.toFixed(pricePrecision);
|
|
5601
5823
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
5602
5824
|
const tw = ctx.measureText(label).width;
|
|
5603
|
-
ctx.fillStyle =
|
|
5825
|
+
ctx.fillStyle = s.strokeColor;
|
|
5604
5826
|
ctx.fillText(label, chartW - tw - 4, y - 3);
|
|
5605
5827
|
ctx.restore();
|
|
5606
5828
|
}
|
|
@@ -5622,12 +5844,13 @@ var VerticalLine = class {
|
|
|
5622
5844
|
this.points = [point];
|
|
5623
5845
|
this.complete = true;
|
|
5624
5846
|
}
|
|
5625
|
-
render(ctx, scale, viewport, _priceRange, chartW, chartH, isSelected) {
|
|
5847
|
+
render(ctx, scale, viewport, _priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
5626
5848
|
const x = scale.xToPixel(this.points[0].barIndex, viewport, chartW);
|
|
5849
|
+
const s = resolveStyle("verticalLine", this.style, theme);
|
|
5627
5850
|
ctx.save();
|
|
5628
|
-
ctx.strokeStyle =
|
|
5629
|
-
ctx.lineWidth = isSelected ?
|
|
5630
|
-
|
|
5851
|
+
ctx.strokeStyle = s.strokeColor;
|
|
5852
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
5853
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
5631
5854
|
ctx.beginPath();
|
|
5632
5855
|
ctx.moveTo(x, 0);
|
|
5633
5856
|
ctx.lineTo(x, chartH);
|
|
@@ -5660,24 +5883,27 @@ var TrendLine = class {
|
|
|
5660
5883
|
this.points = [first, { ...first }];
|
|
5661
5884
|
this.complete = false;
|
|
5662
5885
|
}
|
|
5663
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
5886
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
5664
5887
|
if (this.points.length < 2) return;
|
|
5665
5888
|
const [p1, p2] = this.points;
|
|
5666
5889
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
5667
5890
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
5668
5891
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
5669
5892
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
5893
|
+
const s = resolveStyle("trendLine", this.style, theme);
|
|
5670
5894
|
ctx.save();
|
|
5671
|
-
ctx.strokeStyle =
|
|
5672
|
-
ctx.lineWidth = isSelected ?
|
|
5895
|
+
ctx.strokeStyle = s.strokeColor;
|
|
5896
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
5897
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
5673
5898
|
ctx.beginPath();
|
|
5674
5899
|
ctx.moveTo(x1, y1);
|
|
5675
5900
|
ctx.lineTo(x2, y2);
|
|
5676
5901
|
ctx.stroke();
|
|
5902
|
+
ctx.setLineDash([]);
|
|
5677
5903
|
for (const [x, y] of [[x1, y1], [x2, y2]]) {
|
|
5678
5904
|
ctx.beginPath();
|
|
5679
5905
|
ctx.arc(x, y, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
5680
|
-
ctx.fillStyle =
|
|
5906
|
+
ctx.fillStyle = s.strokeColor;
|
|
5681
5907
|
ctx.fill();
|
|
5682
5908
|
}
|
|
5683
5909
|
ctx.restore();
|
|
@@ -5722,7 +5948,7 @@ var TrendAngle = class {
|
|
|
5722
5948
|
this.points = [first, { ...first }];
|
|
5723
5949
|
this.complete = false;
|
|
5724
5950
|
}
|
|
5725
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
5951
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
5726
5952
|
if (this.points.length < 2) return;
|
|
5727
5953
|
const [p1, p2] = this.points;
|
|
5728
5954
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -5731,10 +5957,12 @@ var TrendAngle = class {
|
|
|
5731
5957
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
5732
5958
|
const angleDeg = Math.atan2(-(y2 - y1), x2 - x1) * 180 / Math.PI;
|
|
5733
5959
|
const angleRad = Math.atan2(-(y2 - y1), x2 - x1);
|
|
5960
|
+
const s = resolveStyle("trendAngle", this.style, theme);
|
|
5734
5961
|
ctx.save();
|
|
5735
|
-
ctx.strokeStyle =
|
|
5736
|
-
ctx.fillStyle =
|
|
5737
|
-
ctx.lineWidth = isSelected ?
|
|
5962
|
+
ctx.strokeStyle = s.strokeColor;
|
|
5963
|
+
ctx.fillStyle = s.strokeColor;
|
|
5964
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
5965
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
5738
5966
|
ctx.beginPath();
|
|
5739
5967
|
ctx.moveTo(x1, y1);
|
|
5740
5968
|
ctx.lineTo(x2, y2);
|
|
@@ -5797,14 +6025,15 @@ var CrossLine = class {
|
|
|
5797
6025
|
this.points = [point];
|
|
5798
6026
|
this.complete = true;
|
|
5799
6027
|
}
|
|
5800
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
6028
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
5801
6029
|
const p = this.points[0];
|
|
5802
6030
|
const x = scale.xToPixel(p.barIndex, viewport, chartW);
|
|
5803
6031
|
const y = scale.yToPixel(p.price, priceRange, chartH);
|
|
6032
|
+
const s = resolveStyle("crossLine", this.style, theme);
|
|
5804
6033
|
ctx.save();
|
|
5805
|
-
ctx.strokeStyle =
|
|
5806
|
-
ctx.lineWidth = isSelected ?
|
|
5807
|
-
|
|
6034
|
+
ctx.strokeStyle = s.strokeColor;
|
|
6035
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6036
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
5808
6037
|
ctx.beginPath();
|
|
5809
6038
|
ctx.moveTo(0, y);
|
|
5810
6039
|
ctx.lineTo(chartW, y);
|
|
@@ -5846,7 +6075,7 @@ var ParallelChannel = class {
|
|
|
5846
6075
|
this.points = [first, { ...first }];
|
|
5847
6076
|
this.complete = false;
|
|
5848
6077
|
}
|
|
5849
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
6078
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
5850
6079
|
if (this.points.length < 2) return;
|
|
5851
6080
|
const [p1, p2, p3] = this.points;
|
|
5852
6081
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -5862,16 +6091,18 @@ var ParallelChannel = class {
|
|
|
5862
6091
|
}
|
|
5863
6092
|
const ox1 = x1, oy1 = y1 + offsetY;
|
|
5864
6093
|
const ox2 = x2, oy2 = y2 + offsetY;
|
|
6094
|
+
const s = resolveStyle("parallelChannel", this.style, theme);
|
|
5865
6095
|
ctx.save();
|
|
5866
|
-
ctx.strokeStyle =
|
|
5867
|
-
ctx.lineWidth = isSelected ?
|
|
6096
|
+
ctx.strokeStyle = s.strokeColor;
|
|
6097
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6098
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
5868
6099
|
ctx.beginPath();
|
|
5869
6100
|
ctx.moveTo(x1, y1);
|
|
5870
6101
|
ctx.lineTo(x2, y2);
|
|
5871
6102
|
ctx.lineTo(ox2, oy2);
|
|
5872
6103
|
ctx.lineTo(ox1, oy1);
|
|
5873
6104
|
ctx.closePath();
|
|
5874
|
-
ctx.fillStyle =
|
|
6105
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
5875
6106
|
ctx.fill();
|
|
5876
6107
|
ctx.beginPath();
|
|
5877
6108
|
ctx.moveTo(x1, y1);
|
|
@@ -5883,6 +6114,7 @@ var ParallelChannel = class {
|
|
|
5883
6114
|
ctx.lineTo(ox2, oy2);
|
|
5884
6115
|
ctx.stroke();
|
|
5885
6116
|
}
|
|
6117
|
+
ctx.setLineDash([]);
|
|
5886
6118
|
if (isSelected) {
|
|
5887
6119
|
const anchorPts = [[x1, y1], [x2, y2]];
|
|
5888
6120
|
if (p3) {
|
|
@@ -5890,7 +6122,7 @@ var ParallelChannel = class {
|
|
|
5890
6122
|
const p3y = scale.yToPixel(p3.price, priceRange, chartH);
|
|
5891
6123
|
anchorPts.push([p3x, p3y]);
|
|
5892
6124
|
}
|
|
5893
|
-
ctx.fillStyle =
|
|
6125
|
+
ctx.fillStyle = s.strokeColor;
|
|
5894
6126
|
for (const [ax, ay] of anchorPts) {
|
|
5895
6127
|
ctx.beginPath();
|
|
5896
6128
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -5948,7 +6180,7 @@ var FlatChannel = class {
|
|
|
5948
6180
|
this.points = [first, { ...first }];
|
|
5949
6181
|
this.complete = false;
|
|
5950
6182
|
}
|
|
5951
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
6183
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
5952
6184
|
if (this.points.length < 2) return;
|
|
5953
6185
|
const [p1, p2, p3] = this.points;
|
|
5954
6186
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -5956,12 +6188,14 @@ var FlatChannel = class {
|
|
|
5956
6188
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
5957
6189
|
const priceOffset = p3 ? p3.price - p1.price : 0;
|
|
5958
6190
|
const y2 = scale.yToPixel(p1.price + priceOffset, priceRange, chartH);
|
|
6191
|
+
const s = resolveStyle("flatChannel", this.style, theme);
|
|
5959
6192
|
ctx.save();
|
|
5960
|
-
ctx.strokeStyle =
|
|
5961
|
-
ctx.lineWidth = isSelected ?
|
|
6193
|
+
ctx.strokeStyle = s.strokeColor;
|
|
6194
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6195
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
5962
6196
|
ctx.beginPath();
|
|
5963
6197
|
ctx.rect(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x2 - x1), Math.abs(y2 - y1));
|
|
5964
|
-
ctx.fillStyle =
|
|
6198
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
5965
6199
|
ctx.fill();
|
|
5966
6200
|
ctx.beginPath();
|
|
5967
6201
|
ctx.moveTo(x1, y1);
|
|
@@ -5973,8 +6207,9 @@ var FlatChannel = class {
|
|
|
5973
6207
|
ctx.lineTo(x2, y2);
|
|
5974
6208
|
ctx.stroke();
|
|
5975
6209
|
}
|
|
6210
|
+
ctx.setLineDash([]);
|
|
5976
6211
|
if (isSelected) {
|
|
5977
|
-
ctx.fillStyle =
|
|
6212
|
+
ctx.fillStyle = s.strokeColor;
|
|
5978
6213
|
const anchorPts = [[x1, y1], [x2, y1]];
|
|
5979
6214
|
if (p3) {
|
|
5980
6215
|
const p3x = scale.xToPixel(p3.barIndex, viewport, chartW);
|
|
@@ -6035,7 +6270,7 @@ var DisjointChannel = class {
|
|
|
6035
6270
|
this.points = [first, { ...first }];
|
|
6036
6271
|
this.complete = false;
|
|
6037
6272
|
}
|
|
6038
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
6273
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
6039
6274
|
if (this.points.length < 2) return;
|
|
6040
6275
|
const toXY = (p) => [
|
|
6041
6276
|
scale.xToPixel(p.barIndex, viewport, chartW),
|
|
@@ -6044,9 +6279,11 @@ var DisjointChannel = class {
|
|
|
6044
6279
|
const [p1, p2, p3, p4] = this.points;
|
|
6045
6280
|
const [x1, y1] = toXY(p1);
|
|
6046
6281
|
const [x2, y2] = toXY(p2);
|
|
6282
|
+
const s = resolveStyle("disjointChannel", this.style, theme);
|
|
6047
6283
|
ctx.save();
|
|
6048
|
-
ctx.strokeStyle =
|
|
6049
|
-
ctx.lineWidth = isSelected ?
|
|
6284
|
+
ctx.strokeStyle = s.strokeColor;
|
|
6285
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6286
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
6050
6287
|
if (p3 && p4) {
|
|
6051
6288
|
const [x3, y3] = toXY(p3);
|
|
6052
6289
|
const [x4, y4] = toXY(p4);
|
|
@@ -6056,7 +6293,7 @@ var DisjointChannel = class {
|
|
|
6056
6293
|
ctx.lineTo(x4, y4);
|
|
6057
6294
|
ctx.lineTo(x3, y3);
|
|
6058
6295
|
ctx.closePath();
|
|
6059
|
-
ctx.fillStyle =
|
|
6296
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
6060
6297
|
ctx.fill();
|
|
6061
6298
|
ctx.beginPath();
|
|
6062
6299
|
ctx.moveTo(x1, y1);
|
|
@@ -6066,8 +6303,9 @@ var DisjointChannel = class {
|
|
|
6066
6303
|
ctx.moveTo(x3, y3);
|
|
6067
6304
|
ctx.lineTo(x4, y4);
|
|
6068
6305
|
ctx.stroke();
|
|
6306
|
+
ctx.setLineDash([]);
|
|
6069
6307
|
if (isSelected) {
|
|
6070
|
-
ctx.fillStyle =
|
|
6308
|
+
ctx.fillStyle = s.strokeColor;
|
|
6071
6309
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3], [x4, y4]]) {
|
|
6072
6310
|
ctx.beginPath();
|
|
6073
6311
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -6084,8 +6322,9 @@ var DisjointChannel = class {
|
|
|
6084
6322
|
ctx.moveTo(x3, y3);
|
|
6085
6323
|
ctx.lineTo(x3, y3);
|
|
6086
6324
|
ctx.stroke();
|
|
6325
|
+
ctx.setLineDash([]);
|
|
6087
6326
|
if (isSelected) {
|
|
6088
|
-
ctx.fillStyle =
|
|
6327
|
+
ctx.fillStyle = s.strokeColor;
|
|
6089
6328
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3]]) {
|
|
6090
6329
|
ctx.beginPath();
|
|
6091
6330
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -6097,8 +6336,9 @@ var DisjointChannel = class {
|
|
|
6097
6336
|
ctx.moveTo(x1, y1);
|
|
6098
6337
|
ctx.lineTo(x2, y2);
|
|
6099
6338
|
ctx.stroke();
|
|
6339
|
+
ctx.setLineDash([]);
|
|
6100
6340
|
if (isSelected) {
|
|
6101
|
-
ctx.fillStyle =
|
|
6341
|
+
ctx.fillStyle = s.strokeColor;
|
|
6102
6342
|
for (const [ax, ay] of [[x1, y1], [x2, y2]]) {
|
|
6103
6343
|
ctx.beginPath();
|
|
6104
6344
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -6142,7 +6382,7 @@ var DisjointChannel = class {
|
|
|
6142
6382
|
|
|
6143
6383
|
// src/drawings/FibRetracement.ts
|
|
6144
6384
|
var FIB_LEVELS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
|
|
6145
|
-
var
|
|
6385
|
+
var FIB_FALLBACK_COLORS = ["#ef5350", "#e8b84b", "#26a69a", "#2196f3", "#26a69a", "#e8b84b", "#ef5350"];
|
|
6146
6386
|
var HIT_TOLERANCE9 = 6;
|
|
6147
6387
|
var FibRetracement = class {
|
|
6148
6388
|
constructor(first) {
|
|
@@ -6151,7 +6391,7 @@ var FibRetracement = class {
|
|
|
6151
6391
|
this.points = [first, { ...first }];
|
|
6152
6392
|
this.complete = false;
|
|
6153
6393
|
}
|
|
6154
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
6394
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
6155
6395
|
if (this.points.length < 2) return;
|
|
6156
6396
|
const [p1, p2] = this.points;
|
|
6157
6397
|
const highPrice = Math.max(p1.price, p2.price);
|
|
@@ -6163,17 +6403,23 @@ var FibRetracement = class {
|
|
|
6163
6403
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
6164
6404
|
const xMin = Math.min(x1, x2);
|
|
6165
6405
|
const xMax = Math.max(x1, x2);
|
|
6406
|
+
const s = resolveStyle("fibRetracement", this.style, theme);
|
|
6407
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6408
|
+
const anchorColor = s.strokeColor ?? FIB_FALLBACK_COLORS[1];
|
|
6166
6409
|
ctx.save();
|
|
6167
6410
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6411
|
+
ctx.lineWidth = baseLineWidth;
|
|
6168
6412
|
for (let i = 0; i < FIB_LEVELS.length; i++) {
|
|
6169
6413
|
const level = FIB_LEVELS[i];
|
|
6414
|
+
const key = String(level);
|
|
6415
|
+
const lvl = s.levels?.[key];
|
|
6416
|
+
if (lvl?.visible === false) continue;
|
|
6170
6417
|
const price = highPrice - range * level;
|
|
6171
6418
|
const y = scale.yToPixel(price, priceRange, chartH);
|
|
6172
|
-
const
|
|
6173
|
-
const
|
|
6174
|
-
ctx.strokeStyle = color
|
|
6175
|
-
ctx
|
|
6176
|
-
ctx.setLineDash(level === 0 || level === 1 ? [] : [4, 3]);
|
|
6419
|
+
const baseColor = lvl?.color ?? s.strokeColor ?? FIB_FALLBACK_COLORS[i];
|
|
6420
|
+
const color = hexWithAlpha(baseColor, isSelected ? 0.8 : 0.6);
|
|
6421
|
+
ctx.strokeStyle = color;
|
|
6422
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6177
6423
|
ctx.beginPath();
|
|
6178
6424
|
ctx.moveTo(xMin, y);
|
|
6179
6425
|
ctx.lineTo(xMax, y);
|
|
@@ -6181,13 +6427,13 @@ var FibRetracement = class {
|
|
|
6181
6427
|
ctx.setLineDash([]);
|
|
6182
6428
|
const pct = `${(level * 100).toFixed(1)}%`;
|
|
6183
6429
|
const lbl = `${pct} ${price.toFixed(pricePrecision)}`;
|
|
6184
|
-
ctx.fillStyle = color
|
|
6430
|
+
ctx.fillStyle = color;
|
|
6185
6431
|
ctx.fillText(lbl, xMin + 4, y - 3);
|
|
6186
6432
|
}
|
|
6187
6433
|
for (const [ax, ay] of [[x1, y1], [x2, y2]]) {
|
|
6188
6434
|
ctx.beginPath();
|
|
6189
6435
|
ctx.arc(ax, ay, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6190
|
-
ctx.fillStyle =
|
|
6436
|
+
ctx.fillStyle = anchorColor;
|
|
6191
6437
|
ctx.fill();
|
|
6192
6438
|
}
|
|
6193
6439
|
ctx.restore();
|
|
@@ -6222,7 +6468,7 @@ var FibRetracement = class {
|
|
|
6222
6468
|
// src/drawings/FibExtension.ts
|
|
6223
6469
|
var HIT_TOLERANCE10 = 6;
|
|
6224
6470
|
var FIB_EXT_LEVELS = [0, 0.382, 0.618, 1, 1.382, 1.618, 2, 2.618];
|
|
6225
|
-
var
|
|
6471
|
+
var FIB_EXT_FALLBACK_COLORS = ["#ef5350", "#e8b84b", "#26a69a", "#2196f3", "#26a69a", "#e8b84b", "#ef5350", "#ef5350"];
|
|
6226
6472
|
var FibExtension = class {
|
|
6227
6473
|
constructor(first) {
|
|
6228
6474
|
this.type = "fibExtension";
|
|
@@ -6231,15 +6477,18 @@ var FibExtension = class {
|
|
|
6231
6477
|
this.points = [first, { ...first }];
|
|
6232
6478
|
this.complete = false;
|
|
6233
6479
|
}
|
|
6234
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
6480
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
6235
6481
|
const [p1, p2, p3] = this.points;
|
|
6236
6482
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
6237
6483
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
6238
6484
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
6239
6485
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
6486
|
+
const s = resolveStyle("fibExtension", this.style, theme);
|
|
6487
|
+
const anchorColor = s.strokeColor ?? "#e8b84b";
|
|
6488
|
+
const connectorColor = hexWithAlpha(s.strokeColor ?? "#888888", 0.67);
|
|
6240
6489
|
if (!p3) {
|
|
6241
6490
|
ctx.save();
|
|
6242
|
-
ctx.strokeStyle =
|
|
6491
|
+
ctx.strokeStyle = connectorColor;
|
|
6243
6492
|
ctx.lineWidth = 1;
|
|
6244
6493
|
ctx.setLineDash([4, 3]);
|
|
6245
6494
|
ctx.beginPath();
|
|
@@ -6247,7 +6496,7 @@ var FibExtension = class {
|
|
|
6247
6496
|
ctx.lineTo(x2, y2);
|
|
6248
6497
|
ctx.stroke();
|
|
6249
6498
|
ctx.setLineDash([]);
|
|
6250
|
-
ctx.fillStyle =
|
|
6499
|
+
ctx.fillStyle = anchorColor;
|
|
6251
6500
|
ctx.beginPath();
|
|
6252
6501
|
ctx.arc(x1, y1, 3, 0, Math.PI * 2);
|
|
6253
6502
|
ctx.fill();
|
|
@@ -6259,10 +6508,11 @@ var FibExtension = class {
|
|
|
6259
6508
|
const xMin = Math.min(x1, x2, x3);
|
|
6260
6509
|
const xMax = Math.max(x1, x2, x3);
|
|
6261
6510
|
const priceMove = p2.price - p1.price;
|
|
6511
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6262
6512
|
ctx.save();
|
|
6263
6513
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6264
|
-
ctx.lineWidth =
|
|
6265
|
-
ctx.strokeStyle =
|
|
6514
|
+
ctx.lineWidth = baseLineWidth;
|
|
6515
|
+
ctx.strokeStyle = connectorColor;
|
|
6266
6516
|
ctx.setLineDash([4, 3]);
|
|
6267
6517
|
ctx.beginPath();
|
|
6268
6518
|
ctx.moveTo(x1, y1);
|
|
@@ -6275,12 +6525,15 @@ var FibExtension = class {
|
|
|
6275
6525
|
ctx.setLineDash([]);
|
|
6276
6526
|
for (let i = 0; i < FIB_EXT_LEVELS.length; i++) {
|
|
6277
6527
|
const level = FIB_EXT_LEVELS[i];
|
|
6528
|
+
const key = String(level);
|
|
6529
|
+
const lvl = s.levels?.[key];
|
|
6530
|
+
if (lvl?.visible === false) continue;
|
|
6278
6531
|
const levelPrice = p3.price + level * priceMove;
|
|
6279
6532
|
const y = scale.yToPixel(levelPrice, priceRange, chartH);
|
|
6280
|
-
const
|
|
6281
|
-
const
|
|
6282
|
-
ctx.strokeStyle = color
|
|
6283
|
-
ctx
|
|
6533
|
+
const baseColor = lvl?.color ?? s.strokeColor ?? FIB_EXT_FALLBACK_COLORS[i];
|
|
6534
|
+
const color = hexWithAlpha(baseColor, isSelected ? 0.8 : 0.6);
|
|
6535
|
+
ctx.strokeStyle = color;
|
|
6536
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6284
6537
|
ctx.beginPath();
|
|
6285
6538
|
ctx.moveTo(xMin, y);
|
|
6286
6539
|
ctx.lineTo(xMax, y);
|
|
@@ -6288,13 +6541,13 @@ var FibExtension = class {
|
|
|
6288
6541
|
ctx.setLineDash([]);
|
|
6289
6542
|
const pct = `${(level * 100).toFixed(1)}%`;
|
|
6290
6543
|
const lbl = `${pct} ${levelPrice.toFixed(pricePrecision)}`;
|
|
6291
|
-
ctx.fillStyle = color
|
|
6544
|
+
ctx.fillStyle = color;
|
|
6292
6545
|
ctx.fillText(lbl, xMin + 4, y - 3);
|
|
6293
6546
|
}
|
|
6294
6547
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3]]) {
|
|
6295
6548
|
ctx.beginPath();
|
|
6296
6549
|
ctx.arc(ax, ay, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6297
|
-
ctx.fillStyle =
|
|
6550
|
+
ctx.fillStyle = anchorColor;
|
|
6298
6551
|
ctx.fill();
|
|
6299
6552
|
}
|
|
6300
6553
|
ctx.restore();
|
|
@@ -6328,7 +6581,6 @@ var FibExtension = class {
|
|
|
6328
6581
|
// src/drawings/FibChannel.ts
|
|
6329
6582
|
var HIT_TOLERANCE11 = 6;
|
|
6330
6583
|
var FIB_LEVELS2 = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
|
|
6331
|
-
var FIB_COLORS2 = ["#ef5350", "#e8b84b", "#26a69a", "#2196f3", "#26a69a", "#e8b84b", "#ef5350"];
|
|
6332
6584
|
function pointToSegmentDist6(px, py, x1, y1, x2, y2) {
|
|
6333
6585
|
const dx = x2 - x1, dy = y2 - y1;
|
|
6334
6586
|
const lenSq = dx * dx + dy * dy;
|
|
@@ -6344,15 +6596,17 @@ var FibChannel = class {
|
|
|
6344
6596
|
this.points = [first, { ...first }];
|
|
6345
6597
|
this.complete = false;
|
|
6346
6598
|
}
|
|
6347
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected,
|
|
6599
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision = 2, theme) {
|
|
6348
6600
|
const [p1, p2, p3] = this.points;
|
|
6349
6601
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
6350
6602
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
6351
6603
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
6352
6604
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
6605
|
+
const s = resolveStyle("fibChannel", this.style, theme);
|
|
6606
|
+
const connectorColor = hexWithAlpha(s.strokeColor, 0.67);
|
|
6353
6607
|
if (!p3) {
|
|
6354
6608
|
ctx.save();
|
|
6355
|
-
ctx.strokeStyle =
|
|
6609
|
+
ctx.strokeStyle = connectorColor;
|
|
6356
6610
|
ctx.lineWidth = 1;
|
|
6357
6611
|
ctx.setLineDash([4, 3]);
|
|
6358
6612
|
ctx.beginPath();
|
|
@@ -6360,7 +6614,7 @@ var FibChannel = class {
|
|
|
6360
6614
|
ctx.lineTo(x2, y2);
|
|
6361
6615
|
ctx.stroke();
|
|
6362
6616
|
ctx.setLineDash([]);
|
|
6363
|
-
ctx.fillStyle =
|
|
6617
|
+
ctx.fillStyle = s.strokeColor;
|
|
6364
6618
|
ctx.beginPath();
|
|
6365
6619
|
ctx.arc(x1, y1, 3, 0, Math.PI * 2);
|
|
6366
6620
|
ctx.fill();
|
|
@@ -6376,9 +6630,10 @@ var FibChannel = class {
|
|
|
6376
6630
|
const nx = -dy / len;
|
|
6377
6631
|
const ny = dx / len;
|
|
6378
6632
|
const perpDist = (x3 - x1) * nx + (y3 - y1) * ny;
|
|
6633
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6379
6634
|
ctx.save();
|
|
6380
6635
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6381
|
-
ctx.lineWidth =
|
|
6636
|
+
ctx.lineWidth = baseLineWidth;
|
|
6382
6637
|
for (let i = 0; i < FIB_LEVELS2.length; i++) {
|
|
6383
6638
|
const level = FIB_LEVELS2[i];
|
|
6384
6639
|
const offset = level * perpDist;
|
|
@@ -6386,30 +6641,29 @@ var FibChannel = class {
|
|
|
6386
6641
|
const ly1 = y1 + offset * ny;
|
|
6387
6642
|
const lx2 = x2 + offset * nx;
|
|
6388
6643
|
const ly2 = y2 + offset * ny;
|
|
6389
|
-
const color =
|
|
6390
|
-
|
|
6391
|
-
ctx
|
|
6392
|
-
ctx.setLineDash(level === 0 || level === 1 ? [] : [4, 3]);
|
|
6644
|
+
const color = hexWithAlpha(s.strokeColor, isSelected ? 0.8 : 0.6);
|
|
6645
|
+
ctx.strokeStyle = color;
|
|
6646
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6393
6647
|
ctx.beginPath();
|
|
6394
6648
|
ctx.moveTo(lx1, ly1);
|
|
6395
6649
|
ctx.lineTo(lx2, ly2);
|
|
6396
6650
|
ctx.stroke();
|
|
6397
6651
|
ctx.setLineDash([]);
|
|
6398
6652
|
const pct = `${(level * 100).toFixed(1)}%`;
|
|
6399
|
-
ctx.fillStyle = color
|
|
6653
|
+
ctx.fillStyle = color;
|
|
6400
6654
|
ctx.fillText(pct, lx2 + 4, ly2 + 3);
|
|
6401
6655
|
}
|
|
6402
6656
|
ctx.beginPath();
|
|
6403
6657
|
ctx.arc(x3, y3, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6404
|
-
ctx.fillStyle =
|
|
6658
|
+
ctx.fillStyle = s.strokeColor;
|
|
6405
6659
|
ctx.fill();
|
|
6406
6660
|
for (const [ax, ay] of [[x1, y1], [x2, y2]]) {
|
|
6407
6661
|
ctx.beginPath();
|
|
6408
6662
|
ctx.arc(ax, ay, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6409
|
-
ctx.fillStyle =
|
|
6663
|
+
ctx.fillStyle = s.strokeColor;
|
|
6410
6664
|
ctx.fill();
|
|
6411
6665
|
}
|
|
6412
|
-
ctx.strokeStyle =
|
|
6666
|
+
ctx.strokeStyle = connectorColor;
|
|
6413
6667
|
ctx.setLineDash([3, 3]);
|
|
6414
6668
|
ctx.beginPath();
|
|
6415
6669
|
ctx.moveTo(x1, y1);
|
|
@@ -6469,19 +6723,20 @@ var FibTimezone = class {
|
|
|
6469
6723
|
this.points = [first, { ...first }];
|
|
6470
6724
|
this.complete = false;
|
|
6471
6725
|
}
|
|
6472
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
6726
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
6473
6727
|
if (this.points.length < 2) return;
|
|
6474
6728
|
const [p1, p2] = this.points;
|
|
6475
6729
|
const interval = p2.barIndex - p1.barIndex;
|
|
6476
6730
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
6477
6731
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
6732
|
+
const s = resolveStyle("fibTimezone", this.style, theme);
|
|
6733
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6734
|
+
const lineColor = hexWithAlpha(s.strokeColor, isSelected ? 0.8 : 0.53);
|
|
6478
6735
|
ctx.save();
|
|
6479
6736
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6480
|
-
ctx.lineWidth =
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
ctx.strokeStyle = color + alpha;
|
|
6484
|
-
ctx.setLineDash([3, 3]);
|
|
6737
|
+
ctx.lineWidth = baseLineWidth;
|
|
6738
|
+
ctx.strokeStyle = lineColor;
|
|
6739
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6485
6740
|
ctx.beginPath();
|
|
6486
6741
|
ctx.moveTo(x1, 0);
|
|
6487
6742
|
ctx.lineTo(x1, chartH);
|
|
@@ -6495,14 +6750,14 @@ var FibTimezone = class {
|
|
|
6495
6750
|
const barIndex = p1.barIndex + fib * interval;
|
|
6496
6751
|
const xFib = scale.xToPixel(barIndex, viewport, chartW);
|
|
6497
6752
|
if (xFib < -50 || xFib > chartW + 50) continue;
|
|
6498
|
-
ctx.strokeStyle =
|
|
6499
|
-
ctx.
|
|
6753
|
+
ctx.strokeStyle = lineColor;
|
|
6754
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6500
6755
|
ctx.beginPath();
|
|
6501
6756
|
ctx.moveTo(xFib, 0);
|
|
6502
6757
|
ctx.lineTo(xFib, chartH);
|
|
6503
6758
|
ctx.stroke();
|
|
6504
6759
|
ctx.setLineDash([]);
|
|
6505
|
-
ctx.fillStyle =
|
|
6760
|
+
ctx.fillStyle = lineColor;
|
|
6506
6761
|
ctx.fillText(String(fib), xFib + 3, 12);
|
|
6507
6762
|
}
|
|
6508
6763
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
@@ -6510,7 +6765,7 @@ var FibTimezone = class {
|
|
|
6510
6765
|
for (const [ax, ay] of [[x1, y1], [x2, y2]]) {
|
|
6511
6766
|
ctx.beginPath();
|
|
6512
6767
|
ctx.arc(ax, ay, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6513
|
-
ctx.fillStyle =
|
|
6768
|
+
ctx.fillStyle = s.strokeColor;
|
|
6514
6769
|
ctx.fill();
|
|
6515
6770
|
}
|
|
6516
6771
|
ctx.restore();
|
|
@@ -6546,7 +6801,6 @@ var FibTimezone = class {
|
|
|
6546
6801
|
// src/drawings/FibCircles.ts
|
|
6547
6802
|
var HIT_TOLERANCE13 = 6;
|
|
6548
6803
|
var FIB_RATIOS = [0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618, 2.618];
|
|
6549
|
-
var FIB_COLORS3 = ["#ef5350", "#e8b84b", "#26a69a", "#2196f3", "#26a69a", "#e8b84b", "#ef5350", "#ef5350"];
|
|
6550
6804
|
var FibCircles = class {
|
|
6551
6805
|
constructor(first) {
|
|
6552
6806
|
this.type = "fibCircles";
|
|
@@ -6554,7 +6808,7 @@ var FibCircles = class {
|
|
|
6554
6808
|
this.points = [first, { ...first }];
|
|
6555
6809
|
this.complete = false;
|
|
6556
6810
|
}
|
|
6557
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
6811
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
6558
6812
|
if (this.points.length < 2) return;
|
|
6559
6813
|
const [p1, p2] = this.points;
|
|
6560
6814
|
const cx = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -6562,30 +6816,33 @@ var FibCircles = class {
|
|
|
6562
6816
|
const rx = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
6563
6817
|
const ry = scale.yToPixel(p2.price, priceRange, chartH);
|
|
6564
6818
|
const baseRadius = Math.hypot(rx - cx, ry - cy);
|
|
6819
|
+
const s = resolveStyle("fibCircles", this.style, theme);
|
|
6820
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6565
6821
|
ctx.save();
|
|
6566
6822
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6567
|
-
ctx.lineWidth =
|
|
6823
|
+
ctx.lineWidth = baseLineWidth;
|
|
6568
6824
|
for (let i = 0; i < FIB_RATIOS.length; i++) {
|
|
6569
6825
|
const ratio = FIB_RATIOS[i];
|
|
6570
6826
|
const radius = ratio * baseRadius;
|
|
6571
|
-
const color =
|
|
6572
|
-
|
|
6573
|
-
ctx
|
|
6827
|
+
const color = hexWithAlpha(s.strokeColor, isSelected ? 0.8 : 0.53);
|
|
6828
|
+
ctx.strokeStyle = color;
|
|
6829
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6574
6830
|
ctx.beginPath();
|
|
6575
6831
|
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
|
|
6576
6832
|
ctx.stroke();
|
|
6577
|
-
ctx.
|
|
6833
|
+
ctx.setLineDash([]);
|
|
6834
|
+
ctx.fillStyle = color;
|
|
6578
6835
|
ctx.fillText(`${(ratio * 100).toFixed(1)}%`, cx + 3, cy - radius + 12);
|
|
6579
6836
|
}
|
|
6580
6837
|
ctx.beginPath();
|
|
6581
6838
|
ctx.arc(cx, cy, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6582
|
-
ctx.fillStyle =
|
|
6839
|
+
ctx.fillStyle = s.strokeColor;
|
|
6583
6840
|
ctx.fill();
|
|
6584
6841
|
ctx.beginPath();
|
|
6585
6842
|
ctx.arc(rx, ry, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6586
|
-
ctx.fillStyle =
|
|
6843
|
+
ctx.fillStyle = s.strokeColor;
|
|
6587
6844
|
ctx.fill();
|
|
6588
|
-
ctx.strokeStyle =
|
|
6845
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, 0.4);
|
|
6589
6846
|
ctx.setLineDash([3, 3]);
|
|
6590
6847
|
ctx.beginPath();
|
|
6591
6848
|
ctx.moveTo(cx, cy);
|
|
@@ -6659,7 +6916,7 @@ var FibSpiral = class {
|
|
|
6659
6916
|
}
|
|
6660
6917
|
return arcs;
|
|
6661
6918
|
}
|
|
6662
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
6919
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
6663
6920
|
if (this.points.length < 2) return;
|
|
6664
6921
|
const [p1, p2] = this.points;
|
|
6665
6922
|
const cx = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -6668,10 +6925,13 @@ var FibSpiral = class {
|
|
|
6668
6925
|
const py2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
6669
6926
|
const dx = px2 - cx;
|
|
6670
6927
|
const dy = py2 - cy;
|
|
6928
|
+
const s = resolveStyle("fibSpiral", this.style, theme);
|
|
6929
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6671
6930
|
ctx.save();
|
|
6672
6931
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6673
|
-
ctx.strokeStyle = isSelected ?
|
|
6674
|
-
ctx.lineWidth =
|
|
6932
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, isSelected ? 0.8 : 0.53);
|
|
6933
|
+
ctx.lineWidth = baseLineWidth;
|
|
6934
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6675
6935
|
const arcs = this.computeArcs(cx, cy, dx, dy);
|
|
6676
6936
|
ctx.beginPath();
|
|
6677
6937
|
for (let i = 0; i < arcs.length; i++) {
|
|
@@ -6684,15 +6944,16 @@ var FibSpiral = class {
|
|
|
6684
6944
|
ctx.arc(arc.ox, arc.oy, arc.r, arc.startAngle, arc.endAngle, false);
|
|
6685
6945
|
}
|
|
6686
6946
|
ctx.stroke();
|
|
6947
|
+
ctx.setLineDash([]);
|
|
6687
6948
|
ctx.beginPath();
|
|
6688
6949
|
ctx.arc(cx, cy, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6689
|
-
ctx.fillStyle =
|
|
6950
|
+
ctx.fillStyle = s.strokeColor;
|
|
6690
6951
|
ctx.fill();
|
|
6691
6952
|
ctx.beginPath();
|
|
6692
6953
|
ctx.arc(px2, py2, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6693
|
-
ctx.fillStyle =
|
|
6954
|
+
ctx.fillStyle = s.strokeColor;
|
|
6694
6955
|
ctx.fill();
|
|
6695
|
-
ctx.strokeStyle =
|
|
6956
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, 0.4);
|
|
6696
6957
|
ctx.setLineDash([3, 3]);
|
|
6697
6958
|
ctx.beginPath();
|
|
6698
6959
|
ctx.moveTo(cx, cy);
|
|
@@ -6743,7 +7004,6 @@ var FibSpiral = class {
|
|
|
6743
7004
|
// src/drawings/FibFan.ts
|
|
6744
7005
|
var HIT_TOLERANCE15 = 6;
|
|
6745
7006
|
var FIB_FAN_RATIOS = [0.236, 0.382, 0.5, 0.618, 0.786];
|
|
6746
|
-
var FIB_FAN_COLORS = ["#e8b84b", "#26a69a", "#2196f3", "#26a69a", "#e8b84b"];
|
|
6747
7007
|
function pointToSegmentDist7(px, py, x1, y1, x2, y2) {
|
|
6748
7008
|
const dx = x2 - x1, dy = y2 - y1;
|
|
6749
7009
|
const lenSq = dx * dx + dy * dy;
|
|
@@ -6769,17 +7029,19 @@ var FibFan = class {
|
|
|
6769
7029
|
this.points = [first, { ...first }];
|
|
6770
7030
|
this.complete = false;
|
|
6771
7031
|
}
|
|
6772
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7032
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
6773
7033
|
if (this.points.length < 2) return;
|
|
6774
7034
|
const [p1, p2] = this.points;
|
|
6775
7035
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
6776
7036
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
6777
7037
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
6778
7038
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
7039
|
+
const s = resolveStyle("fibFan", this.style, theme);
|
|
7040
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6779
7041
|
ctx.save();
|
|
6780
7042
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6781
|
-
ctx.lineWidth =
|
|
6782
|
-
ctx.strokeStyle =
|
|
7043
|
+
ctx.lineWidth = baseLineWidth;
|
|
7044
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, 0.67);
|
|
6783
7045
|
ctx.setLineDash([4, 3]);
|
|
6784
7046
|
ctx.beginPath();
|
|
6785
7047
|
ctx.moveTo(x1, y1);
|
|
@@ -6789,24 +7051,25 @@ var FibFan = class {
|
|
|
6789
7051
|
const priceRange2 = p2.price - p1.price;
|
|
6790
7052
|
for (let i = 0; i < FIB_FAN_RATIOS.length; i++) {
|
|
6791
7053
|
const ratio = FIB_FAN_RATIOS[i];
|
|
6792
|
-
const color =
|
|
6793
|
-
const alpha = isSelected ? "cc" : "99";
|
|
7054
|
+
const color = hexWithAlpha(s.strokeColor, isSelected ? 0.8 : 0.6);
|
|
6794
7055
|
const targetPrice = p1.price + ratio * priceRange2;
|
|
6795
7056
|
const tx = x2;
|
|
6796
7057
|
const ty = scale.yToPixel(targetPrice, priceRange, chartH);
|
|
6797
7058
|
const [ex, ey] = extendRay(x1, y1, tx, ty, chartW, chartH);
|
|
6798
|
-
ctx.strokeStyle = color
|
|
7059
|
+
ctx.strokeStyle = color;
|
|
7060
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6799
7061
|
ctx.beginPath();
|
|
6800
7062
|
ctx.moveTo(x1, y1);
|
|
6801
7063
|
ctx.lineTo(ex, ey);
|
|
6802
7064
|
ctx.stroke();
|
|
6803
|
-
ctx.
|
|
7065
|
+
ctx.setLineDash([]);
|
|
7066
|
+
ctx.fillStyle = color;
|
|
6804
7067
|
ctx.fillText(`${(ratio * 100).toFixed(1)}%`, Math.min(ex - 32, chartW - 40), ey - 3);
|
|
6805
7068
|
}
|
|
6806
7069
|
for (const [ax, ay] of [[x1, y1], [x2, y2]]) {
|
|
6807
7070
|
ctx.beginPath();
|
|
6808
7071
|
ctx.arc(ax, ay, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6809
|
-
ctx.fillStyle =
|
|
7072
|
+
ctx.fillStyle = s.strokeColor;
|
|
6810
7073
|
ctx.fill();
|
|
6811
7074
|
}
|
|
6812
7075
|
ctx.restore();
|
|
@@ -6845,7 +7108,7 @@ var FibFan = class {
|
|
|
6845
7108
|
// src/drawings/FibProjection.ts
|
|
6846
7109
|
var HIT_TOLERANCE16 = 6;
|
|
6847
7110
|
var FIB_PROJ_LEVELS = [0, 0.618, 1, 1.272, 1.618, 2, 2.618];
|
|
6848
|
-
var
|
|
7111
|
+
var FIB_PROJ_FALLBACK_COLORS = ["#ef5350", "#26a69a", "#2196f3", "#26a69a", "#e8b84b", "#ef5350", "#ef5350"];
|
|
6849
7112
|
var FibProjection = class {
|
|
6850
7113
|
constructor(first) {
|
|
6851
7114
|
this.type = "fibProjection";
|
|
@@ -6854,15 +7117,18 @@ var FibProjection = class {
|
|
|
6854
7117
|
this.points = [first, { ...first }];
|
|
6855
7118
|
this.complete = false;
|
|
6856
7119
|
}
|
|
6857
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
7120
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
6858
7121
|
const [p1, p2, p3] = this.points;
|
|
6859
7122
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
6860
7123
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
6861
7124
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
6862
7125
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
7126
|
+
const s = resolveStyle("fibProjection", this.style, theme);
|
|
7127
|
+
const anchorColor = s.strokeColor ?? "#e8b84b";
|
|
7128
|
+
const connectorColor = hexWithAlpha(s.strokeColor ?? "#888888", 0.67);
|
|
6863
7129
|
if (!p3) {
|
|
6864
7130
|
ctx.save();
|
|
6865
|
-
ctx.strokeStyle =
|
|
7131
|
+
ctx.strokeStyle = connectorColor;
|
|
6866
7132
|
ctx.lineWidth = 1;
|
|
6867
7133
|
ctx.setLineDash([4, 3]);
|
|
6868
7134
|
ctx.beginPath();
|
|
@@ -6870,7 +7136,7 @@ var FibProjection = class {
|
|
|
6870
7136
|
ctx.lineTo(x2, y2);
|
|
6871
7137
|
ctx.stroke();
|
|
6872
7138
|
ctx.setLineDash([]);
|
|
6873
|
-
ctx.fillStyle =
|
|
7139
|
+
ctx.fillStyle = anchorColor;
|
|
6874
7140
|
ctx.beginPath();
|
|
6875
7141
|
ctx.arc(x1, y1, 3, 0, Math.PI * 2);
|
|
6876
7142
|
ctx.fill();
|
|
@@ -6882,10 +7148,11 @@ var FibProjection = class {
|
|
|
6882
7148
|
const xMin = Math.min(x1, x3);
|
|
6883
7149
|
const xMax = Math.max(x1, x3);
|
|
6884
7150
|
const baseMove = p2.price - p1.price;
|
|
7151
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6885
7152
|
ctx.save();
|
|
6886
7153
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6887
|
-
ctx.lineWidth =
|
|
6888
|
-
ctx.strokeStyle =
|
|
7154
|
+
ctx.lineWidth = baseLineWidth;
|
|
7155
|
+
ctx.strokeStyle = connectorColor;
|
|
6889
7156
|
ctx.setLineDash([4, 3]);
|
|
6890
7157
|
ctx.beginPath();
|
|
6891
7158
|
ctx.moveTo(x1, y1);
|
|
@@ -6898,12 +7165,15 @@ var FibProjection = class {
|
|
|
6898
7165
|
ctx.setLineDash([]);
|
|
6899
7166
|
for (let i = 0; i < FIB_PROJ_LEVELS.length; i++) {
|
|
6900
7167
|
const level = FIB_PROJ_LEVELS[i];
|
|
7168
|
+
const key = String(level);
|
|
7169
|
+
const lvl = s.levels?.[key];
|
|
7170
|
+
if (lvl?.visible === false) continue;
|
|
6901
7171
|
const levelPrice = p3.price + level * baseMove;
|
|
6902
7172
|
const y = scale.yToPixel(levelPrice, priceRange, chartH);
|
|
6903
|
-
const
|
|
6904
|
-
const
|
|
6905
|
-
ctx.strokeStyle = color
|
|
6906
|
-
ctx
|
|
7173
|
+
const baseColor = lvl?.color ?? s.strokeColor ?? FIB_PROJ_FALLBACK_COLORS[i];
|
|
7174
|
+
const color = hexWithAlpha(baseColor, isSelected ? 0.8 : 0.6);
|
|
7175
|
+
ctx.strokeStyle = color;
|
|
7176
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6907
7177
|
ctx.beginPath();
|
|
6908
7178
|
ctx.moveTo(xMin, y);
|
|
6909
7179
|
ctx.lineTo(xMax, y);
|
|
@@ -6911,13 +7181,13 @@ var FibProjection = class {
|
|
|
6911
7181
|
ctx.setLineDash([]);
|
|
6912
7182
|
const pct = `${(level * 100).toFixed(1)}%`;
|
|
6913
7183
|
const lbl = `${pct} ${levelPrice.toFixed(pricePrecision)}`;
|
|
6914
|
-
ctx.fillStyle = color
|
|
7184
|
+
ctx.fillStyle = color;
|
|
6915
7185
|
ctx.fillText(lbl, xMin + 4, y - 3);
|
|
6916
7186
|
}
|
|
6917
7187
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3]]) {
|
|
6918
7188
|
ctx.beginPath();
|
|
6919
7189
|
ctx.arc(ax, ay, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6920
|
-
ctx.fillStyle =
|
|
7190
|
+
ctx.fillStyle = anchorColor;
|
|
6921
7191
|
ctx.fill();
|
|
6922
7192
|
}
|
|
6923
7193
|
ctx.restore();
|
|
@@ -6975,7 +7245,7 @@ var GannFan = class {
|
|
|
6975
7245
|
this.points = [first, { ...first }];
|
|
6976
7246
|
this.complete = false;
|
|
6977
7247
|
}
|
|
6978
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7248
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
6979
7249
|
if (this.points.length < 2) return;
|
|
6980
7250
|
const [p1, p2] = this.points;
|
|
6981
7251
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -6985,18 +7255,20 @@ var GannFan = class {
|
|
|
6985
7255
|
const baseWidth = Math.abs(x2 - x1) || 1;
|
|
6986
7256
|
const baseHeight = y2 - y1;
|
|
6987
7257
|
const dirX = x2 > x1 ? 1 : -1;
|
|
7258
|
+
const s = resolveStyle("gannFan", this.style, theme);
|
|
6988
7259
|
ctx.save();
|
|
6989
7260
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6990
|
-
ctx.lineWidth = isSelected ?
|
|
7261
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7262
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
6991
7263
|
GANN_RATIOS.forEach(([priceUnits, timeUnits, label], idx) => {
|
|
6992
7264
|
const scaleFactor = priceUnits / timeUnits;
|
|
6993
7265
|
const fanDY = baseHeight * scaleFactor;
|
|
6994
7266
|
const tMax = (chartW - x1) / (baseWidth * dirX);
|
|
6995
7267
|
const xEnd = x1 + baseWidth * dirX * tMax;
|
|
6996
7268
|
const yEnd = y1 + fanDY * tMax;
|
|
6997
|
-
const
|
|
6998
|
-
const
|
|
6999
|
-
const color =
|
|
7269
|
+
const alphas = [0.33, 0.4, 0.47, 0.6, 0.8, 0.6, 0.47, 0.4, 0.33];
|
|
7270
|
+
const a = alphas[idx] ?? 0.53;
|
|
7271
|
+
const color = hexWithAlpha(s.strokeColor, a);
|
|
7000
7272
|
ctx.strokeStyle = color;
|
|
7001
7273
|
ctx.beginPath();
|
|
7002
7274
|
ctx.moveTo(x1, y1);
|
|
@@ -7006,7 +7278,8 @@ var GannFan = class {
|
|
|
7006
7278
|
const labelX = Math.min(xEnd, chartW - 30);
|
|
7007
7279
|
ctx.fillText(label, labelX, yEnd - 3);
|
|
7008
7280
|
});
|
|
7009
|
-
ctx.
|
|
7281
|
+
ctx.setLineDash([]);
|
|
7282
|
+
ctx.fillStyle = s.strokeColor;
|
|
7010
7283
|
ctx.beginPath();
|
|
7011
7284
|
ctx.arc(x1, y1, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
7012
7285
|
ctx.fill();
|
|
@@ -7060,7 +7333,7 @@ var GannSquare = class {
|
|
|
7060
7333
|
this.points = [first, { ...first }];
|
|
7061
7334
|
this.complete = false;
|
|
7062
7335
|
}
|
|
7063
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7336
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7064
7337
|
if (this.points.length < 2) return;
|
|
7065
7338
|
const [p1, p2] = this.points;
|
|
7066
7339
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7073,10 +7346,12 @@ var GannSquare = class {
|
|
|
7073
7346
|
const bottom = Math.max(y1, y2);
|
|
7074
7347
|
const w = right - left;
|
|
7075
7348
|
const h = bottom - top;
|
|
7349
|
+
const s = resolveStyle("gannSquare", this.style, theme);
|
|
7076
7350
|
ctx.save();
|
|
7077
|
-
ctx.lineWidth = isSelected ?
|
|
7078
|
-
ctx.strokeStyle =
|
|
7079
|
-
ctx
|
|
7351
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7352
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7353
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7354
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7080
7355
|
ctx.fillRect(left, top, w, h);
|
|
7081
7356
|
ctx.strokeRect(left, top, w, h);
|
|
7082
7357
|
ctx.beginPath();
|
|
@@ -7091,6 +7366,7 @@ var GannSquare = class {
|
|
|
7091
7366
|
ctx.moveTo(left + 2 * w / 3, top);
|
|
7092
7367
|
ctx.lineTo(left + 2 * w / 3, bottom);
|
|
7093
7368
|
ctx.stroke();
|
|
7369
|
+
ctx.setLineDash([]);
|
|
7094
7370
|
ctx.restore();
|
|
7095
7371
|
}
|
|
7096
7372
|
hitTest(x, y, scale, viewport, priceRange, chartW, chartH) {
|
|
@@ -7130,7 +7406,7 @@ var GannBox = class {
|
|
|
7130
7406
|
this.points = [first, { ...first }];
|
|
7131
7407
|
this.complete = false;
|
|
7132
7408
|
}
|
|
7133
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7409
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7134
7410
|
if (this.points.length < 2) return;
|
|
7135
7411
|
const [p1, p2] = this.points;
|
|
7136
7412
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7145,10 +7421,12 @@ var GannBox = class {
|
|
|
7145
7421
|
const h = bottom - top;
|
|
7146
7422
|
const midX = left + w / 2;
|
|
7147
7423
|
const midY = top + h / 2;
|
|
7424
|
+
const s = resolveStyle("gannBox", this.style, theme);
|
|
7148
7425
|
ctx.save();
|
|
7149
|
-
ctx.lineWidth = isSelected ?
|
|
7150
|
-
ctx.strokeStyle =
|
|
7151
|
-
ctx
|
|
7426
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7427
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7428
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7429
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7152
7430
|
ctx.fillRect(left, top, w, h);
|
|
7153
7431
|
ctx.strokeRect(left, top, w, h);
|
|
7154
7432
|
ctx.beginPath();
|
|
@@ -7167,6 +7445,7 @@ var GannBox = class {
|
|
|
7167
7445
|
ctx.moveTo(midX, top);
|
|
7168
7446
|
ctx.lineTo(midX, bottom);
|
|
7169
7447
|
ctx.stroke();
|
|
7448
|
+
ctx.setLineDash([]);
|
|
7170
7449
|
ctx.restore();
|
|
7171
7450
|
}
|
|
7172
7451
|
hitTest(x, y, scale, viewport, priceRange, chartW, chartH) {
|
|
@@ -7206,7 +7485,7 @@ var Rectangle = class {
|
|
|
7206
7485
|
this.points = [first, { ...first }];
|
|
7207
7486
|
this.complete = false;
|
|
7208
7487
|
}
|
|
7209
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7488
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7210
7489
|
if (this.points.length < 2) return;
|
|
7211
7490
|
const [p1, p2] = this.points;
|
|
7212
7491
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7217,17 +7496,20 @@ var Rectangle = class {
|
|
|
7217
7496
|
const ry = Math.min(y1, y2);
|
|
7218
7497
|
const rw = Math.abs(x2 - x1);
|
|
7219
7498
|
const rh = Math.abs(y2 - y1);
|
|
7499
|
+
const s = resolveStyle("rectangle", this.style, theme);
|
|
7220
7500
|
ctx.save();
|
|
7221
|
-
ctx.fillStyle =
|
|
7222
|
-
ctx.strokeStyle =
|
|
7223
|
-
ctx.lineWidth = isSelected ?
|
|
7501
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7502
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7503
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7504
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7224
7505
|
ctx.fillRect(rx, ry, rw, rh);
|
|
7225
7506
|
ctx.strokeRect(rx, ry, rw, rh);
|
|
7507
|
+
ctx.setLineDash([]);
|
|
7226
7508
|
if (isSelected) {
|
|
7227
7509
|
for (const [cx, cy] of [[x1, y1], [x2, y1], [x1, y2], [x2, y2]]) {
|
|
7228
7510
|
ctx.beginPath();
|
|
7229
7511
|
ctx.arc(cx, cy, 3.5, 0, Math.PI * 2);
|
|
7230
|
-
ctx.fillStyle =
|
|
7512
|
+
ctx.fillStyle = s.strokeColor;
|
|
7231
7513
|
ctx.fill();
|
|
7232
7514
|
}
|
|
7233
7515
|
}
|
|
@@ -7309,9 +7591,10 @@ var RotatedRectangle = class {
|
|
|
7309
7591
|
const dx = ax + perpX * width, dy = ay + perpY * width;
|
|
7310
7592
|
return [[ax, ay], [bx, by], [cx, cy], [dx, dy]];
|
|
7311
7593
|
}
|
|
7312
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7594
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7313
7595
|
if (this.points.length < 3) return;
|
|
7314
7596
|
const corners = this.getCorners(scale, viewport, priceRange, chartW, chartH);
|
|
7597
|
+
const s = resolveStyle("rotatedRectangle", this.style, theme);
|
|
7315
7598
|
ctx.save();
|
|
7316
7599
|
ctx.beginPath();
|
|
7317
7600
|
ctx.moveTo(corners[0][0], corners[0][1]);
|
|
@@ -7319,12 +7602,14 @@ var RotatedRectangle = class {
|
|
|
7319
7602
|
ctx.lineTo(corners[i][0], corners[i][1]);
|
|
7320
7603
|
}
|
|
7321
7604
|
ctx.closePath();
|
|
7322
|
-
ctx.fillStyle =
|
|
7605
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7323
7606
|
ctx.fill();
|
|
7324
|
-
ctx.strokeStyle =
|
|
7325
|
-
ctx.lineWidth = isSelected ?
|
|
7607
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7608
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7609
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7326
7610
|
ctx.stroke();
|
|
7327
|
-
ctx.
|
|
7611
|
+
ctx.setLineDash([]);
|
|
7612
|
+
ctx.fillStyle = s.strokeColor;
|
|
7328
7613
|
for (const [x, y] of corners) {
|
|
7329
7614
|
ctx.beginPath();
|
|
7330
7615
|
ctx.arc(x, y, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -7358,7 +7643,7 @@ var Ellipse = class {
|
|
|
7358
7643
|
this.points = [first, { ...first }];
|
|
7359
7644
|
this.complete = false;
|
|
7360
7645
|
}
|
|
7361
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7646
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7362
7647
|
if (this.points.length < 2) return;
|
|
7363
7648
|
const [p1, p2] = this.points;
|
|
7364
7649
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7370,19 +7655,22 @@ var Ellipse = class {
|
|
|
7370
7655
|
const rx = Math.abs(x2 - x1) / 2;
|
|
7371
7656
|
const ry = Math.abs(y2 - y1) / 2;
|
|
7372
7657
|
if (rx < 1 || ry < 1) return;
|
|
7658
|
+
const s = resolveStyle("ellipse", this.style, theme);
|
|
7373
7659
|
ctx.save();
|
|
7374
|
-
ctx.fillStyle =
|
|
7375
|
-
ctx.strokeStyle =
|
|
7376
|
-
ctx.lineWidth = isSelected ?
|
|
7660
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7661
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7662
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7663
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7377
7664
|
ctx.beginPath();
|
|
7378
7665
|
ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
|
|
7379
7666
|
ctx.fill();
|
|
7380
7667
|
ctx.stroke();
|
|
7668
|
+
ctx.setLineDash([]);
|
|
7381
7669
|
if (isSelected) {
|
|
7382
7670
|
for (const [hx, hy] of [[x1, y1], [x2, y2]]) {
|
|
7383
7671
|
ctx.beginPath();
|
|
7384
7672
|
ctx.arc(hx, hy, 3.5, 0, Math.PI * 2);
|
|
7385
|
-
ctx.fillStyle =
|
|
7673
|
+
ctx.fillStyle = s.strokeColor;
|
|
7386
7674
|
ctx.fill();
|
|
7387
7675
|
}
|
|
7388
7676
|
}
|
|
@@ -7442,13 +7730,15 @@ var Triangle = class {
|
|
|
7442
7730
|
this.points = [first, { ...first }];
|
|
7443
7731
|
this.complete = false;
|
|
7444
7732
|
}
|
|
7445
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7733
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7446
7734
|
if (this.points.length < 2) return;
|
|
7447
7735
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
7448
7736
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
7737
|
+
const s = resolveStyle("triangle", this.style, theme);
|
|
7449
7738
|
ctx.save();
|
|
7450
|
-
ctx.strokeStyle =
|
|
7451
|
-
ctx.lineWidth = isSelected ?
|
|
7739
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7740
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7741
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7452
7742
|
ctx.beginPath();
|
|
7453
7743
|
ctx.moveTo(px[0], py[0]);
|
|
7454
7744
|
for (let i = 1; i < px.length; i++) {
|
|
@@ -7456,15 +7746,16 @@ var Triangle = class {
|
|
|
7456
7746
|
}
|
|
7457
7747
|
if (this.complete) {
|
|
7458
7748
|
ctx.closePath();
|
|
7459
|
-
ctx.fillStyle =
|
|
7749
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7460
7750
|
ctx.fill();
|
|
7461
7751
|
}
|
|
7462
7752
|
ctx.stroke();
|
|
7753
|
+
ctx.setLineDash([]);
|
|
7463
7754
|
if (isSelected) {
|
|
7464
7755
|
for (let i = 0; i < px.length; i++) {
|
|
7465
7756
|
ctx.beginPath();
|
|
7466
7757
|
ctx.arc(px[i], py[i], 4, 0, Math.PI * 2);
|
|
7467
|
-
ctx.fillStyle =
|
|
7758
|
+
ctx.fillStyle = s.strokeColor;
|
|
7468
7759
|
ctx.fill();
|
|
7469
7760
|
}
|
|
7470
7761
|
}
|
|
@@ -7503,7 +7794,7 @@ var Circle = class {
|
|
|
7503
7794
|
this.points = [first, { ...first }];
|
|
7504
7795
|
this.complete = false;
|
|
7505
7796
|
}
|
|
7506
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7797
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7507
7798
|
if (this.points.length < 2) return;
|
|
7508
7799
|
const [p1, p2] = this.points;
|
|
7509
7800
|
const cx = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7512,22 +7803,25 @@ var Circle = class {
|
|
|
7512
7803
|
const ey = scale.yToPixel(p2.price, priceRange, chartH);
|
|
7513
7804
|
const r = Math.hypot(ex - cx, ey - cy);
|
|
7514
7805
|
if (r < 1) return;
|
|
7806
|
+
const s = resolveStyle("circle", this.style, theme);
|
|
7515
7807
|
ctx.save();
|
|
7516
|
-
ctx.fillStyle =
|
|
7517
|
-
ctx.strokeStyle =
|
|
7518
|
-
ctx.lineWidth = isSelected ?
|
|
7808
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7809
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7810
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7811
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7519
7812
|
ctx.beginPath();
|
|
7520
7813
|
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
7521
7814
|
ctx.fill();
|
|
7522
7815
|
ctx.stroke();
|
|
7816
|
+
ctx.setLineDash([]);
|
|
7523
7817
|
if (isSelected) {
|
|
7524
7818
|
ctx.beginPath();
|
|
7525
7819
|
ctx.arc(cx, cy, 3, 0, Math.PI * 2);
|
|
7526
|
-
ctx.fillStyle =
|
|
7820
|
+
ctx.fillStyle = s.strokeColor;
|
|
7527
7821
|
ctx.fill();
|
|
7528
7822
|
ctx.beginPath();
|
|
7529
7823
|
ctx.arc(ex, ey, 3.5, 0, Math.PI * 2);
|
|
7530
|
-
ctx.fillStyle =
|
|
7824
|
+
ctx.fillStyle = s.strokeColor;
|
|
7531
7825
|
ctx.fill();
|
|
7532
7826
|
}
|
|
7533
7827
|
ctx.restore();
|
|
@@ -7576,7 +7870,7 @@ var Arc = class {
|
|
|
7576
7870
|
this.points = [first, { ...first }];
|
|
7577
7871
|
this.complete = false;
|
|
7578
7872
|
}
|
|
7579
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7873
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7580
7874
|
if (this.points.length < 3) return;
|
|
7581
7875
|
const [p1, p2, p3] = this.points;
|
|
7582
7876
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7585,9 +7879,11 @@ var Arc = class {
|
|
|
7585
7879
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
7586
7880
|
const x3 = scale.xToPixel(p3.barIndex, viewport, chartW);
|
|
7587
7881
|
const y3 = scale.yToPixel(p3.price, priceRange, chartH);
|
|
7882
|
+
const s = resolveStyle("arc", this.style, theme);
|
|
7588
7883
|
ctx.save();
|
|
7589
|
-
ctx.strokeStyle =
|
|
7590
|
-
ctx.lineWidth = isSelected ?
|
|
7884
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7885
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7886
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7591
7887
|
const ax = x1, ay = y1;
|
|
7592
7888
|
const bx = x2, by = y2;
|
|
7593
7889
|
const cx = x3, cy = y3;
|
|
@@ -7620,8 +7916,9 @@ var Arc = class {
|
|
|
7620
7916
|
ctx.arc(ux, uy, r, aStart, aEnd, anticlockwise);
|
|
7621
7917
|
ctx.stroke();
|
|
7622
7918
|
}
|
|
7919
|
+
ctx.setLineDash([]);
|
|
7623
7920
|
const anchors = [[x1, y1], [x2, y2], [x3, y3]];
|
|
7624
|
-
ctx.fillStyle =
|
|
7921
|
+
ctx.fillStyle = s.strokeColor;
|
|
7625
7922
|
for (const [x, y] of anchors) {
|
|
7626
7923
|
ctx.beginPath();
|
|
7627
7924
|
ctx.arc(x, y, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -7702,24 +7999,26 @@ var Polyline = class {
|
|
|
7702
7999
|
this.points = [first, { ...first }];
|
|
7703
8000
|
this.complete = false;
|
|
7704
8001
|
}
|
|
7705
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
8002
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7706
8003
|
if (this.points.length < 2) return;
|
|
7707
8004
|
const pixelPts = this.points.map((p) => ({
|
|
7708
8005
|
x: scale.xToPixel(p.barIndex, viewport, chartW),
|
|
7709
8006
|
y: scale.yToPixel(p.price, priceRange, chartH)
|
|
7710
8007
|
}));
|
|
8008
|
+
const s = resolveStyle("polyline", this.style, theme);
|
|
7711
8009
|
ctx.save();
|
|
7712
|
-
ctx.strokeStyle =
|
|
7713
|
-
ctx.lineWidth = isSelected ?
|
|
8010
|
+
ctx.strokeStyle = s.strokeColor;
|
|
8011
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7714
8012
|
const solidCount = this.complete ? pixelPts.length : pixelPts.length - 1;
|
|
7715
8013
|
if (solidCount >= 2) {
|
|
7716
|
-
ctx.
|
|
8014
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7717
8015
|
ctx.beginPath();
|
|
7718
8016
|
ctx.moveTo(pixelPts[0].x, pixelPts[0].y);
|
|
7719
8017
|
for (let i = 1; i < solidCount; i++) {
|
|
7720
8018
|
ctx.lineTo(pixelPts[i].x, pixelPts[i].y);
|
|
7721
8019
|
}
|
|
7722
8020
|
ctx.stroke();
|
|
8021
|
+
ctx.setLineDash([]);
|
|
7723
8022
|
}
|
|
7724
8023
|
if (!this.complete && pixelPts.length >= 2) {
|
|
7725
8024
|
const last = pixelPts.length - 1;
|
|
@@ -7730,7 +8029,7 @@ var Polyline = class {
|
|
|
7730
8029
|
ctx.stroke();
|
|
7731
8030
|
ctx.setLineDash([]);
|
|
7732
8031
|
}
|
|
7733
|
-
ctx.fillStyle =
|
|
8032
|
+
ctx.fillStyle = s.strokeColor;
|
|
7734
8033
|
const dotCount = this.complete ? pixelPts.length : pixelPts.length - 1;
|
|
7735
8034
|
for (let i = 0; i < dotCount; i++) {
|
|
7736
8035
|
ctx.beginPath();
|
|
@@ -7782,18 +8081,19 @@ var Path = class {
|
|
|
7782
8081
|
this.points = [first, { ...first }];
|
|
7783
8082
|
this.complete = false;
|
|
7784
8083
|
}
|
|
7785
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
8084
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7786
8085
|
if (this.points.length < 2) return;
|
|
7787
8086
|
const pixelPts = this.points.map((p) => ({
|
|
7788
8087
|
x: scale.xToPixel(p.barIndex, viewport, chartW),
|
|
7789
8088
|
y: scale.yToPixel(p.price, priceRange, chartH)
|
|
7790
8089
|
}));
|
|
7791
8090
|
const solidCount = this.complete ? pixelPts.length : pixelPts.length - 1;
|
|
8091
|
+
const s = resolveStyle("path", this.style, theme);
|
|
7792
8092
|
ctx.save();
|
|
7793
|
-
ctx.strokeStyle =
|
|
7794
|
-
ctx.lineWidth = isSelected ?
|
|
8093
|
+
ctx.strokeStyle = s.strokeColor;
|
|
8094
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7795
8095
|
if (solidCount >= 2) {
|
|
7796
|
-
ctx.
|
|
8096
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7797
8097
|
ctx.beginPath();
|
|
7798
8098
|
ctx.moveTo(pixelPts[0].x, pixelPts[0].y);
|
|
7799
8099
|
if (solidCount === 2) {
|
|
@@ -7807,6 +8107,7 @@ var Path = class {
|
|
|
7807
8107
|
ctx.lineTo(pixelPts[solidCount - 1].x, pixelPts[solidCount - 1].y);
|
|
7808
8108
|
}
|
|
7809
8109
|
ctx.stroke();
|
|
8110
|
+
ctx.setLineDash([]);
|
|
7810
8111
|
}
|
|
7811
8112
|
if (!this.complete && pixelPts.length >= 2) {
|
|
7812
8113
|
const last = pixelPts.length - 1;
|
|
@@ -7817,7 +8118,7 @@ var Path = class {
|
|
|
7817
8118
|
ctx.stroke();
|
|
7818
8119
|
ctx.setLineDash([]);
|
|
7819
8120
|
}
|
|
7820
|
-
ctx.fillStyle =
|
|
8121
|
+
ctx.fillStyle = s.strokeColor;
|
|
7821
8122
|
for (let i = 0; i < solidCount; i++) {
|
|
7822
8123
|
ctx.beginPath();
|
|
7823
8124
|
ctx.arc(pixelPts[i].x, pixelPts[i].y, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -7869,7 +8170,7 @@ var PriceRange = class {
|
|
|
7869
8170
|
this.points = [first, { ...first }];
|
|
7870
8171
|
this.complete = false;
|
|
7871
8172
|
}
|
|
7872
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
8173
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
7873
8174
|
if (this.points.length < 2) return;
|
|
7874
8175
|
const [p1, p2] = this.points;
|
|
7875
8176
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7881,11 +8182,13 @@ var PriceRange = class {
|
|
|
7881
8182
|
const topY = Math.min(y1, y2);
|
|
7882
8183
|
const botY = Math.max(y1, y2);
|
|
7883
8184
|
const tickHalf = 10;
|
|
7884
|
-
const
|
|
8185
|
+
const s = resolveStyle("priceRange", this.style, theme);
|
|
8186
|
+
const color = s.strokeColor;
|
|
7885
8187
|
ctx.save();
|
|
7886
8188
|
ctx.strokeStyle = color;
|
|
7887
8189
|
ctx.fillStyle = color;
|
|
7888
|
-
ctx.lineWidth = isSelected ?
|
|
8190
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8191
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7889
8192
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
7890
8193
|
ctx.beginPath();
|
|
7891
8194
|
ctx.moveTo(midX, topY);
|
|
@@ -7901,6 +8204,7 @@ var PriceRange = class {
|
|
|
7901
8204
|
ctx.moveTo(x2 - tickHalf, y2);
|
|
7902
8205
|
ctx.lineTo(x2 + tickHalf, y2);
|
|
7903
8206
|
ctx.stroke();
|
|
8207
|
+
ctx.setLineDash([]);
|
|
7904
8208
|
const priceDiff = Math.abs(p1.price - p2.price).toFixed(pricePrecision);
|
|
7905
8209
|
const pctChange = ((p2.price - p1.price) / p1.price * 100).toFixed(2) + "%";
|
|
7906
8210
|
const line1 = priceDiff;
|
|
@@ -7910,7 +8214,7 @@ var PriceRange = class {
|
|
|
7910
8214
|
const boxH = 30;
|
|
7911
8215
|
const boxX = midX + 8;
|
|
7912
8216
|
const boxY = midY - boxH / 2;
|
|
7913
|
-
ctx.fillStyle =
|
|
8217
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7914
8218
|
ctx.fillRect(boxX, boxY, boxW, boxH);
|
|
7915
8219
|
ctx.strokeStyle = color;
|
|
7916
8220
|
ctx.strokeRect(boxX, boxY, boxW, boxH);
|
|
@@ -7964,7 +8268,7 @@ var DateRange = class {
|
|
|
7964
8268
|
this.points = [first, { ...first }];
|
|
7965
8269
|
this.complete = false;
|
|
7966
8270
|
}
|
|
7967
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
8271
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7968
8272
|
if (this.points.length < 2) return;
|
|
7969
8273
|
const [p1, p2] = this.points;
|
|
7970
8274
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7975,11 +8279,13 @@ var DateRange = class {
|
|
|
7975
8279
|
const leftX = Math.min(x1, x2);
|
|
7976
8280
|
const rightX = Math.max(x1, x2);
|
|
7977
8281
|
const tickHalf = 10;
|
|
7978
|
-
const
|
|
8282
|
+
const s = resolveStyle("dateRange", this.style, theme);
|
|
8283
|
+
const color = s.strokeColor;
|
|
7979
8284
|
ctx.save();
|
|
7980
8285
|
ctx.strokeStyle = color;
|
|
7981
8286
|
ctx.fillStyle = color;
|
|
7982
|
-
ctx.lineWidth = isSelected ?
|
|
8287
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8288
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7983
8289
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
7984
8290
|
ctx.beginPath();
|
|
7985
8291
|
ctx.moveTo(leftX, midY);
|
|
@@ -7995,6 +8301,7 @@ var DateRange = class {
|
|
|
7995
8301
|
ctx.moveTo(x2, y2 - tickHalf);
|
|
7996
8302
|
ctx.lineTo(x2, y2 + tickHalf);
|
|
7997
8303
|
ctx.stroke();
|
|
8304
|
+
ctx.setLineDash([]);
|
|
7998
8305
|
const barCount = Math.abs(p2.barIndex - p1.barIndex);
|
|
7999
8306
|
const label = `${barCount} bars`;
|
|
8000
8307
|
const midX = (x1 + x2) / 2;
|
|
@@ -8048,7 +8355,7 @@ var DatePriceRange = class {
|
|
|
8048
8355
|
this.points = [first, { ...first }];
|
|
8049
8356
|
this.complete = false;
|
|
8050
8357
|
}
|
|
8051
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
8358
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
8052
8359
|
if (this.points.length < 2) return;
|
|
8053
8360
|
const [p1, p2] = this.points;
|
|
8054
8361
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -8061,14 +8368,15 @@ var DatePriceRange = class {
|
|
|
8061
8368
|
const bottom = Math.max(y1, y2);
|
|
8062
8369
|
const w = right - left;
|
|
8063
8370
|
const h = bottom - top;
|
|
8064
|
-
const
|
|
8371
|
+
const s = resolveStyle("datePriceRange", this.style, theme);
|
|
8372
|
+
const color = s.strokeColor;
|
|
8065
8373
|
ctx.save();
|
|
8066
8374
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
8067
|
-
ctx.lineWidth = isSelected ?
|
|
8068
|
-
ctx.fillStyle =
|
|
8375
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8376
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
8069
8377
|
ctx.fillRect(left, top, w, h);
|
|
8070
8378
|
ctx.strokeStyle = color;
|
|
8071
|
-
ctx.
|
|
8379
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8072
8380
|
ctx.strokeRect(left, top, w, h);
|
|
8073
8381
|
ctx.setLineDash([]);
|
|
8074
8382
|
ctx.strokeStyle = color;
|
|
@@ -8132,27 +8440,28 @@ var Ruler = class {
|
|
|
8132
8440
|
this.points = [first, { ...first }];
|
|
8133
8441
|
this.complete = false;
|
|
8134
8442
|
}
|
|
8135
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
8443
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
8136
8444
|
if (this.points.length < 2) return;
|
|
8137
8445
|
const [p1, p2] = this.points;
|
|
8138
8446
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
8139
8447
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
8140
8448
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
8141
8449
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
8142
|
-
const
|
|
8450
|
+
const s = resolveStyle("ruler", this.style, theme);
|
|
8451
|
+
const color = s.strokeColor;
|
|
8143
8452
|
const angle = Math.atan2(y2 - y1, x2 - x1);
|
|
8144
8453
|
const perpAngle = angle + Math.PI / 2;
|
|
8145
8454
|
const tickLen = 8;
|
|
8146
8455
|
ctx.save();
|
|
8147
8456
|
ctx.strokeStyle = color;
|
|
8148
8457
|
ctx.fillStyle = color;
|
|
8149
|
-
ctx.lineWidth =
|
|
8458
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8459
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8150
8460
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
8151
8461
|
ctx.beginPath();
|
|
8152
8462
|
ctx.moveTo(x1, y1);
|
|
8153
8463
|
ctx.lineTo(x2, y2);
|
|
8154
8464
|
ctx.stroke();
|
|
8155
|
-
ctx.lineWidth = isSelected ? 1.5 : 1;
|
|
8156
8465
|
ctx.beginPath();
|
|
8157
8466
|
ctx.moveTo(x1 - tickLen * Math.cos(perpAngle), y1 - tickLen * Math.sin(perpAngle));
|
|
8158
8467
|
ctx.lineTo(x1 + tickLen * Math.cos(perpAngle), y1 + tickLen * Math.sin(perpAngle));
|
|
@@ -8161,6 +8470,7 @@ var Ruler = class {
|
|
|
8161
8470
|
ctx.moveTo(x2 - tickLen * Math.cos(perpAngle), y2 - tickLen * Math.sin(perpAngle));
|
|
8162
8471
|
ctx.lineTo(x2 + tickLen * Math.cos(perpAngle), y2 + tickLen * Math.sin(perpAngle));
|
|
8163
8472
|
ctx.stroke();
|
|
8473
|
+
ctx.setLineDash([]);
|
|
8164
8474
|
const midX = (x1 + x2) / 2;
|
|
8165
8475
|
const midY = (y1 + y2) / 2;
|
|
8166
8476
|
const barDiff = Math.abs(p2.barIndex - p1.barIndex);
|
|
@@ -8226,16 +8536,18 @@ var Pitchfork = class {
|
|
|
8226
8536
|
this.points = [first, { ...first }];
|
|
8227
8537
|
this.complete = false;
|
|
8228
8538
|
}
|
|
8229
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
8539
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8230
8540
|
if (this.points.length < 2) return;
|
|
8231
8541
|
const [p1, p2, p3] = this.points;
|
|
8232
8542
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
8233
8543
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
8234
8544
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
8235
8545
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
8546
|
+
const s = resolveStyle("pitchfork", this.style, theme);
|
|
8236
8547
|
ctx.save();
|
|
8237
|
-
ctx.strokeStyle =
|
|
8238
|
-
ctx.lineWidth = isSelected ?
|
|
8548
|
+
ctx.strokeStyle = s.strokeColor;
|
|
8549
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8550
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8239
8551
|
if (p3) {
|
|
8240
8552
|
const x3 = scale.xToPixel(p3.barIndex, viewport, chartW);
|
|
8241
8553
|
const y3 = scale.yToPixel(p3.price, priceRange, chartH);
|
|
@@ -8262,8 +8574,9 @@ var Pitchfork = class {
|
|
|
8262
8574
|
ctx.moveTo(x3, y3);
|
|
8263
8575
|
ctx.lineTo(tine2EndX, tine2EndY);
|
|
8264
8576
|
ctx.stroke();
|
|
8577
|
+
ctx.setLineDash([]);
|
|
8265
8578
|
if (isSelected) {
|
|
8266
|
-
ctx.fillStyle =
|
|
8579
|
+
ctx.fillStyle = s.strokeColor;
|
|
8267
8580
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3]]) {
|
|
8268
8581
|
ctx.beginPath();
|
|
8269
8582
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -8275,6 +8588,7 @@ var Pitchfork = class {
|
|
|
8275
8588
|
ctx.moveTo(x1, y1);
|
|
8276
8589
|
ctx.lineTo(x2, y2);
|
|
8277
8590
|
ctx.stroke();
|
|
8591
|
+
ctx.setLineDash([]);
|
|
8278
8592
|
}
|
|
8279
8593
|
ctx.restore();
|
|
8280
8594
|
}
|
|
@@ -8336,16 +8650,18 @@ var SchiffPitchfork = class {
|
|
|
8336
8650
|
this.points = [first, { ...first }];
|
|
8337
8651
|
this.complete = false;
|
|
8338
8652
|
}
|
|
8339
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
8653
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8340
8654
|
if (this.points.length < 2) return;
|
|
8341
8655
|
const [p1, p2, p3] = this.points;
|
|
8342
8656
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
8343
8657
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
8344
8658
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
8345
8659
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
8660
|
+
const s = resolveStyle("schiffPitchfork", this.style, theme);
|
|
8346
8661
|
ctx.save();
|
|
8347
|
-
ctx.strokeStyle =
|
|
8348
|
-
ctx.lineWidth = isSelected ?
|
|
8662
|
+
ctx.strokeStyle = s.strokeColor;
|
|
8663
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8664
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8349
8665
|
if (p3) {
|
|
8350
8666
|
const x3 = scale.xToPixel(p3.barIndex, viewport, chartW);
|
|
8351
8667
|
const y3 = scale.yToPixel(p3.price, priceRange, chartH);
|
|
@@ -8374,8 +8690,9 @@ var SchiffPitchfork = class {
|
|
|
8374
8690
|
ctx.moveTo(x3, y3);
|
|
8375
8691
|
ctx.lineTo(tine2EndX, tine2EndY);
|
|
8376
8692
|
ctx.stroke();
|
|
8693
|
+
ctx.setLineDash([]);
|
|
8377
8694
|
if (isSelected) {
|
|
8378
|
-
ctx.fillStyle =
|
|
8695
|
+
ctx.fillStyle = s.strokeColor;
|
|
8379
8696
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3]]) {
|
|
8380
8697
|
ctx.beginPath();
|
|
8381
8698
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -8387,6 +8704,7 @@ var SchiffPitchfork = class {
|
|
|
8387
8704
|
ctx.moveTo(x1, y1);
|
|
8388
8705
|
ctx.lineTo(x2, y2);
|
|
8389
8706
|
ctx.stroke();
|
|
8707
|
+
ctx.setLineDash([]);
|
|
8390
8708
|
}
|
|
8391
8709
|
ctx.restore();
|
|
8392
8710
|
}
|
|
@@ -8450,16 +8768,18 @@ var ModifiedSchiff = class {
|
|
|
8450
8768
|
this.points = [first, { ...first }];
|
|
8451
8769
|
this.complete = false;
|
|
8452
8770
|
}
|
|
8453
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
8771
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8454
8772
|
if (this.points.length < 2) return;
|
|
8455
8773
|
const [p1, p2, p3] = this.points;
|
|
8456
8774
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
8457
8775
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
8458
8776
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
8459
8777
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
8778
|
+
const s = resolveStyle("modifiedSchiff", this.style, theme);
|
|
8460
8779
|
ctx.save();
|
|
8461
|
-
ctx.strokeStyle =
|
|
8462
|
-
ctx.lineWidth = isSelected ?
|
|
8780
|
+
ctx.strokeStyle = s.strokeColor;
|
|
8781
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8782
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8463
8783
|
if (p3) {
|
|
8464
8784
|
const x3 = scale.xToPixel(p3.barIndex, viewport, chartW);
|
|
8465
8785
|
const y3 = scale.yToPixel(p3.price, priceRange, chartH);
|
|
@@ -8488,8 +8808,9 @@ var ModifiedSchiff = class {
|
|
|
8488
8808
|
ctx.moveTo(x3, y3);
|
|
8489
8809
|
ctx.lineTo(tine2EndX, tine2EndY);
|
|
8490
8810
|
ctx.stroke();
|
|
8811
|
+
ctx.setLineDash([]);
|
|
8491
8812
|
if (isSelected) {
|
|
8492
|
-
ctx.fillStyle =
|
|
8813
|
+
ctx.fillStyle = s.strokeColor;
|
|
8493
8814
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3]]) {
|
|
8494
8815
|
ctx.beginPath();
|
|
8495
8816
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -8501,6 +8822,7 @@ var ModifiedSchiff = class {
|
|
|
8501
8822
|
ctx.moveTo(x1, y1);
|
|
8502
8823
|
ctx.lineTo(x2, y2);
|
|
8503
8824
|
ctx.stroke();
|
|
8825
|
+
ctx.setLineDash([]);
|
|
8504
8826
|
}
|
|
8505
8827
|
ctx.restore();
|
|
8506
8828
|
}
|
|
@@ -8564,16 +8886,18 @@ var InsidePitchfork = class {
|
|
|
8564
8886
|
this.points = [first, { ...first }];
|
|
8565
8887
|
this.complete = false;
|
|
8566
8888
|
}
|
|
8567
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
8889
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8568
8890
|
if (this.points.length < 2) return;
|
|
8569
8891
|
const [p1, p2, p3] = this.points;
|
|
8570
8892
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
8571
8893
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
8572
8894
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
8573
8895
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
8896
|
+
const s = resolveStyle("insidePitchfork", this.style, theme);
|
|
8574
8897
|
ctx.save();
|
|
8575
|
-
ctx.strokeStyle =
|
|
8576
|
-
ctx.lineWidth = isSelected ?
|
|
8898
|
+
ctx.strokeStyle = s.strokeColor;
|
|
8899
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8900
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8577
8901
|
if (p3) {
|
|
8578
8902
|
const x3 = scale.xToPixel(p3.barIndex, viewport, chartW);
|
|
8579
8903
|
const y3 = scale.yToPixel(p3.price, priceRange, chartH);
|
|
@@ -8619,7 +8943,7 @@ var InsidePitchfork = class {
|
|
|
8619
8943
|
ctx.setLineDash([]);
|
|
8620
8944
|
ctx.globalAlpha = 1;
|
|
8621
8945
|
if (isSelected) {
|
|
8622
|
-
ctx.fillStyle =
|
|
8946
|
+
ctx.fillStyle = s.strokeColor;
|
|
8623
8947
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3]]) {
|
|
8624
8948
|
ctx.beginPath();
|
|
8625
8949
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -8631,6 +8955,7 @@ var InsidePitchfork = class {
|
|
|
8631
8955
|
ctx.moveTo(x1, y1);
|
|
8632
8956
|
ctx.lineTo(x2, y2);
|
|
8633
8957
|
ctx.stroke();
|
|
8958
|
+
ctx.setLineDash([]);
|
|
8634
8959
|
}
|
|
8635
8960
|
ctx.restore();
|
|
8636
8961
|
}
|
|
@@ -8680,7 +9005,6 @@ var InsidePitchfork = class {
|
|
|
8680
9005
|
|
|
8681
9006
|
// src/drawings/FixedRangeVP.ts
|
|
8682
9007
|
var HIT_TOLERANCE36 = 6;
|
|
8683
|
-
var COLOR = "#64b5f6";
|
|
8684
9008
|
var BAR_WEIGHTS = [0.35, 0.55, 0.75, 1, 1, 0.75, 0.55, 0.35];
|
|
8685
9009
|
var FixedRangeVP = class {
|
|
8686
9010
|
constructor(first) {
|
|
@@ -8689,7 +9013,7 @@ var FixedRangeVP = class {
|
|
|
8689
9013
|
this.points = [first, { ...first }];
|
|
8690
9014
|
this.complete = false;
|
|
8691
9015
|
}
|
|
8692
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9016
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8693
9017
|
if (this.points.length < 2) return;
|
|
8694
9018
|
const [p1, p2] = this.points;
|
|
8695
9019
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -8700,27 +9024,31 @@ var FixedRangeVP = class {
|
|
|
8700
9024
|
const ry = Math.min(y1, y2);
|
|
8701
9025
|
const rw = Math.abs(x2 - x1);
|
|
8702
9026
|
const rh = Math.abs(y2 - y1);
|
|
9027
|
+
const s = resolveStyle("fixedRangeVP", this.style, theme);
|
|
8703
9028
|
ctx.save();
|
|
8704
|
-
ctx.strokeStyle =
|
|
8705
|
-
ctx.lineWidth = isSelected ?
|
|
9029
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9030
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9031
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8706
9032
|
ctx.strokeRect(rx, ry, rw, rh);
|
|
9033
|
+
ctx.setLineDash([]);
|
|
8707
9034
|
const numBars = BAR_WEIGHTS.length;
|
|
8708
9035
|
const barH = rh / numBars;
|
|
8709
9036
|
const maxBarW = rw * 0.6;
|
|
9037
|
+
const barFill = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
8710
9038
|
for (let i = 0; i < numBars; i++) {
|
|
8711
9039
|
const barW = maxBarW * BAR_WEIGHTS[i];
|
|
8712
9040
|
const barY = ry + i * barH;
|
|
8713
|
-
ctx.fillStyle =
|
|
9041
|
+
ctx.fillStyle = barFill;
|
|
8714
9042
|
ctx.fillRect(rx, barY, barW, barH - 1);
|
|
8715
9043
|
}
|
|
8716
9044
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
8717
|
-
ctx.fillStyle =
|
|
9045
|
+
ctx.fillStyle = s.strokeColor;
|
|
8718
9046
|
ctx.fillText("Volume Profile", rx + 4, ry + 13);
|
|
8719
9047
|
if (isSelected) {
|
|
8720
9048
|
for (const [cx, cy] of [[x1, y1], [x2, y2]]) {
|
|
8721
9049
|
ctx.beginPath();
|
|
8722
9050
|
ctx.arc(cx, cy, 3.5, 0, Math.PI * 2);
|
|
8723
|
-
ctx.fillStyle =
|
|
9051
|
+
ctx.fillStyle = s.strokeColor;
|
|
8724
9052
|
ctx.fill();
|
|
8725
9053
|
}
|
|
8726
9054
|
}
|
|
@@ -8756,7 +9084,6 @@ var FixedRangeVP = class {
|
|
|
8756
9084
|
|
|
8757
9085
|
// src/drawings/AnchoredVP.ts
|
|
8758
9086
|
var HIT_TOLERANCE37 = 6;
|
|
8759
|
-
var COLOR2 = "#4db6ac";
|
|
8760
9087
|
var BAR_WEIGHTS2 = [0.35, 0.55, 0.75, 1, 1, 0.75, 0.55, 0.35];
|
|
8761
9088
|
var AnchoredVP = class {
|
|
8762
9089
|
constructor(first) {
|
|
@@ -8765,7 +9092,7 @@ var AnchoredVP = class {
|
|
|
8765
9092
|
this.points = [first, { ...first }];
|
|
8766
9093
|
this.complete = false;
|
|
8767
9094
|
}
|
|
8768
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9095
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8769
9096
|
if (this.points.length < 2) return;
|
|
8770
9097
|
const [p1, p2] = this.points;
|
|
8771
9098
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -8776,31 +9103,35 @@ var AnchoredVP = class {
|
|
|
8776
9103
|
const ry = Math.min(y1, y2);
|
|
8777
9104
|
const rw = Math.abs(x2 - x1);
|
|
8778
9105
|
const rh = Math.abs(y2 - y1);
|
|
9106
|
+
const s = resolveStyle("anchoredVP", this.style, theme);
|
|
8779
9107
|
ctx.save();
|
|
8780
|
-
ctx.strokeStyle =
|
|
8781
|
-
ctx.lineWidth = isSelected ?
|
|
9108
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9109
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9110
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8782
9111
|
ctx.strokeRect(rx, ry, rw, rh);
|
|
9112
|
+
ctx.setLineDash([]);
|
|
8783
9113
|
const numBars = BAR_WEIGHTS2.length;
|
|
8784
9114
|
const barH = rh / numBars;
|
|
8785
9115
|
const maxBarW = rw * 0.6;
|
|
9116
|
+
const barFill = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
8786
9117
|
for (let i = 0; i < numBars; i++) {
|
|
8787
9118
|
const barW = maxBarW * BAR_WEIGHTS2[i];
|
|
8788
9119
|
const barY = ry + i * barH;
|
|
8789
|
-
ctx.fillStyle =
|
|
9120
|
+
ctx.fillStyle = barFill;
|
|
8790
9121
|
ctx.fillRect(rx, barY, barW, barH - 1);
|
|
8791
9122
|
}
|
|
8792
9123
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
8793
|
-
ctx.fillStyle =
|
|
9124
|
+
ctx.fillStyle = s.strokeColor;
|
|
8794
9125
|
ctx.fillText("Anchored VP", rx + 4, ry + 13);
|
|
8795
9126
|
ctx.beginPath();
|
|
8796
9127
|
ctx.arc(x1, y1, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
8797
|
-
ctx.fillStyle =
|
|
9128
|
+
ctx.fillStyle = s.strokeColor;
|
|
8798
9129
|
ctx.fill();
|
|
8799
9130
|
if (isSelected) {
|
|
8800
9131
|
for (const [cx, cy] of [[x1, y1], [x2, y2]]) {
|
|
8801
9132
|
ctx.beginPath();
|
|
8802
9133
|
ctx.arc(cx, cy, 3.5, 0, Math.PI * 2);
|
|
8803
|
-
ctx.fillStyle =
|
|
9134
|
+
ctx.fillStyle = s.strokeColor;
|
|
8804
9135
|
ctx.fill();
|
|
8805
9136
|
}
|
|
8806
9137
|
}
|
|
@@ -8836,7 +9167,6 @@ var AnchoredVP = class {
|
|
|
8836
9167
|
|
|
8837
9168
|
// src/drawings/VolumeProfile.ts
|
|
8838
9169
|
var HIT_TOLERANCE38 = 6;
|
|
8839
|
-
var COLOR3 = "#26a69a";
|
|
8840
9170
|
var BAR_WEIGHTS3 = [0.25, 0.45, 0.65, 0.85, 1, 1, 0.85, 0.65, 0.45, 0.25];
|
|
8841
9171
|
var VolumeProfile = class {
|
|
8842
9172
|
constructor(first) {
|
|
@@ -8845,7 +9175,7 @@ var VolumeProfile = class {
|
|
|
8845
9175
|
this.points = [first, { ...first }];
|
|
8846
9176
|
this.complete = false;
|
|
8847
9177
|
}
|
|
8848
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9178
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8849
9179
|
if (this.points.length < 2) return;
|
|
8850
9180
|
const [p1, p2] = this.points;
|
|
8851
9181
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -8856,27 +9186,31 @@ var VolumeProfile = class {
|
|
|
8856
9186
|
const ry = Math.min(y1, y2);
|
|
8857
9187
|
const rw = Math.abs(x2 - x1);
|
|
8858
9188
|
const rh = Math.abs(y2 - y1);
|
|
9189
|
+
const s = resolveStyle("volumeProfile", this.style, theme);
|
|
8859
9190
|
ctx.save();
|
|
8860
|
-
ctx.strokeStyle =
|
|
8861
|
-
ctx.lineWidth = isSelected ?
|
|
9191
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9192
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9193
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8862
9194
|
ctx.strokeRect(rx, ry, rw, rh);
|
|
9195
|
+
ctx.setLineDash([]);
|
|
8863
9196
|
const numBars = BAR_WEIGHTS3.length;
|
|
8864
9197
|
const barH = rh / numBars;
|
|
8865
9198
|
const maxBarW = rw * 0.6;
|
|
9199
|
+
const barFill = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
8866
9200
|
for (let i = 0; i < numBars; i++) {
|
|
8867
9201
|
const barW = maxBarW * BAR_WEIGHTS3[i];
|
|
8868
9202
|
const barY = ry + i * barH;
|
|
8869
|
-
ctx.fillStyle =
|
|
9203
|
+
ctx.fillStyle = barFill;
|
|
8870
9204
|
ctx.fillRect(rx, barY, barW, barH - 1);
|
|
8871
9205
|
}
|
|
8872
9206
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
8873
|
-
ctx.fillStyle =
|
|
9207
|
+
ctx.fillStyle = s.strokeColor;
|
|
8874
9208
|
ctx.fillText("Volume Profile", rx + 4, ry + 13);
|
|
8875
9209
|
if (isSelected) {
|
|
8876
9210
|
for (const [cx, cy] of [[x1, y1], [x2, y2]]) {
|
|
8877
9211
|
ctx.beginPath();
|
|
8878
9212
|
ctx.arc(cx, cy, 3.5, 0, Math.PI * 2);
|
|
8879
|
-
ctx.fillStyle =
|
|
9213
|
+
ctx.fillStyle = s.strokeColor;
|
|
8880
9214
|
ctx.fill();
|
|
8881
9215
|
}
|
|
8882
9216
|
}
|
|
@@ -8926,27 +9260,29 @@ var RegressionTrend = class {
|
|
|
8926
9260
|
this.points = [first, { ...first }];
|
|
8927
9261
|
this.complete = false;
|
|
8928
9262
|
}
|
|
8929
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9263
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8930
9264
|
if (this.points.length < 2) return;
|
|
8931
9265
|
const [p1, p2] = this.points;
|
|
8932
9266
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
8933
9267
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
8934
9268
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
8935
9269
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
8936
|
-
const
|
|
9270
|
+
const s = resolveStyle("regressionTrend", this.style, theme);
|
|
9271
|
+
const channelColor = hexWithAlpha(s.strokeColor, 0.4);
|
|
8937
9272
|
const lineLen = Math.hypot(x2 - x1, y2 - y1) || 1;
|
|
8938
9273
|
const perpX = -(y2 - y1) / lineLen;
|
|
8939
9274
|
const perpY = (x2 - x1) / lineLen;
|
|
8940
9275
|
const offset = 20;
|
|
8941
9276
|
ctx.save();
|
|
8942
|
-
ctx.strokeStyle =
|
|
8943
|
-
ctx.fillStyle =
|
|
8944
|
-
ctx.lineWidth = isSelected ?
|
|
8945
|
-
ctx.
|
|
9277
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9278
|
+
ctx.fillStyle = s.strokeColor;
|
|
9279
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9280
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8946
9281
|
ctx.beginPath();
|
|
8947
9282
|
ctx.moveTo(x1, y1);
|
|
8948
9283
|
ctx.lineTo(x2, y2);
|
|
8949
9284
|
ctx.stroke();
|
|
9285
|
+
ctx.strokeStyle = channelColor;
|
|
8950
9286
|
ctx.setLineDash([4, 3]);
|
|
8951
9287
|
ctx.beginPath();
|
|
8952
9288
|
ctx.moveTo(x1 + perpX * offset, y1 + perpY * offset);
|
|
@@ -9001,11 +9337,14 @@ var Text = class {
|
|
|
9001
9337
|
this.points = [point];
|
|
9002
9338
|
this.complete = true;
|
|
9003
9339
|
}
|
|
9004
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9340
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9005
9341
|
const x = scale.xToPixel(this.points[0].barIndex, viewport, chartW);
|
|
9006
9342
|
const y = scale.yToPixel(this.points[0].price, priceRange, chartH);
|
|
9343
|
+
const s = resolveStyle("text", this.style, theme);
|
|
9344
|
+
const fontSize = s.fontSize ?? 13;
|
|
9345
|
+
const textColor = s.textColor ?? "#f5f5f5";
|
|
9007
9346
|
ctx.save();
|
|
9008
|
-
ctx.font =
|
|
9347
|
+
ctx.font = `${fontSize}px system-ui, sans-serif`;
|
|
9009
9348
|
ctx.textBaseline = "top";
|
|
9010
9349
|
const label = this.text || PLACEHOLDER;
|
|
9011
9350
|
const isPlaceholder = !this.text;
|
|
@@ -9015,10 +9354,10 @@ var Text = class {
|
|
|
9015
9354
|
ctx.lineWidth = 1;
|
|
9016
9355
|
ctx.strokeRect(x - 2, y - 2, textW + PAD * 2, 16 + 4);
|
|
9017
9356
|
}
|
|
9018
|
-
ctx.fillStyle = isPlaceholder ? "#888" :
|
|
9357
|
+
ctx.fillStyle = isPlaceholder ? "#888" : textColor;
|
|
9019
9358
|
ctx.fillText(label, x + PAD, y);
|
|
9020
9359
|
if (isSelected) {
|
|
9021
|
-
ctx.strokeStyle =
|
|
9360
|
+
ctx.strokeStyle = textColor;
|
|
9022
9361
|
ctx.lineWidth = 1;
|
|
9023
9362
|
ctx.beginPath();
|
|
9024
9363
|
ctx.moveTo(x, y + 16);
|
|
@@ -9075,16 +9414,20 @@ var Callout = class {
|
|
|
9075
9414
|
const label = this.text || "Note";
|
|
9076
9415
|
return Math.max(60, ctx.measureText(label).width + PAD_H * 2);
|
|
9077
9416
|
}
|
|
9078
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9417
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9079
9418
|
if (this.points.length < 2) return;
|
|
9080
9419
|
const [p1, p2] = this.points;
|
|
9081
9420
|
const tipX = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
9082
9421
|
const tipY = scale.yToPixel(p1.price, priceRange, chartH);
|
|
9083
9422
|
const boxX = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
9084
9423
|
const boxY = scale.yToPixel(p2.price, priceRange, chartH);
|
|
9424
|
+
const s = resolveStyle("callout", this.style, theme);
|
|
9425
|
+
const fontSize = s.fontSize ?? 11;
|
|
9426
|
+
const textColor = s.textColor ?? "#f5f5f5";
|
|
9085
9427
|
ctx.save();
|
|
9086
|
-
ctx.font =
|
|
9087
|
-
ctx.lineWidth = isSelected ?
|
|
9428
|
+
ctx.font = `${fontSize}px system-ui, sans-serif`;
|
|
9429
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9430
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9088
9431
|
const boxW = this.getBoxW(ctx);
|
|
9089
9432
|
const bx = boxX - boxW / 2;
|
|
9090
9433
|
const by = boxY - BOX_H / 2;
|
|
@@ -9092,23 +9435,24 @@ var Callout = class {
|
|
|
9092
9435
|
const clampedY = Math.max(by, Math.min(by + BOX_H, tipY));
|
|
9093
9436
|
const edgeX = tipX < bx ? bx : tipX > bx + boxW ? bx + boxW : clampedX;
|
|
9094
9437
|
const edgeY = tipY < by ? by : tipY > by + BOX_H ? by + BOX_H : clampedY;
|
|
9095
|
-
ctx.strokeStyle =
|
|
9438
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9096
9439
|
ctx.beginPath();
|
|
9097
9440
|
ctx.moveTo(edgeX, edgeY);
|
|
9098
9441
|
ctx.lineTo(tipX, tipY);
|
|
9099
9442
|
ctx.stroke();
|
|
9100
|
-
ctx.
|
|
9443
|
+
ctx.setLineDash([]);
|
|
9444
|
+
ctx.fillStyle = s.strokeColor;
|
|
9101
9445
|
ctx.beginPath();
|
|
9102
9446
|
ctx.arc(tipX, tipY, 3, 0, Math.PI * 2);
|
|
9103
9447
|
ctx.fill();
|
|
9104
9448
|
roundedRect2(ctx, bx, by, boxW, BOX_H, RADIUS);
|
|
9105
|
-
ctx.fillStyle =
|
|
9449
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9106
9450
|
ctx.fill();
|
|
9107
|
-
ctx.strokeStyle =
|
|
9451
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9108
9452
|
ctx.stroke();
|
|
9109
9453
|
const label = this.text || "Note";
|
|
9110
9454
|
const isPlaceholder = !this.text;
|
|
9111
|
-
ctx.fillStyle = isPlaceholder ? "#888" :
|
|
9455
|
+
ctx.fillStyle = isPlaceholder ? "#888" : textColor;
|
|
9112
9456
|
ctx.textBaseline = "middle";
|
|
9113
9457
|
ctx.textAlign = "center";
|
|
9114
9458
|
ctx.fillText(label, boxX, boxY);
|
|
@@ -9162,12 +9506,15 @@ var PriceLabel = class {
|
|
|
9162
9506
|
this.points = [point];
|
|
9163
9507
|
this.complete = true;
|
|
9164
9508
|
}
|
|
9165
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
9509
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
9166
9510
|
const x = scale.xToPixel(this.points[0].barIndex, viewport, chartW);
|
|
9167
9511
|
const y = scale.yToPixel(this.points[0].price, priceRange, chartH);
|
|
9168
9512
|
const price = this.points[0].price;
|
|
9513
|
+
const s = resolveStyle("priceLabel", this.style, theme);
|
|
9514
|
+
const fontSize = s.fontSize ?? 11;
|
|
9515
|
+
const textColor = s.textColor ?? "#ffffff";
|
|
9169
9516
|
ctx.save();
|
|
9170
|
-
ctx.fillStyle =
|
|
9517
|
+
ctx.fillStyle = s.fillColor;
|
|
9171
9518
|
ctx.beginPath();
|
|
9172
9519
|
ctx.moveTo(x, y);
|
|
9173
9520
|
ctx.lineTo(x + ARROW_W, y - BADGE_H2 / 2);
|
|
@@ -9176,19 +9523,19 @@ var PriceLabel = class {
|
|
|
9176
9523
|
ctx.fill();
|
|
9177
9524
|
const bx = x + ARROW_W;
|
|
9178
9525
|
const by = y - BADGE_H2 / 2;
|
|
9179
|
-
ctx.fillStyle =
|
|
9526
|
+
ctx.fillStyle = s.fillColor;
|
|
9180
9527
|
ctx.beginPath();
|
|
9181
9528
|
ctx.roundRect(bx, by, BADGE_W, BADGE_H2, BADGE_H2 / 2);
|
|
9182
9529
|
ctx.fill();
|
|
9183
9530
|
if (isSelected) {
|
|
9184
|
-
ctx.strokeStyle =
|
|
9185
|
-
ctx.lineWidth =
|
|
9531
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9532
|
+
ctx.lineWidth = s.lineWidth + 0.5;
|
|
9186
9533
|
ctx.beginPath();
|
|
9187
9534
|
ctx.roundRect(bx - 2, by - 2, BADGE_W + 4, BADGE_H2 + 4, BADGE_H2 / 2 + 2);
|
|
9188
9535
|
ctx.stroke();
|
|
9189
9536
|
}
|
|
9190
|
-
ctx.font =
|
|
9191
|
-
ctx.fillStyle =
|
|
9537
|
+
ctx.font = `${fontSize}px 'Inter', system-ui, sans-serif`;
|
|
9538
|
+
ctx.fillStyle = textColor;
|
|
9192
9539
|
ctx.textBaseline = "middle";
|
|
9193
9540
|
ctx.textAlign = "center";
|
|
9194
9541
|
ctx.fillText(price.toFixed(pricePrecision), bx + BADGE_W / 2, y);
|
|
@@ -9222,21 +9569,24 @@ var ArrowMarker = class {
|
|
|
9222
9569
|
this.points = [first, { ...first }];
|
|
9223
9570
|
this.complete = false;
|
|
9224
9571
|
}
|
|
9225
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9572
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9226
9573
|
if (this.points.length < 2) return;
|
|
9227
9574
|
const [p1, p2] = this.points;
|
|
9228
9575
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
9229
9576
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
9230
9577
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
9231
9578
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
9579
|
+
const s = resolveStyle("arrowMarker", this.style, theme);
|
|
9232
9580
|
ctx.save();
|
|
9233
|
-
ctx.strokeStyle =
|
|
9234
|
-
ctx.fillStyle =
|
|
9235
|
-
ctx.lineWidth = isSelected ?
|
|
9581
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9582
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9583
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9584
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9236
9585
|
ctx.beginPath();
|
|
9237
9586
|
ctx.moveTo(x1, y1);
|
|
9238
9587
|
ctx.lineTo(x2, y2);
|
|
9239
9588
|
ctx.stroke();
|
|
9589
|
+
ctx.setLineDash([]);
|
|
9240
9590
|
const angle = Math.atan2(y2 - y1, x2 - x1);
|
|
9241
9591
|
const size = 10;
|
|
9242
9592
|
ctx.beginPath();
|
|
@@ -9246,6 +9596,7 @@ var ArrowMarker = class {
|
|
|
9246
9596
|
ctx.closePath();
|
|
9247
9597
|
ctx.fill();
|
|
9248
9598
|
if (isSelected) {
|
|
9599
|
+
ctx.fillStyle = s.strokeColor;
|
|
9249
9600
|
for (const [cx, cy] of [[x1, y1], [x2, y2]]) {
|
|
9250
9601
|
ctx.beginPath();
|
|
9251
9602
|
ctx.arc(cx, cy, 4, 0, Math.PI * 2);
|
|
@@ -9286,19 +9637,22 @@ var Flag = class {
|
|
|
9286
9637
|
this.points = [point];
|
|
9287
9638
|
this.complete = true;
|
|
9288
9639
|
}
|
|
9289
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH,
|
|
9640
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9290
9641
|
const x = scale.xToPixel(this.points[0].barIndex, viewport, chartW);
|
|
9291
9642
|
const y = scale.yToPixel(this.points[0].price, priceRange, chartH);
|
|
9643
|
+
const s = resolveStyle("flag", this.style, theme);
|
|
9292
9644
|
ctx.save();
|
|
9293
|
-
ctx.strokeStyle =
|
|
9294
|
-
ctx.
|
|
9295
|
-
ctx
|
|
9645
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9646
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9647
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9296
9648
|
ctx.beginPath();
|
|
9297
9649
|
ctx.moveTo(x, y + 20);
|
|
9298
9650
|
ctx.lineTo(x, y - 10);
|
|
9299
9651
|
ctx.stroke();
|
|
9652
|
+
ctx.setLineDash([]);
|
|
9300
9653
|
const flagX = x;
|
|
9301
9654
|
const flagY = y - 10;
|
|
9655
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9302
9656
|
ctx.fillRect(flagX, flagY, 16, 10);
|
|
9303
9657
|
ctx.font = "8px system-ui, sans-serif";
|
|
9304
9658
|
ctx.fillStyle = "#ffffff";
|
|
@@ -9329,28 +9683,33 @@ var PriceNote = class {
|
|
|
9329
9683
|
this.points = [point];
|
|
9330
9684
|
this.complete = true;
|
|
9331
9685
|
}
|
|
9332
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH,
|
|
9686
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
9333
9687
|
const x = scale.xToPixel(this.points[0].barIndex, viewport, chartW);
|
|
9334
9688
|
const y = scale.yToPixel(this.points[0].price, priceRange, chartH);
|
|
9335
9689
|
const price = this.points[0].price;
|
|
9336
9690
|
const label = price.toFixed(pricePrecision);
|
|
9691
|
+
const s = resolveStyle("priceNote", this.style, theme);
|
|
9692
|
+
const fontSize = s.fontSize ?? 11;
|
|
9693
|
+
const textColor = s.textColor ?? "#ffffff";
|
|
9337
9694
|
ctx.save();
|
|
9338
|
-
ctx.strokeStyle =
|
|
9339
|
-
ctx.lineWidth =
|
|
9695
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9696
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9697
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9340
9698
|
ctx.beginPath();
|
|
9341
9699
|
ctx.moveTo(x - TICK_HALF, y);
|
|
9342
9700
|
ctx.lineTo(x + TICK_HALF, y);
|
|
9343
9701
|
ctx.stroke();
|
|
9344
|
-
ctx.
|
|
9702
|
+
ctx.setLineDash([]);
|
|
9703
|
+
ctx.font = `${fontSize}px 'Inter', system-ui, sans-serif`;
|
|
9345
9704
|
const tw = ctx.measureText(label).width;
|
|
9346
9705
|
const badgeW = tw + BADGE_PADDING_X * 2;
|
|
9347
9706
|
const bx = x + TICK_HALF + 2;
|
|
9348
9707
|
const by = y - BADGE_H3 / 2;
|
|
9349
|
-
ctx.fillStyle =
|
|
9708
|
+
ctx.fillStyle = s.fillColor;
|
|
9350
9709
|
ctx.beginPath();
|
|
9351
9710
|
ctx.roundRect(bx, by, badgeW, BADGE_H3, 3);
|
|
9352
9711
|
ctx.fill();
|
|
9353
|
-
ctx.fillStyle =
|
|
9712
|
+
ctx.fillStyle = textColor;
|
|
9354
9713
|
ctx.textBaseline = "middle";
|
|
9355
9714
|
ctx.textAlign = "center";
|
|
9356
9715
|
ctx.fillText(label, bx + badgeW / 2, y);
|
|
@@ -9389,42 +9748,45 @@ var AnchoredNote = class {
|
|
|
9389
9748
|
const label = this.text || "Note";
|
|
9390
9749
|
return Math.max(50, ctx.measureText(label).width + PAD_H2 * 2);
|
|
9391
9750
|
}
|
|
9392
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9751
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9393
9752
|
if (this.points.length < 2) return;
|
|
9394
9753
|
const [p1, p2] = this.points;
|
|
9395
9754
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
9396
9755
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
9397
9756
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
9398
9757
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
9758
|
+
const s = resolveStyle("anchoredNote", this.style, theme);
|
|
9759
|
+
const fontSize = s.fontSize ?? 11;
|
|
9760
|
+
const textColor = s.textColor ?? "#f5f5f5";
|
|
9399
9761
|
ctx.save();
|
|
9400
|
-
ctx.font =
|
|
9401
|
-
ctx.lineWidth = isSelected ?
|
|
9762
|
+
ctx.font = `${fontSize}px system-ui, sans-serif`;
|
|
9763
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9402
9764
|
const badgeW = this.getBadgeW(ctx);
|
|
9403
9765
|
const bx = x2 - badgeW / 2;
|
|
9404
9766
|
const by = y2 - BADGE_H4 / 2;
|
|
9405
|
-
ctx.strokeStyle =
|
|
9767
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9406
9768
|
ctx.setLineDash([4, 3]);
|
|
9407
9769
|
ctx.beginPath();
|
|
9408
9770
|
ctx.moveTo(x1, y1);
|
|
9409
9771
|
ctx.lineTo(x2, y2);
|
|
9410
9772
|
ctx.stroke();
|
|
9411
9773
|
ctx.setLineDash([]);
|
|
9412
|
-
ctx.fillStyle =
|
|
9774
|
+
ctx.fillStyle = s.strokeColor;
|
|
9413
9775
|
ctx.beginPath();
|
|
9414
9776
|
ctx.arc(x1, y1, 3, 0, Math.PI * 2);
|
|
9415
9777
|
ctx.fill();
|
|
9416
|
-
ctx.fillStyle =
|
|
9778
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9417
9779
|
ctx.beginPath();
|
|
9418
9780
|
ctx.roundRect(bx, by, badgeW, BADGE_H4, 4);
|
|
9419
9781
|
ctx.fill();
|
|
9420
|
-
ctx.strokeStyle =
|
|
9421
|
-
ctx.lineWidth = isSelected ?
|
|
9782
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9783
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9422
9784
|
ctx.beginPath();
|
|
9423
9785
|
ctx.roundRect(bx, by, badgeW, BADGE_H4, 4);
|
|
9424
9786
|
ctx.stroke();
|
|
9425
9787
|
const label = this.text || "Note";
|
|
9426
9788
|
const isPlaceholder = !this.text;
|
|
9427
|
-
ctx.fillStyle = isPlaceholder ? "#888" :
|
|
9789
|
+
ctx.fillStyle = isPlaceholder ? "#888" : textColor;
|
|
9428
9790
|
ctx.textBaseline = "middle";
|
|
9429
9791
|
ctx.textAlign = "center";
|
|
9430
9792
|
ctx.fillText(label, x2, y2);
|
|
@@ -9472,17 +9834,20 @@ var ArrowUp = class {
|
|
|
9472
9834
|
this.points = [point];
|
|
9473
9835
|
this.complete = true;
|
|
9474
9836
|
}
|
|
9475
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH,
|
|
9837
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9476
9838
|
const x = scale.xToPixel(this.points[0].barIndex, viewport, chartW);
|
|
9477
9839
|
const y = scale.yToPixel(this.points[0].price, priceRange, chartH);
|
|
9840
|
+
const s = resolveStyle("arrowUp", this.style, theme);
|
|
9478
9841
|
ctx.save();
|
|
9479
|
-
ctx.strokeStyle =
|
|
9480
|
-
ctx.fillStyle =
|
|
9481
|
-
ctx.lineWidth =
|
|
9842
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9843
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9844
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9845
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9482
9846
|
ctx.beginPath();
|
|
9483
9847
|
ctx.moveTo(x, y + 10);
|
|
9484
9848
|
ctx.lineTo(x, y - 2);
|
|
9485
9849
|
ctx.stroke();
|
|
9850
|
+
ctx.setLineDash([]);
|
|
9486
9851
|
const tipX = x;
|
|
9487
9852
|
const tipY = y - 10;
|
|
9488
9853
|
const halfBase = 6;
|
|
@@ -9512,17 +9877,20 @@ var ArrowDown = class {
|
|
|
9512
9877
|
this.points = [point];
|
|
9513
9878
|
this.complete = true;
|
|
9514
9879
|
}
|
|
9515
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH,
|
|
9880
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9516
9881
|
const x = scale.xToPixel(this.points[0].barIndex, viewport, chartW);
|
|
9517
9882
|
const y = scale.yToPixel(this.points[0].price, priceRange, chartH);
|
|
9883
|
+
const s = resolveStyle("arrowDown", this.style, theme);
|
|
9518
9884
|
ctx.save();
|
|
9519
|
-
ctx.strokeStyle =
|
|
9520
|
-
ctx.fillStyle =
|
|
9521
|
-
ctx.lineWidth =
|
|
9885
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9886
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9887
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9888
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9522
9889
|
ctx.beginPath();
|
|
9523
9890
|
ctx.moveTo(x, y - 10);
|
|
9524
9891
|
ctx.lineTo(x, y + 2);
|
|
9525
9892
|
ctx.stroke();
|
|
9893
|
+
ctx.setLineDash([]);
|
|
9526
9894
|
const tipX = x;
|
|
9527
9895
|
const tipY = y + 10;
|
|
9528
9896
|
const halfBase = 6;
|
|
@@ -9553,7 +9921,6 @@ function pointToSegmentDist22(px, py, x1, y1, x2, y2) {
|
|
|
9553
9921
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
9554
9922
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
9555
9923
|
}
|
|
9556
|
-
var COLOR4 = "#ff7043";
|
|
9557
9924
|
var HeadShoulders = class {
|
|
9558
9925
|
constructor(first) {
|
|
9559
9926
|
this.type = "headShoulders";
|
|
@@ -9562,15 +9929,17 @@ var HeadShoulders = class {
|
|
|
9562
9929
|
this.points = [first, { ...first }];
|
|
9563
9930
|
this.complete = false;
|
|
9564
9931
|
}
|
|
9565
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9932
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9566
9933
|
if (this.points.length < 2) return;
|
|
9567
9934
|
const labels = ["LS", "H", "RS", "L1", "L2"];
|
|
9568
9935
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
9569
9936
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
9570
9937
|
const n = px.length;
|
|
9938
|
+
const s = resolveStyle("headShoulders", this.style, theme);
|
|
9571
9939
|
ctx.save();
|
|
9572
|
-
ctx.strokeStyle =
|
|
9573
|
-
ctx.lineWidth = isSelected ?
|
|
9940
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9941
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9942
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9574
9943
|
const zigzagOrder = [0, 3, 1, 4, 2];
|
|
9575
9944
|
const availableZigzag = zigzagOrder.filter((i) => i < n);
|
|
9576
9945
|
if (availableZigzag.length >= 2) {
|
|
@@ -9581,6 +9950,7 @@ var HeadShoulders = class {
|
|
|
9581
9950
|
}
|
|
9582
9951
|
ctx.stroke();
|
|
9583
9952
|
}
|
|
9953
|
+
ctx.setLineDash([]);
|
|
9584
9954
|
if (this.complete && n === 5) {
|
|
9585
9955
|
ctx.beginPath();
|
|
9586
9956
|
ctx.moveTo(px[0], py[0]);
|
|
@@ -9589,14 +9959,14 @@ var HeadShoulders = class {
|
|
|
9589
9959
|
ctx.lineTo(px[4], py[4]);
|
|
9590
9960
|
ctx.lineTo(px[2], py[2]);
|
|
9591
9961
|
ctx.closePath();
|
|
9592
|
-
ctx.fillStyle =
|
|
9962
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9593
9963
|
ctx.fill();
|
|
9594
9964
|
}
|
|
9595
9965
|
if (n >= 5) {
|
|
9596
9966
|
ctx.save();
|
|
9597
9967
|
ctx.setLineDash([5, 4]);
|
|
9598
|
-
ctx.strokeStyle =
|
|
9599
|
-
ctx.lineWidth = isSelected ?
|
|
9968
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9969
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9600
9970
|
ctx.beginPath();
|
|
9601
9971
|
ctx.moveTo(px[3], py[3]);
|
|
9602
9972
|
ctx.lineTo(px[4], py[4]);
|
|
@@ -9604,7 +9974,7 @@ var HeadShoulders = class {
|
|
|
9604
9974
|
ctx.restore();
|
|
9605
9975
|
}
|
|
9606
9976
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
9607
|
-
ctx.fillStyle =
|
|
9977
|
+
ctx.fillStyle = s.strokeColor;
|
|
9608
9978
|
for (let i = 0; i < n; i++) {
|
|
9609
9979
|
ctx.beginPath();
|
|
9610
9980
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -9650,7 +10020,6 @@ function pointToSegmentDist23(px, py, x1, y1, x2, y2) {
|
|
|
9650
10020
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
9651
10021
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
9652
10022
|
}
|
|
9653
|
-
var COLOR5 = "#ffa726";
|
|
9654
10023
|
var TrianglePattern = class {
|
|
9655
10024
|
constructor(first) {
|
|
9656
10025
|
this.type = "trianglePattern";
|
|
@@ -9659,26 +10028,29 @@ var TrianglePattern = class {
|
|
|
9659
10028
|
this.points = [first, { ...first }];
|
|
9660
10029
|
this.complete = false;
|
|
9661
10030
|
}
|
|
9662
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10031
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9663
10032
|
if (this.points.length < 2) return;
|
|
9664
10033
|
const labels = ["1", "2", "3", "4", "5"];
|
|
9665
10034
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
9666
10035
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
9667
10036
|
const n = px.length;
|
|
10037
|
+
const s = resolveStyle("trianglePattern", this.style, theme);
|
|
9668
10038
|
ctx.save();
|
|
9669
|
-
ctx.strokeStyle =
|
|
9670
|
-
ctx.lineWidth = isSelected ?
|
|
10039
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10040
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10041
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9671
10042
|
ctx.beginPath();
|
|
9672
10043
|
ctx.moveTo(px[0], py[0]);
|
|
9673
10044
|
for (let i = 1; i < n; i++) {
|
|
9674
10045
|
ctx.lineTo(px[i], py[i]);
|
|
9675
10046
|
}
|
|
9676
10047
|
ctx.stroke();
|
|
10048
|
+
ctx.setLineDash([]);
|
|
9677
10049
|
if (n >= 3) {
|
|
9678
10050
|
ctx.save();
|
|
9679
10051
|
ctx.setLineDash([5, 4]);
|
|
9680
|
-
ctx.strokeStyle =
|
|
9681
|
-
ctx.lineWidth = isSelected ?
|
|
10052
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10053
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9682
10054
|
ctx.beginPath();
|
|
9683
10055
|
ctx.moveTo(px[0], py[0]);
|
|
9684
10056
|
ctx.lineTo(px[2], py[2]);
|
|
@@ -9689,8 +10061,8 @@ var TrianglePattern = class {
|
|
|
9689
10061
|
if (n >= 4) {
|
|
9690
10062
|
ctx.save();
|
|
9691
10063
|
ctx.setLineDash([5, 4]);
|
|
9692
|
-
ctx.strokeStyle =
|
|
9693
|
-
ctx.lineWidth = isSelected ?
|
|
10064
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10065
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9694
10066
|
ctx.beginPath();
|
|
9695
10067
|
ctx.moveTo(px[1], py[1]);
|
|
9696
10068
|
ctx.lineTo(px[3], py[3]);
|
|
@@ -9698,7 +10070,7 @@ var TrianglePattern = class {
|
|
|
9698
10070
|
ctx.restore();
|
|
9699
10071
|
}
|
|
9700
10072
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
9701
|
-
ctx.fillStyle =
|
|
10073
|
+
ctx.fillStyle = s.strokeColor;
|
|
9702
10074
|
for (let i = 0; i < n; i++) {
|
|
9703
10075
|
ctx.beginPath();
|
|
9704
10076
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -9742,7 +10114,6 @@ function pointToSegmentDist24(px, py, x1, y1, x2, y2) {
|
|
|
9742
10114
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
9743
10115
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
9744
10116
|
}
|
|
9745
|
-
var COLOR6 = "#66bb6a";
|
|
9746
10117
|
var ThreeDrives = class {
|
|
9747
10118
|
constructor(first) {
|
|
9748
10119
|
this.type = "threeDrives";
|
|
@@ -9751,21 +10122,24 @@ var ThreeDrives = class {
|
|
|
9751
10122
|
this.points = [first, { ...first }];
|
|
9752
10123
|
this.complete = false;
|
|
9753
10124
|
}
|
|
9754
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10125
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9755
10126
|
if (this.points.length < 2) return;
|
|
9756
10127
|
const labels = ["O", "A", "B", "C", "D", "E", "F"];
|
|
9757
10128
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
9758
10129
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
9759
10130
|
const n = px.length;
|
|
10131
|
+
const s = resolveStyle("threeDrives", this.style, theme);
|
|
9760
10132
|
ctx.save();
|
|
9761
|
-
ctx.strokeStyle =
|
|
9762
|
-
ctx.lineWidth = isSelected ?
|
|
10133
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10134
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10135
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9763
10136
|
ctx.beginPath();
|
|
9764
10137
|
ctx.moveTo(px[0], py[0]);
|
|
9765
10138
|
for (let i = 1; i < n; i++) {
|
|
9766
10139
|
ctx.lineTo(px[i], py[i]);
|
|
9767
10140
|
}
|
|
9768
10141
|
ctx.stroke();
|
|
10142
|
+
ctx.setLineDash([]);
|
|
9769
10143
|
if (this.complete && n === 7) {
|
|
9770
10144
|
ctx.beginPath();
|
|
9771
10145
|
ctx.moveTo(px[0], py[0]);
|
|
@@ -9773,11 +10147,11 @@ var ThreeDrives = class {
|
|
|
9773
10147
|
ctx.lineTo(px[i], py[i]);
|
|
9774
10148
|
}
|
|
9775
10149
|
ctx.closePath();
|
|
9776
|
-
ctx.fillStyle =
|
|
10150
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9777
10151
|
ctx.fill();
|
|
9778
10152
|
}
|
|
9779
10153
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
9780
|
-
ctx.fillStyle =
|
|
10154
|
+
ctx.fillStyle = s.strokeColor;
|
|
9781
10155
|
for (let i = 0; i < n; i++) {
|
|
9782
10156
|
ctx.beginPath();
|
|
9783
10157
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -9818,7 +10192,6 @@ function pointToSegmentDist25(px, py, x1, y1, x2, y2) {
|
|
|
9818
10192
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
9819
10193
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
9820
10194
|
}
|
|
9821
|
-
var COLOR7 = "#42a5f5";
|
|
9822
10195
|
var AbcdPattern = class {
|
|
9823
10196
|
constructor(first) {
|
|
9824
10197
|
this.type = "abcdPattern";
|
|
@@ -9827,26 +10200,29 @@ var AbcdPattern = class {
|
|
|
9827
10200
|
this.points = [first, { ...first }];
|
|
9828
10201
|
this.complete = false;
|
|
9829
10202
|
}
|
|
9830
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10203
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9831
10204
|
if (this.points.length < 2) return;
|
|
9832
10205
|
const labels = ["A", "B", "C", "D"];
|
|
9833
10206
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
9834
10207
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
9835
10208
|
const n = px.length;
|
|
10209
|
+
const s = resolveStyle("abcdPattern", this.style, theme);
|
|
9836
10210
|
ctx.save();
|
|
9837
|
-
ctx.strokeStyle =
|
|
9838
|
-
ctx.lineWidth = isSelected ?
|
|
10211
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10212
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10213
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9839
10214
|
ctx.beginPath();
|
|
9840
10215
|
ctx.moveTo(px[0], py[0]);
|
|
9841
10216
|
for (let i = 1; i < n; i++) {
|
|
9842
10217
|
ctx.lineTo(px[i], py[i]);
|
|
9843
10218
|
}
|
|
9844
10219
|
ctx.stroke();
|
|
10220
|
+
ctx.setLineDash([]);
|
|
9845
10221
|
if (n >= 4) {
|
|
9846
10222
|
ctx.save();
|
|
9847
10223
|
ctx.setLineDash([3, 4]);
|
|
9848
|
-
ctx.strokeStyle =
|
|
9849
|
-
ctx.lineWidth = isSelected ?
|
|
10224
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10225
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9850
10226
|
ctx.beginPath();
|
|
9851
10227
|
ctx.moveTo(px[0], py[0]);
|
|
9852
10228
|
ctx.lineTo(px[3], py[3]);
|
|
@@ -9854,7 +10230,7 @@ var AbcdPattern = class {
|
|
|
9854
10230
|
ctx.restore();
|
|
9855
10231
|
}
|
|
9856
10232
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
9857
|
-
ctx.fillStyle =
|
|
10233
|
+
ctx.fillStyle = s.strokeColor;
|
|
9858
10234
|
for (let i = 0; i < n; i++) {
|
|
9859
10235
|
ctx.beginPath();
|
|
9860
10236
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -9896,7 +10272,6 @@ function pointToSegmentDist26(px, py, x1, y1, x2, y2) {
|
|
|
9896
10272
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
9897
10273
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
9898
10274
|
}
|
|
9899
|
-
var COLOR8 = "#ab47bc";
|
|
9900
10275
|
var CypherPattern = class {
|
|
9901
10276
|
constructor(first) {
|
|
9902
10277
|
this.type = "cypherPattern";
|
|
@@ -9905,21 +10280,24 @@ var CypherPattern = class {
|
|
|
9905
10280
|
this.points = [first, { ...first }];
|
|
9906
10281
|
this.complete = false;
|
|
9907
10282
|
}
|
|
9908
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10283
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9909
10284
|
if (this.points.length < 2) return;
|
|
9910
10285
|
const labels = ["X", "A", "B", "C", "D"];
|
|
9911
10286
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
9912
10287
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
9913
10288
|
const n = px.length;
|
|
10289
|
+
const s = resolveStyle("cypherPattern", this.style, theme);
|
|
9914
10290
|
ctx.save();
|
|
9915
|
-
ctx.strokeStyle =
|
|
9916
|
-
ctx.lineWidth = isSelected ?
|
|
10291
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10292
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10293
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9917
10294
|
ctx.beginPath();
|
|
9918
10295
|
ctx.moveTo(px[0], py[0]);
|
|
9919
10296
|
for (let i = 1; i < n; i++) {
|
|
9920
10297
|
ctx.lineTo(px[i], py[i]);
|
|
9921
10298
|
}
|
|
9922
10299
|
ctx.stroke();
|
|
10300
|
+
ctx.setLineDash([]);
|
|
9923
10301
|
if (this.complete && n === 5) {
|
|
9924
10302
|
ctx.beginPath();
|
|
9925
10303
|
ctx.moveTo(px[0], py[0]);
|
|
@@ -9927,11 +10305,11 @@ var CypherPattern = class {
|
|
|
9927
10305
|
ctx.lineTo(px[i], py[i]);
|
|
9928
10306
|
}
|
|
9929
10307
|
ctx.closePath();
|
|
9930
|
-
ctx.fillStyle =
|
|
10308
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9931
10309
|
ctx.fill();
|
|
9932
10310
|
}
|
|
9933
10311
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
9934
|
-
ctx.fillStyle =
|
|
10312
|
+
ctx.fillStyle = s.strokeColor;
|
|
9935
10313
|
for (let i = 0; i < n; i++) {
|
|
9936
10314
|
ctx.beginPath();
|
|
9937
10315
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -9972,7 +10350,6 @@ function pointToSegmentDist27(px, py, x1, y1, x2, y2) {
|
|
|
9972
10350
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
9973
10351
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
9974
10352
|
}
|
|
9975
|
-
var COLOR9 = "#ec407a";
|
|
9976
10353
|
var ButterflyPattern = class {
|
|
9977
10354
|
constructor(first) {
|
|
9978
10355
|
this.type = "butterflyPattern";
|
|
@@ -9981,26 +10358,29 @@ var ButterflyPattern = class {
|
|
|
9981
10358
|
this.points = [first, { ...first }];
|
|
9982
10359
|
this.complete = false;
|
|
9983
10360
|
}
|
|
9984
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10361
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9985
10362
|
if (this.points.length < 2) return;
|
|
9986
10363
|
const labels = ["X", "A", "B", "C", "D"];
|
|
9987
10364
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
9988
10365
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
9989
10366
|
const n = px.length;
|
|
10367
|
+
const s = resolveStyle("butterflyPattern", this.style, theme);
|
|
9990
10368
|
ctx.save();
|
|
9991
|
-
ctx.strokeStyle =
|
|
9992
|
-
ctx.lineWidth = isSelected ?
|
|
10369
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10370
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10371
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9993
10372
|
ctx.beginPath();
|
|
9994
10373
|
ctx.moveTo(px[0], py[0]);
|
|
9995
10374
|
for (let i = 1; i < n; i++) {
|
|
9996
10375
|
ctx.lineTo(px[i], py[i]);
|
|
9997
10376
|
}
|
|
9998
10377
|
ctx.stroke();
|
|
10378
|
+
ctx.setLineDash([]);
|
|
9999
10379
|
if (n >= 5) {
|
|
10000
10380
|
ctx.save();
|
|
10001
10381
|
ctx.setLineDash([5, 4]);
|
|
10002
|
-
ctx.strokeStyle =
|
|
10003
|
-
ctx.lineWidth = isSelected ?
|
|
10382
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10383
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10004
10384
|
ctx.beginPath();
|
|
10005
10385
|
ctx.moveTo(px[0], py[0]);
|
|
10006
10386
|
ctx.lineTo(px[4], py[4]);
|
|
@@ -10008,7 +10388,7 @@ var ButterflyPattern = class {
|
|
|
10008
10388
|
ctx.restore();
|
|
10009
10389
|
}
|
|
10010
10390
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10011
|
-
ctx.fillStyle =
|
|
10391
|
+
ctx.fillStyle = s.strokeColor;
|
|
10012
10392
|
for (let i = 0; i < n; i++) {
|
|
10013
10393
|
ctx.beginPath();
|
|
10014
10394
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -10050,7 +10430,6 @@ function pointToSegmentDist28(px, py, x1, y1, x2, y2) {
|
|
|
10050
10430
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10051
10431
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10052
10432
|
}
|
|
10053
|
-
var COLOR10 = "#26a69a";
|
|
10054
10433
|
var BatPattern = class {
|
|
10055
10434
|
constructor(first) {
|
|
10056
10435
|
this.type = "batPattern";
|
|
@@ -10059,26 +10438,29 @@ var BatPattern = class {
|
|
|
10059
10438
|
this.points = [first, { ...first }];
|
|
10060
10439
|
this.complete = false;
|
|
10061
10440
|
}
|
|
10062
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10441
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10063
10442
|
if (this.points.length < 2) return;
|
|
10064
10443
|
const labels = ["X", "A", "B", "C", "D"];
|
|
10065
10444
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10066
10445
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10067
10446
|
const n = px.length;
|
|
10447
|
+
const s = resolveStyle("batPattern", this.style, theme);
|
|
10068
10448
|
ctx.save();
|
|
10069
|
-
ctx.strokeStyle =
|
|
10070
|
-
ctx.lineWidth = isSelected ?
|
|
10449
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10450
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10451
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10071
10452
|
ctx.beginPath();
|
|
10072
10453
|
ctx.moveTo(px[0], py[0]);
|
|
10073
10454
|
for (let i = 1; i < n; i++) {
|
|
10074
10455
|
ctx.lineTo(px[i], py[i]);
|
|
10075
10456
|
}
|
|
10076
10457
|
ctx.stroke();
|
|
10458
|
+
ctx.setLineDash([]);
|
|
10077
10459
|
if (n >= 5) {
|
|
10078
10460
|
ctx.save();
|
|
10079
10461
|
ctx.setLineDash([5, 4]);
|
|
10080
|
-
ctx.strokeStyle =
|
|
10081
|
-
ctx.lineWidth = isSelected ?
|
|
10462
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10463
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10082
10464
|
ctx.beginPath();
|
|
10083
10465
|
ctx.moveTo(px[0], py[0]);
|
|
10084
10466
|
ctx.lineTo(px[4], py[4]);
|
|
@@ -10086,7 +10468,7 @@ var BatPattern = class {
|
|
|
10086
10468
|
ctx.restore();
|
|
10087
10469
|
}
|
|
10088
10470
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10089
|
-
ctx.fillStyle =
|
|
10471
|
+
ctx.fillStyle = s.strokeColor;
|
|
10090
10472
|
for (let i = 0; i < n; i++) {
|
|
10091
10473
|
ctx.beginPath();
|
|
10092
10474
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -10128,7 +10510,6 @@ function pointToSegmentDist29(px, py, x1, y1, x2, y2) {
|
|
|
10128
10510
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10129
10511
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10130
10512
|
}
|
|
10131
|
-
var COLOR11 = "#7986cb";
|
|
10132
10513
|
var GartleyPattern = class {
|
|
10133
10514
|
constructor(first) {
|
|
10134
10515
|
this.type = "gartleyPattern";
|
|
@@ -10137,26 +10518,29 @@ var GartleyPattern = class {
|
|
|
10137
10518
|
this.points = [first, { ...first }];
|
|
10138
10519
|
this.complete = false;
|
|
10139
10520
|
}
|
|
10140
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10521
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10141
10522
|
if (this.points.length < 2) return;
|
|
10142
10523
|
const labels = ["X", "A", "B", "C", "D"];
|
|
10143
10524
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10144
10525
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10145
10526
|
const n = px.length;
|
|
10527
|
+
const s = resolveStyle("gartleyPattern", this.style, theme);
|
|
10146
10528
|
ctx.save();
|
|
10147
|
-
ctx.strokeStyle =
|
|
10148
|
-
ctx.lineWidth = isSelected ?
|
|
10529
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10530
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10531
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10149
10532
|
ctx.beginPath();
|
|
10150
10533
|
ctx.moveTo(px[0], py[0]);
|
|
10151
10534
|
for (let i = 1; i < n; i++) {
|
|
10152
10535
|
ctx.lineTo(px[i], py[i]);
|
|
10153
10536
|
}
|
|
10154
10537
|
ctx.stroke();
|
|
10538
|
+
ctx.setLineDash([]);
|
|
10155
10539
|
if (n >= 5) {
|
|
10156
10540
|
ctx.save();
|
|
10157
10541
|
ctx.setLineDash([5, 4]);
|
|
10158
|
-
ctx.strokeStyle =
|
|
10159
|
-
ctx.lineWidth = isSelected ?
|
|
10542
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10543
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10160
10544
|
ctx.beginPath();
|
|
10161
10545
|
ctx.moveTo(px[0], py[0]);
|
|
10162
10546
|
ctx.lineTo(px[4], py[4]);
|
|
@@ -10164,7 +10548,7 @@ var GartleyPattern = class {
|
|
|
10164
10548
|
ctx.restore();
|
|
10165
10549
|
}
|
|
10166
10550
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10167
|
-
ctx.fillStyle =
|
|
10551
|
+
ctx.fillStyle = s.strokeColor;
|
|
10168
10552
|
for (let i = 0; i < n; i++) {
|
|
10169
10553
|
ctx.beginPath();
|
|
10170
10554
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -10206,7 +10590,6 @@ function pointToSegmentDist30(px, py, x1, y1, x2, y2) {
|
|
|
10206
10590
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10207
10591
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10208
10592
|
}
|
|
10209
|
-
var COLOR12 = "#ef5350";
|
|
10210
10593
|
var CrabPattern = class {
|
|
10211
10594
|
constructor(first) {
|
|
10212
10595
|
this.type = "crabPattern";
|
|
@@ -10215,26 +10598,29 @@ var CrabPattern = class {
|
|
|
10215
10598
|
this.points = [first, { ...first }];
|
|
10216
10599
|
this.complete = false;
|
|
10217
10600
|
}
|
|
10218
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10601
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10219
10602
|
if (this.points.length < 2) return;
|
|
10220
10603
|
const labels = ["X", "A", "B", "C", "D"];
|
|
10221
10604
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10222
10605
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10223
10606
|
const n = px.length;
|
|
10607
|
+
const s = resolveStyle("crabPattern", this.style, theme);
|
|
10224
10608
|
ctx.save();
|
|
10225
|
-
ctx.strokeStyle =
|
|
10226
|
-
ctx.lineWidth = isSelected ?
|
|
10609
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10610
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10611
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10227
10612
|
ctx.beginPath();
|
|
10228
10613
|
ctx.moveTo(px[0], py[0]);
|
|
10229
10614
|
for (let i = 1; i < n; i++) {
|
|
10230
10615
|
ctx.lineTo(px[i], py[i]);
|
|
10231
10616
|
}
|
|
10232
10617
|
ctx.stroke();
|
|
10618
|
+
ctx.setLineDash([]);
|
|
10233
10619
|
if (n >= 5) {
|
|
10234
10620
|
ctx.save();
|
|
10235
10621
|
ctx.setLineDash([5, 4]);
|
|
10236
|
-
ctx.strokeStyle =
|
|
10237
|
-
ctx.lineWidth = isSelected ?
|
|
10622
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10623
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10238
10624
|
ctx.beginPath();
|
|
10239
10625
|
ctx.moveTo(px[0], py[0]);
|
|
10240
10626
|
ctx.lineTo(px[4], py[4]);
|
|
@@ -10242,7 +10628,7 @@ var CrabPattern = class {
|
|
|
10242
10628
|
ctx.restore();
|
|
10243
10629
|
}
|
|
10244
10630
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10245
|
-
ctx.fillStyle =
|
|
10631
|
+
ctx.fillStyle = s.strokeColor;
|
|
10246
10632
|
for (let i = 0; i < n; i++) {
|
|
10247
10633
|
ctx.beginPath();
|
|
10248
10634
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -10284,7 +10670,6 @@ function pointToSegmentDist31(px, py, x1, y1, x2, y2) {
|
|
|
10284
10670
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10285
10671
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10286
10672
|
}
|
|
10287
|
-
var COLOR13 = "#78909c";
|
|
10288
10673
|
var SharkPattern = class {
|
|
10289
10674
|
constructor(first) {
|
|
10290
10675
|
this.type = "sharkPattern";
|
|
@@ -10293,26 +10678,29 @@ var SharkPattern = class {
|
|
|
10293
10678
|
this.points = [first, { ...first }];
|
|
10294
10679
|
this.complete = false;
|
|
10295
10680
|
}
|
|
10296
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10681
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10297
10682
|
if (this.points.length < 2) return;
|
|
10298
10683
|
const labels = ["X", "A", "B", "C", "D"];
|
|
10299
10684
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10300
10685
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10301
10686
|
const n = px.length;
|
|
10687
|
+
const s = resolveStyle("sharkPattern", this.style, theme);
|
|
10302
10688
|
ctx.save();
|
|
10303
|
-
ctx.strokeStyle =
|
|
10304
|
-
ctx.lineWidth = isSelected ?
|
|
10689
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10690
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10691
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10305
10692
|
ctx.beginPath();
|
|
10306
10693
|
ctx.moveTo(px[0], py[0]);
|
|
10307
10694
|
for (let i = 1; i < n; i++) {
|
|
10308
10695
|
ctx.lineTo(px[i], py[i]);
|
|
10309
10696
|
}
|
|
10310
10697
|
ctx.stroke();
|
|
10698
|
+
ctx.setLineDash([]);
|
|
10311
10699
|
if (n >= 5) {
|
|
10312
10700
|
ctx.save();
|
|
10313
10701
|
ctx.setLineDash([5, 4]);
|
|
10314
|
-
ctx.strokeStyle =
|
|
10315
|
-
ctx.lineWidth = isSelected ?
|
|
10702
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10703
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10316
10704
|
ctx.beginPath();
|
|
10317
10705
|
ctx.moveTo(px[0], py[0]);
|
|
10318
10706
|
ctx.lineTo(px[4], py[4]);
|
|
@@ -10320,7 +10708,7 @@ var SharkPattern = class {
|
|
|
10320
10708
|
ctx.restore();
|
|
10321
10709
|
}
|
|
10322
10710
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10323
|
-
ctx.fillStyle =
|
|
10711
|
+
ctx.fillStyle = s.strokeColor;
|
|
10324
10712
|
for (let i = 0; i < n; i++) {
|
|
10325
10713
|
ctx.beginPath();
|
|
10326
10714
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -10362,7 +10750,6 @@ function pointToSegmentDist32(px, py, x1, y1, x2, y2) {
|
|
|
10362
10750
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10363
10751
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10364
10752
|
}
|
|
10365
|
-
var COLOR14 = "#29b6f6";
|
|
10366
10753
|
var LABELS = ["0", "1", "2", "3", "4", "5"];
|
|
10367
10754
|
var ElliottImpulse = class {
|
|
10368
10755
|
constructor(first) {
|
|
@@ -10372,28 +10759,31 @@ var ElliottImpulse = class {
|
|
|
10372
10759
|
this.points = [first, { ...first }];
|
|
10373
10760
|
this.complete = false;
|
|
10374
10761
|
}
|
|
10375
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10762
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10376
10763
|
if (this.points.length < 2) return;
|
|
10377
10764
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10378
10765
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10379
10766
|
const n = px.length;
|
|
10767
|
+
const s = resolveStyle("elliottImpulse", this.style, theme);
|
|
10380
10768
|
ctx.save();
|
|
10381
|
-
ctx.lineWidth = isSelected ?
|
|
10769
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10770
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10382
10771
|
for (let i = 0; i < n - 1; i++) {
|
|
10383
10772
|
const isImpulse = i % 2 === 0;
|
|
10384
|
-
ctx.strokeStyle = isImpulse ?
|
|
10773
|
+
ctx.strokeStyle = isImpulse ? s.strokeColor : hexWithAlpha(s.strokeColor, 0.67);
|
|
10385
10774
|
ctx.beginPath();
|
|
10386
10775
|
ctx.moveTo(px[i], py[i]);
|
|
10387
10776
|
ctx.lineTo(px[i + 1], py[i + 1]);
|
|
10388
10777
|
ctx.stroke();
|
|
10389
10778
|
}
|
|
10779
|
+
ctx.setLineDash([]);
|
|
10390
10780
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10391
10781
|
for (let i = 0; i < n; i++) {
|
|
10392
10782
|
ctx.beginPath();
|
|
10393
10783
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
10394
|
-
ctx.fillStyle =
|
|
10784
|
+
ctx.fillStyle = s.strokeColor;
|
|
10395
10785
|
ctx.fill();
|
|
10396
|
-
ctx.fillStyle =
|
|
10786
|
+
ctx.fillStyle = s.strokeColor;
|
|
10397
10787
|
ctx.fillText(LABELS[i] ?? String(i), px[i] + 5, py[i] - 5);
|
|
10398
10788
|
}
|
|
10399
10789
|
ctx.restore();
|
|
@@ -10427,7 +10817,6 @@ function pointToSegmentDist33(px, py, x1, y1, x2, y2) {
|
|
|
10427
10817
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10428
10818
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10429
10819
|
}
|
|
10430
|
-
var COLOR15 = "#ff8a65";
|
|
10431
10820
|
var LABELS2 = ["0", "A", "B", "C"];
|
|
10432
10821
|
var ElliottCorrective = class {
|
|
10433
10822
|
constructor(first) {
|
|
@@ -10437,24 +10826,27 @@ var ElliottCorrective = class {
|
|
|
10437
10826
|
this.points = [first, { ...first }];
|
|
10438
10827
|
this.complete = false;
|
|
10439
10828
|
}
|
|
10440
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10829
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10441
10830
|
if (this.points.length < 2) return;
|
|
10442
10831
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10443
10832
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10833
|
+
const s = resolveStyle("elliottCorrective", this.style, theme);
|
|
10444
10834
|
ctx.save();
|
|
10445
|
-
ctx.strokeStyle =
|
|
10446
|
-
ctx.lineWidth = isSelected ?
|
|
10835
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10836
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10837
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10447
10838
|
ctx.beginPath();
|
|
10448
10839
|
ctx.moveTo(px[0], py[0]);
|
|
10449
10840
|
for (let i = 1; i < px.length; i++) ctx.lineTo(px[i], py[i]);
|
|
10450
10841
|
ctx.stroke();
|
|
10842
|
+
ctx.setLineDash([]);
|
|
10451
10843
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10452
10844
|
for (let i = 0; i < px.length; i++) {
|
|
10453
10845
|
ctx.beginPath();
|
|
10454
10846
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
10455
|
-
ctx.fillStyle =
|
|
10847
|
+
ctx.fillStyle = s.strokeColor;
|
|
10456
10848
|
ctx.fill();
|
|
10457
|
-
ctx.fillStyle =
|
|
10849
|
+
ctx.fillStyle = s.strokeColor;
|
|
10458
10850
|
ctx.fillText(LABELS2[i] ?? String(i), px[i] + 5, py[i] - 5);
|
|
10459
10851
|
}
|
|
10460
10852
|
ctx.restore();
|
|
@@ -10488,7 +10880,6 @@ function pointToSegmentDist34(px, py, x1, y1, x2, y2) {
|
|
|
10488
10880
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10489
10881
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10490
10882
|
}
|
|
10491
|
-
var COLOR16 = "#aed581";
|
|
10492
10883
|
var LABELS3 = ["A", "B", "C", "D", "E", "end"];
|
|
10493
10884
|
var ElliottTriangle = class {
|
|
10494
10885
|
constructor(first) {
|
|
@@ -10498,23 +10889,26 @@ var ElliottTriangle = class {
|
|
|
10498
10889
|
this.points = [first, { ...first }];
|
|
10499
10890
|
this.complete = false;
|
|
10500
10891
|
}
|
|
10501
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10892
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10502
10893
|
if (this.points.length < 2) return;
|
|
10503
10894
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10504
10895
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10505
10896
|
const n = px.length;
|
|
10897
|
+
const s = resolveStyle("elliottTriangle", this.style, theme);
|
|
10506
10898
|
ctx.save();
|
|
10507
|
-
ctx.strokeStyle =
|
|
10508
|
-
ctx.lineWidth = isSelected ?
|
|
10899
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10900
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10901
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10509
10902
|
ctx.beginPath();
|
|
10510
10903
|
ctx.moveTo(px[0], py[0]);
|
|
10511
10904
|
for (let i = 1; i < n; i++) ctx.lineTo(px[i], py[i]);
|
|
10512
10905
|
ctx.stroke();
|
|
10906
|
+
ctx.setLineDash([]);
|
|
10513
10907
|
if (n >= 3) {
|
|
10514
10908
|
ctx.save();
|
|
10515
10909
|
ctx.setLineDash([5, 4]);
|
|
10516
|
-
ctx.strokeStyle =
|
|
10517
|
-
ctx.lineWidth =
|
|
10910
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, 0.67);
|
|
10911
|
+
ctx.lineWidth = s.lineWidth;
|
|
10518
10912
|
ctx.beginPath();
|
|
10519
10913
|
ctx.moveTo(px[0], py[0]);
|
|
10520
10914
|
if (n > 2) ctx.lineTo(px[2], py[2]);
|
|
@@ -10525,8 +10919,8 @@ var ElliottTriangle = class {
|
|
|
10525
10919
|
if (n >= 4) {
|
|
10526
10920
|
ctx.save();
|
|
10527
10921
|
ctx.setLineDash([5, 4]);
|
|
10528
|
-
ctx.strokeStyle =
|
|
10529
|
-
ctx.lineWidth =
|
|
10922
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, 0.67);
|
|
10923
|
+
ctx.lineWidth = s.lineWidth;
|
|
10530
10924
|
ctx.beginPath();
|
|
10531
10925
|
ctx.moveTo(px[1], py[1]);
|
|
10532
10926
|
ctx.lineTo(px[3], py[3]);
|
|
@@ -10537,9 +10931,9 @@ var ElliottTriangle = class {
|
|
|
10537
10931
|
for (let i = 0; i < n; i++) {
|
|
10538
10932
|
ctx.beginPath();
|
|
10539
10933
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
10540
|
-
ctx.fillStyle =
|
|
10934
|
+
ctx.fillStyle = s.strokeColor;
|
|
10541
10935
|
ctx.fill();
|
|
10542
|
-
ctx.fillStyle =
|
|
10936
|
+
ctx.fillStyle = s.strokeColor;
|
|
10543
10937
|
ctx.fillText(LABELS3[i] ?? String(i), px[i] + 5, py[i] - 5);
|
|
10544
10938
|
}
|
|
10545
10939
|
ctx.restore();
|
|
@@ -10573,7 +10967,6 @@ function pointToSegmentDist35(px, py, x1, y1, x2, y2) {
|
|
|
10573
10967
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10574
10968
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10575
10969
|
}
|
|
10576
|
-
var COLOR17 = "#f48fb1";
|
|
10577
10970
|
var LABELS4 = ["W", "X", "Y", "X2", "Z", "X3", "end"];
|
|
10578
10971
|
var ElliottCombination = class {
|
|
10579
10972
|
constructor(first) {
|
|
@@ -10583,24 +10976,27 @@ var ElliottCombination = class {
|
|
|
10583
10976
|
this.points = [first, { ...first }];
|
|
10584
10977
|
this.complete = false;
|
|
10585
10978
|
}
|
|
10586
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10979
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10587
10980
|
if (this.points.length < 2) return;
|
|
10588
10981
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10589
10982
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10983
|
+
const s = resolveStyle("elliottCombination", this.style, theme);
|
|
10590
10984
|
ctx.save();
|
|
10591
|
-
ctx.strokeStyle =
|
|
10592
|
-
ctx.lineWidth = isSelected ?
|
|
10985
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10986
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10987
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10593
10988
|
ctx.beginPath();
|
|
10594
10989
|
ctx.moveTo(px[0], py[0]);
|
|
10595
10990
|
for (let i = 1; i < px.length; i++) ctx.lineTo(px[i], py[i]);
|
|
10596
10991
|
ctx.stroke();
|
|
10992
|
+
ctx.setLineDash([]);
|
|
10597
10993
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10598
10994
|
for (let i = 0; i < px.length; i++) {
|
|
10599
10995
|
ctx.beginPath();
|
|
10600
10996
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
10601
|
-
ctx.fillStyle =
|
|
10997
|
+
ctx.fillStyle = s.strokeColor;
|
|
10602
10998
|
ctx.fill();
|
|
10603
|
-
ctx.fillStyle =
|
|
10999
|
+
ctx.fillStyle = s.strokeColor;
|
|
10604
11000
|
ctx.fillText(LABELS4[i] ?? String(i), px[i] + 5, py[i] - 5);
|
|
10605
11001
|
}
|
|
10606
11002
|
ctx.restore();
|
|
@@ -10634,7 +11030,6 @@ function pointToSegmentDist36(px, py, x1, y1, x2, y2) {
|
|
|
10634
11030
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10635
11031
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10636
11032
|
}
|
|
10637
|
-
var COLOR18 = "#80cbc4";
|
|
10638
11033
|
var LABELS5 = ["W", "X", "Y", "end"];
|
|
10639
11034
|
var ElliottWxy = class {
|
|
10640
11035
|
constructor(first) {
|
|
@@ -10644,24 +11039,27 @@ var ElliottWxy = class {
|
|
|
10644
11039
|
this.points = [first, { ...first }];
|
|
10645
11040
|
this.complete = false;
|
|
10646
11041
|
}
|
|
10647
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11042
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10648
11043
|
if (this.points.length < 2) return;
|
|
10649
11044
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10650
11045
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
11046
|
+
const s = resolveStyle("elliottWxy", this.style, theme);
|
|
10651
11047
|
ctx.save();
|
|
10652
|
-
ctx.strokeStyle =
|
|
10653
|
-
ctx.lineWidth = isSelected ?
|
|
11048
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11049
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
11050
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10654
11051
|
ctx.beginPath();
|
|
10655
11052
|
ctx.moveTo(px[0], py[0]);
|
|
10656
11053
|
for (let i = 1; i < px.length; i++) ctx.lineTo(px[i], py[i]);
|
|
10657
11054
|
ctx.stroke();
|
|
11055
|
+
ctx.setLineDash([]);
|
|
10658
11056
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10659
11057
|
for (let i = 0; i < px.length; i++) {
|
|
10660
11058
|
ctx.beginPath();
|
|
10661
11059
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
10662
|
-
ctx.fillStyle =
|
|
11060
|
+
ctx.fillStyle = s.strokeColor;
|
|
10663
11061
|
ctx.fill();
|
|
10664
|
-
ctx.fillStyle =
|
|
11062
|
+
ctx.fillStyle = s.strokeColor;
|
|
10665
11063
|
ctx.fillText(LABELS5[i] ?? String(i), px[i] + 5, py[i] - 5);
|
|
10666
11064
|
}
|
|
10667
11065
|
ctx.restore();
|
|
@@ -10703,16 +11101,17 @@ var Brush = class {
|
|
|
10703
11101
|
this.points = [first, { ...first }];
|
|
10704
11102
|
this.complete = false;
|
|
10705
11103
|
}
|
|
10706
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11104
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10707
11105
|
if (this.points.length < 2) return;
|
|
10708
11106
|
const pixelPts = this.points.map((p) => ({
|
|
10709
11107
|
x: scale.xToPixel(p.barIndex, viewport, chartW),
|
|
10710
11108
|
y: scale.yToPixel(p.price, priceRange, chartH)
|
|
10711
11109
|
}));
|
|
10712
11110
|
const solidCount = this.complete ? pixelPts.length : pixelPts.length - 1;
|
|
11111
|
+
const s = resolveStyle("brush", this.style, theme);
|
|
10713
11112
|
ctx.save();
|
|
10714
|
-
ctx.strokeStyle =
|
|
10715
|
-
ctx.lineWidth = isSelected ?
|
|
11113
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11114
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10716
11115
|
ctx.lineCap = "round";
|
|
10717
11116
|
ctx.lineJoin = "round";
|
|
10718
11117
|
if (solidCount >= 2) {
|
|
@@ -10780,16 +11179,17 @@ var Highlighter = class {
|
|
|
10780
11179
|
this.points = [first, { ...first }];
|
|
10781
11180
|
this.complete = false;
|
|
10782
11181
|
}
|
|
10783
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11182
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10784
11183
|
if (this.points.length < 2) return;
|
|
10785
11184
|
const pixelPts = this.points.map((p) => ({
|
|
10786
11185
|
x: scale.xToPixel(p.barIndex, viewport, chartW),
|
|
10787
11186
|
y: scale.yToPixel(p.price, priceRange, chartH)
|
|
10788
11187
|
}));
|
|
10789
11188
|
const solidCount = this.complete ? pixelPts.length : pixelPts.length - 1;
|
|
11189
|
+
const s = resolveStyle("highlighter", this.style, theme);
|
|
10790
11190
|
ctx.save();
|
|
10791
|
-
ctx.strokeStyle =
|
|
10792
|
-
ctx.lineWidth =
|
|
11191
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, s.fillOpacity);
|
|
11192
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10793
11193
|
ctx.lineCap = "round";
|
|
10794
11194
|
ctx.lineJoin = "round";
|
|
10795
11195
|
if (solidCount >= 2) {
|
|
@@ -10849,7 +11249,7 @@ var SineLine = class {
|
|
|
10849
11249
|
this.points = [first, { ...first }];
|
|
10850
11250
|
this.complete = false;
|
|
10851
11251
|
}
|
|
10852
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11252
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10853
11253
|
if (this.points.length < 2) return;
|
|
10854
11254
|
const [p1, p2] = this.points;
|
|
10855
11255
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -10859,9 +11259,11 @@ var SineLine = class {
|
|
|
10859
11259
|
const period = x2 - x1;
|
|
10860
11260
|
const amplitude = Math.abs(y2 - y1) / 2;
|
|
10861
11261
|
const midY = (y1 + y2) / 2;
|
|
11262
|
+
const s = resolveStyle("sineLine", this.style, theme);
|
|
10862
11263
|
ctx.save();
|
|
10863
|
-
ctx.strokeStyle =
|
|
10864
|
-
ctx.lineWidth = isSelected ?
|
|
11264
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11265
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
11266
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10865
11267
|
ctx.beginPath();
|
|
10866
11268
|
if (Math.abs(period) < 1) {
|
|
10867
11269
|
ctx.moveTo(x1, y1);
|
|
@@ -10878,7 +11280,8 @@ var SineLine = class {
|
|
|
10878
11280
|
ctx.lineTo(x2, midY + amplitude * Math.sin(2 * Math.PI));
|
|
10879
11281
|
}
|
|
10880
11282
|
ctx.stroke();
|
|
10881
|
-
ctx.
|
|
11283
|
+
ctx.setLineDash([]);
|
|
11284
|
+
ctx.fillStyle = s.strokeColor;
|
|
10882
11285
|
for (const [x, y] of [[x1, y1], [x2, y2]]) {
|
|
10883
11286
|
ctx.beginPath();
|
|
10884
11287
|
ctx.arc(x, y, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -10944,17 +11347,17 @@ var CyclicLines = class {
|
|
|
10944
11347
|
this.points = [first, { ...first }];
|
|
10945
11348
|
this.complete = false;
|
|
10946
11349
|
}
|
|
10947
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11350
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10948
11351
|
if (this.points.length < 2) return;
|
|
10949
11352
|
const [p1, p2] = this.points;
|
|
10950
11353
|
const cycleLen = Math.abs(p2.barIndex - p1.barIndex);
|
|
10951
11354
|
if (cycleLen === 0) return;
|
|
10952
11355
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
10953
|
-
const
|
|
11356
|
+
const s = resolveStyle("cyclicLines", this.style, theme);
|
|
10954
11357
|
ctx.save();
|
|
10955
|
-
ctx.strokeStyle =
|
|
10956
|
-
ctx.lineWidth = isSelected ?
|
|
10957
|
-
ctx.
|
|
11358
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11359
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
11360
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10958
11361
|
for (let i = 0; i <= 8; i++) {
|
|
10959
11362
|
const barIdx = p1.barIndex + cycleLen * i;
|
|
10960
11363
|
const x = scale.xToPixel(barIdx, viewport, chartW);
|
|
@@ -10966,7 +11369,7 @@ var CyclicLines = class {
|
|
|
10966
11369
|
}
|
|
10967
11370
|
ctx.setLineDash([]);
|
|
10968
11371
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
10969
|
-
ctx.fillStyle =
|
|
11372
|
+
ctx.fillStyle = s.strokeColor;
|
|
10970
11373
|
ctx.beginPath();
|
|
10971
11374
|
ctx.arc(x1, chartH / 2, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
10972
11375
|
ctx.fill();
|
|
@@ -11013,16 +11416,17 @@ var TimeCycles = class {
|
|
|
11013
11416
|
this.points = [first, { ...first }];
|
|
11014
11417
|
this.complete = false;
|
|
11015
11418
|
}
|
|
11016
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11419
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
11017
11420
|
if (this.points.length < 2) return;
|
|
11018
11421
|
const [p1, p2] = this.points;
|
|
11019
11422
|
const cycleLen = Math.abs(p2.barIndex - p1.barIndex);
|
|
11020
11423
|
if (cycleLen === 0) return;
|
|
11021
|
-
const
|
|
11424
|
+
const s = resolveStyle("timeCycles", this.style, theme);
|
|
11022
11425
|
const maxLines = 6;
|
|
11426
|
+
const bandFill = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
11023
11427
|
ctx.save();
|
|
11024
11428
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
11025
|
-
ctx.lineWidth = isSelected ?
|
|
11429
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
11026
11430
|
const lineXs = [];
|
|
11027
11431
|
for (let i = 0; i <= maxLines; i++) {
|
|
11028
11432
|
const barIdx = p1.barIndex + cycleLen * i;
|
|
@@ -11032,23 +11436,24 @@ var TimeCycles = class {
|
|
|
11032
11436
|
}
|
|
11033
11437
|
for (let i = 0; i + 1 < lineXs.length; i++) {
|
|
11034
11438
|
if (i % 2 === 0) {
|
|
11035
|
-
ctx.fillStyle =
|
|
11439
|
+
ctx.fillStyle = bandFill;
|
|
11036
11440
|
ctx.fillRect(lineXs[i], 0, lineXs[i + 1] - lineXs[i], chartH);
|
|
11037
11441
|
}
|
|
11038
11442
|
}
|
|
11039
|
-
ctx.strokeStyle =
|
|
11040
|
-
ctx.
|
|
11443
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11444
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
11041
11445
|
for (let i = 0; i < lineXs.length; i++) {
|
|
11042
11446
|
ctx.beginPath();
|
|
11043
11447
|
ctx.moveTo(lineXs[i], 0);
|
|
11044
11448
|
ctx.lineTo(lineXs[i], chartH);
|
|
11045
11449
|
ctx.stroke();
|
|
11046
|
-
ctx.fillStyle =
|
|
11450
|
+
ctx.fillStyle = s.strokeColor;
|
|
11047
11451
|
ctx.fillText(String(i + 1), lineXs[i] + 3, 14);
|
|
11048
11452
|
}
|
|
11453
|
+
ctx.setLineDash([]);
|
|
11049
11454
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
11050
11455
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
11051
|
-
ctx.fillStyle =
|
|
11456
|
+
ctx.fillStyle = s.strokeColor;
|
|
11052
11457
|
ctx.beginPath();
|
|
11053
11458
|
ctx.arc(x1, chartH / 2, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
11054
11459
|
ctx.fill();
|
|
@@ -11107,14 +11512,14 @@ var Projection = class {
|
|
|
11107
11512
|
this.points = [first, { ...first }];
|
|
11108
11513
|
this.complete = false;
|
|
11109
11514
|
}
|
|
11110
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11515
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
11111
11516
|
if (this.points.length < 2) return;
|
|
11112
11517
|
const [p1, p2] = this.points;
|
|
11113
11518
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
11114
11519
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
11115
11520
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
11116
11521
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
11117
|
-
const
|
|
11522
|
+
const s = resolveStyle("projection", this.style, theme);
|
|
11118
11523
|
const dxLine = x2 - x1;
|
|
11119
11524
|
let xEnd;
|
|
11120
11525
|
let yEnd;
|
|
@@ -11127,15 +11532,15 @@ var Projection = class {
|
|
|
11127
11532
|
yEnd = y2 + slope * (chartW - x2);
|
|
11128
11533
|
}
|
|
11129
11534
|
ctx.save();
|
|
11130
|
-
ctx.strokeStyle =
|
|
11131
|
-
ctx.fillStyle =
|
|
11132
|
-
ctx.lineWidth = isSelected ?
|
|
11535
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11536
|
+
ctx.fillStyle = s.strokeColor;
|
|
11537
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
11133
11538
|
ctx.setLineDash([]);
|
|
11134
11539
|
ctx.beginPath();
|
|
11135
11540
|
ctx.moveTo(x1, y1);
|
|
11136
11541
|
ctx.lineTo(x2, y2);
|
|
11137
11542
|
ctx.stroke();
|
|
11138
|
-
ctx.
|
|
11543
|
+
applyLineStyle(ctx, s.lineStyle === "solid" ? "dashed" : s.lineStyle, ctx.lineWidth);
|
|
11139
11544
|
ctx.beginPath();
|
|
11140
11545
|
ctx.moveTo(x2, y2);
|
|
11141
11546
|
ctx.lineTo(xEnd, yEnd);
|
|
@@ -11201,7 +11606,7 @@ var PriceProjection = class {
|
|
|
11201
11606
|
this.points = [first, { ...first }];
|
|
11202
11607
|
this.complete = false;
|
|
11203
11608
|
}
|
|
11204
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
11609
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
11205
11610
|
if (this.points.length < 2) return;
|
|
11206
11611
|
const [p1, p2] = this.points;
|
|
11207
11612
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -11216,13 +11621,14 @@ var PriceProjection = class {
|
|
|
11216
11621
|
const h = botY - topY;
|
|
11217
11622
|
const midX = left + w / 2;
|
|
11218
11623
|
const midPrice = (p1.price + p2.price) / 2;
|
|
11219
|
-
const
|
|
11624
|
+
const s = resolveStyle("priceProjection", this.style, theme);
|
|
11220
11625
|
ctx.save();
|
|
11221
11626
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
11222
|
-
ctx.lineWidth = isSelected ?
|
|
11223
|
-
ctx.fillStyle =
|
|
11627
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
11628
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
11224
11629
|
ctx.fillRect(left, topY, w, h);
|
|
11225
|
-
ctx.strokeStyle =
|
|
11630
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11631
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
11226
11632
|
ctx.beginPath();
|
|
11227
11633
|
ctx.moveTo(left, y1);
|
|
11228
11634
|
ctx.lineTo(right, y1);
|
|
@@ -11236,11 +11642,12 @@ var PriceProjection = class {
|
|
|
11236
11642
|
ctx.moveTo(left, y2);
|
|
11237
11643
|
ctx.lineTo(right, y2);
|
|
11238
11644
|
ctx.stroke();
|
|
11239
|
-
ctx.
|
|
11645
|
+
ctx.setLineDash([]);
|
|
11646
|
+
ctx.fillStyle = s.strokeColor;
|
|
11240
11647
|
ctx.fillText(p1.price.toFixed(pricePrecision), right + 4, y1 + 4);
|
|
11241
11648
|
ctx.fillText(midPrice.toFixed(pricePrecision), right + 4, yMid + 4);
|
|
11242
11649
|
ctx.fillText(p2.price.toFixed(pricePrecision), right + 4, y2 + 4);
|
|
11243
|
-
ctx.fillStyle =
|
|
11650
|
+
ctx.fillStyle = s.strokeColor;
|
|
11244
11651
|
ctx.beginPath();
|
|
11245
11652
|
ctx.moveTo(midX, topY);
|
|
11246
11653
|
ctx.lineTo(midX, botY);
|
|
@@ -11279,7 +11686,6 @@ var PriceProjection = class {
|
|
|
11279
11686
|
|
|
11280
11687
|
// src/drawings/GhostFeed.ts
|
|
11281
11688
|
var HIT_TOLERANCE68 = 6;
|
|
11282
|
-
var COLOR19 = "#90a4ae";
|
|
11283
11689
|
var GhostFeed = class {
|
|
11284
11690
|
constructor(first) {
|
|
11285
11691
|
this.type = "ghostFeed";
|
|
@@ -11287,7 +11693,7 @@ var GhostFeed = class {
|
|
|
11287
11693
|
this.points = [first, { ...first }];
|
|
11288
11694
|
this.complete = false;
|
|
11289
11695
|
}
|
|
11290
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11696
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
11291
11697
|
if (this.points.length < 2) return;
|
|
11292
11698
|
const [p1, p2] = this.points;
|
|
11293
11699
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -11298,19 +11704,20 @@ var GhostFeed = class {
|
|
|
11298
11704
|
const ry = Math.min(y1, y2);
|
|
11299
11705
|
const rw = Math.abs(x2 - x1);
|
|
11300
11706
|
const rh = Math.abs(y2 - y1);
|
|
11707
|
+
const s = resolveStyle("ghostFeed", this.style, theme);
|
|
11301
11708
|
ctx.save();
|
|
11302
|
-
ctx.fillStyle =
|
|
11709
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
11303
11710
|
ctx.fillRect(rx, ry, rw, rh);
|
|
11304
|
-
ctx.strokeStyle =
|
|
11305
|
-
ctx.lineWidth = isSelected ?
|
|
11306
|
-
ctx.
|
|
11711
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11712
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
11713
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
11307
11714
|
ctx.strokeRect(rx, ry, rw, rh);
|
|
11308
11715
|
ctx.setLineDash([]);
|
|
11309
11716
|
ctx.save();
|
|
11310
11717
|
ctx.beginPath();
|
|
11311
11718
|
ctx.rect(rx, ry, rw, rh);
|
|
11312
11719
|
ctx.clip();
|
|
11313
|
-
ctx.strokeStyle =
|
|
11720
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, 0.19);
|
|
11314
11721
|
ctx.lineWidth = 1;
|
|
11315
11722
|
ctx.setLineDash([4, 4]);
|
|
11316
11723
|
const step = 20;
|
|
@@ -11328,7 +11735,7 @@ var GhostFeed = class {
|
|
|
11328
11735
|
ctx.setLineDash([]);
|
|
11329
11736
|
ctx.restore();
|
|
11330
11737
|
ctx.font = "12px 'Inter', system-ui, sans-serif";
|
|
11331
|
-
ctx.fillStyle =
|
|
11738
|
+
ctx.fillStyle = s.strokeColor;
|
|
11332
11739
|
ctx.textAlign = "center";
|
|
11333
11740
|
ctx.textBaseline = "middle";
|
|
11334
11741
|
ctx.fillText("Ghost Feed", rx + rw / 2, ry + rh / 2);
|
|
@@ -11338,7 +11745,7 @@ var GhostFeed = class {
|
|
|
11338
11745
|
for (const [cx, cy] of [[x1, y1], [x2, y2]]) {
|
|
11339
11746
|
ctx.beginPath();
|
|
11340
11747
|
ctx.arc(cx, cy, 3.5, 0, Math.PI * 2);
|
|
11341
|
-
ctx.fillStyle =
|
|
11748
|
+
ctx.fillStyle = s.strokeColor;
|
|
11342
11749
|
ctx.fill();
|
|
11343
11750
|
}
|
|
11344
11751
|
}
|
|
@@ -11396,6 +11803,8 @@ var DrawingManager = class {
|
|
|
11396
11803
|
* original point's timestamp.
|
|
11397
11804
|
*/
|
|
11398
11805
|
this.onPointsMutated = null;
|
|
11806
|
+
/** Called whenever the selected drawing changes (including to/from null). */
|
|
11807
|
+
this.onSelectionChange = null;
|
|
11399
11808
|
/** Set to true by ChartEngine while the text input is focused — prevents Delete/Backspace from deleting the drawing. */
|
|
11400
11809
|
this.editingText = false;
|
|
11401
11810
|
// Drag state (move whole drawing)
|
|
@@ -11406,6 +11815,12 @@ var DrawingManager = class {
|
|
|
11406
11815
|
this.resizeDrawing = null;
|
|
11407
11816
|
this.resizePointIndex = -1;
|
|
11408
11817
|
}
|
|
11818
|
+
/** Centralizes selection mutation + change notification. */
|
|
11819
|
+
setSelected(drawing) {
|
|
11820
|
+
if (this.selectedDrawing === drawing) return;
|
|
11821
|
+
this.selectedDrawing = drawing;
|
|
11822
|
+
this.onSelectionChange?.(drawing);
|
|
11823
|
+
}
|
|
11409
11824
|
/** Called by ChartEngine to be notified when a drawing is finished. */
|
|
11410
11825
|
setOnComplete(cb) {
|
|
11411
11826
|
this.onCompleteCallback = cb;
|
|
@@ -11438,7 +11853,7 @@ var DrawingManager = class {
|
|
|
11438
11853
|
this.drawings.push(this.activeDrawing);
|
|
11439
11854
|
if (this.activeDrawing.complete) {
|
|
11440
11855
|
const completed = this.activeDrawing;
|
|
11441
|
-
this.
|
|
11856
|
+
this.setSelected(completed);
|
|
11442
11857
|
this.activeDrawing = null;
|
|
11443
11858
|
this.activeTool = null;
|
|
11444
11859
|
this.onCompleteCallback?.();
|
|
@@ -11468,7 +11883,7 @@ var DrawingManager = class {
|
|
|
11468
11883
|
}
|
|
11469
11884
|
this.activeDrawing.complete = true;
|
|
11470
11885
|
const completed = this.activeDrawing;
|
|
11471
|
-
this.
|
|
11886
|
+
this.setSelected(completed);
|
|
11472
11887
|
this.activeDrawing = null;
|
|
11473
11888
|
this.state = "idle";
|
|
11474
11889
|
this.activeTool = null;
|
|
@@ -11523,7 +11938,7 @@ var DrawingManager = class {
|
|
|
11523
11938
|
return;
|
|
11524
11939
|
}
|
|
11525
11940
|
this.activeDrawing.complete = true;
|
|
11526
|
-
this.
|
|
11941
|
+
this.setSelected(this.activeDrawing);
|
|
11527
11942
|
this.activeDrawing = null;
|
|
11528
11943
|
this.state = "idle";
|
|
11529
11944
|
this.activeTool = null;
|
|
@@ -11597,11 +12012,35 @@ var DrawingManager = class {
|
|
|
11597
12012
|
return null;
|
|
11598
12013
|
}
|
|
11599
12014
|
selectDrawing(drawing) {
|
|
11600
|
-
this.
|
|
12015
|
+
this.setSelected(drawing);
|
|
11601
12016
|
}
|
|
11602
12017
|
getSelectedDrawing() {
|
|
11603
12018
|
return this.selectedDrawing;
|
|
11604
12019
|
}
|
|
12020
|
+
/** Returns a drawing by id, or null if not found. */
|
|
12021
|
+
getDrawingById(id) {
|
|
12022
|
+
return this.drawings.find((d) => d.id === id) ?? null;
|
|
12023
|
+
}
|
|
12024
|
+
/**
|
|
12025
|
+
* Shallow-merge a style patch into the drawing's `style` field. Triggers
|
|
12026
|
+
* `onDrawingsChange` so the chart re-renders. Returns the updated drawing,
|
|
12027
|
+
* or null if no drawing matches `id`.
|
|
12028
|
+
*/
|
|
12029
|
+
updateDrawingStyle(id, patch) {
|
|
12030
|
+
const drawing = this.getDrawingById(id);
|
|
12031
|
+
if (!drawing) return null;
|
|
12032
|
+
drawing.style = { ...drawing.style ?? {}, ...patch };
|
|
12033
|
+
const patchLevels = patch.levels;
|
|
12034
|
+
const existingLevels = drawing.style.levels;
|
|
12035
|
+
if (patchLevels && existingLevels) {
|
|
12036
|
+
drawing.style.levels = {
|
|
12037
|
+
...existingLevels,
|
|
12038
|
+
...patchLevels
|
|
12039
|
+
};
|
|
12040
|
+
}
|
|
12041
|
+
this.notifyChange();
|
|
12042
|
+
return drawing;
|
|
12043
|
+
}
|
|
11605
12044
|
// ── Keyboard shortcuts ────────────────────────────────────────────────────
|
|
11606
12045
|
onKeyDown(key) {
|
|
11607
12046
|
if (key === "Escape") {
|
|
@@ -11611,7 +12050,7 @@ var DrawingManager = class {
|
|
|
11611
12050
|
this.state = "idle";
|
|
11612
12051
|
}
|
|
11613
12052
|
this.activeTool = null;
|
|
11614
|
-
this.
|
|
12053
|
+
this.setSelected(null);
|
|
11615
12054
|
return true;
|
|
11616
12055
|
}
|
|
11617
12056
|
if ((key === "Delete" || key === "Backspace") && this.selectedDrawing && !this.editingText) {
|
|
@@ -11628,7 +12067,7 @@ var DrawingManager = class {
|
|
|
11628
12067
|
if (!this.selectedDrawing) return;
|
|
11629
12068
|
this.snapshot();
|
|
11630
12069
|
this.drawings = this.drawings.filter((d) => d !== this.selectedDrawing);
|
|
11631
|
-
this.
|
|
12070
|
+
this.setSelected(null);
|
|
11632
12071
|
this.notifyChange();
|
|
11633
12072
|
}
|
|
11634
12073
|
undo() {
|
|
@@ -11636,7 +12075,7 @@ var DrawingManager = class {
|
|
|
11636
12075
|
if (prev !== void 0) {
|
|
11637
12076
|
this.redoStack.push(this.cloneDrawings(this.drawings));
|
|
11638
12077
|
this.drawings = prev;
|
|
11639
|
-
this.
|
|
12078
|
+
this.setSelected(null);
|
|
11640
12079
|
this.notifyChange();
|
|
11641
12080
|
}
|
|
11642
12081
|
}
|
|
@@ -11645,7 +12084,7 @@ var DrawingManager = class {
|
|
|
11645
12084
|
if (next !== void 0) {
|
|
11646
12085
|
this.history.push(this.cloneDrawings(this.drawings));
|
|
11647
12086
|
this.drawings = next;
|
|
11648
|
-
this.
|
|
12087
|
+
this.setSelected(null);
|
|
11649
12088
|
this.notifyChange();
|
|
11650
12089
|
}
|
|
11651
12090
|
}
|
|
@@ -11653,7 +12092,7 @@ var DrawingManager = class {
|
|
|
11653
12092
|
if (this.drawings.length === 0) return;
|
|
11654
12093
|
this.snapshot();
|
|
11655
12094
|
for (const d of this.drawings) d.visible = false;
|
|
11656
|
-
this.
|
|
12095
|
+
this.setSelected(null);
|
|
11657
12096
|
this.notifyChange();
|
|
11658
12097
|
}
|
|
11659
12098
|
showAll() {
|
|
@@ -11666,7 +12105,7 @@ var DrawingManager = class {
|
|
|
11666
12105
|
if (this.drawings.length === 0) return;
|
|
11667
12106
|
this.snapshot();
|
|
11668
12107
|
for (const d of this.drawings) d.locked = true;
|
|
11669
|
-
this.
|
|
12108
|
+
this.setSelected(null);
|
|
11670
12109
|
this.notifyChange();
|
|
11671
12110
|
}
|
|
11672
12111
|
unlockAll() {
|
|
@@ -11676,7 +12115,7 @@ var DrawingManager = class {
|
|
|
11676
12115
|
this.notifyChange();
|
|
11677
12116
|
}
|
|
11678
12117
|
// ── Rendering ─────────────────────────────────────────────────────────────
|
|
11679
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, pricePrecision = 2) {
|
|
12118
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, pricePrecision = 2, theme) {
|
|
11680
12119
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
11681
12120
|
if (this.drawings.length === 0) return;
|
|
11682
12121
|
const dpr = window.devicePixelRatio || 1;
|
|
@@ -11684,7 +12123,7 @@ var DrawingManager = class {
|
|
|
11684
12123
|
ctx.scale(dpr, dpr);
|
|
11685
12124
|
for (const drawing of this.drawings) {
|
|
11686
12125
|
if (drawing.visible === false) continue;
|
|
11687
|
-
drawing.render(ctx, scale, viewport, priceRange, chartW, chartH, drawing === this.selectedDrawing, pricePrecision);
|
|
12126
|
+
drawing.render(ctx, scale, viewport, priceRange, chartW, chartH, drawing === this.selectedDrawing, pricePrecision, theme);
|
|
11688
12127
|
}
|
|
11689
12128
|
ctx.restore();
|
|
11690
12129
|
}
|
|
@@ -11700,7 +12139,8 @@ var DrawingManager = class {
|
|
|
11700
12139
|
complete: d.complete,
|
|
11701
12140
|
...d.visible !== void 0 && { visible: d.visible },
|
|
11702
12141
|
...d.locked !== void 0 && { locked: d.locked },
|
|
11703
|
-
...d.text !== void 0 && { text: d.text }
|
|
12142
|
+
...d.text !== void 0 && { text: d.text },
|
|
12143
|
+
...d.style !== void 0 && { style: { ...d.style } }
|
|
11704
12144
|
}));
|
|
11705
12145
|
}
|
|
11706
12146
|
/** Restores drawings from a serialized snapshot, replacing current drawings. */
|
|
@@ -11713,9 +12153,10 @@ var DrawingManager = class {
|
|
|
11713
12153
|
if (sd.visible !== void 0) drawing.visible = sd.visible;
|
|
11714
12154
|
if (sd.locked !== void 0) drawing.locked = sd.locked;
|
|
11715
12155
|
if (sd.text !== void 0) drawing.text = sd.text;
|
|
12156
|
+
if (sd.style !== void 0) drawing.style = { ...sd.style };
|
|
11716
12157
|
return drawing;
|
|
11717
12158
|
});
|
|
11718
|
-
this.
|
|
12159
|
+
this.setSelected(null);
|
|
11719
12160
|
this.activeDrawing = null;
|
|
11720
12161
|
this.history = [this.drawings.map((d) => ({ ...d, points: d.points.map((p) => ({ ...p })) }))];
|
|
11721
12162
|
this.redoStack = [];
|
|
@@ -11725,7 +12166,7 @@ var DrawingManager = class {
|
|
|
11725
12166
|
if (this.drawings.length === 0) return;
|
|
11726
12167
|
this.snapshot();
|
|
11727
12168
|
this.drawings = [];
|
|
11728
|
-
this.
|
|
12169
|
+
this.setSelected(null);
|
|
11729
12170
|
this.activeDrawing = null;
|
|
11730
12171
|
this.notifyChange();
|
|
11731
12172
|
}
|
|
@@ -12855,30 +13296,48 @@ var ChartSettingsDialog = class {
|
|
|
12855
13296
|
}
|
|
12856
13297
|
this.content.appendChild(this.makeSectionHeader("Colors"));
|
|
12857
13298
|
this.content.appendChild(this.buildColorRows());
|
|
13299
|
+
this.content.appendChild(this.makeSectionHeader("Chart elements"));
|
|
13300
|
+
this.content.appendChild(
|
|
13301
|
+
this.makeHorizontalSettingRow(
|
|
13302
|
+
"Price-axis countdown",
|
|
13303
|
+
"Show the candle close countdown on the right price axis.",
|
|
13304
|
+
this.makeBooleanToggle(this.current.showPriceAxisCountdown, (v) => {
|
|
13305
|
+
this.current.showPriceAxisCountdown = v;
|
|
13306
|
+
this.emit();
|
|
13307
|
+
})
|
|
13308
|
+
)
|
|
13309
|
+
);
|
|
12858
13310
|
}
|
|
12859
|
-
|
|
12860
|
-
|
|
13311
|
+
/**
|
|
13312
|
+
* Reusable on/off toggle switch (track + animated thumb). Optional left/right
|
|
13313
|
+
* side-labels animate their weight and color as the switch flips.
|
|
13314
|
+
*/
|
|
13315
|
+
makeBooleanToggle(initial, onChange, sideLabels) {
|
|
13316
|
+
let value = initial;
|
|
12861
13317
|
const wrap = document.createElement("div");
|
|
12862
13318
|
Object.assign(wrap.style, {
|
|
12863
13319
|
display: "flex",
|
|
12864
13320
|
alignItems: "center",
|
|
12865
13321
|
gap: "8px"
|
|
12866
13322
|
});
|
|
12867
|
-
const
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
13323
|
+
const offLbl = sideLabels ? document.createElement("span") : null;
|
|
13324
|
+
const onLbl = sideLabels ? document.createElement("span") : null;
|
|
13325
|
+
if (offLbl && onLbl && sideLabels) {
|
|
13326
|
+
offLbl.textContent = sideLabels.off;
|
|
13327
|
+
onLbl.textContent = sideLabels.on;
|
|
13328
|
+
}
|
|
12871
13329
|
const updateLabels = () => {
|
|
12872
|
-
|
|
12873
|
-
|
|
12874
|
-
|
|
13330
|
+
if (!offLbl || !onLbl) return;
|
|
13331
|
+
Object.assign(offLbl.style, {
|
|
13332
|
+
color: value ? this.theme.axisText : this.theme.tooltip.text,
|
|
13333
|
+
fontWeight: value ? "400" : "500",
|
|
12875
13334
|
fontSize: "11px",
|
|
12876
13335
|
cursor: "pointer",
|
|
12877
13336
|
userSelect: "none"
|
|
12878
13337
|
});
|
|
12879
|
-
Object.assign(
|
|
12880
|
-
color:
|
|
12881
|
-
fontWeight:
|
|
13338
|
+
Object.assign(onLbl.style, {
|
|
13339
|
+
color: value ? this.theme.tooltip.text : this.theme.axisText,
|
|
13340
|
+
fontWeight: value ? "500" : "400",
|
|
12882
13341
|
fontSize: "11px",
|
|
12883
13342
|
cursor: "pointer",
|
|
12884
13343
|
userSelect: "none"
|
|
@@ -12901,7 +13360,7 @@ var ChartSettingsDialog = class {
|
|
|
12901
13360
|
Object.assign(thumb.style, {
|
|
12902
13361
|
position: "absolute",
|
|
12903
13362
|
top: "2px",
|
|
12904
|
-
left:
|
|
13363
|
+
left: value ? "18px" : "2px",
|
|
12905
13364
|
width: "14px",
|
|
12906
13365
|
height: "14px",
|
|
12907
13366
|
borderRadius: "50%",
|
|
@@ -12911,24 +13370,39 @@ var ChartSettingsDialog = class {
|
|
|
12911
13370
|
});
|
|
12912
13371
|
track.appendChild(thumb);
|
|
12913
13372
|
const doToggle = () => {
|
|
12914
|
-
|
|
12915
|
-
thumb.style.left =
|
|
12916
|
-
this.current.theme = isLight ? "light" : "dark";
|
|
13373
|
+
value = !value;
|
|
13374
|
+
thumb.style.left = value ? "18px" : "2px";
|
|
12917
13375
|
updateLabels();
|
|
12918
|
-
|
|
12919
|
-
this.renderContent();
|
|
13376
|
+
onChange(value);
|
|
12920
13377
|
};
|
|
12921
13378
|
track.addEventListener("click", doToggle);
|
|
12922
|
-
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
|
|
12926
|
-
|
|
12927
|
-
|
|
13379
|
+
if (offLbl && onLbl) {
|
|
13380
|
+
offLbl.addEventListener("click", () => {
|
|
13381
|
+
if (value) doToggle();
|
|
13382
|
+
});
|
|
13383
|
+
onLbl.addEventListener("click", () => {
|
|
13384
|
+
if (!value) doToggle();
|
|
13385
|
+
});
|
|
13386
|
+
}
|
|
12928
13387
|
updateLabels();
|
|
12929
|
-
|
|
13388
|
+
if (offLbl && onLbl) {
|
|
13389
|
+
wrap.append(offLbl, track, onLbl);
|
|
13390
|
+
} else {
|
|
13391
|
+
wrap.append(track);
|
|
13392
|
+
}
|
|
12930
13393
|
return wrap;
|
|
12931
13394
|
}
|
|
13395
|
+
makeThemeToggle() {
|
|
13396
|
+
return this.makeBooleanToggle(
|
|
13397
|
+
this.current.theme === "light",
|
|
13398
|
+
(isLight) => {
|
|
13399
|
+
this.current.theme = isLight ? "light" : "dark";
|
|
13400
|
+
this.emit();
|
|
13401
|
+
this.renderContent();
|
|
13402
|
+
},
|
|
13403
|
+
{ off: "Dark", on: "Light" }
|
|
13404
|
+
);
|
|
13405
|
+
}
|
|
12932
13406
|
/**
|
|
12933
13407
|
* Renders all canvas colors as a label → swatch(es) grid.
|
|
12934
13408
|
* All swatch columns are pixel-aligned regardless of label length.
|
|
@@ -13100,6 +13574,16 @@ var ChartSettingsDialog = class {
|
|
|
13100
13574
|
)
|
|
13101
13575
|
)
|
|
13102
13576
|
);
|
|
13577
|
+
this.content.appendChild(
|
|
13578
|
+
this.makeHorizontalSettingRow(
|
|
13579
|
+
"Always show SL/TP",
|
|
13580
|
+
"Keep stop-loss and take-profit lines visible without hovering.",
|
|
13581
|
+
this.makeBooleanToggle(this.current.showTradeLevelsAlways, (v) => {
|
|
13582
|
+
this.current.showTradeLevelsAlways = v;
|
|
13583
|
+
this.emit();
|
|
13584
|
+
})
|
|
13585
|
+
)
|
|
13586
|
+
);
|
|
13103
13587
|
}
|
|
13104
13588
|
// ── Private – helpers ─────────────────────────────────────────────────────
|
|
13105
13589
|
makeSectionHeader(label, first = false) {
|
|
@@ -13154,6 +13638,54 @@ var ChartSettingsDialog = class {
|
|
|
13154
13638
|
row.append(textGroup, control);
|
|
13155
13639
|
return row;
|
|
13156
13640
|
}
|
|
13641
|
+
/**
|
|
13642
|
+
* Like makeVerticalSettingRow, but the control is placed on the right side
|
|
13643
|
+
* on the same line as the label, vertically centered.
|
|
13644
|
+
*/
|
|
13645
|
+
makeHorizontalSettingRow(label, desc, control) {
|
|
13646
|
+
const row = document.createElement("div");
|
|
13647
|
+
Object.assign(row.style, {
|
|
13648
|
+
display: "flex",
|
|
13649
|
+
flexDirection: "row",
|
|
13650
|
+
alignItems: "center",
|
|
13651
|
+
justifyContent: "space-between",
|
|
13652
|
+
gap: "12px",
|
|
13653
|
+
padding: "14px 0",
|
|
13654
|
+
borderBottom: `1px solid ${this.theme.axisBorder}`
|
|
13655
|
+
});
|
|
13656
|
+
const textGroup = document.createElement("div");
|
|
13657
|
+
Object.assign(textGroup.style, {
|
|
13658
|
+
display: "flex",
|
|
13659
|
+
flexDirection: "column",
|
|
13660
|
+
gap: "0px",
|
|
13661
|
+
flex: "1",
|
|
13662
|
+
minWidth: "0"
|
|
13663
|
+
});
|
|
13664
|
+
const labelEl = document.createElement("span");
|
|
13665
|
+
labelEl.textContent = label;
|
|
13666
|
+
Object.assign(labelEl.style, {
|
|
13667
|
+
color: this.theme.tooltip.text,
|
|
13668
|
+
fontSize: "12px",
|
|
13669
|
+
fontWeight: "500"
|
|
13670
|
+
});
|
|
13671
|
+
const descEl = document.createElement("span");
|
|
13672
|
+
descEl.textContent = desc;
|
|
13673
|
+
Object.assign(descEl.style, {
|
|
13674
|
+
color: this.theme.axisText,
|
|
13675
|
+
fontSize: "10px"
|
|
13676
|
+
});
|
|
13677
|
+
textGroup.append(labelEl, descEl);
|
|
13678
|
+
const controlWrap = document.createElement("div");
|
|
13679
|
+
Object.assign(controlWrap.style, {
|
|
13680
|
+
display: "flex",
|
|
13681
|
+
alignItems: "center",
|
|
13682
|
+
justifyContent: "center",
|
|
13683
|
+
flexShrink: "0"
|
|
13684
|
+
});
|
|
13685
|
+
controlWrap.appendChild(control);
|
|
13686
|
+
row.append(textGroup, controlWrap);
|
|
13687
|
+
return row;
|
|
13688
|
+
}
|
|
13157
13689
|
/** Segmented control (pill-style button group). */
|
|
13158
13690
|
makeSegmented(options, current, onChange) {
|
|
13159
13691
|
const wrap = document.createElement("div");
|
|
@@ -13470,6 +14002,518 @@ var TradePopover = class {
|
|
|
13470
14002
|
}
|
|
13471
14003
|
};
|
|
13472
14004
|
|
|
14005
|
+
// src/ui/DrawingStylePopover.ts
|
|
14006
|
+
var POPOVER_CONTROLS = {
|
|
14007
|
+
// Lines & channels
|
|
14008
|
+
horizontalLine: ["stroke", "width", "style"],
|
|
14009
|
+
verticalLine: ["stroke", "width", "style"],
|
|
14010
|
+
crossLine: ["stroke", "width", "style"],
|
|
14011
|
+
trendLine: ["stroke", "width", "style"],
|
|
14012
|
+
trendAngle: ["stroke", "width", "style"],
|
|
14013
|
+
parallelChannel: ["stroke", "width", "style", "fill"],
|
|
14014
|
+
flatChannel: ["stroke", "width", "style", "fill"],
|
|
14015
|
+
disjointChannel: ["stroke", "width", "style"],
|
|
14016
|
+
// Geometric shapes
|
|
14017
|
+
rectangle: ["stroke", "width", "style", "fill"],
|
|
14018
|
+
rotatedRectangle: ["stroke", "width", "style", "fill"],
|
|
14019
|
+
ellipse: ["stroke", "width", "style", "fill"],
|
|
14020
|
+
circle: ["stroke", "width", "style", "fill"],
|
|
14021
|
+
triangle: ["stroke", "width", "style", "fill"],
|
|
14022
|
+
arc: ["stroke", "width", "style"],
|
|
14023
|
+
path: ["stroke", "width", "style"],
|
|
14024
|
+
polyline: ["stroke", "width", "style"],
|
|
14025
|
+
sineLine: ["stroke", "width", "style"],
|
|
14026
|
+
brush: ["stroke", "width"],
|
|
14027
|
+
highlighter: ["stroke", "width"],
|
|
14028
|
+
// Fib (per-level)
|
|
14029
|
+
fibRetracement: ["width", "style", "fibLevels"],
|
|
14030
|
+
fibExtension: ["width", "style", "fibLevels"],
|
|
14031
|
+
fibProjection: ["width", "style", "fibLevels"],
|
|
14032
|
+
fibChannel: ["stroke", "width", "style"],
|
|
14033
|
+
fibFan: ["stroke", "width", "style"],
|
|
14034
|
+
fibCircles: ["stroke", "width", "style"],
|
|
14035
|
+
fibSpiral: ["stroke", "width", "style"],
|
|
14036
|
+
fibTimezone: ["stroke", "width", "style"],
|
|
14037
|
+
// Gann
|
|
14038
|
+
gannFan: ["stroke", "width", "style"],
|
|
14039
|
+
gannSquare: ["stroke", "width", "style"],
|
|
14040
|
+
gannBox: ["stroke", "width", "style", "fill"],
|
|
14041
|
+
// Pitchfork
|
|
14042
|
+
pitchfork: ["stroke", "width", "style"],
|
|
14043
|
+
schiffPitchfork: ["stroke", "width", "style"],
|
|
14044
|
+
modifiedSchiff: ["stroke", "width", "style"],
|
|
14045
|
+
insidePitchfork: ["stroke", "width", "style"],
|
|
14046
|
+
// Text-bearing
|
|
14047
|
+
text: ["text"],
|
|
14048
|
+
callout: ["text", "stroke", "fill"],
|
|
14049
|
+
priceLabel: ["text", "fill"],
|
|
14050
|
+
priceNote: ["text", "stroke"],
|
|
14051
|
+
anchoredNote: ["text", "stroke", "fill"],
|
|
14052
|
+
// Arrows / markers
|
|
14053
|
+
arrowUp: ["stroke", "fill"],
|
|
14054
|
+
arrowDown: ["stroke", "fill"],
|
|
14055
|
+
arrowMarker: ["stroke", "fill"],
|
|
14056
|
+
flag: ["stroke", "fill"],
|
|
14057
|
+
// Volume profile
|
|
14058
|
+
volumeProfile: ["stroke", "fill"],
|
|
14059
|
+
anchoredVP: ["stroke", "fill"],
|
|
14060
|
+
fixedRangeVP: ["stroke", "fill"],
|
|
14061
|
+
// Measurement
|
|
14062
|
+
priceRange: ["stroke", "fill"],
|
|
14063
|
+
dateRange: ["stroke", "width", "style"],
|
|
14064
|
+
datePriceRange: ["stroke", "fill"],
|
|
14065
|
+
ruler: ["stroke", "width", "style"],
|
|
14066
|
+
// Advanced
|
|
14067
|
+
regressionTrend: ["stroke", "width", "style"],
|
|
14068
|
+
projection: ["stroke", "width", "style"],
|
|
14069
|
+
cyclicLines: ["stroke", "width", "style"],
|
|
14070
|
+
timeCycles: ["stroke", "width", "style", "fill"],
|
|
14071
|
+
priceProjection: ["stroke", "fill"],
|
|
14072
|
+
ghostFeed: ["stroke", "fill"]
|
|
14073
|
+
};
|
|
14074
|
+
var LINE_WIDTHS = [1, 2, 3, 4];
|
|
14075
|
+
var FONT_SIZES = [10, 11, 12, 13, 14, 16, 18, 20, 24];
|
|
14076
|
+
var SWATCH_PX = 24;
|
|
14077
|
+
var DrawingStylePopover = class {
|
|
14078
|
+
constructor(container, theme, onChange) {
|
|
14079
|
+
this.currentDrawing = null;
|
|
14080
|
+
this.fibLevelsExpanded = false;
|
|
14081
|
+
this.fibPanel = null;
|
|
14082
|
+
this.container = container;
|
|
14083
|
+
this.theme = theme;
|
|
14084
|
+
this.onChange = onChange;
|
|
14085
|
+
this.el = document.createElement("div");
|
|
14086
|
+
Object.assign(this.el.style, {
|
|
14087
|
+
position: "absolute",
|
|
14088
|
+
display: "none",
|
|
14089
|
+
flexDirection: "row",
|
|
14090
|
+
alignItems: "center",
|
|
14091
|
+
gap: "10px",
|
|
14092
|
+
background: theme.tooltip.background,
|
|
14093
|
+
border: `1px solid ${theme.axisBorder}`,
|
|
14094
|
+
borderRadius: "8px",
|
|
14095
|
+
padding: "8px 10px",
|
|
14096
|
+
zIndex: "50",
|
|
14097
|
+
boxShadow: "0 6px 18px rgba(0,0,0,0.35)",
|
|
14098
|
+
fontFamily: "'Inter', system-ui, sans-serif",
|
|
14099
|
+
fontSize: "11px",
|
|
14100
|
+
color: theme.tooltip.text,
|
|
14101
|
+
pointerEvents: "auto",
|
|
14102
|
+
userSelect: "none",
|
|
14103
|
+
whiteSpace: "nowrap"
|
|
14104
|
+
});
|
|
14105
|
+
container.appendChild(this.el);
|
|
14106
|
+
}
|
|
14107
|
+
setTheme(theme) {
|
|
14108
|
+
this.theme = theme;
|
|
14109
|
+
this.el.style.background = theme.tooltip.background;
|
|
14110
|
+
this.el.style.border = `1px solid ${theme.axisBorder}`;
|
|
14111
|
+
this.el.style.color = theme.tooltip.text;
|
|
14112
|
+
if (this.currentDrawing) this.rebuild();
|
|
14113
|
+
}
|
|
14114
|
+
/** Show the popover for a drawing, positioned at (x, y) in container-relative coords. */
|
|
14115
|
+
show(drawing, x, y) {
|
|
14116
|
+
const wasSameId = this.currentDrawing?.id === drawing.id;
|
|
14117
|
+
this.currentDrawing = drawing;
|
|
14118
|
+
if (!wasSameId) {
|
|
14119
|
+
this.fibLevelsExpanded = false;
|
|
14120
|
+
this.rebuild();
|
|
14121
|
+
}
|
|
14122
|
+
this.el.style.display = "flex";
|
|
14123
|
+
this.setAnchor(x, y);
|
|
14124
|
+
}
|
|
14125
|
+
/** Reposition the popover without rebuilding controls. */
|
|
14126
|
+
setAnchor(x, y) {
|
|
14127
|
+
requestAnimationFrame(() => {
|
|
14128
|
+
const rect = this.el.getBoundingClientRect();
|
|
14129
|
+
const cRect = this.container.getBoundingClientRect();
|
|
14130
|
+
const w = rect.width || 240;
|
|
14131
|
+
const h = rect.height || 56;
|
|
14132
|
+
let left = x - w / 2;
|
|
14133
|
+
let top = y - h - 12;
|
|
14134
|
+
if (top < 4) top = y + 16;
|
|
14135
|
+
left = Math.max(4, Math.min(cRect.width - w - 4, left));
|
|
14136
|
+
top = Math.max(4, Math.min(cRect.height - h - 4, top));
|
|
14137
|
+
this.el.style.left = `${left}px`;
|
|
14138
|
+
this.el.style.top = `${top}px`;
|
|
14139
|
+
});
|
|
14140
|
+
}
|
|
14141
|
+
hide() {
|
|
14142
|
+
this.el.style.display = "none";
|
|
14143
|
+
this.currentDrawing = null;
|
|
14144
|
+
this.fibLevelsExpanded = false;
|
|
14145
|
+
this.fibPanel?.remove();
|
|
14146
|
+
this.fibPanel = null;
|
|
14147
|
+
}
|
|
14148
|
+
destroy() {
|
|
14149
|
+
this.fibPanel?.remove();
|
|
14150
|
+
this.el.remove();
|
|
14151
|
+
this.currentDrawing = null;
|
|
14152
|
+
}
|
|
14153
|
+
// ── Build ────────────────────────────────────────────────────────────────
|
|
14154
|
+
rebuild() {
|
|
14155
|
+
this.el.innerHTML = "";
|
|
14156
|
+
if (!this.currentDrawing) return;
|
|
14157
|
+
const controls = POPOVER_CONTROLS[this.currentDrawing.type] ?? ["stroke", "width", "style"];
|
|
14158
|
+
const style = this.currentDrawing.style ?? {};
|
|
14159
|
+
const themeEntry = this.theme.drawing[this.currentDrawing.type] ?? {};
|
|
14160
|
+
const groups = [];
|
|
14161
|
+
for (const key of controls) {
|
|
14162
|
+
switch (key) {
|
|
14163
|
+
case "stroke":
|
|
14164
|
+
groups.push(this.group(
|
|
14165
|
+
"Color",
|
|
14166
|
+
this.makeColorSwatch(
|
|
14167
|
+
style.strokeColor ?? themeEntry.strokeColor ?? "#888888",
|
|
14168
|
+
(hex) => this.emit({ strokeColor: hex })
|
|
14169
|
+
)
|
|
14170
|
+
));
|
|
14171
|
+
break;
|
|
14172
|
+
case "width":
|
|
14173
|
+
groups.push(this.group(
|
|
14174
|
+
"Width",
|
|
14175
|
+
this.buildWidth(
|
|
14176
|
+
style.lineWidth ?? themeEntry.lineWidth ?? 1,
|
|
14177
|
+
(w) => this.emit({ lineWidth: w })
|
|
14178
|
+
)
|
|
14179
|
+
));
|
|
14180
|
+
break;
|
|
14181
|
+
case "style":
|
|
14182
|
+
groups.push(this.group(
|
|
14183
|
+
"Style",
|
|
14184
|
+
this.buildLineStyle(
|
|
14185
|
+
style.lineStyle ?? themeEntry.lineStyle ?? "solid",
|
|
14186
|
+
(s) => this.emit({ lineStyle: s })
|
|
14187
|
+
)
|
|
14188
|
+
));
|
|
14189
|
+
break;
|
|
14190
|
+
case "fill": {
|
|
14191
|
+
const fillRow = document.createElement("div");
|
|
14192
|
+
Object.assign(fillRow.style, { display: "flex", alignItems: "center", gap: "6px" });
|
|
14193
|
+
fillRow.appendChild(this.makeColorSwatch(
|
|
14194
|
+
style.fillColor ?? themeEntry.fillColor ?? "#888888",
|
|
14195
|
+
(hex) => this.emit({ fillColor: hex })
|
|
14196
|
+
));
|
|
14197
|
+
fillRow.appendChild(this.buildOpacity(
|
|
14198
|
+
style.fillOpacity ?? themeEntry.fillOpacity ?? 0.1,
|
|
14199
|
+
(o) => this.emit({ fillOpacity: o })
|
|
14200
|
+
));
|
|
14201
|
+
groups.push(this.group("Fill", fillRow));
|
|
14202
|
+
break;
|
|
14203
|
+
}
|
|
14204
|
+
case "text": {
|
|
14205
|
+
const textRow = document.createElement("div");
|
|
14206
|
+
Object.assign(textRow.style, { display: "flex", alignItems: "center", gap: "6px" });
|
|
14207
|
+
textRow.appendChild(this.makeColorSwatch(
|
|
14208
|
+
style.textColor ?? themeEntry.textColor ?? "#f5f5f5",
|
|
14209
|
+
(hex) => this.emit({ textColor: hex })
|
|
14210
|
+
));
|
|
14211
|
+
textRow.appendChild(this.buildFontSize(
|
|
14212
|
+
style.fontSize ?? themeEntry.fontSize ?? 13,
|
|
14213
|
+
(size) => this.emit({ fontSize: size })
|
|
14214
|
+
));
|
|
14215
|
+
groups.push(this.group("Text", textRow));
|
|
14216
|
+
break;
|
|
14217
|
+
}
|
|
14218
|
+
case "fibLevels":
|
|
14219
|
+
groups.push(this.group(
|
|
14220
|
+
"Levels",
|
|
14221
|
+
this.buildFibLevelsButton({ ...themeEntry.levels ?? {}, ...style.levels ?? {} })
|
|
14222
|
+
));
|
|
14223
|
+
break;
|
|
14224
|
+
}
|
|
14225
|
+
}
|
|
14226
|
+
groups.forEach((g, i) => {
|
|
14227
|
+
if (i > 0) this.el.appendChild(this.makeSeparator());
|
|
14228
|
+
this.el.appendChild(g);
|
|
14229
|
+
});
|
|
14230
|
+
}
|
|
14231
|
+
// ── Layout helpers ───────────────────────────────────────────────────────
|
|
14232
|
+
group(label, control) {
|
|
14233
|
+
const wrap = document.createElement("div");
|
|
14234
|
+
Object.assign(wrap.style, {
|
|
14235
|
+
display: "flex",
|
|
14236
|
+
flexDirection: "column",
|
|
14237
|
+
alignItems: "flex-start",
|
|
14238
|
+
gap: "4px"
|
|
14239
|
+
});
|
|
14240
|
+
const lbl = document.createElement("span");
|
|
14241
|
+
lbl.textContent = label;
|
|
14242
|
+
Object.assign(lbl.style, {
|
|
14243
|
+
fontSize: "10px",
|
|
14244
|
+
color: this.theme.axisText,
|
|
14245
|
+
textTransform: "uppercase",
|
|
14246
|
+
letterSpacing: "0.5px",
|
|
14247
|
+
lineHeight: "1"
|
|
14248
|
+
});
|
|
14249
|
+
wrap.appendChild(lbl);
|
|
14250
|
+
wrap.appendChild(control);
|
|
14251
|
+
return wrap;
|
|
14252
|
+
}
|
|
14253
|
+
makeSeparator() {
|
|
14254
|
+
const sep = document.createElement("div");
|
|
14255
|
+
Object.assign(sep.style, {
|
|
14256
|
+
width: "1px",
|
|
14257
|
+
height: "32px",
|
|
14258
|
+
background: this.theme.axisBorder,
|
|
14259
|
+
opacity: "0.6"
|
|
14260
|
+
});
|
|
14261
|
+
return sep;
|
|
14262
|
+
}
|
|
14263
|
+
// ── Controls ────────────────────────────────────────────────────────────
|
|
14264
|
+
/**
|
|
14265
|
+
* 24×24 color swatch backed by a hidden `<input type="color">`. Mirrors
|
|
14266
|
+
* `ChartSettingsDialog.makeColorSwatch` so all color pickers in the app
|
|
14267
|
+
* share the same look.
|
|
14268
|
+
*/
|
|
14269
|
+
makeColorSwatch(initialHex, onChange) {
|
|
14270
|
+
const borderColor = `${this.theme.axisText}80`;
|
|
14271
|
+
const safeHex = initialHex.length === 7 && initialHex.startsWith("#") ? initialHex : "#888888";
|
|
14272
|
+
const wrap = document.createElement("div");
|
|
14273
|
+
Object.assign(wrap.style, {
|
|
14274
|
+
position: "relative",
|
|
14275
|
+
width: `${SWATCH_PX}px`,
|
|
14276
|
+
height: `${SWATCH_PX}px`,
|
|
14277
|
+
borderRadius: "6px",
|
|
14278
|
+
overflow: "hidden",
|
|
14279
|
+
cursor: "pointer",
|
|
14280
|
+
border: `1.5px solid ${borderColor}`,
|
|
14281
|
+
flexShrink: "0",
|
|
14282
|
+
boxSizing: "border-box"
|
|
14283
|
+
});
|
|
14284
|
+
const swatch = document.createElement("div");
|
|
14285
|
+
Object.assign(swatch.style, {
|
|
14286
|
+
position: "absolute",
|
|
14287
|
+
inset: "0",
|
|
14288
|
+
background: initialHex
|
|
14289
|
+
});
|
|
14290
|
+
const input = document.createElement("input");
|
|
14291
|
+
input.type = "color";
|
|
14292
|
+
input.value = safeHex;
|
|
14293
|
+
Object.assign(input.style, {
|
|
14294
|
+
position: "absolute",
|
|
14295
|
+
inset: "0",
|
|
14296
|
+
opacity: "0",
|
|
14297
|
+
width: "100%",
|
|
14298
|
+
height: "100%",
|
|
14299
|
+
cursor: "pointer",
|
|
14300
|
+
padding: "0",
|
|
14301
|
+
border: "none"
|
|
14302
|
+
});
|
|
14303
|
+
let debounceTimer = null;
|
|
14304
|
+
input.addEventListener("input", () => {
|
|
14305
|
+
swatch.style.background = input.value;
|
|
14306
|
+
if (debounceTimer !== null) clearTimeout(debounceTimer);
|
|
14307
|
+
debounceTimer = setTimeout(() => {
|
|
14308
|
+
debounceTimer = null;
|
|
14309
|
+
onChange(input.value);
|
|
14310
|
+
}, 200);
|
|
14311
|
+
});
|
|
14312
|
+
wrap.append(swatch, input);
|
|
14313
|
+
return wrap;
|
|
14314
|
+
}
|
|
14315
|
+
buildWidth(currentWidth, onPick) {
|
|
14316
|
+
const wrap = document.createElement("div");
|
|
14317
|
+
Object.assign(wrap.style, { display: "flex", gap: "2px", alignItems: "center" });
|
|
14318
|
+
for (const w of LINE_WIDTHS) {
|
|
14319
|
+
const btn = this.makeIconButton(`${w}px`);
|
|
14320
|
+
const bar = document.createElement("div");
|
|
14321
|
+
Object.assign(bar.style, {
|
|
14322
|
+
width: "14px",
|
|
14323
|
+
height: `${w}px`,
|
|
14324
|
+
background: this.theme.tooltip.text,
|
|
14325
|
+
borderRadius: "1px"
|
|
14326
|
+
});
|
|
14327
|
+
btn.appendChild(bar);
|
|
14328
|
+
if (Math.round(currentWidth) === w) this.markActive(btn);
|
|
14329
|
+
btn.addEventListener("click", () => onPick(w));
|
|
14330
|
+
wrap.appendChild(btn);
|
|
14331
|
+
}
|
|
14332
|
+
return wrap;
|
|
14333
|
+
}
|
|
14334
|
+
buildLineStyle(current, onPick) {
|
|
14335
|
+
const wrap = document.createElement("div");
|
|
14336
|
+
Object.assign(wrap.style, { display: "flex", gap: "2px", alignItems: "center" });
|
|
14337
|
+
const styles = [
|
|
14338
|
+
{ key: "solid", svg: this.lineSvg("solid"), title: "Solid" },
|
|
14339
|
+
{ key: "dashed", svg: this.lineSvg("dashed"), title: "Dashed" },
|
|
14340
|
+
{ key: "dotted", svg: this.lineSvg("dotted"), title: "Dotted" }
|
|
14341
|
+
];
|
|
14342
|
+
for (const { key, svg, title } of styles) {
|
|
14343
|
+
const btn = this.makeIconButton(title);
|
|
14344
|
+
btn.innerHTML = svg;
|
|
14345
|
+
if (key === current) this.markActive(btn);
|
|
14346
|
+
btn.addEventListener("click", () => onPick(key));
|
|
14347
|
+
wrap.appendChild(btn);
|
|
14348
|
+
}
|
|
14349
|
+
return wrap;
|
|
14350
|
+
}
|
|
14351
|
+
lineSvg(style) {
|
|
14352
|
+
const dash = style === "dashed" ? "4,2" : style === "dotted" ? "1.5,2" : "";
|
|
14353
|
+
return `<svg width="18" height="10" viewBox="0 0 18 10"><line x1="1" y1="5" x2="17" y2="5" stroke="${this.theme.tooltip.text}" stroke-width="1.5" stroke-linecap="round" ${dash ? `stroke-dasharray="${dash}"` : ""} /></svg>`;
|
|
14354
|
+
}
|
|
14355
|
+
buildOpacity(current, onChange) {
|
|
14356
|
+
const wrap = document.createElement("div");
|
|
14357
|
+
Object.assign(wrap.style, { display: "flex", alignItems: "center", gap: "4px" });
|
|
14358
|
+
const slider = document.createElement("input");
|
|
14359
|
+
slider.type = "range";
|
|
14360
|
+
slider.min = "0";
|
|
14361
|
+
slider.max = "100";
|
|
14362
|
+
slider.value = String(Math.round(current * 100));
|
|
14363
|
+
Object.assign(slider.style, { width: "64px", accentColor: this.theme.ui.accent });
|
|
14364
|
+
slider.title = "Fill opacity";
|
|
14365
|
+
const value = document.createElement("span");
|
|
14366
|
+
value.textContent = `${slider.value}%`;
|
|
14367
|
+
Object.assign(value.style, { fontSize: "10px", color: this.theme.axisText, minWidth: "26px", textAlign: "right" });
|
|
14368
|
+
slider.addEventListener("input", () => {
|
|
14369
|
+
value.textContent = `${slider.value}%`;
|
|
14370
|
+
onChange(Number(slider.value) / 100);
|
|
14371
|
+
});
|
|
14372
|
+
wrap.append(slider, value);
|
|
14373
|
+
return wrap;
|
|
14374
|
+
}
|
|
14375
|
+
buildFontSize(current, onPick) {
|
|
14376
|
+
const select = document.createElement("select");
|
|
14377
|
+
Object.assign(select.style, {
|
|
14378
|
+
background: this.theme.tooltip.background,
|
|
14379
|
+
color: this.theme.tooltip.text,
|
|
14380
|
+
border: `1px solid ${this.theme.axisBorder}`,
|
|
14381
|
+
borderRadius: "4px",
|
|
14382
|
+
padding: "3px 4px",
|
|
14383
|
+
fontSize: "11px",
|
|
14384
|
+
cursor: "pointer",
|
|
14385
|
+
height: `${SWATCH_PX}px`,
|
|
14386
|
+
boxSizing: "border-box"
|
|
14387
|
+
});
|
|
14388
|
+
for (const s of FONT_SIZES) {
|
|
14389
|
+
const opt = document.createElement("option");
|
|
14390
|
+
opt.value = String(s);
|
|
14391
|
+
opt.textContent = `${s}px`;
|
|
14392
|
+
if (s === current) opt.selected = true;
|
|
14393
|
+
select.appendChild(opt);
|
|
14394
|
+
}
|
|
14395
|
+
select.addEventListener("change", () => onPick(Number(select.value)));
|
|
14396
|
+
return select;
|
|
14397
|
+
}
|
|
14398
|
+
buildFibLevelsButton(levels) {
|
|
14399
|
+
const wrap = document.createElement("div");
|
|
14400
|
+
wrap.style.position = "relative";
|
|
14401
|
+
const btn = this.makeIconButton("Configure levels");
|
|
14402
|
+
btn.style.padding = "0 8px";
|
|
14403
|
+
btn.style.height = `${SWATCH_PX}px`;
|
|
14404
|
+
btn.style.minWidth = "64px";
|
|
14405
|
+
btn.textContent = "Configure \u25BE";
|
|
14406
|
+
wrap.appendChild(btn);
|
|
14407
|
+
btn.addEventListener("click", (e) => {
|
|
14408
|
+
e.stopPropagation();
|
|
14409
|
+
this.fibLevelsExpanded = !this.fibLevelsExpanded;
|
|
14410
|
+
if (this.fibLevelsExpanded) this.openFibPanel(btn, levels);
|
|
14411
|
+
else this.closeFibPanel();
|
|
14412
|
+
});
|
|
14413
|
+
document.addEventListener("mousedown", (e) => {
|
|
14414
|
+
if (this.fibPanel && !this.fibPanel.contains(e.target) && !wrap.contains(e.target)) {
|
|
14415
|
+
this.closeFibPanel();
|
|
14416
|
+
this.fibLevelsExpanded = false;
|
|
14417
|
+
}
|
|
14418
|
+
});
|
|
14419
|
+
return wrap;
|
|
14420
|
+
}
|
|
14421
|
+
openFibPanel(anchor, levels) {
|
|
14422
|
+
this.closeFibPanel();
|
|
14423
|
+
const panel = document.createElement("div");
|
|
14424
|
+
Object.assign(panel.style, {
|
|
14425
|
+
position: "absolute",
|
|
14426
|
+
display: "grid",
|
|
14427
|
+
gridTemplateColumns: "auto 1fr auto",
|
|
14428
|
+
columnGap: "10px",
|
|
14429
|
+
rowGap: "6px",
|
|
14430
|
+
background: this.theme.tooltip.background,
|
|
14431
|
+
border: `1px solid ${this.theme.axisBorder}`,
|
|
14432
|
+
borderRadius: "6px",
|
|
14433
|
+
padding: "10px 12px",
|
|
14434
|
+
zIndex: "60",
|
|
14435
|
+
boxShadow: "0 6px 18px rgba(0,0,0,0.35)",
|
|
14436
|
+
fontFamily: "'Inter', system-ui, sans-serif",
|
|
14437
|
+
fontSize: "11px",
|
|
14438
|
+
color: this.theme.tooltip.text,
|
|
14439
|
+
alignItems: "center"
|
|
14440
|
+
});
|
|
14441
|
+
const keys = Object.keys(levels).sort((a, b) => Number(a) - Number(b));
|
|
14442
|
+
for (const k of keys) {
|
|
14443
|
+
const level = levels[k];
|
|
14444
|
+
const cb = document.createElement("input");
|
|
14445
|
+
cb.type = "checkbox";
|
|
14446
|
+
cb.checked = level.visible !== false;
|
|
14447
|
+
cb.style.accentColor = this.theme.ui.accent;
|
|
14448
|
+
cb.style.cursor = "pointer";
|
|
14449
|
+
const label = document.createElement("span");
|
|
14450
|
+
label.textContent = k;
|
|
14451
|
+
Object.assign(label.style, { fontSize: "11px", color: this.theme.tooltip.text });
|
|
14452
|
+
const swatch = this.makeColorSwatch(level.color, (hex) => {
|
|
14453
|
+
this.emit({ levels: { [k]: { visible: cb.checked, color: hex } } });
|
|
14454
|
+
});
|
|
14455
|
+
cb.addEventListener("change", () => {
|
|
14456
|
+
this.emit({ levels: { [k]: { visible: cb.checked, color: level.color } } });
|
|
14457
|
+
});
|
|
14458
|
+
panel.append(cb, label, swatch);
|
|
14459
|
+
}
|
|
14460
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
14461
|
+
const cRect = this.container.getBoundingClientRect();
|
|
14462
|
+
this.container.appendChild(panel);
|
|
14463
|
+
const panelRect = panel.getBoundingClientRect();
|
|
14464
|
+
let left = anchorRect.left - cRect.left;
|
|
14465
|
+
let top = anchorRect.bottom - cRect.top + 6;
|
|
14466
|
+
left = Math.max(4, Math.min(cRect.width - panelRect.width - 4, left));
|
|
14467
|
+
if (top + panelRect.height > cRect.height - 4) {
|
|
14468
|
+
top = Math.max(4, anchorRect.top - cRect.top - panelRect.height - 6);
|
|
14469
|
+
}
|
|
14470
|
+
panel.style.left = `${left}px`;
|
|
14471
|
+
panel.style.top = `${top}px`;
|
|
14472
|
+
this.fibPanel = panel;
|
|
14473
|
+
}
|
|
14474
|
+
closeFibPanel() {
|
|
14475
|
+
this.fibPanel?.remove();
|
|
14476
|
+
this.fibPanel = null;
|
|
14477
|
+
}
|
|
14478
|
+
makeIconButton(title) {
|
|
14479
|
+
const btn = document.createElement("div");
|
|
14480
|
+
Object.assign(btn.style, {
|
|
14481
|
+
display: "flex",
|
|
14482
|
+
alignItems: "center",
|
|
14483
|
+
justifyContent: "center",
|
|
14484
|
+
minWidth: `${SWATCH_PX}px`,
|
|
14485
|
+
height: `${SWATCH_PX}px`,
|
|
14486
|
+
padding: "0 4px",
|
|
14487
|
+
borderRadius: "4px",
|
|
14488
|
+
cursor: "pointer",
|
|
14489
|
+
border: `1px solid transparent`,
|
|
14490
|
+
color: this.theme.tooltip.text,
|
|
14491
|
+
background: "transparent",
|
|
14492
|
+
fontSize: "11px",
|
|
14493
|
+
boxSizing: "border-box"
|
|
14494
|
+
});
|
|
14495
|
+
btn.title = title;
|
|
14496
|
+
btn.addEventListener("mouseenter", () => {
|
|
14497
|
+
if (btn.dataset.active !== "1") btn.style.background = this.theme.indicatorOverlay.dropdownHoverBg;
|
|
14498
|
+
});
|
|
14499
|
+
btn.addEventListener("mouseleave", () => {
|
|
14500
|
+
if (btn.dataset.active !== "1") btn.style.background = "transparent";
|
|
14501
|
+
});
|
|
14502
|
+
return btn;
|
|
14503
|
+
}
|
|
14504
|
+
markActive(btn) {
|
|
14505
|
+
btn.dataset.active = "1";
|
|
14506
|
+
btn.style.background = this.theme.ui.accentBg;
|
|
14507
|
+
btn.style.borderColor = this.theme.ui.accent;
|
|
14508
|
+
}
|
|
14509
|
+
// ── Apply patch ──────────────────────────────────────────────────────────
|
|
14510
|
+
emit(patch) {
|
|
14511
|
+
if (!this.currentDrawing) return;
|
|
14512
|
+
this.onChange(this.currentDrawing.id, patch);
|
|
14513
|
+
this.rebuild();
|
|
14514
|
+
}
|
|
14515
|
+
};
|
|
14516
|
+
|
|
13473
14517
|
// src/ui/DraftQtyFlyout.ts
|
|
13474
14518
|
var DraftQtyFlyout = class {
|
|
13475
14519
|
constructor(theme, onCommit, onVisibilityChange) {
|
|
@@ -15865,7 +16909,7 @@ var PivotPoints = class {
|
|
|
15865
16909
|
// src/indicators/FibRetracementIndicator.ts
|
|
15866
16910
|
var FIB_RATIOS2 = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
|
|
15867
16911
|
var FIB_LABELS = ["0%", "23.6%", "38.2%", "50%", "61.8%", "78.6%", "100%"];
|
|
15868
|
-
var
|
|
16912
|
+
var FIB_COLORS = ["#90a4ae", "#4d96ff", "#6bcb77", "#ffd93d", "#ff9800", "#ef5350", "#90a4ae"];
|
|
15869
16913
|
var FibRetracementIndicator = class {
|
|
15870
16914
|
constructor(lookback = 50, color = "#e1bee7") {
|
|
15871
16915
|
this.lookback = lookback;
|
|
@@ -15921,7 +16965,7 @@ var FibRetracementIndicator = class {
|
|
|
15921
16965
|
for (let fi = 0; fi < FIB_RATIOS2.length; fi++) {
|
|
15922
16966
|
const price = fibValues[fi];
|
|
15923
16967
|
const y = scale.yToPixel(price, priceRange, chartHeight);
|
|
15924
|
-
const color =
|
|
16968
|
+
const color = FIB_COLORS[fi];
|
|
15925
16969
|
ctx.beginPath();
|
|
15926
16970
|
ctx.setLineDash([6, 4]);
|
|
15927
16971
|
ctx.strokeStyle = color;
|
|
@@ -17621,6 +18665,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17621
18665
|
this.chartSettingsDialog = null;
|
|
17622
18666
|
this.tradeButton = null;
|
|
17623
18667
|
this.tradePopover = null;
|
|
18668
|
+
this.drawingStylePopover = null;
|
|
17624
18669
|
this.draftOrderLabel = null;
|
|
17625
18670
|
this._minLots = 1;
|
|
17626
18671
|
this._showQuantityField = false;
|
|
@@ -17648,6 +18693,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17648
18693
|
this.showBidAskLines = false;
|
|
17649
18694
|
this.showActLogo = false;
|
|
17650
18695
|
this.hideLevelConfirmCancel = false;
|
|
18696
|
+
this.deselectActiveOnOutsideClick = false;
|
|
17651
18697
|
this.tradeLevelButtonScale = 1;
|
|
17652
18698
|
this.levelClusteringEnabled = true;
|
|
17653
18699
|
this.clusterThresholdDistance = 20;
|
|
@@ -17820,6 +18866,10 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17820
18866
|
this.targetCandleWidth = DEFAULT_CANDLE_SLOT_WIDTH;
|
|
17821
18867
|
this.maxSubPanes = 3;
|
|
17822
18868
|
this.showCandleCountdown = true;
|
|
18869
|
+
this.showTradeLevelsAlways = false;
|
|
18870
|
+
this.showPriceAxisCountdown = false;
|
|
18871
|
+
this.SHOW_SLTP_ALWAYS_LS_KEY = "finchart.showTradeLevelsAlways";
|
|
18872
|
+
this.PRICE_AXIS_COUNTDOWN_LS_KEY = "finchart.showPriceAxisCountdown";
|
|
17823
18873
|
this.loadingOverlayEl = null;
|
|
17824
18874
|
this.noDataOverlayEl = null;
|
|
17825
18875
|
// Watermark
|
|
@@ -18613,7 +19663,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18613
19663
|
}
|
|
18614
19664
|
return;
|
|
18615
19665
|
} else if (this.selectedTradeLabel !== null) {
|
|
18616
|
-
if (!this.hideLevelConfirmCancel) {
|
|
19666
|
+
if (!this.hideLevelConfirmCancel && this.deselectActiveOnOutsideClick) {
|
|
18617
19667
|
const lbl = this.selectedTradeLabel;
|
|
18618
19668
|
const pending = this.pendingLevelDrags.get(lbl);
|
|
18619
19669
|
if (!pending || pending.length === 0) {
|
|
@@ -18850,6 +19900,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18850
19900
|
labels,
|
|
18851
19901
|
tickActivityMs = 3e4,
|
|
18852
19902
|
hideSymbolAndTick = false,
|
|
19903
|
+
hideOHLCV = false,
|
|
18853
19904
|
showBottomBar = false
|
|
18854
19905
|
} = config;
|
|
18855
19906
|
this.dataLoader = dataLoader ?? null;
|
|
@@ -18902,6 +19953,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18902
19953
|
this.showBidAskLines = config.showBidAskLines ?? false;
|
|
18903
19954
|
this.showActLogo = config.showActLogo ?? false;
|
|
18904
19955
|
this.hideLevelConfirmCancel = config.hideLevelConfirmCancel ?? false;
|
|
19956
|
+
this.deselectActiveOnOutsideClick = config.deselectActiveOnOutsideClick ?? false;
|
|
18905
19957
|
this.tradeLevelButtonScale = Math.min(Math.max(config.tradeLevelButtonScale ?? 1, 1), 3);
|
|
18906
19958
|
this.levelClusteringEnabled = config.levelClusteringEnabled ?? true;
|
|
18907
19959
|
this.clusterThresholdDistance = config.clusterThresholdDistance ?? 20;
|
|
@@ -18910,6 +19962,15 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18910
19962
|
this.tfcActive = this.tfcEnabled;
|
|
18911
19963
|
this.showCandleCountdown = config.showCandleCountdown ?? true;
|
|
18912
19964
|
this.candleCountdownTimeframes = config.candleCountdownTimeframes;
|
|
19965
|
+
this.showTradeLevelsAlways = config.showTradeLevelsAlways ?? false;
|
|
19966
|
+
this.showPriceAxisCountdown = config.showPriceAxisCountdown ?? false;
|
|
19967
|
+
try {
|
|
19968
|
+
const persistedSlTp = localStorage.getItem(this.SHOW_SLTP_ALWAYS_LS_KEY);
|
|
19969
|
+
if (persistedSlTp !== null) this.showTradeLevelsAlways = persistedSlTp === "true";
|
|
19970
|
+
const persistedCountdown = localStorage.getItem(this.PRICE_AXIS_COUNTDOWN_LS_KEY);
|
|
19971
|
+
if (persistedCountdown !== null) this.showPriceAxisCountdown = persistedCountdown === "true";
|
|
19972
|
+
} catch {
|
|
19973
|
+
}
|
|
18913
19974
|
this._onOrderSubmit = onOrderSubmit;
|
|
18914
19975
|
if (config.minLots !== void 0) {
|
|
18915
19976
|
console.warn("[acttrader-charts] ChartConfig.minLots is deprecated. DraftOrderQtyInput has been removed. The value is stored and used as the submitted lot size.");
|
|
@@ -19161,7 +20222,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19161
20222
|
this.noDataOverlayEl.appendChild(noDataLabel);
|
|
19162
20223
|
this.canvasWrap.appendChild(this.noDataOverlayEl);
|
|
19163
20224
|
}
|
|
19164
|
-
if (showUI && !hideSymbolAndTick) {
|
|
20225
|
+
if (showUI && !(hideSymbolAndTick && hideOHLCV)) {
|
|
19165
20226
|
this.siEl = document.createElement("div");
|
|
19166
20227
|
Object.assign(this.siEl.style, {
|
|
19167
20228
|
position: "absolute",
|
|
@@ -19173,66 +20234,73 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19173
20234
|
gap: "8px",
|
|
19174
20235
|
pointerEvents: "none"
|
|
19175
20236
|
});
|
|
19176
|
-
|
|
19177
|
-
|
|
19178
|
-
color: this.theme.axisText,
|
|
19179
|
-
fontSize: "17px",
|
|
19180
|
-
fontFamily: "'Inter', system-ui, sans-serif",
|
|
19181
|
-
fontWeight: "700",
|
|
19182
|
-
display: symbol ? "inline" : "none"
|
|
19183
|
-
});
|
|
19184
|
-
this.siSymbolSpan.textContent = symbol ?? "";
|
|
19185
|
-
if (onSymbolClick) {
|
|
20237
|
+
if (!hideSymbolAndTick) {
|
|
20238
|
+
this.siSymbolSpan = document.createElement("span");
|
|
19186
20239
|
Object.assign(this.siSymbolSpan.style, {
|
|
19187
|
-
|
|
19188
|
-
|
|
19189
|
-
|
|
20240
|
+
color: this.theme.axisText,
|
|
20241
|
+
fontSize: "17px",
|
|
20242
|
+
fontFamily: "'Inter', system-ui, sans-serif",
|
|
20243
|
+
fontWeight: "700",
|
|
20244
|
+
display: symbol ? "inline" : "none"
|
|
19190
20245
|
});
|
|
19191
|
-
this.siSymbolSpan.
|
|
19192
|
-
|
|
20246
|
+
this.siSymbolSpan.textContent = symbol ?? "";
|
|
20247
|
+
if (onSymbolClick) {
|
|
20248
|
+
Object.assign(this.siSymbolSpan.style, {
|
|
20249
|
+
cursor: "pointer",
|
|
20250
|
+
textDecoration: "underline",
|
|
20251
|
+
pointerEvents: "auto"
|
|
20252
|
+
});
|
|
20253
|
+
this.siSymbolSpan.addEventListener("click", () => {
|
|
20254
|
+
onSymbolClick(this.siSymbolSpan?.textContent ?? "");
|
|
20255
|
+
});
|
|
20256
|
+
} else if (onIsinSelect) {
|
|
20257
|
+
if (isins) this.isins = isins;
|
|
20258
|
+
this.isinPickerModal = new IsinPickerModal(this.theme, onIsinSelect, () => this.emitUiState());
|
|
20259
|
+
this.getFloatingRoot().appendChild(this.isinPickerModal.el);
|
|
20260
|
+
Object.assign(this.siSymbolSpan.style, {
|
|
20261
|
+
cursor: "pointer",
|
|
20262
|
+
textDecoration: "underline",
|
|
20263
|
+
pointerEvents: "auto"
|
|
20264
|
+
});
|
|
20265
|
+
this.siSymbolSpan.addEventListener("click", () => {
|
|
20266
|
+
this.isinPickerModal.show(this.isins, this.siSymbolSpan.textContent ?? "");
|
|
20267
|
+
});
|
|
20268
|
+
}
|
|
20269
|
+
this.siStreamDot = document.createElement("span");
|
|
20270
|
+
Object.assign(this.siStreamDot.style, {
|
|
20271
|
+
width: "7px",
|
|
20272
|
+
height: "7px",
|
|
20273
|
+
borderRadius: "50%",
|
|
20274
|
+
background: this.theme.ui.stream.disconnected,
|
|
20275
|
+
display: "inline-block",
|
|
20276
|
+
flexShrink: "0"
|
|
19193
20277
|
});
|
|
19194
|
-
}
|
|
19195
|
-
|
|
19196
|
-
this.
|
|
19197
|
-
|
|
19198
|
-
|
|
19199
|
-
|
|
19200
|
-
|
|
19201
|
-
pointerEvents: "auto"
|
|
20278
|
+
}
|
|
20279
|
+
if (!hideOHLCV) {
|
|
20280
|
+
this.siOHLCSpan = document.createElement("span");
|
|
20281
|
+
Object.assign(this.siOHLCSpan.style, {
|
|
20282
|
+
fontSize: "14px",
|
|
20283
|
+
fontFamily: "'Inter', system-ui, sans-serif",
|
|
20284
|
+
whiteSpace: "nowrap"
|
|
19202
20285
|
});
|
|
19203
|
-
this.
|
|
19204
|
-
|
|
20286
|
+
this.siOHLC2Span = document.createElement("span");
|
|
20287
|
+
Object.assign(this.siOHLC2Span.style, {
|
|
20288
|
+
fontSize: "14px",
|
|
20289
|
+
fontFamily: "'Inter', system-ui, sans-serif",
|
|
20290
|
+
whiteSpace: "nowrap"
|
|
19205
20291
|
});
|
|
19206
20292
|
}
|
|
19207
|
-
this.siOHLCSpan = document.createElement("span");
|
|
19208
|
-
Object.assign(this.siOHLCSpan.style, {
|
|
19209
|
-
fontSize: "14px",
|
|
19210
|
-
fontFamily: "'Inter', system-ui, sans-serif",
|
|
19211
|
-
whiteSpace: "nowrap"
|
|
19212
|
-
});
|
|
19213
|
-
this.siOHLC2Span = document.createElement("span");
|
|
19214
|
-
Object.assign(this.siOHLC2Span.style, {
|
|
19215
|
-
fontSize: "14px",
|
|
19216
|
-
fontFamily: "'Inter', system-ui, sans-serif",
|
|
19217
|
-
whiteSpace: "nowrap"
|
|
19218
|
-
});
|
|
19219
|
-
this.siStreamDot = document.createElement("span");
|
|
19220
|
-
Object.assign(this.siStreamDot.style, {
|
|
19221
|
-
width: "7px",
|
|
19222
|
-
height: "7px",
|
|
19223
|
-
borderRadius: "50%",
|
|
19224
|
-
background: this.theme.ui.stream.disconnected,
|
|
19225
|
-
display: "inline-block",
|
|
19226
|
-
flexShrink: "0"
|
|
19227
|
-
});
|
|
19228
20293
|
const siRow1 = document.createElement("div");
|
|
19229
20294
|
Object.assign(siRow1.style, {
|
|
19230
20295
|
display: "flex",
|
|
19231
20296
|
alignItems: "center",
|
|
19232
20297
|
gap: "8px"
|
|
19233
20298
|
});
|
|
19234
|
-
|
|
19235
|
-
this.
|
|
20299
|
+
if (this.siStreamDot) siRow1.append(this.siStreamDot);
|
|
20300
|
+
if (this.siSymbolSpan) siRow1.append(this.siSymbolSpan);
|
|
20301
|
+
if (this.siOHLCSpan) siRow1.append(this.siOHLCSpan);
|
|
20302
|
+
this.siEl.append(siRow1);
|
|
20303
|
+
if (this.siOHLC2Span) this.siEl.append(this.siOHLC2Span);
|
|
19236
20304
|
this.canvasWrap.appendChild(this.siEl);
|
|
19237
20305
|
}
|
|
19238
20306
|
if (showUI) {
|
|
@@ -19381,6 +20449,20 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19381
20449
|
}
|
|
19382
20450
|
};
|
|
19383
20451
|
this.drawingManager.onStartTextEdit = (drawing) => this.showTextInput(drawing);
|
|
20452
|
+
this.drawingStylePopover = new DrawingStylePopover(
|
|
20453
|
+
this.canvasWrap,
|
|
20454
|
+
this.theme,
|
|
20455
|
+
(id, patch) => this.updateDrawingStyle(id, patch)
|
|
20456
|
+
);
|
|
20457
|
+
this.drawingManager.onSelectionChange = (drawing) => {
|
|
20458
|
+
if (drawing && drawing.complete && drawing.locked !== true) {
|
|
20459
|
+
const anchor = this.computeDrawingAnchorPx(drawing);
|
|
20460
|
+
if (anchor) this.drawingStylePopover?.show(drawing, anchor.x, anchor.y);
|
|
20461
|
+
else this.drawingStylePopover?.hide();
|
|
20462
|
+
} else {
|
|
20463
|
+
this.drawingStylePopover?.hide();
|
|
20464
|
+
}
|
|
20465
|
+
};
|
|
19384
20466
|
this.panHandler = new PanHandler(
|
|
19385
20467
|
this.drawCanvas,
|
|
19386
20468
|
() => this._targetViewport,
|
|
@@ -19996,6 +21078,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19996
21078
|
this.leftBar?.setTheme(this.theme);
|
|
19997
21079
|
this.tradeButton?.setTheme(this.theme, this.priceAxisWidth);
|
|
19998
21080
|
this.tradePopover?.setTheme(this.theme);
|
|
21081
|
+
this.drawingStylePopover?.setTheme(this.theme);
|
|
19999
21082
|
this._draftQtyFlyout?.setTheme(this.theme);
|
|
20000
21083
|
this.indicatorSettingsDialog?.setTheme(this.theme);
|
|
20001
21084
|
this.chartSettingsDialog?.setTheme(this.theme);
|
|
@@ -20027,6 +21110,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
20027
21110
|
this.leftBar?.setTheme(this.theme);
|
|
20028
21111
|
this.tradeButton?.setTheme(this.theme, this.priceAxisWidth);
|
|
20029
21112
|
this.tradePopover?.setTheme(this.theme);
|
|
21113
|
+
this.drawingStylePopover?.setTheme(this.theme);
|
|
20030
21114
|
this._draftQtyFlyout?.setTheme(this.theme);
|
|
20031
21115
|
this.indicatorSettingsDialog?.setTheme(this.theme);
|
|
20032
21116
|
this.chartSettingsDialog?.setTheme(this.theme);
|
|
@@ -20612,6 +21696,24 @@ var _ChartEngine = class _ChartEngine {
|
|
|
20612
21696
|
getDrawings() {
|
|
20613
21697
|
return this.drawingManager.getDrawings();
|
|
20614
21698
|
}
|
|
21699
|
+
/** Returns the drawing matching `id`, or null. */
|
|
21700
|
+
getDrawingById(id) {
|
|
21701
|
+
return this.drawingManager.getDrawingById(id);
|
|
21702
|
+
}
|
|
21703
|
+
/** Returns the currently selected drawing, or null. */
|
|
21704
|
+
getSelectedDrawing() {
|
|
21705
|
+
return this.drawingManager.getSelectedDrawing();
|
|
21706
|
+
}
|
|
21707
|
+
/**
|
|
21708
|
+
* Shallow-merge a per-instance style override onto the drawing with `id`.
|
|
21709
|
+
* Fib `levels` are deep-merged (per-key) so partial overrides keep siblings.
|
|
21710
|
+
* Returns `this` for chaining.
|
|
21711
|
+
*/
|
|
21712
|
+
updateDrawingStyle(id, patch) {
|
|
21713
|
+
this.drawingManager.updateDrawingStyle(id, patch);
|
|
21714
|
+
this.scheduleRender();
|
|
21715
|
+
return this;
|
|
21716
|
+
}
|
|
20615
21717
|
connectStream(adapter) {
|
|
20616
21718
|
this.streamAdapter = adapter;
|
|
20617
21719
|
adapter.onTick((tick) => this.onTick(tick));
|
|
@@ -21132,6 +22234,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
21132
22234
|
this.chartSettingsDialog?.destroy();
|
|
21133
22235
|
this.isinPickerModal?.destroy();
|
|
21134
22236
|
this.tradePopover?.destroy();
|
|
22237
|
+
this.drawingStylePopover?.destroy();
|
|
21135
22238
|
this._draftQtyFlyout?.destroy();
|
|
21136
22239
|
this.leftBar?.destroy();
|
|
21137
22240
|
this.drawCanvas.removeEventListener("mousemove", this.onMouseMove);
|
|
@@ -21287,7 +22390,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
21287
22390
|
const allowed = this.candleCountdownTimeframes === "all" ? true : (this.candleCountdownTimeframes ?? INTRADAY).includes(
|
|
21288
22391
|
this.timeframe
|
|
21289
22392
|
);
|
|
21290
|
-
const should = this.showCandleCountdown && allowed && this.dataStore.length > 0;
|
|
22393
|
+
const should = (this.showCandleCountdown || this.showPriceAxisCountdown) && allowed && this.dataStore.length > 0;
|
|
21291
22394
|
if (should && !this.countdownActive) {
|
|
21292
22395
|
this.countdownActive = true;
|
|
21293
22396
|
this.startCountdownLoop();
|
|
@@ -21302,7 +22405,9 @@ var _ChartEngine = class _ChartEngine {
|
|
|
21302
22405
|
{
|
|
21303
22406
|
tradeDisplayFilter: this.tradeDisplayFilter,
|
|
21304
22407
|
positionRenderStyle: this.positionRenderStyle,
|
|
21305
|
-
theme: this.themeName
|
|
22408
|
+
theme: this.themeName,
|
|
22409
|
+
showTradeLevelsAlways: this.showTradeLevelsAlways,
|
|
22410
|
+
showPriceAxisCountdown: this.showPriceAxisCountdown
|
|
21306
22411
|
},
|
|
21307
22412
|
this.labels.dialogs,
|
|
21308
22413
|
!!this._onOrderSubmit && this.tfcActive,
|
|
@@ -21316,6 +22421,8 @@ var _ChartEngine = class _ChartEngine {
|
|
|
21316
22421
|
tradeDisplayFilter: this.tradeDisplayFilter,
|
|
21317
22422
|
positionRenderStyle: this.positionRenderStyle,
|
|
21318
22423
|
theme: this.themeName,
|
|
22424
|
+
showTradeLevelsAlways: this.showTradeLevelsAlways,
|
|
22425
|
+
showPriceAxisCountdown: this.showPriceAxisCountdown,
|
|
21319
22426
|
canvasColors: { ...this.settingsCanvasColors }
|
|
21320
22427
|
},
|
|
21321
22428
|
(settings) => {
|
|
@@ -21326,6 +22433,28 @@ var _ChartEngine = class _ChartEngine {
|
|
|
21326
22433
|
if (settings.canvasColors) {
|
|
21327
22434
|
this.settingsCanvasColors = settings.canvasColors;
|
|
21328
22435
|
}
|
|
22436
|
+
if (this.showTradeLevelsAlways !== settings.showTradeLevelsAlways) {
|
|
22437
|
+
this.showTradeLevelsAlways = settings.showTradeLevelsAlways;
|
|
22438
|
+
try {
|
|
22439
|
+
localStorage.setItem(
|
|
22440
|
+
this.SHOW_SLTP_ALWAYS_LS_KEY,
|
|
22441
|
+
String(settings.showTradeLevelsAlways)
|
|
22442
|
+
);
|
|
22443
|
+
} catch {
|
|
22444
|
+
}
|
|
22445
|
+
}
|
|
22446
|
+
if (this.showPriceAxisCountdown !== settings.showPriceAxisCountdown) {
|
|
22447
|
+
this.showPriceAxisCountdown = settings.showPriceAxisCountdown;
|
|
22448
|
+
try {
|
|
22449
|
+
localStorage.setItem(
|
|
22450
|
+
this.PRICE_AXIS_COUNTDOWN_LS_KEY,
|
|
22451
|
+
String(settings.showPriceAxisCountdown)
|
|
22452
|
+
);
|
|
22453
|
+
} catch {
|
|
22454
|
+
}
|
|
22455
|
+
this.updateCountdownState();
|
|
22456
|
+
this.scheduleRender();
|
|
22457
|
+
}
|
|
21329
22458
|
this.setTheme(settings.theme);
|
|
21330
22459
|
this.renderTradeLayer();
|
|
21331
22460
|
this.emitStateChange();
|
|
@@ -21507,7 +22636,8 @@ var _ChartEngine = class _ChartEngine {
|
|
|
21507
22636
|
this._showQuantityField,
|
|
21508
22637
|
this.tradeDragNewSLPrice,
|
|
21509
22638
|
this.tradeDragNewTPPrice,
|
|
21510
|
-
this.tradeLevelButtonScale
|
|
22639
|
+
this.tradeLevelButtonScale,
|
|
22640
|
+
this.showTradeLevelsAlways
|
|
21511
22641
|
);
|
|
21512
22642
|
ctx.restore();
|
|
21513
22643
|
const offVpResult = renderOffViewportIndicators(
|
|
@@ -22245,28 +23375,62 @@ var _ChartEngine = class _ChartEngine {
|
|
|
22245
23375
|
ctx.stroke();
|
|
22246
23376
|
ctx.setLineDash([]);
|
|
22247
23377
|
const label = prefix + price.toFixed(precision);
|
|
23378
|
+
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
22248
23379
|
const tw = ctx.measureText(label).width;
|
|
23380
|
+
const boxW = tw + 12;
|
|
22249
23381
|
ctx.fillStyle = color;
|
|
22250
|
-
ctx.fillRect(chartW, y - tagH / 2,
|
|
23382
|
+
ctx.fillRect(chartW, y - tagH / 2, boxW, tagH);
|
|
22251
23383
|
ctx.fillStyle = "#ffffff";
|
|
22252
|
-
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
22253
23384
|
ctx.textAlign = "left";
|
|
22254
23385
|
ctx.fillText(label, chartW + 4, y + 4);
|
|
22255
23386
|
ctx.restore();
|
|
23387
|
+
return { y, boxW };
|
|
22256
23388
|
};
|
|
23389
|
+
let primaryPriceY = null;
|
|
23390
|
+
let primaryPriceBoxW = 0;
|
|
22257
23391
|
if (this.liveBidAsk) {
|
|
22258
23392
|
const { bid, ask } = this.liveBidAsk;
|
|
22259
23393
|
if (this.showBidAskLines) {
|
|
22260
|
-
drawPriceLine(bid, theme.candle.down, "");
|
|
22261
|
-
drawPriceLine(ask, theme.candle.up, "");
|
|
23394
|
+
const bidTag = drawPriceLine(bid, theme.candle.down, "");
|
|
23395
|
+
const askTag = drawPriceLine(ask, theme.candle.up, "");
|
|
23396
|
+
const primary = this.tickClosePriceSource === "ask" ? askTag : bidTag;
|
|
23397
|
+
primaryPriceY = primary.y;
|
|
23398
|
+
primaryPriceBoxW = primary.boxW;
|
|
22262
23399
|
} else {
|
|
22263
23400
|
const price = this.tickClosePriceSource === "ask" ? ask : bid;
|
|
22264
23401
|
const color = this.tickClosePriceSource === "ask" ? theme.candle.up : theme.candle.down;
|
|
22265
|
-
drawPriceLine(price, color, "");
|
|
23402
|
+
const tag = drawPriceLine(price, color, "");
|
|
23403
|
+
primaryPriceY = tag.y;
|
|
23404
|
+
primaryPriceBoxW = tag.boxW;
|
|
22266
23405
|
}
|
|
22267
23406
|
} else {
|
|
22268
23407
|
const isUp = lastBar.close >= lastBar.open;
|
|
22269
|
-
|
|
23408
|
+
const tag = drawPriceLine(
|
|
23409
|
+
lastBar.close,
|
|
23410
|
+
isUp ? theme.candle.up : theme.candle.down,
|
|
23411
|
+
""
|
|
23412
|
+
);
|
|
23413
|
+
primaryPriceY = tag.y;
|
|
23414
|
+
primaryPriceBoxW = tag.boxW;
|
|
23415
|
+
}
|
|
23416
|
+
if (this.showPriceAxisCountdown && primaryPriceY !== null) {
|
|
23417
|
+
const INTRADAY_TF = ["1m", "5m", "15m", "30m", "1h", "2h", "4h", "8h"];
|
|
23418
|
+
const allowed = this.candleCountdownTimeframes === "all" ? true : (this.candleCountdownTimeframes ?? INTRADAY_TF).includes(
|
|
23419
|
+
this.timeframe
|
|
23420
|
+
);
|
|
23421
|
+
if (allowed) {
|
|
23422
|
+
const intervalMs = this.timeframeToMs(this.timeframe);
|
|
23423
|
+
renderPriceAxisCountdown(
|
|
23424
|
+
ctx,
|
|
23425
|
+
lastBar.time,
|
|
23426
|
+
intervalMs,
|
|
23427
|
+
theme,
|
|
23428
|
+
chartW,
|
|
23429
|
+
primaryPriceY - tagH / 2,
|
|
23430
|
+
tagH,
|
|
23431
|
+
primaryPriceBoxW
|
|
23432
|
+
);
|
|
23433
|
+
}
|
|
22270
23434
|
}
|
|
22271
23435
|
}
|
|
22272
23436
|
let subPaneY = priceH;
|
|
@@ -22336,7 +23500,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
22336
23500
|
const countdownAllowed = this.candleCountdownTimeframes === "all" ? true : (this.candleCountdownTimeframes ?? INTRADAY_TF).includes(
|
|
22337
23501
|
this.timeframe
|
|
22338
23502
|
);
|
|
22339
|
-
const showCountdown = this.showCandleCountdown && countdownAllowed && bars.length > 0 && viewport.endIndex >= this.dataStore.length - 1;
|
|
23503
|
+
const showCountdown = this.showCandleCountdown && !this.showPriceAxisCountdown && countdownAllowed && bars.length > 0 && viewport.endIndex >= this.dataStore.length - 1;
|
|
22340
23504
|
const countdownBarIndex = showCountdown ? this.dataStore.length - 1 : void 0;
|
|
22341
23505
|
if (this._debug) {
|
|
22342
23506
|
console.log(
|
|
@@ -22432,8 +23596,16 @@ var _ChartEngine = class _ChartEngine {
|
|
|
22432
23596
|
priceRange,
|
|
22433
23597
|
chartW,
|
|
22434
23598
|
priceH,
|
|
22435
|
-
this.dataStore.getPricePrecision()
|
|
23599
|
+
this.dataStore.getPricePrecision(),
|
|
23600
|
+
this.theme
|
|
22436
23601
|
);
|
|
23602
|
+
if (this.drawingStylePopover) {
|
|
23603
|
+
const sel = this.drawingManager.getSelectedDrawing();
|
|
23604
|
+
if (sel && sel.complete && sel.locked !== true) {
|
|
23605
|
+
const anchor = this.computeDrawingAnchorPx(sel);
|
|
23606
|
+
if (anchor) this.drawingStylePopover.setAnchor(anchor.x, anchor.y);
|
|
23607
|
+
}
|
|
23608
|
+
}
|
|
22437
23609
|
if (this.scrollToEndBtn) {
|
|
22438
23610
|
const atEnd = this.dataStore.length === 0 || this._targetViewport.endIndex >= this.dataStore.length - 0.5;
|
|
22439
23611
|
if (atEnd !== !this.scrollToEndVisible) {
|
|
@@ -22514,6 +23686,30 @@ var _ChartEngine = class _ChartEngine {
|
|
|
22514
23686
|
const priceH = rect.height - this.timeAxisHeight - subPanesH;
|
|
22515
23687
|
return { chartW, priceH };
|
|
22516
23688
|
}
|
|
23689
|
+
/**
|
|
23690
|
+
* Returns canvasWrap-relative pixel coords of the topmost rendered point of
|
|
23691
|
+
* a drawing — used to anchor the floating style popover above it. Returns
|
|
23692
|
+
* null when the drawing has no on-screen points (e.g. fully clipped out).
|
|
23693
|
+
*/
|
|
23694
|
+
computeDrawingAnchorPx(drawing) {
|
|
23695
|
+
if (drawing.points.length === 0) return null;
|
|
23696
|
+
const { chartW, priceH } = this.getChartLayout();
|
|
23697
|
+
const viewport = this._targetViewport;
|
|
23698
|
+
const priceRange = this._displayedPriceRange ?? this.getScaledPriceRange(viewport);
|
|
23699
|
+
let bestX = 0, bestY = Infinity;
|
|
23700
|
+
for (const p of drawing.points) {
|
|
23701
|
+
const x = this.scaleManager.xToPixel(p.barIndex, viewport, chartW);
|
|
23702
|
+
const y = this.scaleManager.yToPixel(p.price, priceRange, priceH);
|
|
23703
|
+
if (y < bestY) {
|
|
23704
|
+
bestY = y;
|
|
23705
|
+
bestX = x;
|
|
23706
|
+
}
|
|
23707
|
+
}
|
|
23708
|
+
if (!Number.isFinite(bestY)) return null;
|
|
23709
|
+
bestX = Math.max(0, Math.min(chartW, bestX));
|
|
23710
|
+
bestY = Math.max(0, Math.min(priceH, bestY));
|
|
23711
|
+
return { x: bestX, y: bestY };
|
|
23712
|
+
}
|
|
22517
23713
|
/** Repositions each sub-pane IndicatorOverlay element to sit at the top of its pane */
|
|
22518
23714
|
updateSubPaneOverlayPositions() {
|
|
22519
23715
|
if (this.subPaneOverlays.size === 0) return;
|