hyperprop-charting-library 0.1.65 → 0.1.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hyperprop-charting-library.cjs +80 -46
- package/dist/hyperprop-charting-library.d.ts +3 -0
- package/dist/hyperprop-charting-library.js +80 -46
- package/dist/index.cjs +80 -46
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +80 -46
- package/docs/API.md +3 -0
- package/docs/RECIPES.md +16 -0
- package/package.json +1 -1
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
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
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
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 =
|
|
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));
|
|
@@ -2560,13 +2596,19 @@ function createChart(element, options = {}) {
|
|
|
2560
2596
|
if (!labels.visible || !Number.isFinite(label.price) || label.text.length === 0) {
|
|
2561
2597
|
return;
|
|
2562
2598
|
}
|
|
2599
|
+
if (getLineY(label.price, yFromPrice, chartTop, chartBottom, label.pinOutOfRange) === null) {
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2563
2602
|
priceAxisLabels.push(label);
|
|
2564
2603
|
};
|
|
2565
|
-
const drawReferenceLine = (price, color, style = "dotted") => {
|
|
2604
|
+
const drawReferenceLine = (price, color, style = "dotted", pinOutOfRange = mergedOptions.pinOutOfRangeLines) => {
|
|
2566
2605
|
if (!Number.isFinite(price)) {
|
|
2567
2606
|
return;
|
|
2568
2607
|
}
|
|
2569
|
-
const y =
|
|
2608
|
+
const y = getLineY(price, yFromPrice, chartTop, chartBottom, pinOutOfRange);
|
|
2609
|
+
if (y === null) {
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2570
2612
|
ctx.save();
|
|
2571
2613
|
ctx.strokeStyle = color;
|
|
2572
2614
|
ctx.lineWidth = 1;
|
|
@@ -2593,22 +2635,23 @@ function createChart(element, options = {}) {
|
|
|
2593
2635
|
let tickerColor = null;
|
|
2594
2636
|
if ((ticker.visible ?? true) && lastPoint) {
|
|
2595
2637
|
tickerPrice = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint.c;
|
|
2596
|
-
const
|
|
2597
|
-
const lineY = clamp(tickerY, chartTop + 1, chartBottom - 1);
|
|
2638
|
+
const lineY = getLineY(tickerPrice, yFromPrice, chartTop, chartBottom);
|
|
2598
2639
|
const lastDirection = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice >= lastPoint.o ? "up" : "down" : getCandleDirectionByIndex(data.length - 1);
|
|
2599
2640
|
tickerColor = ticker.color ?? (lastDirection === "up" ? mergedOptions.upColor : mergedOptions.downColor);
|
|
2600
2641
|
const tickerThickness = Math.max(1, ticker.thickness ?? 1);
|
|
2601
2642
|
const tickerStyle = ticker.style ?? "solid";
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2643
|
+
if (lineY !== null) {
|
|
2644
|
+
ctx.save();
|
|
2645
|
+
ctx.strokeStyle = tickerColor;
|
|
2646
|
+
ctx.lineWidth = tickerThickness;
|
|
2647
|
+
applyDashPattern(tickerStyle, dashPatterns.dotted, dashPatterns.dashed);
|
|
2648
|
+
ctx.beginPath();
|
|
2649
|
+
ctx.moveTo(crisp(chartLeft), crisp(lineY));
|
|
2650
|
+
ctx.lineTo(crisp(chartRight), crisp(lineY));
|
|
2651
|
+
ctx.stroke();
|
|
2652
|
+
ctx.setLineDash([]);
|
|
2653
|
+
ctx.restore();
|
|
2654
|
+
}
|
|
2612
2655
|
}
|
|
2613
2656
|
if ((ticker.visible ?? true) && labels.showLastPrice && tickerPrice !== null && tickerColor !== null) {
|
|
2614
2657
|
const tickerSubtexts = [
|
|
@@ -2718,13 +2761,14 @@ function createChart(element, options = {}) {
|
|
|
2718
2761
|
}
|
|
2719
2762
|
const labelTextWidth = Math.max(primaryWidth, subtextWidth);
|
|
2720
2763
|
const labelWidth = getRightAxisLabelWidth(chartRight, labelTextWidth);
|
|
2764
|
+
const labelLineY = getLineY(label.price, yFromPrice, chartTop, chartBottom, label.pinOutOfRange);
|
|
2721
2765
|
return {
|
|
2722
2766
|
...label,
|
|
2723
2767
|
subtexts,
|
|
2724
2768
|
subtextFontSize,
|
|
2725
2769
|
height: labelHeight,
|
|
2726
2770
|
width: labelWidth,
|
|
2727
|
-
targetY:
|
|
2771
|
+
targetY: (labelLineY ?? yFromPrice(label.price)) - labelHeight / 2,
|
|
2728
2772
|
y: 0
|
|
2729
2773
|
};
|
|
2730
2774
|
}).sort((a, b) => a.targetY - b.targetY || b.priority - a.priority);
|
|
@@ -3522,15 +3566,10 @@ function createChart(element, options = {}) {
|
|
|
3522
3566
|
}
|
|
3523
3567
|
const midpoint = getMidpoint(first, second);
|
|
3524
3568
|
const anchorRatio = clamp((midpoint.x - drawState.chartLeft) / drawState.chartWidth, 0, 1);
|
|
3525
|
-
const startYMin = yMinOverride ?? drawState.yMin;
|
|
3526
|
-
const startYMax = yMaxOverride ?? drawState.yMax;
|
|
3527
3569
|
pinchZoomState = {
|
|
3528
3570
|
startDistance: Math.max(1, getPointerDistance(first, second)),
|
|
3529
3571
|
startSpan: xSpan,
|
|
3530
|
-
anchorIndex: drawState.xStart + anchorRatio * xSpan
|
|
3531
|
-
startMidpoint: midpoint,
|
|
3532
|
-
startYMin,
|
|
3533
|
-
startYMax
|
|
3572
|
+
anchorIndex: drawState.xStart + anchorRatio * xSpan
|
|
3534
3573
|
};
|
|
3535
3574
|
isDragging = false;
|
|
3536
3575
|
dragMode = null;
|
|
@@ -3559,11 +3598,6 @@ function createChart(element, options = {}) {
|
|
|
3559
3598
|
xSpan = nextSpan;
|
|
3560
3599
|
xCenter = nextStart + nextSpan / 2;
|
|
3561
3600
|
clampXViewport();
|
|
3562
|
-
const startYRange = pinchZoomState.startYMax - pinchZoomState.startYMin || 1;
|
|
3563
|
-
const priceShift = (midpoint.y - pinchZoomState.startMidpoint.y) / drawState.chartHeight * startYRange;
|
|
3564
|
-
const clampedY = clampYRange(pinchZoomState.startYMin + priceShift, pinchZoomState.startYMax + priceShift);
|
|
3565
|
-
yMinOverride = clampedY.min;
|
|
3566
|
-
yMaxOverride = clampedY.max;
|
|
3567
3601
|
updateFollowLatest(false);
|
|
3568
3602
|
emitViewportChange();
|
|
3569
3603
|
draw();
|
|
@@ -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;
|
|
@@ -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;
|
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
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
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
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 =
|
|
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));
|
|
@@ -2536,13 +2572,19 @@ function createChart(element, options = {}) {
|
|
|
2536
2572
|
if (!labels.visible || !Number.isFinite(label.price) || label.text.length === 0) {
|
|
2537
2573
|
return;
|
|
2538
2574
|
}
|
|
2575
|
+
if (getLineY(label.price, yFromPrice, chartTop, chartBottom, label.pinOutOfRange) === null) {
|
|
2576
|
+
return;
|
|
2577
|
+
}
|
|
2539
2578
|
priceAxisLabels.push(label);
|
|
2540
2579
|
};
|
|
2541
|
-
const drawReferenceLine = (price, color, style = "dotted") => {
|
|
2580
|
+
const drawReferenceLine = (price, color, style = "dotted", pinOutOfRange = mergedOptions.pinOutOfRangeLines) => {
|
|
2542
2581
|
if (!Number.isFinite(price)) {
|
|
2543
2582
|
return;
|
|
2544
2583
|
}
|
|
2545
|
-
const y =
|
|
2584
|
+
const y = getLineY(price, yFromPrice, chartTop, chartBottom, pinOutOfRange);
|
|
2585
|
+
if (y === null) {
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2546
2588
|
ctx.save();
|
|
2547
2589
|
ctx.strokeStyle = color;
|
|
2548
2590
|
ctx.lineWidth = 1;
|
|
@@ -2569,22 +2611,23 @@ function createChart(element, options = {}) {
|
|
|
2569
2611
|
let tickerColor = null;
|
|
2570
2612
|
if ((ticker.visible ?? true) && lastPoint) {
|
|
2571
2613
|
tickerPrice = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint.c;
|
|
2572
|
-
const
|
|
2573
|
-
const lineY = clamp(tickerY, chartTop + 1, chartBottom - 1);
|
|
2614
|
+
const lineY = getLineY(tickerPrice, yFromPrice, chartTop, chartBottom);
|
|
2574
2615
|
const lastDirection = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice >= lastPoint.o ? "up" : "down" : getCandleDirectionByIndex(data.length - 1);
|
|
2575
2616
|
tickerColor = ticker.color ?? (lastDirection === "up" ? mergedOptions.upColor : mergedOptions.downColor);
|
|
2576
2617
|
const tickerThickness = Math.max(1, ticker.thickness ?? 1);
|
|
2577
2618
|
const tickerStyle = ticker.style ?? "solid";
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2619
|
+
if (lineY !== null) {
|
|
2620
|
+
ctx.save();
|
|
2621
|
+
ctx.strokeStyle = tickerColor;
|
|
2622
|
+
ctx.lineWidth = tickerThickness;
|
|
2623
|
+
applyDashPattern(tickerStyle, dashPatterns.dotted, dashPatterns.dashed);
|
|
2624
|
+
ctx.beginPath();
|
|
2625
|
+
ctx.moveTo(crisp(chartLeft), crisp(lineY));
|
|
2626
|
+
ctx.lineTo(crisp(chartRight), crisp(lineY));
|
|
2627
|
+
ctx.stroke();
|
|
2628
|
+
ctx.setLineDash([]);
|
|
2629
|
+
ctx.restore();
|
|
2630
|
+
}
|
|
2588
2631
|
}
|
|
2589
2632
|
if ((ticker.visible ?? true) && labels.showLastPrice && tickerPrice !== null && tickerColor !== null) {
|
|
2590
2633
|
const tickerSubtexts = [
|
|
@@ -2694,13 +2737,14 @@ function createChart(element, options = {}) {
|
|
|
2694
2737
|
}
|
|
2695
2738
|
const labelTextWidth = Math.max(primaryWidth, subtextWidth);
|
|
2696
2739
|
const labelWidth = getRightAxisLabelWidth(chartRight, labelTextWidth);
|
|
2740
|
+
const labelLineY = getLineY(label.price, yFromPrice, chartTop, chartBottom, label.pinOutOfRange);
|
|
2697
2741
|
return {
|
|
2698
2742
|
...label,
|
|
2699
2743
|
subtexts,
|
|
2700
2744
|
subtextFontSize,
|
|
2701
2745
|
height: labelHeight,
|
|
2702
2746
|
width: labelWidth,
|
|
2703
|
-
targetY:
|
|
2747
|
+
targetY: (labelLineY ?? yFromPrice(label.price)) - labelHeight / 2,
|
|
2704
2748
|
y: 0
|
|
2705
2749
|
};
|
|
2706
2750
|
}).sort((a, b) => a.targetY - b.targetY || b.priority - a.priority);
|
|
@@ -3498,15 +3542,10 @@ function createChart(element, options = {}) {
|
|
|
3498
3542
|
}
|
|
3499
3543
|
const midpoint = getMidpoint(first, second);
|
|
3500
3544
|
const anchorRatio = clamp((midpoint.x - drawState.chartLeft) / drawState.chartWidth, 0, 1);
|
|
3501
|
-
const startYMin = yMinOverride ?? drawState.yMin;
|
|
3502
|
-
const startYMax = yMaxOverride ?? drawState.yMax;
|
|
3503
3545
|
pinchZoomState = {
|
|
3504
3546
|
startDistance: Math.max(1, getPointerDistance(first, second)),
|
|
3505
3547
|
startSpan: xSpan,
|
|
3506
|
-
anchorIndex: drawState.xStart + anchorRatio * xSpan
|
|
3507
|
-
startMidpoint: midpoint,
|
|
3508
|
-
startYMin,
|
|
3509
|
-
startYMax
|
|
3548
|
+
anchorIndex: drawState.xStart + anchorRatio * xSpan
|
|
3510
3549
|
};
|
|
3511
3550
|
isDragging = false;
|
|
3512
3551
|
dragMode = null;
|
|
@@ -3535,11 +3574,6 @@ function createChart(element, options = {}) {
|
|
|
3535
3574
|
xSpan = nextSpan;
|
|
3536
3575
|
xCenter = nextStart + nextSpan / 2;
|
|
3537
3576
|
clampXViewport();
|
|
3538
|
-
const startYRange = pinchZoomState.startYMax - pinchZoomState.startYMin || 1;
|
|
3539
|
-
const priceShift = (midpoint.y - pinchZoomState.startMidpoint.y) / drawState.chartHeight * startYRange;
|
|
3540
|
-
const clampedY = clampYRange(pinchZoomState.startYMin + priceShift, pinchZoomState.startYMax + priceShift);
|
|
3541
|
-
yMinOverride = clampedY.min;
|
|
3542
|
-
yMaxOverride = clampedY.max;
|
|
3543
3577
|
updateFollowLatest(false);
|
|
3544
3578
|
emitViewportChange();
|
|
3545
3579
|
draw();
|
package/dist/index.cjs
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
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
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
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 =
|
|
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));
|
|
@@ -2560,13 +2596,19 @@ function createChart(element, options = {}) {
|
|
|
2560
2596
|
if (!labels.visible || !Number.isFinite(label.price) || label.text.length === 0) {
|
|
2561
2597
|
return;
|
|
2562
2598
|
}
|
|
2599
|
+
if (getLineY(label.price, yFromPrice, chartTop, chartBottom, label.pinOutOfRange) === null) {
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2563
2602
|
priceAxisLabels.push(label);
|
|
2564
2603
|
};
|
|
2565
|
-
const drawReferenceLine = (price, color, style = "dotted") => {
|
|
2604
|
+
const drawReferenceLine = (price, color, style = "dotted", pinOutOfRange = mergedOptions.pinOutOfRangeLines) => {
|
|
2566
2605
|
if (!Number.isFinite(price)) {
|
|
2567
2606
|
return;
|
|
2568
2607
|
}
|
|
2569
|
-
const y =
|
|
2608
|
+
const y = getLineY(price, yFromPrice, chartTop, chartBottom, pinOutOfRange);
|
|
2609
|
+
if (y === null) {
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2570
2612
|
ctx.save();
|
|
2571
2613
|
ctx.strokeStyle = color;
|
|
2572
2614
|
ctx.lineWidth = 1;
|
|
@@ -2593,22 +2635,23 @@ function createChart(element, options = {}) {
|
|
|
2593
2635
|
let tickerColor = null;
|
|
2594
2636
|
if ((ticker.visible ?? true) && lastPoint) {
|
|
2595
2637
|
tickerPrice = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint.c;
|
|
2596
|
-
const
|
|
2597
|
-
const lineY = clamp(tickerY, chartTop + 1, chartBottom - 1);
|
|
2638
|
+
const lineY = getLineY(tickerPrice, yFromPrice, chartTop, chartBottom);
|
|
2598
2639
|
const lastDirection = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice >= lastPoint.o ? "up" : "down" : getCandleDirectionByIndex(data.length - 1);
|
|
2599
2640
|
tickerColor = ticker.color ?? (lastDirection === "up" ? mergedOptions.upColor : mergedOptions.downColor);
|
|
2600
2641
|
const tickerThickness = Math.max(1, ticker.thickness ?? 1);
|
|
2601
2642
|
const tickerStyle = ticker.style ?? "solid";
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2643
|
+
if (lineY !== null) {
|
|
2644
|
+
ctx.save();
|
|
2645
|
+
ctx.strokeStyle = tickerColor;
|
|
2646
|
+
ctx.lineWidth = tickerThickness;
|
|
2647
|
+
applyDashPattern(tickerStyle, dashPatterns.dotted, dashPatterns.dashed);
|
|
2648
|
+
ctx.beginPath();
|
|
2649
|
+
ctx.moveTo(crisp(chartLeft), crisp(lineY));
|
|
2650
|
+
ctx.lineTo(crisp(chartRight), crisp(lineY));
|
|
2651
|
+
ctx.stroke();
|
|
2652
|
+
ctx.setLineDash([]);
|
|
2653
|
+
ctx.restore();
|
|
2654
|
+
}
|
|
2612
2655
|
}
|
|
2613
2656
|
if ((ticker.visible ?? true) && labels.showLastPrice && tickerPrice !== null && tickerColor !== null) {
|
|
2614
2657
|
const tickerSubtexts = [
|
|
@@ -2718,13 +2761,14 @@ function createChart(element, options = {}) {
|
|
|
2718
2761
|
}
|
|
2719
2762
|
const labelTextWidth = Math.max(primaryWidth, subtextWidth);
|
|
2720
2763
|
const labelWidth = getRightAxisLabelWidth(chartRight, labelTextWidth);
|
|
2764
|
+
const labelLineY = getLineY(label.price, yFromPrice, chartTop, chartBottom, label.pinOutOfRange);
|
|
2721
2765
|
return {
|
|
2722
2766
|
...label,
|
|
2723
2767
|
subtexts,
|
|
2724
2768
|
subtextFontSize,
|
|
2725
2769
|
height: labelHeight,
|
|
2726
2770
|
width: labelWidth,
|
|
2727
|
-
targetY:
|
|
2771
|
+
targetY: (labelLineY ?? yFromPrice(label.price)) - labelHeight / 2,
|
|
2728
2772
|
y: 0
|
|
2729
2773
|
};
|
|
2730
2774
|
}).sort((a, b) => a.targetY - b.targetY || b.priority - a.priority);
|
|
@@ -3522,15 +3566,10 @@ function createChart(element, options = {}) {
|
|
|
3522
3566
|
}
|
|
3523
3567
|
const midpoint = getMidpoint(first, second);
|
|
3524
3568
|
const anchorRatio = clamp((midpoint.x - drawState.chartLeft) / drawState.chartWidth, 0, 1);
|
|
3525
|
-
const startYMin = yMinOverride ?? drawState.yMin;
|
|
3526
|
-
const startYMax = yMaxOverride ?? drawState.yMax;
|
|
3527
3569
|
pinchZoomState = {
|
|
3528
3570
|
startDistance: Math.max(1, getPointerDistance(first, second)),
|
|
3529
3571
|
startSpan: xSpan,
|
|
3530
|
-
anchorIndex: drawState.xStart + anchorRatio * xSpan
|
|
3531
|
-
startMidpoint: midpoint,
|
|
3532
|
-
startYMin,
|
|
3533
|
-
startYMax
|
|
3572
|
+
anchorIndex: drawState.xStart + anchorRatio * xSpan
|
|
3534
3573
|
};
|
|
3535
3574
|
isDragging = false;
|
|
3536
3575
|
dragMode = null;
|
|
@@ -3559,11 +3598,6 @@ function createChart(element, options = {}) {
|
|
|
3559
3598
|
xSpan = nextSpan;
|
|
3560
3599
|
xCenter = nextStart + nextSpan / 2;
|
|
3561
3600
|
clampXViewport();
|
|
3562
|
-
const startYRange = pinchZoomState.startYMax - pinchZoomState.startYMin || 1;
|
|
3563
|
-
const priceShift = (midpoint.y - pinchZoomState.startMidpoint.y) / drawState.chartHeight * startYRange;
|
|
3564
|
-
const clampedY = clampYRange(pinchZoomState.startYMin + priceShift, pinchZoomState.startYMax + priceShift);
|
|
3565
|
-
yMinOverride = clampedY.min;
|
|
3566
|
-
yMaxOverride = clampedY.max;
|
|
3567
3601
|
updateFollowLatest(false);
|
|
3568
3602
|
emitViewportChange();
|
|
3569
3603
|
draw();
|
package/dist/index.d.cts
CHANGED
|
@@ -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;
|
|
@@ -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;
|
package/dist/index.d.ts
CHANGED
|
@@ -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;
|
|
@@ -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;
|
package/dist/index.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
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
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
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 =
|
|
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));
|
|
@@ -2536,13 +2572,19 @@ function createChart(element, options = {}) {
|
|
|
2536
2572
|
if (!labels.visible || !Number.isFinite(label.price) || label.text.length === 0) {
|
|
2537
2573
|
return;
|
|
2538
2574
|
}
|
|
2575
|
+
if (getLineY(label.price, yFromPrice, chartTop, chartBottom, label.pinOutOfRange) === null) {
|
|
2576
|
+
return;
|
|
2577
|
+
}
|
|
2539
2578
|
priceAxisLabels.push(label);
|
|
2540
2579
|
};
|
|
2541
|
-
const drawReferenceLine = (price, color, style = "dotted") => {
|
|
2580
|
+
const drawReferenceLine = (price, color, style = "dotted", pinOutOfRange = mergedOptions.pinOutOfRangeLines) => {
|
|
2542
2581
|
if (!Number.isFinite(price)) {
|
|
2543
2582
|
return;
|
|
2544
2583
|
}
|
|
2545
|
-
const y =
|
|
2584
|
+
const y = getLineY(price, yFromPrice, chartTop, chartBottom, pinOutOfRange);
|
|
2585
|
+
if (y === null) {
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2546
2588
|
ctx.save();
|
|
2547
2589
|
ctx.strokeStyle = color;
|
|
2548
2590
|
ctx.lineWidth = 1;
|
|
@@ -2569,22 +2611,23 @@ function createChart(element, options = {}) {
|
|
|
2569
2611
|
let tickerColor = null;
|
|
2570
2612
|
if ((ticker.visible ?? true) && lastPoint) {
|
|
2571
2613
|
tickerPrice = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint.c;
|
|
2572
|
-
const
|
|
2573
|
-
const lineY = clamp(tickerY, chartTop + 1, chartBottom - 1);
|
|
2614
|
+
const lineY = getLineY(tickerPrice, yFromPrice, chartTop, chartBottom);
|
|
2574
2615
|
const lastDirection = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice >= lastPoint.o ? "up" : "down" : getCandleDirectionByIndex(data.length - 1);
|
|
2575
2616
|
tickerColor = ticker.color ?? (lastDirection === "up" ? mergedOptions.upColor : mergedOptions.downColor);
|
|
2576
2617
|
const tickerThickness = Math.max(1, ticker.thickness ?? 1);
|
|
2577
2618
|
const tickerStyle = ticker.style ?? "solid";
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2619
|
+
if (lineY !== null) {
|
|
2620
|
+
ctx.save();
|
|
2621
|
+
ctx.strokeStyle = tickerColor;
|
|
2622
|
+
ctx.lineWidth = tickerThickness;
|
|
2623
|
+
applyDashPattern(tickerStyle, dashPatterns.dotted, dashPatterns.dashed);
|
|
2624
|
+
ctx.beginPath();
|
|
2625
|
+
ctx.moveTo(crisp(chartLeft), crisp(lineY));
|
|
2626
|
+
ctx.lineTo(crisp(chartRight), crisp(lineY));
|
|
2627
|
+
ctx.stroke();
|
|
2628
|
+
ctx.setLineDash([]);
|
|
2629
|
+
ctx.restore();
|
|
2630
|
+
}
|
|
2588
2631
|
}
|
|
2589
2632
|
if ((ticker.visible ?? true) && labels.showLastPrice && tickerPrice !== null && tickerColor !== null) {
|
|
2590
2633
|
const tickerSubtexts = [
|
|
@@ -2694,13 +2737,14 @@ function createChart(element, options = {}) {
|
|
|
2694
2737
|
}
|
|
2695
2738
|
const labelTextWidth = Math.max(primaryWidth, subtextWidth);
|
|
2696
2739
|
const labelWidth = getRightAxisLabelWidth(chartRight, labelTextWidth);
|
|
2740
|
+
const labelLineY = getLineY(label.price, yFromPrice, chartTop, chartBottom, label.pinOutOfRange);
|
|
2697
2741
|
return {
|
|
2698
2742
|
...label,
|
|
2699
2743
|
subtexts,
|
|
2700
2744
|
subtextFontSize,
|
|
2701
2745
|
height: labelHeight,
|
|
2702
2746
|
width: labelWidth,
|
|
2703
|
-
targetY:
|
|
2747
|
+
targetY: (labelLineY ?? yFromPrice(label.price)) - labelHeight / 2,
|
|
2704
2748
|
y: 0
|
|
2705
2749
|
};
|
|
2706
2750
|
}).sort((a, b) => a.targetY - b.targetY || b.priority - a.priority);
|
|
@@ -3498,15 +3542,10 @@ function createChart(element, options = {}) {
|
|
|
3498
3542
|
}
|
|
3499
3543
|
const midpoint = getMidpoint(first, second);
|
|
3500
3544
|
const anchorRatio = clamp((midpoint.x - drawState.chartLeft) / drawState.chartWidth, 0, 1);
|
|
3501
|
-
const startYMin = yMinOverride ?? drawState.yMin;
|
|
3502
|
-
const startYMax = yMaxOverride ?? drawState.yMax;
|
|
3503
3545
|
pinchZoomState = {
|
|
3504
3546
|
startDistance: Math.max(1, getPointerDistance(first, second)),
|
|
3505
3547
|
startSpan: xSpan,
|
|
3506
|
-
anchorIndex: drawState.xStart + anchorRatio * xSpan
|
|
3507
|
-
startMidpoint: midpoint,
|
|
3508
|
-
startYMin,
|
|
3509
|
-
startYMax
|
|
3548
|
+
anchorIndex: drawState.xStart + anchorRatio * xSpan
|
|
3510
3549
|
};
|
|
3511
3550
|
isDragging = false;
|
|
3512
3551
|
dragMode = null;
|
|
@@ -3535,11 +3574,6 @@ function createChart(element, options = {}) {
|
|
|
3535
3574
|
xSpan = nextSpan;
|
|
3536
3575
|
xCenter = nextStart + nextSpan / 2;
|
|
3537
3576
|
clampXViewport();
|
|
3538
|
-
const startYRange = pinchZoomState.startYMax - pinchZoomState.startYMin || 1;
|
|
3539
|
-
const priceShift = (midpoint.y - pinchZoomState.startMidpoint.y) / drawState.chartHeight * startYRange;
|
|
3540
|
-
const clampedY = clampYRange(pinchZoomState.startYMin + priceShift, pinchZoomState.startYMax + priceShift);
|
|
3541
|
-
yMinOverride = clampedY.min;
|
|
3542
|
-
yMaxOverride = clampedY.max;
|
|
3543
3577
|
updateFollowLatest(false);
|
|
3544
3578
|
emitViewportChange();
|
|
3545
3579
|
draw();
|
package/docs/API.md
CHANGED
|
@@ -57,6 +57,7 @@ Top-level options:
|
|
|
57
57
|
- `candleColorEpsilon` (default `-1` = auto from `priceDecimals`; set `0` to disable tolerance)
|
|
58
58
|
- `autoScaleSmoothing` (default `0.16`)
|
|
59
59
|
- `autoScaleIgnoreLatestCandle` (default `true`)
|
|
60
|
+
- `pinOutOfRangeLines` (default `false`; when `true`, horizontal price/order/reference lines outside the visible price range are pinned to the top/bottom chart edge)
|
|
60
61
|
- `doubleClickEnabled` (default `true`)
|
|
61
62
|
- `doubleClickAction` (`"reset"` | `"placeLimitOrder"`, default `"reset"`)
|
|
62
63
|
- `crosshair?: CrosshairOptions`
|
|
@@ -226,6 +227,7 @@ createChart(root, {
|
|
|
226
227
|
- `labelTextColor` (default `#0f172a`)
|
|
227
228
|
- `labelBorderRadius` (default `3`)
|
|
228
229
|
- `showLabel` (default `true`)
|
|
230
|
+
- `pinOutOfRange?: boolean` (default `false`; overrides `ChartOptions.pinOutOfRangeLines` for this line)
|
|
229
231
|
|
|
230
232
|
### `OrderActionButton`
|
|
231
233
|
|
|
@@ -269,6 +271,7 @@ Common optional fields:
|
|
|
269
271
|
- `widgetPosition?: "left" | "center" | "right"` (default `"left"`)
|
|
270
272
|
- `widgetPaddingRight?: number` (default `10`, extra right margin when `widgetPosition` is `"right"`)
|
|
271
273
|
- `draggable?: boolean` (default `false`)
|
|
274
|
+
- `pinOutOfRange?: boolean` (default `false`; overrides `ChartOptions.pinOutOfRangeLines` for this order/position line)
|
|
272
275
|
|
|
273
276
|
Legacy single action button:
|
|
274
277
|
|
package/docs/RECIPES.md
CHANGED
|
@@ -85,6 +85,22 @@ chart.addPriceLine({
|
|
|
85
85
|
});
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
## Pin out-of-range lines to the chart edge
|
|
89
|
+
|
|
90
|
+
By default, horizontal price/order/drawing lines outside the visible price range are hidden instead of being pinned to the top or bottom edge. Opt in globally or per line when you want marker-style behavior for important levels.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
const chart = createChart(rootEl, {
|
|
94
|
+
pinOutOfRangeLines: true
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
chart.addPriceLine({
|
|
98
|
+
price: 6400,
|
|
99
|
+
label: "Liquidation",
|
|
100
|
+
pinOutOfRange: true
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
88
104
|
## Add chart drawing tools
|
|
89
105
|
|
|
90
106
|
Drawing tools are separate from indicators. Indicators compute/render data series; drawings are user-created objects that can be persisted.
|