hyperprop-charting-library 0.1.123 → 0.1.125

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.
@@ -303,6 +303,16 @@ var getIndicatorSourceValue = (point, source) => {
303
303
  var clampIndicatorLength = (value, fallback) => {
304
304
  return Math.max(1, Math.round(Number(value) || fallback));
305
305
  };
306
+ var isFullyWhiteColor = (value) => {
307
+ if (!value) return false;
308
+ const normalized = value.trim().toLowerCase();
309
+ if (normalized === "white" || normalized === "#fff" || normalized === "#ffffff") return true;
310
+ const rgb = /^rgba?\(\s*255\s*,\s*255\s*,\s*255\s*(?:,\s*[\d.]+\s*)?\)$/.exec(normalized);
311
+ return rgb !== null;
312
+ };
313
+ var labelTextColorOn = (background, textColor) => {
314
+ return isFullyWhiteColor(background) ? "#000000" : textColor;
315
+ };
306
316
  var computeSmaSeries = (data, length, source) => {
307
317
  const result = new Array(data.length).fill(null);
308
318
  let sum = 0;
@@ -1364,7 +1374,7 @@ function createChart(element, options = {}) {
1364
1374
  return;
1365
1375
  }
1366
1376
  const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1367
- const speed = clamp(tickerOpts.smoothingSpeed ?? 8, 1, 60);
1377
+ const speed = clamp(tickerOpts.smoothingSpeed ?? 4, 1, 60);
1368
1378
  const dt = 1 / 60;
1369
1379
  const lerp = 1 - Math.exp(-speed * dt);
1370
1380
  smoothedTickerPrice += priceDiff * lerp;
@@ -1801,6 +1811,56 @@ function createChart(element, options = {}) {
1801
1811
  const upperDelta = Math.abs(upperPoint.time.getTime() - timeMs);
1802
1812
  return lowerDelta <= upperDelta ? lower : upper;
1803
1813
  };
1814
+ const indexForTimeMs = (timeMs) => {
1815
+ if (!Number.isFinite(timeMs) || data.length === 0) {
1816
+ return null;
1817
+ }
1818
+ const firstMs = data[0].time.getTime();
1819
+ const lastMs = data[data.length - 1].time.getTime();
1820
+ if (timeMs < firstMs) {
1821
+ const stepMs = getTimeStepMs();
1822
+ return stepMs > 0 ? (timeMs - firstMs) / stepMs : 0;
1823
+ }
1824
+ if (timeMs > lastMs) {
1825
+ const stepMs = getTimeStepMs();
1826
+ return data.length - 1 + (stepMs > 0 ? (timeMs - lastMs) / stepMs : 0);
1827
+ }
1828
+ return findNearestIndexForTimeMs(timeMs);
1829
+ };
1830
+ const reanchorDrawingPoint = (point) => {
1831
+ if (!point.time) {
1832
+ return point;
1833
+ }
1834
+ const timeMs = Date.parse(point.time);
1835
+ if (!Number.isFinite(timeMs)) {
1836
+ return point;
1837
+ }
1838
+ const rounded = Math.round(point.index);
1839
+ if (data[rounded]?.time.getTime() === timeMs) {
1840
+ return point;
1841
+ }
1842
+ const mapped = indexForTimeMs(timeMs);
1843
+ if (mapped === null) {
1844
+ return point;
1845
+ }
1846
+ const fraction = point.index - rounded;
1847
+ return { ...point, index: mapped + fraction };
1848
+ };
1849
+ const reanchorDrawingsToData = () => {
1850
+ if (data.length === 0) {
1851
+ return;
1852
+ }
1853
+ drawings = drawings.map((drawing) => ({
1854
+ ...drawing,
1855
+ points: drawing.points.map((point) => reanchorDrawingPoint(point))
1856
+ }));
1857
+ if (draftDrawing) {
1858
+ draftDrawing = {
1859
+ ...draftDrawing,
1860
+ points: draftDrawing.points.map((point) => reanchorDrawingPoint(point))
1861
+ };
1862
+ }
1863
+ };
1804
1864
  const getCandleDirectionByIndex = (index) => {
1805
1865
  const point = data[index];
1806
1866
  if (!point) {
@@ -1933,7 +1993,7 @@ function createChart(element, options = {}) {
1933
1993
  labelY + labelHeight / 2,
1934
1994
  "left",
1935
1995
  "middle",
1936
- mergedLine.labelTextColor
1996
+ labelTextColorOn(mergedLine.labelBackgroundColor, mergedLine.labelTextColor)
1937
1997
  );
1938
1998
  };
1939
1999
  const drawOrderLine = (line, yFromPrice, chartLeft, chartTop, chartRight, chartBottom) => {
@@ -2073,7 +2133,10 @@ function createChart(element, options = {}) {
2073
2133
  const borderRadius = Math.max(0, mergedLine.labelBorderRadius);
2074
2134
  const widgetBackground = mergedOptions.backgroundColor;
2075
2135
  const widgetBorder = color;
2076
- const textColor = line.labelTextColor ?? (mergedLine.type === "takeProfit" ? "#5eead4" : mergedLine.type === "stop" ? "#fbbf24" : mergedLine.labelTextColor);
2136
+ const textColor = labelTextColorOn(
2137
+ widgetBackground,
2138
+ line.labelTextColor ?? (mergedLine.type === "takeProfit" ? "#5eead4" : mergedLine.type === "stop" ? "#fbbf24" : mergedLine.labelTextColor)
2139
+ );
2077
2140
  const mainWidgetX = leftWidgetX + actionButtonsTotalWidth + actionButtonsGap;
2078
2141
  ctx.fillStyle = widgetBackground;
2079
2142
  fillRoundedRect(Math.round(mainWidgetX), Math.round(widgetY), mainWidgetWidth, labelHeight, borderRadius);
@@ -3825,7 +3888,8 @@ function createChart(element, options = {}) {
3825
3888
  fillRoundedRect(Math.round(labelX), Math.round(label.y), label.width, label.height, labelRadius);
3826
3889
  const hasSubtexts = label.subtexts.length > 0;
3827
3890
  const primaryY = hasSubtexts ? label.y + baseLabelHeight / 2 - 1 : label.y + label.height / 2;
3828
- drawText(label.text, labelX + labelPaddingX, primaryY, "left", "middle", label.textColor);
3891
+ const effectiveTextColor = labelTextColorOn(label.backgroundColor, label.textColor);
3892
+ drawText(label.text, labelX + labelPaddingX, primaryY, "left", "middle", effectiveTextColor);
3829
3893
  if (hasSubtexts) {
3830
3894
  const baseFont = ctx.font;
3831
3895
  ctx.font = `${label.subtextFontSize}px ${mergedOptions.fontFamily}`;
@@ -3836,7 +3900,7 @@ function createChart(element, options = {}) {
3836
3900
  label.y + baseLabelHeight + index * (label.subtextFontSize + 5) + label.subtextFontSize / 2,
3837
3901
  "left",
3838
3902
  "middle",
3839
- label.subtextColor ?? label.textColor
3903
+ labelTextColorOn(label.backgroundColor, label.subtextColor ?? label.textColor)
3840
3904
  );
3841
3905
  });
3842
3906
  ctx.font = baseFont;
@@ -3993,7 +4057,7 @@ function createChart(element, options = {}) {
3993
4057
  const labelHeight = 20;
3994
4058
  const labelRadius = Math.max(0, crosshair.labelBorderRadius);
3995
4059
  const labelBackground = crosshair.labelBackgroundColor;
3996
- const labelTextColor = crosshair.labelTextColor;
4060
+ const labelTextColor = labelTextColorOn(labelBackground, crosshair.labelTextColor);
3997
4061
  const labelBorderColor = crosshair.labelBorderColor;
3998
4062
  const labelBorderWidth = Math.max(0, crosshair.labelBorderWidth);
3999
4063
  const labelBorderStyle = crosshair.labelBorderStyle;
@@ -5785,6 +5849,7 @@ function createChart(element, options = {}) {
5785
5849
  fitXViewport();
5786
5850
  }
5787
5851
  }
5852
+ reanchorDrawingsToData();
5788
5853
  const lastPoint = data[data.length - 1];
5789
5854
  if (lastPoint) {
5790
5855
  pushSmoothedTicker(lastPoint.c, lastPoint.v);
@@ -5831,6 +5896,7 @@ function createChart(element, options = {}) {
5831
5896
  }
5832
5897
  merged.set(timeMs, parsed);
5833
5898
  data = Array.from(merged.values()).sort((a, b) => a.time.getTime() - b.time.getTime());
5899
+ reanchorDrawingsToData();
5834
5900
  }
5835
5901
  pushSmoothedTicker(parsed.c, parsed.v);
5836
5902
  scheduleDraw();
@@ -6013,11 +6079,13 @@ function createChart(element, options = {}) {
6013
6079
  const setDrawings = (nextDrawings) => {
6014
6080
  drawings = dedupeDrawingIds(nextDrawings.map((drawing) => normalizeDrawingState(drawing)));
6015
6081
  draftDrawing = null;
6082
+ reanchorDrawingsToData();
6016
6083
  emitDrawingsChange();
6017
6084
  scheduleDraw();
6018
6085
  };
6019
6086
  const addDrawing = (drawing) => {
6020
6087
  const next = normalizeDrawingState(drawing);
6088
+ next.points = next.points.map((point) => reanchorDrawingPoint(point));
6021
6089
  drawings.push(next);
6022
6090
  emitDrawingsChange();
6023
6091
  scheduleDraw();
@@ -6029,7 +6097,7 @@ function createChart(element, options = {}) {
6029
6097
  ...serializeDrawing(drawing),
6030
6098
  ...patch,
6031
6099
  id,
6032
- points: patch.points ?? drawing.points
6100
+ points: (patch.points ?? drawing.points).map((point) => reanchorDrawingPoint(point))
6033
6101
  }) : drawing
6034
6102
  );
6035
6103
  emitDrawingsChange();
@@ -277,6 +277,16 @@ var getIndicatorSourceValue = (point, source) => {
277
277
  var clampIndicatorLength = (value, fallback) => {
278
278
  return Math.max(1, Math.round(Number(value) || fallback));
279
279
  };
280
+ var isFullyWhiteColor = (value) => {
281
+ if (!value) return false;
282
+ const normalized = value.trim().toLowerCase();
283
+ if (normalized === "white" || normalized === "#fff" || normalized === "#ffffff") return true;
284
+ const rgb = /^rgba?\(\s*255\s*,\s*255\s*,\s*255\s*(?:,\s*[\d.]+\s*)?\)$/.exec(normalized);
285
+ return rgb !== null;
286
+ };
287
+ var labelTextColorOn = (background, textColor) => {
288
+ return isFullyWhiteColor(background) ? "#000000" : textColor;
289
+ };
280
290
  var computeSmaSeries = (data, length, source) => {
281
291
  const result = new Array(data.length).fill(null);
282
292
  let sum = 0;
@@ -1338,7 +1348,7 @@ function createChart(element, options = {}) {
1338
1348
  return;
1339
1349
  }
1340
1350
  const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1341
- const speed = clamp(tickerOpts.smoothingSpeed ?? 8, 1, 60);
1351
+ const speed = clamp(tickerOpts.smoothingSpeed ?? 4, 1, 60);
1342
1352
  const dt = 1 / 60;
1343
1353
  const lerp = 1 - Math.exp(-speed * dt);
1344
1354
  smoothedTickerPrice += priceDiff * lerp;
@@ -1775,6 +1785,56 @@ function createChart(element, options = {}) {
1775
1785
  const upperDelta = Math.abs(upperPoint.time.getTime() - timeMs);
1776
1786
  return lowerDelta <= upperDelta ? lower : upper;
1777
1787
  };
1788
+ const indexForTimeMs = (timeMs) => {
1789
+ if (!Number.isFinite(timeMs) || data.length === 0) {
1790
+ return null;
1791
+ }
1792
+ const firstMs = data[0].time.getTime();
1793
+ const lastMs = data[data.length - 1].time.getTime();
1794
+ if (timeMs < firstMs) {
1795
+ const stepMs = getTimeStepMs();
1796
+ return stepMs > 0 ? (timeMs - firstMs) / stepMs : 0;
1797
+ }
1798
+ if (timeMs > lastMs) {
1799
+ const stepMs = getTimeStepMs();
1800
+ return data.length - 1 + (stepMs > 0 ? (timeMs - lastMs) / stepMs : 0);
1801
+ }
1802
+ return findNearestIndexForTimeMs(timeMs);
1803
+ };
1804
+ const reanchorDrawingPoint = (point) => {
1805
+ if (!point.time) {
1806
+ return point;
1807
+ }
1808
+ const timeMs = Date.parse(point.time);
1809
+ if (!Number.isFinite(timeMs)) {
1810
+ return point;
1811
+ }
1812
+ const rounded = Math.round(point.index);
1813
+ if (data[rounded]?.time.getTime() === timeMs) {
1814
+ return point;
1815
+ }
1816
+ const mapped = indexForTimeMs(timeMs);
1817
+ if (mapped === null) {
1818
+ return point;
1819
+ }
1820
+ const fraction = point.index - rounded;
1821
+ return { ...point, index: mapped + fraction };
1822
+ };
1823
+ const reanchorDrawingsToData = () => {
1824
+ if (data.length === 0) {
1825
+ return;
1826
+ }
1827
+ drawings = drawings.map((drawing) => ({
1828
+ ...drawing,
1829
+ points: drawing.points.map((point) => reanchorDrawingPoint(point))
1830
+ }));
1831
+ if (draftDrawing) {
1832
+ draftDrawing = {
1833
+ ...draftDrawing,
1834
+ points: draftDrawing.points.map((point) => reanchorDrawingPoint(point))
1835
+ };
1836
+ }
1837
+ };
1778
1838
  const getCandleDirectionByIndex = (index) => {
1779
1839
  const point = data[index];
1780
1840
  if (!point) {
@@ -1907,7 +1967,7 @@ function createChart(element, options = {}) {
1907
1967
  labelY + labelHeight / 2,
1908
1968
  "left",
1909
1969
  "middle",
1910
- mergedLine.labelTextColor
1970
+ labelTextColorOn(mergedLine.labelBackgroundColor, mergedLine.labelTextColor)
1911
1971
  );
1912
1972
  };
1913
1973
  const drawOrderLine = (line, yFromPrice, chartLeft, chartTop, chartRight, chartBottom) => {
@@ -2047,7 +2107,10 @@ function createChart(element, options = {}) {
2047
2107
  const borderRadius = Math.max(0, mergedLine.labelBorderRadius);
2048
2108
  const widgetBackground = mergedOptions.backgroundColor;
2049
2109
  const widgetBorder = color;
2050
- const textColor = line.labelTextColor ?? (mergedLine.type === "takeProfit" ? "#5eead4" : mergedLine.type === "stop" ? "#fbbf24" : mergedLine.labelTextColor);
2110
+ const textColor = labelTextColorOn(
2111
+ widgetBackground,
2112
+ line.labelTextColor ?? (mergedLine.type === "takeProfit" ? "#5eead4" : mergedLine.type === "stop" ? "#fbbf24" : mergedLine.labelTextColor)
2113
+ );
2051
2114
  const mainWidgetX = leftWidgetX + actionButtonsTotalWidth + actionButtonsGap;
2052
2115
  ctx.fillStyle = widgetBackground;
2053
2116
  fillRoundedRect(Math.round(mainWidgetX), Math.round(widgetY), mainWidgetWidth, labelHeight, borderRadius);
@@ -3799,7 +3862,8 @@ function createChart(element, options = {}) {
3799
3862
  fillRoundedRect(Math.round(labelX), Math.round(label.y), label.width, label.height, labelRadius);
3800
3863
  const hasSubtexts = label.subtexts.length > 0;
3801
3864
  const primaryY = hasSubtexts ? label.y + baseLabelHeight / 2 - 1 : label.y + label.height / 2;
3802
- drawText(label.text, labelX + labelPaddingX, primaryY, "left", "middle", label.textColor);
3865
+ const effectiveTextColor = labelTextColorOn(label.backgroundColor, label.textColor);
3866
+ drawText(label.text, labelX + labelPaddingX, primaryY, "left", "middle", effectiveTextColor);
3803
3867
  if (hasSubtexts) {
3804
3868
  const baseFont = ctx.font;
3805
3869
  ctx.font = `${label.subtextFontSize}px ${mergedOptions.fontFamily}`;
@@ -3810,7 +3874,7 @@ function createChart(element, options = {}) {
3810
3874
  label.y + baseLabelHeight + index * (label.subtextFontSize + 5) + label.subtextFontSize / 2,
3811
3875
  "left",
3812
3876
  "middle",
3813
- label.subtextColor ?? label.textColor
3877
+ labelTextColorOn(label.backgroundColor, label.subtextColor ?? label.textColor)
3814
3878
  );
3815
3879
  });
3816
3880
  ctx.font = baseFont;
@@ -3967,7 +4031,7 @@ function createChart(element, options = {}) {
3967
4031
  const labelHeight = 20;
3968
4032
  const labelRadius = Math.max(0, crosshair.labelBorderRadius);
3969
4033
  const labelBackground = crosshair.labelBackgroundColor;
3970
- const labelTextColor = crosshair.labelTextColor;
4034
+ const labelTextColor = labelTextColorOn(labelBackground, crosshair.labelTextColor);
3971
4035
  const labelBorderColor = crosshair.labelBorderColor;
3972
4036
  const labelBorderWidth = Math.max(0, crosshair.labelBorderWidth);
3973
4037
  const labelBorderStyle = crosshair.labelBorderStyle;
@@ -5759,6 +5823,7 @@ function createChart(element, options = {}) {
5759
5823
  fitXViewport();
5760
5824
  }
5761
5825
  }
5826
+ reanchorDrawingsToData();
5762
5827
  const lastPoint = data[data.length - 1];
5763
5828
  if (lastPoint) {
5764
5829
  pushSmoothedTicker(lastPoint.c, lastPoint.v);
@@ -5805,6 +5870,7 @@ function createChart(element, options = {}) {
5805
5870
  }
5806
5871
  merged.set(timeMs, parsed);
5807
5872
  data = Array.from(merged.values()).sort((a, b) => a.time.getTime() - b.time.getTime());
5873
+ reanchorDrawingsToData();
5808
5874
  }
5809
5875
  pushSmoothedTicker(parsed.c, parsed.v);
5810
5876
  scheduleDraw();
@@ -5987,11 +6053,13 @@ function createChart(element, options = {}) {
5987
6053
  const setDrawings = (nextDrawings) => {
5988
6054
  drawings = dedupeDrawingIds(nextDrawings.map((drawing) => normalizeDrawingState(drawing)));
5989
6055
  draftDrawing = null;
6056
+ reanchorDrawingsToData();
5990
6057
  emitDrawingsChange();
5991
6058
  scheduleDraw();
5992
6059
  };
5993
6060
  const addDrawing = (drawing) => {
5994
6061
  const next = normalizeDrawingState(drawing);
6062
+ next.points = next.points.map((point) => reanchorDrawingPoint(point));
5995
6063
  drawings.push(next);
5996
6064
  emitDrawingsChange();
5997
6065
  scheduleDraw();
@@ -6003,7 +6071,7 @@ function createChart(element, options = {}) {
6003
6071
  ...serializeDrawing(drawing),
6004
6072
  ...patch,
6005
6073
  id,
6006
- points: patch.points ?? drawing.points
6074
+ points: (patch.points ?? drawing.points).map((point) => reanchorDrawingPoint(point))
6007
6075
  }) : drawing
6008
6076
  );
6009
6077
  emitDrawingsChange();
package/dist/index.cjs CHANGED
@@ -303,6 +303,16 @@ var getIndicatorSourceValue = (point, source) => {
303
303
  var clampIndicatorLength = (value, fallback) => {
304
304
  return Math.max(1, Math.round(Number(value) || fallback));
305
305
  };
306
+ var isFullyWhiteColor = (value) => {
307
+ if (!value) return false;
308
+ const normalized = value.trim().toLowerCase();
309
+ if (normalized === "white" || normalized === "#fff" || normalized === "#ffffff") return true;
310
+ const rgb = /^rgba?\(\s*255\s*,\s*255\s*,\s*255\s*(?:,\s*[\d.]+\s*)?\)$/.exec(normalized);
311
+ return rgb !== null;
312
+ };
313
+ var labelTextColorOn = (background, textColor) => {
314
+ return isFullyWhiteColor(background) ? "#000000" : textColor;
315
+ };
306
316
  var computeSmaSeries = (data, length, source) => {
307
317
  const result = new Array(data.length).fill(null);
308
318
  let sum = 0;
@@ -1364,7 +1374,7 @@ function createChart(element, options = {}) {
1364
1374
  return;
1365
1375
  }
1366
1376
  const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1367
- const speed = clamp(tickerOpts.smoothingSpeed ?? 8, 1, 60);
1377
+ const speed = clamp(tickerOpts.smoothingSpeed ?? 4, 1, 60);
1368
1378
  const dt = 1 / 60;
1369
1379
  const lerp = 1 - Math.exp(-speed * dt);
1370
1380
  smoothedTickerPrice += priceDiff * lerp;
@@ -1801,6 +1811,56 @@ function createChart(element, options = {}) {
1801
1811
  const upperDelta = Math.abs(upperPoint.time.getTime() - timeMs);
1802
1812
  return lowerDelta <= upperDelta ? lower : upper;
1803
1813
  };
1814
+ const indexForTimeMs = (timeMs) => {
1815
+ if (!Number.isFinite(timeMs) || data.length === 0) {
1816
+ return null;
1817
+ }
1818
+ const firstMs = data[0].time.getTime();
1819
+ const lastMs = data[data.length - 1].time.getTime();
1820
+ if (timeMs < firstMs) {
1821
+ const stepMs = getTimeStepMs();
1822
+ return stepMs > 0 ? (timeMs - firstMs) / stepMs : 0;
1823
+ }
1824
+ if (timeMs > lastMs) {
1825
+ const stepMs = getTimeStepMs();
1826
+ return data.length - 1 + (stepMs > 0 ? (timeMs - lastMs) / stepMs : 0);
1827
+ }
1828
+ return findNearestIndexForTimeMs(timeMs);
1829
+ };
1830
+ const reanchorDrawingPoint = (point) => {
1831
+ if (!point.time) {
1832
+ return point;
1833
+ }
1834
+ const timeMs = Date.parse(point.time);
1835
+ if (!Number.isFinite(timeMs)) {
1836
+ return point;
1837
+ }
1838
+ const rounded = Math.round(point.index);
1839
+ if (data[rounded]?.time.getTime() === timeMs) {
1840
+ return point;
1841
+ }
1842
+ const mapped = indexForTimeMs(timeMs);
1843
+ if (mapped === null) {
1844
+ return point;
1845
+ }
1846
+ const fraction = point.index - rounded;
1847
+ return { ...point, index: mapped + fraction };
1848
+ };
1849
+ const reanchorDrawingsToData = () => {
1850
+ if (data.length === 0) {
1851
+ return;
1852
+ }
1853
+ drawings = drawings.map((drawing) => ({
1854
+ ...drawing,
1855
+ points: drawing.points.map((point) => reanchorDrawingPoint(point))
1856
+ }));
1857
+ if (draftDrawing) {
1858
+ draftDrawing = {
1859
+ ...draftDrawing,
1860
+ points: draftDrawing.points.map((point) => reanchorDrawingPoint(point))
1861
+ };
1862
+ }
1863
+ };
1804
1864
  const getCandleDirectionByIndex = (index) => {
1805
1865
  const point = data[index];
1806
1866
  if (!point) {
@@ -1933,7 +1993,7 @@ function createChart(element, options = {}) {
1933
1993
  labelY + labelHeight / 2,
1934
1994
  "left",
1935
1995
  "middle",
1936
- mergedLine.labelTextColor
1996
+ labelTextColorOn(mergedLine.labelBackgroundColor, mergedLine.labelTextColor)
1937
1997
  );
1938
1998
  };
1939
1999
  const drawOrderLine = (line, yFromPrice, chartLeft, chartTop, chartRight, chartBottom) => {
@@ -2073,7 +2133,10 @@ function createChart(element, options = {}) {
2073
2133
  const borderRadius = Math.max(0, mergedLine.labelBorderRadius);
2074
2134
  const widgetBackground = mergedOptions.backgroundColor;
2075
2135
  const widgetBorder = color;
2076
- const textColor = line.labelTextColor ?? (mergedLine.type === "takeProfit" ? "#5eead4" : mergedLine.type === "stop" ? "#fbbf24" : mergedLine.labelTextColor);
2136
+ const textColor = labelTextColorOn(
2137
+ widgetBackground,
2138
+ line.labelTextColor ?? (mergedLine.type === "takeProfit" ? "#5eead4" : mergedLine.type === "stop" ? "#fbbf24" : mergedLine.labelTextColor)
2139
+ );
2077
2140
  const mainWidgetX = leftWidgetX + actionButtonsTotalWidth + actionButtonsGap;
2078
2141
  ctx.fillStyle = widgetBackground;
2079
2142
  fillRoundedRect(Math.round(mainWidgetX), Math.round(widgetY), mainWidgetWidth, labelHeight, borderRadius);
@@ -3825,7 +3888,8 @@ function createChart(element, options = {}) {
3825
3888
  fillRoundedRect(Math.round(labelX), Math.round(label.y), label.width, label.height, labelRadius);
3826
3889
  const hasSubtexts = label.subtexts.length > 0;
3827
3890
  const primaryY = hasSubtexts ? label.y + baseLabelHeight / 2 - 1 : label.y + label.height / 2;
3828
- drawText(label.text, labelX + labelPaddingX, primaryY, "left", "middle", label.textColor);
3891
+ const effectiveTextColor = labelTextColorOn(label.backgroundColor, label.textColor);
3892
+ drawText(label.text, labelX + labelPaddingX, primaryY, "left", "middle", effectiveTextColor);
3829
3893
  if (hasSubtexts) {
3830
3894
  const baseFont = ctx.font;
3831
3895
  ctx.font = `${label.subtextFontSize}px ${mergedOptions.fontFamily}`;
@@ -3836,7 +3900,7 @@ function createChart(element, options = {}) {
3836
3900
  label.y + baseLabelHeight + index * (label.subtextFontSize + 5) + label.subtextFontSize / 2,
3837
3901
  "left",
3838
3902
  "middle",
3839
- label.subtextColor ?? label.textColor
3903
+ labelTextColorOn(label.backgroundColor, label.subtextColor ?? label.textColor)
3840
3904
  );
3841
3905
  });
3842
3906
  ctx.font = baseFont;
@@ -3993,7 +4057,7 @@ function createChart(element, options = {}) {
3993
4057
  const labelHeight = 20;
3994
4058
  const labelRadius = Math.max(0, crosshair.labelBorderRadius);
3995
4059
  const labelBackground = crosshair.labelBackgroundColor;
3996
- const labelTextColor = crosshair.labelTextColor;
4060
+ const labelTextColor = labelTextColorOn(labelBackground, crosshair.labelTextColor);
3997
4061
  const labelBorderColor = crosshair.labelBorderColor;
3998
4062
  const labelBorderWidth = Math.max(0, crosshair.labelBorderWidth);
3999
4063
  const labelBorderStyle = crosshair.labelBorderStyle;
@@ -5785,6 +5849,7 @@ function createChart(element, options = {}) {
5785
5849
  fitXViewport();
5786
5850
  }
5787
5851
  }
5852
+ reanchorDrawingsToData();
5788
5853
  const lastPoint = data[data.length - 1];
5789
5854
  if (lastPoint) {
5790
5855
  pushSmoothedTicker(lastPoint.c, lastPoint.v);
@@ -5831,6 +5896,7 @@ function createChart(element, options = {}) {
5831
5896
  }
5832
5897
  merged.set(timeMs, parsed);
5833
5898
  data = Array.from(merged.values()).sort((a, b) => a.time.getTime() - b.time.getTime());
5899
+ reanchorDrawingsToData();
5834
5900
  }
5835
5901
  pushSmoothedTicker(parsed.c, parsed.v);
5836
5902
  scheduleDraw();
@@ -6013,11 +6079,13 @@ function createChart(element, options = {}) {
6013
6079
  const setDrawings = (nextDrawings) => {
6014
6080
  drawings = dedupeDrawingIds(nextDrawings.map((drawing) => normalizeDrawingState(drawing)));
6015
6081
  draftDrawing = null;
6082
+ reanchorDrawingsToData();
6016
6083
  emitDrawingsChange();
6017
6084
  scheduleDraw();
6018
6085
  };
6019
6086
  const addDrawing = (drawing) => {
6020
6087
  const next = normalizeDrawingState(drawing);
6088
+ next.points = next.points.map((point) => reanchorDrawingPoint(point));
6021
6089
  drawings.push(next);
6022
6090
  emitDrawingsChange();
6023
6091
  scheduleDraw();
@@ -6029,7 +6097,7 @@ function createChart(element, options = {}) {
6029
6097
  ...serializeDrawing(drawing),
6030
6098
  ...patch,
6031
6099
  id,
6032
- points: patch.points ?? drawing.points
6100
+ points: (patch.points ?? drawing.points).map((point) => reanchorDrawingPoint(point))
6033
6101
  }) : drawing
6034
6102
  );
6035
6103
  emitDrawingsChange();
package/dist/index.js CHANGED
@@ -277,6 +277,16 @@ var getIndicatorSourceValue = (point, source) => {
277
277
  var clampIndicatorLength = (value, fallback) => {
278
278
  return Math.max(1, Math.round(Number(value) || fallback));
279
279
  };
280
+ var isFullyWhiteColor = (value) => {
281
+ if (!value) return false;
282
+ const normalized = value.trim().toLowerCase();
283
+ if (normalized === "white" || normalized === "#fff" || normalized === "#ffffff") return true;
284
+ const rgb = /^rgba?\(\s*255\s*,\s*255\s*,\s*255\s*(?:,\s*[\d.]+\s*)?\)$/.exec(normalized);
285
+ return rgb !== null;
286
+ };
287
+ var labelTextColorOn = (background, textColor) => {
288
+ return isFullyWhiteColor(background) ? "#000000" : textColor;
289
+ };
280
290
  var computeSmaSeries = (data, length, source) => {
281
291
  const result = new Array(data.length).fill(null);
282
292
  let sum = 0;
@@ -1338,7 +1348,7 @@ function createChart(element, options = {}) {
1338
1348
  return;
1339
1349
  }
1340
1350
  const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1341
- const speed = clamp(tickerOpts.smoothingSpeed ?? 8, 1, 60);
1351
+ const speed = clamp(tickerOpts.smoothingSpeed ?? 4, 1, 60);
1342
1352
  const dt = 1 / 60;
1343
1353
  const lerp = 1 - Math.exp(-speed * dt);
1344
1354
  smoothedTickerPrice += priceDiff * lerp;
@@ -1775,6 +1785,56 @@ function createChart(element, options = {}) {
1775
1785
  const upperDelta = Math.abs(upperPoint.time.getTime() - timeMs);
1776
1786
  return lowerDelta <= upperDelta ? lower : upper;
1777
1787
  };
1788
+ const indexForTimeMs = (timeMs) => {
1789
+ if (!Number.isFinite(timeMs) || data.length === 0) {
1790
+ return null;
1791
+ }
1792
+ const firstMs = data[0].time.getTime();
1793
+ const lastMs = data[data.length - 1].time.getTime();
1794
+ if (timeMs < firstMs) {
1795
+ const stepMs = getTimeStepMs();
1796
+ return stepMs > 0 ? (timeMs - firstMs) / stepMs : 0;
1797
+ }
1798
+ if (timeMs > lastMs) {
1799
+ const stepMs = getTimeStepMs();
1800
+ return data.length - 1 + (stepMs > 0 ? (timeMs - lastMs) / stepMs : 0);
1801
+ }
1802
+ return findNearestIndexForTimeMs(timeMs);
1803
+ };
1804
+ const reanchorDrawingPoint = (point) => {
1805
+ if (!point.time) {
1806
+ return point;
1807
+ }
1808
+ const timeMs = Date.parse(point.time);
1809
+ if (!Number.isFinite(timeMs)) {
1810
+ return point;
1811
+ }
1812
+ const rounded = Math.round(point.index);
1813
+ if (data[rounded]?.time.getTime() === timeMs) {
1814
+ return point;
1815
+ }
1816
+ const mapped = indexForTimeMs(timeMs);
1817
+ if (mapped === null) {
1818
+ return point;
1819
+ }
1820
+ const fraction = point.index - rounded;
1821
+ return { ...point, index: mapped + fraction };
1822
+ };
1823
+ const reanchorDrawingsToData = () => {
1824
+ if (data.length === 0) {
1825
+ return;
1826
+ }
1827
+ drawings = drawings.map((drawing) => ({
1828
+ ...drawing,
1829
+ points: drawing.points.map((point) => reanchorDrawingPoint(point))
1830
+ }));
1831
+ if (draftDrawing) {
1832
+ draftDrawing = {
1833
+ ...draftDrawing,
1834
+ points: draftDrawing.points.map((point) => reanchorDrawingPoint(point))
1835
+ };
1836
+ }
1837
+ };
1778
1838
  const getCandleDirectionByIndex = (index) => {
1779
1839
  const point = data[index];
1780
1840
  if (!point) {
@@ -1907,7 +1967,7 @@ function createChart(element, options = {}) {
1907
1967
  labelY + labelHeight / 2,
1908
1968
  "left",
1909
1969
  "middle",
1910
- mergedLine.labelTextColor
1970
+ labelTextColorOn(mergedLine.labelBackgroundColor, mergedLine.labelTextColor)
1911
1971
  );
1912
1972
  };
1913
1973
  const drawOrderLine = (line, yFromPrice, chartLeft, chartTop, chartRight, chartBottom) => {
@@ -2047,7 +2107,10 @@ function createChart(element, options = {}) {
2047
2107
  const borderRadius = Math.max(0, mergedLine.labelBorderRadius);
2048
2108
  const widgetBackground = mergedOptions.backgroundColor;
2049
2109
  const widgetBorder = color;
2050
- const textColor = line.labelTextColor ?? (mergedLine.type === "takeProfit" ? "#5eead4" : mergedLine.type === "stop" ? "#fbbf24" : mergedLine.labelTextColor);
2110
+ const textColor = labelTextColorOn(
2111
+ widgetBackground,
2112
+ line.labelTextColor ?? (mergedLine.type === "takeProfit" ? "#5eead4" : mergedLine.type === "stop" ? "#fbbf24" : mergedLine.labelTextColor)
2113
+ );
2051
2114
  const mainWidgetX = leftWidgetX + actionButtonsTotalWidth + actionButtonsGap;
2052
2115
  ctx.fillStyle = widgetBackground;
2053
2116
  fillRoundedRect(Math.round(mainWidgetX), Math.round(widgetY), mainWidgetWidth, labelHeight, borderRadius);
@@ -3799,7 +3862,8 @@ function createChart(element, options = {}) {
3799
3862
  fillRoundedRect(Math.round(labelX), Math.round(label.y), label.width, label.height, labelRadius);
3800
3863
  const hasSubtexts = label.subtexts.length > 0;
3801
3864
  const primaryY = hasSubtexts ? label.y + baseLabelHeight / 2 - 1 : label.y + label.height / 2;
3802
- drawText(label.text, labelX + labelPaddingX, primaryY, "left", "middle", label.textColor);
3865
+ const effectiveTextColor = labelTextColorOn(label.backgroundColor, label.textColor);
3866
+ drawText(label.text, labelX + labelPaddingX, primaryY, "left", "middle", effectiveTextColor);
3803
3867
  if (hasSubtexts) {
3804
3868
  const baseFont = ctx.font;
3805
3869
  ctx.font = `${label.subtextFontSize}px ${mergedOptions.fontFamily}`;
@@ -3810,7 +3874,7 @@ function createChart(element, options = {}) {
3810
3874
  label.y + baseLabelHeight + index * (label.subtextFontSize + 5) + label.subtextFontSize / 2,
3811
3875
  "left",
3812
3876
  "middle",
3813
- label.subtextColor ?? label.textColor
3877
+ labelTextColorOn(label.backgroundColor, label.subtextColor ?? label.textColor)
3814
3878
  );
3815
3879
  });
3816
3880
  ctx.font = baseFont;
@@ -3967,7 +4031,7 @@ function createChart(element, options = {}) {
3967
4031
  const labelHeight = 20;
3968
4032
  const labelRadius = Math.max(0, crosshair.labelBorderRadius);
3969
4033
  const labelBackground = crosshair.labelBackgroundColor;
3970
- const labelTextColor = crosshair.labelTextColor;
4034
+ const labelTextColor = labelTextColorOn(labelBackground, crosshair.labelTextColor);
3971
4035
  const labelBorderColor = crosshair.labelBorderColor;
3972
4036
  const labelBorderWidth = Math.max(0, crosshair.labelBorderWidth);
3973
4037
  const labelBorderStyle = crosshair.labelBorderStyle;
@@ -5759,6 +5823,7 @@ function createChart(element, options = {}) {
5759
5823
  fitXViewport();
5760
5824
  }
5761
5825
  }
5826
+ reanchorDrawingsToData();
5762
5827
  const lastPoint = data[data.length - 1];
5763
5828
  if (lastPoint) {
5764
5829
  pushSmoothedTicker(lastPoint.c, lastPoint.v);
@@ -5805,6 +5870,7 @@ function createChart(element, options = {}) {
5805
5870
  }
5806
5871
  merged.set(timeMs, parsed);
5807
5872
  data = Array.from(merged.values()).sort((a, b) => a.time.getTime() - b.time.getTime());
5873
+ reanchorDrawingsToData();
5808
5874
  }
5809
5875
  pushSmoothedTicker(parsed.c, parsed.v);
5810
5876
  scheduleDraw();
@@ -5987,11 +6053,13 @@ function createChart(element, options = {}) {
5987
6053
  const setDrawings = (nextDrawings) => {
5988
6054
  drawings = dedupeDrawingIds(nextDrawings.map((drawing) => normalizeDrawingState(drawing)));
5989
6055
  draftDrawing = null;
6056
+ reanchorDrawingsToData();
5990
6057
  emitDrawingsChange();
5991
6058
  scheduleDraw();
5992
6059
  };
5993
6060
  const addDrawing = (drawing) => {
5994
6061
  const next = normalizeDrawingState(drawing);
6062
+ next.points = next.points.map((point) => reanchorDrawingPoint(point));
5995
6063
  drawings.push(next);
5996
6064
  emitDrawingsChange();
5997
6065
  scheduleDraw();
@@ -6003,7 +6071,7 @@ function createChart(element, options = {}) {
6003
6071
  ...serializeDrawing(drawing),
6004
6072
  ...patch,
6005
6073
  id,
6006
- points: patch.points ?? drawing.points
6074
+ points: (patch.points ?? drawing.points).map((point) => reanchorDrawingPoint(point))
6007
6075
  }) : drawing
6008
6076
  );
6009
6077
  emitDrawingsChange();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.123",
3
+ "version": "0.1.125",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",