hyperprop-charting-library 0.1.146 → 0.1.148

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.
@@ -2036,8 +2036,8 @@ var BUILTIN_DONCHIAN_INDICATOR = {
2036
2036
  }
2037
2037
  };
2038
2038
  var computeVolumeProfile = (data, fromIndex, toIndex, rowCount, valueAreaPercent) => {
2039
- const from = Math.max(0, fromIndex);
2040
- const to = Math.min(data.length - 1, toIndex);
2039
+ const from = Math.max(0, Math.round(fromIndex));
2040
+ const to = Math.min(data.length - 1, Math.round(toIndex));
2041
2041
  if (to < from) return null;
2042
2042
  let low = Number.POSITIVE_INFINITY;
2043
2043
  let high = Number.NEGATIVE_INFINITY;
@@ -3896,6 +3896,9 @@ function createChart(element, options = {}) {
3896
3896
  pointValue: Number(drawing.pointValue) || 1,
3897
3897
  qtyPrecision: Number.isFinite(drawing.qtyPrecision) ? Math.max(0, Math.floor(Number(drawing.qtyPrecision))) : 0,
3898
3898
  fontSize: Math.max(6, Number(drawing.fontSize) || 14),
3899
+ profileRows: Math.max(4, Math.min(200, Math.round(Number(drawing.profileRows)) || 24)),
3900
+ profileValueAreaPercent: Math.max(10, Math.min(99, Number(drawing.profileValueAreaPercent) || 70)),
3901
+ profileWidthRatio: Math.max(0.1, Math.min(1, Number(drawing.profileWidthRatio) || 0.5)),
3899
3902
  ...drawing.label === void 0 ? {} : { label: drawing.label }
3900
3903
  };
3901
3904
  };
@@ -3929,6 +3932,9 @@ function createChart(element, options = {}) {
3929
3932
  pointValue: drawing.pointValue,
3930
3933
  qtyPrecision: drawing.qtyPrecision,
3931
3934
  fontSize: drawing.fontSize,
3935
+ profileRows: drawing.profileRows,
3936
+ profileValueAreaPercent: drawing.profileValueAreaPercent,
3937
+ profileWidthRatio: drawing.profileWidthRatio,
3932
3938
  ...drawing.label === void 0 ? {} : { label: drawing.label }
3933
3939
  });
3934
3940
  let tradeMarkers = [];
