hyperprop-charting-library 0.1.69 → 0.1.71

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.
@@ -20,9 +20,20 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ FIB_DEFAULT_PALETTE: () => FIB_DEFAULT_PALETTE,
23
24
  createChart: () => createChart
24
25
  });
25
26
  module.exports = __toCommonJS(index_exports);
27
+ var FIB_DEFAULT_PALETTE = [
28
+ "#787b86",
29
+ "#f23645",
30
+ "#ff9800",
31
+ "#4caf50",
32
+ "#089981",
33
+ "#00bcd4",
34
+ "#2962ff",
35
+ "#9c27b0"
36
+ ];
26
37
  var DEFAULT_GRID_OPTIONS = {
27
38
  color: "#2b2f38",
28
39
  opacity: 0.38,
@@ -944,6 +955,7 @@ function createChart(element, options = {}) {
944
955
  })),
945
956
  visible: drawing.visible ?? true,
946
957
  color: drawing.color ?? "#94a3b8",
958
+ colors: Array.isArray(drawing.colors) ? drawing.colors.filter((value) => typeof value === "string") : [],
947
959
  style: drawing.style ?? "dotted",
948
960
  width: Math.max(1, Number(drawing.width) || 1),
949
961
  locked: drawing.locked ?? false,
@@ -955,6 +967,7 @@ function createChart(element, options = {}) {
955
967
  points: drawing.points.map((point) => ({ ...point })),
956
968
  visible: drawing.visible,
957
969
  color: drawing.color,
970
+ colors: [...drawing.colors],
958
971
  style: drawing.style,
959
972
  width: drawing.width,
960
973
  locked: drawing.locked,
@@ -2100,6 +2113,7 @@ function createChart(element, options = {}) {
2100
2113
  };
2101
2114
  const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
2102
2115
  const FIB_RATIOS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
2116
+ const FIB_EXT_RATIOS = [0, 0.382, 0.5, 0.618, 1, 1.272, 1.618, 2.618];
2103
2117
  const hexToRgba = (hex, alpha) => {
2104
2118
  const cleaned = hex.replace("#", "");
2105
2119
  if (cleaned.length !== 6) {
@@ -2244,32 +2258,33 @@ function createChart(element, options = {}) {
2244
2258
  const secondY = yFromPrice(second.price);
2245
2259
  const lineLeft = chartLeft;
2246
2260
  const lineRight = chartRight;
2261
+ const palette = drawing.colors.length > 0 ? drawing.colors : null;
2262
+ const levelColorAt = (index) => palette ? palette[index % palette.length] : drawing.color;
2247
2263
  const levelLines = FIB_RATIOS.map((ratio) => {
2248
2264
  const price = first.price + (second.price - first.price) * (1 - ratio);
2249
2265
  return { ratio, price, y: yFromPrice(price) };
2250
2266
  });
2251
- const bandColor = hexToRgba(drawing.color, 0.1);
2252
2267
  for (let index = 0; index < levelLines.length - 1; index += 1) {
2253
2268
  const top = levelLines[index];
2254
2269
  const bottom = levelLines[index + 1];
2255
2270
  const bandTop = Math.min(top.y, bottom.y);
2256
2271
  const bandBottom = Math.max(top.y, bottom.y);
2257
2272
  ctx.save();
2258
- ctx.fillStyle = bandColor;
2273
+ ctx.fillStyle = hexToRgba(levelColorAt(index), 0.1);
2259
2274
  ctx.globalAlpha = draft ? 0.5 : 1;
2260
2275
  ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
2261
2276
  ctx.restore();
2262
2277
  }
2263
2278
  ctx.save();
2264
2279
  ctx.lineWidth = drawing.width;
2265
- ctx.strokeStyle = drawing.color;
2266
2280
  applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
2267
- for (const level of levelLines) {
2281
+ levelLines.forEach((level, index) => {
2282
+ ctx.strokeStyle = levelColorAt(index);
2268
2283
  ctx.beginPath();
2269
2284
  ctx.moveTo(crisp(lineLeft), crisp(level.y));
2270
2285
  ctx.lineTo(crisp(lineRight), crisp(level.y));
2271
2286
  ctx.stroke();
2272
- }
2287
+ });
2273
2288
  ctx.restore();
2274
2289
  ctx.save();
2275
2290
  ctx.strokeStyle = hexToRgba(drawing.color, 0.5);
@@ -2284,7 +2299,7 @@ function createChart(element, options = {}) {
2284
2299
  drawDrawingHandle(secondX, secondY, drawing.color);
2285
2300
  const prevFont = ctx.font;
2286
2301
  ctx.font = `500 11px ${mergedOptions.fontFamily}`;
2287
- for (const level of levelLines) {
2302
+ levelLines.forEach((level, index) => {
2288
2303
  const labelText = `${level.ratio} (${formatPrice(level.price)})`;
2289
2304
  const textWidth = ctx.measureText(labelText).width;
2290
2305
  const padding = 4;
@@ -2292,13 +2307,90 @@ function createChart(element, options = {}) {
2292
2307
  const bgY = level.y - 9;
2293
2308
  ctx.fillStyle = mergedOptions.backgroundColor;
2294
2309
  fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
2295
- ctx.fillStyle = drawing.color;
2310
+ ctx.fillStyle = levelColorAt(index);
2296
2311
  ctx.textAlign = "left";
2297
2312
  ctx.textBaseline = "middle";
2298
2313
  ctx.fillText(labelText, bgX + padding, bgY + 8);
2299
- }
2314
+ });
2300
2315
  ctx.font = prevFont;
2301
2316
  }
2317
+ } else if (drawing.type === "fib-extension") {
2318
+ const p0 = drawing.points[0];
2319
+ const p1 = drawing.points[1];
2320
+ const p2 = drawing.points[2];
2321
+ if (p0 && p1) {
2322
+ const x0 = xFromDrawingPoint(p0);
2323
+ const y0 = yFromPrice(p0.price);
2324
+ const x1 = xFromDrawingPoint(p1);
2325
+ const y1 = yFromPrice(p1.price);
2326
+ ctx.save();
2327
+ ctx.strokeStyle = hexToRgba(drawing.color, 0.6);
2328
+ ctx.setLineDash([4, 4]);
2329
+ ctx.lineWidth = 1;
2330
+ ctx.beginPath();
2331
+ ctx.moveTo(x0, y0);
2332
+ ctx.lineTo(x1, y1);
2333
+ if (p2) {
2334
+ ctx.lineTo(xFromDrawingPoint(p2), yFromPrice(p2.price));
2335
+ }
2336
+ ctx.stroke();
2337
+ ctx.restore();
2338
+ drawDrawingHandle(x0, y0, drawing.color);
2339
+ drawDrawingHandle(x1, y1, drawing.color);
2340
+ if (p2) {
2341
+ const x2 = xFromDrawingPoint(p2);
2342
+ const y2 = yFromPrice(p2.price);
2343
+ drawDrawingHandle(x2, y2, drawing.color);
2344
+ const palette = drawing.colors.length > 0 ? drawing.colors : null;
2345
+ const levelColorAt = (index) => palette ? palette[index % palette.length] : drawing.color;
2346
+ const move = p1.price - p0.price;
2347
+ const levelLines = FIB_EXT_RATIOS.map((ratio) => {
2348
+ const price = p2.price + move * ratio;
2349
+ return { ratio, price, y: yFromPrice(price) };
2350
+ });
2351
+ for (let index = 0; index < levelLines.length - 1; index += 1) {
2352
+ const top = levelLines[index];
2353
+ const bottom = levelLines[index + 1];
2354
+ const bandTop = Math.min(top.y, bottom.y);
2355
+ const bandBottom = Math.max(top.y, bottom.y);
2356
+ ctx.save();
2357
+ ctx.fillStyle = hexToRgba(levelColorAt(index), 0.1);
2358
+ ctx.globalAlpha = draft ? 0.5 : 1;
2359
+ ctx.fillRect(chartLeft, bandTop, chartRight - chartLeft, bandBottom - bandTop);
2360
+ ctx.restore();
2361
+ }
2362
+ ctx.save();
2363
+ ctx.lineWidth = drawing.width;
2364
+ applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
2365
+ levelLines.forEach((level, index) => {
2366
+ ctx.strokeStyle = levelColorAt(index);
2367
+ ctx.beginPath();
2368
+ ctx.moveTo(crisp(chartLeft), crisp(level.y));
2369
+ ctx.lineTo(crisp(chartRight), crisp(level.y));
2370
+ ctx.stroke();
2371
+ });
2372
+ ctx.restore();
2373
+ const prevFont = ctx.font;
2374
+ ctx.font = `500 11px ${mergedOptions.fontFamily}`;
2375
+ levelLines.forEach((level, index) => {
2376
+ const labelText = `${level.ratio} (${formatPrice(level.price)})`;
2377
+ const textWidth = ctx.measureText(labelText).width;
2378
+ const padding = 4;
2379
+ const bgX = chartLeft + 4;
2380
+ const bgY = level.y - 9;
2381
+ ctx.fillStyle = mergedOptions.backgroundColor;
2382
+ fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
2383
+ ctx.fillStyle = levelColorAt(index);
2384
+ ctx.textAlign = "left";
2385
+ ctx.textBaseline = "middle";
2386
+ ctx.fillText(labelText, bgX + padding, bgY + 8);
2387
+ });
2388
+ ctx.font = prevFont;
2389
+ if (drawing.label) {
2390
+ drawDrawingLabel(drawing.label, (x0 + x2) / 2, Math.min(y0, y2), drawing.color);
2391
+ }
2392
+ }
2393
+ }
2302
2394
  }
2303
2395
  ctx.restore();
2304
2396
  };
@@ -3460,6 +3552,41 @@ function createChart(element, options = {}) {
3460
3552
  return { drawing, target: "line" };
3461
3553
  }
3462
3554
  }
3555
+ } else if (drawing.type === "fib-extension") {
3556
+ const p0 = drawing.points[0];
3557
+ const p1 = drawing.points[1];
3558
+ const p2 = drawing.points[2];
3559
+ if (!p0 || !p1) continue;
3560
+ const x0 = canvasXFromDrawingPoint(p0);
3561
+ const y0 = canvasYFromDrawingPrice(p0.price);
3562
+ const x1 = canvasXFromDrawingPoint(p1);
3563
+ const y1 = canvasYFromDrawingPrice(p1.price);
3564
+ if (Math.hypot(x - x0, y - y0) <= 8) {
3565
+ return { drawing, target: "handle", pointIndex: 0 };
3566
+ }
3567
+ if (Math.hypot(x - x1, y - y1) <= 8) {
3568
+ return { drawing, target: "handle", pointIndex: 1 };
3569
+ }
3570
+ if (p2) {
3571
+ const x2 = canvasXFromDrawingPoint(p2);
3572
+ const y2 = canvasYFromDrawingPrice(p2.price);
3573
+ if (Math.hypot(x - x2, y - y2) <= 8) {
3574
+ return { drawing, target: "handle", pointIndex: 2 };
3575
+ }
3576
+ const move = p1.price - p0.price;
3577
+ const fibExtRatios = [0, 0.382, 0.5, 0.618, 1, 1.272, 1.618, 2.618];
3578
+ for (const ratio of fibExtRatios) {
3579
+ const lineY = canvasYFromDrawingPrice(p2.price + move * ratio);
3580
+ if (Math.abs(y - lineY) <= 5) {
3581
+ return { drawing, target: "line" };
3582
+ }
3583
+ }
3584
+ if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 || distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3585
+ return { drawing, target: "line" };
3586
+ }
3587
+ } else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
3588
+ return { drawing, target: "line" };
3589
+ }
3463
3590
  }
