re-arrange layout

This commit is contained in:
saml33 2023-11-23 22:26:46 +11:00
parent 1612c61819
commit 1e46d26141
11 changed files with 556 additions and 448 deletions

View File

@ -0,0 +1,67 @@
import mangoStore from '@store/mangoStore'
import { useTranslation } from 'next-i18next'
import { useEffect, useState } from 'react'
import { formatYAxis } from 'utils/formatting'
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
import NetDepositsChart from './NetDepositsChart'
const DepositsAndBorrows = () => {
const { t } = useTranslation(['common', 'token', 'trade'])
const mangoStats = mangoStore((s) => s.tokenStats.mangoStats)
const loadingStats = mangoStore((s) => s.tokenStats.loading)
const [borrowDaysToShow, setBorrowDaysToShow] = useState('30')
const [depositDaysToShow, setDepositDaysToShow] = useState('30')
const tokenStatsInitialLoad = mangoStore((s) => s.tokenStats.initialLoad)
useEffect(() => {
if (!tokenStatsInitialLoad) {
const actions = mangoStore.getState().actions
actions.fetchTokenStats()
}
}, [tokenStatsInitialLoad])
return (
<>
<h2 className="my-4 px-6 text-lg">Deposits and Borrows</h2>
<div className="grid grid-cols-2 border-t border-th-bkg-3">
<div className="col-span-2 border-b border-th-bkg-3 px-6 py-4 md:col-span-1 ">
<DetailedAreaOrBarChart
changeAsPercent
data={mangoStats}
daysToShow={depositDaysToShow}
setDaysToShow={setDepositDaysToShow}
loading={loadingStats}
heightClass="h-64"
loaderHeightClass="h-[350px]"
prefix="$"
tickFormat={(x) => `$${formatYAxis(x)}`}
title={t('total-deposit-value')}
xKey="date"
yKey={'depositValue'}
/>
</div>
<div className="col-span-2 border-b border-th-bkg-3 px-6 py-4 md:col-span-1 md:pl-6">
<DetailedAreaOrBarChart
changeAsPercent
data={mangoStats}
daysToShow={borrowDaysToShow}
setDaysToShow={setBorrowDaysToShow}
heightClass="h-64"
loaderHeightClass="h-[350px]"
loading={loadingStats}
prefix="$"
tickFormat={(x) => `$${formatYAxis(x)}`}
title={t('total-borrow-value')}
xKey="date"
yKey={'borrowValue'}
/>
</div>
</div>
<div className="col-span-2 border-b border-th-bkg-3 px-6 py-4 md:col-span-1 md:pl-6">
<NetDepositsChart />
</div>
</>
)
}
export default DepositsAndBorrows

View File

