From 55b2c3fb4a6ca188881193afea90d50efd1f6664 Mon Sep 17 00:00:00 2001 From: Jared Vu Date: Thu, 11 Jan 2024 10:31:53 -0800 Subject: [PATCH] Update Funding Chart Tooltip (#225) * :construction: depth chart * :sparkles: Fixed types and shortened FundingRateTooltip * :lipstick: update depth chart color scale to use css var * :lipstick: use layer-6 instead of text-1 * :globe_with_meridians: Add localization, fix nits --- package.json | 2 +- pnpm-lock.yaml | 8 +- .../visx/XYChartWithPointerEvents.tsx | 29 +- src/constants/charts.ts | 40 +++ src/constants/markets.ts | 1 + src/hooks/useOrderbookValues.ts | 44 +++ src/state/perpetualsCalculators.ts | 7 +- src/views/charts/DepthChart/Tooltip.tsx | 220 ++++++++++++ .../{DepthChart.tsx => DepthChart/index.tsx} | 312 ++++-------------- src/views/charts/FundingChart/Tooltip.tsx | 107 ++++++ .../index.tsx} | 135 +------- 11 files changed, 514 insertions(+), 391 deletions(-) create mode 100644 src/constants/charts.ts create mode 100644 src/hooks/useOrderbookValues.ts create mode 100644 src/views/charts/DepthChart/Tooltip.tsx rename src/views/charts/{DepthChart.tsx => DepthChart/index.tsx} (52%) create mode 100644 src/views/charts/FundingChart/Tooltip.tsx rename src/views/charts/{FundingChart.tsx => FundingChart/index.tsx} (61%) diff --git a/package.json b/package.json index f2ed3f1..f1fd4f4 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "@cosmjs/tendermint-rpc": "^0.31.0", "@dydxprotocol/v4-abacus": "^1.1.33", "@dydxprotocol/v4-client-js": "^1.0.11", - "@dydxprotocol/v4-localization": "^1.1.6", + "@dydxprotocol/v4-localization": "^1.1.11", "@ethersproject/providers": "^5.7.2", "@js-joda/core": "^5.5.3", "@radix-ui/react-accordion": "^1.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12f032e..01b14a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,8 +33,8 @@ dependencies: specifier: ^1.0.11 version: 1.0.11 '@dydxprotocol/v4-localization': - specifier: ^1.1.6 - version: 1.1.6 + specifier: ^1.1.11 + version: 1.1.11 '@ethersproject/providers': specifier: ^5.7.2 version: 5.7.2 @@ -1023,8 +1023,8 @@ packages: - utf-8-validate dev: false - /@dydxprotocol/v4-localization@1.1.6: - resolution: {integrity: sha512-Bon6NSRU4/FqneAbnP2G28EAPr0hp4LhvAayX61o0O1PGkxnLzAHkXeFppdM0Zn0fOcp1S1MJ+gvz138ZDephQ==} + /@dydxprotocol/v4-localization@1.1.11: + resolution: {integrity: sha512-ZHnyrWD1bEpvY1tctkcmSV6A5+hQSYBFwyjbiyOqepqDqrv24J5a3hlZU94lDyYf+7Sdk3alOvy9Z7Lpk59nvw==} dev: false /@dydxprotocol/v4-proto@0.4.1: diff --git a/src/components/visx/XYChartWithPointerEvents.tsx b/src/components/visx/XYChartWithPointerEvents.tsx index 0928d6f..a35d2a3 100644 --- a/src/components/visx/XYChartWithPointerEvents.tsx +++ b/src/components/visx/XYChartWithPointerEvents.tsx @@ -1,26 +1,33 @@ -import React, { useContext, useState } from 'react'; +import React, { PropsWithChildren, useContext, useState } from 'react'; import { Point } from '@visx/point'; import { localPoint } from '@visx/event'; -import { XYChart, DataContext } from '@visx/xychart'; +import { XYChart, DataContext, type EventHandlerParams } from '@visx/xychart'; import { getScaleBandwidth } from '@/components/visx/getScaleBandwidth'; export const XYChartWithPointerEvents = ({ - onPointerMove, onPointerUp, onPointerPressedChange, ...props + onPointerMove, + onPointerUp, + onPointerPressedChange, + ...props }: { - onPointerMove?: (point: Point) => void; - onPointerUp?: (point: Point) => void; + onPointerMove?: (point: Point | EventHandlerParams) => void; + onPointerUp?: (point: Point | EventHandlerParams) => void; onPointerPressedChange?: (isPointerPressed: boolean) => void; -} & React.PropsWithChildren>) => { +} & PropsWithChildren[0]>) => { const { xScale, yScale } = useContext(DataContext); - const [lastPointerMoveEvent, setLastPointerMoveEvent] = useState(); const pointerContainerPosition = lastPointerMoveEvent ? localPoint(lastPointerMoveEvent) : null; - const pointerChartPosition = xScale && yScale && pointerContainerPosition && + const pointerChartPosition = + xScale && + yScale && + pointerContainerPosition && new Point({ + // @ts-expect-error invert supposedly doesn't exist on AxisScale x: xScale.invert(pointerContainerPosition?.x - getScaleBandwidth(xScale) / 2), + // @ts-expect-error invert supposedly doesn't exist on AxisScale y: yScale.invert(pointerContainerPosition?.y - getScaleBandwidth(yScale) / 2), }); @@ -29,15 +36,13 @@ export const XYChartWithPointerEvents = ({ {...props} onPointerMove={({ event }) => { setLastPointerMoveEvent(event as React.PointerEvent); - if (pointerChartPosition) - onPointerMove?.(pointerChartPosition); + if (pointerChartPosition) onPointerMove?.(pointerChartPosition); }} onPointerOut={() => setLastPointerMoveEvent(undefined)} onPointerDown={() => onPointerPressedChange?.(true)} onPointerUp={() => { onPointerPressedChange?.(false); - if (pointerChartPosition) - onPointerUp?.(pointerChartPosition); + if (pointerChartPosition) onPointerUp?.(pointerChartPosition); }} > {props.children} diff --git a/src/constants/charts.ts b/src/constants/charts.ts new file mode 100644 index 0000000..0f1c1a1 --- /dev/null +++ b/src/constants/charts.ts @@ -0,0 +1,40 @@ +import { OrderSide } from '@dydxprotocol/v4-client-js'; +import { FundingDirection } from './markets'; + +// ------ Depth Chart ------ // +export enum DepthChartSeries { + Asks = 'Asks', + Bids = 'Bids', + MidMarket = 'MidMarket', +} + +export type DepthChartDatum = { + size: number; + price: number; + depth: number; + seriesKey: DepthChartSeries; +}; + +export type DepthChartPoint = { + side: OrderSide; + price: number; + size: number; +}; + +export const SERIES_KEY_FOR_ORDER_SIDE = { + [OrderSide.BUY]: DepthChartSeries.Bids, + [OrderSide.SELL]: DepthChartSeries.Asks, +}; + +// ------ Funding Chart ------ // +export enum FundingRateResolution { + OneHour = 'OneHour', + EightHour = 'EightHour', + Annualized = 'Annualized', +} + +export type FundingChartDatum = { + time: number; + fundingRate: number; + direction: FundingDirection; +}; diff --git a/src/constants/markets.ts b/src/constants/markets.ts index edb44f9..0ddc47d 100644 --- a/src/constants/markets.ts +++ b/src/constants/markets.ts @@ -25,4 +25,5 @@ export const DEFAULT_MARKETID = 'ETH-USD'; export enum FundingDirection { ToShort = 'ToShort', ToLong = 'ToLong', + None = 'None', } diff --git a/src/hooks/useOrderbookValues.ts b/src/hooks/useOrderbookValues.ts new file mode 100644 index 0000000..7e65a47 --- /dev/null +++ b/src/hooks/useOrderbookValues.ts @@ -0,0 +1,44 @@ +import { useMemo } from 'react'; +import { shallowEqual, useSelector } from 'react-redux'; + +import { DepthChartSeries, DepthChartDatum } from '@/constants/charts'; + +import { getCurrentMarketOrderbook } from '@/state/perpetualsSelectors'; + +import { MustBigNumber } from '@/lib/numbers'; + +export const useOrderbookValuesForDepthChart = () => { + const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual); + + return useMemo(() => { + const bids = (orderbook?.bids?.toArray() ?? []) + .filter(Boolean) + .map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Bids } as DepthChartDatum)); + + const asks = (orderbook?.asks?.toArray() ?? []) + .filter(Boolean) + .map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Asks } as DepthChartDatum)); + + const lowestBid = bids[bids.length - 1]; + const highestBid = bids[0]; + const lowestAsk = asks[0]; + const highestAsk = asks[asks.length - 1]; + + const midMarketPrice = orderbook?.midPrice; + const spread = MustBigNumber(lowestAsk?.price ?? 0).minus(highestBid?.price ?? 0); + const spreadPercent = orderbook?.spreadPercent; + + return { + bids, + asks, + lowestBid, + highestBid, + lowestAsk, + highestAsk, + midMarketPrice, + spread, + spreadPercent, + orderbook, + }; + }, [orderbook]); +}; diff --git a/src/state/perpetualsCalculators.ts b/src/state/perpetualsCalculators.ts index 94e0a6e..09fa68c 100644 --- a/src/state/perpetualsCalculators.ts +++ b/src/state/perpetualsCalculators.ts @@ -26,7 +26,12 @@ export const calculateFundingRateHistory = createSelector( return data.map(({ effectiveAtMilliseconds, rate }) => ({ time: effectiveAtMilliseconds, fundingRate: rate, - direction: rate < 0 ? FundingDirection.ToLong : FundingDirection.ToShort, + direction: + rate === 0 + ? FundingDirection.None + : rate < 0 + ? FundingDirection.ToLong + : FundingDirection.ToShort, })); } ); diff --git a/src/views/charts/DepthChart/Tooltip.tsx b/src/views/charts/DepthChart/Tooltip.tsx new file mode 100644 index 0000000..d3c2532 --- /dev/null +++ b/src/views/charts/DepthChart/Tooltip.tsx @@ -0,0 +1,220 @@ +import { useMemo } from 'react'; +import { OrderSide } from '@dydxprotocol/v4-client-js'; +import type { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip'; +import { shallowEqual, useSelector } from 'react-redux'; + +import type { Nullable } from '@/constants/abacus'; +import { + DepthChartDatum, + DepthChartPoint, + DepthChartSeries, + SERIES_KEY_FOR_ORDER_SIDE, +} from '@/constants/charts'; +import { STRING_KEYS } from '@/constants/localization'; + +import { useStringGetter } from '@/hooks'; +import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues'; + +import { TooltipContent } from '@/components/visx/TooltipContent'; +import { Details } from '@/components/Details'; +import { Output, OutputType } from '@/components/Output'; + +import { getCurrentMarketAssetData } from '@/state/assetsSelectors'; + +import { MustBigNumber } from '@/lib/numbers'; + +type DepthChartTooltipProps = { + chartPointAtPointer: DepthChartPoint; + isEditingOrder?: boolean; + stepSizeDecimals: Nullable; + tickSizeDecimals: Nullable; +} & Pick, 'colorScale' | 'tooltipData'>; + +export const DepthChartTooltipContent = ({ + chartPointAtPointer, + colorScale, + isEditingOrder, + stepSizeDecimals, + tickSizeDecimals, + tooltipData, +}: DepthChartTooltipProps) => { + const { nearestDatum } = tooltipData || {}; + const stringGetter = useStringGetter(); + const { spread, spreadPercent, midMarketPrice } = useOrderbookValuesForDepthChart(); + const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {}; + + const priceImpact = useMemo(() => { + if (nearestDatum) { + const depthChartSeries = nearestDatum.key as DepthChartSeries; + + return { + [DepthChartSeries.Bids]: MustBigNumber(nearestDatum?.datum.price) + .minus(chartPointAtPointer.price) + .div(nearestDatum?.datum.price), + [DepthChartSeries.Asks]: MustBigNumber(chartPointAtPointer.price) + .minus(nearestDatum?.datum.price) + .div(chartPointAtPointer.price), + [DepthChartSeries.MidMarket]: undefined, + }[depthChartSeries]; + } + return undefined; + }, [nearestDatum, chartPointAtPointer.price]); + + if (!isEditingOrder && !nearestDatum?.datum) return null; + + return ( + +

+ {isEditingOrder + ? stringGetter({ key: STRING_KEYS.RELEASE_TO_EDIT }) + : nearestDatum && + { + [DepthChartSeries.Bids]: stringGetter({ key: STRING_KEYS.BIDS }), + [DepthChartSeries.Asks]: stringGetter({ key: STRING_KEYS.ASKS }), + [DepthChartSeries.MidMarket]: stringGetter({ key: STRING_KEYS.MID_MARKET }), + }[nearestDatum.key]} +

+ +
+ ), + }, + { + key: 'limitPrice', + label: stringGetter({ key: STRING_KEYS.LIMIT_PRICE }), + value: ( + + ), + }, + { + key: 'size', + label: stringGetter({ key: STRING_KEYS.AMOUNT }), + value: ( + + ), + }, + ] + : nearestDatum?.key === DepthChartSeries.MidMarket + ? [ + { + key: 'midMarketPrice', + label: stringGetter({ key: STRING_KEYS.PRICE }), + value: ( + + ), + }, + { + key: 'spread', + label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SPREAD }), + value: ( + <> + + + + ), + }, + ] + : [ + { + key: 'price', + label: stringGetter({ key: STRING_KEYS.PRICE }), + value: ( + <> + {nearestDatum && + { + [DepthChartSeries.Bids]: '≥', + [DepthChartSeries.Asks]: '≤', + }[nearestDatum.key]} + + + ), + }, + { + key: 'depth', + label: stringGetter({ key: STRING_KEYS.TOTAL_SIZE }), + value: ( + + ), + }, + { + key: 'cost', + label: stringGetter({ key: STRING_KEYS.TOTAL_COST }), + value: ( + + ), + }, + { + key: 'priceImpact', + label: stringGetter({ key: STRING_KEYS.PRICE_IMPACT }), + value: , + }, + ] + } + /> + + ); +}; diff --git a/src/views/charts/DepthChart.tsx b/src/views/charts/DepthChart/index.tsx similarity index 52% rename from src/views/charts/DepthChart.tsx rename to src/views/charts/DepthChart/index.tsx index 673ccee..3b125d7 100644 --- a/src/views/charts/DepthChart.tsx +++ b/src/views/charts/DepthChart/index.tsx @@ -1,14 +1,20 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import styled, { AnyStyledComponent, css, keyframes } from 'styled-components'; import { useSelector, shallowEqual } from 'react-redux'; +import { OrderSide } from '@dydxprotocol/v4-client-js'; -import { StringGetterFunction, STRING_KEYS } from '@/constants/localization'; +import { + DepthChartDatum, + DepthChartPoint, + DepthChartSeries, + SERIES_KEY_FOR_ORDER_SIDE, +} from '@/constants/charts'; +import { StringGetterFunction } from '@/constants/localization'; import { useBreakpoints } from '@/hooks'; +import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues'; -import { MustBigNumber } from '@/lib/numbers'; - -import { getCurrentMarketConfig, getCurrentMarketOrderbook } from '@/state/perpetualsSelectors'; +import { getCurrentMarketConfig } from '@/state/perpetualsSelectors'; import { getCurrentMarketAssetData } from '@/state/assetsSelectors'; import { XYChartWithPointerEvents } from '@/components/visx/XYChartWithPointerEvents'; @@ -21,24 +27,25 @@ import { darkTheme, DataProvider, EventEmitterProvider, + type EventHandlerParams, } from '@visx/xychart'; import { LinearGradient } from '@visx/gradient'; import { curveStepAfter } from '@visx/curve'; -import type { Point } from '@visx/point'; +import { Point } from '@visx/point'; import Tooltip from '@/components/visx/XYChartTooltipWithBounds'; -import { TooltipContent } from '@/components/visx/TooltipContent'; import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput'; -import { Details } from '@/components/Details'; import { LoadingSpace } from '@/components/Loading/LoadingSpinner'; -import { Output, OutputType } from '@/components/Output'; +import { OutputType } from '@/components/Output'; -import { OrderSide } from '@dydxprotocol/v4-client-js'; +import { MustBigNumber } from '@/lib/numbers'; + +import { DepthChartTooltipContent } from './Tooltip'; // @ts-ignore const theme = buildChartTheme({ ...darkTheme, - colors: ['var(--color-positive)', 'var(--color-negative)', 'white'], // categorical colors, mapped to series via `dataKey`s + colors: ['var(--color-positive)', 'var(--color-negative)', 'var(--color-layer-6)'], // categorical colors, mapped to series via `dataKey`s }); const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n)); @@ -58,30 +65,6 @@ const formatNumber = (n: number, selectedLocale: string, isCompact: boolean = n : formattedNumber; }; -enum DepthChartSeries { - Asks = 'Asks', - Bids = 'Bids', - MidMarket = 'MidMarket', -} - -type DepthChartDatum = { - size: number; - price: number; - depth: number; - seriesKey: DepthChartSeries; -}; - -const seriesKeyForOrderSide = { - [OrderSide.BUY]: DepthChartSeries.Bids, - [OrderSide.SELL]: DepthChartSeries.Asks, -}; - -type DepthChartPoint = { - side: OrderSide; - price: number; - size: number; -}; - export const DepthChart = ({ onChartClick, stringGetter, @@ -96,8 +79,6 @@ export const DepthChart = ({ const { isMobile } = useBreakpoints(); // Chart data - - const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual); const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {}; const { stepSizeDecimals, tickSizeDecimals } = useSelector(getCurrentMarketConfig, shallowEqual) ?? {}; @@ -112,43 +93,15 @@ export const DepthChart = ({ midMarketPrice, spread, spreadPercent, - } = useMemo(() => { - const bids = (orderbook?.bids?.toArray() ?? []) - .filter(Boolean) - .map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Bids } as DepthChartDatum)); - - const asks = (orderbook?.asks?.toArray() ?? []) - .filter(Boolean) - .map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Asks } as DepthChartDatum)); - - const lowestBid = bids[bids.length - 1]; - const highestBid = bids[0]; - const lowestAsk = asks[0]; - const highestAsk = asks[asks.length - 1]; - - const midMarketPrice = orderbook?.midPrice; - const spread = MustBigNumber(lowestAsk?.price ?? 0).minus(highestBid?.price ?? 0); - const spreadPercent = orderbook?.spreadPercent; - - return { - bids, - asks, - lowestBid, - highestBid, - lowestAsk, - highestAsk, - midMarketPrice, - spread, - spreadPercent, - }; - }, [orderbook]); + orderbook, + } = useOrderbookValuesForDepthChart(); // Chart state const [isPointerPressed, setIsPointerPressed] = useState(false); const [chartPointAtPointer, setChartPointAtPointer] = useState(); - const isEditingOrder = isPointerPressed && chartPointAtPointer; + const isEditingOrder = Boolean(isPointerPressed && chartPointAtPointer); const [zoomDomain, setZoomDomain] = useState(); @@ -193,12 +146,24 @@ export const DepthChart = ({ }, [orderbook, zoomDomain]); const getChartPoint = useCallback( - ({ x: price, y: size }: Point) => - ({ - side: price < midMarketPrice! ? OrderSide.BUY : OrderSide.SELL, + (point: Point | EventHandlerParams) => { + let price, size; + if (point instanceof Point) { + const { x, y } = point as Point; + price = x; + size = y; + } else { + const { svgPoint: { x, y } = {} } = point as EventHandlerParams; + price = x; + size = y; + } + + return { + side: MustBigNumber(price).lt(midMarketPrice!) ? OrderSide.BUY : OrderSide.SELL, price, size, - } as DepthChartPoint), + } as DepthChartPoint; + }, [midMarketPrice] ); @@ -256,7 +221,7 @@ export const DepthChart = ({ }} onPointerUp={(point) => point && onChartClick?.(getChartPoint(point))} onPointerMove={(point) => point && setChartPointAtPointer(getChartPoint(point))} - onPointerPressedChange={setIsPointerPressed} + onPointerPressedChange={(isPointerPressed) => setIsPointerPressed(isPointerPressed)} > { - const { nearestDatum } = tooltipData || {}; - - if (!isEditingOrder && !nearestDatum?.datum) return null; - - return ( - -

