hyperprop-charting-library 0.1.61 → 0.1.63
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 +72 -27
- package/dist/hyperprop-charting-library.js +72 -27
- package/dist/index.cjs +72 -27
- package/dist/index.js +72 -27
- package/package.json +1 -1
|
@@ -1068,6 +1068,7 @@ function createChart(element, options = {}) {
|
|
|
1068
1068
|
element.innerHTML = "";
|
|
1069
1069
|
element.appendChild(canvas);
|
|
1070
1070
|
const margin = { top: 16, right: 72, bottom: 34, left: 12 };
|
|
1071
|
+
let cachedRightMargin = margin.right;
|
|
1071
1072
|
const minVisibleBars = Math.max(1, Math.floor(mergedOptions.minVisibleBars));
|
|
1072
1073
|
const maxVisibleBars = Math.max(minVisibleBars, Math.floor(mergedOptions.maxVisibleBars));
|
|
1073
1074
|
const maxPanBars = Math.max(0, Math.floor(mergedOptions.maxPanBars));
|
|
@@ -1289,14 +1290,34 @@ function createChart(element, options = {}) {
|
|
|
1289
1290
|
const decimalPart = decimals > 0 ? `.${"8".repeat(decimals)}` : "";
|
|
1290
1291
|
return `${integerPart}${decimalPart}`;
|
|
1291
1292
|
};
|
|
1293
|
+
const getMeasuredLabelWidth = (text, paddingX) => {
|
|
1294
|
+
return Math.ceil(ctx.measureText(text).width) + paddingX * 2;
|
|
1295
|
+
};
|
|
1296
|
+
const getStabilizedNumericLabelWidth = (text, paddingX) => {
|
|
1297
|
+
const measured = getMeasuredLabelWidth(text, paddingX);
|
|
1298
|
+
if (!mergedOptions.stabilizePriceLabels || !/\d/.test(text)) {
|
|
1299
|
+
return measured;
|
|
1300
|
+
}
|
|
1301
|
+
let stableWidth = measured;
|
|
1302
|
+
for (const digit of ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]) {
|
|
1303
|
+
stableWidth = Math.max(stableWidth, getMeasuredLabelWidth(text.replace(/\d/g, digit), paddingX));
|
|
1304
|
+
}
|
|
1305
|
+
return stableWidth;
|
|
1306
|
+
};
|
|
1292
1307
|
const getPriceLabelWidth = (priceText, paddingX) => {
|
|
1293
|
-
const measured =
|
|
1308
|
+
const measured = getStabilizedNumericLabelWidth(priceText, paddingX);
|
|
1294
1309
|
if (!mergedOptions.stabilizePriceLabels) {
|
|
1295
1310
|
return measured;
|
|
1296
1311
|
}
|
|
1297
|
-
const templateWidth =
|
|
1312
|
+
const templateWidth = getStabilizedNumericLabelWidth(getStabilizedPriceTemplate(), paddingX);
|
|
1298
1313
|
return Math.max(measured, templateWidth);
|
|
1299
1314
|
};
|
|
1315
|
+
const getStableLabelWidth = (text, paddingX) => {
|
|
1316
|
+
return getStabilizedNumericLabelWidth(text, paddingX);
|
|
1317
|
+
};
|
|
1318
|
+
const resetRightMarginCache = () => {
|
|
1319
|
+
cachedRightMargin = margin.right;
|
|
1320
|
+
};
|
|
1300
1321
|
const parseData = (nextData) => {
|
|
1301
1322
|
const dedupedByTime = /* @__PURE__ */ new Map();
|
|
1302
1323
|
for (const point of nextData) {
|
|
@@ -1822,22 +1843,33 @@ function createChart(element, options = {}) {
|
|
|
1822
1843
|
const labels = { ...DEFAULT_LABELS_OPTIONS, ...mergedOptions.labels ?? {} };
|
|
1823
1844
|
const estimateRightMargin = () => {
|
|
1824
1845
|
const paddingX = Math.max(4, labels.labelPaddingX);
|
|
1825
|
-
const measure = (text) => Math.ceil(ctx.measureText(text).width) + paddingX * 2;
|
|
1826
1846
|
let required = margin.right - 1;
|
|
1847
|
+
const includeWidth = (contentWidth) => {
|
|
1848
|
+
if (Number.isFinite(contentWidth)) {
|
|
1849
|
+
required = Math.max(required, contentWidth);
|
|
1850
|
+
}
|
|
1851
|
+
};
|
|
1827
1852
|
const include = (text) => {
|
|
1828
1853
|
const normalized = text?.trim();
|
|
1829
1854
|
if (normalized) {
|
|
1830
|
-
|
|
1855
|
+
includeWidth(getStableLabelWidth(normalized, paddingX));
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
const includePrice = (price) => {
|
|
1859
|
+
if (Number.isFinite(price)) {
|
|
1860
|
+
includeWidth(getPriceLabelWidth(formatPrice(price), paddingX));
|
|
1831
1861
|
}
|
|
1832
1862
|
};
|
|
1833
1863
|
const ticker2 = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
|
|
1834
1864
|
const lastPoint2 = data[data.length - 1];
|
|
1835
1865
|
if ((ticker2.visible ?? true) && labels.showLastPrice && lastPoint2) {
|
|
1836
|
-
|
|
1866
|
+
const tickerPrice2 = ticker2.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint2.c;
|
|
1867
|
+
includePrice(tickerPrice2);
|
|
1837
1868
|
if (ticker2.labelSubtext) include(String(ticker2.labelSubtext));
|
|
1838
1869
|
for (const subtext of ticker2.labelSubtexts ?? []) {
|
|
1839
1870
|
include(String(subtext));
|
|
1840
1871
|
}
|
|
1872
|
+
if (ticker2.showCountdownInLabel) include("88:88");
|
|
1841
1873
|
}
|
|
1842
1874
|
if (labels.showSymbolName) {
|
|
1843
1875
|
include(labels.symbolName);
|
|
@@ -1859,18 +1891,24 @@ function createChart(element, options = {}) {
|
|
|
1859
1891
|
for (const line of priceLines) {
|
|
1860
1892
|
const mergedLine = { ...DEFAULT_PRICE_LINE_OPTIONS, ...line };
|
|
1861
1893
|
if (mergedLine.visible && Number.isFinite(mergedLine.price)) {
|
|
1862
|
-
|
|
1894
|
+
if (mergedLine.label) {
|
|
1895
|
+
include(mergedLine.label);
|
|
1896
|
+
} else {
|
|
1897
|
+
includePrice(mergedLine.price);
|
|
1898
|
+
}
|
|
1863
1899
|
}
|
|
1864
1900
|
}
|
|
1865
1901
|
for (const line of orderLines) {
|
|
1866
1902
|
const mergedLine = { ...DEFAULT_ORDER_LINE_OPTIONS, ...line };
|
|
1867
1903
|
const renderPrice = mergedLine.behavior === "follow" && Number.isFinite(mergedLine.followPrice) ? mergedLine.followPrice : mergedLine.price;
|
|
1868
1904
|
if (mergedLine.visible && Number.isFinite(renderPrice)) {
|
|
1869
|
-
|
|
1905
|
+
includePrice(renderPrice);
|
|
1870
1906
|
}
|
|
1871
1907
|
}
|
|
1872
1908
|
const maxRightMargin = Math.max(margin.right, width - margin.left - 160);
|
|
1873
|
-
|
|
1909
|
+
const nextRightMargin = Math.min(maxRightMargin, Math.max(margin.right, Math.ceil(required + 1)));
|
|
1910
|
+
cachedRightMargin = Math.max(cachedRightMargin, nextRightMargin);
|
|
1911
|
+
return Math.min(maxRightMargin, cachedRightMargin);
|
|
1874
1912
|
};
|
|
1875
1913
|
const rightMargin = estimateRightMargin();
|
|
1876
1914
|
const chartLeft = margin.left;
|
|
@@ -2023,16 +2061,17 @@ function createChart(element, options = {}) {
|
|
|
2023
2061
|
return chartBottom - (price - yMin) / yRange * chartHeight;
|
|
2024
2062
|
};
|
|
2025
2063
|
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
2026
|
-
const
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2064
|
+
const FIB_RATIOS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
|
|
2065
|
+
const hexToRgba = (hex, alpha) => {
|
|
2066
|
+
const cleaned = hex.replace("#", "");
|
|
2067
|
+
if (cleaned.length !== 6) {
|
|
2068
|
+
return `rgba(0,0,0,${alpha})`;
|
|
2069
|
+
}
|
|
2070
|
+
const r = parseInt(cleaned.slice(0, 2), 16);
|
|
2071
|
+
const g = parseInt(cleaned.slice(2, 4), 16);
|
|
2072
|
+
const b = parseInt(cleaned.slice(4, 6), 16);
|
|
2073
|
+
return `rgba(${r},${g},${b},${alpha})`;
|
|
2074
|
+
};
|
|
2036
2075
|
const drawDrawingHandle = (x, y, color) => {
|
|
2037
2076
|
ctx.save();
|
|
2038
2077
|
ctx.setLineDash([]);
|
|
@@ -2119,28 +2158,27 @@ function createChart(element, options = {}) {
|
|
|
2119
2158
|
const secondY = yFromPrice(second.price);
|
|
2120
2159
|
const lineLeft = chartLeft;
|
|
2121
2160
|
const lineRight = chartRight;
|
|
2122
|
-
const levelLines =
|
|
2123
|
-
const price = first.price + (second.price - first.price) * (1 -
|
|
2124
|
-
return {
|
|
2161
|
+
const levelLines = FIB_RATIOS.map((ratio) => {
|
|
2162
|
+
const price = first.price + (second.price - first.price) * (1 - ratio);
|
|
2163
|
+
return { ratio, price, y: yFromPrice(price) };
|
|
2125
2164
|
});
|
|
2165
|
+
const bandColor = hexToRgba(drawing.color, 0.1);
|
|
2126
2166
|
for (let index = 0; index < levelLines.length - 1; index += 1) {
|
|
2127
2167
|
const top = levelLines[index];
|
|
2128
2168
|
const bottom = levelLines[index + 1];
|
|
2129
|
-
const fillColor = bottom.band ?? top.band;
|
|
2130
|
-
if (!fillColor) continue;
|
|
2131
2169
|
const bandTop = Math.min(top.y, bottom.y);
|
|
2132
2170
|
const bandBottom = Math.max(top.y, bottom.y);
|
|
2133
2171
|
ctx.save();
|
|
2134
|
-
ctx.fillStyle =
|
|
2172
|
+
ctx.fillStyle = bandColor;
|
|
2135
2173
|
ctx.globalAlpha = draft ? 0.5 : 1;
|
|
2136
2174
|
ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
|
|
2137
2175
|
ctx.restore();
|
|
2138
2176
|
}
|
|
2139
2177
|
ctx.save();
|
|
2140
2178
|
ctx.lineWidth = drawing.width;
|
|
2179
|
+
ctx.strokeStyle = drawing.color;
|
|
2141
2180
|
applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2142
2181
|
for (const level of levelLines) {
|
|
2143
|
-
ctx.strokeStyle = level.color;
|
|
2144
2182
|
ctx.beginPath();
|
|
2145
2183
|
ctx.moveTo(crisp(lineLeft), crisp(level.y));
|
|
2146
2184
|
ctx.lineTo(crisp(lineRight), crisp(level.y));
|
|
@@ -2148,7 +2186,7 @@ function createChart(element, options = {}) {
|
|
|
2148
2186
|
}
|
|
2149
2187
|
ctx.restore();
|
|
2150
2188
|
ctx.save();
|
|
2151
|
-
ctx.strokeStyle =
|
|
2189
|
+
ctx.strokeStyle = hexToRgba(drawing.color, 0.5);
|
|
2152
2190
|
ctx.setLineDash([4, 4]);
|
|
2153
2191
|
ctx.lineWidth = 1;
|
|
2154
2192
|
ctx.beginPath();
|
|
@@ -2168,7 +2206,7 @@ function createChart(element, options = {}) {
|
|
|
2168
2206
|
const bgY = level.y - 9;
|
|
2169
2207
|
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
2170
2208
|
fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
|
|
2171
|
-
ctx.fillStyle =
|
|
2209
|
+
ctx.fillStyle = drawing.color;
|
|
2172
2210
|
ctx.textAlign = "left";
|
|
2173
2211
|
ctx.textBaseline = "middle";
|
|
2174
2212
|
ctx.fillText(labelText, bgX + padding, bgY + 8);
|
|
@@ -3943,6 +3981,7 @@ function createChart(element, options = {}) {
|
|
|
3943
3981
|
width = nextOptions.width !== void 0 && nextOptions.width > 0 ? mergedOptions.width : previousWidth;
|
|
3944
3982
|
height = nextOptions.height !== void 0 && nextOptions.height > 0 ? mergedOptions.height : previousHeight;
|
|
3945
3983
|
mergedOptions = { ...mergedOptions, width, height };
|
|
3984
|
+
resetRightMarginCache();
|
|
3946
3985
|
doubleClickEnabled = mergedOptions.doubleClickEnabled;
|
|
3947
3986
|
doubleClickAction = mergedOptions.doubleClickAction;
|
|
3948
3987
|
const isTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
|
|
@@ -3969,6 +4008,7 @@ function createChart(element, options = {}) {
|
|
|
3969
4008
|
height = nextHeight;
|
|
3970
4009
|
}
|
|
3971
4010
|
mergedOptions = { ...mergedOptions, width, height };
|
|
4011
|
+
resetRightMarginCache();
|
|
3972
4012
|
draw();
|
|
3973
4013
|
};
|
|
3974
4014
|
const setData = (nextData) => {
|
|
@@ -3985,6 +4025,7 @@ function createChart(element, options = {}) {
|
|
|
3985
4025
|
tickerPriceTarget = null;
|
|
3986
4026
|
smoothedTickerVolume = null;
|
|
3987
4027
|
tickerVolumeTarget = null;
|
|
4028
|
+
resetRightMarginCache();
|
|
3988
4029
|
resetYViewport();
|
|
3989
4030
|
draw();
|
|
3990
4031
|
return;
|
|
@@ -4018,6 +4059,7 @@ function createChart(element, options = {}) {
|
|
|
4018
4059
|
...line,
|
|
4019
4060
|
id: line.id ?? `line-${index + 1}`
|
|
4020
4061
|
}));
|
|
4062
|
+
resetRightMarginCache();
|
|
4021
4063
|
draw();
|
|
4022
4064
|
};
|
|
4023
4065
|
const addPriceLine = (line) => {
|
|
@@ -4028,6 +4070,7 @@ function createChart(element, options = {}) {
|
|
|
4028
4070
|
};
|
|
4029
4071
|
const removePriceLine = (id) => {
|
|
4030
4072
|
priceLines = priceLines.filter((line) => line.id !== id);
|
|
4073
|
+
resetRightMarginCache();
|
|
4031
4074
|
draw();
|
|
4032
4075
|
};
|
|
4033
4076
|
const setOrderLines = (lines) => {
|
|
@@ -4035,6 +4078,7 @@ function createChart(element, options = {}) {
|
|
|
4035
4078
|
...line,
|
|
4036
4079
|
id: line.id ?? `order-${index + 1}`
|
|
4037
4080
|
}));
|
|
4081
|
+
resetRightMarginCache();
|
|
4038
4082
|
const activeIds = new Set(orderLines.map((line) => line.id));
|
|
4039
4083
|
for (const id of Array.from(orderWidgetWidthById.keys())) {
|
|
4040
4084
|
if (!activeIds.has(id)) {
|
|
@@ -4062,6 +4106,7 @@ function createChart(element, options = {}) {
|
|
|
4062
4106
|
orderLines = orderLines.filter((line) => line.id !== id);
|
|
4063
4107
|
orderWidgetWidthById.delete(id);
|
|
4064
4108
|
orderPriceTagWidthById.delete(id);
|
|
4109
|
+
resetRightMarginCache();
|
|
4065
4110
|
draw();
|
|
4066
4111
|
};
|
|
4067
4112
|
const onOrderAction = (handler) => {
|
|
@@ -1044,6 +1044,7 @@ function createChart(element, options = {}) {
|
|
|
1044
1044
|
element.innerHTML = "";
|
|
1045
1045
|
element.appendChild(canvas);
|
|
1046
1046
|
const margin = { top: 16, right: 72, bottom: 34, left: 12 };
|
|
1047
|
+
let cachedRightMargin = margin.right;
|
|
1047
1048
|
const minVisibleBars = Math.max(1, Math.floor(mergedOptions.minVisibleBars));
|
|
1048
1049
|
const maxVisibleBars = Math.max(minVisibleBars, Math.floor(mergedOptions.maxVisibleBars));
|
|
1049
1050
|
const maxPanBars = Math.max(0, Math.floor(mergedOptions.maxPanBars));
|
|
@@ -1265,14 +1266,34 @@ function createChart(element, options = {}) {
|
|
|
1265
1266
|
const decimalPart = decimals > 0 ? `.${"8".repeat(decimals)}` : "";
|
|
1266
1267
|
return `${integerPart}${decimalPart}`;
|
|
1267
1268
|
};
|
|
1269
|
+
const getMeasuredLabelWidth = (text, paddingX) => {
|
|
1270
|
+
return Math.ceil(ctx.measureText(text).width) + paddingX * 2;
|
|
1271
|
+
};
|
|
1272
|
+
const getStabilizedNumericLabelWidth = (text, paddingX) => {
|
|
1273
|
+
const measured = getMeasuredLabelWidth(text, paddingX);
|
|
1274
|
+
if (!mergedOptions.stabilizePriceLabels || !/\d/.test(text)) {
|
|
1275
|
+
return measured;
|
|
1276
|
+
}
|
|
1277
|
+
let stableWidth = measured;
|
|
1278
|
+
for (const digit of ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]) {
|
|
1279
|
+
stableWidth = Math.max(stableWidth, getMeasuredLabelWidth(text.replace(/\d/g, digit), paddingX));
|
|
1280
|
+
}
|
|
1281
|
+
return stableWidth;
|
|
1282
|
+
};
|
|
1268
1283
|
const getPriceLabelWidth = (priceText, paddingX) => {
|
|
1269
|
-
const measured =
|
|
1284
|
+
const measured = getStabilizedNumericLabelWidth(priceText, paddingX);
|
|
1270
1285
|
if (!mergedOptions.stabilizePriceLabels) {
|
|
1271
1286
|
return measured;
|
|
1272
1287
|
}
|
|
1273
|
-
const templateWidth =
|
|
1288
|
+
const templateWidth = getStabilizedNumericLabelWidth(getStabilizedPriceTemplate(), paddingX);
|
|
1274
1289
|
return Math.max(measured, templateWidth);
|
|
1275
1290
|
};
|
|
1291
|
+
const getStableLabelWidth = (text, paddingX) => {
|
|
1292
|
+
return getStabilizedNumericLabelWidth(text, paddingX);
|
|
1293
|
+
};
|
|
1294
|
+
const resetRightMarginCache = () => {
|
|
1295
|
+
cachedRightMargin = margin.right;
|
|
1296
|
+
};
|
|
1276
1297
|
const parseData = (nextData) => {
|
|
1277
1298
|
const dedupedByTime = /* @__PURE__ */ new Map();
|
|
1278
1299
|
for (const point of nextData) {
|
|
@@ -1798,22 +1819,33 @@ function createChart(element, options = {}) {
|
|
|
1798
1819
|
const labels = { ...DEFAULT_LABELS_OPTIONS, ...mergedOptions.labels ?? {} };
|
|
1799
1820
|
const estimateRightMargin = () => {
|
|
1800
1821
|
const paddingX = Math.max(4, labels.labelPaddingX);
|
|
1801
|
-
const measure = (text) => Math.ceil(ctx.measureText(text).width) + paddingX * 2;
|
|
1802
1822
|
let required = margin.right - 1;
|
|
1823
|
+
const includeWidth = (contentWidth) => {
|
|
1824
|
+
if (Number.isFinite(contentWidth)) {
|
|
1825
|
+
required = Math.max(required, contentWidth);
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1803
1828
|
const include = (text) => {
|
|
1804
1829
|
const normalized = text?.trim();
|
|
1805
1830
|
if (normalized) {
|
|
1806
|
-
|
|
1831
|
+
includeWidth(getStableLabelWidth(normalized, paddingX));
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
const includePrice = (price) => {
|
|
1835
|
+
if (Number.isFinite(price)) {
|
|
1836
|
+
includeWidth(getPriceLabelWidth(formatPrice(price), paddingX));
|
|
1807
1837
|
}
|
|
1808
1838
|
};
|
|
1809
1839
|
const ticker2 = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
|
|
1810
1840
|
const lastPoint2 = data[data.length - 1];
|
|
1811
1841
|
if ((ticker2.visible ?? true) && labels.showLastPrice && lastPoint2) {
|
|
1812
|
-
|
|
1842
|
+
const tickerPrice2 = ticker2.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint2.c;
|
|
1843
|
+
includePrice(tickerPrice2);
|
|
1813
1844
|
if (ticker2.labelSubtext) include(String(ticker2.labelSubtext));
|
|
1814
1845
|
for (const subtext of ticker2.labelSubtexts ?? []) {
|
|
1815
1846
|
include(String(subtext));
|
|
1816
1847
|
}
|
|
1848
|
+
if (ticker2.showCountdownInLabel) include("88:88");
|
|
1817
1849
|
}
|
|
1818
1850
|
if (labels.showSymbolName) {
|
|
1819
1851
|
include(labels.symbolName);
|
|
@@ -1835,18 +1867,24 @@ function createChart(element, options = {}) {
|
|
|
1835
1867
|
for (const line of priceLines) {
|
|
1836
1868
|
const mergedLine = { ...DEFAULT_PRICE_LINE_OPTIONS, ...line };
|
|
1837
1869
|
if (mergedLine.visible && Number.isFinite(mergedLine.price)) {
|
|
1838
|
-
|
|
1870
|
+
if (mergedLine.label) {
|
|
1871
|
+
include(mergedLine.label);
|
|
1872
|
+
} else {
|
|
1873
|
+
includePrice(mergedLine.price);
|
|
1874
|
+
}
|
|
1839
1875
|
}
|
|
1840
1876
|
}
|
|
1841
1877
|
for (const line of orderLines) {
|
|
1842
1878
|
const mergedLine = { ...DEFAULT_ORDER_LINE_OPTIONS, ...line };
|
|
1843
1879
|
const renderPrice = mergedLine.behavior === "follow" && Number.isFinite(mergedLine.followPrice) ? mergedLine.followPrice : mergedLine.price;
|
|
1844
1880
|
if (mergedLine.visible && Number.isFinite(renderPrice)) {
|
|
1845
|
-
|
|
1881
|
+
includePrice(renderPrice);
|
|
1846
1882
|
}
|
|
1847
1883
|
}
|
|
1848
1884
|
const maxRightMargin = Math.max(margin.right, width - margin.left - 160);
|
|
1849
|
-
|
|
1885
|
+
const nextRightMargin = Math.min(maxRightMargin, Math.max(margin.right, Math.ceil(required + 1)));
|
|
1886
|
+
cachedRightMargin = Math.max(cachedRightMargin, nextRightMargin);
|
|
1887
|
+
return Math.min(maxRightMargin, cachedRightMargin);
|
|
1850
1888
|
};
|
|
1851
1889
|
const rightMargin = estimateRightMargin();
|
|
1852
1890
|
const chartLeft = margin.left;
|
|
@@ -1999,16 +2037,17 @@ function createChart(element, options = {}) {
|
|
|
1999
2037
|
return chartBottom - (price - yMin) / yRange * chartHeight;
|
|
2000
2038
|
};
|
|
2001
2039
|
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
2002
|
-
const
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2040
|
+
const FIB_RATIOS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
|
|
2041
|
+
const hexToRgba = (hex, alpha) => {
|
|
2042
|
+
const cleaned = hex.replace("#", "");
|
|
2043
|
+
if (cleaned.length !== 6) {
|
|
2044
|
+
return `rgba(0,0,0,${alpha})`;
|
|
2045
|
+
}
|
|
2046
|
+
const r = parseInt(cleaned.slice(0, 2), 16);
|
|
2047
|
+
const g = parseInt(cleaned.slice(2, 4), 16);
|
|
2048
|
+
const b = parseInt(cleaned.slice(4, 6), 16);
|
|
2049
|
+
return `rgba(${r},${g},${b},${alpha})`;
|
|
2050
|
+
};
|
|
2012
2051
|
const drawDrawingHandle = (x, y, color) => {
|
|
2013
2052
|
ctx.save();
|
|
2014
2053
|
ctx.setLineDash([]);
|
|
@@ -2095,28 +2134,27 @@ function createChart(element, options = {}) {
|
|
|
2095
2134
|
const secondY = yFromPrice(second.price);
|
|
2096
2135
|
const lineLeft = chartLeft;
|
|
2097
2136
|
const lineRight = chartRight;
|
|
2098
|
-
const levelLines =
|
|
2099
|
-
const price = first.price + (second.price - first.price) * (1 -
|
|
2100
|
-
return {
|
|
2137
|
+
const levelLines = FIB_RATIOS.map((ratio) => {
|
|
2138
|
+
const price = first.price + (second.price - first.price) * (1 - ratio);
|
|
2139
|
+
return { ratio, price, y: yFromPrice(price) };
|
|
2101
2140
|
});
|
|
2141
|
+
const bandColor = hexToRgba(drawing.color, 0.1);
|
|
2102
2142
|
for (let index = 0; index < levelLines.length - 1; index += 1) {
|
|
2103
2143
|
const top = levelLines[index];
|
|
2104
2144
|
const bottom = levelLines[index + 1];
|
|
2105
|
-
const fillColor = bottom.band ?? top.band;
|
|
2106
|
-
if (!fillColor) continue;
|
|
2107
2145
|
const bandTop = Math.min(top.y, bottom.y);
|
|
2108
2146
|
const bandBottom = Math.max(top.y, bottom.y);
|
|
2109
2147
|
ctx.save();
|
|
2110
|
-
ctx.fillStyle =
|
|
2148
|
+
ctx.fillStyle = bandColor;
|
|
2111
2149
|
ctx.globalAlpha = draft ? 0.5 : 1;
|
|
2112
2150
|
ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
|
|
2113
2151
|
ctx.restore();
|
|
2114
2152
|
}
|
|
2115
2153
|
ctx.save();
|
|
2116
2154
|
ctx.lineWidth = drawing.width;
|
|
2155
|
+
ctx.strokeStyle = drawing.color;
|
|
2117
2156
|
applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2118
2157
|
for (const level of levelLines) {
|
|
2119
|
-
ctx.strokeStyle = level.color;
|
|
2120
2158
|
ctx.beginPath();
|
|
2121
2159
|
ctx.moveTo(crisp(lineLeft), crisp(level.y));
|
|
2122
2160
|
ctx.lineTo(crisp(lineRight), crisp(level.y));
|
|
@@ -2124,7 +2162,7 @@ function createChart(element, options = {}) {
|
|
|
2124
2162
|
}
|
|
2125
2163
|
ctx.restore();
|
|
2126
2164
|
ctx.save();
|
|
2127
|
-
ctx.strokeStyle =
|
|
2165
|
+
ctx.strokeStyle = hexToRgba(drawing.color, 0.5);
|
|
2128
2166
|
ctx.setLineDash([4, 4]);
|
|
2129
2167
|
ctx.lineWidth = 1;
|
|
2130
2168
|
ctx.beginPath();
|
|
@@ -2144,7 +2182,7 @@ function createChart(element, options = {}) {
|
|
|
2144
2182
|
const bgY = level.y - 9;
|
|
2145
2183
|
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
2146
2184
|
fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
|
|
2147
|
-
ctx.fillStyle =
|
|
2185
|
+
ctx.fillStyle = drawing.color;
|
|
2148
2186
|
ctx.textAlign = "left";
|
|
2149
2187
|
ctx.textBaseline = "middle";
|
|
2150
2188
|
ctx.fillText(labelText, bgX + padding, bgY + 8);
|
|
@@ -3919,6 +3957,7 @@ function createChart(element, options = {}) {
|
|
|
3919
3957
|
width = nextOptions.width !== void 0 && nextOptions.width > 0 ? mergedOptions.width : previousWidth;
|
|
3920
3958
|
height = nextOptions.height !== void 0 && nextOptions.height > 0 ? mergedOptions.height : previousHeight;
|
|
3921
3959
|
mergedOptions = { ...mergedOptions, width, height };
|
|
3960
|
+
resetRightMarginCache();
|
|
3922
3961
|
doubleClickEnabled = mergedOptions.doubleClickEnabled;
|
|
3923
3962
|
doubleClickAction = mergedOptions.doubleClickAction;
|
|
3924
3963
|
const isTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
|
|
@@ -3945,6 +3984,7 @@ function createChart(element, options = {}) {
|
|
|
3945
3984
|
height = nextHeight;
|
|
3946
3985
|
}
|
|
3947
3986
|
mergedOptions = { ...mergedOptions, width, height };
|
|
3987
|
+
resetRightMarginCache();
|
|
3948
3988
|
draw();
|
|
3949
3989
|
};
|
|
3950
3990
|
const setData = (nextData) => {
|
|
@@ -3961,6 +4001,7 @@ function createChart(element, options = {}) {
|
|
|
3961
4001
|
tickerPriceTarget = null;
|
|
3962
4002
|
smoothedTickerVolume = null;
|
|
3963
4003
|
tickerVolumeTarget = null;
|
|
4004
|
+
resetRightMarginCache();
|
|
3964
4005
|
resetYViewport();
|
|
3965
4006
|
draw();
|
|
3966
4007
|
return;
|
|
@@ -3994,6 +4035,7 @@ function createChart(element, options = {}) {
|
|
|
3994
4035
|
...line,
|
|
3995
4036
|
id: line.id ?? `line-${index + 1}`
|
|
3996
4037
|
}));
|
|
4038
|
+
resetRightMarginCache();
|
|
3997
4039
|
draw();
|
|
3998
4040
|
};
|
|
3999
4041
|
const addPriceLine = (line) => {
|
|
@@ -4004,6 +4046,7 @@ function createChart(element, options = {}) {
|
|
|
4004
4046
|
};
|
|
4005
4047
|
const removePriceLine = (id) => {
|
|
4006
4048
|
priceLines = priceLines.filter((line) => line.id !== id);
|
|
4049
|
+
resetRightMarginCache();
|
|
4007
4050
|
draw();
|
|
4008
4051
|
};
|
|
4009
4052
|
const setOrderLines = (lines) => {
|
|
@@ -4011,6 +4054,7 @@ function createChart(element, options = {}) {
|
|
|
4011
4054
|
...line,
|
|
4012
4055
|
id: line.id ?? `order-${index + 1}`
|
|
4013
4056
|
}));
|
|
4057
|
+
resetRightMarginCache();
|
|
4014
4058
|
const activeIds = new Set(orderLines.map((line) => line.id));
|
|
4015
4059
|
for (const id of Array.from(orderWidgetWidthById.keys())) {
|
|
4016
4060
|
if (!activeIds.has(id)) {
|
|
@@ -4038,6 +4082,7 @@ function createChart(element, options = {}) {
|
|
|
4038
4082
|
orderLines = orderLines.filter((line) => line.id !== id);
|
|
4039
4083
|
orderWidgetWidthById.delete(id);
|
|
4040
4084
|
orderPriceTagWidthById.delete(id);
|
|
4085
|
+
resetRightMarginCache();
|
|
4041
4086
|
draw();
|
|
4042
4087
|
};
|
|
4043
4088
|
const onOrderAction = (handler) => {
|
package/dist/index.cjs
CHANGED
|
@@ -1068,6 +1068,7 @@ function createChart(element, options = {}) {
|
|
|
1068
1068
|
element.innerHTML = "";
|
|
1069
1069
|
element.appendChild(canvas);
|
|
1070
1070
|
const margin = { top: 16, right: 72, bottom: 34, left: 12 };
|
|
1071
|
+
let cachedRightMargin = margin.right;
|
|
1071
1072
|
const minVisibleBars = Math.max(1, Math.floor(mergedOptions.minVisibleBars));
|
|
1072
1073
|
const maxVisibleBars = Math.max(minVisibleBars, Math.floor(mergedOptions.maxVisibleBars));
|
|
1073
1074
|
const maxPanBars = Math.max(0, Math.floor(mergedOptions.maxPanBars));
|
|
@@ -1289,14 +1290,34 @@ function createChart(element, options = {}) {
|
|
|
1289
1290
|
const decimalPart = decimals > 0 ? `.${"8".repeat(decimals)}` : "";
|
|
1290
1291
|
return `${integerPart}${decimalPart}`;
|
|
1291
1292
|
};
|
|
1293
|
+
const getMeasuredLabelWidth = (text, paddingX) => {
|
|
1294
|
+
return Math.ceil(ctx.measureText(text).width) + paddingX * 2;
|
|
1295
|
+
};
|
|
1296
|
+
const getStabilizedNumericLabelWidth = (text, paddingX) => {
|
|
1297
|
+
const measured = getMeasuredLabelWidth(text, paddingX);
|
|
1298
|
+
if (!mergedOptions.stabilizePriceLabels || !/\d/.test(text)) {
|
|
1299
|
+
return measured;
|
|
1300
|
+
}
|
|
1301
|
+
let stableWidth = measured;
|
|
1302
|
+
for (const digit of ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]) {
|
|
1303
|
+
stableWidth = Math.max(stableWidth, getMeasuredLabelWidth(text.replace(/\d/g, digit), paddingX));
|
|
1304
|
+
}
|
|
1305
|
+
return stableWidth;
|
|
1306
|
+
};
|
|
1292
1307
|
const getPriceLabelWidth = (priceText, paddingX) => {
|
|
1293
|
-
const measured =
|
|
1308
|
+
const measured = getStabilizedNumericLabelWidth(priceText, paddingX);
|
|
1294
1309
|
if (!mergedOptions.stabilizePriceLabels) {
|
|
1295
1310
|
return measured;
|
|
1296
1311
|
}
|
|
1297
|
-
const templateWidth =
|
|
1312
|
+
const templateWidth = getStabilizedNumericLabelWidth(getStabilizedPriceTemplate(), paddingX);
|
|
1298
1313
|
return Math.max(measured, templateWidth);
|
|
1299
1314
|
};
|
|
1315
|
+
const getStableLabelWidth = (text, paddingX) => {
|
|
1316
|
+
return getStabilizedNumericLabelWidth(text, paddingX);
|
|
1317
|
+
};
|
|
1318
|
+
const resetRightMarginCache = () => {
|
|
1319
|
+
cachedRightMargin = margin.right;
|
|
1320
|
+
};
|
|
1300
1321
|
const parseData = (nextData) => {
|
|
1301
1322
|
const dedupedByTime = /* @__PURE__ */ new Map();
|
|
1302
1323
|
for (const point of nextData) {
|
|
@@ -1822,22 +1843,33 @@ function createChart(element, options = {}) {
|
|
|
1822
1843
|
const labels = { ...DEFAULT_LABELS_OPTIONS, ...mergedOptions.labels ?? {} };
|
|
1823
1844
|
const estimateRightMargin = () => {
|
|
1824
1845
|
const paddingX = Math.max(4, labels.labelPaddingX);
|
|
1825
|
-
const measure = (text) => Math.ceil(ctx.measureText(text).width) + paddingX * 2;
|
|
1826
1846
|
let required = margin.right - 1;
|
|
1847
|
+
const includeWidth = (contentWidth) => {
|
|
1848
|
+
if (Number.isFinite(contentWidth)) {
|
|
1849
|
+
required = Math.max(required, contentWidth);
|
|
1850
|
+
}
|
|
1851
|
+
};
|
|
1827
1852
|
const include = (text) => {
|
|
1828
1853
|
const normalized = text?.trim();
|
|
1829
1854
|
if (normalized) {
|
|
1830
|
-
|
|
1855
|
+
includeWidth(getStableLabelWidth(normalized, paddingX));
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
const includePrice = (price) => {
|
|
1859
|
+
if (Number.isFinite(price)) {
|
|
1860
|
+
includeWidth(getPriceLabelWidth(formatPrice(price), paddingX));
|
|
1831
1861
|
}
|
|
1832
1862
|
};
|
|
1833
1863
|
const ticker2 = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
|
|
1834
1864
|
const lastPoint2 = data[data.length - 1];
|
|
1835
1865
|
if ((ticker2.visible ?? true) && labels.showLastPrice && lastPoint2) {
|
|
1836
|
-
|
|
1866
|
+
const tickerPrice2 = ticker2.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint2.c;
|
|
1867
|
+
includePrice(tickerPrice2);
|
|
1837
1868
|
if (ticker2.labelSubtext) include(String(ticker2.labelSubtext));
|
|
1838
1869
|
for (const subtext of ticker2.labelSubtexts ?? []) {
|
|
1839
1870
|
include(String(subtext));
|
|
1840
1871
|
}
|
|
1872
|
+
if (ticker2.showCountdownInLabel) include("88:88");
|
|
1841
1873
|
}
|
|
1842
1874
|
if (labels.showSymbolName) {
|
|
1843
1875
|
include(labels.symbolName);
|
|
@@ -1859,18 +1891,24 @@ function createChart(element, options = {}) {
|
|
|
1859
1891
|
for (const line of priceLines) {
|
|
1860
1892
|
const mergedLine = { ...DEFAULT_PRICE_LINE_OPTIONS, ...line };
|
|
1861
1893
|
if (mergedLine.visible && Number.isFinite(mergedLine.price)) {
|
|
1862
|
-
|
|
1894
|
+
if (mergedLine.label) {
|
|
1895
|
+
include(mergedLine.label);
|
|
1896
|
+
} else {
|
|
1897
|
+
includePrice(mergedLine.price);
|
|
1898
|
+
}
|
|
1863
1899
|
}
|
|
1864
1900
|
}
|
|
1865
1901
|
for (const line of orderLines) {
|
|
1866
1902
|
const mergedLine = { ...DEFAULT_ORDER_LINE_OPTIONS, ...line };
|
|
1867
1903
|
const renderPrice = mergedLine.behavior === "follow" && Number.isFinite(mergedLine.followPrice) ? mergedLine.followPrice : mergedLine.price;
|
|
1868
1904
|
if (mergedLine.visible && Number.isFinite(renderPrice)) {
|
|
1869
|
-
|
|
1905
|
+
includePrice(renderPrice);
|
|
1870
1906
|
}
|
|
1871
1907
|
}
|
|
1872
1908
|
const maxRightMargin = Math.max(margin.right, width - margin.left - 160);
|
|
1873
|
-
|
|
1909
|
+
const nextRightMargin = Math.min(maxRightMargin, Math.max(margin.right, Math.ceil(required + 1)));
|
|
1910
|
+
cachedRightMargin = Math.max(cachedRightMargin, nextRightMargin);
|
|
1911
|
+
return Math.min(maxRightMargin, cachedRightMargin);
|
|
1874
1912
|
};
|
|
1875
1913
|
const rightMargin = estimateRightMargin();
|
|
1876
1914
|
const chartLeft = margin.left;
|
|
@@ -2023,16 +2061,17 @@ function createChart(element, options = {}) {
|
|
|
2023
2061
|
return chartBottom - (price - yMin) / yRange * chartHeight;
|
|
2024
2062
|
};
|
|
2025
2063
|
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
2026
|
-
const
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2064
|
+
const FIB_RATIOS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
|
|
2065
|
+
const hexToRgba = (hex, alpha) => {
|
|
2066
|
+
const cleaned = hex.replace("#", "");
|
|
2067
|
+
if (cleaned.length !== 6) {
|
|
2068
|
+
return `rgba(0,0,0,${alpha})`;
|
|
2069
|
+
}
|
|
2070
|
+
const r = parseInt(cleaned.slice(0, 2), 16);
|
|
2071
|
+
const g = parseInt(cleaned.slice(2, 4), 16);
|
|
2072
|
+
const b = parseInt(cleaned.slice(4, 6), 16);
|
|
2073
|
+
return `rgba(${r},${g},${b},${alpha})`;
|
|
2074
|
+
};
|
|
2036
2075
|
const drawDrawingHandle = (x, y, color) => {
|
|
2037
2076
|
ctx.save();
|
|
2038
2077
|
ctx.setLineDash([]);
|
|
@@ -2119,28 +2158,27 @@ function createChart(element, options = {}) {
|
|
|
2119
2158
|
const secondY = yFromPrice(second.price);
|
|
2120
2159
|
const lineLeft = chartLeft;
|
|
2121
2160
|
const lineRight = chartRight;
|
|
2122
|
-
const levelLines =
|
|
2123
|
-
const price = first.price + (second.price - first.price) * (1 -
|
|
2124
|
-
return {
|
|
2161
|
+
const levelLines = FIB_RATIOS.map((ratio) => {
|
|
2162
|
+
const price = first.price + (second.price - first.price) * (1 - ratio);
|
|
2163
|
+
return { ratio, price, y: yFromPrice(price) };
|
|
2125
2164
|
});
|
|
2165
|
+
const bandColor = hexToRgba(drawing.color, 0.1);
|
|
2126
2166
|
for (let index = 0; index < levelLines.length - 1; index += 1) {
|
|
2127
2167
|
const top = levelLines[index];
|
|
2128
2168
|
const bottom = levelLines[index + 1];
|
|
2129
|
-
const fillColor = bottom.band ?? top.band;
|
|
2130
|
-
if (!fillColor) continue;
|
|
2131
2169
|
const bandTop = Math.min(top.y, bottom.y);
|
|
2132
2170
|
const bandBottom = Math.max(top.y, bottom.y);
|
|
2133
2171
|
ctx.save();
|
|
2134
|
-
ctx.fillStyle =
|
|
2172
|
+
ctx.fillStyle = bandColor;
|
|
2135
2173
|
ctx.globalAlpha = draft ? 0.5 : 1;
|
|
2136
2174
|
ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
|
|
2137
2175
|
ctx.restore();
|
|
2138
2176
|
}
|
|
2139
2177
|
ctx.save();
|
|
2140
2178
|
ctx.lineWidth = drawing.width;
|
|
2179
|
+
ctx.strokeStyle = drawing.color;
|
|
2141
2180
|
applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2142
2181
|
for (const level of levelLines) {
|
|
2143
|
-
ctx.strokeStyle = level.color;
|
|
2144
2182
|
ctx.beginPath();
|
|
2145
2183
|
ctx.moveTo(crisp(lineLeft), crisp(level.y));
|
|
2146
2184
|
ctx.lineTo(crisp(lineRight), crisp(level.y));
|
|
@@ -2148,7 +2186,7 @@ function createChart(element, options = {}) {
|
|
|
2148
2186
|
}
|
|
2149
2187
|
ctx.restore();
|
|
2150
2188
|
ctx.save();
|
|
2151
|
-
ctx.strokeStyle =
|
|
2189
|
+
ctx.strokeStyle = hexToRgba(drawing.color, 0.5);
|
|
2152
2190
|
ctx.setLineDash([4, 4]);
|
|
2153
2191
|
ctx.lineWidth = 1;
|
|
2154
2192
|
ctx.beginPath();
|
|
@@ -2168,7 +2206,7 @@ function createChart(element, options = {}) {
|
|
|
2168
2206
|
const bgY = level.y - 9;
|
|
2169
2207
|
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
2170
2208
|
fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
|
|
2171
|
-
ctx.fillStyle =
|
|
2209
|
+
ctx.fillStyle = drawing.color;
|
|
2172
2210
|
ctx.textAlign = "left";
|
|
2173
2211
|
ctx.textBaseline = "middle";
|
|
2174
2212
|
ctx.fillText(labelText, bgX + padding, bgY + 8);
|
|
@@ -3943,6 +3981,7 @@ function createChart(element, options = {}) {
|
|
|
3943
3981
|
width = nextOptions.width !== void 0 && nextOptions.width > 0 ? mergedOptions.width : previousWidth;
|
|
3944
3982
|
height = nextOptions.height !== void 0 && nextOptions.height > 0 ? mergedOptions.height : previousHeight;
|
|
3945
3983
|
mergedOptions = { ...mergedOptions, width, height };
|
|
3984
|
+
resetRightMarginCache();
|
|
3946
3985
|
doubleClickEnabled = mergedOptions.doubleClickEnabled;
|
|
3947
3986
|
doubleClickAction = mergedOptions.doubleClickAction;
|
|
3948
3987
|
const isTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
|
|
@@ -3969,6 +4008,7 @@ function createChart(element, options = {}) {
|
|
|
3969
4008
|
height = nextHeight;
|
|
3970
4009
|
}
|
|
3971
4010
|
mergedOptions = { ...mergedOptions, width, height };
|
|
4011
|
+
resetRightMarginCache();
|
|
3972
4012
|
draw();
|
|
3973
4013
|
};
|
|
3974
4014
|
const setData = (nextData) => {
|
|
@@ -3985,6 +4025,7 @@ function createChart(element, options = {}) {
|
|
|
3985
4025
|
tickerPriceTarget = null;
|
|
3986
4026
|
smoothedTickerVolume = null;
|
|
3987
4027
|
tickerVolumeTarget = null;
|
|
4028
|
+
resetRightMarginCache();
|
|
3988
4029
|
resetYViewport();
|
|
3989
4030
|
draw();
|
|
3990
4031
|
return;
|
|
@@ -4018,6 +4059,7 @@ function createChart(element, options = {}) {
|
|
|
4018
4059
|
...line,
|
|
4019
4060
|
id: line.id ?? `line-${index + 1}`
|
|
4020
4061
|
}));
|
|
4062
|
+
resetRightMarginCache();
|
|
4021
4063
|
draw();
|
|
4022
4064
|
};
|
|
4023
4065
|
const addPriceLine = (line) => {
|
|
@@ -4028,6 +4070,7 @@ function createChart(element, options = {}) {
|
|
|
4028
4070
|
};
|
|
4029
4071
|
const removePriceLine = (id) => {
|
|
4030
4072
|
priceLines = priceLines.filter((line) => line.id !== id);
|
|
4073
|
+
resetRightMarginCache();
|
|
4031
4074
|
draw();
|
|
4032
4075
|
};
|
|
4033
4076
|
const setOrderLines = (lines) => {
|
|
@@ -4035,6 +4078,7 @@ function createChart(element, options = {}) {
|
|
|
4035
4078
|
...line,
|
|
4036
4079
|
id: line.id ?? `order-${index + 1}`
|
|
4037
4080
|
}));
|
|
4081
|
+
resetRightMarginCache();
|
|
4038
4082
|
const activeIds = new Set(orderLines.map((line) => line.id));
|
|
4039
4083
|
for (const id of Array.from(orderWidgetWidthById.keys())) {
|
|
4040
4084
|
if (!activeIds.has(id)) {
|
|
@@ -4062,6 +4106,7 @@ function createChart(element, options = {}) {
|
|
|
4062
4106
|
orderLines = orderLines.filter((line) => line.id !== id);
|
|
4063
4107
|
orderWidgetWidthById.delete(id);
|
|
4064
4108
|
orderPriceTagWidthById.delete(id);
|
|
4109
|
+
resetRightMarginCache();
|
|
4065
4110
|
draw();
|
|
4066
4111
|
};
|
|
4067
4112
|
const onOrderAction = (handler) => {
|
package/dist/index.js
CHANGED
|
@@ -1044,6 +1044,7 @@ function createChart(element, options = {}) {
|
|
|
1044
1044
|
element.innerHTML = "";
|
|
1045
1045
|
element.appendChild(canvas);
|
|
1046
1046
|
const margin = { top: 16, right: 72, bottom: 34, left: 12 };
|
|
1047
|
+
let cachedRightMargin = margin.right;
|
|
1047
1048
|
const minVisibleBars = Math.max(1, Math.floor(mergedOptions.minVisibleBars));
|
|
1048
1049
|
const maxVisibleBars = Math.max(minVisibleBars, Math.floor(mergedOptions.maxVisibleBars));
|
|
1049
1050
|
const maxPanBars = Math.max(0, Math.floor(mergedOptions.maxPanBars));
|
|
@@ -1265,14 +1266,34 @@ function createChart(element, options = {}) {
|
|
|
1265
1266
|
const decimalPart = decimals > 0 ? `.${"8".repeat(decimals)}` : "";
|
|
1266
1267
|
return `${integerPart}${decimalPart}`;
|
|
1267
1268
|
};
|
|
1269
|
+
const getMeasuredLabelWidth = (text, paddingX) => {
|
|
1270
|
+
return Math.ceil(ctx.measureText(text).width) + paddingX * 2;
|
|
1271
|
+
};
|
|
1272
|
+
const getStabilizedNumericLabelWidth = (text, paddingX) => {
|
|
1273
|
+
const measured = getMeasuredLabelWidth(text, paddingX);
|
|
1274
|
+
if (!mergedOptions.stabilizePriceLabels || !/\d/.test(text)) {
|
|
1275
|
+
return measured;
|
|
1276
|
+
}
|
|
1277
|
+
let stableWidth = measured;
|
|
1278
|
+
for (const digit of ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]) {
|
|
1279
|
+
stableWidth = Math.max(stableWidth, getMeasuredLabelWidth(text.replace(/\d/g, digit), paddingX));
|
|
1280
|
+
}
|
|
1281
|
+
return stableWidth;
|
|
1282
|
+
};
|
|
1268
1283
|
const getPriceLabelWidth = (priceText, paddingX) => {
|
|
1269
|
-
const measured =
|
|
1284
|
+
const measured = getStabilizedNumericLabelWidth(priceText, paddingX);
|
|
1270
1285
|
if (!mergedOptions.stabilizePriceLabels) {
|
|
1271
1286
|
return measured;
|
|
1272
1287
|
}
|
|
1273
|
-
const templateWidth =
|
|
1288
|
+
const templateWidth = getStabilizedNumericLabelWidth(getStabilizedPriceTemplate(), paddingX);
|
|
1274
1289
|
return Math.max(measured, templateWidth);
|
|
1275
1290
|
};
|
|
1291
|
+
const getStableLabelWidth = (text, paddingX) => {
|
|
1292
|
+
return getStabilizedNumericLabelWidth(text, paddingX);
|
|
1293
|
+
};
|
|
1294
|
+
const resetRightMarginCache = () => {
|
|
1295
|
+
cachedRightMargin = margin.right;
|
|
1296
|
+
};
|
|
1276
1297
|
const parseData = (nextData) => {
|
|
1277
1298
|
const dedupedByTime = /* @__PURE__ */ new Map();
|
|
1278
1299
|
for (const point of nextData) {
|
|
@@ -1798,22 +1819,33 @@ function createChart(element, options = {}) {
|
|
|
1798
1819
|
const labels = { ...DEFAULT_LABELS_OPTIONS, ...mergedOptions.labels ?? {} };
|
|
1799
1820
|
const estimateRightMargin = () => {
|
|
1800
1821
|
const paddingX = Math.max(4, labels.labelPaddingX);
|
|
1801
|
-
const measure = (text) => Math.ceil(ctx.measureText(text).width) + paddingX * 2;
|
|
1802
1822
|
let required = margin.right - 1;
|
|
1823
|
+
const includeWidth = (contentWidth) => {
|
|
1824
|
+
if (Number.isFinite(contentWidth)) {
|
|
1825
|
+
required = Math.max(required, contentWidth);
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1803
1828
|
const include = (text) => {
|
|
1804
1829
|
const normalized = text?.trim();
|
|
1805
1830
|
if (normalized) {
|
|
1806
|
-
|
|
1831
|
+
includeWidth(getStableLabelWidth(normalized, paddingX));
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
const includePrice = (price) => {
|
|
1835
|
+
if (Number.isFinite(price)) {
|
|
1836
|
+
includeWidth(getPriceLabelWidth(formatPrice(price), paddingX));
|
|
1807
1837
|
}
|
|
1808
1838
|
};
|
|
1809
1839
|
const ticker2 = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
|
|
1810
1840
|
const lastPoint2 = data[data.length - 1];
|
|
1811
1841
|
if ((ticker2.visible ?? true) && labels.showLastPrice && lastPoint2) {
|
|
1812
|
-
|
|
1842
|
+
const tickerPrice2 = ticker2.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint2.c;
|
|
1843
|
+
includePrice(tickerPrice2);
|
|
1813
1844
|
if (ticker2.labelSubtext) include(String(ticker2.labelSubtext));
|
|
1814
1845
|
for (const subtext of ticker2.labelSubtexts ?? []) {
|
|
1815
1846
|
include(String(subtext));
|
|
1816
1847
|
}
|
|
1848
|
+
if (ticker2.showCountdownInLabel) include("88:88");
|
|
1817
1849
|
}
|
|
1818
1850
|
if (labels.showSymbolName) {
|
|
1819
1851
|
include(labels.symbolName);
|
|
@@ -1835,18 +1867,24 @@ function createChart(element, options = {}) {
|
|
|
1835
1867
|
for (const line of priceLines) {
|
|
1836
1868
|
const mergedLine = { ...DEFAULT_PRICE_LINE_OPTIONS, ...line };
|
|
1837
1869
|
if (mergedLine.visible && Number.isFinite(mergedLine.price)) {
|
|
1838
|
-
|
|
1870
|
+
if (mergedLine.label) {
|
|
1871
|
+
include(mergedLine.label);
|
|
1872
|
+
} else {
|
|
1873
|
+
includePrice(mergedLine.price);
|
|
1874
|
+
}
|
|
1839
1875
|
}
|
|
1840
1876
|
}
|
|
1841
1877
|
for (const line of orderLines) {
|
|
1842
1878
|
const mergedLine = { ...DEFAULT_ORDER_LINE_OPTIONS, ...line };
|
|
1843
1879
|
const renderPrice = mergedLine.behavior === "follow" && Number.isFinite(mergedLine.followPrice) ? mergedLine.followPrice : mergedLine.price;
|
|
1844
1880
|
if (mergedLine.visible && Number.isFinite(renderPrice)) {
|
|
1845
|
-
|
|
1881
|
+
includePrice(renderPrice);
|
|
1846
1882
|
}
|
|
1847
1883
|
}
|
|
1848
1884
|
const maxRightMargin = Math.max(margin.right, width - margin.left - 160);
|
|
1849
|
-
|
|
1885
|
+
const nextRightMargin = Math.min(maxRightMargin, Math.max(margin.right, Math.ceil(required + 1)));
|
|
1886
|
+
cachedRightMargin = Math.max(cachedRightMargin, nextRightMargin);
|
|
1887
|
+
return Math.min(maxRightMargin, cachedRightMargin);
|
|
1850
1888
|
};
|
|
1851
1889
|
const rightMargin = estimateRightMargin();
|
|
1852
1890
|
const chartLeft = margin.left;
|
|
@@ -1999,16 +2037,17 @@ function createChart(element, options = {}) {
|
|
|
1999
2037
|
return chartBottom - (price - yMin) / yRange * chartHeight;
|
|
2000
2038
|
};
|
|
2001
2039
|
const xFromDrawingPoint = (point) => chartLeft + (point.index + 0.5 - xStart) / xSpan * chartWidth;
|
|
2002
|
-
const
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2040
|
+
const FIB_RATIOS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
|
|
2041
|
+
const hexToRgba = (hex, alpha) => {
|
|
2042
|
+
const cleaned = hex.replace("#", "");
|
|
2043
|
+
if (cleaned.length !== 6) {
|
|
2044
|
+
return `rgba(0,0,0,${alpha})`;
|
|
2045
|
+
}
|
|
2046
|
+
const r = parseInt(cleaned.slice(0, 2), 16);
|
|
2047
|
+
const g = parseInt(cleaned.slice(2, 4), 16);
|
|
2048
|
+
const b = parseInt(cleaned.slice(4, 6), 16);
|
|
2049
|
+
return `rgba(${r},${g},${b},${alpha})`;
|
|
2050
|
+
};
|
|
2012
2051
|
const drawDrawingHandle = (x, y, color) => {
|
|
2013
2052
|
ctx.save();
|
|
2014
2053
|
ctx.setLineDash([]);
|
|
@@ -2095,28 +2134,27 @@ function createChart(element, options = {}) {
|
|
|
2095
2134
|
const secondY = yFromPrice(second.price);
|
|
2096
2135
|
const lineLeft = chartLeft;
|
|
2097
2136
|
const lineRight = chartRight;
|
|
2098
|
-
const levelLines =
|
|
2099
|
-
const price = first.price + (second.price - first.price) * (1 -
|
|
2100
|
-
return {
|
|
2137
|
+
const levelLines = FIB_RATIOS.map((ratio) => {
|
|
2138
|
+
const price = first.price + (second.price - first.price) * (1 - ratio);
|
|
2139
|
+
return { ratio, price, y: yFromPrice(price) };
|
|
2101
2140
|
});
|
|
2141
|
+
const bandColor = hexToRgba(drawing.color, 0.1);
|
|
2102
2142
|
for (let index = 0; index < levelLines.length - 1; index += 1) {
|
|
2103
2143
|
const top = levelLines[index];
|
|
2104
2144
|
const bottom = levelLines[index + 1];
|
|
2105
|
-
const fillColor = bottom.band ?? top.band;
|
|
2106
|
-
if (!fillColor) continue;
|
|
2107
2145
|
const bandTop = Math.min(top.y, bottom.y);
|
|
2108
2146
|
const bandBottom = Math.max(top.y, bottom.y);
|
|
2109
2147
|
ctx.save();
|
|
2110
|
-
ctx.fillStyle =
|
|
2148
|
+
ctx.fillStyle = bandColor;
|
|
2111
2149
|
ctx.globalAlpha = draft ? 0.5 : 1;
|
|
2112
2150
|
ctx.fillRect(lineLeft, bandTop, lineRight - lineLeft, bandBottom - bandTop);
|
|
2113
2151
|
ctx.restore();
|
|
2114
2152
|
}
|
|
2115
2153
|
ctx.save();
|
|
2116
2154
|
ctx.lineWidth = drawing.width;
|
|
2155
|
+
ctx.strokeStyle = drawing.color;
|
|
2117
2156
|
applyDashPattern(drawing.style, dashPatterns.dotted, dashPatterns.dashed);
|
|
2118
2157
|
for (const level of levelLines) {
|
|
2119
|
-
ctx.strokeStyle = level.color;
|
|
2120
2158
|
ctx.beginPath();
|
|
2121
2159
|
ctx.moveTo(crisp(lineLeft), crisp(level.y));
|
|
2122
2160
|
ctx.lineTo(crisp(lineRight), crisp(level.y));
|
|
@@ -2124,7 +2162,7 @@ function createChart(element, options = {}) {
|
|
|
2124
2162
|
}
|
|
2125
2163
|
ctx.restore();
|
|
2126
2164
|
ctx.save();
|
|
2127
|
-
ctx.strokeStyle =
|
|
2165
|
+
ctx.strokeStyle = hexToRgba(drawing.color, 0.5);
|
|
2128
2166
|
ctx.setLineDash([4, 4]);
|
|
2129
2167
|
ctx.lineWidth = 1;
|
|
2130
2168
|
ctx.beginPath();
|
|
@@ -2144,7 +2182,7 @@ function createChart(element, options = {}) {
|
|
|
2144
2182
|
const bgY = level.y - 9;
|
|
2145
2183
|
ctx.fillStyle = mergedOptions.backgroundColor;
|
|
2146
2184
|
fillRoundedRect(bgX, bgY, textWidth + padding * 2, 16, 3);
|
|
2147
|
-
ctx.fillStyle =
|
|
2185
|
+
ctx.fillStyle = drawing.color;
|
|
2148
2186
|
ctx.textAlign = "left";
|
|
2149
2187
|
ctx.textBaseline = "middle";
|
|
2150
2188
|
ctx.fillText(labelText, bgX + padding, bgY + 8);
|
|
@@ -3919,6 +3957,7 @@ function createChart(element, options = {}) {
|
|
|
3919
3957
|
width = nextOptions.width !== void 0 && nextOptions.width > 0 ? mergedOptions.width : previousWidth;
|
|
3920
3958
|
height = nextOptions.height !== void 0 && nextOptions.height > 0 ? mergedOptions.height : previousHeight;
|
|
3921
3959
|
mergedOptions = { ...mergedOptions, width, height };
|
|
3960
|
+
resetRightMarginCache();
|
|
3922
3961
|
doubleClickEnabled = mergedOptions.doubleClickEnabled;
|
|
3923
3962
|
doubleClickAction = mergedOptions.doubleClickAction;
|
|
3924
3963
|
const isTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
|
|
@@ -3945,6 +3984,7 @@ function createChart(element, options = {}) {
|
|
|
3945
3984
|
height = nextHeight;
|
|
3946
3985
|
}
|
|
3947
3986
|
mergedOptions = { ...mergedOptions, width, height };
|
|
3987
|
+
resetRightMarginCache();
|
|
3948
3988
|
draw();
|
|
3949
3989
|
};
|
|
3950
3990
|
const setData = (nextData) => {
|
|
@@ -3961,6 +4001,7 @@ function createChart(element, options = {}) {
|
|
|
3961
4001
|
tickerPriceTarget = null;
|
|
3962
4002
|
smoothedTickerVolume = null;
|
|
3963
4003
|
tickerVolumeTarget = null;
|
|
4004
|
+
resetRightMarginCache();
|
|
3964
4005
|
resetYViewport();
|
|
3965
4006
|
draw();
|
|
3966
4007
|
return;
|
|
@@ -3994,6 +4035,7 @@ function createChart(element, options = {}) {
|
|
|
3994
4035
|
...line,
|
|
3995
4036
|
id: line.id ?? `line-${index + 1}`
|
|
3996
4037
|
}));
|
|
4038
|
+
resetRightMarginCache();
|
|
3997
4039
|
draw();
|
|
3998
4040
|
};
|
|
3999
4041
|
const addPriceLine = (line) => {
|
|
@@ -4004,6 +4046,7 @@ function createChart(element, options = {}) {
|
|
|
4004
4046
|
};
|
|
4005
4047
|
const removePriceLine = (id) => {
|
|
4006
4048
|
priceLines = priceLines.filter((line) => line.id !== id);
|
|
4049
|
+
resetRightMarginCache();
|
|
4007
4050
|
draw();
|
|
4008
4051
|
};
|
|
4009
4052
|
const setOrderLines = (lines) => {
|
|
@@ -4011,6 +4054,7 @@ function createChart(element, options = {}) {
|
|
|
4011
4054
|
...line,
|
|
4012
4055
|
id: line.id ?? `order-${index + 1}`
|
|
4013
4056
|
}));
|
|
4057
|
+
resetRightMarginCache();
|
|
4014
4058
|
const activeIds = new Set(orderLines.map((line) => line.id));
|
|
4015
4059
|
for (const id of Array.from(orderWidgetWidthById.keys())) {
|
|
4016
4060
|
if (!activeIds.has(id)) {
|
|
@@ -4038,6 +4082,7 @@ function createChart(element, options = {}) {
|
|
|
4038
4082
|
orderLines = orderLines.filter((line) => line.id !== id);
|
|
4039
4083
|
orderWidgetWidthById.delete(id);
|
|
4040
4084
|
orderPriceTagWidthById.delete(id);
|
|
4085
|
+
resetRightMarginCache();
|
|
4041
4086
|
draw();
|
|
4042
4087
|
};
|
|
4043
4088
|
const onOrderAction = (handler) => {
|