3464
3591
  }
3465
3592
  return null;
@@ -3620,6 +3747,40 @@ function createChart(element, options = {}) {
3620
3747
  type: "fib-retracement",
3621
3748
  points: [point, point],
3622
3749
  color: defaults.color ?? "#2563eb",
3750
+ colors: defaults.colors ?? FIB_DEFAULT_PALETTE,
3751
+ style: defaults.style ?? "solid",
3752
+ width: defaults.width ?? 1
3753
+ });
3754
+ draw();
3755
+ return true;
3756
+ }
3757
+ if (activeDrawingTool === "fib-extension") {
3758
+ if (draftDrawing?.type === "fib-extension") {
3759
+ if (draftDrawing.points.length < 3) {
3760
+ draftDrawing = normalizeDrawingState({
3761
+ ...serializeDrawing(draftDrawing),
3762
+ points: [...draftDrawing.points.slice(0, -1), point, point]
3763
+ });
3764
+ draw();
3765
+ return true;
3766
+ }
3767
+ const completed = normalizeDrawingState({
3768
+ ...serializeDrawing(draftDrawing),
3769
+ points: [draftDrawing.points[0], draftDrawing.points[1], point]
3770
+ });
3771
+ drawings.push(completed);
3772
+ draftDrawing = null;
3773
+ activeDrawingTool = null;
3774
+ emitDrawingsChange();
3775
+ draw();
3776
+ return true;
3777
+ }
3778
+ const defaults = getDrawingToolDefaults("fib-extension");
3779
+ draftDrawing = normalizeDrawingState({
3780
+ type: "fib-extension",
3781
+ points: [point, point],
3782
+ color: defaults.color ?? "#2563eb",
3783
+ colors: defaults.colors ?? FIB_DEFAULT_PALETTE,
3623
3784
  style: defaults.style ?? "solid",
3624
3785
  width: defaults.width ?? 1
3625
3786
  });
@@ -3898,12 +4059,12 @@ function createChart(element, options = {}) {
3898
4059
  setCrosshairPoint(null);
3899
4060
  return;
3900
4061
  }
3901
- if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
4062
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
3902
4063
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3903
4064
  if (nextPoint) {
3904
4065
  draftDrawing = {
3905
4066
  ...draftDrawing,
3906
- points: [draftDrawing.points[0], nextPoint]
4067
+ points: [...draftDrawing.points.slice(0, -1), nextPoint]
3907
4068
  };
3908
4069
  canvas.style.cursor = "crosshair";
3909
4070
  draw();
@@ -4549,5 +4710,6 @@ function createChart(element, options = {}) {
4549
4710
  }
4550
4711
  // Annotate the CommonJS export names for ESM import in node:
4551
4712
  0 && (module.exports = {
4713
+ FIB_DEFAULT_PALETTE,
4552
4714
  createChart
4553
4715
  });
@@ -44,7 +44,7 @@ interface ChartOptions {
44
44
  drawings?: DrawingObjectOptions[];
45
45
  }
46
46
  type IndicatorPane = "overlay" | "separate";
47
- type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement";
47
+ type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension";
48
48
  interface DrawingPoint {
49
49
  index: number;
50
50
  price: number;
@@ -56,11 +56,19 @@ interface DrawingObjectOptions {
56
56
  points: DrawingPoint[];
57
57
  visible?: boolean;
58
58
  color?: string;
59
+ /**
60
+ * Optional per-level colors. Currently used by `fib-retracement`: when set
61
+ * (non-empty), each level/band cycles through these colors instead of using
62
+ * the single `color`. Leave empty for a monochrome drawing.
63
+ */
64
+ colors?: string[];
59
65
  style?: "solid" | "dotted" | "dashed";
60
66
  width?: number;
61
67
  label?: string;
62
68
  locked?: boolean;
63
69
  }
70
+ /** Default multi-color palette for fib-retracement levels. */
71
+ declare const FIB_DEFAULT_PALETTE: string[];
64
72
  interface DrawingSelectEvent {
65
73
  drawing: DrawingObjectOptions;
66
74
  target: "line" | "handle";
@@ -74,7 +82,7 @@ interface DrawingHoverEvent {
74
82
  x: number;
75
83
  y: number;
76
84
  }
77
- type DrawingDefaults = Partial<Pick<DrawingObjectOptions, "color" | "style" | "width">>;
85
+ type DrawingDefaults = Partial<Pick<DrawingObjectOptions, "color" | "colors" | "style" | "width">>;
78
86
  interface IndicatorInstanceOptions<TInputs extends Record<string, unknown> = Record<string, unknown>> {
79
87
  id?: string;
80
88
  type: string;
@@ -447,4 +455,4 @@ interface ViewportState {
447
455
  }
448
456
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
449
457
 
450
- export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
458
+ export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
@@ -1,4 +1,14 @@
1
1
  // src/index.ts
2
+ var FIB_DEFAULT_PALETTE = [
3
+ "#787b86",
4
+ "#f23645",
5
+ "#ff9800",
6
+ "#4caf50",
7
+ "#089981",
8
+ "#00bcd4",
9
+ "#2962ff",
10
+ "#9c27b0"
11
+ ];
2
12
  var DEFAULT_GRID_OPTIONS = {
3
13
  color: "#2b2f38",
4
14
  opacity: 0.38,
@@ -920,6 +930,7 @@ function createChart(element, options = {}) {
920
930
  })),
921
931
  visible: drawing.visible ?? true,
922
932
  color: drawing.color ?? "#94a3b8",
933
+ colors: Array.isArray(drawing.colors) ? drawing.colors.filter((value) => typeof value === "string") : [],
923
934
  style: drawing.style ?? "dotted",
924
935
  width: Math.max(1, Number(drawing.width) || 1),
925
936
  locked: drawing.locked ?? false,
@@ -931,6 +942,7 @@ function createChart(element, options = {}) {
931
942
  points: drawing.points.map((point) => ({ ...point })),
932
943
  visible: drawing.visible,
933
944
  color: drawing.color,
945
+ colors: [...drawing.colors],
934
946
  style: drawing.style,
935
947
  width: drawing.width,
936
948
  locked: drawing.locked,
@@ -2076,6 +2088,7 @@ function createChart(element, options = {}) {
2076
2088
  };
2077
2089
  const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
2078
2090
  const FIB_RATIOS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
2091
+ const FIB_EXT_RATIOS = [0, 0.382, 0.5, 0.618, 1, 1.272, 1.618, 2.618];
2079
2092
  const hexToRgba = (hex, alpha) => {
2080
2093
  const cleaned = hex.replace("#", "");
2081
2094
  if (cleaned.length !== 6) {
@@ -2220,32 +2233,33 @@ function createChart(element, options = {}) {
2220
2233
  const secondY = yFromPrice(second.price);
2221
2234
  const lineLeft = chartLeft;
2222
2235
  const lineRight = chartRight;
2236
+ const palette = drawing.colors.length > 0 ? drawing.colors : null;
2237
+ const levelColorAt = (index) => palette ? palette[index % palette.length] : drawing.color;
2223
2238
  const levelLines = FIB_RATIOS.map((ratio) => {
2224
2239
  const price = first.price + (second.price - first.price) * (1 - ratio);
2225
2240
  return { ratio, price, y: yFromPrice(price) };
2226
2241
  });
2227
- const bandColor = hexToRgba(drawing.color, 0.1);
2228
2242
  for (let index = 0; index < levelLines.length - 1; index += 1) {
2229
2243
  const top = levelLines[index];
2230
2244
  const bottom = levelLines[index + 1];
2231
2245
  const bandTop = Math.min(top.y, bottom.y);
2232
2246
  const bandBottom = Math.max(top.y, bottom.y);
2233
2247
  ctx.save();
2234
- ctx.fillStyle = bandColor;
2248
+ ctx.fillStyle = hexToRgba(levelColorAt(index), 0.1);
2235
2249
  ctx.globalAlpha = draft ? 0.5 : 1;
2236
2250
  ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
2237
2251
  ctx.restore();
2238
2252
  }
2239
2253
  ctx.save();
2240
2254
  ctx.lineWidth = drawing.width;
2241
- ctx.strokeStyle = drawing.color;
2242
2255
  applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
2243
- for (const level of levelLines) {
2256
+ levelLines.forEach((level, index) => {
2257
+ ctx.strokeStyle = levelColorAt(index);
2244
2258
  ctx.beginPath();
2245
2259
  ctx.moveTo(crisp(lineLeft), crisp(level.y));
2246
2260
  ctx.lineTo(crisp(lineRight), crisp(level.y));
2247
2261
  ctx.stroke();
2248
- }
2262
+ });
2249
2263
  ctx.restore();
2250
2264
  ctx.save();
2251
2265
  ctx.strokeStyle = hexToRgba(drawing.color, 0.5);
@@ -2260,7 +2274,7 @@ function createChart(element, options = {}) {
2260
2274
  drawDrawingHandle(secondX, secondY, drawing.color);
2261
2275
  const prevFont = ctx.font;
2262
2276
  ctx.font = `500 11px ${mergedOptions.fontFamily}`;
2263
- for (const level of levelLines) {
2277
+ levelLines.forEach((level, index) => {
2264
2278
  const labelText = `${level.ratio} (${formatPrice(level.price)})`;
2265
2279
  const textWidth = ctx.measureText(labelText).width;
2266
2280
  const padding = 4;
@@ -2268,13 +2282,90 @@ function createChart(element, options = {}) {
2268
2282
  const bgY = level.y - 9;
2269
2283
  ctx.fillStyle = mergedOptions.backgroundColor;
2270
2284
  fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
2271
- ctx.fillStyle = drawing.color;
2285
+ ctx.fillStyle = levelColorAt(index);
2272
2286
  ctx.textAlign = "left";
2273
2287
  ctx.textBaseline = "middle";
2274
2288
  ctx.fillText(labelText, bgX + padding, bgY + 8);
2275
- }
2289
+ });
2276
2290
  ctx.font = prevFont;
2277
2291
  }
2292
+ } else if (drawing.type === "fib-extension") {
2293
+ const p0 = drawing.points[0];
2294
+ const p1 = drawing.points[1];
2295
+ const p2 = drawing.points[2];
2296
+ if (p0 && p1) {
2297
+ const x0 = xFromDrawingPoint(p0);
2298
+ const y0 = yFromPrice(p0.price);
2299
+ const x1 = xFromDrawingPoint(p1);
2300
+ const y1 = yFromPrice(p1.price);
2301
+ ctx.save();
2302
+ ctx.strokeStyle = hexToRgba(drawing.color, 0.6);
2303
+ ctx.setLineDash([4, 4]);
2304
+ ctx.lineWidth = 1;
2305
+ ctx.beginPath();
2306
+ ctx.moveTo(x0, y0);
2307
+ ctx.lineTo(x1, y1);
2308
+ if (p2) {
2309
+ ctx.lineTo(xFromDrawingPoint(p2), yFromPrice(p2.price));
2310
+ }
2311
+ ctx.stroke();
2312
+ ctx.restore();
2313
+ drawDrawingHandle(x0, y0, drawing.color);
2314
+ drawDrawingHandle(x1, y1, drawing.color);
2315
+ if (p2) {
2316
+ const x2 = xFromDrawingPoint(p2);
2317
+ const y2 = yFromPrice(p2.price);
2318
+ drawDrawingHandle(x2, y2, drawing.color);
2319
+ const palette = drawing.colors.length > 0 ? drawing.colors : null;
2320
+ const levelColorAt = (index) => palette ? palette[index % palette.length] : drawing.color;
2321
+ const move = p1.price - p0.price;
2322
+ const levelLines = FIB_EXT_RATIOS.map((ratio) => {
2323
+ const price = p2.price + move * ratio;
2324
+ return { ratio, price, y: yFromPrice(price) };
2325
+ });
2326
+ for (let index = 0; index < levelLines.length - 1; index += 1) {
2327
+ const top = levelLines[index];
2328
+ const bottom = levelLines[index + 1];
2329
+ const bandTop = Math.min(top.y, bottom.y);
2330
+ const bandBottom = Math.max(top.y, bottom.y);
2331
+ ctx.save();
2332
+ ctx.fillStyle = hexToRgba(levelColorAt(index), 0.1);
2333
+ ctx.globalAlpha = draft ? 0.5 : 1;
2334
+ ctx.fillRect(chartLeft, bandTop, chartRight - chartLeft, bandBottom - bandTop);
2335
+ ctx.restore();
2336
+ }
2337
+ ctx.save();
2338
+ ctx.lineWidth = drawing.width;
2339
+ applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
2340
+ levelLines.forEach((level, index) => {
2341
+ ctx.strokeStyle = levelColorAt(index);
2342
+ ctx.beginPath();
2343
+ ctx.moveTo(crisp(chartLeft), crisp(level.y));
2344
+ ctx.lineTo(crisp(chartRight), crisp(level.y));
2345
+ ctx.stroke();
2346
+ });
2347
+ ctx.restore();
2348
+ const prevFont = ctx.font;
2349
+ ctx.font = `500 11px ${mergedOptions.fontFamily}`;
2350
+ levelLines.forEach((level, index) => {
2351
+ const labelText = `${level.ratio} (${formatPrice(level.price)})`;
2352
+ const textWidth = ctx.measureText(labelText).width;
2353
+ const padding = 4;
2354
+ const bgX = chartLeft + 4;
2355
+ const bgY = level.y - 9;
2356
+ ctx.fillStyle = mergedOptions.backgroundColor;
2357
+ fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
2358
+ ctx.fillStyle = levelColorAt(index);
2359
+ ctx.textAlign = "left";
2360
+ ctx.textBaseline = "middle";
2361
+ ctx.fillText(labelText, bgX + padding, bgY + 8);
2362
+ });
2363
+ ctx.font = prevFont;
2364
+ if (drawing.label) {
2365
+ drawDrawingLabel(drawing.label, (x0 + x2) / 2, Math.min(y0, y2), drawing.color);
2366
+ }
2367
+ }
2368
+ }
2278
2369
  }
2279
2370
  ctx.restore();
2280
2371
  };
@@ -3436,6 +3527,41 @@ function createChart(element, options = {}) {
3436
3527
  return { drawing, target: "line" };
3437
3528
  }
3438
3529
  }
3530
+ } else if (drawing.type === "fib-extension") {
3531
+ const p0 = drawing.points[0];
3532
+ const p1 = drawing.points[1];
3533
+ const p2 = drawing.points[2];
3534
+ if (!p0 || !p1) continue;
3535
+ const x0 = canvasXFromDrawingPoint(p0);
3536
+ const y0 = canvasYFromDrawingPrice(p0.price);
3537
+ const x1 = canvasXFromDrawingPoint(p1);
3538
+ const y1 = canvasYFromDrawingPrice(p1.price);
3539
+ if (Math.hypot(x - x0, y - y0) <= 8) {
3540
+ return { drawing, target: "handle", pointIndex: 0 };
3541
+ }
3542
+ if (Math.hypot(x - x1, y - y1) <= 8) {
3543
+ return { drawing, target: "handle", pointIndex: 1 };
3544
+ }
3545
+ if (p2) {
3546
+ const x2 = canvasXFromDrawingPoint(p2);
3547
+ const y2 = canvasYFromDrawingPrice(p2.price);
3548
+ if (Math.hypot(x - x2, y - y2) <= 8) {
3549
+ return { drawing, target: "handle", pointIndex: 2 };
3550
+ }
3551
+ const move = p1.price - p0.price;
3552
+ const fibExtRatios = [0, 0.382, 0.5, 0.618, 1, 1.272, 1.618, 2.618];
3553
+ for (const ratio of fibExtRatios) {
3554
+ const lineY = canvasYFromDrawingPrice(p2.price + move * ratio);
3555
+ if (Math.abs(y - lineY) <= 5) {
3556
+ return { drawing, target: "line" };
3557
+ }
3558
+ }
3559
+ if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 || distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3560
+ return { drawing, target: "line" };
3561
+ }
3562
+ } else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
3563
+ return { drawing, target: "line" };
3564
+ }
3439
3565
  }
3440
3566
  }
3441
3567
  return null;
@@ -3596,6 +3722,40 @@ function createChart(element, options = {}) {
3596
3722
  type: "fib-retracement",
3597
3723
  points: [point, point],
3598
3724
  color: defaults.color ?? "#2563eb",
3725
+ colors: defaults.colors ?? FIB_DEFAULT_PALETTE,
3726
+ style: defaults.style ?? "solid",
3727
+ width: defaults.width ?? 1
3728
+ });
3729
+ draw();
3730
+ return true;
3731
+ }
3732
+ if (activeDrawingTool === "fib-extension") {
3733
+ if (draftDrawing?.type === "fib-extension") {
3734
+ if (draftDrawing.points.length < 3) {
3735
+ draftDrawing = normalizeDrawingState({
3736
+ ...serializeDrawing(draftDrawing),
3737
+ points: [...draftDrawing.points.slice(0, -1), point, point]
3738
+ });
3739
+ draw();
3740
+ return true;
3741
+ }
3742
+ const completed = normalizeDrawingState({
3743
+ ...serializeDrawing(draftDrawing),
3744
+ points: [draftDrawing.points[0], draftDrawing.points[1], point]
3745
+ });
3746
+ drawings.push(completed);
3747
+ draftDrawing = null;
3748
+ activeDrawingTool = null;
3749
+ emitDrawingsChange();
3750
+ draw();
3751
+ return true;
3752
+ }
3753
+ const defaults = getDrawingToolDefaults("fib-extension");
3754
+ draftDrawing = normalizeDrawingState({
3755
+ type: "fib-extension",
3756
+ points: [point, point],
3757
+ color: defaults.color ?? "#2563eb",
3758
+ colors: defaults.colors ?? FIB_DEFAULT_PALETTE,
3599
3759
  style: defaults.style ?? "solid",
3600
3760
  width: defaults.width ?? 1
3601
3761
  });
@@ -3874,12 +4034,12 @@ function createChart(element, options = {}) {
3874
4034
  setCrosshairPoint(null);
3875
4035
  return;
3876
4036
  }
3877
- if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
4037
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
3878
4038
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3879
4039
  if (nextPoint) {
3880
4040
  draftDrawing = {
3881
4041
  ...draftDrawing,
3882
- points: [draftDrawing.points[0], nextPoint]
4042
+ points: [...draftDrawing.points.slice(0, -1), nextPoint]
3883
4043
  };
3884
4044
  canvas.style.cursor = "crosshair";
3885
4045
  draw();
@@ -4524,5 +4684,6 @@ function createChart(element, options = {}) {
4524
4684
  };
4525
4685
  }
4526
4686
  export {
4687
+ FIB_DEFAULT_PALETTE,
4527
4688
  createChart
4528
4689
  };
package/dist/index.cjs CHANGED
@@ -20,9 +20,20 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ FIB_DEFAULT_PALETTE: () => FIB_DEFAULT_PALETTE,
23
24
  createChart: () => createChart
24
25
  });
