hyperprop-charting-library 0.1.59 → 0.1.60

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.
@@ -2021,6 +2021,16 @@ function createChart(element, options = {}) {
2021
2021
  return chartBottom - (price - yMin) / yRange * chartHeight;
2022
2022
  };
2023
2023
  const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
2024
+ const FIB_LEVELS = [
2025
+ { ratio: 0, color: "#787b86" },
2026
+ { ratio: 0.236, color: "#f23645", band: "rgba(242,54,69,0.10)" },
2027
+ { ratio: 0.382, color: "#ff9800", band: "rgba(255,152,0,0.10)" },
2028
+ { ratio: 0.5, color: "#4caf50", band: "rgba(76,175,80,0.10)" },
2029
+ { ratio: 0.618, color: "#089981", band: "rgba(8,153,129,0.10)" },
2030
+ { ratio: 0.786, color: "#00bcd4", band: "rgba(0,188,212,0.10)" },
2031
+ { ratio: 1, color: "#787b86", band: "rgba(120,123,134,0.10)" },
2032
+ { ratio: 1.618, color: "#2962ff", band: "rgba(41,98,255,0.08)" }
2033
+ ];
2024
2034
  const drawDrawingHandle = (x, y, color) => {
2025
2035
  ctx.save();
2026
2036
  ctx.setLineDash([]);
@@ -2097,6 +2107,72 @@ function createChart(element, options = {}) {
2097
2107
  drawDrawingLabel(drawing.label, midX, midY, drawing.color);
2098
2108
  }
2099
2109
  }
2110
+ } else if (drawing.type === "fib-retracement") {
2111
+ const first = drawing.points[0];
2112
+ const second = drawing.points[1];
2113
+ if (first && second) {
2114
+ const firstX = xFromDrawingPoint(first);
2115
+ const secondX = xFromDrawingPoint(second);
2116
+ const firstY = yFromPrice(first.price);
2117
+ const secondY = yFromPrice(second.price);
2118
+ const lineLeft = chartLeft;
2119
+ const lineRight = chartRight;
2120
+ const levelLines = FIB_LEVELS.map((level) => {
2121
+ const price = first.price + (second.price - first.price) * (1 - level.ratio);
2122
+ return { ...level, price, y: yFromPrice(price) };
2123
+ });
2124
+ for (let index = 0; index < levelLines.length - 1; index += 1) {
2125
+ const top = levelLines[index];
2126
+ const bottom = levelLines[index + 1];
2127
+ const fillColor = bottom.band ?? top.band;
2128
+ if (!fillColor) continue;
2129
+ const bandTop = Math.min(top.y, bottom.y);
2130
+ const bandBottom = Math.max(top.y, bottom.y);
2131
+ ctx.save();
2132
+ ctx.fillStyle = fillColor;
2133
+ ctx.globalAlpha = draft ? 0.5 : 1;
2134
+ ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
2135
+ ctx.restore();
2136
+ }
2137
+ ctx.save();
2138
+ ctx.lineWidth = drawing.width;
2139
+ applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
2140
+ for (const level of levelLines) {
2141
+ ctx.strokeStyle = level.color;
2142
+ ctx.beginPath();
2143
+ ctx.moveTo(crisp(lineLeft), crisp(level.y));
2144
+ ctx.lineTo(crisp(lineRight), crisp(level.y));
2145
+ ctx.stroke();
2146
+ }
2147
+ ctx.restore();
2148
+ ctx.save();
2149
+ ctx.strokeStyle = "rgba(148,163,184,0.7)";
2150
+ ctx.setLineDash([4, 4]);
2151
+ ctx.lineWidth = 1;
2152
+ ctx.beginPath();
2153
+ ctx.moveTo(firstX, firstY);
2154
+ ctx.lineTo(secondX, secondY);
2155
+ ctx.stroke();
2156
+ ctx.restore();
2157
+ drawDrawingHandle(firstX, firstY, drawing.color);
2158
+ drawDrawingHandle(secondX, secondY, drawing.color);
2159
+ const prevFont = ctx.font;
2160
+ ctx.font = `500 11px ${mergedOptions.fontFamily}`;
2161
+ for (const level of levelLines) {
2162
+ const labelText = `${level.ratio} (${formatPrice(level.price)})`;
2163
+ const textWidth = ctx.measureText(labelText).width;
2164
+ const padding = 4;
2165
+ const bgX = lineLeft + 4;
2166
+ const bgY = level.y - 9;
2167
+ ctx.fillStyle = mergedOptions.backgroundColor;
2168
+ fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
2169
+ ctx.fillStyle = level.color;
2170
+ ctx.textAlign = "left";
2171
+ ctx.textBaseline = "middle";
2172
+ ctx.fillText(labelText, bgX + padding, bgY + 8);
2173
+ }
2174
+ ctx.font = prevFont;
2175
+ }
2100
2176
  }
