Merge pull request #142 from blockworks-foundation/improve-account-charts

improve account charts
This commit is contained in:
tjshipe 2022-01-21 00:21:29 -06:00 committed by GitHub
commit a538e8aaad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 200 additions and 177 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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": "Crosscollateralized leverage trading",