@pipsend/charts 2.3.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/pipsend-charts.development.mjs +201 -14
- package/dist/pipsend-charts.production.mjs +2 -2
- package/dist/pipsend-charts.standalone.development.js +201 -14
- package/dist/pipsend-charts.standalone.development.mjs +201 -14
- package/dist/pipsend-charts.standalone.production.js +2 -2
- package/dist/pipsend-charts.standalone.production.mjs +2 -2
- package/dist/typings.d.ts +14 -0
- package/package.json +1 -83
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* @license
|
|
3
|
-
* Pipsend Charts v2.3.
|
|
3
|
+
* Pipsend Charts v2.3.1
|
|
4
4
|
* Copyright (c) 2026 Pipsend
|
|
5
5
|
* Licensed under MIT License
|
|
6
6
|
* Built on TradingView Lightweight Charts™ (Apache 2.0)
|
|
@@ -19747,6 +19747,8 @@
|
|
|
19747
19747
|
this._private__pointerId = null;
|
|
19748
19748
|
this._private__pressOrigin = null;
|
|
19749
19749
|
this._private__pressedLine = null;
|
|
19750
|
+
/** Distance from the pointer to the dragged line when the press landed. */
|
|
19751
|
+
this._private__grabOffset = 0;
|
|
19750
19752
|
/** Latest pointer position in pane coordinates, or null when off the panes. */
|
|
19751
19753
|
this._private__point = null;
|
|
19752
19754
|
this._private__paneIndex = undefined;
|
|
@@ -19824,6 +19826,7 @@
|
|
|
19824
19826
|
this._private__pointerId = event.pointerId;
|
|
19825
19827
|
this._private__element.setPointerCapture(event.pointerId);
|
|
19826
19828
|
line.beginDrag();
|
|
19829
|
+
this._private__grabOffset = this._private__measureGrabOffset(line, event);
|
|
19827
19830
|
this._private__applyCursor();
|
|
19828
19831
|
event.preventDefault();
|
|
19829
19832
|
event.stopPropagation();
|
|
@@ -19858,6 +19861,7 @@
|
|
|
19858
19861
|
Math.abs(event.clientY - origin._internal_y) > CLICK_TOLERANCE_PX);
|
|
19859
19862
|
if (dragged !== null) {
|
|
19860
19863
|
this._private__dragging = null;
|
|
19864
|
+
this._private__grabOffset = 0;
|
|
19861
19865
|
if (moved) {
|
|
19862
19866
|
dragged.commitDrag();
|
|
19863
19867
|
}
|
|
@@ -19885,6 +19889,7 @@
|
|
|
19885
19889
|
}
|
|
19886
19890
|
const line = this._private__dragging;
|
|
19887
19891
|
this._private__dragging = null;
|
|
19892
|
+
this._private__grabOffset = 0;
|
|
19888
19893
|
this._private__releasePointer();
|
|
19889
19894
|
line.cancelDrag();
|
|
19890
19895
|
this._private__applyCursor();
|
|
@@ -19914,16 +19919,38 @@
|
|
|
19914
19919
|
// as the pointer leaves the pane canvas — over the price axis, past the
|
|
19915
19920
|
// chart's edge — and a drag driven from it freezes there while the user
|
|
19916
19921
|
// is still moving.
|
|
19922
|
+
const paneY = this._private__paneY(line, event);
|
|
19923
|
+
return paneY === null ? null : line.priceAt(paneY - this._private__grabOffset);
|
|
19924
|
+
}
|
|
19925
|
+
/** The pointer's position inside the line's pane, in pane coordinates. */
|
|
19926
|
+
_private__paneY(line, event) {
|
|
19917
19927
|
const pane = this._private__chart.panes()[line.paneIndex()];
|
|
19918
19928
|
const paneElement = pane ? pane.getHTMLElement() : null;
|
|
19919
19929
|
if (paneElement) {
|
|
19920
|
-
return
|
|
19930
|
+
return event.clientY - paneElement.getBoundingClientRect().top;
|
|
19921
19931
|
}
|
|
19922
19932
|
if (this._private__point !== null && this._private__paneIndex === line.paneIndex()) {
|
|
19923
|
-
return
|
|
19933
|
+
return this._private__point._internal_y;
|
|
19924
19934
|
}
|
|
19925
19935
|
return null;
|
|
19926
19936
|
}
|
|
19937
|
+
/**
|
|
19938
|
+
* Keeps the grab point: a line picked up by the edge of its label moves with
|
|
19939
|
+
* the pointer rather than centring itself on it. Ignored past a sane
|
|
19940
|
+
* distance, so a bad reading cannot send the line off on the first move.
|
|
19941
|
+
*/
|
|
19942
|
+
_private__measureGrabOffset(line, event) {
|
|
19943
|
+
if (!line.dragAnchorY) {
|
|
19944
|
+
return 0;
|
|
19945
|
+
}
|
|
19946
|
+
const anchor = line.dragAnchorY();
|
|
19947
|
+
const paneY = this._private__paneY(line, event);
|
|
19948
|
+
if (anchor === null || paneY === null) {
|
|
19949
|
+
return 0;
|
|
19950
|
+
}
|
|
19951
|
+
const offset = paneY - anchor;
|
|
19952
|
+
return Math.abs(offset) > 40 /* Constants.MaxGrabOffsetPx */ ? 0 : offset;
|
|
19953
|
+
}
|
|
19927
19954
|
/**
|
|
19928
19955
|
* Hit ids are either a bare line id or `id#part`, which is how a line with
|
|
19929
19956
|
* several targets reports which one was hit.
|
|
@@ -20479,6 +20506,126 @@
|
|
|
20479
20506
|
return new InteractiveLineManager(chart, series);
|
|
20480
20507
|
}
|
|
20481
20508
|
|
|
20509
|
+
/**
|
|
20510
|
+
* Fans out labels that land on top of each other.
|
|
20511
|
+
*
|
|
20512
|
+
* Every position line on a chart shares one of these. Two positions opened at
|
|
20513
|
+
* the same price draw their labels at the same coordinate, and the one behind
|
|
20514
|
+
* becomes unreachable — the engine resolves the hit to whichever primitive it
|
|
20515
|
+
* reaches first, and that is always the same one. Nudging each collision down
|
|
20516
|
+
* leaves every label a strip of its own to be grabbed by.
|
|
20517
|
+
*
|
|
20518
|
+
* The line itself never moves: it stays at the price it belongs to, and only
|
|
20519
|
+
* the label is offset.
|
|
20520
|
+
*/
|
|
20521
|
+
class PositionLineLabelLayout {
|
|
20522
|
+
constructor() {
|
|
20523
|
+
/** Registration order. It is what keeps the fan from reshuffling per frame. */
|
|
20524
|
+
this._private__order = [];
|
|
20525
|
+
this._private__claims = new Map();
|
|
20526
|
+
this._private__offsets = new Map();
|
|
20527
|
+
this._private__invalidated = true;
|
|
20528
|
+
}
|
|
20529
|
+
_internal_register(ownerId) {
|
|
20530
|
+
if (!this._private__claims.has(ownerId)) {
|
|
20531
|
+
this._private__order.push(ownerId);
|
|
20532
|
+
this._private__claims.set(ownerId, []);
|
|
20533
|
+
this._private__invalidated = true;
|
|
20534
|
+
}
|
|
20535
|
+
}
|
|
20536
|
+
_internal_unregister(ownerId) {
|
|
20537
|
+
const index = this._private__order.indexOf(ownerId);
|
|
20538
|
+
if (index !== -1) {
|
|
20539
|
+
this._private__order.splice(index, 1);
|
|
20540
|
+
}
|
|
20541
|
+
this._private__claims.delete(ownerId);
|
|
20542
|
+
this._private__invalidated = true;
|
|
20543
|
+
}
|
|
20544
|
+
_internal_isEmpty() {
|
|
20545
|
+
return this._private__order.length === 0;
|
|
20546
|
+
}
|
|
20547
|
+
/**
|
|
20548
|
+
* Publishes where an owner's labels want to sit. Called while each line
|
|
20549
|
+
* rebuilds its renderer data, so a fan can be one frame behind while prices
|
|
20550
|
+
* move — which is invisible, and cheaper than forcing a second pass.
|
|
20551
|
+
*/
|
|
20552
|
+
_internal_setClaims(ownerId, claims) {
|
|
20553
|
+
this._private__claims.set(ownerId, claims);
|
|
20554
|
+
this._private__invalidated = true;
|
|
20555
|
+
}
|
|
20556
|
+
/** How far down a label has to move to clear the ones already placed. */
|
|
20557
|
+
_internal_offsetFor(ownerId, level) {
|
|
20558
|
+
if (this._private__invalidated) {
|
|
20559
|
+
this._private__recalculate();
|
|
20560
|
+
}
|
|
20561
|
+
return this._private__offsets.get(key(ownerId, level)) ?? 0;
|
|
20562
|
+
}
|
|
20563
|
+
_private__recalculate() {
|
|
20564
|
+
const offsets = new Map();
|
|
20565
|
+
const placed = [];
|
|
20566
|
+
for (const ownerId of this._private__order) {
|
|
20567
|
+
for (const claim of this._private__claims.get(ownerId) ?? []) {
|
|
20568
|
+
const gap = minGap(claim._internal_height);
|
|
20569
|
+
const maxOffset = claim._internal_height;
|
|
20570
|
+
// Moved down to the first coordinate that clears everything already
|
|
20571
|
+
// placed, and no further. Two levels whose own prices nearly part
|
|
20572
|
+
// them only need the few pixels they are short of — stepping by a
|
|
20573
|
+
// full gap each time is what made the fan look flung open.
|
|
20574
|
+
let y = claim._internal_y;
|
|
20575
|
+
for (const other of [...placed].sort(byY)) {
|
|
20576
|
+
if (Math.abs(y - other._internal_y) < gap) {
|
|
20577
|
+
y = other._internal_y + gap;
|
|
20578
|
+
}
|
|
20579
|
+
}
|
|
20580
|
+
let offset = y - claim._internal_y;
|
|
20581
|
+
if (offset > maxOffset) {
|
|
20582
|
+
// More labels than there is room to fan. The rest stack, which is
|
|
20583
|
+
// no worse than before and keeps the pile from running off.
|
|
20584
|
+
offset = 0;
|
|
20585
|
+
}
|
|
20586
|
+
placed.push({ _internal_y: claim._internal_y + offset, _internal_height: claim._internal_height });
|
|
20587
|
+
offsets.set(key(ownerId, claim._internal_level), offset);
|
|
20588
|
+
}
|
|
20589
|
+
}
|
|
20590
|
+
this._private__offsets = offsets;
|
|
20591
|
+
this._private__invalidated = false;
|
|
20592
|
+
}
|
|
20593
|
+
}
|
|
20594
|
+
function key(ownerId, level) {
|
|
20595
|
+
return `${ownerId}:${level}`;
|
|
20596
|
+
}
|
|
20597
|
+
/**
|
|
20598
|
+
* The strip a label keeps to itself. Comfortably clickable, and no wider —
|
|
20599
|
+
* every pixel here is a pixel the label sits away from its own line.
|
|
20600
|
+
*/
|
|
20601
|
+
function minGap(height) {
|
|
20602
|
+
return Math.max(9, Math.round(height * 0.4));
|
|
20603
|
+
}
|
|
20604
|
+
function byY(a, b) {
|
|
20605
|
+
return a._internal_y - b._internal_y;
|
|
20606
|
+
}
|
|
20607
|
+
const layouts = new WeakMap();
|
|
20608
|
+
/** The layout is shared by every position line on the same chart. */
|
|
20609
|
+
function acquireLabelLayout(chart, ownerId) {
|
|
20610
|
+
let layout = layouts.get(chart);
|
|
20611
|
+
if (!layout) {
|
|
20612
|
+
layout = new PositionLineLabelLayout();
|
|
20613
|
+
layouts.set(chart, layout);
|
|
20614
|
+
}
|
|
20615
|
+
layout._internal_register(ownerId);
|
|
20616
|
+
return layout;
|
|
20617
|
+
}
|
|
20618
|
+
function releaseLabelLayout(chart, ownerId) {
|
|
20619
|
+
const layout = layouts.get(chart);
|
|
20620
|
+
if (!layout) {
|
|
20621
|
+
return;
|
|
20622
|
+
}
|
|
20623
|
+
layout._internal_unregister(ownerId);
|
|
20624
|
+
if (layout._internal_isEmpty()) {
|
|
20625
|
+
layouts.delete(chart);
|
|
20626
|
+
}
|
|
20627
|
+
}
|
|
20628
|
+
|
|
20482
20629
|
const positionLineDefaultColors = {
|
|
20483
20630
|
entry: '#2962FF',
|
|
20484
20631
|
takeProfit: '#26A17B',
|
|
@@ -20511,12 +20658,12 @@
|
|
|
20511
20658
|
// line, so the narrowest target is tested first.
|
|
20512
20659
|
for (const layout of this._private__layouts) {
|
|
20513
20660
|
if (layout._internal_closable && x >= layout._internal_closeLeft && x <= layout._internal_closeRight &&
|
|
20514
|
-
Math.abs(y - layout.
|
|
20661
|
+
Math.abs(y - layout._internal_labelY) <= layout._internal_height / 2) {
|
|
20515
20662
|
return { _internal_level: layout._internal_level, _internal_part: 'close' };
|
|
20516
20663
|
}
|
|
20517
20664
|
}
|
|
20518
20665
|
for (const layout of this._private__layouts) {
|
|
20519
|
-
if (x >= layout._internal_pillLeft && x <= layout._internal_pillRight && Math.abs(y - layout.
|
|
20666
|
+
if (x >= layout._internal_pillLeft && x <= layout._internal_pillRight && Math.abs(y - layout._internal_labelY) <= layout._internal_height / 2) {
|
|
20520
20667
|
return { _internal_level: layout._internal_level, _internal_part: 'body' };
|
|
20521
20668
|
}
|
|
20522
20669
|
}
|
|
@@ -20566,7 +20713,8 @@
|
|
|
20566
20713
|
const pillWidth = badgeWidth + labelWidth + closeWidth;
|
|
20567
20714
|
const pillRight = Math.max(pillWidth, lineEnd - 8 /* Constants.LabelGap */);
|
|
20568
20715
|
const pillLeft = pillRight - pillWidth;
|
|
20569
|
-
const
|
|
20716
|
+
const labelY = level._internal_y + level._internal_labelOffsetY;
|
|
20717
|
+
const top = labelY - height / 2;
|
|
20570
20718
|
this._private__drawLineSegments(scope, level, pillLeft, pillRight, lineEnd);
|
|
20571
20719
|
this._private__drawPill(scope, data, level, { _internal_left: pillLeft, _internal_top: top, _internal_width: pillWidth, _internal_height: height });
|
|
20572
20720
|
let cellLeft = pillLeft;
|
|
@@ -20579,7 +20727,7 @@
|
|
|
20579
20727
|
ctx.scale(hpr, vpr);
|
|
20580
20728
|
ctx.textAlign = 'center';
|
|
20581
20729
|
ctx.fillStyle = data._internal_textColor ?? level._internal_color;
|
|
20582
|
-
ctx.fillText(level._internal_text, cellLeft + labelWidth / 2,
|
|
20730
|
+
ctx.fillText(level._internal_text, cellLeft + labelWidth / 2, labelY);
|
|
20583
20731
|
ctx.restore();
|
|
20584
20732
|
cellLeft += labelWidth;
|
|
20585
20733
|
if (level._internal_closable) {
|
|
@@ -20589,6 +20737,7 @@
|
|
|
20589
20737
|
return {
|
|
20590
20738
|
_internal_level: level._internal_level,
|
|
20591
20739
|
_internal_y: level._internal_y,
|
|
20740
|
+
_internal_labelY: labelY,
|
|
20592
20741
|
_internal_height: height,
|
|
20593
20742
|
_internal_pillLeft: pillLeft,
|
|
20594
20743
|
_internal_pillRight: pillRight,
|
|
@@ -20607,12 +20756,20 @@
|
|
|
20607
20756
|
ctx.lineWidth = lineWidth;
|
|
20608
20757
|
setLineStyle(ctx, level._internal_lineStyle);
|
|
20609
20758
|
ctx.beginPath();
|
|
20610
|
-
|
|
20611
|
-
|
|
20612
|
-
|
|
20613
|
-
ctx.moveTo(
|
|
20759
|
+
if (level._internal_labelOffsetY !== 0) {
|
|
20760
|
+
// The label has been fanned off its line, so breaking the line for it
|
|
20761
|
+
// would leave a gap with nothing in it.
|
|
20762
|
+
ctx.moveTo(0, y);
|
|
20614
20763
|
ctx.lineTo(Math.round(lineEnd * hpr), y);
|
|
20615
20764
|
}
|
|
20765
|
+
else {
|
|
20766
|
+
ctx.moveTo(0, y);
|
|
20767
|
+
ctx.lineTo(Math.round(Math.max(0, pillLeft - 8 /* Constants.LabelGap */) * hpr), y);
|
|
20768
|
+
if (lineEnd > pillRight + 8 /* Constants.LabelGap */) {
|
|
20769
|
+
ctx.moveTo(Math.round((pillRight + 8 /* Constants.LabelGap */) * hpr), y);
|
|
20770
|
+
ctx.lineTo(Math.round(lineEnd * hpr), y);
|
|
20771
|
+
}
|
|
20772
|
+
}
|
|
20616
20773
|
ctx.stroke();
|
|
20617
20774
|
ctx.restore();
|
|
20618
20775
|
}
|
|
@@ -20807,6 +20964,7 @@
|
|
|
20807
20964
|
this._private__theme = { ...positionLineThemeDefaults, ...options.theme };
|
|
20808
20965
|
this._private__paneViews = [this._private__paneView];
|
|
20809
20966
|
this._private__axisViews = positionLineLevels.map((level) => new PositionLineAxisView(() => this._private__axisLabel(level)));
|
|
20967
|
+
this._private__labelLayout = acquireLabelLayout(this._private__chart, this._private__id);
|
|
20810
20968
|
this._private__series.attachPrimitive(this);
|
|
20811
20969
|
acquireController(this._private__chart)?._internal_register(this);
|
|
20812
20970
|
}
|
|
@@ -20893,6 +21051,23 @@
|
|
|
20893
21051
|
const price = this._private__series.coordinateToPrice(paneY);
|
|
20894
21052
|
return price === null ? null : price;
|
|
20895
21053
|
}
|
|
21054
|
+
/**
|
|
21055
|
+
* Where the level being dragged actually sits. The controller measures the
|
|
21056
|
+
* pointer against it so the level moves by what the pointer moved, instead
|
|
21057
|
+
* of jumping to sit under it — which matters most when the label has been
|
|
21058
|
+
* fanned away from its own line.
|
|
21059
|
+
*/
|
|
21060
|
+
dragAnchorY() {
|
|
21061
|
+
if (this._private__dragLevel === null) {
|
|
21062
|
+
return null;
|
|
21063
|
+
}
|
|
21064
|
+
const price = this._private__levelOptions(this._private__dragLevel)?.price;
|
|
21065
|
+
if (price === undefined || !isFinite(price)) {
|
|
21066
|
+
return null;
|
|
21067
|
+
}
|
|
21068
|
+
const y = this._private__series.priceToCoordinate(price);
|
|
21069
|
+
return y === null ? null : y;
|
|
21070
|
+
}
|
|
20896
21071
|
beginDrag() {
|
|
20897
21072
|
this._private__dragLevel = this._private__hoveredLevel;
|
|
20898
21073
|
if (this._private__dragLevel === null) {
|
|
@@ -21041,6 +21216,7 @@
|
|
|
21041
21216
|
*/
|
|
21042
21217
|
remove() {
|
|
21043
21218
|
releaseController(this._private__chart, this);
|
|
21219
|
+
releaseLabelLayout(this._private__chart, this._private__id);
|
|
21044
21220
|
this._private__series.detachPrimitive(this);
|
|
21045
21221
|
}
|
|
21046
21222
|
requestUpdate() {
|
|
@@ -21086,7 +21262,11 @@
|
|
|
21086
21262
|
if (!this._private__attached || this._private__options.visible === false) {
|
|
21087
21263
|
return null;
|
|
21088
21264
|
}
|
|
21265
|
+
const layout = this._private__chart.options()['layout'];
|
|
21266
|
+
const fontSize = this._private__theme.fontSize ?? layout.fontSize;
|
|
21267
|
+
const labelHeight = fontSize + this._private__theme.paddingY * 2;
|
|
21089
21268
|
const levels = [];
|
|
21269
|
+
const claims = [];
|
|
21090
21270
|
for (const level of positionLineLevels) {
|
|
21091
21271
|
const options = this._private__levelOptions(level);
|
|
21092
21272
|
// A level added through a patch can be missing its price until the
|
|
@@ -21099,9 +21279,11 @@
|
|
|
21099
21279
|
continue;
|
|
21100
21280
|
}
|
|
21101
21281
|
const isEntry = level === 'entry';
|
|
21282
|
+
claims.push({ _internal_level: level, _internal_y: y, _internal_height: labelHeight });
|
|
21102
21283
|
levels.push({
|
|
21103
21284
|
_internal_level: level,
|
|
21104
21285
|
_internal_y: y,
|
|
21286
|
+
_internal_labelOffsetY: 0,
|
|
21105
21287
|
_internal_color: options.color ?? positionLineDefaultColors[level],
|
|
21106
21288
|
_internal_text: options.formatText ? options.formatText(options.price) : options.text ?? '',
|
|
21107
21289
|
_internal_badge: options.badge ?? '',
|
|
@@ -21113,10 +21295,15 @@
|
|
|
21113
21295
|
});
|
|
21114
21296
|
}
|
|
21115
21297
|
if (levels.length === 0) {
|
|
21298
|
+
this._private__labelLayout._internal_setClaims(this._private__id, []);
|
|
21116
21299
|
return null;
|
|
21117
21300
|
}
|
|
21118
|
-
|
|
21119
|
-
|
|
21301
|
+
// Published before the offsets are read so every line on the chart —
|
|
21302
|
+
// positions, pending orders, alerts — is fanned against the same set.
|
|
21303
|
+
this._private__labelLayout._internal_setClaims(this._private__id, claims);
|
|
21304
|
+
for (const level of levels) {
|
|
21305
|
+
level._internal_labelOffsetY = this._private__labelLayout._internal_offsetFor(this._private__id, level._internal_level);
|
|
21306
|
+
}
|
|
21120
21307
|
return {
|
|
21121
21308
|
_internal_levels: levels,
|
|
21122
21309
|
_internal_background: this._private__theme.background ?? resolveBackgroundColor(layout.background),
|
|
@@ -30854,7 +31041,7 @@
|
|
|
30854
31041
|
* Returns the current version as a string. For example `'1.0.0'`.
|
|
30855
31042
|
*/
|
|
30856
31043
|
function version() {
|
|
30857
|
-
return "2.3.
|
|
31044
|
+
return "2.3.1";
|
|
30858
31045
|
}
|
|
30859
31046
|
|
|
30860
31047
|
var PipsendChartsModule = /*#__PURE__*/Object.freeze({
|