@@ -4255,6 +4261,8 @@ function createChart(element, options = {}) {
4255
4261
  let paneButtonRegions = [];
4256
4262
  let hoveredPaneId = null;
4257
4263
  let hoveredPaneButton = null;
4264
+ let legendRowRegions = [];
4265
+ let hoveredLegendId = null;
4258
4266
  let paneDividerDrag = null;
4259
4267
  let indicatorPaneActionHandler = null;
4260
4268
  let indicatorPaneHeightChangeHandler = null;
@@ -5551,6 +5559,85 @@ function createChart(element, options = {}) {
5551
5559
  ctx.closePath();
5552
5560
  ctx.stroke();
5553
5561
  };
5562
+ const INDICATOR_CONTROL_SIZE = 16;
5563
+ const INDICATOR_CONTROL_GAP = 4;
5564
+ const LEGEND_CONTROLS_WIDTH = (INDICATOR_CONTROL_SIZE + INDICATOR_CONTROL_GAP) * 4 + 8;
5565
+ const drawIndicatorControls = (indicatorId, indicatorType, indicatorVisible, startX, centerY) => {
5566
+ const labels = resolvedLabels;
5567
+ const actionList = [
5568
+ "visibility",
5569
+ "settings",
5570
+ "source",
5571
+ "remove"
5572
+ ];
5573
+ let buttonX = startX;
5574
+ for (const action of actionList) {
5575
+ const buttonY = centerY - INDICATOR_CONTROL_SIZE / 2;
5576
+ const hovered = hoveredPaneButton?.id === indicatorId && hoveredPaneButton.action === action;
5577
+ const iconColor = labels.indicatorTextColor;
5578
+ ctx.save();
5579
+ ctx.fillStyle = hovered ? "rgba(148,163,184,0.30)" : "rgba(148,163,184,0.12)";
5580
+ fillRoundedRect(Math.round(buttonX), Math.round(buttonY), INDICATOR_CONTROL_SIZE, INDICATOR_CONTROL_SIZE, 4);
5581
+ ctx.strokeStyle = iconColor;
5582
+ ctx.fillStyle = iconColor;
5583
+ ctx.globalAlpha = hovered ? 1 : 0.75;
5584
+ ctx.lineWidth = 1.2;
5585
+ ctx.setLineDash([]);
5586
+ const cx = buttonX + INDICATOR_CONTROL_SIZE / 2;
5587
+ const cy = centerY;
5588
+ if (action === "visibility") {
5589
+ ctx.beginPath();
5590
+ ctx.ellipse(cx, cy, 4.6, 3, 0, 0, Math.PI * 2);
5591
+ ctx.stroke();
5592
+ ctx.beginPath();
5593
+ ctx.arc(cx, cy, 1.3, 0, Math.PI * 2);
5594
+ ctx.fill();
5595
+ if (!indicatorVisible) {
5596
+ ctx.beginPath();
5597
+ ctx.moveTo(cx - 5.2, cy + 4.6);
5598
+ ctx.lineTo(cx + 5.2, cy - 4.6);
5599
+ ctx.stroke();
5600
+ }
5601
+ } else if (action === "settings") {
5602
+ ctx.beginPath();
5603
+ ctx.arc(cx, cy, 3.4, 0, Math.PI * 2);
5604
+ ctx.stroke();
5605
+ for (let toothIndex = 0; toothIndex < 8; toothIndex += 1) {
5606
+ const angle = Math.PI / 4 * toothIndex;
5607
+ ctx.beginPath();
5608
+ ctx.moveTo(cx + Math.cos(angle) * 3.4, cy + Math.sin(angle) * 3.4);
5609
+ ctx.lineTo(cx + Math.cos(angle) * 5.2, cy + Math.sin(angle) * 5.2);
5610
+ ctx.stroke();
5611
+ }
5612
+ ctx.beginPath();
5613
+ ctx.arc(cx, cy, 1.1, 0, Math.PI * 2);
5614
+ ctx.fill();
5615
+ } else if (action === "source") {
5616
+ const prevSourceFont = ctx.font;
5617
+ ctx.font = `bold 9px ${mergedOptions.fontFamily}`;
5618
+ drawText("{}", cx, cy + 0.5, "center", "middle", iconColor);
5619
+ ctx.font = prevSourceFont;
5620
+ } else {
5621
+ ctx.beginPath();
5622
+ ctx.moveTo(cx - 3.4, cy - 3.4);
5623
+ ctx.lineTo(cx + 3.4, cy + 3.4);
5624
+ ctx.moveTo(cx + 3.4, cy - 3.4);
5625
+ ctx.lineTo(cx - 3.4, cy + 3.4);
5626
+ ctx.stroke();
5627
+ }
5628
+ ctx.restore();
5629
+ paneButtonRegions.push({
5630
+ id: indicatorId,
5631
+ type: indicatorType,
5632
+ action,
5633
+ x: buttonX,
5634
+ y: buttonY,
5635
+ width: INDICATOR_CONTROL_SIZE,
5636
+ height: INDICATOR_CONTROL_SIZE
5637
+ });
5638
+ buttonX += INDICATOR_CONTROL_SIZE + INDICATOR_CONTROL_GAP;
5639
+ }
5640
+ };
5554
5641
  const drawMarkBadge = (x, y, radius, color, shape, label, textColor) => {
5555
5642
  ctx.save();
5556
5643
  ctx.fillStyle = color;
@@ -5631,6 +5718,8 @@ function createChart(element, options = {}) {
5631
5718
  orderDragRegions = [];
5632
5719
  alertRegions = [];
5633
5720
  markRegions = [];
5721
+ paneButtonRegions = [];
5722
+ legendRowRegions = [];
5634
5723
  crosshairPriceActionRegion = null;
5635
5724
  const pixelRatio = getPixelRatio();
5636
5725
  canvas.style.width = `${width}px`;
@@ -6635,6 +6724,81 @@ function createChart(element, options = {}) {
6635
6724
  handleAt(ax, ay, drawing.color);
6636
6725
  handleAt(bx, by, drawing.color);
6637
6726
  }
6727
+ } else if (drawing.type === "fixed-range-volume-profile") {
6728
+ const p0 = drawing.points[0];
6729
+ const p1 = drawing.points[1];
6730
+ if (p0 && p1 && data.length > 0) {
6731
+ const fromIndex = Math.max(0, Math.round(Math.min(p0.index, p1.index)));
6732
+ const toIndex = Math.min(data.length - 1, Math.round(Math.max(p0.index, p1.index)));
6733
+ const leftX = xFromDrawingPoint(p0.index <= p1.index ? p0 : p1) - candleSpacing / 2;
6734
+ const rightX = xFromDrawingPoint(p0.index <= p1.index ? p1 : p0) + candleSpacing / 2;
6735
+ const rangeWidth = Math.max(1, rightX - leftX);
6736
+ const rows = Math.max(4, Math.min(200, Math.round(drawing.profileRows) || 24));
6737
+ const valueAreaPercent = Math.max(10, Math.min(99, Number(drawing.profileValueAreaPercent) || 70));
6738
+ const profile = toIndex >= fromIndex ? computeVolumeProfile(data, fromIndex, toIndex, rows, valueAreaPercent) : null;
6739
+ ctx.save();
6740
+ ctx.globalAlpha = draft ? 0.5 : 1;
6741
+ ctx.fillStyle = hexToRgba(drawing.color, 0.06);
6742
+ ctx.fillRect(leftX, chartTop, rangeWidth, chartBottom - chartTop);
6743
+ ctx.strokeStyle = hexToRgba(drawing.color, 0.5);
6744
+ ctx.lineWidth = 1;
6745
+ ctx.setLineDash([4, 3]);
6746
+ ctx.beginPath();
6747
+ ctx.moveTo(crisp(leftX), chartTop);
6748
+ ctx.lineTo(crisp(leftX), chartBottom);
6749
+ ctx.moveTo(crisp(rightX), chartTop);
6750
+ ctx.lineTo(crisp(rightX), chartBottom);
6751
+ ctx.stroke();
6752
+ ctx.setLineDash([]);
6753
+ if (profile && profile.maxRowVolume > 0) {
6754
+ const widthRatio = Math.max(0.1, Math.min(1, Number(drawing.profileWidthRatio) || 0.5));
6755
+ const maxWidth = rangeWidth * widthRatio;
6756
+ for (let row = 0; row < profile.rows.length; row += 1) {
6757
+ const bucket = profile.rows[row];
6758
+ if (bucket.total <= 0) continue;
6759
+ const yTop = yFromPrice(bucket.high);
6760
+ const yBottom = yFromPrice(bucket.low);
6761
+ const top = Math.min(yTop, yBottom);
6762
+ const height2 = Math.max(1, Math.abs(yBottom - yTop) - 1);
6763
+ if (top > chartBottom || top + height2 < chartTop) continue;
6764
+ const inValueArea = row >= profile.valueAreaFrom && row <= profile.valueAreaTo;
6765
+ ctx.globalAlpha = (draft ? 0.5 : 1) * (inValueArea ? 0.5 : 0.2);
6766
+ const rowWidth = bucket.total / profile.maxRowVolume * maxWidth;
6767
+ const upWidth = rowWidth * (bucket.up / bucket.total);
6768
+ ctx.fillStyle = mergedOptions.upColor;
6769
+ ctx.fillRect(leftX, top, upWidth, height2);
6770
+ ctx.fillStyle = mergedOptions.downColor;
6771
+ ctx.fillRect(leftX + upWidth, top, rowWidth - upWidth, height2);
6772
+ }
6773
+ ctx.globalAlpha = draft ? 0.6 : 1;
6774
+ const poc = profile.rows[profile.pocIndex];
6775
+ const drawProfileLevel = (price, color, label, dashed) => {
6776
+ const y = yFromPrice(price);
6777
+ if (y < chartTop || y > chartBottom) return;
6778
+ ctx.strokeStyle = color;
6779
+ ctx.lineWidth = 1;
6780
+ ctx.setLineDash(dashed ? [4, 3] : []);
6781
+ ctx.beginPath();
6782
+ ctx.moveTo(leftX, crisp(y));
6783
+ ctx.lineTo(leftX + maxWidth, crisp(y));
6784
+ ctx.stroke();
6785
+ ctx.setLineDash([]);
6786
+ const prevFont = ctx.font;
6787
+ ctx.font = `10px ${mergedOptions.fontFamily}`;
6788
+ ctx.fillStyle = color;
6789
+ ctx.textAlign = "left";
6790
+ ctx.textBaseline = "bottom";
6791
+ ctx.fillText(label, leftX + 3, y - 1);
6792
+ ctx.font = prevFont;
6793
+ };
6794
+ drawProfileLevel(profile.rows[profile.valueAreaTo].high, "#7e8aa3", "VAH", true);
6795
+ drawProfileLevel(profile.rows[profile.valueAreaFrom].low, "#7e8aa3", "VAL", true);
6796
+ drawProfileLevel((poc.low + poc.high) / 2, "#f7a600", "POC", false);
6797
+ }
6798
+ ctx.restore();
6799
+ handleAt(leftX, yFromPrice(p0.index <= p1.index ? p0.price : p1.price), drawing.color);
6800
+ handleAt(rightX, yFromPrice(p0.index <= p1.index ? p1.price : p0.price), drawing.color);
6801
+ }
6638
6802
  } else if (drawing.type === "price-range") {
6639
6803
  const p0 = drawing.points[0];
6640
6804
  const p1 = drawing.points[1];
@@ -7644,87 +7808,10 @@ function createChart(element, options = {}) {
7644
7808
  }
7645
7809
  paneLayoutFullChartHeight = fullChartHeight;
7646
7810
  paneLayoutInfos = [];
7647
- paneButtonRegions = [];
7648
7811
  if (activeSeparateIndicators.length > 0) {
7649
7812
  const xFromIndex = (index) => chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
7650
7813
  let paneTopCursor = chartBottom + paneGap;
7651
- const paneButtonSize = 16;
7652
- const paneButtonGap = 4;
7653
- const drawPaneButtons = (indicatorId, indicatorType, indicatorVisible, startX, centerY) => {
7654
- const actionList = [
7655
- "visibility",
7656
- "settings",
7657
- "source",
7658
- "remove"
7659
- ];
7660
- let buttonX = startX;
7661
- for (const action of actionList) {
7662
- const buttonY = centerY - paneButtonSize / 2;
7663
- const hovered = hoveredPaneButton?.id === indicatorId && hoveredPaneButton.action === action;
7664
- const iconColor = labels.indicatorTextColor;
7665
- ctx.save();
7666
- ctx.fillStyle = hovered ? "rgba(148,163,184,0.30)" : "rgba(148,163,184,0.12)";
7667
- fillRoundedRect(Math.round(buttonX), Math.round(buttonY), paneButtonSize, paneButtonSize, 4);
7668
- ctx.strokeStyle = iconColor;
7669
- ctx.fillStyle = iconColor;
7670
- ctx.globalAlpha = hovered ? 1 : 0.75;
7671
- ctx.lineWidth = 1.2;
7672
- ctx.setLineDash([]);
7673
- const cx = buttonX + paneButtonSize / 2;
7674
- const cy = centerY;
7675
- if (action === "visibility") {
7676
- ctx.beginPath();
7677
- ctx.ellipse(cx, cy, 4.6, 3, 0, 0, Math.PI * 2);
7678
- ctx.stroke();
7679
- ctx.beginPath();
7680
- ctx.arc(cx, cy, 1.3, 0, Math.PI * 2);
7681
- ctx.fill();
7682
- if (!indicatorVisible) {
7683
- ctx.beginPath();
7684
- ctx.moveTo(cx - 5.2, cy + 4.6);
7685
- ctx.lineTo(cx + 5.2, cy - 4.6);
7686
- ctx.stroke();
7687
- }
7688
- } else if (action === "settings") {
7689
- ctx.beginPath();
7690
- ctx.arc(cx, cy, 3.4, 0, Math.PI * 2);
7691
- ctx.stroke();
7692
- for (let toothIndex = 0; toothIndex < 8; toothIndex += 1) {
7693
- const angle = Math.PI / 4 * toothIndex;
7694
- ctx.beginPath();
7695
- ctx.moveTo(cx + Math.cos(angle) * 3.4, cy + Math.sin(angle) * 3.4);
7696
- ctx.lineTo(cx + Math.cos(angle) * 5.2, cy + Math.sin(angle) * 5.2);
7697
- ctx.stroke();
7698
- }
7699
- ctx.beginPath();
7700
- ctx.arc(cx, cy, 1.1, 0, Math.PI * 2);
7701
- ctx.fill();
7702
- } else if (action === "source") {
7703
- const prevSourceFont = ctx.font;
7704
- ctx.font = `bold 9px ${mergedOptions.fontFamily}`;
7705
- drawText("{}", cx, cy + 0.5, "center", "middle", iconColor);
7706
- ctx.font = prevSourceFont;
7707
- } else {
7708
- ctx.beginPath();
7709
- ctx.moveTo(cx - 3.4, cy - 3.4);
7710
- ctx.lineTo(cx + 3.4, cy + 3.4);
7711
- ctx.moveTo(cx + 3.4, cy - 3.4);
7712
- ctx.lineTo(cx - 3.4, cy + 3.4);
7713
- ctx.stroke();
7714
- }
7715
- ctx.restore();
7716
- paneButtonRegions.push({
7717
- id: indicatorId,
7718
- type: indicatorType,
7719
- action,
7720
- x: buttonX,
7721
- y: buttonY,
7722
- width: paneButtonSize,
7723
- height: paneButtonSize
7724
- });
7725
- buttonX += paneButtonSize + paneButtonGap;
7726
- }
7727
- };
7814
+ const drawPaneButtons = drawIndicatorControls;
7728
7815
  activeSeparateIndicators.forEach(({ indicator, plugin }, paneIndex) => {
7729
7816
  const paneHeight = separatePaneHeights[paneIndex] ?? 80;
7730
7817
  const paneTop = paneTopCursor;
@@ -8226,26 +8313,56 @@ function createChart(element, options = {}) {
8226
8313
  };
8227
8314
  const labelEntries = activeOverlayIndicators.map(({ indicator, plugin }) => {
8228
8315
  const inputValues = Object.entries(indicator.inputs).filter(([key, value]) => !LEGEND_EXCLUDED_INPUT_KEYS.has(key) && typeof value !== "boolean" && isLegendInputValue(value)).slice(0, 2).map(([, value]) => formatLegendValue(value));
8229
- if (labels.showIndicatorNames && labels.showIndicatorValues && inputValues.length > 0) {
8230
- return `${plugin.name} ${inputValues.join(" ")}`;
8231
- }
8232
- if (labels.showIndicatorNames) {
8233
- return plugin.name;
8234
- }
8235
- return inputValues.join(" ");
8236
- }).filter((entry) => entry.length > 0);
8316
+ const text = labels.showIndicatorNames && labels.showIndicatorValues && inputValues.length > 0 ? `${plugin.name} ${inputValues.join(" ")}` : labels.showIndicatorNames ? plugin.name : inputValues.join(" ");
8317
+ return { id: indicator.id, type: indicator.type, visible: indicator.visible, text };
8318
+ }).filter((entry) => entry.text.length > 0);
8237
8319
  if (labelEntries.length > 0) {
8238
8320
  const prevFont = ctx.font;
8239
- ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
8240
- const legendText = labelEntries.join(" ");
8321
+ const legendFontSize = Math.max(8, axis.fontSize);
8322
+ ctx.font = `${legendFontSize}px ${mergedOptions.fontFamily}`;
8241
8323
  const offsetX = Math.max(0, Number(labels.indicatorLegendOffsetX) || 0);
8242
8324
  const offsetY = Math.max(0, Number(labels.indicatorLegendOffsetY) || 0);
8243
8325
  const position = labels.indicatorLegendPosition;
8244
8326
  const isRight = position === "top-right" || position === "bottom-right";
8245
8327
  const isBottom = position === "bottom-left" || position === "bottom-right";
8246
8328
  const legendX = isRight ? chartRight - offsetX : chartLeft + offsetX;
8247
- const legendY = isBottom ? chartBottom - offsetY : Math.max(chartTop + offsetY, dataLineBottom);
8248
- drawText(legendText, legendX, legendY, isRight ? "right" : "left", isBottom ? "bottom" : "top", labels.indicatorTextColor);
8329
+ const legendTop = isBottom ? chartBottom - offsetY : Math.max(chartTop + offsetY, dataLineBottom);
8330
+ const rowHeight = Math.round(legendFontSize * 1.55);
8331
+ labelEntries.forEach((entry, rowIndex) => {
8332
+ const rowTop = isBottom ? legendTop - (labelEntries.length - rowIndex) * rowHeight : legendTop + rowIndex * rowHeight;
8333
+ const rowCenter = rowTop + rowHeight / 2;
8334
+ const dimmed = entry.visible === false;
8335
+ ctx.save();
8336
+ if (dimmed) ctx.globalAlpha = 0.45;
8337
+ drawText(
8338
+ entry.text,
8339
+ legendX,
8340
+ rowCenter,
8341
+ isRight ? "right" : "left",
8342
+ "middle",
8343
+ labels.indicatorTextColor
8344
+ );
8345
+ ctx.restore();
8346
+ const textWidth = measureTextWidth(entry.text);
8347
+ const rowLeft = isRight ? legendX - textWidth : legendX;
8348
+ legendRowRegions.push({
8349
+ id: entry.id,
8350
+ type: entry.type,
8351
+ x: rowLeft,
8352
+ y: rowTop,
8353
+ width: textWidth + LEGEND_CONTROLS_WIDTH,
8354
+ height: rowHeight
8355
+ });
8356
+ if (hoveredLegendId === entry.id) {
8357
+ drawIndicatorControls(
8358
+ entry.id,
8359
+ entry.type,
8360
+ entry.visible !== false,
8361
+ isRight ? legendX + 6 : rowLeft + textWidth + 8,
8362
+ rowCenter
8363
+ );
8364
+ }
8365
+ });
8249
8366
  ctx.font = prevFont;
8250
8367
  }
8251
8368
  }
@@ -8917,6 +9034,9 @@ function createChart(element, options = {}) {
8917
9034
  if (hits.length === 0) return null;
8918
9035
  return hits.find((region) => region.part === "remove") ?? hits[0];
8919
9036
  };
9037
+ const getLegendRowAt = (x, y) => legendRowRegions.find(
9038
+ (region) => x >= region.x && x <= region.x + region.width && y >= region.y && y <= region.y + region.height
9039
+ ) ?? null;
8920
9040
  const getMarkAt = (x, y) => {
8921
9041
  const tolerance = touchInputActive ? Math.max(1, mergedOptions.touch?.hitToleranceScale ?? 2.2) : 1;
8922
9042
  for (let index = markRegions.length - 1; index >= 0; index -= 1) {
@@ -9407,6 +9527,19 @@ function createChart(element, options = {}) {
9407
9527
  if (distanceToSegment(x, y, bx + blockW / 2, by + blockH / 2, ax, ay) <= 5 * hitTolerance) {
9408
9528
  return { drawing, target: "line" };
9409
9529
  }
9530
+ } else if (drawing.type === "fixed-range-volume-profile") {
9531
+ const p0 = drawing.points[0];
9532
+ const p1 = drawing.points[1];
9533
+ if (!p0 || !p1) continue;
9534
+ const px0 = canvasXFromDrawingPoint(p0);
9535
+ const px1 = canvasXFromDrawingPoint(p1);
9536
+ const y0 = canvasYFromDrawingPrice(p0.price);
9537
+ const y1 = canvasYFromDrawingPrice(p1.price);
9538
+ if (Math.hypot(x - px0, y - y0) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 0 };
9539
+ if (Math.hypot(x - px1, y - y1) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 1 };
9540
+ if (x >= Math.min(px0, px1) && x <= Math.max(px0, px1)) {
9541
+ return { drawing, target: "line" };
9542
+ }
9410
9543
  } else if (drawing.type === "price-range") {
9411
9544
  const p0 = drawing.points[0];
9412
9545
  const p1 = drawing.points[1];
@@ -9858,6 +9991,33 @@ function createChart(element, options = {}) {
9858
9991
  scheduleDraw();
9859
9992
  return true;
9860
9993
  }
9994
+ if (activeDrawingTool === "fixed-range-volume-profile") {
9995
+ if (draftDrawing?.type === "fixed-range-volume-profile") {
9996
+ const completed = normalizeDrawingState({
9997
+ ...serializeDrawing(draftDrawing),
9998
+ points: [draftDrawing.points[0], point]
9999
+ });
10000
+ drawings.push(completed);
10001
+ draftDrawing = null;
10002
+ activeDrawingTool = null;
10003
+ emitDrawingsChange();
10004
+ scheduleDraw();
10005
+ return true;
10006
+ }
10007
+ const defaults = getDrawingToolDefaults("fixed-range-volume-profile");
10008
+ draftDrawing = normalizeDrawingState({
10009
+ type: "fixed-range-volume-profile",
10010
+ points: [point, point],
10011
+ color: defaults.color ?? "#5b8def",
10012
+ style: defaults.style ?? "solid",
10013
+ width: defaults.width ?? 1,
10014
+ ...defaults.profileRows === void 0 ? {} : { profileRows: defaults.profileRows },
10015
+ ...defaults.profileValueAreaPercent === void 0 ? {} : { profileValueAreaPercent: defaults.profileValueAreaPercent },
10016
+ ...defaults.profileWidthRatio === void 0 ? {} : { profileWidthRatio: defaults.profileWidthRatio }
10017
+ });
10018
+ scheduleDraw();
10019
+ return true;
10020
+ }
9861
10021
  if (activeDrawingTool === "price-range") {
9862
10022
  const tick = getConfiguredTickSize();
9863
10023
  const visibleRange = drawState.yMax - drawState.yMin;
@@ -10626,6 +10786,12 @@ function createChart(element, options = {}) {
10626
10786
  setCrosshairPoint(null);
10627
10787
  return;
10628
10788
  }
10789
+ const legendRow = getLegendRowAt(point.x, point.y);
10790
+ const nextLegendId = legendRow?.id ?? null;
10791
+ if (nextLegendId !== hoveredLegendId) {
10792
+ hoveredLegendId = nextLegendId;
10793
+ scheduleDraw();
10794
+ }
10629
10795
  const hoveredPane = getPaneAt(point.x, point.y);
10630
10796
  const nextHoveredPaneId = hoveredPane?.id ?? null;
10631
10797
  if (nextHoveredPaneId !== hoveredPaneId) {
@@ -10844,10 +11010,11 @@ function createChart(element, options = {}) {
10844
11010
  });
10845
11011
  paneDividerDrag = null;
10846
11012
  }
10847
- if (event?.type === "pointerleave" && (hoveredPaneId !== null || hoveredPaneButton !== null || hoveredAlertId !== null || hoveredMarkKey !== null)) {
11013
+ if (event?.type === "pointerleave" && (hoveredPaneId !== null || hoveredPaneButton !== null || hoveredAlertId !== null || hoveredMarkKey !== null || hoveredLegendId !== null)) {
10848
11014
  hoveredPaneId = null;
10849
11015
  hoveredPaneButton = null;
10850
11016
  hoveredAlertId = null;
11017
+ hoveredLegendId = null;
10851
11018
  if (hoveredMarkKey !== null) {
10852
11019
  hoveredMarkKey = null;
10853
11020
  markHoverHandler?.(null);