2101
2177
  ctx.restore();
2102
2178
  };
@@ -3183,6 +3259,28 @@ function createChart(element, options = {}) {
3183
3259
  if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3184
3260
  return { drawing, target: "line" };
3185
3261
  }
3262
+ } else if (drawing.type === "fib-retracement") {
3263
+ const first = drawing.points[0];
3264
+ const second = drawing.points[1];
3265
+ if (!first || !second) continue;
3266
+ const x1 = canvasXFromDrawingPoint(first);
3267
+ const y1 = canvasYFromDrawingPrice(first.price);
3268
+ const x2 = canvasXFromDrawingPoint(second);
3269
+ const y2 = canvasYFromDrawingPrice(second.price);
3270
+ if (Math.hypot(x - x1, y - y1) <= 9) {
3271
+ return { drawing, target: "handle", pointIndex: 0 };
3272
+ }
3273
+ if (Math.hypot(x - x2, y - y2) <= 9) {
3274
+ return { drawing, target: "handle", pointIndex: 1 };
3275
+ }
3276
+ const fibRatios = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
3277
+ for (const ratio of fibRatios) {
3278
+ const price = first.price + (second.price - first.price) * (1 - ratio);
3279
+ const lineY = canvasYFromDrawingPrice(price);
3280
+ if (Math.abs(y - lineY) <= 5) {
3281
+ return { drawing, target: "line" };
3282
+ }
3283
+ }
3186
3284
  }
3187
3285
  }
3188
3286
  return null;
@@ -3284,6 +3382,29 @@ function createChart(element, options = {}) {
3284
3382
  draw();
3285
3383
  return true;
3286
3384
  }
3385
+ if (activeDrawingTool === "fib-retracement") {
3386
+ if (draftDrawing?.type === "fib-retracement") {
3387
+ const completed = normalizeDrawingState({
3388
+ ...serializeDrawing(draftDrawing),
3389
+ points: [draftDrawing.points[0], point]
3390
+ });
3391
+ drawings.push(completed);
3392
+ draftDrawing = null;
3393
+ activeDrawingTool = null;
3394
+ emitDrawingsChange();
3395
+ draw();
3396
+ return true;
3397
+ }
3398
+ draftDrawing = normalizeDrawingState({
3399
+ type: "fib-retracement",
3400
+ points: [point, point],
3401
+ color: "#2563eb",
3402
+ style: "solid",
3403
+ width: 1
3404
+ });
3405
+ draw();
3406
+ return true;
3407
+ }
3287
3408
  return false;
3288
3409
  };
