@pipsend/charts 2.3.12 → 2.3.13

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.
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @license
3
- * Pipsend Charts v2.3.12
3
+ * Pipsend Charts v2.3.13-dev+202609280001
4
4
  * Copyright (c) 2026 Pipsend
5
5
  * Licensed under MIT License
6
6
  * Built on TradingView Lightweight Charts™ (Apache 2.0)
@@ -14677,6 +14677,8 @@
14677
14677
  return size(originalSize, 0.7);
14678
14678
  case 'tag':
14679
14679
  return size(originalSize, 1);
14680
+ case 'xcross':
14681
+ return size(originalSize, 0.7);
14680
14682
  }
14681
14683
  }
14682
14684
  function calculateShapeHeight(barSpacing) {
@@ -14877,6 +14879,27 @@
14877
14879
  y >= textY - halfHeight && y <= textY + halfHeight;
14878
14880
  }
14879
14881
 
14882
+ function drawXCross(ctx, coords, size) {
14883
+ const crossSize = shapeSize('xcross', size);
14884
+ const halfSize = ((crossSize - 1) / 2) * coords._internal_pixelRatio;
14885
+ ctx.save();
14886
+ ctx.strokeStyle = ctx.fillStyle;
14887
+ ctx.lineWidth = Math.max(1, Math.round(1.5 * coords._internal_pixelRatio));
14888
+ ctx.lineCap = 'round';
14889
+ ctx.beginPath();
14890
+ ctx.moveTo(coords._internal_x - halfSize, coords._internal_y - halfSize);
14891
+ ctx.lineTo(coords._internal_x + halfSize, coords._internal_y + halfSize);
14892
+ ctx.moveTo(coords._internal_x + halfSize, coords._internal_y - halfSize);
14893
+ ctx.lineTo(coords._internal_x - halfSize, coords._internal_y + halfSize);
14894
+ ctx.stroke();
14895
+ ctx.restore();
14896
+ }
14897
+ function hitTestXCross(centerX, centerY, size, x, y) {
14898
+ const crossSize = shapeSize('xcross', size);
14899
+ const tolerance = 2 + crossSize / 2;
14900
+ return Math.abs(x - centerX) <= tolerance && Math.abs(y - centerY) <= tolerance;
14901
+ }
14902
+
14880
14903
  class SeriesMarkersRenderer {
14881
14904
  constructor() {
14882
14905
  this._private__data = null;
@@ -15020,6 +15043,9 @@
15020
15043
  case 'square':
15021
15044
  drawSquare(ctx, coordinates, item._internal_size);
15022
15045
  return;
15046
+ case 'xcross':
15047
+ drawXCross(ctx, coordinates, item._internal_size);
15048
+ return;
15023
15049
  case 'tag':
15024
15050
  // Handled by drawTagItem, which needs both pixel ratios and the text.
15025
15051
  return;
@@ -15048,6 +15074,8 @@
15048
15074
  return hitTestCircle(item._internal_x, item._internal_y, item._internal_size, x, y);
15049
15075
  case 'square':
15050
15076
  return hitTestSquare(item._internal_x, item._internal_y, item._internal_size, x, y);
15077
+ case 'xcross':
15078
+ return hitTestXCross(item._internal_x, item._internal_y, item._internal_size, x, y);
15051
15079
  case 'tag':
15052
15080
  return false;
15053
15081
  }
@@ -26127,17 +26155,19 @@
26127
26155
  // Draw border
26128
26156
  const borderColor = this._private__rectOptions.borderColor;
26129
26157
  const borderWidth = this._private__isSelected ? this._private__rectOptions.borderWidth + 1 : this._private__rectOptions.borderWidth;
26130
- ctx.strokeStyle = borderColor;
26131
- ctx.lineWidth = borderWidth * horizontalPixelRatio;
26132
- // Set border style
26133
- if (this._private__rectOptions.borderStyle === 'dashed') {
26134
- ctx.setLineDash([10 * horizontalPixelRatio, 5 * horizontalPixelRatio]);
26135
- }
26136
- else if (this._private__rectOptions.borderStyle === 'dotted') {
26137
- ctx.setLineDash([2 * horizontalPixelRatio, 3 * horizontalPixelRatio]);
26158
+ if (borderWidth > 0) {
26159
+ ctx.strokeStyle = borderColor;
26160
+ ctx.lineWidth = borderWidth * horizontalPixelRatio;
26161
+ // Set border style
26162
+ if (this._private__rectOptions.borderStyle === 'dashed') {
26163
+ ctx.setLineDash([10 * horizontalPixelRatio, 5 * horizontalPixelRatio]);
26164
+ }
26165
+ else if (this._private__rectOptions.borderStyle === 'dotted') {
26166
+ ctx.setLineDash([2 * horizontalPixelRatio, 3 * horizontalPixelRatio]);
26167
+ }
26168
+ ctx.strokeRect(minX, minY, width, height);
26169
+ ctx.setLineDash([]);
26138
26170
  }
26139
- ctx.strokeRect(minX, minY, width, height);
26140
- ctx.setLineDash([]);
26141
26171
  // Draw corner handles if selected
26142
26172
  if ((this._private__isSelected || this._private__isHovered) && !this._private__isDrawing && !this.isLocked()) {
26143
26173
  this._private__drawCornerHandles(ctx, minX, minY, maxX, maxY, horizontalPixelRatio, borderColor);
@@ -26165,17 +26195,20 @@
26165
26195
  }
26166
26196
  _private__drawLabel(ctx, minX, minY, maxX, maxY, horizontalPixelRatio, verticalPixelRatio, color) {
26167
26197
  const padding = 6 * horizontalPixelRatio;
26168
- const fontSize = 12 * horizontalPixelRatio;
26198
+ const fontSize = (this._private__rectOptions.labelFontSize ?? 12) * horizontalPixelRatio;
26169
26199
  ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
26170
26200
  const textMetrics = ctx.measureText(this._private__rectOptions.labelText);
26171
26201
  const textWidth = textMetrics.width;
26172
26202
  const textHeight = fontSize;
26203
+ const middleY = (minY + maxY) / 2 - textHeight / 2 - padding;
26204
+ // A box extended far past the pane keeps right-hand and centred labels on screen.
26205
+ const visibleMaxX = Math.min(maxX, ctx.canvas.width);
26173
26206
  // Calculate label position based on labelPosition option
26174
26207
  let labelX = minX + padding;
26175
26208
  let labelY = minY + padding;
26176
26209
  switch (this._private__rectOptions.labelPosition) {
26177
26210
  case 'top-right':
26178
- labelX = maxX - textWidth - padding * 2;
26211
+ labelX = visibleMaxX - textWidth - padding * 2;
26179
26212
  labelY = minY + padding;
26180
26213
  break;
26181
26214
  case 'bottom-left':
@@ -26183,19 +26216,27 @@
26183
26216
  labelY = maxY - textHeight - padding * 2;
26184
26217
  break;
26185
26218
  case 'bottom-right':
26186
- labelX = maxX - textWidth - padding * 2;
26219
+ labelX = visibleMaxX - textWidth - padding * 2;
26187
26220
  labelY = maxY - textHeight - padding * 2;
26188
26221
  break;
26189
26222
  case 'center':
26190
- labelX = (minX + maxX) / 2 - textWidth / 2 - padding;
26191
- labelY = (minY + maxY) / 2 - textHeight / 2 - padding;
26223
+ labelX = (Math.max(minX, 0) + visibleMaxX) / 2 - textWidth / 2 - padding;
26224
+ labelY = middleY;
26225
+ break;
26226
+ case 'left':
26227
+ labelX = minX + padding;
26228
+ labelY = middleY;
26229
+ break;
26230
+ case 'right':
26231
+ labelX = visibleMaxX - textWidth - padding * 2;
26232
+ labelY = middleY;
26192
26233
  break;
26193
26234
  }
26194
26235
  // Draw background
26195
- ctx.fillStyle = color;
26236
+ ctx.fillStyle = this._private__rectOptions.labelBackgroundColor ?? color;
26196
26237
  ctx.fillRect(labelX, labelY, textWidth + padding * 2, textHeight + padding * 2);
26197
26238
  // Draw text
26198
- ctx.fillStyle = '#ffffff';
26239
+ ctx.fillStyle = this._private__rectOptions.labelColor ?? '#ffffff';
26199
26240
  ctx.textBaseline = 'top';
26200
26241
  ctx.fillText(this._private__rectOptions.labelText, labelX + padding, labelY + padding);
26201
26242
  }
@@ -26433,6 +26474,9 @@
26433
26474
  showLabel: this._private__rectOptions.showLabel,
26434
26475
  labelText: this._private__rectOptions.labelText,
26435
26476
  labelPosition: this._private__rectOptions.labelPosition,
26477
+ labelColor: this._private__rectOptions.labelColor,
26478
+ labelBackgroundColor: this._private__rectOptions.labelBackgroundColor,
26479
+ labelFontSize: this._private__rectOptions.labelFontSize,
26436
26480
  },
26437
26481
  };