@ -0,0 +1,175 @@
import Switch from '@components/forms/Switch'
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
import mangoStore from '@store/mangoStore'
import usePerpStatsChartData from 'hooks/usePerpStatsChartData'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { MangoTokenStatsItem } from 'types'
import { formatYAxis } from 'utils/formatting'
import { groupPerpByHourlyInterval } from './Volume'
interface GroupedTokenDataItem extends MangoTokenStatsItem {
intervalStartMillis: number
}
const groupTokenByHourlyInterval = (
data: MangoTokenStatsItem[],
intervalDurationHours: number,
) => {
const intervalMillis = intervalDurationHours * 60 * 60 * 1000
const groupedData = []
let currentGroup: GroupedTokenDataItem | null = null
for (let i = 0; i < data.length; i++) {
const obj = data[i]
const date = new Date(obj.date)
const intervalStartMillis =
Math.floor(date.getTime() / intervalMillis) * intervalMillis
if (
!currentGroup ||
currentGroup.intervalStartMillis !== intervalStartMillis
) {
currentGroup = {
...obj,
intervalStartMillis: intervalStartMillis,
}
groupedData.push(currentGroup)
} else {
currentGroup.feesCollected += obj.feesCollected
}
}
return groupedData
}
const Fees = () => {
const { t } = useTranslation(['common', 'token', 'trade'])
const mangoStats = mangoStore((s) => s.tokenStats.mangoStats)
const loadingStats = mangoStore((s) => s.tokenStats.loading)
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
const [feesDaysToShow, setFeesDaysToShow] = useState('30')
const [showCumulativeFees, setShowCumulativeFees] = useState(true)
const [feesPerpDaysToShow, setFeesPerpDaysToShow] = useState('30')
const [showCumulativePerpFees, setShowCumulativePerpFees] = useState(true)
const { feeValues: perpFeeChartData } = usePerpStatsChartData()
const tokenFeesChartData = useMemo(() => {
if (!mangoStats.length) return []
if (showCumulativeFees) {
return mangoStats
} else {
const transformedData = []
for (let i = 1; i < mangoStats.length; i++) {
const currentInterval = { ...mangoStats[i] }
const previousInterval = mangoStats[i - 1]
// Calculate the absolute fees for the current interval
currentInterval.feesCollected =
currentInterval.feesCollected - previousInterval.feesCollected
transformedData.push(currentInterval)
}
transformedData.unshift(mangoStats[0])
if (feesDaysToShow === '30') {
return groupTokenByHourlyInterval(transformedData, 24)
} else if (feesDaysToShow === '7') {
return groupTokenByHourlyInterval(transformedData, 4)
} else return transformedData
}
}, [mangoStats, feesDaysToShow, showCumulativeFees])
const perpFeeValues = useMemo(() => {
if (!perpFeeChartData || !perpFeeChartData.length) return []
let feeChartData = perpFeeChartData
if (!showCumulativePerpFees) {
const transformedData = []
for (let i = 1; i < perpFeeChartData.length; i++) {
const currentInterval = { ...perpFeeChartData[i] }
const previousInterval = perpFeeChartData[i - 1]
// Calculate the absolute fees for the current interval
currentInterval.value = currentInterval.value - previousInterval.value
transformedData.push(currentInterval)
}
transformedData.unshift(perpFeeChartData[0])
if (feesDaysToShow === '30') {
feeChartData = groupPerpByHourlyInterval(transformedData, 24)
} else if (feesDaysToShow === '7') {
feeChartData = groupPerpByHourlyInterval(transformedData, 4)
} else feeChartData = transformedData
}
return feeChartData
}, [feesDaysToShow, perpFeeChartData, showCumulativePerpFees])
return (
<>
<h2 className="my-4 px-6 text-lg">Fees</h2>
<div className="grid grid-cols-2 border-t border-th-bkg-3">
<div className="col-span-2 flex flex-col justify-between border-b border-th-bkg-3 md:col-span-1">
<div className="px-4 pt-4 md:px-6">
<DetailedAreaOrBarChart
changeAsPercent
data={tokenFeesChartData}
daysToShow={feesDaysToShow}
setDaysToShow={setFeesDaysToShow}
heightClass="h-64"
loaderHeightClass="h-[350px]"
loading={loadingStats}
prefix="$"
tickFormat={(x) => `$${formatYAxis(x)}`}
title={t('token:token-fees-collected')}
tooltipContent={t('token:tooltip-token-fees-collected')}
xKey="date"
yKey={'feesCollected'}
chartType={showCumulativeFees ? 'area' : 'bar'}
/>
</div>
<div className="flex justify-end px-4 pb-4 md:px-6">
<Switch
checked={showCumulativeFees}
onChange={() => setShowCumulativeFees(!showCumulativeFees)}
small
>
{t('stats:show-cumulative')}
</Switch>
</div>
</div>
<div className="col-span-2 flex flex-col justify-between border-b border-th-bkg-3 md:col-span-1">
<div className="px-4 pt-4 md:px-6">
<DetailedAreaOrBarChart
changeAsPercent
data={perpFeeValues}
daysToShow={feesPerpDaysToShow}
setDaysToShow={setFeesPerpDaysToShow}
heightClass="h-64"
loading={loadingPerpStats}
loaderHeightClass="h-[350px]"
prefix="$"
tickFormat={(x) => `$${formatYAxis(x)}`}
title="Perp Fees"
xKey="date"
yKey="value"
chartType={showCumulativePerpFees ? 'area' : 'bar'}
/>
</div>
<div className="flex justify-end px-4 pb-4 md:px-6">
<Switch
checked={showCumulativePerpFees}
onChange={() =>
setShowCumulativePerpFees(!showCumulativePerpFees)
}
small
>
{t('stats:show-cumulative')}
</Switch>
</div>
</div>
</div>
</>
)
}
export default Fees

