* feat(#1536): remove zero volume rows from orderbook * feat(#1536): add orderbook debug console * feat(#1536): move best bid/ask price line position outside of render functon * feat(#1536): orderbook scroll to price fixes
This commit is contained in:
parent
dee0b016eb
commit
969d66a6a1
@ -1,4 +1,5 @@
|
|||||||
import groupBy from 'lodash/groupBy';
|
import groupBy from 'lodash/groupBy';
|
||||||
|
import uniqBy from 'lodash/uniqBy';
|
||||||
import { VolumeType } from '@vegaprotocol/react-helpers';
|
import { VolumeType } from '@vegaprotocol/react-helpers';
|
||||||
import { MarketTradingMode } from '@vegaprotocol/types';
|
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||||
import type { MarketData } from '@vegaprotocol/market-list';
|
import type { MarketData } from '@vegaprotocol/market-list';
|
||||||
@ -264,7 +265,7 @@ export const updateCompactedRows = (
|
|||||||
) => {
|
) => {
|
||||||
let sellModifiedIndex = -1;
|
let sellModifiedIndex = -1;
|
||||||
let data = [...rows];
|
let data = [...rows];
|
||||||
sell?.forEach((delta) => {
|
uniqBy(sell?.reverse(), 'price')?.forEach((delta) => {
|
||||||
[sellModifiedIndex, data] = partiallyUpdateCompactedRows(
|
[sellModifiedIndex, data] = partiallyUpdateCompactedRows(
|
||||||
VolumeType.ask,
|
VolumeType.ask,
|
||||||
data,
|
data,
|
||||||
@ -274,7 +275,7 @@ export const updateCompactedRows = (
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
let buyModifiedIndex = data.length;
|
let buyModifiedIndex = data.length;
|
||||||
buy?.forEach((delta) => {
|
uniqBy(buy?.reverse(), 'price')?.forEach((delta) => {
|
||||||
[buyModifiedIndex, data] = partiallyUpdateCompactedRows(
|
[buyModifiedIndex, data] = partiallyUpdateCompactedRows(
|
||||||
VolumeType.bid,
|
VolumeType.bid,
|
||||||
data,
|
data,
|
||||||
|
@ -47,11 +47,6 @@ export const OrderbookRow = React.memo(
|
|||||||
relativeValue={relativeBid}
|
relativeValue={relativeBid}
|
||||||
type={VolumeType.bid}
|
type={VolumeType.bid}
|
||||||
/>
|
/>
|
||||||
<PriceCell
|
|
||||||
testId={`price-${price}`}
|
|
||||||
value={BigInt(price)}
|
|
||||||
valueFormatted={addDecimalsFormatNumber(price, decimalPlaces)}
|
|
||||||
/>
|
|
||||||
<Vol
|
<Vol
|
||||||
testId={`ask-vol-${price}`}
|
testId={`ask-vol-${price}`}
|
||||||
value={ask}
|
value={ask}
|
||||||
@ -59,6 +54,11 @@ export const OrderbookRow = React.memo(
|
|||||||
relativeValue={relativeAsk}
|
relativeValue={relativeAsk}
|
||||||
type={VolumeType.ask}
|
type={VolumeType.ask}
|
||||||
/>
|
/>
|
||||||
|
<PriceCell
|
||||||
|
testId={`price-${price}`}
|
||||||
|
value={BigInt(price)}
|
||||||
|
valueFormatted={addDecimalsFormatNumber(price, decimalPlaces)}
|
||||||
|
/>
|
||||||
<CumulativeVol
|
<CumulativeVol
|
||||||
testId={`cumulative-vol-${price}`}
|
testId={`cumulative-vol-${price}`}
|
||||||
positionDecimalPlaces={positionDecimalPlaces}
|
positionDecimalPlaces={positionDecimalPlaces}
|
||||||
|
@ -23,6 +23,7 @@ describe('Orderbook', () => {
|
|||||||
<Orderbook
|
<Orderbook
|
||||||
decimalPlaces={decimalPlaces}
|
decimalPlaces={decimalPlaces}
|
||||||
positionDecimalPlaces={0}
|
positionDecimalPlaces={0}
|
||||||
|
fillGaps
|
||||||
{...generateMockData(params)}
|
{...generateMockData(params)}
|
||||||
onResolutionChange={onResolutionChange}
|
onResolutionChange={onResolutionChange}
|
||||||
/>
|
/>
|
||||||
@ -37,6 +38,7 @@ describe('Orderbook', () => {
|
|||||||
<Orderbook
|
<Orderbook
|
||||||
decimalPlaces={decimalPlaces}
|
decimalPlaces={decimalPlaces}
|
||||||
positionDecimalPlaces={0}
|
positionDecimalPlaces={0}
|
||||||
|
fillGaps
|
||||||
{...generateMockData(params)}
|
{...generateMockData(params)}
|
||||||
onResolutionChange={onResolutionChange}
|
onResolutionChange={onResolutionChange}
|
||||||
/>
|
/>
|
||||||
@ -47,6 +49,7 @@ describe('Orderbook', () => {
|
|||||||
<Orderbook
|
<Orderbook
|
||||||
decimalPlaces={decimalPlaces}
|
decimalPlaces={decimalPlaces}
|
||||||
positionDecimalPlaces={0}
|
positionDecimalPlaces={0}
|
||||||
|
fillGaps
|
||||||
{...generateMockData({
|
{...generateMockData({
|
||||||
...params,
|
...params,
|
||||||
numberOfSellRows: params.numberOfSellRows - 1,
|
numberOfSellRows: params.numberOfSellRows - 1,
|
||||||
@ -64,6 +67,7 @@ describe('Orderbook', () => {
|
|||||||
<Orderbook
|
<Orderbook
|
||||||
decimalPlaces={decimalPlaces}
|
decimalPlaces={decimalPlaces}
|
||||||
positionDecimalPlaces={0}
|
positionDecimalPlaces={0}
|
||||||
|
fillGaps
|
||||||
{...generateMockData(params)}
|
{...generateMockData(params)}
|
||||||
onResolutionChange={onResolutionChange}
|
onResolutionChange={onResolutionChange}
|
||||||
/>
|
/>
|
||||||
@ -74,6 +78,7 @@ describe('Orderbook', () => {
|
|||||||
<Orderbook
|
<Orderbook
|
||||||
decimalPlaces={decimalPlaces}
|
decimalPlaces={decimalPlaces}
|
||||||
positionDecimalPlaces={0}
|
positionDecimalPlaces={0}
|
||||||
|
fillGaps
|
||||||
{...generateMockData({
|
{...generateMockData({
|
||||||
...params,
|
...params,
|
||||||
bestStaticBidPrice: params.bestStaticBidPrice + 1,
|
bestStaticBidPrice: params.bestStaticBidPrice + 1,
|
||||||
@ -92,6 +97,7 @@ describe('Orderbook', () => {
|
|||||||
<Orderbook
|
<Orderbook
|
||||||
decimalPlaces={decimalPlaces}
|
decimalPlaces={decimalPlaces}
|
||||||
positionDecimalPlaces={0}
|
positionDecimalPlaces={0}
|
||||||
|
fillGaps
|
||||||
{...generateMockData(params)}
|
{...generateMockData(params)}
|
||||||
onResolutionChange={onResolutionChange}
|
onResolutionChange={onResolutionChange}
|
||||||
/>
|
/>
|
||||||
@ -105,6 +111,7 @@ describe('Orderbook', () => {
|
|||||||
<Orderbook
|
<Orderbook
|
||||||
decimalPlaces={decimalPlaces}
|
decimalPlaces={decimalPlaces}
|
||||||
positionDecimalPlaces={0}
|
positionDecimalPlaces={0}
|
||||||
|
fillGaps
|
||||||
{...generateMockData({
|
{...generateMockData({
|
||||||
...params,
|
...params,
|
||||||
numberOfSellRows: params.numberOfSellRows - 1,
|
numberOfSellRows: params.numberOfSellRows - 1,
|
||||||
@ -122,6 +129,7 @@ describe('Orderbook', () => {
|
|||||||
<Orderbook
|
<Orderbook
|
||||||
decimalPlaces={decimalPlaces}
|
decimalPlaces={decimalPlaces}
|
||||||
positionDecimalPlaces={0}
|
positionDecimalPlaces={0}
|
||||||
|
fillGaps
|
||||||
{...generateMockData(params)}
|
{...generateMockData(params)}
|
||||||
onResolutionChange={onResolutionChange}
|
onResolutionChange={onResolutionChange}
|
||||||
/>
|
/>
|
||||||
@ -143,6 +151,7 @@ describe('Orderbook', () => {
|
|||||||
<Orderbook
|
<Orderbook
|
||||||
decimalPlaces={decimalPlaces}
|
decimalPlaces={decimalPlaces}
|
||||||
positionDecimalPlaces={0}
|
positionDecimalPlaces={0}
|
||||||
|
fillGaps
|
||||||
{...generateMockData(params)}
|
{...generateMockData(params)}
|
||||||
onResolutionChange={onResolutionChange}
|
onResolutionChange={onResolutionChange}
|
||||||
/>
|
/>
|
||||||
@ -163,6 +172,7 @@ describe('Orderbook', () => {
|
|||||||
<Orderbook
|
<Orderbook
|
||||||
decimalPlaces={decimalPlaces}
|
decimalPlaces={decimalPlaces}
|
||||||
positionDecimalPlaces={0}
|
positionDecimalPlaces={0}
|
||||||
|
fillGaps
|
||||||
{...generateMockData({
|
{...generateMockData({
|
||||||
...params,
|
...params,
|
||||||
resolution: 10,
|
resolution: 10,
|
||||||
|
@ -9,11 +9,13 @@ import {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
|
Fragment,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
formatNumber,
|
formatNumber,
|
||||||
|
addDecimalsFormatNumber,
|
||||||
t,
|
t,
|
||||||
ThemeContext,
|
ThemeContext,
|
||||||
useResizeObserver,
|
useResizeObserver,
|
||||||
@ -29,6 +31,7 @@ interface OrderbookProps extends OrderbookData {
|
|||||||
positionDecimalPlaces: number;
|
positionDecimalPlaces: number;
|
||||||
resolution: number;
|
resolution: number;
|
||||||
onResolutionChange: (resolution: number) => void;
|
onResolutionChange: (resolution: number) => void;
|
||||||
|
fillGaps?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HorizontalLine = ({ top, testId }: { top: string; testId: string }) => (
|
const HorizontalLine = ({ top, testId }: { top: string; testId: string }) => (
|
||||||
@ -100,6 +103,171 @@ const bufferSize = 30;
|
|||||||
// margin size in px, when reached scrollOffset will be updated
|
// margin size in px, when reached scrollOffset will be updated
|
||||||
const marginSize = bufferSize * 0.9 * rowHeight;
|
const marginSize = bufferSize * 0.9 * rowHeight;
|
||||||
|
|
||||||
|
const getBestStaticBidPriceLinePosition = (
|
||||||
|
bestStaticBidPrice: string | undefined,
|
||||||
|
fillGaps: boolean,
|
||||||
|
maxPriceLevel: string,
|
||||||
|
minPriceLevel: string,
|
||||||
|
resolution: number,
|
||||||
|
rows: OrderbookRowData[] | null
|
||||||
|
) => {
|
||||||
|
let bestStaticBidPriceLinePosition = '';
|
||||||
|
if (maxPriceLevel !== '0' && minPriceLevel !== '0') {
|
||||||
|
if (
|
||||||
|
bestStaticBidPrice &&
|
||||||
|
BigInt(bestStaticBidPrice) < BigInt(maxPriceLevel) &&
|
||||||
|
BigInt(bestStaticBidPrice) > BigInt(minPriceLevel)
|
||||||
|
) {
|
||||||
|
if (fillGaps) {
|
||||||
|
bestStaticBidPriceLinePosition = (
|
||||||
|
((BigInt(maxPriceLevel) - BigInt(bestStaticBidPrice)) /
|
||||||
|
BigInt(resolution) +
|
||||||
|
BigInt(1)) *
|
||||||
|
BigInt(rowHeight) +
|
||||||
|
BigInt(1)
|
||||||
|
).toString();
|
||||||
|
} else {
|
||||||
|
const index = rows?.findIndex(
|
||||||
|
(row) => BigInt(row.price) <= BigInt(bestStaticBidPrice)
|
||||||
|
);
|
||||||
|
if (index !== undefined && index !== -1) {
|
||||||
|
bestStaticBidPriceLinePosition = (
|
||||||
|
(index + 1) * rowHeight +
|
||||||
|
1
|
||||||
|
).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestStaticBidPriceLinePosition;
|
||||||
|
};
|
||||||
|
const getBestStaticOfferPriceLinePosition = (
|
||||||
|
bestStaticOfferPrice: string | undefined,
|
||||||
|
fillGaps: boolean,
|
||||||
|
maxPriceLevel: string,
|
||||||
|
minPriceLevel: string,
|
||||||
|
resolution: number,
|
||||||
|
rows: OrderbookRowData[] | null
|
||||||
|
) => {
|
||||||
|
let bestStaticOfferPriceLinePosition = '';
|
||||||
|
if (
|
||||||
|
bestStaticOfferPrice &&
|
||||||
|
BigInt(bestStaticOfferPrice) <= BigInt(maxPriceLevel) &&
|
||||||
|
BigInt(bestStaticOfferPrice) > BigInt(minPriceLevel)
|
||||||
|
) {
|
||||||
|
if (fillGaps) {
|
||||||
|
bestStaticOfferPriceLinePosition = (
|
||||||
|
((BigInt(maxPriceLevel) - BigInt(bestStaticOfferPrice)) /
|
||||||
|
BigInt(resolution) +
|
||||||
|
BigInt(2)) *
|
||||||
|
BigInt(rowHeight) +
|
||||||
|
BigInt(1)
|
||||||
|
).toString();
|
||||||
|
} else {
|
||||||
|
const index = rows?.findIndex(
|
||||||
|
(row) => BigInt(row.price) <= BigInt(bestStaticOfferPrice)
|
||||||
|
);
|
||||||
|
if (index !== undefined && index !== -1) {
|
||||||
|
bestStaticOfferPriceLinePosition = (
|
||||||
|
(index + 2) * rowHeight +
|
||||||
|
1
|
||||||
|
).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestStaticOfferPriceLinePosition;
|
||||||
|
};
|
||||||
|
const OrderbookDebugInfo = ({
|
||||||
|
decimalPlaces,
|
||||||
|
numberOfRows,
|
||||||
|
viewportHeight,
|
||||||
|
lockOnMidPrice,
|
||||||
|
priceInCenter,
|
||||||
|
bestStaticBidPrice,
|
||||||
|
bestStaticOfferPrice,
|
||||||
|
maxPriceLevel,
|
||||||
|
minPriceLevel,
|
||||||
|
resolution,
|
||||||
|
}: {
|
||||||
|
decimalPlaces: number;
|
||||||
|
numberOfRows: number;
|
||||||
|
viewportHeight: number;
|
||||||
|
lockOnMidPrice: boolean;
|
||||||
|
priceInCenter?: string;
|
||||||
|
bestStaticBidPrice?: string;
|
||||||
|
bestStaticOfferPrice?: string;
|
||||||
|
maxPriceLevel: string;
|
||||||
|
minPriceLevel: string;
|
||||||
|
resolution: number;
|
||||||
|
}) => (
|
||||||
|
<Fragment>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '0',
|
||||||
|
borderTop: '1px solid rgba(255,0,0,0.5)',
|
||||||
|
background: 'black',
|
||||||
|
width: '100%',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className="absolute left-0 bottom-0 font-mono"
|
||||||
|
style={{
|
||||||
|
fontSize: '10px',
|
||||||
|
color: '#FFF',
|
||||||
|
background: '#000',
|
||||||
|
padding: '2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<pre>
|
||||||
|
{JSON.stringify(
|
||||||
|
{
|
||||||
|
numberOfRows,
|
||||||
|
viewportHeight,
|
||||||
|
lockOnMidPrice,
|
||||||
|
priceInCenter: priceInCenter
|
||||||
|
? addDecimalsFormatNumber(priceInCenter, decimalPlaces)
|
||||||
|
: '-',
|
||||||
|
maxPriceLevel: addDecimalsFormatNumber(
|
||||||
|
maxPriceLevel ?? '0',
|
||||||
|
decimalPlaces
|
||||||
|
),
|
||||||
|
bestStaticBidPrice: addDecimalsFormatNumber(
|
||||||
|
bestStaticBidPrice ?? '0',
|
||||||
|
decimalPlaces
|
||||||
|
),
|
||||||
|
bestStaticOfferPrice: addDecimalsFormatNumber(
|
||||||
|
bestStaticOfferPrice ?? '0',
|
||||||
|
decimalPlaces
|
||||||
|
),
|
||||||
|
minPriceLevel: addDecimalsFormatNumber(
|
||||||
|
minPriceLevel ?? '0',
|
||||||
|
decimalPlaces
|
||||||
|
),
|
||||||
|
midPrice: addDecimalsFormatNumber(
|
||||||
|
(bestStaticOfferPrice &&
|
||||||
|
bestStaticBidPrice &&
|
||||||
|
getPriceLevel(
|
||||||
|
BigInt(bestStaticOfferPrice) +
|
||||||
|
(BigInt(bestStaticBidPrice) -
|
||||||
|
BigInt(bestStaticOfferPrice)) /
|
||||||
|
BigInt(2),
|
||||||
|
resolution
|
||||||
|
)) ??
|
||||||
|
'0',
|
||||||
|
decimalPlaces
|
||||||
|
),
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
export const Orderbook = ({
|
export const Orderbook = ({
|
||||||
rows,
|
rows,
|
||||||
bestStaticBidPrice,
|
bestStaticBidPrice,
|
||||||
@ -110,10 +278,12 @@ export const Orderbook = ({
|
|||||||
decimalPlaces,
|
decimalPlaces,
|
||||||
positionDecimalPlaces,
|
positionDecimalPlaces,
|
||||||
resolution,
|
resolution,
|
||||||
|
fillGaps: initialFillGaps,
|
||||||
onResolutionChange,
|
onResolutionChange,
|
||||||
}: OrderbookProps) => {
|
}: OrderbookProps) => {
|
||||||
const theme = useContext(ThemeContext);
|
const theme = useContext(ThemeContext);
|
||||||
const scrollElement = useRef<HTMLDivElement>(null);
|
const scrollElement = useRef<HTMLDivElement>(null);
|
||||||
|
const rootElement = useRef<HTMLDivElement>(null);
|
||||||
const gridElement = useRef<HTMLDivElement>(null);
|
const gridElement = useRef<HTMLDivElement>(null);
|
||||||
const headerElement = useRef<HTMLDivElement>(null);
|
const headerElement = useRef<HTMLDivElement>(null);
|
||||||
const footerElement = useRef<HTMLDivElement>(null);
|
const footerElement = useRef<HTMLDivElement>(null);
|
||||||
@ -126,14 +296,19 @@ export const Orderbook = ({
|
|||||||
const priceInCenter = useRef<string>();
|
const priceInCenter = useRef<string>();
|
||||||
const [lockOnMidPrice, setLockOnMidPrice] = useState(true);
|
const [lockOnMidPrice, setLockOnMidPrice] = useState(true);
|
||||||
const resolutionRef = useRef(resolution);
|
const resolutionRef = useRef(resolution);
|
||||||
// stores rows[0].price value
|
|
||||||
const [maxPriceLevel, setMaxPriceLevel] = useState('');
|
|
||||||
const [viewportHeight, setViewportHeight] = useState(window.innerHeight);
|
const [viewportHeight, setViewportHeight] = useState(window.innerHeight);
|
||||||
|
const [fillGaps, setFillGaps] = useState(!!initialFillGaps);
|
||||||
const numberOfRows = useMemo(
|
const numberOfRows = useMemo(
|
||||||
() => getNumberOfRows(rows, resolution),
|
() => (fillGaps ? getNumberOfRows(rows, resolution) : rows?.length ?? 0),
|
||||||
[rows, resolution]
|
[rows, resolution, fillGaps]
|
||||||
);
|
);
|
||||||
|
const maxPriceLevel = rows?.[0]?.price ?? '0';
|
||||||
|
const minPriceLevel = (
|
||||||
|
fillGaps
|
||||||
|
? BigInt(maxPriceLevel) - BigInt(Math.floor(numberOfRows * resolution))
|
||||||
|
: BigInt(rows?.[rows.length - 1].price ?? '0')
|
||||||
|
).toString();
|
||||||
|
const [debug, setDebug] = useState(false);
|
||||||
const updateScrollOffset = useCallback(
|
const updateScrollOffset = useCallback(
|
||||||
(scrollTop: number) => {
|
(scrollTop: number) => {
|
||||||
if (Math.abs(scrollOffset - scrollTop) > marginSize) {
|
if (Math.abs(scrollOffset - scrollTop) > marginSize) {
|
||||||
@ -142,7 +317,6 @@ export const Orderbook = ({
|
|||||||
},
|
},
|
||||||
[scrollOffset]
|
[scrollOffset]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onScroll = useCallback(
|
const onScroll = useCallback(
|
||||||
(event: React.UIEvent<HTMLDivElement>) => {
|
(event: React.UIEvent<HTMLDivElement>) => {
|
||||||
const { scrollTop } = event.currentTarget;
|
const { scrollTop } = event.currentTarget;
|
||||||
@ -150,14 +324,16 @@ export const Orderbook = ({
|
|||||||
if (scrollTop === scrollTopRef.current) {
|
if (scrollTop === scrollTopRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
priceInCenter.current = (
|
const offsetTop = Math.floor(
|
||||||
BigInt(resolution) + // extra row on very top - sticky header
|
(scrollTop + Math.floor(viewportHeight / 2)) / rowHeight
|
||||||
BigInt(maxPriceLevel) -
|
);
|
||||||
BigInt(
|
priceInCenter.current = fillGaps
|
||||||
Math.floor((scrollTop + Math.floor(viewportHeight / 2)) / rowHeight)
|
? (
|
||||||
) *
|
BigInt(resolution) + // extra row on very top - sticky header
|
||||||
BigInt(resolution)
|
BigInt(maxPriceLevel) -
|
||||||
).toString();
|
BigInt(offsetTop) * BigInt(resolution)
|
||||||
|
).toString()
|
||||||
|
: rows?.[Math.min(offsetTop, rows.length - 1)].price.toString();
|
||||||
if (lockOnMidPrice) {
|
if (lockOnMidPrice) {
|
||||||
setLockOnMidPrice(false);
|
setLockOnMidPrice(false);
|
||||||
}
|
}
|
||||||
@ -169,19 +345,42 @@ export const Orderbook = ({
|
|||||||
maxPriceLevel,
|
maxPriceLevel,
|
||||||
viewportHeight,
|
viewportHeight,
|
||||||
updateScrollOffset,
|
updateScrollOffset,
|
||||||
|
fillGaps,
|
||||||
|
rows,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const scrollToPrice = useCallback(
|
const scrollToPrice = useCallback(
|
||||||
(price: string) => {
|
(price: string) => {
|
||||||
if (scrollElement.current && maxPriceLevel) {
|
if (scrollElement.current && maxPriceLevel !== '0') {
|
||||||
let scrollTop =
|
let scrollTop = 0;
|
||||||
// distance in rows between midPrice and price from first row * row Height
|
if (fillGaps) {
|
||||||
(Number(
|
scrollTop =
|
||||||
(BigInt(maxPriceLevel) - BigInt(price)) / BigInt(resolution)
|
// distance in rows between given price and first row price * row Height
|
||||||
) +
|
(Number(
|
||||||
1) * // add one row for sticky header
|
(BigInt(maxPriceLevel) - BigInt(price)) / BigInt(resolution)
|
||||||
rowHeight;
|
) +
|
||||||
|
1) * // add one row for sticky header
|
||||||
|
rowHeight +
|
||||||
|
rowHeight / 2 -
|
||||||
|
(viewportHeight % rowHeight);
|
||||||
|
} else if (rows) {
|
||||||
|
const index = rows.findIndex(
|
||||||
|
(row) => BigInt(row.price) <= BigInt(price)
|
||||||
|
);
|
||||||
|
if (index !== -1) {
|
||||||
|
scrollTop =
|
||||||
|
index * rowHeight + rowHeight / 2 - (viewportHeight % rowHeight);
|
||||||
|
if (
|
||||||
|
price === rows[index].price ||
|
||||||
|
index === 0 ||
|
||||||
|
BigInt(rows[index].price) - BigInt(price) <
|
||||||
|
BigInt(price) - BigInt(rows[index - 1].price)
|
||||||
|
) {
|
||||||
|
scrollTop += rowHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// minus half height of viewport plus half of row
|
// minus half height of viewport plus half of row
|
||||||
scrollTop -= Math.ceil((viewportHeight - rowHeight) / 2);
|
scrollTop -= Math.ceil((viewportHeight - rowHeight) / 2);
|
||||||
// adjust to current rows position
|
// adjust to current rows position
|
||||||
@ -204,16 +403,11 @@ export const Orderbook = ({
|
|||||||
viewportHeight,
|
viewportHeight,
|
||||||
numberOfRows,
|
numberOfRows,
|
||||||
updateScrollOffset,
|
updateScrollOffset,
|
||||||
|
fillGaps,
|
||||||
|
rows,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const newMaxPriceLevel = rows?.[0]?.price ?? '';
|
|
||||||
if (newMaxPriceLevel !== maxPriceLevel) {
|
|
||||||
setMaxPriceLevel(newMaxPriceLevel);
|
|
||||||
}
|
|
||||||
}, [rows, maxPriceLevel]);
|
|
||||||
|
|
||||||
const scrollToMidPrice = useCallback(() => {
|
const scrollToMidPrice = useCallback(() => {
|
||||||
if (!bestStaticOfferPrice || !bestStaticBidPrice) {
|
if (!bestStaticOfferPrice || !bestStaticBidPrice) {
|
||||||
return;
|
return;
|
||||||
@ -227,9 +421,7 @@ export const Orderbook = ({
|
|||||||
if (BigInt(midPrice) > BigInt(maxPriceLevel)) {
|
if (BigInt(midPrice) > BigInt(maxPriceLevel)) {
|
||||||
midPrice = maxPriceLevel;
|
midPrice = maxPriceLevel;
|
||||||
} else {
|
} else {
|
||||||
const minPriceLevel =
|
if (BigInt(midPrice) < BigInt(minPriceLevel)) {
|
||||||
BigInt(maxPriceLevel) - BigInt(Math.floor(numberOfRows * resolution));
|
|
||||||
if (BigInt(midPrice) < minPriceLevel) {
|
|
||||||
midPrice = minPriceLevel.toString();
|
midPrice = minPriceLevel.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,7 +433,7 @@ export const Orderbook = ({
|
|||||||
scrollToPrice,
|
scrollToPrice,
|
||||||
resolution,
|
resolution,
|
||||||
maxPriceLevel,
|
maxPriceLevel,
|
||||||
numberOfRows,
|
minPriceLevel,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// adjust scroll position to keep selected price in center
|
// adjust scroll position to keep selected price in center
|
||||||
@ -260,9 +452,9 @@ export const Orderbook = ({
|
|||||||
// handles window resize
|
// handles window resize
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleResize() {
|
function handleResize() {
|
||||||
if (scrollElement.current) {
|
if (rootElement.current) {
|
||||||
setViewportHeight(
|
setViewportHeight(
|
||||||
scrollElement.current.clientHeight || window.innerHeight
|
rootElement.current.clientHeight || window.innerHeight
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,15 +496,17 @@ export const Orderbook = ({
|
|||||||
);
|
);
|
||||||
// handles resizing of the Allotment.Pane (y-axis)
|
// handles resizing of the Allotment.Pane (y-axis)
|
||||||
// adjusts the scroll height
|
// adjusts the scroll height
|
||||||
const scrollElementResizeHandler: ResizeObserverCallback = useCallback(
|
const rootElementResizeHandler: ResizeObserverCallback = useCallback(
|
||||||
(entries) => {
|
(entries) => {
|
||||||
if (!scrollElement.current || entries.length === 0) return;
|
if (!rootElement.current || entries.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setViewportHeight(entries[0].contentRect.height);
|
setViewportHeight(entries[0].contentRect.height);
|
||||||
},
|
},
|
||||||
[setViewportHeight, scrollElement]
|
[setViewportHeight, rootElement]
|
||||||
);
|
);
|
||||||
useResizeObserver(gridElement.current, gridResizeHandler);
|
useResizeObserver(gridElement.current, gridResizeHandler);
|
||||||
useResizeObserver(scrollElement.current, scrollElementResizeHandler);
|
useResizeObserver(rootElement.current, rootElementResizeHandler);
|
||||||
|
|
||||||
let offset = Math.max(0, Math.round(scrollOffset / rowHeight));
|
let offset = Math.max(0, Math.round(scrollOffset / rowHeight));
|
||||||
const prependingBufferSize = Math.min(bufferSize, offset);
|
const prependingBufferSize = Math.min(bufferSize, offset);
|
||||||
@ -322,12 +516,12 @@ export const Orderbook = ({
|
|||||||
prependingBufferSize + viewportSize + bufferSize,
|
prependingBufferSize + viewportSize + bufferSize,
|
||||||
numberOfRows - offset
|
numberOfRows - offset
|
||||||
);
|
);
|
||||||
const data = getRowsToRender(rows, resolution, offset, limit);
|
const data = fillGaps
|
||||||
|
? getRowsToRender(rows, resolution, offset, limit)
|
||||||
|
: rows?.slice(offset, offset + limit) ?? [];
|
||||||
|
|
||||||
const paddingTop = offset * rowHeight;
|
const paddingTop = offset * rowHeight;
|
||||||
const paddingBottom = (numberOfRows - offset - limit) * rowHeight;
|
const paddingBottom = (numberOfRows - offset - limit) * rowHeight;
|
||||||
const minPriceLevel =
|
|
||||||
BigInt(maxPriceLevel) - BigInt(Math.floor(numberOfRows * resolution));
|
|
||||||
const tableBody =
|
const tableBody =
|
||||||
data && data.length !== 0 ? (
|
data && data.length !== 0 ? (
|
||||||
<div
|
<div
|
||||||
@ -368,16 +562,39 @@ export const Orderbook = ({
|
|||||||
.fill(null)
|
.fill(null)
|
||||||
.map((v, i) => Math.pow(10, i));
|
.map((v, i) => Math.pow(10, i));
|
||||||
|
|
||||||
|
const bestStaticBidPriceLinePosition = getBestStaticBidPriceLinePosition(
|
||||||
|
bestStaticBidPrice,
|
||||||
|
fillGaps,
|
||||||
|
maxPriceLevel,
|
||||||
|
minPriceLevel,
|
||||||
|
resolution,
|
||||||
|
rows
|
||||||
|
);
|
||||||
|
|
||||||
|
const bestStaticOfferPriceLinePosition = getBestStaticOfferPriceLinePosition(
|
||||||
|
bestStaticOfferPrice,
|
||||||
|
fillGaps,
|
||||||
|
maxPriceLevel,
|
||||||
|
minPriceLevel,
|
||||||
|
resolution,
|
||||||
|
rows
|
||||||
|
);
|
||||||
|
|
||||||
|
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||||
return (
|
return (
|
||||||
<div className="h-full relative pl-2 text-xs">
|
<div
|
||||||
|
className="h-full relative pl-2 text-xs"
|
||||||
|
ref={rootElement}
|
||||||
|
onDoubleClick={() => setDebug(!debug)}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className="absolute top-0 grid grid-cols-4 gap-2 text-right border-b pt-2 bg-white dark:bg-black z-10 border-default w-full"
|
className="absolute top-0 grid grid-cols-4 gap-2 text-right border-b pt-2 bg-white dark:bg-black z-10 border-default w-full"
|
||||||
style={{ gridAutoRows: '17px' }}
|
style={{ gridAutoRows: '17px' }}
|
||||||
ref={headerElement}
|
ref={headerElement}
|
||||||
>
|
>
|
||||||
<div>{t('Bid vol')}</div>
|
<div>{t('Bid vol')}</div>
|
||||||
<div>{t('Price')}</div>
|
|
||||||
<div>{t('Ask vol')}</div>
|
<div>{t('Ask vol')}</div>
|
||||||
|
<div>{t('Price')}</div>
|
||||||
<div className="pr-[2px]">{t('Cumulative vol')}</div>
|
<div className="pr-[2px]">{t('Cumulative vol')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -401,43 +618,36 @@ export const Orderbook = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{maxPriceLevel &&
|
{bestStaticBidPriceLinePosition && (
|
||||||
bestStaticBidPrice &&
|
<HorizontalLine
|
||||||
BigInt(bestStaticBidPrice) < BigInt(maxPriceLevel) &&
|
top={`${bestStaticBidPriceLinePosition}px`}
|
||||||
BigInt(bestStaticBidPrice) > minPriceLevel && (
|
testId="best-static-bid-price"
|
||||||
<HorizontalLine
|
/>
|
||||||
top={`${(
|
)}
|
||||||
((BigInt(maxPriceLevel) - BigInt(bestStaticBidPrice)) /
|
{bestStaticOfferPriceLinePosition && (
|
||||||
BigInt(resolution) +
|
<HorizontalLine
|
||||||
BigInt(1)) *
|
top={`${bestStaticOfferPriceLinePosition}px`}
|
||||||
BigInt(rowHeight) +
|
testId={'best-static-offer-price'}
|
||||||
BigInt(1)
|
/>
|
||||||
).toString()}px`}
|
)}
|
||||||
testId="best-static-bid-price"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{maxPriceLevel &&
|
|
||||||
bestStaticOfferPrice &&
|
|
||||||
BigInt(bestStaticOfferPrice) <= BigInt(maxPriceLevel) &&
|
|
||||||
BigInt(bestStaticOfferPrice) > minPriceLevel && (
|
|
||||||
<HorizontalLine
|
|
||||||
top={`${(
|
|
||||||
((BigInt(maxPriceLevel) - BigInt(bestStaticOfferPrice)) /
|
|
||||||
BigInt(resolution) +
|
|
||||||
BigInt(2)) *
|
|
||||||
BigInt(rowHeight) +
|
|
||||||
BigInt(1)
|
|
||||||
).toString()}px`}
|
|
||||||
testId={'best-static-offer-price'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="absolute bottom-0 grid grid-cols-4 gap-2 border-t-[1px] border-default mt-2 z-10 bg-white dark:bg-black w-full"
|
className="absolute bottom-0 grid grid-cols-4 gap-2 border-t-[1px] border-default mt-2 z-10 bg-white dark:bg-black w-full"
|
||||||
style={{ gridAutoRows: '17px' }}
|
style={{ gridAutoRows: '17px' }}
|
||||||
ref={footerElement}
|
ref={footerElement}
|
||||||
>
|
>
|
||||||
<div className="col-start-2">
|
<div className="col-span-2">
|
||||||
|
<label className="flex items-center">
|
||||||
|
<input
|
||||||
|
className="mr-1"
|
||||||
|
type="checkbox"
|
||||||
|
checked={fillGaps}
|
||||||
|
onChange={() => setFillGaps(!fillGaps)}
|
||||||
|
/>
|
||||||
|
{t('Show prices with no orders')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-start-3">
|
||||||
<select
|
<select
|
||||||
onChange={(e) => onResolutionChange(Number(e.currentTarget.value))}
|
onChange={(e) => onResolutionChange(Number(e.currentTarget.value))}
|
||||||
value={resolution}
|
value={resolution}
|
||||||
@ -453,6 +663,7 @@ export const Orderbook = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="col-start-4">
|
<div className="col-start-4">
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
onClick={scrollToMidPrice}
|
onClick={scrollToMidPrice}
|
||||||
className={classNames('w-full h-full', {
|
className={classNames('w-full h-full', {
|
||||||
hidden: lockOnMidPrice,
|
hidden: lockOnMidPrice,
|
||||||
@ -467,8 +678,23 @@ export const Orderbook = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{debug && (
|
||||||
|
<OrderbookDebugInfo
|
||||||
|
decimalPlaces={decimalPlaces}
|
||||||
|
resolution={resolution}
|
||||||
|
numberOfRows={numberOfRows}
|
||||||
|
viewportHeight={viewportHeight}
|
||||||
|
lockOnMidPrice={lockOnMidPrice}
|
||||||
|
priceInCenter={priceInCenter.current}
|
||||||
|
maxPriceLevel={maxPriceLevel}
|
||||||
|
bestStaticBidPrice={bestStaticBidPrice}
|
||||||
|
bestStaticOfferPrice={bestStaticOfferPrice}
|
||||||
|
minPriceLevel={minPriceLevel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
/* eslint-enable jsx-a11y/no-static-element-interactions */
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Orderbook;
|
export default Orderbook;
|
||||||
|
Loading…
Reference in New Issue
Block a user