@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)
|
|
@@ -14674,6 +14674,8 @@ function shapeSize(shape, originalSize) {
|
|
|
14674
14674
|
return size(originalSize, 0.7);
|
|
14675
14675
|
case 'tag':
|
|
14676
14676
|
return size(originalSize, 1);
|
|
14677
|
+
case 'xcross':
|
|
14678
|
+
return size(originalSize, 0.7);
|
|
14677
14679
|
}
|
|
14678
14680
|
}
|
|
14679
14681
|
function calculateShapeHeight(barSpacing) {
|
|
@@ -14874,6 +14876,27 @@ function hitTestText(textX, textY, textWidth, textHeight, x, y) {
|
|
|
14874
14876
|
y >= textY - halfHeight && y <= textY + halfHeight;
|
|
14875
14877
|
}
|
|
14876
14878
|
|
|
14879
|
+
function drawXCross(ctx, coords, size) {
|
|
14880
|
+
const crossSize = shapeSize('xcross', size);
|
|
14881
|
+
const halfSize = ((crossSize - 1) / 2) * coords._internal_pixelRatio;
|
|
14882
|
+
ctx.save();
|
|
14883
|
+
ctx.strokeStyle = ctx.fillStyle;
|
|
14884
|
+
ctx.lineWidth = Math.max(1, Math.round(1.5 * coords._internal_pixelRatio));
|
|
14885
|
+
ctx.lineCap = 'round';
|
|
14886
|
+
ctx.beginPath();
|
|
14887
|
+
ctx.moveTo(coords._internal_x - halfSize, coords._internal_y - halfSize);
|
|
14888
|
+
ctx.lineTo(coords._internal_x + halfSize, coords._internal_y + halfSize);
|
|
14889
|
+
ctx.moveTo(coords._internal_x + halfSize, coords._internal_y - halfSize);
|
|
14890
|
+
ctx.lineTo(coords._internal_x - halfSize, coords._internal_y + halfSize);
|
|
14891
|
+
ctx.stroke();
|
|
14892
|
+
ctx.restore();
|
|
14893
|
+
}
|
|
14894
|
+
function hitTestXCross(centerX, centerY, size, x, y) {
|
|
14895
|
+
const crossSize = shapeSize('xcross', size);
|
|
14896
|
+
const tolerance = 2 + crossSize / 2;
|
|
14897
|
+
return Math.abs(x - centerX) <= tolerance && Math.abs(y - centerY) <= tolerance;
|
|
14898
|
+
}
|
|
14899
|
+
|
|
14877
14900
|
class SeriesMarkersRenderer {
|
|
14878
14901
|
constructor() {
|
|
14879
14902
|
this._private__data = null;
|
|
@@ -15017,6 +15040,9 @@ function drawShape(item, ctx, coordinates) {
|
|
|
15017
15040
|
case 'square':
|
|
15018
15041
|
drawSquare(ctx, coordinates, item._internal_size);
|
|
15019
15042
|
return;
|
|
15043
|
+
case 'xcross':
|
|
15044
|
+
drawXCross(ctx, coordinates, item._internal_size);
|
|
15045
|
+
return;
|
|
15020
15046
|
case 'tag':
|
|
15021
15047
|
// Handled by drawTagItem, which needs both pixel ratios and the text.
|
|
15022
15048
|
return;
|
|
@@ -15045,6 +15071,8 @@ function hitTestShape(item, x, y) {
|
|
|
15045
15071
|
return hitTestCircle(item._internal_x, item._internal_y, item._internal_size, x, y);
|
|
15046
15072
|
case 'square':
|
|
15047
15073
|
return hitTestSquare(item._internal_x, item._internal_y, item._internal_size, x, y);
|
|
15074
|
+
case 'xcross':
|
|
15075
|
+
return hitTestXCross(item._internal_x, item._internal_y, item._internal_size, x, y);
|
|
15048
15076
|
case 'tag':
|
|
15049
15077
|
return false;
|
|
15050
15078
|
}
|
|
@@ -21613,6 +21641,13 @@ class DrawingToolBase {
|
|
|
21613
21641
|
series() {
|
|
21614
21642
|
return this._series;
|
|
21615
21643
|
}
|
|
21644
|
+
/**
|
|
21645
|
+
* Box the selection toolbar anchors to, in pane pixels. Null falls back to
|
|
21646
|
+
* the serialized points, which cannot place a line across the whole pane.
|
|
21647
|
+
*/
|
|
21648
|
+
selectionBox() {
|
|
21649
|
+
return null;
|
|
21650
|
+
}
|
|
21616
21651
|
getState() {
|
|
21617
21652
|
return this._state;
|
|
21618
21653
|
}
|
|
@@ -22289,6 +22324,53 @@ function createTrendLineTool(context, options) {
|
|
|
22289
22324
|
return new TrendLineTool(context, options);
|
|
22290
22325
|
}
|
|
22291
22326
|
|
|
22327
|
+
const END_HANDLE_MARGIN = 10;
|
|
22328
|
+
/**
|
|
22329
|
+
* Hands the next `clicks` clicks on the chart to `onClick`, past the last bar
|
|
22330
|
+
* too. A press that grabbed another drawing does not count.
|
|
22331
|
+
*/
|
|
22332
|
+
function placeByClicks(chart, series, clicks, onClick) {
|
|
22333
|
+
let placed = 0;
|
|
22334
|
+
let handler = null;
|
|
22335
|
+
const stop = () => {
|
|
22336
|
+
if (handler !== null) {
|
|
22337
|
+
chart.unsubscribeClick(handler);
|
|
22338
|
+
handler = null;
|
|
22339
|
+
}
|
|
22340
|
+
};
|
|
22341
|
+
handler = (param) => {
|
|
22342
|
+
if (!param.point || consumeInputClaim(chart)) {
|
|
22343
|
+
return;
|
|
22344
|
+
}
|
|
22345
|
+
const price = series.coordinateToPrice(param.point.y);
|
|
22346
|
+
const time = timeFromCoordinate(chart, series, param.point.x);
|
|
22347
|
+
if (price === null || time === null) {
|
|
22348
|
+
return;
|
|
22349
|
+
}
|
|
22350
|
+
placed++;
|
|
22351
|
+
if (placed >= clicks) {
|
|
22352
|
+
stop();
|
|
22353
|
+
}
|
|
22354
|
+
onClick({ _internal_time: time, _internal_price: price, _internal_x: param.point.x, _internal_y: param.point.y }, placed - 1);
|
|
22355
|
+
};
|
|
22356
|
+
chart.subscribeClick(handler);
|
|
22357
|
+
return {
|
|
22358
|
+
_internal_pending: () => handler !== null,
|
|
22359
|
+
_internal_stop: stop,
|
|
22360
|
+
};
|
|
22361
|
+
}
|
|
22362
|
+
/** A fifth of the pane, and never less than a bar. */
|
|
22363
|
+
function defaultWidth(chart) {
|
|
22364
|
+
const timeScale = chart.timeScale();
|
|
22365
|
+
return Math.max(timeScale.width() / 5, timeScale.options().barSpacing);
|
|
22366
|
+
}
|
|
22367
|
+
/** Kept inside the pane: past it, the handles on the end hide under the price axis. */
|
|
22368
|
+
function placedEndX(chart, x) {
|
|
22369
|
+
const timeScale = chart.timeScale();
|
|
22370
|
+
const onScreen = Math.min(x + defaultWidth(chart), timeScale.width() - END_HANDLE_MARGIN);
|
|
22371
|
+
return Math.max(onScreen, x + timeScale.options().barSpacing);
|
|
22372
|
+
}
|
|
22373
|
+
|
|
22292
22374
|
class HorizontalLinePaneRenderer {
|
|
22293
22375
|
constructor(tool) {
|
|
22294
22376
|
this._private__tool = tool;
|
|
@@ -22329,6 +22411,7 @@ class HorizontalLineTool extends DrawingToolBase {
|
|
|
22329
22411
|
this._private__isDragging = false;
|
|
22330
22412
|
this._private__grab = null;
|
|
22331
22413
|
this._private__moveStartPrice = 0;
|
|
22414
|
+
this._private__placement = null;
|
|
22332
22415
|
this._private__hLineOptions = {
|
|
22333
22416
|
lineColor: '#2962ff',
|
|
22334
22417
|
lineWidth: 2,
|
|
@@ -22341,6 +22424,26 @@ class HorizontalLineTool extends DrawingToolBase {
|
|
|
22341
22424
|
};
|
|
22342
22425
|
this._registerOptions(this._private__hLineOptions);
|
|
22343
22426
|
this._private__createPrimitive();
|
|
22427
|
+
if (this._private__hLineOptions.interactive) {
|
|
22428
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
|
|
22429
|
+
}
|
|
22430
|
+
}
|
|
22431
|
+
_private__placeAt(point) {
|
|
22432
|
+
this._private__hLineOptions.price = point._internal_price;
|
|
22433
|
+
if (this._private__hLineOptions.startTime !== undefined) {
|
|
22434
|
+
this._private__hLineOptions.startTime = point._internal_time;
|
|
22435
|
+
}
|
|
22436
|
+
this._private__requestUpdate();
|
|
22437
|
+
}
|
|
22438
|
+
isComplete() {
|
|
22439
|
+
return !this._private__placement?._internal_pending();
|
|
22440
|
+
}
|
|
22441
|
+
selectionBox() {
|
|
22442
|
+
const y = this._private__hLineOptions.price === undefined ? null : this._series.priceToCoordinate(this._private__hLineOptions.price);
|
|
22443
|
+
if (y === null) {
|
|
22444
|
+
return null;
|
|
22445
|
+
}
|
|
22446
|
+
return { left: this._private__startX(), right: this._chart.timeScale().width(), top: y, bottom: y };
|
|
22344
22447
|
}
|
|
22345
22448
|
_private__createPrimitive() {
|
|
22346
22449
|
this._private__paneView = this._private__createPaneView();
|
|
@@ -22378,7 +22481,7 @@ class HorizontalLineTool extends DrawingToolBase {
|
|
|
22378
22481
|
return coordinateFromTime(this._chart, this._series, this._private__hLineOptions.startTime) ?? 0;
|
|
22379
22482
|
}
|
|
22380
22483
|
drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
22381
|
-
if (this._private__hLineOptions.price === undefined)
|
|
22484
|
+
if (!this.isComplete() || this._private__hLineOptions.price === undefined)
|
|
22382
22485
|
return;
|
|
22383
22486
|
const y = (this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0) * verticalPixelRatio;
|
|
22384
22487
|
const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
|
|
@@ -22472,7 +22575,7 @@ class HorizontalLineTool extends DrawingToolBase {
|
|
|
22472
22575
|
return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
|
|
22473
22576
|
}
|
|
22474
22577
|
hitTest(x, y) {
|
|
22475
|
-
if (this._private__hLineOptions.price === undefined) {
|
|
22578
|
+
if (!this.isComplete() || this._private__hLineOptions.price === undefined) {
|
|
22476
22579
|
return false;
|
|
22477
22580
|
}
|
|
22478
22581
|
const lineY = this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0;
|
|
@@ -22547,6 +22650,7 @@ class HorizontalLineTool extends DrawingToolBase {
|
|
|
22547
22650
|
this._private__requestUpdate();
|
|
22548
22651
|
}
|
|
22549
22652
|
remove() {
|
|
22653
|
+
this._private__placement?._internal_stop();
|
|
22550
22654
|
if (this._private__primitive) {
|
|
22551
22655
|
this._series.detachPrimitive(this._private__primitive);
|
|
22552
22656
|
this._private__primitive = null;
|
|
@@ -22606,6 +22710,7 @@ class CrossLineTool extends DrawingToolBase {
|
|
|
22606
22710
|
this._private__grab = null;
|
|
22607
22711
|
this._private__moveStartTime = 0;
|
|
22608
22712
|
this._private__moveStartPrice = 0;
|
|
22713
|
+
this._private__placement = null;
|
|
22609
22714
|
this._private__crossOptions = {
|
|
22610
22715
|
lineColor: '#787b86',
|
|
22611
22716
|
lineWidth: 1,
|
|
@@ -22615,6 +22720,16 @@ class CrossLineTool extends DrawingToolBase {
|
|
|
22615
22720
|
};
|
|
22616
22721
|
this._registerOptions(this._private__crossOptions);
|
|
22617
22722
|
this._private__createPrimitive();
|
|
22723
|
+
if (this._private__crossOptions.interactive) {
|
|
22724
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
|
|
22725
|
+
this._private__crossOptions.time = point._internal_time;
|
|
22726
|
+
this._private__crossOptions.price = point._internal_price;
|
|
22727
|
+
this._private__requestUpdate();
|
|
22728
|
+
});
|
|
22729
|
+
}
|
|
22730
|
+
}
|
|
22731
|
+
isComplete() {
|
|
22732
|
+
return !this._private__placement?._internal_pending();
|
|
22618
22733
|
}
|
|
22619
22734
|
_private__createPrimitive() {
|
|
22620
22735
|
this._private__paneView = new CrossLinePaneView(this);
|
|
@@ -22642,7 +22757,7 @@ class CrossLineTool extends DrawingToolBase {
|
|
|
22642
22757
|
this._private__requestUpdate();
|
|
22643
22758
|
}
|
|
22644
22759
|
_private__coordinates() {
|
|
22645
|
-
if (this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
|
|
22760
|
+
if (!this.isComplete() || this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
|
|
22646
22761
|
return null;
|
|
22647
22762
|
}
|
|
22648
22763
|
const x = coordinateFromTime(this._chart, this._series, this._private__crossOptions.time);
|
|
@@ -22765,6 +22880,7 @@ class CrossLineTool extends DrawingToolBase {
|
|
|
22765
22880
|
return this._private__isHovered;
|
|
22766
22881
|
}
|
|
22767
22882
|
remove() {
|
|
22883
|
+
this._private__placement?._internal_stop();
|
|
22768
22884
|
if (this._private__primitive) {
|
|
22769
22885
|
try {
|
|
22770
22886
|
this._series.detachPrimitive(this._private__primitive);
|
|
@@ -24187,6 +24303,7 @@ class VerticalLineTool extends DrawingToolBase {
|
|
|
24187
24303
|
this._private__isDragging = false;
|
|
24188
24304
|
this._private__grab = null;
|
|
24189
24305
|
this._private__moveStartTime = 0;
|
|
24306
|
+
this._private__placement = null;
|
|
24190
24307
|
this._private__vLineOptions = {
|
|
24191
24308
|
lineColor: '#787b86',
|
|
24192
24309
|
lineWidth: 1,
|
|
@@ -24198,6 +24315,29 @@ class VerticalLineTool extends DrawingToolBase {
|
|
|
24198
24315
|
};
|
|
24199
24316
|
this._registerOptions(this._private__vLineOptions);
|
|
24200
24317
|
this._private__createPrimitive();
|
|
24318
|
+
if (this._private__vLineOptions.interactive) {
|
|
24319
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
|
|
24320
|
+
this._private__vLineOptions.time = point._internal_time;
|
|
24321
|
+
this._private__requestUpdate();
|
|
24322
|
+
});
|
|
24323
|
+
}
|
|
24324
|
+
}
|
|
24325
|
+
isComplete() {
|
|
24326
|
+
return !this._private__placement?._internal_pending();
|
|
24327
|
+
}
|
|
24328
|
+
selectionBox() {
|
|
24329
|
+
const x = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time);
|
|
24330
|
+
if (x === null) {
|
|
24331
|
+
return null;
|
|
24332
|
+
}
|
|
24333
|
+
let middle;
|
|
24334
|
+
try {
|
|
24335
|
+
middle = this._series.getPane().getHeight() / 2;
|
|
24336
|
+
}
|
|
24337
|
+
catch (error) {
|
|
24338
|
+
return null;
|
|
24339
|
+
}
|
|
24340
|
+
return { left: x, right: x, top: middle, bottom: middle };
|
|
24201
24341
|
}
|
|
24202
24342
|
_private__createPrimitive() {
|
|
24203
24343
|
this._private__paneView = this._private__createPaneView();
|
|
@@ -24228,7 +24368,7 @@ class VerticalLineTool extends DrawingToolBase {
|
|
|
24228
24368
|
return new VerticalLinePaneView(this);
|
|
24229
24369
|
}
|
|
24230
24370
|
drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
24231
|
-
if (this._private__vLineOptions.time === undefined)
|
|
24371
|
+
if (!this.isComplete() || this._private__vLineOptions.time === undefined)
|
|
24232
24372
|
return;
|
|
24233
24373
|
const x = (coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0) * horizontalPixelRatio;
|
|
24234
24374
|
const chartHeight = this._chart.chartElement().clientHeight * verticalPixelRatio;
|
|
@@ -24315,7 +24455,7 @@ class VerticalLineTool extends DrawingToolBase {
|
|
|
24315
24455
|
return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
|
|
24316
24456
|
}
|
|
24317
24457
|
hitTest(x, y) {
|
|
24318
|
-
if (this._private__vLineOptions.time === undefined) {
|
|
24458
|
+
if (!this.isComplete() || this._private__vLineOptions.time === undefined) {
|
|
24319
24459
|
return false;
|
|
24320
24460
|
}
|
|
24321
24461
|
const lineX = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0;
|
|
@@ -24385,6 +24525,7 @@ class VerticalLineTool extends DrawingToolBase {
|
|
|
24385
24525
|
this._private__requestUpdate();
|
|
24386
24526
|
}
|
|
24387
24527
|
remove() {
|
|
24528
|
+
this._private__placement?._internal_stop();
|
|
24388
24529
|
if (this._private__primitive) {
|
|
24389
24530
|
this._series.detachPrimitive(this._private__primitive);
|
|
24390
24531
|
this._private__primitive = null;
|
|
@@ -25106,6 +25247,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
|
|
|
25106
25247
|
this._private__isHovered = false;
|
|
25107
25248
|
this._private__isDragging = false;
|
|
25108
25249
|
this._private__dragTarget = null;
|
|
25250
|
+
this._private__placement = null;
|
|
25109
25251
|
this._private__fibOptions = {
|
|
25110
25252
|
levels: [
|
|
25111
25253
|
{ level: 0, color: '#787B86', label: '0%' },
|
|
@@ -25121,6 +25263,20 @@ class FibonacciExtensionTool extends DrawingToolBase {
|
|
|
25121
25263
|
...options,
|
|
25122
25264
|
};
|
|
25123
25265
|
this._private__createPrimitive();
|
|
25266
|
+
if (this._private__fibOptions.interactive) {
|
|
25267
|
+
this._private__placement = placeByClicks(this._chart, this._series, 3, (point, index) => {
|
|
25268
|
+
const placed = { time: point._internal_time, price: point._internal_price };
|
|
25269
|
+
if (index === 0) {
|
|
25270
|
+
this.setPoint1(placed);
|
|
25271
|
+
}
|
|
25272
|
+
else if (index === 1) {
|
|
25273
|
+
this.setPoint2(placed);
|
|
25274
|
+
}
|
|
25275
|
+
else {
|
|
25276
|
+
this.setPoint3(placed);
|
|
25277
|
+
}
|
|
25278
|
+
});
|
|
25279
|
+
}
|
|
25124
25280
|
}
|
|
25125
25281
|
_private__createPrimitive() {
|
|
25126
25282
|
this._private__paneView = this._private__createPaneView();
|
|
@@ -25178,7 +25334,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
|
|
|
25178
25334
|
this._private__requestUpdate();
|
|
25179
25335
|
}
|
|
25180
25336
|
isComplete() {
|
|
25181
|
-
return !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
|
|
25337
|
+
return !this._private__placement?._internal_pending() && !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
|
|
25182
25338
|
}
|
|
25183
25339
|
setColor(color) {
|
|
25184
25340
|
this._private__fibOptions.lineColor = color;
|
|
@@ -25473,6 +25629,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
|
|
|
25473
25629
|
this._private__requestUpdate();
|
|
25474
25630
|
}
|
|
25475
25631
|
remove() {
|
|
25632
|
+
this._private__placement?._internal_stop();
|
|
25476
25633
|
if (this._private__primitive) {
|
|
25477
25634
|
this._series.detachPrimitive(this._private__primitive);
|
|
25478
25635
|
this._private__primitive = null;
|
|
@@ -26124,17 +26281,19 @@ class RectangleTool extends DrawingToolBase {
|
|
|
26124
26281
|
// Draw border
|
|
26125
26282
|
const borderColor = this._private__rectOptions.borderColor;
|
|
26126
26283
|
const borderWidth = this._private__isSelected ? this._private__rectOptions.borderWidth + 1 : this._private__rectOptions.borderWidth;
|
|
26127
|
-
|
|
26128
|
-
|
|
26129
|
-
|
|
26130
|
-
|
|
26131
|
-
|
|
26132
|
-
|
|
26133
|
-
|
|
26134
|
-
|
|
26284
|
+
if (borderWidth > 0) {
|
|
26285
|
+
ctx.strokeStyle = borderColor;
|
|
26286
|
+
ctx.lineWidth = borderWidth * horizontalPixelRatio;
|
|
26287
|
+
// Set border style
|
|
26288
|
+
if (this._private__rectOptions.borderStyle === 'dashed') {
|
|
26289
|
+
ctx.setLineDash([10 * horizontalPixelRatio, 5 * horizontalPixelRatio]);
|
|
26290
|
+
}
|
|
26291
|
+
else if (this._private__rectOptions.borderStyle === 'dotted') {
|
|
26292
|
+
ctx.setLineDash([2 * horizontalPixelRatio, 3 * horizontalPixelRatio]);
|
|
26293
|
+
}
|
|
26294
|
+
ctx.strokeRect(minX, minY, width, height);
|
|
26295
|
+
ctx.setLineDash([]);
|
|
26135
26296
|
}
|
|
26136
|
-
ctx.strokeRect(minX, minY, width, height);
|
|
26137
|
-
ctx.setLineDash([]);
|
|
26138
26297
|
// Draw corner handles if selected
|
|
26139
26298
|
if ((this._private__isSelected || this._private__isHovered) && !this._private__isDrawing && !this.isLocked()) {
|
|
26140
26299
|
this._private__drawCornerHandles(ctx, minX, minY, maxX, maxY, horizontalPixelRatio, borderColor);
|
|
@@ -26162,17 +26321,20 @@ class RectangleTool extends DrawingToolBase {
|
|
|
26162
26321
|
}
|
|
26163
26322
|
_private__drawLabel(ctx, minX, minY, maxX, maxY, horizontalPixelRatio, verticalPixelRatio, color) {
|
|
26164
26323
|
const padding = 6 * horizontalPixelRatio;
|
|
26165
|
-
const fontSize = 12 * horizontalPixelRatio;
|
|
26324
|
+
const fontSize = (this._private__rectOptions.labelFontSize ?? 12) * horizontalPixelRatio;
|
|
26166
26325
|
ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
|
|
26167
26326
|
const textMetrics = ctx.measureText(this._private__rectOptions.labelText);
|
|
26168
26327
|
const textWidth = textMetrics.width;
|
|
26169
26328
|
const textHeight = fontSize;
|
|
26329
|
+
const middleY = (minY + maxY) / 2 - textHeight / 2 - padding;
|
|
26330
|
+
// A box extended far past the pane keeps right-hand and centred labels on screen.
|
|
26331
|
+
const visibleMaxX = Math.min(maxX, ctx.canvas.width);
|
|
26170
26332
|
// Calculate label position based on labelPosition option
|
|
26171
26333
|
let labelX = minX + padding;
|
|
26172
26334
|
let labelY = minY + padding;
|
|
26173
26335
|
switch (this._private__rectOptions.labelPosition) {
|
|
26174
26336
|
case 'top-right':
|
|
26175
|
-
labelX =
|
|
26337
|
+
labelX = visibleMaxX - textWidth - padding * 2;
|
|
26176
26338
|
labelY = minY + padding;
|
|
26177
26339
|
break;
|
|
26178
26340
|
case 'bottom-left':
|
|
@@ -26180,19 +26342,27 @@ class RectangleTool extends DrawingToolBase {
|
|
|
26180
26342
|
labelY = maxY - textHeight - padding * 2;
|
|
26181
26343
|
break;
|
|
26182
26344
|
case 'bottom-right':
|
|
26183
|
-
labelX =
|
|
26345
|
+
labelX = visibleMaxX - textWidth - padding * 2;
|
|
26184
26346
|
labelY = maxY - textHeight - padding * 2;
|
|
26185
26347
|
break;
|
|
26186
26348
|
case 'center':
|
|
26187
|
-
labelX = (minX +
|
|
26188
|
-
labelY =
|
|
26349
|
+
labelX = (Math.max(minX, 0) + visibleMaxX) / 2 - textWidth / 2 - padding;
|
|
26350
|
+
labelY = middleY;
|
|
26351
|
+
break;
|
|
26352
|
+
case 'left':
|
|
26353
|
+
labelX = minX + padding;
|
|
26354
|
+
labelY = middleY;
|
|
26355
|
+
break;
|
|
26356
|
+
case 'right':
|
|
26357
|
+
labelX = visibleMaxX - textWidth - padding * 2;
|
|
26358
|
+
labelY = middleY;
|
|
26189
26359
|
break;
|
|
26190
26360
|
}
|
|
26191
26361
|
// Draw background
|
|
26192
|
-
ctx.fillStyle = color;
|
|
26362
|
+
ctx.fillStyle = this._private__rectOptions.labelBackgroundColor ?? color;
|
|
26193
26363
|
ctx.fillRect(labelX, labelY, textWidth + padding * 2, textHeight + padding * 2);
|
|
26194
26364
|
// Draw text
|
|
26195
|
-
ctx.fillStyle = '#ffffff';
|
|
26365
|
+
ctx.fillStyle = this._private__rectOptions.labelColor ?? '#ffffff';
|
|
26196
26366
|
ctx.textBaseline = 'top';
|
|
26197
26367
|
ctx.fillText(this._private__rectOptions.labelText, labelX + padding, labelY + padding);
|
|
26198
26368
|
}
|
|
@@ -26430,6 +26600,9 @@ class RectangleTool extends DrawingToolBase {
|
|
|
26430
26600
|
showLabel: this._private__rectOptions.showLabel,
|
|
26431
26601
|
labelText: this._private__rectOptions.labelText,
|
|
26432
26602
|
labelPosition: this._private__rectOptions.labelPosition,
|
|
26603
|
+
labelColor: this._private__rectOptions.labelColor,
|
|
26604
|
+
labelBackgroundColor: this._private__rectOptions.labelBackgroundColor,
|
|
26605
|
+
labelFontSize: this._private__rectOptions.labelFontSize,
|
|
26433
26606
|
},
|
|
26434
26607
|
};
|
|
26435
26608
|
}
|
|
@@ -28022,6 +28195,7 @@ class TextTool extends DrawingToolBase {
|
|
|
28022
28195
|
this._private__moveStart = null;
|
|
28023
28196
|
this._private__isEditing = false;
|
|
28024
28197
|
this._private__clickOutsideHandler = null;
|
|
28198
|
+
this._private__placement = null;
|
|
28025
28199
|
this._private__textOptions = {
|
|
28026
28200
|
text: 'Double-click to edit',
|
|
28027
28201
|
fontSize: 14,
|
|
@@ -28034,11 +28208,22 @@ class TextTool extends DrawingToolBase {
|
|
|
28034
28208
|
borderWidth: 1,
|
|
28035
28209
|
textAlign: 'left',
|
|
28036
28210
|
maxWidth: 200,
|
|
28211
|
+
anchor: 'top-left',
|
|
28212
|
+
pointer: false,
|
|
28213
|
+
borderRadius: 0,
|
|
28037
28214
|
...options,
|
|
28038
28215
|
};
|
|
28039
28216
|
this._registerOptions(this._private__textOptions);
|
|
28040
28217
|
this._private__createPrimitive();
|
|
28041
28218
|
this._private__setupClickOutsideHandler();
|
|
28219
|
+
if (this._private__textOptions.interactive) {
|
|
28220
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
|
|
28221
|
+
this.setPosition(point._internal_time, point._internal_price);
|
|
28222
|
+
if (this._private__textOptions.onComplete) {
|
|
28223
|
+
this._private__textOptions.onComplete();
|
|
28224
|
+
}
|
|
28225
|
+
});
|
|
28226
|
+
}
|
|
28042
28227
|
}
|
|
28043
28228
|
_private__setupClickOutsideHandler() {
|
|
28044
28229
|
// Set up click handler to finish editing when clicking outside
|
|
@@ -28124,7 +28309,7 @@ class TextTool extends DrawingToolBase {
|
|
|
28124
28309
|
this._private__requestUpdate();
|
|
28125
28310
|
}
|
|
28126
28311
|
isComplete() {
|
|
28127
|
-
return !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
|
|
28312
|
+
return !this._private__placement?._internal_pending() && !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
|
|
28128
28313
|
}
|
|
28129
28314
|
setColor(color) {
|
|
28130
28315
|
this._private__textOptions.textColor = color;
|
|
@@ -28185,18 +28370,59 @@ class TextTool extends DrawingToolBase {
|
|
|
28185
28370
|
const py = this._series.priceToCoordinate(price) ?? 0;
|
|
28186
28371
|
// Calculate text box dimensions
|
|
28187
28372
|
const fontSize = this._private__textOptions.fontSize || 14;
|
|
28188
|
-
const padding = this._private__textOptions.padding
|
|
28373
|
+
const padding = this._private__textOptions.padding ?? 8;
|
|
28189
28374
|
const text = this._private__textOptions.text || '';
|
|
28190
28375
|
// Rough estimate of text width (will be more accurate in canvas)
|
|
28191
28376
|
const charWidth = fontSize * 0.6;
|
|
28192
28377
|
const textWidth = Math.min(text.length * charWidth, this._private__textOptions.maxWidth || 200);
|
|
28193
|
-
const
|
|
28194
|
-
|
|
28195
|
-
const
|
|
28196
|
-
const
|
|
28197
|
-
const
|
|
28198
|
-
const
|
|
28199
|
-
return x >= left && x <= right && y >=
|
|
28378
|
+
const boxWidth = textWidth + padding * 2;
|
|
28379
|
+
const boxHeight = fontSize + padding * 2;
|
|
28380
|
+
const tail = this._private__tailSize(boxHeight);
|
|
28381
|
+
const box = this._private__boxOrigin(px, py, boxWidth, boxHeight, tail);
|
|
28382
|
+
const left = box._internal_left - (this._private__textOptions.anchor === 'left' ? tail : 0);
|
|
28383
|
+
const right = box._internal_left + boxWidth + (this._private__textOptions.anchor === 'right' ? tail : 0);
|
|
28384
|
+
return x >= left && x <= right && y >= box._internal_top && y <= box._internal_top + boxHeight;
|
|
28385
|
+
}
|
|
28386
|
+
_private__tailSize(boxHeight) {
|
|
28387
|
+
const anchor = this._private__textOptions.anchor;
|
|
28388
|
+
return this._private__textOptions.pointer && (anchor === 'left' || anchor === 'right') ? Math.round(boxHeight * 0.35) : 0;
|
|
28389
|
+
}
|
|
28390
|
+
_private__boxOrigin(x, y, width, height, tail) {
|
|
28391
|
+
switch (this._private__textOptions.anchor) {
|
|
28392
|
+
case 'left':
|
|
28393
|
+
return { _internal_left: x + tail, _internal_top: y - height / 2 };
|
|
28394
|
+
case 'right':
|
|
28395
|
+
return { _internal_left: x - tail - width, _internal_top: y - height / 2 };
|
|
28396
|
+
case 'center':
|
|
28397
|
+
return { _internal_left: x - width / 2, _internal_top: y - height / 2 };
|
|
28398
|
+
default:
|
|
28399
|
+
return { _internal_left: x, _internal_top: y };
|
|
28400
|
+
}
|
|
28401
|
+
}
|
|
28402
|
+
// eslint-disable-next-line max-params
|
|
28403
|
+
_private__traceBox(ctx, left, top, width, height, tail, anchorX, anchorY, radius) {
|
|
28404
|
+
const r = Math.max(0, Math.min(radius, width / 2, height / 2));
|
|
28405
|
+
ctx.beginPath();
|
|
28406
|
+
ctx.moveTo(left + r, top);
|
|
28407
|
+
ctx.lineTo(left + width - r, top);
|
|
28408
|
+
ctx.arcTo(left + width, top, left + width, top + r, r);
|
|
28409
|
+
if (tail > 0 && this._private__textOptions.anchor === 'right') {
|
|
28410
|
+
ctx.lineTo(left + width, anchorY - tail);
|
|
28411
|
+
ctx.lineTo(anchorX, anchorY);
|
|
28412
|
+
ctx.lineTo(left + width, anchorY + tail);
|
|
28413
|
+
}
|
|
28414
|
+
ctx.lineTo(left + width, top + height - r);
|
|
28415
|
+
ctx.arcTo(left + width, top + height, left + width - r, top + height, r);
|
|
28416
|
+
ctx.lineTo(left + r, top + height);
|
|
28417
|
+
ctx.arcTo(left, top + height, left, top + height - r, r);
|
|
28418
|
+
if (tail > 0 && this._private__textOptions.anchor === 'left') {
|
|
28419
|
+
ctx.lineTo(left, anchorY + tail);
|
|
28420
|
+
ctx.lineTo(anchorX, anchorY);
|
|
28421
|
+
ctx.lineTo(left, anchorY - tail);
|
|
28422
|
+
}
|
|
28423
|
+
ctx.lineTo(left, top + r);
|
|
28424
|
+
ctx.arcTo(left, top, left + r, top, r);
|
|
28425
|
+
ctx.closePath();
|
|
28200
28426
|
}
|
|
28201
28427
|
hitTestEndpoint(x, y) {
|
|
28202
28428
|
if (!this.isComplete())
|
|
@@ -28248,7 +28474,7 @@ class TextTool extends DrawingToolBase {
|
|
|
28248
28474
|
const x = (coordinateFromTime(this._chart, this._series, time) ?? 0) * horizontalPixelRatio;
|
|
28249
28475
|
const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
|
|
28250
28476
|
const fontSize = (this._private__textOptions.fontSize || 14) * horizontalPixelRatio;
|
|
28251
|
-
const padding = (this._private__textOptions.padding
|
|
28477
|
+
const padding = (this._private__textOptions.padding ?? 8) * horizontalPixelRatio;
|
|
28252
28478
|
const maxWidth = (this._private__textOptions.maxWidth || 200) * horizontalPixelRatio;
|
|
28253
28479
|
// Set font
|
|
28254
28480
|
ctx.font = `${fontSize}px ${this._private__textOptions.fontFamily || 'Arial'}`;
|
|
@@ -28280,32 +28506,39 @@ class TextTool extends DrawingToolBase {
|
|
|
28280
28506
|
const boxWidth = maxLineWidth + padding * 2;
|
|
28281
28507
|
const lineHeight = fontSize * 1.2;
|
|
28282
28508
|
const boxHeight = lines.length * lineHeight + padding * 2;
|
|
28509
|
+
const tail = this._private__tailSize(boxHeight);
|
|
28510
|
+
const { _internal_left: left, _internal_top: top } = this._private__boxOrigin(x, y, boxWidth, boxHeight, tail);
|
|
28511
|
+
const radius = (this._private__textOptions.borderRadius ?? 0) * horizontalPixelRatio;
|
|
28283
28512
|
// Draw background
|
|
28284
|
-
const bgOpacity = this._private__textOptions.backgroundOpacity
|
|
28513
|
+
const bgOpacity = this._private__textOptions.backgroundOpacity ?? 0.8;
|
|
28285
28514
|
const bgColor = this._private__textOptions.backgroundColor || '#2962FF';
|
|
28286
|
-
|
|
28287
|
-
|
|
28515
|
+
if (bgOpacity > 0) {
|
|
28516
|
+
ctx.fillStyle = this._private__hexToRgba(bgColor, bgOpacity);
|
|
28517
|
+
this._private__traceBox(ctx, left, top, boxWidth, boxHeight, tail, x, y, radius);
|
|
28518
|
+
ctx.fill();
|
|
28519
|
+
}
|
|
28288
28520
|
// Draw border
|
|
28289
28521
|
if (this._private__isSelected || this._private__isHovered) {
|
|
28290
28522
|
ctx.strokeStyle = this._private__textOptions.borderColor ?? '#FFFFFF';
|
|
28291
28523
|
ctx.lineWidth = (this._private__textOptions.borderWidth || 1) * horizontalPixelRatio * (this._private__isSelected ? 2 : 1);
|
|
28292
|
-
|
|
28524
|
+
this._private__traceBox(ctx, left, top, boxWidth, boxHeight, tail, x, y, radius);
|
|
28525
|
+
ctx.stroke();
|
|
28293
28526
|
}
|
|
28294
28527
|
// Draw text
|
|
28295
28528
|
ctx.fillStyle = this._private__textOptions.textColor || '#FFFFFF';
|
|
28296
28529
|
for (let i = 0; i < lines.length; i++) {
|
|
28297
28530
|
const line = lines[i];
|
|
28298
|
-
let textX =
|
|
28531
|
+
let textX = left + padding;
|
|
28299
28532
|
// Handle text alignment
|
|
28300
28533
|
if (this._private__textOptions.textAlign === 'center') {
|
|
28301
28534
|
const metrics = ctx.measureText(line);
|
|
28302
|
-
textX =
|
|
28535
|
+
textX = left + (boxWidth - metrics.width) / 2;
|
|
28303
28536
|
}
|
|
28304
28537
|
else if (this._private__textOptions.textAlign === 'right') {
|
|
28305
28538
|
const metrics = ctx.measureText(line);
|
|
28306
|
-
textX =
|
|
28539
|
+
textX = left + boxWidth - metrics.width - padding;
|
|
28307
28540
|
}
|
|
28308
|
-
const textY =
|
|
28541
|
+
const textY = top + padding + i * lineHeight;
|
|
28309
28542
|
ctx.fillText(line, textX, textY);
|
|
28310
28543
|
}
|
|
28311
28544
|
// Draw anchor point if selected
|
|
@@ -28349,6 +28582,9 @@ class TextTool extends DrawingToolBase {
|
|
|
28349
28582
|
borderWidth: this._private__textOptions.borderWidth,
|
|
28350
28583
|
textAlign: this._private__textOptions.textAlign,
|
|
28351
28584
|
maxWidth: this._private__textOptions.maxWidth,
|
|
28585
|
+
anchor: this._private__textOptions.anchor,
|
|
28586
|
+
pointer: this._private__textOptions.pointer,
|
|
28587
|
+
borderRadius: this._private__textOptions.borderRadius,
|
|
28352
28588
|
},
|
|
28353
28589
|
extra: {
|
|
28354
28590
|
text: this._private__textOptions.text,
|
|
@@ -28356,6 +28592,7 @@ class TextTool extends DrawingToolBase {
|
|
|
28356
28592
|
};
|
|
28357
28593
|
}
|
|
28358
28594
|
remove() {
|
|
28595
|
+
this._private__placement?._internal_stop();
|
|
28359
28596
|
// Clean up click handler
|
|
28360
28597
|
if (this._private__clickOutsideHandler) {
|
|
28361
28598
|
this._chart.unsubscribeClick(this._private__clickOutsideHandler);
|
|
@@ -29214,6 +29451,7 @@ class PositionTool extends DrawingToolBase {
|
|
|
29214
29451
|
this._private__paneView = null;
|
|
29215
29452
|
this._private__isSelected = false;
|
|
29216
29453
|
this._private__isHovered = false;
|
|
29454
|
+
this._private__placement = null;
|
|
29217
29455
|
this._private__isDragging = false;
|
|
29218
29456
|
this._private__dragTarget = null;
|
|
29219
29457
|
this._private__grab = null;
|
|
@@ -29236,6 +29474,32 @@ class PositionTool extends DrawingToolBase {
|
|
|
29236
29474
|
// Create primitive and setup
|
|
29237
29475
|
this._private__createPrimitive();
|
|
29238
29476
|
this._private__updateMetrics();
|
|
29477
|
+
if (this._private__positionOptions.interactive) {
|
|
29478
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
|
|
29479
|
+
}
|
|
29480
|
+
}
|
|
29481
|
+
_private__placeAt(point) {
|
|
29482
|
+
const options = this._private__positionOptions;
|
|
29483
|
+
const shift = point._internal_price - options.entryPrice;
|
|
29484
|
+
options.entryPrice = point._internal_price;
|
|
29485
|
+
options.entryTime = point._internal_time;
|
|
29486
|
+
if (options.stopLoss !== undefined) {
|
|
29487
|
+
options.stopLoss += shift;
|
|
29488
|
+
}
|
|
29489
|
+
if (options.takeProfit !== undefined) {
|
|
29490
|
+
options.takeProfit += shift;
|
|
29491
|
+
}
|
|
29492
|
+
options.endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? undefined;
|
|
29493
|
+
this._private__updateMetrics();
|
|
29494
|
+
this._private__requestUpdate();
|
|
29495
|
+
}
|
|
29496
|
+
isComplete() {
|
|
29497
|
+
return !this._private__placement?._internal_pending();
|
|
29498
|
+
}
|
|
29499
|
+
_private__span() {
|
|
29500
|
+
const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
|
|
29501
|
+
const endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? entryX + defaultWidth(this._chart);
|
|
29502
|
+
return { _internal_entryX: entryX, _internal_endX: endX };
|
|
29239
29503
|
}
|
|
29240
29504
|
_private__createPrimitive() {
|
|
29241
29505
|
// Create and cache the paneView once
|
|
@@ -29267,6 +29531,9 @@ class PositionTool extends DrawingToolBase {
|
|
|
29267
29531
|
return new PositionPaneView(this);
|
|
29268
29532
|
}
|
|
29269
29533
|
drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
29534
|
+
if (!this.isComplete()) {
|
|
29535
|
+
return;
|
|
29536
|
+
}
|
|
29270
29537
|
// Draw profit rectangle (green)
|
|
29271
29538
|
if (this._private__profitRect && this._private__positionOptions.takeProfit) {
|
|
29272
29539
|
const timeScale = this._chart.timeScale();
|
|
@@ -29300,16 +29567,9 @@ class PositionTool extends DrawingToolBase {
|
|
|
29300
29567
|
}
|
|
29301
29568
|
}
|
|
29302
29569
|
_private__drawRectangle(ctx, rect, horizontalPixelRatio, verticalPixelRatio, timeScale) {
|
|
29303
|
-
const
|
|
29304
|
-
|
|
29305
|
-
|
|
29306
|
-
if (this._private__positionOptions.endTime) {
|
|
29307
|
-
endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
|
|
29308
|
-
}
|
|
29309
|
-
else {
|
|
29310
|
-
const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
|
|
29311
|
-
endX = chartWidth;
|
|
29312
|
-
}
|
|
29570
|
+
const span = this._private__span();
|
|
29571
|
+
const entryX = span._internal_entryX * horizontalPixelRatio;
|
|
29572
|
+
const endX = span._internal_endX * horizontalPixelRatio;
|
|
29313
29573
|
const y1 = (this._series.priceToCoordinate(rect._internal_price1) ?? 0) * verticalPixelRatio;
|
|
29314
29574
|
const y2 = (this._series.priceToCoordinate(rect._internal_price2) ?? 0) * verticalPixelRatio;
|
|
29315
29575
|
// Draw filled rectangle with semi-transparent fill
|
|
@@ -29324,16 +29584,10 @@ class PositionTool extends DrawingToolBase {
|
|
|
29324
29584
|
ctx.stroke();
|
|
29325
29585
|
}
|
|
29326
29586
|
_private__drawEntryLine(ctx, horizontalPixelRatio, verticalPixelRatio, timeScale) {
|
|
29327
|
-
const
|
|
29587
|
+
const span = this._private__span();
|
|
29588
|
+
const entryX = span._internal_entryX * horizontalPixelRatio;
|
|
29589
|
+
const endX = span._internal_endX * horizontalPixelRatio;
|
|
29328
29590
|
const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
|
|
29329
|
-
let endX;
|
|
29330
|
-
if (this._private__positionOptions.endTime) {
|
|
29331
|
-
endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
|
|
29332
|
-
}
|
|
29333
|
-
else {
|
|
29334
|
-
const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
|
|
29335
|
-
endX = chartWidth;
|
|
29336
|
-
}
|
|
29337
29591
|
// Draw horizontal entry line (divider)
|
|
29338
29592
|
const lineColor = this._private__positionOptions.entryLineColor;
|
|
29339
29593
|
ctx.strokeStyle = lineColor;
|
|
@@ -29447,9 +29701,11 @@ class PositionTool extends DrawingToolBase {
|
|
|
29447
29701
|
const padding = 6 * horizontalPixelRatio;
|
|
29448
29702
|
const labelWidth = textMetrics.width + padding * 2;
|
|
29449
29703
|
const labelHeight = 20 * horizontalPixelRatio;
|
|
29450
|
-
//
|
|
29704
|
+
// Outside the box, so a label wider than it does not cover the handles on its edge
|
|
29705
|
+
const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
|
|
29706
|
+
const gap = 3 * verticalPixelRatio;
|
|
29451
29707
|
const labelX = entryX + 8 * horizontalPixelRatio;
|
|
29452
|
-
const labelY = y - labelHeight
|
|
29708
|
+
const labelY = y <= entryY ? y - labelHeight - gap : y + gap;
|
|
29453
29709
|
// Draw label background
|
|
29454
29710
|
ctx.fillStyle = rect._internal_color;
|
|
29455
29711
|
ctx.fillRect(labelX, labelY, labelWidth, labelHeight);
|
|
@@ -29457,10 +29713,12 @@ class PositionTool extends DrawingToolBase {
|
|
|
29457
29713
|
ctx.fillStyle = '#ffffff';
|
|
29458
29714
|
ctx.textAlign = 'left';
|
|
29459
29715
|
ctx.textBaseline = 'middle';
|
|
29460
|
-
ctx.fillText(labelText, labelX + padding,
|
|
29716
|
+
ctx.fillText(labelText, labelX + padding, labelY + labelHeight / 2);
|
|
29461
29717
|
}
|
|
29462
29718
|
_private__drawEndpoint(ctx, price, color, horizontalPixelRatio, verticalPixelRatio, timeScale) {
|
|
29463
|
-
const
|
|
29719
|
+
const span = this._private__span();
|
|
29720
|
+
const entryX = span._internal_entryX * horizontalPixelRatio;
|
|
29721
|
+
const endX = span._internal_endX * horizontalPixelRatio;
|
|
29464
29722
|
const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
|
|
29465
29723
|
// Draw circle endpoint at entry
|
|
29466
29724
|
const circleColor = color;
|
|
@@ -29472,14 +29730,6 @@ class PositionTool extends DrawingToolBase {
|
|
|
29472
29730
|
ctx.fill();
|
|
29473
29731
|
ctx.stroke();
|
|
29474
29732
|
// Draw square endpoint at the right edge
|
|
29475
|
-
let endX;
|
|
29476
|
-
if (this._private__positionOptions.endTime) {
|
|
29477
|
-
endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
|
|
29478
|
-
}
|
|
29479
|
-
else {
|
|
29480
|
-
const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
|
|
29481
|
-
endX = chartWidth - 10 * horizontalPixelRatio;
|
|
29482
|
-
}
|
|
29483
29733
|
const size = 8 * horizontalPixelRatio;
|
|
29484
29734
|
const squareColor = color;
|
|
29485
29735
|
ctx.fillStyle = squareColor;
|
|
@@ -29600,18 +29850,12 @@ class PositionTool extends DrawingToolBase {
|
|
|
29600
29850
|
return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
|
|
29601
29851
|
}
|
|
29602
29852
|
hitTest(x, y) {
|
|
29603
|
-
if (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit) {
|
|
29853
|
+
if (!this.isComplete() || (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit)) {
|
|
29604
29854
|
return false;
|
|
29605
29855
|
}
|
|
29606
|
-
const
|
|
29607
|
-
|
|
29608
|
-
|
|
29609
|
-
endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
|
|
29610
|
-
}
|
|
29611
|
-
else {
|
|
29612
|
-
const chartWidth = this._chart.chartElement().clientWidth;
|
|
29613
|
-
endX = chartWidth;
|
|
29614
|
-
}
|
|
29856
|
+
const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
|
|
29857
|
+
const left = Math.min(entryX, endX);
|
|
29858
|
+
const right = Math.max(entryX, endX);
|
|
29615
29859
|
// Check if inside the entire Position Tool area (rectangles)
|
|
29616
29860
|
if (this._private__positionOptions.takeProfit && this._private__positionOptions.stopLoss) {
|
|
29617
29861
|
const tpY = this._series.priceToCoordinate(this._private__positionOptions.takeProfit) ?? 0;
|
|
@@ -29619,7 +29863,7 @@ class PositionTool extends DrawingToolBase {
|
|
|
29619
29863
|
const minY = Math.min(tpY, slY);
|
|
29620
29864
|
const maxY = Math.max(tpY, slY);
|
|
29621
29865
|
// Check if inside the rectangle bounds (entire tool area)
|
|
29622
|
-
if (x >=
|
|
29866
|
+
if (x >= left && x <= right && y >= minY && y <= maxY) {
|
|
29623
29867
|
return true;
|
|
29624
29868
|
}
|
|
29625
29869
|
// Also check entry line area
|
|
@@ -29631,15 +29875,12 @@ class PositionTool extends DrawingToolBase {
|
|
|
29631
29875
|
return false;
|
|
29632
29876
|
}
|
|
29633
29877
|
hitTestEndpoint(x, y) {
|
|
29634
|
-
|
|
29635
|
-
|
|
29636
|
-
if (this._private__positionOptions.endTime) {
|
|
29637
|
-
endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
|
|
29638
|
-
}
|
|
29639
|
-
else {
|
|
29640
|
-
const chartWidth = this._chart.chartElement().clientWidth;
|
|
29641
|
-
endX = chartWidth;
|
|
29878
|
+
if (!this.isComplete()) {
|
|
29879
|
+
return null;
|
|
29642
29880
|
}
|
|
29881
|
+
const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
|
|
29882
|
+
const left = Math.min(entryX, endX);
|
|
29883
|
+
const right = Math.max(entryX, endX);
|
|
29643
29884
|
const tolerance = 10;
|
|
29644
29885
|
// Check TP left endpoint (circle)
|
|
29645
29886
|
if (this._private__positionOptions.takeProfit) {
|
|
@@ -29676,7 +29917,7 @@ class PositionTool extends DrawingToolBase {
|
|
|
29676
29917
|
// Check entry line (middle divider line) - higher priority than 'move'
|
|
29677
29918
|
const entryY = this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0;
|
|
29678
29919
|
const lineTolerance = 8;
|
|
29679
|
-
if (x >=
|
|
29920
|
+
if (x >= left && x <= right && Math.abs(y - entryY) <= lineTolerance) {
|
|
29680
29921
|
return 'entry';
|
|
29681
29922
|
}
|
|
29682
29923
|
// Check if clicking on the body (rectangles) for moving entire tool
|
|
@@ -29686,7 +29927,7 @@ class PositionTool extends DrawingToolBase {
|
|
|
29686
29927
|
const minY = Math.min(tpY, slY);
|
|
29687
29928
|
const maxY = Math.max(tpY, slY);
|
|
29688
29929
|
// Check if inside the rectangle bounds
|
|
29689
|
-
if (x >=
|
|
29930
|
+
if (x >= left && x <= right && y >= minY && y <= maxY) {
|
|
29690
29931
|
return 'move';
|
|
29691
29932
|
}
|
|
29692
29933
|
}
|
|
@@ -29786,6 +30027,7 @@ class PositionTool extends DrawingToolBase {
|
|
|
29786
30027
|
this._private__requestUpdate();
|
|
29787
30028
|
}
|
|
29788
30029
|
remove() {
|
|
30030
|
+
this._private__placement?._internal_stop();
|
|
29789
30031
|
if (this._private__primitive) {
|
|
29790
30032
|
this._series.detachPrimitive(this._private__primitive);
|
|
29791
30033
|
this._private__primitive = null;
|
|
@@ -29832,6 +30074,7 @@ class GanttPaneView {
|
|
|
29832
30074
|
}
|
|
29833
30075
|
}
|
|
29834
30076
|
|
|
30077
|
+
const TOP_PADDING = 50;
|
|
29835
30078
|
class GanttTool extends DrawingToolBase {
|
|
29836
30079
|
constructor(context, options) {
|
|
29837
30080
|
super(context, 'gantt', options);
|
|
@@ -29845,6 +30088,7 @@ class GanttTool extends DrawingToolBase {
|
|
|
29845
30088
|
this._private__grab = null;
|
|
29846
30089
|
this._private__moveStart = null;
|
|
29847
30090
|
this._private__hoveredSegmentIndex = -1;
|
|
30091
|
+
this._private__placement = null;
|
|
29848
30092
|
this._private__ganttOptions = {
|
|
29849
30093
|
segments: [],
|
|
29850
30094
|
rowHeight: 40,
|
|
@@ -29857,6 +30101,43 @@ class GanttTool extends DrawingToolBase {
|
|
|
29857
30101
|
};
|
|
29858
30102
|
this._registerOptions(this._private__ganttOptions);
|
|
29859
30103
|
this._private__createPrimitive();
|
|
30104
|
+
if (this._private__ganttOptions.interactive) {
|
|
30105
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
|
|
30106
|
+
}
|
|
30107
|
+
}
|
|
30108
|
+
_private__placeAt(point) {
|
|
30109
|
+
const segments = this._private__ganttOptions.segments ?? [];
|
|
30110
|
+
const endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? point._internal_time;
|
|
30111
|
+
const rowPitch = this._private__ganttOptions.rowHeight + this._private__ganttOptions.rowSpacing;
|
|
30112
|
+
const row = Math.max(0, Math.floor((point._internal_y - TOP_PADDING) / rowPitch));
|
|
30113
|
+
const shift = segments.length > 0 ? row - segments[0].level : 0;
|
|
30114
|
+
for (const segment of segments) {
|
|
30115
|
+
segment.startTime = point._internal_time;
|
|
30116
|
+
segment.endTime = endTime;
|
|
30117
|
+
segment.level = Math.max(0, segment.level + shift);
|
|
30118
|
+
}
|
|
30119
|
+
this._private__requestUpdate();
|
|
30120
|
+
}
|
|
30121
|
+
isComplete() {
|
|
30122
|
+
return !this._private__placement?._internal_pending();
|
|
30123
|
+
}
|
|
30124
|
+
selectionBox() {
|
|
30125
|
+
const segments = this._private__ganttOptions.segments ?? [];
|
|
30126
|
+
if (segments.length === 0) {
|
|
30127
|
+
return null;
|
|
30128
|
+
}
|
|
30129
|
+
const rowHeight = this._private__ganttOptions.rowHeight;
|
|
30130
|
+
const pitch = rowHeight + this._private__ganttOptions.rowSpacing;
|
|
30131
|
+
// Typed as the public shape: an inferred one gets its keys renamed by the build
|
|
30132
|
+
const box = { left: Infinity, top: Infinity, right: -Infinity, bottom: -Infinity };
|
|
30133
|
+
for (const segment of segments) {
|
|
30134
|
+
const top = TOP_PADDING + segment.level * pitch;
|
|
30135
|
+
box.left = Math.min(box.left, coordinateFromTime(this._chart, this._series, segment.startTime) ?? 0);
|
|
30136
|
+
box.right = Math.max(box.right, coordinateFromTime(this._chart, this._series, segment.endTime) ?? 0);
|
|
30137
|
+
box.top = Math.min(box.top, top);
|
|
30138
|
+
box.bottom = Math.max(box.bottom, top + rowHeight);
|
|
30139
|
+
}
|
|
30140
|
+
return box;
|
|
29860
30141
|
}
|
|
29861
30142
|
_private__createPrimitive() {
|
|
29862
30143
|
this._private__paneView = this._private__createPaneView();
|
|
@@ -29887,11 +30168,10 @@ class GanttTool extends DrawingToolBase {
|
|
|
29887
30168
|
return new GanttPaneView(this);
|
|
29888
30169
|
}
|
|
29889
30170
|
drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
29890
|
-
if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
|
|
30171
|
+
if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
|
|
29891
30172
|
return;
|
|
29892
30173
|
// Position gantt at top of chart with padding
|
|
29893
|
-
const
|
|
29894
|
-
const baseY = topPadding * verticalPixelRatio;
|
|
30174
|
+
const baseY = TOP_PADDING * verticalPixelRatio;
|
|
29895
30175
|
const rowHeight = this._private__ganttOptions.rowHeight * verticalPixelRatio;
|
|
29896
30176
|
const rowSpacing = this._private__ganttOptions.rowSpacing * verticalPixelRatio;
|
|
29897
30177
|
// Draw grid lines if enabled
|
|
@@ -30070,11 +30350,10 @@ class GanttTool extends DrawingToolBase {
|
|
|
30070
30350
|
this._private__moveStart = null;
|
|
30071
30351
|
}
|
|
30072
30352
|
hitTest(x, y) {
|
|
30073
|
-
if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
|
|
30353
|
+
if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
|
|
30074
30354
|
return false;
|
|
30075
30355
|
}
|
|
30076
|
-
const
|
|
30077
|
-
const baseY = topPadding;
|
|
30356
|
+
const baseY = TOP_PADDING;
|
|
30078
30357
|
const rowHeight = this._private__ganttOptions.rowHeight;
|
|
30079
30358
|
const rowSpacing = this._private__ganttOptions.rowSpacing;
|
|
30080
30359
|
// Check each segment
|
|
@@ -30128,6 +30407,7 @@ class GanttTool extends DrawingToolBase {
|
|
|
30128
30407
|
this._private__requestUpdate();
|
|
30129
30408
|
}
|
|
30130
30409
|
remove() {
|
|
30410
|
+
this._private__placement?._internal_stop();
|
|
30131
30411
|
if (this._private__primitive) {
|
|
30132
30412
|
this._series.detachPrimitive(this._private__primitive);
|
|
30133
30413
|
this._private__primitive = null;
|
|
@@ -30263,6 +30543,7 @@ class DrawingToolsManager {
|
|
|
30263
30543
|
}
|
|
30264
30544
|
this._private__completionCheck = setTimeout(() => {
|
|
30265
30545
|
this._private__completionCheck = null;
|
|
30546
|
+
const completed = [];
|
|
30266
30547
|
for (const id of Array.from(this._private__pendingToolIds)) {
|
|
30267
30548
|
const tool = this._private__tools.get(id);
|
|
30268
30549
|
if (!tool) {
|
|
@@ -30271,9 +30552,15 @@ class DrawingToolsManager {
|
|
|
30271
30552
|
}
|
|
30272
30553
|
if (tool.isComplete()) {
|
|
30273
30554
|
this._private__pendingToolIds.delete(id);
|
|
30274
|
-
|
|
30555
|
+
completed.push(id);
|
|
30275
30556
|
}
|
|
30276
30557
|
}
|
|
30558
|
+
if (completed.length > 0) {
|
|
30559
|
+
this.recordChange();
|
|
30560
|
+
}
|
|
30561
|
+
for (const id of completed) {
|
|
30562
|
+
this._private__drawingCompleted._internal_fire(id);
|
|
30563
|
+
}
|
|
30277
30564
|
}, 0);
|
|
30278
30565
|
}
|
|
30279
30566
|
/**
|
|
@@ -30333,12 +30620,16 @@ class DrawingToolsManager {
|
|
|
30333
30620
|
if (this._private__restoring) {
|
|
30334
30621
|
return;
|
|
30335
30622
|
}
|
|
30623
|
+
const next = this.serializeAll();
|
|
30624
|
+
if (JSON.stringify(next) === JSON.stringify(this._private__committed)) {
|
|
30625
|
+
return;
|
|
30626
|
+
}
|
|
30336
30627
|
this._private__past.push(this._private__committed);
|
|
30337
30628
|
if (this._private__past.length > UNDO_HISTORY_LIMIT) {
|
|
30338
30629
|
this._private__past.shift();
|
|
30339
30630
|
}
|
|
30340
30631
|
this._private__future = [];
|
|
30341
|
-
this._private__committed =
|
|
30632
|
+
this._private__committed = next;
|
|
30342
30633
|
}
|
|
30343
30634
|
canUndo() {
|
|
30344
30635
|
return this._private__past.length > 0;
|
|
@@ -30394,11 +30685,25 @@ class DrawingToolsManager {
|
|
|
30394
30685
|
const tool = this._private__tools.get(this._private__selectedToolId);
|
|
30395
30686
|
return tool ? this._private__boundsOf(tool) : null;
|
|
30396
30687
|
}
|
|
30688
|
+
/** Relative to the chart element; the tool's box is relative to its pane. */
|
|
30689
|
+
_private__boundsOf(tool) {
|
|
30690
|
+
const box = tool.selectionBox() ?? this._private__boxFromPoints(tool);
|
|
30691
|
+
if (box === null) {
|
|
30692
|
+
return null;
|
|
30693
|
+
}
|
|
30694
|
+
const offset = this._private__paneOffsetOf(tool);
|
|
30695
|
+
return {
|
|
30696
|
+
left: box.left + offset._internal_x,
|
|
30697
|
+
right: box.right + offset._internal_x,
|
|
30698
|
+
top: box.top + offset._internal_y,
|
|
30699
|
+
bottom: box.bottom + offset._internal_y,
|
|
30700
|
+
};
|
|
30701
|
+
}
|
|
30397
30702
|
/**
|
|
30398
30703
|
* Bounds come from the tool's own serialized points, so this works for every
|
|
30399
30704
|
* tool without each one having to report a box.
|
|
30400
30705
|
*/
|
|
30401
|
-
|
|
30706
|
+
_private__boxFromPoints(tool) {
|
|
30402
30707
|
const series = tool.series();
|
|
30403
30708
|
const points = tool.serialize().points;
|
|
30404
30709
|
let left = Number.POSITIVE_INFINITY;
|
|
@@ -30421,13 +30726,7 @@ class DrawingToolsManager {
|
|
|
30421
30726
|
if (!found) {
|
|
30422
30727
|
return null;
|
|
30423
30728
|
}
|
|
30424
|
-
|
|
30425
|
-
return {
|
|
30426
|
-
left: left + offset._internal_x,
|
|
30427
|
-
right: right + offset._internal_x,
|
|
30428
|
-
top: top + offset._internal_y,
|
|
30429
|
-
bottom: bottom + offset._internal_y,
|
|
30430
|
-
};
|
|
30729
|
+
return { left, top, right, bottom };
|
|
30431
30730
|
}
|
|
30432
30731
|
/** Panes below the first one are offset inside the chart element. */
|
|
30433
30732
|
_private__paneOffsetOf(tool) {
|
|
@@ -30511,7 +30810,10 @@ class DrawingToolsManager {
|
|
|
30511
30810
|
serializeAll() {
|
|
30512
30811
|
const result = [];
|
|
30513
30812
|
for (const tool of this._private__tools.values()) {
|
|
30514
|
-
|
|
30813
|
+
// Not drawn yet: undo would bring it back as a finished drawing
|
|
30814
|
+
if (tool.isComplete()) {
|
|
30815
|
+
result.push(tool.serialize());
|
|
30816
|
+
}
|
|
30515
30817
|
}
|
|
30516
30818
|
return result;
|
|
30517
30819
|
}
|
|
@@ -31136,7 +31438,7 @@ const customSeriesDefaultOptions = {
|
|
|
31136
31438
|
* Returns the current version as a string. For example `'1.0.0'`.
|
|
31137
31439
|
*/
|
|
31138
31440
|
function version() {
|
|
31139
|
-
return "2.3.
|
|
31441
|
+
return "2.3.14";
|
|
31140
31442
|
}
|
|
31141
31443
|
|
|
31142
31444
|
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 };
|