@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)
|
|
@@ -14677,6 +14677,8 @@
|
|
|
14677
14677
|
return size(originalSize, 0.7);
|
|
14678
14678
|
case 'tag':
|
|
14679
14679
|
return size(originalSize, 1);
|
|
14680
|
+
case 'xcross':
|
|
14681
|
+
return size(originalSize, 0.7);
|
|
14680
14682
|
}
|
|
14681
14683
|
}
|
|
14682
14684
|
function calculateShapeHeight(barSpacing) {
|
|
@@ -14877,6 +14879,27 @@
|
|
|
14877
14879
|
y >= textY - halfHeight && y <= textY + halfHeight;
|
|
14878
14880
|
}
|
|
14879
14881
|
|
|
14882
|
+
function drawXCross(ctx, coords, size) {
|
|
14883
|
+
const crossSize = shapeSize('xcross', size);
|
|
14884
|
+
const halfSize = ((crossSize - 1) / 2) * coords._internal_pixelRatio;
|
|
14885
|
+
ctx.save();
|
|
14886
|
+
ctx.strokeStyle = ctx.fillStyle;
|
|
14887
|
+
ctx.lineWidth = Math.max(1, Math.round(1.5 * coords._internal_pixelRatio));
|
|
14888
|
+
ctx.lineCap = 'round';
|
|
14889
|
+
ctx.beginPath();
|
|
14890
|
+
ctx.moveTo(coords._internal_x - halfSize, coords._internal_y - halfSize);
|
|
14891
|
+
ctx.lineTo(coords._internal_x + halfSize, coords._internal_y + halfSize);
|
|
14892
|
+
ctx.moveTo(coords._internal_x + halfSize, coords._internal_y - halfSize);
|
|
14893
|
+
ctx.lineTo(coords._internal_x - halfSize, coords._internal_y + halfSize);
|
|
14894
|
+
ctx.stroke();
|
|
14895
|
+
ctx.restore();
|
|
14896
|
+
}
|
|
14897
|
+
function hitTestXCross(centerX, centerY, size, x, y) {
|
|
14898
|
+
const crossSize = shapeSize('xcross', size);
|
|
14899
|
+
const tolerance = 2 + crossSize / 2;
|
|
14900
|
+
return Math.abs(x - centerX) <= tolerance && Math.abs(y - centerY) <= tolerance;
|
|
14901
|
+
}
|
|
14902
|
+
|
|
14880
14903
|
class SeriesMarkersRenderer {
|
|
14881
14904
|
constructor() {
|
|
14882
14905
|
this._private__data = null;
|
|
@@ -15020,6 +15043,9 @@
|
|
|
15020
15043
|
case 'square':
|
|
15021
15044
|
drawSquare(ctx, coordinates, item._internal_size);
|
|
15022
15045
|
return;
|
|
15046
|
+
case 'xcross':
|
|
15047
|
+
drawXCross(ctx, coordinates, item._internal_size);
|
|
15048
|
+
return;
|
|
15023
15049
|
case 'tag':
|
|
15024
15050
|
// Handled by drawTagItem, which needs both pixel ratios and the text.
|
|
15025
15051
|
return;
|
|
@@ -15048,6 +15074,8 @@
|
|
|
15048
15074
|
return hitTestCircle(item._internal_x, item._internal_y, item._internal_size, x, y);
|
|
15049
15075
|
case 'square':
|
|
15050
15076
|
return hitTestSquare(item._internal_x, item._internal_y, item._internal_size, x, y);
|
|
15077
|
+
case 'xcross':
|
|
15078
|
+
return hitTestXCross(item._internal_x, item._internal_y, item._internal_size, x, y);
|
|
15051
15079
|
case 'tag':
|
|
15052
15080
|
return false;
|
|
15053
15081
|
}
|
|
@@ -21616,6 +21644,13 @@
|
|
|
21616
21644
|
series() {
|
|
21617
21645
|
return this._series;
|
|
21618
21646
|
}
|
|
21647
|
+
/**
|
|
21648
|
+
* Box the selection toolbar anchors to, in pane pixels. Null falls back to
|
|
21649
|
+
* the serialized points, which cannot place a line across the whole pane.
|
|
21650
|
+
*/
|
|
21651
|
+
selectionBox() {
|
|
21652
|
+
return null;
|
|
21653
|
+
}
|
|
21619
21654
|
getState() {
|
|
21620
21655
|
return this._state;
|
|
21621
21656
|
}
|
|
@@ -22292,6 +22327,53 @@
|
|
|
22292
22327
|
return new TrendLineTool(context, options);
|
|
22293
22328
|
}
|
|
22294
22329
|
|
|
22330
|
+
const END_HANDLE_MARGIN = 10;
|
|
22331
|
+
/**
|
|
22332
|
+
* Hands the next `clicks` clicks on the chart to `onClick`, past the last bar
|
|
22333
|
+
* too. A press that grabbed another drawing does not count.
|
|
22334
|
+
*/
|
|
22335
|
+
function placeByClicks(chart, series, clicks, onClick) {
|
|
22336
|
+
let placed = 0;
|
|
22337
|
+
let handler = null;
|
|
22338
|
+
const stop = () => {
|
|
22339
|
+
if (handler !== null) {
|
|
22340
|
+
chart.unsubscribeClick(handler);
|
|
22341
|
+
handler = null;
|
|
22342
|
+
}
|
|
22343
|
+
};
|
|
22344
|
+
handler = (param) => {
|
|
22345
|
+
if (!param.point || consumeInputClaim(chart)) {
|
|
22346
|
+
return;
|
|
22347
|
+
}
|
|
22348
|
+
const price = series.coordinateToPrice(param.point.y);
|
|
22349
|
+
const time = timeFromCoordinate(chart, series, param.point.x);
|
|
22350
|
+
if (price === null || time === null) {
|
|
22351
|
+
return;
|
|
22352
|
+
}
|
|
22353
|
+
placed++;
|
|
22354
|
+
if (placed >= clicks) {
|
|
22355
|
+
stop();
|
|
22356
|
+
}
|
|
22357
|
+
onClick({ _internal_time: time, _internal_price: price, _internal_x: param.point.x, _internal_y: param.point.y }, placed - 1);
|
|
22358
|
+
};
|
|
22359
|
+
chart.subscribeClick(handler);
|
|
22360
|
+
return {
|
|
22361
|
+
_internal_pending: () => handler !== null,
|
|
22362
|
+
_internal_stop: stop,
|
|
22363
|
+
};
|
|
22364
|
+
}
|
|
22365
|
+
/** A fifth of the pane, and never less than a bar. */
|
|
22366
|
+
function defaultWidth(chart) {
|
|
22367
|
+
const timeScale = chart.timeScale();
|
|
22368
|
+
return Math.max(timeScale.width() / 5, timeScale.options().barSpacing);
|
|
22369
|
+
}
|
|
22370
|
+
/** Kept inside the pane: past it, the handles on the end hide under the price axis. */
|
|
22371
|
+
function placedEndX(chart, x) {
|
|
22372
|
+
const timeScale = chart.timeScale();
|
|
22373
|
+
const onScreen = Math.min(x + defaultWidth(chart), timeScale.width() - END_HANDLE_MARGIN);
|
|
22374
|
+
return Math.max(onScreen, x + timeScale.options().barSpacing);
|
|
22375
|
+
}
|
|
22376
|
+
|
|
22295
22377
|
class HorizontalLinePaneRenderer {
|
|
22296
22378
|
constructor(tool) {
|
|
22297
22379
|
this._private__tool = tool;
|
|
@@ -22332,6 +22414,7 @@
|
|
|
22332
22414
|
this._private__isDragging = false;
|
|
22333
22415
|
this._private__grab = null;
|
|
22334
22416
|
this._private__moveStartPrice = 0;
|
|
22417
|
+
this._private__placement = null;
|
|
22335
22418
|
this._private__hLineOptions = {
|
|
22336
22419
|
lineColor: '#2962ff',
|
|
22337
22420
|
lineWidth: 2,
|
|
@@ -22344,6 +22427,26 @@
|
|
|
22344
22427
|
};
|
|
22345
22428
|
this._registerOptions(this._private__hLineOptions);
|
|
22346
22429
|
this._private__createPrimitive();
|
|
22430
|
+
if (this._private__hLineOptions.interactive) {
|
|
22431
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
|
|
22432
|
+
}
|
|
22433
|
+
}
|
|
22434
|
+
_private__placeAt(point) {
|
|
22435
|
+
this._private__hLineOptions.price = point._internal_price;
|
|
22436
|
+
if (this._private__hLineOptions.startTime !== undefined) {
|
|
22437
|
+
this._private__hLineOptions.startTime = point._internal_time;
|
|
22438
|
+
}
|
|
22439
|
+
this._private__requestUpdate();
|
|
22440
|
+
}
|
|
22441
|
+
isComplete() {
|
|
22442
|
+
return !this._private__placement?._internal_pending();
|
|
22443
|
+
}
|
|
22444
|
+
selectionBox() {
|
|
22445
|
+
const y = this._private__hLineOptions.price === undefined ? null : this._series.priceToCoordinate(this._private__hLineOptions.price);
|
|
22446
|
+
if (y === null) {
|
|
22447
|
+
return null;
|
|
22448
|
+
}
|
|
22449
|
+
return { left: this._private__startX(), right: this._chart.timeScale().width(), top: y, bottom: y };
|
|
22347
22450
|
}
|
|
22348
22451
|
_private__createPrimitive() {
|
|
22349
22452
|
this._private__paneView = this._private__createPaneView();
|
|
@@ -22381,7 +22484,7 @@
|
|
|
22381
22484
|
return coordinateFromTime(this._chart, this._series, this._private__hLineOptions.startTime) ?? 0;
|
|
22382
22485
|
}
|
|
22383
22486
|
drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
22384
|
-
if (this._private__hLineOptions.price === undefined)
|
|
22487
|
+
if (!this.isComplete() || this._private__hLineOptions.price === undefined)
|
|
22385
22488
|
return;
|
|
22386
22489
|
const y = (this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0) * verticalPixelRatio;
|
|
22387
22490
|
const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
|
|
@@ -22475,7 +22578,7 @@
|
|
|
22475
22578
|
return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
|
|
22476
22579
|
}
|
|
22477
22580
|
hitTest(x, y) {
|
|
22478
|
-
if (this._private__hLineOptions.price === undefined) {
|
|
22581
|
+
if (!this.isComplete() || this._private__hLineOptions.price === undefined) {
|
|
22479
22582
|
return false;
|
|
22480
22583
|
}
|
|
22481
22584
|
const lineY = this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0;
|
|
@@ -22550,6 +22653,7 @@
|
|
|
22550
22653
|
this._private__requestUpdate();
|
|
22551
22654
|
}
|
|
22552
22655
|
remove() {
|
|
22656
|
+
this._private__placement?._internal_stop();
|
|
22553
22657
|
if (this._private__primitive) {
|
|
22554
22658
|
this._series.detachPrimitive(this._private__primitive);
|
|
22555
22659
|
this._private__primitive = null;
|
|
@@ -22609,6 +22713,7 @@
|
|
|
22609
22713
|
this._private__grab = null;
|
|
22610
22714
|
this._private__moveStartTime = 0;
|
|
22611
22715
|
this._private__moveStartPrice = 0;
|
|
22716
|
+
this._private__placement = null;
|
|
22612
22717
|
this._private__crossOptions = {
|
|
22613
22718
|
lineColor: '#787b86',
|
|
22614
22719
|
lineWidth: 1,
|
|
@@ -22618,6 +22723,16 @@
|
|
|
22618
22723
|
};
|
|
22619
22724
|
this._registerOptions(this._private__crossOptions);
|
|
22620
22725
|
this._private__createPrimitive();
|
|
22726
|
+
if (this._private__crossOptions.interactive) {
|
|
22727
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
|
|
22728
|
+
this._private__crossOptions.time = point._internal_time;
|
|
22729
|
+
this._private__crossOptions.price = point._internal_price;
|
|
22730
|
+
this._private__requestUpdate();
|
|
22731
|
+
});
|
|
22732
|
+
}
|
|
22733
|
+
}
|
|
22734
|
+
isComplete() {
|
|
22735
|
+
return !this._private__placement?._internal_pending();
|
|
22621
22736
|
}
|
|
22622
22737
|
_private__createPrimitive() {
|
|
22623
22738
|
this._private__paneView = new CrossLinePaneView(this);
|
|
@@ -22645,7 +22760,7 @@
|
|
|
22645
22760
|
this._private__requestUpdate();
|
|
22646
22761
|
}
|
|
22647
22762
|
_private__coordinates() {
|
|
22648
|
-
if (this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
|
|
22763
|
+
if (!this.isComplete() || this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
|
|
22649
22764
|
return null;
|
|
22650
22765
|
}
|
|
22651
22766
|
const x = coordinateFromTime(this._chart, this._series, this._private__crossOptions.time);
|
|
@@ -22768,6 +22883,7 @@
|
|
|
22768
22883
|
return this._private__isHovered;
|
|
22769
22884
|
}
|
|
22770
22885
|
remove() {
|
|
22886
|
+
this._private__placement?._internal_stop();
|
|
22771
22887
|
if (this._private__primitive) {
|
|
22772
22888
|
try {
|
|
22773
22889
|
this._series.detachPrimitive(this._private__primitive);
|
|
@@ -24190,6 +24306,7 @@
|
|
|
24190
24306
|
this._private__isDragging = false;
|
|
24191
24307
|
this._private__grab = null;
|
|
24192
24308
|
this._private__moveStartTime = 0;
|
|
24309
|
+
this._private__placement = null;
|
|
24193
24310
|
this._private__vLineOptions = {
|
|
24194
24311
|
lineColor: '#787b86',
|
|
24195
24312
|
lineWidth: 1,
|
|
@@ -24201,6 +24318,29 @@
|
|
|
24201
24318
|
};
|
|
24202
24319
|
this._registerOptions(this._private__vLineOptions);
|
|
24203
24320
|
this._private__createPrimitive();
|
|
24321
|
+
if (this._private__vLineOptions.interactive) {
|
|
24322
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
|
|
24323
|
+
this._private__vLineOptions.time = point._internal_time;
|
|
24324
|
+
this._private__requestUpdate();
|
|
24325
|
+
});
|
|
24326
|
+
}
|
|
24327
|
+
}
|
|
24328
|
+
isComplete() {
|
|
24329
|
+
return !this._private__placement?._internal_pending();
|
|
24330
|
+
}
|
|
24331
|
+
selectionBox() {
|
|
24332
|
+
const x = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time);
|
|
24333
|
+
if (x === null) {
|
|
24334
|
+
return null;
|
|
24335
|
+
}
|
|
24336
|
+
let middle;
|
|
24337
|
+
try {
|
|
24338
|
+
middle = this._series.getPane().getHeight() / 2;
|
|
24339
|
+
}
|
|
24340
|
+
catch (error) {
|
|
24341
|
+
return null;
|
|
24342
|
+
}
|
|
24343
|
+
return { left: x, right: x, top: middle, bottom: middle };
|
|
24204
24344
|
}
|
|
24205
24345
|
_private__createPrimitive() {
|
|
24206
24346
|
this._private__paneView = this._private__createPaneView();
|
|
@@ -24231,7 +24371,7 @@
|
|
|
24231
24371
|
return new VerticalLinePaneView(this);
|
|
24232
24372
|
}
|
|
24233
24373
|
drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
24234
|
-
if (this._private__vLineOptions.time === undefined)
|
|
24374
|
+
if (!this.isComplete() || this._private__vLineOptions.time === undefined)
|
|
24235
24375
|
return;
|
|
24236
24376
|
const x = (coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0) * horizontalPixelRatio;
|
|
24237
24377
|
const chartHeight = this._chart.chartElement().clientHeight * verticalPixelRatio;
|
|
@@ -24318,7 +24458,7 @@
|
|
|
24318
24458
|
return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
|
|
24319
24459
|
}
|
|
24320
24460
|
hitTest(x, y) {
|
|
24321
|
-
if (this._private__vLineOptions.time === undefined) {
|
|
24461
|
+
if (!this.isComplete() || this._private__vLineOptions.time === undefined) {
|
|
24322
24462
|
return false;
|
|
24323
24463
|
}
|
|
24324
24464
|
const lineX = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0;
|
|
@@ -24388,6 +24528,7 @@
|
|
|
24388
24528
|
this._private__requestUpdate();
|
|
24389
24529
|
}
|
|
24390
24530
|
remove() {
|
|
24531
|
+
this._private__placement?._internal_stop();
|
|
24391
24532
|
if (this._private__primitive) {
|
|
24392
24533
|
this._series.detachPrimitive(this._private__primitive);
|
|
24393
24534
|
this._private__primitive = null;
|
|
@@ -25109,6 +25250,7 @@
|
|
|
25109
25250
|
this._private__isHovered = false;
|
|
25110
25251
|
this._private__isDragging = false;
|
|
25111
25252
|
this._private__dragTarget = null;
|
|
25253
|
+
this._private__placement = null;
|
|
25112
25254
|
this._private__fibOptions = {
|
|
25113
25255
|
levels: [
|
|
25114
25256
|
{ level: 0, color: '#787B86', label: '0%' },
|
|
@@ -25124,6 +25266,20 @@
|
|
|
25124
25266
|
...options,
|
|
25125
25267
|
};
|
|
25126
25268
|
this._private__createPrimitive();
|
|
25269
|
+
if (this._private__fibOptions.interactive) {
|
|
25270
|
+
this._private__placement = placeByClicks(this._chart, this._series, 3, (point, index) => {
|
|
25271
|
+
const placed = { time: point._internal_time, price: point._internal_price };
|
|
25272
|
+
if (index === 0) {
|
|
25273
|
+
this.setPoint1(placed);
|
|
25274
|
+
}
|
|
25275
|
+
else if (index === 1) {
|
|
25276
|
+
this.setPoint2(placed);
|
|
25277
|
+
}
|
|
25278
|
+
else {
|
|
25279
|
+
this.setPoint3(placed);
|
|
25280
|
+
}
|
|
25281
|
+
});
|
|
25282
|
+
}
|
|
25127
25283
|
}
|
|
25128
25284
|
_private__createPrimitive() {
|
|
25129
25285
|
this._private__paneView = this._private__createPaneView();
|
|
@@ -25181,7 +25337,7 @@
|
|
|
25181
25337
|
this._private__requestUpdate();
|
|
25182
25338
|
}
|
|
25183
25339
|
isComplete() {
|
|
25184
|
-
return !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
|
|
25340
|
+
return !this._private__placement?._internal_pending() && !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
|
|
25185
25341
|
}
|
|
25186
25342
|
setColor(color) {
|
|
25187
25343
|
this._private__fibOptions.lineColor = color;
|
|
@@ -25476,6 +25632,7 @@
|
|
|
25476
25632
|
this._private__requestUpdate();
|
|
25477
25633
|
}
|
|
25478
25634
|
remove() {
|
|
25635
|
+
this._private__placement?._internal_stop();
|
|
25479
25636
|
if (this._private__primitive) {
|
|
25480
25637
|
this._series.detachPrimitive(this._private__primitive);
|
|
25481
25638
|
this._private__primitive = null;
|
|
@@ -26127,17 +26284,19 @@
|
|
|
26127
26284
|
// Draw border
|
|
26128
26285
|
const borderColor = this._private__rectOptions.borderColor;
|
|
26129
26286
|
const borderWidth = this._private__isSelected ? this._private__rectOptions.borderWidth + 1 : this._private__rectOptions.borderWidth;
|
|
26130
|
-
|
|
26131
|
-
|
|
26132
|
-
|
|
26133
|
-
|
|
26134
|
-
|
|
26135
|
-
|
|
26136
|
-
|
|
26137
|
-
|
|
26287
|
+
if (borderWidth > 0) {
|
|
26288
|
+
ctx.strokeStyle = borderColor;
|
|
26289
|
+
ctx.lineWidth = borderWidth * horizontalPixelRatio;
|
|
26290
|
+
// Set border style
|
|
26291
|
+
if (this._private__rectOptions.borderStyle === 'dashed') {
|
|
26292
|
+
ctx.setLineDash([10 * horizontalPixelRatio, 5 * horizontalPixelRatio]);
|
|
26293
|
+
}
|
|
26294
|
+
else if (this._private__rectOptions.borderStyle === 'dotted') {
|
|
26295
|
+
ctx.setLineDash([2 * horizontalPixelRatio, 3 * horizontalPixelRatio]);
|
|
26296
|
+
}
|
|
26297
|
+
ctx.strokeRect(minX, minY, width, height);
|
|
26298
|
+
ctx.setLineDash([]);
|
|
26138
26299
|
}
|
|
26139
|
-
ctx.strokeRect(minX, minY, width, height);
|
|
26140
|
-
ctx.setLineDash([]);
|
|
26141
26300
|
// Draw corner handles if selected
|
|
26142
26301
|
if ((this._private__isSelected || this._private__isHovered) && !this._private__isDrawing && !this.isLocked()) {
|
|
26143
26302
|
this._private__drawCornerHandles(ctx, minX, minY, maxX, maxY, horizontalPixelRatio, borderColor);
|
|
@@ -26165,17 +26324,20 @@
|
|
|
26165
26324
|
}
|
|
26166
26325
|
_private__drawLabel(ctx, minX, minY, maxX, maxY, horizontalPixelRatio, verticalPixelRatio, color) {
|
|
26167
26326
|
const padding = 6 * horizontalPixelRatio;
|
|
26168
|
-
const fontSize = 12 * horizontalPixelRatio;
|
|
26327
|
+
const fontSize = (this._private__rectOptions.labelFontSize ?? 12) * horizontalPixelRatio;
|
|
26169
26328
|
ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
|
|
26170
26329
|
const textMetrics = ctx.measureText(this._private__rectOptions.labelText);
|
|
26171
26330
|
const textWidth = textMetrics.width;
|
|
26172
26331
|
const textHeight = fontSize;
|
|
26332
|
+
const middleY = (minY + maxY) / 2 - textHeight / 2 - padding;
|
|
26333
|
+
// A box extended far past the pane keeps right-hand and centred labels on screen.
|
|
26334
|
+
const visibleMaxX = Math.min(maxX, ctx.canvas.width);
|
|
26173
26335
|
// Calculate label position based on labelPosition option
|
|
26174
26336
|
let labelX = minX + padding;
|
|
26175
26337
|
let labelY = minY + padding;
|
|
26176
26338
|
switch (this._private__rectOptions.labelPosition) {
|
|
26177
26339
|
case 'top-right':
|
|
26178
|
-
labelX =
|
|
26340
|
+
labelX = visibleMaxX - textWidth - padding * 2;
|
|
26179
26341
|
labelY = minY + padding;
|
|
26180
26342
|
break;
|
|
26181
26343
|
case 'bottom-left':
|
|
@@ -26183,19 +26345,27 @@
|
|
|
26183
26345
|
labelY = maxY - textHeight - padding * 2;
|
|
26184
26346
|
break;
|
|
26185
26347
|
case 'bottom-right':
|
|
26186
|
-
labelX =
|
|
26348
|
+
labelX = visibleMaxX - textWidth - padding * 2;
|
|
26187
26349
|
labelY = maxY - textHeight - padding * 2;
|
|
26188
26350
|
break;
|
|
26189
26351
|
case 'center':
|
|
26190
|
-
labelX = (minX +
|
|
26191
|
-
labelY =
|
|
26352
|
+
labelX = (Math.max(minX, 0) + visibleMaxX) / 2 - textWidth / 2 - padding;
|
|
26353
|
+
labelY = middleY;
|
|
26354
|
+
break;
|
|
26355
|
+
case 'left':
|
|
26356
|
+
labelX = minX + padding;
|
|
26357
|
+
labelY = middleY;
|
|
26358
|
+
break;
|
|
26359
|
+
case 'right':
|
|
26360
|
+
labelX = visibleMaxX - textWidth - padding * 2;
|
|
26361
|
+
labelY = middleY;
|
|
26192
26362
|
break;
|
|
26193
26363
|
}
|
|
26194
26364
|
// Draw background
|
|
26195
|
-
ctx.fillStyle = color;
|
|
26365
|
+
ctx.fillStyle = this._private__rectOptions.labelBackgroundColor ?? color;
|
|
26196
26366
|
ctx.fillRect(labelX, labelY, textWidth + padding * 2, textHeight + padding * 2);
|
|
26197
26367
|
// Draw text
|
|
26198
|
-
ctx.fillStyle = '#ffffff';
|
|
26368
|
+
ctx.fillStyle = this._private__rectOptions.labelColor ?? '#ffffff';
|
|
26199
26369
|
ctx.textBaseline = 'top';
|
|
26200
26370
|
ctx.fillText(this._private__rectOptions.labelText, labelX + padding, labelY + padding);
|
|
26201
26371
|
}
|
|
@@ -26433,6 +26603,9 @@
|
|
|
26433
26603
|
showLabel: this._private__rectOptions.showLabel,
|
|
26434
26604
|
labelText: this._private__rectOptions.labelText,
|
|
26435
26605
|
labelPosition: this._private__rectOptions.labelPosition,
|
|
26606
|
+
labelColor: this._private__rectOptions.labelColor,
|
|
26607
|
+
labelBackgroundColor: this._private__rectOptions.labelBackgroundColor,
|
|
26608
|
+
labelFontSize: this._private__rectOptions.labelFontSize,
|
|
26436
26609
|
},
|
|
26437
26610
|
};
|
|
26438
26611
|
}
|
|
@@ -28025,6 +28198,7 @@
|
|
|
28025
28198
|
this._private__moveStart = null;
|
|
28026
28199
|
this._private__isEditing = false;
|
|
28027
28200
|
this._private__clickOutsideHandler = null;
|
|
28201
|
+
this._private__placement = null;
|
|
28028
28202
|
this._private__textOptions = {
|
|
28029
28203
|
text: 'Double-click to edit',
|
|
28030
28204
|
fontSize: 14,
|
|
@@ -28037,11 +28211,22 @@
|
|
|
28037
28211
|
borderWidth: 1,
|
|
28038
28212
|
textAlign: 'left',
|
|
28039
28213
|
maxWidth: 200,
|
|
28214
|
+
anchor: 'top-left',
|
|
28215
|
+
pointer: false,
|
|
28216
|
+
borderRadius: 0,
|
|
28040
28217
|
...options,
|
|
28041
28218
|
};
|
|
28042
28219
|
this._registerOptions(this._private__textOptions);
|
|
28043
28220
|
this._private__createPrimitive();
|
|
28044
28221
|
this._private__setupClickOutsideHandler();
|
|
28222
|
+
if (this._private__textOptions.interactive) {
|
|
28223
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
|
|
28224
|
+
this.setPosition(point._internal_time, point._internal_price);
|
|
28225
|
+
if (this._private__textOptions.onComplete) {
|
|
28226
|
+
this._private__textOptions.onComplete();
|
|
28227
|
+
}
|
|
28228
|
+
});
|
|
28229
|
+
}
|
|
28045
28230
|
}
|
|
28046
28231
|
_private__setupClickOutsideHandler() {
|
|
28047
28232
|
// Set up click handler to finish editing when clicking outside
|
|
@@ -28127,7 +28312,7 @@
|
|
|
28127
28312
|
this._private__requestUpdate();
|
|
28128
28313
|
}
|
|
28129
28314
|
isComplete() {
|
|
28130
|
-
return !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
|
|
28315
|
+
return !this._private__placement?._internal_pending() && !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
|
|
28131
28316
|
}
|
|
28132
28317
|
setColor(color) {
|
|
28133
28318
|
this._private__textOptions.textColor = color;
|
|
@@ -28188,18 +28373,59 @@
|
|
|
28188
28373
|
const py = this._series.priceToCoordinate(price) ?? 0;
|
|
28189
28374
|
// Calculate text box dimensions
|
|
28190
28375
|
const fontSize = this._private__textOptions.fontSize || 14;
|
|
28191
|
-
const padding = this._private__textOptions.padding
|
|
28376
|
+
const padding = this._private__textOptions.padding ?? 8;
|
|
28192
28377
|
const text = this._private__textOptions.text || '';
|
|
28193
28378
|
// Rough estimate of text width (will be more accurate in canvas)
|
|
28194
28379
|
const charWidth = fontSize * 0.6;
|
|
28195
28380
|
const textWidth = Math.min(text.length * charWidth, this._private__textOptions.maxWidth || 200);
|
|
28196
|
-
const
|
|
28197
|
-
|
|
28198
|
-
const
|
|
28199
|
-
const
|
|
28200
|
-
const
|
|
28201
|
-
const
|
|
28202
|
-
return x >= left && x <= right && y >=
|
|
28381
|
+
const boxWidth = textWidth + padding * 2;
|
|
28382
|
+
const boxHeight = fontSize + padding * 2;
|
|
28383
|
+
const tail = this._private__tailSize(boxHeight);
|
|
28384
|
+
const box = this._private__boxOrigin(px, py, boxWidth, boxHeight, tail);
|
|
28385
|
+
const left = box._internal_left - (this._private__textOptions.anchor === 'left' ? tail : 0);
|
|
28386
|
+
const right = box._internal_left + boxWidth + (this._private__textOptions.anchor === 'right' ? tail : 0);
|
|
28387
|
+
return x >= left && x <= right && y >= box._internal_top && y <= box._internal_top + boxHeight;
|
|
28388
|
+
}
|
|
28389
|
+
_private__tailSize(boxHeight) {
|
|
28390
|
+
const anchor = this._private__textOptions.anchor;
|
|
28391
|
+
return this._private__textOptions.pointer && (anchor === 'left' || anchor === 'right') ? Math.round(boxHeight * 0.35) : 0;
|
|
28392
|
+
}
|
|
28393
|
+
_private__boxOrigin(x, y, width, height, tail) {
|
|
28394
|
+
switch (this._private__textOptions.anchor) {
|
|
28395
|
+
case 'left':
|
|
28396
|
+
return { _internal_left: x + tail, _internal_top: y - height / 2 };
|
|
28397
|
+
case 'right':
|
|
28398
|
+
return { _internal_left: x - tail - width, _internal_top: y - height / 2 };
|
|
28399
|
+
case 'center':
|
|
28400
|
+
return { _internal_left: x - width / 2, _internal_top: y - height / 2 };
|
|
28401
|
+
default:
|
|
28402
|
+
return { _internal_left: x, _internal_top: y };
|
|
28403
|
+
}
|
|
28404
|
+
}
|
|
28405
|
+
// eslint-disable-next-line max-params
|
|
28406
|
+
_private__traceBox(ctx, left, top, width, height, tail, anchorX, anchorY, radius) {
|
|
28407
|
+
const r = Math.max(0, Math.min(radius, width / 2, height / 2));
|
|
28408
|
+
ctx.beginPath();
|
|
28409
|
+
ctx.moveTo(left + r, top);
|
|
28410
|
+
ctx.lineTo(left + width - r, top);
|
|
28411
|
+
ctx.arcTo(left + width, top, left + width, top + r, r);
|
|
28412
|
+
if (tail > 0 && this._private__textOptions.anchor === 'right') {
|
|
28413
|
+
ctx.lineTo(left + width, anchorY - tail);
|
|
28414
|
+
ctx.lineTo(anchorX, anchorY);
|
|
28415
|
+
ctx.lineTo(left + width, anchorY + tail);
|
|
28416
|
+
}
|
|
28417
|
+
ctx.lineTo(left + width, top + height - r);
|
|
28418
|
+
ctx.arcTo(left + width, top + height, left + width - r, top + height, r);
|
|
28419
|
+
ctx.lineTo(left + r, top + height);
|
|
28420
|
+
ctx.arcTo(left, top + height, left, top + height - r, r);
|
|
28421
|
+
if (tail > 0 && this._private__textOptions.anchor === 'left') {
|
|
28422
|
+
ctx.lineTo(left, anchorY + tail);
|
|
28423
|
+
ctx.lineTo(anchorX, anchorY);
|
|
28424
|
+
ctx.lineTo(left, anchorY - tail);
|
|
28425
|
+
}
|
|
28426
|
+
ctx.lineTo(left, top + r);
|
|
28427
|
+
ctx.arcTo(left, top, left + r, top, r);
|
|
28428
|
+
ctx.closePath();
|
|
28203
28429
|
}
|
|
28204
28430
|
hitTestEndpoint(x, y) {
|
|
28205
28431
|
if (!this.isComplete())
|
|
@@ -28251,7 +28477,7 @@
|
|
|
28251
28477
|
const x = (coordinateFromTime(this._chart, this._series, time) ?? 0) * horizontalPixelRatio;
|
|
28252
28478
|
const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
|
|
28253
28479
|
const fontSize = (this._private__textOptions.fontSize || 14) * horizontalPixelRatio;
|
|
28254
|
-
const padding = (this._private__textOptions.padding
|
|
28480
|
+
const padding = (this._private__textOptions.padding ?? 8) * horizontalPixelRatio;
|
|
28255
28481
|
const maxWidth = (this._private__textOptions.maxWidth || 200) * horizontalPixelRatio;
|
|
28256
28482
|
// Set font
|
|
28257
28483
|
ctx.font = `${fontSize}px ${this._private__textOptions.fontFamily || 'Arial'}`;
|
|
@@ -28283,32 +28509,39 @@
|
|
|
28283
28509
|
const boxWidth = maxLineWidth + padding * 2;
|
|
28284
28510
|
const lineHeight = fontSize * 1.2;
|
|
28285
28511
|
const boxHeight = lines.length * lineHeight + padding * 2;
|
|
28512
|
+
const tail = this._private__tailSize(boxHeight);
|
|
28513
|
+
const { _internal_left: left, _internal_top: top } = this._private__boxOrigin(x, y, boxWidth, boxHeight, tail);
|
|
28514
|
+
const radius = (this._private__textOptions.borderRadius ?? 0) * horizontalPixelRatio;
|
|
28286
28515
|
// Draw background
|
|
28287
|
-
const bgOpacity = this._private__textOptions.backgroundOpacity
|
|
28516
|
+
const bgOpacity = this._private__textOptions.backgroundOpacity ?? 0.8;
|
|
28288
28517
|
const bgColor = this._private__textOptions.backgroundColor || '#2962FF';
|
|
28289
|
-
|
|
28290
|
-
|
|
28518
|
+
if (bgOpacity > 0) {
|
|
28519
|
+
ctx.fillStyle = this._private__hexToRgba(bgColor, bgOpacity);
|
|
28520
|
+
this._private__traceBox(ctx, left, top, boxWidth, boxHeight, tail, x, y, radius);
|
|
28521
|
+
ctx.fill();
|
|
28522
|
+
}
|
|
28291
28523
|
// Draw border
|
|
28292
28524
|
if (this._private__isSelected || this._private__isHovered) {
|
|
28293
28525
|
ctx.strokeStyle = this._private__textOptions.borderColor ?? '#FFFFFF';
|
|
28294
28526
|
ctx.lineWidth = (this._private__textOptions.borderWidth || 1) * horizontalPixelRatio * (this._private__isSelected ? 2 : 1);
|
|
28295
|
-
|
|
28527
|
+
this._private__traceBox(ctx, left, top, boxWidth, boxHeight, tail, x, y, radius);
|
|
28528
|
+
ctx.stroke();
|
|
28296
28529
|
}
|
|
28297
28530
|
// Draw text
|
|
28298
28531
|
ctx.fillStyle = this._private__textOptions.textColor || '#FFFFFF';
|
|
28299
28532
|
for (let i = 0; i < lines.length; i++) {
|
|
28300
28533
|
const line = lines[i];
|
|
28301
|
-
let textX =
|
|
28534
|
+
let textX = left + padding;
|
|
28302
28535
|
// Handle text alignment
|
|
28303
28536
|
if (this._private__textOptions.textAlign === 'center') {
|
|
28304
28537
|
const metrics = ctx.measureText(line);
|
|
28305
|
-
textX =
|
|
28538
|
+
textX = left + (boxWidth - metrics.width) / 2;
|
|
28306
28539
|
}
|
|
28307
28540
|
else if (this._private__textOptions.textAlign === 'right') {
|
|
28308
28541
|
const metrics = ctx.measureText(line);
|
|
28309
|
-
textX =
|
|
28542
|
+
textX = left + boxWidth - metrics.width - padding;
|
|
28310
28543
|
}
|
|
28311
|
-
const textY =
|
|
28544
|
+
const textY = top + padding + i * lineHeight;
|
|
28312
28545
|
ctx.fillText(line, textX, textY);
|
|
28313
28546
|
}
|
|
28314
28547
|
// Draw anchor point if selected
|
|
@@ -28352,6 +28585,9 @@
|
|
|
28352
28585
|
borderWidth: this._private__textOptions.borderWidth,
|
|
28353
28586
|
textAlign: this._private__textOptions.textAlign,
|
|
28354
28587
|
maxWidth: this._private__textOptions.maxWidth,
|
|
28588
|
+
anchor: this._private__textOptions.anchor,
|
|
28589
|
+
pointer: this._private__textOptions.pointer,
|
|
28590
|
+
borderRadius: this._private__textOptions.borderRadius,
|
|
28355
28591
|
},
|
|
28356
28592
|
extra: {
|
|
28357
28593
|
text: this._private__textOptions.text,
|
|
@@ -28359,6 +28595,7 @@
|
|
|
28359
28595
|
};
|
|
28360
28596
|
}
|
|
28361
28597
|
remove() {
|
|
28598
|
+
this._private__placement?._internal_stop();
|
|
28362
28599
|
// Clean up click handler
|
|
28363
28600
|
if (this._private__clickOutsideHandler) {
|
|
28364
28601
|
this._chart.unsubscribeClick(this._private__clickOutsideHandler);
|
|
@@ -29217,6 +29454,7 @@
|
|
|
29217
29454
|
this._private__paneView = null;
|
|
29218
29455
|
this._private__isSelected = false;
|
|
29219
29456
|
this._private__isHovered = false;
|
|
29457
|
+
this._private__placement = null;
|
|
29220
29458
|
this._private__isDragging = false;
|
|
29221
29459
|
this._private__dragTarget = null;
|
|
29222
29460
|
this._private__grab = null;
|
|
@@ -29239,6 +29477,32 @@
|
|
|
29239
29477
|
// Create primitive and setup
|
|
29240
29478
|
this._private__createPrimitive();
|
|
29241
29479
|
this._private__updateMetrics();
|
|
29480
|
+
if (this._private__positionOptions.interactive) {
|
|
29481
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
|
|
29482
|
+
}
|
|
29483
|
+
}
|
|
29484
|
+
_private__placeAt(point) {
|
|
29485
|
+
const options = this._private__positionOptions;
|
|
29486
|
+
const shift = point._internal_price - options.entryPrice;
|
|
29487
|
+
options.entryPrice = point._internal_price;
|
|
29488
|
+
options.entryTime = point._internal_time;
|
|
29489
|
+
if (options.stopLoss !== undefined) {
|
|
29490
|
+
options.stopLoss += shift;
|
|
29491
|
+
}
|
|
29492
|
+
if (options.takeProfit !== undefined) {
|
|
29493
|
+
options.takeProfit += shift;
|
|
29494
|
+
}
|
|
29495
|
+
options.endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? undefined;
|
|
29496
|
+
this._private__updateMetrics();
|
|
29497
|
+
this._private__requestUpdate();
|
|
29498
|
+
}
|
|
29499
|
+
isComplete() {
|
|
29500
|
+
return !this._private__placement?._internal_pending();
|
|
29501
|
+
}
|
|
29502
|
+
_private__span() {
|
|
29503
|
+
const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
|
|
29504
|
+
const endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? entryX + defaultWidth(this._chart);
|
|
29505
|
+
return { _internal_entryX: entryX, _internal_endX: endX };
|
|
29242
29506
|
}
|
|
29243
29507
|
_private__createPrimitive() {
|
|
29244
29508
|
// Create and cache the paneView once
|
|
@@ -29270,6 +29534,9 @@
|
|
|
29270
29534
|
return new PositionPaneView(this);
|
|
29271
29535
|
}
|
|
29272
29536
|
drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
29537
|
+
if (!this.isComplete()) {
|
|
29538
|
+
return;
|
|
29539
|
+
}
|
|
29273
29540
|
// Draw profit rectangle (green)
|
|
29274
29541
|
if (this._private__profitRect && this._private__positionOptions.takeProfit) {
|
|
29275
29542
|
const timeScale = this._chart.timeScale();
|
|
@@ -29303,16 +29570,9 @@
|
|
|
29303
29570
|
}
|
|
29304
29571
|
}
|
|
29305
29572
|
_private__drawRectangle(ctx, rect, horizontalPixelRatio, verticalPixelRatio, timeScale) {
|
|
29306
|
-
const
|
|
29307
|
-
|
|
29308
|
-
|
|
29309
|
-
if (this._private__positionOptions.endTime) {
|
|
29310
|
-
endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
|
|
29311
|
-
}
|
|
29312
|
-
else {
|
|
29313
|
-
const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
|
|
29314
|
-
endX = chartWidth;
|
|
29315
|
-
}
|
|
29573
|
+
const span = this._private__span();
|
|
29574
|
+
const entryX = span._internal_entryX * horizontalPixelRatio;
|
|
29575
|
+
const endX = span._internal_endX * horizontalPixelRatio;
|
|
29316
29576
|
const y1 = (this._series.priceToCoordinate(rect._internal_price1) ?? 0) * verticalPixelRatio;
|
|
29317
29577
|
const y2 = (this._series.priceToCoordinate(rect._internal_price2) ?? 0) * verticalPixelRatio;
|
|
29318
29578
|
// Draw filled rectangle with semi-transparent fill
|
|
@@ -29327,16 +29587,10 @@
|
|
|
29327
29587
|
ctx.stroke();
|
|
29328
29588
|
}
|
|
29329
29589
|
_private__drawEntryLine(ctx, horizontalPixelRatio, verticalPixelRatio, timeScale) {
|
|
29330
|
-
const
|
|
29590
|
+
const span = this._private__span();
|
|
29591
|
+
const entryX = span._internal_entryX * horizontalPixelRatio;
|
|
29592
|
+
const endX = span._internal_endX * horizontalPixelRatio;
|
|
29331
29593
|
const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
|
|
29332
|
-
let endX;
|
|
29333
|
-
if (this._private__positionOptions.endTime) {
|
|
29334
|
-
endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
|
|
29335
|
-
}
|
|
29336
|
-
else {
|
|
29337
|
-
const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
|
|
29338
|
-
endX = chartWidth;
|
|
29339
|
-
}
|
|
29340
29594
|
// Draw horizontal entry line (divider)
|
|
29341
29595
|
const lineColor = this._private__positionOptions.entryLineColor;
|
|
29342
29596
|
ctx.strokeStyle = lineColor;
|
|
@@ -29450,9 +29704,11 @@
|
|
|
29450
29704
|
const padding = 6 * horizontalPixelRatio;
|
|
29451
29705
|
const labelWidth = textMetrics.width + padding * 2;
|
|
29452
29706
|
const labelHeight = 20 * horizontalPixelRatio;
|
|
29453
|
-
//
|
|
29707
|
+
// Outside the box, so a label wider than it does not cover the handles on its edge
|
|
29708
|
+
const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
|
|
29709
|
+
const gap = 3 * verticalPixelRatio;
|
|
29454
29710
|
const labelX = entryX + 8 * horizontalPixelRatio;
|
|
29455
|
-
const labelY = y - labelHeight
|
|
29711
|
+
const labelY = y <= entryY ? y - labelHeight - gap : y + gap;
|
|
29456
29712
|
// Draw label background
|
|
29457
29713
|
ctx.fillStyle = rect._internal_color;
|
|
29458
29714
|
ctx.fillRect(labelX, labelY, labelWidth, labelHeight);
|
|
@@ -29460,10 +29716,12 @@
|
|
|
29460
29716
|
ctx.fillStyle = '#ffffff';
|
|
29461
29717
|
ctx.textAlign = 'left';
|
|
29462
29718
|
ctx.textBaseline = 'middle';
|
|
29463
|
-
ctx.fillText(labelText, labelX + padding,
|
|
29719
|
+
ctx.fillText(labelText, labelX + padding, labelY + labelHeight / 2);
|
|
29464
29720
|
}
|
|
29465
29721
|
_private__drawEndpoint(ctx, price, color, horizontalPixelRatio, verticalPixelRatio, timeScale) {
|
|
29466
|
-
const
|
|
29722
|
+
const span = this._private__span();
|
|
29723
|
+
const entryX = span._internal_entryX * horizontalPixelRatio;
|
|
29724
|
+
const endX = span._internal_endX * horizontalPixelRatio;
|
|
29467
29725
|
const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
|
|
29468
29726
|
// Draw circle endpoint at entry
|
|
29469
29727
|
const circleColor = color;
|
|
@@ -29475,14 +29733,6 @@
|
|
|
29475
29733
|
ctx.fill();
|
|
29476
29734
|
ctx.stroke();
|
|
29477
29735
|
// Draw square endpoint at the right edge
|
|
29478
|
-
let endX;
|
|
29479
|
-
if (this._private__positionOptions.endTime) {
|
|
29480
|
-
endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
|
|
29481
|
-
}
|
|
29482
|
-
else {
|
|
29483
|
-
const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
|
|
29484
|
-
endX = chartWidth - 10 * horizontalPixelRatio;
|
|
29485
|
-
}
|
|
29486
29736
|
const size = 8 * horizontalPixelRatio;
|
|
29487
29737
|
const squareColor = color;
|
|
29488
29738
|
ctx.fillStyle = squareColor;
|
|
@@ -29603,18 +29853,12 @@
|
|
|
29603
29853
|
return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
|
|
29604
29854
|
}
|
|
29605
29855
|
hitTest(x, y) {
|
|
29606
|
-
if (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit) {
|
|
29856
|
+
if (!this.isComplete() || (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit)) {
|
|
29607
29857
|
return false;
|
|
29608
29858
|
}
|
|
29609
|
-
const
|
|
29610
|
-
|
|
29611
|
-
|
|
29612
|
-
endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
|
|
29613
|
-
}
|
|
29614
|
-
else {
|
|
29615
|
-
const chartWidth = this._chart.chartElement().clientWidth;
|
|
29616
|
-
endX = chartWidth;
|
|
29617
|
-
}
|
|
29859
|
+
const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
|
|
29860
|
+
const left = Math.min(entryX, endX);
|
|
29861
|
+
const right = Math.max(entryX, endX);
|
|
29618
29862
|
// Check if inside the entire Position Tool area (rectangles)
|
|
29619
29863
|
if (this._private__positionOptions.takeProfit && this._private__positionOptions.stopLoss) {
|
|
29620
29864
|
const tpY = this._series.priceToCoordinate(this._private__positionOptions.takeProfit) ?? 0;
|
|
@@ -29622,7 +29866,7 @@
|
|
|
29622
29866
|
const minY = Math.min(tpY, slY);
|
|
29623
29867
|
const maxY = Math.max(tpY, slY);
|
|
29624
29868
|
// Check if inside the rectangle bounds (entire tool area)
|
|
29625
|
-
if (x >=
|
|
29869
|
+
if (x >= left && x <= right && y >= minY && y <= maxY) {
|
|
29626
29870
|
return true;
|
|
29627
29871
|
}
|
|
29628
29872
|
// Also check entry line area
|
|
@@ -29634,15 +29878,12 @@
|
|
|
29634
29878
|
return false;
|
|
29635
29879
|
}
|
|
29636
29880
|
hitTestEndpoint(x, y) {
|
|
29637
|
-
|
|
29638
|
-
|
|
29639
|
-
if (this._private__positionOptions.endTime) {
|
|
29640
|
-
endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
|
|
29641
|
-
}
|
|
29642
|
-
else {
|
|
29643
|
-
const chartWidth = this._chart.chartElement().clientWidth;
|
|
29644
|
-
endX = chartWidth;
|
|
29881
|
+
if (!this.isComplete()) {
|
|
29882
|
+
return null;
|
|
29645
29883
|
}
|
|
29884
|
+
const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
|
|
29885
|
+
const left = Math.min(entryX, endX);
|
|
29886
|
+
const right = Math.max(entryX, endX);
|
|
29646
29887
|
const tolerance = 10;
|
|
29647
29888
|
// Check TP left endpoint (circle)
|
|
29648
29889
|
if (this._private__positionOptions.takeProfit) {
|
|
@@ -29679,7 +29920,7 @@
|
|
|
29679
29920
|
// Check entry line (middle divider line) - higher priority than 'move'
|
|
29680
29921
|
const entryY = this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0;
|
|
29681
29922
|
const lineTolerance = 8;
|
|
29682
|
-
if (x >=
|
|
29923
|
+
if (x >= left && x <= right && Math.abs(y - entryY) <= lineTolerance) {
|
|
29683
29924
|
return 'entry';
|
|
29684
29925
|
}
|
|
29685
29926
|
// Check if clicking on the body (rectangles) for moving entire tool
|
|
@@ -29689,7 +29930,7 @@
|
|
|
29689
29930
|
const minY = Math.min(tpY, slY);
|
|
29690
29931
|
const maxY = Math.max(tpY, slY);
|
|
29691
29932
|
// Check if inside the rectangle bounds
|
|
29692
|
-
if (x >=
|
|
29933
|
+
if (x >= left && x <= right && y >= minY && y <= maxY) {
|
|
29693
29934
|
return 'move';
|
|
29694
29935
|
}
|
|
29695
29936
|
}
|
|
@@ -29789,6 +30030,7 @@
|
|
|
29789
30030
|
this._private__requestUpdate();
|
|
29790
30031
|
}
|
|
29791
30032
|
remove() {
|
|
30033
|
+
this._private__placement?._internal_stop();
|
|
29792
30034
|
if (this._private__primitive) {
|
|
29793
30035
|
this._series.detachPrimitive(this._private__primitive);
|
|
29794
30036
|
this._private__primitive = null;
|
|
@@ -29835,6 +30077,7 @@
|
|
|
29835
30077
|
}
|
|
29836
30078
|
}
|
|
29837
30079
|
|
|
30080
|
+
const TOP_PADDING = 50;
|
|
29838
30081
|
class GanttTool extends DrawingToolBase {
|
|
29839
30082
|
constructor(context, options) {
|
|
29840
30083
|
super(context, 'gantt', options);
|
|
@@ -29848,6 +30091,7 @@
|
|
|
29848
30091
|
this._private__grab = null;
|
|
29849
30092
|
this._private__moveStart = null;
|
|
29850
30093
|
this._private__hoveredSegmentIndex = -1;
|
|
30094
|
+
this._private__placement = null;
|
|
29851
30095
|
this._private__ganttOptions = {
|
|
29852
30096
|
segments: [],
|
|
29853
30097
|
rowHeight: 40,
|
|
@@ -29860,6 +30104,43 @@
|
|
|
29860
30104
|
};
|
|
29861
30105
|
this._registerOptions(this._private__ganttOptions);
|
|
29862
30106
|
this._private__createPrimitive();
|
|
30107
|
+
if (this._private__ganttOptions.interactive) {
|
|
30108
|
+
this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
|
|
30109
|
+
}
|
|
30110
|
+
}
|
|
30111
|
+
_private__placeAt(point) {
|
|
30112
|
+
const segments = this._private__ganttOptions.segments ?? [];
|
|
30113
|
+
const endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? point._internal_time;
|
|
30114
|
+
const rowPitch = this._private__ganttOptions.rowHeight + this._private__ganttOptions.rowSpacing;
|
|
30115
|
+
const row = Math.max(0, Math.floor((point._internal_y - TOP_PADDING) / rowPitch));
|
|
30116
|
+
const shift = segments.length > 0 ? row - segments[0].level : 0;
|
|
30117
|
+
for (const segment of segments) {
|
|
30118
|
+
segment.startTime = point._internal_time;
|
|
30119
|
+
segment.endTime = endTime;
|
|
30120
|
+
segment.level = Math.max(0, segment.level + shift);
|
|
30121
|
+
}
|
|
30122
|
+
this._private__requestUpdate();
|
|
30123
|
+
}
|
|
30124
|
+
isComplete() {
|
|
30125
|
+
return !this._private__placement?._internal_pending();
|
|
30126
|
+
}
|
|
30127
|
+
selectionBox() {
|
|
30128
|
+
const segments = this._private__ganttOptions.segments ?? [];
|
|
30129
|
+
if (segments.length === 0) {
|
|
30130
|
+
return null;
|
|
30131
|
+
}
|
|
30132
|
+
const rowHeight = this._private__ganttOptions.rowHeight;
|
|
30133
|
+
const pitch = rowHeight + this._private__ganttOptions.rowSpacing;
|
|
30134
|
+
// Typed as the public shape: an inferred one gets its keys renamed by the build
|
|
30135
|
+
const box = { left: Infinity, top: Infinity, right: -Infinity, bottom: -Infinity };
|
|
30136
|
+
for (const segment of segments) {
|
|
30137
|
+
const top = TOP_PADDING + segment.level * pitch;
|
|
30138
|
+
box.left = Math.min(box.left, coordinateFromTime(this._chart, this._series, segment.startTime) ?? 0);
|
|
30139
|
+
box.right = Math.max(box.right, coordinateFromTime(this._chart, this._series, segment.endTime) ?? 0);
|
|
30140
|
+
box.top = Math.min(box.top, top);
|
|
30141
|
+
box.bottom = Math.max(box.bottom, top + rowHeight);
|
|
30142
|
+
}
|
|
30143
|
+
return box;
|
|
29863
30144
|
}
|
|
29864
30145
|
_private__createPrimitive() {
|
|
29865
30146
|
this._private__paneView = this._private__createPaneView();
|
|
@@ -29890,11 +30171,10 @@
|
|
|
29890
30171
|
return new GanttPaneView(this);
|
|
29891
30172
|
}
|
|
29892
30173
|
drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
29893
|
-
if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
|
|
30174
|
+
if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
|
|
29894
30175
|
return;
|
|
29895
30176
|
// Position gantt at top of chart with padding
|
|
29896
|
-
const
|
|
29897
|
-
const baseY = topPadding * verticalPixelRatio;
|
|
30177
|
+
const baseY = TOP_PADDING * verticalPixelRatio;
|
|
29898
30178
|
const rowHeight = this._private__ganttOptions.rowHeight * verticalPixelRatio;
|
|
29899
30179
|
const rowSpacing = this._private__ganttOptions.rowSpacing * verticalPixelRatio;
|
|
29900
30180
|
// Draw grid lines if enabled
|
|
@@ -30073,11 +30353,10 @@
|
|
|
30073
30353
|
this._private__moveStart = null;
|
|
30074
30354
|
}
|
|
30075
30355
|
hitTest(x, y) {
|
|
30076
|
-
if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
|
|
30356
|
+
if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
|
|
30077
30357
|
return false;
|
|
30078
30358
|
}
|
|
30079
|
-
const
|
|
30080
|
-
const baseY = topPadding;
|
|
30359
|
+
const baseY = TOP_PADDING;
|
|
30081
30360
|
const rowHeight = this._private__ganttOptions.rowHeight;
|
|
30082
30361
|
const rowSpacing = this._private__ganttOptions.rowSpacing;
|
|
30083
30362
|
// Check each segment
|
|
@@ -30131,6 +30410,7 @@
|
|
|
30131
30410
|
this._private__requestUpdate();
|
|
30132
30411
|
}
|
|
30133
30412
|
remove() {
|
|
30413
|
+
this._private__placement?._internal_stop();
|
|
30134
30414
|
if (this._private__primitive) {
|
|
30135
30415
|
this._series.detachPrimitive(this._private__primitive);
|
|
30136
30416
|
this._private__primitive = null;
|
|
@@ -30266,6 +30546,7 @@
|
|
|
30266
30546
|
}
|
|
30267
30547
|
this._private__completionCheck = setTimeout(() => {
|
|
30268
30548
|
this._private__completionCheck = null;
|
|
30549
|
+
const completed = [];
|
|
30269
30550
|
for (const id of Array.from(this._private__pendingToolIds)) {
|
|
30270
30551
|
const tool = this._private__tools.get(id);
|
|
30271
30552
|
if (!tool) {
|
|
@@ -30274,9 +30555,15 @@
|
|
|
30274
30555
|
}
|
|
30275
30556
|
if (tool.isComplete()) {
|
|
30276
30557
|
this._private__pendingToolIds.delete(id);
|
|
30277
|
-
|
|
30558
|
+
completed.push(id);
|
|
30278
30559
|
}
|
|
30279
30560
|
}
|
|
30561
|
+
if (completed.length > 0) {
|
|
30562
|
+
this.recordChange();
|
|
30563
|
+
}
|
|
30564
|
+
for (const id of completed) {
|
|
30565
|
+
this._private__drawingCompleted._internal_fire(id);
|
|
30566
|
+
}
|
|
30280
30567
|
}, 0);
|
|
30281
30568
|
}
|
|
30282
30569
|
/**
|
|
@@ -30336,12 +30623,16 @@
|
|
|
30336
30623
|
if (this._private__restoring) {
|
|
30337
30624
|
return;
|
|
30338
30625
|
}
|
|
30626
|
+
const next = this.serializeAll();
|
|
30627
|
+
if (JSON.stringify(next) === JSON.stringify(this._private__committed)) {
|
|
30628
|
+
return;
|
|
30629
|
+
}
|
|
30339
30630
|
this._private__past.push(this._private__committed);
|
|
30340
30631
|
if (this._private__past.length > UNDO_HISTORY_LIMIT) {
|
|
30341
30632
|
this._private__past.shift();
|
|
30342
30633
|
}
|
|
30343
30634
|
this._private__future = [];
|
|
30344
|
-
this._private__committed =
|
|
30635
|
+
this._private__committed = next;
|
|
30345
30636
|
}
|
|
30346
30637
|
canUndo() {
|
|
30347
30638
|
return this._private__past.length > 0;
|
|
@@ -30397,11 +30688,25 @@
|
|
|
30397
30688
|
const tool = this._private__tools.get(this._private__selectedToolId);
|
|
30398
30689
|
return tool ? this._private__boundsOf(tool) : null;
|
|
30399
30690
|
}
|
|
30691
|
+
/** Relative to the chart element; the tool's box is relative to its pane. */
|
|
30692
|
+
_private__boundsOf(tool) {
|
|
30693
|
+
const box = tool.selectionBox() ?? this._private__boxFromPoints(tool);
|
|
30694
|
+
if (box === null) {
|
|
30695
|
+
return null;
|
|
30696
|
+
}
|
|
30697
|
+
const offset = this._private__paneOffsetOf(tool);
|
|
30698
|
+
return {
|
|
30699
|
+
left: box.left + offset._internal_x,
|
|
30700
|
+
right: box.right + offset._internal_x,
|
|
30701
|
+
top: box.top + offset._internal_y,
|
|
30702
|
+
bottom: box.bottom + offset._internal_y,
|
|
30703
|
+
};
|
|
30704
|
+
}
|
|
30400
30705
|
/**
|
|
30401
30706
|
* Bounds come from the tool's own serialized points, so this works for every
|
|
30402
30707
|
* tool without each one having to report a box.
|
|
30403
30708
|
*/
|
|
30404
|
-
|
|
30709
|
+
_private__boxFromPoints(tool) {
|
|
30405
30710
|
const series = tool.series();
|
|
30406
30711
|
const points = tool.serialize().points;
|
|
30407
30712
|
let left = Number.POSITIVE_INFINITY;
|
|
@@ -30424,13 +30729,7 @@
|
|
|
30424
30729
|
if (!found) {
|
|
30425
30730
|
return null;
|
|
30426
30731
|
}
|
|
30427
|
-
|
|
30428
|
-
return {
|
|
30429
|
-
left: left + offset._internal_x,
|
|
30430
|
-
right: right + offset._internal_x,
|
|
30431
|
-
top: top + offset._internal_y,
|
|
30432
|
-
bottom: bottom + offset._internal_y,
|
|
30433
|
-
};
|
|
30732
|
+
return { left, top, right, bottom };
|
|
30434
30733
|
}
|
|
30435
30734
|
/** Panes below the first one are offset inside the chart element. */
|
|
30436
30735
|
_private__paneOffsetOf(tool) {
|
|
@@ -30514,7 +30813,10 @@
|
|
|
30514
30813
|
serializeAll() {
|
|
30515
30814
|
const result = [];
|
|
30516
30815
|
for (const tool of this._private__tools.values()) {
|
|
30517
|
-
|
|
30816
|
+
// Not drawn yet: undo would bring it back as a finished drawing
|
|
30817
|
+
if (tool.isComplete()) {
|
|
30818
|
+
result.push(tool.serialize());
|
|
30819
|
+
}
|
|
30518
30820
|
}
|
|
30519
30821
|
return result;
|
|
30520
30822
|
}
|
|
@@ -31139,7 +31441,7 @@
|
|
|
31139
31441
|
* Returns the current version as a string. For example `'1.0.0'`.
|
|
31140
31442
|
*/
|
|
31141
31443
|
function version() {
|
|
31142
|
-
return "2.3.
|
|
31444
|
+
return "2.3.14";
|
|
31143
31445
|
}
|
|
31144
31446
|
|
|
31145
31447
|
var PipsendChartsModule = /*#__PURE__*/Object.freeze({
|