View File

@ -0,0 +1,24 @@
import LiquidationsAtRiskChart from './LiquidationsAtRiskChart'
import PerpLiquidationsChart from './PerpLiquidationsChart'
import TokenLiquidationsChart from './TokenLiquidationsChart'
const Liquidations = () => {
return (
<>
<h2 className="my-4 px-6 text-lg">Liquidations</h2>
<div className="grid grid-cols-2 border-t border-th-bkg-3">
<div className="col-span-2 border-b border-th-bkg-3 px-6 py-4 md:col-span-1 md:border-r">
<TokenLiquidationsChart />
</div>
<div className="col-span-2 border-b border-th-bkg-3 px-6 py-4 md:col-span-1 md:pl-6">
<PerpLiquidationsChart />
</div>
<div className="col-span-2 border-b border-th-bkg-3 px-6 py-4">
<LiquidationsAtRiskChart />
</div>
</div>
</>
)
}
export default Liquidations

View File

@ -1,21 +0,0 @@
import LiquidationsAtRiskChart from './LiquidationsAtRiskChart'
import PerpLiquidationsChart from './PerpLiquidationsChart'
import TokenLiquidationsChart from './TokenLiquidationsChart'
const LiquidationsCharts = () => {
return (
<>
<div className="col-span-2 border-b border-th-bkg-3 px-6 py-4 md:col-span-1">
<LiquidationsAtRiskChart />
</div>
<div className="col-span-2 border-b border-th-bkg-3 px-6 py-4 md:col-span-1 md:border-r">
<TokenLiquidationsChart />
</div>
<div className="col-span-2 border-b border-th-bkg-3 px-6 py-4 md:col-span-1 md:pl-6">
<PerpLiquidationsChart />
</div>
</>
)
}
export default LiquidationsCharts

View File