3289
3410
  const updateDrawingDrag = (x, y) => {
@@ -3519,7 +3640,7 @@ function createChart(element, options = {}) {
3519
3640
  setCrosshairPoint(null);
3520
3641
  return;
3521
3642
  }
3522
- if (draftDrawing && activeDrawingTool === "trendline") {
3643
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
3523
3644
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3524
3645
  if (nextPoint) {
3525
3646
  draftDrawing = {
@@ -43,7 +43,7 @@ interface ChartOptions {
43
43
  drawings?: DrawingObjectOptions[];
44
44
  }
45
45
  type IndicatorPane = "overlay" | "separate";
46
- type DrawingToolType = "horizontal-line" | "trendline";
46
+ type DrawingToolType = "horizontal-line" | "trendline" | "fib-retracement";
47
47
  interface DrawingPoint {
48
48
  index: number;
49
49
  price: number;
@@ -1997,6 +1997,16 @@ function createChart(element, options = {}) {
1997
1997
  return chartBottom - (price - yMin) / yRange * chartHeight;
1998
1998
  };
1999
1999
  const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
2000
+ const FIB_LEVELS = [
2001
+ { ratio: 0, color: "#787b86" },
2002
+ { ratio: 0.236, color: "#f23645", band: "rgba(242,54,69,0.10)" },
2003
+ { ratio: 0.382, color: "#ff9800", band: "rgba(255,152,0,0.10)" },
2004
+ { ratio: 0.5, color: "#4caf50", band: "rgba(76,175,80,0.10)" },
2005
+ { ratio: 0.618, color: "#089981", band: "rgba(8,153,129,0.10)" },
2006
+ { ratio: 0.786, color: "#00bcd4", band: "rgba(0,188,212,0.10)" },
2007
+ { ratio: 1, color: "#787b86", band: "rgba(120,123,134,0.10)" },
2008
+ { ratio: 1.618, color: "#2962ff", band: "rgba(41,98,255,0.08)" }
2009
+ ];
2000
2010
  const drawDrawingHandle = (x, y, color) => {
2001
2011
  ctx.save();
2002
2012
  ctx.setLineDash([]);
@@ -2073,6 +2083,72 @@ function createChart(element, options = {}) {
2073
2083
  drawDrawingLabel(drawing.label, midX, midY, drawing.color);
2074
2084
  }
2075
2085
  }
2086
+ } else if (drawing.type === "fib-retracement") {
2087
+ const first = drawing.points[0];
2088
+ const second = drawing.points[1];
2089
+ if (first && second) {
2090
+ const firstX = xFromDrawingPoint(first);
2091
+ const secondX = xFromDrawingPoint(second);
2092
+ const firstY = yFromPrice(first.price);
2093
+ const secondY = yFromPrice(second.price);
2094
+ const lineLeft = chartLeft;
2095
+ const lineRight = chartRight;
2096
+ const levelLines = FIB_LEVELS.map((level) => {
2097
+ const price = first.price + (second.price - first.price) * (1 - level.ratio);
2098
+ return { ...level, price, y: yFromPrice(price) };
2099
+ });
2100
+ for (let index = 0; index < levelLines.length - 1; index += 1) {
2101
+ const top = levelLines[index];
2102
+ const bottom = levelLines[index + 1];
2103
+ const fillColor = bottom.band ?? top.band;
2104
+ if (!fillColor) continue;
2105
+ const bandTop = Math.min(top.y, bottom.y);
2106
+ const bandBottom = Math.max(top.y, bottom.y);
2107
+ ctx.save();
2108
+ ctx.fillStyle = fillColor;
2109
+ ctx.globalAlpha = draft ? 0.5 : 1;
2110
+ ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
2111
+ ctx.restore();
2112
+ }
2113
+ ctx.save();
2114
+ ctx.lineWidth = drawing.width;
2115
+ applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
2116
+ for (const level of levelLines) {
2117
+ ctx.strokeStyle = level.color;
2118
+ ctx.beginPath();
2119
+ ctx.moveTo(crisp(lineLeft), crisp(level.y));
2120
+ ctx.lineTo(crisp(lineRight), crisp(level.y));
2121
+ ctx.stroke();
2122
+ }
2123
+ ctx.restore();
2124
+ ctx.save();
2125
+ ctx.strokeStyle = "rgba(148,163,184,0.7)";
2126
+ ctx.setLineDash([4, 4]);
2127
+ ctx.lineWidth = 1;
2128
+ ctx.beginPath();
2129
+ ctx.moveTo(firstX, firstY);
2130
+ ctx.lineTo(secondX, secondY);
2131
+ ctx.stroke();
2132
+ ctx.restore();
2133
+ drawDrawingHandle(firstX, firstY, drawing.color);
2134
+ drawDrawingHandle(secondX, secondY, drawing.color);
2135
+ const prevFont = ctx.font;
2136
+ ctx.font = `500 11px ${mergedOptions.fontFamily}`;
2137
+ for (const level of levelLines) {
2138
+ const labelText = `${level.ratio} (${formatPrice(level.price)})`;
2139
+ const textWidth = ctx.measureText(labelText).width;
2140
+ const padding = 4;
2141
+ const bgX = lineLeft + 4;
2142
+ const bgY = level.y - 9;
2143
+ ctx.fillStyle = mergedOptions.backgroundColor;
2144
+ fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
2145
+ ctx.fillStyle = level.color;
2146
+ ctx.textAlign = "left";
2147
+ ctx.textBaseline = "middle";
2148
+ ctx.fillText(labelText, bgX + padding, bgY + 8);
2149
+ }
2150
+ ctx.font = prevFont;
2151
+ }
2076
2152
  }
2077
2153
  ctx.restore();
2078
2154
  };
@@ -3159,6 +3235,28 @@ function createChart(element, options = {}) {
3159
3235
  if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3160
3236
  return { drawing, target: "line" };
3161
3237
  }
3238
+ } else if (drawing.type === "fib-retracement") {
3239
+ const first = drawing.points[0];
3240
+ const second = drawing.points[1];
3241
+ if (!first || !second) continue;
3242
+ const x1 = canvasXFromDrawingPoint(first);
3243
+ const y1 = canvasYFromDrawingPrice(first.price);
3244
+ const x2 = canvasXFromDrawingPoint(second);
3245
+ const y2 = canvasYFromDrawingPrice(second.price);
3246
+ if (Math.hypot(x - x1, y - y1) <= 9) {
3247
+ return { drawing, target: "handle", pointIndex: 0 };
3248
+ }
3249
+ if (Math.hypot(x - x2, y - y2) <= 9) {
3250
+ return { drawing, target: "handle", pointIndex: 1 };
3251
+ }
3252
+ const fibRatios = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
3253
+ for (const ratio of fibRatios) {
3254
+ const price = first.price + (second.price - first.price) * (1 - ratio);
3255
+ const lineY = canvasYFromDrawingPrice(price);
3256
+ if (Math.abs(y - lineY) <= 5) {
3257
+ return { drawing, target: "line" };
3258
+ }
3259
+ }
3162
3260
  }
3163
3261
  }
3164
3262
  return null;
@@ -3260,6 +3358,29 @@ function createChart(element, options = {}) {
3260
3358
  draw();
3261
3359
  return true;
3262
3360
  }
3361
+ if (activeDrawingTool === "fib-retracement") {
3362
+ if (draftDrawing?.type === "fib-retracement") {
3363
+ const completed = normalizeDrawingState({
3364
+ ...serializeDrawing(draftDrawing),
3365
+ points: [draftDrawing.points[0], point]
3366
+ });
3367
+ drawings.push(completed);
3368
+ draftDrawing = null;
3369
+ activeDrawingTool = null;
3370
+ emitDrawingsChange();
3371
+ draw();
3372
+ return true;
3373
+ }
3374
+ draftDrawing = normalizeDrawingState({
3375
+ type: "fib-retracement",
3376
+ points: [point, point],
3377
+ color: "#2563eb",
3378
+ style: "solid",
3379
+ width: 1
3380
+ });
3381
+ draw();
3382
+ return true;
3383
+ }
3263
3384
  return false;
3264
3385
  };
