hyperprop-charting-library 0.1.62 → 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.
@@ -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 = Math.ceil(ctx.measureText(priceText).width) + paddingX * 2;
1308
+ const measured = getStabilizedNumericLabelWidth(priceText, paddingX);
1294
1309
  if (!mergedOptions.stabilizePriceLabels) {
1295
1310
  return measured;
1296
1311
  }
1297
- const templateWidth = Math.ceil(ctx.measureText(getStabilizedPriceTemplate()).width) + paddingX * 2;
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
- required = Math.max(required, measure(normalized));
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
- include(formatPrice(lastPoint2.c));
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
- include(mergedLine.label ?? formatPrice(mergedLine.price));
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
- include(formatPrice(renderPrice));
1905
+ includePrice(renderPrice);
1870
1906
  }
1871
1907
  }
1872
1908
  const maxRightMargin = Math.max(margin.right, width - margin.left - 160);
1873
- return Math.min(maxRightMargin, Math.max(margin.right, Math.ceil(required + 1)));
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;
@@ -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 = Math.ceil(ctx.measureText(priceText).width) + paddingX * 2;
1284
+ const measured = getStabilizedNumericLabelWidth(priceText, paddingX);
1270
1285
  if (!mergedOptions.stabilizePriceLabels) {
1271
1286
  return measured;
1272
1287
  }
1273
- const templateWidth = Math.ceil(ctx.measureText(getStabilizedPriceTemplate()).width) + paddingX * 2;
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
- required = Math.max(required, measure(normalized));
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
- include(formatPrice(lastPoint2.c));
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
- include(mergedLine.label ?? formatPrice(mergedLine.price));
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
- include(formatPrice(renderPrice));
1881
+ includePrice(renderPrice);
1846
1882
  }
1847
1883
  }
1848
1884
  const maxRightMargin = Math.max(margin.right, width - margin.left - 160);
1849
- return Math.min(maxRightMargin, Math.max(margin.right, Math.ceil(required + 1)));
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;
@@ -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 = Math.ceil(ctx.measureText(priceText).width) + paddingX * 2;
1308
+ const measured = getStabilizedNumericLabelWidth(priceText, paddingX);
1294
1309
  if (!mergedOptions.stabilizePriceLabels) {
1295
1310
  return measured;
1296
1311
  }
1297
- const templateWidth = Math.ceil(ctx.measureText(getStabilizedPriceTemplate()).width) + paddingX * 2;
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
- required = Math.max(required, measure(normalized));
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
- include(formatPrice(lastPoint2.c));
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
- include(mergedLine.label ?? formatPrice(mergedLine.price));
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
- include(formatPrice(renderPrice));
1905
+ includePrice(renderPrice);
1870
1906
  }
1871
1907
  }
1872
1908
  const maxRightMargin = Math.max(margin.right, width - margin.left - 160);
1873
- return Math.min(maxRightMargin, Math.max(margin.right, Math.ceil(required + 1)));
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;
@@ -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 = Math.ceil(ctx.measureText(priceText).width) + paddingX * 2;
1284
+ const measured = getStabilizedNumericLabelWidth(priceText, paddingX);
1270
1285
  if (!mergedOptions.stabilizePriceLabels) {
1271
1286
  return measured;
1272
1287
  }
1273
- const templateWidth = Math.ceil(ctx.measureText(getStabilizedPriceTemplate()).width) + paddingX * 2;
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
- required = Math.max(required, measure(normalized));
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
- include(formatPrice(lastPoint2.c));
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
- include(mergedLine.label ?? formatPrice(mergedLine.price));
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
- include(formatPrice(renderPrice));
1881
+ includePrice(renderPrice);
1846
1882
  }
1847
1883
  }
1848
1884
  const maxRightMargin = Math.max(margin.right, width - margin.left - 160);
1849
- return Math.min(maxRightMargin, Math.max(margin.right, Math.ceil(required + 1)));
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;
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.62",
3
+ "version": "0.1.63",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",