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.
- package/dist/hyperprop-charting-library.cjs +260 -93
- package/dist/hyperprop-charting-library.d.ts +13 -1
- package/dist/hyperprop-charting-library.js +260 -93
- package/dist/index.cjs +260 -93
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +260 -93
- package/docs/API.md +85 -0
- package/package.json +1 -1
|
@@ -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
|
|
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
|
-
|
|
8230
|
-
|
|
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
|
-
|
|
8240
|
-
|
|
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
|
|
8248
|
-
|
|
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);
|