25
26
  module.exports = __toCommonJS(index_exports);
27
+ var FIB_DEFAULT_PALETTE = [
28
+ "#787b86",
29
+ "#f23645",
30
+ "#ff9800",
31
+ "#4caf50",
32
+ "#089981",
33
+ "#00bcd4",
34
+ "#2962ff",
35
+ "#9c27b0"
36
+ ];
26
37
  var DEFAULT_GRID_OPTIONS = {
27
38
  color: "#2b2f38",
28
39
  opacity: 0.38,
@@ -944,6 +955,7 @@ function createChart(element, options = {}) {
944
955
  })),
945
956
  visible: drawing.visible ?? true,
946
957
  color: drawing.color ?? "#94a3b8",
958
+ colors: Array.isArray(drawing.colors) ? drawing.colors.filter((value) => typeof value === "string") : [],
947
959
  style: drawing.style ?? "dotted",
948
960
  width: Math.max(1, Number(drawing.width) || 1),
949
961
  locked: drawing.locked ?? false,
@@ -955,6 +967,7 @@ function createChart(element, options = {}) {
955
967
  points: drawing.points.map((point) => ({ ...point })),
956
968
  visible: drawing.visible,
957
969
  color: drawing.color,
970
+ colors: [...drawing.colors],
958
971
  style: drawing.style,
959
972
  width: drawing.width,
960
973
  locked: drawing.locked,
@@ -2100,6 +2113,7 @@ function createChart(element, options = {}) {
2100
2113
  };
2101
2114
  const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
2102
2115
  const FIB_RATIOS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
2116
+ const FIB_EXT_RATIOS = [0, 0.382, 0.5, 0.618, 1, 1.272, 1.618, 2.618];
2103
2117
  const hexToRgba = (hex, alpha) => {
2104
2118
  const cleaned = hex.replace("#", "");
2105
2119
  if (cleaned.length !== 6) {
@@ -2244,32 +2258,33 @@ function createChart(element, options = {}) {
2244
2258
  const secondY = yFromPrice(second.price);
2245
2259
  const lineLeft = chartLeft;
2246
2260
  const lineRight = chartRight;
2261
+ const palette = drawing.colors.length > 0 ? drawing.colors : null;
2262
+ const levelColorAt = (index) => palette ? palette[index % palette.length] : drawing.color;
2247
2263
  const levelLines = FIB_RATIOS.map((ratio) => {
2248
2264
  const price = first.price + (second.price - first.price) * (1 - ratio);
2249
2265
  return { ratio, price, y: yFromPrice(price) };
2250
2266
  });
2251
- const bandColor = hexToRgba(drawing.color, 0.1);
2252
2267
  for (let index = 0; index < levelLines.length - 1; index += 1) {
2253
2268
  const top = levelLines[index];
2254
2269
  const bottom = levelLines[index + 1];
2255
2270
  const bandTop = Math.min(top.y, bottom.y);
2256
2271
  const bandBottom = Math.max(top.y, bottom.y);
2257
2272
  ctx.save();
2258
- ctx.fillStyle = bandColor;
2273
+ ctx.fillStyle = hexToRgba(levelColorAt(index), 0.1);
2259
2274
  ctx.globalAlpha = draft ? 0.5 : 1;
2260
2275
  ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
2261
2276
  ctx.restore();
2262
2277
  }
2263
2278
  ctx.save();
2264
2279
  ctx.lineWidth = drawing.width;
2265
- ctx.strokeStyle = drawing.color;
2266
2280
  applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
2267
- for (const level of levelLines) {
2281
+ levelLines.forEach((level, index) => {
2282
+ ctx.strokeStyle = levelColorAt(index);
2268
2283
  ctx.beginPath();
2269
2284
  ctx.moveTo(crisp(lineLeft), crisp(level.y));
2270
2285
  ctx.lineTo(crisp(lineRight), crisp(level.y));
2271
2286
  ctx.stroke();
2272
- }
2287
+ });
2273
2288
  ctx.restore();
2274
2289
  ctx.save();
2275
2290
  ctx.strokeStyle = hexToRgba(drawing.color, 0.5);
@@ -2284,7 +2299,7 @@ function createChart(element, options = {}) {
2284
2299
  drawDrawingHandle(secondX, secondY, drawing.color);
2285
2300
  const prevFont = ctx.font;
2286
2301
  ctx.font = `500 11px ${mergedOptions.fontFamily}`;
2287
- for (const level of levelLines) {
2302
+ levelLines.forEach((level, index) => {
2288
2303
  const labelText = `${level.ratio} (${formatPrice(level.price)})`;
2289
2304
  const textWidth = ctx.measureText(labelText).width;
2290
2305
  const padding = 4;
@@ -2292,13 +2307,90 @@ function createChart(element, options = {}) {
2292
2307
  const bgY = level.y - 9;
2293
2308
  ctx.fillStyle = mergedOptions.backgroundColor;
2294
2309
  fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
2295
- ctx.fillStyle = drawing.color;
2310
+ ctx.fillStyle = levelColorAt(index);
2296
2311
  ctx.textAlign = "left";
2297
2312
  ctx.textBaseline = "middle";
2298
2313
  ctx.fillText(labelText, bgX + padding, bgY + 8);
2299
- }
2314
+ });
2300
2315
  ctx.font = prevFont;
2301
2316
  }
2317
+ } else if (drawing.type === "fib-extension") {
2318
+ const p0 = drawing.points[0];
2319
+ const p1 = drawing.points[1];
2320
+ const p2 = drawing.points[2];
2321
+ if (p0 && p1) {
2322
+ const x0 = xFromDrawingPoint(p0);
2323
+ const y0 = yFromPrice(p0.price);
2324
+ const x1 = xFromDrawingPoint(p1);
2325
+ const y1 = yFromPrice(p1.price);
2326
+ ctx.save();
2327
+ ctx.strokeStyle = hexToRgba(drawing.color, 0.6);
2328
+ ctx.setLineDash([4, 4]);
2329
+ ctx.lineWidth = 1;
2330
+ ctx.beginPath();
2331
+ ctx.moveTo(x0, y0);
2332
+ ctx.lineTo(x1, y1);
2333
+ if (p2) {
2334
+ ctx.lineTo(xFromDrawingPoint(p2), yFromPrice(p2.price));
2335
+ }
2336
+ ctx.stroke();
2337
+ ctx.restore();
2338
+ drawDrawingHandle(x0, y0, drawing.color);
2339
+ drawDrawingHandle(x1, y1, drawing.color);
2340
+ if (p2) {
2341
+ const x2 = xFromDrawingPoint(p2);
2342
+ const y2 = yFromPrice(p2.price);
2343
+ drawDrawingHandle(x2, y2, drawing.color);
2344
+ const palette = drawing.colors.length > 0 ? drawing.colors : null;
2345
+ const levelColorAt = (index) => palette ? palette[index % palette.length] : drawing.color;
2346
+ const move = p1.price - p0.price;
2347
+ const levelLines = FIB_EXT_RATIOS.map((ratio) => {
2348
+ const price = p2.price + move * ratio;
2349
+ return { ratio, price, y: yFromPrice(price) };
2350
+ });
2351
+ for (let index = 0; index < levelLines.length - 1; index += 1) {
2352
+ const top = levelLines[index];
2353
+ const bottom = levelLines[index + 1];
2354
+ const bandTop = Math.min(top.y, bottom.y);
2355
+ const bandBottom = Math.max(top.y, bottom.y);
2356
+ ctx.save();
2357
+ ctx.fillStyle = hexToRgba(levelColorAt(index), 0.1);
2358
+ ctx.globalAlpha = draft ? 0.5 : 1;
2359
+ ctx.fillRect(chartLeft, bandTop, chartRight - chartLeft, bandBottom - bandTop);
2360
+ ctx.restore();
2361
+ }
2362
+ ctx.save();
2363
+ ctx.lineWidth = drawing.width;
2364
+ applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
2365
+ levelLines.forEach((level, index) => {
2366
+ ctx.strokeStyle = levelColorAt(index);
2367
+ ctx.beginPath();
2368
+ ctx.moveTo(crisp(chartLeft), crisp(level.y));
2369
+ ctx.lineTo(crisp(chartRight), crisp(level.y));
2370
+ ctx.stroke();
2371
+ });
2372
+ ctx.restore();
2373
+ const prevFont = ctx.font;
2374
+ ctx.font = `500 11px ${mergedOptions.fontFamily}`;
2375
+ levelLines.forEach((level, index) => {
2376
+ const labelText = `${level.ratio} (${formatPrice(level.price)})`;
2377
+ const textWidth = ctx.measureText(labelText).width;
2378
+ const padding = 4;
2379
+ const bgX = chartLeft + 4;
2380
+ const bgY = level.y - 9;
2381
+ ctx.fillStyle = mergedOptions.backgroundColor;
2382
+ fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
2383
+ ctx.fillStyle = levelColorAt(index);
2384
+ ctx.textAlign = "left";
2385
+ ctx.textBaseline = "middle";
2386
+ ctx.fillText(labelText, bgX + padding, bgY + 8);
2387
+ });
2388
+ ctx.font = prevFont;
2389
+ if (drawing.label) {
2390
+ drawDrawingLabel(drawing.label, (x0 + x2) / 2, Math.min(y0, y2), drawing.color);
2391
+ }
2392
+ }
2393
+ }
2302
2394
  }
2303
2395
  ctx.restore();
2304
2396
  };
@@ -3460,6 +3552,41 @@ function createChart(element, options = {}) {
3460
3552
  return { drawing, target: "line" };
3461
3553
  }
3462
3554
  }
3555
+ } else if (drawing.type === "fib-extension") {
3556
+ const p0 = drawing.points[0];
3557
+ const p1 = drawing.points[1];
3558
+ const p2 = drawing.points[2];
3559
+ if (!p0 || !p1) continue;
3560
+ const x0 = canvasXFromDrawingPoint(p0);
3561
+ const y0 = canvasYFromDrawingPrice(p0.price);
3562
+ const x1 = canvasXFromDrawingPoint(p1);
3563
+ const y1 = canvasYFromDrawingPrice(p1.price);
3564
+ if (Math.hypot(x - x0, y - y0) <= 8) {
3565
+ return { drawing, target: "handle", pointIndex: 0 };
3566
+ }
3567
+ if (Math.hypot(x - x1, y - y1) <= 8) {
3568
+ return { drawing, target: "handle", pointIndex: 1 };
3569
+ }
3570
+ if (p2) {
3571
+ const x2 = canvasXFromDrawingPoint(p2);
3572
+ const y2 = canvasYFromDrawingPrice(p2.price);
3573
+ if (Math.hypot(x - x2, y - y2) <= 8) {
3574
+ return { drawing, target: "handle", pointIndex: 2 };
3575
+ }
3576
+ const move = p1.price - p0.price;
3577
+ const fibExtRatios = [0, 0.382, 0.5, 0.618, 1, 1.272, 1.618, 2.618];
3578
+ for (const ratio of fibExtRatios) {
3579
+ const lineY = canvasYFromDrawingPrice(p2.price + move * ratio);
3580
+ if (Math.abs(y - lineY) <= 5) {
3581
+ return { drawing, target: "line" };
3582
+ }
3583
+ }
3584
+ if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 || distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3585
+ return { drawing, target: "line" };
3586
+ }
3587
+ } else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
3588
+ return { drawing, target: "line" };
3589
+ }
3463
3590
  }