@ -1,264 +0,0 @@
import { useTranslation } from 'next-i18next'
import { useEffect, useMemo, useState } from 'react'
import mangoStore from '@store/mangoStore'
import { PerpStatsItem } from 'types'
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
import { formatYAxis } from 'utils/formatting'
import Switch from '@components/forms/Switch'
interface ValueItem {
date: string
value: number
}
interface PerpStatsData {
feeValues: ValueItem[]
openInterestValues: ValueItem[]
volumeValues: ValueItem[]
}
interface GroupedDataItem extends ValueItem {
intervalStartMillis: number
}
const groupByHourlyInterval = (
data: ValueItem[],
intervalDurationHours: number,
) => {
const intervalMillis = intervalDurationHours * 60 * 60 * 1000
const groupedData = []
let currentGroup: GroupedDataItem | null = null
for (let i = 0; i < data.length; i++) {
const obj = data[i]
const date = new Date(obj.date)
const intervalStartMillis =
Math.floor(date.getTime() / intervalMillis) * intervalMillis
if (
!currentGroup ||
currentGroup.intervalStartMillis !== intervalStartMillis
) {
currentGroup = {
...obj,
intervalStartMillis: intervalStartMillis,
}
groupedData.push(currentGroup)
} else {
currentGroup.value += obj.value
}
}
return groupedData
}
const MangoPerpStatsCharts = () => {
const { t } = useTranslation(['common', 'stats', 'token', 'trade'])
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
const perpStats = mangoStore((s) => s.perpStats.data)
const [feesDaysToShow, setFeesDaysToShow] = useState('30')
const [oiDaysToShow, setOiDaysToShow] = useState('30')
const [volumeDaysToShow, setVolumeDaysToShow] = useState('30')
const [showCumulativeFees, setShowCumulativeFees] = useState(true)
const [showCumulativeVolume, setShowCumulativeVolume] = useState(true)
useEffect(() => {
if (!perpStats || !perpStats.length) {
const actions = mangoStore.getState().actions
actions.fetchPerpStats()
}
}, [perpStats])
const [feeValues, openInterestValues, volumeValues] = useMemo(() => {
if (!perpStats || !perpStats.length) return [[], [], []]
const data = perpStats.reduce(
(a: PerpStatsData, c: PerpStatsItem) => {
const hasDateFee = a.feeValues.find(
(d: ValueItem) => d.date === c.date_hour,
)
const hasDateOpenInterest = a.openInterestValues.find(
(d: ValueItem) => d.date === c.date_hour,
)
const hasDateVolume = a.volumeValues.find(
(d: ValueItem) => d.date === c.date_hour,
)
if (!hasDateFee) {
a.feeValues.push({
date: c.date_hour,
value: c.total_fees,
})
} else {
hasDateFee.value += c.total_fees
}
if (!hasDateOpenInterest) {
a.openInterestValues.push({
date: c.date_hour,
value: Math.floor(c.open_interest * c.price),
})
} else {
hasDateOpenInterest.value += Math.floor(c.open_interest * c.price)
}
if (!hasDateVolume) {
a.volumeValues.push({
date: c.date_hour,
value: c.cumulative_quote_volume,
})
} else {
hasDateVolume.value += c.cumulative_quote_volume
}
return a
},
{ feeValues: [], openInterestValues: [], volumeValues: [] },
)
const { feeValues, openInterestValues, volumeValues } = data
const sortedFeeValues = feeValues.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
)
const sortedOpenInterestValues = openInterestValues.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
)
const sortedVolumeValues = volumeValues.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
)
let feeChartData = sortedFeeValues
if (!showCumulativeFees) {
const transformedData = []
for (let i = 1; i < sortedFeeValues.length; i++) {
const currentInterval = { ...sortedFeeValues[i] }
const previousInterval = sortedFeeValues[i - 1]
// Calculate the absolute fees for the current interval
currentInterval.value = currentInterval.value - previousInterval.value
transformedData.push(currentInterval)
}
transformedData.unshift(sortedFeeValues[0])
if (feesDaysToShow === '30') {
feeChartData = groupByHourlyInterval(transformedData, 24)
} else if (feesDaysToShow === '7') {
feeChartData = groupByHourlyInterval(transformedData, 4)
} else feeChartData = transformedData
}
let volumeChartData = sortedVolumeValues
if (!showCumulativeVolume) {
const transformedData = []
for (let i = 1; i < sortedVolumeValues.length; i++) {
const currentInterval = { ...sortedVolumeValues[i] }
const previousInterval = sortedVolumeValues[i - 1]
// Calculate the absolute fees for the current interval
currentInterval.value = currentInterval.value - previousInterval.value
transformedData.push(currentInterval)
}
transformedData.unshift(sortedVolumeValues[0])
if (volumeDaysToShow === '30') {
volumeChartData = groupByHourlyInterval(transformedData, 24)
} else if (volumeDaysToShow === '7') {
volumeChartData = groupByHourlyInterval(transformedData, 4)
} else volumeChartData = transformedData
}
return [feeChartData, sortedOpenInterestValues, volumeChartData]
}, [
feesDaysToShow,
perpStats,
showCumulativeFees,
showCumulativeVolume,
volumeDaysToShow,
])
return (
<>
{feeValues.length ? (
<div className="col-span-2 flex flex-col justify-between border-b border-th-bkg-3 md:col-span-1 md:border-r">
<div className="px-4 pt-4 md:px-6">
<DetailedAreaOrBarChart
changeAsPercent
data={feeValues}
daysToShow={feesDaysToShow}
setDaysToShow={setFeesDaysToShow}
heightClass="h-64"
loading={loadingPerpStats}
loaderHeightClass="h-[350px]"
prefix="$"
tickFormat={(x) => `$${formatYAxis(x)}`}
title="Perp Fees"
xKey="date"
yKey="value"
chartType={showCumulativeFees ? 'area' : 'bar'}
/>
</div>
<div className="mt-2 flex justify-end border-t border-th-bkg-3 px-4 py-2 md:px-6">
<Switch
checked={showCumulativeFees}
onChange={() => setShowCumulativeFees(!showCumulativeFees)}
small
>
{t('stats:show-cumulative')}
</Switch>
</div>
</div>
) : null}
{openInterestValues.length ? (
<div className="col-span-2 border-b border-th-bkg-3 px-4 py-4 md:col-span-1 md:px-6">
<DetailedAreaOrBarChart
changeAsPercent
data={openInterestValues}
daysToShow={oiDaysToShow}
setDaysToShow={setOiDaysToShow}
heightClass="h-64"
loading={loadingPerpStats}
loaderHeightClass="h-[350px]"
prefix="$"
tickFormat={(x) => `$${formatYAxis(x)}`}
title={t('stats:perp-open-interest')}
xKey="date"
yKey="value"
/>
</div>
) : null}
{volumeValues.length ? (
<div className="col-span-2 border-b border-th-bkg-3 md:col-span-1 md:border-r">
<div className="px-4 pt-4 md:px-6">
<DetailedAreaOrBarChart
changeAsPercent
data={volumeValues}
daysToShow={volumeDaysToShow}
setDaysToShow={setVolumeDaysToShow}
heightClass="h-64"
loading={loadingPerpStats}
loaderHeightClass="h-[350px]"
prefix="$"
tickFormat={(x) => `$${formatYAxis(x)}`}
title={t('stats:perp-volume')}
xKey="date"
yKey="value"
chartType={showCumulativeVolume ? 'area' : 'bar'}
/>
</div>
<div className="mt-2 flex justify-end border-t border-th-bkg-3 px-4 py-2 md:px-6">
<Switch
checked={showCumulativeVolume}
onChange={() => setShowCumulativeVolume(!showCumulativeVolume)}
small
>
{t('stats:show-cumulative')}
</Switch>
</div>
</div>
) : null}
</>
)
}
export default MangoPerpStatsCharts

