hyperprop-charting-library 0.1.67 → 0.1.69
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 +146 -16
- package/dist/hyperprop-charting-library.d.ts +3 -1
- package/dist/hyperprop-charting-library.js +146 -16
- package/dist/index.cjs +146 -16
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +146 -16
- package/docs/API.md +12 -3
- package/docs/RECIPES.md +5 -2
- package/package.json +1 -1
|
@@ -43,6 +43,8 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
|
|
|
43
43
|
color: "#94a3b8",
|
|
44
44
|
width: 1,
|
|
45
45
|
style: "dotted",
|
|
46
|
+
mode: "cross",
|
|
47
|
+
dotRadius: 3,
|
|
46
48
|
showHorizontal: true,
|
|
47
49
|
showVertical: true,
|
|
48
50
|
showPriceLabel: true,
|
|
@@ -2168,6 +2170,20 @@ function createChart(element, options = {}) {
|
|
|
2168
2170
|
drawDrawingLabel(drawing.label, midX, y, drawing.color);
|
|
2169
2171
|
}
|
|
2170
2172
|
}
|
|
2173
|
+
} else if (drawing.type === "vertical-line") {
|
|
2174
|
+
const point = drawing.points[0];
|
|
2175
|
+
if (point) {
|
|
2176
|
+
const x = xFromDrawingPoint(point);
|
|
2177
|
+
ctx.beginPath();
|
|
2178
|
+
ctx.moveTo(crisp(x), crisp(chartTop));
|
|
2179
|
+
ctx.lineTo(crisp(x), crisp(chartBottom));
|
|
2180
|
+
ctx.stroke();
|
|
2181
|
+
const handleY = (chartTop + chartBottom) / 2;
|
|
2182
|
+
drawDrawingHandle(x, handleY, drawing.color);
|
|
2183
|
+
if (drawing.label) {
|
|
2184
|
+
drawDrawingLabel(drawing.label, x, chartTop + 16, drawing.color);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2171
2187
|
} else if (drawing.type === "trendline") {
|
|
2172
2188
|
const first = drawing.points[0];
|
|
2173
2189
|
const second = drawing.points[1];
|
|
@@ -2188,6 +2204,36 @@ function createChart(element, options = {}) {
|
|
|
2188
2204
|
drawDrawingLabel(drawing.label, midX, midY, drawing.color);
|
|
2189
2205
|
}
|
|
2190
2206
|
}
|
|
2207
|
+
} else if (drawing.type === "ray") {
|
|
2208
|
+
const first = drawing.points[0];
|
|
2209
|
+
const second = drawing.points[1];
|
|
2210
|
+
if (first && second) {
|
|
2211
|
+
const firstX = xFromDrawingPoint(first);
|
|
2212
|
+
const firstY = yFromPrice(first.price);
|
|
2213
|
+
const secondX = xFromDrawingPoint(second);
|
|
2214
|
+
const secondY = yFromPrice(second.price);
|
|
2215
|
+
const dx = secondX - firstX;
|
|
2216
|
+
const dy = secondY - firstY;
|
|
2217
|
+
let endX = secondX;
|
|
2218
|
+
let endY = secondY;
|
|
2219
|
+
const len = Math.hypot(dx, dy);
|
|
2220
|
+
if (len > 0) {
|
|
2221
|
+
const far = Math.abs(chartRight - chartLeft) + Math.abs(chartBottom - chartTop) + 2e3;
|
|
2222
|
+
endX = firstX + dx / len * far;
|
|
2223
|
+
endY = firstY + dy / len * far;
|
|
2224
|
+
}
|
|
2225
|
+
ctx.beginPath();
|
|
2226
|
+
ctx.moveTo(firstX, firstY);
|
|
2227
|
+
ctx.lineTo(endX, endY);
|
|
2228
|
+
ctx.stroke();
|
|
2229
|
+
drawDrawingHandle(firstX, firstY, drawing.color);
|
|
2230
|
+
drawDrawingHandle(secondX, secondY, drawing.color);
|
|
2231
|
+
if (drawing.label) {
|
|
2232
|
+
const midX = (firstX + secondX) / 2;
|
|
2233
|
+
const midY = (firstY + secondY) / 2;
|
|
2234
|
+
drawDrawingLabel(drawing.label, midX, midY, drawing.color);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2191
2237
|
} else if (drawing.type === "fib-retracement") {
|
|
2192
2238
|
const first = drawing.points[0];
|
|
2193
2239
|
const second = drawing.points[1];
|
|
@@ -2435,23 +2481,32 @@ function createChart(element, options = {}) {
|
|
|
2435
2481
|
if (crosshair.visible && crosshairPoint) {
|
|
2436
2482
|
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
2437
2483
|
const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2442
|
-
if (crosshair.showVertical) {
|
|
2443
|
-
ctx.beginPath();
|
|
2444
|
-
ctx.moveTo(crisp(cx), crisp(chartTop));
|
|
2445
|
-
ctx.lineTo(crisp(cx), crisp(chartBottom));
|
|
2446
|
-
ctx.stroke();
|
|
2447
|
-
}
|
|
2448
|
-
if (crosshair.showHorizontal) {
|
|
2484
|
+
if (crosshair.mode === "dot") {
|
|
2485
|
+
ctx.save();
|
|
2486
|
+
ctx.fillStyle = crosshair.color;
|
|
2449
2487
|
ctx.beginPath();
|
|
2450
|
-
ctx.
|
|
2451
|
-
ctx.
|
|
2452
|
-
ctx.
|
|
2488
|
+
ctx.arc(cx, cy, Math.max(1, crosshair.dotRadius), 0, Math.PI * 2);
|
|
2489
|
+
ctx.fill();
|
|
2490
|
+
ctx.restore();
|
|
2491
|
+
} else {
|
|
2492
|
+
ctx.save();
|
|
2493
|
+
ctx.strokeStyle = crosshair.color;
|
|
2494
|
+
ctx.lineWidth = Math.max(1, crosshair.width);
|
|
2495
|
+
applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2496
|
+
if (crosshair.showVertical) {
|
|
2497
|
+
ctx.beginPath();
|
|
2498
|
+
ctx.moveTo(crisp(cx), crisp(chartTop));
|
|
2499
|
+
ctx.lineTo(crisp(cx), crisp(chartBottom));
|
|
2500
|
+
ctx.stroke();
|
|
2501
|
+
}
|
|
2502
|
+
if (crosshair.showHorizontal) {
|
|
2503
|
+
ctx.beginPath();
|
|
2504
|
+
ctx.moveTo(crisp(chartLeft), crisp(cy));
|
|
2505
|
+
ctx.lineTo(crisp(chartRight), crisp(cy));
|
|
2506
|
+
ctx.stroke();
|
|
2507
|
+
}
|
|
2508
|
+
ctx.restore();
|
|
2453
2509
|
}
|
|
2454
|
-
ctx.restore();
|
|
2455
2510
|
}
|
|
2456
2511
|
ctx.restore();
|
|
2457
2512
|
if (activeSeparateIndicators.length > 0) {
|
|
@@ -3330,6 +3385,17 @@ function createChart(element, options = {}) {
|
|
|
3330
3385
|
if (Math.abs(y - lineY) <= 7) {
|
|
3331
3386
|
return { drawing, target: "line" };
|
|
3332
3387
|
}
|
|
3388
|
+
} else if (drawing.type === "vertical-line") {
|
|
3389
|
+
const point = drawing.points[0];
|
|
3390
|
+
if (!point) continue;
|
|
3391
|
+
const lineX = canvasXFromDrawingPoint(point);
|
|
3392
|
+
const handleY = drawState ? (drawState.chartTop + drawState.chartBottom) / 2 : 0;
|
|
3393
|
+
if (Math.hypot(x - lineX, y - handleY) <= 8) {
|
|
3394
|
+
return { drawing, target: "handle", pointIndex: 0 };
|
|
3395
|
+
}
|
|
3396
|
+
if (Math.abs(x - lineX) <= 7) {
|
|
3397
|
+
return { drawing, target: "line" };
|
|
3398
|
+
}
|
|
3333
3399
|
} else if (drawing.type === "trendline") {
|
|
3334
3400
|
const first = drawing.points[0];
|
|
3335
3401
|
const second = drawing.points[1];
|
|
@@ -3347,6 +3413,31 @@ function createChart(element, options = {}) {
|
|
|
3347
3413
|
if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
|
|
3348
3414
|
return { drawing, target: "line" };
|
|
3349
3415
|
}
|
|
3416
|
+
} else if (drawing.type === "ray") {
|
|
3417
|
+
const first = drawing.points[0];
|
|
3418
|
+
const second = drawing.points[1];
|
|
3419
|
+
if (!first || !second) continue;
|
|
3420
|
+
const x1 = canvasXFromDrawingPoint(first);
|
|
3421
|
+
const y1 = canvasYFromDrawingPrice(first.price);
|
|
3422
|
+
const x2 = canvasXFromDrawingPoint(second);
|
|
3423
|
+
const y2 = canvasYFromDrawingPrice(second.price);
|
|
3424
|
+
if (Math.hypot(x - x1, y - y1) <= 8) {
|
|
3425
|
+
return { drawing, target: "handle", pointIndex: 0 };
|
|
3426
|
+
}
|
|
3427
|
+
if (Math.hypot(x - x2, y - y2) <= 8) {
|
|
3428
|
+
return { drawing, target: "handle", pointIndex: 1 };
|
|
3429
|
+
}
|
|
3430
|
+
const dx = x2 - x1;
|
|
3431
|
+
const dy = y2 - y1;
|
|
3432
|
+
const lengthSq = dx * dx + dy * dy;
|
|
3433
|
+
if (lengthSq > 0) {
|
|
3434
|
+
const t = Math.max(0, ((x - x1) * dx + (y - y1) * dy) / lengthSq);
|
|
3435
|
+
const projX = x1 + t * dx;
|
|
3436
|
+
const projY = y1 + t * dy;
|
|
3437
|
+
if (Math.hypot(x - projX, y - projY) <= 6) {
|
|
3438
|
+
return { drawing, target: "line" };
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3350
3441
|
} else if (drawing.type === "fib-retracement") {
|
|
3351
3442
|
const first = drawing.points[0];
|
|
3352
3443
|
const second = drawing.points[1];
|
|
@@ -3448,6 +3539,21 @@ function createChart(element, options = {}) {
|
|
|
3448
3539
|
draw();
|
|
3449
3540
|
return true;
|
|
3450
3541
|
}
|
|
3542
|
+
if (activeDrawingTool === "vertical-line") {
|
|
3543
|
+
const defaults = getDrawingToolDefaults("vertical-line");
|
|
3544
|
+
drawings.push(
|
|
3545
|
+
normalizeDrawingState({
|
|
3546
|
+
type: "vertical-line",
|
|
3547
|
+
points: [point],
|
|
3548
|
+
color: defaults.color ?? "#38bdf8",
|
|
3549
|
+
style: defaults.style ?? "solid",
|
|
3550
|
+
width: defaults.width ?? 1
|
|
3551
|
+
})
|
|
3552
|
+
);
|
|
3553
|
+
emitDrawingsChange();
|
|
3554
|
+
draw();
|
|
3555
|
+
return true;
|
|
3556
|
+
}
|
|
3451
3557
|
if (activeDrawingTool === "trendline") {
|
|
3452
3558
|
if (draftDrawing?.type === "trendline") {
|
|
3453
3559
|
const completed = normalizeDrawingState({
|
|
@@ -3472,6 +3578,30 @@ function createChart(element, options = {}) {
|
|
|
3472
3578
|
draw();
|
|
3473
3579
|
return true;
|
|
3474
3580
|
}
|
|
3581
|
+
if (activeDrawingTool === "ray") {
|
|
3582
|
+
if (draftDrawing?.type === "ray") {
|
|
3583
|
+
const completed = normalizeDrawingState({
|
|
3584
|
+
...serializeDrawing(draftDrawing),
|
|
3585
|
+
points: [draftDrawing.points[0], point]
|
|
3586
|
+
});
|
|
3587
|
+
drawings.push(completed);
|
|
3588
|
+
draftDrawing = null;
|
|
3589
|
+
activeDrawingTool = null;
|
|
3590
|
+
emitDrawingsChange();
|
|
3591
|
+
draw();
|
|
3592
|
+
return true;
|
|
3593
|
+
}
|
|
3594
|
+
const defaults = getDrawingToolDefaults("ray");
|
|
3595
|
+
draftDrawing = normalizeDrawingState({
|
|
3596
|
+
type: "ray",
|
|
3597
|
+
points: [point, point],
|
|
3598
|
+
color: defaults.color ?? "#2563eb",
|
|
3599
|
+
style: defaults.style ?? "solid",
|
|
3600
|
+
width: defaults.width ?? 2
|
|
3601
|
+
});
|
|
3602
|
+
draw();
|
|
3603
|
+
return true;
|
|
3604
|
+
}
|
|
3475
3605
|
if (activeDrawingTool === "fib-retracement") {
|
|
3476
3606
|
if (draftDrawing?.type === "fib-retracement") {
|
|
3477
3607
|
const completed = normalizeDrawingState({
|
|
@@ -3768,7 +3898,7 @@ function createChart(element, options = {}) {
|
|
|
3768
3898
|
setCrosshairPoint(null);
|
|
3769
3899
|
return;
|
|
3770
3900
|
}
|
|
3771
|
-
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
|
|
3901
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
|
|
3772
3902
|
const nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
3773
3903
|
if (nextPoint) {
|
|
3774
3904
|
draftDrawing = {
|
|
@@ -44,7 +44,7 @@ interface ChartOptions {
|
|
|
44
44
|
drawings?: DrawingObjectOptions[];
|
|
45
45
|
}
|
|
46
46
|
type IndicatorPane = "overlay" | "separate";
|
|
47
|
-
type DrawingToolType = "horizontal-line" | "trendline" | "fib-retracement";
|
|
47
|
+
type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement";
|
|
48
48
|
interface DrawingPoint {
|
|
49
49
|
index: number;
|
|
50
50
|
price: number;
|
|
@@ -188,6 +188,8 @@ interface CrosshairOptions {
|
|
|
188
188
|
color?: string;
|
|
189
189
|
width?: number;
|
|
190
190
|
style?: "solid" | "dotted" | "dashed";
|
|
191
|
+
mode?: "cross" | "dot";
|
|
192
|
+
dotRadius?: number;
|
|
191
193
|
showHorizontal?: boolean;
|
|
192
194
|
showVertical?: boolean;
|
|
193
195
|
showPriceLabel?: boolean;
|
|
@@ -19,6 +19,8 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
|
|
|
19
19
|
color: "#94a3b8",
|
|
20
20
|
width: 1,
|
|
21
21
|
style: "dotted",
|
|
22
|
+
mode: "cross",
|
|
23
|
+
dotRadius: 3,
|
|
22
24
|
showHorizontal: true,
|
|
23
25
|
showVertical: true,
|
|
24
26
|
showPriceLabel: true,
|
|
@@ -2144,6 +2146,20 @@ function createChart(element, options = {}) {
|
|
|
2144
2146
|
drawDrawingLabel(drawing.label, midX, y, drawing.color);
|
|
2145
2147
|
}
|
|
2146
2148
|
}
|
|
2149
|
+
} else if (drawing.type === "vertical-line") {
|
|
2150
|
+
const point = drawing.points[0];
|
|
2151
|
+
if (point) {
|
|
2152
|
+
const x = xFromDrawingPoint(point);
|
|
2153
|
+
ctx.beginPath();
|
|
2154
|
+
ctx.moveTo(crisp(x), crisp(chartTop));
|
|
2155
|
+
ctx.lineTo(crisp(x), crisp(chartBottom));
|
|
2156
|
+
ctx.stroke();
|
|
2157
|
+
const handleY = (chartTop + chartBottom) / 2;
|
|
2158
|
+
drawDrawingHandle(x, handleY, drawing.color);
|
|
2159
|
+
if (drawing.label) {
|
|
2160
|
+
drawDrawingLabel(drawing.label, x, chartTop + 16, drawing.color);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2147
2163
|
} else if (drawing.type === "trendline") {
|
|
2148
2164
|
const first = drawing.points[0];
|
|
2149
2165
|
const second = drawing.points[1];
|
|
@@ -2164,6 +2180,36 @@ function createChart(element, options = {}) {
|
|
|
2164
2180
|
drawDrawingLabel(drawing.label, midX, midY, drawing.color);
|
|
2165
2181
|
}
|
|
2166
2182
|
}
|
|
2183
|
+
} else if (drawing.type === "ray") {
|
|
2184
|
+
const first = drawing.points[0];
|
|
2185
|
+
const second = drawing.points[1];
|
|
2186
|
+
if (first && second) {
|
|
2187
|
+
const firstX = xFromDrawingPoint(first);
|
|
2188
|
+
const firstY = yFromPrice(first.price);
|
|
2189
|
+
const secondX = xFromDrawingPoint(second);
|
|
2190
|
+
const secondY = yFromPrice(second.price);
|
|
2191
|
+
const dx = secondX - firstX;
|
|
2192
|
+
const dy = secondY - firstY;
|
|
2193
|
+
let endX = secondX;
|
|
2194
|
+
let endY = secondY;
|
|
2195
|
+
const len = Math.hypot(dx, dy);
|
|
2196
|
+
if (len > 0) {
|
|
2197
|
+
const far = Math.abs(chartRight - chartLeft) + Math.abs(chartBottom - chartTop) + 2e3;
|
|
2198
|
+
endX = firstX + dx / len * far;
|
|
2199
|
+
endY = firstY + dy / len * far;
|
|
2200
|
+
}
|
|
2201
|
+
ctx.beginPath();
|
|
2202
|
+
ctx.moveTo(firstX, firstY);
|
|
2203
|
+
ctx.lineTo(endX, endY);
|
|
2204
|
+
ctx.stroke();
|
|
2205
|
+
drawDrawingHandle(firstX, firstY, drawing.color);
|
|
2206
|
+
drawDrawingHandle(secondX, secondY, drawing.color);
|
|
2207
|
+
if (drawing.label) {
|
|
2208
|
+
const midX = (firstX + secondX) / 2;
|
|
2209
|
+
const midY = (firstY + secondY) / 2;
|
|
2210
|
+
drawDrawingLabel(drawing.label, midX, midY, drawing.color);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2167
2213
|
} else if (drawing.type === "fib-retracement") {
|
|
2168
2214
|
const first = drawing.points[0];
|
|
2169
2215
|
const second = drawing.points[1];
|
|
@@ -2411,23 +2457,32 @@ function createChart(element, options = {}) {
|
|
|
2411
2457
|
if (crosshair.visible && crosshairPoint) {
|
|
2412
2458
|
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
2413
2459
|
const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2418
|
-
if (crosshair.showVertical) {
|
|
2419
|
-
ctx.beginPath();
|
|
2420
|
-
ctx.moveTo(crisp(cx), crisp(chartTop));
|
|
2421
|
-
ctx.lineTo(crisp(cx), crisp(chartBottom));
|
|
2422
|
-
ctx.stroke();
|
|
2423
|
-
}
|
|
2424
|
-
if (crosshair.showHorizontal) {
|
|
2460
|
+
if (crosshair.mode === "dot") {
|
|
2461
|
+
ctx.save();
|
|
2462
|
+
ctx.fillStyle = crosshair.color;
|
|
2425
2463
|
ctx.beginPath();
|
|
2426
|
-
ctx.
|
|
2427
|
-
ctx.
|
|
2428
|
-
ctx.
|
|
2464
|
+
ctx.arc(cx, cy, Math.max(1, crosshair.dotRadius), 0, Math.PI * 2);
|
|
2465
|
+
ctx.fill();
|
|
2466
|
+
ctx.restore();
|
|
2467
|
+
} else {
|
|
2468
|
+
ctx.save();
|
|
2469
|
+
ctx.strokeStyle = crosshair.color;
|
|
2470
|
+
ctx.lineWidth = Math.max(1, crosshair.width);
|
|
2471
|
+
applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2472
|
+
if (crosshair.showVertical) {
|
|
2473
|
+
ctx.beginPath();
|
|
2474
|
+
ctx.moveTo(crisp(cx), crisp(chartTop));
|
|
2475
|
+
ctx.lineTo(crisp(cx), crisp(chartBottom));
|
|
2476
|
+
ctx.stroke();
|
|
2477
|
+
}
|
|
2478
|
+
if (crosshair.showHorizontal) {
|
|
2479
|
+
ctx.beginPath();
|
|
2480
|
+
ctx.moveTo(crisp(chartLeft), crisp(cy));
|
|
2481
|
+
ctx.lineTo(crisp(chartRight), crisp(cy));
|
|
2482
|
+
ctx.stroke();
|
|
2483
|
+
}
|
|
2484
|
+
ctx.restore();
|
|
2429
2485
|
}
|
|
2430
|
-
ctx.restore();
|
|
2431
2486
|
}
|
|
2432
2487
|
ctx.restore();
|
|
2433
2488
|
if (activeSeparateIndicators.length > 0) {
|
|
@@ -3306,6 +3361,17 @@ function createChart(element, options = {}) {
|
|
|
3306
3361
|
if (Math.abs(y - lineY) <= 7) {
|
|
3307
3362
|
return { drawing, target: "line" };
|
|
3308
3363
|
}
|
|
3364
|
+
} else if (drawing.type === "vertical-line") {
|
|
3365
|
+
const point = drawing.points[0];
|
|
3366
|
+
if (!point) continue;
|
|
3367
|
+
const lineX = canvasXFromDrawingPoint(point);
|
|
3368
|
+
const handleY = drawState ? (drawState.chartTop + drawState.chartBottom) / 2 : 0;
|
|
3369
|
+
if (Math.hypot(x - lineX, y - handleY) <= 8) {
|
|
3370
|
+
return { drawing, target: "handle", pointIndex: 0 };
|
|
3371
|
+
}
|
|
3372
|
+
if (Math.abs(x - lineX) <= 7) {
|
|
3373
|
+
return { drawing, target: "line" };
|
|
3374
|
+
}
|
|
3309
3375
|
} else if (drawing.type === "trendline") {
|
|
3310
3376
|
const first = drawing.points[0];
|
|
3311
3377
|
const second = drawing.points[1];
|
|
@@ -3323,6 +3389,31 @@ function createChart(element, options = {}) {
|
|
|
3323
3389
|
if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
|
|
3324
3390
|
return { drawing, target: "line" };
|
|
3325
3391
|
}
|
|
3392
|
+
} else if (drawing.type === "ray") {
|
|
3393
|
+
const first = drawing.points[0];
|
|
3394
|
+
const second = drawing.points[1];
|
|
3395
|
+
if (!first || !second) continue;
|
|
3396
|
+
const x1 = canvasXFromDrawingPoint(first);
|
|
3397
|
+
const y1 = canvasYFromDrawingPrice(first.price);
|
|
3398
|
+
const x2 = canvasXFromDrawingPoint(second);
|
|
3399
|
+
const y2 = canvasYFromDrawingPrice(second.price);
|
|
3400
|
+
if (Math.hypot(x - x1, y - y1) <= 8) {
|
|
3401
|
+
return { drawing, target: "handle", pointIndex: 0 };
|
|
3402
|
+
}
|
|
3403
|
+
if (Math.hypot(x - x2, y - y2) <= 8) {
|
|
3404
|
+
return { drawing, target: "handle", pointIndex: 1 };
|
|
3405
|
+
}
|
|
3406
|
+
const dx = x2 - x1;
|
|
3407
|
+
const dy = y2 - y1;
|
|
3408
|
+
const lengthSq = dx * dx + dy * dy;
|
|
3409
|
+
if (lengthSq > 0) {
|
|
3410
|
+
const t = Math.max(0, ((x - x1) * dx + (y - y1) * dy) / lengthSq);
|
|
3411
|
+
const projX = x1 + t * dx;
|
|
3412
|
+
const projY = y1 + t * dy;
|
|
3413
|
+
if (Math.hypot(x - projX, y - projY) <= 6) {
|
|
3414
|
+
return { drawing, target: "line" };
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3326
3417
|
} else if (drawing.type === "fib-retracement") {
|
|
3327
3418
|
const first = drawing.points[0];
|
|
3328
3419
|
const second = drawing.points[1];
|
|
@@ -3424,6 +3515,21 @@ function createChart(element, options = {}) {
|
|
|
3424
3515
|
draw();
|
|
3425
3516
|
return true;
|
|
3426
3517
|
}
|
|
3518
|
+
if (activeDrawingTool === "vertical-line") {
|
|
3519
|
+
const defaults = getDrawingToolDefaults("vertical-line");
|
|
3520
|
+
drawings.push(
|
|
3521
|
+
normalizeDrawingState({
|
|
3522
|
+
type: "vertical-line",
|
|
3523
|
+
points: [point],
|
|
3524
|
+
color: defaults.color ?? "#38bdf8",
|
|
3525
|
+
style: defaults.style ?? "solid",
|
|
3526
|
+
width: defaults.width ?? 1
|
|
3527
|
+
})
|
|
3528
|
+
);
|
|
3529
|
+
emitDrawingsChange();
|
|
3530
|
+
draw();
|
|
3531
|
+
return true;
|
|
3532
|
+
}
|
|
3427
3533
|
if (activeDrawingTool === "trendline") {
|
|
3428
3534
|
if (draftDrawing?.type === "trendline") {
|
|
3429
3535
|
const completed = normalizeDrawingState({
|
|
@@ -3448,6 +3554,30 @@ function createChart(element, options = {}) {
|
|
|
3448
3554
|
draw();
|
|
3449
3555
|
return true;
|
|
3450
3556
|
}
|
|
3557
|
+
if (activeDrawingTool === "ray") {
|
|
3558
|
+
if (draftDrawing?.type === "ray") {
|
|
3559
|
+
const completed = normalizeDrawingState({
|
|
3560
|
+
...serializeDrawing(draftDrawing),
|
|
3561
|
+
points: [draftDrawing.points[0], point]
|
|
3562
|
+
});
|
|
3563
|
+
drawings.push(completed);
|
|
3564
|
+
draftDrawing = null;
|
|
3565
|
+
activeDrawingTool = null;
|
|
3566
|
+
emitDrawingsChange();
|
|
3567
|
+
draw();
|
|
3568
|
+
return true;
|
|
3569
|
+
}
|
|
3570
|
+
const defaults = getDrawingToolDefaults("ray");
|
|
3571
|
+
draftDrawing = normalizeDrawingState({
|
|
3572
|
+
type: "ray",
|
|
3573
|
+
points: [point, point],
|
|
3574
|
+
color: defaults.color ?? "#2563eb",
|
|
3575
|
+
style: defaults.style ?? "solid",
|
|
3576
|
+
width: defaults.width ?? 2
|
|
3577
|
+
});
|
|
3578
|
+
draw();
|
|
3579
|
+
return true;
|
|
3580
|
+
}
|
|
3451
3581
|
if (activeDrawingTool === "fib-retracement") {
|
|
3452
3582
|
if (draftDrawing?.type === "fib-retracement") {
|
|
3453
3583
|
const completed = normalizeDrawingState({
|
|
@@ -3744,7 +3874,7 @@ function createChart(element, options = {}) {
|
|
|
3744
3874
|
setCrosshairPoint(null);
|
|
3745
3875
|
return;
|
|
3746
3876
|
}
|
|
3747
|
-
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
|
|
3877
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
|
|
3748
3878
|
const nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
3749
3879
|
if (nextPoint) {
|
|
3750
3880
|
draftDrawing = {
|
package/dist/index.cjs
CHANGED
|
@@ -43,6 +43,8 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
|
|
|
43
43
|
color: "#94a3b8",
|
|
44
44
|
width: 1,
|
|
45
45
|
style: "dotted",
|
|
46
|
+
mode: "cross",
|
|
47
|
+
dotRadius: 3,
|
|
46
48
|
showHorizontal: true,
|
|
47
49
|
showVertical: true,
|
|
48
50
|
showPriceLabel: true,
|
|
@@ -2168,6 +2170,20 @@ function createChart(element, options = {}) {
|
|
|
2168
2170
|
drawDrawingLabel(drawing.label, midX, y, drawing.color);
|
|
2169
2171
|
}
|
|
2170
2172
|
}
|
|
2173
|
+
} else if (drawing.type === "vertical-line") {
|
|
2174
|
+
const point = drawing.points[0];
|
|
2175
|
+
if (point) {
|
|
2176
|
+
const x = xFromDrawingPoint(point);
|
|
2177
|
+
ctx.beginPath();
|
|
2178
|
+
ctx.moveTo(crisp(x), crisp(chartTop));
|
|
2179
|
+
ctx.lineTo(crisp(x), crisp(chartBottom));
|
|
2180
|
+
ctx.stroke();
|
|
2181
|
+
const handleY = (chartTop + chartBottom) / 2;
|
|
2182
|
+
drawDrawingHandle(x, handleY, drawing.color);
|
|
2183
|
+
if (drawing.label) {
|
|
2184
|
+
drawDrawingLabel(drawing.label, x, chartTop + 16, drawing.color);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2171
2187
|
} else if (drawing.type === "trendline") {
|
|
2172
2188
|
const first = drawing.points[0];
|
|
2173
2189
|
const second = drawing.points[1];
|
|
@@ -2188,6 +2204,36 @@ function createChart(element, options = {}) {
|
|
|
2188
2204
|
drawDrawingLabel(drawing.label, midX, midY, drawing.color);
|
|
2189
2205
|
}
|
|
2190
2206
|
}
|
|
2207
|
+
} else if (drawing.type === "ray") {
|
|
2208
|
+
const first = drawing.points[0];
|
|
2209
|
+
const second = drawing.points[1];
|
|
2210
|
+
if (first && second) {
|
|
2211
|
+
const firstX = xFromDrawingPoint(first);
|
|
2212
|
+
const firstY = yFromPrice(first.price);
|
|
2213
|
+
const secondX = xFromDrawingPoint(second);
|
|
2214
|
+
const secondY = yFromPrice(second.price);
|
|
2215
|
+
const dx = secondX - firstX;
|
|
2216
|
+
const dy = secondY - firstY;
|
|
2217
|
+
let endX = secondX;
|
|
2218
|
+
let endY = secondY;
|
|
2219
|
+
const len = Math.hypot(dx, dy);
|
|
2220
|
+
if (len > 0) {
|
|
2221
|
+
const far = Math.abs(chartRight - chartLeft) + Math.abs(chartBottom - chartTop) + 2e3;
|
|
2222
|
+
endX = firstX + dx / len * far;
|
|
2223
|
+
endY = firstY + dy / len * far;
|
|
2224
|
+
}
|
|
2225
|
+
ctx.beginPath();
|
|
2226
|
+
ctx.moveTo(firstX, firstY);
|
|
2227
|
+
ctx.lineTo(endX, endY);
|
|
2228
|
+
ctx.stroke();
|
|
2229
|
+
drawDrawingHandle(firstX, firstY, drawing.color);
|
|
2230
|
+
drawDrawingHandle(secondX, secondY, drawing.color);
|
|
2231
|
+
if (drawing.label) {
|
|
2232
|
+
const midX = (firstX + secondX) / 2;
|
|
2233
|
+
const midY = (firstY + secondY) / 2;
|
|
2234
|
+
drawDrawingLabel(drawing.label, midX, midY, drawing.color);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2191
2237
|
} else if (drawing.type === "fib-retracement") {
|
|
2192
2238
|
const first = drawing.points[0];
|
|
2193
2239
|
const second = drawing.points[1];
|
|
@@ -2435,23 +2481,32 @@ function createChart(element, options = {}) {
|
|
|
2435
2481
|
if (crosshair.visible && crosshairPoint) {
|
|
2436
2482
|
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
2437
2483
|
const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2442
|
-
if (crosshair.showVertical) {
|
|
2443
|
-
ctx.beginPath();
|
|
2444
|
-
ctx.moveTo(crisp(cx), crisp(chartTop));
|
|
2445
|
-
ctx.lineTo(crisp(cx), crisp(chartBottom));
|
|
2446
|
-
ctx.stroke();
|
|
2447
|
-
}
|
|
2448
|
-
if (crosshair.showHorizontal) {
|
|
2484
|
+
if (crosshair.mode === "dot") {
|
|
2485
|
+
ctx.save();
|
|
2486
|
+
ctx.fillStyle = crosshair.color;
|
|
2449
2487
|
ctx.beginPath();
|
|
2450
|
-
ctx.
|
|
2451
|
-
ctx.
|
|
2452
|
-
ctx.
|
|
2488
|
+
ctx.arc(cx, cy, Math.max(1, crosshair.dotRadius), 0, Math.PI * 2);
|
|
2489
|
+
ctx.fill();
|
|
2490
|
+
ctx.restore();
|
|
2491
|
+
} else {
|
|
2492
|
+
ctx.save();
|
|
2493
|
+
ctx.strokeStyle = crosshair.color;
|
|
2494
|
+
ctx.lineWidth = Math.max(1, crosshair.width);
|
|
2495
|
+
applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2496
|
+
if (crosshair.showVertical) {
|
|
2497
|
+
ctx.beginPath();
|
|
2498
|
+
ctx.moveTo(crisp(cx), crisp(chartTop));
|
|
2499
|
+
ctx.lineTo(crisp(cx), crisp(chartBottom));
|
|
2500
|
+
ctx.stroke();
|
|
2501
|
+
}
|
|
2502
|
+
if (crosshair.showHorizontal) {
|
|
2503
|
+
ctx.beginPath();
|
|
2504
|
+
ctx.moveTo(crisp(chartLeft), crisp(cy));
|
|
2505
|
+
ctx.lineTo(crisp(chartRight), crisp(cy));
|
|
2506
|
+
ctx.stroke();
|
|
2507
|
+
}
|
|
2508
|
+
ctx.restore();
|
|
2453
2509
|
}
|
|
2454
|
-
ctx.restore();
|
|
2455
2510
|
}
|
|
2456
2511
|
ctx.restore();
|
|
2457
2512
|
if (activeSeparateIndicators.length > 0) {
|
|
@@ -3330,6 +3385,17 @@ function createChart(element, options = {}) {
|
|
|
3330
3385
|
if (Math.abs(y - lineY) <= 7) {
|
|
3331
3386
|
return { drawing, target: "line" };
|
|
3332
3387
|
}
|
|
3388
|
+
} else if (drawing.type === "vertical-line") {
|
|
3389
|
+
const point = drawing.points[0];
|
|
3390
|
+
if (!point) continue;
|
|
3391
|
+
const lineX = canvasXFromDrawingPoint(point);
|
|
3392
|
+
const handleY = drawState ? (drawState.chartTop + drawState.chartBottom) / 2 : 0;
|
|
3393
|
+
if (Math.hypot(x - lineX, y - handleY) <= 8) {
|
|
3394
|
+
return { drawing, target: "handle", pointIndex: 0 };
|
|
3395
|
+
}
|
|
3396
|
+
if (Math.abs(x - lineX) <= 7) {
|
|
3397
|
+
return { drawing, target: "line" };
|
|
3398
|
+
}
|
|
3333
3399
|
} else if (drawing.type === "trendline") {
|
|
3334
3400
|
const first = drawing.points[0];
|
|
3335
3401
|
const second = drawing.points[1];
|
|
@@ -3347,6 +3413,31 @@ function createChart(element, options = {}) {
|
|
|
3347
3413
|
if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
|
|
3348
3414
|
return { drawing, target: "line" };
|
|
3349
3415
|
}
|
|
3416
|
+
} else if (drawing.type === "ray") {
|
|
3417
|
+
const first = drawing.points[0];
|
|
3418
|
+
const second = drawing.points[1];
|
|
3419
|
+
if (!first || !second) continue;
|
|
3420
|
+
const x1 = canvasXFromDrawingPoint(first);
|
|
3421
|
+
const y1 = canvasYFromDrawingPrice(first.price);
|
|
3422
|
+
const x2 = canvasXFromDrawingPoint(second);
|
|
3423
|
+
const y2 = canvasYFromDrawingPrice(second.price);
|
|
3424
|
+
if (Math.hypot(x - x1, y - y1) <= 8) {
|
|
3425
|
+
return { drawing, target: "handle", pointIndex: 0 };
|
|
3426
|
+
}
|
|
3427
|
+
if (Math.hypot(x - x2, y - y2) <= 8) {
|
|
3428
|
+
return { drawing, target: "handle", pointIndex: 1 };
|
|
3429
|
+
}
|
|
3430
|
+
const dx = x2 - x1;
|
|
3431
|
+
const dy = y2 - y1;
|
|
3432
|
+
const lengthSq = dx * dx + dy * dy;
|
|
3433
|
+
if (lengthSq > 0) {
|
|
3434
|
+
const t = Math.max(0, ((x - x1) * dx + (y - y1) * dy) / lengthSq);
|
|
3435
|
+
const projX = x1 + t * dx;
|
|
3436
|
+
const projY = y1 + t * dy;
|
|
3437
|
+
if (Math.hypot(x - projX, y - projY) <= 6) {
|
|
3438
|
+
return { drawing, target: "line" };
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3350
3441
|
} else if (drawing.type === "fib-retracement") {
|
|
3351
3442
|
const first = drawing.points[0];
|
|
3352
3443
|
const second = drawing.points[1];
|
|
@@ -3448,6 +3539,21 @@ function createChart(element, options = {}) {
|
|
|
3448
3539
|
draw();
|
|
3449
3540
|
return true;
|
|
3450
3541
|
}
|
|
3542
|
+
if (activeDrawingTool === "vertical-line") {
|
|
3543
|
+
const defaults = getDrawingToolDefaults("vertical-line");
|
|
3544
|
+
drawings.push(
|
|
3545
|
+
normalizeDrawingState({
|
|
3546
|
+
type: "vertical-line",
|
|
3547
|
+
points: [point],
|
|
3548
|
+
color: defaults.color ?? "#38bdf8",
|
|
3549
|
+
style: defaults.style ?? "solid",
|
|
3550
|
+
width: defaults.width ?? 1
|
|
3551
|
+
})
|
|
3552
|
+
);
|
|
3553
|
+
emitDrawingsChange();
|
|
3554
|
+
draw();
|
|
3555
|
+
return true;
|
|
3556
|
+
}
|
|
3451
3557
|
if (activeDrawingTool === "trendline") {
|
|
3452
3558
|
if (draftDrawing?.type === "trendline") {
|
|
3453
3559
|
const completed = normalizeDrawingState({
|
|
@@ -3472,6 +3578,30 @@ function createChart(element, options = {}) {
|
|
|
3472
3578
|
draw();
|
|
3473
3579
|
return true;
|
|
3474
3580
|
}
|
|
3581
|
+
if (activeDrawingTool === "ray") {
|
|
3582
|
+
if (draftDrawing?.type === "ray") {
|
|
3583
|
+
const completed = normalizeDrawingState({
|
|
3584
|
+
...serializeDrawing(draftDrawing),
|
|
3585
|
+
points: [draftDrawing.points[0], point]
|
|
3586
|
+
});
|
|
3587
|
+
drawings.push(completed);
|
|
3588
|
+
draftDrawing = null;
|
|
3589
|
+
activeDrawingTool = null;
|
|
3590
|
+
emitDrawingsChange();
|
|
3591
|
+
draw();
|
|
3592
|
+
return true;
|
|
3593
|
+
}
|
|
3594
|
+
const defaults = getDrawingToolDefaults("ray");
|
|
3595
|
+
draftDrawing = normalizeDrawingState({
|
|
3596
|
+
type: "ray",
|
|
3597
|
+
points: [point, point],
|
|
3598
|
+
color: defaults.color ?? "#2563eb",
|
|
3599
|
+
style: defaults.style ?? "solid",
|
|
3600
|
+
width: defaults.width ?? 2
|
|
3601
|
+
});
|
|
3602
|
+
draw();
|
|
3603
|
+
return true;
|
|
3604
|
+
}
|
|
3475
3605
|
if (activeDrawingTool === "fib-retracement") {
|
|
3476
3606
|
if (draftDrawing?.type === "fib-retracement") {
|
|
3477
3607
|
const completed = normalizeDrawingState({
|
|
@@ -3768,7 +3898,7 @@ function createChart(element, options = {}) {
|
|
|
3768
3898
|
setCrosshairPoint(null);
|
|
3769
3899
|
return;
|
|
3770
3900
|
}
|
|
3771
|
-
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
|
|
3901
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
|
|
3772
3902
|
const nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
3773
3903
|
if (nextPoint) {
|
|
3774
3904
|
draftDrawing = {
|
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" | "trendline" | "fib-retracement";
|
|
47
|
+
type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement";
|
|
48
48
|
interface DrawingPoint {
|
|
49
49
|
index: number;
|
|
50
50
|
price: number;
|
|
@@ -188,6 +188,8 @@ interface CrosshairOptions {
|
|
|
188
188
|
color?: string;
|
|
189
189
|
width?: number;
|
|
190
190
|
style?: "solid" | "dotted" | "dashed";
|
|
191
|
+
mode?: "cross" | "dot";
|
|
192
|
+
dotRadius?: number;
|
|
191
193
|
showHorizontal?: boolean;
|
|
192
194
|
showVertical?: boolean;
|
|
193
195
|
showPriceLabel?: boolean;
|
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" | "trendline" | "fib-retracement";
|
|
47
|
+
type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement";
|
|
48
48
|
interface DrawingPoint {
|
|
49
49
|
index: number;
|
|
50
50
|
price: number;
|
|
@@ -188,6 +188,8 @@ interface CrosshairOptions {
|
|
|
188
188
|
color?: string;
|
|
189
189
|
width?: number;
|
|
190
190
|
style?: "solid" | "dotted" | "dashed";
|
|
191
|
+
mode?: "cross" | "dot";
|
|
192
|
+
dotRadius?: number;
|
|
191
193
|
showHorizontal?: boolean;
|
|
192
194
|
showVertical?: boolean;
|
|
193
195
|
showPriceLabel?: boolean;
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,8 @@ var DEFAULT_CROSSHAIR_OPTIONS = {
|
|
|
19
19
|
color: "#94a3b8",
|
|
20
20
|
width: 1,
|
|
21
21
|
style: "dotted",
|
|
22
|
+
mode: "cross",
|
|
23
|
+
dotRadius: 3,
|
|
22
24
|
showHorizontal: true,
|
|
23
25
|
showVertical: true,
|
|
24
26
|
showPriceLabel: true,
|
|
@@ -2144,6 +2146,20 @@ function createChart(element, options = {}) {
|
|
|
2144
2146
|
drawDrawingLabel(drawing.label, midX, y, drawing.color);
|
|
2145
2147
|
}
|
|
2146
2148
|
}
|
|
2149
|
+
} else if (drawing.type === "vertical-line") {
|
|
2150
|
+
const point = drawing.points[0];
|
|
2151
|
+
if (point) {
|
|
2152
|
+
const x = xFromDrawingPoint(point);
|
|
2153
|
+
ctx.beginPath();
|
|
2154
|
+
ctx.moveTo(crisp(x), crisp(chartTop));
|
|
2155
|
+
ctx.lineTo(crisp(x), crisp(chartBottom));
|
|
2156
|
+
ctx.stroke();
|
|
2157
|
+
const handleY = (chartTop + chartBottom) / 2;
|
|
2158
|
+
drawDrawingHandle(x, handleY, drawing.color);
|
|
2159
|
+
if (drawing.label) {
|
|
2160
|
+
drawDrawingLabel(drawing.label, x, chartTop + 16, drawing.color);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2147
2163
|
} else if (drawing.type === "trendline") {
|
|
2148
2164
|
const first = drawing.points[0];
|
|
2149
2165
|
const second = drawing.points[1];
|
|
@@ -2164,6 +2180,36 @@ function createChart(element, options = {}) {
|
|
|
2164
2180
|
drawDrawingLabel(drawing.label, midX, midY, drawing.color);
|
|
2165
2181
|
}
|
|
2166
2182
|
}
|
|
2183
|
+
} else if (drawing.type === "ray") {
|
|
2184
|
+
const first = drawing.points[0];
|
|
2185
|
+
const second = drawing.points[1];
|
|
2186
|
+
if (first && second) {
|
|
2187
|
+
const firstX = xFromDrawingPoint(first);
|
|
2188
|
+
const firstY = yFromPrice(first.price);
|
|
2189
|
+
const secondX = xFromDrawingPoint(second);
|
|
2190
|
+
const secondY = yFromPrice(second.price);
|
|
2191
|
+
const dx = secondX - firstX;
|
|
2192
|
+
const dy = secondY - firstY;
|
|
2193
|
+
let endX = secondX;
|
|
2194
|
+
let endY = secondY;
|
|
2195
|
+
const len = Math.hypot(dx, dy);
|
|
2196
|
+
if (len > 0) {
|
|
2197
|
+
const far = Math.abs(chartRight - chartLeft) + Math.abs(chartBottom - chartTop) + 2e3;
|
|
2198
|
+
endX = firstX + dx / len * far;
|
|
2199
|
+
endY = firstY + dy / len * far;
|
|
2200
|
+
}
|
|
2201
|
+
ctx.beginPath();
|
|
2202
|
+
ctx.moveTo(firstX, firstY);
|
|
2203
|
+
ctx.lineTo(endX, endY);
|
|
2204
|
+
ctx.stroke();
|
|
2205
|
+
drawDrawingHandle(firstX, firstY, drawing.color);
|
|
2206
|
+
drawDrawingHandle(secondX, secondY, drawing.color);
|
|
2207
|
+
if (drawing.label) {
|
|
2208
|
+
const midX = (firstX + secondX) / 2;
|
|
2209
|
+
const midY = (firstY + secondY) / 2;
|
|
2210
|
+
drawDrawingLabel(drawing.label, midX, midY, drawing.color);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2167
2213
|
} else if (drawing.type === "fib-retracement") {
|
|
2168
2214
|
const first = drawing.points[0];
|
|
2169
2215
|
const second = drawing.points[1];
|
|
@@ -2411,23 +2457,32 @@ function createChart(element, options = {}) {
|
|
|
2411
2457
|
if (crosshair.visible && crosshairPoint) {
|
|
2412
2458
|
const cx = clamp(crosshairPoint.x, chartLeft, chartRight);
|
|
2413
2459
|
const cy = clamp(crosshairPoint.y, chartTop, chartBottom);
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2418
|
-
if (crosshair.showVertical) {
|
|
2419
|
-
ctx.beginPath();
|
|
2420
|
-
ctx.moveTo(crisp(cx), crisp(chartTop));
|
|
2421
|
-
ctx.lineTo(crisp(cx), crisp(chartBottom));
|
|
2422
|
-
ctx.stroke();
|
|
2423
|
-
}
|
|
2424
|
-
if (crosshair.showHorizontal) {
|
|
2460
|
+
if (crosshair.mode === "dot") {
|
|
2461
|
+
ctx.save();
|
|
2462
|
+
ctx.fillStyle = crosshair.color;
|
|
2425
2463
|
ctx.beginPath();
|
|
2426
|
-
ctx.
|
|
2427
|
-
ctx.
|
|
2428
|
-
ctx.
|
|
2464
|
+
ctx.arc(cx, cy, Math.max(1, crosshair.dotRadius), 0, Math.PI * 2);
|
|
2465
|
+
ctx.fill();
|
|
2466
|
+
ctx.restore();
|
|
2467
|
+
} else {
|
|
2468
|
+
ctx.save();
|
|
2469
|
+
ctx.strokeStyle = crosshair.color;
|
|
2470
|
+
ctx.lineWidth = Math.max(1, crosshair.width);
|
|
2471
|
+
applyDashPattern(crosshair.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2472
|
+
if (crosshair.showVertical) {
|
|
2473
|
+
ctx.beginPath();
|
|
2474
|
+
ctx.moveTo(crisp(cx), crisp(chartTop));
|
|
2475
|
+
ctx.lineTo(crisp(cx), crisp(chartBottom));
|
|
2476
|
+
ctx.stroke();
|
|
2477
|
+
}
|
|
2478
|
+
if (crosshair.showHorizontal) {
|
|
2479
|
+
ctx.beginPath();
|
|
2480
|
+
ctx.moveTo(crisp(chartLeft), crisp(cy));
|
|
2481
|
+
ctx.lineTo(crisp(chartRight), crisp(cy));
|
|
2482
|
+
ctx.stroke();
|
|
2483
|
+
}
|
|
2484
|
+
ctx.restore();
|
|
2429
2485
|
}
|
|
2430
|
-
ctx.restore();
|
|
2431
2486
|
}
|
|
2432
2487
|
ctx.restore();
|
|
2433
2488
|
if (activeSeparateIndicators.length > 0) {
|
|
@@ -3306,6 +3361,17 @@ function createChart(element, options = {}) {
|
|
|
3306
3361
|
if (Math.abs(y - lineY) <= 7) {
|
|
3307
3362
|
return { drawing, target: "line" };
|
|
3308
3363
|
}
|
|
3364
|
+
} else if (drawing.type === "vertical-line") {
|
|
3365
|
+
const point = drawing.points[0];
|
|
3366
|
+
if (!point) continue;
|
|
3367
|
+
const lineX = canvasXFromDrawingPoint(point);
|
|
3368
|
+
const handleY = drawState ? (drawState.chartTop + drawState.chartBottom) / 2 : 0;
|
|
3369
|
+
if (Math.hypot(x - lineX, y - handleY) <= 8) {
|
|
3370
|
+
return { drawing, target: "handle", pointIndex: 0 };
|
|
3371
|
+
}
|
|
3372
|
+
if (Math.abs(x - lineX) <= 7) {
|
|
3373
|
+
return { drawing, target: "line" };
|
|
3374
|
+
}
|
|
3309
3375
|
} else if (drawing.type === "trendline") {
|
|
3310
3376
|
const first = drawing.points[0];
|
|
3311
3377
|
const second = drawing.points[1];
|
|
@@ -3323,6 +3389,31 @@ function createChart(element, options = {}) {
|
|
|
3323
3389
|
if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
|
|
3324
3390
|
return { drawing, target: "line" };
|
|
3325
3391
|
}
|
|
3392
|
+
} else if (drawing.type === "ray") {
|
|
3393
|
+
const first = drawing.points[0];
|
|
3394
|
+
const second = drawing.points[1];
|
|
3395
|
+
if (!first || !second) continue;
|
|
3396
|
+
const x1 = canvasXFromDrawingPoint(first);
|
|
3397
|
+
const y1 = canvasYFromDrawingPrice(first.price);
|
|
3398
|
+
const x2 = canvasXFromDrawingPoint(second);
|
|
3399
|
+
const y2 = canvasYFromDrawingPrice(second.price);
|
|
3400
|
+
if (Math.hypot(x - x1, y - y1) <= 8) {
|
|
3401
|
+
return { drawing, target: "handle", pointIndex: 0 };
|
|
3402
|
+
}
|
|
3403
|
+
if (Math.hypot(x - x2, y - y2) <= 8) {
|
|
3404
|
+
return { drawing, target: "handle", pointIndex: 1 };
|
|
3405
|
+
}
|
|
3406
|
+
const dx = x2 - x1;
|
|
3407
|
+
const dy = y2 - y1;
|
|
3408
|
+
const lengthSq = dx * dx + dy * dy;
|
|
3409
|
+
if (lengthSq > 0) {
|
|
3410
|
+
const t = Math.max(0, ((x - x1) * dx + (y - y1) * dy) / lengthSq);
|
|
3411
|
+
const projX = x1 + t * dx;
|
|
3412
|
+
const projY = y1 + t * dy;
|
|
3413
|
+
if (Math.hypot(x - projX, y - projY) <= 6) {
|
|
3414
|
+
return { drawing, target: "line" };
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3326
3417
|
} else if (drawing.type === "fib-retracement") {
|
|
3327
3418
|
const first = drawing.points[0];
|
|
3328
3419
|
const second = drawing.points[1];
|
|
@@ -3424,6 +3515,21 @@ function createChart(element, options = {}) {
|
|
|
3424
3515
|
draw();
|
|
3425
3516
|
return true;
|
|
3426
3517
|
}
|
|
3518
|
+
if (activeDrawingTool === "vertical-line") {
|
|
3519
|
+
const defaults = getDrawingToolDefaults("vertical-line");
|
|
3520
|
+
drawings.push(
|
|
3521
|
+
normalizeDrawingState({
|
|
3522
|
+
type: "vertical-line",
|
|
3523
|
+
points: [point],
|
|
3524
|
+
color: defaults.color ?? "#38bdf8",
|
|
3525
|
+
style: defaults.style ?? "solid",
|
|
3526
|
+
width: defaults.width ?? 1
|
|
3527
|
+
})
|
|
3528
|
+
);
|
|
3529
|
+
emitDrawingsChange();
|
|
3530
|
+
draw();
|
|
3531
|
+
return true;
|
|
3532
|
+
}
|
|
3427
3533
|
if (activeDrawingTool === "trendline") {
|
|
3428
3534
|
if (draftDrawing?.type === "trendline") {
|
|
3429
3535
|
const completed = normalizeDrawingState({
|
|
@@ -3448,6 +3554,30 @@ function createChart(element, options = {}) {
|
|
|
3448
3554
|
draw();
|
|
3449
3555
|
return true;
|
|
3450
3556
|
}
|
|
3557
|
+
if (activeDrawingTool === "ray") {
|
|
3558
|
+
if (draftDrawing?.type === "ray") {
|
|
3559
|
+
const completed = normalizeDrawingState({
|
|
3560
|
+
...serializeDrawing(draftDrawing),
|
|
3561
|
+
points: [draftDrawing.points[0], point]
|
|
3562
|
+
});
|
|
3563
|
+
drawings.push(completed);
|
|
3564
|
+
draftDrawing = null;
|
|
3565
|
+
activeDrawingTool = null;
|
|
3566
|
+
emitDrawingsChange();
|
|
3567
|
+
draw();
|
|
3568
|
+
return true;
|
|
3569
|
+
}
|
|
3570
|
+
const defaults = getDrawingToolDefaults("ray");
|
|
3571
|
+
draftDrawing = normalizeDrawingState({
|
|
3572
|
+
type: "ray",
|
|
3573
|
+
points: [point, point],
|
|
3574
|
+
color: defaults.color ?? "#2563eb",
|
|
3575
|
+
style: defaults.style ?? "solid",
|
|
3576
|
+
width: defaults.width ?? 2
|
|
3577
|
+
});
|
|
3578
|
+
draw();
|
|
3579
|
+
return true;
|
|
3580
|
+
}
|
|
3451
3581
|
if (activeDrawingTool === "fib-retracement") {
|
|
3452
3582
|
if (draftDrawing?.type === "fib-retracement") {
|
|
3453
3583
|
const completed = normalizeDrawingState({
|
|
@@ -3744,7 +3874,7 @@ function createChart(element, options = {}) {
|
|
|
3744
3874
|
setCrosshairPoint(null);
|
|
3745
3875
|
return;
|
|
3746
3876
|
}
|
|
3747
|
-
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
|
|
3877
|
+
if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
|
|
3748
3878
|
const nextPoint = drawingPointFromCanvas(point.x, point.y);
|
|
3749
3879
|
if (nextPoint) {
|
|
3750
3880
|
draftDrawing = {
|
package/docs/API.md
CHANGED
|
@@ -94,6 +94,8 @@ Top-level options:
|
|
|
94
94
|
- `color` (default `#94a3b8`)
|
|
95
95
|
- `width` (default `1`)
|
|
96
96
|
- `style` (`"solid" | "dotted" | "dashed"`, default `"dotted"`)
|
|
97
|
+
- `mode` (`"cross" | "dot"`, default `"cross"`; `"dot"` draws a dot at the cursor instead of crosshair lines)
|
|
98
|
+
- `dotRadius` (default `3`; dot radius in px when `mode` is `"dot"`)
|
|
97
99
|
- `showHorizontal` (default `true`)
|
|
98
100
|
- `showVertical` (default `true`)
|
|
99
101
|
- `showPriceLabel` (default `true`)
|
|
@@ -414,7 +416,11 @@ Volume style inputs:
|
|
|
414
416
|
Drawings are user-created chart tools, separate from indicators. They are interactive chart objects like horizontal lines and trendlines.
|
|
415
417
|
|
|
416
418
|
- `id?: string`
|
|
417
|
-
- `type: "horizontal-line" | "trendline"`
|
|
419
|
+
- `type: "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement"`
|
|
420
|
+
- `horizontal-line` / `vertical-line`: single-point, full-width/full-height line (one click to place)
|
|
421
|
+
- `trendline`: two-point segment (click start, click end)
|
|
422
|
+
- `ray`: two-point line that extends infinitely past the second point
|
|
423
|
+
- `fib-retracement`: two-point retracement with levels/bands
|
|
418
424
|
- `points: DrawingPoint[]`
|
|
419
425
|
- `visible?: boolean`
|
|
420
426
|
- `color?: string`
|
|
@@ -436,7 +442,10 @@ Tool workflow:
|
|
|
436
442
|
|
|
437
443
|
```ts
|
|
438
444
|
chart.setActiveDrawingTool("horizontal-line"); // next plot click creates a line
|
|
445
|
+
chart.setActiveDrawingTool("vertical-line"); // next plot click creates a vertical line
|
|
439
446
|
chart.setActiveDrawingTool("trendline"); // first click starts, second click commits
|
|
447
|
+
chart.setActiveDrawingTool("ray"); // first click starts, second click commits (extends past p2)
|
|
448
|
+
chart.setActiveDrawingTool("fib-retracement"); // first click starts, second click commits
|
|
440
449
|
chart.setActiveDrawingTool(null); // back to normal cursor/pan
|
|
441
450
|
```
|
|
442
451
|
|
|
@@ -466,8 +475,8 @@ Use `getDrawings()` / `setDrawings()` for persistence.
|
|
|
466
475
|
- `panY(priceDelta: number): void` (positive = move viewport up)
|
|
467
476
|
- `fitContent(): void` (x-only fit, keeps y zoom)
|
|
468
477
|
- `resetViewport(): void` (fit x + reset y auto-scale)
|
|
469
|
-
- `setActiveDrawingTool(tool: "horizontal-line" | "trendline" |
|
|
470
|
-
- `getActiveDrawingTool():
|
|
478
|
+
- `setActiveDrawingTool(tool: DrawingToolType | null): void` (`DrawingToolType` = `"horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement"`)
|
|
479
|
+
- `getActiveDrawingTool(): DrawingToolType | null`
|
|
471
480
|
- `setDrawings(drawings: DrawingObjectOptions[]): void`
|
|
472
481
|
- `getDrawings(): DrawingObjectOptions[]`
|
|
473
482
|
- `addDrawing(drawing: DrawingObjectOptions): string`
|
package/docs/RECIPES.md
CHANGED
|
@@ -106,11 +106,14 @@ chart.addPriceLine({
|
|
|
106
106
|
Drawing tools are separate from indicators. Indicators compute/render data series; drawings are user-created objects that can be persisted.
|
|
107
107
|
|
|
108
108
|
```ts
|
|
109
|
-
//
|
|
109
|
+
// Single-click tools: the next plot click creates the drawing.
|
|
110
110
|
chart.setActiveDrawingTool("horizontal-line");
|
|
111
|
+
chart.setActiveDrawingTool("vertical-line");
|
|
111
112
|
|
|
112
|
-
//
|
|
113
|
+
// Two-click tools: first click starts, second click commits.
|
|
113
114
|
chart.setActiveDrawingTool("trendline");
|
|
115
|
+
chart.setActiveDrawingTool("ray"); // extends infinitely past the second point
|
|
116
|
+
chart.setActiveDrawingTool("fib-retracement");
|
|
114
117
|
|
|
115
118
|
// Back to normal cursor/pan mode.
|
|
116
119
|
chart.setActiveDrawingTool(null);
|