Merge pull request #142 from blockworks-foundation/improve-account-charts
improve account charts
This commit is contained in:
commit
a538e8aaad
|
@ -1,7 +1,17 @@
|
|||
import { FunctionComponent, useState, ReactNode } from 'react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import dayjs from 'dayjs'
|
||||
import { AreaChart, Area, XAxis, YAxis, Tooltip, BarChart, Bar } from 'recharts'
|
||||
import {
|
||||
AreaChart,
|
||||
Area,
|
||||
Cell,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Tooltip,
|
||||
BarChart,
|
||||
Bar,
|
||||
ReferenceLine,
|
||||
} from 'recharts'
|
||||
import useDimensions from 'react-cool-dimensions'
|
||||
import { numberCompactFormatter } from '../utils'
|
||||
|
||||
|
@ -17,6 +27,9 @@ interface ChartProps {
|
|||
labelFormat: (x) => ReactNode
|
||||
tickFormat?: (x) => any
|
||||
showAll?: boolean
|
||||
titleValue?: number
|
||||
useMulticoloredBars?: boolean
|
||||
zeroLine?: boolean
|
||||
}
|
||||
|
||||
const Chart: FunctionComponent<ChartProps> = ({
|
||||
|
@ -31,6 +44,9 @@ const Chart: FunctionComponent<ChartProps> = ({
|
|||
hideRangeFilters,
|
||||
yAxisWidth,
|
||||
showAll = false,
|
||||
titleValue,
|
||||
useMulticoloredBars,
|
||||
zeroLine,
|
||||
}) => {
|
||||
const [mouseData, setMouseData] = useState<string | null>(null)
|
||||
const [daysToShow, setDaysToShow] = useState(daysRange || 30)
|
||||
|
@ -82,10 +98,14 @@ const Chart: FunctionComponent<ChartProps> = ({
|
|||
) : data.length > 0 ? (
|
||||
<>
|
||||
<div className="pb-1 text-xl text-th-fgd-1">
|
||||
{labelFormat(data[data.length - 1][yAxis])}
|
||||
{titleValue
|
||||
? labelFormat(titleValue)
|
||||
: labelFormat(data[data.length - 1][yAxis])}
|
||||
</div>
|
||||
<div className="text-xs font-normal text-th-fgd-4">
|
||||
{new Date(data[data.length - 1][xAxis]).toDateString()}
|
||||
<div className="text-xs font-normal h-4 text-th-fgd-4">
|
||||
{titleValue
|
||||
? ''
|
||||
: new Date(data[data.length - 1][xAxis]).toDateString()}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
|
@ -199,6 +219,15 @@ const Chart: FunctionComponent<ChartProps> = ({
|
|||
type="number"
|
||||
width={yAxisWidth || 50}
|
||||
/>
|
||||
{zeroLine ? (
|
||||
<ReferenceLine
|
||||
y={0}
|
||||
stroke={
|
||||
theme === 'Light' ? 'rgba(0,0,0,0.4)' : 'rgba(255,255,255,0.35)'
|
||||
}
|
||||
strokeDasharray="3 3"
|
||||
/>
|
||||
) : null}
|
||||
</AreaChart>
|
||||
) : null}
|
||||
{width > 0 && type === 'bar' ? (
|
||||
|
@ -223,17 +252,49 @@ const Chart: FunctionComponent<ChartProps> = ({
|
|||
content={<></>}
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient id="gradientBar" x1="0" y1="0" x2="0" y2="1">
|
||||
<linearGradient id="defaultGradientBar" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#ffba24" stopOpacity={1} />
|
||||
<stop offset="100%" stopColor="#ffba24" stopOpacity={0.5} />
|
||||
</linearGradient>
|
||||
<linearGradient id="greenGradientBar" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor={theme === 'Mango' ? '#AFD803' : '#5EBF4D'}
|
||||
stopOpacity={1}
|
||||
/>
|
||||
<stop
|
||||
offset="100%"
|
||||
stopColor={theme === 'Mango' ? '#AFD803' : '#5EBF4D'}
|
||||
stopOpacity={0.5}
|
||||
/>
|
||||
</linearGradient>
|
||||
<linearGradient id="redGradientBar" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor={theme === 'Mango' ? '#F84638' : '#CC2929'}
|
||||
stopOpacity={1}
|
||||
/>
|
||||
<stop
|
||||
offset="100%"
|
||||
stopColor={theme === 'Mango' ? '#F84638' : '#CC2929'}
|
||||
stopOpacity={0.5}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Bar
|
||||
isAnimationActive={false}
|
||||
type="monotone"
|
||||
dataKey={yAxis}
|
||||
fill="url(#gradientBar)"
|
||||
/>
|
||||
<Bar dataKey={yAxis}>
|
||||
{data.map((entry, index) => (
|
||||
<Cell
|
||||
key={`cell-${index}`}
|
||||
fill={
|
||||
useMulticoloredBars
|
||||
? entry[yAxis] > 0
|
||||
? 'url(#greenGradientBar)'
|
||||
: 'url(#redGradientBar)'
|
||||
: 'url(#defaultGradientBar)'
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Bar>
|
||||
<XAxis
|
||||
dataKey={xAxis}
|
||||
axisLine={false}
|
||||
|
@ -272,6 +333,14 @@ const Chart: FunctionComponent<ChartProps> = ({
|
|||
type="number"
|
||||
width={yAxisWidth || 50}
|
||||
/>
|
||||
{zeroLine ? (
|
||||
<ReferenceLine
|
||||
y={0}
|
||||
stroke={
|
||||
theme === 'Light' ? 'rgba(0,0,0,0.4)' : 'rgba(255,255,255,0.2)'
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</BarChart>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
@ -26,7 +26,7 @@ const AccountFunding = () => {
|
|||
const mangoAccount = useMangoStore((s) => s.selectedMangoAccount.current)
|
||||
const [fundingStats, setFundingStats] = useState<any>([])
|
||||
const [hourlyFunding, setHourlyFunding] = useState<any>([])
|
||||
const [selectedAsset, setSelectedAsset] = useState<string>('BTC')
|
||||
const [selectedAsset, setSelectedAsset] = useState<string>('')
|
||||
const [loadHourlyStats, setLoadHourlyStats] = useState(false)
|
||||
const [loadTotalStats, setLoadTotalStats] = useState(false)
|
||||
const {
|
||||
|
@ -44,7 +44,6 @@ const AccountFunding = () => {
|
|||
false
|
||||
)
|
||||
const [chartData, setChartData] = useState([])
|
||||
const [showHours, setShowHours] = useState(false)
|
||||
|
||||
const mangoAccountPk = useMemo(() => {
|
||||
return mangoAccount.publicKey.toString()
|
||||
|
@ -168,6 +167,16 @@ const AccountFunding = () => {
|
|||
|
||||
const dailyFunding = []
|
||||
|
||||
for (let i = 0; i < 30; i++) {
|
||||
dailyFunding.push({
|
||||
funding: 0,
|
||||
time: new Date(
|
||||
// @ts-ignore
|
||||
dayjs().utc().hour(0).minute(0).subtract(i, 'day')
|
||||
).getTime(),
|
||||
})
|
||||
}
|
||||
|
||||
filtered.forEach((d) => {
|
||||
const found = dailyFunding.find(
|
||||
(x) =>
|
||||
|
@ -176,27 +185,10 @@ const AccountFunding = () => {
|
|||
if (found) {
|
||||
const newFunding = d.total_funding
|
||||
found.funding = found.funding + newFunding
|
||||
} else {
|
||||
dailyFunding.push({
|
||||
time: d.time,
|
||||
funding: d.total_funding,
|
||||
})
|
||||
}
|
||||
})
|
||||
if (dailyFunding.length <= 1) {
|
||||
const chartFunding = []
|
||||
hourlyFunding[selectedAsset].forEach((a) => {
|
||||
chartFunding.push({
|
||||
funding: a.total_funding,
|
||||
time: a.time,
|
||||
})
|
||||
})
|
||||
setShowHours(true)
|
||||
setChartData(chartFunding.reverse())
|
||||
} else {
|
||||
setShowHours(false)
|
||||
setChartData(dailyFunding.reverse())
|
||||
}
|
||||
|
||||
setChartData(dailyFunding.reverse())
|
||||
}
|
||||
}, [hourlyFunding, selectedAsset])
|
||||
|
||||
|
@ -339,30 +331,35 @@ const AccountFunding = () => {
|
|||
</div>
|
||||
{selectedAsset && chartData.length > 0 ? (
|
||||
<div className="flex flex-col sm:flex-row space-x-0 sm:space-x-4 w-full">
|
||||
<div
|
||||
className="border border-th-bkg-4 relative mb-6 p-4 rounded-md w-full"
|
||||
style={{ height: '330px' }}
|
||||
>
|
||||
<Chart
|
||||
daysRange={showHours ? 1 : 30}
|
||||
hideRangeFilters
|
||||
title={t('funding-chart-title', {
|
||||
symbol: selectedAsset,
|
||||
})}
|
||||
xAxis="time"
|
||||
yAxis="funding"
|
||||
data={chartData}
|
||||
labelFormat={(x) =>
|
||||
x &&
|
||||
`${x?.toLocaleString(undefined, {
|
||||
maximumFractionDigits: 6,
|
||||
})} USDC`
|
||||
}
|
||||
tickFormat={handleDustTicks}
|
||||
type="bar"
|
||||
yAxisWidth={70}
|
||||
/>
|
||||
</div>
|
||||
{chartData.find((d) => d.funding !== 0) ? (
|
||||
<div
|
||||
className="border border-th-bkg-4 relative mb-6 p-4 rounded-md w-full"
|
||||
style={{ height: '330px' }}
|
||||
>
|
||||
<Chart
|
||||
hideRangeFilters
|
||||
title={t('funding-chart-title')}
|
||||
xAxis="time"
|
||||
yAxis="funding"
|
||||
data={chartData}
|
||||
labelFormat={(x) =>
|
||||
x &&
|
||||
`${x?.toLocaleString(undefined, {
|
||||
maximumFractionDigits: 6,
|
||||
})} USDC`
|
||||
}
|
||||
tickFormat={handleDustTicks}
|
||||
titleValue={chartData.reduce(
|
||||
(a, c) => a + c.funding,
|
||||
0
|
||||
)}
|
||||
type="bar"
|
||||
useMulticoloredBars
|
||||
yAxisWidth={70}
|
||||
zeroLine
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
import { getTokenBySymbol } from '@blockworks-foundation/mango-client'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
// import { CurrencyDollarIcon } from '@heroicons/react/outline'
|
||||
import useMangoStore from '../../stores/useMangoStore'
|
||||
import Select from '../Select'
|
||||
import { Table, Td, Th, TrBody, TrHead } from '../TableElements'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { isEmpty } from 'lodash'
|
||||
import usePagination from '../../hooks/usePagination'
|
||||
import {
|
||||
// formatUsdValue,
|
||||
numberCompactFormatter,
|
||||
roundToDecimal,
|
||||
} from '../../utils/'
|
||||
import { numberCompactFormatter, roundToDecimal } from '../../utils/'
|
||||
import Pagination from '../Pagination'
|
||||
import { useViewport } from '../../hooks/useViewport'
|
||||
import { breakpoints } from '../TradePageGrid'
|
||||
|
@ -36,14 +31,14 @@ interface InterestStats {
|
|||
}
|
||||
|
||||
export const handleDustTicks = (v) =>
|
||||
v < 0.000001
|
||||
Math.abs(v) < 0.0000099
|
||||
? v === 0
|
||||
? 0
|
||||
: v.toExponential()
|
||||
: numberCompactFormatter.format(v)
|
||||
|
||||
const handleUsdDustTicks = (v) =>
|
||||
v < 0.000001
|
||||
Math.abs(v) < 0.0000099
|
||||
? v === 0
|
||||
? '$0'
|
||||
: `$${v.toExponential()}`
|
||||
|
@ -58,12 +53,10 @@ const AccountInterest = () => {
|
|||
const mangoCache = useMangoStore((s) => s.selectedMangoGroup.cache)
|
||||
const [interestStats, setInterestStats] = useState<any>([])
|
||||
const [hourlyInterestStats, setHourlyInterestStats] = useState<any>({})
|
||||
// const [totalInterestValue, setTotalInterestValue] = useState(null)
|
||||
const [loadHourlyStats, setLoadHourlyStats] = useState(false)
|
||||
const [loadTotalStats, setLoadTotalStats] = useState(false)
|
||||
const [selectedAsset, setSelectedAsset] = useState<string>('')
|
||||
const [chartData, setChartData] = useState([])
|
||||
const [showHours, setShowHours] = useState(false)
|
||||
const {
|
||||
paginatedData,
|
||||
setData,
|
||||
|
@ -232,22 +225,6 @@ const AccountInterest = () => {
|
|||
getStats()
|
||||
}, [mangoAccountPk, hideInterestDust])
|
||||
|
||||
// For net interest value to be useful we would need to filter on the dates of the user's financial year and convert the USD value below to the user's home currency.
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log(Object.entries(hourlyInterestStats).flat(Infinity))
|
||||
// const totalInterestValue = Object.entries(hourlyInterestStats)
|
||||
// .flat(Infinity)
|
||||
// .reduce((a: number, c: any) => {
|
||||
// if (c.time) {
|
||||
// return (
|
||||
// a + (c.deposit_interest * c.price - c.borrow_interest * c.price)
|
||||
// )
|
||||
// } else return a
|
||||
// }, 0)
|
||||
// setTotalInterestValue(totalInterestValue)
|
||||
// }, [hourlyInterestStats])
|
||||
|
||||
useEffect(() => {
|
||||
if (hourlyInterestStats[selectedAsset]) {
|
||||
const start = new Date(
|
||||
|
@ -261,6 +238,17 @@ const AccountInterest = () => {
|
|||
|
||||
const dailyInterest = []
|
||||
|
||||
for (let i = 0; i < 30; i++) {
|
||||
dailyInterest.push({
|
||||
interest: 0,
|
||||
value: 0,
|
||||
time: new Date(
|
||||
// @ts-ignore
|
||||
dayjs().utc().hour(0).minute(0).subtract(i, 'day')
|
||||
).getTime(),
|
||||
})
|
||||
}
|
||||
|
||||
filtered.forEach((d) => {
|
||||
const found = dailyInterest.find(
|
||||
(x) =>
|
||||
|
@ -275,43 +263,9 @@ const AccountInterest = () => {
|
|||
: d.deposit_interest * d.price
|
||||
found.interest = found.interest + newInterest
|
||||
found.value = found.value + newValue
|
||||
} else {
|
||||
dailyInterest.push({
|
||||
// @ts-ignore
|
||||
time: new Date(d.time).getTime(),
|
||||
interest:
|
||||
d.borrow_interest > 0
|
||||
? d.borrow_interest * -1
|
||||
: d.deposit_interest,
|
||||
value:
|
||||
d.borrow_interest > 0
|
||||
? d.borrow_interest * d.price * -1
|
||||
: d.deposit_interest * d.price,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (dailyInterest.length === 1) {
|
||||
const chartInterest = []
|
||||
filtered.forEach((a) => {
|
||||
chartInterest.push({
|
||||
time: new Date(a.time).getTime(),
|
||||
interest:
|
||||
a.borrow_interest > 0
|
||||
? a.borrow_interest * -1
|
||||
: a.deposit_interest,
|
||||
value:
|
||||
a.borrow_interest > 0
|
||||
? a.borrow_interest * a.price * -1
|
||||
: a.deposit_interest * a.price,
|
||||
})
|
||||
})
|
||||
setShowHours(true)
|
||||
setChartData(chartInterest.reverse())
|
||||
} else {
|
||||
setShowHours(false)
|
||||
setChartData(dailyInterest.reverse())
|
||||
}
|
||||
setChartData(dailyInterest.reverse())
|
||||
}
|
||||
}, [hourlyInterestStats, selectedAsset])
|
||||
|
||||
|
@ -472,22 +426,6 @@ const AccountInterest = () => {
|
|||
})}
|
||||
</>
|
||||
)}
|
||||
{/* {totalInterestValue > 0 ? (
|
||||
<div className="border border-th-bkg-4 mt-8 p-3 sm:p-4 rounded-md sm:rounded-lg">
|
||||
<div className="font-bold pb-0.5 text-th-fgd-1 text-xs sm:text-sm">
|
||||
{t('net-interest-value')}
|
||||
</div>
|
||||
<div className="pb-0.5 sm:pb-2 text-th-fgd-3 text-xs">
|
||||
{t('net-interest-value-desc')}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<CurrencyDollarIcon className="flex-shrink-0 h-5 w-5 sm:h-7 sm:w-7 mr-1.5 text-th-primary" />
|
||||
<div className="font-bold text-th-fgd-1 text-xl sm:text-2xl">
|
||||
{formatUsdValue(totalInterestValue)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null} */}
|
||||
<>
|
||||
{!isEmpty(hourlyInterestStats) && !loadHourlyStats ? (
|
||||
<>
|
||||
|
@ -532,47 +470,66 @@ const AccountInterest = () => {
|
|||
</div>
|
||||
{selectedAsset && chartData.length > 0 ? (
|
||||
<div className="flex flex-col sm:flex-row space-x-0 sm:space-x-4 w-full">
|
||||
<div
|
||||
className="border border-th-bkg-4 relative mb-6 p-4 rounded-md w-full sm:w-1/2"
|
||||
style={{ height: '330px' }}
|
||||
>
|
||||
<Chart
|
||||
daysRange={showHours ? 1 : 30}
|
||||
hideRangeFilters
|
||||
title={t('interest-chart-title', {
|
||||
symbol: selectedAsset,
|
||||
})}
|
||||
xAxis="time"
|
||||
yAxis="interest"
|
||||
data={chartData}
|
||||
labelFormat={(x) => x && x?.toFixed(token.decimals + 1)}
|
||||
tickFormat={handleDustTicks}
|
||||
type="bar"
|
||||
yAxisWidth={increaseYAxisWidth ? 70 : 50}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="border border-th-bkg-4 relative mb-6 p-4 rounded-md w-full sm:w-1/2"
|
||||
style={{ height: '330px' }}
|
||||
>
|
||||
<Chart
|
||||
hideRangeFilters
|
||||
title={t('interest-chart-value-title', {
|
||||
symbol: selectedAsset,
|
||||
})}
|
||||
xAxis="time"
|
||||
yAxis="value"
|
||||
data={chartData}
|
||||
labelFormat={(x) =>
|
||||
x && x < 0
|
||||
? `-$${Math.abs(x)?.toFixed(token.decimals + 1)}`
|
||||
: `$${x?.toFixed(token.decimals + 1)}`
|
||||
}
|
||||
tickFormat={handleUsdDustTicks}
|
||||
type="bar"
|
||||
yAxisWidth={increaseYAxisWidth ? 70 : 50}
|
||||
/>
|
||||
</div>
|
||||
{chartData.find((d) => d.interest !== 0) ? (
|
||||
<div
|
||||
className="border border-th-bkg-4 relative mb-6 p-4 rounded-md w-full sm:w-1/2"
|
||||
style={{ height: '330px' }}
|
||||
>
|
||||
<Chart
|
||||
hideRangeFilters
|
||||
title={t('interest-chart-title', {
|
||||
symbol: selectedAsset,
|
||||
})}
|
||||
xAxis="time"
|
||||
yAxis="interest"
|
||||
data={chartData}
|
||||
labelFormat={(x) =>
|
||||
x === 0 ? 0 : x.toFixed(token.decimals + 1)
|
||||
}
|
||||
tickFormat={handleDustTicks}
|
||||
titleValue={chartData.reduce(
|
||||
(a, c) => a + c.interest,
|
||||
0
|
||||
)}
|
||||
type="bar"
|
||||
useMulticoloredBars
|
||||
yAxisWidth={increaseYAxisWidth ? 70 : 50}
|
||||
zeroLine
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{chartData.find((d) => d.value !== 0) ? (
|
||||
<div
|
||||
className="border border-th-bkg-4 relative mb-6 p-4 rounded-md w-full sm:w-1/2"
|
||||
style={{ height: '330px' }}
|
||||
>
|
||||
<Chart
|
||||
hideRangeFilters
|
||||
title={t('interest-chart-value-title', {
|
||||
symbol: selectedAsset,
|
||||
})}
|
||||
xAxis="time"
|
||||
yAxis="value"
|
||||
data={chartData}
|
||||
labelFormat={(x) =>
|
||||
x === 0
|
||||
? 0
|
||||
: x < 0
|
||||
? `-$${Math.abs(x)?.toFixed(token.decimals + 1)}`
|
||||
: `$${x?.toFixed(token.decimals + 1)}`
|
||||
}
|
||||
tickFormat={handleUsdDustTicks}
|
||||
titleValue={chartData.reduce(
|
||||
(a, c) => a + c.value,
|
||||
0
|
||||
)}
|
||||
type="bar"
|
||||
useMulticoloredBars
|
||||
yAxisWidth={increaseYAxisWidth ? 70 : 50}
|
||||
zeroLine
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
|
|
|
@ -137,7 +137,7 @@
|
|||
"fee-discount": "Fee Discount",
|
||||
"first-deposit-desc": "There is a one-time cost of 0.035 SOL when you make your first deposit. This covers the rent on the Solana Blockchain for your account.",
|
||||
"funding": "Funding",
|
||||
"funding-chart-title": "Funding (Last 30 days)",
|
||||
"funding-chart-title": "Funding – Last 30 days (current bar is delayed)",
|
||||
"get-started": "Get Started",
|
||||
"health": "Health",
|
||||
"health-check": "Account Health Check",
|
||||
|
@ -159,8 +159,8 @@
|
|||
"insufficient-balance-withdraw": "Insufficient balance. Borrow funds to withdraw",
|
||||
"insufficient-sol": "There is a one-time cost of 0.035 SOL to create a Mango Account. This covers the rent on the Solana Blockchain.",
|
||||
"interest": "Interest",
|
||||
"interest-chart-title": "{{symbol}} Interest (Last 30 days)",
|
||||
"interest-chart-value-title": "{{symbol}} Interest Value (Last 30 days)",
|
||||
"interest-chart-title": "{{symbol}} Interest – Last 30 days (current bar is delayed)",
|
||||
"interest-chart-value-title": "{{symbol}} Interest Value – Last 30 days (current bar is delayed)",
|
||||
"interest-earned": "Total Interest",
|
||||
"interest-info": "Interest is earned continuously on all deposits.",
|
||||
"intro-feature-1": "Cross‑collateralized leverage trading",
|
||||
|
|
Loading…
Reference in New Issue