3464
3591
  }
3465
3592
  return null;
@@ -3620,6 +3747,40 @@ function createChart(element, options = {}) {
3620
3747
  type: "fib-retracement",
3621
3748
  points: [point, point],
3622
3749
  color: defaults.color ?? "#2563eb",
3750
+ colors: defaults.colors ?? FIB_DEFAULT_PALETTE,
3751
+ style: defaults.style ?? "solid",
3752
+ width: defaults.width ?? 1
3753
+ });
3754
+ draw();
3755
+ return true;
3756
+ }
3757
+ if (activeDrawingTool === "fib-extension") {
3758
+ if (draftDrawing?.type === "fib-extension") {
3759
+ if (draftDrawing.points.length < 3) {
3760
+ draftDrawing = normalizeDrawingState({
3761
+ ...serializeDrawing(draftDrawing),
3762
+ points: [...draftDrawing.points.slice(0, -1), point, point]
3763
+ });
3764
+ draw();
3765
+ return true;
3766
+ }
3767
+ const completed = normalizeDrawingState({
3768
+ ...serializeDrawing(draftDrawing),
3769
+ points: [draftDrawing.points[0], draftDrawing.points[1], point]
3770
+ });
3771
+ drawings.push(completed);
3772
+ draftDrawing = null;
3773
+ activeDrawingTool = null;
3774
+ emitDrawingsChange();
3775
+ draw();
3776
+ return true;
3777
+ }
3778
+ const defaults = getDrawingToolDefaults("fib-extension");
3779
+ draftDrawing = normalizeDrawingState({
3780
+ type: "fib-extension",
3781
+ points: [point, point],
3782
+ color: defaults.color ?? "#2563eb",
3783
+ colors: defaults.colors ?? FIB_DEFAULT_PALETTE,
3623
3784
  style: defaults.style ?? "solid",
3624
3785
  width: defaults.width ?? 1
3625
3786
  });
@@ -3898,12 +4059,12 @@ function createChart(element, options = {}) {
3898
4059
  setCrosshairPoint(null);
3899
4060
  return;
3900
4061
  }
3901
- if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
4062
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
3902
4063
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3903
4064
  if (nextPoint) {
3904
4065
  draftDrawing = {
3905
4066
  ...draftDrawing,
3906
- points: [draftDrawing.points[0], nextPoint]
4067
+ points: [...draftDrawing.points.slice(0, -1), nextPoint]
3907
4068
  };
3908
4069
  canvas.style.cursor = "crosshair";
3909
4070
  draw();
@@ -4549,5 +4710,6 @@ function createChart(element, options = {}) {
4549
4710
  }
4550
4711
  // Annotate the CommonJS export names for ESM import in node:
4551
4712
  0 && (module.exports = {
4713
+ FIB_DEFAULT_PALETTE,
4552
4714
  createChart
4553
4715
  });
package/dist/index.d.cts CHANGED
@@ -44,7 +44,7 @@ interface ChartOptions {
44
44
  drawings?: DrawingObjectOptions[];
45
45
  }
46
46
  type IndicatorPane = "overlay" | "separate";
47
- type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement";
47
+ type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension";
48
48
  interface DrawingPoint {
49
49
  index: number;
50
50
  price: number;
@@ -56,11 +56,19 @@ interface DrawingObjectOptions {
56
56
  points: DrawingPoint[];
57
57
  visible?: boolean;
58
58
  color?: string;
59
+ /**
60
+ * Optional per-level colors. Currently used by `fib-retracement`: when set
61
+ * (non-empty), each level/band cycles through these colors instead of using
62
+ * the single `color`. Leave empty for a monochrome drawing.
63
+ */
64
+ colors?: string[];
59
65
  style?: "solid" | "dotted" | "dashed";
60
66
  width?: number;
61
67
  label?: string;
62
68
  locked?: boolean;
63
69
  }
70
+ /** Default multi-color palette for fib-retracement levels. */
71
+ declare const FIB_DEFAULT_PALETTE: string[];
64
72
  interface DrawingSelectEvent {
65
73
  drawing: DrawingObjectOptions;
66
74
  target: "line" | "handle";
@@ -74,7 +82,7 @@ interface DrawingHoverEvent {
74
82
  x: number;
75
83
  y: number;
76
84
  }
77
- type DrawingDefaults = Partial<Pick<DrawingObjectOptions, "color" | "style" | "width">>;
85
+ type DrawingDefaults = Partial<Pick<DrawingObjectOptions, "color" | "colors" | "style" | "width">>;
78
86
  interface IndicatorInstanceOptions<TInputs extends Record<string, unknown> = Record<string, unknown>> {
79
87
  id?: string;
80
88
  type: string;
@@ -447,4 +455,4 @@ interface ViewportState {
447
455
  }
448
456
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
449
457
 
450
- export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
458
+ export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
package/dist/index.d.ts CHANGED
@@ -44,7 +44,7 @@ interface ChartOptions {
44
44
  drawings?: DrawingObjectOptions[];
45
45
  }
46
46
  type IndicatorPane = "overlay" | "separate";
47
- type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement";
47
+ type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension";
48
48
  interface DrawingPoint {
49
49
  index: number;
50
50
  price: number;
@@ -56,11 +56,19 @@ interface DrawingObjectOptions {
56
56
  points: DrawingPoint[];
57
57
  visible?: boolean;
58
58
  color?: string;
59
+ /**
60
+ * Optional per-level colors. Currently used by `fib-retracement`: when set
61
+ * (non-empty), each level/band cycles through these colors instead of using
62
+ * the single `color`. Leave empty for a monochrome drawing.
63
+ */
64
+ colors?: string[];
59
65
  style?: "solid" | "dotted" | "dashed";
60
66
  width?: number;
61
67
  label?: string;
62
68
  locked?: boolean;
63
69
  }
70
+ /** Default multi-color palette for fib-retracement levels. */
71
+ declare const FIB_DEFAULT_PALETTE: string[];
64
72
  interface DrawingSelectEvent {
65
73
  drawing: DrawingObjectOptions;
66
74
  target: "line" | "handle";
@@ -74,7 +82,7 @@ interface DrawingHoverEvent {
74
82
  x: number;
75
83
  y: number;
76
84
  }
77
- type DrawingDefaults = Partial<Pick<DrawingObjectOptions, "color" | "style" | "width">>;
85
+ type DrawingDefaults = Partial<Pick<DrawingObjectOptions, "color" | "colors" | "style" | "width">>;
78
86
  interface IndicatorInstanceOptions<TInputs extends Record<string, unknown> = Record<string, unknown>> {
79
87
  id?: string;
80
88
  type: string;
@@ -447,4 +455,4 @@ interface ViewportState {
447
455
  }
448
456
  declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
449
457
 
450
- export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
458
+ export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
package/dist/index.js CHANGED
@@ -1,4 +1,14 @@
1
1
  // src/index.ts
2
+ var FIB_DEFAULT_PALETTE = [
3
+ "#787b86",
4
+ "#f23645",
5
+ "#ff9800",
6
+ "#4caf50",
7
+ "#089981",
8
+ "#00bcd4",
9
+ "#2962ff",
10
+ "#9c27b0"
11
+ ];
2
12
  var DEFAULT_GRID_OPTIONS = {
3
13
  color: "#2b2f38",
4
14
  opacity: 0.38,
@@ -920,6 +930,7 @@ function createChart(element, options = {}) {
920
930
  })),
921
931
  visible: drawing.visible ?? true,
922
932
  color: drawing.color ?? "#94a3b8",
933
+ colors: Array.isArray(drawing.colors) ? drawing.colors.filter((value) => typeof value === "string") : [],
923
934
  style: drawing.style ?? "dotted",
924
935
  width: Math.max(1, Number(drawing.width) || 1),
925
936
  locked: drawing.locked ?? false,
@@ -931,6 +942,7 @@ function createChart(element, options = {}) {
931
942
  points: drawing.points.map((point) => ({ ...point })),
932
943
  visible: drawing.visible,
933
944
  color: drawing.color,
945
+ colors: [...drawing.colors],
934
946
  style: drawing.style,
935
947
  width: drawing.width,
936
948
  locked: drawing.locked,
@@ -2076,6 +2088,7 @@ function createChart(element, options = {}) {
2076
2088
  };
2077
2089
  const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
2078
2090
  const FIB_RATIOS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
2091
+ const FIB_EXT_RATIOS = [0, 0.382, 0.5, 0.618, 1, 1.272, 1.618, 2.618];
2079
2092
  const hexToRgba = (hex, alpha) => {
2080
2093
  const cleaned = hex.replace("#", "");
2081
2094
  if (cleaned.length !== 6) {
@@ -2220,32 +2233,33 @@ function createChart(element, options = {}) {
2220
2233
  const secondY = yFromPrice(second.price);
2221
2234
  const lineLeft = chartLeft;
2222
2235
  const lineRight = chartRight;
2236
+ const palette = drawing.colors.length > 0 ? drawing.colors : null;
2237
+ const levelColorAt = (index) => palette ? palette[index % palette.length] : drawing.color;
2223
2238
  const levelLines = FIB_RATIOS.map((ratio) => {
2224
2239
  const price = first.price + (second.price - first.price) * (1 - ratio);
2225
2240
  return { ratio, price, y: yFromPrice(price) };
2226
2241
  });
2227
- const bandColor = hexToRgba(drawing.color, 0.1);
2228
2242
  for (let index = 0; index < levelLines.length - 1; index += 1) {
2229
2243
  const top = levelLines[index];
2230
2244
  const bottom = levelLines[index + 1];
2231
2245
  const bandTop = Math.min(top.y, bottom.y);
2232
2246
  const bandBottom = Math.max(top.y, bottom.y);
2233
2247
  ctx.save();
2234
- ctx.fillStyle = bandColor;
2248
+ ctx.fillStyle = hexToRgba(levelColorAt(index), 0.1);
2235
2249
  ctx.globalAlpha = draft ? 0.5 : 1;
2236
2250
  ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
2237
2251
  ctx.restore();
2238
2252
  }
2239
2253
  ctx.save();
2240
2254
  ctx.lineWidth = drawing.width;
2241
- ctx.strokeStyle = drawing.color;
2242
2255
  applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
2243
- for (const level of levelLines) {
2256
+ levelLines.forEach((level, index) => {
2257
+ ctx.strokeStyle = levelColorAt(index);
2244
2258
  ctx.beginPath();
2245
2259
  ctx.moveTo(crisp(lineLeft), crisp(level.y));
2246
2260
  ctx.lineTo(crisp(lineRight), crisp(level.y));
2247
2261
  ctx.stroke();
2248
- }
2262
+ });
2249
2263
  ctx.restore();
2250
2264
  ctx.save();
2251
2265
  ctx.strokeStyle = hexToRgba(drawing.color, 0.5);
@@ -2260,7 +2274,7 @@ function createChart(element, options = {}) {
2260
2274
  drawDrawingHandle(secondX, secondY, drawing.color);
2261
2275
  const prevFont = ctx.font;
2262
2276
  ctx.font = `500 11px ${mergedOptions.fontFamily}`;
2263
- for (const level of levelLines) {
2277
+ levelLines.forEach((level, index) => {
2264
2278
  const labelText = `${level.ratio} (${formatPrice(level.price)})`;
2265
2279
  const textWidth = ctx.measureText(labelText).width;
2266
2280
  const padding = 4;
@@ -2268,13 +2282,90 @@ function createChart(element, options = {}) {
2268
2282
  const bgY = level.y - 9;
2269
2283
  ctx.fillStyle = mergedOptions.backgroundColor;
2270
2284
  fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
2271
- ctx.fillStyle = drawing.color;
2285
+ ctx.fillStyle = levelColorAt(index);
2272
2286
  ctx.textAlign = "left";
2273
2287
  ctx.textBaseline = "middle";
2274
2288
  ctx.fillText(labelText, bgX + padding, bgY + 8);
2275
- }
2289
+ });
2276
2290
  ctx.font = prevFont;
2277
2291
  }
2292
+ } else if (drawing.type === "fib-extension") {
2293
+ const p0 = drawing.points[0];
2294
+ const p1 = drawing.points[1];
2295
+ const p2 = drawing.points[2];
2296
+ if (p0 && p1) {
2297
+ const x0 = xFromDrawingPoint(p0);
2298
+ const y0 = yFromPrice(p0.price);
2299
+ const x1 = xFromDrawingPoint(p1);
2300
+ const y1 = yFromPrice(p1.price);
2301
+ ctx.save();
2302
+ ctx.strokeStyle = hexToRgba(drawing.color, 0.6);
2303
+ ctx.setLineDash([4, 4]);
2304
+ ctx.lineWidth = 1;
2305
+ ctx.beginPath();
2306
+ ctx.moveTo(x0, y0);
2307
+ ctx.lineTo(x1, y1);
2308
+ if (p2) {
2309
+ ctx.lineTo(xFromDrawingPoint(p2), yFromPrice(p2.price));
2310
+ }
2311
+ ctx.stroke();
2312
+ ctx.restore();
2313
+ drawDrawingHandle(x0, y0, drawing.color);
2314
+ drawDrawingHandle(x1, y1, drawing.color);
2315
+ if (p2) {
2316
+ const x2 = xFromDrawingPoint(p2);
2317
+ const y2 = yFromPrice(p2.price);
2318
+ drawDrawingHandle(x2, y2, drawing.color);
2319
+ const palette = drawing.colors.length > 0 ? drawing.colors : null;
2320
+ const levelColorAt = (index) => palette ? palette[index % palette.length] : drawing.color;
2321
+ const move = p1.price - p0.price;
2322
+ const levelLines = FIB_EXT_RATIOS.map((ratio) => {
2323
+ const price = p2.price + move * ratio;
2324
+ return { ratio, price, y: yFromPrice(price) };
2325
+ });
2326
+ for (let index = 0; index < levelLines.length - 1; index += 1) {
2327
+ const top = levelLines[index];
2328
+ const bottom = levelLines[index + 1];
2329
+ const bandTop = Math.min(top.y, bottom.y);
2330
+ const bandBottom = Math.max(top.y, bottom.y);
2331
+ ctx.save();
2332
+ ctx.fillStyle = hexToRgba(levelColorAt(index), 0.1);
2333
+ ctx.globalAlpha = draft ? 0.5 : 1;
2334
+ ctx.fillRect(chartLeft, bandTop, chartRight - chartLeft, bandBottom - bandTop);
2335
+ ctx.restore();
2336
+ }
2337
+ ctx.save();
2338
+ ctx.lineWidth = drawing.width;
2339
+ applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
2340
+ levelLines.forEach((level, index) => {
2341
+ ctx.strokeStyle = levelColorAt(index);
2342
+ ctx.beginPath();
2343
+ ctx.moveTo(crisp(chartLeft), crisp(level.y));
2344
+ ctx.lineTo(crisp(chartRight), crisp(level.y));
2345
+ ctx.stroke();
2346
+ });
2347
+ ctx.restore();
2348
+ const prevFont = ctx.font;
2349
+ ctx.font = `500 11px ${mergedOptions.fontFamily}`;
2350
+ levelLines.forEach((level, index) => {
2351
+ const labelText = `${level.ratio} (${formatPrice(level.price)})`;
2352
+ const textWidth = ctx.measureText(labelText).width;
2353
+ const padding = 4;
2354
+ const bgX = chartLeft + 4;
2355
+ const bgY = level.y - 9;
2356
+ ctx.fillStyle = mergedOptions.backgroundColor;
2357
+ fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
2358
+ ctx.fillStyle = levelColorAt(index);
2359
+ ctx.textAlign = "left";
2360
+ ctx.textBaseline = "middle";
2361
+ ctx.fillText(labelText, bgX + padding, bgY + 8);
2362
+ });
2363
+ ctx.font = prevFont;
2364
+ if (drawing.label) {
2365
+ drawDrawingLabel(drawing.label, (x0 + x2) / 2, Math.min(y0, y2), drawing.color);
2366
+ }
2367
+ }
2368
+ }
2278
2369
  }
2279
2370
  ctx.restore();
2280
2371
  };
@@ -3436,6 +3527,41 @@ function createChart(element, options = {}) {
3436
3527
  return { drawing, target: "line" };
3437
3528
  }
3438
3529
  }
3530
+ } else if (drawing.type === "fib-extension") {
3531
+ const p0 = drawing.points[0];
3532
+ const p1 = drawing.points[1];
3533
+ const p2 = drawing.points[2];
3534
+ if (!p0 || !p1) continue;
3535
+ const x0 = canvasXFromDrawingPoint(p0);
3536
+ const y0 = canvasYFromDrawingPrice(p0.price);
3537
+ const x1 = canvasXFromDrawingPoint(p1);
3538
+ const y1 = canvasYFromDrawingPrice(p1.price);
3539
+ if (Math.hypot(x - x0, y - y0) <= 8) {
3540
+ return { drawing, target: "handle", pointIndex: 0 };
3541
+ }
3542
+ if (Math.hypot(x - x1, y - y1) <= 8) {
3543
+ return { drawing, target: "handle", pointIndex: 1 };
3544
+ }
3545
+ if (p2) {
3546
+ const x2 = canvasXFromDrawingPoint(p2);
3547
+ const y2 = canvasYFromDrawingPrice(p2.price);
3548
+ if (Math.hypot(x - x2, y - y2) <= 8) {
3549
+ return { drawing, target: "handle", pointIndex: 2 };
3550
+ }
3551
+ const move = p1.price - p0.price;
3552
+ const fibExtRatios = [0, 0.382, 0.5, 0.618, 1, 1.272, 1.618, 2.618];
3553
+ for (const ratio of fibExtRatios) {
3554
+ const lineY = canvasYFromDrawingPrice(p2.price + move * ratio);
3555
+ if (Math.abs(y - lineY) <= 5) {
3556
+ return { drawing, target: "line" };
3557
+ }
3558
+ }
3559
+ if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 || distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3560
+ return { drawing, target: "line" };
3561
+ }
3562
+ } else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
3563
+ return { drawing, target: "line" };
3564
+ }
3439
3565
  }
3440
3566
  }
3441
3567
  return null;
@@ -3596,6 +3722,40 @@ function createChart(element, options = {}) {
3596
3722
  type: "fib-retracement",
3597
3723
  points: [point, point],
3598
3724
  color: defaults.color ?? "#2563eb",
3725
+ colors: defaults.colors ?? FIB_DEFAULT_PALETTE,
3726
+ style: defaults.style ?? "solid",
3727
+ width: defaults.width ?? 1
3728
+ });
3729
+ draw();
3730
+ return true;
3731
+ }
3732
+ if (activeDrawingTool === "fib-extension") {
3733
+ if (draftDrawing?.type === "fib-extension") {
3734
+ if (draftDrawing.points.length < 3) {
3735
+ draftDrawing = normalizeDrawingState({
3736
+ ...serializeDrawing(draftDrawing),
3737
+ points: [...draftDrawing.points.slice(0, -1), point, point]
3738
+ });
3739
+ draw();
3740
+ return true;
3741
+ }
3742
+ const completed = normalizeDrawingState({
3743
+ ...serializeDrawing(draftDrawing),
3744
+ points: [draftDrawing.points[0], draftDrawing.points[1], point]
3745
+ });
3746
+ drawings.push(completed);
3747
+ draftDrawing = null;
3748
+ activeDrawingTool = null;
3749
+ emitDrawingsChange();
3750
+ draw();
3751
+ return true;
3752
+ }
3753
+ const defaults = getDrawingToolDefaults("fib-extension");
3754
+ draftDrawing = normalizeDrawingState({
3755
+ type: "fib-extension",
3756
+ points: [point, point],
3757
+ color: defaults.color ?? "#2563eb",
3758
+ colors: defaults.colors ?? FIB_DEFAULT_PALETTE,
3599
3759
  style: defaults.style ?? "solid",
3600
3760
  width: defaults.width ?? 1
3601
3761
  });
@@ -3874,12 +4034,12 @@ function createChart(element, options = {}) {
3874
4034
  setCrosshairPoint(null);
3875
4035
  return;
3876
4036
  }
3877
- if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
4037
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
3878
4038
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3879
4039
  if (nextPoint) {
3880
4040
  draftDrawing = {
3881
4041
  ...draftDrawing,
3882
- points: [draftDrawing.points[0], nextPoint]
4042
+ points: [...draftDrawing.points.slice(0, -1), nextPoint]
3883
4043
  };
3884
4044
  canvas.style.cursor = "crosshair";
3885
4045
  draw();
@@ -4524,5 +4684,6 @@ function createChart(element, options = {}) {
4524
4684
  };
4525
4685
  }
4526
4686
  export {
4687
+ FIB_DEFAULT_PALETTE,
4527
4688
  createChart
4528
4689
  };
package/docs/API.md CHANGED
@@ -416,14 +416,16 @@ Volume style inputs:
416
416
  Drawings are user-created chart tools, separate from indicators. They are interactive chart objects like horizontal lines and trendlines.
417
417
 
418
418
  - `id?: string`
419
- - `type: "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement"`
419
+ - `type: "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension"`
420
420
  - `horizontal-line` / `vertical-line`: single-point, full-width/full-height line (one click to place)
421
421
  - `trendline`: two-point segment (click start, click end)
422
422
  - `ray`: two-point line that extends infinitely past the second point
423
423
  - `fib-retracement`: two-point retracement with levels/bands
424
+ - `fib-extension`: trend-based three-point extension (click trend start, trend end, then the projection origin); levels project from the third point by the first→second move
424
425
  - `points: DrawingPoint[]`
425
426
  - `visible?: boolean`
426
427
  - `color?: string`
428
+ - `colors?: string[]` (per-level colors; used by `fib-retracement`. When non-empty, each level/band cycles through these instead of `color`. New fib drawings default to `FIB_DEFAULT_PALETTE`; set `colors: []` for a monochrome fib that follows `color`.)
427
429
  - `style?: "solid" | "dotted" | "dashed"`
428
430
  - `width?: number`
429
431
  - `label?: string`
@@ -446,6 +448,7 @@ chart.setActiveDrawingTool("vertical-line"); // next plot click creates a vert
446
448
  chart.setActiveDrawingTool("trendline"); // first click starts, second click commits
447
449
  chart.setActiveDrawingTool("ray"); // first click starts, second click commits (extends past p2)
448
450
  chart.setActiveDrawingTool("fib-retracement"); // first click starts, second click commits
451
+ chart.setActiveDrawingTool("fib-extension"); // three clicks: trend start, trend end, projection origin
449
452
  chart.setActiveDrawingTool(null); // back to normal cursor/pan
450
453
  ```
451
454
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.69",
3
+ "version": "0.1.71",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",