hyperprop-charting-library 0.1.66 → 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.
@@ -102,7 +102,8 @@ var DEFAULT_PRICE_LINE_OPTIONS = {
102
102
  labelBackgroundColor: "#0b1220",
103
103
  labelTextColor: "#60a5fa",
104
104
  labelBorderRadius: 3,
105
- showLabel: true
105
+ showLabel: true,
106
+ pinOutOfRange: false
106
107
  };
107
108
  var DEFAULT_ORDER_LINE_OPTIONS = {
108
109
  visible: true,
@@ -136,7 +137,8 @@ var DEFAULT_ORDER_LINE_OPTIONS = {
136
137
  connectorThickness: 1,
137
138
  connectorAnchorPaddingRight: 10,
138
139
  fillToPrice: Number.NaN,
139
- fillColor: "rgba(37,99,235,0.18)"
140
+ fillColor: "rgba(37,99,235,0.18)",
141
+ pinOutOfRange: false
140
142
  };
141
143
  var DEFAULT_OPTIONS = {
142
144
  width: 720,
@@ -169,6 +171,7 @@ var DEFAULT_OPTIONS = {
169
171
  candleColorEpsilon: -1,
170
172
  autoScaleSmoothing: 0.16,
171
173
  autoScaleIgnoreLatestCandle: true,
174
+ pinOutOfRangeLines: false,
172
175
  doubleClickEnabled: true,
173
176
  doubleClickAction: "reset",
174
177
  crosshair: DEFAULT_CROSSHAIR_OPTIONS,
@@ -1477,12 +1480,27 @@ function createChart(element, options = {}) {
1477
1480
  const scaleWidth = Math.max(0, Math.floor(width - getRightAxisLabelX(chartRight)));
1478
1481
  return Math.max(contentWidth, scaleWidth);
1479
1482
  };
1483
+ const getLineY = (price, yFromPrice, chartTop, chartBottom, pinOutOfRange = mergedOptions.pinOutOfRangeLines) => {
1484
+ const y = yFromPrice(price);
1485
+ if (!Number.isFinite(y)) {
1486
+ return null;
1487
+ }
1488
+ const minY = chartTop + 1;
1489
+ const maxY = chartBottom - 1;
1490
+ if (y < minY || y > maxY) {
1491
+ return pinOutOfRange ? clamp(y, minY, maxY) : null;
1492
+ }
1493
+ return y;
1494
+ };
1480
1495
  const drawPriceLine = (line, yFromPrice, chartLeft, chartTop, chartRight, chartBottom) => {
1481
1496
  const mergedLine = { ...DEFAULT_PRICE_LINE_OPTIONS, ...line };
1482
1497
  if (!mergedLine.visible || !Number.isFinite(mergedLine.price)) {
1483
1498
  return;
1484
1499
  }
1485
- const lineY = clamp(yFromPrice(mergedLine.price), chartTop + 1, chartBottom - 1);
1500
+ const lineY = getLineY(mergedLine.price, yFromPrice, chartTop, chartBottom, mergedLine.pinOutOfRange);
1501
+ if (lineY === null) {
1502
+ return;
1503
+ }
1486
1504
  const color = mergedLine.color;
1487
1505
  ctx.save();
1488
1506
  ctx.strokeStyle = color;
@@ -1528,10 +1546,16 @@ function createChart(element, options = {}) {
1528
1546
  if (!mergedLine.visible || !Number.isFinite(renderPrice)) {
1529
1547
  return;
1530
1548
  }
1531
- const lineY = clamp(yFromPrice(renderPrice), chartTop + 1, chartBottom - 1);
1549
+ const lineY = getLineY(renderPrice, yFromPrice, chartTop, chartBottom, mergedLine.pinOutOfRange);
1550
+ if (lineY === null) {
1551
+ return;
1552
+ }
1532
1553
  const color = line.color ?? (mergedLine.type === "takeProfit" ? "rgba(45,212,191,0.86)" : mergedLine.type === "stop" ? "rgba(245,158,11,0.86)" : "rgba(59,130,246,0.8)");
1533
1554
  if (Number.isFinite(mergedLine.fillToPrice)) {
1534
- const fillY = clamp(yFromPrice(mergedLine.fillToPrice), chartTop + 1, chartBottom - 1);
1555
+ const fillY = getLineY(mergedLine.fillToPrice, yFromPrice, chartTop, chartBottom, true);
1556
+ if (fillY === null) {
1557
+ return;
1558
+ }
1535
1559
  const topY = Math.min(lineY, fillY);
1536
1560
  const heightY = Math.max(1, Math.abs(lineY - fillY));
1537
1561
  ctx.save();
@@ -1549,21 +1573,29 @@ function createChart(element, options = {}) {
1549
1573
  ctx.stroke();
1550
1574
  ctx.restore();
1551
1575
  if (Number.isFinite(mergedLine.connectorToPrice)) {
1552
- const connectorY = clamp(yFromPrice(mergedLine.connectorToPrice), chartTop + 1, chartBottom - 1);
1553
- const connectorX = chartRight - Math.max(6, mergedLine.connectorAnchorPaddingRight);
1554
- ctx.save();
1555
- ctx.strokeStyle = mergedLine.connectorColor ?? color;
1556
- ctx.lineWidth = Math.max(1, mergedLine.connectorThickness);
1557
- applyDashPattern(
1558
- mergedLine.connectorStyle,
1559
- dashPatterns.connectorDotted,
1560
- dashPatterns.connectorDashed
1576
+ const connectorY = getLineY(
1577
+ mergedLine.connectorToPrice,
1578
+ yFromPrice,
1579
+ chartTop,
1580
+ chartBottom,
1581
+ mergedLine.pinOutOfRange
1561
1582
  );
1562
- ctx.beginPath();
1563
- ctx.moveTo(crisp(connectorX), crisp(lineY));
1564
- ctx.lineTo(crisp(connectorX), crisp(connectorY));
1565
- ctx.stroke();
1566
- ctx.restore();
1583
+ if (connectorY !== null) {
1584
+ const connectorX = chartRight - Math.max(6, mergedLine.connectorAnchorPaddingRight);
1585
+ ctx.save();
1586
+ ctx.strokeStyle = mergedLine.connectorColor ?? color;
1587
+ ctx.lineWidth = Math.max(1, mergedLine.connectorThickness);
1588
+ applyDashPattern(
1589
+ mergedLine.connectorStyle,
1590
+ dashPatterns.connectorDotted,
1591
+ dashPatterns.connectorDashed
1592
+ );
1593
+ ctx.beginPath();
1594
+ ctx.moveTo(crisp(connectorX), crisp(lineY));
1595
+ ctx.lineTo(crisp(connectorX), crisp(connectorY));
1596
+ ctx.stroke();
1597
+ ctx.restore();
1598
+ }
1567
1599
  }
1568
1600
  const qtyText = mergedLine.qty === void 0 ? "" : String(mergedLine.qty);
1569
1601
  const typeTextMap = {
@@ -2096,7 +2128,11 @@ function createChart(element, options = {}) {
2096
2128
  if (drawing.type === "horizontal-line") {
2097
2129
  const point = drawing.points[0];
2098
2130
  if (point) {
2099
- const y = clamp(yFromPrice(point.price), chartTop + 1, chartBottom - 1);
2131
+ const y = getLineY(point.price, yFromPrice, chartTop, chartBottom);
2132
+ if (y === null) {
2133
+ ctx.restore();
2134
+ return;
2135
+ }
2100
2136
  const handleX = chartRight - 64;
2101
2137
  ctx.beginPath();
2102
2138
  ctx.moveTo(crisp(chartLeft), crisp(y));
@@ -2108,6 +2144,20 @@ function createChart(element, options = {}) {
2108
2144
  drawDrawingLabel(drawing.label, midX, y, drawing.color);
2109
2145
  }
2110
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
+ }
2111
2161
  } else if (drawing.type === "trendline") {
2112
2162
  const first = drawing.points[0];
2113
2163
  const second = drawing.points[1];
@@ -2128,6 +2178,36 @@ function createChart(element, options = {}) {
2128
2178
  drawDrawingLabel(drawing.label, midX, midY, drawing.color);
2129
2179
  }
2130
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
+ }
2131
2211
  } else if (drawing.type === "fib-retracement") {
2132
2212
  const first = drawing.points[0];
2133
2213
  const second = drawing.points[1];
@@ -2536,13 +2616,19 @@ function createChart(element, options = {}) {
2536
2616
  if (!labels.visible || !Number.isFinite(label.price) || label.text.length === 0) {
2537
2617
  return;
2538
2618
  }
2619
+ if (getLineY(label.price, yFromPrice, chartTop, chartBottom, label.pinOutOfRange) === null) {
2620
+ return;
2621
+ }
2539
2622
  priceAxisLabels.push(label);
2540
2623
  };
2541
- const drawReferenceLine = (price, color, style = "dotted") => {
2624
+ const drawReferenceLine = (price, color, style = "dotted", pinOutOfRange = mergedOptions.pinOutOfRangeLines) => {
2542
2625
  if (!Number.isFinite(price)) {
2543
2626
  return;
2544
2627
  }
2545
- const y = clamp(yFromPrice(price), chartTop + 1, chartBottom - 1);
2628
+ const y = getLineY(price, yFromPrice, chartTop, chartBottom, pinOutOfRange);
2629
+ if (y === null) {
2630
+ return;
2631
+ }
2546
2632
  ctx.save();
2547
2633
  ctx.strokeStyle = color;
2548
2634
  ctx.lineWidth = 1;
@@ -2569,22 +2655,23 @@ function createChart(element, options = {}) {
2569
2655
  let tickerColor = null;
2570
2656
  if ((ticker.visible ?? true) && lastPoint) {
2571
2657
  tickerPrice = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint.c;
2572
- const tickerY = yFromPrice(tickerPrice);
2573
- const lineY = clamp(tickerY, chartTop + 1, chartBottom - 1);
2658
+ const lineY = getLineY(tickerPrice, yFromPrice, chartTop, chartBottom);
2574
2659
  const lastDirection = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice >= lastPoint.o ? "up" : "down" : getCandleDirectionByIndex(data.length - 1);
2575
2660
  tickerColor = ticker.color ?? (lastDirection === "up" ? mergedOptions.upColor : mergedOptions.downColor);
2576
2661
  const tickerThickness = Math.max(1, ticker.thickness ?? 1);
2577
2662
  const tickerStyle = ticker.style ?? "solid";
2578
- ctx.save();
2579
- ctx.strokeStyle = tickerColor;
2580
- ctx.lineWidth = tickerThickness;
2581
- applyDashPattern(tickerStyle, dashPatterns.dotted, dashPatterns.dashed);
2582
- ctx.beginPath();
2583
- ctx.moveTo(crisp(chartLeft), crisp(lineY));
2584
- ctx.lineTo(crisp(chartRight), crisp(lineY));
2585
- ctx.stroke();
2586
- ctx.setLineDash([]);
2587
- ctx.restore();
2663
+ if (lineY !== null) {
2664
+ ctx.save();
2665
+ ctx.strokeStyle = tickerColor;
2666
+ ctx.lineWidth = tickerThickness;
2667
+ applyDashPattern(tickerStyle, dashPatterns.dotted, dashPatterns.dashed);
2668
+ ctx.beginPath();
2669
+ ctx.moveTo(crisp(chartLeft), crisp(lineY));
2670
+ ctx.lineTo(crisp(chartRight), crisp(lineY));
2671
+ ctx.stroke();
2672
+ ctx.setLineDash([]);
2673
+ ctx.restore();
2674
+ }
2588
2675
  }
2589
2676
  if ((ticker.visible ?? true) && labels.showLastPrice && tickerPrice !== null && tickerColor !== null) {
2590
2677
  const tickerSubtexts = [
@@ -2694,13 +2781,14 @@ function createChart(element, options = {}) {
2694
2781
  }
2695
2782
  const labelTextWidth = Math.max(primaryWidth, subtextWidth);
2696
2783
  const labelWidth = getRightAxisLabelWidth(chartRight, labelTextWidth);
2784
+ const labelLineY = getLineY(label.price, yFromPrice, chartTop, chartBottom, label.pinOutOfRange);
2697
2785
  return {
2698
2786
  ...label,
2699
2787
  subtexts,
2700
2788
  subtextFontSize,
2701
2789
  height: labelHeight,
2702
2790
  width: labelWidth,
2703
- targetY: clamp(yFromPrice(label.price), chartTop + 1, chartBottom - 1) - labelHeight / 2,
2791
+ targetY: (labelLineY ?? yFromPrice(label.price)) - labelHeight / 2,
2704
2792
  y: 0
2705
2793
  };
2706
2794
  }).sort((a, b) => a.targetY - b.targetY || b.priority - a.priority);
@@ -3262,6 +3350,17 @@ function createChart(element, options = {}) {
3262
3350
  if (Math.abs(y - lineY) <= 7) {
3263
3351
  return { drawing, target: "line" };
3264
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
+ }
3265
3364
  } else if (drawing.type === "trendline") {
3266
3365
  const first = drawing.points[0];
3267
3366
  const second = drawing.points[1];
@@ -3279,6 +3378,31 @@ function createChart(element, options = {}) {
3279
3378
  if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3280
3379
  return { drawing, target: "line" };
3281
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
+ }
3282
3406
  } else if (drawing.type === "fib-retracement") {
3283
3407
  const first = drawing.points[0];
3284
3408
  const second = drawing.points[1];
@@ -3380,6 +3504,21 @@ function createChart(element, options = {}) {
3380
3504
  draw();
3381
3505
  return true;
3382
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
+ }
3383
3522
  if (activeDrawingTool === "trendline") {
3384
3523
  if (draftDrawing?.type === "trendline") {
3385
3524
  const completed = normalizeDrawingState({
@@ -3404,6 +3543,30 @@ function createChart(element, options = {}) {
3404
3543
  draw();
3405
3544
  return true;
3406
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
+ }
3407
3570
  if (activeDrawingTool === "fib-retracement") {
3408
3571
  if (draftDrawing?.type === "fib-retracement") {
3409
3572
  const completed = normalizeDrawingState({
@@ -3700,7 +3863,7 @@ function createChart(element, options = {}) {
3700
3863
  setCrosshairPoint(null);
3701
3864
  return;
3702
3865
  }
3703
- if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
3866
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
3704
3867
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3705
3868
  if (nextPoint) {
3706
3869
  draftDrawing = {