3265
3386
  const updateDrawingDrag = (x, y) => {
@@ -3495,7 +3616,7 @@ function createChart(element, options = {}) {
3495
3616
  setCrosshairPoint(null);
3496
3617
  return;
3497
3618
  }
3498
- if (draftDrawing && activeDrawingTool === "trendline") {
3619
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
3499
3620
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3500
3621
  if (nextPoint) {
3501
3622
  draftDrawing = {
package/dist/index.cjs CHANGED
@@ -2021,6 +2021,16 @@ function createChart(element, options = {}) {
2021
2021
  return chartBottom - (price - yMin) / yRange * chartHeight;
2022
2022
  };
2023
2023
  const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
2024
+ const FIB_LEVELS = [
2025
+ { ratio: 0, color: "#787b86" },
2026
+ { ratio: 0.236, color: "#f23645", band: "rgba(242,54,69,0.10)" },
2027
+ { ratio: 0.382, color: "#ff9800", band: "rgba(255,152,0,0.10)" },
2028
+ { ratio: 0.5, color: "#4caf50", band: "rgba(76,175,80,0.10)" },
2029
+ { ratio: 0.618, color: "#089981", band: "rgba(8,153,129,0.10)" },
2030
+ { ratio: 0.786, color: "#00bcd4", band: "rgba(0,188,212,0.10)" },
2031
+ { ratio: 1, color: "#787b86", band: "rgba(120,123,134,0.10)" },
2032
+ { ratio: 1.618, color: "#2962ff", band: "rgba(41,98,255,0.08)" }
2033
+ ];
2024
2034
  const drawDrawingHandle = (x, y, color) => {
2025
2035
  ctx.save();
2026
2036
  ctx.setLineDash([]);
@@ -2097,6 +2107,72 @@ function createChart(element, options = {}) {
2097
2107
  drawDrawingLabel(drawing.label, midX, midY, drawing.color);
2098
2108
  }
2099
2109
  }
2110
+ } else if (drawing.type === "fib-retracement") {
2111
+ const first = drawing.points[0];
2112
+ const second = drawing.points[1];
2113
+ if (first && second) {
2114
+ const firstX = xFromDrawingPoint(first);
2115
+ const secondX = xFromDrawingPoint(second);
2116
+ const firstY = yFromPrice(first.price);
2117
+ const secondY = yFromPrice(second.price);
2118
+ const lineLeft = chartLeft;
2119
+ const lineRight = chartRight;
2120
+ const levelLines = FIB_LEVELS.map((level) => {
2121
+ const price = first.price + (second.price - first.price) * (1 - level.ratio);
2122
+ return { ...level, price, y: yFromPrice(price) };
2123
+ });
2124
+ for (let index = 0; index < levelLines.length - 1; index += 1) {
2125
+ const top = levelLines[index];
2126
+ const bottom = levelLines[index + 1];
2127
+ const fillColor = bottom.band ?? top.band;
2128
+ if (!fillColor) continue;
2129
+ const bandTop = Math.min(top.y, bottom.y);
2130
+ const bandBottom = Math.max(top.y, bottom.y);
2131
+ ctx.save();
2132
+ ctx.fillStyle = fillColor;
2133
+ ctx.globalAlpha = draft ? 0.5 : 1;
2134
+ ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
2135
+ ctx.restore();
2136
+ }
2137
+ ctx.save();
2138
+ ctx.lineWidth = drawing.width;
2139
+ applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
2140
+ for (const level of levelLines) {
2141
+ ctx.strokeStyle = level.color;
2142
+ ctx.beginPath();
2143
+ ctx.moveTo(crisp(lineLeft), crisp(level.y));
2144
+ ctx.lineTo(crisp(lineRight), crisp(level.y));
2145
+ ctx.stroke();
2146
+ }
2147
+ ctx.restore();
2148
+ ctx.save();
2149
+ ctx.strokeStyle = "rgba(148,163,184,0.7)";
2150
+ ctx.setLineDash([4, 4]);
2151
+ ctx.lineWidth = 1;
2152
+ ctx.beginPath();
2153
+ ctx.moveTo(firstX, firstY);
2154
+ ctx.lineTo(secondX, secondY);
2155
+ ctx.stroke();
2156
+ ctx.restore();
2157
+ drawDrawingHandle(firstX, firstY, drawing.color);
2158
+ drawDrawingHandle(secondX, secondY, drawing.color);
2159
+ const prevFont = ctx.font;
2160
+ ctx.font = `500 11px ${mergedOptions.fontFamily}`;
2161
+ for (const level of levelLines) {
2162
+ const labelText = `${level.ratio} (${formatPrice(level.price)})`;
2163
+ const textWidth = ctx.measureText(labelText).width;
2164
+ const padding = 4;
2165
+ const bgX = lineLeft + 4;
2166
+ const bgY = level.y - 9;
2167
+ ctx.fillStyle = mergedOptions.backgroundColor;
2168
+ fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
2169
+ ctx.fillStyle = level.color;
2170
+ ctx.textAlign = "left";
2171
+ ctx.textBaseline = "middle";
2172
+ ctx.fillText(labelText, bgX + padding, bgY + 8);
2173
+ }
2174
+ ctx.font = prevFont;
2175
+ }
2100
2176
  }
2101
2177
  ctx.restore();
2102
2178
  };
@@ -3183,6 +3259,28 @@ function createChart(element, options = {}) {
3183
3259
  if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3184
3260
  return { drawing, target: "line" };
3185
3261
  }
3262
+ } else if (drawing.type === "fib-retracement") {
3263
+ const first = drawing.points[0];
3264
+ const second = drawing.points[1];
3265
+ if (!first || !second) continue;
3266
+ const x1 = canvasXFromDrawingPoint(first);
3267
+ const y1 = canvasYFromDrawingPrice(first.price);
3268
+ const x2 = canvasXFromDrawingPoint(second);
3269
+ const y2 = canvasYFromDrawingPrice(second.price);
3270
+ if (Math.hypot(x - x1, y - y1) <= 9) {
3271
+ return { drawing, target: "handle", pointIndex: 0 };
3272
+ }
3273
+ if (Math.hypot(x - x2, y - y2) <= 9) {
3274
+ return { drawing, target: "handle", pointIndex: 1 };
3275
+ }
3276
+ const fibRatios = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
3277
+ for (const ratio of fibRatios) {
3278
+ const price = first.price + (second.price - first.price) * (1 - ratio);
3279
+ const lineY = canvasYFromDrawingPrice(price);
3280
+ if (Math.abs(y - lineY) <= 5) {
3281
+ return { drawing, target: "line" };
3282
+ }
3283
+ }
3186
3284
  }
3187
3285
  }