View File

@ -1,14 +1,16 @@
import LiquidationsCharts from './LiquidationsCharts'
import MangoPerpStatsCharts from './MangoPerpStatsCharts'
import TokenStatsCharts from './TokenStatsCharts'
import Liquidations from './Liquidations'
import DepositsAndBorrows from './DepositsAndBorrows'
import Fees from './Fees'
import Volume from './Volume'
const MangoStats = () => {
return (
<div className="grid grid-cols-2">
<TokenStatsCharts />
<MangoPerpStatsCharts />
<LiquidationsCharts />
</div>
<>
<DepositsAndBorrows />
<Fees />
<Volume />
<Liquidations />
</>
)
}

View File

@ -0,0 +1,39 @@
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
import mangoStore from '@store/mangoStore'
import usePerpStatsChartData from 'hooks/usePerpStatsChartData'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { formatYAxis } from 'utils/formatting'
const OpenInterest = () => {
const { t } = useTranslation(['common', 'token', 'trade'])
const [oiDaysToShow, setOiDaysToShow] = useState('30')
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
const { openInterestValues } = usePerpStatsChartData()
return (
<>
<h2>Open Interest</h2>
<div className="grid grid-cols-1">
<div className="border-b border-th-bkg-3 px-4 py-4 md:px-6">
<DetailedAreaOrBarChart
changeAsPercent
data={openInterestValues}
daysToShow={oiDaysToShow}
setDaysToShow={setOiDaysToShow}
heightClass="h-64"
loading={loadingPerpStats}
loaderHeightClass="h-[350px]"
prefix="$"
tickFormat={(x) => `$${formatYAxis(x)}`}
title={t('stats:perp-open-interest')}
xKey="date"
yKey="value"
/>
</div>
</div>
</>
)
}
export default OpenInterest

View File

