acttrader-charts 1.0.21 → 1.1.0

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