acttrader-charts 1.0.21 → 1.1.0
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 +5 -2
- package/dist/{MACD-DGFPNwy9.d.cts → MACD-BgCuLbX_.d.cts} +70 -5
- package/dist/{MACD-DGFPNwy9.d.ts → MACD-BgCuLbX_.d.ts} +70 -5
- package/dist/index.cjs +1749 -555
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +179 -75
- package/dist/index.d.ts +179 -75
- package/dist/index.js +1747 -553
- 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.js
CHANGED
|
@@ -187,9 +187,20 @@ function deepMergeTheme(base, override) {
|
|
|
187
187
|
bottomBar: { ...base.bottomBar, ...override.bottomBar },
|
|
188
188
|
indicatorOverlay: { ...base.indicatorOverlay, ...override.indicatorOverlay },
|
|
189
189
|
tradeLevels: { ...base.tradeLevels, ...override.tradeLevels },
|
|
190
|
-
tradePanel: { ...base.tradePanel, ...override.tradePanel }
|
|
190
|
+
tradePanel: { ...base.tradePanel, ...override.tradePanel },
|
|
191
|
+
drawing: mergeDrawingTheme(base.drawing, override.drawing)
|
|
191
192
|
};
|
|
192
193
|
}
|
|
194
|
+
function mergeDrawingTheme(base, override) {
|
|
195
|
+
if (!override) return base;
|
|
196
|
+
const result = { ...base };
|
|
197
|
+
for (const key of Object.keys(override)) {
|
|
198
|
+
const o = override[key];
|
|
199
|
+
if (!o) continue;
|
|
200
|
+
result[key] = { ...base[key], ...o };
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
193
204
|
function resolveCssVarsInTheme(theme, container) {
|
|
194
205
|
const tmp = document.createElement("div");
|
|
195
206
|
tmp.style.display = "none";
|
|
@@ -288,11 +299,157 @@ function resolveCssVarsInTheme(theme, container) {
|
|
|
288
299
|
rowHoverBg: resolve(theme.tradePanel.rowHoverBg),
|
|
289
300
|
rowText: resolve(theme.tradePanel.rowText),
|
|
290
301
|
rowSubText: resolve(theme.tradePanel.rowSubText)
|
|
291
|
-
}
|
|
302
|
+
},
|
|
303
|
+
drawing: resolveDrawingThemeVars(theme.drawing, resolve)
|
|
292
304
|
};
|
|
293
305
|
container.removeChild(tmp);
|
|
294
306
|
return result;
|
|
295
307
|
}
|
|
308
|
+
function resolveDrawingThemeVars(drawing, resolve) {
|
|
309
|
+
const out = {};
|
|
310
|
+
for (const key of Object.keys(drawing)) {
|
|
311
|
+
const entry = drawing[key];
|
|
312
|
+
const resolved = { ...entry };
|
|
313
|
+
if (resolved.strokeColor) resolved.strokeColor = resolve(resolved.strokeColor);
|
|
314
|
+
if (resolved.fillColor) resolved.fillColor = resolve(resolved.fillColor);
|
|
315
|
+
if ("textColor" in resolved && resolved.textColor) {
|
|
316
|
+
resolved.textColor = resolve(resolved.textColor);
|
|
317
|
+
}
|
|
318
|
+
if ("levels" in resolved && resolved.levels) {
|
|
319
|
+
const levels = {};
|
|
320
|
+
for (const lk of Object.keys(resolved.levels)) {
|
|
321
|
+
const lv = resolved.levels[lk];
|
|
322
|
+
levels[lk] = { ...lv, color: resolve(lv.color) };
|
|
323
|
+
}
|
|
324
|
+
resolved.levels = levels;
|
|
325
|
+
}
|
|
326
|
+
out[key] = resolved;
|
|
327
|
+
}
|
|
328
|
+
return out;
|
|
329
|
+
}
|
|
330
|
+
var DEFAULT_DRAWING_THEME = {
|
|
331
|
+
// ── Lines ────────────────────────────────────────────────────────────────
|
|
332
|
+
horizontalLine: { strokeColor: "#e8b84b", lineWidth: 1, lineStyle: "dashed" },
|
|
333
|
+
verticalLine: { strokeColor: "#e8b84b", lineWidth: 1, lineStyle: "dashed" },
|
|
334
|
+
trendLine: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
335
|
+
crossLine: { strokeColor: "#78909c", lineWidth: 1, lineStyle: "dashed" },
|
|
336
|
+
trendAngle: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
337
|
+
// ── Channels ────────────────────────────────────────────────────────────
|
|
338
|
+
flatChannel: { strokeColor: "#ab47bc", lineWidth: 1, lineStyle: "solid", fillColor: "#ab47bc", fillOpacity: 0.08 },
|
|
339
|
+
parallelChannel: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid", fillColor: "#26c6da", fillOpacity: 0.08 },
|
|
340
|
+
disjointChannel: { strokeColor: "#ef9a9a", lineWidth: 1, lineStyle: "solid" },
|
|
341
|
+
// ── Pitchfork family ────────────────────────────────────────────────────
|
|
342
|
+
pitchfork: { strokeColor: "#ffa726", lineWidth: 1, lineStyle: "solid" },
|
|
343
|
+
schiffPitchfork: { strokeColor: "#ff7043", lineWidth: 1, lineStyle: "solid" },
|
|
344
|
+
modifiedSchiff: { strokeColor: "#ef5350", lineWidth: 1, lineStyle: "solid" },
|
|
345
|
+
insidePitchfork: { strokeColor: "#ab47bc", lineWidth: 1, lineStyle: "solid" },
|
|
346
|
+
// ── Fib family (multi-level) ────────────────────────────────────────────
|
|
347
|
+
fibRetracement: {
|
|
348
|
+
lineWidth: 1,
|
|
349
|
+
lineStyle: "dashed",
|
|
350
|
+
levels: {
|
|
351
|
+
"0": { visible: true, color: "#ef5350" },
|
|
352
|
+
"0.236": { visible: true, color: "#e8b84b" },
|
|
353
|
+
"0.382": { visible: true, color: "#26a69a" },
|
|
354
|
+
"0.5": { visible: true, color: "#2196f3" },
|
|
355
|
+
"0.618": { visible: true, color: "#26a69a" },
|
|
356
|
+
"0.786": { visible: true, color: "#e8b84b" },
|
|
357
|
+
"1": { visible: true, color: "#ef5350" }
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
fibExtension: {
|
|
361
|
+
lineWidth: 1,
|
|
362
|
+
lineStyle: "dashed",
|
|
363
|
+
levels: {
|
|
364
|
+
"0": { visible: true, color: "#888888" },
|
|
365
|
+
"0.618": { visible: true, color: "#26a69a" },
|
|
366
|
+
"1": { visible: true, color: "#888888" },
|
|
367
|
+
"1.272": { visible: true, color: "#2196f3" },
|
|
368
|
+
"1.618": { visible: true, color: "#e8b84b" },
|
|
369
|
+
"2": { visible: true, color: "#ef5350" },
|
|
370
|
+
"2.618": { visible: true, color: "#ef5350" }
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
fibProjection: {
|
|
374
|
+
lineWidth: 1,
|
|
375
|
+
lineStyle: "dashed",
|
|
376
|
+
levels: {
|
|
377
|
+
"0": { visible: true, color: "#ef5350" },
|
|
378
|
+
"0.618": { visible: true, color: "#26a69a" },
|
|
379
|
+
"1": { visible: true, color: "#2196f3" },
|
|
380
|
+
"1.272": { visible: true, color: "#26a69a" },
|
|
381
|
+
"1.618": { visible: true, color: "#e8b84b" },
|
|
382
|
+
"2": { visible: true, color: "#ef5350" },
|
|
383
|
+
"2.618": { visible: true, color: "#ef5350" }
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
fibChannel: { strokeColor: "#888888", lineWidth: 1, lineStyle: "dashed" },
|
|
387
|
+
fibFan: { strokeColor: "#888888", lineWidth: 1, lineStyle: "dashed" },
|
|
388
|
+
fibCircles: { strokeColor: "#e8b84b", lineWidth: 1, lineStyle: "dashed" },
|
|
389
|
+
fibSpiral: { strokeColor: "#e8b84b", lineWidth: 1, lineStyle: "solid" },
|
|
390
|
+
fibTimezone: { strokeColor: "#e8b84b", lineWidth: 1, lineStyle: "dashed" },
|
|
391
|
+
// ── Geometric shapes ────────────────────────────────────────────────────
|
|
392
|
+
rectangle: { strokeColor: "#9b59b6", lineWidth: 1, lineStyle: "solid", fillColor: "#9b59b6", fillOpacity: 0.09 },
|
|
393
|
+
rotatedRectangle: { strokeColor: "#9c27b0", lineWidth: 1, lineStyle: "solid", fillColor: "#9c27b0", fillOpacity: 0.09 },
|
|
394
|
+
ellipse: { strokeColor: "#2196f3", lineWidth: 1, lineStyle: "solid", fillColor: "#2196f3", fillOpacity: 0.08 },
|
|
395
|
+
circle: { strokeColor: "#4caf50", lineWidth: 1, lineStyle: "solid", fillColor: "#4caf50", fillOpacity: 0.08 },
|
|
396
|
+
triangle: { strokeColor: "#ff9800", lineWidth: 1, lineStyle: "solid", fillColor: "#ff9800", fillOpacity: 0.08 },
|
|
397
|
+
arc: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
398
|
+
sineLine: { strokeColor: "#ce93d8", lineWidth: 1, lineStyle: "solid" },
|
|
399
|
+
path: { strokeColor: "#ec407a", lineWidth: 1, lineStyle: "solid" },
|
|
400
|
+
polyline: { strokeColor: "#ff7043", lineWidth: 1, lineStyle: "solid" },
|
|
401
|
+
// ── Brush / Highlighter ─────────────────────────────────────────────────
|
|
402
|
+
brush: { strokeColor: "#ff5722", lineWidth: 2, lineStyle: "solid" },
|
|
403
|
+
highlighter: { strokeColor: "#ffeb3b", lineWidth: 8, lineStyle: "solid", fillOpacity: 0.4 },
|
|
404
|
+
// ── Text-bearing ────────────────────────────────────────────────────────
|
|
405
|
+
text: { textColor: "#f5f5f5", fontSize: 13, strokeColor: "#2196f3" },
|
|
406
|
+
callout: { textColor: "#f5f5f5", fontSize: 13, strokeColor: "#546e7a", fillColor: "#546e7a", fillOpacity: 0.18 },
|
|
407
|
+
priceLabel: { textColor: "#ffffff", fontSize: 11, strokeColor: "#26a69a", fillColor: "#26a69a", fillOpacity: 1 },
|
|
408
|
+
priceNote: { textColor: "#f5f5f5", fontSize: 12, strokeColor: "#ffa726" },
|
|
409
|
+
anchoredNote: { textColor: "#f5f5f5", fontSize: 12, strokeColor: "#42a5f5" },
|
|
410
|
+
// ── Arrows / Markers ────────────────────────────────────────────────────
|
|
411
|
+
arrowUp: { strokeColor: "#26a69a", lineWidth: 1.5, fillColor: "#26a69a", fillOpacity: 1 },
|
|
412
|
+
arrowDown: { strokeColor: "#ef5350", lineWidth: 1.5, fillColor: "#ef5350", fillOpacity: 1 },
|
|
413
|
+
arrowMarker: { strokeColor: "#ef5350", lineWidth: 1.5, fillColor: "#ef5350", fillOpacity: 1 },
|
|
414
|
+
flag: { strokeColor: "#ef5350", lineWidth: 1, fillColor: "#ef5350", fillOpacity: 0.85 },
|
|
415
|
+
// ── Volume profile ──────────────────────────────────────────────────────
|
|
416
|
+
volumeProfile: { strokeColor: "#26c6da", lineWidth: 1, fillColor: "#26c6da", fillOpacity: 0.35 },
|
|
417
|
+
anchoredVP: { strokeColor: "#42a5f5", lineWidth: 1, fillColor: "#42a5f5", fillOpacity: 0.35 },
|
|
418
|
+
fixedRangeVP: { strokeColor: "#7e57c2", lineWidth: 1, fillColor: "#7e57c2", fillOpacity: 0.35 },
|
|
419
|
+
// ── Harmonic patterns ───────────────────────────────────────────────────
|
|
420
|
+
abcdPattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
421
|
+
gartleyPattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
422
|
+
batPattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
423
|
+
butterflyPattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
424
|
+
crabPattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
425
|
+
sharkPattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
426
|
+
cypherPattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
427
|
+
headShoulders: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
428
|
+
trianglePattern: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
429
|
+
threeDrives: { strokeColor: "#26c6da", lineWidth: 1, lineStyle: "solid" },
|
|
430
|
+
// ── Elliott waves ───────────────────────────────────────────────────────
|
|
431
|
+
elliottImpulse: { strokeColor: "#42a5f5", lineWidth: 1, lineStyle: "solid" },
|
|
432
|
+
elliottCorrective: { strokeColor: "#42a5f5", lineWidth: 1, lineStyle: "solid" },
|
|
433
|
+
elliottTriangle: { strokeColor: "#42a5f5", lineWidth: 1, lineStyle: "solid" },
|
|
434
|
+
elliottWxy: { strokeColor: "#42a5f5", lineWidth: 1, lineStyle: "solid" },
|
|
435
|
+
elliottCombination: { strokeColor: "#42a5f5", lineWidth: 1, lineStyle: "solid" },
|
|
436
|
+
// ── Gann ────────────────────────────────────────────────────────────────
|
|
437
|
+
gannFan: { strokeColor: "#ffa726", lineWidth: 1, lineStyle: "solid" },
|
|
438
|
+
gannSquare: { strokeColor: "#ab47bc", lineWidth: 1, lineStyle: "solid" },
|
|
439
|
+
gannBox: { strokeColor: "#7e57c2", lineWidth: 1, lineStyle: "solid" },
|
|
440
|
+
// ── Measurement ─────────────────────────────────────────────────────────
|
|
441
|
+
priceRange: { strokeColor: "#66bb6a", lineWidth: 1, fillColor: "#66bb6a", fillOpacity: 0.07 },
|
|
442
|
+
dateRange: { strokeColor: "#66bb6a", lineWidth: 1, fillColor: "#66bb6a", fillOpacity: 0.07 },
|
|
443
|
+
datePriceRange: { strokeColor: "#66bb6a", lineWidth: 1, fillColor: "#66bb6a", fillOpacity: 0.07 },
|
|
444
|
+
ruler: { strokeColor: "#90a4ae", lineWidth: 1, lineStyle: "dashed" },
|
|
445
|
+
// ── Advanced ────────────────────────────────────────────────────────────
|
|
446
|
+
regressionTrend: { strokeColor: "#42a5f5", lineWidth: 1, lineStyle: "solid" },
|
|
447
|
+
projection: { strokeColor: "#9575cd", lineWidth: 1, lineStyle: "dashed" },
|
|
448
|
+
cyclicLines: { strokeColor: "#80cbc4", lineWidth: 1, lineStyle: "dashed" },
|
|
449
|
+
timeCycles: { strokeColor: "#80cbc4", lineWidth: 1, lineStyle: "dashed", fillColor: "#80cbc4", fillOpacity: 0.06 },
|
|
450
|
+
priceProjection: { strokeColor: "#ef9a9a", lineWidth: 1, lineStyle: "dashed", fillColor: "#ef9a9a", fillOpacity: 0.09 },
|
|
451
|
+
ghostFeed: { strokeColor: "#9e9e9e", lineWidth: 1, lineStyle: "dotted" }
|
|
452
|
+
};
|
|
296
453
|
var DARK_THEME = {
|
|
297
454
|
background: "#141d22",
|
|
298
455
|
grid: "#243344",
|
|
@@ -366,7 +523,8 @@ var DARK_THEME = {
|
|
|
366
523
|
rowHoverBg: "rgba(255,255,255,0.04)",
|
|
367
524
|
rowText: "#e8eef2",
|
|
368
525
|
rowSubText: "#9ca3af"
|
|
369
|
-
}
|
|
526
|
+
},
|
|
527
|
+
drawing: DEFAULT_DRAWING_THEME
|
|
370
528
|
};
|
|
371
529
|
var LIGHT_THEME = {
|
|
372
530
|
background: "#ffffff",
|
|
@@ -440,7 +598,8 @@ var LIGHT_THEME = {
|
|
|
440
598
|
rowHoverBg: "rgba(0,0,0,0.03)",
|
|
441
599
|
rowText: "#333333",
|
|
442
600
|
rowSubText: "#666666"
|
|
443
|
-
}
|
|
601
|
+
},
|
|
602
|
+
drawing: DEFAULT_DRAWING_THEME
|
|
444
603
|
};
|
|
445
604
|
|
|
446
605
|
// src/core/uiConfig.ts
|
|
@@ -1752,7 +1911,7 @@ function cascadeBracketAwayFromEntry(entry, preferredBoxY, boxH, direction, same
|
|
|
1752
1911
|
}
|
|
1753
1912
|
return boxY;
|
|
1754
1913
|
}
|
|
1755
|
-
function getLevelDisplayText(level, price, precision) {
|
|
1914
|
+
function getLevelDisplayText(level, price, precision, omitLots = false) {
|
|
1756
1915
|
if (level.data?._isDraft) {
|
|
1757
1916
|
const p = level;
|
|
1758
1917
|
const sideLabel = p.side.charAt(0).toUpperCase() + p.side.slice(1);
|
|
@@ -1761,13 +1920,13 @@ function getLevelDisplayText(level, price, precision) {
|
|
|
1761
1920
|
}
|
|
1762
1921
|
if (level.type === "pending") {
|
|
1763
1922
|
const p = level;
|
|
1764
|
-
const lotsStr = p.lots !== void 0 ? ` ${p.lots}` : "";
|
|
1923
|
+
const lotsStr = !omitLots && p.lots !== void 0 ? ` ${p.lots}` : "";
|
|
1765
1924
|
return level.text ?? `${p.side.toUpperCase()}${lotsStr} @${price.toFixed(precision)}`;
|
|
1766
1925
|
}
|
|
1767
1926
|
if (level.type === "position" && level.entryPriceEditable) {
|
|
1768
1927
|
const pos = level;
|
|
1769
1928
|
const sideStr = pos.side ? pos.side.toUpperCase() : "";
|
|
1770
|
-
const lotsStr = pos.lots !== void 0 ? ` ${pos.lots}` : "";
|
|
1929
|
+
const lotsStr = !omitLots && pos.lots !== void 0 ? ` ${pos.lots}` : "";
|
|
1771
1930
|
return level.text ?? `${sideStr}${lotsStr} @${price.toFixed(precision)}`;
|
|
1772
1931
|
}
|
|
1773
1932
|
if (level.type === "position") return level.text ?? "";
|
|
@@ -1872,7 +2031,7 @@ function isDraggable(level) {
|
|
|
1872
2031
|
return !!level.entryPriceEditable;
|
|
1873
2032
|
return false;
|
|
1874
2033
|
}
|
|
1875
|
-
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) {
|
|
2034
|
+
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) {
|
|
1876
2035
|
if (levels.length === 0)
|
|
1877
2036
|
return {
|
|
1878
2037
|
hitAreas: [],
|
|
@@ -1912,9 +2071,7 @@ function renderTradeLevels(ctx, levels, scale, priceRange, theme, chartW, priceH
|
|
|
1912
2071
|
}
|
|
1913
2072
|
const pnlText = hasPnl(level) ? level.pnlText : null;
|
|
1914
2073
|
const hasPending = pendingLabels.has(level.label);
|
|
1915
|
-
const displayText = getLevelDisplayText(level, price, precision);
|
|
1916
2074
|
const pnlW = pnlText ? ctx.measureText(pnlText).width + PNL_GAP : 0;
|
|
1917
|
-
const labelW = ctx.measureText(displayText).width;
|
|
1918
2075
|
const effectivePending = hasPending && !hideConfirmCancel;
|
|
1919
2076
|
const confirmW = effectivePending ? (confirmR * 2 + confirmGap) * 2 : 0;
|
|
1920
2077
|
const closeW = effectivePending || hideConfirmCancel ? 0 : editR * 2 + editGap + closeR * 2 + BOX_PAD_X / 2;
|
|
@@ -1924,6 +2081,8 @@ function renderTradeLevels(ctx, levels, scale, priceRange, theme, chartW, priceH
|
|
|
1924
2081
|
const supportsQtyField = level.type === "pending" || isEntryOrderPosition;
|
|
1925
2082
|
const levelLots = level.lots;
|
|
1926
2083
|
const qtyPillText = showQuantityField && (isDraftLevel || isEditingLevel) && supportsQtyField && levelLots !== void 0 ? String(levelLots) : null;
|
|
2084
|
+
const displayText = getLevelDisplayText(level, price, precision, qtyPillText !== null);
|
|
2085
|
+
const labelW = ctx.measureText(displayText).width;
|
|
1927
2086
|
const pillW = qtyPillText ? PILL_PAD_X + ctx.measureText(qtyPillText).width + PILL_PAD_X + PILL_GAP : 0;
|
|
1928
2087
|
const bW = BOX_PAD_X + pillW + pnlW + labelW + BOX_PAD_X + confirmW + closeW;
|
|
1929
2088
|
measured.push({ level, price, y, boxW: bW, boxH });
|
|
@@ -2083,7 +2242,8 @@ function renderTradeLevels(ctx, levels, scale, priceRange, theme, chartW, priceH
|
|
|
2083
2242
|
dragNewSLPrice,
|
|
2084
2243
|
dragNewTPPrice,
|
|
2085
2244
|
buttonScale,
|
|
2086
|
-
bracketBoxYMap
|
|
2245
|
+
bracketBoxYMap,
|
|
2246
|
+
alwaysShowBrackets
|
|
2087
2247
|
);
|
|
2088
2248
|
const ec = labelToExpandedCluster.get(level.label);
|
|
2089
2249
|
if (ec && layout.boxY !== layout.y - boxH / 2) {
|
|
@@ -2320,10 +2480,12 @@ function drawDotHoverBox(ctx, level, y, dotX, dotR, theme, precision) {
|
|
|
2320
2480
|
});
|
|
2321
2481
|
ctx.restore();
|
|
2322
2482
|
}
|
|
2323
|
-
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) {
|
|
2483
|
+
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) {
|
|
2324
2484
|
const { y, boxY, boxW, boxH } = layout;
|
|
2325
2485
|
const color = levelColor(level, colors, theme);
|
|
2326
2486
|
const active = hovered || selected;
|
|
2487
|
+
const passiveBracketShow = alwaysShowBrackets && !active;
|
|
2488
|
+
const effectiveHideConfirmCancel = hideConfirmCancel || passiveBracketShow;
|
|
2327
2489
|
const s = Math.min(Math.max(buttonScale, 1), 3);
|
|
2328
2490
|
const closeR = CLOSE_R * s;
|
|
2329
2491
|
const editR = EDIT_R * s;
|
|
@@ -2367,7 +2529,7 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2367
2529
|
const gripX = draggable ? groupX : 0;
|
|
2368
2530
|
const boxX = draggable ? groupX + GRIP_W + 2 : groupX;
|
|
2369
2531
|
const bracketHost = level;
|
|
2370
|
-
if (active || isMarketDraft) {
|
|
2532
|
+
if (active || isMarketDraft || passiveBracketShow) {
|
|
2371
2533
|
const slDragKey = `${level.label}:stopLoss`;
|
|
2372
2534
|
const tpDragKey = `${level.label}:takeProfit`;
|
|
2373
2535
|
const mainDragActive = draggingLabel === level.label;
|
|
@@ -2409,7 +2571,7 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2409
2571
|
dragNewPrice,
|
|
2410
2572
|
hitAreas,
|
|
2411
2573
|
dragAreas,
|
|
2412
|
-
|
|
2574
|
+
effectiveHideConfirmCancel,
|
|
2413
2575
|
bracketHost._slOrderLabel,
|
|
2414
2576
|
draftBracketPnl?.sl,
|
|
2415
2577
|
buttonScale,
|
|
@@ -2433,7 +2595,7 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2433
2595
|
dragNewPrice,
|
|
2434
2596
|
hitAreas,
|
|
2435
2597
|
dragAreas,
|
|
2436
|
-
|
|
2598
|
+
effectiveHideConfirmCancel,
|
|
2437
2599
|
void 0,
|
|
2438
2600
|
draftBracketPnl?.sl,
|
|
2439
2601
|
buttonScale
|
|
@@ -2457,7 +2619,7 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2457
2619
|
dragNewPrice,
|
|
2458
2620
|
hitAreas,
|
|
2459
2621
|
dragAreas,
|
|
2460
|
-
|
|
2622
|
+
effectiveHideConfirmCancel,
|
|
2461
2623
|
bracketHost._tpOrderLabel,
|
|
2462
2624
|
draftBracketPnl?.tp,
|
|
2463
2625
|
buttonScale,
|
|
@@ -2481,7 +2643,7 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2481
2643
|
dragNewPrice,
|
|
2482
2644
|
hitAreas,
|
|
2483
2645
|
dragAreas,
|
|
2484
|
-
|
|
2646
|
+
effectiveHideConfirmCancel,
|
|
2485
2647
|
void 0,
|
|
2486
2648
|
draftBracketPnl?.tp,
|
|
2487
2649
|
buttonScale
|
|
@@ -2545,7 +2707,8 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2545
2707
|
const isDraftLevel = level.data?._isDraft === true;
|
|
2546
2708
|
const supportsQtyField = level.type === "pending" || isEntryOrderPos;
|
|
2547
2709
|
const levelLots = level.lots;
|
|
2548
|
-
|
|
2710
|
+
const showQtyPill = showQuantityField && (isDraftLevel || selected || hasPendingChanges) && supportsQtyField && levelLots !== void 0;
|
|
2711
|
+
if (showQtyPill) {
|
|
2549
2712
|
const qtyText = String(levelLots);
|
|
2550
2713
|
ctx.font = FONT_MAIN;
|
|
2551
2714
|
const pillContentW = ctx.measureText(qtyText).width;
|
|
@@ -2576,7 +2739,7 @@ function drawLevel(ctx, level, layout, chartW, colors, theme, precision, hovered
|
|
|
2576
2739
|
ctx.fillText(pnlText, textX, midY);
|
|
2577
2740
|
textX += ctx.measureText(pnlText).width + PNL_GAP;
|
|
2578
2741
|
}
|
|
2579
|
-
const displayText = getLevelDisplayText(level, layout.price, precision);
|
|
2742
|
+
const displayText = getLevelDisplayText(level, layout.price, precision, showQtyPill);
|
|
2580
2743
|
ctx.font = FONT_MAIN;
|
|
2581
2744
|
ctx.fillStyle = monoColor(theme);
|
|
2582
2745
|
ctx.textAlign = "left";
|
|
@@ -3265,6 +3428,20 @@ function renderCandleCountdown(ctx, lastBarTime, intervalMs, lastBarIndex, viewp
|
|
|
3265
3428
|
ctx.textBaseline = "alphabetic";
|
|
3266
3429
|
ctx.restore();
|
|
3267
3430
|
}
|
|
3431
|
+
function renderPriceAxisCountdown(ctx, lastBarTime, intervalMs, theme, boxX, priceTagY, tagH, boxW) {
|
|
3432
|
+
const remaining = Math.max(0, lastBarTime + intervalMs - Date.now());
|
|
3433
|
+
const totalSec = Math.ceil(remaining / 1e3);
|
|
3434
|
+
const label = formatRemaining(totalSec);
|
|
3435
|
+
const boxY = priceTagY + tagH;
|
|
3436
|
+
ctx.save();
|
|
3437
|
+
ctx.fillStyle = theme.axisText;
|
|
3438
|
+
ctx.fillRect(boxX, boxY, boxW, tagH);
|
|
3439
|
+
ctx.fillStyle = theme.background;
|
|
3440
|
+
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
3441
|
+
ctx.textAlign = "left";
|
|
3442
|
+
ctx.fillText(label, boxX + 4, boxY + tagH - 4);
|
|
3443
|
+
ctx.restore();
|
|
3444
|
+
}
|
|
3268
3445
|
|
|
3269
3446
|
// src/axis/PriceAxis.ts
|
|
3270
3447
|
function renderPriceAxis(ctx, priceRange, scale, theme, chartWidth, chartHeight, axisWidth, gridLines, pricePrecision = 2, cfg = DEFAULT_PRICE_AXIS_CONFIG) {
|
|
@@ -5577,6 +5754,50 @@ var LeftBar = class {
|
|
|
5577
5754
|
}
|
|
5578
5755
|
};
|
|
5579
5756
|
|
|
5757
|
+
// src/drawings/styleUtils.ts
|
|
5758
|
+
function resolveStyle(type, instanceStyle, theme) {
|
|
5759
|
+
const themeEntry = theme?.drawing?.[type] ?? {};
|
|
5760
|
+
const merged = { ...themeEntry, ...instanceStyle ?? {} };
|
|
5761
|
+
const themeLevels = themeEntry.levels;
|
|
5762
|
+
const instLevels = instanceStyle?.levels;
|
|
5763
|
+
if (themeLevels || instLevels) {
|
|
5764
|
+
const out = { ...themeLevels ?? {} };
|
|
5765
|
+
if (instLevels) {
|
|
5766
|
+
for (const k of Object.keys(instLevels)) {
|
|
5767
|
+
out[k] = { ...out[k] ?? { visible: true, color: "#888888" }, ...instLevels[k] };
|
|
5768
|
+
}
|
|
5769
|
+
}
|
|
5770
|
+
merged.levels = out;
|
|
5771
|
+
}
|
|
5772
|
+
return {
|
|
5773
|
+
strokeColor: merged.strokeColor ?? "#888888",
|
|
5774
|
+
lineWidth: merged.lineWidth ?? 1,
|
|
5775
|
+
lineStyle: merged.lineStyle ?? "solid",
|
|
5776
|
+
fillColor: merged.fillColor ?? merged.strokeColor ?? "#888888",
|
|
5777
|
+
fillOpacity: merged.fillOpacity ?? 0.1,
|
|
5778
|
+
...merged.textColor !== void 0 && { textColor: merged.textColor },
|
|
5779
|
+
...merged.fontSize !== void 0 && { fontSize: merged.fontSize },
|
|
5780
|
+
...merged.levels !== void 0 && { levels: merged.levels }
|
|
5781
|
+
};
|
|
5782
|
+
}
|
|
5783
|
+
function applyLineStyle(ctx, style, width) {
|
|
5784
|
+
const w = Math.max(1, width);
|
|
5785
|
+
if (style === "dashed") ctx.setLineDash([6 * w, 3 * w]);
|
|
5786
|
+
else if (style === "dotted") ctx.setLineDash([2 * w, 3 * w]);
|
|
5787
|
+
else ctx.setLineDash([]);
|
|
5788
|
+
}
|
|
5789
|
+
function hexWithAlpha(hex, alpha) {
|
|
5790
|
+
if (!hex.startsWith("#")) return hex;
|
|
5791
|
+
const a = Math.max(0, Math.min(1, alpha));
|
|
5792
|
+
const aHex = Math.round(a * 255).toString(16).padStart(2, "0");
|
|
5793
|
+
let body = hex.slice(1);
|
|
5794
|
+
if (body.length === 3) body = body.split("").map((c) => c + c).join("");
|
|
5795
|
+
else if (body.length === 4) body = body.slice(0, 3).split("").map((c) => c + c).join("");
|
|
5796
|
+
else if (body.length === 8) body = body.slice(0, 6);
|
|
5797
|
+
if (body.length !== 6) return hex;
|
|
5798
|
+
return `#${body}${aHex}`;
|
|
5799
|
+
}
|
|
5800
|
+
|
|
5580
5801
|
// src/drawings/HorizontalLine.ts
|
|
5581
5802
|
var HIT_TOLERANCE = 6;
|
|
5582
5803
|
var HorizontalLine = class {
|
|
@@ -5586,13 +5807,14 @@ var HorizontalLine = class {
|
|
|
5586
5807
|
this.points = [point];
|
|
5587
5808
|
this.complete = true;
|
|
5588
5809
|
}
|
|
5589
|
-
render(ctx, scale, _viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
5810
|
+
render(ctx, scale, _viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
5590
5811
|
const price = this.points[0].price;
|
|
5591
5812
|
const y = scale.yToPixel(price, priceRange, chartH);
|
|
5813
|
+
const s = resolveStyle("horizontalLine", this.style, theme);
|
|
5592
5814
|
ctx.save();
|
|
5593
|
-
ctx.strokeStyle =
|
|
5594
|
-
ctx.lineWidth = isSelected ?
|
|
5595
|
-
|
|
5815
|
+
ctx.strokeStyle = s.strokeColor;
|
|
5816
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
5817
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
5596
5818
|
ctx.beginPath();
|
|
5597
5819
|
ctx.moveTo(0, y);
|
|
5598
5820
|
ctx.lineTo(chartW, y);
|
|
@@ -5601,7 +5823,7 @@ var HorizontalLine = class {
|
|
|
5601
5823
|
const label = price.toFixed(pricePrecision);
|
|
5602
5824
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
5603
5825
|
const tw = ctx.measureText(label).width;
|
|
5604
|
-
ctx.fillStyle =
|
|
5826
|
+
ctx.fillStyle = s.strokeColor;
|
|
5605
5827
|
ctx.fillText(label, chartW - tw - 4, y - 3);
|
|
5606
5828
|
ctx.restore();
|
|
5607
5829
|
}
|
|
@@ -5623,12 +5845,13 @@ var VerticalLine = class {
|
|
|
5623
5845
|
this.points = [point];
|
|
5624
5846
|
this.complete = true;
|
|
5625
5847
|
}
|
|
5626
|
-
render(ctx, scale, viewport, _priceRange, chartW, chartH, isSelected) {
|
|
5848
|
+
render(ctx, scale, viewport, _priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
5627
5849
|
const x = scale.xToPixel(this.points[0].barIndex, viewport, chartW);
|
|
5850
|
+
const s = resolveStyle("verticalLine", this.style, theme);
|
|
5628
5851
|
ctx.save();
|
|
5629
|
-
ctx.strokeStyle =
|
|
5630
|
-
ctx.lineWidth = isSelected ?
|
|
5631
|
-
|
|
5852
|
+
ctx.strokeStyle = s.strokeColor;
|
|
5853
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
5854
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
5632
5855
|
ctx.beginPath();
|
|
5633
5856
|
ctx.moveTo(x, 0);
|
|
5634
5857
|
ctx.lineTo(x, chartH);
|
|
@@ -5661,24 +5884,27 @@ var TrendLine = class {
|
|
|
5661
5884
|
this.points = [first, { ...first }];
|
|
5662
5885
|
this.complete = false;
|
|
5663
5886
|
}
|
|
5664
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
5887
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
5665
5888
|
if (this.points.length < 2) return;
|
|
5666
5889
|
const [p1, p2] = this.points;
|
|
5667
5890
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
5668
5891
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
5669
5892
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
5670
5893
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
5894
|
+
const s = resolveStyle("trendLine", this.style, theme);
|
|
5671
5895
|
ctx.save();
|
|
5672
|
-
ctx.strokeStyle =
|
|
5673
|
-
ctx.lineWidth = isSelected ?
|
|
5896
|
+
ctx.strokeStyle = s.strokeColor;
|
|
5897
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
5898
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
5674
5899
|
ctx.beginPath();
|
|
5675
5900
|
ctx.moveTo(x1, y1);
|
|
5676
5901
|
ctx.lineTo(x2, y2);
|
|
5677
5902
|
ctx.stroke();
|
|
5903
|
+
ctx.setLineDash([]);
|
|
5678
5904
|
for (const [x, y] of [[x1, y1], [x2, y2]]) {
|
|
5679
5905
|
ctx.beginPath();
|
|
5680
5906
|
ctx.arc(x, y, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
5681
|
-
ctx.fillStyle =
|
|
5907
|
+
ctx.fillStyle = s.strokeColor;
|
|
5682
5908
|
ctx.fill();
|
|
5683
5909
|
}
|
|
5684
5910
|
ctx.restore();
|
|
@@ -5723,7 +5949,7 @@ var TrendAngle = class {
|
|
|
5723
5949
|
this.points = [first, { ...first }];
|
|
5724
5950
|
this.complete = false;
|
|
5725
5951
|
}
|
|
5726
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
5952
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
5727
5953
|
if (this.points.length < 2) return;
|
|
5728
5954
|
const [p1, p2] = this.points;
|
|
5729
5955
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -5732,10 +5958,12 @@ var TrendAngle = class {
|
|
|
5732
5958
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
5733
5959
|
const angleDeg = Math.atan2(-(y2 - y1), x2 - x1) * 180 / Math.PI;
|
|
5734
5960
|
const angleRad = Math.atan2(-(y2 - y1), x2 - x1);
|
|
5961
|
+
const s = resolveStyle("trendAngle", this.style, theme);
|
|
5735
5962
|
ctx.save();
|
|
5736
|
-
ctx.strokeStyle =
|
|
5737
|
-
ctx.fillStyle =
|
|
5738
|
-
ctx.lineWidth = isSelected ?
|
|
5963
|
+
ctx.strokeStyle = s.strokeColor;
|
|
5964
|
+
ctx.fillStyle = s.strokeColor;
|
|
5965
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
5966
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
5739
5967
|
ctx.beginPath();
|
|
5740
5968
|
ctx.moveTo(x1, y1);
|
|
5741
5969
|
ctx.lineTo(x2, y2);
|
|
@@ -5798,14 +6026,15 @@ var CrossLine = class {
|
|
|
5798
6026
|
this.points = [point];
|
|
5799
6027
|
this.complete = true;
|
|
5800
6028
|
}
|
|
5801
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
6029
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
5802
6030
|
const p = this.points[0];
|
|
5803
6031
|
const x = scale.xToPixel(p.barIndex, viewport, chartW);
|
|
5804
6032
|
const y = scale.yToPixel(p.price, priceRange, chartH);
|
|
6033
|
+
const s = resolveStyle("crossLine", this.style, theme);
|
|
5805
6034
|
ctx.save();
|
|
5806
|
-
ctx.strokeStyle =
|
|
5807
|
-
ctx.lineWidth = isSelected ?
|
|
5808
|
-
|
|
6035
|
+
ctx.strokeStyle = s.strokeColor;
|
|
6036
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6037
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
5809
6038
|
ctx.beginPath();
|
|
5810
6039
|
ctx.moveTo(0, y);
|
|
5811
6040
|
ctx.lineTo(chartW, y);
|
|
@@ -5847,7 +6076,7 @@ var ParallelChannel = class {
|
|
|
5847
6076
|
this.points = [first, { ...first }];
|
|
5848
6077
|
this.complete = false;
|
|
5849
6078
|
}
|
|
5850
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
6079
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
5851
6080
|
if (this.points.length < 2) return;
|
|
5852
6081
|
const [p1, p2, p3] = this.points;
|
|
5853
6082
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -5863,16 +6092,18 @@ var ParallelChannel = class {
|
|
|
5863
6092
|
}
|
|
5864
6093
|
const ox1 = x1, oy1 = y1 + offsetY;
|
|
5865
6094
|
const ox2 = x2, oy2 = y2 + offsetY;
|
|
6095
|
+
const s = resolveStyle("parallelChannel", this.style, theme);
|
|
5866
6096
|
ctx.save();
|
|
5867
|
-
ctx.strokeStyle =
|
|
5868
|
-
ctx.lineWidth = isSelected ?
|
|
6097
|
+
ctx.strokeStyle = s.strokeColor;
|
|
6098
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6099
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
5869
6100
|
ctx.beginPath();
|
|
5870
6101
|
ctx.moveTo(x1, y1);
|
|
5871
6102
|
ctx.lineTo(x2, y2);
|
|
5872
6103
|
ctx.lineTo(ox2, oy2);
|
|
5873
6104
|
ctx.lineTo(ox1, oy1);
|
|
5874
6105
|
ctx.closePath();
|
|
5875
|
-
ctx.fillStyle =
|
|
6106
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
5876
6107
|
ctx.fill();
|
|
5877
6108
|
ctx.beginPath();
|
|
5878
6109
|
ctx.moveTo(x1, y1);
|
|
@@ -5884,6 +6115,7 @@ var ParallelChannel = class {
|
|
|
5884
6115
|
ctx.lineTo(ox2, oy2);
|
|
5885
6116
|
ctx.stroke();
|
|
5886
6117
|
}
|
|
6118
|
+
ctx.setLineDash([]);
|
|
5887
6119
|
if (isSelected) {
|
|
5888
6120
|
const anchorPts = [[x1, y1], [x2, y2]];
|
|
5889
6121
|
if (p3) {
|
|
@@ -5891,7 +6123,7 @@ var ParallelChannel = class {
|
|
|
5891
6123
|
const p3y = scale.yToPixel(p3.price, priceRange, chartH);
|
|
5892
6124
|
anchorPts.push([p3x, p3y]);
|
|
5893
6125
|
}
|
|
5894
|
-
ctx.fillStyle =
|
|
6126
|
+
ctx.fillStyle = s.strokeColor;
|
|
5895
6127
|
for (const [ax, ay] of anchorPts) {
|
|
5896
6128
|
ctx.beginPath();
|
|
5897
6129
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -5949,7 +6181,7 @@ var FlatChannel = class {
|
|
|
5949
6181
|
this.points = [first, { ...first }];
|
|
5950
6182
|
this.complete = false;
|
|
5951
6183
|
}
|
|
5952
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
6184
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
5953
6185
|
if (this.points.length < 2) return;
|
|
5954
6186
|
const [p1, p2, p3] = this.points;
|
|
5955
6187
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -5957,12 +6189,14 @@ var FlatChannel = class {
|
|
|
5957
6189
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
5958
6190
|
const priceOffset = p3 ? p3.price - p1.price : 0;
|
|
5959
6191
|
const y2 = scale.yToPixel(p1.price + priceOffset, priceRange, chartH);
|
|
6192
|
+
const s = resolveStyle("flatChannel", this.style, theme);
|
|
5960
6193
|
ctx.save();
|
|
5961
|
-
ctx.strokeStyle =
|
|
5962
|
-
ctx.lineWidth = isSelected ?
|
|
6194
|
+
ctx.strokeStyle = s.strokeColor;
|
|
6195
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6196
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
5963
6197
|
ctx.beginPath();
|
|
5964
6198
|
ctx.rect(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x2 - x1), Math.abs(y2 - y1));
|
|
5965
|
-
ctx.fillStyle =
|
|
6199
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
5966
6200
|
ctx.fill();
|
|
5967
6201
|
ctx.beginPath();
|
|
5968
6202
|
ctx.moveTo(x1, y1);
|
|
@@ -5974,8 +6208,9 @@ var FlatChannel = class {
|
|
|
5974
6208
|
ctx.lineTo(x2, y2);
|
|
5975
6209
|
ctx.stroke();
|
|
5976
6210
|
}
|
|
6211
|
+
ctx.setLineDash([]);
|
|
5977
6212
|
if (isSelected) {
|
|
5978
|
-
ctx.fillStyle =
|
|
6213
|
+
ctx.fillStyle = s.strokeColor;
|
|
5979
6214
|
const anchorPts = [[x1, y1], [x2, y1]];
|
|
5980
6215
|
if (p3) {
|
|
5981
6216
|
const p3x = scale.xToPixel(p3.barIndex, viewport, chartW);
|
|
@@ -6036,7 +6271,7 @@ var DisjointChannel = class {
|
|
|
6036
6271
|
this.points = [first, { ...first }];
|
|
6037
6272
|
this.complete = false;
|
|
6038
6273
|
}
|
|
6039
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
6274
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
6040
6275
|
if (this.points.length < 2) return;
|
|
6041
6276
|
const toXY = (p) => [
|
|
6042
6277
|
scale.xToPixel(p.barIndex, viewport, chartW),
|
|
@@ -6045,9 +6280,11 @@ var DisjointChannel = class {
|
|
|
6045
6280
|
const [p1, p2, p3, p4] = this.points;
|
|
6046
6281
|
const [x1, y1] = toXY(p1);
|
|
6047
6282
|
const [x2, y2] = toXY(p2);
|
|
6283
|
+
const s = resolveStyle("disjointChannel", this.style, theme);
|
|
6048
6284
|
ctx.save();
|
|
6049
|
-
ctx.strokeStyle =
|
|
6050
|
-
ctx.lineWidth = isSelected ?
|
|
6285
|
+
ctx.strokeStyle = s.strokeColor;
|
|
6286
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6287
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
6051
6288
|
if (p3 && p4) {
|
|
6052
6289
|
const [x3, y3] = toXY(p3);
|
|
6053
6290
|
const [x4, y4] = toXY(p4);
|
|
@@ -6057,7 +6294,7 @@ var DisjointChannel = class {
|
|
|
6057
6294
|
ctx.lineTo(x4, y4);
|
|
6058
6295
|
ctx.lineTo(x3, y3);
|
|
6059
6296
|
ctx.closePath();
|
|
6060
|
-
ctx.fillStyle =
|
|
6297
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
6061
6298
|
ctx.fill();
|
|
6062
6299
|
ctx.beginPath();
|
|
6063
6300
|
ctx.moveTo(x1, y1);
|
|
@@ -6067,8 +6304,9 @@ var DisjointChannel = class {
|
|
|
6067
6304
|
ctx.moveTo(x3, y3);
|
|
6068
6305
|
ctx.lineTo(x4, y4);
|
|
6069
6306
|
ctx.stroke();
|
|
6307
|
+
ctx.setLineDash([]);
|
|
6070
6308
|
if (isSelected) {
|
|
6071
|
-
ctx.fillStyle =
|
|
6309
|
+
ctx.fillStyle = s.strokeColor;
|
|
6072
6310
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3], [x4, y4]]) {
|
|
6073
6311
|
ctx.beginPath();
|
|
6074
6312
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -6085,8 +6323,9 @@ var DisjointChannel = class {
|
|
|
6085
6323
|
ctx.moveTo(x3, y3);
|
|
6086
6324
|
ctx.lineTo(x3, y3);
|
|
6087
6325
|
ctx.stroke();
|
|
6326
|
+
ctx.setLineDash([]);
|
|
6088
6327
|
if (isSelected) {
|
|
6089
|
-
ctx.fillStyle =
|
|
6328
|
+
ctx.fillStyle = s.strokeColor;
|
|
6090
6329
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3]]) {
|
|
6091
6330
|
ctx.beginPath();
|
|
6092
6331
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -6098,8 +6337,9 @@ var DisjointChannel = class {
|
|
|
6098
6337
|
ctx.moveTo(x1, y1);
|
|
6099
6338
|
ctx.lineTo(x2, y2);
|
|
6100
6339
|
ctx.stroke();
|
|
6340
|
+
ctx.setLineDash([]);
|
|
6101
6341
|
if (isSelected) {
|
|
6102
|
-
ctx.fillStyle =
|
|
6342
|
+
ctx.fillStyle = s.strokeColor;
|
|
6103
6343
|
for (const [ax, ay] of [[x1, y1], [x2, y2]]) {
|
|
6104
6344
|
ctx.beginPath();
|
|
6105
6345
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -6143,7 +6383,7 @@ var DisjointChannel = class {
|
|
|
6143
6383
|
|
|
6144
6384
|
// src/drawings/FibRetracement.ts
|
|
6145
6385
|
var FIB_LEVELS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
|
|
6146
|
-
var
|
|
6386
|
+
var FIB_FALLBACK_COLORS = ["#ef5350", "#e8b84b", "#26a69a", "#2196f3", "#26a69a", "#e8b84b", "#ef5350"];
|
|
6147
6387
|
var HIT_TOLERANCE9 = 6;
|
|
6148
6388
|
var FibRetracement = class {
|
|
6149
6389
|
constructor(first) {
|
|
@@ -6152,7 +6392,7 @@ var FibRetracement = class {
|
|
|
6152
6392
|
this.points = [first, { ...first }];
|
|
6153
6393
|
this.complete = false;
|
|
6154
6394
|
}
|
|
6155
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
6395
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
6156
6396
|
if (this.points.length < 2) return;
|
|
6157
6397
|
const [p1, p2] = this.points;
|
|
6158
6398
|
const highPrice = Math.max(p1.price, p2.price);
|
|
@@ -6164,17 +6404,23 @@ var FibRetracement = class {
|
|
|
6164
6404
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
6165
6405
|
const xMin = Math.min(x1, x2);
|
|
6166
6406
|
const xMax = Math.max(x1, x2);
|
|
6407
|
+
const s = resolveStyle("fibRetracement", this.style, theme);
|
|
6408
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6409
|
+
const anchorColor = s.strokeColor ?? FIB_FALLBACK_COLORS[1];
|
|
6167
6410
|
ctx.save();
|
|
6168
6411
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6412
|
+
ctx.lineWidth = baseLineWidth;
|
|
6169
6413
|
for (let i = 0; i < FIB_LEVELS.length; i++) {
|
|
6170
6414
|
const level = FIB_LEVELS[i];
|
|
6415
|
+
const key = String(level);
|
|
6416
|
+
const lvl = s.levels?.[key];
|
|
6417
|
+
if (lvl?.visible === false) continue;
|
|
6171
6418
|
const price = highPrice - range * level;
|
|
6172
6419
|
const y = scale.yToPixel(price, priceRange, chartH);
|
|
6173
|
-
const
|
|
6174
|
-
const
|
|
6175
|
-
ctx.strokeStyle = color
|
|
6176
|
-
ctx
|
|
6177
|
-
ctx.setLineDash(level === 0 || level === 1 ? [] : [4, 3]);
|
|
6420
|
+
const baseColor = lvl?.color ?? s.strokeColor ?? FIB_FALLBACK_COLORS[i];
|
|
6421
|
+
const color = hexWithAlpha(baseColor, isSelected ? 0.8 : 0.6);
|
|
6422
|
+
ctx.strokeStyle = color;
|
|
6423
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6178
6424
|
ctx.beginPath();
|
|
6179
6425
|
ctx.moveTo(xMin, y);
|
|
6180
6426
|
ctx.lineTo(xMax, y);
|
|
@@ -6182,13 +6428,13 @@ var FibRetracement = class {
|
|
|
6182
6428
|
ctx.setLineDash([]);
|
|
6183
6429
|
const pct = `${(level * 100).toFixed(1)}%`;
|
|
6184
6430
|
const lbl = `${pct} ${price.toFixed(pricePrecision)}`;
|
|
6185
|
-
ctx.fillStyle = color
|
|
6431
|
+
ctx.fillStyle = color;
|
|
6186
6432
|
ctx.fillText(lbl, xMin + 4, y - 3);
|
|
6187
6433
|
}
|
|
6188
6434
|
for (const [ax, ay] of [[x1, y1], [x2, y2]]) {
|
|
6189
6435
|
ctx.beginPath();
|
|
6190
6436
|
ctx.arc(ax, ay, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6191
|
-
ctx.fillStyle =
|
|
6437
|
+
ctx.fillStyle = anchorColor;
|
|
6192
6438
|
ctx.fill();
|
|
6193
6439
|
}
|
|
6194
6440
|
ctx.restore();
|
|
@@ -6223,7 +6469,7 @@ var FibRetracement = class {
|
|
|
6223
6469
|
// src/drawings/FibExtension.ts
|
|
6224
6470
|
var HIT_TOLERANCE10 = 6;
|
|
6225
6471
|
var FIB_EXT_LEVELS = [0, 0.382, 0.618, 1, 1.382, 1.618, 2, 2.618];
|
|
6226
|
-
var
|
|
6472
|
+
var FIB_EXT_FALLBACK_COLORS = ["#ef5350", "#e8b84b", "#26a69a", "#2196f3", "#26a69a", "#e8b84b", "#ef5350", "#ef5350"];
|
|
6227
6473
|
var FibExtension = class {
|
|
6228
6474
|
constructor(first) {
|
|
6229
6475
|
this.type = "fibExtension";
|
|
@@ -6232,15 +6478,18 @@ var FibExtension = class {
|
|
|
6232
6478
|
this.points = [first, { ...first }];
|
|
6233
6479
|
this.complete = false;
|
|
6234
6480
|
}
|
|
6235
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
6481
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
6236
6482
|
const [p1, p2, p3] = this.points;
|
|
6237
6483
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
6238
6484
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
6239
6485
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
6240
6486
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
6487
|
+
const s = resolveStyle("fibExtension", this.style, theme);
|
|
6488
|
+
const anchorColor = s.strokeColor ?? "#e8b84b";
|
|
6489
|
+
const connectorColor = hexWithAlpha(s.strokeColor ?? "#888888", 0.67);
|
|
6241
6490
|
if (!p3) {
|
|
6242
6491
|
ctx.save();
|
|
6243
|
-
ctx.strokeStyle =
|
|
6492
|
+
ctx.strokeStyle = connectorColor;
|
|
6244
6493
|
ctx.lineWidth = 1;
|
|
6245
6494
|
ctx.setLineDash([4, 3]);
|
|
6246
6495
|
ctx.beginPath();
|
|
@@ -6248,7 +6497,7 @@ var FibExtension = class {
|
|
|
6248
6497
|
ctx.lineTo(x2, y2);
|
|
6249
6498
|
ctx.stroke();
|
|
6250
6499
|
ctx.setLineDash([]);
|
|
6251
|
-
ctx.fillStyle =
|
|
6500
|
+
ctx.fillStyle = anchorColor;
|
|
6252
6501
|
ctx.beginPath();
|
|
6253
6502
|
ctx.arc(x1, y1, 3, 0, Math.PI * 2);
|
|
6254
6503
|
ctx.fill();
|
|
@@ -6260,10 +6509,11 @@ var FibExtension = class {
|
|
|
6260
6509
|
const xMin = Math.min(x1, x2, x3);
|
|
6261
6510
|
const xMax = Math.max(x1, x2, x3);
|
|
6262
6511
|
const priceMove = p2.price - p1.price;
|
|
6512
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6263
6513
|
ctx.save();
|
|
6264
6514
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6265
|
-
ctx.lineWidth =
|
|
6266
|
-
ctx.strokeStyle =
|
|
6515
|
+
ctx.lineWidth = baseLineWidth;
|
|
6516
|
+
ctx.strokeStyle = connectorColor;
|
|
6267
6517
|
ctx.setLineDash([4, 3]);
|
|
6268
6518
|
ctx.beginPath();
|
|
6269
6519
|
ctx.moveTo(x1, y1);
|
|
@@ -6276,12 +6526,15 @@ var FibExtension = class {
|
|
|
6276
6526
|
ctx.setLineDash([]);
|
|
6277
6527
|
for (let i = 0; i < FIB_EXT_LEVELS.length; i++) {
|
|
6278
6528
|
const level = FIB_EXT_LEVELS[i];
|
|
6529
|
+
const key = String(level);
|
|
6530
|
+
const lvl = s.levels?.[key];
|
|
6531
|
+
if (lvl?.visible === false) continue;
|
|
6279
6532
|
const levelPrice = p3.price + level * priceMove;
|
|
6280
6533
|
const y = scale.yToPixel(levelPrice, priceRange, chartH);
|
|
6281
|
-
const
|
|
6282
|
-
const
|
|
6283
|
-
ctx.strokeStyle = color
|
|
6284
|
-
ctx
|
|
6534
|
+
const baseColor = lvl?.color ?? s.strokeColor ?? FIB_EXT_FALLBACK_COLORS[i];
|
|
6535
|
+
const color = hexWithAlpha(baseColor, isSelected ? 0.8 : 0.6);
|
|
6536
|
+
ctx.strokeStyle = color;
|
|
6537
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6285
6538
|
ctx.beginPath();
|
|
6286
6539
|
ctx.moveTo(xMin, y);
|
|
6287
6540
|
ctx.lineTo(xMax, y);
|
|
@@ -6289,13 +6542,13 @@ var FibExtension = class {
|
|
|
6289
6542
|
ctx.setLineDash([]);
|
|
6290
6543
|
const pct = `${(level * 100).toFixed(1)}%`;
|
|
6291
6544
|
const lbl = `${pct} ${levelPrice.toFixed(pricePrecision)}`;
|
|
6292
|
-
ctx.fillStyle = color
|
|
6545
|
+
ctx.fillStyle = color;
|
|
6293
6546
|
ctx.fillText(lbl, xMin + 4, y - 3);
|
|
6294
6547
|
}
|
|
6295
6548
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3]]) {
|
|
6296
6549
|
ctx.beginPath();
|
|
6297
6550
|
ctx.arc(ax, ay, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6298
|
-
ctx.fillStyle =
|
|
6551
|
+
ctx.fillStyle = anchorColor;
|
|
6299
6552
|
ctx.fill();
|
|
6300
6553
|
}
|
|
6301
6554
|
ctx.restore();
|
|
@@ -6329,7 +6582,6 @@ var FibExtension = class {
|
|
|
6329
6582
|
// src/drawings/FibChannel.ts
|
|
6330
6583
|
var HIT_TOLERANCE11 = 6;
|
|
6331
6584
|
var FIB_LEVELS2 = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
|
|
6332
|
-
var FIB_COLORS2 = ["#ef5350", "#e8b84b", "#26a69a", "#2196f3", "#26a69a", "#e8b84b", "#ef5350"];
|
|
6333
6585
|
function pointToSegmentDist6(px, py, x1, y1, x2, y2) {
|
|
6334
6586
|
const dx = x2 - x1, dy = y2 - y1;
|
|
6335
6587
|
const lenSq = dx * dx + dy * dy;
|
|
@@ -6345,15 +6597,17 @@ var FibChannel = class {
|
|
|
6345
6597
|
this.points = [first, { ...first }];
|
|
6346
6598
|
this.complete = false;
|
|
6347
6599
|
}
|
|
6348
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected,
|
|
6600
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision = 2, theme) {
|
|
6349
6601
|
const [p1, p2, p3] = this.points;
|
|
6350
6602
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
6351
6603
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
6352
6604
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
6353
6605
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
6606
|
+
const s = resolveStyle("fibChannel", this.style, theme);
|
|
6607
|
+
const connectorColor = hexWithAlpha(s.strokeColor, 0.67);
|
|
6354
6608
|
if (!p3) {
|
|
6355
6609
|
ctx.save();
|
|
6356
|
-
ctx.strokeStyle =
|
|
6610
|
+
ctx.strokeStyle = connectorColor;
|
|
6357
6611
|
ctx.lineWidth = 1;
|
|
6358
6612
|
ctx.setLineDash([4, 3]);
|
|
6359
6613
|
ctx.beginPath();
|
|
@@ -6361,7 +6615,7 @@ var FibChannel = class {
|
|
|
6361
6615
|
ctx.lineTo(x2, y2);
|
|
6362
6616
|
ctx.stroke();
|
|
6363
6617
|
ctx.setLineDash([]);
|
|
6364
|
-
ctx.fillStyle =
|
|
6618
|
+
ctx.fillStyle = s.strokeColor;
|
|
6365
6619
|
ctx.beginPath();
|
|
6366
6620
|
ctx.arc(x1, y1, 3, 0, Math.PI * 2);
|
|
6367
6621
|
ctx.fill();
|
|
@@ -6377,9 +6631,10 @@ var FibChannel = class {
|
|
|
6377
6631
|
const nx = -dy / len;
|
|
6378
6632
|
const ny = dx / len;
|
|
6379
6633
|
const perpDist = (x3 - x1) * nx + (y3 - y1) * ny;
|
|
6634
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6380
6635
|
ctx.save();
|
|
6381
6636
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6382
|
-
ctx.lineWidth =
|
|
6637
|
+
ctx.lineWidth = baseLineWidth;
|
|
6383
6638
|
for (let i = 0; i < FIB_LEVELS2.length; i++) {
|
|
6384
6639
|
const level = FIB_LEVELS2[i];
|
|
6385
6640
|
const offset = level * perpDist;
|
|
@@ -6387,30 +6642,29 @@ var FibChannel = class {
|
|
|
6387
6642
|
const ly1 = y1 + offset * ny;
|
|
6388
6643
|
const lx2 = x2 + offset * nx;
|
|
6389
6644
|
const ly2 = y2 + offset * ny;
|
|
6390
|
-
const color =
|
|
6391
|
-
|
|
6392
|
-
ctx
|
|
6393
|
-
ctx.setLineDash(level === 0 || level === 1 ? [] : [4, 3]);
|
|
6645
|
+
const color = hexWithAlpha(s.strokeColor, isSelected ? 0.8 : 0.6);
|
|
6646
|
+
ctx.strokeStyle = color;
|
|
6647
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6394
6648
|
ctx.beginPath();
|
|
6395
6649
|
ctx.moveTo(lx1, ly1);
|
|
6396
6650
|
ctx.lineTo(lx2, ly2);
|
|
6397
6651
|
ctx.stroke();
|
|
6398
6652
|
ctx.setLineDash([]);
|
|
6399
6653
|
const pct = `${(level * 100).toFixed(1)}%`;
|
|
6400
|
-
ctx.fillStyle = color
|
|
6654
|
+
ctx.fillStyle = color;
|
|
6401
6655
|
ctx.fillText(pct, lx2 + 4, ly2 + 3);
|
|
6402
6656
|
}
|
|
6403
6657
|
ctx.beginPath();
|
|
6404
6658
|
ctx.arc(x3, y3, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6405
|
-
ctx.fillStyle =
|
|
6659
|
+
ctx.fillStyle = s.strokeColor;
|
|
6406
6660
|
ctx.fill();
|
|
6407
6661
|
for (const [ax, ay] of [[x1, y1], [x2, y2]]) {
|
|
6408
6662
|
ctx.beginPath();
|
|
6409
6663
|
ctx.arc(ax, ay, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6410
|
-
ctx.fillStyle =
|
|
6664
|
+
ctx.fillStyle = s.strokeColor;
|
|
6411
6665
|
ctx.fill();
|
|
6412
6666
|
}
|
|
6413
|
-
ctx.strokeStyle =
|
|
6667
|
+
ctx.strokeStyle = connectorColor;
|
|
6414
6668
|
ctx.setLineDash([3, 3]);
|
|
6415
6669
|
ctx.beginPath();
|
|
6416
6670
|
ctx.moveTo(x1, y1);
|
|
@@ -6470,19 +6724,20 @@ var FibTimezone = class {
|
|
|
6470
6724
|
this.points = [first, { ...first }];
|
|
6471
6725
|
this.complete = false;
|
|
6472
6726
|
}
|
|
6473
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
6727
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
6474
6728
|
if (this.points.length < 2) return;
|
|
6475
6729
|
const [p1, p2] = this.points;
|
|
6476
6730
|
const interval = p2.barIndex - p1.barIndex;
|
|
6477
6731
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
6478
6732
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
6733
|
+
const s = resolveStyle("fibTimezone", this.style, theme);
|
|
6734
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6735
|
+
const lineColor = hexWithAlpha(s.strokeColor, isSelected ? 0.8 : 0.53);
|
|
6479
6736
|
ctx.save();
|
|
6480
6737
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6481
|
-
ctx.lineWidth =
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
ctx.strokeStyle = color + alpha;
|
|
6485
|
-
ctx.setLineDash([3, 3]);
|
|
6738
|
+
ctx.lineWidth = baseLineWidth;
|
|
6739
|
+
ctx.strokeStyle = lineColor;
|
|
6740
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6486
6741
|
ctx.beginPath();
|
|
6487
6742
|
ctx.moveTo(x1, 0);
|
|
6488
6743
|
ctx.lineTo(x1, chartH);
|
|
@@ -6496,14 +6751,14 @@ var FibTimezone = class {
|
|
|
6496
6751
|
const barIndex = p1.barIndex + fib * interval;
|
|
6497
6752
|
const xFib = scale.xToPixel(barIndex, viewport, chartW);
|
|
6498
6753
|
if (xFib < -50 || xFib > chartW + 50) continue;
|
|
6499
|
-
ctx.strokeStyle =
|
|
6500
|
-
ctx.
|
|
6754
|
+
ctx.strokeStyle = lineColor;
|
|
6755
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6501
6756
|
ctx.beginPath();
|
|
6502
6757
|
ctx.moveTo(xFib, 0);
|
|
6503
6758
|
ctx.lineTo(xFib, chartH);
|
|
6504
6759
|
ctx.stroke();
|
|
6505
6760
|
ctx.setLineDash([]);
|
|
6506
|
-
ctx.fillStyle =
|
|
6761
|
+
ctx.fillStyle = lineColor;
|
|
6507
6762
|
ctx.fillText(String(fib), xFib + 3, 12);
|
|
6508
6763
|
}
|
|
6509
6764
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
@@ -6511,7 +6766,7 @@ var FibTimezone = class {
|
|
|
6511
6766
|
for (const [ax, ay] of [[x1, y1], [x2, y2]]) {
|
|
6512
6767
|
ctx.beginPath();
|
|
6513
6768
|
ctx.arc(ax, ay, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6514
|
-
ctx.fillStyle =
|
|
6769
|
+
ctx.fillStyle = s.strokeColor;
|
|
6515
6770
|
ctx.fill();
|
|
6516
6771
|
}
|
|
6517
6772
|
ctx.restore();
|
|
@@ -6547,7 +6802,6 @@ var FibTimezone = class {
|
|
|
6547
6802
|
// src/drawings/FibCircles.ts
|
|
6548
6803
|
var HIT_TOLERANCE13 = 6;
|
|
6549
6804
|
var FIB_RATIOS = [0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618, 2.618];
|
|
6550
|
-
var FIB_COLORS3 = ["#ef5350", "#e8b84b", "#26a69a", "#2196f3", "#26a69a", "#e8b84b", "#ef5350", "#ef5350"];
|
|
6551
6805
|
var FibCircles = class {
|
|
6552
6806
|
constructor(first) {
|
|
6553
6807
|
this.type = "fibCircles";
|
|
@@ -6555,7 +6809,7 @@ var FibCircles = class {
|
|
|
6555
6809
|
this.points = [first, { ...first }];
|
|
6556
6810
|
this.complete = false;
|
|
6557
6811
|
}
|
|
6558
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
6812
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
6559
6813
|
if (this.points.length < 2) return;
|
|
6560
6814
|
const [p1, p2] = this.points;
|
|
6561
6815
|
const cx = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -6563,30 +6817,33 @@ var FibCircles = class {
|
|
|
6563
6817
|
const rx = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
6564
6818
|
const ry = scale.yToPixel(p2.price, priceRange, chartH);
|
|
6565
6819
|
const baseRadius = Math.hypot(rx - cx, ry - cy);
|
|
6820
|
+
const s = resolveStyle("fibCircles", this.style, theme);
|
|
6821
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6566
6822
|
ctx.save();
|
|
6567
6823
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6568
|
-
ctx.lineWidth =
|
|
6824
|
+
ctx.lineWidth = baseLineWidth;
|
|
6569
6825
|
for (let i = 0; i < FIB_RATIOS.length; i++) {
|
|
6570
6826
|
const ratio = FIB_RATIOS[i];
|
|
6571
6827
|
const radius = ratio * baseRadius;
|
|
6572
|
-
const color =
|
|
6573
|
-
|
|
6574
|
-
ctx
|
|
6828
|
+
const color = hexWithAlpha(s.strokeColor, isSelected ? 0.8 : 0.53);
|
|
6829
|
+
ctx.strokeStyle = color;
|
|
6830
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6575
6831
|
ctx.beginPath();
|
|
6576
6832
|
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
|
|
6577
6833
|
ctx.stroke();
|
|
6578
|
-
ctx.
|
|
6834
|
+
ctx.setLineDash([]);
|
|
6835
|
+
ctx.fillStyle = color;
|
|
6579
6836
|
ctx.fillText(`${(ratio * 100).toFixed(1)}%`, cx + 3, cy - radius + 12);
|
|
6580
6837
|
}
|
|
6581
6838
|
ctx.beginPath();
|
|
6582
6839
|
ctx.arc(cx, cy, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6583
|
-
ctx.fillStyle =
|
|
6840
|
+
ctx.fillStyle = s.strokeColor;
|
|
6584
6841
|
ctx.fill();
|
|
6585
6842
|
ctx.beginPath();
|
|
6586
6843
|
ctx.arc(rx, ry, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6587
|
-
ctx.fillStyle =
|
|
6844
|
+
ctx.fillStyle = s.strokeColor;
|
|
6588
6845
|
ctx.fill();
|
|
6589
|
-
ctx.strokeStyle =
|
|
6846
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, 0.4);
|
|
6590
6847
|
ctx.setLineDash([3, 3]);
|
|
6591
6848
|
ctx.beginPath();
|
|
6592
6849
|
ctx.moveTo(cx, cy);
|
|
@@ -6660,7 +6917,7 @@ var FibSpiral = class {
|
|
|
6660
6917
|
}
|
|
6661
6918
|
return arcs;
|
|
6662
6919
|
}
|
|
6663
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
6920
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
6664
6921
|
if (this.points.length < 2) return;
|
|
6665
6922
|
const [p1, p2] = this.points;
|
|
6666
6923
|
const cx = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -6669,10 +6926,13 @@ var FibSpiral = class {
|
|
|
6669
6926
|
const py2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
6670
6927
|
const dx = px2 - cx;
|
|
6671
6928
|
const dy = py2 - cy;
|
|
6929
|
+
const s = resolveStyle("fibSpiral", this.style, theme);
|
|
6930
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6672
6931
|
ctx.save();
|
|
6673
6932
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6674
|
-
ctx.strokeStyle = isSelected ?
|
|
6675
|
-
ctx.lineWidth =
|
|
6933
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, isSelected ? 0.8 : 0.53);
|
|
6934
|
+
ctx.lineWidth = baseLineWidth;
|
|
6935
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6676
6936
|
const arcs = this.computeArcs(cx, cy, dx, dy);
|
|
6677
6937
|
ctx.beginPath();
|
|
6678
6938
|
for (let i = 0; i < arcs.length; i++) {
|
|
@@ -6685,15 +6945,16 @@ var FibSpiral = class {
|
|
|
6685
6945
|
ctx.arc(arc.ox, arc.oy, arc.r, arc.startAngle, arc.endAngle, false);
|
|
6686
6946
|
}
|
|
6687
6947
|
ctx.stroke();
|
|
6948
|
+
ctx.setLineDash([]);
|
|
6688
6949
|
ctx.beginPath();
|
|
6689
6950
|
ctx.arc(cx, cy, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6690
|
-
ctx.fillStyle =
|
|
6951
|
+
ctx.fillStyle = s.strokeColor;
|
|
6691
6952
|
ctx.fill();
|
|
6692
6953
|
ctx.beginPath();
|
|
6693
6954
|
ctx.arc(px2, py2, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6694
|
-
ctx.fillStyle =
|
|
6955
|
+
ctx.fillStyle = s.strokeColor;
|
|
6695
6956
|
ctx.fill();
|
|
6696
|
-
ctx.strokeStyle =
|
|
6957
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, 0.4);
|
|
6697
6958
|
ctx.setLineDash([3, 3]);
|
|
6698
6959
|
ctx.beginPath();
|
|
6699
6960
|
ctx.moveTo(cx, cy);
|
|
@@ -6744,7 +7005,6 @@ var FibSpiral = class {
|
|
|
6744
7005
|
// src/drawings/FibFan.ts
|
|
6745
7006
|
var HIT_TOLERANCE15 = 6;
|
|
6746
7007
|
var FIB_FAN_RATIOS = [0.236, 0.382, 0.5, 0.618, 0.786];
|
|
6747
|
-
var FIB_FAN_COLORS = ["#e8b84b", "#26a69a", "#2196f3", "#26a69a", "#e8b84b"];
|
|
6748
7008
|
function pointToSegmentDist7(px, py, x1, y1, x2, y2) {
|
|
6749
7009
|
const dx = x2 - x1, dy = y2 - y1;
|
|
6750
7010
|
const lenSq = dx * dx + dy * dy;
|
|
@@ -6770,17 +7030,19 @@ var FibFan = class {
|
|
|
6770
7030
|
this.points = [first, { ...first }];
|
|
6771
7031
|
this.complete = false;
|
|
6772
7032
|
}
|
|
6773
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7033
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
6774
7034
|
if (this.points.length < 2) return;
|
|
6775
7035
|
const [p1, p2] = this.points;
|
|
6776
7036
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
6777
7037
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
6778
7038
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
6779
7039
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
7040
|
+
const s = resolveStyle("fibFan", this.style, theme);
|
|
7041
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6780
7042
|
ctx.save();
|
|
6781
7043
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6782
|
-
ctx.lineWidth =
|
|
6783
|
-
ctx.strokeStyle =
|
|
7044
|
+
ctx.lineWidth = baseLineWidth;
|
|
7045
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, 0.67);
|
|
6784
7046
|
ctx.setLineDash([4, 3]);
|
|
6785
7047
|
ctx.beginPath();
|
|
6786
7048
|
ctx.moveTo(x1, y1);
|
|
@@ -6790,24 +7052,25 @@ var FibFan = class {
|
|
|
6790
7052
|
const priceRange2 = p2.price - p1.price;
|
|
6791
7053
|
for (let i = 0; i < FIB_FAN_RATIOS.length; i++) {
|
|
6792
7054
|
const ratio = FIB_FAN_RATIOS[i];
|
|
6793
|
-
const color =
|
|
6794
|
-
const alpha = isSelected ? "cc" : "99";
|
|
7055
|
+
const color = hexWithAlpha(s.strokeColor, isSelected ? 0.8 : 0.6);
|
|
6795
7056
|
const targetPrice = p1.price + ratio * priceRange2;
|
|
6796
7057
|
const tx = x2;
|
|
6797
7058
|
const ty = scale.yToPixel(targetPrice, priceRange, chartH);
|
|
6798
7059
|
const [ex, ey] = extendRay(x1, y1, tx, ty, chartW, chartH);
|
|
6799
|
-
ctx.strokeStyle = color
|
|
7060
|
+
ctx.strokeStyle = color;
|
|
7061
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6800
7062
|
ctx.beginPath();
|
|
6801
7063
|
ctx.moveTo(x1, y1);
|
|
6802
7064
|
ctx.lineTo(ex, ey);
|
|
6803
7065
|
ctx.stroke();
|
|
6804
|
-
ctx.
|
|
7066
|
+
ctx.setLineDash([]);
|
|
7067
|
+
ctx.fillStyle = color;
|
|
6805
7068
|
ctx.fillText(`${(ratio * 100).toFixed(1)}%`, Math.min(ex - 32, chartW - 40), ey - 3);
|
|
6806
7069
|
}
|
|
6807
7070
|
for (const [ax, ay] of [[x1, y1], [x2, y2]]) {
|
|
6808
7071
|
ctx.beginPath();
|
|
6809
7072
|
ctx.arc(ax, ay, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6810
|
-
ctx.fillStyle =
|
|
7073
|
+
ctx.fillStyle = s.strokeColor;
|
|
6811
7074
|
ctx.fill();
|
|
6812
7075
|
}
|
|
6813
7076
|
ctx.restore();
|
|
@@ -6846,7 +7109,7 @@ var FibFan = class {
|
|
|
6846
7109
|
// src/drawings/FibProjection.ts
|
|
6847
7110
|
var HIT_TOLERANCE16 = 6;
|
|
6848
7111
|
var FIB_PROJ_LEVELS = [0, 0.618, 1, 1.272, 1.618, 2, 2.618];
|
|
6849
|
-
var
|
|
7112
|
+
var FIB_PROJ_FALLBACK_COLORS = ["#ef5350", "#26a69a", "#2196f3", "#26a69a", "#e8b84b", "#ef5350", "#ef5350"];
|
|
6850
7113
|
var FibProjection = class {
|
|
6851
7114
|
constructor(first) {
|
|
6852
7115
|
this.type = "fibProjection";
|
|
@@ -6855,15 +7118,18 @@ var FibProjection = class {
|
|
|
6855
7118
|
this.points = [first, { ...first }];
|
|
6856
7119
|
this.complete = false;
|
|
6857
7120
|
}
|
|
6858
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
7121
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
6859
7122
|
const [p1, p2, p3] = this.points;
|
|
6860
7123
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
6861
7124
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
6862
7125
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
6863
7126
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
7127
|
+
const s = resolveStyle("fibProjection", this.style, theme);
|
|
7128
|
+
const anchorColor = s.strokeColor ?? "#e8b84b";
|
|
7129
|
+
const connectorColor = hexWithAlpha(s.strokeColor ?? "#888888", 0.67);
|
|
6864
7130
|
if (!p3) {
|
|
6865
7131
|
ctx.save();
|
|
6866
|
-
ctx.strokeStyle =
|
|
7132
|
+
ctx.strokeStyle = connectorColor;
|
|
6867
7133
|
ctx.lineWidth = 1;
|
|
6868
7134
|
ctx.setLineDash([4, 3]);
|
|
6869
7135
|
ctx.beginPath();
|
|
@@ -6871,7 +7137,7 @@ var FibProjection = class {
|
|
|
6871
7137
|
ctx.lineTo(x2, y2);
|
|
6872
7138
|
ctx.stroke();
|
|
6873
7139
|
ctx.setLineDash([]);
|
|
6874
|
-
ctx.fillStyle =
|
|
7140
|
+
ctx.fillStyle = anchorColor;
|
|
6875
7141
|
ctx.beginPath();
|
|
6876
7142
|
ctx.arc(x1, y1, 3, 0, Math.PI * 2);
|
|
6877
7143
|
ctx.fill();
|
|
@@ -6883,10 +7149,11 @@ var FibProjection = class {
|
|
|
6883
7149
|
const xMin = Math.min(x1, x3);
|
|
6884
7150
|
const xMax = Math.max(x1, x3);
|
|
6885
7151
|
const baseMove = p2.price - p1.price;
|
|
7152
|
+
const baseLineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
6886
7153
|
ctx.save();
|
|
6887
7154
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6888
|
-
ctx.lineWidth =
|
|
6889
|
-
ctx.strokeStyle =
|
|
7155
|
+
ctx.lineWidth = baseLineWidth;
|
|
7156
|
+
ctx.strokeStyle = connectorColor;
|
|
6890
7157
|
ctx.setLineDash([4, 3]);
|
|
6891
7158
|
ctx.beginPath();
|
|
6892
7159
|
ctx.moveTo(x1, y1);
|
|
@@ -6899,12 +7166,15 @@ var FibProjection = class {
|
|
|
6899
7166
|
ctx.setLineDash([]);
|
|
6900
7167
|
for (let i = 0; i < FIB_PROJ_LEVELS.length; i++) {
|
|
6901
7168
|
const level = FIB_PROJ_LEVELS[i];
|
|
7169
|
+
const key = String(level);
|
|
7170
|
+
const lvl = s.levels?.[key];
|
|
7171
|
+
if (lvl?.visible === false) continue;
|
|
6902
7172
|
const levelPrice = p3.price + level * baseMove;
|
|
6903
7173
|
const y = scale.yToPixel(levelPrice, priceRange, chartH);
|
|
6904
|
-
const
|
|
6905
|
-
const
|
|
6906
|
-
ctx.strokeStyle = color
|
|
6907
|
-
ctx
|
|
7174
|
+
const baseColor = lvl?.color ?? s.strokeColor ?? FIB_PROJ_FALLBACK_COLORS[i];
|
|
7175
|
+
const color = hexWithAlpha(baseColor, isSelected ? 0.8 : 0.6);
|
|
7176
|
+
ctx.strokeStyle = color;
|
|
7177
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
6908
7178
|
ctx.beginPath();
|
|
6909
7179
|
ctx.moveTo(xMin, y);
|
|
6910
7180
|
ctx.lineTo(xMax, y);
|
|
@@ -6912,13 +7182,13 @@ var FibProjection = class {
|
|
|
6912
7182
|
ctx.setLineDash([]);
|
|
6913
7183
|
const pct = `${(level * 100).toFixed(1)}%`;
|
|
6914
7184
|
const lbl = `${pct} ${levelPrice.toFixed(pricePrecision)}`;
|
|
6915
|
-
ctx.fillStyle = color
|
|
7185
|
+
ctx.fillStyle = color;
|
|
6916
7186
|
ctx.fillText(lbl, xMin + 4, y - 3);
|
|
6917
7187
|
}
|
|
6918
7188
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3]]) {
|
|
6919
7189
|
ctx.beginPath();
|
|
6920
7190
|
ctx.arc(ax, ay, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
6921
|
-
ctx.fillStyle =
|
|
7191
|
+
ctx.fillStyle = anchorColor;
|
|
6922
7192
|
ctx.fill();
|
|
6923
7193
|
}
|
|
6924
7194
|
ctx.restore();
|
|
@@ -6976,7 +7246,7 @@ var GannFan = class {
|
|
|
6976
7246
|
this.points = [first, { ...first }];
|
|
6977
7247
|
this.complete = false;
|
|
6978
7248
|
}
|
|
6979
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7249
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
6980
7250
|
if (this.points.length < 2) return;
|
|
6981
7251
|
const [p1, p2] = this.points;
|
|
6982
7252
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -6986,18 +7256,20 @@ var GannFan = class {
|
|
|
6986
7256
|
const baseWidth = Math.abs(x2 - x1) || 1;
|
|
6987
7257
|
const baseHeight = y2 - y1;
|
|
6988
7258
|
const dirX = x2 > x1 ? 1 : -1;
|
|
7259
|
+
const s = resolveStyle("gannFan", this.style, theme);
|
|
6989
7260
|
ctx.save();
|
|
6990
7261
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
6991
|
-
ctx.lineWidth = isSelected ?
|
|
7262
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7263
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
6992
7264
|
GANN_RATIOS.forEach(([priceUnits, timeUnits, label], idx) => {
|
|
6993
7265
|
const scaleFactor = priceUnits / timeUnits;
|
|
6994
7266
|
const fanDY = baseHeight * scaleFactor;
|
|
6995
7267
|
const tMax = (chartW - x1) / (baseWidth * dirX);
|
|
6996
7268
|
const xEnd = x1 + baseWidth * dirX * tMax;
|
|
6997
7269
|
const yEnd = y1 + fanDY * tMax;
|
|
6998
|
-
const
|
|
6999
|
-
const
|
|
7000
|
-
const color =
|
|
7270
|
+
const alphas = [0.33, 0.4, 0.47, 0.6, 0.8, 0.6, 0.47, 0.4, 0.33];
|
|
7271
|
+
const a = alphas[idx] ?? 0.53;
|
|
7272
|
+
const color = hexWithAlpha(s.strokeColor, a);
|
|
7001
7273
|
ctx.strokeStyle = color;
|
|
7002
7274
|
ctx.beginPath();
|
|
7003
7275
|
ctx.moveTo(x1, y1);
|
|
@@ -7007,7 +7279,8 @@ var GannFan = class {
|
|
|
7007
7279
|
const labelX = Math.min(xEnd, chartW - 30);
|
|
7008
7280
|
ctx.fillText(label, labelX, yEnd - 3);
|
|
7009
7281
|
});
|
|
7010
|
-
ctx.
|
|
7282
|
+
ctx.setLineDash([]);
|
|
7283
|
+
ctx.fillStyle = s.strokeColor;
|
|
7011
7284
|
ctx.beginPath();
|
|
7012
7285
|
ctx.arc(x1, y1, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
7013
7286
|
ctx.fill();
|
|
@@ -7061,7 +7334,7 @@ var GannSquare = class {
|
|
|
7061
7334
|
this.points = [first, { ...first }];
|
|
7062
7335
|
this.complete = false;
|
|
7063
7336
|
}
|
|
7064
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7337
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7065
7338
|
if (this.points.length < 2) return;
|
|
7066
7339
|
const [p1, p2] = this.points;
|
|
7067
7340
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7074,10 +7347,12 @@ var GannSquare = class {
|
|
|
7074
7347
|
const bottom = Math.max(y1, y2);
|
|
7075
7348
|
const w = right - left;
|
|
7076
7349
|
const h = bottom - top;
|
|
7350
|
+
const s = resolveStyle("gannSquare", this.style, theme);
|
|
7077
7351
|
ctx.save();
|
|
7078
|
-
ctx.lineWidth = isSelected ?
|
|
7079
|
-
ctx.strokeStyle =
|
|
7080
|
-
ctx
|
|
7352
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7353
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7354
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7355
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7081
7356
|
ctx.fillRect(left, top, w, h);
|
|
7082
7357
|
ctx.strokeRect(left, top, w, h);
|
|
7083
7358
|
ctx.beginPath();
|
|
@@ -7092,6 +7367,7 @@ var GannSquare = class {
|
|
|
7092
7367
|
ctx.moveTo(left + 2 * w / 3, top);
|
|
7093
7368
|
ctx.lineTo(left + 2 * w / 3, bottom);
|
|
7094
7369
|
ctx.stroke();
|
|
7370
|
+
ctx.setLineDash([]);
|
|
7095
7371
|
ctx.restore();
|
|
7096
7372
|
}
|
|
7097
7373
|
hitTest(x, y, scale, viewport, priceRange, chartW, chartH) {
|
|
@@ -7131,7 +7407,7 @@ var GannBox = class {
|
|
|
7131
7407
|
this.points = [first, { ...first }];
|
|
7132
7408
|
this.complete = false;
|
|
7133
7409
|
}
|
|
7134
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7410
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7135
7411
|
if (this.points.length < 2) return;
|
|
7136
7412
|
const [p1, p2] = this.points;
|
|
7137
7413
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7146,10 +7422,12 @@ var GannBox = class {
|
|
|
7146
7422
|
const h = bottom - top;
|
|
7147
7423
|
const midX = left + w / 2;
|
|
7148
7424
|
const midY = top + h / 2;
|
|
7425
|
+
const s = resolveStyle("gannBox", this.style, theme);
|
|
7149
7426
|
ctx.save();
|
|
7150
|
-
ctx.lineWidth = isSelected ?
|
|
7151
|
-
ctx.strokeStyle =
|
|
7152
|
-
ctx
|
|
7427
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7428
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7429
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7430
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7153
7431
|
ctx.fillRect(left, top, w, h);
|
|
7154
7432
|
ctx.strokeRect(left, top, w, h);
|
|
7155
7433
|
ctx.beginPath();
|
|
@@ -7168,6 +7446,7 @@ var GannBox = class {
|
|
|
7168
7446
|
ctx.moveTo(midX, top);
|
|
7169
7447
|
ctx.lineTo(midX, bottom);
|
|
7170
7448
|
ctx.stroke();
|
|
7449
|
+
ctx.setLineDash([]);
|
|
7171
7450
|
ctx.restore();
|
|
7172
7451
|
}
|
|
7173
7452
|
hitTest(x, y, scale, viewport, priceRange, chartW, chartH) {
|
|
@@ -7207,7 +7486,7 @@ var Rectangle = class {
|
|
|
7207
7486
|
this.points = [first, { ...first }];
|
|
7208
7487
|
this.complete = false;
|
|
7209
7488
|
}
|
|
7210
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7489
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7211
7490
|
if (this.points.length < 2) return;
|
|
7212
7491
|
const [p1, p2] = this.points;
|
|
7213
7492
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7218,17 +7497,20 @@ var Rectangle = class {
|
|
|
7218
7497
|
const ry = Math.min(y1, y2);
|
|
7219
7498
|
const rw = Math.abs(x2 - x1);
|
|
7220
7499
|
const rh = Math.abs(y2 - y1);
|
|
7500
|
+
const s = resolveStyle("rectangle", this.style, theme);
|
|
7221
7501
|
ctx.save();
|
|
7222
|
-
ctx.fillStyle =
|
|
7223
|
-
ctx.strokeStyle =
|
|
7224
|
-
ctx.lineWidth = isSelected ?
|
|
7502
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7503
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7504
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7505
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7225
7506
|
ctx.fillRect(rx, ry, rw, rh);
|
|
7226
7507
|
ctx.strokeRect(rx, ry, rw, rh);
|
|
7508
|
+
ctx.setLineDash([]);
|
|
7227
7509
|
if (isSelected) {
|
|
7228
7510
|
for (const [cx, cy] of [[x1, y1], [x2, y1], [x1, y2], [x2, y2]]) {
|
|
7229
7511
|
ctx.beginPath();
|
|
7230
7512
|
ctx.arc(cx, cy, 3.5, 0, Math.PI * 2);
|
|
7231
|
-
ctx.fillStyle =
|
|
7513
|
+
ctx.fillStyle = s.strokeColor;
|
|
7232
7514
|
ctx.fill();
|
|
7233
7515
|
}
|
|
7234
7516
|
}
|
|
@@ -7310,9 +7592,10 @@ var RotatedRectangle = class {
|
|
|
7310
7592
|
const dx = ax + perpX * width, dy = ay + perpY * width;
|
|
7311
7593
|
return [[ax, ay], [bx, by], [cx, cy], [dx, dy]];
|
|
7312
7594
|
}
|
|
7313
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7595
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7314
7596
|
if (this.points.length < 3) return;
|
|
7315
7597
|
const corners = this.getCorners(scale, viewport, priceRange, chartW, chartH);
|
|
7598
|
+
const s = resolveStyle("rotatedRectangle", this.style, theme);
|
|
7316
7599
|
ctx.save();
|
|
7317
7600
|
ctx.beginPath();
|
|
7318
7601
|
ctx.moveTo(corners[0][0], corners[0][1]);
|
|
@@ -7320,12 +7603,14 @@ var RotatedRectangle = class {
|
|
|
7320
7603
|
ctx.lineTo(corners[i][0], corners[i][1]);
|
|
7321
7604
|
}
|
|
7322
7605
|
ctx.closePath();
|
|
7323
|
-
ctx.fillStyle =
|
|
7606
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7324
7607
|
ctx.fill();
|
|
7325
|
-
ctx.strokeStyle =
|
|
7326
|
-
ctx.lineWidth = isSelected ?
|
|
7608
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7609
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7610
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7327
7611
|
ctx.stroke();
|
|
7328
|
-
ctx.
|
|
7612
|
+
ctx.setLineDash([]);
|
|
7613
|
+
ctx.fillStyle = s.strokeColor;
|
|
7329
7614
|
for (const [x, y] of corners) {
|
|
7330
7615
|
ctx.beginPath();
|
|
7331
7616
|
ctx.arc(x, y, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -7359,7 +7644,7 @@ var Ellipse = class {
|
|
|
7359
7644
|
this.points = [first, { ...first }];
|
|
7360
7645
|
this.complete = false;
|
|
7361
7646
|
}
|
|
7362
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7647
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7363
7648
|
if (this.points.length < 2) return;
|
|
7364
7649
|
const [p1, p2] = this.points;
|
|
7365
7650
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7371,19 +7656,22 @@ var Ellipse = class {
|
|
|
7371
7656
|
const rx = Math.abs(x2 - x1) / 2;
|
|
7372
7657
|
const ry = Math.abs(y2 - y1) / 2;
|
|
7373
7658
|
if (rx < 1 || ry < 1) return;
|
|
7659
|
+
const s = resolveStyle("ellipse", this.style, theme);
|
|
7374
7660
|
ctx.save();
|
|
7375
|
-
ctx.fillStyle =
|
|
7376
|
-
ctx.strokeStyle =
|
|
7377
|
-
ctx.lineWidth = isSelected ?
|
|
7661
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7662
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7663
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7664
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7378
7665
|
ctx.beginPath();
|
|
7379
7666
|
ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
|
|
7380
7667
|
ctx.fill();
|
|
7381
7668
|
ctx.stroke();
|
|
7669
|
+
ctx.setLineDash([]);
|
|
7382
7670
|
if (isSelected) {
|
|
7383
7671
|
for (const [hx, hy] of [[x1, y1], [x2, y2]]) {
|
|
7384
7672
|
ctx.beginPath();
|
|
7385
7673
|
ctx.arc(hx, hy, 3.5, 0, Math.PI * 2);
|
|
7386
|
-
ctx.fillStyle =
|
|
7674
|
+
ctx.fillStyle = s.strokeColor;
|
|
7387
7675
|
ctx.fill();
|
|
7388
7676
|
}
|
|
7389
7677
|
}
|
|
@@ -7443,13 +7731,15 @@ var Triangle = class {
|
|
|
7443
7731
|
this.points = [first, { ...first }];
|
|
7444
7732
|
this.complete = false;
|
|
7445
7733
|
}
|
|
7446
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7734
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7447
7735
|
if (this.points.length < 2) return;
|
|
7448
7736
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
7449
7737
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
7738
|
+
const s = resolveStyle("triangle", this.style, theme);
|
|
7450
7739
|
ctx.save();
|
|
7451
|
-
ctx.strokeStyle =
|
|
7452
|
-
ctx.lineWidth = isSelected ?
|
|
7740
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7741
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7742
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7453
7743
|
ctx.beginPath();
|
|
7454
7744
|
ctx.moveTo(px[0], py[0]);
|
|
7455
7745
|
for (let i = 1; i < px.length; i++) {
|
|
@@ -7457,15 +7747,16 @@ var Triangle = class {
|
|
|
7457
7747
|
}
|
|
7458
7748
|
if (this.complete) {
|
|
7459
7749
|
ctx.closePath();
|
|
7460
|
-
ctx.fillStyle =
|
|
7750
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7461
7751
|
ctx.fill();
|
|
7462
7752
|
}
|
|
7463
7753
|
ctx.stroke();
|
|
7754
|
+
ctx.setLineDash([]);
|
|
7464
7755
|
if (isSelected) {
|
|
7465
7756
|
for (let i = 0; i < px.length; i++) {
|
|
7466
7757
|
ctx.beginPath();
|
|
7467
7758
|
ctx.arc(px[i], py[i], 4, 0, Math.PI * 2);
|
|
7468
|
-
ctx.fillStyle =
|
|
7759
|
+
ctx.fillStyle = s.strokeColor;
|
|
7469
7760
|
ctx.fill();
|
|
7470
7761
|
}
|
|
7471
7762
|
}
|
|
@@ -7504,7 +7795,7 @@ var Circle = class {
|
|
|
7504
7795
|
this.points = [first, { ...first }];
|
|
7505
7796
|
this.complete = false;
|
|
7506
7797
|
}
|
|
7507
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7798
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7508
7799
|
if (this.points.length < 2) return;
|
|
7509
7800
|
const [p1, p2] = this.points;
|
|
7510
7801
|
const cx = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7513,22 +7804,25 @@ var Circle = class {
|
|
|
7513
7804
|
const ey = scale.yToPixel(p2.price, priceRange, chartH);
|
|
7514
7805
|
const r = Math.hypot(ex - cx, ey - cy);
|
|
7515
7806
|
if (r < 1) return;
|
|
7807
|
+
const s = resolveStyle("circle", this.style, theme);
|
|
7516
7808
|
ctx.save();
|
|
7517
|
-
ctx.fillStyle =
|
|
7518
|
-
ctx.strokeStyle =
|
|
7519
|
-
ctx.lineWidth = isSelected ?
|
|
7809
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7810
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7811
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7812
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7520
7813
|
ctx.beginPath();
|
|
7521
7814
|
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
7522
7815
|
ctx.fill();
|
|
7523
7816
|
ctx.stroke();
|
|
7817
|
+
ctx.setLineDash([]);
|
|
7524
7818
|
if (isSelected) {
|
|
7525
7819
|
ctx.beginPath();
|
|
7526
7820
|
ctx.arc(cx, cy, 3, 0, Math.PI * 2);
|
|
7527
|
-
ctx.fillStyle =
|
|
7821
|
+
ctx.fillStyle = s.strokeColor;
|
|
7528
7822
|
ctx.fill();
|
|
7529
7823
|
ctx.beginPath();
|
|
7530
7824
|
ctx.arc(ex, ey, 3.5, 0, Math.PI * 2);
|
|
7531
|
-
ctx.fillStyle =
|
|
7825
|
+
ctx.fillStyle = s.strokeColor;
|
|
7532
7826
|
ctx.fill();
|
|
7533
7827
|
}
|
|
7534
7828
|
ctx.restore();
|
|
@@ -7577,7 +7871,7 @@ var Arc = class {
|
|
|
7577
7871
|
this.points = [first, { ...first }];
|
|
7578
7872
|
this.complete = false;
|
|
7579
7873
|
}
|
|
7580
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
7874
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7581
7875
|
if (this.points.length < 3) return;
|
|
7582
7876
|
const [p1, p2, p3] = this.points;
|
|
7583
7877
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7586,9 +7880,11 @@ var Arc = class {
|
|
|
7586
7880
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
7587
7881
|
const x3 = scale.xToPixel(p3.barIndex, viewport, chartW);
|
|
7588
7882
|
const y3 = scale.yToPixel(p3.price, priceRange, chartH);
|
|
7883
|
+
const s = resolveStyle("arc", this.style, theme);
|
|
7589
7884
|
ctx.save();
|
|
7590
|
-
ctx.strokeStyle =
|
|
7591
|
-
ctx.lineWidth = isSelected ?
|
|
7885
|
+
ctx.strokeStyle = s.strokeColor;
|
|
7886
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7887
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7592
7888
|
const ax = x1, ay = y1;
|
|
7593
7889
|
const bx = x2, by = y2;
|
|
7594
7890
|
const cx = x3, cy = y3;
|
|
@@ -7621,8 +7917,9 @@ var Arc = class {
|
|
|
7621
7917
|
ctx.arc(ux, uy, r, aStart, aEnd, anticlockwise);
|
|
7622
7918
|
ctx.stroke();
|
|
7623
7919
|
}
|
|
7920
|
+
ctx.setLineDash([]);
|
|
7624
7921
|
const anchors = [[x1, y1], [x2, y2], [x3, y3]];
|
|
7625
|
-
ctx.fillStyle =
|
|
7922
|
+
ctx.fillStyle = s.strokeColor;
|
|
7626
7923
|
for (const [x, y] of anchors) {
|
|
7627
7924
|
ctx.beginPath();
|
|
7628
7925
|
ctx.arc(x, y, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -7703,24 +8000,26 @@ var Polyline = class {
|
|
|
7703
8000
|
this.points = [first, { ...first }];
|
|
7704
8001
|
this.complete = false;
|
|
7705
8002
|
}
|
|
7706
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
8003
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7707
8004
|
if (this.points.length < 2) return;
|
|
7708
8005
|
const pixelPts = this.points.map((p) => ({
|
|
7709
8006
|
x: scale.xToPixel(p.barIndex, viewport, chartW),
|
|
7710
8007
|
y: scale.yToPixel(p.price, priceRange, chartH)
|
|
7711
8008
|
}));
|
|
8009
|
+
const s = resolveStyle("polyline", this.style, theme);
|
|
7712
8010
|
ctx.save();
|
|
7713
|
-
ctx.strokeStyle =
|
|
7714
|
-
ctx.lineWidth = isSelected ?
|
|
8011
|
+
ctx.strokeStyle = s.strokeColor;
|
|
8012
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7715
8013
|
const solidCount = this.complete ? pixelPts.length : pixelPts.length - 1;
|
|
7716
8014
|
if (solidCount >= 2) {
|
|
7717
|
-
ctx.
|
|
8015
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7718
8016
|
ctx.beginPath();
|
|
7719
8017
|
ctx.moveTo(pixelPts[0].x, pixelPts[0].y);
|
|
7720
8018
|
for (let i = 1; i < solidCount; i++) {
|
|
7721
8019
|
ctx.lineTo(pixelPts[i].x, pixelPts[i].y);
|
|
7722
8020
|
}
|
|
7723
8021
|
ctx.stroke();
|
|
8022
|
+
ctx.setLineDash([]);
|
|
7724
8023
|
}
|
|
7725
8024
|
if (!this.complete && pixelPts.length >= 2) {
|
|
7726
8025
|
const last = pixelPts.length - 1;
|
|
@@ -7731,7 +8030,7 @@ var Polyline = class {
|
|
|
7731
8030
|
ctx.stroke();
|
|
7732
8031
|
ctx.setLineDash([]);
|
|
7733
8032
|
}
|
|
7734
|
-
ctx.fillStyle =
|
|
8033
|
+
ctx.fillStyle = s.strokeColor;
|
|
7735
8034
|
const dotCount = this.complete ? pixelPts.length : pixelPts.length - 1;
|
|
7736
8035
|
for (let i = 0; i < dotCount; i++) {
|
|
7737
8036
|
ctx.beginPath();
|
|
@@ -7783,18 +8082,19 @@ var Path = class {
|
|
|
7783
8082
|
this.points = [first, { ...first }];
|
|
7784
8083
|
this.complete = false;
|
|
7785
8084
|
}
|
|
7786
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
8085
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7787
8086
|
if (this.points.length < 2) return;
|
|
7788
8087
|
const pixelPts = this.points.map((p) => ({
|
|
7789
8088
|
x: scale.xToPixel(p.barIndex, viewport, chartW),
|
|
7790
8089
|
y: scale.yToPixel(p.price, priceRange, chartH)
|
|
7791
8090
|
}));
|
|
7792
8091
|
const solidCount = this.complete ? pixelPts.length : pixelPts.length - 1;
|
|
8092
|
+
const s = resolveStyle("path", this.style, theme);
|
|
7793
8093
|
ctx.save();
|
|
7794
|
-
ctx.strokeStyle =
|
|
7795
|
-
ctx.lineWidth = isSelected ?
|
|
8094
|
+
ctx.strokeStyle = s.strokeColor;
|
|
8095
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
7796
8096
|
if (solidCount >= 2) {
|
|
7797
|
-
ctx.
|
|
8097
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7798
8098
|
ctx.beginPath();
|
|
7799
8099
|
ctx.moveTo(pixelPts[0].x, pixelPts[0].y);
|
|
7800
8100
|
if (solidCount === 2) {
|
|
@@ -7808,6 +8108,7 @@ var Path = class {
|
|
|
7808
8108
|
ctx.lineTo(pixelPts[solidCount - 1].x, pixelPts[solidCount - 1].y);
|
|
7809
8109
|
}
|
|
7810
8110
|
ctx.stroke();
|
|
8111
|
+
ctx.setLineDash([]);
|
|
7811
8112
|
}
|
|
7812
8113
|
if (!this.complete && pixelPts.length >= 2) {
|
|
7813
8114
|
const last = pixelPts.length - 1;
|
|
@@ -7818,7 +8119,7 @@ var Path = class {
|
|
|
7818
8119
|
ctx.stroke();
|
|
7819
8120
|
ctx.setLineDash([]);
|
|
7820
8121
|
}
|
|
7821
|
-
ctx.fillStyle =
|
|
8122
|
+
ctx.fillStyle = s.strokeColor;
|
|
7822
8123
|
for (let i = 0; i < solidCount; i++) {
|
|
7823
8124
|
ctx.beginPath();
|
|
7824
8125
|
ctx.arc(pixelPts[i].x, pixelPts[i].y, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -7870,7 +8171,7 @@ var PriceRange = class {
|
|
|
7870
8171
|
this.points = [first, { ...first }];
|
|
7871
8172
|
this.complete = false;
|
|
7872
8173
|
}
|
|
7873
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
8174
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
7874
8175
|
if (this.points.length < 2) return;
|
|
7875
8176
|
const [p1, p2] = this.points;
|
|
7876
8177
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7882,11 +8183,13 @@ var PriceRange = class {
|
|
|
7882
8183
|
const topY = Math.min(y1, y2);
|
|
7883
8184
|
const botY = Math.max(y1, y2);
|
|
7884
8185
|
const tickHalf = 10;
|
|
7885
|
-
const
|
|
8186
|
+
const s = resolveStyle("priceRange", this.style, theme);
|
|
8187
|
+
const color = s.strokeColor;
|
|
7886
8188
|
ctx.save();
|
|
7887
8189
|
ctx.strokeStyle = color;
|
|
7888
8190
|
ctx.fillStyle = color;
|
|
7889
|
-
ctx.lineWidth = isSelected ?
|
|
8191
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8192
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7890
8193
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
7891
8194
|
ctx.beginPath();
|
|
7892
8195
|
ctx.moveTo(midX, topY);
|
|
@@ -7902,6 +8205,7 @@ var PriceRange = class {
|
|
|
7902
8205
|
ctx.moveTo(x2 - tickHalf, y2);
|
|
7903
8206
|
ctx.lineTo(x2 + tickHalf, y2);
|
|
7904
8207
|
ctx.stroke();
|
|
8208
|
+
ctx.setLineDash([]);
|
|
7905
8209
|
const priceDiff = Math.abs(p1.price - p2.price).toFixed(pricePrecision);
|
|
7906
8210
|
const pctChange = ((p2.price - p1.price) / p1.price * 100).toFixed(2) + "%";
|
|
7907
8211
|
const line1 = priceDiff;
|
|
@@ -7911,7 +8215,7 @@ var PriceRange = class {
|
|
|
7911
8215
|
const boxH = 30;
|
|
7912
8216
|
const boxX = midX + 8;
|
|
7913
8217
|
const boxY = midY - boxH / 2;
|
|
7914
|
-
ctx.fillStyle =
|
|
8218
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
7915
8219
|
ctx.fillRect(boxX, boxY, boxW, boxH);
|
|
7916
8220
|
ctx.strokeStyle = color;
|
|
7917
8221
|
ctx.strokeRect(boxX, boxY, boxW, boxH);
|
|
@@ -7965,7 +8269,7 @@ var DateRange = class {
|
|
|
7965
8269
|
this.points = [first, { ...first }];
|
|
7966
8270
|
this.complete = false;
|
|
7967
8271
|
}
|
|
7968
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
8272
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
7969
8273
|
if (this.points.length < 2) return;
|
|
7970
8274
|
const [p1, p2] = this.points;
|
|
7971
8275
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -7976,11 +8280,13 @@ var DateRange = class {
|
|
|
7976
8280
|
const leftX = Math.min(x1, x2);
|
|
7977
8281
|
const rightX = Math.max(x1, x2);
|
|
7978
8282
|
const tickHalf = 10;
|
|
7979
|
-
const
|
|
8283
|
+
const s = resolveStyle("dateRange", this.style, theme);
|
|
8284
|
+
const color = s.strokeColor;
|
|
7980
8285
|
ctx.save();
|
|
7981
8286
|
ctx.strokeStyle = color;
|
|
7982
8287
|
ctx.fillStyle = color;
|
|
7983
|
-
ctx.lineWidth = isSelected ?
|
|
8288
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8289
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
7984
8290
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
7985
8291
|
ctx.beginPath();
|
|
7986
8292
|
ctx.moveTo(leftX, midY);
|
|
@@ -7996,6 +8302,7 @@ var DateRange = class {
|
|
|
7996
8302
|
ctx.moveTo(x2, y2 - tickHalf);
|
|
7997
8303
|
ctx.lineTo(x2, y2 + tickHalf);
|
|
7998
8304
|
ctx.stroke();
|
|
8305
|
+
ctx.setLineDash([]);
|
|
7999
8306
|
const barCount = Math.abs(p2.barIndex - p1.barIndex);
|
|
8000
8307
|
const label = `${barCount} bars`;
|
|
8001
8308
|
const midX = (x1 + x2) / 2;
|
|
@@ -8049,7 +8356,7 @@ var DatePriceRange = class {
|
|
|
8049
8356
|
this.points = [first, { ...first }];
|
|
8050
8357
|
this.complete = false;
|
|
8051
8358
|
}
|
|
8052
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
8359
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
8053
8360
|
if (this.points.length < 2) return;
|
|
8054
8361
|
const [p1, p2] = this.points;
|
|
8055
8362
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -8062,14 +8369,15 @@ var DatePriceRange = class {
|
|
|
8062
8369
|
const bottom = Math.max(y1, y2);
|
|
8063
8370
|
const w = right - left;
|
|
8064
8371
|
const h = bottom - top;
|
|
8065
|
-
const
|
|
8372
|
+
const s = resolveStyle("datePriceRange", this.style, theme);
|
|
8373
|
+
const color = s.strokeColor;
|
|
8066
8374
|
ctx.save();
|
|
8067
8375
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
8068
|
-
ctx.lineWidth = isSelected ?
|
|
8069
|
-
ctx.fillStyle =
|
|
8376
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8377
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
8070
8378
|
ctx.fillRect(left, top, w, h);
|
|
8071
8379
|
ctx.strokeStyle = color;
|
|
8072
|
-
ctx.
|
|
8380
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8073
8381
|
ctx.strokeRect(left, top, w, h);
|
|
8074
8382
|
ctx.setLineDash([]);
|
|
8075
8383
|
ctx.strokeStyle = color;
|
|
@@ -8133,27 +8441,28 @@ var Ruler = class {
|
|
|
8133
8441
|
this.points = [first, { ...first }];
|
|
8134
8442
|
this.complete = false;
|
|
8135
8443
|
}
|
|
8136
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
8444
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
8137
8445
|
if (this.points.length < 2) return;
|
|
8138
8446
|
const [p1, p2] = this.points;
|
|
8139
8447
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
8140
8448
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
8141
8449
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
8142
8450
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
8143
|
-
const
|
|
8451
|
+
const s = resolveStyle("ruler", this.style, theme);
|
|
8452
|
+
const color = s.strokeColor;
|
|
8144
8453
|
const angle = Math.atan2(y2 - y1, x2 - x1);
|
|
8145
8454
|
const perpAngle = angle + Math.PI / 2;
|
|
8146
8455
|
const tickLen = 8;
|
|
8147
8456
|
ctx.save();
|
|
8148
8457
|
ctx.strokeStyle = color;
|
|
8149
8458
|
ctx.fillStyle = color;
|
|
8150
|
-
ctx.lineWidth =
|
|
8459
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8460
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8151
8461
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
8152
8462
|
ctx.beginPath();
|
|
8153
8463
|
ctx.moveTo(x1, y1);
|
|
8154
8464
|
ctx.lineTo(x2, y2);
|
|
8155
8465
|
ctx.stroke();
|
|
8156
|
-
ctx.lineWidth = isSelected ? 1.5 : 1;
|
|
8157
8466
|
ctx.beginPath();
|
|
8158
8467
|
ctx.moveTo(x1 - tickLen * Math.cos(perpAngle), y1 - tickLen * Math.sin(perpAngle));
|
|
8159
8468
|
ctx.lineTo(x1 + tickLen * Math.cos(perpAngle), y1 + tickLen * Math.sin(perpAngle));
|
|
@@ -8162,6 +8471,7 @@ var Ruler = class {
|
|
|
8162
8471
|
ctx.moveTo(x2 - tickLen * Math.cos(perpAngle), y2 - tickLen * Math.sin(perpAngle));
|
|
8163
8472
|
ctx.lineTo(x2 + tickLen * Math.cos(perpAngle), y2 + tickLen * Math.sin(perpAngle));
|
|
8164
8473
|
ctx.stroke();
|
|
8474
|
+
ctx.setLineDash([]);
|
|
8165
8475
|
const midX = (x1 + x2) / 2;
|
|
8166
8476
|
const midY = (y1 + y2) / 2;
|
|
8167
8477
|
const barDiff = Math.abs(p2.barIndex - p1.barIndex);
|
|
@@ -8227,16 +8537,18 @@ var Pitchfork = class {
|
|
|
8227
8537
|
this.points = [first, { ...first }];
|
|
8228
8538
|
this.complete = false;
|
|
8229
8539
|
}
|
|
8230
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
8540
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8231
8541
|
if (this.points.length < 2) return;
|
|
8232
8542
|
const [p1, p2, p3] = this.points;
|
|
8233
8543
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
8234
8544
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
8235
8545
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
8236
8546
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
8547
|
+
const s = resolveStyle("pitchfork", this.style, theme);
|
|
8237
8548
|
ctx.save();
|
|
8238
|
-
ctx.strokeStyle =
|
|
8239
|
-
ctx.lineWidth = isSelected ?
|
|
8549
|
+
ctx.strokeStyle = s.strokeColor;
|
|
8550
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8551
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8240
8552
|
if (p3) {
|
|
8241
8553
|
const x3 = scale.xToPixel(p3.barIndex, viewport, chartW);
|
|
8242
8554
|
const y3 = scale.yToPixel(p3.price, priceRange, chartH);
|
|
@@ -8263,8 +8575,9 @@ var Pitchfork = class {
|
|
|
8263
8575
|
ctx.moveTo(x3, y3);
|
|
8264
8576
|
ctx.lineTo(tine2EndX, tine2EndY);
|
|
8265
8577
|
ctx.stroke();
|
|
8578
|
+
ctx.setLineDash([]);
|
|
8266
8579
|
if (isSelected) {
|
|
8267
|
-
ctx.fillStyle =
|
|
8580
|
+
ctx.fillStyle = s.strokeColor;
|
|
8268
8581
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3]]) {
|
|
8269
8582
|
ctx.beginPath();
|
|
8270
8583
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -8276,6 +8589,7 @@ var Pitchfork = class {
|
|
|
8276
8589
|
ctx.moveTo(x1, y1);
|
|
8277
8590
|
ctx.lineTo(x2, y2);
|
|
8278
8591
|
ctx.stroke();
|
|
8592
|
+
ctx.setLineDash([]);
|
|
8279
8593
|
}
|
|
8280
8594
|
ctx.restore();
|
|
8281
8595
|
}
|
|
@@ -8337,16 +8651,18 @@ var SchiffPitchfork = class {
|
|
|
8337
8651
|
this.points = [first, { ...first }];
|
|
8338
8652
|
this.complete = false;
|
|
8339
8653
|
}
|
|
8340
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
8654
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8341
8655
|
if (this.points.length < 2) return;
|
|
8342
8656
|
const [p1, p2, p3] = this.points;
|
|
8343
8657
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
8344
8658
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
8345
8659
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
8346
8660
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
8661
|
+
const s = resolveStyle("schiffPitchfork", this.style, theme);
|
|
8347
8662
|
ctx.save();
|
|
8348
|
-
ctx.strokeStyle =
|
|
8349
|
-
ctx.lineWidth = isSelected ?
|
|
8663
|
+
ctx.strokeStyle = s.strokeColor;
|
|
8664
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8665
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8350
8666
|
if (p3) {
|
|
8351
8667
|
const x3 = scale.xToPixel(p3.barIndex, viewport, chartW);
|
|
8352
8668
|
const y3 = scale.yToPixel(p3.price, priceRange, chartH);
|
|
@@ -8375,8 +8691,9 @@ var SchiffPitchfork = class {
|
|
|
8375
8691
|
ctx.moveTo(x3, y3);
|
|
8376
8692
|
ctx.lineTo(tine2EndX, tine2EndY);
|
|
8377
8693
|
ctx.stroke();
|
|
8694
|
+
ctx.setLineDash([]);
|
|
8378
8695
|
if (isSelected) {
|
|
8379
|
-
ctx.fillStyle =
|
|
8696
|
+
ctx.fillStyle = s.strokeColor;
|
|
8380
8697
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3]]) {
|
|
8381
8698
|
ctx.beginPath();
|
|
8382
8699
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -8388,6 +8705,7 @@ var SchiffPitchfork = class {
|
|
|
8388
8705
|
ctx.moveTo(x1, y1);
|
|
8389
8706
|
ctx.lineTo(x2, y2);
|
|
8390
8707
|
ctx.stroke();
|
|
8708
|
+
ctx.setLineDash([]);
|
|
8391
8709
|
}
|
|
8392
8710
|
ctx.restore();
|
|
8393
8711
|
}
|
|
@@ -8451,16 +8769,18 @@ var ModifiedSchiff = class {
|
|
|
8451
8769
|
this.points = [first, { ...first }];
|
|
8452
8770
|
this.complete = false;
|
|
8453
8771
|
}
|
|
8454
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
8772
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8455
8773
|
if (this.points.length < 2) return;
|
|
8456
8774
|
const [p1, p2, p3] = this.points;
|
|
8457
8775
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
8458
8776
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
8459
8777
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
8460
8778
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
8779
|
+
const s = resolveStyle("modifiedSchiff", this.style, theme);
|
|
8461
8780
|
ctx.save();
|
|
8462
|
-
ctx.strokeStyle =
|
|
8463
|
-
ctx.lineWidth = isSelected ?
|
|
8781
|
+
ctx.strokeStyle = s.strokeColor;
|
|
8782
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8783
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8464
8784
|
if (p3) {
|
|
8465
8785
|
const x3 = scale.xToPixel(p3.barIndex, viewport, chartW);
|
|
8466
8786
|
const y3 = scale.yToPixel(p3.price, priceRange, chartH);
|
|
@@ -8489,8 +8809,9 @@ var ModifiedSchiff = class {
|
|
|
8489
8809
|
ctx.moveTo(x3, y3);
|
|
8490
8810
|
ctx.lineTo(tine2EndX, tine2EndY);
|
|
8491
8811
|
ctx.stroke();
|
|
8812
|
+
ctx.setLineDash([]);
|
|
8492
8813
|
if (isSelected) {
|
|
8493
|
-
ctx.fillStyle =
|
|
8814
|
+
ctx.fillStyle = s.strokeColor;
|
|
8494
8815
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3]]) {
|
|
8495
8816
|
ctx.beginPath();
|
|
8496
8817
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -8502,6 +8823,7 @@ var ModifiedSchiff = class {
|
|
|
8502
8823
|
ctx.moveTo(x1, y1);
|
|
8503
8824
|
ctx.lineTo(x2, y2);
|
|
8504
8825
|
ctx.stroke();
|
|
8826
|
+
ctx.setLineDash([]);
|
|
8505
8827
|
}
|
|
8506
8828
|
ctx.restore();
|
|
8507
8829
|
}
|
|
@@ -8565,16 +8887,18 @@ var InsidePitchfork = class {
|
|
|
8565
8887
|
this.points = [first, { ...first }];
|
|
8566
8888
|
this.complete = false;
|
|
8567
8889
|
}
|
|
8568
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
8890
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8569
8891
|
if (this.points.length < 2) return;
|
|
8570
8892
|
const [p1, p2, p3] = this.points;
|
|
8571
8893
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
8572
8894
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
8573
8895
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
8574
8896
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
8897
|
+
const s = resolveStyle("insidePitchfork", this.style, theme);
|
|
8575
8898
|
ctx.save();
|
|
8576
|
-
ctx.strokeStyle =
|
|
8577
|
-
ctx.lineWidth = isSelected ?
|
|
8899
|
+
ctx.strokeStyle = s.strokeColor;
|
|
8900
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
8901
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8578
8902
|
if (p3) {
|
|
8579
8903
|
const x3 = scale.xToPixel(p3.barIndex, viewport, chartW);
|
|
8580
8904
|
const y3 = scale.yToPixel(p3.price, priceRange, chartH);
|
|
@@ -8620,7 +8944,7 @@ var InsidePitchfork = class {
|
|
|
8620
8944
|
ctx.setLineDash([]);
|
|
8621
8945
|
ctx.globalAlpha = 1;
|
|
8622
8946
|
if (isSelected) {
|
|
8623
|
-
ctx.fillStyle =
|
|
8947
|
+
ctx.fillStyle = s.strokeColor;
|
|
8624
8948
|
for (const [ax, ay] of [[x1, y1], [x2, y2], [x3, y3]]) {
|
|
8625
8949
|
ctx.beginPath();
|
|
8626
8950
|
ctx.arc(ax, ay, 4, 0, Math.PI * 2);
|
|
@@ -8632,6 +8956,7 @@ var InsidePitchfork = class {
|
|
|
8632
8956
|
ctx.moveTo(x1, y1);
|
|
8633
8957
|
ctx.lineTo(x2, y2);
|
|
8634
8958
|
ctx.stroke();
|
|
8959
|
+
ctx.setLineDash([]);
|
|
8635
8960
|
}
|
|
8636
8961
|
ctx.restore();
|
|
8637
8962
|
}
|
|
@@ -8681,7 +9006,6 @@ var InsidePitchfork = class {
|
|
|
8681
9006
|
|
|
8682
9007
|
// src/drawings/FixedRangeVP.ts
|
|
8683
9008
|
var HIT_TOLERANCE36 = 6;
|
|
8684
|
-
var COLOR = "#64b5f6";
|
|
8685
9009
|
var BAR_WEIGHTS = [0.35, 0.55, 0.75, 1, 1, 0.75, 0.55, 0.35];
|
|
8686
9010
|
var FixedRangeVP = class {
|
|
8687
9011
|
constructor(first) {
|
|
@@ -8690,7 +9014,7 @@ var FixedRangeVP = class {
|
|
|
8690
9014
|
this.points = [first, { ...first }];
|
|
8691
9015
|
this.complete = false;
|
|
8692
9016
|
}
|
|
8693
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9017
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8694
9018
|
if (this.points.length < 2) return;
|
|
8695
9019
|
const [p1, p2] = this.points;
|
|
8696
9020
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -8701,27 +9025,31 @@ var FixedRangeVP = class {
|
|
|
8701
9025
|
const ry = Math.min(y1, y2);
|
|
8702
9026
|
const rw = Math.abs(x2 - x1);
|
|
8703
9027
|
const rh = Math.abs(y2 - y1);
|
|
9028
|
+
const s = resolveStyle("fixedRangeVP", this.style, theme);
|
|
8704
9029
|
ctx.save();
|
|
8705
|
-
ctx.strokeStyle =
|
|
8706
|
-
ctx.lineWidth = isSelected ?
|
|
9030
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9031
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9032
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8707
9033
|
ctx.strokeRect(rx, ry, rw, rh);
|
|
9034
|
+
ctx.setLineDash([]);
|
|
8708
9035
|
const numBars = BAR_WEIGHTS.length;
|
|
8709
9036
|
const barH = rh / numBars;
|
|
8710
9037
|
const maxBarW = rw * 0.6;
|
|
9038
|
+
const barFill = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
8711
9039
|
for (let i = 0; i < numBars; i++) {
|
|
8712
9040
|
const barW = maxBarW * BAR_WEIGHTS[i];
|
|
8713
9041
|
const barY = ry + i * barH;
|
|
8714
|
-
ctx.fillStyle =
|
|
9042
|
+
ctx.fillStyle = barFill;
|
|
8715
9043
|
ctx.fillRect(rx, barY, barW, barH - 1);
|
|
8716
9044
|
}
|
|
8717
9045
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
8718
|
-
ctx.fillStyle =
|
|
9046
|
+
ctx.fillStyle = s.strokeColor;
|
|
8719
9047
|
ctx.fillText("Volume Profile", rx + 4, ry + 13);
|
|
8720
9048
|
if (isSelected) {
|
|
8721
9049
|
for (const [cx, cy] of [[x1, y1], [x2, y2]]) {
|
|
8722
9050
|
ctx.beginPath();
|
|
8723
9051
|
ctx.arc(cx, cy, 3.5, 0, Math.PI * 2);
|
|
8724
|
-
ctx.fillStyle =
|
|
9052
|
+
ctx.fillStyle = s.strokeColor;
|
|
8725
9053
|
ctx.fill();
|
|
8726
9054
|
}
|
|
8727
9055
|
}
|
|
@@ -8757,7 +9085,6 @@ var FixedRangeVP = class {
|
|
|
8757
9085
|
|
|
8758
9086
|
// src/drawings/AnchoredVP.ts
|
|
8759
9087
|
var HIT_TOLERANCE37 = 6;
|
|
8760
|
-
var COLOR2 = "#4db6ac";
|
|
8761
9088
|
var BAR_WEIGHTS2 = [0.35, 0.55, 0.75, 1, 1, 0.75, 0.55, 0.35];
|
|
8762
9089
|
var AnchoredVP = class {
|
|
8763
9090
|
constructor(first) {
|
|
@@ -8766,7 +9093,7 @@ var AnchoredVP = class {
|
|
|
8766
9093
|
this.points = [first, { ...first }];
|
|
8767
9094
|
this.complete = false;
|
|
8768
9095
|
}
|
|
8769
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9096
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8770
9097
|
if (this.points.length < 2) return;
|
|
8771
9098
|
const [p1, p2] = this.points;
|
|
8772
9099
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -8777,31 +9104,35 @@ var AnchoredVP = class {
|
|
|
8777
9104
|
const ry = Math.min(y1, y2);
|
|
8778
9105
|
const rw = Math.abs(x2 - x1);
|
|
8779
9106
|
const rh = Math.abs(y2 - y1);
|
|
9107
|
+
const s = resolveStyle("anchoredVP", this.style, theme);
|
|
8780
9108
|
ctx.save();
|
|
8781
|
-
ctx.strokeStyle =
|
|
8782
|
-
ctx.lineWidth = isSelected ?
|
|
9109
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9110
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9111
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8783
9112
|
ctx.strokeRect(rx, ry, rw, rh);
|
|
9113
|
+
ctx.setLineDash([]);
|
|
8784
9114
|
const numBars = BAR_WEIGHTS2.length;
|
|
8785
9115
|
const barH = rh / numBars;
|
|
8786
9116
|
const maxBarW = rw * 0.6;
|
|
9117
|
+
const barFill = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
8787
9118
|
for (let i = 0; i < numBars; i++) {
|
|
8788
9119
|
const barW = maxBarW * BAR_WEIGHTS2[i];
|
|
8789
9120
|
const barY = ry + i * barH;
|
|
8790
|
-
ctx.fillStyle =
|
|
9121
|
+
ctx.fillStyle = barFill;
|
|
8791
9122
|
ctx.fillRect(rx, barY, barW, barH - 1);
|
|
8792
9123
|
}
|
|
8793
9124
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
8794
|
-
ctx.fillStyle =
|
|
9125
|
+
ctx.fillStyle = s.strokeColor;
|
|
8795
9126
|
ctx.fillText("Anchored VP", rx + 4, ry + 13);
|
|
8796
9127
|
ctx.beginPath();
|
|
8797
9128
|
ctx.arc(x1, y1, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
8798
|
-
ctx.fillStyle =
|
|
9129
|
+
ctx.fillStyle = s.strokeColor;
|
|
8799
9130
|
ctx.fill();
|
|
8800
9131
|
if (isSelected) {
|
|
8801
9132
|
for (const [cx, cy] of [[x1, y1], [x2, y2]]) {
|
|
8802
9133
|
ctx.beginPath();
|
|
8803
9134
|
ctx.arc(cx, cy, 3.5, 0, Math.PI * 2);
|
|
8804
|
-
ctx.fillStyle =
|
|
9135
|
+
ctx.fillStyle = s.strokeColor;
|
|
8805
9136
|
ctx.fill();
|
|
8806
9137
|
}
|
|
8807
9138
|
}
|
|
@@ -8837,7 +9168,6 @@ var AnchoredVP = class {
|
|
|
8837
9168
|
|
|
8838
9169
|
// src/drawings/VolumeProfile.ts
|
|
8839
9170
|
var HIT_TOLERANCE38 = 6;
|
|
8840
|
-
var COLOR3 = "#26a69a";
|
|
8841
9171
|
var BAR_WEIGHTS3 = [0.25, 0.45, 0.65, 0.85, 1, 1, 0.85, 0.65, 0.45, 0.25];
|
|
8842
9172
|
var VolumeProfile = class {
|
|
8843
9173
|
constructor(first) {
|
|
@@ -8846,7 +9176,7 @@ var VolumeProfile = class {
|
|
|
8846
9176
|
this.points = [first, { ...first }];
|
|
8847
9177
|
this.complete = false;
|
|
8848
9178
|
}
|
|
8849
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9179
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8850
9180
|
if (this.points.length < 2) return;
|
|
8851
9181
|
const [p1, p2] = this.points;
|
|
8852
9182
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -8857,27 +9187,31 @@ var VolumeProfile = class {
|
|
|
8857
9187
|
const ry = Math.min(y1, y2);
|
|
8858
9188
|
const rw = Math.abs(x2 - x1);
|
|
8859
9189
|
const rh = Math.abs(y2 - y1);
|
|
9190
|
+
const s = resolveStyle("volumeProfile", this.style, theme);
|
|
8860
9191
|
ctx.save();
|
|
8861
|
-
ctx.strokeStyle =
|
|
8862
|
-
ctx.lineWidth = isSelected ?
|
|
9192
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9193
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9194
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8863
9195
|
ctx.strokeRect(rx, ry, rw, rh);
|
|
9196
|
+
ctx.setLineDash([]);
|
|
8864
9197
|
const numBars = BAR_WEIGHTS3.length;
|
|
8865
9198
|
const barH = rh / numBars;
|
|
8866
9199
|
const maxBarW = rw * 0.6;
|
|
9200
|
+
const barFill = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
8867
9201
|
for (let i = 0; i < numBars; i++) {
|
|
8868
9202
|
const barW = maxBarW * BAR_WEIGHTS3[i];
|
|
8869
9203
|
const barY = ry + i * barH;
|
|
8870
|
-
ctx.fillStyle =
|
|
9204
|
+
ctx.fillStyle = barFill;
|
|
8871
9205
|
ctx.fillRect(rx, barY, barW, barH - 1);
|
|
8872
9206
|
}
|
|
8873
9207
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
8874
|
-
ctx.fillStyle =
|
|
9208
|
+
ctx.fillStyle = s.strokeColor;
|
|
8875
9209
|
ctx.fillText("Volume Profile", rx + 4, ry + 13);
|
|
8876
9210
|
if (isSelected) {
|
|
8877
9211
|
for (const [cx, cy] of [[x1, y1], [x2, y2]]) {
|
|
8878
9212
|
ctx.beginPath();
|
|
8879
9213
|
ctx.arc(cx, cy, 3.5, 0, Math.PI * 2);
|
|
8880
|
-
ctx.fillStyle =
|
|
9214
|
+
ctx.fillStyle = s.strokeColor;
|
|
8881
9215
|
ctx.fill();
|
|
8882
9216
|
}
|
|
8883
9217
|
}
|
|
@@ -8927,27 +9261,29 @@ var RegressionTrend = class {
|
|
|
8927
9261
|
this.points = [first, { ...first }];
|
|
8928
9262
|
this.complete = false;
|
|
8929
9263
|
}
|
|
8930
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9264
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
8931
9265
|
if (this.points.length < 2) return;
|
|
8932
9266
|
const [p1, p2] = this.points;
|
|
8933
9267
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
8934
9268
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
8935
9269
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
8936
9270
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
8937
|
-
const
|
|
9271
|
+
const s = resolveStyle("regressionTrend", this.style, theme);
|
|
9272
|
+
const channelColor = hexWithAlpha(s.strokeColor, 0.4);
|
|
8938
9273
|
const lineLen = Math.hypot(x2 - x1, y2 - y1) || 1;
|
|
8939
9274
|
const perpX = -(y2 - y1) / lineLen;
|
|
8940
9275
|
const perpY = (x2 - x1) / lineLen;
|
|
8941
9276
|
const offset = 20;
|
|
8942
9277
|
ctx.save();
|
|
8943
|
-
ctx.strokeStyle =
|
|
8944
|
-
ctx.fillStyle =
|
|
8945
|
-
ctx.lineWidth = isSelected ?
|
|
8946
|
-
ctx.
|
|
9278
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9279
|
+
ctx.fillStyle = s.strokeColor;
|
|
9280
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9281
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
8947
9282
|
ctx.beginPath();
|
|
8948
9283
|
ctx.moveTo(x1, y1);
|
|
8949
9284
|
ctx.lineTo(x2, y2);
|
|
8950
9285
|
ctx.stroke();
|
|
9286
|
+
ctx.strokeStyle = channelColor;
|
|
8951
9287
|
ctx.setLineDash([4, 3]);
|
|
8952
9288
|
ctx.beginPath();
|
|
8953
9289
|
ctx.moveTo(x1 + perpX * offset, y1 + perpY * offset);
|
|
@@ -9002,11 +9338,14 @@ var Text = class {
|
|
|
9002
9338
|
this.points = [point];
|
|
9003
9339
|
this.complete = true;
|
|
9004
9340
|
}
|
|
9005
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9341
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9006
9342
|
const x = scale.xToPixel(this.points[0].barIndex, viewport, chartW);
|
|
9007
9343
|
const y = scale.yToPixel(this.points[0].price, priceRange, chartH);
|
|
9344
|
+
const s = resolveStyle("text", this.style, theme);
|
|
9345
|
+
const fontSize = s.fontSize ?? 13;
|
|
9346
|
+
const textColor = s.textColor ?? "#f5f5f5";
|
|
9008
9347
|
ctx.save();
|
|
9009
|
-
ctx.font =
|
|
9348
|
+
ctx.font = `${fontSize}px system-ui, sans-serif`;
|
|
9010
9349
|
ctx.textBaseline = "top";
|
|
9011
9350
|
const label = this.text || PLACEHOLDER;
|
|
9012
9351
|
const isPlaceholder = !this.text;
|
|
@@ -9016,10 +9355,10 @@ var Text = class {
|
|
|
9016
9355
|
ctx.lineWidth = 1;
|
|
9017
9356
|
ctx.strokeRect(x - 2, y - 2, textW + PAD * 2, 16 + 4);
|
|
9018
9357
|
}
|
|
9019
|
-
ctx.fillStyle = isPlaceholder ? "#888" :
|
|
9358
|
+
ctx.fillStyle = isPlaceholder ? "#888" : textColor;
|
|
9020
9359
|
ctx.fillText(label, x + PAD, y);
|
|
9021
9360
|
if (isSelected) {
|
|
9022
|
-
ctx.strokeStyle =
|
|
9361
|
+
ctx.strokeStyle = textColor;
|
|
9023
9362
|
ctx.lineWidth = 1;
|
|
9024
9363
|
ctx.beginPath();
|
|
9025
9364
|
ctx.moveTo(x, y + 16);
|
|
@@ -9076,16 +9415,20 @@ var Callout = class {
|
|
|
9076
9415
|
const label = this.text || "Note";
|
|
9077
9416
|
return Math.max(60, ctx.measureText(label).width + PAD_H * 2);
|
|
9078
9417
|
}
|
|
9079
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9418
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9080
9419
|
if (this.points.length < 2) return;
|
|
9081
9420
|
const [p1, p2] = this.points;
|
|
9082
9421
|
const tipX = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
9083
9422
|
const tipY = scale.yToPixel(p1.price, priceRange, chartH);
|
|
9084
9423
|
const boxX = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
9085
9424
|
const boxY = scale.yToPixel(p2.price, priceRange, chartH);
|
|
9425
|
+
const s = resolveStyle("callout", this.style, theme);
|
|
9426
|
+
const fontSize = s.fontSize ?? 11;
|
|
9427
|
+
const textColor = s.textColor ?? "#f5f5f5";
|
|
9086
9428
|
ctx.save();
|
|
9087
|
-
ctx.font =
|
|
9088
|
-
ctx.lineWidth = isSelected ?
|
|
9429
|
+
ctx.font = `${fontSize}px system-ui, sans-serif`;
|
|
9430
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9431
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9089
9432
|
const boxW = this.getBoxW(ctx);
|
|
9090
9433
|
const bx = boxX - boxW / 2;
|
|
9091
9434
|
const by = boxY - BOX_H / 2;
|
|
@@ -9093,23 +9436,24 @@ var Callout = class {
|
|
|
9093
9436
|
const clampedY = Math.max(by, Math.min(by + BOX_H, tipY));
|
|
9094
9437
|
const edgeX = tipX < bx ? bx : tipX > bx + boxW ? bx + boxW : clampedX;
|
|
9095
9438
|
const edgeY = tipY < by ? by : tipY > by + BOX_H ? by + BOX_H : clampedY;
|
|
9096
|
-
ctx.strokeStyle =
|
|
9439
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9097
9440
|
ctx.beginPath();
|
|
9098
9441
|
ctx.moveTo(edgeX, edgeY);
|
|
9099
9442
|
ctx.lineTo(tipX, tipY);
|
|
9100
9443
|
ctx.stroke();
|
|
9101
|
-
ctx.
|
|
9444
|
+
ctx.setLineDash([]);
|
|
9445
|
+
ctx.fillStyle = s.strokeColor;
|
|
9102
9446
|
ctx.beginPath();
|
|
9103
9447
|
ctx.arc(tipX, tipY, 3, 0, Math.PI * 2);
|
|
9104
9448
|
ctx.fill();
|
|
9105
9449
|
roundedRect2(ctx, bx, by, boxW, BOX_H, RADIUS);
|
|
9106
|
-
ctx.fillStyle =
|
|
9450
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9107
9451
|
ctx.fill();
|
|
9108
|
-
ctx.strokeStyle =
|
|
9452
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9109
9453
|
ctx.stroke();
|
|
9110
9454
|
const label = this.text || "Note";
|
|
9111
9455
|
const isPlaceholder = !this.text;
|
|
9112
|
-
ctx.fillStyle = isPlaceholder ? "#888" :
|
|
9456
|
+
ctx.fillStyle = isPlaceholder ? "#888" : textColor;
|
|
9113
9457
|
ctx.textBaseline = "middle";
|
|
9114
9458
|
ctx.textAlign = "center";
|
|
9115
9459
|
ctx.fillText(label, boxX, boxY);
|
|
@@ -9163,12 +9507,15 @@ var PriceLabel = class {
|
|
|
9163
9507
|
this.points = [point];
|
|
9164
9508
|
this.complete = true;
|
|
9165
9509
|
}
|
|
9166
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
9510
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
9167
9511
|
const x = scale.xToPixel(this.points[0].barIndex, viewport, chartW);
|
|
9168
9512
|
const y = scale.yToPixel(this.points[0].price, priceRange, chartH);
|
|
9169
9513
|
const price = this.points[0].price;
|
|
9514
|
+
const s = resolveStyle("priceLabel", this.style, theme);
|
|
9515
|
+
const fontSize = s.fontSize ?? 11;
|
|
9516
|
+
const textColor = s.textColor ?? "#ffffff";
|
|
9170
9517
|
ctx.save();
|
|
9171
|
-
ctx.fillStyle =
|
|
9518
|
+
ctx.fillStyle = s.fillColor;
|
|
9172
9519
|
ctx.beginPath();
|
|
9173
9520
|
ctx.moveTo(x, y);
|
|
9174
9521
|
ctx.lineTo(x + ARROW_W, y - BADGE_H2 / 2);
|
|
@@ -9177,19 +9524,19 @@ var PriceLabel = class {
|
|
|
9177
9524
|
ctx.fill();
|
|
9178
9525
|
const bx = x + ARROW_W;
|
|
9179
9526
|
const by = y - BADGE_H2 / 2;
|
|
9180
|
-
ctx.fillStyle =
|
|
9527
|
+
ctx.fillStyle = s.fillColor;
|
|
9181
9528
|
ctx.beginPath();
|
|
9182
9529
|
ctx.roundRect(bx, by, BADGE_W, BADGE_H2, BADGE_H2 / 2);
|
|
9183
9530
|
ctx.fill();
|
|
9184
9531
|
if (isSelected) {
|
|
9185
|
-
ctx.strokeStyle =
|
|
9186
|
-
ctx.lineWidth =
|
|
9532
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9533
|
+
ctx.lineWidth = s.lineWidth + 0.5;
|
|
9187
9534
|
ctx.beginPath();
|
|
9188
9535
|
ctx.roundRect(bx - 2, by - 2, BADGE_W + 4, BADGE_H2 + 4, BADGE_H2 / 2 + 2);
|
|
9189
9536
|
ctx.stroke();
|
|
9190
9537
|
}
|
|
9191
|
-
ctx.font =
|
|
9192
|
-
ctx.fillStyle =
|
|
9538
|
+
ctx.font = `${fontSize}px 'Inter', system-ui, sans-serif`;
|
|
9539
|
+
ctx.fillStyle = textColor;
|
|
9193
9540
|
ctx.textBaseline = "middle";
|
|
9194
9541
|
ctx.textAlign = "center";
|
|
9195
9542
|
ctx.fillText(price.toFixed(pricePrecision), bx + BADGE_W / 2, y);
|
|
@@ -9223,21 +9570,24 @@ var ArrowMarker = class {
|
|
|
9223
9570
|
this.points = [first, { ...first }];
|
|
9224
9571
|
this.complete = false;
|
|
9225
9572
|
}
|
|
9226
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9573
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9227
9574
|
if (this.points.length < 2) return;
|
|
9228
9575
|
const [p1, p2] = this.points;
|
|
9229
9576
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
9230
9577
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
9231
9578
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
9232
9579
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
9580
|
+
const s = resolveStyle("arrowMarker", this.style, theme);
|
|
9233
9581
|
ctx.save();
|
|
9234
|
-
ctx.strokeStyle =
|
|
9235
|
-
ctx.fillStyle =
|
|
9236
|
-
ctx.lineWidth = isSelected ?
|
|
9582
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9583
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9584
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9585
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9237
9586
|
ctx.beginPath();
|
|
9238
9587
|
ctx.moveTo(x1, y1);
|
|
9239
9588
|
ctx.lineTo(x2, y2);
|
|
9240
9589
|
ctx.stroke();
|
|
9590
|
+
ctx.setLineDash([]);
|
|
9241
9591
|
const angle = Math.atan2(y2 - y1, x2 - x1);
|
|
9242
9592
|
const size = 10;
|
|
9243
9593
|
ctx.beginPath();
|
|
@@ -9247,6 +9597,7 @@ var ArrowMarker = class {
|
|
|
9247
9597
|
ctx.closePath();
|
|
9248
9598
|
ctx.fill();
|
|
9249
9599
|
if (isSelected) {
|
|
9600
|
+
ctx.fillStyle = s.strokeColor;
|
|
9250
9601
|
for (const [cx, cy] of [[x1, y1], [x2, y2]]) {
|
|
9251
9602
|
ctx.beginPath();
|
|
9252
9603
|
ctx.arc(cx, cy, 4, 0, Math.PI * 2);
|
|
@@ -9287,19 +9638,22 @@ var Flag = class {
|
|
|
9287
9638
|
this.points = [point];
|
|
9288
9639
|
this.complete = true;
|
|
9289
9640
|
}
|
|
9290
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH,
|
|
9641
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9291
9642
|
const x = scale.xToPixel(this.points[0].barIndex, viewport, chartW);
|
|
9292
9643
|
const y = scale.yToPixel(this.points[0].price, priceRange, chartH);
|
|
9644
|
+
const s = resolveStyle("flag", this.style, theme);
|
|
9293
9645
|
ctx.save();
|
|
9294
|
-
ctx.strokeStyle =
|
|
9295
|
-
ctx.
|
|
9296
|
-
ctx
|
|
9646
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9647
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9648
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9297
9649
|
ctx.beginPath();
|
|
9298
9650
|
ctx.moveTo(x, y + 20);
|
|
9299
9651
|
ctx.lineTo(x, y - 10);
|
|
9300
9652
|
ctx.stroke();
|
|
9653
|
+
ctx.setLineDash([]);
|
|
9301
9654
|
const flagX = x;
|
|
9302
9655
|
const flagY = y - 10;
|
|
9656
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9303
9657
|
ctx.fillRect(flagX, flagY, 16, 10);
|
|
9304
9658
|
ctx.font = "8px system-ui, sans-serif";
|
|
9305
9659
|
ctx.fillStyle = "#ffffff";
|
|
@@ -9330,28 +9684,33 @@ var PriceNote = class {
|
|
|
9330
9684
|
this.points = [point];
|
|
9331
9685
|
this.complete = true;
|
|
9332
9686
|
}
|
|
9333
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH,
|
|
9687
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
9334
9688
|
const x = scale.xToPixel(this.points[0].barIndex, viewport, chartW);
|
|
9335
9689
|
const y = scale.yToPixel(this.points[0].price, priceRange, chartH);
|
|
9336
9690
|
const price = this.points[0].price;
|
|
9337
9691
|
const label = price.toFixed(pricePrecision);
|
|
9692
|
+
const s = resolveStyle("priceNote", this.style, theme);
|
|
9693
|
+
const fontSize = s.fontSize ?? 11;
|
|
9694
|
+
const textColor = s.textColor ?? "#ffffff";
|
|
9338
9695
|
ctx.save();
|
|
9339
|
-
ctx.strokeStyle =
|
|
9340
|
-
ctx.lineWidth =
|
|
9696
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9697
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9698
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9341
9699
|
ctx.beginPath();
|
|
9342
9700
|
ctx.moveTo(x - TICK_HALF, y);
|
|
9343
9701
|
ctx.lineTo(x + TICK_HALF, y);
|
|
9344
9702
|
ctx.stroke();
|
|
9345
|
-
ctx.
|
|
9703
|
+
ctx.setLineDash([]);
|
|
9704
|
+
ctx.font = `${fontSize}px 'Inter', system-ui, sans-serif`;
|
|
9346
9705
|
const tw = ctx.measureText(label).width;
|
|
9347
9706
|
const badgeW = tw + BADGE_PADDING_X * 2;
|
|
9348
9707
|
const bx = x + TICK_HALF + 2;
|
|
9349
9708
|
const by = y - BADGE_H3 / 2;
|
|
9350
|
-
ctx.fillStyle =
|
|
9709
|
+
ctx.fillStyle = s.fillColor;
|
|
9351
9710
|
ctx.beginPath();
|
|
9352
9711
|
ctx.roundRect(bx, by, badgeW, BADGE_H3, 3);
|
|
9353
9712
|
ctx.fill();
|
|
9354
|
-
ctx.fillStyle =
|
|
9713
|
+
ctx.fillStyle = textColor;
|
|
9355
9714
|
ctx.textBaseline = "middle";
|
|
9356
9715
|
ctx.textAlign = "center";
|
|
9357
9716
|
ctx.fillText(label, bx + badgeW / 2, y);
|
|
@@ -9390,42 +9749,45 @@ var AnchoredNote = class {
|
|
|
9390
9749
|
const label = this.text || "Note";
|
|
9391
9750
|
return Math.max(50, ctx.measureText(label).width + PAD_H2 * 2);
|
|
9392
9751
|
}
|
|
9393
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9752
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9394
9753
|
if (this.points.length < 2) return;
|
|
9395
9754
|
const [p1, p2] = this.points;
|
|
9396
9755
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
9397
9756
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
9398
9757
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
9399
9758
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
9759
|
+
const s = resolveStyle("anchoredNote", this.style, theme);
|
|
9760
|
+
const fontSize = s.fontSize ?? 11;
|
|
9761
|
+
const textColor = s.textColor ?? "#f5f5f5";
|
|
9400
9762
|
ctx.save();
|
|
9401
|
-
ctx.font =
|
|
9402
|
-
ctx.lineWidth = isSelected ?
|
|
9763
|
+
ctx.font = `${fontSize}px system-ui, sans-serif`;
|
|
9764
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9403
9765
|
const badgeW = this.getBadgeW(ctx);
|
|
9404
9766
|
const bx = x2 - badgeW / 2;
|
|
9405
9767
|
const by = y2 - BADGE_H4 / 2;
|
|
9406
|
-
ctx.strokeStyle =
|
|
9768
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9407
9769
|
ctx.setLineDash([4, 3]);
|
|
9408
9770
|
ctx.beginPath();
|
|
9409
9771
|
ctx.moveTo(x1, y1);
|
|
9410
9772
|
ctx.lineTo(x2, y2);
|
|
9411
9773
|
ctx.stroke();
|
|
9412
9774
|
ctx.setLineDash([]);
|
|
9413
|
-
ctx.fillStyle =
|
|
9775
|
+
ctx.fillStyle = s.strokeColor;
|
|
9414
9776
|
ctx.beginPath();
|
|
9415
9777
|
ctx.arc(x1, y1, 3, 0, Math.PI * 2);
|
|
9416
9778
|
ctx.fill();
|
|
9417
|
-
ctx.fillStyle =
|
|
9779
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9418
9780
|
ctx.beginPath();
|
|
9419
9781
|
ctx.roundRect(bx, by, badgeW, BADGE_H4, 4);
|
|
9420
9782
|
ctx.fill();
|
|
9421
|
-
ctx.strokeStyle =
|
|
9422
|
-
ctx.lineWidth = isSelected ?
|
|
9783
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9784
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9423
9785
|
ctx.beginPath();
|
|
9424
9786
|
ctx.roundRect(bx, by, badgeW, BADGE_H4, 4);
|
|
9425
9787
|
ctx.stroke();
|
|
9426
9788
|
const label = this.text || "Note";
|
|
9427
9789
|
const isPlaceholder = !this.text;
|
|
9428
|
-
ctx.fillStyle = isPlaceholder ? "#888" :
|
|
9790
|
+
ctx.fillStyle = isPlaceholder ? "#888" : textColor;
|
|
9429
9791
|
ctx.textBaseline = "middle";
|
|
9430
9792
|
ctx.textAlign = "center";
|
|
9431
9793
|
ctx.fillText(label, x2, y2);
|
|
@@ -9473,17 +9835,20 @@ var ArrowUp = class {
|
|
|
9473
9835
|
this.points = [point];
|
|
9474
9836
|
this.complete = true;
|
|
9475
9837
|
}
|
|
9476
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH,
|
|
9838
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9477
9839
|
const x = scale.xToPixel(this.points[0].barIndex, viewport, chartW);
|
|
9478
9840
|
const y = scale.yToPixel(this.points[0].price, priceRange, chartH);
|
|
9841
|
+
const s = resolveStyle("arrowUp", this.style, theme);
|
|
9479
9842
|
ctx.save();
|
|
9480
|
-
ctx.strokeStyle =
|
|
9481
|
-
ctx.fillStyle =
|
|
9482
|
-
ctx.lineWidth =
|
|
9843
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9844
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9845
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9846
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9483
9847
|
ctx.beginPath();
|
|
9484
9848
|
ctx.moveTo(x, y + 10);
|
|
9485
9849
|
ctx.lineTo(x, y - 2);
|
|
9486
9850
|
ctx.stroke();
|
|
9851
|
+
ctx.setLineDash([]);
|
|
9487
9852
|
const tipX = x;
|
|
9488
9853
|
const tipY = y - 10;
|
|
9489
9854
|
const halfBase = 6;
|
|
@@ -9513,17 +9878,20 @@ var ArrowDown = class {
|
|
|
9513
9878
|
this.points = [point];
|
|
9514
9879
|
this.complete = true;
|
|
9515
9880
|
}
|
|
9516
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH,
|
|
9881
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9517
9882
|
const x = scale.xToPixel(this.points[0].barIndex, viewport, chartW);
|
|
9518
9883
|
const y = scale.yToPixel(this.points[0].price, priceRange, chartH);
|
|
9884
|
+
const s = resolveStyle("arrowDown", this.style, theme);
|
|
9519
9885
|
ctx.save();
|
|
9520
|
-
ctx.strokeStyle =
|
|
9521
|
-
ctx.fillStyle =
|
|
9522
|
-
ctx.lineWidth =
|
|
9886
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9887
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9888
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9889
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9523
9890
|
ctx.beginPath();
|
|
9524
9891
|
ctx.moveTo(x, y - 10);
|
|
9525
9892
|
ctx.lineTo(x, y + 2);
|
|
9526
9893
|
ctx.stroke();
|
|
9894
|
+
ctx.setLineDash([]);
|
|
9527
9895
|
const tipX = x;
|
|
9528
9896
|
const tipY = y + 10;
|
|
9529
9897
|
const halfBase = 6;
|
|
@@ -9554,7 +9922,6 @@ function pointToSegmentDist22(px, py, x1, y1, x2, y2) {
|
|
|
9554
9922
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
9555
9923
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
9556
9924
|
}
|
|
9557
|
-
var COLOR4 = "#ff7043";
|
|
9558
9925
|
var HeadShoulders = class {
|
|
9559
9926
|
constructor(first) {
|
|
9560
9927
|
this.type = "headShoulders";
|
|
@@ -9563,15 +9930,17 @@ var HeadShoulders = class {
|
|
|
9563
9930
|
this.points = [first, { ...first }];
|
|
9564
9931
|
this.complete = false;
|
|
9565
9932
|
}
|
|
9566
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
9933
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9567
9934
|
if (this.points.length < 2) return;
|
|
9568
9935
|
const labels = ["LS", "H", "RS", "L1", "L2"];
|
|
9569
9936
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
9570
9937
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
9571
9938
|
const n = px.length;
|
|
9939
|
+
const s = resolveStyle("headShoulders", this.style, theme);
|
|
9572
9940
|
ctx.save();
|
|
9573
|
-
ctx.strokeStyle =
|
|
9574
|
-
ctx.lineWidth = isSelected ?
|
|
9941
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9942
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9943
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9575
9944
|
const zigzagOrder = [0, 3, 1, 4, 2];
|
|
9576
9945
|
const availableZigzag = zigzagOrder.filter((i) => i < n);
|
|
9577
9946
|
if (availableZigzag.length >= 2) {
|
|
@@ -9582,6 +9951,7 @@ var HeadShoulders = class {
|
|
|
9582
9951
|
}
|
|
9583
9952
|
ctx.stroke();
|
|
9584
9953
|
}
|
|
9954
|
+
ctx.setLineDash([]);
|
|
9585
9955
|
if (this.complete && n === 5) {
|
|
9586
9956
|
ctx.beginPath();
|
|
9587
9957
|
ctx.moveTo(px[0], py[0]);
|
|
@@ -9590,14 +9960,14 @@ var HeadShoulders = class {
|
|
|
9590
9960
|
ctx.lineTo(px[4], py[4]);
|
|
9591
9961
|
ctx.lineTo(px[2], py[2]);
|
|
9592
9962
|
ctx.closePath();
|
|
9593
|
-
ctx.fillStyle =
|
|
9963
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9594
9964
|
ctx.fill();
|
|
9595
9965
|
}
|
|
9596
9966
|
if (n >= 5) {
|
|
9597
9967
|
ctx.save();
|
|
9598
9968
|
ctx.setLineDash([5, 4]);
|
|
9599
|
-
ctx.strokeStyle =
|
|
9600
|
-
ctx.lineWidth = isSelected ?
|
|
9969
|
+
ctx.strokeStyle = s.strokeColor;
|
|
9970
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9601
9971
|
ctx.beginPath();
|
|
9602
9972
|
ctx.moveTo(px[3], py[3]);
|
|
9603
9973
|
ctx.lineTo(px[4], py[4]);
|
|
@@ -9605,7 +9975,7 @@ var HeadShoulders = class {
|
|
|
9605
9975
|
ctx.restore();
|
|
9606
9976
|
}
|
|
9607
9977
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
9608
|
-
ctx.fillStyle =
|
|
9978
|
+
ctx.fillStyle = s.strokeColor;
|
|
9609
9979
|
for (let i = 0; i < n; i++) {
|
|
9610
9980
|
ctx.beginPath();
|
|
9611
9981
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -9651,7 +10021,6 @@ function pointToSegmentDist23(px, py, x1, y1, x2, y2) {
|
|
|
9651
10021
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
9652
10022
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
9653
10023
|
}
|
|
9654
|
-
var COLOR5 = "#ffa726";
|
|
9655
10024
|
var TrianglePattern = class {
|
|
9656
10025
|
constructor(first) {
|
|
9657
10026
|
this.type = "trianglePattern";
|
|
@@ -9660,26 +10029,29 @@ var TrianglePattern = class {
|
|
|
9660
10029
|
this.points = [first, { ...first }];
|
|
9661
10030
|
this.complete = false;
|
|
9662
10031
|
}
|
|
9663
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10032
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9664
10033
|
if (this.points.length < 2) return;
|
|
9665
10034
|
const labels = ["1", "2", "3", "4", "5"];
|
|
9666
10035
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
9667
10036
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
9668
10037
|
const n = px.length;
|
|
10038
|
+
const s = resolveStyle("trianglePattern", this.style, theme);
|
|
9669
10039
|
ctx.save();
|
|
9670
|
-
ctx.strokeStyle =
|
|
9671
|
-
ctx.lineWidth = isSelected ?
|
|
10040
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10041
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10042
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9672
10043
|
ctx.beginPath();
|
|
9673
10044
|
ctx.moveTo(px[0], py[0]);
|
|
9674
10045
|
for (let i = 1; i < n; i++) {
|
|
9675
10046
|
ctx.lineTo(px[i], py[i]);
|
|
9676
10047
|
}
|
|
9677
10048
|
ctx.stroke();
|
|
10049
|
+
ctx.setLineDash([]);
|
|
9678
10050
|
if (n >= 3) {
|
|
9679
10051
|
ctx.save();
|
|
9680
10052
|
ctx.setLineDash([5, 4]);
|
|
9681
|
-
ctx.strokeStyle =
|
|
9682
|
-
ctx.lineWidth = isSelected ?
|
|
10053
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10054
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9683
10055
|
ctx.beginPath();
|
|
9684
10056
|
ctx.moveTo(px[0], py[0]);
|
|
9685
10057
|
ctx.lineTo(px[2], py[2]);
|
|
@@ -9690,8 +10062,8 @@ var TrianglePattern = class {
|
|
|
9690
10062
|
if (n >= 4) {
|
|
9691
10063
|
ctx.save();
|
|
9692
10064
|
ctx.setLineDash([5, 4]);
|
|
9693
|
-
ctx.strokeStyle =
|
|
9694
|
-
ctx.lineWidth = isSelected ?
|
|
10065
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10066
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9695
10067
|
ctx.beginPath();
|
|
9696
10068
|
ctx.moveTo(px[1], py[1]);
|
|
9697
10069
|
ctx.lineTo(px[3], py[3]);
|
|
@@ -9699,7 +10071,7 @@ var TrianglePattern = class {
|
|
|
9699
10071
|
ctx.restore();
|
|
9700
10072
|
}
|
|
9701
10073
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
9702
|
-
ctx.fillStyle =
|
|
10074
|
+
ctx.fillStyle = s.strokeColor;
|
|
9703
10075
|
for (let i = 0; i < n; i++) {
|
|
9704
10076
|
ctx.beginPath();
|
|
9705
10077
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -9743,7 +10115,6 @@ function pointToSegmentDist24(px, py, x1, y1, x2, y2) {
|
|
|
9743
10115
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
9744
10116
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
9745
10117
|
}
|
|
9746
|
-
var COLOR6 = "#66bb6a";
|
|
9747
10118
|
var ThreeDrives = class {
|
|
9748
10119
|
constructor(first) {
|
|
9749
10120
|
this.type = "threeDrives";
|
|
@@ -9752,21 +10123,24 @@ var ThreeDrives = class {
|
|
|
9752
10123
|
this.points = [first, { ...first }];
|
|
9753
10124
|
this.complete = false;
|
|
9754
10125
|
}
|
|
9755
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10126
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9756
10127
|
if (this.points.length < 2) return;
|
|
9757
10128
|
const labels = ["O", "A", "B", "C", "D", "E", "F"];
|
|
9758
10129
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
9759
10130
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
9760
10131
|
const n = px.length;
|
|
10132
|
+
const s = resolveStyle("threeDrives", this.style, theme);
|
|
9761
10133
|
ctx.save();
|
|
9762
|
-
ctx.strokeStyle =
|
|
9763
|
-
ctx.lineWidth = isSelected ?
|
|
10134
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10135
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10136
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9764
10137
|
ctx.beginPath();
|
|
9765
10138
|
ctx.moveTo(px[0], py[0]);
|
|
9766
10139
|
for (let i = 1; i < n; i++) {
|
|
9767
10140
|
ctx.lineTo(px[i], py[i]);
|
|
9768
10141
|
}
|
|
9769
10142
|
ctx.stroke();
|
|
10143
|
+
ctx.setLineDash([]);
|
|
9770
10144
|
if (this.complete && n === 7) {
|
|
9771
10145
|
ctx.beginPath();
|
|
9772
10146
|
ctx.moveTo(px[0], py[0]);
|
|
@@ -9774,11 +10148,11 @@ var ThreeDrives = class {
|
|
|
9774
10148
|
ctx.lineTo(px[i], py[i]);
|
|
9775
10149
|
}
|
|
9776
10150
|
ctx.closePath();
|
|
9777
|
-
ctx.fillStyle =
|
|
10151
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9778
10152
|
ctx.fill();
|
|
9779
10153
|
}
|
|
9780
10154
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
9781
|
-
ctx.fillStyle =
|
|
10155
|
+
ctx.fillStyle = s.strokeColor;
|
|
9782
10156
|
for (let i = 0; i < n; i++) {
|
|
9783
10157
|
ctx.beginPath();
|
|
9784
10158
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -9819,7 +10193,6 @@ function pointToSegmentDist25(px, py, x1, y1, x2, y2) {
|
|
|
9819
10193
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
9820
10194
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
9821
10195
|
}
|
|
9822
|
-
var COLOR7 = "#42a5f5";
|
|
9823
10196
|
var AbcdPattern = class {
|
|
9824
10197
|
constructor(first) {
|
|
9825
10198
|
this.type = "abcdPattern";
|
|
@@ -9828,26 +10201,29 @@ var AbcdPattern = class {
|
|
|
9828
10201
|
this.points = [first, { ...first }];
|
|
9829
10202
|
this.complete = false;
|
|
9830
10203
|
}
|
|
9831
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10204
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9832
10205
|
if (this.points.length < 2) return;
|
|
9833
10206
|
const labels = ["A", "B", "C", "D"];
|
|
9834
10207
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
9835
10208
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
9836
10209
|
const n = px.length;
|
|
10210
|
+
const s = resolveStyle("abcdPattern", this.style, theme);
|
|
9837
10211
|
ctx.save();
|
|
9838
|
-
ctx.strokeStyle =
|
|
9839
|
-
ctx.lineWidth = isSelected ?
|
|
10212
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10213
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10214
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9840
10215
|
ctx.beginPath();
|
|
9841
10216
|
ctx.moveTo(px[0], py[0]);
|
|
9842
10217
|
for (let i = 1; i < n; i++) {
|
|
9843
10218
|
ctx.lineTo(px[i], py[i]);
|
|
9844
10219
|
}
|
|
9845
10220
|
ctx.stroke();
|
|
10221
|
+
ctx.setLineDash([]);
|
|
9846
10222
|
if (n >= 4) {
|
|
9847
10223
|
ctx.save();
|
|
9848
10224
|
ctx.setLineDash([3, 4]);
|
|
9849
|
-
ctx.strokeStyle =
|
|
9850
|
-
ctx.lineWidth = isSelected ?
|
|
10225
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10226
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
9851
10227
|
ctx.beginPath();
|
|
9852
10228
|
ctx.moveTo(px[0], py[0]);
|
|
9853
10229
|
ctx.lineTo(px[3], py[3]);
|
|
@@ -9855,7 +10231,7 @@ var AbcdPattern = class {
|
|
|
9855
10231
|
ctx.restore();
|
|
9856
10232
|
}
|
|
9857
10233
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
9858
|
-
ctx.fillStyle =
|
|
10234
|
+
ctx.fillStyle = s.strokeColor;
|
|
9859
10235
|
for (let i = 0; i < n; i++) {
|
|
9860
10236
|
ctx.beginPath();
|
|
9861
10237
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -9897,7 +10273,6 @@ function pointToSegmentDist26(px, py, x1, y1, x2, y2) {
|
|
|
9897
10273
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
9898
10274
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
9899
10275
|
}
|
|
9900
|
-
var COLOR8 = "#ab47bc";
|
|
9901
10276
|
var CypherPattern = class {
|
|
9902
10277
|
constructor(first) {
|
|
9903
10278
|
this.type = "cypherPattern";
|
|
@@ -9906,21 +10281,24 @@ var CypherPattern = class {
|
|
|
9906
10281
|
this.points = [first, { ...first }];
|
|
9907
10282
|
this.complete = false;
|
|
9908
10283
|
}
|
|
9909
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10284
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9910
10285
|
if (this.points.length < 2) return;
|
|
9911
10286
|
const labels = ["X", "A", "B", "C", "D"];
|
|
9912
10287
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
9913
10288
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
9914
10289
|
const n = px.length;
|
|
10290
|
+
const s = resolveStyle("cypherPattern", this.style, theme);
|
|
9915
10291
|
ctx.save();
|
|
9916
|
-
ctx.strokeStyle =
|
|
9917
|
-
ctx.lineWidth = isSelected ?
|
|
10292
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10293
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10294
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9918
10295
|
ctx.beginPath();
|
|
9919
10296
|
ctx.moveTo(px[0], py[0]);
|
|
9920
10297
|
for (let i = 1; i < n; i++) {
|
|
9921
10298
|
ctx.lineTo(px[i], py[i]);
|
|
9922
10299
|
}
|
|
9923
10300
|
ctx.stroke();
|
|
10301
|
+
ctx.setLineDash([]);
|
|
9924
10302
|
if (this.complete && n === 5) {
|
|
9925
10303
|
ctx.beginPath();
|
|
9926
10304
|
ctx.moveTo(px[0], py[0]);
|
|
@@ -9928,11 +10306,11 @@ var CypherPattern = class {
|
|
|
9928
10306
|
ctx.lineTo(px[i], py[i]);
|
|
9929
10307
|
}
|
|
9930
10308
|
ctx.closePath();
|
|
9931
|
-
ctx.fillStyle =
|
|
10309
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
9932
10310
|
ctx.fill();
|
|
9933
10311
|
}
|
|
9934
10312
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
9935
|
-
ctx.fillStyle =
|
|
10313
|
+
ctx.fillStyle = s.strokeColor;
|
|
9936
10314
|
for (let i = 0; i < n; i++) {
|
|
9937
10315
|
ctx.beginPath();
|
|
9938
10316
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -9973,7 +10351,6 @@ function pointToSegmentDist27(px, py, x1, y1, x2, y2) {
|
|
|
9973
10351
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
9974
10352
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
9975
10353
|
}
|
|
9976
|
-
var COLOR9 = "#ec407a";
|
|
9977
10354
|
var ButterflyPattern = class {
|
|
9978
10355
|
constructor(first) {
|
|
9979
10356
|
this.type = "butterflyPattern";
|
|
@@ -9982,26 +10359,29 @@ var ButterflyPattern = class {
|
|
|
9982
10359
|
this.points = [first, { ...first }];
|
|
9983
10360
|
this.complete = false;
|
|
9984
10361
|
}
|
|
9985
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10362
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
9986
10363
|
if (this.points.length < 2) return;
|
|
9987
10364
|
const labels = ["X", "A", "B", "C", "D"];
|
|
9988
10365
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
9989
10366
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
9990
10367
|
const n = px.length;
|
|
10368
|
+
const s = resolveStyle("butterflyPattern", this.style, theme);
|
|
9991
10369
|
ctx.save();
|
|
9992
|
-
ctx.strokeStyle =
|
|
9993
|
-
ctx.lineWidth = isSelected ?
|
|
10370
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10371
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10372
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
9994
10373
|
ctx.beginPath();
|
|
9995
10374
|
ctx.moveTo(px[0], py[0]);
|
|
9996
10375
|
for (let i = 1; i < n; i++) {
|
|
9997
10376
|
ctx.lineTo(px[i], py[i]);
|
|
9998
10377
|
}
|
|
9999
10378
|
ctx.stroke();
|
|
10379
|
+
ctx.setLineDash([]);
|
|
10000
10380
|
if (n >= 5) {
|
|
10001
10381
|
ctx.save();
|
|
10002
10382
|
ctx.setLineDash([5, 4]);
|
|
10003
|
-
ctx.strokeStyle =
|
|
10004
|
-
ctx.lineWidth = isSelected ?
|
|
10383
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10384
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10005
10385
|
ctx.beginPath();
|
|
10006
10386
|
ctx.moveTo(px[0], py[0]);
|
|
10007
10387
|
ctx.lineTo(px[4], py[4]);
|
|
@@ -10009,7 +10389,7 @@ var ButterflyPattern = class {
|
|
|
10009
10389
|
ctx.restore();
|
|
10010
10390
|
}
|
|
10011
10391
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10012
|
-
ctx.fillStyle =
|
|
10392
|
+
ctx.fillStyle = s.strokeColor;
|
|
10013
10393
|
for (let i = 0; i < n; i++) {
|
|
10014
10394
|
ctx.beginPath();
|
|
10015
10395
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -10051,7 +10431,6 @@ function pointToSegmentDist28(px, py, x1, y1, x2, y2) {
|
|
|
10051
10431
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10052
10432
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10053
10433
|
}
|
|
10054
|
-
var COLOR10 = "#26a69a";
|
|
10055
10434
|
var BatPattern = class {
|
|
10056
10435
|
constructor(first) {
|
|
10057
10436
|
this.type = "batPattern";
|
|
@@ -10060,26 +10439,29 @@ var BatPattern = class {
|
|
|
10060
10439
|
this.points = [first, { ...first }];
|
|
10061
10440
|
this.complete = false;
|
|
10062
10441
|
}
|
|
10063
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10442
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10064
10443
|
if (this.points.length < 2) return;
|
|
10065
10444
|
const labels = ["X", "A", "B", "C", "D"];
|
|
10066
10445
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10067
10446
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10068
10447
|
const n = px.length;
|
|
10448
|
+
const s = resolveStyle("batPattern", this.style, theme);
|
|
10069
10449
|
ctx.save();
|
|
10070
|
-
ctx.strokeStyle =
|
|
10071
|
-
ctx.lineWidth = isSelected ?
|
|
10450
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10451
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10452
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10072
10453
|
ctx.beginPath();
|
|
10073
10454
|
ctx.moveTo(px[0], py[0]);
|
|
10074
10455
|
for (let i = 1; i < n; i++) {
|
|
10075
10456
|
ctx.lineTo(px[i], py[i]);
|
|
10076
10457
|
}
|
|
10077
10458
|
ctx.stroke();
|
|
10459
|
+
ctx.setLineDash([]);
|
|
10078
10460
|
if (n >= 5) {
|
|
10079
10461
|
ctx.save();
|
|
10080
10462
|
ctx.setLineDash([5, 4]);
|
|
10081
|
-
ctx.strokeStyle =
|
|
10082
|
-
ctx.lineWidth = isSelected ?
|
|
10463
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10464
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10083
10465
|
ctx.beginPath();
|
|
10084
10466
|
ctx.moveTo(px[0], py[0]);
|
|
10085
10467
|
ctx.lineTo(px[4], py[4]);
|
|
@@ -10087,7 +10469,7 @@ var BatPattern = class {
|
|
|
10087
10469
|
ctx.restore();
|
|
10088
10470
|
}
|
|
10089
10471
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10090
|
-
ctx.fillStyle =
|
|
10472
|
+
ctx.fillStyle = s.strokeColor;
|
|
10091
10473
|
for (let i = 0; i < n; i++) {
|
|
10092
10474
|
ctx.beginPath();
|
|
10093
10475
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -10129,7 +10511,6 @@ function pointToSegmentDist29(px, py, x1, y1, x2, y2) {
|
|
|
10129
10511
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10130
10512
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10131
10513
|
}
|
|
10132
|
-
var COLOR11 = "#7986cb";
|
|
10133
10514
|
var GartleyPattern = class {
|
|
10134
10515
|
constructor(first) {
|
|
10135
10516
|
this.type = "gartleyPattern";
|
|
@@ -10138,26 +10519,29 @@ var GartleyPattern = class {
|
|
|
10138
10519
|
this.points = [first, { ...first }];
|
|
10139
10520
|
this.complete = false;
|
|
10140
10521
|
}
|
|
10141
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10522
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10142
10523
|
if (this.points.length < 2) return;
|
|
10143
10524
|
const labels = ["X", "A", "B", "C", "D"];
|
|
10144
10525
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10145
10526
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10146
10527
|
const n = px.length;
|
|
10528
|
+
const s = resolveStyle("gartleyPattern", this.style, theme);
|
|
10147
10529
|
ctx.save();
|
|
10148
|
-
ctx.strokeStyle =
|
|
10149
|
-
ctx.lineWidth = isSelected ?
|
|
10530
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10531
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10532
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10150
10533
|
ctx.beginPath();
|
|
10151
10534
|
ctx.moveTo(px[0], py[0]);
|
|
10152
10535
|
for (let i = 1; i < n; i++) {
|
|
10153
10536
|
ctx.lineTo(px[i], py[i]);
|
|
10154
10537
|
}
|
|
10155
10538
|
ctx.stroke();
|
|
10539
|
+
ctx.setLineDash([]);
|
|
10156
10540
|
if (n >= 5) {
|
|
10157
10541
|
ctx.save();
|
|
10158
10542
|
ctx.setLineDash([5, 4]);
|
|
10159
|
-
ctx.strokeStyle =
|
|
10160
|
-
ctx.lineWidth = isSelected ?
|
|
10543
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10544
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10161
10545
|
ctx.beginPath();
|
|
10162
10546
|
ctx.moveTo(px[0], py[0]);
|
|
10163
10547
|
ctx.lineTo(px[4], py[4]);
|
|
@@ -10165,7 +10549,7 @@ var GartleyPattern = class {
|
|
|
10165
10549
|
ctx.restore();
|
|
10166
10550
|
}
|
|
10167
10551
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10168
|
-
ctx.fillStyle =
|
|
10552
|
+
ctx.fillStyle = s.strokeColor;
|
|
10169
10553
|
for (let i = 0; i < n; i++) {
|
|
10170
10554
|
ctx.beginPath();
|
|
10171
10555
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -10207,7 +10591,6 @@ function pointToSegmentDist30(px, py, x1, y1, x2, y2) {
|
|
|
10207
10591
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10208
10592
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10209
10593
|
}
|
|
10210
|
-
var COLOR12 = "#ef5350";
|
|
10211
10594
|
var CrabPattern = class {
|
|
10212
10595
|
constructor(first) {
|
|
10213
10596
|
this.type = "crabPattern";
|
|
@@ -10216,26 +10599,29 @@ var CrabPattern = class {
|
|
|
10216
10599
|
this.points = [first, { ...first }];
|
|
10217
10600
|
this.complete = false;
|
|
10218
10601
|
}
|
|
10219
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10602
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10220
10603
|
if (this.points.length < 2) return;
|
|
10221
10604
|
const labels = ["X", "A", "B", "C", "D"];
|
|
10222
10605
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10223
10606
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10224
10607
|
const n = px.length;
|
|
10608
|
+
const s = resolveStyle("crabPattern", this.style, theme);
|
|
10225
10609
|
ctx.save();
|
|
10226
|
-
ctx.strokeStyle =
|
|
10227
|
-
ctx.lineWidth = isSelected ?
|
|
10610
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10611
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10612
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10228
10613
|
ctx.beginPath();
|
|
10229
10614
|
ctx.moveTo(px[0], py[0]);
|
|
10230
10615
|
for (let i = 1; i < n; i++) {
|
|
10231
10616
|
ctx.lineTo(px[i], py[i]);
|
|
10232
10617
|
}
|
|
10233
10618
|
ctx.stroke();
|
|
10619
|
+
ctx.setLineDash([]);
|
|
10234
10620
|
if (n >= 5) {
|
|
10235
10621
|
ctx.save();
|
|
10236
10622
|
ctx.setLineDash([5, 4]);
|
|
10237
|
-
ctx.strokeStyle =
|
|
10238
|
-
ctx.lineWidth = isSelected ?
|
|
10623
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10624
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10239
10625
|
ctx.beginPath();
|
|
10240
10626
|
ctx.moveTo(px[0], py[0]);
|
|
10241
10627
|
ctx.lineTo(px[4], py[4]);
|
|
@@ -10243,7 +10629,7 @@ var CrabPattern = class {
|
|
|
10243
10629
|
ctx.restore();
|
|
10244
10630
|
}
|
|
10245
10631
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10246
|
-
ctx.fillStyle =
|
|
10632
|
+
ctx.fillStyle = s.strokeColor;
|
|
10247
10633
|
for (let i = 0; i < n; i++) {
|
|
10248
10634
|
ctx.beginPath();
|
|
10249
10635
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -10285,7 +10671,6 @@ function pointToSegmentDist31(px, py, x1, y1, x2, y2) {
|
|
|
10285
10671
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10286
10672
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10287
10673
|
}
|
|
10288
|
-
var COLOR13 = "#78909c";
|
|
10289
10674
|
var SharkPattern = class {
|
|
10290
10675
|
constructor(first) {
|
|
10291
10676
|
this.type = "sharkPattern";
|
|
@@ -10294,26 +10679,29 @@ var SharkPattern = class {
|
|
|
10294
10679
|
this.points = [first, { ...first }];
|
|
10295
10680
|
this.complete = false;
|
|
10296
10681
|
}
|
|
10297
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10682
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10298
10683
|
if (this.points.length < 2) return;
|
|
10299
10684
|
const labels = ["X", "A", "B", "C", "D"];
|
|
10300
10685
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10301
10686
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10302
10687
|
const n = px.length;
|
|
10688
|
+
const s = resolveStyle("sharkPattern", this.style, theme);
|
|
10303
10689
|
ctx.save();
|
|
10304
|
-
ctx.strokeStyle =
|
|
10305
|
-
ctx.lineWidth = isSelected ?
|
|
10690
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10691
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10692
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10306
10693
|
ctx.beginPath();
|
|
10307
10694
|
ctx.moveTo(px[0], py[0]);
|
|
10308
10695
|
for (let i = 1; i < n; i++) {
|
|
10309
10696
|
ctx.lineTo(px[i], py[i]);
|
|
10310
10697
|
}
|
|
10311
10698
|
ctx.stroke();
|
|
10699
|
+
ctx.setLineDash([]);
|
|
10312
10700
|
if (n >= 5) {
|
|
10313
10701
|
ctx.save();
|
|
10314
10702
|
ctx.setLineDash([5, 4]);
|
|
10315
|
-
ctx.strokeStyle =
|
|
10316
|
-
ctx.lineWidth = isSelected ?
|
|
10703
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10704
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10317
10705
|
ctx.beginPath();
|
|
10318
10706
|
ctx.moveTo(px[0], py[0]);
|
|
10319
10707
|
ctx.lineTo(px[4], py[4]);
|
|
@@ -10321,7 +10709,7 @@ var SharkPattern = class {
|
|
|
10321
10709
|
ctx.restore();
|
|
10322
10710
|
}
|
|
10323
10711
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10324
|
-
ctx.fillStyle =
|
|
10712
|
+
ctx.fillStyle = s.strokeColor;
|
|
10325
10713
|
for (let i = 0; i < n; i++) {
|
|
10326
10714
|
ctx.beginPath();
|
|
10327
10715
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -10363,7 +10751,6 @@ function pointToSegmentDist32(px, py, x1, y1, x2, y2) {
|
|
|
10363
10751
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10364
10752
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10365
10753
|
}
|
|
10366
|
-
var COLOR14 = "#29b6f6";
|
|
10367
10754
|
var LABELS = ["0", "1", "2", "3", "4", "5"];
|
|
10368
10755
|
var ElliottImpulse = class {
|
|
10369
10756
|
constructor(first) {
|
|
@@ -10373,28 +10760,31 @@ var ElliottImpulse = class {
|
|
|
10373
10760
|
this.points = [first, { ...first }];
|
|
10374
10761
|
this.complete = false;
|
|
10375
10762
|
}
|
|
10376
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10763
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10377
10764
|
if (this.points.length < 2) return;
|
|
10378
10765
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10379
10766
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10380
10767
|
const n = px.length;
|
|
10768
|
+
const s = resolveStyle("elliottImpulse", this.style, theme);
|
|
10381
10769
|
ctx.save();
|
|
10382
|
-
ctx.lineWidth = isSelected ?
|
|
10770
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10771
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10383
10772
|
for (let i = 0; i < n - 1; i++) {
|
|
10384
10773
|
const isImpulse = i % 2 === 0;
|
|
10385
|
-
ctx.strokeStyle = isImpulse ?
|
|
10774
|
+
ctx.strokeStyle = isImpulse ? s.strokeColor : hexWithAlpha(s.strokeColor, 0.67);
|
|
10386
10775
|
ctx.beginPath();
|
|
10387
10776
|
ctx.moveTo(px[i], py[i]);
|
|
10388
10777
|
ctx.lineTo(px[i + 1], py[i + 1]);
|
|
10389
10778
|
ctx.stroke();
|
|
10390
10779
|
}
|
|
10780
|
+
ctx.setLineDash([]);
|
|
10391
10781
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10392
10782
|
for (let i = 0; i < n; i++) {
|
|
10393
10783
|
ctx.beginPath();
|
|
10394
10784
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
10395
|
-
ctx.fillStyle =
|
|
10785
|
+
ctx.fillStyle = s.strokeColor;
|
|
10396
10786
|
ctx.fill();
|
|
10397
|
-
ctx.fillStyle =
|
|
10787
|
+
ctx.fillStyle = s.strokeColor;
|
|
10398
10788
|
ctx.fillText(LABELS[i] ?? String(i), px[i] + 5, py[i] - 5);
|
|
10399
10789
|
}
|
|
10400
10790
|
ctx.restore();
|
|
@@ -10428,7 +10818,6 @@ function pointToSegmentDist33(px, py, x1, y1, x2, y2) {
|
|
|
10428
10818
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10429
10819
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10430
10820
|
}
|
|
10431
|
-
var COLOR15 = "#ff8a65";
|
|
10432
10821
|
var LABELS2 = ["0", "A", "B", "C"];
|
|
10433
10822
|
var ElliottCorrective = class {
|
|
10434
10823
|
constructor(first) {
|
|
@@ -10438,24 +10827,27 @@ var ElliottCorrective = class {
|
|
|
10438
10827
|
this.points = [first, { ...first }];
|
|
10439
10828
|
this.complete = false;
|
|
10440
10829
|
}
|
|
10441
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10830
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10442
10831
|
if (this.points.length < 2) return;
|
|
10443
10832
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10444
10833
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10834
|
+
const s = resolveStyle("elliottCorrective", this.style, theme);
|
|
10445
10835
|
ctx.save();
|
|
10446
|
-
ctx.strokeStyle =
|
|
10447
|
-
ctx.lineWidth = isSelected ?
|
|
10836
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10837
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10838
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10448
10839
|
ctx.beginPath();
|
|
10449
10840
|
ctx.moveTo(px[0], py[0]);
|
|
10450
10841
|
for (let i = 1; i < px.length; i++) ctx.lineTo(px[i], py[i]);
|
|
10451
10842
|
ctx.stroke();
|
|
10843
|
+
ctx.setLineDash([]);
|
|
10452
10844
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10453
10845
|
for (let i = 0; i < px.length; i++) {
|
|
10454
10846
|
ctx.beginPath();
|
|
10455
10847
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
10456
|
-
ctx.fillStyle =
|
|
10848
|
+
ctx.fillStyle = s.strokeColor;
|
|
10457
10849
|
ctx.fill();
|
|
10458
|
-
ctx.fillStyle =
|
|
10850
|
+
ctx.fillStyle = s.strokeColor;
|
|
10459
10851
|
ctx.fillText(LABELS2[i] ?? String(i), px[i] + 5, py[i] - 5);
|
|
10460
10852
|
}
|
|
10461
10853
|
ctx.restore();
|
|
@@ -10489,7 +10881,6 @@ function pointToSegmentDist34(px, py, x1, y1, x2, y2) {
|
|
|
10489
10881
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10490
10882
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10491
10883
|
}
|
|
10492
|
-
var COLOR16 = "#aed581";
|
|
10493
10884
|
var LABELS3 = ["A", "B", "C", "D", "E", "end"];
|
|
10494
10885
|
var ElliottTriangle = class {
|
|
10495
10886
|
constructor(first) {
|
|
@@ -10499,23 +10890,26 @@ var ElliottTriangle = class {
|
|
|
10499
10890
|
this.points = [first, { ...first }];
|
|
10500
10891
|
this.complete = false;
|
|
10501
10892
|
}
|
|
10502
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10893
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10503
10894
|
if (this.points.length < 2) return;
|
|
10504
10895
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10505
10896
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10506
10897
|
const n = px.length;
|
|
10898
|
+
const s = resolveStyle("elliottTriangle", this.style, theme);
|
|
10507
10899
|
ctx.save();
|
|
10508
|
-
ctx.strokeStyle =
|
|
10509
|
-
ctx.lineWidth = isSelected ?
|
|
10900
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10901
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10902
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10510
10903
|
ctx.beginPath();
|
|
10511
10904
|
ctx.moveTo(px[0], py[0]);
|
|
10512
10905
|
for (let i = 1; i < n; i++) ctx.lineTo(px[i], py[i]);
|
|
10513
10906
|
ctx.stroke();
|
|
10907
|
+
ctx.setLineDash([]);
|
|
10514
10908
|
if (n >= 3) {
|
|
10515
10909
|
ctx.save();
|
|
10516
10910
|
ctx.setLineDash([5, 4]);
|
|
10517
|
-
ctx.strokeStyle =
|
|
10518
|
-
ctx.lineWidth =
|
|
10911
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, 0.67);
|
|
10912
|
+
ctx.lineWidth = s.lineWidth;
|
|
10519
10913
|
ctx.beginPath();
|
|
10520
10914
|
ctx.moveTo(px[0], py[0]);
|
|
10521
10915
|
if (n > 2) ctx.lineTo(px[2], py[2]);
|
|
@@ -10526,8 +10920,8 @@ var ElliottTriangle = class {
|
|
|
10526
10920
|
if (n >= 4) {
|
|
10527
10921
|
ctx.save();
|
|
10528
10922
|
ctx.setLineDash([5, 4]);
|
|
10529
|
-
ctx.strokeStyle =
|
|
10530
|
-
ctx.lineWidth =
|
|
10923
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, 0.67);
|
|
10924
|
+
ctx.lineWidth = s.lineWidth;
|
|
10531
10925
|
ctx.beginPath();
|
|
10532
10926
|
ctx.moveTo(px[1], py[1]);
|
|
10533
10927
|
ctx.lineTo(px[3], py[3]);
|
|
@@ -10538,9 +10932,9 @@ var ElliottTriangle = class {
|
|
|
10538
10932
|
for (let i = 0; i < n; i++) {
|
|
10539
10933
|
ctx.beginPath();
|
|
10540
10934
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
10541
|
-
ctx.fillStyle =
|
|
10935
|
+
ctx.fillStyle = s.strokeColor;
|
|
10542
10936
|
ctx.fill();
|
|
10543
|
-
ctx.fillStyle =
|
|
10937
|
+
ctx.fillStyle = s.strokeColor;
|
|
10544
10938
|
ctx.fillText(LABELS3[i] ?? String(i), px[i] + 5, py[i] - 5);
|
|
10545
10939
|
}
|
|
10546
10940
|
ctx.restore();
|
|
@@ -10574,7 +10968,6 @@ function pointToSegmentDist35(px, py, x1, y1, x2, y2) {
|
|
|
10574
10968
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10575
10969
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10576
10970
|
}
|
|
10577
|
-
var COLOR17 = "#f48fb1";
|
|
10578
10971
|
var LABELS4 = ["W", "X", "Y", "X2", "Z", "X3", "end"];
|
|
10579
10972
|
var ElliottCombination = class {
|
|
10580
10973
|
constructor(first) {
|
|
@@ -10584,24 +10977,27 @@ var ElliottCombination = class {
|
|
|
10584
10977
|
this.points = [first, { ...first }];
|
|
10585
10978
|
this.complete = false;
|
|
10586
10979
|
}
|
|
10587
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
10980
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10588
10981
|
if (this.points.length < 2) return;
|
|
10589
10982
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10590
10983
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
10984
|
+
const s = resolveStyle("elliottCombination", this.style, theme);
|
|
10591
10985
|
ctx.save();
|
|
10592
|
-
ctx.strokeStyle =
|
|
10593
|
-
ctx.lineWidth = isSelected ?
|
|
10986
|
+
ctx.strokeStyle = s.strokeColor;
|
|
10987
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10988
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10594
10989
|
ctx.beginPath();
|
|
10595
10990
|
ctx.moveTo(px[0], py[0]);
|
|
10596
10991
|
for (let i = 1; i < px.length; i++) ctx.lineTo(px[i], py[i]);
|
|
10597
10992
|
ctx.stroke();
|
|
10993
|
+
ctx.setLineDash([]);
|
|
10598
10994
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10599
10995
|
for (let i = 0; i < px.length; i++) {
|
|
10600
10996
|
ctx.beginPath();
|
|
10601
10997
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
10602
|
-
ctx.fillStyle =
|
|
10998
|
+
ctx.fillStyle = s.strokeColor;
|
|
10603
10999
|
ctx.fill();
|
|
10604
|
-
ctx.fillStyle =
|
|
11000
|
+
ctx.fillStyle = s.strokeColor;
|
|
10605
11001
|
ctx.fillText(LABELS4[i] ?? String(i), px[i] + 5, py[i] - 5);
|
|
10606
11002
|
}
|
|
10607
11003
|
ctx.restore();
|
|
@@ -10635,7 +11031,6 @@ function pointToSegmentDist36(px, py, x1, y1, x2, y2) {
|
|
|
10635
11031
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
10636
11032
|
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
10637
11033
|
}
|
|
10638
|
-
var COLOR18 = "#80cbc4";
|
|
10639
11034
|
var LABELS5 = ["W", "X", "Y", "end"];
|
|
10640
11035
|
var ElliottWxy = class {
|
|
10641
11036
|
constructor(first) {
|
|
@@ -10645,24 +11040,27 @@ var ElliottWxy = class {
|
|
|
10645
11040
|
this.points = [first, { ...first }];
|
|
10646
11041
|
this.complete = false;
|
|
10647
11042
|
}
|
|
10648
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11043
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10649
11044
|
if (this.points.length < 2) return;
|
|
10650
11045
|
const px = this.points.map((p) => scale.xToPixel(p.barIndex, viewport, chartW));
|
|
10651
11046
|
const py = this.points.map((p) => scale.yToPixel(p.price, priceRange, chartH));
|
|
11047
|
+
const s = resolveStyle("elliottWxy", this.style, theme);
|
|
10652
11048
|
ctx.save();
|
|
10653
|
-
ctx.strokeStyle =
|
|
10654
|
-
ctx.lineWidth = isSelected ?
|
|
11049
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11050
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
11051
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10655
11052
|
ctx.beginPath();
|
|
10656
11053
|
ctx.moveTo(px[0], py[0]);
|
|
10657
11054
|
for (let i = 1; i < px.length; i++) ctx.lineTo(px[i], py[i]);
|
|
10658
11055
|
ctx.stroke();
|
|
11056
|
+
ctx.setLineDash([]);
|
|
10659
11057
|
ctx.font = "11px 'Inter', system-ui, sans-serif";
|
|
10660
11058
|
for (let i = 0; i < px.length; i++) {
|
|
10661
11059
|
ctx.beginPath();
|
|
10662
11060
|
ctx.arc(px[i], py[i], isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
10663
|
-
ctx.fillStyle =
|
|
11061
|
+
ctx.fillStyle = s.strokeColor;
|
|
10664
11062
|
ctx.fill();
|
|
10665
|
-
ctx.fillStyle =
|
|
11063
|
+
ctx.fillStyle = s.strokeColor;
|
|
10666
11064
|
ctx.fillText(LABELS5[i] ?? String(i), px[i] + 5, py[i] - 5);
|
|
10667
11065
|
}
|
|
10668
11066
|
ctx.restore();
|
|
@@ -10704,16 +11102,17 @@ var Brush = class {
|
|
|
10704
11102
|
this.points = [first, { ...first }];
|
|
10705
11103
|
this.complete = false;
|
|
10706
11104
|
}
|
|
10707
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11105
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10708
11106
|
if (this.points.length < 2) return;
|
|
10709
11107
|
const pixelPts = this.points.map((p) => ({
|
|
10710
11108
|
x: scale.xToPixel(p.barIndex, viewport, chartW),
|
|
10711
11109
|
y: scale.yToPixel(p.price, priceRange, chartH)
|
|
10712
11110
|
}));
|
|
10713
11111
|
const solidCount = this.complete ? pixelPts.length : pixelPts.length - 1;
|
|
11112
|
+
const s = resolveStyle("brush", this.style, theme);
|
|
10714
11113
|
ctx.save();
|
|
10715
|
-
ctx.strokeStyle =
|
|
10716
|
-
ctx.lineWidth = isSelected ?
|
|
11114
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11115
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10717
11116
|
ctx.lineCap = "round";
|
|
10718
11117
|
ctx.lineJoin = "round";
|
|
10719
11118
|
if (solidCount >= 2) {
|
|
@@ -10781,16 +11180,17 @@ var Highlighter = class {
|
|
|
10781
11180
|
this.points = [first, { ...first }];
|
|
10782
11181
|
this.complete = false;
|
|
10783
11182
|
}
|
|
10784
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11183
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10785
11184
|
if (this.points.length < 2) return;
|
|
10786
11185
|
const pixelPts = this.points.map((p) => ({
|
|
10787
11186
|
x: scale.xToPixel(p.barIndex, viewport, chartW),
|
|
10788
11187
|
y: scale.yToPixel(p.price, priceRange, chartH)
|
|
10789
11188
|
}));
|
|
10790
11189
|
const solidCount = this.complete ? pixelPts.length : pixelPts.length - 1;
|
|
11190
|
+
const s = resolveStyle("highlighter", this.style, theme);
|
|
10791
11191
|
ctx.save();
|
|
10792
|
-
ctx.strokeStyle =
|
|
10793
|
-
ctx.lineWidth =
|
|
11192
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, s.fillOpacity);
|
|
11193
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
10794
11194
|
ctx.lineCap = "round";
|
|
10795
11195
|
ctx.lineJoin = "round";
|
|
10796
11196
|
if (solidCount >= 2) {
|
|
@@ -10850,7 +11250,7 @@ var SineLine = class {
|
|
|
10850
11250
|
this.points = [first, { ...first }];
|
|
10851
11251
|
this.complete = false;
|
|
10852
11252
|
}
|
|
10853
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11253
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10854
11254
|
if (this.points.length < 2) return;
|
|
10855
11255
|
const [p1, p2] = this.points;
|
|
10856
11256
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -10860,9 +11260,11 @@ var SineLine = class {
|
|
|
10860
11260
|
const period = x2 - x1;
|
|
10861
11261
|
const amplitude = Math.abs(y2 - y1) / 2;
|
|
10862
11262
|
const midY = (y1 + y2) / 2;
|
|
11263
|
+
const s = resolveStyle("sineLine", this.style, theme);
|
|
10863
11264
|
ctx.save();
|
|
10864
|
-
ctx.strokeStyle =
|
|
10865
|
-
ctx.lineWidth = isSelected ?
|
|
11265
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11266
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
11267
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10866
11268
|
ctx.beginPath();
|
|
10867
11269
|
if (Math.abs(period) < 1) {
|
|
10868
11270
|
ctx.moveTo(x1, y1);
|
|
@@ -10879,7 +11281,8 @@ var SineLine = class {
|
|
|
10879
11281
|
ctx.lineTo(x2, midY + amplitude * Math.sin(2 * Math.PI));
|
|
10880
11282
|
}
|
|
10881
11283
|
ctx.stroke();
|
|
10882
|
-
ctx.
|
|
11284
|
+
ctx.setLineDash([]);
|
|
11285
|
+
ctx.fillStyle = s.strokeColor;
|
|
10883
11286
|
for (const [x, y] of [[x1, y1], [x2, y2]]) {
|
|
10884
11287
|
ctx.beginPath();
|
|
10885
11288
|
ctx.arc(x, y, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -10945,17 +11348,17 @@ var CyclicLines = class {
|
|
|
10945
11348
|
this.points = [first, { ...first }];
|
|
10946
11349
|
this.complete = false;
|
|
10947
11350
|
}
|
|
10948
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11351
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
10949
11352
|
if (this.points.length < 2) return;
|
|
10950
11353
|
const [p1, p2] = this.points;
|
|
10951
11354
|
const cycleLen = Math.abs(p2.barIndex - p1.barIndex);
|
|
10952
11355
|
if (cycleLen === 0) return;
|
|
10953
11356
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
10954
|
-
const
|
|
11357
|
+
const s = resolveStyle("cyclicLines", this.style, theme);
|
|
10955
11358
|
ctx.save();
|
|
10956
|
-
ctx.strokeStyle =
|
|
10957
|
-
ctx.lineWidth = isSelected ?
|
|
10958
|
-
ctx.
|
|
11359
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11360
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
11361
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
10959
11362
|
for (let i = 0; i <= 8; i++) {
|
|
10960
11363
|
const barIdx = p1.barIndex + cycleLen * i;
|
|
10961
11364
|
const x = scale.xToPixel(barIdx, viewport, chartW);
|
|
@@ -10967,7 +11370,7 @@ var CyclicLines = class {
|
|
|
10967
11370
|
}
|
|
10968
11371
|
ctx.setLineDash([]);
|
|
10969
11372
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
10970
|
-
ctx.fillStyle =
|
|
11373
|
+
ctx.fillStyle = s.strokeColor;
|
|
10971
11374
|
ctx.beginPath();
|
|
10972
11375
|
ctx.arc(x1, chartH / 2, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
10973
11376
|
ctx.fill();
|
|
@@ -11014,16 +11417,17 @@ var TimeCycles = class {
|
|
|
11014
11417
|
this.points = [first, { ...first }];
|
|
11015
11418
|
this.complete = false;
|
|
11016
11419
|
}
|
|
11017
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11420
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
11018
11421
|
if (this.points.length < 2) return;
|
|
11019
11422
|
const [p1, p2] = this.points;
|
|
11020
11423
|
const cycleLen = Math.abs(p2.barIndex - p1.barIndex);
|
|
11021
11424
|
if (cycleLen === 0) return;
|
|
11022
|
-
const
|
|
11425
|
+
const s = resolveStyle("timeCycles", this.style, theme);
|
|
11023
11426
|
const maxLines = 6;
|
|
11427
|
+
const bandFill = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
11024
11428
|
ctx.save();
|
|
11025
11429
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
11026
|
-
ctx.lineWidth = isSelected ?
|
|
11430
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
11027
11431
|
const lineXs = [];
|
|
11028
11432
|
for (let i = 0; i <= maxLines; i++) {
|
|
11029
11433
|
const barIdx = p1.barIndex + cycleLen * i;
|
|
@@ -11033,23 +11437,24 @@ var TimeCycles = class {
|
|
|
11033
11437
|
}
|
|
11034
11438
|
for (let i = 0; i + 1 < lineXs.length; i++) {
|
|
11035
11439
|
if (i % 2 === 0) {
|
|
11036
|
-
ctx.fillStyle =
|
|
11440
|
+
ctx.fillStyle = bandFill;
|
|
11037
11441
|
ctx.fillRect(lineXs[i], 0, lineXs[i + 1] - lineXs[i], chartH);
|
|
11038
11442
|
}
|
|
11039
11443
|
}
|
|
11040
|
-
ctx.strokeStyle =
|
|
11041
|
-
ctx.
|
|
11444
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11445
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
11042
11446
|
for (let i = 0; i < lineXs.length; i++) {
|
|
11043
11447
|
ctx.beginPath();
|
|
11044
11448
|
ctx.moveTo(lineXs[i], 0);
|
|
11045
11449
|
ctx.lineTo(lineXs[i], chartH);
|
|
11046
11450
|
ctx.stroke();
|
|
11047
|
-
ctx.fillStyle =
|
|
11451
|
+
ctx.fillStyle = s.strokeColor;
|
|
11048
11452
|
ctx.fillText(String(i + 1), lineXs[i] + 3, 14);
|
|
11049
11453
|
}
|
|
11454
|
+
ctx.setLineDash([]);
|
|
11050
11455
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
11051
11456
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
11052
|
-
ctx.fillStyle =
|
|
11457
|
+
ctx.fillStyle = s.strokeColor;
|
|
11053
11458
|
ctx.beginPath();
|
|
11054
11459
|
ctx.arc(x1, chartH / 2, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
11055
11460
|
ctx.fill();
|
|
@@ -11108,14 +11513,14 @@ var Projection = class {
|
|
|
11108
11513
|
this.points = [first, { ...first }];
|
|
11109
11514
|
this.complete = false;
|
|
11110
11515
|
}
|
|
11111
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11516
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
11112
11517
|
if (this.points.length < 2) return;
|
|
11113
11518
|
const [p1, p2] = this.points;
|
|
11114
11519
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
11115
11520
|
const y1 = scale.yToPixel(p1.price, priceRange, chartH);
|
|
11116
11521
|
const x2 = scale.xToPixel(p2.barIndex, viewport, chartW);
|
|
11117
11522
|
const y2 = scale.yToPixel(p2.price, priceRange, chartH);
|
|
11118
|
-
const
|
|
11523
|
+
const s = resolveStyle("projection", this.style, theme);
|
|
11119
11524
|
const dxLine = x2 - x1;
|
|
11120
11525
|
let xEnd;
|
|
11121
11526
|
let yEnd;
|
|
@@ -11128,15 +11533,15 @@ var Projection = class {
|
|
|
11128
11533
|
yEnd = y2 + slope * (chartW - x2);
|
|
11129
11534
|
}
|
|
11130
11535
|
ctx.save();
|
|
11131
|
-
ctx.strokeStyle =
|
|
11132
|
-
ctx.fillStyle =
|
|
11133
|
-
ctx.lineWidth = isSelected ?
|
|
11536
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11537
|
+
ctx.fillStyle = s.strokeColor;
|
|
11538
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
11134
11539
|
ctx.setLineDash([]);
|
|
11135
11540
|
ctx.beginPath();
|
|
11136
11541
|
ctx.moveTo(x1, y1);
|
|
11137
11542
|
ctx.lineTo(x2, y2);
|
|
11138
11543
|
ctx.stroke();
|
|
11139
|
-
ctx.
|
|
11544
|
+
applyLineStyle(ctx, s.lineStyle === "solid" ? "dashed" : s.lineStyle, ctx.lineWidth);
|
|
11140
11545
|
ctx.beginPath();
|
|
11141
11546
|
ctx.moveTo(x2, y2);
|
|
11142
11547
|
ctx.lineTo(xEnd, yEnd);
|
|
@@ -11202,7 +11607,7 @@ var PriceProjection = class {
|
|
|
11202
11607
|
this.points = [first, { ...first }];
|
|
11203
11608
|
this.complete = false;
|
|
11204
11609
|
}
|
|
11205
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2) {
|
|
11610
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, pricePrecision = 2, theme) {
|
|
11206
11611
|
if (this.points.length < 2) return;
|
|
11207
11612
|
const [p1, p2] = this.points;
|
|
11208
11613
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -11217,13 +11622,14 @@ var PriceProjection = class {
|
|
|
11217
11622
|
const h = botY - topY;
|
|
11218
11623
|
const midX = left + w / 2;
|
|
11219
11624
|
const midPrice = (p1.price + p2.price) / 2;
|
|
11220
|
-
const
|
|
11625
|
+
const s = resolveStyle("priceProjection", this.style, theme);
|
|
11221
11626
|
ctx.save();
|
|
11222
11627
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
11223
|
-
ctx.lineWidth = isSelected ?
|
|
11224
|
-
ctx.fillStyle =
|
|
11628
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
11629
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
11225
11630
|
ctx.fillRect(left, topY, w, h);
|
|
11226
|
-
ctx.strokeStyle =
|
|
11631
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11632
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
11227
11633
|
ctx.beginPath();
|
|
11228
11634
|
ctx.moveTo(left, y1);
|
|
11229
11635
|
ctx.lineTo(right, y1);
|
|
@@ -11237,11 +11643,12 @@ var PriceProjection = class {
|
|
|
11237
11643
|
ctx.moveTo(left, y2);
|
|
11238
11644
|
ctx.lineTo(right, y2);
|
|
11239
11645
|
ctx.stroke();
|
|
11240
|
-
ctx.
|
|
11646
|
+
ctx.setLineDash([]);
|
|
11647
|
+
ctx.fillStyle = s.strokeColor;
|
|
11241
11648
|
ctx.fillText(p1.price.toFixed(pricePrecision), right + 4, y1 + 4);
|
|
11242
11649
|
ctx.fillText(midPrice.toFixed(pricePrecision), right + 4, yMid + 4);
|
|
11243
11650
|
ctx.fillText(p2.price.toFixed(pricePrecision), right + 4, y2 + 4);
|
|
11244
|
-
ctx.fillStyle =
|
|
11651
|
+
ctx.fillStyle = s.strokeColor;
|
|
11245
11652
|
ctx.beginPath();
|
|
11246
11653
|
ctx.moveTo(midX, topY);
|
|
11247
11654
|
ctx.lineTo(midX, botY);
|
|
@@ -11280,7 +11687,6 @@ var PriceProjection = class {
|
|
|
11280
11687
|
|
|
11281
11688
|
// src/drawings/GhostFeed.ts
|
|
11282
11689
|
var HIT_TOLERANCE68 = 6;
|
|
11283
|
-
var COLOR19 = "#90a4ae";
|
|
11284
11690
|
var GhostFeed = class {
|
|
11285
11691
|
constructor(first) {
|
|
11286
11692
|
this.type = "ghostFeed";
|
|
@@ -11288,7 +11694,7 @@ var GhostFeed = class {
|
|
|
11288
11694
|
this.points = [first, { ...first }];
|
|
11289
11695
|
this.complete = false;
|
|
11290
11696
|
}
|
|
11291
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected) {
|
|
11697
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, isSelected, _pricePrecision, theme) {
|
|
11292
11698
|
if (this.points.length < 2) return;
|
|
11293
11699
|
const [p1, p2] = this.points;
|
|
11294
11700
|
const x1 = scale.xToPixel(p1.barIndex, viewport, chartW);
|
|
@@ -11299,19 +11705,20 @@ var GhostFeed = class {
|
|
|
11299
11705
|
const ry = Math.min(y1, y2);
|
|
11300
11706
|
const rw = Math.abs(x2 - x1);
|
|
11301
11707
|
const rh = Math.abs(y2 - y1);
|
|
11708
|
+
const s = resolveStyle("ghostFeed", this.style, theme);
|
|
11302
11709
|
ctx.save();
|
|
11303
|
-
ctx.fillStyle =
|
|
11710
|
+
ctx.fillStyle = hexWithAlpha(s.fillColor, s.fillOpacity);
|
|
11304
11711
|
ctx.fillRect(rx, ry, rw, rh);
|
|
11305
|
-
ctx.strokeStyle =
|
|
11306
|
-
ctx.lineWidth = isSelected ?
|
|
11307
|
-
ctx.
|
|
11712
|
+
ctx.strokeStyle = s.strokeColor;
|
|
11713
|
+
ctx.lineWidth = isSelected ? s.lineWidth + 0.5 : s.lineWidth;
|
|
11714
|
+
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, ctx.lineWidth);
|
|
11308
11715
|
ctx.strokeRect(rx, ry, rw, rh);
|
|
11309
11716
|
ctx.setLineDash([]);
|
|
11310
11717
|
ctx.save();
|
|
11311
11718
|
ctx.beginPath();
|
|
11312
11719
|
ctx.rect(rx, ry, rw, rh);
|
|
11313
11720
|
ctx.clip();
|
|
11314
|
-
ctx.strokeStyle =
|
|
11721
|
+
ctx.strokeStyle = hexWithAlpha(s.strokeColor, 0.19);
|
|
11315
11722
|
ctx.lineWidth = 1;
|
|
11316
11723
|
ctx.setLineDash([4, 4]);
|
|
11317
11724
|
const step = 20;
|
|
@@ -11329,7 +11736,7 @@ var GhostFeed = class {
|
|
|
11329
11736
|
ctx.setLineDash([]);
|
|
11330
11737
|
ctx.restore();
|
|
11331
11738
|
ctx.font = "12px 'Inter', system-ui, sans-serif";
|
|
11332
|
-
ctx.fillStyle =
|
|
11739
|
+
ctx.fillStyle = s.strokeColor;
|
|
11333
11740
|
ctx.textAlign = "center";
|
|
11334
11741
|
ctx.textBaseline = "middle";
|
|
11335
11742
|
ctx.fillText("Ghost Feed", rx + rw / 2, ry + rh / 2);
|
|
@@ -11339,7 +11746,7 @@ var GhostFeed = class {
|
|
|
11339
11746
|
for (const [cx, cy] of [[x1, y1], [x2, y2]]) {
|
|
11340
11747
|
ctx.beginPath();
|
|
11341
11748
|
ctx.arc(cx, cy, 3.5, 0, Math.PI * 2);
|
|
11342
|
-
ctx.fillStyle =
|
|
11749
|
+
ctx.fillStyle = s.strokeColor;
|
|
11343
11750
|
ctx.fill();
|
|
11344
11751
|
}
|
|
11345
11752
|
}
|
|
@@ -11397,6 +11804,8 @@ var DrawingManager = class {
|
|
|
11397
11804
|
* original point's timestamp.
|
|
11398
11805
|
*/
|
|
11399
11806
|
this.onPointsMutated = null;
|
|
11807
|
+
/** Called whenever the selected drawing changes (including to/from null). */
|
|
11808
|
+
this.onSelectionChange = null;
|
|
11400
11809
|
/** Set to true by ChartEngine while the text input is focused — prevents Delete/Backspace from deleting the drawing. */
|
|
11401
11810
|
this.editingText = false;
|
|
11402
11811
|
// Drag state (move whole drawing)
|
|
@@ -11407,6 +11816,12 @@ var DrawingManager = class {
|
|
|
11407
11816
|
this.resizeDrawing = null;
|
|
11408
11817
|
this.resizePointIndex = -1;
|
|
11409
11818
|
}
|
|
11819
|
+
/** Centralizes selection mutation + change notification. */
|
|
11820
|
+
setSelected(drawing) {
|
|
11821
|
+
if (this.selectedDrawing === drawing) return;
|
|
11822
|
+
this.selectedDrawing = drawing;
|
|
11823
|
+
this.onSelectionChange?.(drawing);
|
|
11824
|
+
}
|
|
11410
11825
|
/** Called by ChartEngine to be notified when a drawing is finished. */
|
|
11411
11826
|
setOnComplete(cb) {
|
|
11412
11827
|
this.onCompleteCallback = cb;
|
|
@@ -11439,7 +11854,7 @@ var DrawingManager = class {
|
|
|
11439
11854
|
this.drawings.push(this.activeDrawing);
|
|
11440
11855
|
if (this.activeDrawing.complete) {
|
|
11441
11856
|
const completed = this.activeDrawing;
|
|
11442
|
-
this.
|
|
11857
|
+
this.setSelected(completed);
|
|
11443
11858
|
this.activeDrawing = null;
|
|
11444
11859
|
this.activeTool = null;
|
|
11445
11860
|
this.onCompleteCallback?.();
|
|
@@ -11469,7 +11884,7 @@ var DrawingManager = class {
|
|
|
11469
11884
|
}
|
|
11470
11885
|
this.activeDrawing.complete = true;
|
|
11471
11886
|
const completed = this.activeDrawing;
|
|
11472
|
-
this.
|
|
11887
|
+
this.setSelected(completed);
|
|
11473
11888
|
this.activeDrawing = null;
|
|
11474
11889
|
this.state = "idle";
|
|
11475
11890
|
this.activeTool = null;
|
|
@@ -11524,7 +11939,7 @@ var DrawingManager = class {
|
|
|
11524
11939
|
return;
|
|
11525
11940
|
}
|
|
11526
11941
|
this.activeDrawing.complete = true;
|
|
11527
|
-
this.
|
|
11942
|
+
this.setSelected(this.activeDrawing);
|
|
11528
11943
|
this.activeDrawing = null;
|
|
11529
11944
|
this.state = "idle";
|
|
11530
11945
|
this.activeTool = null;
|
|
@@ -11598,11 +12013,35 @@ var DrawingManager = class {
|
|
|
11598
12013
|
return null;
|
|
11599
12014
|
}
|
|
11600
12015
|
selectDrawing(drawing) {
|
|
11601
|
-
this.
|
|
12016
|
+
this.setSelected(drawing);
|
|
11602
12017
|
}
|
|
11603
12018
|
getSelectedDrawing() {
|
|
11604
12019
|
return this.selectedDrawing;
|
|
11605
12020
|
}
|
|
12021
|
+
/** Returns a drawing by id, or null if not found. */
|
|
12022
|
+
getDrawingById(id) {
|
|
12023
|
+
return this.drawings.find((d) => d.id === id) ?? null;
|
|
12024
|
+
}
|
|
12025
|
+
/**
|
|
12026
|
+
* Shallow-merge a style patch into the drawing's `style` field. Triggers
|
|
12027
|
+
* `onDrawingsChange` so the chart re-renders. Returns the updated drawing,
|
|
12028
|
+
* or null if no drawing matches `id`.
|
|
12029
|
+
*/
|
|
12030
|
+
updateDrawingStyle(id, patch) {
|
|
12031
|
+
const drawing = this.getDrawingById(id);
|
|
12032
|
+
if (!drawing) return null;
|
|
12033
|
+
drawing.style = { ...drawing.style ?? {}, ...patch };
|
|
12034
|
+
const patchLevels = patch.levels;
|
|
12035
|
+
const existingLevels = drawing.style.levels;
|
|
12036
|
+
if (patchLevels && existingLevels) {
|
|
12037
|
+
drawing.style.levels = {
|
|
12038
|
+
...existingLevels,
|
|
12039
|
+
...patchLevels
|
|
12040
|
+
};
|
|
12041
|
+
}
|
|
12042
|
+
this.notifyChange();
|
|
12043
|
+
return drawing;
|
|
12044
|
+
}
|
|
11606
12045
|
// ── Keyboard shortcuts ────────────────────────────────────────────────────
|
|
11607
12046
|
onKeyDown(key) {
|
|
11608
12047
|
if (key === "Escape") {
|
|
@@ -11612,7 +12051,7 @@ var DrawingManager = class {
|
|
|
11612
12051
|
this.state = "idle";
|
|
11613
12052
|
}
|
|
11614
12053
|
this.activeTool = null;
|
|
11615
|
-
this.
|
|
12054
|
+
this.setSelected(null);
|
|
11616
12055
|
return true;
|
|
11617
12056
|
}
|
|
11618
12057
|
if ((key === "Delete" || key === "Backspace") && this.selectedDrawing && !this.editingText) {
|
|
@@ -11629,7 +12068,7 @@ var DrawingManager = class {
|
|
|
11629
12068
|
if (!this.selectedDrawing) return;
|
|
11630
12069
|
this.snapshot();
|
|
11631
12070
|
this.drawings = this.drawings.filter((d) => d !== this.selectedDrawing);
|
|
11632
|
-
this.
|
|
12071
|
+
this.setSelected(null);
|
|
11633
12072
|
this.notifyChange();
|
|
11634
12073
|
}
|
|
11635
12074
|
undo() {
|
|
@@ -11637,7 +12076,7 @@ var DrawingManager = class {
|
|
|
11637
12076
|
if (prev !== void 0) {
|
|
11638
12077
|
this.redoStack.push(this.cloneDrawings(this.drawings));
|
|
11639
12078
|
this.drawings = prev;
|
|
11640
|
-
this.
|
|
12079
|
+
this.setSelected(null);
|
|
11641
12080
|
this.notifyChange();
|
|
11642
12081
|
}
|
|
11643
12082
|
}
|
|
@@ -11646,7 +12085,7 @@ var DrawingManager = class {
|
|
|
11646
12085
|
if (next !== void 0) {
|
|
11647
12086
|
this.history.push(this.cloneDrawings(this.drawings));
|
|
11648
12087
|
this.drawings = next;
|
|
11649
|
-
this.
|
|
12088
|
+
this.setSelected(null);
|
|
11650
12089
|
this.notifyChange();
|
|
11651
12090
|
}
|
|
11652
12091
|
}
|
|
@@ -11654,7 +12093,7 @@ var DrawingManager = class {
|
|
|
11654
12093
|
if (this.drawings.length === 0) return;
|
|
11655
12094
|
this.snapshot();
|
|
11656
12095
|
for (const d of this.drawings) d.visible = false;
|
|
11657
|
-
this.
|
|
12096
|
+
this.setSelected(null);
|
|
11658
12097
|
this.notifyChange();
|
|
11659
12098
|
}
|
|
11660
12099
|
showAll() {
|
|
@@ -11667,7 +12106,7 @@ var DrawingManager = class {
|
|
|
11667
12106
|
if (this.drawings.length === 0) return;
|
|
11668
12107
|
this.snapshot();
|
|
11669
12108
|
for (const d of this.drawings) d.locked = true;
|
|
11670
|
-
this.
|
|
12109
|
+
this.setSelected(null);
|
|
11671
12110
|
this.notifyChange();
|
|
11672
12111
|
}
|
|
11673
12112
|
unlockAll() {
|
|
@@ -11677,7 +12116,7 @@ var DrawingManager = class {
|
|
|
11677
12116
|
this.notifyChange();
|
|
11678
12117
|
}
|
|
11679
12118
|
// ── Rendering ─────────────────────────────────────────────────────────────
|
|
11680
|
-
render(ctx, scale, viewport, priceRange, chartW, chartH, pricePrecision = 2) {
|
|
12119
|
+
render(ctx, scale, viewport, priceRange, chartW, chartH, pricePrecision = 2, theme) {
|
|
11681
12120
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
11682
12121
|
if (this.drawings.length === 0) return;
|
|
11683
12122
|
const dpr = window.devicePixelRatio || 1;
|
|
@@ -11685,7 +12124,7 @@ var DrawingManager = class {
|
|
|
11685
12124
|
ctx.scale(dpr, dpr);
|
|
11686
12125
|
for (const drawing of this.drawings) {
|
|
11687
12126
|
if (drawing.visible === false) continue;
|
|
11688
|
-
drawing.render(ctx, scale, viewport, priceRange, chartW, chartH, drawing === this.selectedDrawing, pricePrecision);
|
|
12127
|
+
drawing.render(ctx, scale, viewport, priceRange, chartW, chartH, drawing === this.selectedDrawing, pricePrecision, theme);
|
|
11689
12128
|
}
|
|
11690
12129
|
ctx.restore();
|
|
11691
12130
|
}
|
|
@@ -11701,7 +12140,8 @@ var DrawingManager = class {
|
|
|
11701
12140
|
complete: d.complete,
|
|
11702
12141
|
...d.visible !== void 0 && { visible: d.visible },
|
|
11703
12142
|
...d.locked !== void 0 && { locked: d.locked },
|
|
11704
|
-
...d.text !== void 0 && { text: d.text }
|
|
12143
|
+
...d.text !== void 0 && { text: d.text },
|
|
12144
|
+
...d.style !== void 0 && { style: { ...d.style } }
|
|
11705
12145
|
}));
|
|
11706
12146
|
}
|
|
11707
12147
|
/** Restores drawings from a serialized snapshot, replacing current drawings. */
|
|
@@ -11714,9 +12154,10 @@ var DrawingManager = class {
|
|
|
11714
12154
|
if (sd.visible !== void 0) drawing.visible = sd.visible;
|
|
11715
12155
|
if (sd.locked !== void 0) drawing.locked = sd.locked;
|
|
11716
12156
|
if (sd.text !== void 0) drawing.text = sd.text;
|
|
12157
|
+
if (sd.style !== void 0) drawing.style = { ...sd.style };
|
|
11717
12158
|
return drawing;
|
|
11718
12159
|
});
|
|
11719
|
-
this.
|
|
12160
|
+
this.setSelected(null);
|
|
11720
12161
|
this.activeDrawing = null;
|
|
11721
12162
|
this.history = [this.drawings.map((d) => ({ ...d, points: d.points.map((p) => ({ ...p })) }))];
|
|
11722
12163
|
this.redoStack = [];
|
|
@@ -11726,7 +12167,7 @@ var DrawingManager = class {
|
|
|
11726
12167
|
if (this.drawings.length === 0) return;
|
|
11727
12168
|
this.snapshot();
|
|
11728
12169
|
this.drawings = [];
|
|
11729
|
-
this.
|
|
12170
|
+
this.setSelected(null);
|
|
11730
12171
|
this.activeDrawing = null;
|
|
11731
12172
|
this.notifyChange();
|
|
11732
12173
|
}
|
|
@@ -12856,30 +13297,48 @@ var ChartSettingsDialog = class {
|
|
|
12856
13297
|
}
|
|
12857
13298
|
this.content.appendChild(this.makeSectionHeader("Colors"));
|
|
12858
13299
|
this.content.appendChild(this.buildColorRows());
|
|
13300
|
+
this.content.appendChild(this.makeSectionHeader("Chart elements"));
|
|
13301
|
+
this.content.appendChild(
|
|
13302
|
+
this.makeHorizontalSettingRow(
|
|
13303
|
+
"Price-axis countdown",
|
|
13304
|
+
"Show the candle close countdown on the right price axis.",
|
|
13305
|
+
this.makeBooleanToggle(this.current.showPriceAxisCountdown, (v) => {
|
|
13306
|
+
this.current.showPriceAxisCountdown = v;
|
|
13307
|
+
this.emit();
|
|
13308
|
+
})
|
|
13309
|
+
)
|
|
13310
|
+
);
|
|
12859
13311
|
}
|
|
12860
|
-
|
|
12861
|
-
|
|
13312
|
+
/**
|
|
13313
|
+
* Reusable on/off toggle switch (track + animated thumb). Optional left/right
|
|
13314
|
+
* side-labels animate their weight and color as the switch flips.
|
|
13315
|
+
*/
|
|
13316
|
+
makeBooleanToggle(initial, onChange, sideLabels) {
|
|
13317
|
+
let value = initial;
|
|
12862
13318
|
const wrap = document.createElement("div");
|
|
12863
13319
|
Object.assign(wrap.style, {
|
|
12864
13320
|
display: "flex",
|
|
12865
13321
|
alignItems: "center",
|
|
12866
13322
|
gap: "8px"
|
|
12867
13323
|
});
|
|
12868
|
-
const
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
|
|
13324
|
+
const offLbl = sideLabels ? document.createElement("span") : null;
|
|
13325
|
+
const onLbl = sideLabels ? document.createElement("span") : null;
|
|
13326
|
+
if (offLbl && onLbl && sideLabels) {
|
|
13327
|
+
offLbl.textContent = sideLabels.off;
|
|
13328
|
+
onLbl.textContent = sideLabels.on;
|
|
13329
|
+
}
|
|
12872
13330
|
const updateLabels = () => {
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
|
|
13331
|
+
if (!offLbl || !onLbl) return;
|
|
13332
|
+
Object.assign(offLbl.style, {
|
|
13333
|
+
color: value ? this.theme.axisText : this.theme.tooltip.text,
|
|
13334
|
+
fontWeight: value ? "400" : "500",
|
|
12876
13335
|
fontSize: "11px",
|
|
12877
13336
|
cursor: "pointer",
|
|
12878
13337
|
userSelect: "none"
|
|
12879
13338
|
});
|
|
12880
|
-
Object.assign(
|
|
12881
|
-
color:
|
|
12882
|
-
fontWeight:
|
|
13339
|
+
Object.assign(onLbl.style, {
|
|
13340
|
+
color: value ? this.theme.tooltip.text : this.theme.axisText,
|
|
13341
|
+
fontWeight: value ? "500" : "400",
|
|
12883
13342
|
fontSize: "11px",
|
|
12884
13343
|
cursor: "pointer",
|
|
12885
13344
|
userSelect: "none"
|
|
@@ -12902,7 +13361,7 @@ var ChartSettingsDialog = class {
|
|
|
12902
13361
|
Object.assign(thumb.style, {
|
|
12903
13362
|
position: "absolute",
|
|
12904
13363
|
top: "2px",
|
|
12905
|
-
left:
|
|
13364
|
+
left: value ? "18px" : "2px",
|
|
12906
13365
|
width: "14px",
|
|
12907
13366
|
height: "14px",
|
|
12908
13367
|
borderRadius: "50%",
|
|
@@ -12912,24 +13371,39 @@ var ChartSettingsDialog = class {
|
|
|
12912
13371
|
});
|
|
12913
13372
|
track.appendChild(thumb);
|
|
12914
13373
|
const doToggle = () => {
|
|
12915
|
-
|
|
12916
|
-
thumb.style.left =
|
|
12917
|
-
this.current.theme = isLight ? "light" : "dark";
|
|
13374
|
+
value = !value;
|
|
13375
|
+
thumb.style.left = value ? "18px" : "2px";
|
|
12918
13376
|
updateLabels();
|
|
12919
|
-
|
|
12920
|
-
this.renderContent();
|
|
13377
|
+
onChange(value);
|
|
12921
13378
|
};
|
|
12922
13379
|
track.addEventListener("click", doToggle);
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
|
|
12926
|
-
|
|
12927
|
-
|
|
12928
|
-
|
|
13380
|
+
if (offLbl && onLbl) {
|
|
13381
|
+
offLbl.addEventListener("click", () => {
|
|
13382
|
+
if (value) doToggle();
|
|
13383
|
+
});
|
|
13384
|
+
onLbl.addEventListener("click", () => {
|
|
13385
|
+
if (!value) doToggle();
|
|
13386
|
+
});
|
|
13387
|
+
}
|
|
12929
13388
|
updateLabels();
|
|
12930
|
-
|
|
13389
|
+
if (offLbl && onLbl) {
|
|
13390
|
+
wrap.append(offLbl, track, onLbl);
|
|
13391
|
+
} else {
|
|
13392
|
+
wrap.append(track);
|
|
13393
|
+
}
|
|
12931
13394
|
return wrap;
|
|
12932
13395
|
}
|
|
13396
|
+
makeThemeToggle() {
|
|
13397
|
+
return this.makeBooleanToggle(
|
|
13398
|
+
this.current.theme === "light",
|
|
13399
|
+
(isLight) => {
|
|
13400
|
+
this.current.theme = isLight ? "light" : "dark";
|
|
13401
|
+
this.emit();
|
|
13402
|
+
this.renderContent();
|
|
13403
|
+
},
|
|
13404
|
+
{ off: "Dark", on: "Light" }
|
|
13405
|
+
);
|
|
13406
|
+
}
|
|
12933
13407
|
/**
|
|
12934
13408
|
* Renders all canvas colors as a label → swatch(es) grid.
|
|
12935
13409
|
* All swatch columns are pixel-aligned regardless of label length.
|
|
@@ -13101,6 +13575,16 @@ var ChartSettingsDialog = class {
|
|
|
13101
13575
|
)
|
|
13102
13576
|
)
|
|
13103
13577
|
);
|
|
13578
|
+
this.content.appendChild(
|
|
13579
|
+
this.makeHorizontalSettingRow(
|
|
13580
|
+
"Always show SL/TP",
|
|
13581
|
+
"Keep stop-loss and take-profit lines visible without hovering.",
|
|
13582
|
+
this.makeBooleanToggle(this.current.showTradeLevelsAlways, (v) => {
|
|
13583
|
+
this.current.showTradeLevelsAlways = v;
|
|
13584
|
+
this.emit();
|
|
13585
|
+
})
|
|
13586
|
+
)
|
|
13587
|
+
);
|
|
13104
13588
|
}
|
|
13105
13589
|
// ── Private – helpers ─────────────────────────────────────────────────────
|
|
13106
13590
|
makeSectionHeader(label, first = false) {
|
|
@@ -13155,6 +13639,54 @@ var ChartSettingsDialog = class {
|
|
|
13155
13639
|
row.append(textGroup, control);
|
|
13156
13640
|
return row;
|
|
13157
13641
|
}
|
|
13642
|
+
/**
|
|
13643
|
+
* Like makeVerticalSettingRow, but the control is placed on the right side
|
|
13644
|
+
* on the same line as the label, vertically centered.
|
|
13645
|
+
*/
|
|
13646
|
+
makeHorizontalSettingRow(label, desc, control) {
|
|
13647
|
+
const row = document.createElement("div");
|
|
13648
|
+
Object.assign(row.style, {
|
|
13649
|
+
display: "flex",
|
|
13650
|
+
flexDirection: "row",
|
|
13651
|
+
alignItems: "center",
|
|
13652
|
+
justifyContent: "space-between",
|
|
13653
|
+
gap: "12px",
|
|
13654
|
+
padding: "14px 0",
|
|
13655
|
+
borderBottom: `1px solid ${this.theme.axisBorder}`
|
|
13656
|
+
});
|
|
13657
|
+
const textGroup = document.createElement("div");
|
|
13658
|
+
Object.assign(textGroup.style, {
|
|
13659
|
+
display: "flex",
|
|
13660
|
+
flexDirection: "column",
|
|
13661
|
+
gap: "0px",
|
|
13662
|
+
flex: "1",
|
|
13663
|
+
minWidth: "0"
|
|
13664
|
+
});
|
|
13665
|
+
const labelEl = document.createElement("span");
|
|
13666
|
+
labelEl.textContent = label;
|
|
13667
|
+
Object.assign(labelEl.style, {
|
|
13668
|
+
color: this.theme.tooltip.text,
|
|
13669
|
+
fontSize: "12px",
|
|
13670
|
+
fontWeight: "500"
|
|
13671
|
+
});
|
|
13672
|
+
const descEl = document.createElement("span");
|
|
13673
|
+
descEl.textContent = desc;
|
|
13674
|
+
Object.assign(descEl.style, {
|
|
13675
|
+
color: this.theme.axisText,
|
|
13676
|
+
fontSize: "10px"
|
|
13677
|
+
});
|
|
13678
|
+
textGroup.append(labelEl, descEl);
|
|
13679
|
+
const controlWrap = document.createElement("div");
|
|
13680
|
+
Object.assign(controlWrap.style, {
|
|
13681
|
+
display: "flex",
|
|
13682
|
+
alignItems: "center",
|
|
13683
|
+
justifyContent: "center",
|
|
13684
|
+
flexShrink: "0"
|
|
13685
|
+
});
|
|
13686
|
+
controlWrap.appendChild(control);
|
|
13687
|
+
row.append(textGroup, controlWrap);
|
|
13688
|
+
return row;
|
|
13689
|
+
}
|
|
13158
13690
|
/** Segmented control (pill-style button group). */
|
|
13159
13691
|
makeSegmented(options, current, onChange) {
|
|
13160
13692
|
const wrap = document.createElement("div");
|
|
@@ -13471,6 +14003,518 @@ var TradePopover = class {
|
|
|
13471
14003
|
}
|
|
13472
14004
|
};
|
|
13473
14005
|
|
|
14006
|
+
// src/ui/DrawingStylePopover.ts
|
|
14007
|
+
var POPOVER_CONTROLS = {
|
|
14008
|
+
// Lines & channels
|
|
14009
|
+
horizontalLine: ["stroke", "width", "style"],
|
|
14010
|
+
verticalLine: ["stroke", "width", "style"],
|
|
14011
|
+
crossLine: ["stroke", "width", "style"],
|
|
14012
|
+
trendLine: ["stroke", "width", "style"],
|
|
14013
|
+
trendAngle: ["stroke", "width", "style"],
|
|
14014
|
+
parallelChannel: ["stroke", "width", "style", "fill"],
|
|
14015
|
+
flatChannel: ["stroke", "width", "style", "fill"],
|
|
14016
|
+
disjointChannel: ["stroke", "width", "style"],
|
|
14017
|
+
// Geometric shapes
|
|
14018
|
+
rectangle: ["stroke", "width", "style", "fill"],
|
|
14019
|
+
rotatedRectangle: ["stroke", "width", "style", "fill"],
|
|
14020
|
+
ellipse: ["stroke", "width", "style", "fill"],
|
|
14021
|
+
circle: ["stroke", "width", "style", "fill"],
|
|
14022
|
+
triangle: ["stroke", "width", "style", "fill"],
|
|
14023
|
+
arc: ["stroke", "width", "style"],
|
|
14024
|
+
path: ["stroke", "width", "style"],
|
|
14025
|
+
polyline: ["stroke", "width", "style"],
|
|
14026
|
+
sineLine: ["stroke", "width", "style"],
|
|
14027
|
+
brush: ["stroke", "width"],
|
|
14028
|
+
highlighter: ["stroke", "width"],
|
|
14029
|
+
// Fib (per-level)
|
|
14030
|
+
fibRetracement: ["width", "style", "fibLevels"],
|
|
14031
|
+
fibExtension: ["width", "style", "fibLevels"],
|
|
14032
|
+
fibProjection: ["width", "style", "fibLevels"],
|
|
14033
|
+
fibChannel: ["stroke", "width", "style"],
|
|
14034
|
+
fibFan: ["stroke", "width", "style"],
|
|
14035
|
+
fibCircles: ["stroke", "width", "style"],
|
|
14036
|
+
fibSpiral: ["stroke", "width", "style"],
|
|
14037
|
+
fibTimezone: ["stroke", "width", "style"],
|
|
14038
|
+
// Gann
|
|
14039
|
+
gannFan: ["stroke", "width", "style"],
|
|
14040
|
+
gannSquare: ["stroke", "width", "style"],
|
|
14041
|
+
gannBox: ["stroke", "width", "style", "fill"],
|
|
14042
|
+
// Pitchfork
|
|
14043
|
+
pitchfork: ["stroke", "width", "style"],
|
|
14044
|
+
schiffPitchfork: ["stroke", "width", "style"],
|
|
14045
|
+
modifiedSchiff: ["stroke", "width", "style"],
|
|
14046
|
+
insidePitchfork: ["stroke", "width", "style"],
|
|
14047
|
+
// Text-bearing
|
|
14048
|
+
text: ["text"],
|
|
14049
|
+
callout: ["text", "stroke", "fill"],
|
|
14050
|
+
priceLabel: ["text", "fill"],
|
|
14051
|
+
priceNote: ["text", "stroke"],
|
|
14052
|
+
anchoredNote: ["text", "stroke", "fill"],
|
|
14053
|
+
// Arrows / markers
|
|
14054
|
+
arrowUp: ["stroke", "fill"],
|
|
14055
|
+
arrowDown: ["stroke", "fill"],
|
|
14056
|
+
arrowMarker: ["stroke", "fill"],
|
|
14057
|
+
flag: ["stroke", "fill"],
|
|
14058
|
+
// Volume profile
|
|
14059
|
+
volumeProfile: ["stroke", "fill"],
|
|
14060
|
+
anchoredVP: ["stroke", "fill"],
|
|
14061
|
+
fixedRangeVP: ["stroke", "fill"],
|
|
14062
|
+
// Measurement
|
|
14063
|
+
priceRange: ["stroke", "fill"],
|
|
14064
|
+
dateRange: ["stroke", "width", "style"],
|
|
14065
|
+
datePriceRange: ["stroke", "fill"],
|
|
14066
|
+
ruler: ["stroke", "width", "style"],
|
|
14067
|
+
// Advanced
|
|
14068
|
+
regressionTrend: ["stroke", "width", "style"],
|
|
14069
|
+
projection: ["stroke", "width", "style"],
|
|
14070
|
+
cyclicLines: ["stroke", "width", "style"],
|
|
14071
|
+
timeCycles: ["stroke", "width", "style", "fill"],
|
|
14072
|
+
priceProjection: ["stroke", "fill"],
|
|
14073
|
+
ghostFeed: ["stroke", "fill"]
|
|
14074
|
+
};
|
|
14075
|
+
var LINE_WIDTHS = [1, 2, 3, 4];
|
|
14076
|
+
var FONT_SIZES = [10, 11, 12, 13, 14, 16, 18, 20, 24];
|
|
14077
|
+
var SWATCH_PX = 24;
|
|
14078
|
+
var DrawingStylePopover = class {
|
|
14079
|
+
constructor(container, theme, onChange) {
|
|
14080
|
+
this.currentDrawing = null;
|
|
14081
|
+
this.fibLevelsExpanded = false;
|
|
14082
|
+
this.fibPanel = null;
|
|
14083
|
+
this.container = container;
|
|
14084
|
+
this.theme = theme;
|
|
14085
|
+
this.onChange = onChange;
|
|
14086
|
+
this.el = document.createElement("div");
|
|
14087
|
+
Object.assign(this.el.style, {
|
|
14088
|
+
position: "absolute",
|
|
14089
|
+
display: "none",
|
|
14090
|
+
flexDirection: "row",
|
|
14091
|
+
alignItems: "center",
|
|
14092
|
+
gap: "10px",
|
|
14093
|
+
background: theme.tooltip.background,
|
|
14094
|
+
border: `1px solid ${theme.axisBorder}`,
|
|
14095
|
+
borderRadius: "8px",
|
|
14096
|
+
padding: "8px 10px",
|
|
14097
|
+
zIndex: "50",
|
|
14098
|
+
boxShadow: "0 6px 18px rgba(0,0,0,0.35)",
|
|
14099
|
+
fontFamily: "'Inter', system-ui, sans-serif",
|
|
14100
|
+
fontSize: "11px",
|
|
14101
|
+
color: theme.tooltip.text,
|
|
14102
|
+
pointerEvents: "auto",
|
|
14103
|
+
userSelect: "none",
|
|
14104
|
+
whiteSpace: "nowrap"
|
|
14105
|
+
});
|
|
14106
|
+
container.appendChild(this.el);
|
|
14107
|
+
}
|
|
14108
|
+
setTheme(theme) {
|
|
14109
|
+
this.theme = theme;
|
|
14110
|
+
this.el.style.background = theme.tooltip.background;
|
|
14111
|
+
this.el.style.border = `1px solid ${theme.axisBorder}`;
|
|
14112
|
+
this.el.style.color = theme.tooltip.text;
|
|
14113
|
+
if (this.currentDrawing) this.rebuild();
|
|
14114
|
+
}
|
|
14115
|
+
/** Show the popover for a drawing, positioned at (x, y) in container-relative coords. */
|
|
14116
|
+
show(drawing, x, y) {
|
|
14117
|
+
const wasSameId = this.currentDrawing?.id === drawing.id;
|
|
14118
|
+
this.currentDrawing = drawing;
|
|
14119
|
+
if (!wasSameId) {
|
|
14120
|
+
this.fibLevelsExpanded = false;
|
|
14121
|
+
this.rebuild();
|
|
14122
|
+
}
|
|
14123
|
+
this.el.style.display = "flex";
|
|
14124
|
+
this.setAnchor(x, y);
|
|
14125
|
+
}
|
|
14126
|
+
/** Reposition the popover without rebuilding controls. */
|
|
14127
|
+
setAnchor(x, y) {
|
|
14128
|
+
requestAnimationFrame(() => {
|
|
14129
|
+
const rect = this.el.getBoundingClientRect();
|
|
14130
|
+
const cRect = this.container.getBoundingClientRect();
|
|
14131
|
+
const w = rect.width || 240;
|
|
14132
|
+
const h = rect.height || 56;
|
|
14133
|
+
let left = x - w / 2;
|
|
14134
|
+
let top = y - h - 12;
|
|
14135
|
+
if (top < 4) top = y + 16;
|
|
14136
|
+
left = Math.max(4, Math.min(cRect.width - w - 4, left));
|
|
14137
|
+
top = Math.max(4, Math.min(cRect.height - h - 4, top));
|
|
14138
|
+
this.el.style.left = `${left}px`;
|
|
14139
|
+
this.el.style.top = `${top}px`;
|
|
14140
|
+
});
|
|
14141
|
+
}
|
|
14142
|
+
hide() {
|
|
14143
|
+
this.el.style.display = "none";
|
|
14144
|
+
this.currentDrawing = null;
|
|
14145
|
+
this.fibLevelsExpanded = false;
|
|
14146
|
+
this.fibPanel?.remove();
|
|
14147
|
+
this.fibPanel = null;
|
|
14148
|
+
}
|
|
14149
|
+
destroy() {
|
|
14150
|
+
this.fibPanel?.remove();
|
|
14151
|
+
this.el.remove();
|
|
14152
|
+
this.currentDrawing = null;
|
|
14153
|
+
}
|
|
14154
|
+
// ── Build ────────────────────────────────────────────────────────────────
|
|
14155
|
+
rebuild() {
|
|
14156
|
+
this.el.innerHTML = "";
|
|
14157
|
+
if (!this.currentDrawing) return;
|
|
14158
|
+
const controls = POPOVER_CONTROLS[this.currentDrawing.type] ?? ["stroke", "width", "style"];
|
|
14159
|
+
const style = this.currentDrawing.style ?? {};
|
|
14160
|
+
const themeEntry = this.theme.drawing[this.currentDrawing.type] ?? {};
|
|
14161
|
+
const groups = [];
|
|
14162
|
+
for (const key of controls) {
|
|
14163
|
+
switch (key) {
|
|
14164
|
+
case "stroke":
|
|
14165
|
+
groups.push(this.group(
|
|
14166
|
+
"Color",
|
|
14167
|
+
this.makeColorSwatch(
|
|
14168
|
+
style.strokeColor ?? themeEntry.strokeColor ?? "#888888",
|
|
14169
|
+
(hex) => this.emit({ strokeColor: hex })
|
|
14170
|
+
)
|
|
14171
|
+
));
|
|
14172
|
+
break;
|
|
14173
|
+
case "width":
|
|
14174
|
+
groups.push(this.group(
|
|
14175
|
+
"Width",
|
|
14176
|
+
this.buildWidth(
|
|
14177
|
+
style.lineWidth ?? themeEntry.lineWidth ?? 1,
|
|
14178
|
+
(w) => this.emit({ lineWidth: w })
|
|
14179
|
+
)
|
|
14180
|
+
));
|
|
14181
|
+
break;
|
|
14182
|
+
case "style":
|
|
14183
|
+
groups.push(this.group(
|
|
14184
|
+
"Style",
|
|
14185
|
+
this.buildLineStyle(
|
|
14186
|
+
style.lineStyle ?? themeEntry.lineStyle ?? "solid",
|
|
14187
|
+
(s) => this.emit({ lineStyle: s })
|
|
14188
|
+
)
|
|
14189
|
+
));
|
|
14190
|
+
break;
|
|
14191
|
+
case "fill": {
|
|
14192
|
+
const fillRow = document.createElement("div");
|
|
14193
|
+
Object.assign(fillRow.style, { display: "flex", alignItems: "center", gap: "6px" });
|
|
14194
|
+
fillRow.appendChild(this.makeColorSwatch(
|
|
14195
|
+
style.fillColor ?? themeEntry.fillColor ?? "#888888",
|
|
14196
|
+
(hex) => this.emit({ fillColor: hex })
|
|
14197
|
+
));
|
|
14198
|
+
fillRow.appendChild(this.buildOpacity(
|
|
14199
|
+
style.fillOpacity ?? themeEntry.fillOpacity ?? 0.1,
|
|
14200
|
+
(o) => this.emit({ fillOpacity: o })
|
|
14201
|
+
));
|
|
14202
|
+
groups.push(this.group("Fill", fillRow));
|
|
14203
|
+
break;
|
|
14204
|
+
}
|
|
14205
|
+
case "text": {
|
|
14206
|
+
const textRow = document.createElement("div");
|
|
14207
|
+
Object.assign(textRow.style, { display: "flex", alignItems: "center", gap: "6px" });
|
|
14208
|
+
textRow.appendChild(this.makeColorSwatch(
|
|
14209
|
+
style.textColor ?? themeEntry.textColor ?? "#f5f5f5",
|
|
14210
|
+
(hex) => this.emit({ textColor: hex })
|
|
14211
|
+
));
|
|
14212
|
+
textRow.appendChild(this.buildFontSize(
|
|
14213
|
+
style.fontSize ?? themeEntry.fontSize ?? 13,
|
|
14214
|
+
(size) => this.emit({ fontSize: size })
|
|
14215
|
+
));
|
|
14216
|
+
groups.push(this.group("Text", textRow));
|
|
14217
|
+
break;
|
|
14218
|
+
}
|
|
14219
|
+
case "fibLevels":
|
|
14220
|
+
groups.push(this.group(
|
|
14221
|
+
"Levels",
|
|
14222
|
+
this.buildFibLevelsButton({ ...themeEntry.levels ?? {}, ...style.levels ?? {} })
|
|
14223
|
+
));
|
|
14224
|
+
break;
|
|
14225
|
+
}
|
|
14226
|
+
}
|
|
14227
|
+
groups.forEach((g, i) => {
|
|
14228
|
+
if (i > 0) this.el.appendChild(this.makeSeparator());
|
|
14229
|
+
this.el.appendChild(g);
|
|
14230
|
+
});
|
|
14231
|
+
}
|
|
14232
|
+
// ── Layout helpers ───────────────────────────────────────────────────────
|
|
14233
|
+
group(label, control) {
|
|
14234
|
+
const wrap = document.createElement("div");
|
|
14235
|
+
Object.assign(wrap.style, {
|
|
14236
|
+
display: "flex",
|
|
14237
|
+
flexDirection: "column",
|
|
14238
|
+
alignItems: "flex-start",
|
|
14239
|
+
gap: "4px"
|
|
14240
|
+
});
|
|
14241
|
+
const lbl = document.createElement("span");
|
|
14242
|
+
lbl.textContent = label;
|
|
14243
|
+
Object.assign(lbl.style, {
|
|
14244
|
+
fontSize: "10px",
|
|
14245
|
+
color: this.theme.axisText,
|
|
14246
|
+
textTransform: "uppercase",
|
|
14247
|
+
letterSpacing: "0.5px",
|
|
14248
|
+
lineHeight: "1"
|
|
14249
|
+
});
|
|
14250
|
+
wrap.appendChild(lbl);
|
|
14251
|
+
wrap.appendChild(control);
|
|
14252
|
+
return wrap;
|
|
14253
|
+
}
|
|
14254
|
+
makeSeparator() {
|
|
14255
|
+
const sep = document.createElement("div");
|
|
14256
|
+
Object.assign(sep.style, {
|
|
14257
|
+
width: "1px",
|
|
14258
|
+
height: "32px",
|
|
14259
|
+
background: this.theme.axisBorder,
|
|
14260
|
+
opacity: "0.6"
|
|
14261
|
+
});
|
|
14262
|
+
return sep;
|
|
14263
|
+
}
|
|
14264
|
+
// ── Controls ────────────────────────────────────────────────────────────
|
|
14265
|
+
/**
|
|
14266
|
+
* 24×24 color swatch backed by a hidden `<input type="color">`. Mirrors
|
|
14267
|
+
* `ChartSettingsDialog.makeColorSwatch` so all color pickers in the app
|
|
14268
|
+
* share the same look.
|
|
14269
|
+
*/
|
|
14270
|
+
makeColorSwatch(initialHex, onChange) {
|
|
14271
|
+
const borderColor = `${this.theme.axisText}80`;
|
|
14272
|
+
const safeHex = initialHex.length === 7 && initialHex.startsWith("#") ? initialHex : "#888888";
|
|
14273
|
+
const wrap = document.createElement("div");
|
|
14274
|
+
Object.assign(wrap.style, {
|
|
14275
|
+
position: "relative",
|
|
14276
|
+
width: `${SWATCH_PX}px`,
|
|
14277
|
+
height: `${SWATCH_PX}px`,
|
|
14278
|
+
borderRadius: "6px",
|
|
14279
|
+
overflow: "hidden",
|
|
14280
|
+
cursor: "pointer",
|
|
14281
|
+
border: `1.5px solid ${borderColor}`,
|
|
14282
|
+
flexShrink: "0",
|
|
14283
|
+
boxSizing: "border-box"
|
|
14284
|
+
});
|
|
14285
|
+
const swatch = document.createElement("div");
|
|
14286
|
+
Object.assign(swatch.style, {
|
|
14287
|
+
position: "absolute",
|
|
14288
|
+
inset: "0",
|
|
14289
|
+
background: initialHex
|
|
14290
|
+
});
|
|
14291
|
+
const input = document.createElement("input");
|
|
14292
|
+
input.type = "color";
|
|
14293
|
+
input.value = safeHex;
|
|
14294
|
+
Object.assign(input.style, {
|
|
14295
|
+
position: "absolute",
|
|
14296
|
+
inset: "0",
|
|
14297
|
+
opacity: "0",
|
|
14298
|
+
width: "100%",
|
|
14299
|
+
height: "100%",
|
|
14300
|
+
cursor: "pointer",
|
|
14301
|
+
padding: "0",
|
|
14302
|
+
border: "none"
|
|
14303
|
+
});
|
|
14304
|
+
let debounceTimer = null;
|
|
14305
|
+
input.addEventListener("input", () => {
|
|
14306
|
+
swatch.style.background = input.value;
|
|
14307
|
+
if (debounceTimer !== null) clearTimeout(debounceTimer);
|
|
14308
|
+
debounceTimer = setTimeout(() => {
|
|
14309
|
+
debounceTimer = null;
|
|
14310
|
+
onChange(input.value);
|
|
14311
|
+
}, 200);
|
|
14312
|
+
});
|
|
14313
|
+
wrap.append(swatch, input);
|
|
14314
|
+
return wrap;
|
|
14315
|
+
}
|
|
14316
|
+
buildWidth(currentWidth, onPick) {
|
|
14317
|
+
const wrap = document.createElement("div");
|
|
14318
|
+
Object.assign(wrap.style, { display: "flex", gap: "2px", alignItems: "center" });
|
|
14319
|
+
for (const w of LINE_WIDTHS) {
|
|
14320
|
+
const btn = this.makeIconButton(`${w}px`);
|
|
14321
|
+
const bar = document.createElement("div");
|
|
14322
|
+
Object.assign(bar.style, {
|
|
14323
|
+
width: "14px",
|
|
14324
|
+
height: `${w}px`,
|
|
14325
|
+
background: this.theme.tooltip.text,
|
|
14326
|
+
borderRadius: "1px"
|
|
14327
|
+
});
|
|
14328
|
+
btn.appendChild(bar);
|
|
14329
|
+
if (Math.round(currentWidth) === w) this.markActive(btn);
|
|
14330
|
+
btn.addEventListener("click", () => onPick(w));
|
|
14331
|
+
wrap.appendChild(btn);
|
|
14332
|
+
}
|
|
14333
|
+
return wrap;
|
|
14334
|
+
}
|
|
14335
|
+
buildLineStyle(current, onPick) {
|
|
14336
|
+
const wrap = document.createElement("div");
|
|
14337
|
+
Object.assign(wrap.style, { display: "flex", gap: "2px", alignItems: "center" });
|
|
14338
|
+
const styles = [
|
|
14339
|
+
{ key: "solid", svg: this.lineSvg("solid"), title: "Solid" },
|
|
14340
|
+
{ key: "dashed", svg: this.lineSvg("dashed"), title: "Dashed" },
|
|
14341
|
+
{ key: "dotted", svg: this.lineSvg("dotted"), title: "Dotted" }
|
|
14342
|
+
];
|
|
14343
|
+
for (const { key, svg, title } of styles) {
|
|
14344
|
+
const btn = this.makeIconButton(title);
|
|
14345
|
+
btn.innerHTML = svg;
|
|
14346
|
+
if (key === current) this.markActive(btn);
|
|
14347
|
+
btn.addEventListener("click", () => onPick(key));
|
|
14348
|
+
wrap.appendChild(btn);
|
|
14349
|
+
}
|
|
14350
|
+
return wrap;
|
|
14351
|
+
}
|
|
14352
|
+
lineSvg(style) {
|
|
14353
|
+
const dash = style === "dashed" ? "4,2" : style === "dotted" ? "1.5,2" : "";
|
|
14354
|
+
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>`;
|
|
14355
|
+
}
|
|
14356
|
+
buildOpacity(current, onChange) {
|
|
14357
|
+
const wrap = document.createElement("div");
|
|
14358
|
+
Object.assign(wrap.style, { display: "flex", alignItems: "center", gap: "4px" });
|
|
14359
|
+
const slider = document.createElement("input");
|
|
14360
|
+
slider.type = "range";
|
|
14361
|
+
slider.min = "0";
|
|
14362
|
+
slider.max = "100";
|
|
14363
|
+
slider.value = String(Math.round(current * 100));
|
|
14364
|
+
Object.assign(slider.style, { width: "64px", accentColor: this.theme.ui.accent });
|
|
14365
|
+
slider.title = "Fill opacity";
|
|
14366
|
+
const value = document.createElement("span");
|
|
14367
|
+
value.textContent = `${slider.value}%`;
|
|
14368
|
+
Object.assign(value.style, { fontSize: "10px", color: this.theme.axisText, minWidth: "26px", textAlign: "right" });
|
|
14369
|
+
slider.addEventListener("input", () => {
|
|
14370
|
+
value.textContent = `${slider.value}%`;
|
|
14371
|
+
onChange(Number(slider.value) / 100);
|
|
14372
|
+
});
|
|
14373
|
+
wrap.append(slider, value);
|
|
14374
|
+
return wrap;
|
|
14375
|
+
}
|
|
14376
|
+
buildFontSize(current, onPick) {
|
|
14377
|
+
const select = document.createElement("select");
|
|
14378
|
+
Object.assign(select.style, {
|
|
14379
|
+
background: this.theme.tooltip.background,
|
|
14380
|
+
color: this.theme.tooltip.text,
|
|
14381
|
+
border: `1px solid ${this.theme.axisBorder}`,
|
|
14382
|
+
borderRadius: "4px",
|
|
14383
|
+
padding: "3px 4px",
|
|
14384
|
+
fontSize: "11px",
|
|
14385
|
+
cursor: "pointer",
|
|
14386
|
+
height: `${SWATCH_PX}px`,
|
|
14387
|
+
boxSizing: "border-box"
|
|
14388
|
+
});
|
|
14389
|
+
for (const s of FONT_SIZES) {
|
|
14390
|
+
const opt = document.createElement("option");
|
|
14391
|
+
opt.value = String(s);
|
|
14392
|
+
opt.textContent = `${s}px`;
|
|
14393
|
+
if (s === current) opt.selected = true;
|
|
14394
|
+
select.appendChild(opt);
|
|
14395
|
+
}
|
|
14396
|
+
select.addEventListener("change", () => onPick(Number(select.value)));
|
|
14397
|
+
return select;
|
|
14398
|
+
}
|
|
14399
|
+
buildFibLevelsButton(levels) {
|
|
14400
|
+
const wrap = document.createElement("div");
|
|
14401
|
+
wrap.style.position = "relative";
|
|
14402
|
+
const btn = this.makeIconButton("Configure levels");
|
|
14403
|
+
btn.style.padding = "0 8px";
|
|
14404
|
+
btn.style.height = `${SWATCH_PX}px`;
|
|
14405
|
+
btn.style.minWidth = "64px";
|
|
14406
|
+
btn.textContent = "Configure \u25BE";
|
|
14407
|
+
wrap.appendChild(btn);
|
|
14408
|
+
btn.addEventListener("click", (e) => {
|
|
14409
|
+
e.stopPropagation();
|
|
14410
|
+
this.fibLevelsExpanded = !this.fibLevelsExpanded;
|
|
14411
|
+
if (this.fibLevelsExpanded) this.openFibPanel(btn, levels);
|
|
14412
|
+
else this.closeFibPanel();
|
|
14413
|
+
});
|
|
14414
|
+
document.addEventListener("mousedown", (e) => {
|
|
14415
|
+
if (this.fibPanel && !this.fibPanel.contains(e.target) && !wrap.contains(e.target)) {
|
|
14416
|
+
this.closeFibPanel();
|
|
14417
|
+
this.fibLevelsExpanded = false;
|
|
14418
|
+
}
|
|
14419
|
+
});
|
|
14420
|
+
return wrap;
|
|
14421
|
+
}
|
|
14422
|
+
openFibPanel(anchor, levels) {
|
|
14423
|
+
this.closeFibPanel();
|
|
14424
|
+
const panel = document.createElement("div");
|
|
14425
|
+
Object.assign(panel.style, {
|
|
14426
|
+
position: "absolute",
|
|
14427
|
+
display: "grid",
|
|
14428
|
+
gridTemplateColumns: "auto 1fr auto",
|
|
14429
|
+
columnGap: "10px",
|
|
14430
|
+
rowGap: "6px",
|
|
14431
|
+
background: this.theme.tooltip.background,
|
|
14432
|
+
border: `1px solid ${this.theme.axisBorder}`,
|
|
14433
|
+
borderRadius: "6px",
|
|
14434
|
+
padding: "10px 12px",
|
|
14435
|
+
zIndex: "60",
|
|
14436
|
+
boxShadow: "0 6px 18px rgba(0,0,0,0.35)",
|
|
14437
|
+
fontFamily: "'Inter', system-ui, sans-serif",
|
|
14438
|
+
fontSize: "11px",
|
|
14439
|
+
color: this.theme.tooltip.text,
|
|
14440
|
+
alignItems: "center"
|
|
14441
|
+
});
|
|
14442
|
+
const keys = Object.keys(levels).sort((a, b) => Number(a) - Number(b));
|
|
14443
|
+
for (const k of keys) {
|
|
14444
|
+
const level = levels[k];
|
|
14445
|
+
const cb = document.createElement("input");
|
|
14446
|
+
cb.type = "checkbox";
|
|
14447
|
+
cb.checked = level.visible !== false;
|
|
14448
|
+
cb.style.accentColor = this.theme.ui.accent;
|
|
14449
|
+
cb.style.cursor = "pointer";
|
|
14450
|
+
const label = document.createElement("span");
|
|
14451
|
+
label.textContent = k;
|
|
14452
|
+
Object.assign(label.style, { fontSize: "11px", color: this.theme.tooltip.text });
|
|
14453
|
+
const swatch = this.makeColorSwatch(level.color, (hex) => {
|
|
14454
|
+
this.emit({ levels: { [k]: { visible: cb.checked, color: hex } } });
|
|
14455
|
+
});
|
|
14456
|
+
cb.addEventListener("change", () => {
|
|
14457
|
+
this.emit({ levels: { [k]: { visible: cb.checked, color: level.color } } });
|
|
14458
|
+
});
|
|
14459
|
+
panel.append(cb, label, swatch);
|
|
14460
|
+
}
|
|
14461
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
14462
|
+
const cRect = this.container.getBoundingClientRect();
|
|
14463
|
+
this.container.appendChild(panel);
|
|
14464
|
+
const panelRect = panel.getBoundingClientRect();
|
|
14465
|
+
let left = anchorRect.left - cRect.left;
|
|
14466
|
+
let top = anchorRect.bottom - cRect.top + 6;
|
|
14467
|
+
left = Math.max(4, Math.min(cRect.width - panelRect.width - 4, left));
|
|
14468
|
+
if (top + panelRect.height > cRect.height - 4) {
|
|
14469
|
+
top = Math.max(4, anchorRect.top - cRect.top - panelRect.height - 6);
|
|
14470
|
+
}
|
|
14471
|
+
panel.style.left = `${left}px`;
|
|
14472
|
+
panel.style.top = `${top}px`;
|
|
14473
|
+
this.fibPanel = panel;
|
|
14474
|
+
}
|
|
14475
|
+
closeFibPanel() {
|
|
14476
|
+
this.fibPanel?.remove();
|
|
14477
|
+
this.fibPanel = null;
|
|
14478
|
+
}
|
|
14479
|
+
makeIconButton(title) {
|
|
14480
|
+
const btn = document.createElement("div");
|
|
14481
|
+
Object.assign(btn.style, {
|
|
14482
|
+
display: "flex",
|
|
14483
|
+
alignItems: "center",
|
|
14484
|
+
justifyContent: "center",
|
|
14485
|
+
minWidth: `${SWATCH_PX}px`,
|
|
14486
|
+
height: `${SWATCH_PX}px`,
|
|
14487
|
+
padding: "0 4px",
|
|
14488
|
+
borderRadius: "4px",
|
|
14489
|
+
cursor: "pointer",
|
|
14490
|
+
border: `1px solid transparent`,
|
|
14491
|
+
color: this.theme.tooltip.text,
|
|
14492
|
+
background: "transparent",
|
|
14493
|
+
fontSize: "11px",
|
|
14494
|
+
boxSizing: "border-box"
|
|
14495
|
+
});
|
|
14496
|
+
btn.title = title;
|
|
14497
|
+
btn.addEventListener("mouseenter", () => {
|
|
14498
|
+
if (btn.dataset.active !== "1") btn.style.background = this.theme.indicatorOverlay.dropdownHoverBg;
|
|
14499
|
+
});
|
|
14500
|
+
btn.addEventListener("mouseleave", () => {
|
|
14501
|
+
if (btn.dataset.active !== "1") btn.style.background = "transparent";
|
|
14502
|
+
});
|
|
14503
|
+
return btn;
|
|
14504
|
+
}
|
|
14505
|
+
markActive(btn) {
|
|
14506
|
+
btn.dataset.active = "1";
|
|
14507
|
+
btn.style.background = this.theme.ui.accentBg;
|
|
14508
|
+
btn.style.borderColor = this.theme.ui.accent;
|
|
14509
|
+
}
|
|
14510
|
+
// ── Apply patch ──────────────────────────────────────────────────────────
|
|
14511
|
+
emit(patch) {
|
|
14512
|
+
if (!this.currentDrawing) return;
|
|
14513
|
+
this.onChange(this.currentDrawing.id, patch);
|
|
14514
|
+
this.rebuild();
|
|
14515
|
+
}
|
|
14516
|
+
};
|
|
14517
|
+
|
|
13474
14518
|
// src/ui/DraftQtyFlyout.ts
|
|
13475
14519
|
var DraftQtyFlyout = class {
|
|
13476
14520
|
constructor(theme, onCommit, onVisibilityChange) {
|
|
@@ -15113,6 +16157,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15113
16157
|
this.chartSettingsDialog = null;
|
|
15114
16158
|
this.tradeButton = null;
|
|
15115
16159
|
this.tradePopover = null;
|
|
16160
|
+
this.drawingStylePopover = null;
|
|
15116
16161
|
this.draftOrderLabel = null;
|
|
15117
16162
|
this._minLots = 1;
|
|
15118
16163
|
this._showQuantityField = false;
|
|
@@ -15312,6 +16357,10 @@ var _ChartEngine = class _ChartEngine {
|
|
|
15312
16357
|
this.targetCandleWidth = DEFAULT_CANDLE_SLOT_WIDTH;
|
|
15313
16358
|
this.maxSubPanes = 3;
|
|
15314
16359
|
this.showCandleCountdown = true;
|
|
16360
|
+
this.showTradeLevelsAlways = false;
|
|
16361
|
+
this.showPriceAxisCountdown = false;
|
|
16362
|
+
this.SHOW_SLTP_ALWAYS_LS_KEY = "finchart.showTradeLevelsAlways";
|
|
16363
|
+
this.PRICE_AXIS_COUNTDOWN_LS_KEY = "finchart.showPriceAxisCountdown";
|
|
15315
16364
|
this.loadingOverlayEl = null;
|
|
15316
16365
|
this.noDataOverlayEl = null;
|
|
15317
16366
|
// Watermark
|
|
@@ -16342,6 +17391,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
16342
17391
|
labels,
|
|
16343
17392
|
tickActivityMs = 3e4,
|
|
16344
17393
|
hideSymbolAndTick = false,
|
|
17394
|
+
hideOHLCV = false,
|
|
16345
17395
|
showBottomBar = false
|
|
16346
17396
|
} = config;
|
|
16347
17397
|
this.dataLoader = dataLoader ?? null;
|
|
@@ -16402,6 +17452,15 @@ var _ChartEngine = class _ChartEngine {
|
|
|
16402
17452
|
this.tfcActive = this.tfcEnabled;
|
|
16403
17453
|
this.showCandleCountdown = config.showCandleCountdown ?? true;
|
|
16404
17454
|
this.candleCountdownTimeframes = config.candleCountdownTimeframes;
|
|
17455
|
+
this.showTradeLevelsAlways = config.showTradeLevelsAlways ?? false;
|
|
17456
|
+
this.showPriceAxisCountdown = config.showPriceAxisCountdown ?? false;
|
|
17457
|
+
try {
|
|
17458
|
+
const persistedSlTp = localStorage.getItem(this.SHOW_SLTP_ALWAYS_LS_KEY);
|
|
17459
|
+
if (persistedSlTp !== null) this.showTradeLevelsAlways = persistedSlTp === "true";
|
|
17460
|
+
const persistedCountdown = localStorage.getItem(this.PRICE_AXIS_COUNTDOWN_LS_KEY);
|
|
17461
|
+
if (persistedCountdown !== null) this.showPriceAxisCountdown = persistedCountdown === "true";
|
|
17462
|
+
} catch {
|
|
17463
|
+
}
|
|
16405
17464
|
this._onOrderSubmit = onOrderSubmit;
|
|
16406
17465
|
if (config.minLots !== void 0) {
|
|
16407
17466
|
console.warn("[acttrader-charts] ChartConfig.minLots is deprecated. DraftOrderQtyInput has been removed. The value is stored and used as the submitted lot size.");
|
|
@@ -16653,7 +17712,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
16653
17712
|
this.noDataOverlayEl.appendChild(noDataLabel);
|
|
16654
17713
|
this.canvasWrap.appendChild(this.noDataOverlayEl);
|
|
16655
17714
|
}
|
|
16656
|
-
if (showUI && !hideSymbolAndTick) {
|
|
17715
|
+
if (showUI && !(hideSymbolAndTick && hideOHLCV)) {
|
|
16657
17716
|
this.siEl = document.createElement("div");
|
|
16658
17717
|
Object.assign(this.siEl.style, {
|
|
16659
17718
|
position: "absolute",
|
|
@@ -16665,66 +17724,73 @@ var _ChartEngine = class _ChartEngine {
|
|
|
16665
17724
|
gap: "8px",
|
|
16666
17725
|
pointerEvents: "none"
|
|
16667
17726
|
});
|
|
16668
|
-
|
|
16669
|
-
|
|
16670
|
-
color: this.theme.axisText,
|
|
16671
|
-
fontSize: "17px",
|
|
16672
|
-
fontFamily: "'Inter', system-ui, sans-serif",
|
|
16673
|
-
fontWeight: "700",
|
|
16674
|
-
display: symbol ? "inline" : "none"
|
|
16675
|
-
});
|
|
16676
|
-
this.siSymbolSpan.textContent = symbol ?? "";
|
|
16677
|
-
if (onSymbolClick) {
|
|
17727
|
+
if (!hideSymbolAndTick) {
|
|
17728
|
+
this.siSymbolSpan = document.createElement("span");
|
|
16678
17729
|
Object.assign(this.siSymbolSpan.style, {
|
|
16679
|
-
|
|
16680
|
-
|
|
16681
|
-
|
|
17730
|
+
color: this.theme.axisText,
|
|
17731
|
+
fontSize: "17px",
|
|
17732
|
+
fontFamily: "'Inter', system-ui, sans-serif",
|
|
17733
|
+
fontWeight: "700",
|
|
17734
|
+
display: symbol ? "inline" : "none"
|
|
16682
17735
|
});
|
|
16683
|
-
this.siSymbolSpan.
|
|
16684
|
-
|
|
17736
|
+
this.siSymbolSpan.textContent = symbol ?? "";
|
|
17737
|
+
if (onSymbolClick) {
|
|
17738
|
+
Object.assign(this.siSymbolSpan.style, {
|
|
17739
|
+
cursor: "pointer",
|
|
17740
|
+
textDecoration: "underline",
|
|
17741
|
+
pointerEvents: "auto"
|
|
17742
|
+
});
|
|
17743
|
+
this.siSymbolSpan.addEventListener("click", () => {
|
|
17744
|
+
onSymbolClick(this.siSymbolSpan?.textContent ?? "");
|
|
17745
|
+
});
|
|
17746
|
+
} else if (onIsinSelect) {
|
|
17747
|
+
if (isins) this.isins = isins;
|
|
17748
|
+
this.isinPickerModal = new IsinPickerModal(this.theme, onIsinSelect, () => this.emitUiState());
|
|
17749
|
+
this.getFloatingRoot().appendChild(this.isinPickerModal.el);
|
|
17750
|
+
Object.assign(this.siSymbolSpan.style, {
|
|
17751
|
+
cursor: "pointer",
|
|
17752
|
+
textDecoration: "underline",
|
|
17753
|
+
pointerEvents: "auto"
|
|
17754
|
+
});
|
|
17755
|
+
this.siSymbolSpan.addEventListener("click", () => {
|
|
17756
|
+
this.isinPickerModal.show(this.isins, this.siSymbolSpan.textContent ?? "");
|
|
17757
|
+
});
|
|
17758
|
+
}
|
|
17759
|
+
this.siStreamDot = document.createElement("span");
|
|
17760
|
+
Object.assign(this.siStreamDot.style, {
|
|
17761
|
+
width: "7px",
|
|
17762
|
+
height: "7px",
|
|
17763
|
+
borderRadius: "50%",
|
|
17764
|
+
background: this.theme.ui.stream.disconnected,
|
|
17765
|
+
display: "inline-block",
|
|
17766
|
+
flexShrink: "0"
|
|
16685
17767
|
});
|
|
16686
|
-
}
|
|
16687
|
-
|
|
16688
|
-
this.
|
|
16689
|
-
|
|
16690
|
-
|
|
16691
|
-
|
|
16692
|
-
|
|
16693
|
-
pointerEvents: "auto"
|
|
17768
|
+
}
|
|
17769
|
+
if (!hideOHLCV) {
|
|
17770
|
+
this.siOHLCSpan = document.createElement("span");
|
|
17771
|
+
Object.assign(this.siOHLCSpan.style, {
|
|
17772
|
+
fontSize: "14px",
|
|
17773
|
+
fontFamily: "'Inter', system-ui, sans-serif",
|
|
17774
|
+
whiteSpace: "nowrap"
|
|
16694
17775
|
});
|
|
16695
|
-
this.
|
|
16696
|
-
|
|
17776
|
+
this.siOHLC2Span = document.createElement("span");
|
|
17777
|
+
Object.assign(this.siOHLC2Span.style, {
|
|
17778
|
+
fontSize: "14px",
|
|
17779
|
+
fontFamily: "'Inter', system-ui, sans-serif",
|
|
17780
|
+
whiteSpace: "nowrap"
|
|
16697
17781
|
});
|
|
16698
17782
|
}
|
|
16699
|
-
this.siOHLCSpan = document.createElement("span");
|
|
16700
|
-
Object.assign(this.siOHLCSpan.style, {
|
|
16701
|
-
fontSize: "14px",
|
|
16702
|
-
fontFamily: "'Inter', system-ui, sans-serif",
|
|
16703
|
-
whiteSpace: "nowrap"
|
|
16704
|
-
});
|
|
16705
|
-
this.siOHLC2Span = document.createElement("span");
|
|
16706
|
-
Object.assign(this.siOHLC2Span.style, {
|
|
16707
|
-
fontSize: "14px",
|
|
16708
|
-
fontFamily: "'Inter', system-ui, sans-serif",
|
|
16709
|
-
whiteSpace: "nowrap"
|
|
16710
|
-
});
|
|
16711
|
-
this.siStreamDot = document.createElement("span");
|
|
16712
|
-
Object.assign(this.siStreamDot.style, {
|
|
16713
|
-
width: "7px",
|
|
16714
|
-
height: "7px",
|
|
16715
|
-
borderRadius: "50%",
|
|
16716
|
-
background: this.theme.ui.stream.disconnected,
|
|
16717
|
-
display: "inline-block",
|
|
16718
|
-
flexShrink: "0"
|
|
16719
|
-
});
|
|
16720
17783
|
const siRow1 = document.createElement("div");
|
|
16721
17784
|
Object.assign(siRow1.style, {
|
|
16722
17785
|
display: "flex",
|
|
16723
17786
|
alignItems: "center",
|
|
16724
17787
|
gap: "8px"
|
|
16725
17788
|
});
|
|
16726
|
-
|
|
16727
|
-
this.
|
|
17789
|
+
if (this.siStreamDot) siRow1.append(this.siStreamDot);
|
|
17790
|
+
if (this.siSymbolSpan) siRow1.append(this.siSymbolSpan);
|
|
17791
|
+
if (this.siOHLCSpan) siRow1.append(this.siOHLCSpan);
|
|
17792
|
+
this.siEl.append(siRow1);
|
|
17793
|
+
if (this.siOHLC2Span) this.siEl.append(this.siOHLC2Span);
|
|
16728
17794
|
this.canvasWrap.appendChild(this.siEl);
|
|
16729
17795
|
}
|
|
16730
17796
|
if (showUI) {
|
|
@@ -16873,6 +17939,20 @@ var _ChartEngine = class _ChartEngine {
|
|
|
16873
17939
|
}
|
|
16874
17940
|
};
|
|
16875
17941
|
this.drawingManager.onStartTextEdit = (drawing) => this.showTextInput(drawing);
|
|
17942
|
+
this.drawingStylePopover = new DrawingStylePopover(
|
|
17943
|
+
this.canvasWrap,
|
|
17944
|
+
this.theme,
|
|
17945
|
+
(id, patch) => this.updateDrawingStyle(id, patch)
|
|
17946
|
+
);
|
|
17947
|
+
this.drawingManager.onSelectionChange = (drawing) => {
|
|
17948
|
+
if (drawing && drawing.complete && drawing.locked !== true) {
|
|
17949
|
+
const anchor = this.computeDrawingAnchorPx(drawing);
|
|
17950
|
+
if (anchor) this.drawingStylePopover?.show(drawing, anchor.x, anchor.y);
|
|
17951
|
+
else this.drawingStylePopover?.hide();
|
|
17952
|
+
} else {
|
|
17953
|
+
this.drawingStylePopover?.hide();
|
|
17954
|
+
}
|
|
17955
|
+
};
|
|
16876
17956
|
this.panHandler = new PanHandler(
|
|
16877
17957
|
this.drawCanvas,
|
|
16878
17958
|
() => this._targetViewport,
|
|
@@ -17488,6 +18568,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17488
18568
|
this.leftBar?.setTheme(this.theme);
|
|
17489
18569
|
this.tradeButton?.setTheme(this.theme, this.priceAxisWidth);
|
|
17490
18570
|
this.tradePopover?.setTheme(this.theme);
|
|
18571
|
+
this.drawingStylePopover?.setTheme(this.theme);
|
|
17491
18572
|
this._draftQtyFlyout?.setTheme(this.theme);
|
|
17492
18573
|
this.indicatorSettingsDialog?.setTheme(this.theme);
|
|
17493
18574
|
this.chartSettingsDialog?.setTheme(this.theme);
|
|
@@ -17519,6 +18600,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
17519
18600
|
this.leftBar?.setTheme(this.theme);
|
|
17520
18601
|
this.tradeButton?.setTheme(this.theme, this.priceAxisWidth);
|
|
17521
18602
|
this.tradePopover?.setTheme(this.theme);
|
|
18603
|
+
this.drawingStylePopover?.setTheme(this.theme);
|
|
17522
18604
|
this._draftQtyFlyout?.setTheme(this.theme);
|
|
17523
18605
|
this.indicatorSettingsDialog?.setTheme(this.theme);
|
|
17524
18606
|
this.chartSettingsDialog?.setTheme(this.theme);
|
|
@@ -18104,6 +19186,24 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18104
19186
|
getDrawings() {
|
|
18105
19187
|
return this.drawingManager.getDrawings();
|
|
18106
19188
|
}
|
|
19189
|
+
/** Returns the drawing matching `id`, or null. */
|
|
19190
|
+
getDrawingById(id) {
|
|
19191
|
+
return this.drawingManager.getDrawingById(id);
|
|
19192
|
+
}
|
|
19193
|
+
/** Returns the currently selected drawing, or null. */
|
|
19194
|
+
getSelectedDrawing() {
|
|
19195
|
+
return this.drawingManager.getSelectedDrawing();
|
|
19196
|
+
}
|
|
19197
|
+
/**
|
|
19198
|
+
* Shallow-merge a per-instance style override onto the drawing with `id`.
|
|
19199
|
+
* Fib `levels` are deep-merged (per-key) so partial overrides keep siblings.
|
|
19200
|
+
* Returns `this` for chaining.
|
|
19201
|
+
*/
|
|
19202
|
+
updateDrawingStyle(id, patch) {
|
|
19203
|
+
this.drawingManager.updateDrawingStyle(id, patch);
|
|
19204
|
+
this.scheduleRender();
|
|
19205
|
+
return this;
|
|
19206
|
+
}
|
|
18107
19207
|
connectStream(adapter) {
|
|
18108
19208
|
this.streamAdapter = adapter;
|
|
18109
19209
|
adapter.onTick((tick) => this.onTick(tick));
|
|
@@ -18624,6 +19724,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18624
19724
|
this.chartSettingsDialog?.destroy();
|
|
18625
19725
|
this.isinPickerModal?.destroy();
|
|
18626
19726
|
this.tradePopover?.destroy();
|
|
19727
|
+
this.drawingStylePopover?.destroy();
|
|
18627
19728
|
this._draftQtyFlyout?.destroy();
|
|
18628
19729
|
this.leftBar?.destroy();
|
|
18629
19730
|
this.drawCanvas.removeEventListener("mousemove", this.onMouseMove);
|
|
@@ -18779,7 +19880,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18779
19880
|
const allowed = this.candleCountdownTimeframes === "all" ? true : (this.candleCountdownTimeframes ?? INTRADAY).includes(
|
|
18780
19881
|
this.timeframe
|
|
18781
19882
|
);
|
|
18782
|
-
const should = this.showCandleCountdown && allowed && this.dataStore.length > 0;
|
|
19883
|
+
const should = (this.showCandleCountdown || this.showPriceAxisCountdown) && allowed && this.dataStore.length > 0;
|
|
18783
19884
|
if (should && !this.countdownActive) {
|
|
18784
19885
|
this.countdownActive = true;
|
|
18785
19886
|
this.startCountdownLoop();
|
|
@@ -18794,7 +19895,9 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18794
19895
|
{
|
|
18795
19896
|
tradeDisplayFilter: this.tradeDisplayFilter,
|
|
18796
19897
|
positionRenderStyle: this.positionRenderStyle,
|
|
18797
|
-
theme: this.themeName
|
|
19898
|
+
theme: this.themeName,
|
|
19899
|
+
showTradeLevelsAlways: this.showTradeLevelsAlways,
|
|
19900
|
+
showPriceAxisCountdown: this.showPriceAxisCountdown
|
|
18798
19901
|
},
|
|
18799
19902
|
this.labels.dialogs,
|
|
18800
19903
|
!!this._onOrderSubmit && this.tfcActive,
|
|
@@ -18808,6 +19911,8 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18808
19911
|
tradeDisplayFilter: this.tradeDisplayFilter,
|
|
18809
19912
|
positionRenderStyle: this.positionRenderStyle,
|
|
18810
19913
|
theme: this.themeName,
|
|
19914
|
+
showTradeLevelsAlways: this.showTradeLevelsAlways,
|
|
19915
|
+
showPriceAxisCountdown: this.showPriceAxisCountdown,
|
|
18811
19916
|
canvasColors: { ...this.settingsCanvasColors }
|
|
18812
19917
|
},
|
|
18813
19918
|
(settings) => {
|
|
@@ -18818,6 +19923,28 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18818
19923
|
if (settings.canvasColors) {
|
|
18819
19924
|
this.settingsCanvasColors = settings.canvasColors;
|
|
18820
19925
|
}
|
|
19926
|
+
if (this.showTradeLevelsAlways !== settings.showTradeLevelsAlways) {
|
|
19927
|
+
this.showTradeLevelsAlways = settings.showTradeLevelsAlways;
|
|
19928
|
+
try {
|
|
19929
|
+
localStorage.setItem(
|
|
19930
|
+
this.SHOW_SLTP_ALWAYS_LS_KEY,
|
|
19931
|
+
String(settings.showTradeLevelsAlways)
|
|
19932
|
+
);
|
|
19933
|
+
} catch {
|
|
19934
|
+
}
|
|
19935
|
+
}
|
|
19936
|
+
if (this.showPriceAxisCountdown !== settings.showPriceAxisCountdown) {
|
|
19937
|
+
this.showPriceAxisCountdown = settings.showPriceAxisCountdown;
|
|
19938
|
+
try {
|
|
19939
|
+
localStorage.setItem(
|
|
19940
|
+
this.PRICE_AXIS_COUNTDOWN_LS_KEY,
|
|
19941
|
+
String(settings.showPriceAxisCountdown)
|
|
19942
|
+
);
|
|
19943
|
+
} catch {
|
|
19944
|
+
}
|
|
19945
|
+
this.updateCountdownState();
|
|
19946
|
+
this.scheduleRender();
|
|
19947
|
+
}
|
|
18821
19948
|
this.setTheme(settings.theme);
|
|
18822
19949
|
this.renderTradeLayer();
|
|
18823
19950
|
this.emitStateChange();
|
|
@@ -18999,7 +20126,8 @@ var _ChartEngine = class _ChartEngine {
|
|
|
18999
20126
|
this._showQuantityField,
|
|
19000
20127
|
this.tradeDragNewSLPrice,
|
|
19001
20128
|
this.tradeDragNewTPPrice,
|
|
19002
|
-
this.tradeLevelButtonScale
|
|
20129
|
+
this.tradeLevelButtonScale,
|
|
20130
|
+
this.showTradeLevelsAlways
|
|
19003
20131
|
);
|
|
19004
20132
|
ctx.restore();
|
|
19005
20133
|
const offVpResult = renderOffViewportIndicators(
|
|
@@ -19737,28 +20865,62 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19737
20865
|
ctx.stroke();
|
|
19738
20866
|
ctx.setLineDash([]);
|
|
19739
20867
|
const label = prefix + price.toFixed(precision);
|
|
20868
|
+
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
19740
20869
|
const tw = ctx.measureText(label).width;
|
|
20870
|
+
const boxW = tw + 12;
|
|
19741
20871
|
ctx.fillStyle = color;
|
|
19742
|
-
ctx.fillRect(chartW, y - tagH / 2,
|
|
20872
|
+
ctx.fillRect(chartW, y - tagH / 2, boxW, tagH);
|
|
19743
20873
|
ctx.fillStyle = "#ffffff";
|
|
19744
|
-
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
19745
20874
|
ctx.textAlign = "left";
|
|
19746
20875
|
ctx.fillText(label, chartW + 4, y + 4);
|
|
19747
20876
|
ctx.restore();
|
|
20877
|
+
return { y, boxW };
|
|
19748
20878
|
};
|
|
20879
|
+
let primaryPriceY = null;
|
|
20880
|
+
let primaryPriceBoxW = 0;
|
|
19749
20881
|
if (this.liveBidAsk) {
|
|
19750
20882
|
const { bid, ask } = this.liveBidAsk;
|
|
19751
20883
|
if (this.showBidAskLines) {
|
|
19752
|
-
drawPriceLine(bid, theme.candle.down, "");
|
|
19753
|
-
drawPriceLine(ask, theme.candle.up, "");
|
|
20884
|
+
const bidTag = drawPriceLine(bid, theme.candle.down, "");
|
|
20885
|
+
const askTag = drawPriceLine(ask, theme.candle.up, "");
|
|
20886
|
+
const primary = this.tickClosePriceSource === "ask" ? askTag : bidTag;
|
|
20887
|
+
primaryPriceY = primary.y;
|
|
20888
|
+
primaryPriceBoxW = primary.boxW;
|
|
19754
20889
|
} else {
|
|
19755
20890
|
const price = this.tickClosePriceSource === "ask" ? ask : bid;
|
|
19756
20891
|
const color = this.tickClosePriceSource === "ask" ? theme.candle.up : theme.candle.down;
|
|
19757
|
-
drawPriceLine(price, color, "");
|
|
20892
|
+
const tag = drawPriceLine(price, color, "");
|
|
20893
|
+
primaryPriceY = tag.y;
|
|
20894
|
+
primaryPriceBoxW = tag.boxW;
|
|
19758
20895
|
}
|
|
19759
20896
|
} else {
|
|
19760
20897
|
const isUp = lastBar.close >= lastBar.open;
|
|
19761
|
-
|
|
20898
|
+
const tag = drawPriceLine(
|
|
20899
|
+
lastBar.close,
|
|
20900
|
+
isUp ? theme.candle.up : theme.candle.down,
|
|
20901
|
+
""
|
|
20902
|
+
);
|
|
20903
|
+
primaryPriceY = tag.y;
|
|
20904
|
+
primaryPriceBoxW = tag.boxW;
|
|
20905
|
+
}
|
|
20906
|
+
if (this.showPriceAxisCountdown && primaryPriceY !== null) {
|
|
20907
|
+
const INTRADAY_TF = ["1m", "5m", "15m", "30m", "1h", "2h", "4h", "8h"];
|
|
20908
|
+
const allowed = this.candleCountdownTimeframes === "all" ? true : (this.candleCountdownTimeframes ?? INTRADAY_TF).includes(
|
|
20909
|
+
this.timeframe
|
|
20910
|
+
);
|
|
20911
|
+
if (allowed) {
|
|
20912
|
+
const intervalMs = this.timeframeToMs(this.timeframe);
|
|
20913
|
+
renderPriceAxisCountdown(
|
|
20914
|
+
ctx,
|
|
20915
|
+
lastBar.time,
|
|
20916
|
+
intervalMs,
|
|
20917
|
+
theme,
|
|
20918
|
+
chartW,
|
|
20919
|
+
primaryPriceY - tagH / 2,
|
|
20920
|
+
tagH,
|
|
20921
|
+
primaryPriceBoxW
|
|
20922
|
+
);
|
|
20923
|
+
}
|
|
19762
20924
|
}
|
|
19763
20925
|
}
|
|
19764
20926
|
let subPaneY = priceH;
|
|
@@ -19828,7 +20990,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19828
20990
|
const countdownAllowed = this.candleCountdownTimeframes === "all" ? true : (this.candleCountdownTimeframes ?? INTRADAY_TF).includes(
|
|
19829
20991
|
this.timeframe
|
|
19830
20992
|
);
|
|
19831
|
-
const showCountdown = this.showCandleCountdown && countdownAllowed && bars.length > 0 && viewport.endIndex >= this.dataStore.length - 1;
|
|
20993
|
+
const showCountdown = this.showCandleCountdown && !this.showPriceAxisCountdown && countdownAllowed && bars.length > 0 && viewport.endIndex >= this.dataStore.length - 1;
|
|
19832
20994
|
const countdownBarIndex = showCountdown ? this.dataStore.length - 1 : void 0;
|
|
19833
20995
|
if (this._debug) {
|
|
19834
20996
|
console.log(
|
|
@@ -19924,8 +21086,16 @@ var _ChartEngine = class _ChartEngine {
|
|
|
19924
21086
|
priceRange,
|
|
19925
21087
|
chartW,
|
|
19926
21088
|
priceH,
|
|
19927
|
-
this.dataStore.getPricePrecision()
|
|
21089
|
+
this.dataStore.getPricePrecision(),
|
|
21090
|
+
this.theme
|
|
19928
21091
|
);
|
|
21092
|
+
if (this.drawingStylePopover) {
|
|
21093
|
+
const sel = this.drawingManager.getSelectedDrawing();
|
|
21094
|
+
if (sel && sel.complete && sel.locked !== true) {
|
|
21095
|
+
const anchor = this.computeDrawingAnchorPx(sel);
|
|
21096
|
+
if (anchor) this.drawingStylePopover.setAnchor(anchor.x, anchor.y);
|
|
21097
|
+
}
|
|
21098
|
+
}
|
|
19929
21099
|
if (this.scrollToEndBtn) {
|
|
19930
21100
|
const atEnd = this.dataStore.length === 0 || this._targetViewport.endIndex >= this.dataStore.length - 0.5;
|
|
19931
21101
|
if (atEnd !== !this.scrollToEndVisible) {
|
|
@@ -20006,6 +21176,30 @@ var _ChartEngine = class _ChartEngine {
|
|
|
20006
21176
|
const priceH = rect.height - this.timeAxisHeight - subPanesH;
|
|
20007
21177
|
return { chartW, priceH };
|
|
20008
21178
|
}
|
|
21179
|
+
/**
|
|
21180
|
+
* Returns canvasWrap-relative pixel coords of the topmost rendered point of
|
|
21181
|
+
* a drawing — used to anchor the floating style popover above it. Returns
|
|
21182
|
+
* null when the drawing has no on-screen points (e.g. fully clipped out).
|
|
21183
|
+
*/
|
|
21184
|
+
computeDrawingAnchorPx(drawing) {
|
|
21185
|
+
if (drawing.points.length === 0) return null;
|
|
21186
|
+
const { chartW, priceH } = this.getChartLayout();
|
|
21187
|
+
const viewport = this._targetViewport;
|
|
21188
|
+
const priceRange = this._displayedPriceRange ?? this.getScaledPriceRange(viewport);
|
|
21189
|
+
let bestX = 0, bestY = Infinity;
|
|
21190
|
+
for (const p of drawing.points) {
|
|
21191
|
+
const x = this.scaleManager.xToPixel(p.barIndex, viewport, chartW);
|
|
21192
|
+
const y = this.scaleManager.yToPixel(p.price, priceRange, priceH);
|
|
21193
|
+
if (y < bestY) {
|
|
21194
|
+
bestY = y;
|
|
21195
|
+
bestX = x;
|
|
21196
|
+
}
|
|
21197
|
+
}
|
|
21198
|
+
if (!Number.isFinite(bestY)) return null;
|
|
21199
|
+
bestX = Math.max(0, Math.min(chartW, bestX));
|
|
21200
|
+
bestY = Math.max(0, Math.min(priceH, bestY));
|
|
21201
|
+
return { x: bestX, y: bestY };
|
|
21202
|
+
}
|
|
20009
21203
|
/** Repositions each sub-pane IndicatorOverlay element to sit at the top of its pane */
|
|
20010
21204
|
updateSubPaneOverlayPositions() {
|
|
20011
21205
|
if (this.subPaneOverlays.size === 0) return;
|