hyperprop-charting-library 0.1.67 → 0.1.68

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.
@@ -2168,6 +2168,20 @@ function createChart(element, options = {}) {
2168
2168
  drawDrawingLabel(drawing.label, midX, y, drawing.color);
2169
2169
  }
2170
2170
  }
2171
+ } else if (drawing.type === "vertical-line") {
2172
+ const point = drawing.points[0];
2173
+ if (point) {
2174
+ const x = xFromDrawingPoint(point);
2175
+ ctx.beginPath();
2176
+ ctx.moveTo(crisp(x), crisp(chartTop));
2177
+ ctx.lineTo(crisp(x), crisp(chartBottom));
2178
+ ctx.stroke();
2179
+ const handleY = (chartTop + chartBottom) / 2;
2180
+ drawDrawingHandle(x, handleY, drawing.color);
2181
+ if (drawing.label) {
2182
+ drawDrawingLabel(drawing.label, x, chartTop + 16, drawing.color);
2183
+ }
2184
+ }
2171
2185
  } else if (drawing.type === "trendline") {
2172
2186
  const first = drawing.points[0];
2173
2187
  const second = drawing.points[1];
@@ -2188,6 +2202,36 @@ function createChart(element, options = {}) {
2188
2202
  drawDrawingLabel(drawing.label, midX, midY, drawing.color);
2189
2203
  }
2190
2204
  }
