hyperprop-charting-library 0.1.70 → 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.
- package/dist/hyperprop-charting-library.cjs +148 -2
- package/dist/hyperprop-charting-library.d.ts +1 -1
- package/dist/hyperprop-charting-library.js +148 -2
- package/dist/index.cjs +148 -2
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +148 -2
- package/docs/API.md +3 -1
- package/package.json +1 -1
|
@@ -2113,6 +2113,7 @@ function createChart(element, options = {}) {
|
|
|
2113
2113
|
};
|
|
2114
2114
|
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
2115
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];
|
|
2116
2117
|
const hexToRgba = (hex, alpha) => {
|
|
2117
2118
|
const cleaned = hex.replace("#", "");
|
|
2118
2119
|
if (cleaned.length !== 6) {
|
|
@@ -2313,6 +2314,83 @@ function createChart(element, options = {}) {
|
|
|
2313
2314
|
});
|
|
2314
2315
|
ctx.font = prevFont;
|
|
2315
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
|
+
}
|
|
2316
2394
|
}
|
|
2317
2395
|
ctx.restore();
|
|
2318
2396
|
};
|
|
@@ -3474,6 +3552,41 @@ function createChart(element, options = {}) {
|
|
|
3474
3552
|
return { drawing, target: "line" };
|
|
3475
3553
|
}
|
|
3476
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
|
+
}
|
|
3477
3590
|
}
|
|
3478
3591
|
}
|
|
3479
3592
|
return null;
|
|
@@ -3641,6 +3754,39 @@ function createChart(element, options = {}) {
|
|
|
3641
3754
|
draw();
|
|
3642
3755
|
return true;
|
|
3643
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,
|
|
3784
|
+
style: defaults.style ?? "solid",
|
|
3785
|
+
width: defaults.width ?? 1
|
|
3786
|
+
});
|
|
3787
|
+
draw();
|
|
3788
|
+
return true;
|
|
3789
|
+
}
|
|
3644
3790
|
return false;
|
|
3645
3791
|
};
|
|
3646
3792
|
const setDrawingDefaults = (tool, defaults) => {
|
|
@@ -3913,12 +4059,12 @@ function createChart(element, options = {}) {
|
|
|
3913
4059
|
setCrosshairPoint(null);
|
|
3914
4060
|
return;
|
|
3915
4061
|
}
|
|
3916
|
-
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
|
|
4062
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
|
|
3917
4063
|
const nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
3918
4064
|
if (nextPoint) {
|
|
3919
4065
|
draftDrawing = {
|
|
3920
4066
|
...draftDrawing,
|
|
3921
|
-
points: [draftDrawing.points
|
|
4067
|
+
points: [...draftDrawing.points.slice(0, -1), nextPoint]
|
|
3922
4068
|
};
|
|
3923
4069
|
canvas.style.cursor = "crosshair";
|
|
3924
4070
|
draw();
|
|
@@ -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;
|
|
@@ -2088,6 +2088,7 @@ function createChart(element, options = {}) {
|
|
|
2088
2088
|
};
|
|
2089
2089
|
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
2090
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];
|
|
2091
2092
|
const hexToRgba = (hex, alpha) => {
|
|
2092
2093
|
const cleaned = hex.replace("#", "");
|
|
2093
2094
|
if (cleaned.length !== 6) {
|
|
@@ -2288,6 +2289,83 @@ function createChart(element, options = {}) {
|
|
|
2288
2289
|
});
|
|
2289
2290
|
ctx.font = prevFont;
|
|
2290
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
|
+
}
|
|
2291
2369
|
}
|
|
2292
2370
|
ctx.restore();
|
|
2293
2371
|
};
|
|
@@ -3449,6 +3527,41 @@ function createChart(element, options = {}) {
|
|
|
3449
3527
|
return { drawing, target: "line" };
|
|
3450
3528
|
}
|
|
3451
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
|
+
}
|
|
3452
3565
|
}
|
|
3453
3566
|
}
|
|
3454
3567
|
return null;
|
|
@@ -3616,6 +3729,39 @@ function createChart(element, options = {}) {
|
|
|
3616
3729
|
draw();
|
|
3617
3730
|
return true;
|
|
3618
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,
|
|
3759
|
+
style: defaults.style ?? "solid",
|
|
3760
|
+
width: defaults.width ?? 1
|
|
3761
|
+
});
|
|
3762
|
+
draw();
|
|
3763
|
+
return true;
|
|
3764
|
+
}
|
|
3619
3765
|
return false;
|
|
3620
3766
|
};
|
|
3621
3767
|
const setDrawingDefaults = (tool, defaults) => {
|
|
@@ -3888,12 +4034,12 @@ function createChart(element, options = {}) {
|
|
|
3888
4034
|
setCrosshairPoint(null);
|
|
3889
4035
|
return;
|
|
3890
4036
|
}
|
|
3891
|
-
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
|
|
4037
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
|
|
3892
4038
|
const nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
3893
4039
|
if (nextPoint) {
|
|
3894
4040
|
draftDrawing = {
|
|
3895
4041
|
...draftDrawing,
|
|
3896
|
-
points: [draftDrawing.points
|
|
4042
|
+
points: [...draftDrawing.points.slice(0, -1), nextPoint]
|
|
3897
4043
|
};
|
|
3898
4044
|
canvas.style.cursor = "crosshair";
|
|
3899
4045
|
draw();
|
package/dist/index.cjs
CHANGED
|
@@ -2113,6 +2113,7 @@ function createChart(element, options = {}) {
|
|
|
2113
2113
|
};
|
|
2114
2114
|
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
2115
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];
|
|
2116
2117
|
const hexToRgba = (hex, alpha) => {
|
|
2117
2118
|
const cleaned = hex.replace("#", "");
|
|
2118
2119
|
if (cleaned.length !== 6) {
|
|
@@ -2313,6 +2314,83 @@ function createChart(element, options = {}) {
|
|
|
2313
2314
|
});
|
|
2314
2315
|
ctx.font = prevFont;
|
|
2315
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
|
+
}
|
|
2316
2394
|
}
|
|
2317
2395
|
ctx.restore();
|
|
2318
2396
|
};
|
|
@@ -3474,6 +3552,41 @@ function createChart(element, options = {}) {
|
|
|
3474
3552
|
return { drawing, target: "line" };
|
|
3475
3553
|
}
|
|
3476
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
|
+
}
|
|
3477
3590
|
}
|
|
3478
3591
|
}
|
|
3479
3592
|
return null;
|
|
@@ -3641,6 +3754,39 @@ function createChart(element, options = {}) {
|
|
|
3641
3754
|
draw();
|
|
3642
3755
|
return true;
|
|
3643
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,
|
|
3784
|
+
style: defaults.style ?? "solid",
|
|
3785
|
+
width: defaults.width ?? 1
|
|
3786
|
+
});
|
|
3787
|
+
draw();
|
|
3788
|
+
return true;
|
|
3789
|
+
}
|
|
3644
3790
|
return false;
|
|
3645
3791
|
};
|
|
3646
3792
|
const setDrawingDefaults = (tool, defaults) => {
|
|
@@ -3913,12 +4059,12 @@ function createChart(element, options = {}) {
|
|
|
3913
4059
|
setCrosshairPoint(null);
|
|
3914
4060
|
return;
|
|
3915
4061
|
}
|
|
3916
|
-
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
|
|
4062
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
|
|
3917
4063
|
const nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
3918
4064
|
if (nextPoint) {
|
|
3919
4065
|
draftDrawing = {
|
|
3920
4066
|
...draftDrawing,
|
|
3921
|
-
points: [draftDrawing.points
|
|
4067
|
+
points: [...draftDrawing.points.slice(0, -1), nextPoint]
|
|
3922
4068
|
};
|
|
3923
4069
|
canvas.style.cursor = "crosshair";
|
|
3924
4070
|
draw();
|
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;
|
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;
|
package/dist/index.js
CHANGED
|
@@ -2088,6 +2088,7 @@ function createChart(element, options = {}) {
|
|
|
2088
2088
|
};
|
|
2089
2089
|
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
2090
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];
|
|
2091
2092
|
const hexToRgba = (hex, alpha) => {
|
|
2092
2093
|
const cleaned = hex.replace("#", "");
|
|
2093
2094
|
if (cleaned.length !== 6) {
|
|
@@ -2288,6 +2289,83 @@ function createChart(element, options = {}) {
|
|
|
2288
2289
|
});
|
|
2289
2290
|
ctx.font = prevFont;
|
|
2290
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
|
+
}
|
|
2291
2369
|
}
|
|
2292
2370
|
ctx.restore();
|
|
2293
2371
|
};
|
|
@@ -3449,6 +3527,41 @@ function createChart(element, options = {}) {
|
|
|
3449
3527
|
return { drawing, target: "line" };
|
|
3450
3528
|
}
|
|
3451
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
|
+
}
|
|
3452
3565
|
}
|
|
3453
3566
|
}
|
|
3454
3567
|
return null;
|
|
@@ -3616,6 +3729,39 @@ function createChart(element, options = {}) {
|
|
|
3616
3729
|
draw();
|
|
3617
3730
|
return true;
|
|
3618
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,
|
|
3759
|
+
style: defaults.style ?? "solid",
|
|
3760
|
+
width: defaults.width ?? 1
|
|
3761
|
+
});
|
|
3762
|
+
draw();
|
|
3763
|
+
return true;
|
|
3764
|
+
}
|
|
3619
3765
|
return false;
|
|
3620
3766
|
};
|
|
3621
3767
|
const setDrawingDefaults = (tool, defaults) => {
|
|
@@ -3888,12 +4034,12 @@ function createChart(element, options = {}) {
|
|
|
3888
4034
|
setCrosshairPoint(null);
|
|
3889
4035
|
return;
|
|
3890
4036
|
}
|
|
3891
|
-
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
|
|
4037
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement" || activeDrawingTool === "fib-extension")) {
|
|
3892
4038
|
const nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
3893
4039
|
if (nextPoint) {
|
|
3894
4040
|
draftDrawing = {
|
|
3895
4041
|
...draftDrawing,
|
|
3896
|
-
points: [draftDrawing.points
|
|
4042
|
+
points: [...draftDrawing.points.slice(0, -1), nextPoint]
|
|
3897
4043
|
};
|
|
3898
4044
|
canvas.style.cursor = "crosshair";
|
|
3899
4045
|
draw();
|
package/docs/API.md
CHANGED
|
@@ -416,11 +416,12 @@ 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`
|
|
@@ -447,6 +448,7 @@ chart.setActiveDrawingTool("vertical-line"); // next plot click creates a vert
|
|
|
447
448
|
chart.setActiveDrawingTool("trendline"); // first click starts, second click commits
|
|
448
449
|
chart.setActiveDrawingTool("ray"); // first click starts, second click commits (extends past p2)
|
|
449
450
|
chart.setActiveDrawingTool("fib-retracement"); // first click starts, second click commits
|
|
451
|
+
chart.setActiveDrawingTool("fib-extension"); // three clicks: trend start, trend end, projection origin
|
|
450
452
|
chart.setActiveDrawingTool(null); // back to normal cursor/pan
|
|
451
453
|
```
|
|
452
454
|
|