@pipsend/charts 2.2.0 → 2.3.0
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/README.md +28 -0
- package/dist/pipsend-charts.development.mjs +1126 -121
- package/dist/pipsend-charts.production.mjs +2 -2
- package/dist/pipsend-charts.standalone.development.js +1127 -120
- package/dist/pipsend-charts.standalone.development.mjs +1126 -121
- package/dist/pipsend-charts.standalone.production.js +2 -2
- package/dist/pipsend-charts.standalone.production.mjs +2 -2
- package/dist/typings.d.ts +354 -1
- package/package.json +83 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* @license
|
|
3
|
-
* Pipsend Charts v2.
|
|
3
|
+
* Pipsend Charts v2.3.0-dev+202609220108
|
|
4
4
|
* Copyright (c) 2026 Pipsend
|
|
5
5
|
* Licensed under MIT License
|
|
6
6
|
* Built on TradingView Lightweight Charts™ (Apache 2.0)
|
|
@@ -14245,6 +14245,8 @@ function shapeSize(shape, originalSize) {
|
|
|
14245
14245
|
return size(originalSize, 0.8);
|
|
14246
14246
|
case 'square':
|
|
14247
14247
|
return size(originalSize, 0.7);
|
|
14248
|
+
case 'tag':
|
|
14249
|
+
return size(originalSize, 1);
|
|
14248
14250
|
}
|
|
14249
14251
|
}
|
|
14250
14252
|
function calculateShapeHeight(barSpacing) {
|
|
@@ -14354,6 +14356,85 @@ function hitTestSquare(centerX, centerY, size, x, y) {
|
|
|
14354
14356
|
y >= top && y <= top + squareSize;
|
|
14355
14357
|
}
|
|
14356
14358
|
|
|
14359
|
+
const TAIL_HALF_WIDTH_RATIO = 0.62;
|
|
14360
|
+
/**
|
|
14361
|
+
* Lays the pointer into the edge the path is already travelling along, so the
|
|
14362
|
+
* bubble and its tail are a single outline. Two subpaths would leave a stroked
|
|
14363
|
+
* seam where they meet.
|
|
14364
|
+
*/
|
|
14365
|
+
// eslint-disable-next-line max-params
|
|
14366
|
+
function tailPath(ctx, xCenter, edgeY, tailHeight, halfWidth, up) {
|
|
14367
|
+
// A tip with a small radius reads as drawn rather than as an artefact of the
|
|
14368
|
+
// rasteriser, which a bare triangle does at these sizes.
|
|
14369
|
+
const tipRadius = Math.min(halfWidth / 3, tailHeight / 3);
|
|
14370
|
+
const sign = up ? -1 : 1;
|
|
14371
|
+
const tipY = edgeY + sign * tailHeight;
|
|
14372
|
+
const shoulderY = tipY - sign * tipRadius;
|
|
14373
|
+
// The top edge is travelled left to right, the bottom edge right to left.
|
|
14374
|
+
const entry = up ? -halfWidth : halfWidth;
|
|
14375
|
+
ctx.lineTo(xCenter + entry, edgeY);
|
|
14376
|
+
ctx.lineTo(xCenter + (up ? -tipRadius : tipRadius), shoulderY);
|
|
14377
|
+
ctx.quadraticCurveTo(xCenter, tipY, xCenter + (up ? tipRadius : -tipRadius), shoulderY);
|
|
14378
|
+
ctx.lineTo(xCenter - entry, edgeY);
|
|
14379
|
+
}
|
|
14380
|
+
/**
|
|
14381
|
+
* Draws a rounded speech-bubble tag with an optional pointer towards its anchor.
|
|
14382
|
+
*/
|
|
14383
|
+
function drawTag(ctx, params) {
|
|
14384
|
+
const hpr = params._internal_horizontalPixelRatio;
|
|
14385
|
+
const vpr = params._internal_verticalPixelRatio;
|
|
14386
|
+
const left = Math.round((params._internal_x - params._internal_width / 2) * hpr);
|
|
14387
|
+
const right = Math.round((params._internal_x + params._internal_width / 2) * hpr);
|
|
14388
|
+
const top = Math.round((params._internal_centerY - params._internal_height / 2) * vpr);
|
|
14389
|
+
const bottom = Math.round((params._internal_centerY + params._internal_height / 2) * vpr);
|
|
14390
|
+
const xCenter = Math.round(params._internal_x * hpr);
|
|
14391
|
+
const radius = Math.min(params._internal_radius * hpr, (right - left) / 2, (bottom - top) / 2);
|
|
14392
|
+
const tailHeight = params._internal_tailHeight * vpr;
|
|
14393
|
+
const tailHalfWidth = Math.min(tailHeight * TAIL_HALF_WIDTH_RATIO, Math.max(0, (right - left) / 2 - radius));
|
|
14394
|
+
const hasTail = params._internal_tailDirection !== 'none' && tailHalfWidth > 0 && tailHeight > 0;
|
|
14395
|
+
ctx.save();
|
|
14396
|
+
ctx.beginPath();
|
|
14397
|
+
if (!hasTail) {
|
|
14398
|
+
drawRoundRect(ctx, left, top, right - left, bottom - top, [radius, radius, radius, radius]);
|
|
14399
|
+
}
|
|
14400
|
+
else {
|
|
14401
|
+
ctx.moveTo(left + radius, top);
|
|
14402
|
+
if (params._internal_tailDirection === 'up') {
|
|
14403
|
+
tailPath(ctx, xCenter, top, tailHeight, tailHalfWidth, true);
|
|
14404
|
+
}
|
|
14405
|
+
ctx.lineTo(right - radius, top);
|
|
14406
|
+
ctx.arcTo(right, top, right, top + radius, radius);
|
|
14407
|
+
ctx.lineTo(right, bottom - radius);
|
|
14408
|
+
ctx.arcTo(right, bottom, right - radius, bottom, radius);
|
|
14409
|
+
if (params._internal_tailDirection === 'down') {
|
|
14410
|
+
tailPath(ctx, xCenter, bottom, tailHeight, tailHalfWidth, false);
|
|
14411
|
+
}
|
|
14412
|
+
ctx.lineTo(left + radius, bottom);
|
|
14413
|
+
ctx.arcTo(left, bottom, left, bottom - radius, radius);
|
|
14414
|
+
ctx.lineTo(left, top + radius);
|
|
14415
|
+
ctx.arcTo(left, top, left + radius, top, radius);
|
|
14416
|
+
}
|
|
14417
|
+
ctx.closePath();
|
|
14418
|
+
ctx.fillStyle = params._internal_fillColor;
|
|
14419
|
+
ctx.fill();
|
|
14420
|
+
if (params._internal_borderColor !== undefined && params._internal_borderWidth > 0) {
|
|
14421
|
+
ctx.lineWidth = Math.max(1, Math.round(params._internal_borderWidth * hpr));
|
|
14422
|
+
ctx.strokeStyle = params._internal_borderColor;
|
|
14423
|
+
ctx.stroke();
|
|
14424
|
+
}
|
|
14425
|
+
ctx.restore();
|
|
14426
|
+
}
|
|
14427
|
+
function hitTestTag(centerX, centerY, tag, x, y) {
|
|
14428
|
+
if (tag._internal_width <= 0) {
|
|
14429
|
+
return false;
|
|
14430
|
+
}
|
|
14431
|
+
const halfHeight = tag._internal_height / 2;
|
|
14432
|
+
const top = tag._internal_tailDirection === 'up' ? centerY - halfHeight - tag._internal_tailHeight : centerY - halfHeight;
|
|
14433
|
+
const bottom = tag._internal_tailDirection === 'down' ? centerY + halfHeight + tag._internal_tailHeight : centerY + halfHeight;
|
|
14434
|
+
const left = centerX - tag._internal_width / 2;
|
|
14435
|
+
return x >= left && x <= left + tag._internal_width && y >= top && y <= bottom;
|
|
14436
|
+
}
|
|
14437
|
+
|
|
14357
14438
|
function drawText(ctx, text, x, y, horizontalPixelRatio, verticalPixelRatio) {
|
|
14358
14439
|
ctx.save();
|
|
14359
14440
|
ctx.scale(horizontalPixelRatio, verticalPixelRatio);
|
|
@@ -14370,9 +14451,11 @@ class SeriesMarkersRenderer {
|
|
|
14370
14451
|
constructor() {
|
|
14371
14452
|
this._private__data = null;
|
|
14372
14453
|
this._private__textWidthCache = new TextWidthCache();
|
|
14454
|
+
this._private__tagTextWidthCache = new TextWidthCache();
|
|
14373
14455
|
this._private__fontSize = -1;
|
|
14374
14456
|
this._private__fontFamily = '';
|
|
14375
14457
|
this._private__font = '';
|
|
14458
|
+
this._private__tagFont = '';
|
|
14376
14459
|
this._private__zOrder = 'normal';
|
|
14377
14460
|
}
|
|
14378
14461
|
_internal_setData(data) {
|
|
@@ -14383,7 +14466,11 @@ class SeriesMarkersRenderer {
|
|
|
14383
14466
|
this._private__fontSize = fontSize;
|
|
14384
14467
|
this._private__fontFamily = fontFamily;
|
|
14385
14468
|
this._private__font = makeFont(fontSize, fontFamily);
|
|
14469
|
+
// Tags carry a label rather than an annotation, so they get the heavier
|
|
14470
|
+
// weight — and their own cache, since widths differ per font.
|
|
14471
|
+
this._private__tagFont = makeFont(fontSize, fontFamily, '600');
|
|
14386
14472
|
this._private__textWidthCache._internal_reset();
|
|
14473
|
+
this._private__tagTextWidthCache._internal_reset();
|
|
14387
14474
|
}
|
|
14388
14475
|
this._private__zOrder = zOrder;
|
|
14389
14476
|
}
|
|
@@ -14423,14 +14510,21 @@ class SeriesMarkersRenderer {
|
|
|
14423
14510
|
return;
|
|
14424
14511
|
}
|
|
14425
14512
|
ctx.textBaseline = 'middle';
|
|
14426
|
-
ctx.font = this._private__font;
|
|
14427
14513
|
for (let index = this._private__data._internal_visibleRange.from; index < this._private__data._internal_visibleRange.to; index++) {
|
|
14428
14514
|
const item = this._private__data._internal_items[index];
|
|
14515
|
+
const isTag = item._internal_tag !== undefined;
|
|
14516
|
+
ctx.font = isTag ? this._private__tagFont : this._private__font;
|
|
14429
14517
|
if (item._internal_text !== undefined) {
|
|
14430
|
-
|
|
14518
|
+
const cache = isTag ? this._private__tagTextWidthCache : this._private__textWidthCache;
|
|
14519
|
+
item._internal_text._internal_width = cache._internal_measureText(ctx, item._internal_text._internal_content);
|
|
14431
14520
|
item._internal_text._internal_height = this._private__fontSize;
|
|
14432
14521
|
item._internal_text._internal_x = item._internal_x - item._internal_text._internal_width / 2;
|
|
14433
14522
|
}
|
|
14523
|
+
if (item._internal_tag !== undefined) {
|
|
14524
|
+
const textWidth = item._internal_text !== undefined ? item._internal_text._internal_width : 0;
|
|
14525
|
+
item._internal_tag._internal_width = Math.max(textWidth + item._internal_tag._internal_paddingX * 2, item._internal_tag._internal_height);
|
|
14526
|
+
item._internal_tag._internal_left = item._internal_x - item._internal_tag._internal_width / 2;
|
|
14527
|
+
}
|
|
14434
14528
|
drawItem(item, ctx, horizontalPixelRatio, verticalPixelRatio);
|
|
14435
14529
|
}
|
|
14436
14530
|
}
|
|
@@ -14445,12 +14539,40 @@ function bitmapShapeItemCoordinates(item, horizontalPixelRatio, verticalPixelRat
|
|
|
14445
14539
|
};
|
|
14446
14540
|
}
|
|
14447
14541
|
function drawItem(item, ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
14542
|
+
if (item._internal_tag !== undefined) {
|
|
14543
|
+
drawTagItem(item, item._internal_tag, ctx, horizontalPixelRatio, verticalPixelRatio);
|
|
14544
|
+
return;
|
|
14545
|
+
}
|
|
14448
14546
|
ctx.fillStyle = item._internal_color;
|
|
14449
14547
|
if (item._internal_text !== undefined) {
|
|
14450
14548
|
drawText(ctx, item._internal_text._internal_content, item._internal_text._internal_x, item._internal_text._internal_y, horizontalPixelRatio, verticalPixelRatio);
|
|
14451
14549
|
}
|
|
14452
14550
|
drawShape(item, ctx, bitmapShapeItemCoordinates(item, horizontalPixelRatio, verticalPixelRatio));
|
|
14453
14551
|
}
|
|
14552
|
+
// eslint-disable-next-line max-params
|
|
14553
|
+
function drawTagItem(item, tag, ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
14554
|
+
if (item._internal_size === 0) {
|
|
14555
|
+
return;
|
|
14556
|
+
}
|
|
14557
|
+
drawTag(ctx, {
|
|
14558
|
+
_internal_x: item._internal_x,
|
|
14559
|
+
_internal_centerY: item._internal_y,
|
|
14560
|
+
_internal_width: tag._internal_width,
|
|
14561
|
+
_internal_height: tag._internal_height,
|
|
14562
|
+
_internal_tailHeight: tag._internal_tailHeight,
|
|
14563
|
+
_internal_tailDirection: tag._internal_tailDirection,
|
|
14564
|
+
_internal_radius: tag._internal_radius,
|
|
14565
|
+
_internal_fillColor: item._internal_color,
|
|
14566
|
+
_internal_borderColor: item._internal_borderColor,
|
|
14567
|
+
_internal_borderWidth: item._internal_borderWidth ?? 1,
|
|
14568
|
+
_internal_horizontalPixelRatio: horizontalPixelRatio,
|
|
14569
|
+
_internal_verticalPixelRatio: verticalPixelRatio,
|
|
14570
|
+
});
|
|
14571
|
+
if (item._internal_text !== undefined) {
|
|
14572
|
+
ctx.fillStyle = item._internal_textColor ?? '#FFFFFF';
|
|
14573
|
+
drawText(ctx, item._internal_text._internal_content, item._internal_text._internal_x, item._internal_text._internal_y, horizontalPixelRatio, verticalPixelRatio);
|
|
14574
|
+
}
|
|
14575
|
+
}
|
|
14454
14576
|
function drawShape(item, ctx, coordinates) {
|
|
14455
14577
|
if (item._internal_size === 0) {
|
|
14456
14578
|
return;
|
|
@@ -14468,10 +14590,16 @@ function drawShape(item, ctx, coordinates) {
|
|
|
14468
14590
|
case 'square':
|
|
14469
14591
|
drawSquare(ctx, coordinates, item._internal_size);
|
|
14470
14592
|
return;
|
|
14593
|
+
case 'tag':
|
|
14594
|
+
// Handled by drawTagItem, which needs both pixel ratios and the text.
|
|
14595
|
+
return;
|
|
14471
14596
|
}
|
|
14472
14597
|
ensureNever(item._internal_shape);
|
|
14473
14598
|
}
|
|
14474
14599
|
function hitTestItem(item, x, y) {
|
|
14600
|
+
if (item._internal_tag !== undefined) {
|
|
14601
|
+
return item._internal_size !== 0 && hitTestTag(item._internal_x, item._internal_y, item._internal_tag, x, y);
|
|
14602
|
+
}
|
|
14475
14603
|
if (item._internal_text !== undefined && hitTestText(item._internal_text._internal_x, item._internal_text._internal_y, item._internal_text._internal_width, item._internal_text._internal_height, x, y)) {
|
|
14476
14604
|
return true;
|
|
14477
14605
|
}
|
|
@@ -14490,6 +14618,8 @@ function hitTestShape(item, x, y) {
|
|
|
14490
14618
|
return hitTestCircle(item._internal_x, item._internal_y, item._internal_size, x, y);
|
|
14491
14619
|
case 'square':
|
|
14492
14620
|
return hitTestSquare(item._internal_x, item._internal_y, item._internal_size, x, y);
|
|
14621
|
+
case 'tag':
|
|
14622
|
+
return false;
|
|
14493
14623
|
}
|
|
14494
14624
|
}
|
|
14495
14625
|
|
|
@@ -14522,15 +14652,71 @@ function getPrice(seriesData, marker, isInverted) {
|
|
|
14522
14652
|
}
|
|
14523
14653
|
return;
|
|
14524
14654
|
}
|
|
14655
|
+
/**
|
|
14656
|
+
* Lays out a `tag` marker: the bubble carries the text, so it is measured from
|
|
14657
|
+
* the font rather than from the bar spacing, and its pointer is aimed back at
|
|
14658
|
+
* the anchor price.
|
|
14659
|
+
*/
|
|
14660
|
+
// eslint-disable-next-line max-params
|
|
14661
|
+
function fillTagSizeAndY(rendererItem, marker, anchorY, offsets, textHeight, shapeMargin, sizeMultiplier) {
|
|
14662
|
+
const height = Math.round((textHeight + 5 /* Constants.TagPaddingY */ * 2) * sizeMultiplier);
|
|
14663
|
+
const tailHeight = Math.round(7 /* Constants.TagTailHeight */ * sizeMultiplier);
|
|
14664
|
+
const ignoreOffset = isPriceMarker(marker.position);
|
|
14665
|
+
const halfHeight = height / 2;
|
|
14666
|
+
let tailDirection = 'none';
|
|
14667
|
+
let centerY = anchorY;
|
|
14668
|
+
switch (marker.position) {
|
|
14669
|
+
case 'aboveBar':
|
|
14670
|
+
case 'atPriceTop': {
|
|
14671
|
+
const offset = ignoreOffset ? 0 : offsets._internal_aboveBar;
|
|
14672
|
+
tailDirection = 'down';
|
|
14673
|
+
centerY = (anchorY - offset - tailHeight - halfHeight);
|
|
14674
|
+
if (!ignoreOffset) {
|
|
14675
|
+
offsets._internal_aboveBar += height + tailHeight + shapeMargin;
|
|
14676
|
+
}
|
|
14677
|
+
break;
|
|
14678
|
+
}
|
|
14679
|
+
case 'belowBar':
|
|
14680
|
+
case 'atPriceBottom': {
|
|
14681
|
+
const offset = ignoreOffset ? 0 : offsets._internal_belowBar;
|
|
14682
|
+
tailDirection = 'up';
|
|
14683
|
+
centerY = (anchorY + offset + tailHeight + halfHeight);
|
|
14684
|
+
if (!ignoreOffset) {
|
|
14685
|
+
offsets._internal_belowBar += height + tailHeight + shapeMargin;
|
|
14686
|
+
}
|
|
14687
|
+
break;
|
|
14688
|
+
}
|
|
14689
|
+
}
|
|
14690
|
+
const tag = {
|
|
14691
|
+
_internal_height: height,
|
|
14692
|
+
_internal_tailHeight: tailHeight,
|
|
14693
|
+
_internal_tailDirection: tailDirection,
|
|
14694
|
+
_internal_radius: marker.borderRadius ?? 6 /* Constants.TagRadius */,
|
|
14695
|
+
_internal_paddingX: 9 /* Constants.TagPaddingX */ * sizeMultiplier,
|
|
14696
|
+
_internal_left: 0,
|
|
14697
|
+
_internal_width: 0,
|
|
14698
|
+
};
|
|
14699
|
+
rendererItem._internal_size = height;
|
|
14700
|
+
rendererItem._internal_y = centerY;
|
|
14701
|
+
rendererItem._internal_tag = tag;
|
|
14702
|
+
if (rendererItem._internal_text !== undefined) {
|
|
14703
|
+
rendererItem._internal_text._internal_y = centerY;
|
|
14704
|
+
}
|
|
14705
|
+
}
|
|
14525
14706
|
// eslint-disable-next-line max-params, complexity
|
|
14526
|
-
function fillSizeAndY(rendererItem, marker, seriesData, offsets, textHeight, shapeMargin, series, chart) {
|
|
14527
|
-
const price = getPrice(seriesData, marker,
|
|
14707
|
+
function fillSizeAndY(rendererItem, marker, seriesData, offsets, textHeight, shapeMargin, series, chart, invertScale) {
|
|
14708
|
+
const price = getPrice(seriesData, marker, invertScale);
|
|
14528
14709
|
if (price === undefined) {
|
|
14529
14710
|
return;
|
|
14530
14711
|
}
|
|
14531
14712
|
const ignoreOffset = isPriceMarker(marker.position);
|
|
14532
14713
|
const timeScale = chart.timeScale();
|
|
14533
14714
|
const sizeMultiplier = isNumber(marker.size) ? Math.max(marker.size, 0) : 1;
|
|
14715
|
+
if (marker.shape === 'tag') {
|
|
14716
|
+
const anchorY = ensureNotNull(series.priceToCoordinate(price));
|
|
14717
|
+
fillTagSizeAndY(rendererItem, marker, anchorY, offsets, textHeight, shapeMargin, sizeMultiplier);
|
|
14718
|
+
return;
|
|
14719
|
+
}
|
|
14534
14720
|
const shapeSize = calculateShapeHeight(timeScale.options().barSpacing) * sizeMultiplier;
|
|
14535
14721
|
const halfSize = shapeSize / 2;
|
|
14536
14722
|
rendererItem._internal_size = shapeSize;
|
|
@@ -14637,6 +14823,10 @@ class SeriesMarkersPaneView {
|
|
|
14637
14823
|
_internal_externalId: marker.id,
|
|
14638
14824
|
_internal_internalId: marker._internal_internalId,
|
|
14639
14825
|
_internal_text: undefined,
|
|
14826
|
+
_internal_textColor: marker.textColor,
|
|
14827
|
+
_internal_borderColor: marker.borderColor,
|
|
14828
|
+
_internal_borderWidth: marker.borderWidth,
|
|
14829
|
+
_internal_tag: undefined,
|
|
14640
14830
|
}));
|
|
14641
14831
|
this._private__dataInvalidated = false;
|
|
14642
14832
|
}
|
|
@@ -14647,13 +14837,22 @@ class SeriesMarkersPaneView {
|
|
|
14647
14837
|
return;
|
|
14648
14838
|
}
|
|
14649
14839
|
const visibleBarsRange = new RangeImpl(Math.floor(visibleBars.from), Math.ceil(visibleBars.to));
|
|
14650
|
-
const firstValue = this._private__series.
|
|
14840
|
+
const firstValue = this._private__series.dataByIndex(0, 1 /* MismatchDirection.NearestRight */);
|
|
14651
14841
|
if (firstValue === null) {
|
|
14652
14842
|
return;
|
|
14653
14843
|
}
|
|
14654
14844
|
if (this._private__data._internal_items.length === 0) {
|
|
14655
14845
|
return;
|
|
14656
14846
|
}
|
|
14847
|
+
// This runs inside the hit-test and paint loops, where a throw costs the
|
|
14848
|
+
// rest of the frame.
|
|
14849
|
+
let invertScale = false;
|
|
14850
|
+
try {
|
|
14851
|
+
invertScale = this._private__series.priceScale().options().invertScale;
|
|
14852
|
+
}
|
|
14853
|
+
catch (error) {
|
|
14854
|
+
invertScale = false;
|
|
14855
|
+
}
|
|
14657
14856
|
let prevTimeIndex = NaN;
|
|
14658
14857
|
const shapeMargin$1 = shapeMargin(timeScale.options().barSpacing);
|
|
14659
14858
|
const offsets = {
|
|
@@ -14684,7 +14883,7 @@ class SeriesMarkersPaneView {
|
|
|
14684
14883
|
if (dataAt === null) {
|
|
14685
14884
|
continue;
|
|
14686
14885
|
}
|
|
14687
|
-
fillSizeAndY(rendererItem, marker, dataAt, offsets, layoutOptions.fontSize, shapeMargin$1, this._private__series, this._private__chart);
|
|
14886
|
+
fillSizeAndY(rendererItem, marker, dataAt, offsets, layoutOptions.fontSize, shapeMargin$1, this._private__series, this._private__chart, invertScale);
|
|
14688
14887
|
}
|
|
14689
14888
|
this._private__invalidated = false;
|
|
14690
14889
|
}
|
|
@@ -14842,6 +15041,10 @@ class SeriesMarkersPrimitive {
|
|
|
14842
15041
|
text: marker.text,
|
|
14843
15042
|
size: marker.size,
|
|
14844
15043
|
price: marker.price,
|
|
15044
|
+
textColor: marker.textColor,
|
|
15045
|
+
borderColor: marker.borderColor,
|
|
15046
|
+
borderWidth: marker.borderWidth,
|
|
15047
|
+
borderRadius: marker.borderRadius,
|
|
14845
15048
|
_internal_originalTime: marker.time,
|
|
14846
15049
|
};
|
|
14847
15050
|
if (marker.position === 'atPriceTop' ||
|
|
@@ -19143,6 +19346,7 @@ class TradingLineController {
|
|
|
19143
19346
|
constructor(chart, element) {
|
|
19144
19347
|
this._private__lines = new Map();
|
|
19145
19348
|
this._private__hoveredId = null;
|
|
19349
|
+
this._private__hoveredPart = null;
|
|
19146
19350
|
this._private__dragging = null;
|
|
19147
19351
|
this._private__pointerId = null;
|
|
19148
19352
|
this._private__pressOrigin = null;
|
|
@@ -19200,8 +19404,12 @@ class TradingLineController {
|
|
|
19200
19404
|
if (this._private__dragging !== null) {
|
|
19201
19405
|
return;
|
|
19202
19406
|
}
|
|
19203
|
-
const
|
|
19407
|
+
const hit = this._private__splitHit(param.hoveredObjectId);
|
|
19408
|
+
const hovered = hit === null ? null : this._private__lines.get(hit._internal_id) ?? null;
|
|
19204
19409
|
this._private__setHovered(hovered ? hovered.id() : null);
|
|
19410
|
+
// The part is updated after the id, and separately from it: moving from a
|
|
19411
|
+
// label onto its close button keeps the same line hovered.
|
|
19412
|
+
this._private__setHoveredPart(hovered, hovered === null ? null : hit?._internal_part ?? null);
|
|
19205
19413
|
this._private__applyCursor();
|
|
19206
19414
|
}
|
|
19207
19415
|
_private__onPointerDown(event) {
|
|
@@ -19305,24 +19513,43 @@ class TradingLineController {
|
|
|
19305
19513
|
if (line === null) {
|
|
19306
19514
|
return null;
|
|
19307
19515
|
}
|
|
19308
|
-
//
|
|
19309
|
-
//
|
|
19310
|
-
//
|
|
19516
|
+
// The pointer event is the gesture's own position, so it is the source of
|
|
19517
|
+
// truth. The crosshair is only a mirror of it: it stops updating as soon
|
|
19518
|
+
// as the pointer leaves the pane canvas — over the price axis, past the
|
|
19519
|
+
// chart's edge — and a drag driven from it freezes there while the user
|
|
19520
|
+
// is still moving.
|
|
19521
|
+
const pane = this._private__chart.panes()[line.paneIndex()];
|
|
19522
|
+
const paneElement = pane ? pane.getHTMLElement() : null;
|
|
19523
|
+
if (paneElement) {
|
|
19524
|
+
return line.priceAt(event.clientY - paneElement.getBoundingClientRect().top);
|
|
19525
|
+
}
|
|
19311
19526
|
if (this._private__point !== null && this._private__paneIndex === line.paneIndex()) {
|
|
19312
19527
|
return line.priceAt(this._private__point._internal_y);
|
|
19313
19528
|
}
|
|
19314
|
-
|
|
19315
|
-
|
|
19316
|
-
|
|
19529
|
+
return null;
|
|
19530
|
+
}
|
|
19531
|
+
/**
|
|
19532
|
+
* Hit ids are either a bare line id or `id#part`, which is how a line with
|
|
19533
|
+
* several targets reports which one was hit.
|
|
19534
|
+
*/
|
|
19535
|
+
_private__splitHit(externalId) {
|
|
19536
|
+
if (typeof externalId !== 'string' || externalId.length === 0) {
|
|
19317
19537
|
return null;
|
|
19318
19538
|
}
|
|
19319
|
-
|
|
19539
|
+
const separator = externalId.indexOf('#');
|
|
19540
|
+
if (separator === -1) {
|
|
19541
|
+
return { _internal_id: externalId, _internal_part: null };
|
|
19542
|
+
}
|
|
19543
|
+
return { _internal_id: externalId.slice(0, separator), _internal_part: externalId.slice(separator + 1) };
|
|
19320
19544
|
}
|
|
19321
|
-
|
|
19322
|
-
if (
|
|
19323
|
-
return
|
|
19545
|
+
_private__setHoveredPart(line, part) {
|
|
19546
|
+
if (this._private__hoveredPart === part) {
|
|
19547
|
+
return;
|
|
19548
|
+
}
|
|
19549
|
+
this._private__hoveredPart = part;
|
|
19550
|
+
if (line && line.setHoveredPart) {
|
|
19551
|
+
line.setHoveredPart(part);
|
|
19324
19552
|
}
|
|
19325
|
-
return this._private__lines.get(externalId) ?? null;
|
|
19326
19553
|
}
|
|
19327
19554
|
_private__setHovered(id) {
|
|
19328
19555
|
if (this._private__hoveredId === id) {
|
|
@@ -19332,6 +19559,7 @@ class TradingLineController {
|
|
|
19332
19559
|
this._private__lines.get(this._private__hoveredId)?.setHovered(false);
|
|
19333
19560
|
}
|
|
19334
19561
|
this._private__hoveredId = id;
|
|
19562
|
+
this._private__hoveredPart = null;
|
|
19335
19563
|
if (id !== null) {
|
|
19336
19564
|
this._private__lines.get(id)?.setHovered(true);
|
|
19337
19565
|
}
|
|
@@ -19855,6 +20083,702 @@ function createInteractiveLineManager(chart, series) {
|
|
|
19855
20083
|
return new InteractiveLineManager(chart, series);
|
|
19856
20084
|
}
|
|
19857
20085
|
|
|
20086
|
+
const positionLineDefaultColors = {
|
|
20087
|
+
entry: '#2962FF',
|
|
20088
|
+
takeProfit: '#26A17B',
|
|
20089
|
+
stopLoss: '#D9A23B',
|
|
20090
|
+
};
|
|
20091
|
+
const positionLineThemeDefaults = {
|
|
20092
|
+
badgeTextColor: '#FFFFFF',
|
|
20093
|
+
borderRadius: 4,
|
|
20094
|
+
borderWidth: 1,
|
|
20095
|
+
paddingX: 8,
|
|
20096
|
+
paddingY: 6,
|
|
20097
|
+
backgroundOpacity: 1,
|
|
20098
|
+
};
|
|
20099
|
+
const positionLineLevels = ['takeProfit', 'entry', 'stopLoss'];
|
|
20100
|
+
|
|
20101
|
+
class PositionLineRenderer {
|
|
20102
|
+
constructor() {
|
|
20103
|
+
this._private__data = null;
|
|
20104
|
+
this._private__layouts = [];
|
|
20105
|
+
this._private__connectorX = 0;
|
|
20106
|
+
this._private__font = '';
|
|
20107
|
+
this._private__colorParser = new ColorParser([]);
|
|
20108
|
+
this._private__textWidthCache = new TextWidthCache(50);
|
|
20109
|
+
}
|
|
20110
|
+
_internal_setData(data) {
|
|
20111
|
+
this._private__data = data;
|
|
20112
|
+
}
|
|
20113
|
+
_internal_hitTest(x, y) {
|
|
20114
|
+
// The close button sits on top of the label, which sits on top of the
|
|
20115
|
+
// line, so the narrowest target is tested first.
|
|
20116
|
+
for (const layout of this._private__layouts) {
|
|
20117
|
+
if (layout._internal_closable && x >= layout._internal_closeLeft && x <= layout._internal_closeRight &&
|
|
20118
|
+
Math.abs(y - layout._internal_y) <= layout._internal_height / 2) {
|
|
20119
|
+
return { _internal_level: layout._internal_level, _internal_part: 'close' };
|
|
20120
|
+
}
|
|
20121
|
+
}
|
|
20122
|
+
for (const layout of this._private__layouts) {
|
|
20123
|
+
if (x >= layout._internal_pillLeft && x <= layout._internal_pillRight && Math.abs(y - layout._internal_y) <= layout._internal_height / 2) {
|
|
20124
|
+
return { _internal_level: layout._internal_level, _internal_part: 'body' };
|
|
20125
|
+
}
|
|
20126
|
+
}
|
|
20127
|
+
for (const layout of this._private__layouts) {
|
|
20128
|
+
if (Math.abs(y - layout._internal_y) <= 8 /* Constants.LineHitTolerance */) {
|
|
20129
|
+
return { _internal_level: layout._internal_level, _internal_part: 'line' };
|
|
20130
|
+
}
|
|
20131
|
+
}
|
|
20132
|
+
return null;
|
|
20133
|
+
}
|
|
20134
|
+
draw(target) {
|
|
20135
|
+
target.useBitmapCoordinateSpace((scope) => this._private__drawImpl(scope));
|
|
20136
|
+
}
|
|
20137
|
+
_private__drawImpl(scope) {
|
|
20138
|
+
const data = this._private__data;
|
|
20139
|
+
this._private__layouts = [];
|
|
20140
|
+
if (data === null || data._internal_levels.length === 0) {
|
|
20141
|
+
return;
|
|
20142
|
+
}
|
|
20143
|
+
const { context: ctx, mediaSize } = scope;
|
|
20144
|
+
ctx.save();
|
|
20145
|
+
if (this._private__font !== data._internal_font) {
|
|
20146
|
+
this._private__font = data._internal_font;
|
|
20147
|
+
this._private__textWidthCache._internal_reset();
|
|
20148
|
+
}
|
|
20149
|
+
ctx.font = data._internal_font;
|
|
20150
|
+
ctx.textBaseline = 'middle';
|
|
20151
|
+
this._private__connectorX = Math.max(0, mediaSize.width - data._internal_rightOffset);
|
|
20152
|
+
const lineEnd = data._internal_connectorVisible ? this._private__connectorX : mediaSize.width;
|
|
20153
|
+
for (const level of data._internal_levels) {
|
|
20154
|
+
this._private__layouts.push(this._private__drawLevel(scope, data, level, lineEnd));
|
|
20155
|
+
}
|
|
20156
|
+
if (data._internal_connectorVisible && data._internal_levels.length > 1) {
|
|
20157
|
+
this._private__drawConnector(scope, data);
|
|
20158
|
+
}
|
|
20159
|
+
ctx.restore();
|
|
20160
|
+
}
|
|
20161
|
+
// eslint-disable-next-line max-params
|
|
20162
|
+
_private__drawLevel(scope, data, level, lineEnd) {
|
|
20163
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20164
|
+
const height = data._internal_fontSize + data._internal_paddingY * 2;
|
|
20165
|
+
const badgeWidth = level._internal_badge.length === 0
|
|
20166
|
+
? 0
|
|
20167
|
+
: Math.max(this._private__textWidthCache._internal_measureText(ctx, level._internal_badge) + data._internal_paddingX * 2, height * 0.85 /* Constants.BadgeMinWidthRatio */);
|
|
20168
|
+
const labelWidth = this._private__textWidthCache._internal_measureText(ctx, level._internal_text) + data._internal_paddingX * 2;
|
|
20169
|
+
const closeWidth = level._internal_closable ? height : 0;
|
|
20170
|
+
const pillWidth = badgeWidth + labelWidth + closeWidth;
|
|
20171
|
+
const pillRight = Math.max(pillWidth, lineEnd - 8 /* Constants.LabelGap */);
|
|
20172
|
+
const pillLeft = pillRight - pillWidth;
|
|
20173
|
+
const top = level._internal_y - height / 2;
|
|
20174
|
+
this._private__drawLineSegments(scope, level, pillLeft, pillRight, lineEnd);
|
|
20175
|
+
this._private__drawPill(scope, data, level, { _internal_left: pillLeft, _internal_top: top, _internal_width: pillWidth, _internal_height: height });
|
|
20176
|
+
let cellLeft = pillLeft;
|
|
20177
|
+
if (badgeWidth > 0) {
|
|
20178
|
+
this._private__drawBadge(scope, data, level, { _internal_left: cellLeft, _internal_top: top, _internal_width: badgeWidth, _internal_height: height });
|
|
20179
|
+
cellLeft += badgeWidth;
|
|
20180
|
+
this._private__drawSeparator(ctx, level._internal_color, cellLeft, top, height, hpr, vpr);
|
|
20181
|
+
}
|
|
20182
|
+
ctx.save();
|
|
20183
|
+
ctx.scale(hpr, vpr);
|
|
20184
|
+
ctx.textAlign = 'center';
|
|
20185
|
+
ctx.fillStyle = data._internal_textColor ?? level._internal_color;
|
|
20186
|
+
ctx.fillText(level._internal_text, cellLeft + labelWidth / 2, level._internal_y);
|
|
20187
|
+
ctx.restore();
|
|
20188
|
+
cellLeft += labelWidth;
|
|
20189
|
+
if (level._internal_closable) {
|
|
20190
|
+
this._private__drawSeparator(ctx, level._internal_color, cellLeft, top, height, hpr, vpr);
|
|
20191
|
+
this._private__drawCloseButton(scope, data, level, { _internal_left: cellLeft, _internal_top: top, _internal_width: closeWidth, _internal_height: height });
|
|
20192
|
+
}
|
|
20193
|
+
return {
|
|
20194
|
+
_internal_level: level._internal_level,
|
|
20195
|
+
_internal_y: level._internal_y,
|
|
20196
|
+
_internal_height: height,
|
|
20197
|
+
_internal_pillLeft: pillLeft,
|
|
20198
|
+
_internal_pillRight: pillRight,
|
|
20199
|
+
_internal_closeLeft: cellLeft,
|
|
20200
|
+
_internal_closeRight: cellLeft + closeWidth,
|
|
20201
|
+
_internal_closable: level._internal_closable,
|
|
20202
|
+
};
|
|
20203
|
+
}
|
|
20204
|
+
// eslint-disable-next-line max-params
|
|
20205
|
+
_private__drawLineSegments(scope, level, pillLeft, pillRight, lineEnd) {
|
|
20206
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20207
|
+
const lineWidth = Math.max(1, Math.round(level._internal_lineWidth * vpr));
|
|
20208
|
+
const y = Math.round(level._internal_y * vpr) - Math.floor(lineWidth / 2);
|
|
20209
|
+
ctx.save();
|
|
20210
|
+
ctx.strokeStyle = level._internal_color;
|
|
20211
|
+
ctx.lineWidth = lineWidth;
|
|
20212
|
+
setLineStyle(ctx, level._internal_lineStyle);
|
|
20213
|
+
ctx.beginPath();
|
|
20214
|
+
ctx.moveTo(0, y);
|
|
20215
|
+
ctx.lineTo(Math.round(Math.max(0, pillLeft - 8 /* Constants.LabelGap */) * hpr), y);
|
|
20216
|
+
if (lineEnd > pillRight + 8 /* Constants.LabelGap */) {
|
|
20217
|
+
ctx.moveTo(Math.round((pillRight + 8 /* Constants.LabelGap */) * hpr), y);
|
|
20218
|
+
ctx.lineTo(Math.round(lineEnd * hpr), y);
|
|
20219
|
+
}
|
|
20220
|
+
ctx.stroke();
|
|
20221
|
+
ctx.restore();
|
|
20222
|
+
}
|
|
20223
|
+
// eslint-disable-next-line max-params
|
|
20224
|
+
_private__drawPill(scope, data, level, box) {
|
|
20225
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20226
|
+
const hovered = data._internal_hoveredLevel === level._internal_level;
|
|
20227
|
+
const borderWidth = Math.max(1, Math.round((hovered ? data._internal_borderWidth + 1 : data._internal_borderWidth) * hpr));
|
|
20228
|
+
ctx.save();
|
|
20229
|
+
ctx.setLineDash([]);
|
|
20230
|
+
bitmapRoundRect(ctx, box, data._internal_borderRadius, hpr, vpr);
|
|
20231
|
+
ctx.fillStyle = this._private__colorParser._internal_applyAlpha(data._internal_background, data._internal_backgroundOpacity);
|
|
20232
|
+
ctx.fill();
|
|
20233
|
+
ctx.lineWidth = borderWidth;
|
|
20234
|
+
ctx.strokeStyle = level._internal_color;
|
|
20235
|
+
ctx.stroke();
|
|
20236
|
+
ctx.restore();
|
|
20237
|
+
}
|
|
20238
|
+
// eslint-disable-next-line max-params
|
|
20239
|
+
_private__drawBadge(scope, data, level, box) {
|
|
20240
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20241
|
+
if (level._internal_badgeFilled) {
|
|
20242
|
+
// The badge shares the label's left corners, so its fill has to stop
|
|
20243
|
+
// short of the border instead of squaring it off.
|
|
20244
|
+
const inset = data._internal_borderWidth;
|
|
20245
|
+
ctx.save();
|
|
20246
|
+
bitmapRoundRect(ctx, { _internal_left: box._internal_left + inset, _internal_top: box._internal_top + inset, _internal_width: box._internal_width - inset, _internal_height: box._internal_height - inset * 2 }, Math.max(0, data._internal_borderRadius - inset), hpr, vpr, 'left');
|
|
20247
|
+
ctx.fillStyle = level._internal_color;
|
|
20248
|
+
ctx.fill();
|
|
20249
|
+
ctx.restore();
|
|
20250
|
+
}
|
|
20251
|
+
ctx.save();
|
|
20252
|
+
ctx.scale(hpr, vpr);
|
|
20253
|
+
ctx.textAlign = 'center';
|
|
20254
|
+
ctx.fillStyle = level._internal_badgeFilled ? data._internal_badgeTextColor : level._internal_color;
|
|
20255
|
+
ctx.fillText(level._internal_badge, box._internal_left + box._internal_width / 2, box._internal_top + box._internal_height / 2);
|
|
20256
|
+
ctx.restore();
|
|
20257
|
+
}
|
|
20258
|
+
// eslint-disable-next-line max-params
|
|
20259
|
+
_private__drawSeparator(ctx, color, x, top, height, hpr, vpr) {
|
|
20260
|
+
const width = Math.max(1, Math.floor(hpr));
|
|
20261
|
+
ctx.save();
|
|
20262
|
+
ctx.setLineDash([]);
|
|
20263
|
+
ctx.fillStyle = this._private__colorParser._internal_applyAlpha(color, 0.4 /* Constants.SeparatorAlpha */);
|
|
20264
|
+
ctx.fillRect(Math.round(x * hpr), Math.round(top * vpr), width, Math.round(height * vpr));
|
|
20265
|
+
ctx.restore();
|
|
20266
|
+
}
|
|
20267
|
+
// eslint-disable-next-line max-params
|
|
20268
|
+
_private__drawCloseButton(scope, data, level, box) {
|
|
20269
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20270
|
+
const hovered = data._internal_hoveredLevel === level._internal_level && data._internal_hoveredPart === 'close';
|
|
20271
|
+
if (hovered) {
|
|
20272
|
+
ctx.save();
|
|
20273
|
+
const inset = data._internal_borderWidth;
|
|
20274
|
+
bitmapRoundRect(ctx, { _internal_left: box._internal_left, _internal_top: box._internal_top + inset, _internal_width: box._internal_width - inset, _internal_height: box._internal_height - inset * 2 }, Math.max(0, data._internal_borderRadius - inset), hpr, vpr, 'right');
|
|
20275
|
+
ctx.fillStyle = this._private__colorParser._internal_applyAlpha(level._internal_color, 0.25);
|
|
20276
|
+
ctx.fill();
|
|
20277
|
+
ctx.restore();
|
|
20278
|
+
}
|
|
20279
|
+
const centerX = (box._internal_left + box._internal_width / 2) * hpr;
|
|
20280
|
+
const centerY = (box._internal_top + box._internal_height / 2) * vpr;
|
|
20281
|
+
const arm = (7 /* Constants.CloseGlyphSize */ / 2) * hpr;
|
|
20282
|
+
ctx.save();
|
|
20283
|
+
ctx.setLineDash([]);
|
|
20284
|
+
ctx.lineCap = 'round';
|
|
20285
|
+
ctx.lineWidth = Math.max(1, Math.round(1.4 * hpr));
|
|
20286
|
+
ctx.strokeStyle = level._internal_color;
|
|
20287
|
+
ctx.beginPath();
|
|
20288
|
+
ctx.moveTo(centerX - arm, centerY - arm);
|
|
20289
|
+
ctx.lineTo(centerX + arm, centerY + arm);
|
|
20290
|
+
ctx.moveTo(centerX + arm, centerY - arm);
|
|
20291
|
+
ctx.lineTo(centerX - arm, centerY + arm);
|
|
20292
|
+
ctx.stroke();
|
|
20293
|
+
ctx.restore();
|
|
20294
|
+
}
|
|
20295
|
+
_private__drawConnector(scope, data) {
|
|
20296
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20297
|
+
const entry = data._internal_levels.find((level) => level._internal_level === 'entry') ?? data._internal_levels[0];
|
|
20298
|
+
const ys = data._internal_levels.map((level) => level._internal_y);
|
|
20299
|
+
const x = Math.round(this._private__connectorX * hpr);
|
|
20300
|
+
ctx.save();
|
|
20301
|
+
ctx.setLineDash([]);
|
|
20302
|
+
ctx.strokeStyle = entry._internal_color;
|
|
20303
|
+
ctx.lineWidth = Math.max(1, Math.floor(hpr));
|
|
20304
|
+
ctx.beginPath();
|
|
20305
|
+
ctx.moveTo(x, Math.round(Math.min(...ys) * vpr));
|
|
20306
|
+
ctx.lineTo(x, Math.round(Math.max(...ys) * vpr));
|
|
20307
|
+
ctx.stroke();
|
|
20308
|
+
for (const level of data._internal_levels) {
|
|
20309
|
+
ctx.beginPath();
|
|
20310
|
+
ctx.arc(x, Math.round(level._internal_y * vpr), 4.5 /* Constants.HandleRadius */ * hpr, 0, 2 * Math.PI);
|
|
20311
|
+
ctx.fillStyle = level._internal_level === 'entry'
|
|
20312
|
+
? entry._internal_color
|
|
20313
|
+
: this._private__colorParser._internal_applyAlpha(data._internal_background, data._internal_backgroundOpacity);
|
|
20314
|
+
ctx.fill();
|
|
20315
|
+
ctx.lineWidth = Math.max(1, Math.round(1.5 * hpr));
|
|
20316
|
+
ctx.strokeStyle = level._internal_color;
|
|
20317
|
+
ctx.stroke();
|
|
20318
|
+
}
|
|
20319
|
+
ctx.restore();
|
|
20320
|
+
}
|
|
20321
|
+
}
|
|
20322
|
+
// eslint-disable-next-line max-params
|
|
20323
|
+
function bitmapRoundRect(ctx, box, radius, hpr, vpr, side = 'all') {
|
|
20324
|
+
const left = Math.round(box._internal_left * hpr);
|
|
20325
|
+
const top = Math.round(box._internal_top * vpr);
|
|
20326
|
+
const width = Math.round(box._internal_width * hpr);
|
|
20327
|
+
const height = Math.round(box._internal_height * vpr);
|
|
20328
|
+
const r = Math.min(radius * hpr, width / 2, height / 2);
|
|
20329
|
+
const radii = side === 'left'
|
|
20330
|
+
? [r, 0, 0, r]
|
|
20331
|
+
: side === 'right'
|
|
20332
|
+
? [0, r, r, 0]
|
|
20333
|
+
: [r, r, r, r];
|
|
20334
|
+
drawRoundRect(ctx, left, top, width, height, radii);
|
|
20335
|
+
}
|
|
20336
|
+
|
|
20337
|
+
class PositionLinePaneView {
|
|
20338
|
+
constructor() {
|
|
20339
|
+
this._private__renderer = new PositionLineRenderer();
|
|
20340
|
+
this._private__data = null;
|
|
20341
|
+
}
|
|
20342
|
+
_internal_update(data) {
|
|
20343
|
+
this._private__data = data;
|
|
20344
|
+
}
|
|
20345
|
+
renderer() {
|
|
20346
|
+
if (this._private__data === null) {
|
|
20347
|
+
return null;
|
|
20348
|
+
}
|
|
20349
|
+
this._private__renderer._internal_setData(this._private__data);
|
|
20350
|
+
return this._private__renderer;
|
|
20351
|
+
}
|
|
20352
|
+
zOrder() {
|
|
20353
|
+
return 'top';
|
|
20354
|
+
}
|
|
20355
|
+
_internal_hitTest(x, y) {
|
|
20356
|
+
return this._private__data === null ? null : this._private__renderer._internal_hitTest(x, y);
|
|
20357
|
+
}
|
|
20358
|
+
}
|
|
20359
|
+
|
|
20360
|
+
class PositionLineAxisView {
|
|
20361
|
+
constructor(source) {
|
|
20362
|
+
this._private__source = source;
|
|
20363
|
+
}
|
|
20364
|
+
coordinate() {
|
|
20365
|
+
// A hidden label is pushed far off the axis so it cannot reserve a gap
|
|
20366
|
+
// between the labels that are shown.
|
|
20367
|
+
return this._private__label()?._internal_coordinate ?? -1e3;
|
|
20368
|
+
}
|
|
20369
|
+
text() {
|
|
20370
|
+
return this._private__label()?._internal_text ?? '';
|
|
20371
|
+
}
|
|
20372
|
+
textColor() {
|
|
20373
|
+
return this._private__label()?._internal_textColor ?? '#FFFFFF';
|
|
20374
|
+
}
|
|
20375
|
+
backColor() {
|
|
20376
|
+
return this._private__label()?._internal_color ?? '#000000';
|
|
20377
|
+
}
|
|
20378
|
+
visible() {
|
|
20379
|
+
return this._private__label() !== null;
|
|
20380
|
+
}
|
|
20381
|
+
_private__label() {
|
|
20382
|
+
return this._private__source();
|
|
20383
|
+
}
|
|
20384
|
+
}
|
|
20385
|
+
|
|
20386
|
+
let nextPositionId = 0;
|
|
20387
|
+
const DEFAULT_RIGHT_OFFSET = 56;
|
|
20388
|
+
/**
|
|
20389
|
+
* A group of trading levels — entry, take profit and stop loss — drawn as one
|
|
20390
|
+
* unit: a line per level, a label carrying size and P&L, and a connector that
|
|
20391
|
+
* ties the three together.
|
|
20392
|
+
*
|
|
20393
|
+
* Input is not handled here. Every trading line on a chart shares a single
|
|
20394
|
+
* TradingLineController, which is what guarantees that one gesture moves one
|
|
20395
|
+
* level, and that a position line and a plain draggable price line on the same
|
|
20396
|
+
* chart do not fight over the pointer.
|
|
20397
|
+
*/
|
|
20398
|
+
class PositionLine {
|
|
20399
|
+
constructor(chart, series, options) {
|
|
20400
|
+
this._private__paneView = new PositionLinePaneView();
|
|
20401
|
+
this._private__colorParser = new ColorParser([]);
|
|
20402
|
+
this._private__attached = false;
|
|
20403
|
+
this._private__hoveredLevel = null;
|
|
20404
|
+
this._private__hoveredPart = null;
|
|
20405
|
+
this._private__dragLevel = null;
|
|
20406
|
+
this._private__priceBeforeDrag = null;
|
|
20407
|
+
this._private__chart = chart;
|
|
20408
|
+
this._private__series = series;
|
|
20409
|
+
this._private__id = `pipsend-position-line-${nextPositionId++}`;
|
|
20410
|
+
this._private__options = options;
|
|
20411
|
+
this._private__theme = { ...positionLineThemeDefaults, ...options.theme };
|
|
20412
|
+
this._private__paneViews = [this._private__paneView];
|
|
20413
|
+
this._private__axisViews = positionLineLevels.map((level) => new PositionLineAxisView(() => this._private__axisLabel(level)));
|
|
20414
|
+
this._private__series.attachPrimitive(this);
|
|
20415
|
+
acquireController(this._private__chart)?._internal_register(this);
|
|
20416
|
+
}
|
|
20417
|
+
// ---- ISeriesPrimitive ----
|
|
20418
|
+
attached(param) {
|
|
20419
|
+
this._private__requestUpdate = param.requestUpdate;
|
|
20420
|
+
this._private__attached = true;
|
|
20421
|
+
this.requestUpdate();
|
|
20422
|
+
}
|
|
20423
|
+
detached() {
|
|
20424
|
+
this._private__attached = false;
|
|
20425
|
+
this._private__requestUpdate = undefined;
|
|
20426
|
+
}
|
|
20427
|
+
paneViews() {
|
|
20428
|
+
return this._private__paneViews;
|
|
20429
|
+
}
|
|
20430
|
+
priceAxisViews() {
|
|
20431
|
+
return this._private__axisViews;
|
|
20432
|
+
}
|
|
20433
|
+
updateAllViews() {
|
|
20434
|
+
this._private__paneView._internal_update(this._private__buildRendererData());
|
|
20435
|
+
}
|
|
20436
|
+
hitTest(x, y) {
|
|
20437
|
+
const hit = this._private__paneView._internal_hitTest(x, y);
|
|
20438
|
+
if (hit === null) {
|
|
20439
|
+
return null;
|
|
20440
|
+
}
|
|
20441
|
+
const level = this._private__levelOptions(hit._internal_level);
|
|
20442
|
+
if (level === null) {
|
|
20443
|
+
return null;
|
|
20444
|
+
}
|
|
20445
|
+
return {
|
|
20446
|
+
externalId: `${this._private__id}#${hit._internal_level}:${hit._internal_part}`,
|
|
20447
|
+
zOrder: 'top',
|
|
20448
|
+
cursorStyle: hit._internal_part === 'close'
|
|
20449
|
+
? 'pointer'
|
|
20450
|
+
: (level.draggable ?? true) ? 'ns-resize' : undefined,
|
|
20451
|
+
};
|
|
20452
|
+
}
|
|
20453
|
+
// ---- ControllableLine ----
|
|
20454
|
+
id() {
|
|
20455
|
+
return this._private__id;
|
|
20456
|
+
}
|
|
20457
|
+
paneIndex() {
|
|
20458
|
+
try {
|
|
20459
|
+
return this._private__series.getPane().paneIndex();
|
|
20460
|
+
}
|
|
20461
|
+
catch (error) {
|
|
20462
|
+
// The engine asserts the series belongs to a pane, and that lookup can
|
|
20463
|
+
// come back empty while panes are being rebuilt. The main pane is the
|
|
20464
|
+
// right guess and keeps the gesture alive instead of throwing mid-drag.
|
|
20465
|
+
return 0;
|
|
20466
|
+
}
|
|
20467
|
+
}
|
|
20468
|
+
setHoveredPart(part) {
|
|
20469
|
+
const [level, name] = part === null ? [null, null] : part.split(':');
|
|
20470
|
+
const nextLevel = level ?? null;
|
|
20471
|
+
const nextPart = name ?? null;
|
|
20472
|
+
if (this._private__hoveredLevel === nextLevel && this._private__hoveredPart === nextPart) {
|
|
20473
|
+
return;
|
|
20474
|
+
}
|
|
20475
|
+
this._private__hoveredLevel = nextLevel;
|
|
20476
|
+
this._private__hoveredPart = nextPart;
|
|
20477
|
+
this.requestUpdate();
|
|
20478
|
+
}
|
|
20479
|
+
setHovered(hovered) {
|
|
20480
|
+
if (!hovered) {
|
|
20481
|
+
this.setHoveredPart(null);
|
|
20482
|
+
}
|
|
20483
|
+
}
|
|
20484
|
+
isDraggable() {
|
|
20485
|
+
if (this._private__hoveredLevel === null || this._private__hoveredPart === 'close') {
|
|
20486
|
+
return false;
|
|
20487
|
+
}
|
|
20488
|
+
return this._private__levelOptions(this._private__hoveredLevel)?.draggable ?? true;
|
|
20489
|
+
}
|
|
20490
|
+
isClickable() {
|
|
20491
|
+
if (this._private__hoveredPart === 'close') {
|
|
20492
|
+
return true;
|
|
20493
|
+
}
|
|
20494
|
+
return this._private__options.onClick !== undefined;
|
|
20495
|
+
}
|
|
20496
|
+
priceAt(paneY) {
|
|
20497
|
+
const price = this._private__series.coordinateToPrice(paneY);
|
|
20498
|
+
return price === null ? null : price;
|
|
20499
|
+
}
|
|
20500
|
+
beginDrag() {
|
|
20501
|
+
this._private__dragLevel = this._private__hoveredLevel;
|
|
20502
|
+
if (this._private__dragLevel === null) {
|
|
20503
|
+
return;
|
|
20504
|
+
}
|
|
20505
|
+
const price = this._private__levelOptions(this._private__dragLevel)?.price ?? null;
|
|
20506
|
+
this._private__priceBeforeDrag = price;
|
|
20507
|
+
if (price !== null && this._private__options.onDragStart) {
|
|
20508
|
+
this._private__options.onDragStart(this._private__dragLevel, price);
|
|
20509
|
+
}
|
|
20510
|
+
}
|
|
20511
|
+
dragTo(price) {
|
|
20512
|
+
if (this._private__dragLevel === null) {
|
|
20513
|
+
return;
|
|
20514
|
+
}
|
|
20515
|
+
this._private__applyPrice(this._private__dragLevel, price);
|
|
20516
|
+
if (this._private__options.onDragMove) {
|
|
20517
|
+
this._private__options.onDragMove(this._private__dragLevel, price);
|
|
20518
|
+
}
|
|
20519
|
+
}
|
|
20520
|
+
commitDrag() {
|
|
20521
|
+
const level = this._private__dragLevel;
|
|
20522
|
+
this._private__dragLevel = null;
|
|
20523
|
+
this._private__priceBeforeDrag = null;
|
|
20524
|
+
if (level === null) {
|
|
20525
|
+
return;
|
|
20526
|
+
}
|
|
20527
|
+
const price = this._private__levelOptions(level)?.price;
|
|
20528
|
+
if (price !== undefined && this._private__options.onDragEnd) {
|
|
20529
|
+
this._private__options.onDragEnd(level, price);
|
|
20530
|
+
}
|
|
20531
|
+
}
|
|
20532
|
+
cancelDrag() {
|
|
20533
|
+
const level = this._private__dragLevel;
|
|
20534
|
+
const original = this._private__priceBeforeDrag;
|
|
20535
|
+
this._private__dragLevel = null;
|
|
20536
|
+
this._private__priceBeforeDrag = null;
|
|
20537
|
+
if (level === null) {
|
|
20538
|
+
return;
|
|
20539
|
+
}
|
|
20540
|
+
// Reported even when the price did not move, so a caller that pauses work
|
|
20541
|
+
// on `onDragStart` always gets the matching end of the gesture.
|
|
20542
|
+
const restored = original !== null && original !== this._private__levelOptions(level)?.price;
|
|
20543
|
+
if (restored) {
|
|
20544
|
+
this._private__applyPrice(level, original);
|
|
20545
|
+
if (this._private__options.onDragMove) {
|
|
20546
|
+
this._private__options.onDragMove(level, original);
|
|
20547
|
+
}
|
|
20548
|
+
}
|
|
20549
|
+
if (this._private__options.onDragCancel) {
|
|
20550
|
+
this._private__options.onDragCancel(level, this._private__levelOptions(level)?.price ?? 0);
|
|
20551
|
+
}
|
|
20552
|
+
}
|
|
20553
|
+
click() {
|
|
20554
|
+
const level = this._private__hoveredLevel;
|
|
20555
|
+
if (level === null) {
|
|
20556
|
+
return;
|
|
20557
|
+
}
|
|
20558
|
+
if (this._private__hoveredPart === 'close') {
|
|
20559
|
+
if (this._private__options.onClose) {
|
|
20560
|
+
this._private__options.onClose(level);
|
|
20561
|
+
}
|
|
20562
|
+
return;
|
|
20563
|
+
}
|
|
20564
|
+
const price = this._private__levelOptions(level)?.price;
|
|
20565
|
+
if (price !== undefined && this._private__options.onClick) {
|
|
20566
|
+
this._private__options.onClick(level, price);
|
|
20567
|
+
}
|
|
20568
|
+
}
|
|
20569
|
+
// ---- public API ----
|
|
20570
|
+
/**
|
|
20571
|
+
* Updates the group. Any subset of options can be passed; the levels given
|
|
20572
|
+
* are merged into the current ones, and a level set to `undefined` is
|
|
20573
|
+
* removed.
|
|
20574
|
+
*/
|
|
20575
|
+
applyOptions(options) {
|
|
20576
|
+
// A level under an active gesture owns its price. Callers feeding the
|
|
20577
|
+
// group from a socket re-send the old price several times a second, and
|
|
20578
|
+
// letting that through would pull the level out from under the cursor.
|
|
20579
|
+
const dragged = this._private__dragLevel;
|
|
20580
|
+
const draggedPrice = dragged === null ? undefined : this._private__levelOptions(dragged)?.price;
|
|
20581
|
+
const merged = { ...this._private__options, ...options };
|
|
20582
|
+
merged.entry = { ...this._private__options.entry, ...options.entry };
|
|
20583
|
+
merged.takeProfit = mergeLevel(this._private__options.takeProfit, options, 'takeProfit');
|
|
20584
|
+
merged.stopLoss = mergeLevel(this._private__options.stopLoss, options, 'stopLoss');
|
|
20585
|
+
this._private__options = merged;
|
|
20586
|
+
if (dragged !== null && draggedPrice !== undefined) {
|
|
20587
|
+
const level = this._private__levelOptions(dragged);
|
|
20588
|
+
if (level !== null) {
|
|
20589
|
+
level.price = draggedPrice;
|
|
20590
|
+
}
|
|
20591
|
+
}
|
|
20592
|
+
if (options.theme) {
|
|
20593
|
+
// Merged onto what is already applied, so two partial theme updates do
|
|
20594
|
+
// not undo each other.
|
|
20595
|
+
this._private__theme = { ...this._private__theme, ...options.theme };
|
|
20596
|
+
}
|
|
20597
|
+
this.requestUpdate();
|
|
20598
|
+
}
|
|
20599
|
+
/**
|
|
20600
|
+
* Moves one level to a new price, recomputing its text when the level has a
|
|
20601
|
+
* `formatText`.
|
|
20602
|
+
*
|
|
20603
|
+
* Ignored for a level the user is currently dragging — the gesture owns the
|
|
20604
|
+
* price until it reports back through
|
|
20605
|
+
* {@link PositionLineOptions.onDragEnd} or
|
|
20606
|
+
* {@link PositionLineOptions.onDragCancel}.
|
|
20607
|
+
*/
|
|
20608
|
+
setPrice(level, price) {
|
|
20609
|
+
if (this._private__dragLevel === level) {
|
|
20610
|
+
return;
|
|
20611
|
+
}
|
|
20612
|
+
this._private__applyPrice(level, price);
|
|
20613
|
+
}
|
|
20614
|
+
/**
|
|
20615
|
+
* Replaces one level's label text. Use this for text that changes on its own
|
|
20616
|
+
* — a ticking P&L on a fixed entry — rather than with the price.
|
|
20617
|
+
*/
|
|
20618
|
+
setText(level, text) {
|
|
20619
|
+
const options = this._private__levelOptions(level);
|
|
20620
|
+
if (options === null) {
|
|
20621
|
+
return;
|
|
20622
|
+
}
|
|
20623
|
+
options.text = text;
|
|
20624
|
+
this.requestUpdate();
|
|
20625
|
+
}
|
|
20626
|
+
/**
|
|
20627
|
+
* The current price of each level that is present.
|
|
20628
|
+
*/
|
|
20629
|
+
prices() {
|
|
20630
|
+
const result = {};
|
|
20631
|
+
for (const level of positionLineLevels) {
|
|
20632
|
+
const options = this._private__levelOptions(level);
|
|
20633
|
+
if (options !== null) {
|
|
20634
|
+
result[level] = options.price;
|
|
20635
|
+
}
|
|
20636
|
+
}
|
|
20637
|
+
return result;
|
|
20638
|
+
}
|
|
20639
|
+
options() {
|
|
20640
|
+
return this._private__options;
|
|
20641
|
+
}
|
|
20642
|
+
/**
|
|
20643
|
+
* Removes the group from the chart and releases its share of the input
|
|
20644
|
+
* controller.
|
|
20645
|
+
*/
|
|
20646
|
+
remove() {
|
|
20647
|
+
releaseController(this._private__chart, this);
|
|
20648
|
+
this._private__series.detachPrimitive(this);
|
|
20649
|
+
}
|
|
20650
|
+
requestUpdate() {
|
|
20651
|
+
if (this._private__requestUpdate) {
|
|
20652
|
+
this._private__requestUpdate();
|
|
20653
|
+
}
|
|
20654
|
+
}
|
|
20655
|
+
/** Reads back the label the price axis should draw for a level. */
|
|
20656
|
+
_private__axisLabel(level) {
|
|
20657
|
+
const options = this._private__levelOptions(level);
|
|
20658
|
+
if (options === null || !isFinite(options.price) ||
|
|
20659
|
+
this._private__options.visible === false || options.axisLabelVisible === false) {
|
|
20660
|
+
return null;
|
|
20661
|
+
}
|
|
20662
|
+
const y = this._private__series.priceToCoordinate(options.price);
|
|
20663
|
+
if (y === null) {
|
|
20664
|
+
return null;
|
|
20665
|
+
}
|
|
20666
|
+
const color = options.color ?? positionLineDefaultColors[level];
|
|
20667
|
+
return {
|
|
20668
|
+
_internal_coordinate: y,
|
|
20669
|
+
_internal_text: this._private__series.priceFormatter().format(options.price),
|
|
20670
|
+
_internal_color: color,
|
|
20671
|
+
_internal_textColor: this._private__colorParser._internal_generateContrastColors(color)._internal_foreground,
|
|
20672
|
+
};
|
|
20673
|
+
}
|
|
20674
|
+
_private__levelOptions(level) {
|
|
20675
|
+
const options = level === 'entry' ? this._private__options.entry : this._private__options[level];
|
|
20676
|
+
return options ?? null;
|
|
20677
|
+
}
|
|
20678
|
+
_private__applyPrice(level, price) {
|
|
20679
|
+
const options = this._private__levelOptions(level);
|
|
20680
|
+
if (options === null) {
|
|
20681
|
+
return;
|
|
20682
|
+
}
|
|
20683
|
+
options.price = price;
|
|
20684
|
+
if (options.formatText) {
|
|
20685
|
+
options.text = options.formatText(price);
|
|
20686
|
+
}
|
|
20687
|
+
this.requestUpdate();
|
|
20688
|
+
}
|
|
20689
|
+
_private__buildRendererData() {
|
|
20690
|
+
if (!this._private__attached || this._private__options.visible === false) {
|
|
20691
|
+
return null;
|
|
20692
|
+
}
|
|
20693
|
+
const levels = [];
|
|
20694
|
+
for (const level of positionLineLevels) {
|
|
20695
|
+
const options = this._private__levelOptions(level);
|
|
20696
|
+
// A level added through a patch can be missing its price until the
|
|
20697
|
+
// caller supplies one; drawing it would put it at NaN.
|
|
20698
|
+
if (options === null || !isFinite(options.price)) {
|
|
20699
|
+
continue;
|
|
20700
|
+
}
|
|
20701
|
+
const y = this._private__series.priceToCoordinate(options.price);
|
|
20702
|
+
if (y === null) {
|
|
20703
|
+
continue;
|
|
20704
|
+
}
|
|
20705
|
+
const isEntry = level === 'entry';
|
|
20706
|
+
levels.push({
|
|
20707
|
+
_internal_level: level,
|
|
20708
|
+
_internal_y: y,
|
|
20709
|
+
_internal_color: options.color ?? positionLineDefaultColors[level],
|
|
20710
|
+
_internal_text: options.formatText ? options.formatText(options.price) : options.text ?? '',
|
|
20711
|
+
_internal_badge: options.badge ?? '',
|
|
20712
|
+
_internal_badgeFilled: options.badgeFilled ?? isEntry,
|
|
20713
|
+
_internal_lineWidth: options.lineWidth ?? 1,
|
|
20714
|
+
_internal_lineStyle: options.lineStyle ?? (isEntry ? 0 /* LineStyle.Solid */ : 2 /* LineStyle.Dashed */),
|
|
20715
|
+
_internal_closable: options.closable ?? true,
|
|
20716
|
+
_internal_draggable: options.draggable ?? true,
|
|
20717
|
+
});
|
|
20718
|
+
}
|
|
20719
|
+
if (levels.length === 0) {
|
|
20720
|
+
return null;
|
|
20721
|
+
}
|
|
20722
|
+
const layout = this._private__chart.options()['layout'];
|
|
20723
|
+
const fontSize = this._private__theme.fontSize ?? layout.fontSize;
|
|
20724
|
+
return {
|
|
20725
|
+
_internal_levels: levels,
|
|
20726
|
+
_internal_background: this._private__theme.background ?? resolveBackgroundColor(layout.background),
|
|
20727
|
+
_internal_textColor: this._private__theme.textColor,
|
|
20728
|
+
_internal_badgeTextColor: this._private__theme.badgeTextColor,
|
|
20729
|
+
_internal_borderRadius: this._private__theme.borderRadius,
|
|
20730
|
+
_internal_borderWidth: this._private__theme.borderWidth,
|
|
20731
|
+
_internal_paddingX: this._private__theme.paddingX,
|
|
20732
|
+
_internal_paddingY: this._private__theme.paddingY,
|
|
20733
|
+
_internal_backgroundOpacity: this._private__theme.backgroundOpacity,
|
|
20734
|
+
_internal_font: makeFont(fontSize, this._private__theme.fontFamily ?? layout.fontFamily),
|
|
20735
|
+
_internal_fontSize: fontSize,
|
|
20736
|
+
_internal_connectorVisible: this._private__options.connectorVisible !== false && levels.length > 1,
|
|
20737
|
+
_internal_rightOffset: this._private__options.rightOffset ?? DEFAULT_RIGHT_OFFSET,
|
|
20738
|
+
_internal_hoveredLevel: this._private__hoveredLevel,
|
|
20739
|
+
_internal_hoveredPart: this._private__hoveredPart,
|
|
20740
|
+
};
|
|
20741
|
+
}
|
|
20742
|
+
}
|
|
20743
|
+
function mergeLevel(current, options, key) {
|
|
20744
|
+
if (!(key in options)) {
|
|
20745
|
+
return current;
|
|
20746
|
+
}
|
|
20747
|
+
const next = options[key];
|
|
20748
|
+
return next === undefined ? undefined : { ...current, ...next };
|
|
20749
|
+
}
|
|
20750
|
+
/**
|
|
20751
|
+
* Reads the chart's own background so the label sits on a solid surface. The
|
|
20752
|
+
* parameter is typed as the public {@link Background}: the production build
|
|
20753
|
+
* renames the members of structural types it does not see in the public API,
|
|
20754
|
+
* and an anonymous `{ color: string }` here resolved to nothing at runtime.
|
|
20755
|
+
*/
|
|
20756
|
+
function resolveBackgroundColor(background) {
|
|
20757
|
+
return background.type === "solid" /* ColorType.Solid */ ? background.color : background.topColor;
|
|
20758
|
+
}
|
|
20759
|
+
/**
|
|
20760
|
+
* Creates a position line group: entry, take profit and stop loss drawn as one
|
|
20761
|
+
* interactive unit.
|
|
20762
|
+
*
|
|
20763
|
+
* @param series - The series the levels are priced against.
|
|
20764
|
+
* @param chart - The chart the series belongs to.
|
|
20765
|
+
* @param options - The levels and their style.
|
|
20766
|
+
*
|
|
20767
|
+
* @example
|
|
20768
|
+
* ```js
|
|
20769
|
+
* const position = createPositionLine(candleSeries, chart, {
|
|
20770
|
+
* entry: { price: 4306.79, badge: '1', formatText: () => '+ 2.32 USD' },
|
|
20771
|
+
* takeProfit: { price: 4349.58, badge: '1', text: '+ 42.79 USD' },
|
|
20772
|
+
* stopLoss: { price: 4266.06, badge: '1', text: '- 40.73 USD' },
|
|
20773
|
+
* onClose: (level) => console.log('close', level),
|
|
20774
|
+
* onDragEnd: (level, price) => console.log(level, 'moved to', price),
|
|
20775
|
+
* });
|
|
20776
|
+
* ```
|
|
20777
|
+
*/
|
|
20778
|
+
function createPositionLine(series, chart, options) {
|
|
20779
|
+
return new PositionLine(chart, series, options);
|
|
20780
|
+
}
|
|
20781
|
+
|
|
19858
20782
|
/** Hit parts that drag the whole object rather than a single handle. */
|
|
19859
20783
|
const WHOLE_OBJECT_PARTS = new Set([
|
|
19860
20784
|
'line',
|
|
@@ -20012,6 +20936,9 @@ class DrawingToolBase {
|
|
|
20012
20936
|
get type() {
|
|
20013
20937
|
return this._type;
|
|
20014
20938
|
}
|
|
20939
|
+
isComplete() {
|
|
20940
|
+
return true;
|
|
20941
|
+
}
|
|
20015
20942
|
/**
|
|
20016
20943
|
* Lets a tool hand the base its own options object, once, in the constructor.
|
|
20017
20944
|
*
|
|
@@ -20339,6 +21266,7 @@ class TrendLineTool extends DrawingToolBase {
|
|
|
20339
21266
|
this._private__dragStartPoint2 = null;
|
|
20340
21267
|
// Interactive mode
|
|
20341
21268
|
this._private__isComplete = false;
|
|
21269
|
+
this._private__interactive = null;
|
|
20342
21270
|
this._private__previewPoint = null;
|
|
20343
21271
|
this._private__trendOptions = {
|
|
20344
21272
|
lineColor: '#2962ff',
|
|
@@ -20386,7 +21314,7 @@ class TrendLineTool extends DrawingToolBase {
|
|
|
20386
21314
|
this._private__isComplete = true;
|
|
20387
21315
|
}
|
|
20388
21316
|
});
|
|
20389
|
-
|
|
21317
|
+
this._private__interactive = manager;
|
|
20390
21318
|
manager._internal_startDrawing();
|
|
20391
21319
|
}
|
|
20392
21320
|
_private__createPrimitive() {
|
|
@@ -20716,20 +21644,20 @@ class TrendLineTool extends DrawingToolBase {
|
|
|
20716
21644
|
_update() {
|
|
20717
21645
|
this._private__requestUpdate();
|
|
20718
21646
|
}
|
|
21647
|
+
isComplete() {
|
|
21648
|
+
return this._private__isComplete;
|
|
21649
|
+
}
|
|
20719
21650
|
remove() {
|
|
21651
|
+
if (this._private__interactive) {
|
|
21652
|
+
this._private__interactive._internal_destroy();
|
|
21653
|
+
this._private__interactive = null;
|
|
21654
|
+
}
|
|
20720
21655
|
if (this._private__primitive) {
|
|
20721
21656
|
// Detach from series
|
|
20722
21657
|
this._series.detachPrimitive(this._private__primitive);
|
|
20723
21658
|
// Clean up references
|
|
20724
21659
|
this._private__primitive = null;
|
|
20725
21660
|
this._private__paneView = null;
|
|
20726
|
-
// Force chart update by triggering a resize
|
|
20727
|
-
const chartElement = this._chart.chartElement?.();
|
|
20728
|
-
if (chartElement) {
|
|
20729
|
-
const currentWidth = chartElement.clientWidth;
|
|
20730
|
-
const currentHeight = chartElement.clientHeight;
|
|
20731
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
20732
|
-
}
|
|
20733
21661
|
}
|
|
20734
21662
|
if (this._private__trendOptions.onRemove) {
|
|
20735
21663
|
this._private__trendOptions.onRemove();
|
|
@@ -21002,12 +21930,6 @@ class HorizontalLineTool extends DrawingToolBase {
|
|
|
21002
21930
|
this._series.detachPrimitive(this._private__primitive);
|
|
21003
21931
|
this._private__primitive = null;
|
|
21004
21932
|
this._private__paneView = null;
|
|
21005
|
-
const chartElement = this._chart.chartElement?.();
|
|
21006
|
-
if (chartElement) {
|
|
21007
|
-
const currentWidth = chartElement.clientWidth;
|
|
21008
|
-
const currentHeight = chartElement.clientHeight;
|
|
21009
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
21010
|
-
}
|
|
21011
21933
|
}
|
|
21012
21934
|
if (this._private__hLineOptions.onRemove) {
|
|
21013
21935
|
this._private__hLineOptions.onRemove();
|
|
@@ -21623,6 +22545,9 @@ class PolygonTool extends DrawingToolBase {
|
|
|
21623
22545
|
points() {
|
|
21624
22546
|
return this._private__points;
|
|
21625
22547
|
}
|
|
22548
|
+
isComplete() {
|
|
22549
|
+
return this._private__isComplete;
|
|
22550
|
+
}
|
|
21626
22551
|
remove() {
|
|
21627
22552
|
this._private__stopDrawing();
|
|
21628
22553
|
if (this._private__primitive) {
|
|
@@ -22093,6 +23018,9 @@ class ParallelChannelTool extends DrawingToolBase {
|
|
|
22093
23018
|
isHovered() {
|
|
22094
23019
|
return this._private__isHovered;
|
|
22095
23020
|
}
|
|
23021
|
+
isComplete() {
|
|
23022
|
+
return this._private__isComplete;
|
|
23023
|
+
}
|
|
22096
23024
|
remove() {
|
|
22097
23025
|
this._private__stopDrawing();
|
|
22098
23026
|
if (this._private__primitive) {
|
|
@@ -22557,6 +23485,9 @@ class PitchforkTool extends DrawingToolBase {
|
|
|
22557
23485
|
isHovered() {
|
|
22558
23486
|
return this._private__isHovered;
|
|
22559
23487
|
}
|
|
23488
|
+
isComplete() {
|
|
23489
|
+
return this._private__isComplete;
|
|
23490
|
+
}
|
|
22560
23491
|
remove() {
|
|
22561
23492
|
this._private__stopDrawing();
|
|
22562
23493
|
if (this._private__primitive) {
|
|
@@ -22837,12 +23768,6 @@ class VerticalLineTool extends DrawingToolBase {
|
|
|
22837
23768
|
this._series.detachPrimitive(this._private__primitive);
|
|
22838
23769
|
this._private__primitive = null;
|
|
22839
23770
|
this._private__paneView = null;
|
|
22840
|
-
const chartElement = this._chart.chartElement?.();
|
|
22841
|
-
if (chartElement) {
|
|
22842
|
-
const currentWidth = chartElement.clientWidth;
|
|
22843
|
-
const currentHeight = chartElement.clientHeight;
|
|
22844
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
22845
|
-
}
|
|
22846
23771
|
}
|
|
22847
23772
|
if (this._private__vLineOptions.onRemove) {
|
|
22848
23773
|
this._private__vLineOptions.onRemove();
|
|
@@ -23487,17 +24412,14 @@ class FibonacciTool extends DrawingToolBase {
|
|
|
23487
24412
|
}
|
|
23488
24413
|
return false;
|
|
23489
24414
|
}
|
|
24415
|
+
isComplete() {
|
|
24416
|
+
return this._private__isComplete;
|
|
24417
|
+
}
|
|
23490
24418
|
remove() {
|
|
23491
24419
|
if (this._private__primitive) {
|
|
23492
24420
|
this._series.detachPrimitive(this._private__primitive);
|
|
23493
24421
|
this._private__primitive = null;
|
|
23494
24422
|
this._private__paneView = null;
|
|
23495
|
-
const chartElement = this._chart.chartElement?.();
|
|
23496
|
-
if (chartElement) {
|
|
23497
|
-
const currentWidth = chartElement.clientWidth;
|
|
23498
|
-
const currentHeight = chartElement.clientHeight;
|
|
23499
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
23500
|
-
}
|
|
23501
24423
|
}
|
|
23502
24424
|
if (this._private__drawingManager) {
|
|
23503
24425
|
this._private__drawingManager._internal_destroy();
|
|
@@ -23513,12 +24435,6 @@ class FibonacciTool extends DrawingToolBase {
|
|
|
23513
24435
|
this._series.detachPrimitive(this._private__primitive);
|
|
23514
24436
|
this._private__primitive = null;
|
|
23515
24437
|
this._private__paneView = null;
|
|
23516
|
-
const chartElement = this._chart.chartElement?.();
|
|
23517
|
-
if (chartElement) {
|
|
23518
|
-
const currentWidth = chartElement.clientWidth;
|
|
23519
|
-
const currentHeight = chartElement.clientHeight;
|
|
23520
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
23521
|
-
}
|
|
23522
24438
|
}
|
|
23523
24439
|
if (this._private__fibOptions.onRemove) {
|
|
23524
24440
|
this._private__fibOptions.onRemove();
|
|
@@ -23946,12 +24862,6 @@ class FibonacciExtensionTool extends DrawingToolBase {
|
|
|
23946
24862
|
this._series.detachPrimitive(this._private__primitive);
|
|
23947
24863
|
this._private__primitive = null;
|
|
23948
24864
|
this._private__paneView = null;
|
|
23949
|
-
const chartElement = this._chart.chartElement?.();
|
|
23950
|
-
if (chartElement) {
|
|
23951
|
-
const currentWidth = chartElement.clientWidth;
|
|
23952
|
-
const currentHeight = chartElement.clientHeight;
|
|
23953
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
23954
|
-
}
|
|
23955
24865
|
}
|
|
23956
24866
|
}
|
|
23957
24867
|
}
|
|
@@ -24377,6 +25287,9 @@ class FibonacciFanTool extends DrawingToolBase {
|
|
|
24377
25287
|
_update() {
|
|
24378
25288
|
this._private__requestUpdate();
|
|
24379
25289
|
}
|
|
25290
|
+
isComplete() {
|
|
25291
|
+
return !this._private__isDrawing;
|
|
25292
|
+
}
|
|
24380
25293
|
remove() {
|
|
24381
25294
|
if (this._private__clickHandler) {
|
|
24382
25295
|
this._chart.unsubscribeClick(this._private__clickHandler);
|
|
@@ -24390,12 +25303,6 @@ class FibonacciFanTool extends DrawingToolBase {
|
|
|
24390
25303
|
this._series.detachPrimitive(this._private__primitive);
|
|
24391
25304
|
this._private__primitive = null;
|
|
24392
25305
|
this._private__paneView = null;
|
|
24393
|
-
const chartElement = this._chart.chartElement?.();
|
|
24394
|
-
if (chartElement) {
|
|
24395
|
-
const currentWidth = chartElement.clientWidth;
|
|
24396
|
-
const currentHeight = chartElement.clientHeight;
|
|
24397
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
24398
|
-
}
|
|
24399
25306
|
}
|
|
24400
25307
|
if (this._private__fanOptions.onRemove) {
|
|
24401
25308
|
this._private__fanOptions.onRemove();
|
|
@@ -24920,6 +25827,9 @@ class RectangleTool extends DrawingToolBase {
|
|
|
24920
25827
|
_update() {
|
|
24921
25828
|
this._private__requestUpdate();
|
|
24922
25829
|
}
|
|
25830
|
+
isComplete() {
|
|
25831
|
+
return !this._private__isDrawing;
|
|
25832
|
+
}
|
|
24923
25833
|
remove() {
|
|
24924
25834
|
if (this._private__clickHandler) {
|
|
24925
25835
|
this._chart.unsubscribeClick(this._private__clickHandler);
|
|
@@ -24933,12 +25843,6 @@ class RectangleTool extends DrawingToolBase {
|
|
|
24933
25843
|
this._series.detachPrimitive(this._private__primitive);
|
|
24934
25844
|
this._private__primitive = null;
|
|
24935
25845
|
this._private__paneView = null;
|
|
24936
|
-
const chartElement = this._chart.chartElement?.();
|
|
24937
|
-
if (chartElement) {
|
|
24938
|
-
const currentWidth = chartElement.clientWidth;
|
|
24939
|
-
const currentHeight = chartElement.clientHeight;
|
|
24940
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
24941
|
-
}
|
|
24942
25846
|
}
|
|
24943
25847
|
if (this._private__rectOptions.onRemove) {
|
|
24944
25848
|
this._private__rectOptions.onRemove();
|
|
@@ -25383,6 +26287,9 @@ class CircleTool extends DrawingToolBase {
|
|
|
25383
26287
|
_update() {
|
|
25384
26288
|
this._private__requestUpdate();
|
|
25385
26289
|
}
|
|
26290
|
+
isComplete() {
|
|
26291
|
+
return !this._private__isDrawing;
|
|
26292
|
+
}
|
|
25386
26293
|
remove() {
|
|
25387
26294
|
if (this._private__clickHandler) {
|
|
25388
26295
|
this._chart.unsubscribeClick(this._private__clickHandler);
|
|
@@ -25396,12 +26303,6 @@ class CircleTool extends DrawingToolBase {
|
|
|
25396
26303
|
this._series.detachPrimitive(this._private__primitive);
|
|
25397
26304
|
this._private__primitive = null;
|
|
25398
26305
|
this._private__paneView = null;
|
|
25399
|
-
const chartElement = this._chart.chartElement?.();
|
|
25400
|
-
if (chartElement) {
|
|
25401
|
-
const currentWidth = chartElement.clientWidth;
|
|
25402
|
-
const currentHeight = chartElement.clientHeight;
|
|
25403
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
25404
|
-
}
|
|
25405
26306
|
}
|
|
25406
26307
|
if (this._private__circleOptions.onRemove) {
|
|
25407
26308
|
this._private__circleOptions.onRemove();
|
|
@@ -25925,6 +26826,9 @@ class ArrowTool extends DrawingToolBase {
|
|
|
25925
26826
|
_update() {
|
|
25926
26827
|
this._private__requestUpdate();
|
|
25927
26828
|
}
|
|
26829
|
+
isComplete() {
|
|
26830
|
+
return !this._private__isDrawing;
|
|
26831
|
+
}
|
|
25928
26832
|
remove() {
|
|
25929
26833
|
// Clean up click handler
|
|
25930
26834
|
if (this._private__clickHandler) {
|
|
@@ -25948,12 +26852,6 @@ class ArrowTool extends DrawingToolBase {
|
|
|
25948
26852
|
this._series.detachPrimitive(this._private__primitive);
|
|
25949
26853
|
this._private__primitive = null;
|
|
25950
26854
|
this._private__paneView = null;
|
|
25951
|
-
const chartElement = this._chart.chartElement?.();
|
|
25952
|
-
if (chartElement) {
|
|
25953
|
-
const currentWidth = chartElement.clientWidth;
|
|
25954
|
-
const currentHeight = chartElement.clientHeight;
|
|
25955
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
25956
|
-
}
|
|
25957
26855
|
}
|
|
25958
26856
|
if (this._private__arrowOptions.onRemove) {
|
|
25959
26857
|
this._private__arrowOptions.onRemove();
|
|
@@ -26491,7 +27389,7 @@ class TextTool extends DrawingToolBase {
|
|
|
26491
27389
|
}
|
|
26492
27390
|
_private__setupClickOutsideHandler() {
|
|
26493
27391
|
// Set up click handler to finish editing when clicking outside
|
|
26494
|
-
|
|
27392
|
+
const handler = (param) => {
|
|
26495
27393
|
if (!this._private__isEditing)
|
|
26496
27394
|
return;
|
|
26497
27395
|
// Get click coordinates
|
|
@@ -26507,7 +27405,9 @@ class TextTool extends DrawingToolBase {
|
|
|
26507
27405
|
this.remove();
|
|
26508
27406
|
}
|
|
26509
27407
|
}
|
|
26510
|
-
}
|
|
27408
|
+
};
|
|
27409
|
+
this._private__clickOutsideHandler = handler;
|
|
27410
|
+
this._chart.subscribeClick(handler);
|
|
26511
27411
|
}
|
|
26512
27412
|
_private__createPrimitive() {
|
|
26513
27413
|
this._private__paneView = this._private__createPaneView();
|
|
@@ -26812,12 +27712,6 @@ class TextTool extends DrawingToolBase {
|
|
|
26812
27712
|
this._series.detachPrimitive(this._private__primitive);
|
|
26813
27713
|
this._private__primitive = null;
|
|
26814
27714
|
this._private__paneView = null;
|
|
26815
|
-
const chartElement = this._chart.chartElement?.();
|
|
26816
|
-
if (chartElement) {
|
|
26817
|
-
const currentWidth = chartElement.clientWidth;
|
|
26818
|
-
const currentHeight = chartElement.clientHeight;
|
|
26819
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
26820
|
-
}
|
|
26821
27715
|
}
|
|
26822
27716
|
if (this._private__textOptions.onRemove) {
|
|
26823
27717
|
this._private__textOptions.onRemove();
|
|
@@ -27400,12 +28294,6 @@ class RulerTool extends DrawingToolBase {
|
|
|
27400
28294
|
this._series.detachPrimitive(this._private__primitive);
|
|
27401
28295
|
this._private__primitive = null;
|
|
27402
28296
|
this._private__paneView = null;
|
|
27403
|
-
const chartElement = this._chart.chartElement?.();
|
|
27404
|
-
if (chartElement) {
|
|
27405
|
-
const currentWidth = chartElement.clientWidth;
|
|
27406
|
-
const currentHeight = chartElement.clientHeight;
|
|
27407
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
27408
|
-
}
|
|
27409
28297
|
}
|
|
27410
28298
|
if (this._private__drawingManager) {
|
|
27411
28299
|
this._private__drawingManager._internal_destroy();
|
|
@@ -28261,12 +29149,6 @@ class PositionTool extends DrawingToolBase {
|
|
|
28261
29149
|
this._series.detachPrimitive(this._private__primitive);
|
|
28262
29150
|
this._private__primitive = null;
|
|
28263
29151
|
this._private__paneView = null;
|
|
28264
|
-
const chartElement = this._chart.chartElement?.();
|
|
28265
|
-
if (chartElement) {
|
|
28266
|
-
const currentWidth = chartElement.clientWidth;
|
|
28267
|
-
const currentHeight = chartElement.clientHeight;
|
|
28268
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
28269
|
-
}
|
|
28270
29152
|
}
|
|
28271
29153
|
if (this._private__positionOptions.onRemove) {
|
|
28272
29154
|
this._private__positionOptions.onRemove();
|
|
@@ -28609,12 +29491,6 @@ class GanttTool extends DrawingToolBase {
|
|
|
28609
29491
|
this._series.detachPrimitive(this._private__primitive);
|
|
28610
29492
|
this._private__primitive = null;
|
|
28611
29493
|
this._private__paneView = null;
|
|
28612
|
-
const chartElement = this._chart.chartElement?.();
|
|
28613
|
-
if (chartElement) {
|
|
28614
|
-
const currentWidth = chartElement.clientWidth;
|
|
28615
|
-
const currentHeight = chartElement.clientHeight;
|
|
28616
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
28617
|
-
}
|
|
28618
29494
|
}
|
|
28619
29495
|
if (this._private__ganttOptions.onRemove) {
|
|
28620
29496
|
this._private__ganttOptions.onRemove();
|
|
@@ -28625,6 +29501,8 @@ function createGanttTool(context, options) {
|
|
|
28625
29501
|
return new GanttTool(context, options);
|
|
28626
29502
|
}
|
|
28627
29503
|
|
|
29504
|
+
/** Undo steps kept, each a full snapshot of every drawing. */
|
|
29505
|
+
const UNDO_HISTORY_LIMIT = 100;
|
|
28628
29506
|
/**
|
|
28629
29507
|
* Central manager for all drawing tools - TradingView style
|
|
28630
29508
|
* Handles multiple tools, selection, deletion, and keyboard shortcuts
|
|
@@ -28647,6 +29525,8 @@ class DrawingToolsManager {
|
|
|
28647
29525
|
this._private__pointerDownHandler = null;
|
|
28648
29526
|
this._private__pointerUpHandler = null;
|
|
28649
29527
|
this._private__pointerCancelHandler = null;
|
|
29528
|
+
this._private__lostCaptureHandler = null;
|
|
29529
|
+
this._private__windowPointerUpHandler = null;
|
|
28650
29530
|
this._private__pointerId = null;
|
|
28651
29531
|
/** A press that grabbed a tool already selected it; its click must not undo that. */
|
|
28652
29532
|
this._private__gestureHandled = false;
|
|
@@ -28656,6 +29536,9 @@ class DrawingToolsManager {
|
|
|
28656
29536
|
this._private__selectionChanged = new Delegate();
|
|
28657
29537
|
this._private__isDragging = false;
|
|
28658
29538
|
this._private__draggedToolId = null;
|
|
29539
|
+
this._private__pendingToolIds = new Set();
|
|
29540
|
+
this._private__completionCheck = null;
|
|
29541
|
+
this._private__drawingCompleted = new Delegate();
|
|
28659
29542
|
this._private__chart = chart;
|
|
28660
29543
|
this._private__series = series;
|
|
28661
29544
|
this._private__options = {
|
|
@@ -28694,9 +29577,63 @@ class DrawingToolsManager {
|
|
|
28694
29577
|
addTool(tool, id) {
|
|
28695
29578
|
const toolId = id ?? tool.id;
|
|
28696
29579
|
this._private__tools.set(toolId, tool);
|
|
29580
|
+
if (!tool.isComplete()) {
|
|
29581
|
+
this._private__pendingToolIds.add(toolId);
|
|
29582
|
+
}
|
|
28697
29583
|
this.recordChange();
|
|
28698
29584
|
return toolId;
|
|
28699
29585
|
}
|
|
29586
|
+
hasPendingDrawing() {
|
|
29587
|
+
return this._private__pendingToolIds.size > 0;
|
|
29588
|
+
}
|
|
29589
|
+
/** Removes every tool still waiting for points; returns how many were dropped. */
|
|
29590
|
+
cancelPendingDrawings() {
|
|
29591
|
+
if (this._private__pendingToolIds.size === 0) {
|
|
29592
|
+
return 0;
|
|
29593
|
+
}
|
|
29594
|
+
let cancelled = 0;
|
|
29595
|
+
for (const id of Array.from(this._private__pendingToolIds)) {
|
|
29596
|
+
const tool = this._private__tools.get(id);
|
|
29597
|
+
if (tool) {
|
|
29598
|
+
if (this._private__selectedToolId === id) {
|
|
29599
|
+
this._private__applySelection(null);
|
|
29600
|
+
}
|
|
29601
|
+
tool.remove();
|
|
29602
|
+
this._private__tools.delete(id);
|
|
29603
|
+
cancelled++;
|
|
29604
|
+
}
|
|
29605
|
+
}
|
|
29606
|
+
this._private__pendingToolIds.clear();
|
|
29607
|
+
if (cancelled > 0) {
|
|
29608
|
+
this.recordChange();
|
|
29609
|
+
}
|
|
29610
|
+
return cancelled;
|
|
29611
|
+
}
|
|
29612
|
+
subscribeDrawingCompleted(handler) {
|
|
29613
|
+
this._private__drawingCompleted._internal_subscribe(handler);
|
|
29614
|
+
}
|
|
29615
|
+
unsubscribeDrawingCompleted(handler) {
|
|
29616
|
+
this._private__drawingCompleted._internal_unsubscribe(handler);
|
|
29617
|
+
}
|
|
29618
|
+
_private__scheduleCompletionCheck() {
|
|
29619
|
+
if (this._private__pendingToolIds.size === 0 || this._private__completionCheck !== null) {
|
|
29620
|
+
return;
|
|
29621
|
+
}
|
|
29622
|
+
this._private__completionCheck = setTimeout(() => {
|
|
29623
|
+
this._private__completionCheck = null;
|
|
29624
|
+
for (const id of Array.from(this._private__pendingToolIds)) {
|
|
29625
|
+
const tool = this._private__tools.get(id);
|
|
29626
|
+
if (!tool) {
|
|
29627
|
+
this._private__pendingToolIds.delete(id);
|
|
29628
|
+
continue;
|
|
29629
|
+
}
|
|
29630
|
+
if (tool.isComplete()) {
|
|
29631
|
+
this._private__pendingToolIds.delete(id);
|
|
29632
|
+
this._private__drawingCompleted._internal_fire(id);
|
|
29633
|
+
}
|
|
29634
|
+
}
|
|
29635
|
+
}, 0);
|
|
29636
|
+
}
|
|
28700
29637
|
/**
|
|
28701
29638
|
* Remove a tool by ID
|
|
28702
29639
|
*/
|
|
@@ -28708,6 +29645,7 @@ class DrawingToolsManager {
|
|
|
28708
29645
|
}
|
|
28709
29646
|
tool.remove();
|
|
28710
29647
|
this._private__tools.delete(id);
|
|
29648
|
+
this._private__pendingToolIds.delete(id);
|
|
28711
29649
|
this.recordChange();
|
|
28712
29650
|
return true;
|
|
28713
29651
|
}
|
|
@@ -28754,6 +29692,9 @@ class DrawingToolsManager {
|
|
|
28754
29692
|
return;
|
|
28755
29693
|
}
|
|
28756
29694
|
this._private__past.push(this._private__committed);
|
|
29695
|
+
if (this._private__past.length > UNDO_HISTORY_LIMIT) {
|
|
29696
|
+
this._private__past.shift();
|
|
29697
|
+
}
|
|
28757
29698
|
this._private__future = [];
|
|
28758
29699
|
this._private__committed = this.serializeAll();
|
|
28759
29700
|
}
|
|
@@ -28907,6 +29848,7 @@ class DrawingToolsManager {
|
|
|
28907
29848
|
this._private__applySelection(null);
|
|
28908
29849
|
this._private__tools.forEach((tool) => tool.remove());
|
|
28909
29850
|
this._private__tools.clear();
|
|
29851
|
+
this._private__pendingToolIds.clear();
|
|
28910
29852
|
this.recordChange();
|
|
28911
29853
|
}
|
|
28912
29854
|
/**
|
|
@@ -29127,8 +30069,17 @@ class DrawingToolsManager {
|
|
|
29127
30069
|
* Destroy the manager and cleanup
|
|
29128
30070
|
*/
|
|
29129
30071
|
destroy() {
|
|
30072
|
+
if (this._private__completionCheck !== null) {
|
|
30073
|
+
clearTimeout(this._private__completionCheck);
|
|
30074
|
+
this._private__completionCheck = null;
|
|
30075
|
+
}
|
|
30076
|
+
this._private__drawingCompleted._internal_destroy();
|
|
30077
|
+
this._private__selectionChanged._internal_destroy();
|
|
29130
30078
|
this.clearAll();
|
|
29131
30079
|
this._private__detachEventHandlers();
|
|
30080
|
+
this._private__past = [];
|
|
30081
|
+
this._private__future = [];
|
|
30082
|
+
this._private__committed = [];
|
|
29132
30083
|
}
|
|
29133
30084
|
_private__attachEventHandlers() {
|
|
29134
30085
|
// Click handler for selection
|
|
@@ -29146,11 +30097,17 @@ class DrawingToolsManager {
|
|
|
29146
30097
|
const chartElement = this._private__chart.chartElement();
|
|
29147
30098
|
if (chartElement) {
|
|
29148
30099
|
this._private__pointerDownHandler = (event) => this._private__handlePointerDown(event);
|
|
29149
|
-
this._private__pointerUpHandler = (event) => this._private__handlePointerUp();
|
|
29150
|
-
this._private__pointerCancelHandler = (event) => this._private__handlePointerUp();
|
|
30100
|
+
this._private__pointerUpHandler = (event) => this._private__handlePointerUp(event);
|
|
30101
|
+
this._private__pointerCancelHandler = (event) => this._private__handlePointerUp(event);
|
|
30102
|
+
this._private__lostCaptureHandler = (event) => this._private__handleLostPointerCapture(event);
|
|
29151
30103
|
chartElement.addEventListener('pointerdown', this._private__pointerDownHandler);
|
|
29152
30104
|
chartElement.addEventListener('pointerup', this._private__pointerUpHandler);
|
|
29153
30105
|
chartElement.addEventListener('pointercancel', this._private__pointerCancelHandler);
|
|
30106
|
+
chartElement.addEventListener('lostpointercapture', this._private__lostCaptureHandler);
|
|
30107
|
+
}
|
|
30108
|
+
if (isRunningOnClientSide) {
|
|
30109
|
+
this._private__windowPointerUpHandler = (event) => this._private__handlePointerUp(event);
|
|
30110
|
+
window.addEventListener('pointerup', this._private__windowPointerUpHandler);
|
|
29154
30111
|
}
|
|
29155
30112
|
// Keyboard handler for shortcuts
|
|
29156
30113
|
if (this._private__options.enableKeyboardShortcuts) {
|
|
@@ -29183,6 +30140,14 @@ class DrawingToolsManager {
|
|
|
29183
30140
|
chartElement.removeEventListener('pointercancel', this._private__pointerCancelHandler);
|
|
29184
30141
|
this._private__pointerCancelHandler = null;
|
|
29185
30142
|
}
|
|
30143
|
+
if (this._private__lostCaptureHandler) {
|
|
30144
|
+
chartElement.removeEventListener('lostpointercapture', this._private__lostCaptureHandler);
|
|
30145
|
+
this._private__lostCaptureHandler = null;
|
|
30146
|
+
}
|
|
30147
|
+
}
|
|
30148
|
+
if (this._private__windowPointerUpHandler) {
|
|
30149
|
+
window.removeEventListener('pointerup', this._private__windowPointerUpHandler);
|
|
30150
|
+
this._private__windowPointerUpHandler = null;
|
|
29186
30151
|
}
|
|
29187
30152
|
if (this._private__keyboardHandler) {
|
|
29188
30153
|
document.removeEventListener('keydown', this._private__keyboardHandler);
|
|
@@ -29190,6 +30155,7 @@ class DrawingToolsManager {
|
|
|
29190
30155
|
}
|
|
29191
30156
|
}
|
|
29192
30157
|
_private__handleClick(param) {
|
|
30158
|
+
this._private__scheduleCompletionCheck();
|
|
29193
30159
|
if (!param.point) {
|
|
29194
30160
|
return;
|
|
29195
30161
|
}
|
|
@@ -29269,6 +30235,9 @@ class DrawingToolsManager {
|
|
|
29269
30235
|
return null;
|
|
29270
30236
|
}
|
|
29271
30237
|
_private__handlePointerDown(event) {
|
|
30238
|
+
if (this._private__isDragging) {
|
|
30239
|
+
return;
|
|
30240
|
+
}
|
|
29272
30241
|
// Cleared for every press, then raised only if this one grabs a drawing
|
|
29273
30242
|
setInputClaim(this._private__chart, false);
|
|
29274
30243
|
const chartElement = this._private__chart.chartElement();
|
|
@@ -29304,28 +30273,51 @@ class DrawingToolsManager {
|
|
|
29304
30273
|
this._private__gestureHandled = true;
|
|
29305
30274
|
this._private__draggedToolId = toolId;
|
|
29306
30275
|
this._private__pointerId = event.pointerId;
|
|
29307
|
-
|
|
30276
|
+
try {
|
|
30277
|
+
chartElement.setPointerCapture(event.pointerId);
|
|
30278
|
+
}
|
|
30279
|
+
catch (error) {
|
|
30280
|
+
this._private__pointerId = null;
|
|
30281
|
+
}
|
|
29308
30282
|
tool.startDrag(endpoint, anchor);
|
|
29309
30283
|
// Panning is stopped by disabling it for the gesture, not by preventing
|
|
29310
30284
|
// the event: preventDefault on pointerdown suppresses the compatibility
|
|
29311
30285
|
// mouse events, and those are what drive the crosshair the drag reads.
|
|
29312
30286
|
this._private__disableChartInteractions();
|
|
29313
30287
|
}
|
|
29314
|
-
_private__handlePointerUp() {
|
|
30288
|
+
_private__handlePointerUp(event) {
|
|
29315
30289
|
if (!this._private__isDragging || this._private__draggedToolId === null) {
|
|
29316
30290
|
return;
|
|
29317
30291
|
}
|
|
29318
|
-
|
|
29319
|
-
|
|
29320
|
-
tool.stopDrag();
|
|
30292
|
+
if (event && this._private__pointerId !== null && event.pointerId !== this._private__pointerId) {
|
|
30293
|
+
return;
|
|
29321
30294
|
}
|
|
29322
|
-
this.
|
|
29323
|
-
|
|
30295
|
+
const draggedToolId = this._private__draggedToolId;
|
|
30296
|
+
// Cleared first: anything below can throw, and a half-finished release
|
|
30297
|
+
// would leave the manager dragging forever.
|
|
29324
30298
|
this._private__isDragging = false;
|
|
29325
30299
|
this._private__draggedToolId = null;
|
|
30300
|
+
try {
|
|
30301
|
+
const tool = this._private__tools.get(draggedToolId);
|
|
30302
|
+
if (tool) {
|
|
30303
|
+
tool.stopDrag();
|
|
30304
|
+
}
|
|
30305
|
+
}
|
|
30306
|
+
finally {
|
|
30307
|
+
this._private__releasePointer();
|
|
30308
|
+
this._private__enableChartInteractions();
|
|
30309
|
+
}
|
|
29326
30310
|
// A finished drag is one step, not one per frame
|
|
29327
30311
|
this.recordChange();
|
|
29328
30312
|
}
|
|
30313
|
+
/** Ends the drag when capture is lost without a pointerup ever arriving. */
|
|
30314
|
+
_private__handleLostPointerCapture(event) {
|
|
30315
|
+
if (this._private__pointerId !== null && event.pointerId !== this._private__pointerId) {
|
|
30316
|
+
return;
|
|
30317
|
+
}
|
|
30318
|
+
this._private__pointerId = null;
|
|
30319
|
+
this._private__handlePointerUp();
|
|
30320
|
+
}
|
|
29329
30321
|
/**
|
|
29330
30322
|
* Pane element of a tool's series. The engine asserts the series has a pane,
|
|
29331
30323
|
* so the lookup is guarded.
|
|
@@ -29372,10 +30364,15 @@ class DrawingToolsManager {
|
|
|
29372
30364
|
this._private__pointerId = null;
|
|
29373
30365
|
}
|
|
29374
30366
|
_private__disableChartInteractions() {
|
|
30367
|
+
if (this._private__interactionsBeforeDrag !== null) {
|
|
30368
|
+
return;
|
|
30369
|
+
}
|
|
29375
30370
|
const options = this._private__chart.options();
|
|
30371
|
+
// `options()` returns the live object and `applyOptions` merges into it
|
|
30372
|
+
// in place, so a snapshot by reference restores all-false.
|
|
29376
30373
|
this._private__interactionsBeforeDrag = {
|
|
29377
|
-
_internal_handleScroll: options.handleScroll,
|
|
29378
|
-
_internal_handleScale: options.handleScale,
|
|
30374
|
+
_internal_handleScroll: clone(options.handleScroll),
|
|
30375
|
+
_internal_handleScale: clone(options.handleScale),
|
|
29379
30376
|
};
|
|
29380
30377
|
this._private__chart.applyOptions({
|
|
29381
30378
|
handleScroll: false,
|
|
@@ -29396,6 +30393,14 @@ class DrawingToolsManager {
|
|
|
29396
30393
|
});
|
|
29397
30394
|
}
|
|
29398
30395
|
_private__handleKeyboard(event) {
|
|
30396
|
+
const target = event.target;
|
|
30397
|
+
const typing = !!target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable);
|
|
30398
|
+
if (typing) {
|
|
30399
|
+
return;
|
|
30400
|
+
}
|
|
30401
|
+
if (event.key === 'Enter') {
|
|
30402
|
+
this._private__scheduleCompletionCheck();
|
|
30403
|
+
}
|
|
29399
30404
|
// Delete or Backspace to remove selected tool
|
|
29400
30405
|
if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
29401
30406
|
if (this.deleteSelected()) {
|
|
@@ -29416,8 +30421,8 @@ class DrawingToolsManager {
|
|
|
29416
30421
|
}
|
|
29417
30422
|
return;
|
|
29418
30423
|
}
|
|
29419
|
-
// Escape to deselect
|
|
29420
30424
|
if (event.key === 'Escape') {
|
|
30425
|
+
this.cancelPendingDrawings();
|
|
29421
30426
|
this._private__handlePointerUp();
|
|
29422
30427
|
this._private__deselectAll();
|
|
29423
30428
|
this.stopDrawing();
|
|
@@ -29453,7 +30458,7 @@ const customSeriesDefaultOptions = {
|
|
|
29453
30458
|
* Returns the current version as a string. For example `'1.0.0'`.
|
|
29454
30459
|
*/
|
|
29455
30460
|
function version() {
|
|
29456
|
-
return "2.
|
|
30461
|
+
return "2.3.0-dev+202609220108";
|
|
29457
30462
|
}
|
|
29458
30463
|
|
|
29459
|
-
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, 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, createPositionMarkers, createPositionTool, createRectangleTool, createRulerTool, createSeriesMarkers, createTextTool, createTextWatermark, createTradingLine, createTradingSessionsIndicator, createTrendLineTool, createUpDownMarkers, createVerticalLineTool, createVolumeProfileIndicator, createYieldCurveChart, customSeriesDefaultOptions, defaultHorzScaleBehavior, getPipConfig, hideSeriesPaint, isBusinessDay, isUTCTimestamp, setSeriesVolumes, setVolumeProfileData, timeFromCoordinate, version };
|
|
30464
|
+
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 };
|