@pipsend/charts 2.3.12 → 2.3.14
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/pipsend-charts.development.mjs +414 -112
- package/dist/pipsend-charts.production.mjs +2 -2
- package/dist/pipsend-charts.standalone.development.js +414 -112
- package/dist/pipsend-charts.standalone.development.mjs +414 -112
- package/dist/pipsend-charts.standalone.production.js +2 -2
- package/dist/pipsend-charts.standalone.production.mjs +2 -2
- package/dist/typings.d.ts +60 -6
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* @license
|
|
3
|
-
* Pipsend Charts v2.3.
|
|
3
|
+
* Pipsend Charts v2.3.14
|
|
4
4
|
* Copyright (c) 2026 Pipsend
|
|
5
5
|
* Licensed under MIT License
|
|
6
6
|
* Built on TradingView Lightweight Charts™ (Apache 2.0)
|
|
@@ -14281,6 +14281,8 @@ function shapeSize(shape, originalSize) {
|
|
|
14281
14281
|
return size(originalSize, 0.7);
|
|
14282
14282
|
case 'tag':
|
|
14283
14283
|
return size(originalSize, 1);
|
|
14284
|
+
case 'xcross':
|
|
14285
|
+
return size(originalSize, 0.7);
|
|
14284
14286
|
}
|
|
14285
14287
|
}
|
|
14286
14288
|
function calculateShapeHeight(barSpacing) {
|
|
@@ -14481,6 +14483,27 @@ function hitTestText(textX, textY, textWidth, textHeight, x, y) {
|
|
|
14481
14483
|
y >= textY - halfHeight && y <= textY + halfHeight;
|
|
14482
14484
|
}
|
|
14483
14485
|
|
|
14486
|
+
function drawXCross(ctx, coords, size) {
|
|
14487
|
+
const crossSize = shapeSize('xcross', size);
|
|
14488
|
+
const halfSize = ((crossSize - 1) / 2) * coords._internal_pixelRatio;
|
|
14489
|
+
ctx.save();
|
|
14490
|
+
ctx.strokeStyle = ctx.fillStyle;
|
|
14491
|
+
ctx.lineWidth = Math.max(1, Math.round(1.5 * coords._internal_pixelRatio));
|
|
14492
|
+
ctx.lineCap = 'round';
|
|
14493
|
+
ctx.beginPath();
|
|
14494
|
+
ctx.moveTo(coords._internal_x - halfSize, coords._internal_y - halfSize);
|
|
14495
|
+
ctx.lineTo(coords._internal_x + halfSize, coords._internal_y + halfSize);
|
|
14496
|
+
ctx.moveTo(coords._internal_x + halfSize, coords._internal_y - halfSize);
|
|
14497
|
+
ctx.lineTo(coords._internal_x - halfSize, coords._internal_y + halfSize);
|
|
14498
|
+
ctx.stroke();
|
|
14499
|
+
ctx.restore();
|
|
14500
|
+
}
|
|
14501
|
+
function hitTestXCross(centerX, centerY, size, x, y) {
|
|
14502
|
+
const crossSize = shapeSize('xcross', size);
|
|
14503
|
+
const tolerance = 2 + crossSize / 2;
|
|
14504
|
+
return Math.abs(x - centerX) <= tolerance && Math.abs(y - centerY) <= tolerance;
|
|
14505
|
+
}
|
|
14506
|
+
|
|
14484
14507
|
class SeriesMarkersRenderer {
|
|
14485
14508
|
constructor() {
|
|
14486
14509
|
this._private__data = null;
|
|
@@ -14624,6 +14647,9 @@ function drawShape(item, ctx, coordinates) {
|
|
|
14624
14647
|
case 'square':
|
|
14625
14648
|
drawSquare(ctx, coordinates, item._internal_size);
|
|
14626
14649
|
return;
|
|
14650
|
+
case 'xcross':
|
|
14651
|
+
drawXCross(ctx, coordinates, item._internal_size);
|
|
14652
|
+
return;
|
|
14627
14653
|
case 'tag':
|
|
14628
14654
|
// Handled by drawTagItem, which needs both pixel ratios and the text.
|
|
14629
14655
|
return;
|
|
@@ -14652,6 +14678,8 @@ function hitTestShape(item, x, y) {
|
|
|
14652
14678
|
return hitTestCircle(item._internal_x, item._internal_y, item._internal_size, x, y);
|
|
14653
14679
|
case 'square':
|
|
14654
14680
|
return hitTestSquare(item._internal_x, item._internal_y, item._internal_size, x, y);
|
|
14681
|
+
case 'xcross':
|
|
14682
|
+
return hitTestXCross(item._internal_x, item._internal_y, item._internal_size, x, y);
|
|
14655
14683
|
case 'tag':
|
|
14656
14684
|
return false;
|
|
14657
14685
|
}
|
|
@@ -21220,6 +21248,13 @@ class DrawingToolBase {
|
|
|
21220
21248
|
series() {
|
|
21221
21249
|
return this._series;
|
|
21222
21250
|
}
|
|
21251
|
+
/**
|
|
21252
|
+
* Box the selection toolbar anchors to, in pane pixels. Null falls back to
|
|
21253
|
+
* the serialized points, which cannot place a line across the whole pane.
|
|
21254
|
+
*/
|
|
21255
|
+
selectionBox() {
|
|
21256
|
+
return null;
|
|
21257
|
+
}
|
|
21223
21258
|
getState() {
|
|
21224
21259
|
return this._state;
|
|
21225
21260
|
}
|
|
@@ -21896,6 +21931,53 @@ function createTrendLineTool(context, options) {
|
|
|
21896
21931
|
return new TrendLineTool(context, options);
|
|
21897
21932
|
}
|
|
21898
21933
|
|
|
21934
|
+
const END_HANDLE_MARGIN = 10;
|
|
21935
|
+
/**
|
|
21936
|
+
* Hands the next `clicks` clicks on the chart to `onClick`, past the last bar
|
|
21937
|
+
* too. A press that grabbed another drawing does not count.
|
|
21938
|
+
*/
|
|
21939
|
+
function placeByClicks(chart, series, clicks, onClick) {
|
|
21940
|
+
let placed = 0;
|
|
21941
|
+
let handler = null;
|
|
21942
|
+
const stop = () => {
|
|
21943
|
+
if (handler !== null) {
|
|
21944
|
+
chart.unsubscribeClick(handler);
|
|
21945
|
+
handler = null;
|
|
21946
|
+
}
|
|
21947
|
+
};
|
|
21948
|
+
handler = (param) => {
|
|
21949
|
+
if (!param.point || consumeInputClaim(chart)) {
|
|
21950
|
+
return;
|
|
21951
|
+
}
|
|
21952
|
+
const price = series.coordinateToPrice(param.point.y);
|
|
21953
|
+
const time = timeFromCoordinate(chart, series, param.point.x);
|
|
21954
|
+
if (price === null || time === null) {
|
|
21955
|
+
return;
|
|
21956
|
+
}
|
|
21957
|
+
placed++;
|
|
21958
|
+
if (placed >= clicks) {
|
|
21959
|
+
stop();
|
|
21960
|
+
}
|
|
21961
|
+
onClick({ _internal_time: time, _internal_price: price, _internal_x: param.point.x, _internal_y: param.point.y }, placed - 1);
|
|
21962
|
+
};
|
|
21963
|
+
chart.subscribeClick(handler);
|
|
21964
|
+
return {
|
|
21965
|
+
_internal_pending: () => handler !== null,
|
|
21966
|
+
_internal_stop: stop,
|
|
21967
|
+
};
|
|
21968
|
+
}
|
|
21969
|
+
/** A fifth of the pane, and never less than a bar. */
|
|
21970
|
+
function defaultWidth(chart) {
|
|
21971
|
+
const timeScale = chart.timeScale();
|
|
21972
|
+
return Math.max(timeScale.width() / 5, timeScale.options().barSpacing);
|
|
21973
|
+
}
|
|
21974
|
+
/** Kept inside the pane: past it, the handles on the end hide under the price axis. */
|
|
21975
|
+
function placedEndX(chart, x) {
|
|
21976
|
+
const timeScale = chart.timeScale();
|
|
21977
|
+
const onScreen = Math.min(x + defaultWidth(chart), timeScale.width() - END_HANDLE_MARGIN);
|
|
21978
|
+
return Math.max(onScreen, x + timeScale.options().barSpacing);
|
|
21979
|
+
}
|
|
21980
|
+
|
|
21899
21981
|
class HorizontalLinePaneRenderer {
|
|
21900
21982
|
constructor(tool) {
|
|
21901
21983
|
this._private__tool = tool;
|
|
@@ -21936,6 +22018,7 @@ class HorizontalLineTool extends DrawingToolBase {
|
|
|
21936
22018
|
this._private__isDragging = false;
|
|
21937
22019
|
this._private__grab = null;
|
|
21938
22020
|
this._private__moveStartPrice = 0;
|
|
22021
|
+
this._private__placement = null;
|
|
21939
22022
|
this._private__hLineOptions = {
|
|
21940
22023
|
lineColor: '#2962ff',
|
|
21941
22024
|
lineWidth: 2,
|
|
@@ -21948,6 +22031,26 @@ class HorizontalLineTool extends DrawingToolBase {
|
|
|
21948
22031
|
};
|
|
21949
22032
|
this._registerOptions(this._private__hLineOptions);
|
|
21950
22033
|
this._private__createPrimitive();
|
|
22034
|
+
if (this._private__hLineOptions.interactive) {
|
|
22035
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
|
|
22036
|
+
}
|
|
22037
|
+
}
|
|
22038
|
+
_private__placeAt(point) {
|
|
22039
|
+
this._private__hLineOptions.price = point._internal_price;
|
|
22040
|
+
if (this._private__hLineOptions.startTime !== undefined) {
|
|
22041
|
+
this._private__hLineOptions.startTime = point._internal_time;
|
|
22042
|
+
}
|
|
22043
|
+
this._private__requestUpdate();
|
|
22044
|
+
}
|
|
22045
|
+
isComplete() {
|
|
22046
|
+
return !this._private__placement?._internal_pending();
|
|
22047
|
+
}
|
|
22048
|
+
selectionBox() {
|
|
22049
|
+
const y = this._private__hLineOptions.price === undefined ? null : this._series.priceToCoordinate(this._private__hLineOptions.price);
|
|
22050
|
+
if (y === null) {
|
|
22051
|
+
return null;
|
|
22052
|
+
}
|
|
22053
|
+
return { left: this._private__startX(), right: this._chart.timeScale().width(), top: y, bottom: y };
|
|
21951
22054
|
}
|
|
21952
22055
|
_private__createPrimitive() {
|
|
21953
22056
|
this._private__paneView = this._private__createPaneView();
|
|
@@ -21985,7 +22088,7 @@ class HorizontalLineTool extends DrawingToolBase {
|
|
|
21985
22088
|
return coordinateFromTime(this._chart, this._series, this._private__hLineOptions.startTime) ?? 0;
|
|
21986
22089
|
}
|
|
21987
22090
|
drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
21988
|
-
if (this._private__hLineOptions.price === undefined)
|
|
22091
|
+
if (!this.isComplete() || this._private__hLineOptions.price === undefined)
|
|
21989
22092
|
return;
|
|
21990
22093
|
const y = (this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0) * verticalPixelRatio;
|
|
21991
22094
|
const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
|
|
@@ -22079,7 +22182,7 @@ class HorizontalLineTool extends DrawingToolBase {
|
|
|
22079
22182
|
return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
|
|
22080
22183
|
}
|
|
22081
22184
|
hitTest(x, y) {
|
|
22082
|
-
if (this._private__hLineOptions.price === undefined) {
|
|
22185
|
+
if (!this.isComplete() || this._private__hLineOptions.price === undefined) {
|
|
22083
22186
|
return false;
|
|
22084
22187
|
}
|
|
22085
22188
|
const lineY = this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0;
|
|
@@ -22154,6 +22257,7 @@ class HorizontalLineTool extends DrawingToolBase {
|
|
|
22154
22257
|
this._private__requestUpdate();
|
|
22155
22258
|
}
|
|
22156
22259
|
remove() {
|
|
22260
|
+
this._private__placement?._internal_stop();
|
|
22157
22261
|
if (this._private__primitive) {
|
|
22158
22262
|
this._series.detachPrimitive(this._private__primitive);
|
|
22159
22263
|
this._private__primitive = null;
|
|
@@ -22213,6 +22317,7 @@ class CrossLineTool extends DrawingToolBase {
|
|
|
22213
22317
|
this._private__grab = null;
|
|
22214
22318
|
this._private__moveStartTime = 0;
|
|
22215
22319
|
this._private__moveStartPrice = 0;
|
|
22320
|
+
this._private__placement = null;
|
|
22216
22321
|
this._private__crossOptions = {
|
|
22217
22322
|
lineColor: '#787b86',
|
|
22218
22323
|
lineWidth: 1,
|
|
@@ -22222,6 +22327,16 @@ class CrossLineTool extends DrawingToolBase {
|
|
|
22222
22327
|
};
|
|
22223
22328
|
this._registerOptions(this._private__crossOptions);
|
|
22224
22329
|
this._private__createPrimitive();
|
|
22330
|
+
if (this._private__crossOptions.interactive) {
|
|
22331
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
|
|
22332
|
+
this._private__crossOptions.time = point._internal_time;
|
|
22333
|
+
this._private__crossOptions.price = point._internal_price;
|
|
22334
|
+
this._private__requestUpdate();
|
|
22335
|
+
});
|
|
22336
|
+
}
|
|
22337
|
+
}
|
|
22338
|
+
isComplete() {
|
|
22339
|
+
return !this._private__placement?._internal_pending();
|
|
22225
22340
|
}
|
|
22226
22341
|
_private__createPrimitive() {
|
|
22227
22342
|
this._private__paneView = new CrossLinePaneView(this);
|
|
@@ -22249,7 +22364,7 @@ class CrossLineTool extends DrawingToolBase {
|
|
|
22249
22364
|
this._private__requestUpdate();
|
|
22250
22365
|
}
|
|
22251
22366
|
_private__coordinates() {
|
|
22252
|
-
if (this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
|
|
22367
|
+
if (!this.isComplete() || this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
|
|
22253
22368
|
return null;
|
|
22254
22369
|
}
|
|
22255
22370
|
const x = coordinateFromTime(this._chart, this._series, this._private__crossOptions.time);
|
|
@@ -22372,6 +22487,7 @@ class CrossLineTool extends DrawingToolBase {
|
|
|
22372
22487
|
return this._private__isHovered;
|
|
22373
22488
|
}
|
|
22374
22489
|
remove() {
|
|
22490
|
+
this._private__placement?._internal_stop();
|
|
22375
22491
|
if (this._private__primitive) {
|
|
22376
22492
|
try {
|
|
22377
22493
|
this._series.detachPrimitive(this._private__primitive);
|
|
@@ -23794,6 +23910,7 @@ class VerticalLineTool extends DrawingToolBase {
|
|
|
23794
23910
|
this._private__isDragging = false;
|
|
23795
23911
|
this._private__grab = null;
|
|
23796
23912
|
this._private__moveStartTime = 0;
|
|
23913
|
+
this._private__placement = null;
|
|
23797
23914
|
this._private__vLineOptions = {
|
|
23798
23915
|
lineColor: '#787b86',
|
|
23799
23916
|
lineWidth: 1,
|
|
@@ -23805,6 +23922,29 @@ class VerticalLineTool extends DrawingToolBase {
|
|
|
23805
23922
|
};
|
|
23806
23923
|
this._registerOptions(this._private__vLineOptions);
|
|
23807
23924
|
this._private__createPrimitive();
|
|
23925
|
+
if (this._private__vLineOptions.interactive) {
|
|
23926
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
|
|
23927
|
+
this._private__vLineOptions.time = point._internal_time;
|
|
23928
|
+
this._private__requestUpdate();
|
|
23929
|
+
});
|
|
23930
|
+
}
|
|
23931
|
+
}
|
|
23932
|
+
isComplete() {
|
|
23933
|
+
return !this._private__placement?._internal_pending();
|
|
23934
|
+
}
|
|
23935
|
+
selectionBox() {
|
|
23936
|
+
const x = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time);
|
|
23937
|
+
if (x === null) {
|
|
23938
|
+
return null;
|
|
23939
|
+
}
|
|
23940
|
+
let middle;
|
|
23941
|
+
try {
|
|
23942
|
+
middle = this._series.getPane().getHeight() / 2;
|
|
23943
|
+
}
|
|
23944
|
+
catch (error) {
|
|
23945
|
+
return null;
|
|
23946
|
+
}
|
|
23947
|
+
return { left: x, right: x, top: middle, bottom: middle };
|
|
23808
23948
|
}
|
|
23809
23949
|
_private__createPrimitive() {
|
|
23810
23950
|
this._private__paneView = this._private__createPaneView();
|
|
@@ -23835,7 +23975,7 @@ class VerticalLineTool extends DrawingToolBase {
|
|
|
23835
23975
|
return new VerticalLinePaneView(this);
|
|
23836
23976
|
}
|
|
23837
23977
|
drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
23838
|
-
if (this._private__vLineOptions.time === undefined)
|
|
23978
|
+
if (!this.isComplete() || this._private__vLineOptions.time === undefined)
|
|
23839
23979
|
return;
|
|
23840
23980
|
const x = (coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0) * horizontalPixelRatio;
|
|
23841
23981
|
const chartHeight = this._chart.chartElement().clientHeight * verticalPixelRatio;
|
|
@@ -23922,7 +24062,7 @@ class VerticalLineTool extends DrawingToolBase {
|
|
|
23922
24062
|
return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
|
|
23923
24063
|
}
|
|
23924
24064
|
hitTest(x, y) {
|
|
23925
|
-
if (this._private__vLineOptions.time === undefined) {
|
|
24065
|
+
if (!this.isComplete() || this._private__vLineOptions.time === undefined) {
|
|
23926
24066
|
return false;
|
|
23927
24067
|
}
|
|
23928
24068
|
const lineX = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0;
|
|
@@ -23992,6 +24132,7 @@ class VerticalLineTool extends DrawingToolBase {
|
|
|
23992
24132
|
this._private__requestUpdate();
|
|
23993
24133
|
}
|
|
23994
24134
|
remove() {
|
|
24135
|
+
this._private__placement?._internal_stop();
|
|
23995
24136
|
if (this._private__primitive) {
|
|
23996
24137
|
this._series.detachPrimitive(this._private__primitive);
|
|
23997
24138
|
this._private__primitive = null;
|
|
@@ -24713,6 +24854,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
|
|
|
24713
24854
|
this._private__isHovered = false;
|
|
24714
24855
|
this._private__isDragging = false;
|
|
24715
24856
|
this._private__dragTarget = null;
|
|
24857
|
+
this._private__placement = null;
|
|
24716
24858
|
this._private__fibOptions = {
|
|
24717
24859
|
levels: [
|
|
24718
24860
|
{ level: 0, color: '#787B86', label: '0%' },
|
|
@@ -24728,6 +24870,20 @@ class FibonacciExtensionTool extends DrawingToolBase {
|
|
|
24728
24870
|
...options,
|
|
24729
24871
|
};
|
|
24730
24872
|
this._private__createPrimitive();
|
|
24873
|
+
if (this._private__fibOptions.interactive) {
|
|
24874
|
+
this._private__placement = placeByClicks(this._chart, this._series, 3, (point, index) => {
|
|
24875
|
+
const placed = { time: point._internal_time, price: point._internal_price };
|
|
24876
|
+
if (index === 0) {
|
|
24877
|
+
this.setPoint1(placed);
|
|
24878
|
+
}
|
|
24879
|
+
else if (index === 1) {
|
|
24880
|
+
this.setPoint2(placed);
|
|
24881
|
+
}
|
|
24882
|
+
else {
|
|
24883
|
+
this.setPoint3(placed);
|
|
24884
|
+
}
|
|
24885
|
+
});
|
|
24886
|
+
}
|
|
24731
24887
|
}
|
|
24732
24888
|
_private__createPrimitive() {
|
|
24733
24889
|
this._private__paneView = this._private__createPaneView();
|
|
@@ -24785,7 +24941,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
|
|
|
24785
24941
|
this._private__requestUpdate();
|
|
24786
24942
|
}
|
|
24787
24943
|
isComplete() {
|
|
24788
|
-
return !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
|
|
24944
|
+
return !this._private__placement?._internal_pending() && !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
|
|
24789
24945
|
}
|
|
24790
24946
|
setColor(color) {
|
|
24791
24947
|
this._private__fibOptions.lineColor = color;
|
|
@@ -25080,6 +25236,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
|
|
|
25080
25236
|
this._private__requestUpdate();
|
|
25081
25237
|
}
|
|
25082
25238
|
remove() {
|
|
25239
|
+
this._private__placement?._internal_stop();
|
|
25083
25240
|
if (this._private__primitive) {
|
|
25084
25241
|
this._series.detachPrimitive(this._private__primitive);
|
|
25085
25242
|
this._private__primitive = null;
|
|
@@ -25731,17 +25888,19 @@ class RectangleTool extends DrawingToolBase {
|
|
|
25731
25888
|
// Draw border
|
|
25732
25889
|
const borderColor = this._private__rectOptions.borderColor;
|
|
25733
25890
|
const borderWidth = this._private__isSelected ? this._private__rectOptions.borderWidth + 1 : this._private__rectOptions.borderWidth;
|
|
25734
|
-
|
|
25735
|
-
|
|
25736
|
-
|
|
25737
|
-
|
|
25738
|
-
|
|
25739
|
-
|
|
25740
|
-
|
|
25741
|
-
|
|
25891
|
+
if (borderWidth > 0) {
|
|
25892
|
+
ctx.strokeStyle = borderColor;
|
|
25893
|
+
ctx.lineWidth = borderWidth * horizontalPixelRatio;
|
|
25894
|
+
// Set border style
|
|
25895
|
+
if (this._private__rectOptions.borderStyle === 'dashed') {
|
|
25896
|
+
ctx.setLineDash([10 * horizontalPixelRatio, 5 * horizontalPixelRatio]);
|
|
25897
|
+
}
|
|
25898
|
+
else if (this._private__rectOptions.borderStyle === 'dotted') {
|
|
25899
|
+
ctx.setLineDash([2 * horizontalPixelRatio, 3 * horizontalPixelRatio]);
|
|
25900
|
+
}
|
|
25901
|
+
ctx.strokeRect(minX, minY, width, height);
|
|
25902
|
+
ctx.setLineDash([]);
|
|
25742
25903
|
}
|
|
25743
|
-
ctx.strokeRect(minX, minY, width, height);
|
|
25744
|
-
ctx.setLineDash([]);
|
|
25745
25904
|
// Draw corner handles if selected
|
|
25746
25905
|
if ((this._private__isSelected || this._private__isHovered) && !this._private__isDrawing && !this.isLocked()) {
|
|
25747
25906
|
this._private__drawCornerHandles(ctx, minX, minY, maxX, maxY, horizontalPixelRatio, borderColor);
|
|
@@ -25769,17 +25928,20 @@ class RectangleTool extends DrawingToolBase {
|
|
|
25769
25928
|
}
|
|
25770
25929
|
_private__drawLabel(ctx, minX, minY, maxX, maxY, horizontalPixelRatio, verticalPixelRatio, color) {
|
|
25771
25930
|
const padding = 6 * horizontalPixelRatio;
|
|
25772
|
-
const fontSize = 12 * horizontalPixelRatio;
|
|
25931
|
+
const fontSize = (this._private__rectOptions.labelFontSize ?? 12) * horizontalPixelRatio;
|
|
25773
25932
|
ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
|
|
25774
25933
|
const textMetrics = ctx.measureText(this._private__rectOptions.labelText);
|
|
25775
25934
|
const textWidth = textMetrics.width;
|
|
25776
25935
|
const textHeight = fontSize;
|
|
25936
|
+
const middleY = (minY + maxY) / 2 - textHeight / 2 - padding;
|
|
25937
|
+
// A box extended far past the pane keeps right-hand and centred labels on screen.
|
|
25938
|
+
const visibleMaxX = Math.min(maxX, ctx.canvas.width);
|
|
25777
25939
|
// Calculate label position based on labelPosition option
|
|
25778
25940
|
let labelX = minX + padding;
|
|
25779
25941
|
let labelY = minY + padding;
|
|
25780
25942
|
switch (this._private__rectOptions.labelPosition) {
|
|
25781
25943
|
case 'top-right':
|
|
25782
|
-
labelX =
|
|
25944
|
+
labelX = visibleMaxX - textWidth - padding * 2;
|
|
25783
25945
|
labelY = minY + padding;
|
|
25784
25946
|
break;
|
|
25785
25947
|
case 'bottom-left':
|
|
@@ -25787,19 +25949,27 @@ class RectangleTool extends DrawingToolBase {
|
|
|
25787
25949
|
labelY = maxY - textHeight - padding * 2;
|
|
25788
25950
|
break;
|
|
25789
25951
|
case 'bottom-right':
|
|
25790
|
-
labelX =
|
|
25952
|
+
labelX = visibleMaxX - textWidth - padding * 2;
|
|
25791
25953
|
labelY = maxY - textHeight - padding * 2;
|
|
25792
25954
|
break;
|
|
25793
25955
|
case 'center':
|
|
25794
|
-
labelX = (minX +
|
|
25795
|
-
labelY =
|
|
25956
|
+
labelX = (Math.max(minX, 0) + visibleMaxX) / 2 - textWidth / 2 - padding;
|
|
25957
|
+
labelY = middleY;
|
|
25958
|
+
break;
|
|
25959
|
+
case 'left':
|
|
25960
|
+
labelX = minX + padding;
|
|
25961
|
+
labelY = middleY;
|
|
25962
|
+
break;
|
|
25963
|
+
case 'right':
|
|
25964
|
+
labelX = visibleMaxX - textWidth - padding * 2;
|
|
25965
|
+
labelY = middleY;
|
|
25796
25966
|
break;
|
|
25797
25967
|
}
|
|
25798
25968
|
// Draw background
|
|
25799
|
-
ctx.fillStyle = color;
|
|
25969
|
+
ctx.fillStyle = this._private__rectOptions.labelBackgroundColor ?? color;
|
|
25800
25970
|
ctx.fillRect(labelX, labelY, textWidth + padding * 2, textHeight + padding * 2);
|
|
25801
25971
|
// Draw text
|
|
25802
|
-
ctx.fillStyle = '#ffffff';
|
|
25972
|
+
ctx.fillStyle = this._private__rectOptions.labelColor ?? '#ffffff';
|
|
25803
25973
|
ctx.textBaseline = 'top';
|
|
25804
25974
|
ctx.fillText(this._private__rectOptions.labelText, labelX + padding, labelY + padding);
|
|
25805
25975
|
}
|
|
@@ -26037,6 +26207,9 @@ class RectangleTool extends DrawingToolBase {
|
|
|
26037
26207
|
showLabel: this._private__rectOptions.showLabel,
|
|
26038
26208
|
labelText: this._private__rectOptions.labelText,
|
|
26039
26209
|
labelPosition: this._private__rectOptions.labelPosition,
|
|
26210
|
+
labelColor: this._private__rectOptions.labelColor,
|
|
26211
|
+
labelBackgroundColor: this._private__rectOptions.labelBackgroundColor,
|
|
26212
|
+
labelFontSize: this._private__rectOptions.labelFontSize,
|
|
26040
26213
|
},
|
|
26041
26214
|
};
|
|
26042
26215
|
}
|
|
@@ -27629,6 +27802,7 @@ class TextTool extends DrawingToolBase {
|
|
|
27629
27802
|
this._private__moveStart = null;
|
|
27630
27803
|
this._private__isEditing = false;
|
|
27631
27804
|
this._private__clickOutsideHandler = null;
|
|
27805
|
+
this._private__placement = null;
|
|
27632
27806
|
this._private__textOptions = {
|
|
27633
27807
|
text: 'Double-click to edit',
|
|
27634
27808
|
fontSize: 14,
|
|
@@ -27641,11 +27815,22 @@ class TextTool extends DrawingToolBase {
|
|
|
27641
27815
|
borderWidth: 1,
|
|
27642
27816
|
textAlign: 'left',
|
|
27643
27817
|
maxWidth: 200,
|
|
27818
|
+
anchor: 'top-left',
|
|
27819
|
+
pointer: false,
|
|
27820
|
+
borderRadius: 0,
|
|
27644
27821
|
...options,
|
|
27645
27822
|
};
|
|
27646
27823
|
this._registerOptions(this._private__textOptions);
|
|
27647
27824
|
this._private__createPrimitive();
|
|
27648
27825
|
this._private__setupClickOutsideHandler();
|
|
27826
|
+
if (this._private__textOptions.interactive) {
|
|
27827
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
|
|
27828
|
+
this.setPosition(point._internal_time, point._internal_price);
|
|
27829
|
+
if (this._private__textOptions.onComplete) {
|
|
27830
|
+
this._private__textOptions.onComplete();
|
|
27831
|
+
}
|
|
27832
|
+
});
|
|
27833
|
+
}
|
|
27649
27834
|
}
|
|
27650
27835
|
_private__setupClickOutsideHandler() {
|
|
27651
27836
|
// Set up click handler to finish editing when clicking outside
|
|
@@ -27731,7 +27916,7 @@ class TextTool extends DrawingToolBase {
|
|
|
27731
27916
|
this._private__requestUpdate();
|
|
27732
27917
|
}
|
|
27733
27918
|
isComplete() {
|
|
27734
|
-
return !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
|
|
27919
|
+
return !this._private__placement?._internal_pending() && !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
|
|
27735
27920
|
}
|
|
27736
27921
|
setColor(color) {
|
|
27737
27922
|
this._private__textOptions.textColor = color;
|
|
@@ -27792,18 +27977,59 @@ class TextTool extends DrawingToolBase {
|
|
|
27792
27977
|
const py = this._series.priceToCoordinate(price) ?? 0;
|
|
27793
27978
|
// Calculate text box dimensions
|
|
27794
27979
|
const fontSize = this._private__textOptions.fontSize || 14;
|
|
27795
|
-
const padding = this._private__textOptions.padding
|
|
27980
|
+
const padding = this._private__textOptions.padding ?? 8;
|
|
27796
27981
|
const text = this._private__textOptions.text || '';
|
|
27797
27982
|
// Rough estimate of text width (will be more accurate in canvas)
|
|
27798
27983
|
const charWidth = fontSize * 0.6;
|
|
27799
27984
|
const textWidth = Math.min(text.length * charWidth, this._private__textOptions.maxWidth || 200);
|
|
27800
|
-
const
|
|
27801
|
-
|
|
27802
|
-
const
|
|
27803
|
-
const
|
|
27804
|
-
const
|
|
27805
|
-
const
|
|
27806
|
-
return x >= left && x <= right && y >=
|
|
27985
|
+
const boxWidth = textWidth + padding * 2;
|
|
27986
|
+
const boxHeight = fontSize + padding * 2;
|
|
27987
|
+
const tail = this._private__tailSize(boxHeight);
|
|
27988
|
+
const box = this._private__boxOrigin(px, py, boxWidth, boxHeight, tail);
|
|
27989
|
+
const left = box._internal_left - (this._private__textOptions.anchor === 'left' ? tail : 0);
|
|
27990
|
+
const right = box._internal_left + boxWidth + (this._private__textOptions.anchor === 'right' ? tail : 0);
|
|
27991
|
+
return x >= left && x <= right && y >= box._internal_top && y <= box._internal_top + boxHeight;
|
|
27992
|
+
}
|
|
27993
|
+
_private__tailSize(boxHeight) {
|
|
27994
|
+
const anchor = this._private__textOptions.anchor;
|
|
27995
|
+
return this._private__textOptions.pointer && (anchor === 'left' || anchor === 'right') ? Math.round(boxHeight * 0.35) : 0;
|
|
27996
|
+
}
|
|
27997
|
+
_private__boxOrigin(x, y, width, height, tail) {
|
|
27998
|
+
switch (this._private__textOptions.anchor) {
|
|
27999
|
+
case 'left':
|
|
28000
|
+
return { _internal_left: x + tail, _internal_top: y - height / 2 };
|
|
28001
|
+
case 'right':
|
|
28002
|
+
return { _internal_left: x - tail - width, _internal_top: y - height / 2 };
|
|
28003
|
+
case 'center':
|
|
28004
|
+
return { _internal_left: x - width / 2, _internal_top: y - height / 2 };
|
|
28005
|
+
default:
|
|
28006
|
+
return { _internal_left: x, _internal_top: y };
|
|
28007
|
+
}
|
|
28008
|
+
}
|
|
28009
|
+
// eslint-disable-next-line max-params
|
|
28010
|
+
_private__traceBox(ctx, left, top, width, height, tail, anchorX, anchorY, radius) {
|
|
28011
|
+
const r = Math.max(0, Math.min(radius, width / 2, height / 2));
|
|
28012
|
+
ctx.beginPath();
|
|
28013
|
+
ctx.moveTo(left + r, top);
|
|
28014
|
+
ctx.lineTo(left + width - r, top);
|
|
28015
|
+
ctx.arcTo(left + width, top, left + width, top + r, r);
|
|
28016
|
+
if (tail > 0 && this._private__textOptions.anchor === 'right') {
|
|
28017
|
+
ctx.lineTo(left + width, anchorY - tail);
|
|
28018
|
+
ctx.lineTo(anchorX, anchorY);
|
|
28019
|
+
ctx.lineTo(left + width, anchorY + tail);
|
|
28020
|
+
}
|
|
28021
|
+
ctx.lineTo(left + width, top + height - r);
|
|
28022
|
+
ctx.arcTo(left + width, top + height, left + width - r, top + height, r);
|
|
28023
|
+
ctx.lineTo(left + r, top + height);
|
|
28024
|
+
ctx.arcTo(left, top + height, left, top + height - r, r);
|
|
28025
|
+
if (tail > 0 && this._private__textOptions.anchor === 'left') {
|
|
28026
|
+
ctx.lineTo(left, anchorY + tail);
|
|
28027
|
+
ctx.lineTo(anchorX, anchorY);
|
|
28028
|
+
ctx.lineTo(left, anchorY - tail);
|
|
28029
|
+
}
|
|
28030
|
+
ctx.lineTo(left, top + r);
|
|
28031
|
+
ctx.arcTo(left, top, left + r, top, r);
|
|
28032
|
+
ctx.closePath();
|
|
27807
28033
|
}
|
|
27808
28034
|
hitTestEndpoint(x, y) {
|
|
27809
28035
|
if (!this.isComplete())
|
|
@@ -27855,7 +28081,7 @@ class TextTool extends DrawingToolBase {
|
|
|
27855
28081
|
const x = (coordinateFromTime(this._chart, this._series, time) ?? 0) * horizontalPixelRatio;
|
|
27856
28082
|
const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
|
|
27857
28083
|
const fontSize = (this._private__textOptions.fontSize || 14) * horizontalPixelRatio;
|
|
27858
|
-
const padding = (this._private__textOptions.padding
|
|
28084
|
+
const padding = (this._private__textOptions.padding ?? 8) * horizontalPixelRatio;
|
|
27859
28085
|
const maxWidth = (this._private__textOptions.maxWidth || 200) * horizontalPixelRatio;
|
|
27860
28086
|
// Set font
|
|
27861
28087
|
ctx.font = `${fontSize}px ${this._private__textOptions.fontFamily || 'Arial'}`;
|
|
@@ -27887,32 +28113,39 @@ class TextTool extends DrawingToolBase {
|
|
|
27887
28113
|
const boxWidth = maxLineWidth + padding * 2;
|
|
27888
28114
|
const lineHeight = fontSize * 1.2;
|
|
27889
28115
|
const boxHeight = lines.length * lineHeight + padding * 2;
|
|
28116
|
+
const tail = this._private__tailSize(boxHeight);
|
|
28117
|
+
const { _internal_left: left, _internal_top: top } = this._private__boxOrigin(x, y, boxWidth, boxHeight, tail);
|
|
28118
|
+
const radius = (this._private__textOptions.borderRadius ?? 0) * horizontalPixelRatio;
|
|
27890
28119
|
// Draw background
|
|
27891
|
-
const bgOpacity = this._private__textOptions.backgroundOpacity
|
|
28120
|
+
const bgOpacity = this._private__textOptions.backgroundOpacity ?? 0.8;
|
|
27892
28121
|
const bgColor = this._private__textOptions.backgroundColor || '#2962FF';
|
|
27893
|
-
|
|
27894
|
-
|
|
28122
|
+
if (bgOpacity > 0) {
|
|
28123
|
+
ctx.fillStyle = this._private__hexToRgba(bgColor, bgOpacity);
|
|
28124
|
+
this._private__traceBox(ctx, left, top, boxWidth, boxHeight, tail, x, y, radius);
|
|
28125
|
+
ctx.fill();
|
|
28126
|
+
}
|
|
27895
28127
|
// Draw border
|
|
27896
28128
|
if (this._private__isSelected || this._private__isHovered) {
|
|
27897
28129
|
ctx.strokeStyle = this._private__textOptions.borderColor ?? '#FFFFFF';
|
|
27898
28130
|
ctx.lineWidth = (this._private__textOptions.borderWidth || 1) * horizontalPixelRatio * (this._private__isSelected ? 2 : 1);
|
|
27899
|
-
|
|
28131
|
+
this._private__traceBox(ctx, left, top, boxWidth, boxHeight, tail, x, y, radius);
|
|
28132
|
+
ctx.stroke();
|
|
27900
28133
|
}
|
|
27901
28134
|
// Draw text
|
|
27902
28135
|
ctx.fillStyle = this._private__textOptions.textColor || '#FFFFFF';
|
|
27903
28136
|
for (let i = 0; i < lines.length; i++) {
|
|
27904
28137
|
const line = lines[i];
|
|
27905
|
-
let textX =
|
|
28138
|
+
let textX = left + padding;
|
|
27906
28139
|
// Handle text alignment
|
|
27907
28140
|
if (this._private__textOptions.textAlign === 'center') {
|
|
27908
28141
|
const metrics = ctx.measureText(line);
|
|
27909
|
-
textX =
|
|
28142
|
+
textX = left + (boxWidth - metrics.width) / 2;
|
|
27910
28143
|
}
|
|
27911
28144
|
else if (this._private__textOptions.textAlign === 'right') {
|
|
27912
28145
|
const metrics = ctx.measureText(line);
|
|
27913
|
-
textX =
|
|
28146
|
+
textX = left + boxWidth - metrics.width - padding;
|
|
27914
28147
|
}
|
|
27915
|
-
const textY =
|
|
28148
|
+
const textY = top + padding + i * lineHeight;
|
|
27916
28149
|
ctx.fillText(line, textX, textY);
|
|
27917
28150
|
}
|
|
27918
28151
|
// Draw anchor point if selected
|
|
@@ -27956,6 +28189,9 @@ class TextTool extends DrawingToolBase {
|
|
|
27956
28189
|
borderWidth: this._private__textOptions.borderWidth,
|
|
27957
28190
|
textAlign: this._private__textOptions.textAlign,
|
|
27958
28191
|
maxWidth: this._private__textOptions.maxWidth,
|
|
28192
|
+
anchor: this._private__textOptions.anchor,
|
|
28193
|
+
pointer: this._private__textOptions.pointer,
|
|
28194
|
+
borderRadius: this._private__textOptions.borderRadius,
|
|
27959
28195
|
},
|
|
27960
28196
|
extra: {
|
|
27961
28197
|
text: this._private__textOptions.text,
|
|
@@ -27963,6 +28199,7 @@ class TextTool extends DrawingToolBase {
|
|
|
27963
28199
|
};
|
|
27964
28200
|
}
|
|
27965
28201
|
remove() {
|
|
28202
|
+
this._private__placement?._internal_stop();
|
|
27966
28203
|
// Clean up click handler
|
|
27967
28204
|
if (this._private__clickOutsideHandler) {
|
|
27968
28205
|
this._chart.unsubscribeClick(this._private__clickOutsideHandler);
|
|
@@ -28821,6 +29058,7 @@ class PositionTool extends DrawingToolBase {
|
|
|
28821
29058
|
this._private__paneView = null;
|
|
28822
29059
|
this._private__isSelected = false;
|
|
28823
29060
|
this._private__isHovered = false;
|
|
29061
|
+
this._private__placement = null;
|
|
28824
29062
|
this._private__isDragging = false;
|
|
28825
29063
|
this._private__dragTarget = null;
|
|
28826
29064
|
this._private__grab = null;
|
|
@@ -28843,6 +29081,32 @@ class PositionTool extends DrawingToolBase {
|
|
|
28843
29081
|
// Create primitive and setup
|
|
28844
29082
|
this._private__createPrimitive();
|
|
28845
29083
|
this._private__updateMetrics();
|
|
29084
|
+
if (this._private__positionOptions.interactive) {
|
|
29085
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
|
|
29086
|
+
}
|
|
29087
|
+
}
|
|
29088
|
+
_private__placeAt(point) {
|
|
29089
|
+
const options = this._private__positionOptions;
|
|
29090
|
+
const shift = point._internal_price - options.entryPrice;
|
|
29091
|
+
options.entryPrice = point._internal_price;
|
|
29092
|
+
options.entryTime = point._internal_time;
|
|
29093
|
+
if (options.stopLoss !== undefined) {
|
|
29094
|
+
options.stopLoss += shift;
|
|
29095
|
+
}
|
|
29096
|
+
if (options.takeProfit !== undefined) {
|
|
29097
|
+
options.takeProfit += shift;
|
|
29098
|
+
}
|
|
29099
|
+
options.endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? undefined;
|
|
29100
|
+
this._private__updateMetrics();
|
|
29101
|
+
this._private__requestUpdate();
|
|
29102
|
+
}
|
|
29103
|
+
isComplete() {
|
|
29104
|
+
return !this._private__placement?._internal_pending();
|
|
29105
|
+
}
|
|
29106
|
+
_private__span() {
|
|
29107
|
+
const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
|
|
29108
|
+
const endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? entryX + defaultWidth(this._chart);
|
|
29109
|
+
return { _internal_entryX: entryX, _internal_endX: endX };
|
|
28846
29110
|
}
|
|
28847
29111
|
_private__createPrimitive() {
|
|
28848
29112
|
// Create and cache the paneView once
|
|
@@ -28874,6 +29138,9 @@ class PositionTool extends DrawingToolBase {
|
|
|
28874
29138
|
return new PositionPaneView(this);
|
|
28875
29139
|
}
|
|
28876
29140
|
drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
29141
|
+
if (!this.isComplete()) {
|
|
29142
|
+
return;
|
|
29143
|
+
}
|
|
28877
29144
|
// Draw profit rectangle (green)
|
|
28878
29145
|
if (this._private__profitRect && this._private__positionOptions.takeProfit) {
|
|
28879
29146
|
const timeScale = this._chart.timeScale();
|
|
@@ -28907,16 +29174,9 @@ class PositionTool extends DrawingToolBase {
|
|
|
28907
29174
|
}
|
|
28908
29175
|
}
|
|
28909
29176
|
_private__drawRectangle(ctx, rect, horizontalPixelRatio, verticalPixelRatio, timeScale) {
|
|
28910
|
-
const
|
|
28911
|
-
|
|
28912
|
-
|
|
28913
|
-
if (this._private__positionOptions.endTime) {
|
|
28914
|
-
endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
|
|
28915
|
-
}
|
|
28916
|
-
else {
|
|
28917
|
-
const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
|
|
28918
|
-
endX = chartWidth;
|
|
28919
|
-
}
|
|
29177
|
+
const span = this._private__span();
|
|
29178
|
+
const entryX = span._internal_entryX * horizontalPixelRatio;
|
|
29179
|
+
const endX = span._internal_endX * horizontalPixelRatio;
|
|
28920
29180
|
const y1 = (this._series.priceToCoordinate(rect._internal_price1) ?? 0) * verticalPixelRatio;
|
|
28921
29181
|
const y2 = (this._series.priceToCoordinate(rect._internal_price2) ?? 0) * verticalPixelRatio;
|
|
28922
29182
|
// Draw filled rectangle with semi-transparent fill
|
|
@@ -28931,16 +29191,10 @@ class PositionTool extends DrawingToolBase {
|
|
|
28931
29191
|
ctx.stroke();
|
|
28932
29192
|
}
|
|
28933
29193
|
_private__drawEntryLine(ctx, horizontalPixelRatio, verticalPixelRatio, timeScale) {
|
|
28934
|
-
const
|
|
29194
|
+
const span = this._private__span();
|
|
29195
|
+
const entryX = span._internal_entryX * horizontalPixelRatio;
|
|
29196
|
+
const endX = span._internal_endX * horizontalPixelRatio;
|
|
28935
29197
|
const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
|
|
28936
|
-
let endX;
|
|
28937
|
-
if (this._private__positionOptions.endTime) {
|
|
28938
|
-
endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
|
|
28939
|
-
}
|
|
28940
|
-
else {
|
|
28941
|
-
const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
|
|
28942
|
-
endX = chartWidth;
|
|
28943
|
-
}
|
|
28944
29198
|
// Draw horizontal entry line (divider)
|
|
28945
29199
|
const lineColor = this._private__positionOptions.entryLineColor;
|
|
28946
29200
|
ctx.strokeStyle = lineColor;
|
|
@@ -29054,9 +29308,11 @@ class PositionTool extends DrawingToolBase {
|
|
|
29054
29308
|
const padding = 6 * horizontalPixelRatio;
|
|
29055
29309
|
const labelWidth = textMetrics.width + padding * 2;
|
|
29056
29310
|
const labelHeight = 20 * horizontalPixelRatio;
|
|
29057
|
-
//
|
|
29311
|
+
// Outside the box, so a label wider than it does not cover the handles on its edge
|
|
29312
|
+
const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
|
|
29313
|
+
const gap = 3 * verticalPixelRatio;
|
|
29058
29314
|
const labelX = entryX + 8 * horizontalPixelRatio;
|
|
29059
|
-
const labelY = y - labelHeight
|
|
29315
|
+
const labelY = y <= entryY ? y - labelHeight - gap : y + gap;
|
|
29060
29316
|
// Draw label background
|
|
29061
29317
|
ctx.fillStyle = rect._internal_color;
|
|
29062
29318
|
ctx.fillRect(labelX, labelY, labelWidth, labelHeight);
|
|
@@ -29064,10 +29320,12 @@ class PositionTool extends DrawingToolBase {
|
|
|
29064
29320
|
ctx.fillStyle = '#ffffff';
|
|
29065
29321
|
ctx.textAlign = 'left';
|
|
29066
29322
|
ctx.textBaseline = 'middle';
|
|
29067
|
-
ctx.fillText(labelText, labelX + padding,
|
|
29323
|
+
ctx.fillText(labelText, labelX + padding, labelY + labelHeight / 2);
|
|
29068
29324
|
}
|
|
29069
29325
|
_private__drawEndpoint(ctx, price, color, horizontalPixelRatio, verticalPixelRatio, timeScale) {
|
|
29070
|
-
const
|
|
29326
|
+
const span = this._private__span();
|
|
29327
|
+
const entryX = span._internal_entryX * horizontalPixelRatio;
|
|
29328
|
+
const endX = span._internal_endX * horizontalPixelRatio;
|
|
29071
29329
|
const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
|
|
29072
29330
|
// Draw circle endpoint at entry
|
|
29073
29331
|
const circleColor = color;
|
|
@@ -29079,14 +29337,6 @@ class PositionTool extends DrawingToolBase {
|
|
|
29079
29337
|
ctx.fill();
|
|
29080
29338
|
ctx.stroke();
|
|
29081
29339
|
// Draw square endpoint at the right edge
|
|
29082
|
-
let endX;
|
|
29083
|
-
if (this._private__positionOptions.endTime) {
|
|
29084
|
-
endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
|
|
29085
|
-
}
|
|
29086
|
-
else {
|
|
29087
|
-
const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
|
|
29088
|
-
endX = chartWidth - 10 * horizontalPixelRatio;
|
|
29089
|
-
}
|
|
29090
29340
|
const size = 8 * horizontalPixelRatio;
|
|
29091
29341
|
const squareColor = color;
|
|
29092
29342
|
ctx.fillStyle = squareColor;
|
|
@@ -29207,18 +29457,12 @@ class PositionTool extends DrawingToolBase {
|
|
|
29207
29457
|
return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
|
|
29208
29458
|
}
|
|
29209
29459
|
hitTest(x, y) {
|
|
29210
|
-
if (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit) {
|
|
29460
|
+
if (!this.isComplete() || (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit)) {
|
|
29211
29461
|
return false;
|
|
29212
29462
|
}
|
|
29213
|
-
const
|
|
29214
|
-
|
|
29215
|
-
|
|
29216
|
-
endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
|
|
29217
|
-
}
|
|
29218
|
-
else {
|
|
29219
|
-
const chartWidth = this._chart.chartElement().clientWidth;
|
|
29220
|
-
endX = chartWidth;
|
|
29221
|
-
}
|
|
29463
|
+
const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
|
|
29464
|
+
const left = Math.min(entryX, endX);
|
|
29465
|
+
const right = Math.max(entryX, endX);
|
|
29222
29466
|
// Check if inside the entire Position Tool area (rectangles)
|
|
29223
29467
|
if (this._private__positionOptions.takeProfit && this._private__positionOptions.stopLoss) {
|
|
29224
29468
|
const tpY = this._series.priceToCoordinate(this._private__positionOptions.takeProfit) ?? 0;
|
|
@@ -29226,7 +29470,7 @@ class PositionTool extends DrawingToolBase {
|
|
|
29226
29470
|
const minY = Math.min(tpY, slY);
|
|
29227
29471
|
const maxY = Math.max(tpY, slY);
|
|
29228
29472
|
// Check if inside the rectangle bounds (entire tool area)
|
|
29229
|
-
if (x >=
|
|
29473
|
+
if (x >= left && x <= right && y >= minY && y <= maxY) {
|
|
29230
29474
|
return true;
|
|
29231
29475
|
}
|
|
29232
29476
|
// Also check entry line area
|
|
@@ -29238,15 +29482,12 @@ class PositionTool extends DrawingToolBase {
|
|
|
29238
29482
|
return false;
|
|
29239
29483
|
}
|
|
29240
29484
|
hitTestEndpoint(x, y) {
|
|
29241
|
-
|
|
29242
|
-
|
|
29243
|
-
if (this._private__positionOptions.endTime) {
|
|
29244
|
-
endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
|
|
29245
|
-
}
|
|
29246
|
-
else {
|
|
29247
|
-
const chartWidth = this._chart.chartElement().clientWidth;
|
|
29248
|
-
endX = chartWidth;
|
|
29485
|
+
if (!this.isComplete()) {
|
|
29486
|
+
return null;
|
|
29249
29487
|
}
|
|
29488
|
+
const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
|
|
29489
|
+
const left = Math.min(entryX, endX);
|
|
29490
|
+
const right = Math.max(entryX, endX);
|
|
29250
29491
|
const tolerance = 10;
|
|
29251
29492
|
// Check TP left endpoint (circle)
|
|
29252
29493
|
if (this._private__positionOptions.takeProfit) {
|
|
@@ -29283,7 +29524,7 @@ class PositionTool extends DrawingToolBase {
|
|
|
29283
29524
|
// Check entry line (middle divider line) - higher priority than 'move'
|
|
29284
29525
|
const entryY = this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0;
|
|
29285
29526
|
const lineTolerance = 8;
|
|
29286
|
-
if (x >=
|
|
29527
|
+
if (x >= left && x <= right && Math.abs(y - entryY) <= lineTolerance) {
|
|
29287
29528
|
return 'entry';
|
|
29288
29529
|
}
|
|
29289
29530
|
// Check if clicking on the body (rectangles) for moving entire tool
|
|
@@ -29293,7 +29534,7 @@ class PositionTool extends DrawingToolBase {
|
|
|
29293
29534
|
const minY = Math.min(tpY, slY);
|
|
29294
29535
|
const maxY = Math.max(tpY, slY);
|
|
29295
29536
|
// Check if inside the rectangle bounds
|
|
29296
|
-
if (x >=
|
|
29537
|
+
if (x >= left && x <= right && y >= minY && y <= maxY) {
|
|
29297
29538
|
return 'move';
|
|
29298
29539
|
}
|
|
29299
29540
|
}
|
|
@@ -29393,6 +29634,7 @@ class PositionTool extends DrawingToolBase {
|
|
|
29393
29634
|
this._private__requestUpdate();
|
|
29394
29635
|
}
|
|
29395
29636
|
remove() {
|
|
29637
|
+
this._private__placement?._internal_stop();
|
|
29396
29638
|
if (this._private__primitive) {
|
|
29397
29639
|
this._series.detachPrimitive(this._private__primitive);
|
|
29398
29640
|
this._private__primitive = null;
|
|
@@ -29439,6 +29681,7 @@ class GanttPaneView {
|
|
|
29439
29681
|
}
|
|
29440
29682
|
}
|
|
29441
29683
|
|
|
29684
|
+
const TOP_PADDING = 50;
|
|
29442
29685
|
class GanttTool extends DrawingToolBase {
|
|
29443
29686
|
constructor(context, options) {
|
|
29444
29687
|
super(context, 'gantt', options);
|
|
@@ -29452,6 +29695,7 @@ class GanttTool extends DrawingToolBase {
|
|
|
29452
29695
|
this._private__grab = null;
|
|
29453
29696
|
this._private__moveStart = null;
|
|
29454
29697
|
this._private__hoveredSegmentIndex = -1;
|
|
29698
|
+
this._private__placement = null;
|
|
29455
29699
|
this._private__ganttOptions = {
|
|
29456
29700
|
segments: [],
|
|
29457
29701
|
rowHeight: 40,
|
|
@@ -29464,6 +29708,43 @@ class GanttTool extends DrawingToolBase {
|
|
|
29464
29708
|
};
|
|
29465
29709
|
this._registerOptions(this._private__ganttOptions);
|
|
29466
29710
|
this._private__createPrimitive();
|
|
29711
|
+
if (this._private__ganttOptions.interactive) {
|
|
29712
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
|
|
29713
|
+
}
|
|
29714
|
+
}
|
|
29715
|
+
_private__placeAt(point) {
|
|
29716
|
+
const segments = this._private__ganttOptions.segments ?? [];
|
|
29717
|
+
const endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? point._internal_time;
|
|
29718
|
+
const rowPitch = this._private__ganttOptions.rowHeight + this._private__ganttOptions.rowSpacing;
|
|
29719
|
+
const row = Math.max(0, Math.floor((point._internal_y - TOP_PADDING) / rowPitch));
|
|
29720
|
+
const shift = segments.length > 0 ? row - segments[0].level : 0;
|
|
29721
|
+
for (const segment of segments) {
|
|
29722
|
+
segment.startTime = point._internal_time;
|
|
29723
|
+
segment.endTime = endTime;
|
|
29724
|
+
segment.level = Math.max(0, segment.level + shift);
|
|
29725
|
+
}
|
|
29726
|
+
this._private__requestUpdate();
|
|
29727
|
+
}
|
|
29728
|
+
isComplete() {
|
|
29729
|
+
return !this._private__placement?._internal_pending();
|
|
29730
|
+
}
|
|
29731
|
+
selectionBox() {
|
|
29732
|
+
const segments = this._private__ganttOptions.segments ?? [];
|
|
29733
|
+
if (segments.length === 0) {
|
|
29734
|
+
return null;
|
|
29735
|
+
}
|
|
29736
|
+
const rowHeight = this._private__ganttOptions.rowHeight;
|
|
29737
|
+
const pitch = rowHeight + this._private__ganttOptions.rowSpacing;
|
|
29738
|
+
// Typed as the public shape: an inferred one gets its keys renamed by the build
|
|
29739
|
+
const box = { left: Infinity, top: Infinity, right: -Infinity, bottom: -Infinity };
|
|
29740
|
+
for (const segment of segments) {
|
|
29741
|
+
const top = TOP_PADDING + segment.level * pitch;
|
|
29742
|
+
box.left = Math.min(box.left, coordinateFromTime(this._chart, this._series, segment.startTime) ?? 0);
|
|
29743
|
+
box.right = Math.max(box.right, coordinateFromTime(this._chart, this._series, segment.endTime) ?? 0);
|
|
29744
|
+
box.top = Math.min(box.top, top);
|
|
29745
|
+
box.bottom = Math.max(box.bottom, top + rowHeight);
|
|
29746
|
+
}
|
|
29747
|
+
return box;
|
|
29467
29748
|
}
|
|
29468
29749
|
_private__createPrimitive() {
|
|
29469
29750
|
this._private__paneView = this._private__createPaneView();
|
|
@@ -29494,11 +29775,10 @@ class GanttTool extends DrawingToolBase {
|
|
|
29494
29775
|
return new GanttPaneView(this);
|
|
29495
29776
|
}
|
|
29496
29777
|
drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
29497
|
-
if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
|
|
29778
|
+
if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
|
|
29498
29779
|
return;
|
|
29499
29780
|
// Position gantt at top of chart with padding
|
|
29500
|
-
const
|
|
29501
|
-
const baseY = topPadding * verticalPixelRatio;
|
|
29781
|
+
const baseY = TOP_PADDING * verticalPixelRatio;
|
|
29502
29782
|
const rowHeight = this._private__ganttOptions.rowHeight * verticalPixelRatio;
|
|
29503
29783
|
const rowSpacing = this._private__ganttOptions.rowSpacing * verticalPixelRatio;
|
|
29504
29784
|
// Draw grid lines if enabled
|
|
@@ -29677,11 +29957,10 @@ class GanttTool extends DrawingToolBase {
|
|
|
29677
29957
|
this._private__moveStart = null;
|
|
29678
29958
|
}
|
|
29679
29959
|
hitTest(x, y) {
|
|
29680
|
-
if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
|
|
29960
|
+
if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
|
|
29681
29961
|
return false;
|
|
29682
29962
|
}
|
|
29683
|
-
const
|
|
29684
|
-
const baseY = topPadding;
|
|
29963
|
+
const baseY = TOP_PADDING;
|
|
29685
29964
|
const rowHeight = this._private__ganttOptions.rowHeight;
|
|
29686
29965
|
const rowSpacing = this._private__ganttOptions.rowSpacing;
|
|
29687
29966
|
// Check each segment
|
|
@@ -29735,6 +30014,7 @@ class GanttTool extends DrawingToolBase {
|
|
|
29735
30014
|
this._private__requestUpdate();
|
|
29736
30015
|
}
|
|
29737
30016
|
remove() {
|
|
30017
|
+
this._private__placement?._internal_stop();
|
|
29738
30018
|
if (this._private__primitive) {
|
|
29739
30019
|
this._series.detachPrimitive(this._private__primitive);
|
|
29740
30020
|
this._private__primitive = null;
|
|
@@ -29870,6 +30150,7 @@ class DrawingToolsManager {
|
|
|
29870
30150
|
}
|
|
29871
30151
|
this._private__completionCheck = setTimeout(() => {
|
|
29872
30152
|
this._private__completionCheck = null;
|
|
30153
|
+
const completed = [];
|
|
29873
30154
|
for (const id of Array.from(this._private__pendingToolIds)) {
|
|
29874
30155
|
const tool = this._private__tools.get(id);
|
|
29875
30156
|
if (!tool) {
|
|
@@ -29878,9 +30159,15 @@ class DrawingToolsManager {
|
|
|
29878
30159
|
}
|
|
29879
30160
|
if (tool.isComplete()) {
|
|
29880
30161
|
this._private__pendingToolIds.delete(id);
|
|
29881
|
-
|
|
30162
|
+
completed.push(id);
|
|
29882
30163
|
}
|
|
29883
30164
|
}
|
|
30165
|
+
if (completed.length > 0) {
|
|
30166
|
+
this.recordChange();
|
|
30167
|
+
}
|
|
30168
|
+
for (const id of completed) {
|
|
30169
|
+
this._private__drawingCompleted._internal_fire(id);
|
|
30170
|
+
}
|
|
29884
30171
|
}, 0);
|
|
29885
30172
|
}
|
|
29886
30173
|
/**
|
|
@@ -29940,12 +30227,16 @@ class DrawingToolsManager {
|
|
|
29940
30227
|
if (this._private__restoring) {
|
|
29941
30228
|
return;
|
|
29942
30229
|
}
|
|
30230
|
+
const next = this.serializeAll();
|
|
30231
|
+
if (JSON.stringify(next) === JSON.stringify(this._private__committed)) {
|
|
30232
|
+
return;
|
|
30233
|
+
}
|
|
29943
30234
|
this._private__past.push(this._private__committed);
|
|
29944
30235
|
if (this._private__past.length > UNDO_HISTORY_LIMIT) {
|
|
29945
30236
|
this._private__past.shift();
|
|
29946
30237
|
}
|
|
29947
30238
|
this._private__future = [];
|
|
29948
|
-
this._private__committed =
|
|
30239
|
+
this._private__committed = next;
|
|
29949
30240
|
}
|
|
29950
30241
|
canUndo() {
|
|
29951
30242
|
return this._private__past.length > 0;
|
|
@@ -30001,11 +30292,25 @@ class DrawingToolsManager {
|
|
|
30001
30292
|
const tool = this._private__tools.get(this._private__selectedToolId);
|
|
30002
30293
|
return tool ? this._private__boundsOf(tool) : null;
|
|
30003
30294
|
}
|
|
30295
|
+
/** Relative to the chart element; the tool's box is relative to its pane. */
|
|
30296
|
+
_private__boundsOf(tool) {
|
|
30297
|
+
const box = tool.selectionBox() ?? this._private__boxFromPoints(tool);
|
|
30298
|
+
if (box === null) {
|
|
30299
|
+
return null;
|
|
30300
|
+
}
|
|
30301
|
+
const offset = this._private__paneOffsetOf(tool);
|
|
30302
|
+
return {
|
|
30303
|
+
left: box.left + offset._internal_x,
|
|
30304
|
+
right: box.right + offset._internal_x,
|
|
30305
|
+
top: box.top + offset._internal_y,
|
|
30306
|
+
bottom: box.bottom + offset._internal_y,
|
|
30307
|
+
};
|
|
30308
|
+
}
|
|
30004
30309
|
/**
|
|
30005
30310
|
* Bounds come from the tool's own serialized points, so this works for every
|
|
30006
30311
|
* tool without each one having to report a box.
|
|
30007
30312
|
*/
|
|
30008
|
-
|
|
30313
|
+
_private__boxFromPoints(tool) {
|
|
30009
30314
|
const series = tool.series();
|
|
30010
30315
|
const points = tool.serialize().points;
|
|
30011
30316
|
let left = Number.POSITIVE_INFINITY;
|
|
@@ -30028,13 +30333,7 @@ class DrawingToolsManager {
|
|
|
30028
30333
|
if (!found) {
|
|
30029
30334
|
return null;
|
|
30030
30335
|
}
|
|
30031
|
-
|
|
30032
|
-
return {
|
|
30033
|
-
left: left + offset._internal_x,
|
|
30034
|
-
right: right + offset._internal_x,
|
|
30035
|
-
top: top + offset._internal_y,
|
|
30036
|
-
bottom: bottom + offset._internal_y,
|
|
30037
|
-
};
|
|
30336
|
+
return { left, top, right, bottom };
|
|
30038
30337
|
}
|
|
30039
30338
|
/** Panes below the first one are offset inside the chart element. */
|
|
30040
30339
|
_private__paneOffsetOf(tool) {
|
|
@@ -30118,7 +30417,10 @@ class DrawingToolsManager {
|
|
|
30118
30417
|
serializeAll() {
|
|
30119
30418
|
const result = [];
|
|
30120
30419
|
for (const tool of this._private__tools.values()) {
|
|
30121
|
-
|
|
30420
|
+
// Not drawn yet: undo would bring it back as a finished drawing
|
|
30421
|
+
if (tool.isComplete()) {
|
|
30422
|
+
result.push(tool.serialize());
|
|
30423
|
+
}
|
|
30122
30424
|
}
|
|
30123
30425
|
return result;
|
|
30124
30426
|
}
|
|
@@ -30743,7 +31045,7 @@ const customSeriesDefaultOptions = {
|
|
|
30743
31045
|
* Returns the current version as a string. For example `'1.0.0'`.
|
|
30744
31046
|
*/
|
|
30745
31047
|
function version() {
|
|
30746
|
-
return "2.3.
|
|
31048
|
+
return "2.3.14";
|
|
30747
31049
|
}
|
|
30748
31050
|
|
|
30749
31051
|
export { areaSeries as AreaSeries, ArrowTool, barSeries as BarSeries, baselineSeries as BaselineSeries, BrushTool, candlestickSeries as CandlestickSeries, CircleTool, ColorType, CrossLineTool, CrosshairMode, DEFAULT_SESSIONS, DraggablePriceLine, DrawingToolsManager, EXTENDED_FIBONACCI_RATIOS, FibonacciCalculator, FibonacciExtensionTool, FibonacciFanTool, FibonacciTool, GanttTool, histogramSeries as HistogramSeries, HorizontalLineTool, InteractiveLineManager, LastPriceAnimationMode, lineSeries as LineSeries, LineStyle, LineType, MismatchDirection, ParallelChannelTool, PitchforkTool, PolygonTool, PositionCalculator, PositionLine, PositionMarkersManager, PositionTool, PriceLineSource, PriceScaleMode, RectangleTool, RulerCalculator, RulerTool, STANDARD_FIBONACCI_RATIOS, TextTool, TickMarkType, TrackingModeExitMode, TradingSessionsIndicator, TrendLineTool, VerticalLineTool, VolumeProfileIndicator, applyADX, applyATR, applyBollingerBands, applyCCI, applyDonchianChannels, applyEMA, applyHeikinAshi, applyIchimoku, applyKeltnerChannels, applyMACD, applyMFI, applyOBV, applyParabolicSAR, applyPivotPoints, applyRSI, applySMA, applyStochastic, applySuperTrend, applyVWAP, applyVolume, applyWMA, applyWilliamsR, calculateHeikinAshi, calculatePipConfig, coordinateFromTime, createArrowTool, createBrushTool, createChart, createChartEx, createCircleTool, createCrossLineTool, createFibonacciExtensionTool, createFibonacciFanTool, createFibonacciTool, createGanttTool, createHorizontalLineTool, createImageWatermark, createInteractiveLineManager, createOptionsChart, createParallelChannelTool, createPitchforkTool, createPolygonTool, createPositionLine, createPositionMarkers, createPositionTool, createRectangleTool, createRulerTool, createSeriesMarkers, createTextTool, createTextWatermark, createTradingLine, createTradingSessionsIndicator, createTrendLineTool, createUpDownMarkers, createVerticalLineTool, createVolumeProfileIndicator, createYieldCurveChart, customSeriesDefaultOptions, defaultHorzScaleBehavior, getPipConfig, hideSeriesPaint, isBusinessDay, isUTCTimestamp, setSeriesVolumes, setVolumeProfileData, timeFromCoordinate, version };
|