26438
26482
  }
@@ -28037,6 +28081,9 @@
28037
28081
  borderWidth: 1,
28038
28082
  textAlign: 'left',
28039
28083
  maxWidth: 200,
28084
+ anchor: 'top-left',
28085
+ pointer: false,
28086
+ borderRadius: 0,
28040
28087
  ...options,
28041
28088
  };
28042
28089
  this._registerOptions(this._private__textOptions);
@@ -28188,18 +28235,59 @@
28188
28235
  const py = this._series.priceToCoordinate(price) ?? 0;
28189
28236
  // Calculate text box dimensions
28190
28237
  const fontSize = this._private__textOptions.fontSize || 14;
28191
- const padding = this._private__textOptions.padding || 8;
28238
+ const padding = this._private__textOptions.padding ?? 8;
28192
28239
  const text = this._private__textOptions.text || '';
28193
28240
  // Rough estimate of text width (will be more accurate in canvas)
28194
28241
  const charWidth = fontSize * 0.6;
28195
28242
  const textWidth = Math.min(text.length * charWidth, this._private__textOptions.maxWidth || 200);
28196
- const textHeight = fontSize + padding * 2;
28197
- // Check if click is within text box bounds
28198
- const left = px;
28199
- const right = px + textWidth + padding * 2;
28200
- const top = py;
28201
- const bottom = py + textHeight;
28202
- return x >= left && x <= right && y >= top && y <= bottom;
28243
+ const boxWidth = textWidth + padding * 2;
28244
+ const boxHeight = fontSize + padding * 2;
28245
+ const tail = this._private__tailSize(boxHeight);
28246
+ const box = this._private__boxOrigin(px, py, boxWidth, boxHeight, tail);
28247
+ const left = box._internal_left - (this._private__textOptions.anchor === 'left' ? tail : 0);
28248
+ const right = box._internal_left + boxWidth + (this._private__textOptions.anchor === 'right' ? tail : 0);
28249
+ return x >= left && x <= right && y >= box._internal_top && y <= box._internal_top + boxHeight;
28250
+ }
28251
+ _private__tailSize(boxHeight) {
28252
+ const anchor = this._private__textOptions.anchor;
28253
+ return this._private__textOptions.pointer && (anchor === 'left' || anchor === 'right') ? Math.round(boxHeight * 0.35) : 0;
28254
+ }
28255
+ _private__boxOrigin(x, y, width, height, tail) {
28256
+ switch (this._private__textOptions.anchor) {
28257
+ case 'left':
28258
+ return { _internal_left: x + tail, _internal_top: y - height / 2 };
28259
+ case 'right':
28260
+ return { _internal_left: x - tail - width, _internal_top: y - height / 2 };
28261
+ case 'center':
28262
+ return { _internal_left: x - width / 2, _internal_top: y - height / 2 };
28263
+ default:
28264
+ return { _internal_left: x, _internal_top: y };
28265
+ }
28266
+ }
28267
+ // eslint-disable-next-line max-params
28268
+ _private__traceBox(ctx, left, top, width, height, tail, anchorX, anchorY, radius) {
28269
+ const r = Math.max(0, Math.min(radius, width / 2, height / 2));
28270
+ ctx.beginPath();
28271
+ ctx.moveTo(left + r, top);
28272
+ ctx.lineTo(left + width - r, top);
28273
+ ctx.arcTo(left + width, top, left + width, top + r, r);
28274
+ if (tail > 0 && this._private__textOptions.anchor === 'right') {
28275
+ ctx.lineTo(left + width, anchorY - tail);
28276
+ ctx.lineTo(anchorX, anchorY);
28277
+ ctx.lineTo(left + width, anchorY + tail);
28278
+ }
28279
+ ctx.lineTo(left + width, top + height - r);
28280
+ ctx.arcTo(left + width, top + height, left + width - r, top + height, r);
28281
+ ctx.lineTo(left + r, top + height);
28282
+ ctx.arcTo(left, top + height, left, top + height - r, r);
28283
+ if (tail > 0 && this._private__textOptions.anchor === 'left') {
28284
+ ctx.lineTo(left, anchorY + tail);
28285
+ ctx.lineTo(anchorX, anchorY);
28286
+ ctx.lineTo(left, anchorY - tail);
28287
+ }
28288
+ ctx.lineTo(left, top + r);
28289
+ ctx.arcTo(left, top, left + r, top, r);
28290
+ ctx.closePath();
28203
28291
  }
