acttrader-charts 1.0.21 → 1.1.1

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