import { Locale } from '@lib';
import { Ohlc } from '@src/core/Legend';
import { getThemeStore } from '@src/theme/store';
import { getLocale } from '@src/translations';
import {
getPercentPrecision,
getPricePrecision,
} from '@src/utils/precision';
import { isBarData } from './typeGuards';
export function formatCompactNumber(
value: number,
locale: Locale = getLocale(),
maxFractionDigits: number = getPricePrecision(),
): string {
const unitList: {
divisor: number;
suffix: string;
}[] = [
{ divisor: 1, suffix: '' },
{ divisor: 1_000, suffix: 'k' },
{ divisor: 1_000_000, suffix: 'm' },
{ divisor: 1_000_000_000, suffix: 'b' },
{
divisor: 1_000_000_000_000,
suffix: 't',
},
];
let selectedUnit = unitList[0];
for (
let index = unitList.length - 1;
index >= 0;
index -= 1
) {
if (
Math.abs(value) >=
unitList[index].divisor
) {
selectedUnit = unitList[index];
break;
}
}
const divided =
value / selectedUnit.divisor;
const formatter = new Intl.NumberFormat(
locale,
{
maximumFractionDigits:
maxFractionDigits,
minimumFractionDigits: 0,
useGrouping: false,
},
);
return (
formatter.format(divided) +
selectedUnit.suffix
);
}
export const formatPrice = (
price?: number | string,
precision = getPricePrecision(),
locale: Locale = getLocale(),
): string | undefined => {
if (price === undefined) {
return undefined;
}
const value = Number(price);
if (!Number.isFinite(value)) {
return undefined;
}
return new Intl.NumberFormat(locale, {
maximumFractionDigits: precision,
minimumFractionDigits: 0,
useGrouping: false,
}).format(value);
};
export const formatPercent = (
value: number,
precision = getPercentPrecision(),
locale: Locale = getLocale(),
): string => {
if (!Number.isFinite(value)) {
return '0%';
}
const formattedValue =
new Intl.NumberFormat(locale, {
maximumFractionDigits: precision,
minimumFractionDigits: 0,
useGrouping: false,
}).format(value);
return `${formattedValue}%`;
};
export const formatSignedNumber = (
value: number,
): string => {
if (!Number.isFinite(value)) {
return '0';
}
const formatted =
formatPrice(Math.abs(value)) ?? '0';
return value < 0
? `-${formatted}`
: formatted;
};
export const formatVolume = (
volume?: number,
): string => {
if (!volume) return '';
if (volume >= 1_000_000) {
return `${(volume / 1_000_000)
.toFixed(1)
.replace(/\.0$/, '')}M`;
}
if (volume >= 1_000) {
return `${(volume / 1_000)
.toFixed(1)
.replace(/\.0$/, '')}K`;
}
return volume.toString();
};
export function getPriceColor(
ohlc: Ohlc | null,
upColor?: string,
downColor?: string,
): string {
if (!ohlc) return '';
const { colors } = getThemeStore();
const positiveColor =
upColor ?? colors.chartCandleUp;
const negativeColor =
downColor ?? colors.chartCandleDown;
if (
ohlc.open &&
ohlc.close &&
isBarData(ohlc)
) {
return ohlc.close >= ohlc.open
? positiveColor
: negativeColor;
}
if (
typeof ohlc.absoluteChange ===
'number'
) {
return ohlc.absoluteChange >= 0
? positiveColor
: negativeColor;
}
return '';
}