Skip to content

Commit

Permalink
feat: compact, consistent asset amount rendering in orderbook + trades (
Browse files Browse the repository at this point in the history
  • Loading branch information
tyleroooo authored Jun 28, 2024
1 parent dc02b27 commit 3570967
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 36 deletions.
72 changes: 41 additions & 31 deletions src/hooks/Orderbook/useDrawOrderbook.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useEffect, useRef, useState } from 'react';

import BigNumber from 'bignumber.js';
import { shallowEqual } from 'react-redux';

import type { PerpetualMarketOrderbookLevel } from '@/constants/abacus';
Expand All @@ -15,17 +14,21 @@ import {

import { useAppThemeAndColorModeContext } from '@/hooks/useAppThemeAndColorMode';

import { OutputType, formatNumberOutput } from '@/components/Output';

import { useAppSelector } from '@/state/appTypes';
import { getSelectedLocale } from '@/state/localizationSelectors';
import { getCurrentMarketConfig, getCurrentMarketOrderbookMap } from '@/state/perpetualsSelectors';

import { MustBigNumber } from '@/lib/numbers';
import { getConsistentAssetSizeString } from '@/lib/consistentAssetSize';
import {
getHistogramXValues,
getRektFromIdx,
getXByColumn,
getYForElements,
} from '@/lib/orderbookHelpers';
import { generateFadedColorVariant } from '@/lib/styles';
import { orEmptyObj } from '@/lib/typeUtils';

import { useLocaleSeparators } from '../useLocaleSeparators';

Expand Down Expand Up @@ -58,11 +61,13 @@ export const useDrawOrderbook = ({
const canvasRef = useRef<HTMLCanvasElement>(null);
const canvas = canvasRef.current;
const currentOrderbookMap = useAppSelector(getCurrentMarketOrderbookMap, shallowEqual);
const { decimal: LOCALE_DECIMAL_SEPARATOR, group: LOCALE_GROUP_SEPARATOR } =
useLocaleSeparators();
const { decimal: decimalSeparator, group: groupSeparator } = useLocaleSeparators();
const selectedLocale = useAppSelector(getSelectedLocale);

const { stepSizeDecimals = TOKEN_DECIMALS, tickSizeDecimals = SMALL_USD_DECIMALS } =
useAppSelector(getCurrentMarketConfig, shallowEqual) ?? {};
const marketConfig = orEmptyObj(useAppSelector(getCurrentMarketConfig));
const stepSizeDecimals = marketConfig.stepSizeDecimals ?? TOKEN_DECIMALS;
const tickSizeDecimals = marketConfig.tickSizeDecimals ?? SMALL_USD_DECIMALS;
const stepSize = marketConfig.stepSize ?? 10 ** (-1 * TOKEN_DECIMALS);
const prevData = useRef<typeof data>(data);
const theme = useAppThemeAndColorModeContext();

Expand Down Expand Up @@ -217,43 +222,41 @@ export const useDrawOrderbook = ({
}
}

const format = {
decimalSeparator: LOCALE_DECIMAL_SEPARATOR,
...{
groupSeparator: LOCALE_GROUP_SEPARATOR,
groupSize: 3,
secondaryGroupSize: 0,
fractionGroupSeparator: ' ',
fractionGroupSize: 0,
},
};

// Price text
if (price != null) {
ctx.fillStyle = textColor;
ctx.fillText(
MustBigNumber(price).toFormat(
tickSizeDecimals ?? SMALL_USD_DECIMALS,
BigNumber.ROUND_HALF_UP,
{
...format,
}
),
formatNumberOutput(price, OutputType.Number, {
decimalSeparator,
groupSeparator,
fractionDigits: tickSizeDecimals,
}),
getXByColumn({ canvasWidth, colIdx: 0 }) - ORDERBOOK_ROW_PADDING_RIGHT,
y
);
}

const decimalPlaces = displayUnit === 'asset' ? stepSizeDecimals ?? TOKEN_DECIMALS : 0;
const getSizeInFiatString = (sizeToRender: number) =>
formatNumberOutput(sizeToRender, OutputType.Number, {
decimalSeparator,
groupSeparator,
fractionDigits: 0,
});

// Size text
const displaySize = displayUnit === 'asset' ? size : sizeCost;
if (displaySize != null) {
ctx.fillStyle = updatedTextColor ?? textColor;
ctx.fillText(
MustBigNumber(displaySize).toFormat(decimalPlaces, BigNumber.ROUND_HALF_UP, {
...format,
}),
displayUnit === 'asset'
? getConsistentAssetSizeString(displaySize, {
decimalSeparator,
groupSeparator,
selectedLocale,
stepSize,
stepSizeDecimals,
})
: getSizeInFiatString(displaySize),
getXByColumn({ canvasWidth, colIdx: 1 }) - ORDERBOOK_ROW_PADDING_RIGHT,
y
);
Expand All @@ -264,9 +267,15 @@ export const useDrawOrderbook = ({
if (displayDepth != null) {
ctx.fillStyle = textColor;
ctx.fillText(
MustBigNumber(displayDepth).toFormat(decimalPlaces, BigNumber.ROUND_HALF_UP, {
...format,
}),
displayUnit === 'asset'
? getConsistentAssetSizeString(displayDepth, {
decimalSeparator,
groupSeparator,
selectedLocale,
stepSize,
stepSizeDecimals,
})
: getSizeInFiatString(displayDepth),
getXByColumn({ canvasWidth, colIdx: 2 }) - ORDERBOOK_ROW_PADDING_RIGHT,
y
);
Expand Down Expand Up @@ -381,6 +390,7 @@ export const useDrawOrderbook = ({
theme,
currentOrderbookMap,
displayUnit,
canvas,
]);

return { canvasRef };
Expand Down
74 changes: 74 additions & 0 deletions src/lib/consistentAssetSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { mapValues, range, zipObject } from 'lodash';

import { SUPPORTED_LOCALE_STRING_LABELS, SupportedLocales } from '@/constants/localization';

import { formatNumberOutput, OutputType } from '@/components/Output';

// for each locale, an array of the correct compact number suffix to use for 10^{index}
// e.g. for "en" we have ['', '', '', 'k', 'k', 'k', 'm', 'm', 'm', 'b', 'b', 'b', 't', 't', 't']
const supportedLocaleToCompactSuffixByPowerOfTen = mapValues(
SUPPORTED_LOCALE_STRING_LABELS,
(name, lang) =>
range(15)
.map((a) =>
Intl.NumberFormat(lang, {
style: 'decimal',
notation: 'compact',
maximumSignificantDigits: 6,
}).format(Math.abs(10 ** a))
)
// first capture group grabs all the numbers with normal separator, then we grab any groups of whitespace+numbers
// this is so we know which languages keep whitespace before the suffix
.map((b) => b.replace(/(^[\d,.]+){1}(\s\d+)*/, ''))
.map((b) => b.toLowerCase())
);

const zipObjectFn = <T extends string, K>(arr: T[], valueGenerator: (val: T) => K) =>
zipObject(
arr,
arr.map((val) => valueGenerator(val))
);

// for each locale, look up a given suffix (from map above) and get the correct power of ten to divide numbers by when using this suffix
// e.g. for "en" if you look up "k" you get 3 (1000), if you look up "m" you get 6 (1,000,000)
const supportedLocaleToSuffixPowers = mapValues(
supportedLocaleToCompactSuffixByPowerOfTen,
(values) => zipObjectFn([...new Set(values)], (f) => values.indexOf(f))
);

export const getConsistentAssetSizeString = (
sizeToRender: number,
{
decimalSeparator,
groupSeparator,
selectedLocale,
stepSize,
stepSizeDecimals,
}: {
selectedLocale: SupportedLocales;
stepSizeDecimals: number;
stepSize: number;
decimalSeparator: string | undefined;
groupSeparator: string | undefined;
}
) => {
const { displayDivisor, displaySuffix } = (() => {
if (stepSizeDecimals !== 0 || stepSize == null || stepSize < 10) {
return { displayDivisor: 1, displaySuffix: '' };
}
const unitToUse =
supportedLocaleToCompactSuffixByPowerOfTen[selectedLocale][Math.floor(Math.log10(stepSize))];
if (unitToUse == null) {
return { displayDivisor: 1, displaySuffix: '' };
}
return {
displayDivisor: 10 ** supportedLocaleToSuffixPowers[selectedLocale][unitToUse],
displaySuffix: unitToUse,
};
})();
return `${formatNumberOutput(sizeToRender / displayDivisor, OutputType.Number, {
decimalSeparator,
groupSeparator,
fractionDigits: stepSizeDecimals,
})}${displaySuffix}`;
};
20 changes: 15 additions & 5 deletions src/views/tables/LiveTrades.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import styled, { css, keyframes } from 'styled-components';

import { MarketTrade } from '@/constants/abacus';
import { STRING_KEYS } from '@/constants/localization';
import { TOKEN_DECIMALS } from '@/constants/numbers';
import { EMPTY_ARR } from '@/constants/objects';

import { useBreakpoints } from '@/hooks/useBreakpoints';
import { useLocaleSeparators } from '@/hooks/useLocaleSeparators';
import { useStringGetter } from '@/hooks/useStringGetter';

import breakpoints from '@/styles/breakpoints';
Expand All @@ -18,8 +20,10 @@ import { Output, OutputType } from '@/components/Output';

import { useAppSelector } from '@/state/appTypes';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { getSelectedLocale } from '@/state/localizationSelectors';
import { getCurrentMarketConfig, getCurrentMarketLiveTrades } from '@/state/perpetualsSelectors';

import { getConsistentAssetSizeString } from '@/lib/consistentAssetSize';
import { getSimpleStyledOutputType } from '@/lib/genericFunctionalComponentUtils';
import { isTruthy } from '@/lib/isTruthy';
import { getSelectedOrderSide } from '@/lib/tradeData';
Expand Down Expand Up @@ -52,7 +56,9 @@ export const LiveTrades = ({ className, histogramSide = 'left' }: StyleProps) =>
useAppSelector(getCurrentMarketLiveTrades, shallowEqual) ?? EMPTY_ARR;

const { id = '' } = currentMarketAssetData ?? {};
const { stepSizeDecimals, tickSizeDecimals } = currentMarketConfig ?? {};
const { stepSizeDecimals, tickSizeDecimals, stepSize } = currentMarketConfig ?? {};
const { decimal: decimalSeparator, group: groupSeparator } = useLocaleSeparators();
const selectedLocale = useAppSelector(getSelectedLocale);

const rows = currentMarketLiveTrades.map(
({ createdAtMilliseconds, price, size, side }: MarketTrade, idx) => ({
Expand Down Expand Up @@ -95,11 +101,15 @@ export const LiveTrades = ({ className, histogramSide = 'left' }: StyleProps) =>
tag: id,
renderCell: (row: RowData) => (
<$SizeOutput
type={OutputType.Asset}
value={row.size}
fractionDigits={stepSizeDecimals}
type={OutputType.Text}
value={getConsistentAssetSizeString(row.size, {
decimalSeparator,
groupSeparator,
selectedLocale,
stepSize: stepSize ?? 10 ** (-1 * TOKEN_DECIMALS),
stepSizeDecimals: stepSizeDecimals ?? TOKEN_DECIMALS,
})}
histogramSide={histogramSide}
useGrouping={false}
/>
),
},
Expand Down

0 comments on commit 3570967

Please sign in to comment.