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.
@@ -126,7 +126,8 @@ var DEFAULT_PRICE_LINE_OPTIONS = {
126
126
  labelBackgroundColor: "#0b1220",
127
127
  labelTextColor: "#60a5fa",
128
128
  labelBorderRadius: 3,
129
- showLabel: true
129
+ showLabel: true,
130
+ pinOutOfRange: false
130
131
  };
131
132
  var DEFAULT_ORDER_LINE_OPTIONS = {
132
133
  visible: true,
@@ -160,7 +161,8 @@ var DEFAULT_ORDER_LINE_OPTIONS = {
160
161
  connectorThickness: 1,
161
162
  connectorAnchorPaddingRight: 10,
162
163
  fillToPrice: Number.NaN,
163
- fillColor: "rgba(37,99,235,0.18)"
164
+ fillColor: "rgba(37,99,235,0.18)",
165
+ pinOutOfRange: false
164
166
  };
165
167
  var DEFAULT_OPTIONS = {
166
168
  width: 720,
@@ -193,6 +195,7 @@ var DEFAULT_OPTIONS = {
193
195
  candleColorEpsilon: -1,
194
196
  autoScaleSmoothing: 0.16,
195
197
  autoScaleIgnoreLatestCandle: true,
198
+ pinOutOfRangeLines: false,
196
199
  doubleClickEnabled: true,
197
200
  doubleClickAction: "reset",
198
201
  crosshair: DEFAULT_CROSSHAIR_OPTIONS,
@@ -1501,12 +1504,27 @@ function createChart(element, options = {}) {
1501
1504
  const scaleWidth = Math.max(0, Math.floor(width - getRightAxisLabelX(chartRight)));
1502
1505
  return Math.max(contentWidth, scaleWidth);
1503
1506
  };
1507
+ const getLineY = (price, yFromPrice, chartTop, chartBottom, pinOutOfRange = mergedOptions.pinOutOfRangeLines) => {
1508
+ const y = yFromPrice(price);
1509
+ if (!Number.isFinite(y)) {
1510
+ return null;
1511
+ }
1512
+ const minY = chartTop + 1;
1513
+ const maxY = chartBottom - 1;
1514
+ if (y < minY || y > maxY) {
1515
+ return pinOutOfRange ? clamp(y, minY, maxY) : null;
1516
+ }
1517
+ return y;
1518
+ };
1504
1519
  const drawPriceLine = (line, yFromPrice, chartLeft, chartTop, chartRight, chartBottom) => {
1505
1520
  const mergedLine = { ...DEFAULT_PRICE_LINE_OPTIONS, ...line };
1506
1521
  if (!mergedLine.visible || !Number.isFinite(mergedLine.price)) {
1507
1522
  return;
1508
1523
  }
1509
- const lineY = clamp(yFromPrice(mergedLine.price), chartTop + 1, chartBottom - 1);
1524
+ const lineY = getLineY(mergedLine.price, yFromPrice, chartTop, chartBottom, mergedLine.pinOutOfRange);
1525
+ if (lineY === null) {
1526
+ return;
1527
+ }
1510
1528
  const color = mergedLine.color;
1511
1529
  ctx.save();
1512
1530
  ctx.strokeStyle = color;
@@ -1552,10 +1570,16 @@ function createChart(element, options = {}) {
1552
1570
  if (!mergedLine.visible || !Number.isFinite(renderPrice)) {
1553
1571
  return;
1554
1572
  }
1555
- const lineY = clamp(yFromPrice(renderPrice), chartTop + 1, chartBottom - 1);
1573
+ const lineY = getLineY(renderPrice, yFromPrice, chartTop, chartBottom, mergedLine.pinOutOfRange);
1574
+ if (lineY === null) {
1575
+ return;
1576
+ }
1556
1577
  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)");
1557
1578
  if (Number.isFinite(mergedLine.fillToPrice)) {
1558
- const fillY = clamp(yFromPrice(mergedLine.fillToPrice), chartTop + 1, chartBottom - 1);
1579
+ const fillY = getLineY(mergedLine.fillToPrice, yFromPrice, chartTop, chartBottom, true);
1580
+ if (fillY === null) {
1581
+ return;
1582
+ }
1559
1583
  const topY = Math.min(lineY, fillY);
1560
1584
  const heightY = Math.max(1, Math.abs(lineY - fillY));
1561
1585
  ctx.save();
@@ -1573,21 +1597,29 @@ function createChart(element, options = {}) {
1573
1597
  ctx.stroke();
1574
1598
  ctx.restore();
1575
1599
  if (Number.isFinite(mergedLine.connectorToPrice)) {
1576
- const connectorY = clamp(yFromPrice(mergedLine.connectorToPrice), chartTop + 1, chartBottom - 1);
1577
- const connectorX = chartRight - Math.max(6, mergedLine.connectorAnchorPaddingRight);
1578
- ctx.save();
1579
- ctx.strokeStyle = mergedLine.connectorColor ?? color;
1580
- ctx.lineWidth = Math.max(1, mergedLine.connectorThickness);
1581
- applyDashPattern(
1582
- mergedLine.connectorStyle,
1583
- dashPatterns.connectorDotted,
1584
- dashPatterns.connectorDashed
1600
+ const connectorY = getLineY(
1601
+ mergedLine.connectorToPrice,
1602
+ yFromPrice,
1603
+ chartTop,
1604
+ chartBottom,
1605
+ mergedLine.pinOutOfRange
1585
1606
  );
1586
- ctx.beginPath();
1587
- ctx.moveTo(crisp(connectorX), crisp(lineY));
1588
- ctx.lineTo(crisp(connectorX), crisp(connectorY));
1589
- ctx.stroke();
1590
- ctx.restore();
1607
+ if (connectorY !== null) {
1608
+ const connectorX = chartRight - Math.max(6, mergedLine.connectorAnchorPaddingRight);
1609
+ ctx.save();
1610
+ ctx.strokeStyle = mergedLine.connectorColor ?? color;
1611
+ ctx.lineWidth = Math.max(1, mergedLine.connectorThickness);
1612
+ applyDashPattern(
1613
+ mergedLine.connectorStyle,
1614
+ dashPatterns.connectorDotted,
1615
+ dashPatterns.connectorDashed
1616
+ );
1617
+ ctx.beginPath();
1618
+ ctx.moveTo(crisp(connectorX), crisp(lineY));
1619
+ ctx.lineTo(crisp(connectorX), crisp(connectorY));
1620
+ ctx.stroke();
1621
+ ctx.restore();
1622
+ }
1591
1623
  }
1592
1624
  const qtyText = mergedLine.qty === void 0 ? "" : String(mergedLine.qty);
1593
1625
  const typeTextMap = {
@@ -2120,7 +2152,11 @@ function createChart(element, options = {}) {
2120
2152
  if (drawing.type === "horizontal-line") {
2121
2153
  const point = drawing.points[0];
2122
2154
  if (point) {
2123
- const y = clamp(yFromPrice(point.price), chartTop + 1, chartBottom - 1);
2155
+ const y = getLineY(point.price, yFromPrice, chartTop, chartBottom);
2156
+ if (y === null) {
2157
+ ctx.restore();
2158
+ return;
2159
+ }
2124
2160
  const handleX = chartRight - 64;
2125
2161
  ctx.beginPath();
2126
2162
  ctx.moveTo(crisp(chartLeft), crisp(y));
@@ -2132,6 +2168,20 @@ function createChart(element, options = {}) {
2132
2168
  drawDrawingLabel(drawing.label, midX, y, drawing.color);
2133
2169
  }
2134
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
+ }
2135
2185
  } else if (drawing.type === "trendline") {
2136
2186
  const first = drawing.points[0];
2137
2187
  const second = drawing.points[1];
@@ -2152,6 +2202,36 @@ function createChart(element, options = {}) {
2152
2202
  drawDrawingLabel(drawing.label, midX, midY, drawing.color);
2153
2203
  }
2154
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
+ }
2155
2235
  } else if (drawing.type === "fib-retracement") {
2156
2236
  const first = drawing.points[0];
2157
2237
  const second = drawing.points[1];
@@ -2560,13 +2640,19 @@ function createChart(element, options = {}) {
2560
2640
  if (!labels.visible || !Number.isFinite(label.price) || label.text.length === 0) {
2561
2641
  return;
2562
2642
  }
2643
+ if (getLineY(label.price, yFromPrice, chartTop, chartBottom, label.pinOutOfRange) === null) {
2644
+ return;
2645
+ }
2563
2646
  priceAxisLabels.push(label);
2564
2647
  };
2565
- const drawReferenceLine = (price, color, style = "dotted") => {
2648
+ const drawReferenceLine = (price, color, style = "dotted", pinOutOfRange = mergedOptions.pinOutOfRangeLines) => {
2566
2649
  if (!Number.isFinite(price)) {
2567
2650
  return;
2568
2651
  }
2569
- const y = clamp(yFromPrice(price), chartTop + 1, chartBottom - 1);
2652
+ const y = getLineY(price, yFromPrice, chartTop, chartBottom, pinOutOfRange);
2653
+ if (y === null) {
2654
+ return;
2655
+ }
2570
2656
  ctx.save();
2571
2657
  ctx.strokeStyle = color;
2572
2658
  ctx.lineWidth = 1;
@@ -2593,22 +2679,23 @@ function createChart(element, options = {}) {
2593
2679
  let tickerColor = null;
2594
2680
  if ((ticker.visible ?? true) && lastPoint) {
2595
2681
  tickerPrice = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint.c;
2596
- const tickerY = yFromPrice(tickerPrice);
2597
- const lineY = clamp(tickerY, chartTop + 1, chartBottom - 1);
2682
+ const lineY = getLineY(tickerPrice, yFromPrice, chartTop, chartBottom);
2598
2683
  const lastDirection = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice >= lastPoint.o ? "up" : "down" : getCandleDirectionByIndex(data.length - 1);
2599
2684
  tickerColor = ticker.color ?? (lastDirection === "up" ? mergedOptions.upColor : mergedOptions.downColor);
2600
2685
  const tickerThickness = Math.max(1, ticker.thickness ?? 1);
2601
2686
  const tickerStyle = ticker.style ?? "solid";
2602
- ctx.save();
2603
- ctx.strokeStyle = tickerColor;
2604
- ctx.lineWidth = tickerThickness;
2605
- applyDashPattern(tickerStyle, dashPatterns.dotted, dashPatterns.dashed);
2606
- ctx.beginPath();
2607
- ctx.moveTo(crisp(chartLeft), crisp(lineY));
2608
- ctx.lineTo(crisp(chartRight), crisp(lineY));
2609
- ctx.stroke();
2610
- ctx.setLineDash([]);
2611
- ctx.restore();
2687
+ if (lineY !== null) {
2688
+ ctx.save();
2689
+ ctx.strokeStyle = tickerColor;
2690
+ ctx.lineWidth = tickerThickness;
2691
+ applyDashPattern(tickerStyle, dashPatterns.dotted, dashPatterns.dashed);
2692
+ ctx.beginPath();
2693
+ ctx.moveTo(crisp(chartLeft), crisp(lineY));
2694
+ ctx.lineTo(crisp(chartRight), crisp(lineY));
2695
+ ctx.stroke();
2696
+ ctx.setLineDash([]);
2697
+ ctx.restore();
2698
+ }
2612
2699
  }
2613
2700
  if ((ticker.visible ?? true) && labels.showLastPrice && tickerPrice !== null && tickerColor !== null) {
2614
2701
  const tickerSubtexts = [
@@ -2718,13 +2805,14 @@ function createChart(element, options = {}) {
2718
2805
  }
2719
2806
  const labelTextWidth = Math.max(primaryWidth, subtextWidth);
2720
2807
  const labelWidth = getRightAxisLabelWidth(chartRight, labelTextWidth);
2808
+ const labelLineY = getLineY(label.price, yFromPrice, chartTop, chartBottom, label.pinOutOfRange);
2721
2809
  return {
2722
2810
  ...label,
2723
2811
  subtexts,
2724
2812
  subtextFontSize,
2725
2813
  height: labelHeight,
2726
2814
  width: labelWidth,
2727
- targetY: clamp(yFromPrice(label.price), chartTop + 1, chartBottom - 1) - labelHeight / 2,
2815
+ targetY: (labelLineY ?? yFromPrice(label.price)) - labelHeight / 2,
2728
2816
  y: 0
2729
2817
  };
2730
2818
  }).sort((a, b) => a.targetY - b.targetY || b.priority - a.priority);
@@ -3286,6 +3374,17 @@ function createChart(element, options = {}) {
3286
3374
  if (Math.abs(y - lineY) <= 7) {
3287
3375
  return { drawing, target: "line" };
3288
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
+ }
3289
3388
  } else if (drawing.type === "trendline") {
3290
3389
  const first = drawing.points[0];
3291
3390
  const second = drawing.points[1];
@@ -3303,6 +3402,31 @@ function createChart(element, options = {}) {
3303
3402
  if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3304
3403
  return { drawing, target: "line" };
3305
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
+ }
3306
3430
  } else if (drawing.type === "fib-retracement") {
3307
3431
  const first = drawing.points[0];
3308
3432
  const second = drawing.points[1];
@@ -3404,6 +3528,21 @@ function createChart(element, options = {}) {
3404
3528
  draw();
3405
3529
  return true;
3406
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
+ }
3407
3546
  if (activeDrawingTool === "trendline") {
3408
3547
  if (draftDrawing?.type === "trendline") {
3409
3548
  const completed = normalizeDrawingState({
@@ -3428,6 +3567,30 @@ function createChart(element, options = {}) {
3428
3567
  draw();
3429
3568
  return true;
3430
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
+ }
3431
3594
  if (activeDrawingTool === "fib-retracement") {
3432
3595
  if (draftDrawing?.type === "fib-retracement") {
3433
3596
  const completed = normalizeDrawingState({
@@ -3724,7 +3887,7 @@ function createChart(element, options = {}) {
3724
3887
  setCrosshairPoint(null);
3725
3888
  return;
3726
3889
  }
3727
- if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "fib-retracement")) {
3890
+ if (draftDrawing && (activeDrawingTool === "trendline" || activeDrawingTool === "ray" || activeDrawingTool === "fib-retracement")) {
3728
3891
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3729
3892
  if (nextPoint) {
3730
3893
  draftDrawing = {
@@ -29,6 +29,7 @@ interface ChartOptions {
29
29
  candleColorEpsilon?: number;
30
30
  autoScaleSmoothing?: number;
31
31
  autoScaleIgnoreLatestCandle?: boolean;
32
+ pinOutOfRangeLines?: boolean;
32
33
  doubleClickEnabled?: boolean;
33
34
  doubleClickAction?: "reset" | "placeLimitOrder";
34
35
  crosshair?: CrosshairOptions;
@@ -43,7 +44,7 @@ interface ChartOptions {
43
44
  drawings?: DrawingObjectOptions[];
44
45
  }
45
46
  type IndicatorPane = "overlay" | "separate";
46
- type DrawingToolType = "horizontal-line" | "trendline" | "fib-retracement";
47
+ type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement";
47
48
  interface DrawingPoint {
48
49
  index: number;
49
50
  price: number;
@@ -233,6 +234,7 @@ interface PriceLineOptions {
233
234
  labelTextColor?: string;
234
235
  labelBorderRadius?: number;
235
236
  showLabel?: boolean;
237
+ pinOutOfRange?: boolean;
236
238
  }
237
239
  interface OrderLineOptions {
238
240
  id?: string;
@@ -276,6 +278,7 @@ interface OrderLineOptions {
276
278
  connectorAnchorPaddingRight?: number;
277
279
  fillToPrice?: number;
278
280
  fillColor?: string;
281
+ pinOutOfRange?: boolean;
279
282
  }
280
283
  interface OrderActionEvent {
281
284
  orderId?: string;