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.
- package/dist/hyperprop-charting-library.cjs +172 -10
- package/dist/hyperprop-charting-library.d.ts +11 -3
- package/dist/hyperprop-charting-library.js +171 -10
- package/dist/index.cjs +172 -10
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.js +171 -10
- package/docs/API.md +4 -1
- package/package.json +1 -1
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|