hyperprop-charting-library 0.1.122 → 0.1.124

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.
@@ -222,6 +222,7 @@ var DEFAULT_OPTIONS = {
222
222
  autoScaleIgnoreLatestCandle: true,
223
223
  kineticScroll: { touch: true, mouse: false },
224
224
  timeStepMs: 0,
225
+ clockOffsetMs: 0,
225
226
  pinOutOfRangeLines: false,
226
227
  doubleClickEnabled: true,
227
228
  doubleClickAction: "reset",
@@ -1363,7 +1364,7 @@ function createChart(element, options = {}) {
1363
1364
  return;
1364
1365
  }
1365
1366
  const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1366
- const speed = clamp(tickerOpts.smoothingSpeed ?? 8, 1, 60);
1367
+ const speed = clamp(tickerOpts.smoothingSpeed ?? 4, 1, 60);
1367
1368
  const dt = 1 / 60;
1368
1369
  const lerp = 1 - Math.exp(-speed * dt);
1369
1370
  smoothedTickerPrice += priceDiff * lerp;
@@ -1800,6 +1801,56 @@ function createChart(element, options = {}) {
1800
1801
  const upperDelta = Math.abs(upperPoint.time.getTime() - timeMs);
1801
1802
  return lowerDelta <= upperDelta ? lower : upper;
1802
1803
  };
1804
+ const indexForTimeMs = (timeMs) => {
1805
+ if (!Number.isFinite(timeMs) || data.length === 0) {
1806
+ return null;
1807
+ }
1808
+ const firstMs = data[0].time.getTime();
1809
+ const lastMs = data[data.length - 1].time.getTime();
1810
+ if (timeMs < firstMs) {
1811
+ const stepMs = getTimeStepMs();
1812
+ return stepMs > 0 ? (timeMs - firstMs) / stepMs : 0;
1813
+ }
1814
+ if (timeMs > lastMs) {
1815
+ const stepMs = getTimeStepMs();
1816
+ return data.length - 1 + (stepMs > 0 ? (timeMs - lastMs) / stepMs : 0);
1817
+ }
1818
+ return findNearestIndexForTimeMs(timeMs);
1819
+ };
1820
+ const reanchorDrawingPoint = (point) => {
1821
+ if (!point.time) {
1822
+ return point;
1823
+ }
1824
+ const timeMs = Date.parse(point.time);
1825
+ if (!Number.isFinite(timeMs)) {
1826
+ return point;
1827
+ }
1828
+ const rounded = Math.round(point.index);
1829
+ if (data[rounded]?.time.getTime() === timeMs) {
1830
+ return point;
1831
+ }
1832
+ const mapped = indexForTimeMs(timeMs);
1833
+ if (mapped === null) {
1834
+ return point;
1835
+ }
1836
+ const fraction = point.index - rounded;
1837
+ return { ...point, index: mapped + fraction };
1838
+ };
1839
+ const reanchorDrawingsToData = () => {
1840
+ if (data.length === 0) {
1841
+ return;
1842
+ }
1843
+ drawings = drawings.map((drawing) => ({
1844
+ ...drawing,
1845
+ points: drawing.points.map((point) => reanchorDrawingPoint(point))
1846
+ }));
1847
+ if (draftDrawing) {
1848
+ draftDrawing = {
1849
+ ...draftDrawing,
1850
+ points: draftDrawing.points.map((point) => reanchorDrawingPoint(point))
1851
+ };
1852
+ }
1853
+ };
1803
1854
  const getCandleDirectionByIndex = (index) => {
1804
1855
  const point = data[index];
1805
1856
  if (!point) {
@@ -3639,7 +3690,8 @@ function createChart(element, options = {}) {
3639
3690
  return null;
3640
3691
  }
3641
3692
  const stepMs = getTimeStepMs();
3642
- const elapsedMs = Date.now() - last.time.getTime();
3693
+ const nowMs = Date.now() + (Number(mergedOptions.clockOffsetMs) || 0);
3694
+ const elapsedMs = nowMs - last.time.getTime();
3643
3695
  const countdownMs = elapsedMs >= 0 ? stepMs - elapsedMs % stepMs : Math.min(stepMs, stepMs - elapsedMs);
3644
3696
  return formatDuration(countdownMs);
3645
3697
  };
@@ -3893,7 +3945,8 @@ function createChart(element, options = {}) {
3893
3945
  }
3894
3946
  if (labels.visible && labels.showCountdownToBarClose && lastPoint) {
3895
3947
  const stepMs = getTimeStepMs();
3896
- const elapsedMs = Date.now() - lastPoint.time.getTime();
3948
+ const nowMs = Date.now() + (Number(mergedOptions.clockOffsetMs) || 0);
3949
+ const elapsedMs = nowMs - lastPoint.time.getTime();
3897
3950
  const countdownMs = elapsedMs >= 0 ? stepMs - elapsedMs % stepMs : Math.min(stepMs, stepMs - elapsedMs);
3898
3951
  const countdownText = formatDuration(countdownMs);
3899
3952
  const prevFont = ctx.font;
@@ -5782,6 +5835,7 @@ function createChart(element, options = {}) {
5782
5835
  fitXViewport();
5783
5836
  }
5784
5837
  }
5838
+ reanchorDrawingsToData();
5785
5839
  const lastPoint = data[data.length - 1];
5786
5840
  if (lastPoint) {
5787
5841
  pushSmoothedTicker(lastPoint.c, lastPoint.v);
@@ -5828,6 +5882,7 @@ function createChart(element, options = {}) {
5828
5882
  }
5829
5883
  merged.set(timeMs, parsed);
5830
5884
  data = Array.from(merged.values()).sort((a, b) => a.time.getTime() - b.time.getTime());
5885
+ reanchorDrawingsToData();
5831
5886
  }
5832
5887
  pushSmoothedTicker(parsed.c, parsed.v);
5833
5888
  scheduleDraw();
@@ -6010,11 +6065,13 @@ function createChart(element, options = {}) {
6010
6065
  const setDrawings = (nextDrawings) => {
6011
6066
  drawings = dedupeDrawingIds(nextDrawings.map((drawing) => normalizeDrawingState(drawing)));
6012
6067
  draftDrawing = null;
6068
+ reanchorDrawingsToData();
6013
6069
  emitDrawingsChange();
6014
6070
  scheduleDraw();
6015
6071
  };
6016
6072
  const addDrawing = (drawing) => {
6017
6073
  const next = normalizeDrawingState(drawing);
6074
+ next.points = next.points.map((point) => reanchorDrawingPoint(point));
6018
6075
  drawings.push(next);
6019
6076
  emitDrawingsChange();
6020
6077
  scheduleDraw();
@@ -6026,7 +6083,7 @@ function createChart(element, options = {}) {
6026
6083
  ...serializeDrawing(drawing),
6027
6084
  ...patch,
6028
6085
  id,
6029
- points: patch.points ?? drawing.points
6086
+ points: (patch.points ?? drawing.points).map((point) => reanchorDrawingPoint(point))
6030
6087
  }) : drawing
6031
6088
  );
6032
6089
  emitDrawingsChange();
@@ -45,6 +45,14 @@ interface ChartOptions {
45
45
  * futures data with sparse back-month eras). 0/undefined = infer.
46
46
  */
47
47
  timeStepMs?: number;
48
+ /**
49
+ * Correction added to the machine's clock for time-sensitive rendering
50
+ * (bar-close countdowns): exchangeNow ≈ Date.now() + clockOffsetMs. Hosts
51
+ * should derive it from exchange-stamped ticks; without it a skewed local
52
+ * clock freezes the countdown at one full interval (bars arrive stamped
53
+ * "in the future") or makes it run visibly early/late.
54
+ */
55
+ clockOffsetMs?: number;
48
56
  pinOutOfRangeLines?: boolean;
49
57
  doubleClickEnabled?: boolean;
50
58
  doubleClickAction?: "reset" | "placeLimitOrder";
@@ -196,6 +196,7 @@ var DEFAULT_OPTIONS = {
196
196
  autoScaleIgnoreLatestCandle: true,
197
197
  kineticScroll: { touch: true, mouse: false },
198
198
  timeStepMs: 0,
199
+ clockOffsetMs: 0,
199
200
  pinOutOfRangeLines: false,
200
201
  doubleClickEnabled: true,
201
202
  doubleClickAction: "reset",
@@ -1337,7 +1338,7 @@ function createChart(element, options = {}) {
1337
1338
  return;
1338
1339
  }
1339
1340
  const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1340
- const speed = clamp(tickerOpts.smoothingSpeed ?? 8, 1, 60);
1341
+ const speed = clamp(tickerOpts.smoothingSpeed ?? 4, 1, 60);
1341
1342
  const dt = 1 / 60;
1342
1343
  const lerp = 1 - Math.exp(-speed * dt);
1343
1344
  smoothedTickerPrice += priceDiff * lerp;
@@ -1774,6 +1775,56 @@ function createChart(element, options = {}) {
1774
1775
  const upperDelta = Math.abs(upperPoint.time.getTime() - timeMs);
1775
1776
  return lowerDelta <= upperDelta ? lower : upper;
1776
1777
  };
1778
+ const indexForTimeMs = (timeMs) => {
1779
+ if (!Number.isFinite(timeMs) || data.length === 0) {
1780
+ return null;
1781
+ }
1782
+ const firstMs = data[0].time.getTime();
1783
+ const lastMs = data[data.length - 1].time.getTime();
1784
+ if (timeMs < firstMs) {
1785
+ const stepMs = getTimeStepMs();
1786
+ return stepMs > 0 ? (timeMs - firstMs) / stepMs : 0;
1787
+ }
1788
+ if (timeMs > lastMs) {
1789
+ const stepMs = getTimeStepMs();
1790
+ return data.length - 1 + (stepMs > 0 ? (timeMs - lastMs) / stepMs : 0);
1791
+ }
1792
+ return findNearestIndexForTimeMs(timeMs);
1793
+ };
1794
+ const reanchorDrawingPoint = (point) => {
1795
+ if (!point.time) {
1796
+ return point;
1797
+ }
1798
+ const timeMs = Date.parse(point.time);
1799
+ if (!Number.isFinite(timeMs)) {
1800
+ return point;
1801
+ }
1802
+ const rounded = Math.round(point.index);
1803
+ if (data[rounded]?.time.getTime() === timeMs) {
1804
+ return point;
1805
+ }
1806
+ const mapped = indexForTimeMs(timeMs);
1807
+ if (mapped === null) {
1808
+ return point;
1809
+ }
1810
+ const fraction = point.index - rounded;
1811
+ return { ...point, index: mapped + fraction };
1812
+ };
1813
+ const reanchorDrawingsToData = () => {
1814
+ if (data.length === 0) {
1815
+ return;
1816
+ }
1817
+ drawings = drawings.map((drawing) => ({
1818
+ ...drawing,
1819
+ points: drawing.points.map((point) => reanchorDrawingPoint(point))
1820
+ }));
1821
+ if (draftDrawing) {
1822
+ draftDrawing = {
1823
+ ...draftDrawing,
1824
+ points: draftDrawing.points.map((point) => reanchorDrawingPoint(point))
1825
+ };
1826
+ }
1827
+ };
1777
1828
  const getCandleDirectionByIndex = (index) => {
1778
1829
  const point = data[index];
1779
1830
  if (!point) {
@@ -3613,7 +3664,8 @@ function createChart(element, options = {}) {
3613
3664
  return null;
3614
3665
  }
3615
3666
  const stepMs = getTimeStepMs();
3616
- const elapsedMs = Date.now() - last.time.getTime();
3667
+ const nowMs = Date.now() + (Number(mergedOptions.clockOffsetMs) || 0);
3668
+ const elapsedMs = nowMs - last.time.getTime();
3617
3669
  const countdownMs = elapsedMs >= 0 ? stepMs - elapsedMs % stepMs : Math.min(stepMs, stepMs - elapsedMs);
3618
3670
  return formatDuration(countdownMs);
3619
3671
  };
@@ -3867,7 +3919,8 @@ function createChart(element, options = {}) {
3867
3919
  }
3868
3920
  if (labels.visible && labels.showCountdownToBarClose && lastPoint) {
3869
3921
  const stepMs = getTimeStepMs();
3870
- const elapsedMs = Date.now() - lastPoint.time.getTime();
3922
+ const nowMs = Date.now() + (Number(mergedOptions.clockOffsetMs) || 0);
3923
+ const elapsedMs = nowMs - lastPoint.time.getTime();
3871
3924
  const countdownMs = elapsedMs >= 0 ? stepMs - elapsedMs % stepMs : Math.min(stepMs, stepMs - elapsedMs);
3872
3925
  const countdownText = formatDuration(countdownMs);
3873
3926
  const prevFont = ctx.font;
@@ -5756,6 +5809,7 @@ function createChart(element, options = {}) {
5756
5809
  fitXViewport();
5757
5810
  }
5758
5811
  }
5812
+ reanchorDrawingsToData();
5759
5813
  const lastPoint = data[data.length - 1];
5760
5814
  if (lastPoint) {
5761
5815
  pushSmoothedTicker(lastPoint.c, lastPoint.v);
@@ -5802,6 +5856,7 @@ function createChart(element, options = {}) {
5802
5856
  }
5803
5857
  merged.set(timeMs, parsed);
5804
5858
  data = Array.from(merged.values()).sort((a, b) => a.time.getTime() - b.time.getTime());
5859
+ reanchorDrawingsToData();
5805
5860
  }
5806
5861
  pushSmoothedTicker(parsed.c, parsed.v);
5807
5862
  scheduleDraw();
@@ -5984,11 +6039,13 @@ function createChart(element, options = {}) {
5984
6039
  const setDrawings = (nextDrawings) => {
5985
6040
  drawings = dedupeDrawingIds(nextDrawings.map((drawing) => normalizeDrawingState(drawing)));
5986
6041
  draftDrawing = null;
6042
+ reanchorDrawingsToData();
5987
6043
  emitDrawingsChange();
5988
6044
  scheduleDraw();
5989
6045
  };
5990
6046
  const addDrawing = (drawing) => {
5991
6047
  const next = normalizeDrawingState(drawing);
6048
+ next.points = next.points.map((point) => reanchorDrawingPoint(point));
5992
6049
  drawings.push(next);
5993
6050
  emitDrawingsChange();
5994
6051
  scheduleDraw();
@@ -6000,7 +6057,7 @@ function createChart(element, options = {}) {
6000
6057
  ...serializeDrawing(drawing),
6001
6058
  ...patch,
6002
6059
  id,
6003
- points: patch.points ?? drawing.points
6060
+ points: (patch.points ?? drawing.points).map((point) => reanchorDrawingPoint(point))
6004
6061
  }) : drawing
6005
6062
  );
6006
6063
  emitDrawingsChange();
package/dist/index.cjs CHANGED
@@ -222,6 +222,7 @@ var DEFAULT_OPTIONS = {
222
222
  autoScaleIgnoreLatestCandle: true,
223
223
  kineticScroll: { touch: true, mouse: false },
224
224
  timeStepMs: 0,
225
+ clockOffsetMs: 0,
225
226
  pinOutOfRangeLines: false,
226
227
  doubleClickEnabled: true,
227
228
  doubleClickAction: "reset",
@@ -1363,7 +1364,7 @@ function createChart(element, options = {}) {
1363
1364
  return;
1364
1365
  }
1365
1366
  const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1366
- const speed = clamp(tickerOpts.smoothingSpeed ?? 8, 1, 60);
1367
+ const speed = clamp(tickerOpts.smoothingSpeed ?? 4, 1, 60);
1367
1368
  const dt = 1 / 60;
1368
1369
  const lerp = 1 - Math.exp(-speed * dt);
1369
1370
  smoothedTickerPrice += priceDiff * lerp;
@@ -1800,6 +1801,56 @@ function createChart(element, options = {}) {
1800
1801
  const upperDelta = Math.abs(upperPoint.time.getTime() - timeMs);
1801
1802
  return lowerDelta <= upperDelta ? lower : upper;
1802
1803
  };
1804
+ const indexForTimeMs = (timeMs) => {
1805
+ if (!Number.isFinite(timeMs) || data.length === 0) {
1806
+ return null;
1807
+ }
1808
+ const firstMs = data[0].time.getTime();
1809
+ const lastMs = data[data.length - 1].time.getTime();
1810
+ if (timeMs < firstMs) {
1811
+ const stepMs = getTimeStepMs();
1812
+ return stepMs > 0 ? (timeMs - firstMs) / stepMs : 0;
1813
+ }
1814
+ if (timeMs > lastMs) {
1815
+ const stepMs = getTimeStepMs();
1816
+ return data.length - 1 + (stepMs > 0 ? (timeMs - lastMs) / stepMs : 0);
1817
+ }
1818
+ return findNearestIndexForTimeMs(timeMs);
1819
+ };
1820
+ const reanchorDrawingPoint = (point) => {
1821
+ if (!point.time) {
1822
+ return point;
1823
+ }
1824
+ const timeMs = Date.parse(point.time);
1825
+ if (!Number.isFinite(timeMs)) {
1826
+ return point;
1827
+ }
1828
+ const rounded = Math.round(point.index);
1829
+ if (data[rounded]?.time.getTime() === timeMs) {
1830
+ return point;
1831
+ }
1832
+ const mapped = indexForTimeMs(timeMs);
1833
+ if (mapped === null) {
1834
+ return point;
1835
+ }
1836
+ const fraction = point.index - rounded;
1837
+ return { ...point, index: mapped + fraction };
1838
+ };
1839
+ const reanchorDrawingsToData = () => {
1840
+ if (data.length === 0) {
1841
+ return;
1842
+ }
1843
+ drawings = drawings.map((drawing) => ({
1844
+ ...drawing,
1845
+ points: drawing.points.map((point) => reanchorDrawingPoint(point))
1846
+ }));
1847
+ if (draftDrawing) {
1848
+ draftDrawing = {
1849
+ ...draftDrawing,
1850
+ points: draftDrawing.points.map((point) => reanchorDrawingPoint(point))
1851
+ };
1852
+ }
1853
+ };
1803
1854
  const getCandleDirectionByIndex = (index) => {
1804
1855
  const point = data[index];
1805
1856
  if (!point) {
@@ -3639,7 +3690,8 @@ function createChart(element, options = {}) {
3639
3690
  return null;
3640
3691
  }
3641
3692
  const stepMs = getTimeStepMs();
3642
- const elapsedMs = Date.now() - last.time.getTime();
3693
+ const nowMs = Date.now() + (Number(mergedOptions.clockOffsetMs) || 0);
3694
+ const elapsedMs = nowMs - last.time.getTime();
3643
3695
  const countdownMs = elapsedMs >= 0 ? stepMs - elapsedMs % stepMs : Math.min(stepMs, stepMs - elapsedMs);
3644
3696
  return formatDuration(countdownMs);
3645
3697
  };
@@ -3893,7 +3945,8 @@ function createChart(element, options = {}) {
3893
3945
  }
3894
3946
  if (labels.visible && labels.showCountdownToBarClose && lastPoint) {
3895
3947
  const stepMs = getTimeStepMs();
3896
- const elapsedMs = Date.now() - lastPoint.time.getTime();
3948
+ const nowMs = Date.now() + (Number(mergedOptions.clockOffsetMs) || 0);
3949
+ const elapsedMs = nowMs - lastPoint.time.getTime();
3897
3950
  const countdownMs = elapsedMs >= 0 ? stepMs - elapsedMs % stepMs : Math.min(stepMs, stepMs - elapsedMs);
3898
3951
  const countdownText = formatDuration(countdownMs);
3899
3952
  const prevFont = ctx.font;
@@ -5782,6 +5835,7 @@ function createChart(element, options = {}) {
5782
5835
  fitXViewport();
5783
5836
  }
5784
5837
  }
5838
+ reanchorDrawingsToData();
5785
5839
  const lastPoint = data[data.length - 1];
5786
5840
  if (lastPoint) {
5787
5841
  pushSmoothedTicker(lastPoint.c, lastPoint.v);
@@ -5828,6 +5882,7 @@ function createChart(element, options = {}) {
5828
5882
  }
5829
5883
  merged.set(timeMs, parsed);
5830
5884
  data = Array.from(merged.values()).sort((a, b) => a.time.getTime() - b.time.getTime());
5885
+ reanchorDrawingsToData();
5831
5886
  }
5832
5887
  pushSmoothedTicker(parsed.c, parsed.v);
5833
5888
  scheduleDraw();
@@ -6010,11 +6065,13 @@ function createChart(element, options = {}) {
6010
6065
  const setDrawings = (nextDrawings) => {
6011
6066
  drawings = dedupeDrawingIds(nextDrawings.map((drawing) => normalizeDrawingState(drawing)));
6012
6067
  draftDrawing = null;
6068
+ reanchorDrawingsToData();
6013
6069
  emitDrawingsChange();
6014
6070
  scheduleDraw();
6015
6071
  };
6016
6072
  const addDrawing = (drawing) => {
6017
6073
  const next = normalizeDrawingState(drawing);
6074
+ next.points = next.points.map((point) => reanchorDrawingPoint(point));
6018
6075
  drawings.push(next);
6019
6076
  emitDrawingsChange();
6020
6077
  scheduleDraw();
@@ -6026,7 +6083,7 @@ function createChart(element, options = {}) {
6026
6083
  ...serializeDrawing(drawing),
6027
6084
  ...patch,
6028
6085
  id,
6029
- points: patch.points ?? drawing.points
6086
+ points: (patch.points ?? drawing.points).map((point) => reanchorDrawingPoint(point))
6030
6087
  }) : drawing
6031
6088
  );
6032
6089
  emitDrawingsChange();
package/dist/index.d.cts CHANGED
@@ -45,6 +45,14 @@ interface ChartOptions {
45
45
  * futures data with sparse back-month eras). 0/undefined = infer.
46
46
  */
47
47
  timeStepMs?: number;
48
+ /**
49
+ * Correction added to the machine's clock for time-sensitive rendering
50
+ * (bar-close countdowns): exchangeNow ≈ Date.now() + clockOffsetMs. Hosts
51
+ * should derive it from exchange-stamped ticks; without it a skewed local
52
+ * clock freezes the countdown at one full interval (bars arrive stamped
53
+ * "in the future") or makes it run visibly early/late.
54
+ */
55
+ clockOffsetMs?: number;
48
56
  pinOutOfRangeLines?: boolean;
49
57
  doubleClickEnabled?: boolean;
50
58
  doubleClickAction?: "reset" | "placeLimitOrder";
package/dist/index.d.ts CHANGED
@@ -45,6 +45,14 @@ interface ChartOptions {
45
45
  * futures data with sparse back-month eras). 0/undefined = infer.
46
46
  */
47
47
  timeStepMs?: number;
48
+ /**
49
+ * Correction added to the machine's clock for time-sensitive rendering
50
+ * (bar-close countdowns): exchangeNow ≈ Date.now() + clockOffsetMs. Hosts
51
+ * should derive it from exchange-stamped ticks; without it a skewed local
52
+ * clock freezes the countdown at one full interval (bars arrive stamped
53
+ * "in the future") or makes it run visibly early/late.
54
+ */
55
+ clockOffsetMs?: number;
48
56
  pinOutOfRangeLines?: boolean;
49
57
  doubleClickEnabled?: boolean;
50
58
  doubleClickAction?: "reset" | "placeLimitOrder";
package/dist/index.js CHANGED
@@ -196,6 +196,7 @@ var DEFAULT_OPTIONS = {
196
196
  autoScaleIgnoreLatestCandle: true,
197
197
  kineticScroll: { touch: true, mouse: false },
198
198
  timeStepMs: 0,
199
+ clockOffsetMs: 0,
199
200
  pinOutOfRangeLines: false,
200
201
  doubleClickEnabled: true,
201
202
  doubleClickAction: "reset",
@@ -1337,7 +1338,7 @@ function createChart(element, options = {}) {
1337
1338
  return;
1338
1339
  }
1339
1340
  const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1340
- const speed = clamp(tickerOpts.smoothingSpeed ?? 8, 1, 60);
1341
+ const speed = clamp(tickerOpts.smoothingSpeed ?? 4, 1, 60);
1341
1342
  const dt = 1 / 60;
1342
1343
  const lerp = 1 - Math.exp(-speed * dt);
1343
1344
  smoothedTickerPrice += priceDiff * lerp;
@@ -1774,6 +1775,56 @@ function createChart(element, options = {}) {
1774
1775
  const upperDelta = Math.abs(upperPoint.time.getTime() - timeMs);
1775
1776
  return lowerDelta <= upperDelta ? lower : upper;
1776
1777
  };
1778
+ const indexForTimeMs = (timeMs) => {
1779
+ if (!Number.isFinite(timeMs) || data.length === 0) {
1780
+ return null;
1781
+ }
1782
+ const firstMs = data[0].time.getTime();
1783
+ const lastMs = data[data.length - 1].time.getTime();
1784
+ if (timeMs < firstMs) {
1785
+ const stepMs = getTimeStepMs();
1786
+ return stepMs > 0 ? (timeMs - firstMs) / stepMs : 0;
1787
+ }
1788
+ if (timeMs > lastMs) {
1789
+ const stepMs = getTimeStepMs();
1790
+ return data.length - 1 + (stepMs > 0 ? (timeMs - lastMs) / stepMs : 0);
1791
+ }
1792
+ return findNearestIndexForTimeMs(timeMs);
1793
+ };
1794
+ const reanchorDrawingPoint = (point) => {
1795
+ if (!point.time) {
1796
+ return point;
1797
+ }
1798
+ const timeMs = Date.parse(point.time);
1799
+ if (!Number.isFinite(timeMs)) {
1800
+ return point;
1801
+ }
1802
+ const rounded = Math.round(point.index);
1803
+ if (data[rounded]?.time.getTime() === timeMs) {
1804
+ return point;
1805
+ }
1806
+ const mapped = indexForTimeMs(timeMs);
1807
+ if (mapped === null) {
1808
+ return point;
1809
+ }
1810
+ const fraction = point.index - rounded;
1811
+ return { ...point, index: mapped + fraction };
1812
+ };
1813
+ const reanchorDrawingsToData = () => {
1814
+ if (data.length === 0) {
1815
+ return;
1816
+ }
1817
+ drawings = drawings.map((drawing) => ({
1818
+ ...drawing,
1819
+ points: drawing.points.map((point) => reanchorDrawingPoint(point))
1820
+ }));
1821
+ if (draftDrawing) {
1822
+ draftDrawing = {
1823
+ ...draftDrawing,
1824
+ points: draftDrawing.points.map((point) => reanchorDrawingPoint(point))
1825
+ };
1826
+ }
1827
+ };
1777
1828
  const getCandleDirectionByIndex = (index) => {
1778
1829
  const point = data[index];
1779
1830
  if (!point) {
@@ -3613,7 +3664,8 @@ function createChart(element, options = {}) {
3613
3664
  return null;
3614
3665
  }
3615
3666
  const stepMs = getTimeStepMs();
3616
- const elapsedMs = Date.now() - last.time.getTime();
3667
+ const nowMs = Date.now() + (Number(mergedOptions.clockOffsetMs) || 0);
3668
+ const elapsedMs = nowMs - last.time.getTime();
3617
3669
  const countdownMs = elapsedMs >= 0 ? stepMs - elapsedMs % stepMs : Math.min(stepMs, stepMs - elapsedMs);
3618
3670
  return formatDuration(countdownMs);
3619
3671
  };
@@ -3867,7 +3919,8 @@ function createChart(element, options = {}) {
3867
3919
  }
3868
3920
  if (labels.visible && labels.showCountdownToBarClose && lastPoint) {
3869
3921
  const stepMs = getTimeStepMs();
3870
- const elapsedMs = Date.now() - lastPoint.time.getTime();
3922
+ const nowMs = Date.now() + (Number(mergedOptions.clockOffsetMs) || 0);
3923
+ const elapsedMs = nowMs - lastPoint.time.getTime();
3871
3924
  const countdownMs = elapsedMs >= 0 ? stepMs - elapsedMs % stepMs : Math.min(stepMs, stepMs - elapsedMs);
3872
3925
  const countdownText = formatDuration(countdownMs);
3873
3926
  const prevFont = ctx.font;
@@ -5756,6 +5809,7 @@ function createChart(element, options = {}) {
5756
5809
  fitXViewport();
5757
5810
  }
5758
5811
  }
5812
+ reanchorDrawingsToData();
5759
5813
  const lastPoint = data[data.length - 1];
5760
5814
  if (lastPoint) {
5761
5815
  pushSmoothedTicker(lastPoint.c, lastPoint.v);
@@ -5802,6 +5856,7 @@ function createChart(element, options = {}) {
5802
5856
  }
5803
5857
  merged.set(timeMs, parsed);
5804
5858
  data = Array.from(merged.values()).sort((a, b) => a.time.getTime() - b.time.getTime());
5859
+ reanchorDrawingsToData();
5805
5860
  }
5806
5861
  pushSmoothedTicker(parsed.c, parsed.v);
5807
5862
  scheduleDraw();
@@ -5984,11 +6039,13 @@ function createChart(element, options = {}) {
5984
6039
  const setDrawings = (nextDrawings) => {
5985
6040
  drawings = dedupeDrawingIds(nextDrawings.map((drawing) => normalizeDrawingState(drawing)));
5986
6041
  draftDrawing = null;
6042
+ reanchorDrawingsToData();
5987
6043
  emitDrawingsChange();
5988
6044
  scheduleDraw();
5989
6045
  };
5990
6046
  const addDrawing = (drawing) => {
5991
6047
  const next = normalizeDrawingState(drawing);
6048
+ next.points = next.points.map((point) => reanchorDrawingPoint(point));
5992
6049
  drawings.push(next);
5993
6050
  emitDrawingsChange();
5994
6051
  scheduleDraw();
@@ -6000,7 +6057,7 @@ function createChart(element, options = {}) {
6000
6057
  ...serializeDrawing(drawing),
6001
6058
  ...patch,
6002
6059
  id,
6003
- points: patch.points ?? drawing.points
6060
+ points: (patch.points ?? drawing.points).map((point) => reanchorDrawingPoint(point))
6004
6061
  }) : drawing
6005
6062
  );
6006
6063
  emitDrawingsChange();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.122",
3
+ "version": "0.1.124",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",