hyperprop-charting-library 0.1.49 → 0.1.50
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 +198 -18
- package/dist/hyperprop-charting-library.d.ts +38 -2
- package/dist/hyperprop-charting-library.js +198 -18
- package/dist/index.cjs +198 -18
- package/dist/index.d.cts +38 -2
- package/dist/index.d.ts +38 -2
- package/dist/index.js +198 -18
- package/docs/API.md +44 -2
- package/docs/RECIPES.md +100 -0
- package/package.json +1 -1
|
@@ -74,6 +74,9 @@ var DEFAULT_LABELS_OPTIONS = {
|
|
|
74
74
|
askPrice: Number.NaN,
|
|
75
75
|
showIndicatorNames: false,
|
|
76
76
|
showIndicatorValues: false,
|
|
77
|
+
indicatorLegendPosition: "top-left",
|
|
78
|
+
indicatorLegendOffsetX: 10,
|
|
79
|
+
indicatorLegendOffsetY: 10,
|
|
77
80
|
showCountdownToBarClose: false,
|
|
78
81
|
noOverlapping: true,
|
|
79
82
|
backgroundColor: "#0b1220",
|
|
@@ -507,7 +510,7 @@ var drawOverlaySeries = (ctx, renderContext, values, color, width) => {
|
|
|
507
510
|
}
|
|
508
511
|
ctx.restore();
|
|
509
512
|
};
|
|
510
|
-
var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride, maxOverride, guideLines) => {
|
|
513
|
+
var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride, maxOverride, guideLines, options = {}) => {
|
|
511
514
|
const visible = [];
|
|
512
515
|
for (let index = renderContext.startIndex; index <= renderContext.endIndex; index += 1) {
|
|
513
516
|
const value = values[index];
|
|
@@ -515,7 +518,7 @@ var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride,
|
|
|
515
518
|
visible.push(value);
|
|
516
519
|
}
|
|
517
520
|
}
|
|
518
|
-
if (visible.length === 0) return;
|
|
521
|
+
if (visible.length === 0) return void 0;
|
|
519
522
|
const minValue = minOverride ?? Math.min(...visible);
|
|
520
523
|
const maxValue = maxOverride ?? Math.max(...visible);
|
|
521
524
|
const range = maxValue - minValue || 1;
|
|
@@ -564,6 +567,51 @@ var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride,
|
|
|
564
567
|
}
|
|
565
568
|
}
|
|
566
569
|
ctx.restore();
|
|
570
|
+
let latestValue = null;
|
|
571
|
+
for (let index = values.length - 1; index >= 0; index -= 1) {
|
|
572
|
+
const value = values[index];
|
|
573
|
+
if (Number.isFinite(value ?? Number.NaN)) {
|
|
574
|
+
latestValue = value;
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
const decimals = options.decimals ?? 2;
|
|
579
|
+
const formatValue = (value) => value.toFixed(decimals);
|
|
580
|
+
const axisTicks = options.axisTicks ?? guideLines;
|
|
581
|
+
const paneInfo = {
|
|
582
|
+
...options.title ? { title: options.title } : {},
|
|
583
|
+
axis: {
|
|
584
|
+
min: minValue,
|
|
585
|
+
max: maxValue,
|
|
586
|
+
...axisTicks ? { ticks: axisTicks } : {},
|
|
587
|
+
decimals
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
if (guideLines) {
|
|
591
|
+
paneInfo.guideLines = guideLines.map((value) => ({ value, label: formatValue(value), style: "dotted" }));
|
|
592
|
+
}
|
|
593
|
+
if (latestValue !== null) {
|
|
594
|
+
paneInfo.legendValues = [
|
|
595
|
+
{
|
|
596
|
+
...options.legendLabel ? { label: options.legendLabel } : {},
|
|
597
|
+
value: latestValue,
|
|
598
|
+
text: formatValue(latestValue),
|
|
599
|
+
color
|
|
600
|
+
}
|
|
601
|
+
];
|
|
602
|
+
}
|
|
603
|
+
if (options.valueLabel !== false && latestValue !== null) {
|
|
604
|
+
paneInfo.valueLabels = [
|
|
605
|
+
{
|
|
606
|
+
value: latestValue,
|
|
607
|
+
text: formatValue(latestValue),
|
|
608
|
+
color,
|
|
609
|
+
backgroundColor: color,
|
|
610
|
+
textColor: "#0f172a"
|
|
611
|
+
}
|
|
612
|
+
];
|
|
613
|
+
}
|
|
614
|
+
return paneInfo;
|
|
567
615
|
};
|
|
568
616
|
var getPercentileValue = (values, percentile) => {
|
|
569
617
|
if (values.length === 0) {
|
|
@@ -736,7 +784,10 @@ var BUILTIN_STDDEV_INDICATOR = {
|
|
|
736
784
|
renderContext.data,
|
|
737
785
|
() => computeStdDevSeries(renderContext.data, length, inputs.source ?? "close")
|
|
738
786
|
);
|
|
739
|
-
drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#f97316", Number(inputs.width) || 2
|
|
787
|
+
return drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#f97316", Number(inputs.width) || 2, void 0, void 0, void 0, {
|
|
788
|
+
title: `StdDev ${length}`,
|
|
789
|
+
decimals: 2
|
|
790
|
+
});
|
|
740
791
|
}
|
|
741
792
|
};
|
|
742
793
|
var BUILTIN_ATR_INDICATOR = {
|
|
@@ -748,7 +799,10 @@ var BUILTIN_ATR_INDICATOR = {
|
|
|
748
799
|
draw: (ctx, renderContext, inputs) => {
|
|
749
800
|
const length = clampIndicatorLength(inputs.length, 14);
|
|
750
801
|
const values = withCachedSeries(`atr|${length}`, renderContext.data, () => computeAtrSeries(renderContext.data, length));
|
|
751
|
-
drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#eab308", Number(inputs.width) || 2
|
|
802
|
+
return drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#eab308", Number(inputs.width) || 2, void 0, void 0, void 0, {
|
|
803
|
+
title: `ATR ${length}`,
|
|
804
|
+
decimals: 2
|
|
805
|
+
});
|
|
752
806
|
}
|
|
753
807
|
};
|
|
754
808
|
var BUILTIN_RSI_INDICATOR = {
|
|
@@ -760,7 +814,7 @@ var BUILTIN_RSI_INDICATOR = {
|
|
|
760
814
|
draw: (ctx, renderContext, inputs) => {
|
|
761
815
|
const length = clampIndicatorLength(inputs.length, 14);
|
|
762
816
|
const values = withCachedSeries(`rsi|${length}`, renderContext.data, () => computeRsiSeries(renderContext.data, length));
|
|
763
|
-
drawSeparateSeries(
|
|
817
|
+
return drawSeparateSeries(
|
|
764
818
|
ctx,
|
|
765
819
|
renderContext,
|
|
766
820
|
values,
|
|
@@ -768,7 +822,12 @@ var BUILTIN_RSI_INDICATOR = {
|
|
|
768
822
|
Number(inputs.width) || 2,
|
|
769
823
|
0,
|
|
770
824
|
100,
|
|
771
|
-
[30, 50, 70]
|
|
825
|
+
[30, 50, 70],
|
|
826
|
+
{
|
|
827
|
+
title: `RSI ${length}`,
|
|
828
|
+
axisTicks: [0, 30, 50, 70, 100],
|
|
829
|
+
decimals: 2
|
|
830
|
+
}
|
|
772
831
|
);
|
|
773
832
|
}
|
|
774
833
|
};
|
|
@@ -1332,6 +1391,11 @@ function createChart(element, options = {}) {
|
|
|
1332
1391
|
const pad = (value) => String(value).padStart(2, "0");
|
|
1333
1392
|
return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;
|
|
1334
1393
|
};
|
|
1394
|
+
const getRightAxisLabelX = (chartRight) => chartRight + 1;
|
|
1395
|
+
const getRightAxisLabelWidth = (chartRight, contentWidth) => {
|
|
1396
|
+
const scaleWidth = Math.max(0, Math.floor(width - getRightAxisLabelX(chartRight)));
|
|
1397
|
+
return Math.max(contentWidth, scaleWidth);
|
|
1398
|
+
};
|
|
1335
1399
|
const drawPriceLine = (line, yFromPrice, chartLeft, chartTop, chartRight, chartBottom) => {
|
|
1336
1400
|
const mergedLine = { ...DEFAULT_PRICE_LINE_OPTIONS, ...line };
|
|
1337
1401
|
if (!mergedLine.visible || !Number.isFinite(mergedLine.price)) {
|
|
@@ -1356,8 +1420,9 @@ function createChart(element, options = {}) {
|
|
|
1356
1420
|
ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
|
|
1357
1421
|
const labelPaddingX = 8;
|
|
1358
1422
|
const labelHeight = 20;
|
|
1359
|
-
const
|
|
1360
|
-
const
|
|
1423
|
+
const measuredLabelWidth = mergedLine.label === void 0 ? getPriceLabelWidth(labelText, labelPaddingX) : Math.ceil(ctx.measureText(labelText).width) + labelPaddingX * 2;
|
|
1424
|
+
const labelWidth = getRightAxisLabelWidth(chartRight, measuredLabelWidth);
|
|
1425
|
+
const labelX = getRightAxisLabelX(chartRight);
|
|
1361
1426
|
const labelY = placeRightAxisLabel(lineY - labelHeight / 2, labelHeight, chartTop, chartBottom - labelHeight);
|
|
1362
1427
|
ctx.fillStyle = mergedLine.labelBackgroundColor;
|
|
1363
1428
|
fillRoundedRect(
|
|
@@ -1595,11 +1660,11 @@ function createChart(element, options = {}) {
|
|
|
1595
1660
|
const priceText = formatPrice(renderPrice);
|
|
1596
1661
|
const pricePaddingX = 8;
|
|
1597
1662
|
const measuredPriceWidth = getPriceLabelWidth(priceText, pricePaddingX);
|
|
1598
|
-
const priceWidth = mergedLine.id === void 0 ? measuredPriceWidth : Math.max(measuredPriceWidth, orderPriceTagWidthById.get(mergedLine.id) ?? 0);
|
|
1663
|
+
const priceWidth = mergedLine.id === void 0 ? getRightAxisLabelWidth(chartRight, measuredPriceWidth) : getRightAxisLabelWidth(chartRight, Math.max(measuredPriceWidth, orderPriceTagWidthById.get(mergedLine.id) ?? 0));
|
|
1599
1664
|
if (mergedLine.id) {
|
|
1600
1665
|
orderPriceTagWidthById.set(mergedLine.id, priceWidth);
|
|
1601
1666
|
}
|
|
1602
|
-
const priceX = chartRight
|
|
1667
|
+
const priceX = getRightAxisLabelX(chartRight);
|
|
1603
1668
|
const priceY = placeRightAxisLabel(targetLabelY, labelHeight, chartTop, chartBottom - labelHeight);
|
|
1604
1669
|
ctx.fillStyle = mergedLine.labelBackgroundColor ?? color;
|
|
1605
1670
|
fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, borderRadius);
|
|
@@ -1674,9 +1739,63 @@ function createChart(element, options = {}) {
|
|
|
1674
1739
|
ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
|
|
1675
1740
|
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
1676
1741
|
ctx.fillRect(0, 0, width, height);
|
|
1742
|
+
const labels = { ...DEFAULT_LABELS_OPTIONS, ...mergedOptions.labels ?? {} };
|
|
1743
|
+
const estimateRightMargin = () => {
|
|
1744
|
+
const paddingX = Math.max(4, labels.labelPaddingX);
|
|
1745
|
+
const measure = (text) => Math.ceil(ctx.measureText(text).width) + paddingX * 2;
|
|
1746
|
+
let required = margin.right - 1;
|
|
1747
|
+
const include = (text) => {
|
|
1748
|
+
const normalized = text?.trim();
|
|
1749
|
+
if (normalized) {
|
|
1750
|
+
required = Math.max(required, measure(normalized));
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
const ticker2 = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
|
|
1754
|
+
const lastPoint2 = data[data.length - 1];
|
|
1755
|
+
if ((ticker2.visible ?? true) && labels.showLastPrice && lastPoint2) {
|
|
1756
|
+
include(formatPrice(lastPoint2.c));
|
|
1757
|
+
if (ticker2.labelSubtext) include(String(ticker2.labelSubtext));
|
|
1758
|
+
for (const subtext of ticker2.labelSubtexts ?? []) {
|
|
1759
|
+
include(String(subtext));
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
if (labels.showSymbolName) {
|
|
1763
|
+
include(labels.symbolName);
|
|
1764
|
+
}
|
|
1765
|
+
if (labels.showPreviousClose) {
|
|
1766
|
+
const previousCloseCandidate = Number.isFinite(labels.previousClosePrice) ? labels.previousClosePrice : data.length > 1 ? data[data.length - 2]?.c : Number.NaN;
|
|
1767
|
+
if (previousCloseCandidate !== void 0 && Number.isFinite(previousCloseCandidate)) {
|
|
1768
|
+
include(`PDC ${formatPrice(previousCloseCandidate)}`);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
if (labels.showHighLow && data.length > 0) {
|
|
1772
|
+
include(`H ${formatPrice(Math.max(...data.map((point) => point.h)))}`);
|
|
1773
|
+
include(`L ${formatPrice(Math.min(...data.map((point) => point.l)))}`);
|
|
1774
|
+
}
|
|
1775
|
+
if (labels.showBidAsk) {
|
|
1776
|
+
if (Number.isFinite(labels.bidPrice)) include(`B ${formatPrice(labels.bidPrice)}`);
|
|
1777
|
+
if (Number.isFinite(labels.askPrice)) include(`A ${formatPrice(labels.askPrice)}`);
|
|
1778
|
+
}
|
|
1779
|
+
for (const line of priceLines) {
|
|
1780
|
+
const mergedLine = { ...DEFAULT_PRICE_LINE_OPTIONS, ...line };
|
|
1781
|
+
if (mergedLine.visible && Number.isFinite(mergedLine.price)) {
|
|
1782
|
+
include(mergedLine.label ?? formatPrice(mergedLine.price));
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
for (const line of orderLines) {
|
|
1786
|
+
const mergedLine = { ...DEFAULT_ORDER_LINE_OPTIONS, ...line };
|
|
1787
|
+
const renderPrice = mergedLine.behavior === "follow" && Number.isFinite(mergedLine.followPrice) ? mergedLine.followPrice : mergedLine.price;
|
|
1788
|
+
if (mergedLine.visible && Number.isFinite(renderPrice)) {
|
|
1789
|
+
include(formatPrice(renderPrice));
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
const maxRightMargin = Math.max(margin.right, width - margin.left - 160);
|
|
1793
|
+
return Math.min(maxRightMargin, Math.max(margin.right, Math.ceil(required + 1)));
|
|
1794
|
+
};
|
|
1795
|
+
const rightMargin = estimateRightMargin();
|
|
1677
1796
|
const chartLeft = margin.left;
|
|
1678
1797
|
const chartTop = margin.top;
|
|
1679
|
-
const chartWidth = width - margin.left -
|
|
1798
|
+
const chartWidth = width - margin.left - rightMargin;
|
|
1680
1799
|
const fullChartHeight = height - margin.top - margin.bottom;
|
|
1681
1800
|
const fullChartBottom = chartTop + fullChartHeight;
|
|
1682
1801
|
const chartRight = chartLeft + chartWidth;
|
|
@@ -2029,7 +2148,7 @@ function createChart(element, options = {}) {
|
|
|
2029
2148
|
ctx.beginPath();
|
|
2030
2149
|
ctx.rect(chartLeft + 1, paneTop + 1, Math.max(0, chartWidth - 2), Math.max(0, paneHeight - 2));
|
|
2031
2150
|
ctx.clip();
|
|
2032
|
-
plugin.draw(
|
|
2151
|
+
const paneInfo = plugin.draw(
|
|
2033
2152
|
ctx,
|
|
2034
2153
|
{
|
|
2035
2154
|
data,
|
|
@@ -2062,6 +2181,60 @@ function createChart(element, options = {}) {
|
|
|
2062
2181
|
ctx.lineTo(crisp(chartRight), crisp(paneTop));
|
|
2063
2182
|
ctx.stroke();
|
|
2064
2183
|
ctx.restore();
|
|
2184
|
+
const axisInfo = paneInfo?.axis;
|
|
2185
|
+
if (axisInfo && Number.isFinite(axisInfo.min) && Number.isFinite(axisInfo.max) && axisInfo.max !== axisInfo.min) {
|
|
2186
|
+
const paneRange = axisInfo.max - axisInfo.min;
|
|
2187
|
+
const yFromPaneValue = (value) => {
|
|
2188
|
+
const ratio = (value - axisInfo.min) / paneRange;
|
|
2189
|
+
return paneBottom - ratio * paneHeight;
|
|
2190
|
+
};
|
|
2191
|
+
const formatPaneValue = (value) => {
|
|
2192
|
+
if (axisInfo.format) {
|
|
2193
|
+
return axisInfo.format(value);
|
|
2194
|
+
}
|
|
2195
|
+
const decimals = axisInfo.decimals ?? (Math.abs(paneRange) <= 2 ? 2 : Math.abs(paneRange) <= 20 ? 1 : 0);
|
|
2196
|
+
return value.toFixed(Math.max(0, Math.min(8, Math.round(decimals))));
|
|
2197
|
+
};
|
|
2198
|
+
const axisTicks = axisInfo.ticks && axisInfo.ticks.length > 0 ? axisInfo.ticks : [axisInfo.min, axisInfo.min + paneRange / 2, axisInfo.max];
|
|
2199
|
+
const uniqueTicks = Array.from(new Set(axisTicks.filter((tick) => Number.isFinite(tick))));
|
|
2200
|
+
ctx.save();
|
|
2201
|
+
ctx.font = `${yAxisFontSize}px ${mergedOptions.fontFamily}`;
|
|
2202
|
+
for (const tick of uniqueTicks) {
|
|
2203
|
+
if (tick < axisInfo.min || tick > axisInfo.max) {
|
|
2204
|
+
continue;
|
|
2205
|
+
}
|
|
2206
|
+
drawText(formatPaneValue(tick), chartRight + 6, yFromPaneValue(tick), "left", "middle", yAxis.textColor);
|
|
2207
|
+
}
|
|
2208
|
+
ctx.restore();
|
|
2209
|
+
if (labels.visible) {
|
|
2210
|
+
const prevFont = ctx.font;
|
|
2211
|
+
ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
|
|
2212
|
+
const legendTitle = paneInfo.title ?? plugin.name;
|
|
2213
|
+
const legendValues = paneInfo.legendValues ?? [];
|
|
2214
|
+
const legendParts = [
|
|
2215
|
+
legendTitle,
|
|
2216
|
+
...legendValues.map((value) => value.text ?? (value.value === void 0 ? "" : formatPaneValue(value.value))).filter(Boolean)
|
|
2217
|
+
].filter(Boolean);
|
|
2218
|
+
if (legendParts.length > 0) {
|
|
2219
|
+
drawText(legendParts.join(" "), chartLeft + 10, paneTop + 8, "left", "top", labels.indicatorTextColor);
|
|
2220
|
+
}
|
|
2221
|
+
for (const label of paneInfo.valueLabels ?? []) {
|
|
2222
|
+
if (!Number.isFinite(label.value) || label.value < axisInfo.min || label.value > axisInfo.max) {
|
|
2223
|
+
continue;
|
|
2224
|
+
}
|
|
2225
|
+
const text = label.text ?? formatPaneValue(label.value);
|
|
2226
|
+
const labelPaddingX = 7;
|
|
2227
|
+
const labelHeight = Math.max(16, yAxisFontSize + 8);
|
|
2228
|
+
const labelWidth = getRightAxisLabelWidth(chartRight, Math.ceil(ctx.measureText(text).width) + labelPaddingX * 2);
|
|
2229
|
+
const labelX = getRightAxisLabelX(chartRight);
|
|
2230
|
+
const labelY = clamp(yFromPaneValue(label.value) - labelHeight / 2, paneTop + 2, paneBottom - labelHeight - 2);
|
|
2231
|
+
ctx.fillStyle = label.backgroundColor ?? label.color ?? labels.backgroundColor;
|
|
2232
|
+
fillRoundedRect(Math.round(labelX), Math.round(labelY), labelWidth, labelHeight, Math.max(0, labels.borderRadius));
|
|
2233
|
+
drawText(text, labelX + labelPaddingX, labelY + labelHeight / 2, "left", "middle", label.textColor ?? labels.textColor);
|
|
2234
|
+
}
|
|
2235
|
+
ctx.font = prevFont;
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2065
2238
|
});
|
|
2066
2239
|
}
|
|
2067
2240
|
if (crosshair.visible && crosshairPoint && crosshair.showVertical) {
|
|
@@ -2093,7 +2266,6 @@ function createChart(element, options = {}) {
|
|
|
2093
2266
|
drawText(formatPrice(price), chartRight + 6, y, "left", "middle", yAxis.textColor);
|
|
2094
2267
|
ctx.font = prevFont;
|
|
2095
2268
|
}
|
|
2096
|
-
const labels = { ...DEFAULT_LABELS_OPTIONS, ...mergedOptions.labels ?? {} };
|
|
2097
2269
|
resetLabelSlots(labels.noOverlapping);
|
|
2098
2270
|
const priceAxisLabels = [];
|
|
2099
2271
|
const addPriceAxisLabel = (label) => {
|
|
@@ -2257,12 +2429,13 @@ function createChart(element, options = {}) {
|
|
|
2257
2429
|
ctx.font = baseFont;
|
|
2258
2430
|
}
|
|
2259
2431
|
const labelTextWidth = Math.max(primaryWidth, subtextWidth);
|
|
2432
|
+
const labelWidth = getRightAxisLabelWidth(chartRight, labelTextWidth);
|
|
2260
2433
|
return {
|
|
2261
2434
|
...label,
|
|
2262
2435
|
subtexts,
|
|
2263
2436
|
subtextFontSize,
|
|
2264
2437
|
height: labelHeight,
|
|
2265
|
-
width:
|
|
2438
|
+
width: labelWidth,
|
|
2266
2439
|
targetY: clamp(yFromPrice(label.price), chartTop + 1, chartBottom - 1) - labelHeight / 2,
|
|
2267
2440
|
y: 0
|
|
2268
2441
|
};
|
|
@@ -2292,7 +2465,7 @@ function createChart(element, options = {}) {
|
|
|
2292
2465
|
);
|
|
2293
2466
|
rightAxisLabelSlots.sort((a, b) => a.y - b.y);
|
|
2294
2467
|
}
|
|
2295
|
-
const labelX = chartRight
|
|
2468
|
+
const labelX = getRightAxisLabelX(chartRight);
|
|
2296
2469
|
for (const label of positionedLabels) {
|
|
2297
2470
|
ctx.fillStyle = label.backgroundColor;
|
|
2298
2471
|
fillRoundedRect(Math.round(labelX), Math.round(label.y), label.width, label.height, labelRadius);
|
|
@@ -2337,7 +2510,14 @@ function createChart(element, options = {}) {
|
|
|
2337
2510
|
const prevFont = ctx.font;
|
|
2338
2511
|
ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
|
|
2339
2512
|
const legendText = labelEntries.join(" ");
|
|
2340
|
-
|
|
2513
|
+
const offsetX = Math.max(0, Number(labels.indicatorLegendOffsetX) || 0);
|
|
2514
|
+
const offsetY = Math.max(0, Number(labels.indicatorLegendOffsetY) || 0);
|
|
2515
|
+
const position = labels.indicatorLegendPosition;
|
|
2516
|
+
const isRight = position === "top-right" || position === "bottom-right";
|
|
2517
|
+
const isBottom = position === "bottom-left" || position === "bottom-right";
|
|
2518
|
+
const legendX = isRight ? chartRight - offsetX : chartLeft + offsetX;
|
|
2519
|
+
const legendY = isBottom ? chartBottom - offsetY : chartTop + offsetY;
|
|
2520
|
+
drawText(legendText, legendX, legendY, isRight ? "right" : "left", isBottom ? "bottom" : "top", labels.indicatorTextColor);
|
|
2341
2521
|
ctx.font = prevFont;
|
|
2342
2522
|
}
|
|
2343
2523
|
}
|
|
@@ -2395,8 +2575,8 @@ function createChart(element, options = {}) {
|
|
|
2395
2575
|
if (crosshair.showPriceLabel) {
|
|
2396
2576
|
const hoverPrice = yMin + (chartBottom - cy) / chartHeight * yRange;
|
|
2397
2577
|
const priceText = formatPrice(hoverPrice);
|
|
2398
|
-
const priceWidth = getPriceLabelWidth(priceText, labelPaddingX);
|
|
2399
|
-
const priceX = chartRight
|
|
2578
|
+
const priceWidth = getRightAxisLabelWidth(chartRight, getPriceLabelWidth(priceText, labelPaddingX));
|
|
2579
|
+
const priceX = getRightAxisLabelX(chartRight);
|
|
2400
2580
|
const priceY = clamp(cy - labelHeight / 2, chartTop, chartBottom - labelHeight);
|
|
2401
2581
|
ctx.fillStyle = labelBackground;
|
|
2402
2582
|
fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, labelRadius);
|