hyperprop-charting-library 0.1.34 → 0.1.36

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.
@@ -171,8 +171,6 @@ var DEFAULT_OPTIONS = {
171
171
  visible: true,
172
172
  style: "dotted",
173
173
  thickness: 1,
174
- color: "#38bdf8",
175
- labelBackgroundColor: "#38bdf8",
176
174
  labelTextColor: "#0b1220",
177
175
  labelBorderRadius: 3
178
176
  },
@@ -852,6 +850,41 @@ function createChart(element, options = {}) {
852
850
  let crosshairPoint = null;
853
851
  let doubleClickEnabled = mergedOptions.doubleClickEnabled;
854
852
  let doubleClickAction = mergedOptions.doubleClickAction;
853
+ let smoothedTickerPrice = null;
854
+ let tickerPriceTarget = null;
855
+ let smoothingRafId = null;
856
+ const tickerSmoothingLoop = () => {
857
+ smoothingRafId = null;
858
+ if (smoothedTickerPrice === null || tickerPriceTarget === null) return;
859
+ const diff = tickerPriceTarget - smoothedTickerPrice;
860
+ if (Math.abs(diff) < 1e-9) {
861
+ smoothedTickerPrice = tickerPriceTarget;
862
+ draw();
863
+ return;
864
+ }
865
+ const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
866
+ const speed = clamp(tickerOpts.smoothingSpeed ?? 8, 1, 60);
867
+ const dt = 1 / 60;
868
+ const lerp = 1 - Math.exp(-speed * dt);
869
+ smoothedTickerPrice += diff * lerp;
870
+ draw();
871
+ smoothingRafId = requestAnimationFrame(tickerSmoothingLoop);
872
+ };
873
+ const pushSmoothedPrice = (target) => {
874
+ const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
875
+ if (!tickerOpts.smoothing) {
876
+ smoothedTickerPrice = null;
877
+ tickerPriceTarget = null;
878
+ return;
879
+ }
880
+ tickerPriceTarget = target;
881
+ if (smoothedTickerPrice === null) {
882
+ smoothedTickerPrice = target;
883
+ }
884
+ if (smoothingRafId === null) {
885
+ smoothingRafId = requestAnimationFrame(tickerSmoothingLoop);
886
+ }
887
+ };
855
888
  const canvas = document.createElement("canvas");
856
889
  const ctx = canvas.getContext("2d");
857
890
  if (!ctx) {
@@ -1812,28 +1845,37 @@ function createChart(element, options = {}) {
1812
1845
  }
1813
1846
  ctx.restore();
1814
1847
  }
1848
+ const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1849
+ const useSmoothedCandle = tickerOpts.smoothing && smoothedTickerPrice !== null;
1850
+ const lastDataIndex = data.length - 1;
1815
1851
  for (let index = startIndex; index <= endIndex; index += 1) {
1816
1852
  const point = data[index];
1817
1853
  if (!point) {
1818
1854
  continue;
1819
1855
  }
1856
+ const isLastCandle = useSmoothedCandle && index === lastDataIndex;
1857
+ const displayClose = isLastCandle ? smoothedTickerPrice : point.c;
1858
+ const displayHigh = isLastCandle ? Math.max(point.h, smoothedTickerPrice) : point.h;
1859
+ const displayLow = isLastCandle ? Math.min(point.l, smoothedTickerPrice) : point.l;
1820
1860
  const centerX = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
1821
1861
  const openY = yFromPrice(point.o);
1822
- const closeY = yFromPrice(point.c);
1823
- const highY = yFromPrice(point.h);
1824
- const lowY = yFromPrice(point.l);
1825
- const direction = getCandleDirectionByIndex(index);
1862
+ const closeY = yFromPrice(displayClose);
1863
+ const highY = yFromPrice(displayHigh);
1864
+ const lowY = yFromPrice(displayLow);
1865
+ const direction = isLastCandle ? displayClose >= point.o ? "up" : "down" : getCandleDirectionByIndex(index);
1826
1866
  const candleColor = direction === "up" ? mergedOptions.upColor : mergedOptions.downColor;
1867
+ const roundedCenterX = Math.round(centerX);
1827
1868
  ctx.strokeStyle = candleColor;
1828
1869
  ctx.lineWidth = candleWickWidth;
1829
1870
  ctx.beginPath();
1830
- ctx.moveTo(crisp(centerX), crisp(highY));
1831
- ctx.lineTo(crisp(centerX), crisp(lowY));
1871
+ ctx.moveTo(roundedCenterX + 0.5, crisp(highY));
1872
+ ctx.lineTo(roundedCenterX + 0.5, crisp(lowY));
1832
1873
  ctx.stroke();
1874
+ const bodyLeft = roundedCenterX - Math.floor(bodyWidth / 2);
1833
1875
  const bodyTop = Math.min(openY, closeY);
1834
1876
  const bodyHeight = Math.max(1, Math.abs(closeY - openY));
1835
1877
  ctx.fillStyle = candleColor;
1836
- ctx.fillRect(Math.round(centerX - bodyWidth / 2), Math.round(bodyTop), bodyWidth, Math.max(1, Math.round(bodyHeight)));
1878
+ ctx.fillRect(bodyLeft, Math.round(bodyTop), bodyWidth, Math.max(1, Math.round(bodyHeight)));
1837
1879
  }
1838
1880
  const activeOverlayIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
1839
1881
  (value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "overlay"
@@ -1967,10 +2009,10 @@ function createChart(element, options = {}) {
1967
2009
  const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1968
2010
  const lastPoint = data[data.length - 1];
1969
2011
  if ((ticker.visible ?? true) && lastPoint) {
1970
- const tickerPrice = lastPoint.c;
2012
+ const tickerPrice = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint.c;
1971
2013
  const tickerY = yFromPrice(tickerPrice);
1972
2014
  const lineY = clamp(tickerY, chartTop + 1, chartBottom - 1);
1973
- const lastDirection = getCandleDirectionByIndex(data.length - 1);
2015
+ const lastDirection = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice >= lastPoint.o ? "up" : "down" : getCandleDirectionByIndex(data.length - 1);
1974
2016
  const tickerColor = ticker.color ?? (lastDirection === "up" ? mergedOptions.upColor : mergedOptions.downColor);
1975
2017
  const tickerThickness = Math.max(1, ticker.thickness ?? 1);
1976
2018
  const tickerStyle = ticker.style ?? "solid";
@@ -2802,6 +2844,10 @@ function createChart(element, options = {}) {
2802
2844
  fitXViewport();
2803
2845
  }
2804
2846
  }
2847
+ const lastClose = data.length > 0 ? data[data.length - 1].c : null;
2848
+ if (lastClose !== null) {
2849
+ pushSmoothedPrice(lastClose);
2850
+ }
2805
2851
  draw();
2806
2852
  };
2807
2853
  const setPriceLines = (lines) => {
@@ -2952,6 +2998,10 @@ function createChart(element, options = {}) {
2952
2998
  draw();
2953
2999
  };
2954
3000
  const destroy = () => {
3001
+ if (smoothingRafId !== null) {
3002
+ cancelAnimationFrame(smoothingRafId);
3003
+ smoothingRafId = null;
3004
+ }
2955
3005
  canvas.removeEventListener("pointerdown", onPointerDown);
2956
3006
  canvas.removeEventListener("pointermove", onPointerMove);
2957
3007
  canvas.removeEventListener("pointerup", endPointerDrag);
@@ -259,6 +259,8 @@ interface TickerLineOptions {
259
259
  labelBackgroundColor?: string;
260
260
  labelTextColor?: string;
261
261
  labelBorderRadius?: number;
262
+ smoothing?: boolean;
263
+ smoothingSpeed?: number;
262
264
  }
263
265
  interface ChartInstance {
264
266
  setData: (data: OhlcDataPoint[]) => void;
@@ -147,8 +147,6 @@ var DEFAULT_OPTIONS = {
147
147
  visible: true,
148
148
  style: "dotted",
149
149
  thickness: 1,
150
- color: "#38bdf8",
151
- labelBackgroundColor: "#38bdf8",
152
150
  labelTextColor: "#0b1220",
153
151
  labelBorderRadius: 3
154
152
  },
@@ -828,6 +826,41 @@ function createChart(element, options = {}) {
828
826
  let crosshairPoint = null;
829
827
  let doubleClickEnabled = mergedOptions.doubleClickEnabled;
830
828
  let doubleClickAction = mergedOptions.doubleClickAction;
829
+ let smoothedTickerPrice = null;
830
+ let tickerPriceTarget = null;
831
+ let smoothingRafId = null;
832
+ const tickerSmoothingLoop = () => {
833
+ smoothingRafId = null;
834
+ if (smoothedTickerPrice === null || tickerPriceTarget === null) return;
835
+ const diff = tickerPriceTarget - smoothedTickerPrice;
836
+ if (Math.abs(diff) < 1e-9) {
837
+ smoothedTickerPrice = tickerPriceTarget;
838
+ draw();
839
+ return;
840
+ }
841
+ const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
842
+ const speed = clamp(tickerOpts.smoothingSpeed ?? 8, 1, 60);
843
+ const dt = 1 / 60;
844
+ const lerp = 1 - Math.exp(-speed * dt);
845
+ smoothedTickerPrice += diff * lerp;
846
+ draw();
847
+ smoothingRafId = requestAnimationFrame(tickerSmoothingLoop);
848
+ };
849
+ const pushSmoothedPrice = (target) => {
850
+ const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
851
+ if (!tickerOpts.smoothing) {
852
+ smoothedTickerPrice = null;
853
+ tickerPriceTarget = null;
854
+ return;
855
+ }
856
+ tickerPriceTarget = target;
857
+ if (smoothedTickerPrice === null) {
858
+ smoothedTickerPrice = target;
859
+ }
860
+ if (smoothingRafId === null) {
861
+ smoothingRafId = requestAnimationFrame(tickerSmoothingLoop);
862
+ }
863
+ };
831
864
  const canvas = document.createElement("canvas");
832
865
  const ctx = canvas.getContext("2d");
833
866
  if (!ctx) {
@@ -1788,28 +1821,37 @@ function createChart(element, options = {}) {
1788
1821
  }
1789
1822
  ctx.restore();
1790
1823
  }
1824
+ const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1825
+ const useSmoothedCandle = tickerOpts.smoothing && smoothedTickerPrice !== null;
1826
+ const lastDataIndex = data.length - 1;
1791
1827
  for (let index = startIndex; index <= endIndex; index += 1) {
1792
1828
  const point = data[index];
1793
1829
  if (!point) {
1794
1830
  continue;
1795
1831
  }
1832
+ const isLastCandle = useSmoothedCandle && index === lastDataIndex;
1833
+ const displayClose = isLastCandle ? smoothedTickerPrice : point.c;
1834
+ const displayHigh = isLastCandle ? Math.max(point.h, smoothedTickerPrice) : point.h;
1835
+ const displayLow = isLastCandle ? Math.min(point.l, smoothedTickerPrice) : point.l;
1796
1836
  const centerX = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
1797
1837
  const openY = yFromPrice(point.o);
1798
- const closeY = yFromPrice(point.c);
1799
- const highY = yFromPrice(point.h);
1800
- const lowY = yFromPrice(point.l);
1801
- const direction = getCandleDirectionByIndex(index);
1838
+ const closeY = yFromPrice(displayClose);
1839
+ const highY = yFromPrice(displayHigh);
1840
+ const lowY = yFromPrice(displayLow);
1841
+ const direction = isLastCandle ? displayClose >= point.o ? "up" : "down" : getCandleDirectionByIndex(index);
1802
1842
  const candleColor = direction === "up" ? mergedOptions.upColor : mergedOptions.downColor;
1843
+ const roundedCenterX = Math.round(centerX);
1803
1844
  ctx.strokeStyle = candleColor;
1804
1845
  ctx.lineWidth = candleWickWidth;
1805
1846
  ctx.beginPath();
1806
- ctx.moveTo(crisp(centerX), crisp(highY));
1807
- ctx.lineTo(crisp(centerX), crisp(lowY));
1847
+ ctx.moveTo(roundedCenterX + 0.5, crisp(highY));
1848
+ ctx.lineTo(roundedCenterX + 0.5, crisp(lowY));
1808
1849
  ctx.stroke();
1850
+ const bodyLeft = roundedCenterX - Math.floor(bodyWidth / 2);
1809
1851
  const bodyTop = Math.min(openY, closeY);
1810
1852
  const bodyHeight = Math.max(1, Math.abs(closeY - openY));
1811
1853
  ctx.fillStyle = candleColor;
1812
- ctx.fillRect(Math.round(centerX - bodyWidth / 2), Math.round(bodyTop), bodyWidth, Math.max(1, Math.round(bodyHeight)));
1854
+ ctx.fillRect(bodyLeft, Math.round(bodyTop), bodyWidth, Math.max(1, Math.round(bodyHeight)));
1813
1855
  }
1814
1856
  const activeOverlayIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
1815
1857
  (value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "overlay"
@@ -1943,10 +1985,10 @@ function createChart(element, options = {}) {
1943
1985
  const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1944
1986
  const lastPoint = data[data.length - 1];
1945
1987
  if ((ticker.visible ?? true) && lastPoint) {
1946
- const tickerPrice = lastPoint.c;
1988
+ const tickerPrice = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint.c;
1947
1989
  const tickerY = yFromPrice(tickerPrice);
1948
1990
  const lineY = clamp(tickerY, chartTop + 1, chartBottom - 1);
1949
- const lastDirection = getCandleDirectionByIndex(data.length - 1);
1991
+ const lastDirection = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice >= lastPoint.o ? "up" : "down" : getCandleDirectionByIndex(data.length - 1);
1950
1992
  const tickerColor = ticker.color ?? (lastDirection === "up" ? mergedOptions.upColor : mergedOptions.downColor);
1951
1993
  const tickerThickness = Math.max(1, ticker.thickness ?? 1);
1952
1994
  const tickerStyle = ticker.style ?? "solid";
@@ -2778,6 +2820,10 @@ function createChart(element, options = {}) {
2778
2820
  fitXViewport();
2779
2821
  }
2780
2822
  }
2823
+ const lastClose = data.length > 0 ? data[data.length - 1].c : null;
2824
+ if (lastClose !== null) {
2825
+ pushSmoothedPrice(lastClose);
2826
+ }
2781
2827
  draw();
2782
2828
  };
2783
2829
  const setPriceLines = (lines) => {
@@ -2928,6 +2974,10 @@ function createChart(element, options = {}) {
2928
2974
  draw();
2929
2975
  };
2930
2976
  const destroy = () => {
2977
+ if (smoothingRafId !== null) {
2978
+ cancelAnimationFrame(smoothingRafId);
2979
+ smoothingRafId = null;
2980
+ }
2931
2981
  canvas.removeEventListener("pointerdown", onPointerDown);
2932
2982
  canvas.removeEventListener("pointermove", onPointerMove);
2933
2983
  canvas.removeEventListener("pointerup", endPointerDrag);
package/dist/index.cjs CHANGED
@@ -171,8 +171,6 @@ var DEFAULT_OPTIONS = {
171
171
  visible: true,
172
172
  style: "dotted",
173
173
  thickness: 1,
174
- color: "#38bdf8",
175
- labelBackgroundColor: "#38bdf8",
176
174
  labelTextColor: "#0b1220",
177
175
  labelBorderRadius: 3
178
176
  },
@@ -852,6 +850,41 @@ function createChart(element, options = {}) {
852
850
  let crosshairPoint = null;
853
851
  let doubleClickEnabled = mergedOptions.doubleClickEnabled;
854
852
  let doubleClickAction = mergedOptions.doubleClickAction;
853
+ let smoothedTickerPrice = null;
854
+ let tickerPriceTarget = null;
855
+ let smoothingRafId = null;
856
+ const tickerSmoothingLoop = () => {
857
+ smoothingRafId = null;
858
+ if (smoothedTickerPrice === null || tickerPriceTarget === null) return;
859
+ const diff = tickerPriceTarget - smoothedTickerPrice;
860
+ if (Math.abs(diff) < 1e-9) {
861
+ smoothedTickerPrice = tickerPriceTarget;
862
+ draw();
863
+ return;
864
+ }
865
+ const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
866
+ const speed = clamp(tickerOpts.smoothingSpeed ?? 8, 1, 60);
867
+ const dt = 1 / 60;
868
+ const lerp = 1 - Math.exp(-speed * dt);
869
+ smoothedTickerPrice += diff * lerp;
870
+ draw();
871
+ smoothingRafId = requestAnimationFrame(tickerSmoothingLoop);
872
+ };
873
+ const pushSmoothedPrice = (target) => {
874
+ const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
875
+ if (!tickerOpts.smoothing) {
876
+ smoothedTickerPrice = null;
877
+ tickerPriceTarget = null;
878
+ return;
879
+ }
880
+ tickerPriceTarget = target;
881
+ if (smoothedTickerPrice === null) {
882
+ smoothedTickerPrice = target;
883
+ }
884
+ if (smoothingRafId === null) {
885
+ smoothingRafId = requestAnimationFrame(tickerSmoothingLoop);
886
+ }
887
+ };
855
888
  const canvas = document.createElement("canvas");
856
889
  const ctx = canvas.getContext("2d");
857
890
  if (!ctx) {
@@ -1812,28 +1845,37 @@ function createChart(element, options = {}) {
1812
1845
  }
1813
1846
  ctx.restore();
1814
1847
  }
1848
+ const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1849
+ const useSmoothedCandle = tickerOpts.smoothing && smoothedTickerPrice !== null;
1850
+ const lastDataIndex = data.length - 1;
1815
1851
  for (let index = startIndex; index <= endIndex; index += 1) {
1816
1852
  const point = data[index];
1817
1853
  if (!point) {
1818
1854
  continue;
1819
1855
  }
1856
+ const isLastCandle = useSmoothedCandle && index === lastDataIndex;
1857
+ const displayClose = isLastCandle ? smoothedTickerPrice : point.c;
1858
+ const displayHigh = isLastCandle ? Math.max(point.h, smoothedTickerPrice) : point.h;
1859
+ const displayLow = isLastCandle ? Math.min(point.l, smoothedTickerPrice) : point.l;
1820
1860
  const centerX = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
1821
1861
  const openY = yFromPrice(point.o);
1822
- const closeY = yFromPrice(point.c);
1823
- const highY = yFromPrice(point.h);
1824
- const lowY = yFromPrice(point.l);
1825
- const direction = getCandleDirectionByIndex(index);
1862
+ const closeY = yFromPrice(displayClose);
1863
+ const highY = yFromPrice(displayHigh);
1864
+ const lowY = yFromPrice(displayLow);
1865
+ const direction = isLastCandle ? displayClose >= point.o ? "up" : "down" : getCandleDirectionByIndex(index);
1826
1866
  const candleColor = direction === "up" ? mergedOptions.upColor : mergedOptions.downColor;
1867
+ const roundedCenterX = Math.round(centerX);
1827
1868
  ctx.strokeStyle = candleColor;
1828
1869
  ctx.lineWidth = candleWickWidth;
1829
1870
  ctx.beginPath();
1830
- ctx.moveTo(crisp(centerX), crisp(highY));
1831
- ctx.lineTo(crisp(centerX), crisp(lowY));
1871
+ ctx.moveTo(roundedCenterX + 0.5, crisp(highY));
1872
+ ctx.lineTo(roundedCenterX + 0.5, crisp(lowY));
1832
1873
  ctx.stroke();
1874
+ const bodyLeft = roundedCenterX - Math.floor(bodyWidth / 2);
1833
1875
  const bodyTop = Math.min(openY, closeY);
1834
1876
  const bodyHeight = Math.max(1, Math.abs(closeY - openY));
1835
1877
  ctx.fillStyle = candleColor;
1836
- ctx.fillRect(Math.round(centerX - bodyWidth / 2), Math.round(bodyTop), bodyWidth, Math.max(1, Math.round(bodyHeight)));
1878
+ ctx.fillRect(bodyLeft, Math.round(bodyTop), bodyWidth, Math.max(1, Math.round(bodyHeight)));
1837
1879
  }
1838
1880
  const activeOverlayIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
1839
1881
  (value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "overlay"
@@ -1967,10 +2009,10 @@ function createChart(element, options = {}) {
1967
2009
  const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1968
2010
  const lastPoint = data[data.length - 1];
1969
2011
  if ((ticker.visible ?? true) && lastPoint) {
1970
- const tickerPrice = lastPoint.c;
2012
+ const tickerPrice = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint.c;
1971
2013
  const tickerY = yFromPrice(tickerPrice);
1972
2014
  const lineY = clamp(tickerY, chartTop + 1, chartBottom - 1);
1973
- const lastDirection = getCandleDirectionByIndex(data.length - 1);
2015
+ const lastDirection = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice >= lastPoint.o ? "up" : "down" : getCandleDirectionByIndex(data.length - 1);
1974
2016
  const tickerColor = ticker.color ?? (lastDirection === "up" ? mergedOptions.upColor : mergedOptions.downColor);
1975
2017
  const tickerThickness = Math.max(1, ticker.thickness ?? 1);
1976
2018
  const tickerStyle = ticker.style ?? "solid";
@@ -2802,6 +2844,10 @@ function createChart(element, options = {}) {
2802
2844
  fitXViewport();
2803
2845
  }
2804
2846
  }
2847
+ const lastClose = data.length > 0 ? data[data.length - 1].c : null;
2848
+ if (lastClose !== null) {
2849
+ pushSmoothedPrice(lastClose);
2850
+ }
2805
2851
  draw();
2806
2852
  };
2807
2853
  const setPriceLines = (lines) => {
@@ -2952,6 +2998,10 @@ function createChart(element, options = {}) {
2952
2998
  draw();
2953
2999
  };
2954
3000
  const destroy = () => {
3001
+ if (smoothingRafId !== null) {
3002
+ cancelAnimationFrame(smoothingRafId);
3003
+ smoothingRafId = null;
3004
+ }
2955
3005
  canvas.removeEventListener("pointerdown", onPointerDown);
2956
3006
  canvas.removeEventListener("pointermove", onPointerMove);
2957
3007
  canvas.removeEventListener("pointerup", endPointerDrag);
package/dist/index.d.cts CHANGED
@@ -259,6 +259,8 @@ interface TickerLineOptions {
259
259
  labelBackgroundColor?: string;
260
260
  labelTextColor?: string;
261
261
  labelBorderRadius?: number;
262
+ smoothing?: boolean;
263
+ smoothingSpeed?: number;
262
264
  }
263
265
  interface ChartInstance {
264
266
  setData: (data: OhlcDataPoint[]) => void;
package/dist/index.d.ts CHANGED
@@ -259,6 +259,8 @@ interface TickerLineOptions {
259
259
  labelBackgroundColor?: string;
260
260
  labelTextColor?: string;
261
261
  labelBorderRadius?: number;
262
+ smoothing?: boolean;
263
+ smoothingSpeed?: number;
262
264
  }
263
265
  interface ChartInstance {
264
266
  setData: (data: OhlcDataPoint[]) => void;
package/dist/index.js CHANGED
@@ -147,8 +147,6 @@ var DEFAULT_OPTIONS = {
147
147
  visible: true,
148
148
  style: "dotted",
149
149
  thickness: 1,
150
- color: "#38bdf8",
151
- labelBackgroundColor: "#38bdf8",
152
150
  labelTextColor: "#0b1220",
153
151
  labelBorderRadius: 3
154
152
  },
@@ -828,6 +826,41 @@ function createChart(element, options = {}) {
828
826
  let crosshairPoint = null;
829
827
  let doubleClickEnabled = mergedOptions.doubleClickEnabled;
830
828
  let doubleClickAction = mergedOptions.doubleClickAction;
829
+ let smoothedTickerPrice = null;
830
+ let tickerPriceTarget = null;
831
+ let smoothingRafId = null;
832
+ const tickerSmoothingLoop = () => {
833
+ smoothingRafId = null;
834
+ if (smoothedTickerPrice === null || tickerPriceTarget === null) return;
835
+ const diff = tickerPriceTarget - smoothedTickerPrice;
836
+ if (Math.abs(diff) < 1e-9) {
837
+ smoothedTickerPrice = tickerPriceTarget;
838
+ draw();
839
+ return;
840
+ }
841
+ const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
842
+ const speed = clamp(tickerOpts.smoothingSpeed ?? 8, 1, 60);
843
+ const dt = 1 / 60;
844
+ const lerp = 1 - Math.exp(-speed * dt);
845
+ smoothedTickerPrice += diff * lerp;
846
+ draw();
847
+ smoothingRafId = requestAnimationFrame(tickerSmoothingLoop);
848
+ };
849
+ const pushSmoothedPrice = (target) => {
850
+ const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
851
+ if (!tickerOpts.smoothing) {
852
+ smoothedTickerPrice = null;
853
+ tickerPriceTarget = null;
854
+ return;
855
+ }
856
+ tickerPriceTarget = target;
857
+ if (smoothedTickerPrice === null) {
858
+ smoothedTickerPrice = target;
859
+ }
860
+ if (smoothingRafId === null) {
861
+ smoothingRafId = requestAnimationFrame(tickerSmoothingLoop);
862
+ }
863
+ };
831
864
  const canvas = document.createElement("canvas");
832
865
  const ctx = canvas.getContext("2d");
833
866
  if (!ctx) {
@@ -1788,28 +1821,37 @@ function createChart(element, options = {}) {
1788
1821
  }
1789
1822
  ctx.restore();
1790
1823
  }
1824
+ const tickerOpts = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1825
+ const useSmoothedCandle = tickerOpts.smoothing && smoothedTickerPrice !== null;
1826
+ const lastDataIndex = data.length - 1;
1791
1827
  for (let index = startIndex; index <= endIndex; index += 1) {
1792
1828
  const point = data[index];
1793
1829
  if (!point) {
1794
1830
  continue;
1795
1831
  }
1832
+ const isLastCandle = useSmoothedCandle && index === lastDataIndex;
1833
+ const displayClose = isLastCandle ? smoothedTickerPrice : point.c;
1834
+ const displayHigh = isLastCandle ? Math.max(point.h, smoothedTickerPrice) : point.h;
1835
+ const displayLow = isLastCandle ? Math.min(point.l, smoothedTickerPrice) : point.l;
1796
1836
  const centerX = chartLeft + (index + 0.5 - xStart) / xSpan * chartWidth;
1797
1837
  const openY = yFromPrice(point.o);
1798
- const closeY = yFromPrice(point.c);
1799
- const highY = yFromPrice(point.h);
1800
- const lowY = yFromPrice(point.l);
1801
- const direction = getCandleDirectionByIndex(index);
1838
+ const closeY = yFromPrice(displayClose);
1839
+ const highY = yFromPrice(displayHigh);
1840
+ const lowY = yFromPrice(displayLow);
1841
+ const direction = isLastCandle ? displayClose >= point.o ? "up" : "down" : getCandleDirectionByIndex(index);
1802
1842
  const candleColor = direction === "up" ? mergedOptions.upColor : mergedOptions.downColor;
1843
+ const roundedCenterX = Math.round(centerX);
1803
1844
  ctx.strokeStyle = candleColor;
1804
1845
  ctx.lineWidth = candleWickWidth;
1805
1846
  ctx.beginPath();
1806
- ctx.moveTo(crisp(centerX), crisp(highY));
1807
- ctx.lineTo(crisp(centerX), crisp(lowY));
1847
+ ctx.moveTo(roundedCenterX + 0.5, crisp(highY));
1848
+ ctx.lineTo(roundedCenterX + 0.5, crisp(lowY));
1808
1849
  ctx.stroke();
1850
+ const bodyLeft = roundedCenterX - Math.floor(bodyWidth / 2);
1809
1851
  const bodyTop = Math.min(openY, closeY);
1810
1852
  const bodyHeight = Math.max(1, Math.abs(closeY - openY));
1811
1853
  ctx.fillStyle = candleColor;
1812
- ctx.fillRect(Math.round(centerX - bodyWidth / 2), Math.round(bodyTop), bodyWidth, Math.max(1, Math.round(bodyHeight)));
1854
+ ctx.fillRect(bodyLeft, Math.round(bodyTop), bodyWidth, Math.max(1, Math.round(bodyHeight)));
1813
1855
  }
1814
1856
  const activeOverlayIndicators = indicators.filter((indicator) => indicator.visible).map((indicator) => ({ indicator, plugin: indicatorRegistry.get(indicator.type) })).filter(
1815
1857
  (value) => value.plugin !== void 0 && (value.indicator.pane ?? value.plugin.pane ?? "overlay") === "overlay"
@@ -1943,10 +1985,10 @@ function createChart(element, options = {}) {
1943
1985
  const ticker = mergedOptions.tickerLine ?? DEFAULT_OPTIONS.tickerLine;
1944
1986
  const lastPoint = data[data.length - 1];
1945
1987
  if ((ticker.visible ?? true) && lastPoint) {
1946
- const tickerPrice = lastPoint.c;
1988
+ const tickerPrice = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice : lastPoint.c;
1947
1989
  const tickerY = yFromPrice(tickerPrice);
1948
1990
  const lineY = clamp(tickerY, chartTop + 1, chartBottom - 1);
1949
- const lastDirection = getCandleDirectionByIndex(data.length - 1);
1991
+ const lastDirection = ticker.smoothing && smoothedTickerPrice !== null ? smoothedTickerPrice >= lastPoint.o ? "up" : "down" : getCandleDirectionByIndex(data.length - 1);
1950
1992
  const tickerColor = ticker.color ?? (lastDirection === "up" ? mergedOptions.upColor : mergedOptions.downColor);
1951
1993
  const tickerThickness = Math.max(1, ticker.thickness ?? 1);
1952
1994
  const tickerStyle = ticker.style ?? "solid";
@@ -2778,6 +2820,10 @@ function createChart(element, options = {}) {
2778
2820
  fitXViewport();
2779
2821
  }
2780
2822
  }
2823
+ const lastClose = data.length > 0 ? data[data.length - 1].c : null;
2824
+ if (lastClose !== null) {
2825
+ pushSmoothedPrice(lastClose);
2826
+ }
2781
2827
  draw();
2782
2828
  };
2783
2829
  const setPriceLines = (lines) => {
@@ -2928,6 +2974,10 @@ function createChart(element, options = {}) {
2928
2974
  draw();
2929
2975
  };
2930
2976
  const destroy = () => {
2977
+ if (smoothingRafId !== null) {
2978
+ cancelAnimationFrame(smoothingRafId);
2979
+ smoothingRafId = null;
2980
+ }
2931
2981
  canvas.removeEventListener("pointerdown", onPointerDown);
2932
2982
  canvas.removeEventListener("pointermove", onPointerMove);
2933
2983
  canvas.removeEventListener("pointerup", endPointerDrag);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",