2205
+ } else if (drawing.type === "ray") {
2206
+ const first = drawing.points[0];
2207
+ const second = drawing.points[1];
2208
+ if (first && second) {
2209
+ const firstX = xFromDrawingPoint(first);
2210
+ const firstY = yFromPrice(first.price);
2211
+ const secondX = xFromDrawingPoint(second);
2212
+ const secondY = yFromPrice(second.price);
2213
+ const dx = secondX - firstX;
2214
+ const dy = secondY - firstY;
2215
+ let endX = secondX;
2216
+ let endY = secondY;
2217
+ const len = Math.hypot(dx, dy);
2218
+ if (len > 0) {
2219
+ const far = Math.abs(chartRight - chartLeft) + Math.abs(chartBottom - chartTop) + 2e3;
2220
+ endX = firstX + dx / len * far;
2221
+ endY = firstY + dy / len * far;
2222
+ }
2223
+ ctx.beginPath();
2224
+ ctx.moveTo(firstX, firstY);
2225
+ ctx.lineTo(endX, endY);
2226
+ ctx.stroke();
2227
+ drawDrawingHandle(firstX, firstY, drawing.color);
2228
+ drawDrawingHandle(secondX, secondY, drawing.color);
2229
+ if (drawing.label) {
2230
+ const midX = (firstX + secondX) / 2;
2231
+ const midY = (firstY + secondY) / 2;
2232
+ drawDrawingLabel(drawing.label, midX, midY, drawing.color);
2233
+ }
2234
+ }
2191
2235
  } else if (drawing.type === "fib-retracement") {
2192
2236
  const first = drawing.points[0];
2193
2237
  const second = drawing.points[1];
@@ -3330,6 +3374,17 @@ function createChart(element, options = {}) {
3330
3374
  if (Math.abs(y - lineY) <= 7) {
3331
3375
  return { drawing, target: "line" };
3332
3376
  }
3377
+ } else if (drawing.type === "vertical-line") {
3378
+ const point = drawing.points[0];
3379
+ if (!point) continue;
3380
+ const lineX = canvasXFromDrawingPoint(point);
3381
+ const handleY = drawState ? (drawState.chartTop + drawState.chartBottom) / 2 : 0;
3382
+ if (Math.hypot(x - lineX, y - handleY) <= 8) {
3383
+ return { drawing, target: "handle", pointIndex: 0 };
3384
+ }
3385
+ if (Math.abs(x - lineX) <= 7) {
3386
+ return { drawing, target: "line" };
3387
+ }
3333
3388
  } else if (drawing.type === "trendline") {
3334
3389
  const first = drawing.points[0];
3335
3390
  const second = drawing.points[1];
@@ -3347,6 +3402,31 @@ function createChart(element, options = {}) {
3347
3402
  if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3348
3403
  return { drawing, target: "line" };
3349
3404
  }
3405
+ } else if (drawing.type === "ray") {
3406
+ const first = drawing.points[0];
3407
+ const second = drawing.points[1];
3408
+ if (!first || !second) continue;
3409
+ const x1 = canvasXFromDrawingPoint(first);
3410
+ const y1 = canvasYFromDrawingPrice(first.price);
3411
+ const x2 = canvasXFromDrawingPoint(second);
3412
+ const y2 = canvasYFromDrawingPrice(second.price);
3413
+ if (Math.hypot(x - x1, y - y1) <= 8) {
3414
+ return { drawing, target: "handle", pointIndex: 0 };
3415
+ }
3416
+ if (Math.hypot(x - x2, y - y2) <= 8) {
3417
+ return { drawing, target: "handle", pointIndex: 1 };
3418
+ }
3419
+ const dx = x2 - x1;
3420
+ const dy = y2 - y1;
3421
+ const lengthSq = dx * dx + dy * dy;
3422
+ if (lengthSq > 0) {
3423
+ const t = Math.max(0, ((x - x1) * dx + (y - y1) * dy) / lengthSq);
3424
+ const projX = x1 + t * dx;
3425
+ const projY = y1 + t * dy;
3426
+ if (Math.hypot(x - projX, y - projY) <= 6) {
3427
+ return { drawing, target: "line" };
3428
+ }
3429
+ }
3350
3430
  } else if (drawing.type === "fib-retracement") {
3351
3431
  const first = drawing.points[0];
3352
3432
  const second = drawing.points[1];
@@ -3448,6 +3528,21 @@ function createChart(element, options = {}) {
3448
3528
  draw();
3449
3529
  return true;
3450
3530
  }
3531
+ if (activeDrawingTool === "vertical-line") {
3532
+ const defaults = getDrawingToolDefaults("vertical-line");
3533
+ drawings.push(
3534
+ normalizeDrawingState({
3535
+ type: "vertical-line",
3536
+ points: [point],
3537
+ color: defaults.color ?? "#38bdf8",
3538
+ style: defaults.style ?? "solid",
3539
+ width: defaults.width ?? 1
3540
+ })
3541
+ );
3542
+ emitDrawingsChange();
3543
+ draw();
3544
+ return true;
3545
+ }
3451
3546
  if (activeDrawingTool === "trendline") {
3452
3547
  if (draftDrawing?.type === "trendline") {
3453
3548
  const completed = normalizeDrawingState({
@@ -3472,6 +3567,30 @@ function createChart(element, options = {}) {
3472
3567
  draw();
3473
3568
  return true;
3474
3569
  }
3570
+ if (activeDrawingTool === "ray") {
3571
+ if (draftDrawing?.type === "ray") {
3572
+ const completed = normalizeDrawingState({
3573
+ ...serializeDrawing(draftDrawing),
3574
+ points: [draftDrawing.points[0], point]
3575
+ });
3576
+ drawings.push(completed);
3577
+ draftDrawing = null;
3578
+ activeDrawingTool = null;
3579
+ emitDrawingsChange();
3580
+ draw();
3581
+ return true;
3582
+ }
3583
+ const defaults = getDrawingToolDefaults("ray");
3584
+ draftDrawing = normalizeDrawingState({
3585
+ type: "ray",
3586
+ points: [point, point],
3587
+ color: defaults.color ?? "#2563eb",
3588
+ style: defaults.style ?? "solid",
3589
+ width: defaults.width ?? 2
3590
+ });
3591
+ draw();
3592
+ return true;
3593
+ }
3475
3594
  if (activeDrawingTool === "fib-retracement") {
3476
3595
  if (draftDrawing?.type === "fib-retracement") {
3477
3596
  const completed = normalizeDrawingState({
@@ -3768,7 +3887,7 @@ function createChart(element, options = {}) {
3768
3887
  setCrosshairPoint(null);
3769
3888
  return;
3770
3889
  }
3771
- if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
3890
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
3772
3891
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3773
3892
  if (nextPoint) {
3774
3893
  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;
@@ -2144,6 +2144,20 @@ function createChart(element, options = {}) {
2144
2144
  drawDrawingLabel(drawing.label, midX, y, drawing.color);
2145
2145
  }
2146
2146
  }
2147
+ } else if (drawing.type === "vertical-line") {
2148
+ const point = drawing.points[0];
2149
+ if (point) {
2150
+ const x = xFromDrawingPoint(point);
2151
+ ctx.beginPath();
2152
+ ctx.moveTo(crisp(x), crisp(chartTop));
2153
+ ctx.lineTo(crisp(x), crisp(chartBottom));
2154
+ ctx.stroke();
2155
+ const handleY = (chartTop + chartBottom) / 2;
2156
+ drawDrawingHandle(x, handleY, drawing.color);
2157
+ if (drawing.label) {
2158
+ drawDrawingLabel(drawing.label, x, chartTop + 16, drawing.color);
2159
+ }
2160
+ }
2147
2161
  } else if (drawing.type === "trendline") {
2148
2162
  const first = drawing.points[0];
2149
2163
  const second = drawing.points[1];
@@ -2164,6 +2178,36 @@ function createChart(element, options = {}) {
2164
2178
  drawDrawingLabel(drawing.label, midX, midY, drawing.color);
2165
2179
  }
2166
2180
  }
2181
+ } else if (drawing.type === "ray") {
2182
+ const first = drawing.points[0];
2183
+ const second = drawing.points[1];
2184
+ if (first && second) {
2185
+ const firstX = xFromDrawingPoint(first);
2186
+ const firstY = yFromPrice(first.price);
2187
+ const secondX = xFromDrawingPoint(second);
2188
+ const secondY = yFromPrice(second.price);
2189
+ const dx = secondX - firstX;
2190
+ const dy = secondY - firstY;
2191
+ let endX = secondX;
2192
+ let endY = secondY;
2193
+ const len = Math.hypot(dx, dy);
2194
+ if (len > 0) {
2195
+ const far = Math.abs(chartRight - chartLeft) + Math.abs(chartBottom - chartTop) + 2e3;
2196
+ endX = firstX + dx / len * far;
2197
+ endY = firstY + dy / len * far;
2198
+ }
2199
+ ctx.beginPath();
2200
+ ctx.moveTo(firstX, firstY);
2201
+ ctx.lineTo(endX, endY);
2202
+ ctx.stroke();
2203
+ drawDrawingHandle(firstX, firstY, drawing.color);
2204
+ drawDrawingHandle(secondX, secondY, drawing.color);
2205
+ if (drawing.label) {
2206
+ const midX = (firstX + secondX) / 2;
2207
+ const midY = (firstY + secondY) / 2;
2208
+ drawDrawingLabel(drawing.label, midX, midY, drawing.color);
2209
+ }
2210
+ }
2167
2211
  } else if (drawing.type === "fib-retracement") {
2168
2212
  const first = drawing.points[0];
2169
2213
  const second = drawing.points[1];
@@ -3306,6 +3350,17 @@ function createChart(element, options = {}) {
3306
3350
  if (Math.abs(y - lineY) <= 7) {
3307
3351
  return { drawing, target: "line" };
3308
3352
  }
3353
+ } else if (drawing.type === "vertical-line") {
3354
+ const point = drawing.points[0];
3355
+ if (!point) continue;
3356
+ const lineX = canvasXFromDrawingPoint(point);
3357
+ const handleY = drawState ? (drawState.chartTop + drawState.chartBottom) / 2 : 0;
3358
+ if (Math.hypot(x - lineX, y - handleY) <= 8) {
3359
+ return { drawing, target: "handle", pointIndex: 0 };
3360
+ }
3361
+ if (Math.abs(x - lineX) <= 7) {
3362
+ return { drawing, target: "line" };
3363
+ }
3309
3364
  } else if (drawing.type === "trendline") {
3310
3365
  const first = drawing.points[0];
3311
3366
  const second = drawing.points[1];
@@ -3323,6 +3378,31 @@ function createChart(element, options = {}) {
3323
3378
  if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3324
3379
  return { drawing, target: "line" };
3325
3380
  }
3381
+ } else if (drawing.type === "ray") {
3382
+ const first = drawing.points[0];
3383
+ const second = drawing.points[1];
3384
+ if (!first || !second) continue;
3385
+ const x1 = canvasXFromDrawingPoint(first);
3386
+ const y1 = canvasYFromDrawingPrice(first.price);
3387
+ const x2 = canvasXFromDrawingPoint(second);
3388
+ const y2 = canvasYFromDrawingPrice(second.price);
3389
+ if (Math.hypot(x - x1, y - y1) <= 8) {
3390
+ return { drawing, target: "handle", pointIndex: 0 };
3391
+ }
3392
+ if (Math.hypot(x - x2, y - y2) <= 8) {
3393
+ return { drawing, target: "handle", pointIndex: 1 };
3394
+ }
3395
+ const dx = x2 - x1;
3396
+ const dy = y2 - y1;
3397
+ const lengthSq = dx * dx + dy * dy;
3398
+ if (lengthSq > 0) {
3399
+ const t = Math.max(0, ((x - x1) * dx + (y - y1) * dy) / lengthSq);
3400
+ const projX = x1 + t * dx;
3401
+ const projY = y1 + t * dy;
3402
+ if (Math.hypot(x - projX, y - projY) <= 6) {
3403
+ return { drawing, target: "line" };
3404
+ }
3405
+ }
3326
3406
  } else if (drawing.type === "fib-retracement") {
3327
3407
  const first = drawing.points[0];
3328
3408
  const second = drawing.points[1];
@@ -3424,6 +3504,21 @@ function createChart(element, options = {}) {
3424
3504
  draw();
3425
3505
  return true;
3426
3506
  }
3507
+ if (activeDrawingTool === "vertical-line") {
3508
+ const defaults = getDrawingToolDefaults("vertical-line");
3509
+ drawings.push(
3510
+ normalizeDrawingState({
3511
+ type: "vertical-line",
3512
+ points: [point],
3513
+ color: defaults.color ?? "#38bdf8",
3514
+ style: defaults.style ?? "solid",
3515
+ width: defaults.width ?? 1
3516
+ })
3517
+ );
3518
+ emitDrawingsChange();
3519
+ draw();
3520
+ return true;
3521
+ }
3427
3522
  if (activeDrawingTool === "trendline") {
3428
3523
  if (draftDrawing?.type === "trendline") {
3429
3524
  const completed = normalizeDrawingState({
@@ -3448,6 +3543,30 @@ function createChart(element, options = {}) {
3448
3543
  draw();
3449
3544
  return true;
3450
3545
  }
3546
+ if (activeDrawingTool === "ray") {
3547
+ if (draftDrawing?.type === "ray") {
3548
+ const completed = normalizeDrawingState({
3549
+ ...serializeDrawing(draftDrawing),
3550
+ points: [draftDrawing.points[0], point]
3551
+ });
3552
+ drawings.push(completed);
3553
+ draftDrawing = null;
3554
+ activeDrawingTool = null;
3555
+ emitDrawingsChange();
3556
+ draw();
3557
+ return true;
3558
+ }
3559
+ const defaults = getDrawingToolDefaults("ray");
3560
+ draftDrawing = normalizeDrawingState({
3561
+ type: "ray",
3562
+ points: [point, point],
3563
+ color: defaults.color ?? "#2563eb",
3564
+ style: defaults.style ?? "solid",
3565
+ width: defaults.width ?? 2
3566
+ });
3567
+ draw();
3568
+ return true;
3569
+ }
3451
3570
  if (activeDrawingTool === "fib-retracement") {
3452
3571
  if (draftDrawing?.type === "fib-retracement") {
3453
3572
  const completed = normalizeDrawingState({
@@ -3744,7 +3863,7 @@ function createChart(element, options = {}) {
3744
3863
  setCrosshairPoint(null);
3745
3864
  return;
3746
3865
  }
3747
- if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
3866
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
3748
3867
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3749
3868
  if (nextPoint) {
3750
3869
  draftDrawing = {
package/dist/index.cjs CHANGED
@@ -2168,6 +2168,20 @@ function createChart(element, options = {}) {
2168
2168
  drawDrawingLabel(drawing.label, midX, y, drawing.color);
2169
2169
  }
2170
2170
  }
2171
+ } else if (drawing.type === "vertical-line") {
2172
+ const point = drawing.points[0];
2173
+ if (point) {
2174
+ const x = xFromDrawingPoint(point);
2175
+ ctx.beginPath();
2176
+ ctx.moveTo(crisp(x), crisp(chartTop));
2177
+ ctx.lineTo(crisp(x), crisp(chartBottom));
2178
+ ctx.stroke();
2179
+ const handleY = (chartTop + chartBottom) / 2;
2180
+ drawDrawingHandle(x, handleY, drawing.color);
2181
+ if (drawing.label) {
2182
+ drawDrawingLabel(drawing.label, x, chartTop + 16, drawing.color);
2183
+ }
2184
+ }
2171
2185
  } else if (drawing.type === "trendline") {
2172
2186
  const first = drawing.points[0];
2173
2187
  const second = drawing.points[1];
@@ -2188,6 +2202,36 @@ function createChart(element, options = {}) {
2188
2202
  drawDrawingLabel(drawing.label, midX, midY, drawing.color);
2189
2203
  }
2190
2204
  }
2205
+ } else if (drawing.type === "ray") {
2206
+ const first = drawing.points[0];
2207
+ const second = drawing.points[1];
2208
+ if (first && second) {
2209
+ const firstX = xFromDrawingPoint(first);
2210
+ const firstY = yFromPrice(first.price);
2211
+ const secondX = xFromDrawingPoint(second);
2212
+ const secondY = yFromPrice(second.price);
2213
+ const dx = secondX - firstX;
2214
+ const dy = secondY - firstY;
2215
+ let endX = secondX;
2216
+ let endY = secondY;
2217
+ const len = Math.hypot(dx, dy);
2218
+ if (len > 0) {
2219
+ const far = Math.abs(chartRight - chartLeft) + Math.abs(chartBottom - chartTop) + 2e3;
2220
+ endX = firstX + dx / len * far;
2221
+ endY = firstY + dy / len * far;
2222
+ }
2223
+ ctx.beginPath();
2224
+ ctx.moveTo(firstX, firstY);
2225
+ ctx.lineTo(endX, endY);
2226
+ ctx.stroke();
2227
+ drawDrawingHandle(firstX, firstY, drawing.color);
2228
+ drawDrawingHandle(secondX, secondY, drawing.color);
2229
+ if (drawing.label) {
2230
+ const midX = (firstX + secondX) / 2;
2231
+ const midY = (firstY + secondY) / 2;
2232
+ drawDrawingLabel(drawing.label, midX, midY, drawing.color);
2233
+ }
2234
+ }
2191
2235
  } else if (drawing.type === "fib-retracement") {
2192
2236
  const first = drawing.points[0];
2193
2237
  const second = drawing.points[1];
@@ -3330,6 +3374,17 @@ function createChart(element, options = {}) {
3330
3374
  if (Math.abs(y - lineY) <= 7) {
3331
3375
  return { drawing, target: "line" };
3332
3376
  }
3377
+ } else if (drawing.type === "vertical-line") {
3378
+ const point = drawing.points[0];
3379
+ if (!point) continue;
3380
+ const lineX = canvasXFromDrawingPoint(point);
3381
+ const handleY = drawState ? (drawState.chartTop + drawState.chartBottom) / 2 : 0;
3382
+ if (Math.hypot(x - lineX, y - handleY) <= 8) {
3383
+ return { drawing, target: "handle", pointIndex: 0 };
3384
+ }
3385
+ if (Math.abs(x - lineX) <= 7) {
3386
+ return { drawing, target: "line" };
3387
+ }
3333
3388
  } else if (drawing.type === "trendline") {
3334
3389
  const first = drawing.points[0];
3335
3390
  const second = drawing.points[1];
@@ -3347,6 +3402,31 @@ function createChart(element, options = {}) {
3347
3402
  if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3348
3403
  return { drawing, target: "line" };
3349
3404
  }
3405
+ } else if (drawing.type === "ray") {
3406
+ const first = drawing.points[0];
3407
+ const second = drawing.points[1];
3408
+ if (!first || !second) continue;
3409
+ const x1 = canvasXFromDrawingPoint(first);
3410
+ const y1 = canvasYFromDrawingPrice(first.price);
3411
+ const x2 = canvasXFromDrawingPoint(second);
3412
+ const y2 = canvasYFromDrawingPrice(second.price);
3413
+ if (Math.hypot(x - x1, y - y1) <= 8) {
3414
+ return { drawing, target: "handle", pointIndex: 0 };
3415
+ }
3416
+ if (Math.hypot(x - x2, y - y2) <= 8) {
3417
+ return { drawing, target: "handle", pointIndex: 1 };
3418
+ }
3419
+ const dx = x2 - x1;
3420
+ const dy = y2 - y1;
3421
+ const lengthSq = dx * dx + dy * dy;
3422
+ if (lengthSq > 0) {
3423
+ const t = Math.max(0, ((x - x1) * dx + (y - y1) * dy) / lengthSq);
3424
+ const projX = x1 + t * dx;
3425
+ const projY = y1 + t * dy;
3426
+ if (Math.hypot(x - projX, y - projY) <= 6) {
3427
+ return { drawing, target: "line" };
3428
+ }
3429
+ }
3350
3430
  } else if (drawing.type === "fib-retracement") {
3351
3431
  const first = drawing.points[0];
3352
3432
  const second = drawing.points[1];
@@ -3448,6 +3528,21 @@ function createChart(element, options = {}) {
3448
3528
  draw();
3449
3529
  return true;
3450
3530
  }
3531
+ if (activeDrawingTool === "vertical-line") {
3532
+ const defaults = getDrawingToolDefaults("vertical-line");
3533
+ drawings.push(
3534
+ normalizeDrawingState({
3535
+ type: "vertical-line",
3536
+ points: [point],
3537
+ color: defaults.color ?? "#38bdf8",
3538
+ style: defaults.style ?? "solid",
3539
+ width: defaults.width ?? 1
3540
+ })
3541
+ );
3542
+ emitDrawingsChange();
3543
+ draw();
3544
+ return true;
3545
+ }
3451
3546
  if (activeDrawingTool === "trendline") {
3452
3547
  if (draftDrawing?.type === "trendline") {
3453
3548
  const completed = normalizeDrawingState({
@@ -3472,6 +3567,30 @@ function createChart(element, options = {}) {
3472
3567
  draw();
3473
3568
  return true;
3474
3569
  }
3570
+ if (activeDrawingTool === "ray") {
3571
+ if (draftDrawing?.type === "ray") {
3572
+ const completed = normalizeDrawingState({
3573
+ ...serializeDrawing(draftDrawing),
3574
+ points: [draftDrawing.points[0], point]
3575
+ });
3576
+ drawings.push(completed);
3577
+ draftDrawing = null;
3578
+ activeDrawingTool = null;
3579
+ emitDrawingsChange();
3580
+ draw();
3581
+ return true;
3582
+ }
3583
+ const defaults = getDrawingToolDefaults("ray");
3584
+ draftDrawing = normalizeDrawingState({
3585
+ type: "ray",
3586
+ points: [point, point],
3587
+ color: defaults.color ?? "#2563eb",
3588
+ style: defaults.style ?? "solid",
3589
+ width: defaults.width ?? 2
3590
+ });
3591
+ draw();
3592
+ return true;
3593
+ }
3475
3594
  if (activeDrawingTool === "fib-retracement") {
3476
3595
  if (draftDrawing?.type === "fib-retracement") {
3477
3596
  const completed = normalizeDrawingState({
@@ -3768,7 +3887,7 @@ function createChart(element, options = {}) {
3768
3887
  setCrosshairPoint(null);
3769
3888
  return;
3770
3889
  }
3771
- if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
3890
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
3772
3891
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3773
3892
  if (nextPoint) {
3774
3893
  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;
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;
package/dist/index.js CHANGED
@@ -2144,6 +2144,20 @@ function createChart(element, options = {}) {
2144
2144
  drawDrawingLabel(drawing.label, midX, y, drawing.color);
2145
2145
  }
2146
2146
  }
2147
+ } else if (drawing.type === "vertical-line") {
2148
+ const point = drawing.points[0];
2149
+ if (point) {
2150
+ const x = xFromDrawingPoint(point);
2151
+ ctx.beginPath();
2152
+ ctx.moveTo(crisp(x), crisp(chartTop));
2153
+ ctx.lineTo(crisp(x), crisp(chartBottom));
2154
+ ctx.stroke();
2155
+ const handleY = (chartTop + chartBottom) / 2;
2156
+ drawDrawingHandle(x, handleY, drawing.color);
2157
+ if (drawing.label) {
2158
+ drawDrawingLabel(drawing.label, x, chartTop + 16, drawing.color);
2159
+ }
2160
+ }
2147
2161
  } else if (drawing.type === "trendline") {
2148
2162
  const first = drawing.points[0];
2149
2163
  const second = drawing.points[1];
@@ -2164,6 +2178,36 @@ function createChart(element, options = {}) {
2164
2178
  drawDrawingLabel(drawing.label, midX, midY, drawing.color);
2165
2179
  }
2166
2180
  }
2181
+ } else if (drawing.type === "ray") {
2182
+ const first = drawing.points[0];
2183
+ const second = drawing.points[1];
2184
+ if (first && second) {
2185
+ const firstX = xFromDrawingPoint(first);
2186
+ const firstY = yFromPrice(first.price);
2187
+ const secondX = xFromDrawingPoint(second);
2188
+ const secondY = yFromPrice(second.price);
2189
+ const dx = secondX - firstX;
2190
+ const dy = secondY - firstY;
2191
+ let endX = secondX;
2192
+ let endY = secondY;
2193
+ const len = Math.hypot(dx, dy);
2194
+ if (len > 0) {
2195
+ const far = Math.abs(chartRight - chartLeft) + Math.abs(chartBottom - chartTop) + 2e3;
2196
+ endX = firstX + dx / len * far;
2197
+ endY = firstY + dy / len * far;
2198
+ }
2199
+ ctx.beginPath();
2200
+ ctx.moveTo(firstX, firstY);
2201
+ ctx.lineTo(endX, endY);
2202
+ ctx.stroke();
2203
+ drawDrawingHandle(firstX, firstY, drawing.color);
2204
+ drawDrawingHandle(secondX, secondY, drawing.color);
2205
+ if (drawing.label) {
2206
+ const midX = (firstX + secondX) / 2;
2207
+ const midY = (firstY + secondY) / 2;
2208
+ drawDrawingLabel(drawing.label, midX, midY, drawing.color);
2209
+ }
2210
+ }
2167
2211
  } else if (drawing.type === "fib-retracement") {
2168
2212
  const first = drawing.points[0];
2169
2213
  const second = drawing.points[1];
@@ -3306,6 +3350,17 @@ function createChart(element, options = {}) {
3306
3350
  if (Math.abs(y - lineY) <= 7) {
3307
3351
  return { drawing, target: "line" };
3308
3352
  }
3353
+ } else if (drawing.type === "vertical-line") {
3354
+ const point = drawing.points[0];
3355
+ if (!point) continue;
3356
+ const lineX = canvasXFromDrawingPoint(point);
3357
+ const handleY = drawState ? (drawState.chartTop + drawState.chartBottom) / 2 : 0;
3358
+ if (Math.hypot(x - lineX, y - handleY) <= 8) {
3359
+ return { drawing, target: "handle", pointIndex: 0 };
3360
+ }
3361
+ if (Math.abs(x - lineX) <= 7) {
3362
+ return { drawing, target: "line" };
3363
+ }
3309
3364
  } else if (drawing.type === "trendline") {
3310
3365
  const first = drawing.points[0];
3311
3366
  const second = drawing.points[1];
@@ -3323,6 +3378,31 @@ function createChart(element, options = {}) {
3323
3378
  if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3324
3379
  return { drawing, target: "line" };
3325
3380
  }
3381
+ } else if (drawing.type === "ray") {
3382
+ const first = drawing.points[0];
3383
+ const second = drawing.points[1];
3384
+ if (!first || !second) continue;
3385
+ const x1 = canvasXFromDrawingPoint(first);
3386
+ const y1 = canvasYFromDrawingPrice(first.price);
3387
+ const x2 = canvasXFromDrawingPoint(second);
3388
+ const y2 = canvasYFromDrawingPrice(second.price);
3389
+ if (Math.hypot(x - x1, y - y1) <= 8) {
3390
+ return { drawing, target: "handle", pointIndex: 0 };
3391
+ }
3392
+ if (Math.hypot(x - x2, y - y2) <= 8) {
3393
+ return { drawing, target: "handle", pointIndex: 1 };
3394
+ }
3395
+ const dx = x2 - x1;
3396
+ const dy = y2 - y1;
3397
+ const lengthSq = dx * dx + dy * dy;
3398
+ if (lengthSq > 0) {
3399
+ const t = Math.max(0, ((x - x1) * dx + (y - y1) * dy) / lengthSq);
3400
+ const projX = x1 + t * dx;
3401
+ const projY = y1 + t * dy;
3402
+ if (Math.hypot(x - projX, y - projY) <= 6) {
3403
+ return { drawing, target: "line" };
3404
+ }
3405
+ }
3326
3406
  } else if (drawing.type === "fib-retracement") {
3327
3407
  const first = drawing.points[0];
3328
3408
  const second = drawing.points[1];
@@ -3424,6 +3504,21 @@ function createChart(element, options = {}) {
3424
3504
  draw();
3425
3505
  return true;
3426
3506
  }
3507
+ if (activeDrawingTool === "vertical-line") {
3508
+ const defaults = getDrawingToolDefaults("vertical-line");
3509
+ drawings.push(
3510
+ normalizeDrawingState({
3511
+ type: "vertical-line",
3512
+ points: [point],
3513
+ color: defaults.color ?? "#38bdf8",
3514
+ style: defaults.style ?? "solid",
3515
+ width: defaults.width ?? 1
3516
+ })
3517
+ );
3518
+ emitDrawingsChange();
3519
+ draw();
3520
+ return true;
3521
+ }
3427
3522
  if (activeDrawingTool === "trendline") {
3428
3523
  if (draftDrawing?.type === "trendline") {
3429
3524
  const completed = normalizeDrawingState({
@@ -3448,6 +3543,30 @@ function createChart(element, options = {}) {
3448
3543
  draw();
3449
3544
  return true;
3450
3545
  }
3546
+ if (activeDrawingTool === "ray") {
3547
+ if (draftDrawing?.type === "ray") {
3548
+ const completed = normalizeDrawingState({
3549
+ ...serializeDrawing(draftDrawing),
3550
+ points: [draftDrawing.points[0], point]
3551
+ });
3552
+ drawings.push(completed);
3553
+ draftDrawing = null;
3554
+ activeDrawingTool = null;
3555
+ emitDrawingsChange();
3556
+ draw();
3557
+ return true;
3558
+ }
3559
+ const defaults = getDrawingToolDefaults("ray");
3560
+ draftDrawing = normalizeDrawingState({
3561
+ type: "ray",
3562
+ points: [point, point],
3563
+ color: defaults.color ?? "#2563eb",
3564
+ style: defaults.style ?? "solid",
3565
+ width: defaults.width ?? 2
3566
+ });
3567
+ draw();
3568
+ return true;
3569
+ }
3451
3570
  if (activeDrawingTool === "fib-retracement") {
3452
3571
  if (draftDrawing?.type === "fib-retracement") {
3453
3572
  const completed = normalizeDrawingState({
@@ -3744,7 +3863,7 @@ function createChart(element, options = {}) {
3744
3863
  setCrosshairPoint(null);
3745
3864
  return;
3746
3865
  }
3747
- if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
3866
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
3748
3867
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3749
3868
  if (nextPoint) {
3750
3869
  draftDrawing = {
package/docs/API.md CHANGED
@@ -414,7 +414,11 @@ Volume style inputs:
414
414
  Drawings are user-created chart tools, separate from indicators. They are interactive chart objects like horizontal lines and trendlines.
415
415
 
416
416
  - `id?: string`
417
- - `type: "horizontal-line" | "trendline"`
417
+ - `type: "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement"`
418
+ - `horizontal-line` / `vertical-line`: single-point, full-width/full-height line (one click to place)
419
+ - `trendline`: two-point segment (click start, click end)
420
+ - `ray`: two-point line that extends infinitely past the second point
421
+ - `fib-retracement`: two-point retracement with levels/bands
418
422
  - `points: DrawingPoint[]`
419
423
  - `visible?: boolean`
420
424
  - `color?: string`
@@ -436,7 +440,10 @@ Tool workflow:
436
440
 
437
441
  ```ts
438
442
  chart.setActiveDrawingTool("horizontal-line"); // next plot click creates a line
443
+ chart.setActiveDrawingTool("vertical-line"); // next plot click creates a vertical line
439
444
  chart.setActiveDrawingTool("trendline"); // first click starts, second click commits
445
+ chart.setActiveDrawingTool("ray"); // first click starts, second click commits (extends past p2)
446
+ chart.setActiveDrawingTool("fib-retracement"); // first click starts, second click commits
440
447
  chart.setActiveDrawingTool(null); // back to normal cursor/pan
441
448
  ```
442
449
 
@@ -466,8 +473,8 @@ Use `getDrawings()` / `setDrawings()` for persistence.
466
473
  - `panY(priceDelta: number): void` (positive = move viewport up)
467
474
  - `fitContent(): void` (x-only fit, keeps y zoom)
468
475
  - `resetViewport(): void` (fit x + reset y auto-scale)
469
- - `setActiveDrawingTool(tool: "horizontal-line" | "trendline" | null): void`
470
- - `getActiveDrawingTool(): "horizontal-line" | "trendline" | null`
476
+ - `setActiveDrawingTool(tool: DrawingToolType | null): void` (`DrawingToolType` = `"horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement"`)
477
+ - `getActiveDrawingTool(): DrawingToolType | null`
471
478
  - `setDrawings(drawings: DrawingObjectOptions[]): void`
472
479
  - `getDrawings(): DrawingObjectOptions[]`
473
480
  - `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.68",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",