- {isEditingOrder - ? 'Release mouse to edit order' - : { - [DepthChartSeries.Bids]: 'Bids', - [DepthChartSeries.Asks]: 'Asks', - [DepthChartSeries.MidMarket]: 'Mid-Market', - }[nearestDatum.key]} -

- -
- ), - }, - { - key: 'limitPrice', - label: stringGetter({ key: STRING_KEYS.LIMIT_PRICE }), - value: ( - - ), - }, - { - key: 'size', - label: stringGetter({ key: STRING_KEYS.AMOUNT }), - value: ( - - ), - }, - ] - : nearestDatum?.key === DepthChartSeries.MidMarket - ? [ - { - key: 'midMarketPrice', - // label: stringGetter({ key: STRING_KEYS.ORDERBOOK_MID_MARKET_PRICE }), - label: stringGetter({ key: STRING_KEYS.PRICE }), - value: ( - - ), - }, - { - key: 'spread', - label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SPREAD }), - value: ( - <> - - - - ), - }, - ] - : [ - { - key: 'price', - label: stringGetter({ key: STRING_KEYS.PRICE }), - value: ( - <> - {nearestDatum && - { - [DepthChartSeries.Bids]: '≥', - [DepthChartSeries.Asks]: '≤', - }[nearestDatum.key]} - - - ), - }, - { - key: 'depth', - label: stringGetter({ key: STRING_KEYS.TOTAL_SIZE }), - value: ( - - ), - }, - { - key: 'cost', - label: stringGetter({ key: STRING_KEYS.TOTAL_COST }), - value: ( - - ), - }, - { - key: 'priceImpact', - label: stringGetter({ key: STRING_KEYS.PRICE_IMPACT }), - value: ( - - MustBigNumber(nearestDatum.datum.price) - .minus(lowestAsk.price) - .div(nearestDatum.datum.price), - [DepthChartSeries.Bids]: () => - MustBigNumber(highestBid.price) - .minus(nearestDatum.datum.price) - .div(highestBid.price), - }[nearestDatum.key]()} - /> - ), - }, - ] - } - /> - - ); - }} + renderTooltip={({ tooltipData, colorScale }) => + chartPointAtPointer && ( + + ) + } /> diff --git a/src/views/charts/FundingChart/Tooltip.tsx b/src/views/charts/FundingChart/Tooltip.tsx new file mode 100644 index 0000000..5e5a87a --- /dev/null +++ b/src/views/charts/FundingChart/Tooltip.tsx @@ -0,0 +1,107 @@ +import type { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip'; +import { TooltipContent } from '@/components/visx/TooltipContent'; + +import { FundingRateResolution, type FundingChartDatum } from '@/constants/charts'; +import { STRING_KEYS } from '@/constants/localization'; +import { FundingDirection } from '@/constants/markets'; +import { useStringGetter } from '@/hooks'; + +import { Details, DetailsItem } from '@/components/Details'; +import { Output, OutputType, ShowSign } from '@/components/Output'; + +type FundingChartTooltipProps = { + fundingRateView: FundingRateResolution; + latestDatum: FundingChartDatum; +} & Pick, 'tooltipData'>; + +export const FundingChartTooltipContent = ({ + fundingRateView, + latestDatum, + tooltipData, +}: FundingChartTooltipProps) => { + const { nearestDatum } = tooltipData || {}; + const stringGetter = useStringGetter(); + + const tooltipDatum = nearestDatum?.datum ?? latestDatum; + const isShowingCurrentFundingRate = tooltipDatum === latestDatum; + + return ( + +

+ {isShowingCurrentFundingRate + ? stringGetter({ key: STRING_KEYS.CURRENT_FUNDING_RATE }) + : stringGetter({ key: STRING_KEYS.HISTORICAL_FUNDING_RATE })} +

+ +
+ ), + }, + { + key: 'fundingRate', + label: stringGetter({ + key: { + [FundingRateResolution.OneHour]: STRING_KEYS.RATE_1H, + [FundingRateResolution.EightHour]: STRING_KEYS.RATE_8H, + [FundingRateResolution.Annualized]: STRING_KEYS.ANNUALIZED, + }[fundingRateView], + }), + value: ( + + ), + }, + { + key: 'time', + label: isShowingCurrentFundingRate + ? 'Time Remaining' + : stringGetter({ key: STRING_KEYS.TIME }), + value: , + }, + ].filter(Boolean) as Array + } + /> + + ); +}; diff --git a/src/views/charts/FundingChart.tsx b/src/views/charts/FundingChart/index.tsx similarity index 61% rename from src/views/charts/FundingChart.tsx rename to src/views/charts/FundingChart/index.tsx index 75fbde0..136a22c 100644 --- a/src/views/charts/FundingChart.tsx +++ b/src/views/charts/FundingChart/index.tsx @@ -3,39 +3,27 @@ import { shallowEqual, useSelector } from 'react-redux'; import styled, { type AnyStyledComponent, css } from 'styled-components'; import { curveMonotoneX, curveStepAfter } from '@visx/curve'; -import { useBreakpoints, useStringGetter } from '@/hooks'; import { ButtonSize } from '@/constants/buttons'; +import { FundingRateResolution, type FundingChartDatum } from '@/constants/charts'; import { STRING_KEYS } from '@/constants/localization'; -import { SMALL_PERCENT_DECIMALS, TINY_PERCENT_DECIMALS } from '@/constants/numbers'; import { FundingDirection } from '@/constants/markets'; +import { SMALL_PERCENT_DECIMALS, TINY_PERCENT_DECIMALS } from '@/constants/numbers'; +import { useBreakpoints, useStringGetter } from '@/hooks'; import { breakpoints } from '@/styles'; -import { Details, DetailsItem } from '@/components/Details'; -import { Output, OutputType, ShowSign } from '@/components/Output'; +import { Output, OutputType } from '@/components/Output'; import { LoadingSpace } from '@/components/Loading/LoadingSpinner'; import { ToggleGroup } from '@/components/ToggleGroup'; import { TimeSeriesChart } from '@/components/visx/TimeSeriesChart'; import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput'; -import { TooltipContent } from '@/components/visx/TooltipContent'; import type { TooltipContextType } from '@visx/xychart'; import { calculateFundingRateHistory } from '@/state/perpetualsCalculators'; import { MustBigNumber } from '@/lib/numbers'; - -enum FundingRateResolution { - OneHour = 'OneHour', - EightHour = 'EightHour', - Annualized = 'Annualized', -} - -type FundingChartDatum = { - time: number; - fundingRate: number; - direction: FundingDirection; -}; +import { FundingChartTooltipContent } from './Tooltip'; const FUNDING_RATE_TIME_RESOLUTION = 60 * 60 * 1000; // 1 hour @@ -116,114 +104,20 @@ export const FundingChart = ({ selectedLocale }: ElementProps) => { { [FundingDirection.ToLong]: 'var(--color-negative)', [FundingDirection.ToShort]: 'var(--color-positive)', + [FundingDirection.None]: 'var(--color-layer-6)', }[tooltipDatum.direction] } /> ); }} - renderTooltip={({ tooltipData }) => { - const { nearestDatum } = tooltipData || {}; - - const tooltipDatum = nearestDatum?.datum ?? latestDatum; - const isShowingCurrentFundingRate = tooltipDatum === latestDatum; - - return ( - -

