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.
@@ -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
- ctx.save();
2439
- ctx.strokeStyle = crosshair.color;
2440
- ctx.lineWidth = Math.max(1, crosshair.width);
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.moveTo(crisp(chartLeft), crisp(cy));
2451
- ctx.lineTo(crisp(chartRight), crisp(cy));
2452
- ctx.stroke();
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
- ctx.save();
2415
- ctx.strokeStyle = crosshair.color;
2416
- ctx.lineWidth = Math.max(1, crosshair.width);
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.moveTo(crisp(chartLeft), crisp(cy));
2427
- ctx.lineTo(crisp(chartRight), crisp(cy));
2428
- ctx.stroke();
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
- ctx.save();
2439
- ctx.strokeStyle = crosshair.color;
2440
- ctx.lineWidth = Math.max(1, crosshair.width);
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.moveTo(crisp(chartLeft), crisp(cy));
2451
- ctx.lineTo(crisp(chartRight), crisp(cy));
2452
- ctx.stroke();
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
- ctx.save();
2415
- ctx.strokeStyle = crosshair.color;
2416
- ctx.lineWidth = Math.max(1, crosshair.width);
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.moveTo(crisp(chartLeft), crisp(cy));
2427
- ctx.lineTo(crisp(chartRight), crisp(cy));
2428
- ctx.stroke();
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" | null): void`
470
- - `getActiveDrawingTool(): "horizontal-line" | "trendline" | null`
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
- // Let the next click create a horizontal line.
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
- // First click starts a trendline, second click commits it.
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.67",
3
+ "version": "0.1.69",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",