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
|
@@ -98,6 +98,9 @@ var DEFAULT_LABELS_OPTIONS = {
|
|
|
98
98
|
askPrice: Number.NaN,
|
|
99
99
|
showIndicatorNames: false,
|
|
100
100
|
showIndicatorValues: false,
|
|
101
|
+
indicatorLegendPosition: "top-left",
|
|
102
|
+
indicatorLegendOffsetX: 10,
|
|
103
|
+
indicatorLegendOffsetY: 10,
|
|
101
104
|
showCountdownToBarClose: false,
|
|
102
105
|
noOverlapping: true,
|
|
103
106
|
backgroundColor: "#0b1220",
|
|
@@ -531,7 +534,7 @@ var drawOverlaySeries = (ctx, renderContext, values, color, width) => {
|
|
|
531
534
|
}
|
|
532
535
|
ctx.restore();
|
|
533
536
|
};
|
|
534
|
-
var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride, maxOverride, guideLines) => {
|
|
537
|
+
var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride, maxOverride, guideLines, options = {}) => {
|
|
535
538
|
const visible = [];
|
|
536
539
|
for (let index = renderContext.startIndex; index <= renderContext.endIndex; index += 1) {
|
|
537
540
|
const value = values[index];
|
|
@@ -539,7 +542,7 @@ var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride,
|
|
|
539
542
|
visible.push(value);
|
|
540
543
|
}
|
|
541
544
|
}
|
|
542
|
-
if (visible.length === 0) return;
|
|
545
|
+
if (visible.length === 0) return void 0;
|
|
543
546
|
const minValue = minOverride ?? Math.min(...visible);
|
|
544
547
|
const maxValue = maxOverride ?? Math.max(...visible);
|
|
545
548
|
const range = maxValue - minValue || 1;
|
|
@@ -588,6 +591,51 @@ var drawSeparateSeries = (ctx, renderContext, values, color, width, minOverride,
|
|
|
588
591
|
}
|
|
589
592
|
}
|
|
590
593
|
ctx.restore();
|
|
594
|
+
let latestValue = null;
|
|
595
|
+
for (let index = values.length - 1; index >= 0; index -= 1) {
|
|
596
|
+
const value = values[index];
|
|
597
|
+
if (Number.isFinite(value ?? Number.NaN)) {
|
|
598
|
+
latestValue = value;
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const decimals = options.decimals ?? 2;
|
|
603
|
+
const formatValue = (value) => value.toFixed(decimals);
|
|
604
|
+
const axisTicks = options.axisTicks ?? guideLines;
|
|
605
|
+
const paneInfo = {
|
|
606
|
+
...options.title ? { title: options.title } : {},
|
|
607
|
+
axis: {
|
|
608
|
+
min: minValue,
|
|
609
|
+
max: maxValue,
|
|
610
|
+
...axisTicks ? { ticks: axisTicks } : {},
|
|
611
|
+
decimals
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
if (guideLines) {
|
|
615
|
+
paneInfo.guideLines = guideLines.map((value) => ({ value, label: formatValue(value), style: "dotted" }));
|
|
616
|
+
}
|
|
617
|
+
if (latestValue !== null) {
|
|
618
|
+
paneInfo.legendValues = [
|
|
619
|
+
{
|
|
620
|
+
...options.legendLabel ? { label: options.legendLabel } : {},
|
|
621
|
+
value: latestValue,
|
|
622
|
+
text: formatValue(latestValue),
|
|
623
|
+
color
|
|
624
|
+
}
|
|
625
|
+
];
|
|
626
|
+
}
|
|
627
|
+
if (options.valueLabel !== false && latestValue !== null) {
|
|
628
|
+
paneInfo.valueLabels = [
|
|
629
|
+
{
|
|
630
|
+
value: latestValue,
|
|
631
|
+
text: formatValue(latestValue),
|
|
632
|
+
color,
|
|
633
|
+
backgroundColor: color,
|
|
634
|
+
textColor: "#0f172a"
|
|
635
|
+
}
|
|
636
|
+
];
|
|
637
|
+
}
|
|
638
|
+
return paneInfo;
|
|
591
639
|
};
|
|
592
640
|
var getPercentileValue = (values, percentile) => {
|
|
593
641
|
if (values.length === 0) {
|
|
@@ -760,7 +808,10 @@ var BUILTIN_STDDEV_INDICATOR = {
|
|
|
760
808
|
renderContext.data,
|
|
761
809
|
() => computeStdDevSeries(renderContext.data, length, inputs.source ?? "close")
|
|
762
810
|
);
|
|
763
|
-
drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#f97316", Number(inputs.width) || 2
|
|
811
|
+
return drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#f97316", Number(inputs.width) || 2, void 0, void 0, void 0, {
|
|
812
|
+
title: `StdDev ${length}`,
|
|
813
|
+
decimals: 2
|
|
814
|
+
});
|
|
764
815
|
}
|
|
765
816
|
};
|
|
766
817
|
var BUILTIN_ATR_INDICATOR = {
|
|
@@ -772,7 +823,10 @@ var BUILTIN_ATR_INDICATOR = {
|
|
|
772
823
|
draw: (ctx, renderContext, inputs) => {
|
|
773
824
|
const length = clampIndicatorLength(inputs.length, 14);
|
|
774
825
|
const values = withCachedSeries(`atr|${length}`, renderContext.data, () => computeAtrSeries(renderContext.data, length));
|
|
775
|
-
drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#eab308", Number(inputs.width) || 2
|
|
826
|
+
return drawSeparateSeries(ctx, renderContext, values, inputs.color ?? "#eab308", Number(inputs.width) || 2, void 0, void 0, void 0, {
|
|
827
|
+
title: `ATR ${length}`,
|
|
828
|
+
decimals: 2
|
|
829
|
+
});
|
|
776
830
|
}
|
|
777
831
|
};
|
|
778
832
|
var BUILTIN_RSI_INDICATOR = {
|
|
@@ -784,7 +838,7 @@ var BUILTIN_RSI_INDICATOR = {
|
|
|
784
838
|
draw: (ctx, renderContext, inputs) => {
|
|
785
839
|
const length = clampIndicatorLength(inputs.length, 14);
|
|
786
840
|
const values = withCachedSeries(`rsi|${length}`, renderContext.data, () => computeRsiSeries(renderContext.data, length));
|
|
787
|
-
drawSeparateSeries(
|
|
841
|
+
return drawSeparateSeries(
|
|
788
842
|
ctx,
|
|
789
843
|
renderContext,
|
|
790
844
|
values,
|
|
@@ -792,7 +846,12 @@ var BUILTIN_RSI_INDICATOR = {
|
|
|
792
846
|
Number(inputs.width) || 2,
|
|
793
847
|
0,
|
|
794
848
|
100,
|
|
795
|
-
[30, 50, 70]
|
|
849
|
+
[30, 50, 70],
|
|
850
|
+
{
|
|
851
|
+
title: `RSI ${length}`,
|
|
852
|
+
axisTicks: [0, 30, 50, 70, 100],
|
|
853
|
+
decimals: 2
|
|
854
|
+
}
|
|
796
855
|
);
|
|
797
856
|
}
|
|
798
857
|
};
|
|
@@ -1356,6 +1415,11 @@ function createChart(element, options = {}) {
|
|
|
1356
1415
|
const pad = (value) => String(value).padStart(2, "0");
|
|
1357
1416
|
return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${pad(minutes)}:${pad(seconds)}`;
|
|
1358
1417
|
};
|
|
1418
|
+
const getRightAxisLabelX = (chartRight) => chartRight + 1;
|
|
1419
|
+
const getRightAxisLabelWidth = (chartRight, contentWidth) => {
|
|
1420
|
+
const scaleWidth = Math.max(0, Math.floor(width - getRightAxisLabelX(chartRight)));
|
|
1421
|
+
return Math.max(contentWidth, scaleWidth);
|
|
1422
|
+
};
|
|
1359
1423
|
const drawPriceLine = (line, yFromPrice, chartLeft, chartTop, chartRight, chartBottom) => {
|
|
1360
1424
|
const mergedLine = { ...DEFAULT_PRICE_LINE_OPTIONS, ...line };
|
|
1361
1425
|
if (!mergedLine.visible || !Number.isFinite(mergedLine.price)) {
|
|
@@ -1380,8 +1444,9 @@ function createChart(element, options = {}) {
|
|
|
1380
1444
|
ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
|
|
1381
1445
|
const labelPaddingX = 8;
|
|
1382
1446
|
const labelHeight = 20;
|
|
1383
|
-
const
|
|
1384
|
-
const
|
|
1447
|
+
const measuredLabelWidth = mergedLine.label === void 0 ? getPriceLabelWidth(labelText, labelPaddingX) : Math.ceil(ctx.measureText(labelText).width) + labelPaddingX * 2;
|
|
1448
|
+
const labelWidth = getRightAxisLabelWidth(chartRight, measuredLabelWidth);
|
|
1449
|
+
const labelX = getRightAxisLabelX(chartRight);
|
|
1385
1450
|
const labelY = placeRightAxisLabel(lineY - labelHeight / 2, labelHeight, chartTop, chartBottom - labelHeight);
|
|
1386
1451
|
ctx.fillStyle = mergedLine.labelBackgroundColor;
|
|
1387
1452
|
fillRoundedRect(
|
|
@@ -1619,11 +1684,11 @@ function createChart(element, options = {}) {
|
|
|
1619
1684
|
const priceText = formatPrice(renderPrice);
|
|
1620
1685
|
const pricePaddingX = 8;
|
|
1621
1686
|
const measuredPriceWidth = getPriceLabelWidth(priceText, pricePaddingX);
|
|
1622
|
-
const priceWidth = mergedLine.id === void 0 ? measuredPriceWidth : Math.max(measuredPriceWidth, orderPriceTagWidthById.get(mergedLine.id) ?? 0);
|
|
1687
|
+
const priceWidth = mergedLine.id === void 0 ? getRightAxisLabelWidth(chartRight, measuredPriceWidth) : getRightAxisLabelWidth(chartRight, Math.max(measuredPriceWidth, orderPriceTagWidthById.get(mergedLine.id) ?? 0));
|
|
1623
1688
|
if (mergedLine.id) {
|
|
1624
1689
|
orderPriceTagWidthById.set(mergedLine.id, priceWidth);
|
|
1625
1690
|
}
|
|
1626
|
-
const priceX = chartRight
|
|
1691
|
+
const priceX = getRightAxisLabelX(chartRight);
|
|
1627
1692
|
const priceY = placeRightAxisLabel(targetLabelY, labelHeight, chartTop, chartBottom - labelHeight);
|
|
1628
1693
|
ctx.fillStyle = mergedLine.labelBackgroundColor ?? color;
|
|
1629
1694
|
fillRoundedRect(Math.round(priceX), Math.round(priceY), priceWidth, labelHeight, borderRadius);
|
|
@@ -1698,9 +1763,63 @@ function createChart(element, options = {}) {
|
|
|
1698
1763
|
ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
|
|
1699
1764
|
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
1700
1765
|
ctx.fillRect(0, 0, width, height);
|
|
1766
|
+
const labels = { ...DEFAULT_LABELS_OPTIONS, ...mergedOptions.labels ?? {} };
|
|
1767
|
+
const estimateRightMargin = () => {
|
|
1768
|
+
const paddingX = Math.max(4, labels.labelPaddingX);
|
|
1769
|
+
const measure = (text) => Math.ceil(ctx.measureText(text).width) + paddingX * 2;
|
|
1770
|
+
let required = margin.right - 1;
|
|
1771
|
+
const include = (text) => {
|
|
1772
|
+
const normalized = text?.trim();
|
|
1773
|
+
if (normalized) {
|
|
1774
|
+
required = Math.max(required, measure(normalized));
|
|
1775
|
+
}
|
|
1776
|
+
};
|
|
1777
|
+
const ticker2 = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
|
|
1778
|
+
const lastPoint2 = data[data.length - 1];
|
|
1779
|
+
if ((ticker2.visible ?? true) && labels.showLastPrice && lastPoint2) {
|
|
1780
|
+
include(formatPrice(lastPoint2.c));
|
|
1781
|
+
if (ticker2.labelSubtext) include(String(ticker2.labelSubtext));
|
|
1782
|
+
for (const subtext of ticker2.labelSubtexts ?? []) {
|
|
1783
|
+
include(String(subtext));
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
if (labels.showSymbolName) {
|
|
1787
|
+
include(labels.symbolName);
|
|
1788
|
+
}
|
|
1789
|
+
if (labels.showPreviousClose) {
|
|
1790
|
+
const previousCloseCandidate = Number.isFinite(labels.previousClosePrice) ? labels.previousClosePrice : data.length > 1 ? data[data.length - 2]?.c : Number.NaN;
|
|
1791
|
+
if (previousCloseCandidate !== void 0 && Number.isFinite(previousCloseCandidate)) {
|
|
1792
|
+
include(`PDC ${formatPrice(previousCloseCandidate)}`);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
if (labels.showHighLow && data.length > 0) {
|
|
1796
|
+
include(`H ${formatPrice(Math.max(...data.map((point) => point.h)))}`);
|
|
1797
|
+
include(`L ${formatPrice(Math.min(...data.map((point) => point.l)))}`);
|
|
1798
|
+
}
|
|
1799
|
+
if (labels.showBidAsk) {
|
|
1800
|
+
if (Number.isFinite(labels.bidPrice)) include(`B ${formatPrice(labels.bidPrice)}`);
|
|
1801
|
+
if (Number.isFinite(labels.askPrice)) include(`A ${formatPrice(labels.askPrice)}`);
|
|
1802
|
+
}
|
|
1803
|
+
for (const line of priceLines) {
|
|
1804
|
+
const mergedLine = { ...DEFAULT_PRICE_LINE_OPTIONS, ...line };
|
|
1805
|
+
if (mergedLine.visible && Number.isFinite(mergedLine.price)) {
|
|
1806
|
+
include(mergedLine.label ?? formatPrice(mergedLine.price));
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
for (const line of orderLines) {
|
|
1810
|
+
const mergedLine = { ...DEFAULT_ORDER_LINE_OPTIONS, ...line };
|
|
1811
|
+
const renderPrice = mergedLine.behavior === "follow" && Number.isFinite(mergedLine.followPrice) ? mergedLine.followPrice : mergedLine.price;
|
|
1812
|
+
if (mergedLine.visible && Number.isFinite(renderPrice)) {
|
|
1813
|
+
include(formatPrice(renderPrice));
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
const maxRightMargin = Math.max(margin.right, width - margin.left - 160);
|
|
1817
|
+
return Math.min(maxRightMargin, Math.max(margin.right, Math.ceil(required + 1)));
|
|
1818
|
+
};
|
|
1819
|
+
const rightMargin = estimateRightMargin();
|
|
1701
1820
|
const chartLeft = margin.left;
|
|
1702
1821
|
const chartTop = margin.top;
|
|
1703
|
-
const chartWidth = width - margin.left -
|
|
1822
|
+
const chartWidth = width - margin.left - rightMargin;
|
|
1704
1823
|
const fullChartHeight = height - margin.top - margin.bottom;
|
|
1705
1824
|
const fullChartBottom = chartTop + fullChartHeight;
|
|
1706
1825
|
const chartRight = chartLeft + chartWidth;
|
|
@@ -2053,7 +2172,7 @@ function createChart(element, options = {}) {
|
|
|
2053
2172
|
ctx.beginPath();
|
|
2054
2173
|
ctx.rect(chartLeft + 1, paneTop + 1, Math.max(0, chartWidth - 2), Math.max(0, paneHeight - 2));
|
|
2055
2174
|
ctx.clip();
|
|
2056
|
-
plugin.draw(
|
|
2175
|
+
const paneInfo = plugin.draw(
|
|
2057
2176
|
ctx,
|
|
2058
2177
|
{
|
|
2059
2178
|
data,
|
|
@@ -2086,6 +2205,60 @@ function createChart(element, options = {}) {
|
|
|
2086
2205
|
ctx.lineTo(crisp(chartRight), crisp(paneTop));
|
|
2087
2206
|
ctx.stroke();
|
|
2088
2207
|
ctx.restore();
|
|
2208
|
+
const axisInfo = paneInfo?.axis;
|
|
2209
|
+
if (axisInfo && Number.isFinite(axisInfo.min) && Number.isFinite(axisInfo.max) && axisInfo.max !== axisInfo.min) {
|
|
2210
|
+
const paneRange = axisInfo.max - axisInfo.min;
|
|
2211
|
+
const yFromPaneValue = (value) => {
|
|
2212
|
+
const ratio = (value - axisInfo.min) / paneRange;
|
|
2213
|
+
return paneBottom - ratio * paneHeight;
|
|
2214
|
+
};
|
|
2215
|
+
const formatPaneValue = (value) => {
|
|
2216
|
+
if (axisInfo.format) {
|
|
2217
|
+
return axisInfo.format(value);
|
|
2218
|
+
}
|
|
2219
|
+
const decimals = axisInfo.decimals ?? (Math.abs(paneRange) <= 2 ? 2 : Math.abs(paneRange) <= 20 ? 1 : 0);
|
|
2220
|
+
return value.toFixed(Math.max(0, Math.min(8, Math.round(decimals))));
|
|
2221
|
+
};
|
|
2222
|
+
const axisTicks = axisInfo.ticks && axisInfo.ticks.length > 0 ? axisInfo.ticks : [axisInfo.min, axisInfo.min + paneRange / 2, axisInfo.max];
|
|
2223
|
+
const uniqueTicks = Array.from(new Set(axisTicks.filter((tick) => Number.isFinite(tick))));
|
|
2224
|
+
ctx.save();
|
|
2225
|
+
ctx.font = `${yAxisFontSize}px ${mergedOptions.fontFamily}`;
|
|
2226
|
+
for (const tick of uniqueTicks) {
|
|
2227
|
+
if (tick < axisInfo.min || tick > axisInfo.max) {
|
|
2228
|
+
continue;
|
|
2229
|
+
}
|
|
2230
|
+
drawText(formatPaneValue(tick), chartRight + 6, yFromPaneValue(tick), "left", "middle", yAxis.textColor);
|
|
2231
|
+
}
|
|
2232
|
+
ctx.restore();
|
|
2233
|
+
if (labels.visible) {
|
|
2234
|
+
const prevFont = ctx.font;
|
|
2235
|
+
ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
|
|
2236
|
+
const legendTitle = paneInfo.title ?? plugin.name;
|
|
2237
|
+
const legendValues = paneInfo.legendValues ?? [];
|
|
2238
|
+
const legendParts = [
|
|
2239
|
+
legendTitle,
|
|
2240
|
+
...legendValues.map((value) => value.text ?? (value.value === void 0 ? "" : formatPaneValue(value.value))).filter(Boolean)
|
|
2241
|
+
].filter(Boolean);
|
|
2242
|
+
if (legendParts.length > 0) {
|
|
2243
|
+
drawText(legendParts.join(" "), chartLeft + 10, paneTop + 8, "left", "top", labels.indicatorTextColor);
|
|
2244
|
+
}
|
|
2245
|
+
for (const label of paneInfo.valueLabels ?? []) {
|
|
2246
|
+
if (!Number.isFinite(label.value) || label.value < axisInfo.min || label.value > axisInfo.max) {
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2249
|
+
const text = label.text ?? formatPaneValue(label.value);
|
|
2250
|
+
const labelPaddingX = 7;
|
|
2251
|
+
const labelHeight = Math.max(16, yAxisFontSize + 8);
|
|
2252
|
+
const labelWidth = getRightAxisLabelWidth(chartRight, Math.ceil(ctx.measureText(text).width) + labelPaddingX * 2);
|
|
2253
|
+
const labelX = getRightAxisLabelX(chartRight);
|
|
2254
|
+
const labelY = clamp(yFromPaneValue(label.value) - labelHeight / 2, paneTop + 2, paneBottom - labelHeight - 2);
|
|
2255
|
+
ctx.fillStyle = label.backgroundColor ?? label.color ?? labels.backgroundColor;
|
|
2256
|
+
fillRoundedRect(Math.round(labelX), Math.round(labelY), labelWidth, labelHeight, Math.max(0, labels.borderRadius));
|
|
2257
|
+
drawText(text, labelX + labelPaddingX, labelY + labelHeight / 2, "left", "middle", label.textColor ?? labels.textColor);
|
|
2258
|
+
}
|
|
2259
|
+
ctx.font = prevFont;
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2089
2262
|
});
|
|
2090
2263
|
}
|
|
2091
2264
|
if (crosshair.visible && crosshairPoint && crosshair.showVertical) {
|
|
@@ -2117,7 +2290,6 @@ function createChart(element, options = {}) {
|
|
|
2117
2290
|
drawText(formatPrice(price), chartRight + 6, y, "left", "middle", yAxis.textColor);
|
|
2118
2291
|
ctx.font = prevFont;
|
|
2119
2292
|
}
|
|
2120
|
-
const labels = { ...DEFAULT_LABELS_OPTIONS, ...mergedOptions.labels ?? {} };
|
|
2121
2293
|
resetLabelSlots(labels.noOverlapping);
|
|
2122
2294
|
const priceAxisLabels = [];
|
|
2123
2295
|
const addPriceAxisLabel = (label) => {
|
|
@@ -2281,12 +2453,13 @@ function createChart(element, options = {}) {
|
|
|
2281
2453
|
ctx.font = baseFont;
|
|
2282
2454
|
}
|
|
2283
2455
|
const labelTextWidth = Math.max(primaryWidth, subtextWidth);
|
|
2456
|
+
const labelWidth = getRightAxisLabelWidth(chartRight, labelTextWidth);
|
|
2284
2457
|
return {
|
|
2285
2458
|
...label,
|
|
2286
2459
|
subtexts,
|
|
2287
2460
|
subtextFontSize,
|
|
2288
2461
|
height: labelHeight,
|
|
2289
|
-
width:
|
|
2462
|
+
width: labelWidth,
|
|
2290
2463
|
targetY: clamp(yFromPrice(label.price), chartTop + 1, chartBottom - 1) - labelHeight / 2,
|
|
2291
2464
|
y: 0
|
|
2292
2465
|
};
|
|
@@ -2316,7 +2489,7 @@ function createChart(element, options = {}) {
|
|
|
2316
2489
|
);
|
|
2317
2490
|
rightAxisLabelSlots.sort((a, b) => a.y - b.y);
|
|
2318
2491
|
}
|
|
2319
|
-
const labelX = chartRight
|
|
2492
|
+
const labelX = getRightAxisLabelX(chartRight);
|
|
2320
2493
|
for (const label of positionedLabels) {
|
|
2321
2494
|
ctx.fillStyle = label.backgroundColor;
|
|
2322
2495
|
fillRoundedRect(Math.round(labelX), Math.round(label.y), label.width, label.height, labelRadius);
|
|
@@ -2361,7 +2534,14 @@ function createChart(element, options = {}) {
|
|
|
2361
2534
|
const prevFont = ctx.font;
|
|
2362
2535
|
ctx.font = `${Math.max(8, axis.fontSize)}px ${mergedOptions.fontFamily}`;
|
|
2363
2536
|
const legendText = labelEntries.join(" ");
|
|
2364
|
-
|
|
2537
|
+
const offsetX = Math.max(0, Number(labels.indicatorLegendOffsetX) || 0);
|
|
2538
|
+
const offsetY = Math.max(0, Number(labels.indicatorLegendOffsetY) || 0);
|
|
2539
|
+
const position = labels.indicatorLegendPosition;
|
|
2540
|
+
const isRight = position === "top-right" || position === "bottom-right";
|
|
2541
|
+
const isBottom = position === "bottom-left" || position === "bottom-right";
|
|
2542
|
+
const legendX = isRight ? chartRight - offsetX : chartLeft + offsetX;
|
|
2543
|
+
const legendY = isBottom ? chartBottom - offsetY : chartTop + offsetY;
|
|
2544
|
+
drawText(legendText, legendX, legendY, isRight ? "right" : "left", isBottom ? "bottom" : "top", labels.indicatorTextColor);
|
|
2365
2545
|
ctx.font = prevFont;
|
|
2366
2546
|
}
|
|
2367
2547
|
}
|
|
@@ -2419,8 +2599,8 @@ function createChart(element, options = {}) {
|
|
|
2419
2599
|
if (crosshair.showPriceLabel) {
|
|
2420
2600
|
const hoverPrice = yMin + (chartBottom - cy) / chartHeight * yRange;
|
|
2421
2601
|
const priceText = formatPrice(hoverPrice);
|
|
2422
|
-
const priceWidth = getPriceLabelWidth(priceText, labelPaddingX);
|
|
2423
|
-
const priceX = chartRight
|
|
2602
|
+
const priceWidth = getRightAxisLabelWidth(chartRight, getPriceLabelWidth(priceText, labelPaddingX));
|
|
2603
|
+
const priceX = getRightAxisLabelX(chartRight);
|
|
2424
2604
|
const priceY = clamp(cy - labelHeight / 2, chartTop, chartBottom - labelHeight);
|
|
2425
2605
|
ctx.fillStyle = labelBackground;
|
|
2426
2606
|
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,9 @@ interface LabelsOptions {
|
|
|
282
315
|
askPrice?: number;
|
|
283
316
|
showIndicatorNames?: boolean;
|
|
284
317
|
showIndicatorValues?: boolean;
|
|
318
|
+
indicatorLegendPosition?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
|
319
|
+
indicatorLegendOffsetX?: number;
|
|
320
|
+
indicatorLegendOffsetY?: number;
|
|
285
321
|
showCountdownToBarClose?: boolean;
|
|
286
322
|
noOverlapping?: boolean;
|
|
287
323
|
backgroundColor?: string;
|
|
@@ -361,4 +397,4 @@ interface ViewportState {
|
|
|
361
397
|
}
|
|
362
398
|
declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
|
|
363
399
|
|
|
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 };
|
|
400
|
+
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 };
|