3188
3286
  return null;
@@ -3284,6 +3382,29 @@ function createChart(element, options = {}) {
3284
3382
  draw();
3285
3383
  return true;
3286
3384
  }
3385
+ if (activeDrawingTool === "fib-retracement") {
3386
+ if (draftDrawing?.type === "fib-retracement") {
3387
+ const completed = normalizeDrawingState({
3388
+ ...serializeDrawing(draftDrawing),
3389
+ points: [draftDrawing.points[0], point]
3390
+ });
3391
+ drawings.push(completed);
3392
+ draftDrawing = null;
3393
+ activeDrawingTool = null;
3394
+ emitDrawingsChange();
3395
+ draw();
3396
+ return true;
3397
+ }
3398
+ draftDrawing = normalizeDrawingState({
3399
+ type: "fib-retracement",
3400
+ points: [point, point],
3401
+ color: "#2563eb",
3402
+ style: "solid",
3403
+ width: 1
3404
+ });
3405
+ draw();
3406
+ return true;
3407
+ }
3287
3408
  return false;
3288
3409
  };
3289
3410
  const updateDrawingDrag = (x, y) => {
@@ -3519,7 +3640,7 @@ function createChart(element, options = {}) {
3519
3640
  setCrosshairPoint(null);
3520
3641
  return;
3521
3642
  }
3522
- if (draftDrawing && activeDrawingTool === "trendline") {
3643
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
3523
3644
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3524
3645
  if (nextPoint) {
3525
3646
  draftDrawing = {
package/dist/index.d.cts CHANGED
@@ -43,7 +43,7 @@ interface ChartOptions {
43
43
  drawings?: DrawingObjectOptions[];
44
44
  }
45
45
  type IndicatorPane = "overlay" | "separate";
46
- type DrawingToolType = "horizontal-line" | "trendline";
46
+ type DrawingToolType = "horizontal-line" | "trendline" | "fib-retracement";
47
47
  interface DrawingPoint {
48
48
  index: number;
49
49
  price: number;
package/dist/index.d.ts CHANGED
@@ -43,7 +43,7 @@ interface ChartOptions {
43
43
  drawings?: DrawingObjectOptions[];
44
44
  }
45
45
  type IndicatorPane = "overlay" | "separate";
46
- type DrawingToolType = "horizontal-line" | "trendline";
46
+ type DrawingToolType = "horizontal-line" | "trendline" | "fib-retracement";
47
47
  interface DrawingPoint {
48
48
  index: number;
49
49
  price: number;
package/dist/index.js CHANGED
@@ -1997,6 +1997,16 @@ function createChart(element, options = {}) {
1997
1997
  return chartBottom - (price - yMin) / yRange * chartHeight;
1998
1998
  };
1999
1999
  const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
2000
+ const FIB_LEVELS = [
2001
+ { ratio: 0, color: "#787b86" },
2002
+ { ratio: 0.236, color: "#f23645", band: "rgba(242,54,69,0.10)" },
2003
+ { ratio: 0.382, color: "#ff9800", band: "rgba(255,152,0,0.10)" },
2004
+ { ratio: 0.5, color: "#4caf50", band: "rgba(76,175,80,0.10)" },
2005
+ { ratio: 0.618, color: "#089981", band: "rgba(8,153,129,0.10)" },
2006
+ { ratio: 0.786, color: "#00bcd4", band: "rgba(0,188,212,0.10)" },
2007
+ { ratio: 1, color: "#787b86", band: "rgba(120,123,134,0.10)" },
2008
+ { ratio: 1.618, color: "#2962ff", band: "rgba(41,98,255,0.08)" }
2009
+ ];
2000
2010
  const drawDrawingHandle = (x, y, color) => {
2001
2011
  ctx.save();
2002
2012
  ctx.setLineDash([]);
@@ -2073,6 +2083,72 @@ function createChart(element, options = {}) {
2073
2083
  drawDrawingLabel(drawing.label, midX, midY, drawing.color);
2074
2084
  }
2075
2085
  }
2086
+ } else if (drawing.type === "fib-retracement") {
2087
+ const first = drawing.points[0];
2088
+ const second = drawing.points[1];
2089
+ if (first && second) {
2090
+ const firstX = xFromDrawingPoint(first);
2091
+ const secondX = xFromDrawingPoint(second);
2092
+ const firstY = yFromPrice(first.price);
2093
+ const secondY = yFromPrice(second.price);
2094
+ const lineLeft = chartLeft;
2095
+ const lineRight = chartRight;
2096
+ const levelLines = FIB_LEVELS.map((level) => {
2097
+ const price = first.price + (second.price - first.price) * (1 - level.ratio);
2098
+ return { ...level, price, y: yFromPrice(price) };
2099
+ });
2100
+ for (let index = 0; index < levelLines.length - 1; index += 1) {
2101
+ const top = levelLines[index];
2102
+ const bottom = levelLines[index + 1];
2103
+ const fillColor = bottom.band ?? top.band;
2104
+ if (!fillColor) continue;
2105
+ const bandTop = Math.min(top.y, bottom.y);
2106
+ const bandBottom = Math.max(top.y, bottom.y);
2107
+ ctx.save();
2108
+ ctx.fillStyle = fillColor;
2109
+ ctx.globalAlpha = draft ? 0.5 : 1;
2110
+ ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
2111
+ ctx.restore();
2112
+ }
2113
+ ctx.save();
2114
+ ctx.lineWidth = drawing.width;
2115
+ applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
2116
+ for (const level of levelLines) {
2117
+ ctx.strokeStyle = level.color;
2118
+ ctx.beginPath();
2119
+ ctx.moveTo(crisp(lineLeft), crisp(level.y));
2120
+ ctx.lineTo(crisp(lineRight), crisp(level.y));
2121
+ ctx.stroke();
2122
+ }
2123
+ ctx.restore();
2124
+ ctx.save();
2125
+ ctx.strokeStyle = "rgba(148,163,184,0.7)";
2126
+ ctx.setLineDash([4, 4]);
2127
+ ctx.lineWidth = 1;
2128
+ ctx.beginPath();
2129
+ ctx.moveTo(firstX, firstY);
2130
+ ctx.lineTo(secondX, secondY);
2131
+ ctx.stroke();
2132
+ ctx.restore();
2133
+ drawDrawingHandle(firstX, firstY, drawing.color);
2134
+ drawDrawingHandle(secondX, secondY, drawing.color);
2135
+ const prevFont = ctx.font;
2136
+ ctx.font = `500 11px ${mergedOptions.fontFamily}`;
2137
+ for (const level of levelLines) {
2138
+ const labelText = `${level.ratio} (${formatPrice(level.price)})`;
2139
+ const textWidth = ctx.measureText(labelText).width;
2140
+ const padding = 4;
2141
+ const bgX = lineLeft + 4;
2142
+ const bgY = level.y - 9;
2143
+ ctx.fillStyle = mergedOptions.backgroundColor;
2144
+ fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
2145
+ ctx.fillStyle = level.color;
2146
+ ctx.textAlign = "left";
2147
+ ctx.textBaseline = "middle";
2148
+ ctx.fillText(labelText, bgX + padding, bgY + 8);
2149
+ }
2150
+ ctx.font = prevFont;
2151
+ }
2076
2152
  }
2077
2153
  ctx.restore();
2078
2154
  };
@@ -3159,6 +3235,28 @@ function createChart(element, options = {}) {
3159
3235
  if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3160
3236
  return { drawing, target: "line" };
3161
3237
  }
3238
+ } else if (drawing.type === "fib-retracement") {
3239
+ const first = drawing.points[0];
3240
+ const second = drawing.points[1];
3241
+ if (!first || !second) continue;
3242
+ const x1 = canvasXFromDrawingPoint(first);
3243
+ const y1 = canvasYFromDrawingPrice(first.price);
3244
+ const x2 = canvasXFromDrawingPoint(second);
3245
+ const y2 = canvasYFromDrawingPrice(second.price);
3246
+ if (Math.hypot(x - x1, y - y1) <= 9) {
3247
+ return { drawing, target: "handle", pointIndex: 0 };
3248
+ }
3249
+ if (Math.hypot(x - x2, y - y2) <= 9) {
3250
+ return { drawing, target: "handle", pointIndex: 1 };
3251
+ }
3252
+ const fibRatios = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
3253
+ for (const ratio of fibRatios) {
3254
+ const price = first.price + (second.price - first.price) * (1 - ratio);
3255
+ const lineY = canvasYFromDrawingPrice(price);
3256
+ if (Math.abs(y - lineY) <= 5) {
3257
+ return { drawing, target: "line" };
3258
+ }
3259
+ }
3162
3260
  }
3163
3261
  }
3164
3262
  return null;
@@ -3260,6 +3358,29 @@ function createChart(element, options = {}) {
3260
3358
  draw();
3261
3359
  return true;
3262
3360
  }
3361
+ if (activeDrawingTool === "fib-retracement") {
3362
+ if (draftDrawing?.type === "fib-retracement") {
3363
+ const completed = normalizeDrawingState({
3364
+ ...serializeDrawing(draftDrawing),
3365
+ points: [draftDrawing.points[0], point]
3366
+ });
3367
+ drawings.push(completed);
3368
+ draftDrawing = null;
3369
+ activeDrawingTool = null;
3370
+ emitDrawingsChange();
3371
+ draw();
3372
+ return true;
3373
+ }
3374
+ draftDrawing = normalizeDrawingState({
3375
+ type: "fib-retracement",
3376
+ points: [point, point],
3377
+ color: "#2563eb",
3378
+ style: "solid",
3379
+ width: 1
3380
+ });
3381
+ draw();
3382
+ return true;
3383
+ }
3263
3384
  return false;
3264
3385
  };
3265
3386
  const updateDrawingDrag = (x, y) => {
@@ -3495,7 +3616,7 @@ function createChart(element, options = {}) {
3495
3616
  setCrosshairPoint(null);
3496
3617
  return;
3497
3618
  }
3498
- if (draftDrawing && activeDrawingTool === "trendline") {
3619
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
3499
3620
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3500
3621
  if (nextPoint) {
3501
3622
  draftDrawing = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.59",
3
+ "version": "0.1.60",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",