@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)
|
|
@@ -14641,6 +14641,8 @@
|
|
|
14641
14641
|
return size(originalSize, 0.8);
|
|
14642
14642
|
case 'square':
|
|
14643
14643
|
return size(originalSize, 0.7);
|
|
14644
|
+
case 'tag':
|
|
14645
|
+
return size(originalSize, 1);
|
|
14644
14646
|
}
|
|
14645
14647
|
}
|
|
14646
14648
|
function calculateShapeHeight(barSpacing) {
|
|
@@ -14750,6 +14752,85 @@
|
|
|
14750
14752
|
y >= top && y <= top + squareSize;
|
|
14751
14753
|
}
|
|
14752
14754
|
|
|
14755
|
+
const TAIL_HALF_WIDTH_RATIO = 0.62;
|
|
14756
|
+
/**
|
|
14757
|
+
* Lays the pointer into the edge the path is already travelling along, so the
|
|
14758
|
+
* bubble and its tail are a single outline. Two subpaths would leave a stroked
|
|
14759
|
+
* seam where they meet.
|
|
14760
|
+
*/
|
|
14761
|
+
// eslint-disable-next-line max-params
|
|
14762
|
+
function tailPath(ctx, xCenter, edgeY, tailHeight, halfWidth, up) {
|
|
14763
|
+
// A tip with a small radius reads as drawn rather than as an artefact of the
|
|
14764
|
+
// rasteriser, which a bare triangle does at these sizes.
|
|
14765
|
+
const tipRadius = Math.min(halfWidth / 3, tailHeight / 3);
|
|
14766
|
+
const sign = up ? -1 : 1;
|
|
14767
|
+
const tipY = edgeY + sign * tailHeight;
|
|
14768
|
+
const shoulderY = tipY - sign * tipRadius;
|
|
14769
|
+
// The top edge is travelled left to right, the bottom edge right to left.
|
|
14770
|
+
const entry = up ? -halfWidth : halfWidth;
|
|
14771
|
+
ctx.lineTo(xCenter + entry, edgeY);
|
|
14772
|
+
ctx.lineTo(xCenter + (up ? -tipRadius : tipRadius), shoulderY);
|
|
14773
|
+
ctx.quadraticCurveTo(xCenter, tipY, xCenter + (up ? tipRadius : -tipRadius), shoulderY);
|
|
14774
|
+
ctx.lineTo(xCenter - entry, edgeY);
|
|
14775
|
+
}
|
|
14776
|
+
/**
|
|
14777
|
+
* Draws a rounded speech-bubble tag with an optional pointer towards its anchor.
|
|
14778
|
+
*/
|
|
14779
|
+
function drawTag(ctx, params) {
|
|
14780
|
+
const hpr = params._internal_horizontalPixelRatio;
|
|
14781
|
+
const vpr = params._internal_verticalPixelRatio;
|
|
14782
|
+
const left = Math.round((params._internal_x - params._internal_width / 2) * hpr);
|
|
14783
|
+
const right = Math.round((params._internal_x + params._internal_width / 2) * hpr);
|
|
14784
|
+
const top = Math.round((params._internal_centerY - params._internal_height / 2) * vpr);
|
|
14785
|
+
const bottom = Math.round((params._internal_centerY + params._internal_height / 2) * vpr);
|
|
14786
|
+
const xCenter = Math.round(params._internal_x * hpr);
|
|
14787
|
+
const radius = Math.min(params._internal_radius * hpr, (right - left) / 2, (bottom - top) / 2);
|
|
14788
|
+
const tailHeight = params._internal_tailHeight * vpr;
|
|
14789
|
+
const tailHalfWidth = Math.min(tailHeight * TAIL_HALF_WIDTH_RATIO, Math.max(0, (right - left) / 2 - radius));
|
|
14790
|
+
const hasTail = params._internal_tailDirection !== 'none' && tailHalfWidth > 0 && tailHeight > 0;
|
|
14791
|
+
ctx.save();
|
|
14792
|
+
ctx.beginPath();
|
|
14793
|
+
if (!hasTail) {
|
|
14794
|
+
drawRoundRect(ctx, left, top, right - left, bottom - top, [radius, radius, radius, radius]);
|
|
14795
|
+
}
|
|
14796
|
+
else {
|
|
14797
|
+
ctx.moveTo(left + radius, top);
|
|
14798
|
+
if (params._internal_tailDirection === 'up') {
|
|
14799
|
+
tailPath(ctx, xCenter, top, tailHeight, tailHalfWidth, true);
|
|
14800
|
+
}
|
|
14801
|
+
ctx.lineTo(right - radius, top);
|
|
14802
|
+
ctx.arcTo(right, top, right, top + radius, radius);
|
|
14803
|
+
ctx.lineTo(right, bottom - radius);
|
|
14804
|
+
ctx.arcTo(right, bottom, right - radius, bottom, radius);
|
|
14805
|
+
if (params._internal_tailDirection === 'down') {
|
|
14806
|
+
tailPath(ctx, xCenter, bottom, tailHeight, tailHalfWidth, false);
|
|
14807
|
+
}
|
|
14808
|
+
ctx.lineTo(left + radius, bottom);
|
|
14809
|
+
ctx.arcTo(left, bottom, left, bottom - radius, radius);
|
|
14810
|
+
ctx.lineTo(left, top + radius);
|
|
14811
|
+
ctx.arcTo(left, top, left + radius, top, radius);
|
|
14812
|
+
}
|
|
14813
|
+
ctx.closePath();
|
|
14814
|
+
ctx.fillStyle = params._internal_fillColor;
|
|
14815
|
+
ctx.fill();
|
|
14816
|
+
if (params._internal_borderColor !== undefined && params._internal_borderWidth > 0) {
|
|
14817
|
+
ctx.lineWidth = Math.max(1, Math.round(params._internal_borderWidth * hpr));
|
|
14818
|
+
ctx.strokeStyle = params._internal_borderColor;
|
|
14819
|
+
ctx.stroke();
|
|
14820
|
+
}
|
|
14821
|
+
ctx.restore();
|
|
14822
|
+
}
|
|
14823
|
+
function hitTestTag(centerX, centerY, tag, x, y) {
|
|
14824
|
+
if (tag._internal_width <= 0) {
|
|
14825
|
+
return false;
|
|
14826
|
+
}
|
|
14827
|
+
const halfHeight = tag._internal_height / 2;
|
|
14828
|
+
const top = tag._internal_tailDirection === 'up' ? centerY - halfHeight - tag._internal_tailHeight : centerY - halfHeight;
|
|
14829
|
+
const bottom = tag._internal_tailDirection === 'down' ? centerY + halfHeight + tag._internal_tailHeight : centerY + halfHeight;
|
|
14830
|
+
const left = centerX - tag._internal_width / 2;
|
|
14831
|
+
return x >= left && x <= left + tag._internal_width && y >= top && y <= bottom;
|
|
14832
|
+
}
|
|
14833
|
+
|
|
14753
14834
|
function drawText(ctx, text, x, y, horizontalPixelRatio, verticalPixelRatio) {
|
|
14754
14835
|
ctx.save();
|
|
14755
14836
|
ctx.scale(horizontalPixelRatio, verticalPixelRatio);
|
|
@@ -14766,9 +14847,11 @@
|
|
|
14766
14847
|
constructor() {
|
|
14767
14848
|
this._private__data = null;
|
|
14768
14849
|
this._private__textWidthCache = new TextWidthCache();
|
|
14850
|
+
this._private__tagTextWidthCache = new TextWidthCache();
|
|
14769
14851
|
this._private__fontSize = -1;
|
|
14770
14852
|
this._private__fontFamily = '';
|
|
14771
14853
|
this._private__font = '';
|
|
14854
|
+
this._private__tagFont = '';
|
|
14772
14855
|
this._private__zOrder = 'normal';
|
|
14773
14856
|
}
|
|
14774
14857
|
_internal_setData(data) {
|
|
@@ -14779,7 +14862,11 @@
|
|
|
14779
14862
|
this._private__fontSize = fontSize;
|
|
14780
14863
|
this._private__fontFamily = fontFamily;
|
|
14781
14864
|
this._private__font = makeFont(fontSize, fontFamily);
|
|
14865
|
+
// Tags carry a label rather than an annotation, so they get the heavier
|
|
14866
|
+
// weight — and their own cache, since widths differ per font.
|
|
14867
|
+
this._private__tagFont = makeFont(fontSize, fontFamily, '600');
|
|
14782
14868
|
this._private__textWidthCache._internal_reset();
|
|
14869
|
+
this._private__tagTextWidthCache._internal_reset();
|
|
14783
14870
|
}
|
|
14784
14871
|
this._private__zOrder = zOrder;
|
|
14785
14872
|
}
|
|
@@ -14819,14 +14906,21 @@
|
|
|
14819
14906
|
return;
|
|
14820
14907
|
}
|
|
14821
14908
|
ctx.textBaseline = 'middle';
|
|
14822
|
-
ctx.font = this._private__font;
|
|
14823
14909
|
for (let index = this._private__data._internal_visibleRange.from; index < this._private__data._internal_visibleRange.to; index++) {
|
|
14824
14910
|
const item = this._private__data._internal_items[index];
|
|
14911
|
+
const isTag = item._internal_tag !== undefined;
|
|
14912
|
+
ctx.font = isTag ? this._private__tagFont : this._private__font;
|
|
14825
14913
|
if (item._internal_text !== undefined) {
|
|
14826
|
-
|
|
14914
|
+
const cache = isTag ? this._private__tagTextWidthCache : this._private__textWidthCache;
|
|
14915
|
+
item._internal_text._internal_width = cache._internal_measureText(ctx, item._internal_text._internal_content);
|
|
14827
14916
|
item._internal_text._internal_height = this._private__fontSize;
|
|
14828
14917
|
item._internal_text._internal_x = item._internal_x - item._internal_text._internal_width / 2;
|
|
14829
14918
|
}
|
|
14919
|
+
if (item._internal_tag !== undefined) {
|
|
14920
|
+
const textWidth = item._internal_text !== undefined ? item._internal_text._internal_width : 0;
|
|
14921
|
+
item._internal_tag._internal_width = Math.max(textWidth + item._internal_tag._internal_paddingX * 2, item._internal_tag._internal_height);
|
|
14922
|
+
item._internal_tag._internal_left = item._internal_x - item._internal_tag._internal_width / 2;
|
|
14923
|
+
}
|
|
14830
14924
|
drawItem(item, ctx, horizontalPixelRatio, verticalPixelRatio);
|
|
14831
14925
|
}
|
|
14832
14926
|
}
|
|
@@ -14841,12 +14935,40 @@
|
|
|
14841
14935
|
};
|
|
14842
14936
|
}
|
|
14843
14937
|
function drawItem(item, ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
14938
|
+
if (item._internal_tag !== undefined) {
|
|
14939
|
+
drawTagItem(item, item._internal_tag, ctx, horizontalPixelRatio, verticalPixelRatio);
|
|
14940
|
+
return;
|
|
14941
|
+
}
|
|
14844
14942
|
ctx.fillStyle = item._internal_color;
|
|
14845
14943
|
if (item._internal_text !== undefined) {
|
|
14846
14944
|
drawText(ctx, item._internal_text._internal_content, item._internal_text._internal_x, item._internal_text._internal_y, horizontalPixelRatio, verticalPixelRatio);
|
|
14847
14945
|
}
|
|
14848
14946
|
drawShape(item, ctx, bitmapShapeItemCoordinates(item, horizontalPixelRatio, verticalPixelRatio));
|
|
14849
14947
|
}
|
|
14948
|
+
// eslint-disable-next-line max-params
|
|
14949
|
+
function drawTagItem(item, tag, ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
14950
|
+
if (item._internal_size === 0) {
|
|
14951
|
+
return;
|
|
14952
|
+
}
|
|
14953
|
+
drawTag(ctx, {
|
|
14954
|
+
_internal_x: item._internal_x,
|
|
14955
|
+
_internal_centerY: item._internal_y,
|
|
14956
|
+
_internal_width: tag._internal_width,
|
|
14957
|
+
_internal_height: tag._internal_height,
|
|
14958
|
+
_internal_tailHeight: tag._internal_tailHeight,
|
|
14959
|
+
_internal_tailDirection: tag._internal_tailDirection,
|
|
14960
|
+
_internal_radius: tag._internal_radius,
|
|
14961
|
+
_internal_fillColor: item._internal_color,
|
|
14962
|
+
_internal_borderColor: item._internal_borderColor,
|
|
14963
|
+
_internal_borderWidth: item._internal_borderWidth ?? 1,
|
|
14964
|
+
_internal_horizontalPixelRatio: horizontalPixelRatio,
|
|
14965
|
+
_internal_verticalPixelRatio: verticalPixelRatio,
|
|
14966
|
+
});
|
|
14967
|
+
if (item._internal_text !== undefined) {
|
|
14968
|
+
ctx.fillStyle = item._internal_textColor ?? '#FFFFFF';
|
|
14969
|
+
drawText(ctx, item._internal_text._internal_content, item._internal_text._internal_x, item._internal_text._internal_y, horizontalPixelRatio, verticalPixelRatio);
|
|
14970
|
+
}
|
|
14971
|
+
}
|
|
14850
14972
|
function drawShape(item, ctx, coordinates) {
|
|
14851
14973
|
if (item._internal_size === 0) {
|
|
14852
14974
|
return;
|
|
@@ -14864,10 +14986,16 @@
|
|
|
14864
14986
|
case 'square':
|
|
14865
14987
|
drawSquare(ctx, coordinates, item._internal_size);
|
|
14866
14988
|
return;
|
|
14989
|
+
case 'tag':
|
|
14990
|
+
// Handled by drawTagItem, which needs both pixel ratios and the text.
|
|
14991
|
+
return;
|
|
14867
14992
|
}
|
|
14868
14993
|
ensureNever(item._internal_shape);
|
|
14869
14994
|
}
|
|
14870
14995
|
function hitTestItem(item, x, y) {
|
|
14996
|
+
if (item._internal_tag !== undefined) {
|
|
14997
|
+
return item._internal_size !== 0 && hitTestTag(item._internal_x, item._internal_y, item._internal_tag, x, y);
|
|
14998
|
+
}
|
|
14871
14999
|
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)) {
|
|
14872
15000
|
return true;
|
|
14873
15001
|
}
|
|
@@ -14886,6 +15014,8 @@
|
|
|
14886
15014
|
return hitTestCircle(item._internal_x, item._internal_y, item._internal_size, x, y);
|
|
14887
15015
|
case 'square':
|
|
14888
15016
|
return hitTestSquare(item._internal_x, item._internal_y, item._internal_size, x, y);
|
|
15017
|
+
case 'tag':
|
|
15018
|
+
return false;
|
|
14889
15019
|
}
|
|
14890
15020
|
}
|
|
14891
15021
|
|
|
@@ -14918,15 +15048,71 @@
|
|
|
14918
15048
|
}
|
|
14919
15049
|
return;
|
|
14920
15050
|
}
|
|
15051
|
+
/**
|
|
15052
|
+
* Lays out a `tag` marker: the bubble carries the text, so it is measured from
|
|
15053
|
+
* the font rather than from the bar spacing, and its pointer is aimed back at
|
|
15054
|
+
* the anchor price.
|
|
15055
|
+
*/
|
|
15056
|
+
// eslint-disable-next-line max-params
|
|
15057
|
+
function fillTagSizeAndY(rendererItem, marker, anchorY, offsets, textHeight, shapeMargin, sizeMultiplier) {
|
|
15058
|
+
const height = Math.round((textHeight + 5 /* Constants.TagPaddingY */ * 2) * sizeMultiplier);
|
|
15059
|
+
const tailHeight = Math.round(7 /* Constants.TagTailHeight */ * sizeMultiplier);
|
|
15060
|
+
const ignoreOffset = isPriceMarker(marker.position);
|
|
15061
|
+
const halfHeight = height / 2;
|
|
15062
|
+
let tailDirection = 'none';
|
|
15063
|
+
let centerY = anchorY;
|
|
15064
|
+
switch (marker.position) {
|
|
15065
|
+
case 'aboveBar':
|
|
15066
|
+
case 'atPriceTop': {
|
|
15067
|
+
const offset = ignoreOffset ? 0 : offsets._internal_aboveBar;
|
|
15068
|
+
tailDirection = 'down';
|
|
15069
|
+
centerY = (anchorY - offset - tailHeight - halfHeight);
|
|
15070
|
+
if (!ignoreOffset) {
|
|
15071
|
+
offsets._internal_aboveBar += height + tailHeight + shapeMargin;
|
|
15072
|
+
}
|
|
15073
|
+
break;
|
|
15074
|
+
}
|
|
15075
|
+
case 'belowBar':
|
|
15076
|
+
case 'atPriceBottom': {
|
|
15077
|
+
const offset = ignoreOffset ? 0 : offsets._internal_belowBar;
|
|
15078
|
+
tailDirection = 'up';
|
|
15079
|
+
centerY = (anchorY + offset + tailHeight + halfHeight);
|
|
15080
|
+
if (!ignoreOffset) {
|
|
15081
|
+
offsets._internal_belowBar += height + tailHeight + shapeMargin;
|
|
15082
|
+
}
|
|
15083
|
+
break;
|
|
15084
|
+
}
|
|
15085
|
+
}
|
|
15086
|
+
const tag = {
|
|
15087
|
+
_internal_height: height,
|
|
15088
|
+
_internal_tailHeight: tailHeight,
|
|
15089
|
+
_internal_tailDirection: tailDirection,
|
|
15090
|
+
_internal_radius: marker.borderRadius ?? 6 /* Constants.TagRadius */,
|
|
15091
|
+
_internal_paddingX: 9 /* Constants.TagPaddingX */ * sizeMultiplier,
|
|
15092
|
+
_internal_left: 0,
|
|
15093
|
+
_internal_width: 0,
|
|
15094
|
+
};
|
|
15095
|
+
rendererItem._internal_size = height;
|
|
15096
|
+
rendererItem._internal_y = centerY;
|
|
15097
|
+
rendererItem._internal_tag = tag;
|
|
15098
|
+
if (rendererItem._internal_text !== undefined) {
|
|
15099
|
+
rendererItem._internal_text._internal_y = centerY;
|
|
15100
|
+
}
|
|
15101
|
+
}
|
|
14921
15102
|
// eslint-disable-next-line max-params, complexity
|
|
14922
|
-
function fillSizeAndY(rendererItem, marker, seriesData, offsets, textHeight, shapeMargin, series, chart) {
|
|
14923
|
-
const price = getPrice(seriesData, marker,
|
|
15103
|
+
function fillSizeAndY(rendererItem, marker, seriesData, offsets, textHeight, shapeMargin, series, chart, invertScale) {
|
|
15104
|
+
const price = getPrice(seriesData, marker, invertScale);
|
|
14924
15105
|
if (price === undefined) {
|
|
14925
15106
|
return;
|
|
14926
15107
|
}
|
|
14927
15108
|
const ignoreOffset = isPriceMarker(marker.position);
|
|
14928
15109
|
const timeScale = chart.timeScale();
|
|
14929
15110
|
const sizeMultiplier = isNumber(marker.size) ? Math.max(marker.size, 0) : 1;
|
|
15111
|
+
if (marker.shape === 'tag') {
|
|
15112
|
+
const anchorY = ensureNotNull(series.priceToCoordinate(price));
|
|
15113
|
+
fillTagSizeAndY(rendererItem, marker, anchorY, offsets, textHeight, shapeMargin, sizeMultiplier);
|
|
15114
|
+
return;
|
|
15115
|
+
}
|
|
14930
15116
|
const shapeSize = calculateShapeHeight(timeScale.options().barSpacing) * sizeMultiplier;
|
|
14931
15117
|
const halfSize = shapeSize / 2;
|
|
14932
15118
|
rendererItem._internal_size = shapeSize;
|
|
@@ -15033,6 +15219,10 @@
|
|
|
15033
15219
|
_internal_externalId: marker.id,
|
|
15034
15220
|
_internal_internalId: marker._internal_internalId,
|
|
15035
15221
|
_internal_text: undefined,
|
|
15222
|
+
_internal_textColor: marker.textColor,
|
|
15223
|
+
_internal_borderColor: marker.borderColor,
|
|
15224
|
+
_internal_borderWidth: marker.borderWidth,
|
|
15225
|
+
_internal_tag: undefined,
|
|
15036
15226
|
}));
|
|
15037
15227
|
this._private__dataInvalidated = false;
|
|
15038
15228
|
}
|
|
@@ -15043,13 +15233,22 @@
|
|
|
15043
15233
|
return;
|
|
15044
15234
|
}
|
|
15045
15235
|
const visibleBarsRange = new RangeImpl(Math.floor(visibleBars.from), Math.ceil(visibleBars.to));
|
|
15046
|
-
const firstValue = this._private__series.
|
|
15236
|
+
const firstValue = this._private__series.dataByIndex(0, 1 /* MismatchDirection.NearestRight */);
|
|
15047
15237
|
if (firstValue === null) {
|
|
15048
15238
|
return;
|
|
15049
15239
|
}
|
|
15050
15240
|
if (this._private__data._internal_items.length === 0) {
|
|
15051
15241
|
return;
|
|
15052
15242
|
}
|
|
15243
|
+
// This runs inside the hit-test and paint loops, where a throw costs the
|
|
15244
|
+
// rest of the frame.
|
|
15245
|
+
let invertScale = false;
|
|
15246
|
+
try {
|
|
15247
|
+
invertScale = this._private__series.priceScale().options().invertScale;
|
|
15248
|
+
}
|
|
15249
|
+
catch (error) {
|
|
15250
|
+
invertScale = false;
|
|
15251
|
+
}
|
|
15053
15252
|
let prevTimeIndex = NaN;
|
|
15054
15253
|
const shapeMargin$1 = shapeMargin(timeScale.options().barSpacing);
|
|
15055
15254
|
const offsets = {
|
|
@@ -15080,7 +15279,7 @@
|
|
|
15080
15279
|
if (dataAt === null) {
|
|
15081
15280
|
continue;
|
|
15082
15281
|
}
|
|
15083
|
-
fillSizeAndY(rendererItem, marker, dataAt, offsets, layoutOptions.fontSize, shapeMargin$1, this._private__series, this._private__chart);
|
|
15282
|
+
fillSizeAndY(rendererItem, marker, dataAt, offsets, layoutOptions.fontSize, shapeMargin$1, this._private__series, this._private__chart, invertScale);
|
|
15084
15283
|
}
|
|
15085
15284
|
this._private__invalidated = false;
|
|
15086
15285
|
}
|
|
@@ -15238,6 +15437,10 @@
|
|
|
15238
15437
|
text: marker.text,
|
|
15239
15438
|
size: marker.size,
|
|
15240
15439
|
price: marker.price,
|
|
15440
|
+
textColor: marker.textColor,
|
|
15441
|
+
borderColor: marker.borderColor,
|
|
15442
|
+
borderWidth: marker.borderWidth,
|
|
15443
|
+
borderRadius: marker.borderRadius,
|
|
15241
15444
|
_internal_originalTime: marker.time,
|
|
15242
15445
|
};
|
|
15243
15446
|
if (marker.position === 'atPriceTop' ||
|
|
@@ -19539,6 +19742,7 @@
|
|
|
19539
19742
|
constructor(chart, element) {
|
|
19540
19743
|
this._private__lines = new Map();
|
|
19541
19744
|
this._private__hoveredId = null;
|
|
19745
|
+
this._private__hoveredPart = null;
|
|
19542
19746
|
this._private__dragging = null;
|
|
19543
19747
|
this._private__pointerId = null;
|
|
19544
19748
|
this._private__pressOrigin = null;
|
|
@@ -19596,8 +19800,12 @@
|
|
|
19596
19800
|
if (this._private__dragging !== null) {
|
|
19597
19801
|
return;
|
|
19598
19802
|
}
|
|
19599
|
-
const
|
|
19803
|
+
const hit = this._private__splitHit(param.hoveredObjectId);
|
|
19804
|
+
const hovered = hit === null ? null : this._private__lines.get(hit._internal_id) ?? null;
|
|
19600
19805
|
this._private__setHovered(hovered ? hovered.id() : null);
|
|
19806
|
+
// The part is updated after the id, and separately from it: moving from a
|
|
19807
|
+
// label onto its close button keeps the same line hovered.
|
|
19808
|
+
this._private__setHoveredPart(hovered, hovered === null ? null : hit?._internal_part ?? null);
|
|
19601
19809
|
this._private__applyCursor();
|
|
19602
19810
|
}
|
|
19603
19811
|
_private__onPointerDown(event) {
|
|
@@ -19701,24 +19909,43 @@
|
|
|
19701
19909
|
if (line === null) {
|
|
19702
19910
|
return null;
|
|
19703
19911
|
}
|
|
19704
|
-
//
|
|
19705
|
-
//
|
|
19706
|
-
//
|
|
19912
|
+
// The pointer event is the gesture's own position, so it is the source of
|
|
19913
|
+
// truth. The crosshair is only a mirror of it: it stops updating as soon
|
|
19914
|
+
// as the pointer leaves the pane canvas — over the price axis, past the
|
|
19915
|
+
// chart's edge — and a drag driven from it freezes there while the user
|
|
19916
|
+
// is still moving.
|
|
19917
|
+
const pane = this._private__chart.panes()[line.paneIndex()];
|
|
19918
|
+
const paneElement = pane ? pane.getHTMLElement() : null;
|
|
19919
|
+
if (paneElement) {
|
|
19920
|
+
return line.priceAt(event.clientY - paneElement.getBoundingClientRect().top);
|
|
19921
|
+
}
|
|
19707
19922
|
if (this._private__point !== null && this._private__paneIndex === line.paneIndex()) {
|
|
19708
19923
|
return line.priceAt(this._private__point._internal_y);
|
|
19709
19924
|
}
|
|
19710
|
-
|
|
19711
|
-
|
|
19712
|
-
|
|
19925
|
+
return null;
|
|
19926
|
+
}
|
|
19927
|
+
/**
|
|
19928
|
+
* Hit ids are either a bare line id or `id#part`, which is how a line with
|
|
19929
|
+
* several targets reports which one was hit.
|
|
19930
|
+
*/
|
|
19931
|
+
_private__splitHit(externalId) {
|
|
19932
|
+
if (typeof externalId !== 'string' || externalId.length === 0) {
|
|
19713
19933
|
return null;
|
|
19714
19934
|
}
|
|
19715
|
-
|
|
19935
|
+
const separator = externalId.indexOf('#');
|
|
19936
|
+
if (separator === -1) {
|
|
19937
|
+
return { _internal_id: externalId, _internal_part: null };
|
|
19938
|
+
}
|
|
19939
|
+
return { _internal_id: externalId.slice(0, separator), _internal_part: externalId.slice(separator + 1) };
|
|
19716
19940
|
}
|
|
19717
|
-
|
|
19718
|
-
if (
|
|
19719
|
-
return
|
|
19941
|
+
_private__setHoveredPart(line, part) {
|
|
19942
|
+
if (this._private__hoveredPart === part) {
|
|
19943
|
+
return;
|
|
19944
|
+
}
|
|
19945
|
+
this._private__hoveredPart = part;
|
|
19946
|
+
if (line && line.setHoveredPart) {
|
|
19947
|
+
line.setHoveredPart(part);
|
|
19720
19948
|
}
|
|
19721
|
-
return this._private__lines.get(externalId) ?? null;
|
|
19722
19949
|
}
|
|
19723
19950
|
_private__setHovered(id) {
|
|
19724
19951
|
if (this._private__hoveredId === id) {
|
|
@@ -19728,6 +19955,7 @@
|
|
|
19728
19955
|
this._private__lines.get(this._private__hoveredId)?.setHovered(false);
|
|
19729
19956
|
}
|
|
19730
19957
|
this._private__hoveredId = id;
|
|
19958
|
+
this._private__hoveredPart = null;
|
|
19731
19959
|
if (id !== null) {
|
|
19732
19960
|
this._private__lines.get(id)?.setHovered(true);
|
|
19733
19961
|
}
|
|
@@ -20251,6 +20479,702 @@
|
|
|
20251
20479
|
return new InteractiveLineManager(chart, series);
|
|
20252
20480
|
}
|
|
20253
20481
|
|
|
20482
|
+
const positionLineDefaultColors = {
|
|
20483
|
+
entry: '#2962FF',
|
|
20484
|
+
takeProfit: '#26A17B',
|
|
20485
|
+
stopLoss: '#D9A23B',
|
|
20486
|
+
};
|
|
20487
|
+
const positionLineThemeDefaults = {
|
|
20488
|
+
badgeTextColor: '#FFFFFF',
|
|
20489
|
+
borderRadius: 4,
|
|
20490
|
+
borderWidth: 1,
|
|
20491
|
+
paddingX: 8,
|
|
20492
|
+
paddingY: 6,
|
|
20493
|
+
backgroundOpacity: 1,
|
|
20494
|
+
};
|
|
20495
|
+
const positionLineLevels = ['takeProfit', 'entry', 'stopLoss'];
|
|
20496
|
+
|
|
20497
|
+
class PositionLineRenderer {
|
|
20498
|
+
constructor() {
|
|
20499
|
+
this._private__data = null;
|
|
20500
|
+
this._private__layouts = [];
|
|
20501
|
+
this._private__connectorX = 0;
|
|
20502
|
+
this._private__font = '';
|
|
20503
|
+
this._private__colorParser = new ColorParser([]);
|
|
20504
|
+
this._private__textWidthCache = new TextWidthCache(50);
|
|
20505
|
+
}
|
|
20506
|
+
_internal_setData(data) {
|
|
20507
|
+
this._private__data = data;
|
|
20508
|
+
}
|
|
20509
|
+
_internal_hitTest(x, y) {
|
|
20510
|
+
// The close button sits on top of the label, which sits on top of the
|
|
20511
|
+
// line, so the narrowest target is tested first.
|
|
20512
|
+
for (const layout of this._private__layouts) {
|
|
20513
|
+
if (layout._internal_closable && x >= layout._internal_closeLeft && x <= layout._internal_closeRight &&
|
|
20514
|
+
Math.abs(y - layout._internal_y) <= layout._internal_height / 2) {
|
|
20515
|
+
return { _internal_level: layout._internal_level, _internal_part: 'close' };
|
|
20516
|
+
}
|
|
20517
|
+
}
|
|
20518
|
+
for (const layout of this._private__layouts) {
|
|
20519
|
+
if (x >= layout._internal_pillLeft && x <= layout._internal_pillRight && Math.abs(y - layout._internal_y) <= layout._internal_height / 2) {
|
|
20520
|
+
return { _internal_level: layout._internal_level, _internal_part: 'body' };
|
|
20521
|
+
}
|
|
20522
|
+
}
|
|
20523
|
+
for (const layout of this._private__layouts) {
|
|
20524
|
+
if (Math.abs(y - layout._internal_y) <= 8 /* Constants.LineHitTolerance */) {
|
|
20525
|
+
return { _internal_level: layout._internal_level, _internal_part: 'line' };
|
|
20526
|
+
}
|
|
20527
|
+
}
|
|
20528
|
+
return null;
|
|
20529
|
+
}
|
|
20530
|
+
draw(target) {
|
|
20531
|
+
target.useBitmapCoordinateSpace((scope) => this._private__drawImpl(scope));
|
|
20532
|
+
}
|
|
20533
|
+
_private__drawImpl(scope) {
|
|
20534
|
+
const data = this._private__data;
|
|
20535
|
+
this._private__layouts = [];
|
|
20536
|
+
if (data === null || data._internal_levels.length === 0) {
|
|
20537
|
+
return;
|
|
20538
|
+
}
|
|
20539
|
+
const { context: ctx, mediaSize } = scope;
|
|
20540
|
+
ctx.save();
|
|
20541
|
+
if (this._private__font !== data._internal_font) {
|
|
20542
|
+
this._private__font = data._internal_font;
|
|
20543
|
+
this._private__textWidthCache._internal_reset();
|
|
20544
|
+
}
|
|
20545
|
+
ctx.font = data._internal_font;
|
|
20546
|
+
ctx.textBaseline = 'middle';
|
|
20547
|
+
this._private__connectorX = Math.max(0, mediaSize.width - data._internal_rightOffset);
|
|
20548
|
+
const lineEnd = data._internal_connectorVisible ? this._private__connectorX : mediaSize.width;
|
|
20549
|
+
for (const level of data._internal_levels) {
|
|
20550
|
+
this._private__layouts.push(this._private__drawLevel(scope, data, level, lineEnd));
|
|
20551
|
+
}
|
|
20552
|
+
if (data._internal_connectorVisible && data._internal_levels.length > 1) {
|
|
20553
|
+
this._private__drawConnector(scope, data);
|
|
20554
|
+
}
|
|
20555
|
+
ctx.restore();
|
|
20556
|
+
}
|
|
20557
|
+
// eslint-disable-next-line max-params
|
|
20558
|
+
_private__drawLevel(scope, data, level, lineEnd) {
|
|
20559
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20560
|
+
const height = data._internal_fontSize + data._internal_paddingY * 2;
|
|
20561
|
+
const badgeWidth = level._internal_badge.length === 0
|
|
20562
|
+
? 0
|
|
20563
|
+
: Math.max(this._private__textWidthCache._internal_measureText(ctx, level._internal_badge) + data._internal_paddingX * 2, height * 0.85 /* Constants.BadgeMinWidthRatio */);
|
|
20564
|
+
const labelWidth = this._private__textWidthCache._internal_measureText(ctx, level._internal_text) + data._internal_paddingX * 2;
|
|
20565
|
+
const closeWidth = level._internal_closable ? height : 0;
|
|
20566
|
+
const pillWidth = badgeWidth + labelWidth + closeWidth;
|
|
20567
|
+
const pillRight = Math.max(pillWidth, lineEnd - 8 /* Constants.LabelGap */);
|
|
20568
|
+
const pillLeft = pillRight - pillWidth;
|
|
20569
|
+
const top = level._internal_y - height / 2;
|
|
20570
|
+
this._private__drawLineSegments(scope, level, pillLeft, pillRight, lineEnd);
|
|
20571
|
+
this._private__drawPill(scope, data, level, { _internal_left: pillLeft, _internal_top: top, _internal_width: pillWidth, _internal_height: height });
|
|
20572
|
+
let cellLeft = pillLeft;
|
|
20573
|
+
if (badgeWidth > 0) {
|
|
20574
|
+
this._private__drawBadge(scope, data, level, { _internal_left: cellLeft, _internal_top: top, _internal_width: badgeWidth, _internal_height: height });
|
|
20575
|
+
cellLeft += badgeWidth;
|
|
20576
|
+
this._private__drawSeparator(ctx, level._internal_color, cellLeft, top, height, hpr, vpr);
|
|
20577
|
+
}
|
|
20578
|
+
ctx.save();
|
|
20579
|
+
ctx.scale(hpr, vpr);
|
|
20580
|
+
ctx.textAlign = 'center';
|
|
20581
|
+
ctx.fillStyle = data._internal_textColor ?? level._internal_color;
|
|
20582
|
+
ctx.fillText(level._internal_text, cellLeft + labelWidth / 2, level._internal_y);
|
|
20583
|
+
ctx.restore();
|
|
20584
|
+
cellLeft += labelWidth;
|
|
20585
|
+
if (level._internal_closable) {
|
|
20586
|
+
this._private__drawSeparator(ctx, level._internal_color, cellLeft, top, height, hpr, vpr);
|
|
20587
|
+
this._private__drawCloseButton(scope, data, level, { _internal_left: cellLeft, _internal_top: top, _internal_width: closeWidth, _internal_height: height });
|
|
20588
|
+
}
|
|
20589
|
+
return {
|
|
20590
|
+
_internal_level: level._internal_level,
|
|
20591
|
+
_internal_y: level._internal_y,
|
|
20592
|
+
_internal_height: height,
|
|
20593
|
+
_internal_pillLeft: pillLeft,
|
|
20594
|
+
_internal_pillRight: pillRight,
|
|
20595
|
+
_internal_closeLeft: cellLeft,
|
|
20596
|
+
_internal_closeRight: cellLeft + closeWidth,
|
|
20597
|
+
_internal_closable: level._internal_closable,
|
|
20598
|
+
};
|
|
20599
|
+
}
|
|
20600
|
+
// eslint-disable-next-line max-params
|
|
20601
|
+
_private__drawLineSegments(scope, level, pillLeft, pillRight, lineEnd) {
|
|
20602
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20603
|
+
const lineWidth = Math.max(1, Math.round(level._internal_lineWidth * vpr));
|
|
20604
|
+
const y = Math.round(level._internal_y * vpr) - Math.floor(lineWidth / 2);
|
|
20605
|
+
ctx.save();
|
|
20606
|
+
ctx.strokeStyle = level._internal_color;
|
|
20607
|
+
ctx.lineWidth = lineWidth;
|
|
20608
|
+
setLineStyle(ctx, level._internal_lineStyle);
|
|
20609
|
+
ctx.beginPath();
|
|
20610
|
+
ctx.moveTo(0, y);
|
|
20611
|
+
ctx.lineTo(Math.round(Math.max(0, pillLeft - 8 /* Constants.LabelGap */) * hpr), y);
|
|
20612
|
+
if (lineEnd > pillRight + 8 /* Constants.LabelGap */) {
|
|
20613
|
+
ctx.moveTo(Math.round((pillRight + 8 /* Constants.LabelGap */) * hpr), y);
|
|
20614
|
+
ctx.lineTo(Math.round(lineEnd * hpr), y);
|
|
20615
|
+
}
|
|
20616
|
+
ctx.stroke();
|
|
20617
|
+
ctx.restore();
|
|
20618
|
+
}
|
|
20619
|
+
// eslint-disable-next-line max-params
|
|
20620
|
+
_private__drawPill(scope, data, level, box) {
|
|
20621
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20622
|
+
const hovered = data._internal_hoveredLevel === level._internal_level;
|
|
20623
|
+
const borderWidth = Math.max(1, Math.round((hovered ? data._internal_borderWidth + 1 : data._internal_borderWidth) * hpr));
|
|
20624
|
+
ctx.save();
|
|
20625
|
+
ctx.setLineDash([]);
|
|
20626
|
+
bitmapRoundRect(ctx, box, data._internal_borderRadius, hpr, vpr);
|
|
20627
|
+
ctx.fillStyle = this._private__colorParser._internal_applyAlpha(data._internal_background, data._internal_backgroundOpacity);
|
|
20628
|
+
ctx.fill();
|
|
20629
|
+
ctx.lineWidth = borderWidth;
|
|
20630
|
+
ctx.strokeStyle = level._internal_color;
|
|
20631
|
+
ctx.stroke();
|
|
20632
|
+
ctx.restore();
|
|
20633
|
+
}
|
|
20634
|
+
// eslint-disable-next-line max-params
|
|
20635
|
+
_private__drawBadge(scope, data, level, box) {
|
|
20636
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20637
|
+
if (level._internal_badgeFilled) {
|
|
20638
|
+
// The badge shares the label's left corners, so its fill has to stop
|
|
20639
|
+
// short of the border instead of squaring it off.
|
|
20640
|
+
const inset = data._internal_borderWidth;
|
|
20641
|
+
ctx.save();
|
|
20642
|
+
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');
|
|
20643
|
+
ctx.fillStyle = level._internal_color;
|
|
20644
|
+
ctx.fill();
|
|
20645
|
+
ctx.restore();
|
|
20646
|
+
}
|
|
20647
|
+
ctx.save();
|
|
20648
|
+
ctx.scale(hpr, vpr);
|
|
20649
|
+
ctx.textAlign = 'center';
|
|
20650
|
+
ctx.fillStyle = level._internal_badgeFilled ? data._internal_badgeTextColor : level._internal_color;
|
|
20651
|
+
ctx.fillText(level._internal_badge, box._internal_left + box._internal_width / 2, box._internal_top + box._internal_height / 2);
|
|
20652
|
+
ctx.restore();
|
|
20653
|
+
}
|
|
20654
|
+
// eslint-disable-next-line max-params
|
|
20655
|
+
_private__drawSeparator(ctx, color, x, top, height, hpr, vpr) {
|
|
20656
|
+
const width = Math.max(1, Math.floor(hpr));
|
|
20657
|
+
ctx.save();
|
|
20658
|
+
ctx.setLineDash([]);
|
|
20659
|
+
ctx.fillStyle = this._private__colorParser._internal_applyAlpha(color, 0.4 /* Constants.SeparatorAlpha */);
|
|
20660
|
+
ctx.fillRect(Math.round(x * hpr), Math.round(top * vpr), width, Math.round(height * vpr));
|
|
20661
|
+
ctx.restore();
|
|
20662
|
+
}
|
|
20663
|
+
// eslint-disable-next-line max-params
|
|
20664
|
+
_private__drawCloseButton(scope, data, level, box) {
|
|
20665
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20666
|
+
const hovered = data._internal_hoveredLevel === level._internal_level && data._internal_hoveredPart === 'close';
|
|
20667
|
+
if (hovered) {
|
|
20668
|
+
ctx.save();
|
|
20669
|
+
const inset = data._internal_borderWidth;
|
|
20670
|
+
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');
|
|
20671
|
+
ctx.fillStyle = this._private__colorParser._internal_applyAlpha(level._internal_color, 0.25);
|
|
20672
|
+
ctx.fill();
|
|
20673
|
+
ctx.restore();
|
|
20674
|
+
}
|
|
20675
|
+
const centerX = (box._internal_left + box._internal_width / 2) * hpr;
|
|
20676
|
+
const centerY = (box._internal_top + box._internal_height / 2) * vpr;
|
|
20677
|
+
const arm = (7 /* Constants.CloseGlyphSize */ / 2) * hpr;
|
|
20678
|
+
ctx.save();
|
|
20679
|
+
ctx.setLineDash([]);
|
|
20680
|
+
ctx.lineCap = 'round';
|
|
20681
|
+
ctx.lineWidth = Math.max(1, Math.round(1.4 * hpr));
|
|
20682
|
+
ctx.strokeStyle = level._internal_color;
|
|
20683
|
+
ctx.beginPath();
|
|
20684
|
+
ctx.moveTo(centerX - arm, centerY - arm);
|
|
20685
|
+
ctx.lineTo(centerX + arm, centerY + arm);
|
|
20686
|
+
ctx.moveTo(centerX + arm, centerY - arm);
|
|
20687
|
+
ctx.lineTo(centerX - arm, centerY + arm);
|
|
20688
|
+
ctx.stroke();
|
|
20689
|
+
ctx.restore();
|
|
20690
|
+
}
|
|
20691
|
+
_private__drawConnector(scope, data) {
|
|
20692
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20693
|
+
const entry = data._internal_levels.find((level) => level._internal_level === 'entry') ?? data._internal_levels[0];
|
|
20694
|
+
const ys = data._internal_levels.map((level) => level._internal_y);
|
|
20695
|
+
const x = Math.round(this._private__connectorX * hpr);
|
|
20696
|
+
ctx.save();
|
|
20697
|
+
ctx.setLineDash([]);
|
|
20698
|
+
ctx.strokeStyle = entry._internal_color;
|
|
20699
|
+
ctx.lineWidth = Math.max(1, Math.floor(hpr));
|
|
20700
|
+
ctx.beginPath();
|
|
20701
|
+
ctx.moveTo(x, Math.round(Math.min(...ys) * vpr));
|
|
20702
|
+
ctx.lineTo(x, Math.round(Math.max(...ys) * vpr));
|
|
20703
|
+
ctx.stroke();
|
|
20704
|
+
for (const level of data._internal_levels) {
|
|
20705
|
+
ctx.beginPath();
|
|
20706
|
+
ctx.arc(x, Math.round(level._internal_y * vpr), 4.5 /* Constants.HandleRadius */ * hpr, 0, 2 * Math.PI);
|
|
20707
|
+
ctx.fillStyle = level._internal_level === 'entry'
|
|
20708
|
+
? entry._internal_color
|
|
20709
|
+
: this._private__colorParser._internal_applyAlpha(data._internal_background, data._internal_backgroundOpacity);
|
|
20710
|
+
ctx.fill();
|
|
20711
|
+
ctx.lineWidth = Math.max(1, Math.round(1.5 * hpr));
|
|
20712
|
+
ctx.strokeStyle = level._internal_color;
|
|
20713
|
+
ctx.stroke();
|
|
20714
|
+
}
|
|
20715
|
+
ctx.restore();
|
|
20716
|
+
}
|
|
20717
|
+
}
|
|
20718
|
+
// eslint-disable-next-line max-params
|
|
20719
|
+
function bitmapRoundRect(ctx, box, radius, hpr, vpr, side = 'all') {
|
|
20720
|
+
const left = Math.round(box._internal_left * hpr);
|
|
20721
|
+
const top = Math.round(box._internal_top * vpr);
|
|
20722
|
+
const width = Math.round(box._internal_width * hpr);
|
|
20723
|
+
const height = Math.round(box._internal_height * vpr);
|
|
20724
|
+
const r = Math.min(radius * hpr, width / 2, height / 2);
|
|
20725
|
+
const radii = side === 'left'
|
|
20726
|
+
? [r, 0, 0, r]
|
|
20727
|
+
: side === 'right'
|
|
20728
|
+
? [0, r, r, 0]
|
|
20729
|
+
: [r, r, r, r];
|
|
20730
|
+
drawRoundRect(ctx, left, top, width, height, radii);
|
|
20731
|
+
}
|
|
20732
|
+
|
|
20733
|
+
class PositionLinePaneView {
|
|
20734
|
+
constructor() {
|
|
20735
|
+
this._private__renderer = new PositionLineRenderer();
|
|
20736
|
+
this._private__data = null;
|
|
20737
|
+
}
|
|
20738
|
+
_internal_update(data) {
|
|
20739
|
+
this._private__data = data;
|
|
20740
|
+
}
|
|
20741
|
+
renderer() {
|
|
20742
|
+
if (this._private__data === null) {
|
|
20743
|
+
return null;
|
|
20744
|
+
}
|
|
20745
|
+
this._private__renderer._internal_setData(this._private__data);
|
|
20746
|
+
return this._private__renderer;
|
|
20747
|
+
}
|
|
20748
|
+
zOrder() {
|
|
20749
|
+
return 'top';
|
|
20750
|
+
}
|
|
20751
|
+
_internal_hitTest(x, y) {
|
|
20752
|
+
return this._private__data === null ? null : this._private__renderer._internal_hitTest(x, y);
|
|
20753
|
+
}
|
|
20754
|
+
}
|
|
20755
|
+
|
|
20756
|
+
class PositionLineAxisView {
|
|
20757
|
+
constructor(source) {
|
|
20758
|
+
this._private__source = source;
|
|
20759
|
+
}
|
|
20760
|
+
coordinate() {
|
|
20761
|
+
// A hidden label is pushed far off the axis so it cannot reserve a gap
|
|
20762
|
+
// between the labels that are shown.
|
|
20763
|
+
return this._private__label()?._internal_coordinate ?? -1e3;
|
|
20764
|
+
}
|
|
20765
|
+
text() {
|
|
20766
|
+
return this._private__label()?._internal_text ?? '';
|
|
20767
|
+
}
|
|
20768
|
+
textColor() {
|
|
20769
|
+
return this._private__label()?._internal_textColor ?? '#FFFFFF';
|
|
20770
|
+
}
|
|
20771
|
+
backColor() {
|
|
20772
|
+
return this._private__label()?._internal_color ?? '#000000';
|
|
20773
|
+
}
|
|
20774
|
+
visible() {
|
|
20775
|
+
return this._private__label() !== null;
|
|
20776
|
+
}
|
|
20777
|
+
_private__label() {
|
|
20778
|
+
return this._private__source();
|
|
20779
|
+
}
|
|
20780
|
+
}
|
|
20781
|
+
|
|
20782
|
+
let nextPositionId = 0;
|
|
20783
|
+
const DEFAULT_RIGHT_OFFSET = 56;
|
|
20784
|
+
/**
|
|
20785
|
+
* A group of trading levels — entry, take profit and stop loss — drawn as one
|
|
20786
|
+
* unit: a line per level, a label carrying size and P&L, and a connector that
|
|
20787
|
+
* ties the three together.
|
|
20788
|
+
*
|
|
20789
|
+
* Input is not handled here. Every trading line on a chart shares a single
|
|
20790
|
+
* TradingLineController, which is what guarantees that one gesture moves one
|
|
20791
|
+
* level, and that a position line and a plain draggable price line on the same
|
|
20792
|
+
* chart do not fight over the pointer.
|
|
20793
|
+
*/
|
|
20794
|
+
class PositionLine {
|
|
20795
|
+
constructor(chart, series, options) {
|
|
20796
|
+
this._private__paneView = new PositionLinePaneView();
|
|
20797
|
+
this._private__colorParser = new ColorParser([]);
|
|
20798
|
+
this._private__attached = false;
|
|
20799
|
+
this._private__hoveredLevel = null;
|
|
20800
|
+
this._private__hoveredPart = null;
|
|
20801
|
+
this._private__dragLevel = null;
|
|
20802
|
+
this._private__priceBeforeDrag = null;
|
|
20803
|
+
this._private__chart = chart;
|
|
20804
|
+
this._private__series = series;
|
|
20805
|
+
this._private__id = `pipsend-position-line-${nextPositionId++}`;
|
|
20806
|
+
this._private__options = options;
|
|
20807
|
+
this._private__theme = { ...positionLineThemeDefaults, ...options.theme };
|
|
20808
|
+
this._private__paneViews = [this._private__paneView];
|
|
20809
|
+
this._private__axisViews = positionLineLevels.map((level) => new PositionLineAxisView(() => this._private__axisLabel(level)));
|
|
20810
|
+
this._private__series.attachPrimitive(this);
|
|
20811
|
+
acquireController(this._private__chart)?._internal_register(this);
|
|
20812
|
+
}
|
|
20813
|
+
// ---- ISeriesPrimitive ----
|
|
20814
|
+
attached(param) {
|
|
20815
|
+
this._private__requestUpdate = param.requestUpdate;
|
|
20816
|
+
this._private__attached = true;
|
|
20817
|
+
this.requestUpdate();
|
|
20818
|
+
}
|
|
20819
|
+
detached() {
|
|
20820
|
+
this._private__attached = false;
|
|
20821
|
+
this._private__requestUpdate = undefined;
|
|
20822
|
+
}
|
|
20823
|
+
paneViews() {
|
|
20824
|
+
return this._private__paneViews;
|
|
20825
|
+
}
|
|
20826
|
+
priceAxisViews() {
|
|
20827
|
+
return this._private__axisViews;
|
|
20828
|
+
}
|
|
20829
|
+
updateAllViews() {
|
|
20830
|
+
this._private__paneView._internal_update(this._private__buildRendererData());
|
|
20831
|
+
}
|
|
20832
|
+
hitTest(x, y) {
|
|
20833
|
+
const hit = this._private__paneView._internal_hitTest(x, y);
|
|
20834
|
+
if (hit === null) {
|
|
20835
|
+
return null;
|
|
20836
|
+
}
|
|
20837
|
+
const level = this._private__levelOptions(hit._internal_level);
|
|
20838
|
+
if (level === null) {
|
|
20839
|
+
return null;
|
|
20840
|
+
}
|
|
20841
|
+
return {
|
|
20842
|
+
externalId: `${this._private__id}#${hit._internal_level}:${hit._internal_part}`,
|
|
20843
|
+
zOrder: 'top',
|
|
20844
|
+
cursorStyle: hit._internal_part === 'close'
|
|
20845
|
+
? 'pointer'
|
|
20846
|
+
: (level.draggable ?? true) ? 'ns-resize' : undefined,
|
|
20847
|
+
};
|
|
20848
|
+
}
|
|
20849
|
+
// ---- ControllableLine ----
|
|
20850
|
+
id() {
|
|
20851
|
+
return this._private__id;
|
|
20852
|
+
}
|
|
20853
|
+
paneIndex() {
|
|
20854
|
+
try {
|
|
20855
|
+
return this._private__series.getPane().paneIndex();
|
|
20856
|
+
}
|
|
20857
|
+
catch (error) {
|
|
20858
|
+
// The engine asserts the series belongs to a pane, and that lookup can
|
|
20859
|
+
// come back empty while panes are being rebuilt. The main pane is the
|
|
20860
|
+
// right guess and keeps the gesture alive instead of throwing mid-drag.
|
|
20861
|
+
return 0;
|
|
20862
|
+
}
|
|
20863
|
+
}
|
|
20864
|
+
setHoveredPart(part) {
|
|
20865
|
+
const [level, name] = part === null ? [null, null] : part.split(':');
|
|
20866
|
+
const nextLevel = level ?? null;
|
|
20867
|
+
const nextPart = name ?? null;
|
|
20868
|
+
if (this._private__hoveredLevel === nextLevel && this._private__hoveredPart === nextPart) {
|
|
20869
|
+
return;
|
|
20870
|
+
}
|
|
20871
|
+
this._private__hoveredLevel = nextLevel;
|
|
20872
|
+
this._private__hoveredPart = nextPart;
|
|
20873
|
+
this.requestUpdate();
|
|
20874
|
+
}
|
|
20875
|
+
setHovered(hovered) {
|
|
20876
|
+
if (!hovered) {
|
|
20877
|
+
this.setHoveredPart(null);
|
|
20878
|
+
}
|
|
20879
|
+
}
|
|
20880
|
+
isDraggable() {
|
|
20881
|
+
if (this._private__hoveredLevel === null || this._private__hoveredPart === 'close') {
|
|
20882
|
+
return false;
|
|
20883
|
+
}
|
|
20884
|
+
return this._private__levelOptions(this._private__hoveredLevel)?.draggable ?? true;
|
|
20885
|
+
}
|
|
20886
|
+
isClickable() {
|
|
20887
|
+
if (this._private__hoveredPart === 'close') {
|
|
20888
|
+
return true;
|
|
20889
|
+
}
|
|
20890
|
+
return this._private__options.onClick !== undefined;
|
|
20891
|
+
}
|
|
20892
|
+
priceAt(paneY) {
|
|
20893
|
+
const price = this._private__series.coordinateToPrice(paneY);
|
|
20894
|
+
return price === null ? null : price;
|
|
20895
|
+
}
|
|
20896
|
+
beginDrag() {
|
|
20897
|
+
this._private__dragLevel = this._private__hoveredLevel;
|
|
20898
|
+
if (this._private__dragLevel === null) {
|
|
20899
|
+
return;
|
|
20900
|
+
}
|
|
20901
|
+
const price = this._private__levelOptions(this._private__dragLevel)?.price ?? null;
|
|
20902
|
+
this._private__priceBeforeDrag = price;
|
|
20903
|
+
if (price !== null && this._private__options.onDragStart) {
|
|
20904
|
+
this._private__options.onDragStart(this._private__dragLevel, price);
|
|
20905
|
+
}
|
|
20906
|
+
}
|
|
20907
|
+
dragTo(price) {
|
|
20908
|
+
if (this._private__dragLevel === null) {
|
|
20909
|
+
return;
|
|
20910
|
+
}
|
|
20911
|
+
this._private__applyPrice(this._private__dragLevel, price);
|
|
20912
|
+
if (this._private__options.onDragMove) {
|
|
20913
|
+
this._private__options.onDragMove(this._private__dragLevel, price);
|
|
20914
|
+
}
|
|
20915
|
+
}
|
|
20916
|
+
commitDrag() {
|
|
20917
|
+
const level = this._private__dragLevel;
|
|
20918
|
+
this._private__dragLevel = null;
|
|
20919
|
+
this._private__priceBeforeDrag = null;
|
|
20920
|
+
if (level === null) {
|
|
20921
|
+
return;
|
|
20922
|
+
}
|
|
20923
|
+
const price = this._private__levelOptions(level)?.price;
|
|
20924
|
+
if (price !== undefined && this._private__options.onDragEnd) {
|
|
20925
|
+
this._private__options.onDragEnd(level, price);
|
|
20926
|
+
}
|
|
20927
|
+
}
|
|
20928
|
+
cancelDrag() {
|
|
20929
|
+
const level = this._private__dragLevel;
|
|
20930
|
+
const original = this._private__priceBeforeDrag;
|
|
20931
|
+
this._private__dragLevel = null;
|
|
20932
|
+
this._private__priceBeforeDrag = null;
|
|
20933
|
+
if (level === null) {
|
|
20934
|
+
return;
|
|
20935
|
+
}
|
|
20936
|
+
// Reported even when the price did not move, so a caller that pauses work
|
|
20937
|
+
// on `onDragStart` always gets the matching end of the gesture.
|
|
20938
|
+
const restored = original !== null && original !== this._private__levelOptions(level)?.price;
|
|
20939
|
+
if (restored) {
|
|
20940
|
+
this._private__applyPrice(level, original);
|
|
20941
|
+
if (this._private__options.onDragMove) {
|
|
20942
|
+
this._private__options.onDragMove(level, original);
|
|
20943
|
+
}
|
|
20944
|
+
}
|
|
20945
|
+
if (this._private__options.onDragCancel) {
|
|
20946
|
+
this._private__options.onDragCancel(level, this._private__levelOptions(level)?.price ?? 0);
|
|
20947
|
+
}
|
|
20948
|
+
}
|
|
20949
|
+
click() {
|
|
20950
|
+
const level = this._private__hoveredLevel;
|
|
20951
|
+
if (level === null) {
|
|
20952
|
+
return;
|
|
20953
|
+
}
|
|
20954
|
+
if (this._private__hoveredPart === 'close') {
|
|
20955
|
+
if (this._private__options.onClose) {
|
|
20956
|
+
this._private__options.onClose(level);
|
|
20957
|
+
}
|
|
20958
|
+
return;
|
|
20959
|
+
}
|
|
20960
|
+
const price = this._private__levelOptions(level)?.price;
|
|
20961
|
+
if (price !== undefined && this._private__options.onClick) {
|
|
20962
|
+
this._private__options.onClick(level, price);
|
|
20963
|
+
}
|
|
20964
|
+
}
|
|
20965
|
+
// ---- public API ----
|
|
20966
|
+
/**
|
|
20967
|
+
* Updates the group. Any subset of options can be passed; the levels given
|
|
20968
|
+
* are merged into the current ones, and a level set to `undefined` is
|
|
20969
|
+
* removed.
|
|
20970
|
+
*/
|
|
20971
|
+
applyOptions(options) {
|
|
20972
|
+
// A level under an active gesture owns its price. Callers feeding the
|
|
20973
|
+
// group from a socket re-send the old price several times a second, and
|
|
20974
|
+
// letting that through would pull the level out from under the cursor.
|
|
20975
|
+
const dragged = this._private__dragLevel;
|
|
20976
|
+
const draggedPrice = dragged === null ? undefined : this._private__levelOptions(dragged)?.price;
|
|
20977
|
+
const merged = { ...this._private__options, ...options };
|
|
20978
|
+
merged.entry = { ...this._private__options.entry, ...options.entry };
|
|
20979
|
+
merged.takeProfit = mergeLevel(this._private__options.takeProfit, options, 'takeProfit');
|
|
20980
|
+
merged.stopLoss = mergeLevel(this._private__options.stopLoss, options, 'stopLoss');
|
|
20981
|
+
this._private__options = merged;
|
|
20982
|
+
if (dragged !== null && draggedPrice !== undefined) {
|
|
20983
|
+
const level = this._private__levelOptions(dragged);
|
|
20984
|
+
if (level !== null) {
|
|
20985
|
+
level.price = draggedPrice;
|
|
20986
|
+
}
|
|
20987
|
+
}
|
|
20988
|
+
if (options.theme) {
|
|
20989
|
+
// Merged onto what is already applied, so two partial theme updates do
|
|
20990
|
+
// not undo each other.
|
|
20991
|
+
this._private__theme = { ...this._private__theme, ...options.theme };
|
|
20992
|
+
}
|
|
20993
|
+
this.requestUpdate();
|
|
20994
|
+
}
|
|
20995
|
+
/**
|
|
20996
|
+
* Moves one level to a new price, recomputing its text when the level has a
|
|
20997
|
+
* `formatText`.
|
|
20998
|
+
*
|
|
20999
|
+
* Ignored for a level the user is currently dragging — the gesture owns the
|
|
21000
|
+
* price until it reports back through
|
|
21001
|
+
* {@link PositionLineOptions.onDragEnd} or
|
|
21002
|
+
* {@link PositionLineOptions.onDragCancel}.
|
|
21003
|
+
*/
|
|
21004
|
+
setPrice(level, price) {
|
|
21005
|
+
if (this._private__dragLevel === level) {
|
|
21006
|
+
return;
|
|
21007
|
+
}
|
|
21008
|
+
this._private__applyPrice(level, price);
|
|
21009
|
+
}
|
|
21010
|
+
/**
|
|
21011
|
+
* Replaces one level's label text. Use this for text that changes on its own
|
|
21012
|
+
* — a ticking P&L on a fixed entry — rather than with the price.
|
|
21013
|
+
*/
|
|
21014
|
+
setText(level, text) {
|
|
21015
|
+
const options = this._private__levelOptions(level);
|
|
21016
|
+
if (options === null) {
|
|
21017
|
+
return;
|
|
21018
|
+
}
|
|
21019
|
+
options.text = text;
|
|
21020
|
+
this.requestUpdate();
|
|
21021
|
+
}
|
|
21022
|
+
/**
|
|
21023
|
+
* The current price of each level that is present.
|
|
21024
|
+
*/
|
|
21025
|
+
prices() {
|
|
21026
|
+
const result = {};
|
|
21027
|
+
for (const level of positionLineLevels) {
|
|
21028
|
+
const options = this._private__levelOptions(level);
|
|
21029
|
+
if (options !== null) {
|
|
21030
|
+
result[level] = options.price;
|
|
21031
|
+
}
|
|
21032
|
+
}
|
|
21033
|
+
return result;
|
|
21034
|
+
}
|
|
21035
|
+
options() {
|
|
21036
|
+
return this._private__options;
|
|
21037
|
+
}
|
|
21038
|
+
/**
|
|
21039
|
+
* Removes the group from the chart and releases its share of the input
|
|
21040
|
+
* controller.
|
|
21041
|
+
*/
|
|
21042
|
+
remove() {
|
|
21043
|
+
releaseController(this._private__chart, this);
|
|
21044
|
+
this._private__series.detachPrimitive(this);
|
|
21045
|
+
}
|
|
21046
|
+
requestUpdate() {
|
|
21047
|
+
if (this._private__requestUpdate) {
|
|
21048
|
+
this._private__requestUpdate();
|
|
21049
|
+
}
|
|
21050
|
+
}
|
|
21051
|
+
/** Reads back the label the price axis should draw for a level. */
|
|
21052
|
+
_private__axisLabel(level) {
|
|
21053
|
+
const options = this._private__levelOptions(level);
|
|
21054
|
+
if (options === null || !isFinite(options.price) ||
|
|
21055
|
+
this._private__options.visible === false || options.axisLabelVisible === false) {
|
|
21056
|
+
return null;
|
|
21057
|
+
}
|
|
21058
|
+
const y = this._private__series.priceToCoordinate(options.price);
|
|
21059
|
+
if (y === null) {
|
|
21060
|
+
return null;
|
|
21061
|
+
}
|
|
21062
|
+
const color = options.color ?? positionLineDefaultColors[level];
|
|
21063
|
+
return {
|
|
21064
|
+
_internal_coordinate: y,
|
|
21065
|
+
_internal_text: this._private__series.priceFormatter().format(options.price),
|
|
21066
|
+
_internal_color: color,
|
|
21067
|
+
_internal_textColor: this._private__colorParser._internal_generateContrastColors(color)._internal_foreground,
|
|
21068
|
+
};
|
|
21069
|
+
}
|
|
21070
|
+
_private__levelOptions(level) {
|
|
21071
|
+
const options = level === 'entry' ? this._private__options.entry : this._private__options[level];
|
|
21072
|
+
return options ?? null;
|
|
21073
|
+
}
|
|
21074
|
+
_private__applyPrice(level, price) {
|
|
21075
|
+
const options = this._private__levelOptions(level);
|
|
21076
|
+
if (options === null) {
|
|
21077
|
+
return;
|
|
21078
|
+
}
|
|
21079
|
+
options.price = price;
|
|
21080
|
+
if (options.formatText) {
|
|
21081
|
+
options.text = options.formatText(price);
|
|
21082
|
+
}
|
|
21083
|
+
this.requestUpdate();
|
|
21084
|
+
}
|
|
21085
|
+
_private__buildRendererData() {
|
|
21086
|
+
if (!this._private__attached || this._private__options.visible === false) {
|
|
21087
|
+
return null;
|
|
21088
|
+
}
|
|
21089
|
+
const levels = [];
|
|
21090
|
+
for (const level of positionLineLevels) {
|
|
21091
|
+
const options = this._private__levelOptions(level);
|
|
21092
|
+
// A level added through a patch can be missing its price until the
|
|
21093
|
+
// caller supplies one; drawing it would put it at NaN.
|
|
21094
|
+
if (options === null || !isFinite(options.price)) {
|
|
21095
|
+
continue;
|
|
21096
|
+
}
|
|
21097
|
+
const y = this._private__series.priceToCoordinate(options.price);
|
|
21098
|
+
if (y === null) {
|
|
21099
|
+
continue;
|
|
21100
|
+
}
|
|
21101
|
+
const isEntry = level === 'entry';
|
|
21102
|
+
levels.push({
|
|
21103
|
+
_internal_level: level,
|
|
21104
|
+
_internal_y: y,
|
|
21105
|
+
_internal_color: options.color ?? positionLineDefaultColors[level],
|
|
21106
|
+
_internal_text: options.formatText ? options.formatText(options.price) : options.text ?? '',
|
|
21107
|
+
_internal_badge: options.badge ?? '',
|
|
21108
|
+
_internal_badgeFilled: options.badgeFilled ?? isEntry,
|
|
21109
|
+
_internal_lineWidth: options.lineWidth ?? 1,
|
|
21110
|
+
_internal_lineStyle: options.lineStyle ?? (isEntry ? 0 /* LineStyle.Solid */ : 2 /* LineStyle.Dashed */),
|
|
21111
|
+
_internal_closable: options.closable ?? true,
|
|
21112
|
+
_internal_draggable: options.draggable ?? true,
|
|
21113
|
+
});
|
|
21114
|
+
}
|
|
21115
|
+
if (levels.length === 0) {
|
|
21116
|
+
return null;
|
|
21117
|
+
}
|
|
21118
|
+
const layout = this._private__chart.options()['layout'];
|
|
21119
|
+
const fontSize = this._private__theme.fontSize ?? layout.fontSize;
|
|
21120
|
+
return {
|
|
21121
|
+
_internal_levels: levels,
|
|
21122
|
+
_internal_background: this._private__theme.background ?? resolveBackgroundColor(layout.background),
|
|
21123
|
+
_internal_textColor: this._private__theme.textColor,
|
|
21124
|
+
_internal_badgeTextColor: this._private__theme.badgeTextColor,
|
|
21125
|
+
_internal_borderRadius: this._private__theme.borderRadius,
|
|
21126
|
+
_internal_borderWidth: this._private__theme.borderWidth,
|
|
21127
|
+
_internal_paddingX: this._private__theme.paddingX,
|
|
21128
|
+
_internal_paddingY: this._private__theme.paddingY,
|
|
21129
|
+
_internal_backgroundOpacity: this._private__theme.backgroundOpacity,
|
|
21130
|
+
_internal_font: makeFont(fontSize, this._private__theme.fontFamily ?? layout.fontFamily),
|
|
21131
|
+
_internal_fontSize: fontSize,
|
|
21132
|
+
_internal_connectorVisible: this._private__options.connectorVisible !== false && levels.length > 1,
|
|
21133
|
+
_internal_rightOffset: this._private__options.rightOffset ?? DEFAULT_RIGHT_OFFSET,
|
|
21134
|
+
_internal_hoveredLevel: this._private__hoveredLevel,
|
|
21135
|
+
_internal_hoveredPart: this._private__hoveredPart,
|
|
21136
|
+
};
|
|
21137
|
+
}
|
|
21138
|
+
}
|
|
21139
|
+
function mergeLevel(current, options, key) {
|
|
21140
|
+
if (!(key in options)) {
|
|
21141
|
+
return current;
|
|
21142
|
+
}
|
|
21143
|
+
const next = options[key];
|
|
21144
|
+
return next === undefined ? undefined : { ...current, ...next };
|
|
21145
|
+
}
|
|
21146
|
+
/**
|
|
21147
|
+
* Reads the chart's own background so the label sits on a solid surface. The
|
|
21148
|
+
* parameter is typed as the public {@link Background}: the production build
|
|
21149
|
+
* renames the members of structural types it does not see in the public API,
|
|
21150
|
+
* and an anonymous `{ color: string }` here resolved to nothing at runtime.
|
|
21151
|
+
*/
|
|
21152
|
+
function resolveBackgroundColor(background) {
|
|
21153
|
+
return background.type === "solid" /* ColorType.Solid */ ? background.color : background.topColor;
|
|
21154
|
+
}
|
|
21155
|
+
/**
|
|
21156
|
+
* Creates a position line group: entry, take profit and stop loss drawn as one
|
|
21157
|
+
* interactive unit.
|
|
21158
|
+
*
|
|
21159
|
+
* @param series - The series the levels are priced against.
|
|
21160
|
+
* @param chart - The chart the series belongs to.
|
|
21161
|
+
* @param options - The levels and their style.
|
|
21162
|
+
*
|
|
21163
|
+
* @example
|
|
21164
|
+
* ```js
|
|
21165
|
+
* const position = createPositionLine(candleSeries, chart, {
|
|
21166
|
+
* entry: { price: 4306.79, badge: '1', formatText: () => '+ 2.32 USD' },
|
|
21167
|
+
* takeProfit: { price: 4349.58, badge: '1', text: '+ 42.79 USD' },
|
|
21168
|
+
* stopLoss: { price: 4266.06, badge: '1', text: '- 40.73 USD' },
|
|
21169
|
+
* onClose: (level) => console.log('close', level),
|
|
21170
|
+
* onDragEnd: (level, price) => console.log(level, 'moved to', price),
|
|
21171
|
+
* });
|
|
21172
|
+
* ```
|
|
21173
|
+
*/
|
|
21174
|
+
function createPositionLine(series, chart, options) {
|
|
21175
|
+
return new PositionLine(chart, series, options);
|
|
21176
|
+
}
|
|
21177
|
+
|
|
20254
21178
|
/** Hit parts that drag the whole object rather than a single handle. */
|
|
20255
21179
|
const WHOLE_OBJECT_PARTS = new Set([
|
|
20256
21180
|
'line',
|
|
@@ -20408,6 +21332,9 @@
|
|
|
20408
21332
|
get type() {
|
|
20409
21333
|
return this._type;
|
|
20410
21334
|
}
|
|
21335
|
+
isComplete() {
|
|
21336
|
+
return true;
|
|
21337
|
+
}
|
|
20411
21338
|
/**
|
|
20412
21339
|
* Lets a tool hand the base its own options object, once, in the constructor.
|
|
20413
21340
|
*
|
|
@@ -20735,6 +21662,7 @@
|
|
|
20735
21662
|
this._private__dragStartPoint2 = null;
|
|
20736
21663
|
// Interactive mode
|
|
20737
21664
|
this._private__isComplete = false;
|
|
21665
|
+
this._private__interactive = null;
|
|
20738
21666
|
this._private__previewPoint = null;
|
|
20739
21667
|
this._private__trendOptions = {
|
|
20740
21668
|
lineColor: '#2962ff',
|
|
@@ -20782,7 +21710,7 @@
|
|
|
20782
21710
|
this._private__isComplete = true;
|
|
20783
21711
|
}
|
|
20784
21712
|
});
|
|
20785
|
-
|
|
21713
|
+
this._private__interactive = manager;
|
|
20786
21714
|
manager._internal_startDrawing();
|
|
20787
21715
|
}
|
|
20788
21716
|
_private__createPrimitive() {
|
|
@@ -21112,20 +22040,20 @@
|
|
|
21112
22040
|
_update() {
|
|
21113
22041
|
this._private__requestUpdate();
|
|
21114
22042
|
}
|
|
22043
|
+
isComplete() {
|
|
22044
|
+
return this._private__isComplete;
|
|
22045
|
+
}
|
|
21115
22046
|
remove() {
|
|
22047
|
+
if (this._private__interactive) {
|
|
22048
|
+
this._private__interactive._internal_destroy();
|
|
22049
|
+
this._private__interactive = null;
|
|
22050
|
+
}
|
|
21116
22051
|
if (this._private__primitive) {
|
|
21117
22052
|
// Detach from series
|
|
21118
22053
|
this._series.detachPrimitive(this._private__primitive);
|
|
21119
22054
|
// Clean up references
|
|
21120
22055
|
this._private__primitive = null;
|
|
21121
22056
|
this._private__paneView = null;
|
|
21122
|
-
// Force chart update by triggering a resize
|
|
21123
|
-
const chartElement = this._chart.chartElement?.();
|
|
21124
|
-
if (chartElement) {
|
|
21125
|
-
const currentWidth = chartElement.clientWidth;
|
|
21126
|
-
const currentHeight = chartElement.clientHeight;
|
|
21127
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
21128
|
-
}
|
|
21129
22057
|
}
|
|
21130
22058
|
if (this._private__trendOptions.onRemove) {
|
|
21131
22059
|
this._private__trendOptions.onRemove();
|
|
@@ -21398,12 +22326,6 @@
|
|
|
21398
22326
|
this._series.detachPrimitive(this._private__primitive);
|
|
21399
22327
|
this._private__primitive = null;
|
|
21400
22328
|
this._private__paneView = null;
|
|
21401
|
-
const chartElement = this._chart.chartElement?.();
|
|
21402
|
-
if (chartElement) {
|
|
21403
|
-
const currentWidth = chartElement.clientWidth;
|
|
21404
|
-
const currentHeight = chartElement.clientHeight;
|
|
21405
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
21406
|
-
}
|
|
21407
22329
|
}
|
|
21408
22330
|
if (this._private__hLineOptions.onRemove) {
|
|
21409
22331
|
this._private__hLineOptions.onRemove();
|
|
@@ -22019,6 +22941,9 @@
|
|
|
22019
22941
|
points() {
|
|
22020
22942
|
return this._private__points;
|
|
22021
22943
|
}
|
|
22944
|
+
isComplete() {
|
|
22945
|
+
return this._private__isComplete;
|
|
22946
|
+
}
|
|
22022
22947
|
remove() {
|
|
22023
22948
|
this._private__stopDrawing();
|
|
22024
22949
|
if (this._private__primitive) {
|
|
@@ -22489,6 +23414,9 @@
|
|
|
22489
23414
|
isHovered() {
|
|
22490
23415
|
return this._private__isHovered;
|
|
22491
23416
|
}
|
|
23417
|
+
isComplete() {
|
|
23418
|
+
return this._private__isComplete;
|
|
23419
|
+
}
|
|
22492
23420
|
remove() {
|
|
22493
23421
|
this._private__stopDrawing();
|
|
22494
23422
|
if (this._private__primitive) {
|
|
@@ -22953,6 +23881,9 @@
|
|
|
22953
23881
|
isHovered() {
|
|
22954
23882
|
return this._private__isHovered;
|
|
22955
23883
|
}
|
|
23884
|
+
isComplete() {
|
|
23885
|
+
return this._private__isComplete;
|
|
23886
|
+
}
|
|
22956
23887
|
remove() {
|
|
22957
23888
|
this._private__stopDrawing();
|
|
22958
23889
|
if (this._private__primitive) {
|
|
@@ -23233,12 +24164,6 @@
|
|
|
23233
24164
|
this._series.detachPrimitive(this._private__primitive);
|
|
23234
24165
|
this._private__primitive = null;
|
|
23235
24166
|
this._private__paneView = null;
|
|
23236
|
-
const chartElement = this._chart.chartElement?.();
|
|
23237
|
-
if (chartElement) {
|
|
23238
|
-
const currentWidth = chartElement.clientWidth;
|
|
23239
|
-
const currentHeight = chartElement.clientHeight;
|
|
23240
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
23241
|
-
}
|
|
23242
24167
|
}
|
|
23243
24168
|
if (this._private__vLineOptions.onRemove) {
|
|
23244
24169
|
this._private__vLineOptions.onRemove();
|
|
@@ -23883,17 +24808,14 @@
|
|
|
23883
24808
|
}
|
|
23884
24809
|
return false;
|
|
23885
24810
|
}
|
|
24811
|
+
isComplete() {
|
|
24812
|
+
return this._private__isComplete;
|
|
24813
|
+
}
|
|
23886
24814
|
remove() {
|
|
23887
24815
|
if (this._private__primitive) {
|
|
23888
24816
|
this._series.detachPrimitive(this._private__primitive);
|
|
23889
24817
|
this._private__primitive = null;
|
|
23890
24818
|
this._private__paneView = null;
|
|
23891
|
-
const chartElement = this._chart.chartElement?.();
|
|
23892
|
-
if (chartElement) {
|
|
23893
|
-
const currentWidth = chartElement.clientWidth;
|
|
23894
|
-
const currentHeight = chartElement.clientHeight;
|
|
23895
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
23896
|
-
}
|
|
23897
24819
|
}
|
|
23898
24820
|
if (this._private__drawingManager) {
|
|
23899
24821
|
this._private__drawingManager._internal_destroy();
|
|
@@ -23909,12 +24831,6 @@
|
|
|
23909
24831
|
this._series.detachPrimitive(this._private__primitive);
|
|
23910
24832
|
this._private__primitive = null;
|
|
23911
24833
|
this._private__paneView = null;
|
|
23912
|
-
const chartElement = this._chart.chartElement?.();
|
|
23913
|
-
if (chartElement) {
|
|
23914
|
-
const currentWidth = chartElement.clientWidth;
|
|
23915
|
-
const currentHeight = chartElement.clientHeight;
|
|
23916
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
23917
|
-
}
|
|
23918
24834
|
}
|
|
23919
24835
|
if (this._private__fibOptions.onRemove) {
|
|
23920
24836
|
this._private__fibOptions.onRemove();
|
|
@@ -24342,12 +25258,6 @@
|
|
|
24342
25258
|
this._series.detachPrimitive(this._private__primitive);
|
|
24343
25259
|
this._private__primitive = null;
|
|
24344
25260
|
this._private__paneView = null;
|
|
24345
|
-
const chartElement = this._chart.chartElement?.();
|
|
24346
|
-
if (chartElement) {
|
|
24347
|
-
const currentWidth = chartElement.clientWidth;
|
|
24348
|
-
const currentHeight = chartElement.clientHeight;
|
|
24349
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
24350
|
-
}
|
|
24351
25261
|
}
|
|
24352
25262
|
}
|
|
24353
25263
|
}
|
|
@@ -24773,6 +25683,9 @@
|
|
|
24773
25683
|
_update() {
|
|
24774
25684
|
this._private__requestUpdate();
|
|
24775
25685
|
}
|
|
25686
|
+
isComplete() {
|
|
25687
|
+
return !this._private__isDrawing;
|
|
25688
|
+
}
|
|
24776
25689
|
remove() {
|
|
24777
25690
|
if (this._private__clickHandler) {
|
|
24778
25691
|
this._chart.unsubscribeClick(this._private__clickHandler);
|
|
@@ -24786,12 +25699,6 @@
|
|
|
24786
25699
|
this._series.detachPrimitive(this._private__primitive);
|
|
24787
25700
|
this._private__primitive = null;
|
|
24788
25701
|
this._private__paneView = null;
|
|
24789
|
-
const chartElement = this._chart.chartElement?.();
|
|
24790
|
-
if (chartElement) {
|
|
24791
|
-
const currentWidth = chartElement.clientWidth;
|
|
24792
|
-
const currentHeight = chartElement.clientHeight;
|
|
24793
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
24794
|
-
}
|
|
24795
25702
|
}
|
|
24796
25703
|
if (this._private__fanOptions.onRemove) {
|
|
24797
25704
|
this._private__fanOptions.onRemove();
|
|
@@ -25316,6 +26223,9 @@
|
|
|
25316
26223
|
_update() {
|
|
25317
26224
|
this._private__requestUpdate();
|
|
25318
26225
|
}
|
|
26226
|
+
isComplete() {
|
|
26227
|
+
return !this._private__isDrawing;
|
|
26228
|
+
}
|
|
25319
26229
|
remove() {
|
|
25320
26230
|
if (this._private__clickHandler) {
|
|
25321
26231
|
this._chart.unsubscribeClick(this._private__clickHandler);
|
|
@@ -25329,12 +26239,6 @@
|
|
|
25329
26239
|
this._series.detachPrimitive(this._private__primitive);
|
|
25330
26240
|
this._private__primitive = null;
|
|
25331
26241
|
this._private__paneView = null;
|
|
25332
|
-
const chartElement = this._chart.chartElement?.();
|
|
25333
|
-
if (chartElement) {
|
|
25334
|
-
const currentWidth = chartElement.clientWidth;
|
|
25335
|
-
const currentHeight = chartElement.clientHeight;
|
|
25336
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
25337
|
-
}
|
|
25338
26242
|
}
|
|
25339
26243
|
if (this._private__rectOptions.onRemove) {
|
|
25340
26244
|
this._private__rectOptions.onRemove();
|
|
@@ -25779,6 +26683,9 @@
|
|
|
25779
26683
|
_update() {
|
|
25780
26684
|
this._private__requestUpdate();
|
|
25781
26685
|
}
|
|
26686
|
+
isComplete() {
|
|
26687
|
+
return !this._private__isDrawing;
|
|
26688
|
+
}
|
|
25782
26689
|
remove() {
|
|
25783
26690
|
if (this._private__clickHandler) {
|
|
25784
26691
|
this._chart.unsubscribeClick(this._private__clickHandler);
|
|
@@ -25792,12 +26699,6 @@
|
|
|
25792
26699
|
this._series.detachPrimitive(this._private__primitive);
|
|
25793
26700
|
this._private__primitive = null;
|
|
25794
26701
|
this._private__paneView = null;
|
|
25795
|
-
const chartElement = this._chart.chartElement?.();
|
|
25796
|
-
if (chartElement) {
|
|
25797
|
-
const currentWidth = chartElement.clientWidth;
|
|
25798
|
-
const currentHeight = chartElement.clientHeight;
|
|
25799
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
25800
|
-
}
|
|
25801
26702
|
}
|
|
25802
26703
|
if (this._private__circleOptions.onRemove) {
|
|
25803
26704
|
this._private__circleOptions.onRemove();
|
|
@@ -26321,6 +27222,9 @@
|
|
|
26321
27222
|
_update() {
|
|
26322
27223
|
this._private__requestUpdate();
|
|
26323
27224
|
}
|
|
27225
|
+
isComplete() {
|
|
27226
|
+
return !this._private__isDrawing;
|
|
27227
|
+
}
|
|
26324
27228
|
remove() {
|
|
26325
27229
|
// Clean up click handler
|
|
26326
27230
|
if (this._private__clickHandler) {
|
|
@@ -26344,12 +27248,6 @@
|
|
|
26344
27248
|
this._series.detachPrimitive(this._private__primitive);
|
|
26345
27249
|
this._private__primitive = null;
|
|
26346
27250
|
this._private__paneView = null;
|
|
26347
|
-
const chartElement = this._chart.chartElement?.();
|
|
26348
|
-
if (chartElement) {
|
|
26349
|
-
const currentWidth = chartElement.clientWidth;
|
|
26350
|
-
const currentHeight = chartElement.clientHeight;
|
|
26351
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
26352
|
-
}
|
|
26353
27251
|
}
|
|
26354
27252
|
if (this._private__arrowOptions.onRemove) {
|
|
26355
27253
|
this._private__arrowOptions.onRemove();
|
|
@@ -26887,7 +27785,7 @@
|
|
|
26887
27785
|
}
|
|
26888
27786
|
_private__setupClickOutsideHandler() {
|
|
26889
27787
|
// Set up click handler to finish editing when clicking outside
|
|
26890
|
-
|
|
27788
|
+
const handler = (param) => {
|
|
26891
27789
|
if (!this._private__isEditing)
|
|
26892
27790
|
return;
|
|
26893
27791
|
// Get click coordinates
|
|
@@ -26903,7 +27801,9 @@
|
|
|
26903
27801
|
this.remove();
|
|
26904
27802
|
}
|
|
26905
27803
|
}
|
|
26906
|
-
}
|
|
27804
|
+
};
|
|
27805
|
+
this._private__clickOutsideHandler = handler;
|
|
27806
|
+
this._chart.subscribeClick(handler);
|
|
26907
27807
|
}
|
|
26908
27808
|
_private__createPrimitive() {
|
|
26909
27809
|
this._private__paneView = this._private__createPaneView();
|
|
@@ -27208,12 +28108,6 @@
|
|
|
27208
28108
|
this._series.detachPrimitive(this._private__primitive);
|
|
27209
28109
|
this._private__primitive = null;
|
|
27210
28110
|
this._private__paneView = null;
|
|
27211
|
-
const chartElement = this._chart.chartElement?.();
|
|
27212
|
-
if (chartElement) {
|
|
27213
|
-
const currentWidth = chartElement.clientWidth;
|
|
27214
|
-
const currentHeight = chartElement.clientHeight;
|
|
27215
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
27216
|
-
}
|
|
27217
28111
|
}
|
|
27218
28112
|
if (this._private__textOptions.onRemove) {
|
|
27219
28113
|
this._private__textOptions.onRemove();
|
|
@@ -27796,12 +28690,6 @@
|
|
|
27796
28690
|
this._series.detachPrimitive(this._private__primitive);
|
|
27797
28691
|
this._private__primitive = null;
|
|
27798
28692
|
this._private__paneView = null;
|
|
27799
|
-
const chartElement = this._chart.chartElement?.();
|
|
27800
|
-
if (chartElement) {
|
|
27801
|
-
const currentWidth = chartElement.clientWidth;
|
|
27802
|
-
const currentHeight = chartElement.clientHeight;
|
|
27803
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
27804
|
-
}
|
|
27805
28693
|
}
|
|
27806
28694
|
if (this._private__drawingManager) {
|
|
27807
28695
|
this._private__drawingManager._internal_destroy();
|
|
@@ -28657,12 +29545,6 @@
|
|
|
28657
29545
|
this._series.detachPrimitive(this._private__primitive);
|
|
28658
29546
|
this._private__primitive = null;
|
|
28659
29547
|
this._private__paneView = null;
|
|
28660
|
-
const chartElement = this._chart.chartElement?.();
|
|
28661
|
-
if (chartElement) {
|
|
28662
|
-
const currentWidth = chartElement.clientWidth;
|
|
28663
|
-
const currentHeight = chartElement.clientHeight;
|
|
28664
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
28665
|
-
}
|
|
28666
29548
|
}
|
|
28667
29549
|
if (this._private__positionOptions.onRemove) {
|
|
28668
29550
|
this._private__positionOptions.onRemove();
|
|
@@ -29005,12 +29887,6 @@
|
|
|
29005
29887
|
this._series.detachPrimitive(this._private__primitive);
|
|
29006
29888
|
this._private__primitive = null;
|
|
29007
29889
|
this._private__paneView = null;
|
|
29008
|
-
const chartElement = this._chart.chartElement?.();
|
|
29009
|
-
if (chartElement) {
|
|
29010
|
-
const currentWidth = chartElement.clientWidth;
|
|
29011
|
-
const currentHeight = chartElement.clientHeight;
|
|
29012
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
29013
|
-
}
|
|
29014
29890
|
}
|
|
29015
29891
|
if (this._private__ganttOptions.onRemove) {
|
|
29016
29892
|
this._private__ganttOptions.onRemove();
|
|
@@ -29021,6 +29897,8 @@
|
|
|
29021
29897
|
return new GanttTool(context, options);
|
|
29022
29898
|
}
|
|
29023
29899
|
|
|
29900
|
+
/** Undo steps kept, each a full snapshot of every drawing. */
|
|
29901
|
+
const UNDO_HISTORY_LIMIT = 100;
|
|
29024
29902
|
/**
|
|
29025
29903
|
* Central manager for all drawing tools - TradingView style
|
|
29026
29904
|
* Handles multiple tools, selection, deletion, and keyboard shortcuts
|
|
@@ -29043,6 +29921,8 @@
|
|
|
29043
29921
|
this._private__pointerDownHandler = null;
|
|
29044
29922
|
this._private__pointerUpHandler = null;
|
|
29045
29923
|
this._private__pointerCancelHandler = null;
|
|
29924
|
+
this._private__lostCaptureHandler = null;
|
|
29925
|
+
this._private__windowPointerUpHandler = null;
|
|
29046
29926
|
this._private__pointerId = null;
|
|
29047
29927
|
/** A press that grabbed a tool already selected it; its click must not undo that. */
|
|
29048
29928
|
this._private__gestureHandled = false;
|
|
@@ -29052,6 +29932,9 @@
|
|
|
29052
29932
|
this._private__selectionChanged = new Delegate();
|
|
29053
29933
|
this._private__isDragging = false;
|
|
29054
29934
|
this._private__draggedToolId = null;
|
|
29935
|
+
this._private__pendingToolIds = new Set();
|
|
29936
|
+
this._private__completionCheck = null;
|
|
29937
|
+
this._private__drawingCompleted = new Delegate();
|
|
29055
29938
|
this._private__chart = chart;
|
|
29056
29939
|
this._private__series = series;
|
|
29057
29940
|
this._private__options = {
|
|
@@ -29090,9 +29973,63 @@
|
|
|
29090
29973
|
addTool(tool, id) {
|
|
29091
29974
|
const toolId = id ?? tool.id;
|
|
29092
29975
|
this._private__tools.set(toolId, tool);
|
|
29976
|
+
if (!tool.isComplete()) {
|
|
29977
|
+
this._private__pendingToolIds.add(toolId);
|
|
29978
|
+
}
|
|
29093
29979
|
this.recordChange();
|
|
29094
29980
|
return toolId;
|
|
29095
29981
|
}
|
|
29982
|
+
hasPendingDrawing() {
|
|
29983
|
+
return this._private__pendingToolIds.size > 0;
|
|
29984
|
+
}
|
|
29985
|
+
/** Removes every tool still waiting for points; returns how many were dropped. */
|
|
29986
|
+
cancelPendingDrawings() {
|
|
29987
|
+
if (this._private__pendingToolIds.size === 0) {
|
|
29988
|
+
return 0;
|
|
29989
|
+
}
|
|
29990
|
+
let cancelled = 0;
|
|
29991
|
+
for (const id of Array.from(this._private__pendingToolIds)) {
|
|
29992
|
+
const tool = this._private__tools.get(id);
|
|
29993
|
+
if (tool) {
|
|
29994
|
+
if (this._private__selectedToolId === id) {
|
|
29995
|
+
this._private__applySelection(null);
|
|
29996
|
+
}
|
|
29997
|
+
tool.remove();
|
|
29998
|
+
this._private__tools.delete(id);
|
|
29999
|
+
cancelled++;
|
|
30000
|
+
}
|
|
30001
|
+
}
|
|
30002
|
+
this._private__pendingToolIds.clear();
|
|
30003
|
+
if (cancelled > 0) {
|
|
30004
|
+
this.recordChange();
|
|
30005
|
+
}
|
|
30006
|
+
return cancelled;
|
|
30007
|
+
}
|
|
30008
|
+
subscribeDrawingCompleted(handler) {
|
|
30009
|
+
this._private__drawingCompleted._internal_subscribe(handler);
|
|
30010
|
+
}
|
|
30011
|
+
unsubscribeDrawingCompleted(handler) {
|
|
30012
|
+
this._private__drawingCompleted._internal_unsubscribe(handler);
|
|
30013
|
+
}
|
|
30014
|
+
_private__scheduleCompletionCheck() {
|
|
30015
|
+
if (this._private__pendingToolIds.size === 0 || this._private__completionCheck !== null) {
|
|
30016
|
+
return;
|
|
30017
|
+
}
|
|
30018
|
+
this._private__completionCheck = setTimeout(() => {
|
|
30019
|
+
this._private__completionCheck = null;
|
|
30020
|
+
for (const id of Array.from(this._private__pendingToolIds)) {
|
|
30021
|
+
const tool = this._private__tools.get(id);
|
|
30022
|
+
if (!tool) {
|
|
30023
|
+
this._private__pendingToolIds.delete(id);
|
|
30024
|
+
continue;
|
|
30025
|
+
}
|
|
30026
|
+
if (tool.isComplete()) {
|
|
30027
|
+
this._private__pendingToolIds.delete(id);
|
|
30028
|
+
this._private__drawingCompleted._internal_fire(id);
|
|
30029
|
+
}
|
|
30030
|
+
}
|
|
30031
|
+
}, 0);
|
|
30032
|
+
}
|
|
29096
30033
|
/**
|
|
29097
30034
|
* Remove a tool by ID
|
|
29098
30035
|
*/
|
|
@@ -29104,6 +30041,7 @@
|
|
|
29104
30041
|
}
|
|
29105
30042
|
tool.remove();
|
|
29106
30043
|
this._private__tools.delete(id);
|
|
30044
|
+
this._private__pendingToolIds.delete(id);
|
|
29107
30045
|
this.recordChange();
|
|
29108
30046
|
return true;
|
|
29109
30047
|
}
|
|
@@ -29150,6 +30088,9 @@
|
|
|
29150
30088
|
return;
|
|
29151
30089
|
}
|
|
29152
30090
|
this._private__past.push(this._private__committed);
|
|
30091
|
+
if (this._private__past.length > UNDO_HISTORY_LIMIT) {
|
|
30092
|
+
this._private__past.shift();
|
|
30093
|
+
}
|
|
29153
30094
|
this._private__future = [];
|
|
29154
30095
|
this._private__committed = this.serializeAll();
|
|
29155
30096
|
}
|
|
@@ -29303,6 +30244,7 @@
|
|
|
29303
30244
|
this._private__applySelection(null);
|
|
29304
30245
|
this._private__tools.forEach((tool) => tool.remove());
|
|
29305
30246
|
this._private__tools.clear();
|
|
30247
|
+
this._private__pendingToolIds.clear();
|
|
29306
30248
|
this.recordChange();
|
|
29307
30249
|
}
|
|
29308
30250
|
/**
|
|
@@ -29523,8 +30465,17 @@
|
|
|
29523
30465
|
* Destroy the manager and cleanup
|
|
29524
30466
|
*/
|
|
29525
30467
|
destroy() {
|
|
30468
|
+
if (this._private__completionCheck !== null) {
|
|
30469
|
+
clearTimeout(this._private__completionCheck);
|
|
30470
|
+
this._private__completionCheck = null;
|
|
30471
|
+
}
|
|
30472
|
+
this._private__drawingCompleted._internal_destroy();
|
|
30473
|
+
this._private__selectionChanged._internal_destroy();
|
|
29526
30474
|
this.clearAll();
|
|
29527
30475
|
this._private__detachEventHandlers();
|
|
30476
|
+
this._private__past = [];
|
|
30477
|
+
this._private__future = [];
|
|
30478
|
+
this._private__committed = [];
|
|
29528
30479
|
}
|
|
29529
30480
|
_private__attachEventHandlers() {
|
|
29530
30481
|
// Click handler for selection
|
|
@@ -29542,11 +30493,17 @@
|
|
|
29542
30493
|
const chartElement = this._private__chart.chartElement();
|
|
29543
30494
|
if (chartElement) {
|
|
29544
30495
|
this._private__pointerDownHandler = (event) => this._private__handlePointerDown(event);
|
|
29545
|
-
this._private__pointerUpHandler = (event) => this._private__handlePointerUp();
|
|
29546
|
-
this._private__pointerCancelHandler = (event) => this._private__handlePointerUp();
|
|
30496
|
+
this._private__pointerUpHandler = (event) => this._private__handlePointerUp(event);
|
|
30497
|
+
this._private__pointerCancelHandler = (event) => this._private__handlePointerUp(event);
|
|
30498
|
+
this._private__lostCaptureHandler = (event) => this._private__handleLostPointerCapture(event);
|
|
29547
30499
|
chartElement.addEventListener('pointerdown', this._private__pointerDownHandler);
|
|
29548
30500
|
chartElement.addEventListener('pointerup', this._private__pointerUpHandler);
|
|
29549
30501
|
chartElement.addEventListener('pointercancel', this._private__pointerCancelHandler);
|
|
30502
|
+
chartElement.addEventListener('lostpointercapture', this._private__lostCaptureHandler);
|
|
30503
|
+
}
|
|
30504
|
+
if (isRunningOnClientSide) {
|
|
30505
|
+
this._private__windowPointerUpHandler = (event) => this._private__handlePointerUp(event);
|
|
30506
|
+
window.addEventListener('pointerup', this._private__windowPointerUpHandler);
|
|
29550
30507
|
}
|
|
29551
30508
|
// Keyboard handler for shortcuts
|
|
29552
30509
|
if (this._private__options.enableKeyboardShortcuts) {
|
|
@@ -29579,6 +30536,14 @@
|
|
|
29579
30536
|
chartElement.removeEventListener('pointercancel', this._private__pointerCancelHandler);
|
|
29580
30537
|
this._private__pointerCancelHandler = null;
|
|
29581
30538
|
}
|
|
30539
|
+
if (this._private__lostCaptureHandler) {
|
|
30540
|
+
chartElement.removeEventListener('lostpointercapture', this._private__lostCaptureHandler);
|
|
30541
|
+
this._private__lostCaptureHandler = null;
|
|
30542
|
+
}
|
|
30543
|
+
}
|
|
30544
|
+
if (this._private__windowPointerUpHandler) {
|
|
30545
|
+
window.removeEventListener('pointerup', this._private__windowPointerUpHandler);
|
|
30546
|
+
this._private__windowPointerUpHandler = null;
|
|
29582
30547
|
}
|
|
29583
30548
|
if (this._private__keyboardHandler) {
|
|
29584
30549
|
document.removeEventListener('keydown', this._private__keyboardHandler);
|
|
@@ -29586,6 +30551,7 @@
|
|
|
29586
30551
|
}
|
|
29587
30552
|
}
|
|
29588
30553
|
_private__handleClick(param) {
|
|
30554
|
+
this._private__scheduleCompletionCheck();
|
|
29589
30555
|
if (!param.point) {
|
|
29590
30556
|
return;
|
|
29591
30557
|
}
|
|
@@ -29665,6 +30631,9 @@
|
|
|
29665
30631
|
return null;
|
|
29666
30632
|
}
|
|
29667
30633
|
_private__handlePointerDown(event) {
|
|
30634
|
+
if (this._private__isDragging) {
|
|
30635
|
+
return;
|
|
30636
|
+
}
|
|
29668
30637
|
// Cleared for every press, then raised only if this one grabs a drawing
|
|
29669
30638
|
setInputClaim(this._private__chart, false);
|
|
29670
30639
|
const chartElement = this._private__chart.chartElement();
|
|
@@ -29700,28 +30669,51 @@
|
|
|
29700
30669
|
this._private__gestureHandled = true;
|
|
29701
30670
|
this._private__draggedToolId = toolId;
|
|
29702
30671
|
this._private__pointerId = event.pointerId;
|
|
29703
|
-
|
|
30672
|
+
try {
|
|
30673
|
+
chartElement.setPointerCapture(event.pointerId);
|
|
30674
|
+
}
|
|
30675
|
+
catch (error) {
|
|
30676
|
+
this._private__pointerId = null;
|
|
30677
|
+
}
|
|
29704
30678
|
tool.startDrag(endpoint, anchor);
|
|
29705
30679
|
// Panning is stopped by disabling it for the gesture, not by preventing
|
|
29706
30680
|
// the event: preventDefault on pointerdown suppresses the compatibility
|
|
29707
30681
|
// mouse events, and those are what drive the crosshair the drag reads.
|
|
29708
30682
|
this._private__disableChartInteractions();
|
|
29709
30683
|
}
|
|
29710
|
-
_private__handlePointerUp() {
|
|
30684
|
+
_private__handlePointerUp(event) {
|
|
29711
30685
|
if (!this._private__isDragging || this._private__draggedToolId === null) {
|
|
29712
30686
|
return;
|
|
29713
30687
|
}
|
|
29714
|
-
|
|
29715
|
-
|
|
29716
|
-
tool.stopDrag();
|
|
30688
|
+
if (event && this._private__pointerId !== null && event.pointerId !== this._private__pointerId) {
|
|
30689
|
+
return;
|
|
29717
30690
|
}
|
|
29718
|
-
this.
|
|
29719
|
-
|
|
30691
|
+
const draggedToolId = this._private__draggedToolId;
|
|
30692
|
+
// Cleared first: anything below can throw, and a half-finished release
|
|
30693
|
+
// would leave the manager dragging forever.
|
|
29720
30694
|
this._private__isDragging = false;
|
|
29721
30695
|
this._private__draggedToolId = null;
|
|
30696
|
+
try {
|
|
30697
|
+
const tool = this._private__tools.get(draggedToolId);
|
|
30698
|
+
if (tool) {
|
|
30699
|
+
tool.stopDrag();
|
|
30700
|
+
}
|
|
30701
|
+
}
|
|
30702
|
+
finally {
|
|
30703
|
+
this._private__releasePointer();
|
|
30704
|
+
this._private__enableChartInteractions();
|
|
30705
|
+
}
|
|
29722
30706
|
// A finished drag is one step, not one per frame
|
|
29723
30707
|
this.recordChange();
|
|
29724
30708
|
}
|
|
30709
|
+
/** Ends the drag when capture is lost without a pointerup ever arriving. */
|
|
30710
|
+
_private__handleLostPointerCapture(event) {
|
|
30711
|
+
if (this._private__pointerId !== null && event.pointerId !== this._private__pointerId) {
|
|
30712
|
+
return;
|
|
30713
|
+
}
|
|
30714
|
+
this._private__pointerId = null;
|
|
30715
|
+
this._private__handlePointerUp();
|
|
30716
|
+
}
|
|
29725
30717
|
/**
|
|
29726
30718
|
* Pane element of a tool's series. The engine asserts the series has a pane,
|
|
29727
30719
|
* so the lookup is guarded.
|
|
@@ -29768,10 +30760,15 @@
|
|
|
29768
30760
|
this._private__pointerId = null;
|
|
29769
30761
|
}
|
|
29770
30762
|
_private__disableChartInteractions() {
|
|
30763
|
+
if (this._private__interactionsBeforeDrag !== null) {
|
|
30764
|
+
return;
|
|
30765
|
+
}
|
|
29771
30766
|
const options = this._private__chart.options();
|
|
30767
|
+
// `options()` returns the live object and `applyOptions` merges into it
|
|
30768
|
+
// in place, so a snapshot by reference restores all-false.
|
|
29772
30769
|
this._private__interactionsBeforeDrag = {
|
|
29773
|
-
_internal_handleScroll: options.handleScroll,
|
|
29774
|
-
_internal_handleScale: options.handleScale,
|
|
30770
|
+
_internal_handleScroll: clone(options.handleScroll),
|
|
30771
|
+
_internal_handleScale: clone(options.handleScale),
|
|
29775
30772
|
};
|
|
29776
30773
|
this._private__chart.applyOptions({
|
|
29777
30774
|
handleScroll: false,
|
|
@@ -29792,6 +30789,14 @@
|
|
|
29792
30789
|
});
|
|
29793
30790
|
}
|
|
29794
30791
|
_private__handleKeyboard(event) {
|
|
30792
|
+
const target = event.target;
|
|
30793
|
+
const typing = !!target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable);
|
|
30794
|
+
if (typing) {
|
|
30795
|
+
return;
|
|
30796
|
+
}
|
|
30797
|
+
if (event.key === 'Enter') {
|
|
30798
|
+
this._private__scheduleCompletionCheck();
|
|
30799
|
+
}
|
|
29795
30800
|
// Delete or Backspace to remove selected tool
|
|
29796
30801
|
if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
29797
30802
|
if (this.deleteSelected()) {
|
|
@@ -29812,8 +30817,8 @@
|
|
|
29812
30817
|
}
|
|
29813
30818
|
return;
|
|
29814
30819
|
}
|
|
29815
|
-
// Escape to deselect
|
|
29816
30820
|
if (event.key === 'Escape') {
|
|
30821
|
+
this.cancelPendingDrawings();
|
|
29817
30822
|
this._private__handlePointerUp();
|
|
29818
30823
|
this._private__deselectAll();
|
|
29819
30824
|
this.stopDrawing();
|
|
@@ -29849,7 +30854,7 @@
|
|
|
29849
30854
|
* Returns the current version as a string. For example `'1.0.0'`.
|
|
29850
30855
|
*/
|
|
29851
30856
|
function version() {
|
|
29852
|
-
return "2.
|
|
30857
|
+
return "2.3.0-dev+202609220108";
|
|
29853
30858
|
}
|
|
29854
30859
|
|
|
29855
30860
|
var PipsendChartsModule = /*#__PURE__*/Object.freeze({
|
|
@@ -29885,6 +30890,7 @@
|
|
|
29885
30890
|
PitchforkTool: PitchforkTool,
|
|
29886
30891
|
PolygonTool: PolygonTool,
|
|
29887
30892
|
PositionCalculator: PositionCalculator,
|
|
30893
|
+
PositionLine: PositionLine,
|
|
29888
30894
|
PositionMarkersManager: PositionMarkersManager,
|
|
29889
30895
|
PositionTool: PositionTool,
|
|
29890
30896
|
get PriceLineSource () { return PriceLineSource; },
|
|
@@ -29942,6 +30948,7 @@
|
|
|
29942
30948
|
createParallelChannelTool: createParallelChannelTool,
|
|
29943
30949
|
createPitchforkTool: createPitchforkTool,
|
|
29944
30950
|
createPolygonTool: createPolygonTool,
|
|
30951
|
+
createPositionLine: createPositionLine,
|
|
29945
30952
|
createPositionMarkers: createPositionMarkers,
|
|
29946
30953
|
createPositionTool: createPositionTool,
|
|
29947
30954
|
createRectangleTool: createRectangleTool,
|