- {isShowingCurrentFundingRate - ? stringGetter({ key: STRING_KEYS.CURRENT_FUNDING_RATE }) - : stringGetter({ key: STRING_KEYS.HISTORICAL_FUNDING_RATE })} -

- -
- ), - }, - { - key: 'fundingRate1h', - label: stringGetter({ key: STRING_KEYS.RATE_1H }), - value: ( - - ), - }, - { - key: 'fundingRate8h', - label: stringGetter({ key: STRING_KEYS.RATE_8H }), - value: ( - - ), - }, - { - key: 'fundingRateAnnualized', - label: stringGetter({ key: STRING_KEYS.ANNUALIZED }), - value: ( - - ), - }, - { - key: 'time', - label: isShowingCurrentFundingRate - ? 'Time Remaining' - : stringGetter({ key: STRING_KEYS.TIME }), - value: , - }, - ].filter(Boolean) as Array - } - /> - - ); - }} - onTooltipContext={setTooltipContext} + renderTooltip={({ tooltipData }) => ( + + )} + onTooltipContext={(tooltipContext) => setTooltipContext(tooltipContext)} minZoomDomain={FUNDING_RATE_TIME_RESOLUTION * 4} numGridLines={1} slotEmpty={} @@ -290,6 +184,7 @@ Styled.FundingRateToggle = styled.div` Styled.CurrentFundingRate = styled.div<{ isShowing?: boolean }>` place-self: start center; padding: clamp(1.5rem, 9rem - 15%, 4rem); + pointer-events: none; font: var(--font-large-book);