@ -0,0 +1,30 @@
import { useQuery } from '@tanstack/react-query'
import { MANGO_DATA_API_URL } from 'utils/constants'
export const fetchSwapVolume = async () => {
try {
const response = await fetch(
`${MANGO_DATA_API_URL}/stats/mango-swap-volume`,
)
const data = await response.json()
return data
} catch (e) {
console.log('Failed to fetch swap volume', e)
return []
}
}
const SwapVolumeChart = () => {
const { data } = useQuery(['mango-swap-volume'], () => fetchSwapVolume(), {
cacheTime: 1000 * 60 * 10,
staleTime: 1000 * 60,
retry: 3,
refetchOnWindowFocus: false,
})
console.log(data)
return <div />
}
export default SwapVolumeChart

View File

@ -1,155 +0,0 @@
import mangoStore from '@store/mangoStore'
import { useTranslation } from 'next-i18next'
import { useEffect, useMemo, useState } from 'react'
import { formatYAxis } from 'utils/formatting'
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
import { MangoTokenStatsItem } from 'types'
import Switch from '@components/forms/Switch'
import NetDepositsChart from './NetDepositsChart'
interface GroupedDataItem extends MangoTokenStatsItem {
intervalStartMillis: number
}
const groupByHourlyInterval = (
data: MangoTokenStatsItem[],
intervalDurationHours: number,
) => {
const intervalMillis = intervalDurationHours * 60 * 60 * 1000
const groupedData = []
let currentGroup: GroupedDataItem | null = null
for (let i = 0; i < data.length; i++) {
const obj = data[i]
const date = new Date(obj.date)
const intervalStartMillis =
Math.floor(date.getTime() / intervalMillis) * intervalMillis
if (
!currentGroup ||
currentGroup.intervalStartMillis !== intervalStartMillis
) {
currentGroup = {
...obj,
intervalStartMillis: intervalStartMillis,
}
groupedData.push(currentGroup)
} else {
currentGroup.feesCollected += obj.feesCollected
}
}
return groupedData
}
const TokenStatsCharts = () => {
const { t } = useTranslation(['common', 'token', 'trade'])
const mangoStats = mangoStore((s) => s.tokenStats.mangoStats)
const loadingStats = mangoStore((s) => s.tokenStats.loading)
const [borrowDaysToShow, setBorrowDaysToShow] = useState('30')
const [depositDaysToShow, setDepositDaysToShow] = useState('30')
const [feesDaysToShow, setFeesDaysToShow] = useState('30')
const [showCumulativeFees, setShowCumulativeFees] = useState(true)
const tokenStatsInitialLoad = mangoStore((s) => s.tokenStats.initialLoad)
useEffect(() => {
if (!tokenStatsInitialLoad) {
const actions = mangoStore.getState().actions
actions.fetchTokenStats()
}
}, [tokenStatsInitialLoad])
const tokenFeesChartData = useMemo(() => {
if (!mangoStats.length) return []
if (showCumulativeFees) {
return mangoStats
} else {
const transformedData = []
for (let i = 1; i < mangoStats.length; i++) {
const currentInterval = { ...mangoStats[i] }
const previousInterval = mangoStats[i - 1]
// Calculate the absolute fees for the current interval
currentInterval.feesCollected =
currentInterval.feesCollected - previousInterval.feesCollected
transformedData.push(currentInterval)
}
transformedData.unshift(mangoStats[0])
if (feesDaysToShow === '30') {
return groupByHourlyInterval(transformedData, 24)
} else if (feesDaysToShow === '7') {
return groupByHourlyInterval(transformedData, 4)
} else return transformedData
}
}, [mangoStats, feesDaysToShow, showCumulativeFees])
return (
<>
<div className="col-span-2 border-b border-th-bkg-3 px-6 py-4 md:col-span-1 md:border-r">
<DetailedAreaOrBarChart
changeAsPercent
data={mangoStats}
daysToShow={depositDaysToShow}
setDaysToShow={setDepositDaysToShow}
loading={loadingStats}
heightClass="h-64"
loaderHeightClass="h-[350px]"
prefix="$"
tickFormat={(x) => `$${formatYAxis(x)}`}
title={t('total-deposit-value')}
xKey="date"
yKey={'depositValue'}
/>
</div>
<div className="col-span-2 border-b border-th-bkg-3 px-6 py-4 md:col-span-1 md:pl-6">
<DetailedAreaOrBarChart
changeAsPercent
data={mangoStats}
daysToShow={borrowDaysToShow}
setDaysToShow={setBorrowDaysToShow}
heightClass="h-64"
loaderHeightClass="h-[350px]"
loading={loadingStats}
prefix="$"
tickFormat={(x) => `$${formatYAxis(x)}`}
title={t('total-borrow-value')}
xKey="date"
yKey={'borrowValue'}
/>
</div>
<div className="col-span-2 border-b border-th-bkg-3 px-6 py-4 md:col-span-1 md:border-r md:pl-6">
<NetDepositsChart />
</div>
<div className="col-span-2 flex flex-col justify-between border-b border-th-bkg-3 md:col-span-1">
<div className="px-4 pt-4 md:px-6">
<DetailedAreaOrBarChart
changeAsPercent
data={tokenFeesChartData}
daysToShow={feesDaysToShow}
setDaysToShow={setFeesDaysToShow}
heightClass="h-64"
loaderHeightClass="h-[350px]"
loading={loadingStats}
prefix="$"
tickFormat={(x) => `$${formatYAxis(x)}`}
title={t('token:token-fees-collected')}
tooltipContent={t('token:tooltip-token-fees-collected')}
xKey="date"
yKey={'feesCollected'}
chartType={showCumulativeFees ? 'area' : 'bar'}
/>
</div>
<div className="mt-2 flex justify-end border-t border-th-bkg-3 px-4 py-2 md:px-6">
<Switch
checked={showCumulativeFees}
onChange={() => setShowCumulativeFees(!showCumulativeFees)}
small
>
{t('stats:show-cumulative')}
</Switch>
</div>
</div>
</>
)
}
export default TokenStatsCharts

