@pipsend/charts 2.2.1 → 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 +1001 -115
- package/dist/pipsend-charts.production.mjs +2 -2
- package/dist/pipsend-charts.standalone.development.js +1002 -114
- package/dist/pipsend-charts.standalone.development.mjs +1001 -115
- package/dist/pipsend-charts.standalone.production.js +2 -2
- package/dist/pipsend-charts.standalone.production.mjs +2 -2
- package/dist/typings.d.ts +334 -1
- package/package.json +83 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* @license
|
|
3
|
-
* Pipsend Charts v2.
|
|
3
|
+
* Pipsend Charts v2.3.0-dev+202609220108
|
|
4
4
|
* Copyright (c) 2026 Pipsend
|
|
5
5
|
* Licensed under MIT License
|
|
6
6
|
* Built on TradingView Lightweight Charts™ (Apache 2.0)
|
|
@@ -14641,6 +14641,8 @@
|
|
|
14641
14641
|
return size(originalSize, 0.8);
|
|
14642
14642
|
case 'square':
|
|
14643
14643
|
return size(originalSize, 0.7);
|
|
14644
|
+
case 'tag':
|
|
14645
|
+
return size(originalSize, 1);
|
|
14644
14646
|
}
|
|
14645
14647
|
}
|
|
14646
14648
|
function calculateShapeHeight(barSpacing) {
|
|
@@ -14750,6 +14752,85 @@
|
|
|
14750
14752
|
y >= top && y <= top + squareSize;
|
|
14751
14753
|
}
|
|
14752
14754
|
|
|
14755
|
+
const TAIL_HALF_WIDTH_RATIO = 0.62;
|
|
14756
|
+
/**
|
|
14757
|
+
* Lays the pointer into the edge the path is already travelling along, so the
|
|
14758
|
+
* bubble and its tail are a single outline. Two subpaths would leave a stroked
|
|
14759
|
+
* seam where they meet.
|
|
14760
|
+
*/
|
|
14761
|
+
// eslint-disable-next-line max-params
|
|
14762
|
+
function tailPath(ctx, xCenter, edgeY, tailHeight, halfWidth, up) {
|
|
14763
|
+
// A tip with a small radius reads as drawn rather than as an artefact of the
|
|
14764
|
+
// rasteriser, which a bare triangle does at these sizes.
|
|
14765
|
+
const tipRadius = Math.min(halfWidth / 3, tailHeight / 3);
|
|
14766
|
+
const sign = up ? -1 : 1;
|
|
14767
|
+
const tipY = edgeY + sign * tailHeight;
|
|
14768
|
+
const shoulderY = tipY - sign * tipRadius;
|
|
14769
|
+
// The top edge is travelled left to right, the bottom edge right to left.
|
|
14770
|
+
const entry = up ? -halfWidth : halfWidth;
|
|
14771
|
+
ctx.lineTo(xCenter + entry, edgeY);
|
|
14772
|
+
ctx.lineTo(xCenter + (up ? -tipRadius : tipRadius), shoulderY);
|
|
14773
|
+
ctx.quadraticCurveTo(xCenter, tipY, xCenter + (up ? tipRadius : -tipRadius), shoulderY);
|
|
14774
|
+
ctx.lineTo(xCenter - entry, edgeY);
|
|
14775
|
+
}
|
|
14776
|
+
/**
|
|
14777
|
+
* Draws a rounded speech-bubble tag with an optional pointer towards its anchor.
|
|
14778
|
+
*/
|
|
14779
|
+
function drawTag(ctx, params) {
|
|
14780
|
+
const hpr = params._internal_horizontalPixelRatio;
|
|
14781
|
+
const vpr = params._internal_verticalPixelRatio;
|
|
14782
|
+
const left = Math.round((params._internal_x - params._internal_width / 2) * hpr);
|
|
14783
|
+
const right = Math.round((params._internal_x + params._internal_width / 2) * hpr);
|
|
14784
|
+
const top = Math.round((params._internal_centerY - params._internal_height / 2) * vpr);
|
|
14785
|
+
const bottom = Math.round((params._internal_centerY + params._internal_height / 2) * vpr);
|
|
14786
|
+
const xCenter = Math.round(params._internal_x * hpr);
|
|
14787
|
+
const radius = Math.min(params._internal_radius * hpr, (right - left) / 2, (bottom - top) / 2);
|
|
14788
|
+
const tailHeight = params._internal_tailHeight * vpr;
|
|
14789
|
+
const tailHalfWidth = Math.min(tailHeight * TAIL_HALF_WIDTH_RATIO, Math.max(0, (right - left) / 2 - radius));
|
|
14790
|
+
const hasTail = params._internal_tailDirection !== 'none' && tailHalfWidth > 0 && tailHeight > 0;
|
|
14791
|
+
ctx.save();
|
|
14792
|
+
ctx.beginPath();
|
|
14793
|
+
if (!hasTail) {
|
|
14794
|
+
drawRoundRect(ctx, left, top, right - left, bottom - top, [radius, radius, radius, radius]);
|
|
14795
|
+
}
|
|
14796
|
+
else {
|
|
14797
|
+
ctx.moveTo(left + radius, top);
|
|
14798
|
+
if (params._internal_tailDirection === 'up') {
|
|
14799
|
+
tailPath(ctx, xCenter, top, tailHeight, tailHalfWidth, true);
|
|
14800
|
+
}
|
|
14801
|
+
ctx.lineTo(right - radius, top);
|
|
14802
|
+
ctx.arcTo(right, top, right, top + radius, radius);
|
|
14803
|
+
ctx.lineTo(right, bottom - radius);
|
|
14804
|
+
ctx.arcTo(right, bottom, right - radius, bottom, radius);
|
|
14805
|
+
if (params._internal_tailDirection === 'down') {
|
|
14806
|
+
tailPath(ctx, xCenter, bottom, tailHeight, tailHalfWidth, false);
|
|
14807
|
+
}
|
|
14808
|
+
ctx.lineTo(left + radius, bottom);
|
|
14809
|
+
ctx.arcTo(left, bottom, left, bottom - radius, radius);
|
|
14810
|
+
ctx.lineTo(left, top + radius);
|
|
14811
|
+
ctx.arcTo(left, top, left + radius, top, radius);
|
|
14812
|
+
}
|
|
14813
|
+
ctx.closePath();
|
|
14814
|
+
ctx.fillStyle = params._internal_fillColor;
|
|
14815
|
+
ctx.fill();
|
|
14816
|
+
if (params._internal_borderColor !== undefined && params._internal_borderWidth > 0) {
|
|
14817
|
+
ctx.lineWidth = Math.max(1, Math.round(params._internal_borderWidth * hpr));
|
|
14818
|
+
ctx.strokeStyle = params._internal_borderColor;
|
|
14819
|
+
ctx.stroke();
|
|
14820
|
+
}
|
|
14821
|
+
ctx.restore();
|
|
14822
|
+
}
|
|
14823
|
+
function hitTestTag(centerX, centerY, tag, x, y) {
|
|
14824
|
+
if (tag._internal_width <= 0) {
|
|
14825
|
+
return false;
|
|
14826
|
+
}
|
|
14827
|
+
const halfHeight = tag._internal_height / 2;
|
|
14828
|
+
const top = tag._internal_tailDirection === 'up' ? centerY - halfHeight - tag._internal_tailHeight : centerY - halfHeight;
|
|
14829
|
+
const bottom = tag._internal_tailDirection === 'down' ? centerY + halfHeight + tag._internal_tailHeight : centerY + halfHeight;
|
|
14830
|
+
const left = centerX - tag._internal_width / 2;
|
|
14831
|
+
return x >= left && x <= left + tag._internal_width && y >= top && y <= bottom;
|
|
14832
|
+
}
|
|
14833
|
+
|
|
14753
14834
|
function drawText(ctx, text, x, y, horizontalPixelRatio, verticalPixelRatio) {
|
|
14754
14835
|
ctx.save();
|
|
14755
14836
|
ctx.scale(horizontalPixelRatio, verticalPixelRatio);
|
|
@@ -14766,9 +14847,11 @@
|
|
|
14766
14847
|
constructor() {
|
|
14767
14848
|
this._private__data = null;
|
|
14768
14849
|
this._private__textWidthCache = new TextWidthCache();
|
|
14850
|
+
this._private__tagTextWidthCache = new TextWidthCache();
|
|
14769
14851
|
this._private__fontSize = -1;
|
|
14770
14852
|
this._private__fontFamily = '';
|
|
14771
14853
|
this._private__font = '';
|
|
14854
|
+
this._private__tagFont = '';
|
|
14772
14855
|
this._private__zOrder = 'normal';
|
|
14773
14856
|
}
|
|
14774
14857
|
_internal_setData(data) {
|
|
@@ -14779,7 +14862,11 @@
|
|
|
14779
14862
|
this._private__fontSize = fontSize;
|
|
14780
14863
|
this._private__fontFamily = fontFamily;
|
|
14781
14864
|
this._private__font = makeFont(fontSize, fontFamily);
|
|
14865
|
+
// Tags carry a label rather than an annotation, so they get the heavier
|
|
14866
|
+
// weight — and their own cache, since widths differ per font.
|
|
14867
|
+
this._private__tagFont = makeFont(fontSize, fontFamily, '600');
|
|
14782
14868
|
this._private__textWidthCache._internal_reset();
|
|
14869
|
+
this._private__tagTextWidthCache._internal_reset();
|
|
14783
14870
|
}
|
|
14784
14871
|
this._private__zOrder = zOrder;
|
|
14785
14872
|
}
|
|
@@ -14819,14 +14906,21 @@
|
|
|
14819
14906
|
return;
|
|
14820
14907
|
}
|
|
14821
14908
|
ctx.textBaseline = 'middle';
|
|
14822
|
-
ctx.font = this._private__font;
|
|
14823
14909
|
for (let index = this._private__data._internal_visibleRange.from; index < this._private__data._internal_visibleRange.to; index++) {
|
|
14824
14910
|
const item = this._private__data._internal_items[index];
|
|
14911
|
+
const isTag = item._internal_tag !== undefined;
|
|
14912
|
+
ctx.font = isTag ? this._private__tagFont : this._private__font;
|
|
14825
14913
|
if (item._internal_text !== undefined) {
|
|
14826
|
-
|
|
14914
|
+
const cache = isTag ? this._private__tagTextWidthCache : this._private__textWidthCache;
|
|
14915
|
+
item._internal_text._internal_width = cache._internal_measureText(ctx, item._internal_text._internal_content);
|
|
14827
14916
|
item._internal_text._internal_height = this._private__fontSize;
|
|
14828
14917
|
item._internal_text._internal_x = item._internal_x - item._internal_text._internal_width / 2;
|
|
14829
14918
|
}
|
|
14919
|
+
if (item._internal_tag !== undefined) {
|
|
14920
|
+
const textWidth = item._internal_text !== undefined ? item._internal_text._internal_width : 0;
|
|
14921
|
+
item._internal_tag._internal_width = Math.max(textWidth + item._internal_tag._internal_paddingX * 2, item._internal_tag._internal_height);
|
|
14922
|
+
item._internal_tag._internal_left = item._internal_x - item._internal_tag._internal_width / 2;
|
|
14923
|
+
}
|
|
14830
14924
|
drawItem(item, ctx, horizontalPixelRatio, verticalPixelRatio);
|
|
14831
14925
|
}
|
|
14832
14926
|
}
|
|
@@ -14841,12 +14935,40 @@
|
|
|
14841
14935
|
};
|
|
14842
14936
|
}
|
|
14843
14937
|
function drawItem(item, ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
14938
|
+
if (item._internal_tag !== undefined) {
|
|
14939
|
+
drawTagItem(item, item._internal_tag, ctx, horizontalPixelRatio, verticalPixelRatio);
|
|
14940
|
+
return;
|
|
14941
|
+
}
|
|
14844
14942
|
ctx.fillStyle = item._internal_color;
|
|
14845
14943
|
if (item._internal_text !== undefined) {
|
|
14846
14944
|
drawText(ctx, item._internal_text._internal_content, item._internal_text._internal_x, item._internal_text._internal_y, horizontalPixelRatio, verticalPixelRatio);
|
|
14847
14945
|
}
|
|
14848
14946
|
drawShape(item, ctx, bitmapShapeItemCoordinates(item, horizontalPixelRatio, verticalPixelRatio));
|
|
14849
14947
|
}
|
|
14948
|
+
// eslint-disable-next-line max-params
|
|
14949
|
+
function drawTagItem(item, tag, ctx, horizontalPixelRatio, verticalPixelRatio) {
|
|
14950
|
+
if (item._internal_size === 0) {
|
|
14951
|
+
return;
|
|
14952
|
+
}
|
|
14953
|
+
drawTag(ctx, {
|
|
14954
|
+
_internal_x: item._internal_x,
|
|
14955
|
+
_internal_centerY: item._internal_y,
|
|
14956
|
+
_internal_width: tag._internal_width,
|
|
14957
|
+
_internal_height: tag._internal_height,
|
|
14958
|
+
_internal_tailHeight: tag._internal_tailHeight,
|
|
14959
|
+
_internal_tailDirection: tag._internal_tailDirection,
|
|
14960
|
+
_internal_radius: tag._internal_radius,
|
|
14961
|
+
_internal_fillColor: item._internal_color,
|
|
14962
|
+
_internal_borderColor: item._internal_borderColor,
|
|
14963
|
+
_internal_borderWidth: item._internal_borderWidth ?? 1,
|
|
14964
|
+
_internal_horizontalPixelRatio: horizontalPixelRatio,
|
|
14965
|
+
_internal_verticalPixelRatio: verticalPixelRatio,
|
|
14966
|
+
});
|
|
14967
|
+
if (item._internal_text !== undefined) {
|
|
14968
|
+
ctx.fillStyle = item._internal_textColor ?? '#FFFFFF';
|
|
14969
|
+
drawText(ctx, item._internal_text._internal_content, item._internal_text._internal_x, item._internal_text._internal_y, horizontalPixelRatio, verticalPixelRatio);
|
|
14970
|
+
}
|
|
14971
|
+
}
|
|
14850
14972
|
function drawShape(item, ctx, coordinates) {
|
|
14851
14973
|
if (item._internal_size === 0) {
|
|
14852
14974
|
return;
|
|
@@ -14864,10 +14986,16 @@
|
|
|
14864
14986
|
case 'square':
|
|
14865
14987
|
drawSquare(ctx, coordinates, item._internal_size);
|
|
14866
14988
|
return;
|
|
14989
|
+
case 'tag':
|
|
14990
|
+
// Handled by drawTagItem, which needs both pixel ratios and the text.
|
|
14991
|
+
return;
|
|
14867
14992
|
}
|
|
14868
14993
|
ensureNever(item._internal_shape);
|
|
14869
14994
|
}
|
|
14870
14995
|
function hitTestItem(item, x, y) {
|
|
14996
|
+
if (item._internal_tag !== undefined) {
|
|
14997
|
+
return item._internal_size !== 0 && hitTestTag(item._internal_x, item._internal_y, item._internal_tag, x, y);
|
|
14998
|
+
}
|
|
14871
14999
|
if (item._internal_text !== undefined && hitTestText(item._internal_text._internal_x, item._internal_text._internal_y, item._internal_text._internal_width, item._internal_text._internal_height, x, y)) {
|
|
14872
15000
|
return true;
|
|
14873
15001
|
}
|
|
@@ -14886,6 +15014,8 @@
|
|
|
14886
15014
|
return hitTestCircle(item._internal_x, item._internal_y, item._internal_size, x, y);
|
|
14887
15015
|
case 'square':
|
|
14888
15016
|
return hitTestSquare(item._internal_x, item._internal_y, item._internal_size, x, y);
|
|
15017
|
+
case 'tag':
|
|
15018
|
+
return false;
|
|
14889
15019
|
}
|
|
14890
15020
|
}
|
|
14891
15021
|
|
|
@@ -14918,15 +15048,71 @@
|
|
|
14918
15048
|
}
|
|
14919
15049
|
return;
|
|
14920
15050
|
}
|
|
15051
|
+
/**
|
|
15052
|
+
* Lays out a `tag` marker: the bubble carries the text, so it is measured from
|
|
15053
|
+
* the font rather than from the bar spacing, and its pointer is aimed back at
|
|
15054
|
+
* the anchor price.
|
|
15055
|
+
*/
|
|
15056
|
+
// eslint-disable-next-line max-params
|
|
15057
|
+
function fillTagSizeAndY(rendererItem, marker, anchorY, offsets, textHeight, shapeMargin, sizeMultiplier) {
|
|
15058
|
+
const height = Math.round((textHeight + 5 /* Constants.TagPaddingY */ * 2) * sizeMultiplier);
|
|
15059
|
+
const tailHeight = Math.round(7 /* Constants.TagTailHeight */ * sizeMultiplier);
|
|
15060
|
+
const ignoreOffset = isPriceMarker(marker.position);
|
|
15061
|
+
const halfHeight = height / 2;
|
|
15062
|
+
let tailDirection = 'none';
|
|
15063
|
+
let centerY = anchorY;
|
|
15064
|
+
switch (marker.position) {
|
|
15065
|
+
case 'aboveBar':
|
|
15066
|
+
case 'atPriceTop': {
|
|
15067
|
+
const offset = ignoreOffset ? 0 : offsets._internal_aboveBar;
|
|
15068
|
+
tailDirection = 'down';
|
|
15069
|
+
centerY = (anchorY - offset - tailHeight - halfHeight);
|
|
15070
|
+
if (!ignoreOffset) {
|
|
15071
|
+
offsets._internal_aboveBar += height + tailHeight + shapeMargin;
|
|
15072
|
+
}
|
|
15073
|
+
break;
|
|
15074
|
+
}
|
|
15075
|
+
case 'belowBar':
|
|
15076
|
+
case 'atPriceBottom': {
|
|
15077
|
+
const offset = ignoreOffset ? 0 : offsets._internal_belowBar;
|
|
15078
|
+
tailDirection = 'up';
|
|
15079
|
+
centerY = (anchorY + offset + tailHeight + halfHeight);
|
|
15080
|
+
if (!ignoreOffset) {
|
|
15081
|
+
offsets._internal_belowBar += height + tailHeight + shapeMargin;
|
|
15082
|
+
}
|
|
15083
|
+
break;
|
|
15084
|
+
}
|
|
15085
|
+
}
|
|
15086
|
+
const tag = {
|
|
15087
|
+
_internal_height: height,
|
|
15088
|
+
_internal_tailHeight: tailHeight,
|
|
15089
|
+
_internal_tailDirection: tailDirection,
|
|
15090
|
+
_internal_radius: marker.borderRadius ?? 6 /* Constants.TagRadius */,
|
|
15091
|
+
_internal_paddingX: 9 /* Constants.TagPaddingX */ * sizeMultiplier,
|
|
15092
|
+
_internal_left: 0,
|
|
15093
|
+
_internal_width: 0,
|
|
15094
|
+
};
|
|
15095
|
+
rendererItem._internal_size = height;
|
|
15096
|
+
rendererItem._internal_y = centerY;
|
|
15097
|
+
rendererItem._internal_tag = tag;
|
|
15098
|
+
if (rendererItem._internal_text !== undefined) {
|
|
15099
|
+
rendererItem._internal_text._internal_y = centerY;
|
|
15100
|
+
}
|
|
15101
|
+
}
|
|
14921
15102
|
// eslint-disable-next-line max-params, complexity
|
|
14922
|
-
function fillSizeAndY(rendererItem, marker, seriesData, offsets, textHeight, shapeMargin, series, chart) {
|
|
14923
|
-
const price = getPrice(seriesData, marker,
|
|
15103
|
+
function fillSizeAndY(rendererItem, marker, seriesData, offsets, textHeight, shapeMargin, series, chart, invertScale) {
|
|
15104
|
+
const price = getPrice(seriesData, marker, invertScale);
|
|
14924
15105
|
if (price === undefined) {
|
|
14925
15106
|
return;
|
|
14926
15107
|
}
|
|
14927
15108
|
const ignoreOffset = isPriceMarker(marker.position);
|
|
14928
15109
|
const timeScale = chart.timeScale();
|
|
14929
15110
|
const sizeMultiplier = isNumber(marker.size) ? Math.max(marker.size, 0) : 1;
|
|
15111
|
+
if (marker.shape === 'tag') {
|
|
15112
|
+
const anchorY = ensureNotNull(series.priceToCoordinate(price));
|
|
15113
|
+
fillTagSizeAndY(rendererItem, marker, anchorY, offsets, textHeight, shapeMargin, sizeMultiplier);
|
|
15114
|
+
return;
|
|
15115
|
+
}
|
|
14930
15116
|
const shapeSize = calculateShapeHeight(timeScale.options().barSpacing) * sizeMultiplier;
|
|
14931
15117
|
const halfSize = shapeSize / 2;
|
|
14932
15118
|
rendererItem._internal_size = shapeSize;
|
|
@@ -15033,6 +15219,10 @@
|
|
|
15033
15219
|
_internal_externalId: marker.id,
|
|
15034
15220
|
_internal_internalId: marker._internal_internalId,
|
|
15035
15221
|
_internal_text: undefined,
|
|
15222
|
+
_internal_textColor: marker.textColor,
|
|
15223
|
+
_internal_borderColor: marker.borderColor,
|
|
15224
|
+
_internal_borderWidth: marker.borderWidth,
|
|
15225
|
+
_internal_tag: undefined,
|
|
15036
15226
|
}));
|
|
15037
15227
|
this._private__dataInvalidated = false;
|
|
15038
15228
|
}
|
|
@@ -15043,13 +15233,22 @@
|
|
|
15043
15233
|
return;
|
|
15044
15234
|
}
|
|
15045
15235
|
const visibleBarsRange = new RangeImpl(Math.floor(visibleBars.from), Math.ceil(visibleBars.to));
|
|
15046
|
-
const firstValue = this._private__series.
|
|
15236
|
+
const firstValue = this._private__series.dataByIndex(0, 1 /* MismatchDirection.NearestRight */);
|
|
15047
15237
|
if (firstValue === null) {
|
|
15048
15238
|
return;
|
|
15049
15239
|
}
|
|
15050
15240
|
if (this._private__data._internal_items.length === 0) {
|
|
15051
15241
|
return;
|
|
15052
15242
|
}
|
|
15243
|
+
// This runs inside the hit-test and paint loops, where a throw costs the
|
|
15244
|
+
// rest of the frame.
|
|
15245
|
+
let invertScale = false;
|
|
15246
|
+
try {
|
|
15247
|
+
invertScale = this._private__series.priceScale().options().invertScale;
|
|
15248
|
+
}
|
|
15249
|
+
catch (error) {
|
|
15250
|
+
invertScale = false;
|
|
15251
|
+
}
|
|
15053
15252
|
let prevTimeIndex = NaN;
|
|
15054
15253
|
const shapeMargin$1 = shapeMargin(timeScale.options().barSpacing);
|
|
15055
15254
|
const offsets = {
|
|
@@ -15080,7 +15279,7 @@
|
|
|
15080
15279
|
if (dataAt === null) {
|
|
15081
15280
|
continue;
|
|
15082
15281
|
}
|
|
15083
|
-
fillSizeAndY(rendererItem, marker, dataAt, offsets, layoutOptions.fontSize, shapeMargin$1, this._private__series, this._private__chart);
|
|
15282
|
+
fillSizeAndY(rendererItem, marker, dataAt, offsets, layoutOptions.fontSize, shapeMargin$1, this._private__series, this._private__chart, invertScale);
|
|
15084
15283
|
}
|
|
15085
15284
|
this._private__invalidated = false;
|
|
15086
15285
|
}
|
|
@@ -15238,6 +15437,10 @@
|
|
|
15238
15437
|
text: marker.text,
|
|
15239
15438
|
size: marker.size,
|
|
15240
15439
|
price: marker.price,
|
|
15440
|
+
textColor: marker.textColor,
|
|
15441
|
+
borderColor: marker.borderColor,
|
|
15442
|
+
borderWidth: marker.borderWidth,
|
|
15443
|
+
borderRadius: marker.borderRadius,
|
|
15241
15444
|
_internal_originalTime: marker.time,
|
|
15242
15445
|
};
|
|
15243
15446
|
if (marker.position === 'atPriceTop' ||
|
|
@@ -19539,6 +19742,7 @@
|
|
|
19539
19742
|
constructor(chart, element) {
|
|
19540
19743
|
this._private__lines = new Map();
|
|
19541
19744
|
this._private__hoveredId = null;
|
|
19745
|
+
this._private__hoveredPart = null;
|
|
19542
19746
|
this._private__dragging = null;
|
|
19543
19747
|
this._private__pointerId = null;
|
|
19544
19748
|
this._private__pressOrigin = null;
|
|
@@ -19596,8 +19800,12 @@
|
|
|
19596
19800
|
if (this._private__dragging !== null) {
|
|
19597
19801
|
return;
|
|
19598
19802
|
}
|
|
19599
|
-
const
|
|
19803
|
+
const hit = this._private__splitHit(param.hoveredObjectId);
|
|
19804
|
+
const hovered = hit === null ? null : this._private__lines.get(hit._internal_id) ?? null;
|
|
19600
19805
|
this._private__setHovered(hovered ? hovered.id() : null);
|
|
19806
|
+
// The part is updated after the id, and separately from it: moving from a
|
|
19807
|
+
// label onto its close button keeps the same line hovered.
|
|
19808
|
+
this._private__setHoveredPart(hovered, hovered === null ? null : hit?._internal_part ?? null);
|
|
19601
19809
|
this._private__applyCursor();
|
|
19602
19810
|
}
|
|
19603
19811
|
_private__onPointerDown(event) {
|
|
@@ -19701,24 +19909,43 @@
|
|
|
19701
19909
|
if (line === null) {
|
|
19702
19910
|
return null;
|
|
19703
19911
|
}
|
|
19704
|
-
//
|
|
19705
|
-
//
|
|
19706
|
-
//
|
|
19912
|
+
// The pointer event is the gesture's own position, so it is the source of
|
|
19913
|
+
// truth. The crosshair is only a mirror of it: it stops updating as soon
|
|
19914
|
+
// as the pointer leaves the pane canvas — over the price axis, past the
|
|
19915
|
+
// chart's edge — and a drag driven from it freezes there while the user
|
|
19916
|
+
// is still moving.
|
|
19917
|
+
const pane = this._private__chart.panes()[line.paneIndex()];
|
|
19918
|
+
const paneElement = pane ? pane.getHTMLElement() : null;
|
|
19919
|
+
if (paneElement) {
|
|
19920
|
+
return line.priceAt(event.clientY - paneElement.getBoundingClientRect().top);
|
|
19921
|
+
}
|
|
19707
19922
|
if (this._private__point !== null && this._private__paneIndex === line.paneIndex()) {
|
|
19708
19923
|
return line.priceAt(this._private__point._internal_y);
|
|
19709
19924
|
}
|
|
19710
|
-
|
|
19711
|
-
|
|
19712
|
-
|
|
19925
|
+
return null;
|
|
19926
|
+
}
|
|
19927
|
+
/**
|
|
19928
|
+
* Hit ids are either a bare line id or `id#part`, which is how a line with
|
|
19929
|
+
* several targets reports which one was hit.
|
|
19930
|
+
*/
|
|
19931
|
+
_private__splitHit(externalId) {
|
|
19932
|
+
if (typeof externalId !== 'string' || externalId.length === 0) {
|
|
19713
19933
|
return null;
|
|
19714
19934
|
}
|
|
19715
|
-
|
|
19935
|
+
const separator = externalId.indexOf('#');
|
|
19936
|
+
if (separator === -1) {
|
|
19937
|
+
return { _internal_id: externalId, _internal_part: null };
|
|
19938
|
+
}
|
|
19939
|
+
return { _internal_id: externalId.slice(0, separator), _internal_part: externalId.slice(separator + 1) };
|
|
19716
19940
|
}
|
|
19717
|
-
|
|
19718
|
-
if (
|
|
19719
|
-
return
|
|
19941
|
+
_private__setHoveredPart(line, part) {
|
|
19942
|
+
if (this._private__hoveredPart === part) {
|
|
19943
|
+
return;
|
|
19944
|
+
}
|
|
19945
|
+
this._private__hoveredPart = part;
|
|
19946
|
+
if (line && line.setHoveredPart) {
|
|
19947
|
+
line.setHoveredPart(part);
|
|
19720
19948
|
}
|
|
19721
|
-
return this._private__lines.get(externalId) ?? null;
|
|
19722
19949
|
}
|
|
19723
19950
|
_private__setHovered(id) {
|
|
19724
19951
|
if (this._private__hoveredId === id) {
|
|
@@ -19728,6 +19955,7 @@
|
|
|
19728
19955
|
this._private__lines.get(this._private__hoveredId)?.setHovered(false);
|
|
19729
19956
|
}
|
|
19730
19957
|
this._private__hoveredId = id;
|
|
19958
|
+
this._private__hoveredPart = null;
|
|
19731
19959
|
if (id !== null) {
|
|
19732
19960
|
this._private__lines.get(id)?.setHovered(true);
|
|
19733
19961
|
}
|
|
@@ -20251,6 +20479,702 @@
|
|
|
20251
20479
|
return new InteractiveLineManager(chart, series);
|
|
20252
20480
|
}
|
|
20253
20481
|
|
|
20482
|
+
const positionLineDefaultColors = {
|
|
20483
|
+
entry: '#2962FF',
|
|
20484
|
+
takeProfit: '#26A17B',
|
|
20485
|
+
stopLoss: '#D9A23B',
|
|
20486
|
+
};
|
|
20487
|
+
const positionLineThemeDefaults = {
|
|
20488
|
+
badgeTextColor: '#FFFFFF',
|
|
20489
|
+
borderRadius: 4,
|
|
20490
|
+
borderWidth: 1,
|
|
20491
|
+
paddingX: 8,
|
|
20492
|
+
paddingY: 6,
|
|
20493
|
+
backgroundOpacity: 1,
|
|
20494
|
+
};
|
|
20495
|
+
const positionLineLevels = ['takeProfit', 'entry', 'stopLoss'];
|
|
20496
|
+
|
|
20497
|
+
class PositionLineRenderer {
|
|
20498
|
+
constructor() {
|
|
20499
|
+
this._private__data = null;
|
|
20500
|
+
this._private__layouts = [];
|
|
20501
|
+
this._private__connectorX = 0;
|
|
20502
|
+
this._private__font = '';
|
|
20503
|
+
this._private__colorParser = new ColorParser([]);
|
|
20504
|
+
this._private__textWidthCache = new TextWidthCache(50);
|
|
20505
|
+
}
|
|
20506
|
+
_internal_setData(data) {
|
|
20507
|
+
this._private__data = data;
|
|
20508
|
+
}
|
|
20509
|
+
_internal_hitTest(x, y) {
|
|
20510
|
+
// The close button sits on top of the label, which sits on top of the
|
|
20511
|
+
// line, so the narrowest target is tested first.
|
|
20512
|
+
for (const layout of this._private__layouts) {
|
|
20513
|
+
if (layout._internal_closable && x >= layout._internal_closeLeft && x <= layout._internal_closeRight &&
|
|
20514
|
+
Math.abs(y - layout._internal_y) <= layout._internal_height / 2) {
|
|
20515
|
+
return { _internal_level: layout._internal_level, _internal_part: 'close' };
|
|
20516
|
+
}
|
|
20517
|
+
}
|
|
20518
|
+
for (const layout of this._private__layouts) {
|
|
20519
|
+
if (x >= layout._internal_pillLeft && x <= layout._internal_pillRight && Math.abs(y - layout._internal_y) <= layout._internal_height / 2) {
|
|
20520
|
+
return { _internal_level: layout._internal_level, _internal_part: 'body' };
|
|
20521
|
+
}
|
|
20522
|
+
}
|
|
20523
|
+
for (const layout of this._private__layouts) {
|
|
20524
|
+
if (Math.abs(y - layout._internal_y) <= 8 /* Constants.LineHitTolerance */) {
|
|
20525
|
+
return { _internal_level: layout._internal_level, _internal_part: 'line' };
|
|
20526
|
+
}
|
|
20527
|
+
}
|
|
20528
|
+
return null;
|
|
20529
|
+
}
|
|
20530
|
+
draw(target) {
|
|
20531
|
+
target.useBitmapCoordinateSpace((scope) => this._private__drawImpl(scope));
|
|
20532
|
+
}
|
|
20533
|
+
_private__drawImpl(scope) {
|
|
20534
|
+
const data = this._private__data;
|
|
20535
|
+
this._private__layouts = [];
|
|
20536
|
+
if (data === null || data._internal_levels.length === 0) {
|
|
20537
|
+
return;
|
|
20538
|
+
}
|
|
20539
|
+
const { context: ctx, mediaSize } = scope;
|
|
20540
|
+
ctx.save();
|
|
20541
|
+
if (this._private__font !== data._internal_font) {
|
|
20542
|
+
this._private__font = data._internal_font;
|
|
20543
|
+
this._private__textWidthCache._internal_reset();
|
|
20544
|
+
}
|
|
20545
|
+
ctx.font = data._internal_font;
|
|
20546
|
+
ctx.textBaseline = 'middle';
|
|
20547
|
+
this._private__connectorX = Math.max(0, mediaSize.width - data._internal_rightOffset);
|
|
20548
|
+
const lineEnd = data._internal_connectorVisible ? this._private__connectorX : mediaSize.width;
|
|
20549
|
+
for (const level of data._internal_levels) {
|
|
20550
|
+
this._private__layouts.push(this._private__drawLevel(scope, data, level, lineEnd));
|
|
20551
|
+
}
|
|
20552
|
+
if (data._internal_connectorVisible && data._internal_levels.length > 1) {
|
|
20553
|
+
this._private__drawConnector(scope, data);
|
|
20554
|
+
}
|
|
20555
|
+
ctx.restore();
|
|
20556
|
+
}
|
|
20557
|
+
// eslint-disable-next-line max-params
|
|
20558
|
+
_private__drawLevel(scope, data, level, lineEnd) {
|
|
20559
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20560
|
+
const height = data._internal_fontSize + data._internal_paddingY * 2;
|
|
20561
|
+
const badgeWidth = level._internal_badge.length === 0
|
|
20562
|
+
? 0
|
|
20563
|
+
: Math.max(this._private__textWidthCache._internal_measureText(ctx, level._internal_badge) + data._internal_paddingX * 2, height * 0.85 /* Constants.BadgeMinWidthRatio */);
|
|
20564
|
+
const labelWidth = this._private__textWidthCache._internal_measureText(ctx, level._internal_text) + data._internal_paddingX * 2;
|
|
20565
|
+
const closeWidth = level._internal_closable ? height : 0;
|
|
20566
|
+
const pillWidth = badgeWidth + labelWidth + closeWidth;
|
|
20567
|
+
const pillRight = Math.max(pillWidth, lineEnd - 8 /* Constants.LabelGap */);
|
|
20568
|
+
const pillLeft = pillRight - pillWidth;
|
|
20569
|
+
const top = level._internal_y - height / 2;
|
|
20570
|
+
this._private__drawLineSegments(scope, level, pillLeft, pillRight, lineEnd);
|
|
20571
|
+
this._private__drawPill(scope, data, level, { _internal_left: pillLeft, _internal_top: top, _internal_width: pillWidth, _internal_height: height });
|
|
20572
|
+
let cellLeft = pillLeft;
|
|
20573
|
+
if (badgeWidth > 0) {
|
|
20574
|
+
this._private__drawBadge(scope, data, level, { _internal_left: cellLeft, _internal_top: top, _internal_width: badgeWidth, _internal_height: height });
|
|
20575
|
+
cellLeft += badgeWidth;
|
|
20576
|
+
this._private__drawSeparator(ctx, level._internal_color, cellLeft, top, height, hpr, vpr);
|
|
20577
|
+
}
|
|
20578
|
+
ctx.save();
|
|
20579
|
+
ctx.scale(hpr, vpr);
|
|
20580
|
+
ctx.textAlign = 'center';
|
|
20581
|
+
ctx.fillStyle = data._internal_textColor ?? level._internal_color;
|
|
20582
|
+
ctx.fillText(level._internal_text, cellLeft + labelWidth / 2, level._internal_y);
|
|
20583
|
+
ctx.restore();
|
|
20584
|
+
cellLeft += labelWidth;
|
|
20585
|
+
if (level._internal_closable) {
|
|
20586
|
+
this._private__drawSeparator(ctx, level._internal_color, cellLeft, top, height, hpr, vpr);
|
|
20587
|
+
this._private__drawCloseButton(scope, data, level, { _internal_left: cellLeft, _internal_top: top, _internal_width: closeWidth, _internal_height: height });
|
|
20588
|
+
}
|
|
20589
|
+
return {
|
|
20590
|
+
_internal_level: level._internal_level,
|
|
20591
|
+
_internal_y: level._internal_y,
|
|
20592
|
+
_internal_height: height,
|
|
20593
|
+
_internal_pillLeft: pillLeft,
|
|
20594
|
+
_internal_pillRight: pillRight,
|
|
20595
|
+
_internal_closeLeft: cellLeft,
|
|
20596
|
+
_internal_closeRight: cellLeft + closeWidth,
|
|
20597
|
+
_internal_closable: level._internal_closable,
|
|
20598
|
+
};
|
|
20599
|
+
}
|
|
20600
|
+
// eslint-disable-next-line max-params
|
|
20601
|
+
_private__drawLineSegments(scope, level, pillLeft, pillRight, lineEnd) {
|
|
20602
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20603
|
+
const lineWidth = Math.max(1, Math.round(level._internal_lineWidth * vpr));
|
|
20604
|
+
const y = Math.round(level._internal_y * vpr) - Math.floor(lineWidth / 2);
|
|
20605
|
+
ctx.save();
|
|
20606
|
+
ctx.strokeStyle = level._internal_color;
|
|
20607
|
+
ctx.lineWidth = lineWidth;
|
|
20608
|
+
setLineStyle(ctx, level._internal_lineStyle);
|
|
20609
|
+
ctx.beginPath();
|
|
20610
|
+
ctx.moveTo(0, y);
|
|
20611
|
+
ctx.lineTo(Math.round(Math.max(0, pillLeft - 8 /* Constants.LabelGap */) * hpr), y);
|
|
20612
|
+
if (lineEnd > pillRight + 8 /* Constants.LabelGap */) {
|
|
20613
|
+
ctx.moveTo(Math.round((pillRight + 8 /* Constants.LabelGap */) * hpr), y);
|
|
20614
|
+
ctx.lineTo(Math.round(lineEnd * hpr), y);
|
|
20615
|
+
}
|
|
20616
|
+
ctx.stroke();
|
|
20617
|
+
ctx.restore();
|
|
20618
|
+
}
|
|
20619
|
+
// eslint-disable-next-line max-params
|
|
20620
|
+
_private__drawPill(scope, data, level, box) {
|
|
20621
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20622
|
+
const hovered = data._internal_hoveredLevel === level._internal_level;
|
|
20623
|
+
const borderWidth = Math.max(1, Math.round((hovered ? data._internal_borderWidth + 1 : data._internal_borderWidth) * hpr));
|
|
20624
|
+
ctx.save();
|
|
20625
|
+
ctx.setLineDash([]);
|
|
20626
|
+
bitmapRoundRect(ctx, box, data._internal_borderRadius, hpr, vpr);
|
|
20627
|
+
ctx.fillStyle = this._private__colorParser._internal_applyAlpha(data._internal_background, data._internal_backgroundOpacity);
|
|
20628
|
+
ctx.fill();
|
|
20629
|
+
ctx.lineWidth = borderWidth;
|
|
20630
|
+
ctx.strokeStyle = level._internal_color;
|
|
20631
|
+
ctx.stroke();
|
|
20632
|
+
ctx.restore();
|
|
20633
|
+
}
|
|
20634
|
+
// eslint-disable-next-line max-params
|
|
20635
|
+
_private__drawBadge(scope, data, level, box) {
|
|
20636
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20637
|
+
if (level._internal_badgeFilled) {
|
|
20638
|
+
// The badge shares the label's left corners, so its fill has to stop
|
|
20639
|
+
// short of the border instead of squaring it off.
|
|
20640
|
+
const inset = data._internal_borderWidth;
|
|
20641
|
+
ctx.save();
|
|
20642
|
+
bitmapRoundRect(ctx, { _internal_left: box._internal_left + inset, _internal_top: box._internal_top + inset, _internal_width: box._internal_width - inset, _internal_height: box._internal_height - inset * 2 }, Math.max(0, data._internal_borderRadius - inset), hpr, vpr, 'left');
|
|
20643
|
+
ctx.fillStyle = level._internal_color;
|
|
20644
|
+
ctx.fill();
|
|
20645
|
+
ctx.restore();
|
|
20646
|
+
}
|
|
20647
|
+
ctx.save();
|
|
20648
|
+
ctx.scale(hpr, vpr);
|
|
20649
|
+
ctx.textAlign = 'center';
|
|
20650
|
+
ctx.fillStyle = level._internal_badgeFilled ? data._internal_badgeTextColor : level._internal_color;
|
|
20651
|
+
ctx.fillText(level._internal_badge, box._internal_left + box._internal_width / 2, box._internal_top + box._internal_height / 2);
|
|
20652
|
+
ctx.restore();
|
|
20653
|
+
}
|
|
20654
|
+
// eslint-disable-next-line max-params
|
|
20655
|
+
_private__drawSeparator(ctx, color, x, top, height, hpr, vpr) {
|
|
20656
|
+
const width = Math.max(1, Math.floor(hpr));
|
|
20657
|
+
ctx.save();
|
|
20658
|
+
ctx.setLineDash([]);
|
|
20659
|
+
ctx.fillStyle = this._private__colorParser._internal_applyAlpha(color, 0.4 /* Constants.SeparatorAlpha */);
|
|
20660
|
+
ctx.fillRect(Math.round(x * hpr), Math.round(top * vpr), width, Math.round(height * vpr));
|
|
20661
|
+
ctx.restore();
|
|
20662
|
+
}
|
|
20663
|
+
// eslint-disable-next-line max-params
|
|
20664
|
+
_private__drawCloseButton(scope, data, level, box) {
|
|
20665
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20666
|
+
const hovered = data._internal_hoveredLevel === level._internal_level && data._internal_hoveredPart === 'close';
|
|
20667
|
+
if (hovered) {
|
|
20668
|
+
ctx.save();
|
|
20669
|
+
const inset = data._internal_borderWidth;
|
|
20670
|
+
bitmapRoundRect(ctx, { _internal_left: box._internal_left, _internal_top: box._internal_top + inset, _internal_width: box._internal_width - inset, _internal_height: box._internal_height - inset * 2 }, Math.max(0, data._internal_borderRadius - inset), hpr, vpr, 'right');
|
|
20671
|
+
ctx.fillStyle = this._private__colorParser._internal_applyAlpha(level._internal_color, 0.25);
|
|
20672
|
+
ctx.fill();
|
|
20673
|
+
ctx.restore();
|
|
20674
|
+
}
|
|
20675
|
+
const centerX = (box._internal_left + box._internal_width / 2) * hpr;
|
|
20676
|
+
const centerY = (box._internal_top + box._internal_height / 2) * vpr;
|
|
20677
|
+
const arm = (7 /* Constants.CloseGlyphSize */ / 2) * hpr;
|
|
20678
|
+
ctx.save();
|
|
20679
|
+
ctx.setLineDash([]);
|
|
20680
|
+
ctx.lineCap = 'round';
|
|
20681
|
+
ctx.lineWidth = Math.max(1, Math.round(1.4 * hpr));
|
|
20682
|
+
ctx.strokeStyle = level._internal_color;
|
|
20683
|
+
ctx.beginPath();
|
|
20684
|
+
ctx.moveTo(centerX - arm, centerY - arm);
|
|
20685
|
+
ctx.lineTo(centerX + arm, centerY + arm);
|
|
20686
|
+
ctx.moveTo(centerX + arm, centerY - arm);
|
|
20687
|
+
ctx.lineTo(centerX - arm, centerY + arm);
|
|
20688
|
+
ctx.stroke();
|
|
20689
|
+
ctx.restore();
|
|
20690
|
+
}
|
|
20691
|
+
_private__drawConnector(scope, data) {
|
|
20692
|
+
const { context: ctx, horizontalPixelRatio: hpr, verticalPixelRatio: vpr } = scope;
|
|
20693
|
+
const entry = data._internal_levels.find((level) => level._internal_level === 'entry') ?? data._internal_levels[0];
|
|
20694
|
+
const ys = data._internal_levels.map((level) => level._internal_y);
|
|
20695
|
+
const x = Math.round(this._private__connectorX * hpr);
|
|
20696
|
+
ctx.save();
|
|
20697
|
+
ctx.setLineDash([]);
|
|
20698
|
+
ctx.strokeStyle = entry._internal_color;
|
|
20699
|
+
ctx.lineWidth = Math.max(1, Math.floor(hpr));
|
|
20700
|
+
ctx.beginPath();
|
|
20701
|
+
ctx.moveTo(x, Math.round(Math.min(...ys) * vpr));
|
|
20702
|
+
ctx.lineTo(x, Math.round(Math.max(...ys) * vpr));
|
|
20703
|
+
ctx.stroke();
|
|
20704
|
+
for (const level of data._internal_levels) {
|
|
20705
|
+
ctx.beginPath();
|
|
20706
|
+
ctx.arc(x, Math.round(level._internal_y * vpr), 4.5 /* Constants.HandleRadius */ * hpr, 0, 2 * Math.PI);
|
|
20707
|
+
ctx.fillStyle = level._internal_level === 'entry'
|
|
20708
|
+
? entry._internal_color
|
|
20709
|
+
: this._private__colorParser._internal_applyAlpha(data._internal_background, data._internal_backgroundOpacity);
|
|
20710
|
+
ctx.fill();
|
|
20711
|
+
ctx.lineWidth = Math.max(1, Math.round(1.5 * hpr));
|
|
20712
|
+
ctx.strokeStyle = level._internal_color;
|
|
20713
|
+
ctx.stroke();
|
|
20714
|
+
}
|
|
20715
|
+
ctx.restore();
|
|
20716
|
+
}
|
|
20717
|
+
}
|
|
20718
|
+
// eslint-disable-next-line max-params
|
|
20719
|
+
function bitmapRoundRect(ctx, box, radius, hpr, vpr, side = 'all') {
|
|
20720
|
+
const left = Math.round(box._internal_left * hpr);
|
|
20721
|
+
const top = Math.round(box._internal_top * vpr);
|
|
20722
|
+
const width = Math.round(box._internal_width * hpr);
|
|
20723
|
+
const height = Math.round(box._internal_height * vpr);
|
|
20724
|
+
const r = Math.min(radius * hpr, width / 2, height / 2);
|
|
20725
|
+
const radii = side === 'left'
|
|
20726
|
+
? [r, 0, 0, r]
|
|
20727
|
+
: side === 'right'
|
|
20728
|
+
? [0, r, r, 0]
|
|
20729
|
+
: [r, r, r, r];
|
|
20730
|
+
drawRoundRect(ctx, left, top, width, height, radii);
|
|
20731
|
+
}
|
|
20732
|
+
|
|
20733
|
+
class PositionLinePaneView {
|
|
20734
|
+
constructor() {
|
|
20735
|
+
this._private__renderer = new PositionLineRenderer();
|
|
20736
|
+
this._private__data = null;
|
|
20737
|
+
}
|
|
20738
|
+
_internal_update(data) {
|
|
20739
|
+
this._private__data = data;
|
|
20740
|
+
}
|
|
20741
|
+
renderer() {
|
|
20742
|
+
if (this._private__data === null) {
|
|
20743
|
+
return null;
|
|
20744
|
+
}
|
|
20745
|
+
this._private__renderer._internal_setData(this._private__data);
|
|
20746
|
+
return this._private__renderer;
|
|
20747
|
+
}
|
|
20748
|
+
zOrder() {
|
|
20749
|
+
return 'top';
|
|
20750
|
+
}
|
|
20751
|
+
_internal_hitTest(x, y) {
|
|
20752
|
+
return this._private__data === null ? null : this._private__renderer._internal_hitTest(x, y);
|
|
20753
|
+
}
|
|
20754
|
+
}
|
|
20755
|
+
|
|
20756
|
+
class PositionLineAxisView {
|
|
20757
|
+
constructor(source) {
|
|
20758
|
+
this._private__source = source;
|
|
20759
|
+
}
|
|
20760
|
+
coordinate() {
|
|
20761
|
+
// A hidden label is pushed far off the axis so it cannot reserve a gap
|
|
20762
|
+
// between the labels that are shown.
|
|
20763
|
+
return this._private__label()?._internal_coordinate ?? -1e3;
|
|
20764
|
+
}
|
|
20765
|
+
text() {
|
|
20766
|
+
return this._private__label()?._internal_text ?? '';
|
|
20767
|
+
}
|
|
20768
|
+
textColor() {
|
|
20769
|
+
return this._private__label()?._internal_textColor ?? '#FFFFFF';
|
|
20770
|
+
}
|
|
20771
|
+
backColor() {
|
|
20772
|
+
return this._private__label()?._internal_color ?? '#000000';
|
|
20773
|
+
}
|
|
20774
|
+
visible() {
|
|
20775
|
+
return this._private__label() !== null;
|
|
20776
|
+
}
|
|
20777
|
+
_private__label() {
|
|
20778
|
+
return this._private__source();
|
|
20779
|
+
}
|
|
20780
|
+
}
|
|
20781
|
+
|
|
20782
|
+
let nextPositionId = 0;
|
|
20783
|
+
const DEFAULT_RIGHT_OFFSET = 56;
|
|
20784
|
+
/**
|
|
20785
|
+
* A group of trading levels — entry, take profit and stop loss — drawn as one
|
|
20786
|
+
* unit: a line per level, a label carrying size and P&L, and a connector that
|
|
20787
|
+
* ties the three together.
|
|
20788
|
+
*
|
|
20789
|
+
* Input is not handled here. Every trading line on a chart shares a single
|
|
20790
|
+
* TradingLineController, which is what guarantees that one gesture moves one
|
|
20791
|
+
* level, and that a position line and a plain draggable price line on the same
|
|
20792
|
+
* chart do not fight over the pointer.
|
|
20793
|
+
*/
|
|
20794
|
+
class PositionLine {
|
|
20795
|
+
constructor(chart, series, options) {
|
|
20796
|
+
this._private__paneView = new PositionLinePaneView();
|
|
20797
|
+
this._private__colorParser = new ColorParser([]);
|
|
20798
|
+
this._private__attached = false;
|
|
20799
|
+
this._private__hoveredLevel = null;
|
|
20800
|
+
this._private__hoveredPart = null;
|
|
20801
|
+
this._private__dragLevel = null;
|
|
20802
|
+
this._private__priceBeforeDrag = null;
|
|
20803
|
+
this._private__chart = chart;
|
|
20804
|
+
this._private__series = series;
|
|
20805
|
+
this._private__id = `pipsend-position-line-${nextPositionId++}`;
|
|
20806
|
+
this._private__options = options;
|
|
20807
|
+
this._private__theme = { ...positionLineThemeDefaults, ...options.theme };
|
|
20808
|
+
this._private__paneViews = [this._private__paneView];
|
|
20809
|
+
this._private__axisViews = positionLineLevels.map((level) => new PositionLineAxisView(() => this._private__axisLabel(level)));
|
|
20810
|
+
this._private__series.attachPrimitive(this);
|
|
20811
|
+
acquireController(this._private__chart)?._internal_register(this);
|
|
20812
|
+
}
|
|
20813
|
+
// ---- ISeriesPrimitive ----
|
|
20814
|
+
attached(param) {
|
|
20815
|
+
this._private__requestUpdate = param.requestUpdate;
|
|
20816
|
+
this._private__attached = true;
|
|
20817
|
+
this.requestUpdate();
|
|
20818
|
+
}
|
|
20819
|
+
detached() {
|
|
20820
|
+
this._private__attached = false;
|
|
20821
|
+
this._private__requestUpdate = undefined;
|
|
20822
|
+
}
|
|
20823
|
+
paneViews() {
|
|
20824
|
+
return this._private__paneViews;
|
|
20825
|
+
}
|
|
20826
|
+
priceAxisViews() {
|
|
20827
|
+
return this._private__axisViews;
|
|
20828
|
+
}
|
|
20829
|
+
updateAllViews() {
|
|
20830
|
+
this._private__paneView._internal_update(this._private__buildRendererData());
|
|
20831
|
+
}
|
|
20832
|
+
hitTest(x, y) {
|
|
20833
|
+
const hit = this._private__paneView._internal_hitTest(x, y);
|
|
20834
|
+
if (hit === null) {
|
|
20835
|
+
return null;
|
|
20836
|
+
}
|
|
20837
|
+
const level = this._private__levelOptions(hit._internal_level);
|
|
20838
|
+
if (level === null) {
|
|
20839
|
+
return null;
|
|
20840
|
+
}
|
|
20841
|
+
return {
|
|
20842
|
+
externalId: `${this._private__id}#${hit._internal_level}:${hit._internal_part}`,
|
|
20843
|
+
zOrder: 'top',
|
|
20844
|
+
cursorStyle: hit._internal_part === 'close'
|
|
20845
|
+
? 'pointer'
|
|
20846
|
+
: (level.draggable ?? true) ? 'ns-resize' : undefined,
|
|
20847
|
+
};
|
|
20848
|
+
}
|
|
20849
|
+
// ---- ControllableLine ----
|
|
20850
|
+
id() {
|
|
20851
|
+
return this._private__id;
|
|
20852
|
+
}
|
|
20853
|
+
paneIndex() {
|
|
20854
|
+
try {
|
|
20855
|
+
return this._private__series.getPane().paneIndex();
|
|
20856
|
+
}
|
|
20857
|
+
catch (error) {
|
|
20858
|
+
// The engine asserts the series belongs to a pane, and that lookup can
|
|
20859
|
+
// come back empty while panes are being rebuilt. The main pane is the
|
|
20860
|
+
// right guess and keeps the gesture alive instead of throwing mid-drag.
|
|
20861
|
+
return 0;
|
|
20862
|
+
}
|
|
20863
|
+
}
|
|
20864
|
+
setHoveredPart(part) {
|
|
20865
|
+
const [level, name] = part === null ? [null, null] : part.split(':');
|
|
20866
|
+
const nextLevel = level ?? null;
|
|
20867
|
+
const nextPart = name ?? null;
|
|
20868
|
+
if (this._private__hoveredLevel === nextLevel && this._private__hoveredPart === nextPart) {
|
|
20869
|
+
return;
|
|
20870
|
+
}
|
|
20871
|
+
this._private__hoveredLevel = nextLevel;
|
|
20872
|
+
this._private__hoveredPart = nextPart;
|
|
20873
|
+
this.requestUpdate();
|
|
20874
|
+
}
|
|
20875
|
+
setHovered(hovered) {
|
|
20876
|
+
if (!hovered) {
|
|
20877
|
+
this.setHoveredPart(null);
|
|
20878
|
+
}
|
|
20879
|
+
}
|
|
20880
|
+
isDraggable() {
|
|
20881
|
+
if (this._private__hoveredLevel === null || this._private__hoveredPart === 'close') {
|
|
20882
|
+
return false;
|
|
20883
|
+
}
|
|
20884
|
+
return this._private__levelOptions(this._private__hoveredLevel)?.draggable ?? true;
|
|
20885
|
+
}
|
|
20886
|
+
isClickable() {
|
|
20887
|
+
if (this._private__hoveredPart === 'close') {
|
|
20888
|
+
return true;
|
|
20889
|
+
}
|
|
20890
|
+
return this._private__options.onClick !== undefined;
|
|
20891
|
+
}
|
|
20892
|
+
priceAt(paneY) {
|
|
20893
|
+
const price = this._private__series.coordinateToPrice(paneY);
|
|
20894
|
+
return price === null ? null : price;
|
|
20895
|
+
}
|
|
20896
|
+
beginDrag() {
|
|
20897
|
+
this._private__dragLevel = this._private__hoveredLevel;
|
|
20898
|
+
if (this._private__dragLevel === null) {
|
|
20899
|
+
return;
|
|
20900
|
+
}
|
|
20901
|
+
const price = this._private__levelOptions(this._private__dragLevel)?.price ?? null;
|
|
20902
|
+
this._private__priceBeforeDrag = price;
|
|
20903
|
+
if (price !== null && this._private__options.onDragStart) {
|
|
20904
|
+
this._private__options.onDragStart(this._private__dragLevel, price);
|
|
20905
|
+
}
|
|
20906
|
+
}
|
|
20907
|
+
dragTo(price) {
|
|
20908
|
+
if (this._private__dragLevel === null) {
|
|
20909
|
+
return;
|
|
20910
|
+
}
|
|
20911
|
+
this._private__applyPrice(this._private__dragLevel, price);
|
|
20912
|
+
if (this._private__options.onDragMove) {
|
|
20913
|
+
this._private__options.onDragMove(this._private__dragLevel, price);
|
|
20914
|
+
}
|
|
20915
|
+
}
|
|
20916
|
+
commitDrag() {
|
|
20917
|
+
const level = this._private__dragLevel;
|
|
20918
|
+
this._private__dragLevel = null;
|
|
20919
|
+
this._private__priceBeforeDrag = null;
|
|
20920
|
+
if (level === null) {
|
|
20921
|
+
return;
|
|
20922
|
+
}
|
|
20923
|
+
const price = this._private__levelOptions(level)?.price;
|
|
20924
|
+
if (price !== undefined && this._private__options.onDragEnd) {
|
|
20925
|
+
this._private__options.onDragEnd(level, price);
|
|
20926
|
+
}
|
|
20927
|
+
}
|
|
20928
|
+
cancelDrag() {
|
|
20929
|
+
const level = this._private__dragLevel;
|
|
20930
|
+
const original = this._private__priceBeforeDrag;
|
|
20931
|
+
this._private__dragLevel = null;
|
|
20932
|
+
this._private__priceBeforeDrag = null;
|
|
20933
|
+
if (level === null) {
|
|
20934
|
+
return;
|
|
20935
|
+
}
|
|
20936
|
+
// Reported even when the price did not move, so a caller that pauses work
|
|
20937
|
+
// on `onDragStart` always gets the matching end of the gesture.
|
|
20938
|
+
const restored = original !== null && original !== this._private__levelOptions(level)?.price;
|
|
20939
|
+
if (restored) {
|
|
20940
|
+
this._private__applyPrice(level, original);
|
|
20941
|
+
if (this._private__options.onDragMove) {
|
|
20942
|
+
this._private__options.onDragMove(level, original);
|
|
20943
|
+
}
|
|
20944
|
+
}
|
|
20945
|
+
if (this._private__options.onDragCancel) {
|
|
20946
|
+
this._private__options.onDragCancel(level, this._private__levelOptions(level)?.price ?? 0);
|
|
20947
|
+
}
|
|
20948
|
+
}
|
|
20949
|
+
click() {
|
|
20950
|
+
const level = this._private__hoveredLevel;
|
|
20951
|
+
if (level === null) {
|
|
20952
|
+
return;
|
|
20953
|
+
}
|
|
20954
|
+
if (this._private__hoveredPart === 'close') {
|
|
20955
|
+
if (this._private__options.onClose) {
|
|
20956
|
+
this._private__options.onClose(level);
|
|
20957
|
+
}
|
|
20958
|
+
return;
|
|
20959
|
+
}
|
|
20960
|
+
const price = this._private__levelOptions(level)?.price;
|
|
20961
|
+
if (price !== undefined && this._private__options.onClick) {
|
|
20962
|
+
this._private__options.onClick(level, price);
|
|
20963
|
+
}
|
|
20964
|
+
}
|
|
20965
|
+
// ---- public API ----
|
|
20966
|
+
/**
|
|
20967
|
+
* Updates the group. Any subset of options can be passed; the levels given
|
|
20968
|
+
* are merged into the current ones, and a level set to `undefined` is
|
|
20969
|
+
* removed.
|
|
20970
|
+
*/
|
|
20971
|
+
applyOptions(options) {
|
|
20972
|
+
// A level under an active gesture owns its price. Callers feeding the
|
|
20973
|
+
// group from a socket re-send the old price several times a second, and
|
|
20974
|
+
// letting that through would pull the level out from under the cursor.
|
|
20975
|
+
const dragged = this._private__dragLevel;
|
|
20976
|
+
const draggedPrice = dragged === null ? undefined : this._private__levelOptions(dragged)?.price;
|
|
20977
|
+
const merged = { ...this._private__options, ...options };
|
|
20978
|
+
merged.entry = { ...this._private__options.entry, ...options.entry };
|
|
20979
|
+
merged.takeProfit = mergeLevel(this._private__options.takeProfit, options, 'takeProfit');
|
|
20980
|
+
merged.stopLoss = mergeLevel(this._private__options.stopLoss, options, 'stopLoss');
|
|
20981
|
+
this._private__options = merged;
|
|
20982
|
+
if (dragged !== null && draggedPrice !== undefined) {
|
|
20983
|
+
const level = this._private__levelOptions(dragged);
|
|
20984
|
+
if (level !== null) {
|
|
20985
|
+
level.price = draggedPrice;
|
|
20986
|
+
}
|
|
20987
|
+
}
|
|
20988
|
+
if (options.theme) {
|
|
20989
|
+
// Merged onto what is already applied, so two partial theme updates do
|
|
20990
|
+
// not undo each other.
|
|
20991
|
+
this._private__theme = { ...this._private__theme, ...options.theme };
|
|
20992
|
+
}
|
|
20993
|
+
this.requestUpdate();
|
|
20994
|
+
}
|
|
20995
|
+
/**
|
|
20996
|
+
* Moves one level to a new price, recomputing its text when the level has a
|
|
20997
|
+
* `formatText`.
|
|
20998
|
+
*
|
|
20999
|
+
* Ignored for a level the user is currently dragging — the gesture owns the
|
|
21000
|
+
* price until it reports back through
|
|
21001
|
+
* {@link PositionLineOptions.onDragEnd} or
|
|
21002
|
+
* {@link PositionLineOptions.onDragCancel}.
|
|
21003
|
+
*/
|
|
21004
|
+
setPrice(level, price) {
|
|
21005
|
+
if (this._private__dragLevel === level) {
|
|
21006
|
+
return;
|
|
21007
|
+
}
|
|
21008
|
+
this._private__applyPrice(level, price);
|
|
21009
|
+
}
|
|
21010
|
+
/**
|
|
21011
|
+
* Replaces one level's label text. Use this for text that changes on its own
|
|
21012
|
+
* — a ticking P&L on a fixed entry — rather than with the price.
|
|
21013
|
+
*/
|
|
21014
|
+
setText(level, text) {
|
|
21015
|
+
const options = this._private__levelOptions(level);
|
|
21016
|
+
if (options === null) {
|
|
21017
|
+
return;
|
|
21018
|
+
}
|
|
21019
|
+
options.text = text;
|
|
21020
|
+
this.requestUpdate();
|
|
21021
|
+
}
|
|
21022
|
+
/**
|
|
21023
|
+
* The current price of each level that is present.
|
|
21024
|
+
*/
|
|
21025
|
+
prices() {
|
|
21026
|
+
const result = {};
|
|
21027
|
+
for (const level of positionLineLevels) {
|
|
21028
|
+
const options = this._private__levelOptions(level);
|
|
21029
|
+
if (options !== null) {
|
|
21030
|
+
result[level] = options.price;
|
|
21031
|
+
}
|
|
21032
|
+
}
|
|
21033
|
+
return result;
|
|
21034
|
+
}
|
|
21035
|
+
options() {
|
|
21036
|
+
return this._private__options;
|
|
21037
|
+
}
|
|
21038
|
+
/**
|
|
21039
|
+
* Removes the group from the chart and releases its share of the input
|
|
21040
|
+
* controller.
|
|
21041
|
+
*/
|
|
21042
|
+
remove() {
|
|
21043
|
+
releaseController(this._private__chart, this);
|
|
21044
|
+
this._private__series.detachPrimitive(this);
|
|
21045
|
+
}
|
|
21046
|
+
requestUpdate() {
|
|
21047
|
+
if (this._private__requestUpdate) {
|
|
21048
|
+
this._private__requestUpdate();
|
|
21049
|
+
}
|
|
21050
|
+
}
|
|
21051
|
+
/** Reads back the label the price axis should draw for a level. */
|
|
21052
|
+
_private__axisLabel(level) {
|
|
21053
|
+
const options = this._private__levelOptions(level);
|
|
21054
|
+
if (options === null || !isFinite(options.price) ||
|
|
21055
|
+
this._private__options.visible === false || options.axisLabelVisible === false) {
|
|
21056
|
+
return null;
|
|
21057
|
+
}
|
|
21058
|
+
const y = this._private__series.priceToCoordinate(options.price);
|
|
21059
|
+
if (y === null) {
|
|
21060
|
+
return null;
|
|
21061
|
+
}
|
|
21062
|
+
const color = options.color ?? positionLineDefaultColors[level];
|
|
21063
|
+
return {
|
|
21064
|
+
_internal_coordinate: y,
|
|
21065
|
+
_internal_text: this._private__series.priceFormatter().format(options.price),
|
|
21066
|
+
_internal_color: color,
|
|
21067
|
+
_internal_textColor: this._private__colorParser._internal_generateContrastColors(color)._internal_foreground,
|
|
21068
|
+
};
|
|
21069
|
+
}
|
|
21070
|
+
_private__levelOptions(level) {
|
|
21071
|
+
const options = level === 'entry' ? this._private__options.entry : this._private__options[level];
|
|
21072
|
+
return options ?? null;
|
|
21073
|
+
}
|
|
21074
|
+
_private__applyPrice(level, price) {
|
|
21075
|
+
const options = this._private__levelOptions(level);
|
|
21076
|
+
if (options === null) {
|
|
21077
|
+
return;
|
|
21078
|
+
}
|
|
21079
|
+
options.price = price;
|
|
21080
|
+
if (options.formatText) {
|
|
21081
|
+
options.text = options.formatText(price);
|
|
21082
|
+
}
|
|
21083
|
+
this.requestUpdate();
|
|
21084
|
+
}
|
|
21085
|
+
_private__buildRendererData() {
|
|
21086
|
+
if (!this._private__attached || this._private__options.visible === false) {
|
|
21087
|
+
return null;
|
|
21088
|
+
}
|
|
21089
|
+
const levels = [];
|
|
21090
|
+
for (const level of positionLineLevels) {
|
|
21091
|
+
const options = this._private__levelOptions(level);
|
|
21092
|
+
// A level added through a patch can be missing its price until the
|
|
21093
|
+
// caller supplies one; drawing it would put it at NaN.
|
|
21094
|
+
if (options === null || !isFinite(options.price)) {
|
|
21095
|
+
continue;
|
|
21096
|
+
}
|
|
21097
|
+
const y = this._private__series.priceToCoordinate(options.price);
|
|
21098
|
+
if (y === null) {
|
|
21099
|
+
continue;
|
|
21100
|
+
}
|
|
21101
|
+
const isEntry = level === 'entry';
|
|
21102
|
+
levels.push({
|
|
21103
|
+
_internal_level: level,
|
|
21104
|
+
_internal_y: y,
|
|
21105
|
+
_internal_color: options.color ?? positionLineDefaultColors[level],
|
|
21106
|
+
_internal_text: options.formatText ? options.formatText(options.price) : options.text ?? '',
|
|
21107
|
+
_internal_badge: options.badge ?? '',
|
|
21108
|
+
_internal_badgeFilled: options.badgeFilled ?? isEntry,
|
|
21109
|
+
_internal_lineWidth: options.lineWidth ?? 1,
|
|
21110
|
+
_internal_lineStyle: options.lineStyle ?? (isEntry ? 0 /* LineStyle.Solid */ : 2 /* LineStyle.Dashed */),
|
|
21111
|
+
_internal_closable: options.closable ?? true,
|
|
21112
|
+
_internal_draggable: options.draggable ?? true,
|
|
21113
|
+
});
|
|
21114
|
+
}
|
|
21115
|
+
if (levels.length === 0) {
|
|
21116
|
+
return null;
|
|
21117
|
+
}
|
|
21118
|
+
const layout = this._private__chart.options()['layout'];
|
|
21119
|
+
const fontSize = this._private__theme.fontSize ?? layout.fontSize;
|
|
21120
|
+
return {
|
|
21121
|
+
_internal_levels: levels,
|
|
21122
|
+
_internal_background: this._private__theme.background ?? resolveBackgroundColor(layout.background),
|
|
21123
|
+
_internal_textColor: this._private__theme.textColor,
|
|
21124
|
+
_internal_badgeTextColor: this._private__theme.badgeTextColor,
|
|
21125
|
+
_internal_borderRadius: this._private__theme.borderRadius,
|
|
21126
|
+
_internal_borderWidth: this._private__theme.borderWidth,
|
|
21127
|
+
_internal_paddingX: this._private__theme.paddingX,
|
|
21128
|
+
_internal_paddingY: this._private__theme.paddingY,
|
|
21129
|
+
_internal_backgroundOpacity: this._private__theme.backgroundOpacity,
|
|
21130
|
+
_internal_font: makeFont(fontSize, this._private__theme.fontFamily ?? layout.fontFamily),
|
|
21131
|
+
_internal_fontSize: fontSize,
|
|
21132
|
+
_internal_connectorVisible: this._private__options.connectorVisible !== false && levels.length > 1,
|
|
21133
|
+
_internal_rightOffset: this._private__options.rightOffset ?? DEFAULT_RIGHT_OFFSET,
|
|
21134
|
+
_internal_hoveredLevel: this._private__hoveredLevel,
|
|
21135
|
+
_internal_hoveredPart: this._private__hoveredPart,
|
|
21136
|
+
};
|
|
21137
|
+
}
|
|
21138
|
+
}
|
|
21139
|
+
function mergeLevel(current, options, key) {
|
|
21140
|
+
if (!(key in options)) {
|
|
21141
|
+
return current;
|
|
21142
|
+
}
|
|
21143
|
+
const next = options[key];
|
|
21144
|
+
return next === undefined ? undefined : { ...current, ...next };
|
|
21145
|
+
}
|
|
21146
|
+
/**
|
|
21147
|
+
* Reads the chart's own background so the label sits on a solid surface. The
|
|
21148
|
+
* parameter is typed as the public {@link Background}: the production build
|
|
21149
|
+
* renames the members of structural types it does not see in the public API,
|
|
21150
|
+
* and an anonymous `{ color: string }` here resolved to nothing at runtime.
|
|
21151
|
+
*/
|
|
21152
|
+
function resolveBackgroundColor(background) {
|
|
21153
|
+
return background.type === "solid" /* ColorType.Solid */ ? background.color : background.topColor;
|
|
21154
|
+
}
|
|
21155
|
+
/**
|
|
21156
|
+
* Creates a position line group: entry, take profit and stop loss drawn as one
|
|
21157
|
+
* interactive unit.
|
|
21158
|
+
*
|
|
21159
|
+
* @param series - The series the levels are priced against.
|
|
21160
|
+
* @param chart - The chart the series belongs to.
|
|
21161
|
+
* @param options - The levels and their style.
|
|
21162
|
+
*
|
|
21163
|
+
* @example
|
|
21164
|
+
* ```js
|
|
21165
|
+
* const position = createPositionLine(candleSeries, chart, {
|
|
21166
|
+
* entry: { price: 4306.79, badge: '1', formatText: () => '+ 2.32 USD' },
|
|
21167
|
+
* takeProfit: { price: 4349.58, badge: '1', text: '+ 42.79 USD' },
|
|
21168
|
+
* stopLoss: { price: 4266.06, badge: '1', text: '- 40.73 USD' },
|
|
21169
|
+
* onClose: (level) => console.log('close', level),
|
|
21170
|
+
* onDragEnd: (level, price) => console.log(level, 'moved to', price),
|
|
21171
|
+
* });
|
|
21172
|
+
* ```
|
|
21173
|
+
*/
|
|
21174
|
+
function createPositionLine(series, chart, options) {
|
|
21175
|
+
return new PositionLine(chart, series, options);
|
|
21176
|
+
}
|
|
21177
|
+
|
|
20254
21178
|
/** Hit parts that drag the whole object rather than a single handle. */
|
|
20255
21179
|
const WHOLE_OBJECT_PARTS = new Set([
|
|
20256
21180
|
'line',
|
|
@@ -21130,13 +22054,6 @@
|
|
|
21130
22054
|
// Clean up references
|
|
21131
22055
|
this._private__primitive = null;
|
|
21132
22056
|
this._private__paneView = null;
|
|
21133
|
-
// Force chart update by triggering a resize
|
|
21134
|
-
const chartElement = this._chart.chartElement?.();
|
|
21135
|
-
if (chartElement) {
|
|
21136
|
-
const currentWidth = chartElement.clientWidth;
|
|
21137
|
-
const currentHeight = chartElement.clientHeight;
|
|
21138
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
21139
|
-
}
|
|
21140
22057
|
}
|
|
21141
22058
|
if (this._private__trendOptions.onRemove) {
|
|
21142
22059
|
this._private__trendOptions.onRemove();
|
|
@@ -21409,12 +22326,6 @@
|
|
|
21409
22326
|
this._series.detachPrimitive(this._private__primitive);
|
|
21410
22327
|
this._private__primitive = null;
|
|
21411
22328
|
this._private__paneView = null;
|
|
21412
|
-
const chartElement = this._chart.chartElement?.();
|
|
21413
|
-
if (chartElement) {
|
|
21414
|
-
const currentWidth = chartElement.clientWidth;
|
|
21415
|
-
const currentHeight = chartElement.clientHeight;
|
|
21416
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
21417
|
-
}
|
|
21418
22329
|
}
|
|
21419
22330
|
if (this._private__hLineOptions.onRemove) {
|
|
21420
22331
|
this._private__hLineOptions.onRemove();
|
|
@@ -23253,12 +24164,6 @@
|
|
|
23253
24164
|
this._series.detachPrimitive(this._private__primitive);
|
|
23254
24165
|
this._private__primitive = null;
|
|
23255
24166
|
this._private__paneView = null;
|
|
23256
|
-
const chartElement = this._chart.chartElement?.();
|
|
23257
|
-
if (chartElement) {
|
|
23258
|
-
const currentWidth = chartElement.clientWidth;
|
|
23259
|
-
const currentHeight = chartElement.clientHeight;
|
|
23260
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
23261
|
-
}
|
|
23262
24167
|
}
|
|
23263
24168
|
if (this._private__vLineOptions.onRemove) {
|
|
23264
24169
|
this._private__vLineOptions.onRemove();
|
|
@@ -23911,12 +24816,6 @@
|
|
|
23911
24816
|
this._series.detachPrimitive(this._private__primitive);
|
|
23912
24817
|
this._private__primitive = null;
|
|
23913
24818
|
this._private__paneView = null;
|
|
23914
|
-
const chartElement = this._chart.chartElement?.();
|
|
23915
|
-
if (chartElement) {
|
|
23916
|
-
const currentWidth = chartElement.clientWidth;
|
|
23917
|
-
const currentHeight = chartElement.clientHeight;
|
|
23918
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
23919
|
-
}
|
|
23920
24819
|
}
|
|
23921
24820
|
if (this._private__drawingManager) {
|
|
23922
24821
|
this._private__drawingManager._internal_destroy();
|
|
@@ -23932,12 +24831,6 @@
|
|
|
23932
24831
|
this._series.detachPrimitive(this._private__primitive);
|
|
23933
24832
|
this._private__primitive = null;
|
|
23934
24833
|
this._private__paneView = null;
|
|
23935
|
-
const chartElement = this._chart.chartElement?.();
|
|
23936
|
-
if (chartElement) {
|
|
23937
|
-
const currentWidth = chartElement.clientWidth;
|
|
23938
|
-
const currentHeight = chartElement.clientHeight;
|
|
23939
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
23940
|
-
}
|
|
23941
24834
|
}
|
|
23942
24835
|
if (this._private__fibOptions.onRemove) {
|
|
23943
24836
|
this._private__fibOptions.onRemove();
|
|
@@ -24365,12 +25258,6 @@
|
|
|
24365
25258
|
this._series.detachPrimitive(this._private__primitive);
|
|
24366
25259
|
this._private__primitive = null;
|
|
24367
25260
|
this._private__paneView = null;
|
|
24368
|
-
const chartElement = this._chart.chartElement?.();
|
|
24369
|
-
if (chartElement) {
|
|
24370
|
-
const currentWidth = chartElement.clientWidth;
|
|
24371
|
-
const currentHeight = chartElement.clientHeight;
|
|
24372
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
24373
|
-
}
|
|
24374
25261
|
}
|
|
24375
25262
|
}
|
|
24376
25263
|
}
|
|
@@ -24812,12 +25699,6 @@
|
|
|
24812
25699
|
this._series.detachPrimitive(this._private__primitive);
|
|
24813
25700
|
this._private__primitive = null;
|
|
24814
25701
|
this._private__paneView = null;
|
|
24815
|
-
const chartElement = this._chart.chartElement?.();
|
|
24816
|
-
if (chartElement) {
|
|
24817
|
-
const currentWidth = chartElement.clientWidth;
|
|
24818
|
-
const currentHeight = chartElement.clientHeight;
|
|
24819
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
24820
|
-
}
|
|
24821
25702
|
}
|
|
24822
25703
|
if (this._private__fanOptions.onRemove) {
|
|
24823
25704
|
this._private__fanOptions.onRemove();
|
|
@@ -25358,12 +26239,6 @@
|
|
|
25358
26239
|
this._series.detachPrimitive(this._private__primitive);
|
|
25359
26240
|
this._private__primitive = null;
|
|
25360
26241
|
this._private__paneView = null;
|
|
25361
|
-
const chartElement = this._chart.chartElement?.();
|
|
25362
|
-
if (chartElement) {
|
|
25363
|
-
const currentWidth = chartElement.clientWidth;
|
|
25364
|
-
const currentHeight = chartElement.clientHeight;
|
|
25365
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
25366
|
-
}
|
|
25367
26242
|
}
|
|
25368
26243
|
if (this._private__rectOptions.onRemove) {
|
|
25369
26244
|
this._private__rectOptions.onRemove();
|
|
@@ -25824,12 +26699,6 @@
|
|
|
25824
26699
|
this._series.detachPrimitive(this._private__primitive);
|
|
25825
26700
|
this._private__primitive = null;
|
|
25826
26701
|
this._private__paneView = null;
|
|
25827
|
-
const chartElement = this._chart.chartElement?.();
|
|
25828
|
-
if (chartElement) {
|
|
25829
|
-
const currentWidth = chartElement.clientWidth;
|
|
25830
|
-
const currentHeight = chartElement.clientHeight;
|
|
25831
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
25832
|
-
}
|
|
25833
26702
|
}
|
|
25834
26703
|
if (this._private__circleOptions.onRemove) {
|
|
25835
26704
|
this._private__circleOptions.onRemove();
|
|
@@ -26379,12 +27248,6 @@
|
|
|
26379
27248
|
this._series.detachPrimitive(this._private__primitive);
|
|
26380
27249
|
this._private__primitive = null;
|
|
26381
27250
|
this._private__paneView = null;
|
|
26382
|
-
const chartElement = this._chart.chartElement?.();
|
|
26383
|
-
if (chartElement) {
|
|
26384
|
-
const currentWidth = chartElement.clientWidth;
|
|
26385
|
-
const currentHeight = chartElement.clientHeight;
|
|
26386
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
26387
|
-
}
|
|
26388
27251
|
}
|
|
26389
27252
|
if (this._private__arrowOptions.onRemove) {
|
|
26390
27253
|
this._private__arrowOptions.onRemove();
|
|
@@ -27245,12 +28108,6 @@
|
|
|
27245
28108
|
this._series.detachPrimitive(this._private__primitive);
|
|
27246
28109
|
this._private__primitive = null;
|
|
27247
28110
|
this._private__paneView = null;
|
|
27248
|
-
const chartElement = this._chart.chartElement?.();
|
|
27249
|
-
if (chartElement) {
|
|
27250
|
-
const currentWidth = chartElement.clientWidth;
|
|
27251
|
-
const currentHeight = chartElement.clientHeight;
|
|
27252
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
27253
|
-
}
|
|
27254
28111
|
}
|
|
27255
28112
|
if (this._private__textOptions.onRemove) {
|
|
27256
28113
|
this._private__textOptions.onRemove();
|
|
@@ -27833,12 +28690,6 @@
|
|
|
27833
28690
|
this._series.detachPrimitive(this._private__primitive);
|
|
27834
28691
|
this._private__primitive = null;
|
|
27835
28692
|
this._private__paneView = null;
|
|
27836
|
-
const chartElement = this._chart.chartElement?.();
|
|
27837
|
-
if (chartElement) {
|
|
27838
|
-
const currentWidth = chartElement.clientWidth;
|
|
27839
|
-
const currentHeight = chartElement.clientHeight;
|
|
27840
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
27841
|
-
}
|
|
27842
28693
|
}
|
|
27843
28694
|
if (this._private__drawingManager) {
|
|
27844
28695
|
this._private__drawingManager._internal_destroy();
|
|
@@ -28694,12 +29545,6 @@
|
|
|
28694
29545
|
this._series.detachPrimitive(this._private__primitive);
|
|
28695
29546
|
this._private__primitive = null;
|
|
28696
29547
|
this._private__paneView = null;
|
|
28697
|
-
const chartElement = this._chart.chartElement?.();
|
|
28698
|
-
if (chartElement) {
|
|
28699
|
-
const currentWidth = chartElement.clientWidth;
|
|
28700
|
-
const currentHeight = chartElement.clientHeight;
|
|
28701
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
28702
|
-
}
|
|
28703
29548
|
}
|
|
28704
29549
|
if (this._private__positionOptions.onRemove) {
|
|
28705
29550
|
this._private__positionOptions.onRemove();
|
|
@@ -29042,12 +29887,6 @@
|
|
|
29042
29887
|
this._series.detachPrimitive(this._private__primitive);
|
|
29043
29888
|
this._private__primitive = null;
|
|
29044
29889
|
this._private__paneView = null;
|
|
29045
|
-
const chartElement = this._chart.chartElement?.();
|
|
29046
|
-
if (chartElement) {
|
|
29047
|
-
const currentWidth = chartElement.clientWidth;
|
|
29048
|
-
const currentHeight = chartElement.clientHeight;
|
|
29049
|
-
this._chart.resize(currentWidth, currentHeight);
|
|
29050
|
-
}
|
|
29051
29890
|
}
|
|
29052
29891
|
if (this._private__ganttOptions.onRemove) {
|
|
29053
29892
|
this._private__ganttOptions.onRemove();
|
|
@@ -29058,6 +29897,8 @@
|
|
|
29058
29897
|
return new GanttTool(context, options);
|
|
29059
29898
|
}
|
|
29060
29899
|
|
|
29900
|
+
/** Undo steps kept, each a full snapshot of every drawing. */
|
|
29901
|
+
const UNDO_HISTORY_LIMIT = 100;
|
|
29061
29902
|
/**
|
|
29062
29903
|
* Central manager for all drawing tools - TradingView style
|
|
29063
29904
|
* Handles multiple tools, selection, deletion, and keyboard shortcuts
|
|
@@ -29080,6 +29921,8 @@
|
|
|
29080
29921
|
this._private__pointerDownHandler = null;
|
|
29081
29922
|
this._private__pointerUpHandler = null;
|
|
29082
29923
|
this._private__pointerCancelHandler = null;
|
|
29924
|
+
this._private__lostCaptureHandler = null;
|
|
29925
|
+
this._private__windowPointerUpHandler = null;
|
|
29083
29926
|
this._private__pointerId = null;
|
|
29084
29927
|
/** A press that grabbed a tool already selected it; its click must not undo that. */
|
|
29085
29928
|
this._private__gestureHandled = false;
|
|
@@ -29245,6 +30088,9 @@
|
|
|
29245
30088
|
return;
|
|
29246
30089
|
}
|
|
29247
30090
|
this._private__past.push(this._private__committed);
|
|
30091
|
+
if (this._private__past.length > UNDO_HISTORY_LIMIT) {
|
|
30092
|
+
this._private__past.shift();
|
|
30093
|
+
}
|
|
29248
30094
|
this._private__future = [];
|
|
29249
30095
|
this._private__committed = this.serializeAll();
|
|
29250
30096
|
}
|
|
@@ -29624,8 +30470,12 @@
|
|
|
29624
30470
|
this._private__completionCheck = null;
|
|
29625
30471
|
}
|
|
29626
30472
|
this._private__drawingCompleted._internal_destroy();
|
|
30473
|
+
this._private__selectionChanged._internal_destroy();
|
|
29627
30474
|
this.clearAll();
|
|
29628
30475
|
this._private__detachEventHandlers();
|
|
30476
|
+
this._private__past = [];
|
|
30477
|
+
this._private__future = [];
|
|
30478
|
+
this._private__committed = [];
|
|
29629
30479
|
}
|
|
29630
30480
|
_private__attachEventHandlers() {
|
|
29631
30481
|
// Click handler for selection
|
|
@@ -29645,9 +30495,15 @@
|
|
|
29645
30495
|
this._private__pointerDownHandler = (event) => this._private__handlePointerDown(event);
|
|
29646
30496
|
this._private__pointerUpHandler = (event) => this._private__handlePointerUp(event);
|
|
29647
30497
|
this._private__pointerCancelHandler = (event) => this._private__handlePointerUp(event);
|
|
30498
|
+
this._private__lostCaptureHandler = (event) => this._private__handleLostPointerCapture(event);
|
|
29648
30499
|
chartElement.addEventListener('pointerdown', this._private__pointerDownHandler);
|
|
29649
30500
|
chartElement.addEventListener('pointerup', this._private__pointerUpHandler);
|
|
29650
30501
|
chartElement.addEventListener('pointercancel', this._private__pointerCancelHandler);
|
|
30502
|
+
chartElement.addEventListener('lostpointercapture', this._private__lostCaptureHandler);
|
|
30503
|
+
}
|
|
30504
|
+
if (isRunningOnClientSide) {
|
|
30505
|
+
this._private__windowPointerUpHandler = (event) => this._private__handlePointerUp(event);
|
|
30506
|
+
window.addEventListener('pointerup', this._private__windowPointerUpHandler);
|
|
29651
30507
|
}
|
|
29652
30508
|
// Keyboard handler for shortcuts
|
|
29653
30509
|
if (this._private__options.enableKeyboardShortcuts) {
|
|
@@ -29680,6 +30536,14 @@
|
|
|
29680
30536
|
chartElement.removeEventListener('pointercancel', this._private__pointerCancelHandler);
|
|
29681
30537
|
this._private__pointerCancelHandler = null;
|
|
29682
30538
|
}
|
|
30539
|
+
if (this._private__lostCaptureHandler) {
|
|
30540
|
+
chartElement.removeEventListener('lostpointercapture', this._private__lostCaptureHandler);
|
|
30541
|
+
this._private__lostCaptureHandler = null;
|
|
30542
|
+
}
|
|
30543
|
+
}
|
|
30544
|
+
if (this._private__windowPointerUpHandler) {
|
|
30545
|
+
window.removeEventListener('pointerup', this._private__windowPointerUpHandler);
|
|
30546
|
+
this._private__windowPointerUpHandler = null;
|
|
29683
30547
|
}
|
|
29684
30548
|
if (this._private__keyboardHandler) {
|
|
29685
30549
|
document.removeEventListener('keydown', this._private__keyboardHandler);
|
|
@@ -29805,7 +30669,12 @@
|
|
|
29805
30669
|
this._private__gestureHandled = true;
|
|
29806
30670
|
this._private__draggedToolId = toolId;
|
|
29807
30671
|
this._private__pointerId = event.pointerId;
|
|
29808
|
-
|
|
30672
|
+
try {
|
|
30673
|
+
chartElement.setPointerCapture(event.pointerId);
|
|
30674
|
+
}
|
|
30675
|
+
catch (error) {
|
|
30676
|
+
this._private__pointerId = null;
|
|
30677
|
+
}
|
|
29809
30678
|
tool.startDrag(endpoint, anchor);
|
|
29810
30679
|
// Panning is stopped by disabling it for the gesture, not by preventing
|
|
29811
30680
|
// the event: preventDefault on pointerdown suppresses the compatibility
|
|
@@ -29819,17 +30688,32 @@
|
|
|
29819
30688
|
if (event && this._private__pointerId !== null && event.pointerId !== this._private__pointerId) {
|
|
29820
30689
|
return;
|
|
29821
30690
|
}
|
|
29822
|
-
const
|
|
29823
|
-
|
|
29824
|
-
|
|
29825
|
-
}
|
|
29826
|
-
this._private__releasePointer();
|
|
29827
|
-
this._private__enableChartInteractions();
|
|
30691
|
+
const draggedToolId = this._private__draggedToolId;
|
|
30692
|
+
// Cleared first: anything below can throw, and a half-finished release
|
|
30693
|
+
// would leave the manager dragging forever.
|
|
29828
30694
|
this._private__isDragging = false;
|
|
29829
30695
|
this._private__draggedToolId = null;
|
|
30696
|
+
try {
|
|
30697
|
+
const tool = this._private__tools.get(draggedToolId);
|
|
30698
|
+
if (tool) {
|
|
30699
|
+
tool.stopDrag();
|
|
30700
|
+
}
|
|
30701
|
+
}
|
|
30702
|
+
finally {
|
|
30703
|
+
this._private__releasePointer();
|
|
30704
|
+
this._private__enableChartInteractions();
|
|
30705
|
+
}
|
|
29830
30706
|
// A finished drag is one step, not one per frame
|
|
29831
30707
|
this.recordChange();
|
|
29832
30708
|
}
|
|
30709
|
+
/** Ends the drag when capture is lost without a pointerup ever arriving. */
|
|
30710
|
+
_private__handleLostPointerCapture(event) {
|
|
30711
|
+
if (this._private__pointerId !== null && event.pointerId !== this._private__pointerId) {
|
|
30712
|
+
return;
|
|
30713
|
+
}
|
|
30714
|
+
this._private__pointerId = null;
|
|
30715
|
+
this._private__handlePointerUp();
|
|
30716
|
+
}
|
|
29833
30717
|
/**
|
|
29834
30718
|
* Pane element of a tool's series. The engine asserts the series has a pane,
|
|
29835
30719
|
* so the lookup is guarded.
|
|
@@ -29880,9 +30764,11 @@
|
|
|
29880
30764
|
return;
|
|
29881
30765
|
}
|
|
29882
30766
|
const options = this._private__chart.options();
|
|
30767
|
+
// `options()` returns the live object and `applyOptions` merges into it
|
|
30768
|
+
// in place, so a snapshot by reference restores all-false.
|
|
29883
30769
|
this._private__interactionsBeforeDrag = {
|
|
29884
|
-
_internal_handleScroll: options.handleScroll,
|
|
29885
|
-
_internal_handleScale: options.handleScale,
|
|
30770
|
+
_internal_handleScroll: clone(options.handleScroll),
|
|
30771
|
+
_internal_handleScale: clone(options.handleScale),
|
|
29886
30772
|
};
|
|
29887
30773
|
this._private__chart.applyOptions({
|
|
29888
30774
|
handleScroll: false,
|
|
@@ -29968,7 +30854,7 @@
|
|
|
29968
30854
|
* Returns the current version as a string. For example `'1.0.0'`.
|
|
29969
30855
|
*/
|
|
29970
30856
|
function version() {
|
|
29971
|
-
return "2.
|
|
30857
|
+
return "2.3.0-dev+202609220108";
|
|
29972
30858
|
}
|
|
29973
30859
|
|
|
29974
30860
|
var PipsendChartsModule = /*#__PURE__*/Object.freeze({
|
|
@@ -30004,6 +30890,7 @@
|
|
|
30004
30890
|
PitchforkTool: PitchforkTool,
|
|
30005
30891
|
PolygonTool: PolygonTool,
|
|
30006
30892
|
PositionCalculator: PositionCalculator,
|
|
30893
|
+
PositionLine: PositionLine,
|
|
30007
30894
|
PositionMarkersManager: PositionMarkersManager,
|
|
30008
30895
|
PositionTool: PositionTool,
|
|
30009
30896
|
get PriceLineSource () { return PriceLineSource; },
|
|
@@ -30061,6 +30948,7 @@
|
|
|
30061
30948
|
createParallelChannelTool: createParallelChannelTool,
|
|
30062
30949
|
createPitchforkTool: createPitchforkTool,
|
|
30063
30950
|
createPolygonTool: createPolygonTool,
|
|
30951
|
+
createPositionLine: createPositionLine,
|
|
30064
30952
|
createPositionMarkers: createPositionMarkers,
|
|
30065
30953
|
createPositionTool: createPositionTool,
|
|
30066
30954
|
createRectangleTool: createRectangleTool,
|