28204
28292
  hitTestEndpoint(x, y) {
28205
28293
  if (!this.isComplete())
@@ -28251,7 +28339,7 @@
28251
28339
  const x = (coordinateFromTime(this._chart, this._series, time) ?? 0) * horizontalPixelRatio;
28252
28340
  const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
28253
28341
  const fontSize = (this._private__textOptions.fontSize || 14) * horizontalPixelRatio;
28254
- const padding = (this._private__textOptions.padding || 8) * horizontalPixelRatio;
28342
+ const padding = (this._private__textOptions.padding ?? 8) * horizontalPixelRatio;
28255
28343
  const maxWidth = (this._private__textOptions.maxWidth || 200) * horizontalPixelRatio;
28256
28344
  // Set font
28257
28345
  ctx.font = `${fontSize}px ${this._private__textOptions.fontFamily || 'Arial'}`;
@@ -28283,32 +28371,39 @@
28283
28371
  const boxWidth = maxLineWidth + padding * 2;
28284
28372
  const lineHeight = fontSize * 1.2;
28285
28373
  const boxHeight = lines.length * lineHeight + padding * 2;
28374
+ const tail = this._private__tailSize(boxHeight);
28375
+ const { _internal_left: left, _internal_top: top } = this._private__boxOrigin(x, y, boxWidth, boxHeight, tail);
28376
+ const radius = (this._private__textOptions.borderRadius ?? 0) * horizontalPixelRatio;
28286
28377
  // Draw background
28287
- const bgOpacity = this._private__textOptions.backgroundOpacity || 0.8;
28378
+ const bgOpacity = this._private__textOptions.backgroundOpacity ?? 0.8;
28288
28379
  const bgColor = this._private__textOptions.backgroundColor || '#2962FF';
28289
- ctx.fillStyle = this._private__hexToRgba(bgColor, bgOpacity);
28290
- ctx.fillRect(x, y, boxWidth, boxHeight);
28380
+ if (bgOpacity > 0) {
28381
+ ctx.fillStyle = this._private__hexToRgba(bgColor, bgOpacity);
28382
+ this._private__traceBox(ctx, left, top, boxWidth, boxHeight, tail, x, y, radius);
28383
+ ctx.fill();
28384
+ }
28291
28385
  // Draw border
28292
28386
  if (this._private__isSelected || this._private__isHovered) {
28293
28387
  ctx.strokeStyle = this._private__textOptions.borderColor ?? '#FFFFFF';
28294
28388
  ctx.lineWidth = (this._private__textOptions.borderWidth || 1) * horizontalPixelRatio * (this._private__isSelected ? 2 : 1);
28295
- ctx.strokeRect(x, y, boxWidth, boxHeight);
28389
+ this._private__traceBox(ctx, left, top, boxWidth, boxHeight, tail, x, y, radius);
28390
+ ctx.stroke();
28296
28391
  }
28297
28392
  // Draw text
28298
28393
  ctx.fillStyle = this._private__textOptions.textColor || '#FFFFFF';
28299
28394
  for (let i = 0; i < lines.length; i++) {
28300
28395
  const line = lines[i];
28301
- let textX = x + padding;
28396
+ let textX = left + padding;
28302
28397
  // Handle text alignment
28303
28398
  if (this._private__textOptions.textAlign === 'center') {
28304
28399
  const metrics = ctx.measureText(line);
28305
- textX = x + (boxWidth - metrics.width) / 2;
28400
+ textX = left + (boxWidth - metrics.width) / 2;
28306
28401
  }
28307
28402
  else if (this._private__textOptions.textAlign === 'right') {
28308
28403
  const metrics = ctx.measureText(line);
28309
- textX = x + boxWidth - metrics.width - padding;
28404
+ textX = left + boxWidth - metrics.width - padding;
28310
28405
  }
28311
- const textY = y + padding + i * lineHeight;
28406
+ const textY = top + padding + i * lineHeight;
28312
28407
  ctx.fillText(line, textX, textY);
28313
28408
  }
28314
28409
  // Draw anchor point if selected
@@ -28352,6 +28447,9 @@
28352
28447
  borderWidth: this._private__textOptions.borderWidth,
28353
28448
  textAlign: this._private__textOptions.textAlign,
28354
28449
  maxWidth: this._private__textOptions.maxWidth,
28450
+ anchor: this._private__textOptions.anchor,
28451
+ pointer: this._private__textOptions.pointer,
28452
+ borderRadius: this._private__textOptions.borderRadius,
28355
28453
  },