View File

@ -0,0 +1,121 @@
import { useTranslation } from 'next-i18next'
import { useMemo, useState } from 'react'
import mangoStore from '@store/mangoStore'
import DetailedAreaOrBarChart from '@components/shared/DetailedAreaOrBarChart'
import { formatYAxis } from 'utils/formatting'
import Switch from '@components/forms/Switch'
import usePerpStatsChartData from 'hooks/usePerpStatsChartData'
import SwapVolumeChart from './SwapVolumeChart'
export interface PerpValueItem {
date: string
value: number
}
interface GroupedDataPerpItem extends PerpValueItem {
intervalStartMillis: number
}
export const groupPerpByHourlyInterval = (
data: PerpValueItem[],
intervalDurationHours: number,
) => {
const intervalMillis = intervalDurationHours * 60 * 60 * 1000
const groupedData = []
let currentGroup: GroupedDataPerpItem | null = null
for (let i = 0; i < data.length; i++) {
const obj = data[i]
const date = new Date(obj.date)
const intervalStartMillis =
Math.floor(date.getTime() / intervalMillis) * intervalMillis
if (
!currentGroup ||
currentGroup.intervalStartMillis !== intervalStartMillis
) {
currentGroup = {
...obj,
intervalStartMillis: intervalStartMillis,
}
groupedData.push(currentGroup)
} else {
currentGroup.value += obj.value
}
}
return groupedData
}
const Volume = () => {
const { t } = useTranslation(['common', 'stats', 'token', 'trade'])
const loadingPerpStats = mangoStore((s) => s.perpStats.loading)
const [perpVolumeDaysToShow, setPerpPerpVolumeDaysToShow] = useState('30')
const [showCumulativePerpVolume, setShowCumulativePerpVolume] = useState(true)
const { volumeValues: perpVolumeChartData } = usePerpStatsChartData()
const perpVolumeValues = useMemo(() => {
if (!perpVolumeChartData || !perpVolumeChartData.length) return []
let volumeChartData = perpVolumeChartData
if (!showCumulativePerpVolume) {
const transformedData = []
for (let i = 1; i < perpVolumeChartData.length; i++) {
const currentInterval = { ...perpVolumeChartData[i] }
const previousInterval = perpVolumeChartData[i - 1]
// Calculate the absolute fees for the current interval
currentInterval.value = currentInterval.value - previousInterval.value
transformedData.push(currentInterval)
}
transformedData.unshift(perpVolumeChartData[0])
if (perpVolumeDaysToShow === '30') {
volumeChartData = groupPerpByHourlyInterval(transformedData, 24)
} else if (perpVolumeDaysToShow === '7') {
volumeChartData = groupPerpByHourlyInterval(transformedData, 4)
} else volumeChartData = transformedData
}
return volumeChartData
}, [perpVolumeDaysToShow, perpVolumeChartData, showCumulativePerpVolume])
return (
<>
<h2 className="my-4 px-6 text-lg">Volume</h2>
<div className="grid grid-cols-2 border-t border-th-bkg-3">
<div className="col-span-2 border-b border-th-bkg-3">
<div className="px-4 pt-4 md:px-6">
<DetailedAreaOrBarChart
changeAsPercent
data={perpVolumeValues}
daysToShow={perpVolumeDaysToShow}
setDaysToShow={setPerpPerpVolumeDaysToShow}
heightClass="h-64"
loading={loadingPerpStats}
loaderHeightClass="h-[350px]"
prefix="$"
tickFormat={(x) => `$${formatYAxis(x)}`}
title={t('stats:perp-volume')}
xKey="date"
yKey="value"
chartType={showCumulativePerpVolume ? 'area' : 'bar'}
/>
</div>
<div className="flex justify-end px-4 pb-4 md:px-6">
<Switch
checked={showCumulativePerpVolume}
onChange={() =>
setShowCumulativePerpVolume(!showCumulativePerpVolume)
}
small
>
{t('stats:show-cumulative')}
</Switch>
</div>
</div>
<SwapVolumeChart />
</div>
</>
)
}
export default Volume

