hyperprop-charting-library 0.1.49 → 0.1.51
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 +205 -18
- package/dist/hyperprop-charting-library.d.ts +39 -2
- package/dist/hyperprop-charting-library.js +205 -18
- package/dist/index.cjs +205 -18
- package/dist/index.d.cts +39 -2
- package/dist/index.d.ts +39 -2
- package/dist/index.js +205 -18
- package/docs/API.md +46 -2
- package/docs/RECIPES.md +112 -0
- package/package.json +1 -1
|
@@ -98,6 +98,10 @@ var DEFAULT_LABELS_OPTIONS = {
|
|
|
98
98
|
askPrice: Number.NaN,
|
|
99
99
|
showIndicatorNames: false,
|
|
100
100
|
showIndicatorValues: false,
|
|
101
|
+
showIndicatorValueLabels: true,
|
|
102
|
+
indicatorLegendPosition: "top-left",
|
|
103
|
+
indicatorLegendOffsetX: 10,
|
|
104
|
+
indicatorLegendOffsetY: 10,
|
|
101
105
|
showCountdownToBarClose: false,
|
|
102
106
|
noOverlapping: true,
|
|
103
107
|
backgroundColor: "#0b1220",
|
|
@@ -531,7 +535,7 @@ var drawOverlaySeries = (ctx, renderContext, values, color, width) => {
|
|
|
531
535
|
}
|
|
532
536
|
ctx.restore();
|
|
533
537
|
};
|
|
534
|
-
var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride, maxOverride, guideLines) => {
|
|
538
|
+
var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride, maxOverride, guideLines, options = {}) => {
|
|
535
539
|
const visible = [];
|
|
536
540
|
for (let index = renderContext.startIndex; index <= renderContext.endIndex; index += 1) {
|
|
537
541
|
const value = values[index];
|
|
@@ -539,7 +543,7 @@ var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride,
|
|
|
539
543
|
visible.push(value);
|
|
540
544
|
}
|
|
541
545
|
}
|
|
542
|
-
if (visible.length === 0) return;
|
|
546
|
+
if (visible.length === 0) return void 0;
|
|
543
547
|
const minValue = minOverride ?? Math.min(...visible);
|
|
544
548
|
const maxValue = maxOverride ?? Math.max(...visible);
|
|
545
549
|
const range = maxValue - minValue || 1;
|
|
@@ -588,6 +592,51 @@ var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride,
|
|
|
588
592
|
}
|
|
589
593
|
}
|
|
590
594
|
ctx.restore();
|
|
595
|
+
let latestValue = null;
|
|
596
|
+
for (let index = values.length - 1; index >= 0; index -= 1) {
|
|
597
|
+
const value = values[index];
|
|
598
|
+
if (Number.isFinite(value ?? Number.NaN)) {
|
|
599
|
+
latestValue = value;
|
|
600
|
+
break;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
const decimals = options.decimals ?? 2;
|
|
604
|
+
const formatValue = (value) => value.toFixed(decimals);
|
|
605
|
+
const axisTicks = options.axisTicks ?? guideLines;
|
|
606
|
+
const paneInfo = {
|
|
607
|
+
...options.title ? { title: options.title } : {},
|
|
608
|
+
axis: {
|
|
609
|
+
min: minValue,
|
|
610
|
+
max: maxValue,
|
|
611
|
+
...axisTicks ? { ticks: axisTicks } : {},
|
|
612
|
+
decimals
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
if (guideLines) {
|
|
616
|
+
paneInfo.guideLines = guideLines.map((value) => ({ value, label: formatValue(value), style: "dotted" }));
|
|
617
|
+
}
|
|
618
|
+
if (latestValue !== null) {
|
|
619
|
+
paneInfo.legendValues = [
|
|
620
|
+
{
|
|
621
|
+
...options.legendLabel ? { label: options.legendLabel } : {},
|
|
622
|
+
value: latestValue,
|
|
623
|
+
text: formatValue(latestValue),
|
|
624
|
+
color
|
|
625
|
+
}
|
|
626
|
+
];
|
|
627
|
+
}
|
|
628
|
+
if (options.valueLabel !== false && latestValue !== null) {
|
|
629
|
+
paneInfo.valueLabels = [
|
|
630
|
+
{
|
|
631
|
+
value: latestValue,
|
|
632
|
+
text: formatValue(latestValue),
|
|
633
|
+
color: options.valueLabelColor ?? color,
|
|
634
|
+
backgroundColor: options.valueLabelBackgroundColor ?? options.valueLabelColor ?? color,
|
|
635
|
+
textColor: options.valueLabelTextColor ?? "#0f172a"
|
|
636
|
+
}
|
|
637
|
+
];
|
|
638
|
+
}
|
|
639
|
+
return paneInfo;
|
|
591
640
|
};
|
|
592
641
|
var getPercentileValue = (values, percentile) => {
|
|
593
642
|
if (values.length === 0) {
|
|
@@ -760,7 +809,10 @@ var BUILTIN_STDDEV_INDICATOR = {
|
|
|
760
809
|
renderContext.data,
|
|
761
810
|
() => computeStdDevSeries(renderContext.data, length, inputs.source ?? "close")
|
|
762
811
|
);
|
|
763
|
-
drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#f97316", Number(inputs.width) || 2
|
|
812
|
+
return drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#f97316", Number(inputs.width) || 2, void 0, void 0, void 0, {
|
|
813
|
+
title: `StdDev ${length}`,
|
|
814
|
+
decimals: 2
|
|
815
|
+
});
|
|
764
816
|
}
|
|
765
817
|
};
|
|
766
818
|
var BUILTIN_ATR_INDICATOR = {
|
|
@@ -772,7 +824,10 @@ var BUILTIN_ATR_INDICATOR = {
|
|
|
772
824
|
draw: (ctx, renderContext, inputs) => {
|
|
773
825
|
const length = clampIndicatorLength(inputs.length, 14);
|
|
774
826
|
const values = withCachedSeries(`atr|${length}`, renderContext.data, () => computeAtrSeries(renderContext.data, length));
|
|
775
|
-
drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#eab308", Number(inputs.width) || 2
|
|
827
|
+
return drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#eab308", Number(inputs.width) || 2, void 0, void 0, void 0, {
|
|
828
|
+
title: `ATR ${length}`,
|
|
829
|
+
decimals: 2
|
|
830
|
+
});
|
|
776
831
|
}
|
|
777
832
|
};
|
|
778
833
|
var BUILTIN_RSI_INDICATOR = {
|
|
@@ -784,7 +839,7 @@ var BUILTIN_RSI_INDICATOR = {
|
|
|
784
839
|
draw: (ctx, renderContext, inputs) => {
|
|
785
840
|
const length = clampIndicatorLength(inputs.length, 14);
|
|
786
841
|
const values = withCachedSeries(`rsi|${length}`, renderContext.data, () => computeRsiSeries(renderContext.data, length));
|
|
787
|
-
drawSeparateSeries(
|
|
842
|
+
return drawSeparateSeries(
|
|
788
843
|
ctx,
|
|
789
844
|
renderContext,
|
|
790
845
|
values,
|
|
@@ -792,7 +847,15 @@ var BUILTIN_RSI_INDICATOR = {
|
|
|
792
847
|
Number(inputs.width) || 2,
|
|
793
848
|
0,
|
|
794
849
|
100,
|
|
795
|
-
[30, 50, 70]
|
|
850
|
+
[30, 50, 70],
|
|
851
|
+
{
|
|
852
|
+
title: `RSI ${length}`,
|
|
853
|
+
axisTicks: [0, 30, 50, 70, 100],
|
|
854
|
+
decimals: 2,
|
|
855
|
+
valueLabelColor: "#9E9E9E",
|
|
856
|
+
valueLabelBackgroundColor: "#9E9E9E",
|
|
857
|
+
valueLabelTextColor: "#0f172a"
|
|
858
|
+
}
|
|
796
859
|
);
|
|
797
860
|
}
|
|
798
861
|
};
|
|
@@ -1356,6 +1419,11 @@ function createChart(element, options = {}) {
|
|
|
1356
1419
|
const pad = (value) => String(value).padStart(2, "0");
|
|
1357
1420
|
return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;
|
|
1358
1421
|
};
|
|
1422
|
+
const getRightAxisLabelX = (chartRight) => chartRight + 1;
|
|
1423
|
+
const getRightAxisLabelWidth = (chartRight, contentWidth) => {
|
|
1424
|
+
const scaleWidth = Math.max(0, Math.floor(width - getRightAxisLabelX(chartRight)));
|
|
1425
|
+
return Math.max(contentWidth, scaleWidth);
|
|
1426
|
+
};
|
|
1359
1427
|
const drawPriceLine = (line, yFromPrice, chartLeft, chartTop, chartRight, chartBottom) => {
|
|
1360
1428
|
const mergedLine = { ...DEFAULT_PRICE_LINE_OPTIONS, ...line };
|
|
1361
1429
|
if (!mergedLine.visible || !Number.isFinite(mergedLine.price)) {
|
|
@@ -1380,8 +1448,9 @@ function createChart(element, options = {}) {
|
|
|
1380
1448
|
ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
|
|
1381
1449
|
const labelPaddingX = 8;
|
|
1382
1450
|
const labelHeight = 20;
|
|
1383
|
-
const
|
|
1384
|
-
const
|
|
1451
|
+
const measuredLabelWidth = mergedLine.label === void 0 ? getPriceLabelWidth(labelText, labelPaddingX) : Math.ceil(ctx.measureText(labelText).width) + labelPaddingX * 2;
|
|
1452
|
+
const labelWidth = getRightAxisLabelWidth(chartRight, measuredLabelWidth);
|
|
1453
|
+
const labelX = getRightAxisLabelX(chartRight);
|
|
1385
1454
|
const labelY = placeRightAxisLabel(lineY - labelHeight / 2, labelHeight, chartTop, chartBottom - labelHeight);
|
|
1386
1455
|
ctx.fillStyle = mergedLine.labelBackgroundColor;
|
|
1387
1456
|
fillRoundedRect(
|
|
@@ -1619,11 +1688,11 @@ function createChart(element, options = {}) {
|
|
|
1619
1688
|
const priceText = formatPrice(renderPrice);
|
|
1620
1689
|
const pricePaddingX = 8;
|
|
1621
1690
|
const measuredPriceWidth = getPriceLabelWidth(priceText, pricePaddingX);
|
|
1622
|
-
const priceWidth = mergedLine.id === void 0 ? measuredPriceWidth : Math.max(measuredPriceWidth, orderPriceTagWidthById.get(mergedLine.id) ?? 0);
|
|
1691
|
+
const priceWidth = mergedLine.id === void 0 ? getRightAxisLabelWidth(chartRight, measuredPriceWidth) : getRightAxisLabelWidth(chartRight, Math.max(measuredPriceWidth, orderPriceTagWidthById.get(mergedLine.id) ?? 0));
|
|
1623
1692
|
if (mergedLine.id) {
|
|
1624
1693
|
orderPriceTagWidthById.set(mergedLine.id, priceWidth);
|
|
1625
1694
|
}
|
|
1626
|
-
const priceX = chartRight
|
|
1695
|
+
const priceX = getRightAxisLabelX(chartRight);
|
|
1627
1696
|
const priceY = placeRightAxisLabel(targetLabelY, labelHeight, chartTop, chartBottom - labelHeight);
|
|
1628
1697
|
ctx.fillStyle = mergedLine.labelBackgroundColor ?? color;
|
|
1629
1698
|
fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, borderRadius);
|
|
@@ -1698,9 +1767,63 @@ function createChart(element, options = {}) {
|
|
|
1698
1767
|
ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
|
|
1699
1768
|
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
1700
1769
|
ctx.fillRect(0, 0, width, height);
|
|
1770
|
+
const labels = { ...DEFAULT_LABELS_OPTIONS, ...mergedOptions.labels ?? {} };
|
|
1771
|
+
const estimateRightMargin = () => {
|
|
1772
|
+
const paddingX = Math.max(4, labels.labelPaddingX);
|
|
1773
|
+
const measure = (text) => Math.ceil(ctx.measureText(text).width) + paddingX * 2;
|
|
1774
|
+
let required = margin.right - 1;
|
|
1775
|
+
const include = (text) => {
|
|
1776
|
+
const normalized = text?.trim();
|
|
1777
|
+
if (normalized) {
|
|
1778
|
+
required = Math.max(required, measure(normalized));
|
|
1779
|
+
}
|
|
1780
|
+
};
|
|
1781
|
+
const ticker2 = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
|
|
1782
|
+
const lastPoint2 = data[data.length - 1];
|
|
1783
|
+
if ((ticker2.visible ?? true) && labels.showLastPrice && lastPoint2) {
|
|
1784
|
+
include(formatPrice(lastPoint2.c));
|
|
1785
|
+
if (ticker2.labelSubtext) include(String(ticker2.labelSubtext));
|
|
1786
|
+
for (const subtext of ticker2.labelSubtexts ?? []) {
|
|
1787
|
+
include(String(subtext));
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
if (labels.showSymbolName) {
|
|
1791
|
+
include(labels.symbolName);
|
|
1792
|
+
}
|
|
1793
|
+
if (labels.showPreviousClose) {
|
|
1794
|
+
const previousCloseCandidate = Number.isFinite(labels.previousClosePrice) ? labels.previousClosePrice : data.length > 1 ? data[data.length - 2]?.c : Number.NaN;
|
|
1795
|
+
if (previousCloseCandidate !== void 0 && Number.isFinite(previousCloseCandidate)) {
|
|
1796
|
+
include(`PDC ${formatPrice(previousCloseCandidate)}`);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
if (labels.showHighLow && data.length > 0) {
|
|
1800
|
+
include(`H ${formatPrice(Math.max(...data.map((point) => point.h)))}`);
|
|
1801
|
+
include(`L ${formatPrice(Math.min(...data.map((point) => point.l)))}`);
|
|
1802
|
+
}
|
|
1803
|
+
if (labels.showBidAsk) {
|
|
1804
|
+
if (Number.isFinite(labels.bidPrice)) include(`B ${formatPrice(labels.bidPrice)}`);
|
|
1805
|
+
if (Number.isFinite(labels.askPrice)) include(`A ${formatPrice(labels.askPrice)}`);
|
|
1806
|
+
}
|
|
1807
|
+
for (const line of priceLines) {
|
|
1808
|
+
const mergedLine = { ...DEFAULT_PRICE_LINE_OPTIONS, ...line };
|
|
1809
|
+
if (mergedLine.visible && Number.isFinite(mergedLine.price)) {
|
|
1810
|
+
include(mergedLine.label ?? formatPrice(mergedLine.price));
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
for (const line of orderLines) {
|
|
1814
|
+
const mergedLine = { ...DEFAULT_ORDER_LINE_OPTIONS, ...line };
|
|
1815
|
+
const renderPrice = mergedLine.behavior === "follow" && Number.isFinite(mergedLine.followPrice) ? mergedLine.followPrice : mergedLine.price;
|
|
1816
|
+
if (mergedLine.visible && Number.isFinite(renderPrice)) {
|
|
1817
|
+
include(formatPrice(renderPrice));
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
const maxRightMargin = Math.max(margin.right, width - margin.left - 160);
|
|
1821
|
+
return Math.min(maxRightMargin, Math.max(margin.right, Math.ceil(required + 1)));
|
|
1822
|
+
};
|
|
1823
|
+
const rightMargin = estimateRightMargin();
|
|
1701
1824
|
const chartLeft = margin.left;
|
|
1702
1825
|
const chartTop = margin.top;
|
|
1703
|
-
const chartWidth = width - margin.left -
|
|
1826
|
+
const chartWidth = width - margin.left - rightMargin;
|
|
1704
1827
|
const fullChartHeight = height - margin.top - margin.bottom;
|
|
1705
1828
|
const fullChartBottom = chartTop + fullChartHeight;
|
|
1706
1829
|
const chartRight = chartLeft + chartWidth;
|
|
@@ -2053,7 +2176,7 @@ function createChart(element, options = {}) {
|
|
|
2053
2176
|
ctx.beginPath();
|
|
2054
2177
|
ctx.rect(chartLeft + 1, paneTop + 1, Math.max(0, chartWidth - 2), Math.max(0, paneHeight - 2));
|
|
2055
2178
|
ctx.clip();
|
|
2056
|
-
plugin.draw(
|
|
2179
|
+
const paneInfo = plugin.draw(
|
|
2057
2180
|
ctx,
|
|
2058
2181
|
{
|
|
2059
2182
|
data,
|
|
@@ -2086,6 +2209,63 @@ function createChart(element, options = {}) {
|
|
|
2086
2209
|
ctx.lineTo(crisp(chartRight), crisp(paneTop));
|
|
2087
2210
|
ctx.stroke();
|
|
2088
2211
|
ctx.restore();
|
|
2212
|
+
const axisInfo = paneInfo?.axis;
|
|
2213
|
+
if (axisInfo && Number.isFinite(axisInfo.min) && Number.isFinite(axisInfo.max) && axisInfo.max !== axisInfo.min) {
|
|
2214
|
+
const paneRange = axisInfo.max - axisInfo.min;
|
|
2215
|
+
const yFromPaneValue = (value) => {
|
|
2216
|
+
const ratio = (value - axisInfo.min) / paneRange;
|
|
2217
|
+
return paneBottom - ratio * paneHeight;
|
|
2218
|
+
};
|
|
2219
|
+
const formatPaneValue = (value) => {
|
|
2220
|
+
if (axisInfo.format) {
|
|
2221
|
+
return axisInfo.format(value);
|
|
2222
|
+
}
|
|
2223
|
+
const decimals = axisInfo.decimals ?? (Math.abs(paneRange) <= 2 ? 2 : Math.abs(paneRange) <= 20 ? 1 : 0);
|
|
2224
|
+
return value.toFixed(Math.max(0, Math.min(8, Math.round(decimals))));
|
|
2225
|
+
};
|
|
2226
|
+
const axisTicks = axisInfo.ticks && axisInfo.ticks.length > 0 ? axisInfo.ticks : [axisInfo.min, axisInfo.min + paneRange / 2, axisInfo.max];
|
|
2227
|
+
const uniqueTicks = Array.from(new Set(axisTicks.filter((tick) => Number.isFinite(tick))));
|
|
2228
|
+
ctx.save();
|
|
2229
|
+
ctx.font = `${yAxisFontSize}px ${mergedOptions.fontFamily}`;
|
|
2230
|
+
for (const tick of uniqueTicks) {
|
|
2231
|
+
if (tick < axisInfo.min || tick > axisInfo.max) {
|
|
2232
|
+
continue;
|
|
2233
|
+
}
|
|
2234
|
+
drawText(formatPaneValue(tick), chartRight + 6, yFromPaneValue(tick), "left", "middle", yAxis.textColor);
|
|
2235
|
+
}
|
|
2236
|
+
ctx.restore();
|
|
2237
|
+
if (labels.visible) {
|
|
2238
|
+
const prevFont = ctx.font;
|
|
2239
|
+
ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
|
|
2240
|
+
const legendTitle = paneInfo.title ?? plugin.name;
|
|
2241
|
+
const legendValues = paneInfo.legendValues ?? [];
|
|
2242
|
+
const legendParts = [
|
|
2243
|
+
legendTitle,
|
|
2244
|
+
...legendValues.map((value) => value.text ?? (value.value === void 0 ? "" : formatPaneValue(value.value))).filter(Boolean)
|
|
2245
|
+
].filter(Boolean);
|
|
2246
|
+
if (legendParts.length > 0) {
|
|
2247
|
+
drawText(legendParts.join(" "), chartLeft + 10, paneTop + 8, "left", "top", labels.indicatorTextColor);
|
|
2248
|
+
}
|
|
2249
|
+
for (const label of paneInfo.valueLabels ?? []) {
|
|
2250
|
+
if (!labels.showIndicatorValueLabels) {
|
|
2251
|
+
continue;
|
|
2252
|
+
}
|
|
2253
|
+
if (!Number.isFinite(label.value) || label.value < axisInfo.min || label.value > axisInfo.max) {
|
|
2254
|
+
continue;
|
|
2255
|
+
}
|
|
2256
|
+
const text = label.text ?? formatPaneValue(label.value);
|
|
2257
|
+
const labelPaddingX = 7;
|
|
2258
|
+
const labelHeight = Math.max(16, yAxisFontSize + 8);
|
|
2259
|
+
const labelWidth = getRightAxisLabelWidth(chartRight, Math.ceil(ctx.measureText(text).width) + labelPaddingX * 2);
|
|
2260
|
+
const labelX = getRightAxisLabelX(chartRight);
|
|
2261
|
+
const labelY = clamp(yFromPaneValue(label.value) - labelHeight / 2, paneTop + 2, paneBottom - labelHeight - 2);
|
|
2262
|
+
ctx.fillStyle = label.backgroundColor ?? label.color ?? labels.backgroundColor;
|
|
2263
|
+
fillRoundedRect(Math.round(labelX), Math.round(labelY), labelWidth, labelHeight, Math.max(0, labels.borderRadius));
|
|
2264
|
+
drawText(text, labelX + labelPaddingX, labelY + labelHeight / 2, "left", "middle", label.textColor ?? labels.textColor);
|
|
2265
|
+
}
|
|
2266
|
+
ctx.font = prevFont;
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2089
2269
|
});
|
|
2090
2270
|
}
|
|
2091
2271
|
if (crosshair.visible && crosshairPoint && crosshair.showVertical) {
|
|
@@ -2117,7 +2297,6 @@ function createChart(element, options = {}) {
|
|
|
2117
2297
|
drawText(formatPrice(price), chartRight + 6, y, "left", "middle", yAxis.textColor);
|
|
2118
2298
|
ctx.font = prevFont;
|
|
2119
2299
|
}
|
|
2120
|
-
const labels = { ...DEFAULT_LABELS_OPTIONS, ...mergedOptions.labels ?? {} };
|
|
2121
2300
|
resetLabelSlots(labels.noOverlapping);
|
|
2122
2301
|
const priceAxisLabels = [];
|
|
2123
2302
|
const addPriceAxisLabel = (label) => {
|
|
@@ -2281,12 +2460,13 @@ function createChart(element, options = {}) {
|
|
|
2281
2460
|
ctx.font = baseFont;
|
|
2282
2461
|
}
|
|
2283
2462
|
const labelTextWidth = Math.max(primaryWidth, subtextWidth);
|
|
2463
|
+
const labelWidth = getRightAxisLabelWidth(chartRight, labelTextWidth);
|
|
2284
2464
|
return {
|
|
2285
2465
|
...label,
|
|
2286
2466
|
subtexts,
|
|
2287
2467
|
subtextFontSize,
|
|
2288
2468
|
height: labelHeight,
|
|
2289
|
-
width:
|
|
2469
|
+
width: labelWidth,
|
|
2290
2470
|
targetY: clamp(yFromPrice(label.price), chartTop + 1, chartBottom - 1) - labelHeight / 2,
|
|
2291
2471
|
y: 0
|
|
2292
2472
|
};
|
|
@@ -2316,7 +2496,7 @@ function createChart(element, options = {}) {
|
|
|
2316
2496
|
);
|
|
2317
2497
|
rightAxisLabelSlots.sort((a, b) => a.y - b.y);
|
|
2318
2498
|
}
|
|
2319
|
-
const labelX = chartRight
|
|
2499
|
+
const labelX = getRightAxisLabelX(chartRight);
|
|
2320
2500
|
for (const label of positionedLabels) {
|
|
2321
2501
|
ctx.fillStyle = label.backgroundColor;
|
|
2322
2502
|
fillRoundedRect(Math.round(labelX), Math.round(label.y), label.width, label.height, labelRadius);
|
|
@@ -2361,7 +2541,14 @@ function createChart(element, options = {}) {
|
|
|
2361
2541
|
const prevFont = ctx.font;
|
|
2362
2542
|
ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
|
|
2363
2543
|
const legendText = labelEntries.join(" ");
|
|
2364
|
-
|
|
2544
|
+
const offsetX = Math.max(0, Number(labels.indicatorLegendOffsetX) || 0);
|
|
2545
|
+
const offsetY = Math.max(0, Number(labels.indicatorLegendOffsetY) || 0);
|
|
2546
|
+
const position = labels.indicatorLegendPosition;
|
|
2547
|
+
const isRight = position === "top-right" || position === "bottom-right";
|
|
2548
|
+
const isBottom = position === "bottom-left" || position === "bottom-right";
|
|
2549
|
+
const legendX = isRight ? chartRight - offsetX : chartLeft + offsetX;
|
|
2550
|
+
const legendY = isBottom ? chartBottom - offsetY : chartTop + offsetY;
|
|
2551
|
+
drawText(legendText, legendX, legendY, isRight ? "right" : "left", isBottom ? "bottom" : "top", labels.indicatorTextColor);
|
|
2365
2552
|
ctx.font = prevFont;
|
|
2366
2553
|
}
|
|
2367
2554
|
}
|
|
@@ -2419,8 +2606,8 @@ function createChart(element, options = {}) {
|
|
|
2419
2606
|
if (crosshair.showPriceLabel) {
|
|
2420
2607
|
const hoverPrice = yMin + (chartBottom - cy) / chartHeight * yRange;
|
|
2421
2608
|
const priceText = formatPrice(hoverPrice);
|
|
2422
|
-
const priceWidth = getPriceLabelWidth(priceText, labelPaddingX);
|
|
2423
|
-
const priceX = chartRight
|
|
2609
|
+
const priceWidth = getRightAxisLabelWidth(chartRight, getPriceLabelWidth(priceText, labelPaddingX));
|
|
2610
|
+
const priceX = getRightAxisLabelX(chartRight);
|
|
2424
2611
|
const priceY = clamp(cy - labelHeight / 2, chartTop, chartBottom - labelHeight);
|
|
2425
2612
|
ctx.fillStyle = labelBackground;
|
|
2426
2613
|
fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, labelRadius);
|
|
@@ -80,13 +80,46 @@ interface IndicatorRenderContext {
|
|
|
80
80
|
upColor: string;
|
|
81
81
|
downColor: string;
|
|
82
82
|
}
|
|
83
|
+
interface IndicatorPaneAxisOptions {
|
|
84
|
+
min: number;
|
|
85
|
+
max: number;
|
|
86
|
+
ticks?: number[];
|
|
87
|
+
decimals?: number;
|
|
88
|
+
format?: (value: number) => string;
|
|
89
|
+
}
|
|
90
|
+
interface IndicatorPaneGuideLine {
|
|
91
|
+
value: number;
|
|
92
|
+
label?: string;
|
|
93
|
+
color?: string;
|
|
94
|
+
style?: "solid" | "dotted" | "dashed";
|
|
95
|
+
}
|
|
96
|
+
interface IndicatorPaneValue {
|
|
97
|
+
label?: string;
|
|
98
|
+
value?: number;
|
|
99
|
+
text?: string;
|
|
100
|
+
color?: string;
|
|
101
|
+
}
|
|
102
|
+
interface IndicatorPaneValueLabel {
|
|
103
|
+
value: number;
|
|
104
|
+
text?: string;
|
|
105
|
+
color?: string;
|
|
106
|
+
backgroundColor?: string;
|
|
107
|
+
textColor?: string;
|
|
108
|
+
}
|
|
109
|
+
interface IndicatorPaneRenderInfo {
|
|
110
|
+
title?: string;
|
|
111
|
+
axis?: IndicatorPaneAxisOptions;
|
|
112
|
+
guideLines?: IndicatorPaneGuideLine[];
|
|
113
|
+
legendValues?: IndicatorPaneValue[];
|
|
114
|
+
valueLabels?: IndicatorPaneValueLabel[];
|
|
115
|
+
}
|
|
83
116
|
interface IndicatorPlugin<TInputs extends Record<string, unknown> = Record<string, unknown>> {
|
|
84
117
|
id: string;
|
|
85
118
|
name: string;
|
|
86
119
|
pane?: IndicatorPane;
|
|
87
120
|
paneHeightRatio?: number;
|
|
88
121
|
defaultInputs?: TInputs;
|
|
89
|
-
draw: (ctx: CanvasRenderingContext2D, renderContext: IndicatorRenderContext, inputs: TInputs) => void;
|
|
122
|
+
draw: (ctx: CanvasRenderingContext2D, renderContext: IndicatorRenderContext, inputs: TInputs) => void | IndicatorPaneRenderInfo;
|
|
90
123
|
}
|
|
91
124
|
interface BuiltInIndicatorInfo {
|
|
92
125
|
id: string;
|
|
@@ -282,6 +315,10 @@ interface LabelsOptions {
|
|
|
282
315
|
askPrice?: number;
|
|
283
316
|
showIndicatorNames?: boolean;
|
|
284
317
|
showIndicatorValues?: boolean;
|
|
318
|
+
showIndicatorValueLabels?: boolean;
|
|
319
|
+
indicatorLegendPosition?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
|
320
|
+
indicatorLegendOffsetX?: number;
|
|
321
|
+
indicatorLegendOffsetY?: number;
|
|
285
322
|
showCountdownToBarClose?: boolean;
|
|
286
323
|
noOverlapping?: boolean;
|
|
287
324
|
backgroundColor?: string;
|
|
@@ -361,4 +398,4 @@ interface ViewportState {
|
|
|
361
398
|
}
|
|
362
399
|
declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
|
|
363
400
|
|
|
364
|
-
export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
|
|
401
|
+
export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
|