28356
28454
  extra: {
28357
28455
  text: this._private__textOptions.text,
@@ -31139,7 +31237,7 @@
31139
31237
  * Returns the current version as a string. For example `'1.0.0'`.
31140
31238
  */
31141
31239
  function version() {
31142
- return "2.3.12";
31240
+ return "2.3.13-dev+202609280001";
31143
31241
  }
31144
31242
 
31145
31243
  var PipsendChartsModule = /*#__PURE__*/Object.freeze({
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @license
3
- * Pipsend Charts v2.3.12
3
+ * Pipsend Charts v2.3.13-dev+202609280001
4
4
  * Copyright (c) 2026 Pipsend
5
5
  * Licensed under MIT License
6
6
  * Built on TradingView Lightweight Charts™ (Apache 2.0)
@@ -14674,6 +14674,8 @@ function shapeSize(shape, originalSize) {
14674
14674
  return size(originalSize, 0.7);
14675
14675
  case 'tag':
14676
14676
  return size(originalSize, 1);
14677
+ case 'xcross':
14678
+ return size(originalSize, 0.7);
14677
14679
  }
14678
14680
  }
14679
14681
  function calculateShapeHeight(barSpacing) {
@@ -14874,6 +14876,27 @@ function hitTestText(textX, textY, textWidth, textHeight, x, y) {
14874
14876
  y >= textY - halfHeight && y <= textY + halfHeight;
14875
14877
  }
14876
14878
 
14879
+ function drawXCross(ctx, coords, size) {
14880
+ const crossSize = shapeSize('xcross', size);
14881
+ const halfSize = ((crossSize - 1) / 2) * coords._internal_pixelRatio;
14882
+ ctx.save();
14883
+ ctx.strokeStyle = ctx.fillStyle;
14884
+ ctx.lineWidth = Math.max(1, Math.round(1.5 * coords._internal_pixelRatio));
14885
+ ctx.lineCap = 'round';
14886
+ ctx.beginPath();
14887
+ ctx.moveTo(coords._internal_x - halfSize, coords._internal_y - halfSize);
14888
+ ctx.lineTo(coords._internal_x + halfSize, coords._internal_y + halfSize);
14889
+ ctx.moveTo(coords._internal_x + halfSize, coords._internal_y - halfSize);
14890
+ ctx.lineTo(coords._internal_x - halfSize, coords._internal_y + halfSize);
14891
+ ctx.stroke();
14892
+ ctx.restore();
14893
+ }
14894
+ function hitTestXCross(centerX, centerY, size, x, y) {
14895
+ const crossSize = shapeSize('xcross', size);
14896
+ const tolerance = 2 + crossSize / 2;
14897
+ return Math.abs(x - centerX) <= tolerance && Math.abs(y - centerY) <= tolerance;
14898
+ }
14899
+
14877
14900
  class SeriesMarkersRenderer {
14878
14901
  constructor() {
14879
14902
  this._private__data = null;
@@ -15017,6 +15040,9 @@ function drawShape(item, ctx, coordinates) {
15017
15040
  case 'square':
15018
15041
  drawSquare(ctx, coordinates, item._internal_size);
15019
15042
  return;
15043
+ case 'xcross':
15044
+ drawXCross(ctx, coordinates, item._internal_size);
15045
+ return;
15020
15046
  case 'tag':
15021
15047
  // Handled by drawTagItem, which needs both pixel ratios and the text.
15022
15048
  return;
@@ -15045,6 +15071,8 @@ function hitTestShape(item, x, y) {
15045
15071
  return hitTestCircle(item._internal_x, item._internal_y, item._internal_size, x, y);
15046
15072
  case 'square':
15047
15073
  return hitTestSquare(item._internal_x, item._internal_y, item._internal_size, x, y);
15074
+ case 'xcross':
15075
+ return hitTestXCross(item._internal_x, item._internal_y, item._internal_size, x, y);
15048
15076
  case 'tag':
15049
15077
  return false;
15050
15078
  }
@@ -26124,17 +26152,19 @@ class RectangleTool extends DrawingToolBase {
26124
26152
  // Draw border
26125
26153
  const borderColor = this._private__rectOptions.borderColor;
26126
26154
  const borderWidth = this._private__isSelected ? this._private__rectOptions.borderWidth + 1 : this._private__rectOptions.borderWidth;
26127
- ctx.strokeStyle = borderColor;
26128
- ctx.lineWidth = borderWidth * horizontalPixelRatio;
26129
- // Set border style
26130
- if (this._private__rectOptions.borderStyle === 'dashed') {
26131
- ctx.setLineDash([10 * horizontalPixelRatio, 5 * horizontalPixelRatio]);
26132
- }
26133
- else if (this._private__rectOptions.borderStyle === 'dotted') {
26134
- ctx.setLineDash([2 * horizontalPixelRatio, 3 * horizontalPixelRatio]);
26155
+ if (borderWidth > 0) {
26156
+ ctx.strokeStyle = borderColor;
26157
+ ctx.lineWidth = borderWidth * horizontalPixelRatio;
26158
+ // Set border style
26159
+ if (this._private__rectOptions.borderStyle === 'dashed') {
26160
+ ctx.setLineDash([10 * horizontalPixelRatio, 5 * horizontalPixelRatio]);
26161
+ }
26162
+ else if (this._private__rectOptions.borderStyle === 'dotted') {
26163
+ ctx.setLineDash([2 * horizontalPixelRatio, 3 * horizontalPixelRatio]);
26164
+ }
26165
+ ctx.strokeRect(minX, minY, width, height);
26166
+ ctx.setLineDash([]);
26135
26167
  }
26136
- ctx.strokeRect(minX, minY, width, height);
26137
- ctx.setLineDash([]);
26138
26168
  // Draw corner handles if selected
26139
26169
  if ((this._private__isSelected || this._private__isHovered) && !this._private__isDrawing && !this.isLocked()) {
26140
26170
  this._private__drawCornerHandles(ctx, minX, minY, maxX, maxY, horizontalPixelRatio, borderColor);
@@ -26162,17 +26192,20 @@ class RectangleTool extends DrawingToolBase {
26162
26192
  }
26163
26193
  _private__drawLabel(ctx, minX, minY, maxX, maxY, horizontalPixelRatio, verticalPixelRatio, color) {
26164
26194
  const padding = 6 * horizontalPixelRatio;
26165
- const fontSize = 12 * horizontalPixelRatio;
26195
+ const fontSize = (this._private__rectOptions.labelFontSize ?? 12) * horizontalPixelRatio;
26166
26196
  ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
26167
26197
  const textMetrics = ctx.measureText(this._private__rectOptions.labelText);
26168
26198
  const textWidth = textMetrics.width;
26169
26199
  const textHeight = fontSize;
26200
+ const middleY = (minY + maxY) / 2 - textHeight / 2 - padding;
26201
+ // A box extended far past the pane keeps right-hand and centred labels on screen.
26202
+ const visibleMaxX = Math.min(maxX, ctx.canvas.width);
26170
26203
  // Calculate label position based on labelPosition option
26171
26204
  let labelX = minX + padding;
26172
26205
  let labelY = minY + padding;
26173
26206
  switch (this._private__rectOptions.labelPosition) {
26174
26207
  case 'top-right':
26175
- labelX = maxX - textWidth - padding * 2;
26208
+ labelX = visibleMaxX - textWidth - padding * 2;
26176
26209
  labelY = minY + padding;
26177
26210
  break;
26178
26211
  case 'bottom-left':
@@ -26180,19 +26213,27 @@ class RectangleTool extends DrawingToolBase {
26180
26213
  labelY = maxY - textHeight - padding * 2;
26181
26214
  break;
26182
26215
  case 'bottom-right':
26183
- labelX = maxX - textWidth - padding * 2;
26216
+ labelX = visibleMaxX - textWidth - padding * 2;
26184
26217
  labelY = maxY - textHeight - padding * 2;
26185
26218
  break;
26186
26219
  case 'center':
26187
- labelX = (minX + maxX) / 2 - textWidth / 2 - padding;
26188
- labelY = (minY + maxY) / 2 - textHeight / 2 - padding;
26220
+ labelX = (Math.max(minX, 0) + visibleMaxX) / 2 - textWidth / 2 - padding;
26221
+ labelY = middleY;
26222
+ break;
26223
+ case 'left':
26224
+ labelX = minX + padding;
26225
+ labelY = middleY;
26226
+ break;
26227
+ case 'right':
26228
+ labelX = visibleMaxX - textWidth - padding * 2;
26229
+ labelY = middleY;
26189
26230
  break;
26190
26231
  }
26191
26232
  // Draw background
26192
- ctx.fillStyle = color;
26233
+ ctx.fillStyle = this._private__rectOptions.labelBackgroundColor ?? color;
26193
26234
  ctx.fillRect(labelX, labelY, textWidth + padding * 2, textHeight + padding * 2);
26194
26235
  // Draw text
26195
- ctx.fillStyle = '#ffffff';
26236
+ ctx.fillStyle = this._private__rectOptions.labelColor ?? '#ffffff';
26196
26237
  ctx.textBaseline = 'top';
26197
26238
  ctx.fillText(this._private__rectOptions.labelText, labelX + padding, labelY + padding);
26198
26239
  }
@@ -26430,6 +26471,9 @@ class RectangleTool extends DrawingToolBase {
26430
26471
  showLabel: this._private__rectOptions.showLabel,
26431
26472
  labelText: this._private__rectOptions.labelText,
26432
26473
  labelPosition: this._private__rectOptions.labelPosition,
26474
+ labelColor: this._private__rectOptions.labelColor,
26475
+ labelBackgroundColor: this._private__rectOptions.labelBackgroundColor,
26476
+ labelFontSize: this._private__rectOptions.labelFontSize,
26433
26477
  },
26434
26478
  };
26435
26479
  }
@@ -28034,6 +28078,9 @@ class TextTool extends DrawingToolBase {
28034
28078
  borderWidth: 1,
28035
28079
  textAlign: 'left',
28036
28080
  maxWidth: 200,
28081
+ anchor: 'top-left',
28082
+ pointer: false,
28083
+ borderRadius: 0,
28037
28084
  ...options,
28038
28085
  };
28039
28086
  this._registerOptions(this._private__textOptions);
@@ -28185,18 +28232,59 @@ class TextTool extends DrawingToolBase {
28185
28232
  const py = this._series.priceToCoordinate(price) ?? 0;
28186
28233
  // Calculate text box dimensions
28187
28234
  const fontSize = this._private__textOptions.fontSize || 14;
28188
- const padding = this._private__textOptions.padding || 8;
28235
+ const padding = this._private__textOptions.padding ?? 8;
28189
28236
  const text = this._private__textOptions.text || '';
28190
28237
  // Rough estimate of text width (will be more accurate in canvas)
28191
28238
  const charWidth = fontSize * 0.6;
28192
28239
  const textWidth = Math.min(text.length * charWidth, this._private__textOptions.maxWidth || 200);
28193
- const textHeight = fontSize + padding * 2;
28194
- // Check if click is within text box bounds
28195
- const left = px;
28196
- const right = px + textWidth + padding * 2;
28197
- const top = py;
28198
- const bottom = py + textHeight;
28199
- return x >= left && x <= right && y >= top && y <= bottom;
28240
+ const boxWidth = textWidth + padding * 2;
28241
+ const boxHeight = fontSize + padding * 2;
28242
+ const tail = this._private__tailSize(boxHeight);
28243
+ const box = this._private__boxOrigin(px, py, boxWidth, boxHeight, tail);
28244
+ const left = box._internal_left - (this._private__textOptions.anchor === 'left' ? tail : 0);
28245
+ const right = box._internal_left + boxWidth + (this._private__textOptions.anchor === 'right' ? tail : 0);
28246
+ return x >= left && x <= right && y >= box._internal_top && y <= box._internal_top + boxHeight;
28247
+ }
28248
+ _private__tailSize(boxHeight) {
28249
+ const anchor = this._private__textOptions.anchor;
28250
+ return this._private__textOptions.pointer && (anchor === 'left' || anchor === 'right') ? Math.round(boxHeight * 0.35) : 0;
28251
+ }
28252
+ _private__boxOrigin(x, y, width, height, tail) {
28253
+ switch (this._private__textOptions.anchor) {
28254
+ case 'left':
28255
+ return { _internal_left: x + tail, _internal_top: y - height / 2 };
28256
+ case 'right':
28257
+ return { _internal_left: x - tail - width, _internal_top: y - height / 2 };
28258
+ case 'center':
28259
+ return { _internal_left: x - width / 2, _internal_top: y - height / 2 };
28260
+ default:
28261
+ return { _internal_left: x, _internal_top: y };
28262
+ }
28263
+ }
28264
+ // eslint-disable-next-line max-params
28265
+ _private__traceBox(ctx, left, top, width, height, tail, anchorX, anchorY, radius) {
28266
+ const r = Math.max(0, Math.min(radius, width / 2, height / 2));
28267
+ ctx.beginPath();
28268
+ ctx.moveTo(left + r, top);
28269
+ ctx.lineTo(left + width - r, top);
28270
+ ctx.arcTo(left + width, top, left + width, top + r, r);
28271
+ if (tail > 0 && this._private__textOptions.anchor === 'right') {
28272
+ ctx.lineTo(left + width, anchorY - tail);
28273
+ ctx.lineTo(anchorX, anchorY);
28274
+ ctx.lineTo(left + width, anchorY + tail);
28275
+ }
28276
+ ctx.lineTo(left + width, top + height - r);
28277
+ ctx.arcTo(left + width, top + height, left + width - r, top + height, r);
28278
+ ctx.lineTo(left + r, top + height);
28279
+ ctx.arcTo(left, top + height, left, top + height - r, r);
28280
+ if (tail > 0 && this._private__textOptions.anchor === 'left') {
28281
+ ctx.lineTo(left, anchorY + tail);
28282
+ ctx.lineTo(anchorX, anchorY);
28283
+ ctx.lineTo(left, anchorY - tail);
28284
+ }
28285
+ ctx.lineTo(left, top + r);
28286
+ ctx.arcTo(left, top, left + r, top, r);
28287
+ ctx.closePath();
28200
28288
  }
28201
28289
  hitTestEndpoint(x, y) {
28202
28290
  if (!this.isComplete())
@@ -28248,7 +28336,7 @@ class TextTool extends DrawingToolBase {
28248
28336
  const x = (coordinateFromTime(this._chart, this._series, time) ?? 0) * horizontalPixelRatio;
28249
28337
  const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
28250
28338
  const fontSize = (this._private__textOptions.fontSize || 14) * horizontalPixelRatio;
28251
- const padding = (this._private__textOptions.padding || 8) * horizontalPixelRatio;
28339
+ const padding = (this._private__textOptions.padding ?? 8) * horizontalPixelRatio;
28252
28340
  const maxWidth = (this._private__textOptions.maxWidth || 200) * horizontalPixelRatio;
28253
28341
  // Set font
28254
28342
  ctx.font = `${fontSize}px ${this._private__textOptions.fontFamily || 'Arial'}`;
@@ -28280,32 +28368,39 @@ class TextTool extends DrawingToolBase {
28280
28368
  const boxWidth = maxLineWidth + padding * 2;
28281
28369
  const lineHeight = fontSize * 1.2;
28282
28370
  const boxHeight = lines.length * lineHeight + padding * 2;
28371
+ const tail = this._private__tailSize(boxHeight);
28372
+ const { _internal_left: left, _internal_top: top } = this._private__boxOrigin(x, y, boxWidth, boxHeight, tail);
28373
+ const radius = (this._private__textOptions.borderRadius ?? 0) * horizontalPixelRatio;
28283
28374
  // Draw background
28284
- const bgOpacity = this._private__textOptions.backgroundOpacity || 0.8;
28375
+ const bgOpacity = this._private__textOptions.backgroundOpacity ?? 0.8;
28285
28376
  const bgColor = this._private__textOptions.backgroundColor || '#2962FF';
28286
- ctx.fillStyle = this._private__hexToRgba(bgColor, bgOpacity);
28287
- ctx.fillRect(x, y, boxWidth, boxHeight);
28377
+ if (bgOpacity > 0) {
28378
+ ctx.fillStyle = this._private__hexToRgba(bgColor, bgOpacity);
28379
+ this._private__traceBox(ctx, left, top, boxWidth, boxHeight, tail, x, y, radius);
28380
+ ctx.fill();
28381
+ }
28288
28382
  // Draw border
28289
28383
  if (this._private__isSelected || this._private__isHovered) {
28290
28384
  ctx.strokeStyle = this._private__textOptions.borderColor ?? '#FFFFFF';
28291
28385
  ctx.lineWidth = (this._private__textOptions.borderWidth || 1) * horizontalPixelRatio * (this._private__isSelected ? 2 : 1);
28292
- ctx.strokeRect(x, y, boxWidth, boxHeight);
28386
+ this._private__traceBox(ctx, left, top, boxWidth, boxHeight, tail, x, y, radius);
28387
+ ctx.stroke();
28293
28388
  }
28294
28389
  // Draw text
28295
28390
  ctx.fillStyle = this._private__textOptions.textColor || '#FFFFFF';
28296
28391
  for (let i = 0; i < lines.length; i++) {
28297
28392
  const line = lines[i];
28298
- let textX = x + padding;
28393
+ let textX = left + padding;
28299
28394
  // Handle text alignment
28300
28395
  if (this._private__textOptions.textAlign === 'center') {
28301
28396
  const metrics = ctx.measureText(line);
28302
- textX = x + (boxWidth - metrics.width) / 2;
28397
+ textX = left + (boxWidth - metrics.width) / 2;
28303
28398
  }
28304
28399
  else if (this._private__textOptions.textAlign === 'right') {
28305
28400
  const metrics = ctx.measureText(line);
28306
- textX = x + boxWidth - metrics.width - padding;
28401
+ textX = left + boxWidth - metrics.width - padding;
28307
28402
  }
28308
- const textY = y + padding + i * lineHeight;
28403
+ const textY = top + padding + i * lineHeight;
28309
28404
  ctx.fillText(line, textX, textY);
28310
28405
  }
28311
28406
  // Draw anchor point if selected
@@ -28349,6 +28444,9 @@ class TextTool extends DrawingToolBase {
28349
28444
  borderWidth: this._private__textOptions.borderWidth,
28350
28445
  textAlign: this._private__textOptions.textAlign,
28351
28446
  maxWidth: this._private__textOptions.maxWidth,
28447
+ anchor: this._private__textOptions.anchor,
28448
+ pointer: this._private__textOptions.pointer,
28449
+ borderRadius: this._private__textOptions.borderRadius,
28352
28450
  },
28353
28451
  extra: {
28354
28452
  text: this._private__textOptions.text,
@@ -31136,7 +31234,7 @@ const customSeriesDefaultOptions = {
31136
31234
  * Returns the current version as a string. For example `'1.0.0'`.
31137
31235
  */
31138
31236
  function version() {
31139
- return "2.3.12";
31237
+ return "2.3.13-dev+202609280001";
31140
31238
  }
31141
31239
 
31142
31240
  export { areaSeries as AreaSeries, ArrowTool, barSeries as BarSeries, baselineSeries as BaselineSeries, BrushTool, candlestickSeries as CandlestickSeries, CircleTool, ColorType, CrossLineTool, CrosshairMode, DEFAULT_SESSIONS, DraggablePriceLine, DrawingToolsManager, EXTENDED_FIBONACCI_RATIOS, FibonacciCalculator, FibonacciExtensionTool, FibonacciFanTool, FibonacciTool, GanttTool, histogramSeries as HistogramSeries, HorizontalLineTool, InteractiveLineManager, LastPriceAnimationMode, lineSeries as LineSeries, LineStyle, LineType, MismatchDirection, ParallelChannelTool, PitchforkTool, PolygonTool, PositionCalculator, PositionLine, PositionMarkersManager, PositionTool, PriceLineSource, PriceScaleMode, RectangleTool, RulerCalculator, RulerTool, STANDARD_FIBONACCI_RATIOS, TextTool, TickMarkType, TrackingModeExitMode, TradingSessionsIndicator, TrendLineTool, VerticalLineTool, VolumeProfileIndicator, applyADX, applyATR, applyBollingerBands, applyCCI, applyDonchianChannels, applyEMA, applyHeikinAshi, applyIchimoku, applyKeltnerChannels, applyMACD, applyMFI, applyOBV, applyParabolicSAR, applyPivotPoints, applyRSI, applySMA, applyStochastic, applySuperTrend, applyVWAP, applyVolume, applyWMA, applyWilliamsR, calculateHeikinAshi, calculatePipConfig, coordinateFromTime, createArrowTool, createBrushTool, createChart, createChartEx, createCircleTool, createCrossLineTool, createFibonacciExtensionTool, createFibonacciFanTool, createFibonacciTool, createGanttTool, createHorizontalLineTool, createImageWatermark, createInteractiveLineManager, createOptionsChart, createParallelChannelTool, createPitchforkTool, createPolygonTool, createPositionLine, createPositionMarkers, createPositionTool, createRectangleTool, createRulerTool, createSeriesMarkers, createTextTool, createTextWatermark, createTradingLine, createTradingSessionsIndicator, createTrendLineTool, createUpDownMarkers, createVerticalLineTool, createVolumeProfileIndicator, createYieldCurveChart, customSeriesDefaultOptions, defaultHorzScaleBehavior, getPipConfig, hideSeriesPaint, isBusinessDay, isUTCTimestamp, setSeriesVolumes, setVolumeProfileData, timeFromCoordinate, version };