View File

@ -0,0 +1,90 @@
import mangoStore from '@store/mangoStore'
import { useEffect, useMemo } from 'react'
import { PerpStatsItem } from 'types'
export interface PerpValueItem {
date: string
value: number
}
interface PerpStatsData {
feeValues: PerpValueItem[]
openInterestValues: PerpValueItem[]
volumeValues: PerpValueItem[]
}
export default function usePerpStatsChartData() {
const perpStats = mangoStore((s) => s.perpStats.data)
useEffect(() => {
if (!perpStats || !perpStats.length) {
const actions = mangoStore.getState().actions
actions.fetchPerpStats()
}
}, [perpStats])
const [feeValues, openInterestValues, volumeValues] = useMemo(() => {
if (!perpStats || !perpStats.length) return [[], [], []]
const data = perpStats.reduce(
(a: PerpStatsData, c: PerpStatsItem) => {
const hasDateFee = a.feeValues.find(
(d: PerpValueItem) => d.date === c.date_hour,
)
const hasDateOpenInterest = a.openInterestValues.find(
(d: PerpValueItem) => d.date === c.date_hour,
)
const hasDateVolume = a.volumeValues.find(
(d: PerpValueItem) => d.date === c.date_hour,
)
if (!hasDateFee) {
a.feeValues.push({
date: c.date_hour,
value: c.total_fees,
})
} else {
hasDateFee.value += c.total_fees
}
if (!hasDateOpenInterest) {
a.openInterestValues.push({
date: c.date_hour,
value: Math.floor(c.open_interest * c.price),
})
} else {
hasDateOpenInterest.value += Math.floor(c.open_interest * c.price)
}
if (!hasDateVolume) {
a.volumeValues.push({
date: c.date_hour,
value: c.cumulative_quote_volume,
})
} else {
hasDateVolume.value += c.cumulative_quote_volume
}
return a
},
{ feeValues: [], openInterestValues: [], volumeValues: [] },
)
const { feeValues, openInterestValues, volumeValues } = data
const sortedFeeValues = feeValues.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
)
const sortedOpenInterestValues = openInterestValues.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
)
const sortedVolumeValues = volumeValues.sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
)
return [sortedFeeValues, sortedOpenInterestValues, sortedVolumeValues]
}, [perpStats])
return { feeValues, openInterestValues, volumeValues }
}