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