From 3184e6b7a12c7fcd1898a2317f189e2f0412d64a Mon Sep 17 00:00:00 2001 From: Connor Prussin Date: Sat, 7 Jun 2025 21:27:25 -0700 Subject: [PATCH] fix(insights): don't show very small numbers as zero --- .../src/components/LivePrices/index.tsx | 42 ++++++++++--------- .../src/components/PriceFeed/chart.tsx | 7 +++- .../insights/src/hooks/use-price-formatter.ts | 40 ++++++++++++++++++ 3 files changed, 68 insertions(+), 21 deletions(-) create mode 100644 apps/insights/src/hooks/use-price-formatter.ts diff --git a/apps/insights/src/components/LivePrices/index.tsx b/apps/insights/src/components/LivePrices/index.tsx index db45c120d0..0bf30b37e7 100644 --- a/apps/insights/src/components/LivePrices/index.tsx +++ b/apps/insights/src/components/LivePrices/index.tsx @@ -5,13 +5,14 @@ import type { PriceData, PriceComponent } from "@pythnetwork/client"; import { Skeleton } from "@pythnetwork/component-library/Skeleton"; import type { ReactNode } from "react"; import { useMemo } from "react"; -import { useNumberFormatter, useDateFormatter } from "react-aria"; +import { useDateFormatter } from "react-aria"; import styles from "./index.module.scss"; import { useLivePriceComponent, useLivePriceData, } from "../../hooks/use-live-price-data"; +import { usePriceFormatter } from "../../hooks/use-price-formatter"; import type { Cluster } from "../../services/pyth"; export const SKELETON_WIDTH = 20; @@ -66,20 +67,17 @@ const Price = ({ }: { prev?: number | undefined; current?: number | undefined; -}) => { - const numberFormatter = useNumberFormatter({ maximumFractionDigits: 5 }); - - return current === undefined ? ( +}) => + current === undefined ? ( ) : ( - {numberFormatter.format(current)} + ); -}; export const LiveConfidence = ({ publisherKey, @@ -119,19 +117,23 @@ const LiveComponentConfidence = ({ return ; }; -const Confidence = ({ confidence }: { confidence?: number | undefined }) => { - const numberFormatter = useNumberFormatter({ maximumFractionDigits: 5 }); - - return ( - - - {confidence === undefined ? ( - - ) : ( - {numberFormatter.format(confidence)} - )} - - ); +const Confidence = ({ confidence }: { confidence?: number | undefined }) => ( + + + {confidence === undefined ? ( + + ) : ( + + + + )} + +); + +const FormattedPriceValue = ({ n }: { n: number }) => { + const formatter = usePriceFormatter(); + + return useMemo(() => formatter.format(n), [n, formatter]); }; export const LiveLastUpdated = ({ diff --git a/apps/insights/src/components/PriceFeed/chart.tsx b/apps/insights/src/components/PriceFeed/chart.tsx index 96d6d72026..d5ee596505 100644 --- a/apps/insights/src/components/PriceFeed/chart.tsx +++ b/apps/insights/src/components/PriceFeed/chart.tsx @@ -11,6 +11,7 @@ import { z } from "zod"; import styles from "./chart.module.scss"; import { useLivePriceData } from "../../hooks/use-live-price-data"; +import { usePriceFormatter } from "../../hooks/use-price-formatter"; import { Cluster } from "../../services/pyth"; type Props = { @@ -44,6 +45,7 @@ const useChartElem = (symbol: string, feedId: string) => { const chartRef = useRef(undefined); const earliestDateRef = useRef(undefined); const isBackfilling = useRef(false); + const priceFormatter = usePriceFormatter(); const backfillData = useCallback(() => { if (!isBackfilling.current && earliestDateRef.current) { @@ -113,6 +115,9 @@ const useChartElem = (symbol: string, feedId: string) => { timeVisible: true, secondsVisible: true, }, + localization: { + priceFormatter: priceFormatter.format, + }, }); const price = chart.addSeries(LineSeries, { priceFormat }); @@ -141,7 +146,7 @@ const useChartElem = (symbol: string, feedId: string) => { chart.remove(); }; } - }, [backfillData]); + }, [backfillData, priceFormatter]); useEffect(() => { if (current && chartRef.current) { diff --git a/apps/insights/src/hooks/use-price-formatter.ts b/apps/insights/src/hooks/use-price-formatter.ts new file mode 100644 index 0000000000..db578a279d --- /dev/null +++ b/apps/insights/src/hooks/use-price-formatter.ts @@ -0,0 +1,40 @@ +import { useCallback, useMemo } from "react"; +import { useNumberFormatter } from "react-aria"; + +export const usePriceFormatter = () => { + const bigNumberFormatter = useNumberFormatter({ maximumFractionDigits: 2 }); + const smallNumberFormatter = useNumberFormatter({ + maximumSignificantDigits: 5, + }); + const format = useCallback( + (n: number) => + n >= 1000 + ? bigNumberFormatter.format(n) + : formatToSubscriptNumber(smallNumberFormatter.format(n)), + [bigNumberFormatter, smallNumberFormatter], + ); + return useMemo(() => ({ format }), [format]); +}; + +const formatToSubscriptNumber = (numString: string) => { + const parts = numString.split("."); + + const [integerPart, decimalPart] = parts; + if (integerPart && decimalPart) { + const zerosCount = + decimalPart.length - decimalPart.replace(/^0+/, "").length; + + return zerosCount < 5 + ? numString + : integerPart + + "." + + "0" + + (zerosCount > 9 + ? String.fromCodePoint(0x20_80 + Math.floor(zerosCount / 10)) + : "") + + String.fromCodePoint(0x20_80 + (zerosCount % 10)) + + decimalPart.replace(/^0+/, ""); + } else { + return numString; + } +};