mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
split up fetching & data manipulation, refactor
This commit is contained in:
parent
1e4446116b
commit
58f1c884de
@ -1,271 +0,0 @@
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
ReactElement,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState
|
||||
} from 'react'
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
LinearScale,
|
||||
CategoryScale,
|
||||
PointElement,
|
||||
Tooltip,
|
||||
ChartData,
|
||||
ChartOptions,
|
||||
defaults,
|
||||
ChartDataset,
|
||||
TooltipOptions,
|
||||
TooltipItem,
|
||||
BarElement,
|
||||
LineElement,
|
||||
LineController,
|
||||
BarController
|
||||
} from 'chart.js'
|
||||
import { Chart } from 'react-chartjs-2'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import { formatPrice } from '@shared/Price/PriceUnit'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
import useDarkMode from 'use-dark-mode'
|
||||
import { darkModeConfig } from '../../../../../app.config'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import { gql, OperationResult } from 'urql'
|
||||
import { PoolHistory } from '../../../../@types/subgraph/PoolHistory'
|
||||
import { fetchData, getQueryContext } from '@utils/subgraph'
|
||||
import styles from './Graph.module.css'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
ChartJS.register(
|
||||
LineElement,
|
||||
BarElement,
|
||||
PointElement,
|
||||
LinearScale,
|
||||
CategoryScale,
|
||||
// Title,
|
||||
Tooltip,
|
||||
LineController,
|
||||
BarController
|
||||
)
|
||||
|
||||
declare type GraphType = 'liquidity' | 'price' | 'volume'
|
||||
|
||||
// Chart.js global defaults
|
||||
defaults.font.family = `'Sharp Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif`
|
||||
defaults.animation = { easing: 'easeInOutQuart', duration: 1000 }
|
||||
|
||||
const REFETCH_INTERVAL = 10000
|
||||
|
||||
const lineStyle: Partial<ChartDataset> = {
|
||||
fill: false,
|
||||
borderWidth: 2,
|
||||
pointBorderWidth: 0,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBorderWidth: 0,
|
||||
pointHitRadius: 2,
|
||||
pointHoverBackgroundColor: '#ff4092'
|
||||
}
|
||||
|
||||
const tooltipOptions: Partial<TooltipOptions> = {
|
||||
intersect: false,
|
||||
displayColors: false,
|
||||
padding: 10,
|
||||
cornerRadius: 3,
|
||||
borderWidth: 1,
|
||||
caretSize: 7
|
||||
}
|
||||
|
||||
function getOptions(locale: string, isDarkMode: boolean): ChartOptions {
|
||||
return {
|
||||
layout: {
|
||||
padding: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 10
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
...tooltipOptions,
|
||||
backgroundColor: isDarkMode ? `#141414` : `#fff`,
|
||||
titleColor: isDarkMode ? `#e2e2e2` : `#303030`,
|
||||
bodyColor: isDarkMode ? `#fff` : `#141414`,
|
||||
borderColor: isDarkMode ? `#41474e` : `#e2e2e2`,
|
||||
callbacks: {
|
||||
label: (tooltipItem: TooltipItem<any>) =>
|
||||
`${formatPrice(`${tooltipItem.formattedValue}`, locale)} OCEAN`
|
||||
}
|
||||
}
|
||||
},
|
||||
hover: { intersect: false },
|
||||
scales: {
|
||||
y: { display: false },
|
||||
x: { display: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const graphTypes = ['Liquidity', 'Price', 'Volume']
|
||||
|
||||
const poolHistoryQuery = gql`
|
||||
query PoolHistory($id: String!) {
|
||||
poolSnapshots(first: 1000, where: { pool: $id }, orderBy: date) {
|
||||
date
|
||||
spotPrice
|
||||
baseTokenLiquidity
|
||||
datatokenLiquidity
|
||||
swapVolume
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function Graph(): ReactElement {
|
||||
const { locale } = useUserPreferences()
|
||||
const { price, ddo } = useAsset()
|
||||
const darkMode = useDarkMode(false, darkModeConfig)
|
||||
|
||||
const [options, setOptions] = useState<ChartOptions>()
|
||||
const [graphType, setGraphType] = useState<GraphType>('liquidity')
|
||||
const [error, setError] = useState<Error>()
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [dataHistory, setDataHistory] = useState<PoolHistory>()
|
||||
const [graphData, setGraphData] = useState<ChartData>()
|
||||
const [graphFetchInterval, setGraphFetchInterval] = useState<NodeJS.Timeout>()
|
||||
|
||||
const getPoolHistory = useCallback(async () => {
|
||||
try {
|
||||
const queryResult: OperationResult<PoolHistory> = await fetchData(
|
||||
poolHistoryQuery,
|
||||
{ id: price.address.toLowerCase() },
|
||||
getQueryContext(ddo.chainId)
|
||||
)
|
||||
setDataHistory(queryResult?.data)
|
||||
} catch (error) {
|
||||
console.error('Error fetchData: ', error.message)
|
||||
setError(error)
|
||||
}
|
||||
}, [ddo?.chainId, price?.address])
|
||||
|
||||
const refetchGraph = useCallback(async () => {
|
||||
if (graphFetchInterval) return
|
||||
|
||||
const newInterval = setInterval(() => getPoolHistory(), REFETCH_INTERVAL)
|
||||
setGraphFetchInterval(newInterval)
|
||||
}, [getPoolHistory, graphFetchInterval])
|
||||
|
||||
useEffect(() => {
|
||||
LoggerInstance.log('Fired GraphOptions!')
|
||||
const options = getOptions(locale, darkMode.value)
|
||||
setOptions(options)
|
||||
}, [locale, darkMode.value])
|
||||
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
if (!dataHistory) {
|
||||
await getPoolHistory()
|
||||
return
|
||||
}
|
||||
LoggerInstance.log('Fired GraphData!')
|
||||
|
||||
const timestamps = dataHistory.poolSnapshots.map((item) => {
|
||||
const date = new Date(item.date * 1000)
|
||||
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
|
||||
})
|
||||
|
||||
let baseTokenLiquidityCumulative = '0'
|
||||
const liquidityHistory = dataHistory.poolSnapshots.map((item) => {
|
||||
baseTokenLiquidityCumulative = new Decimal(baseTokenLiquidityCumulative)
|
||||
.add(item.baseTokenLiquidity)
|
||||
.toString()
|
||||
return baseTokenLiquidityCumulative
|
||||
})
|
||||
|
||||
const priceHistory = dataHistory.poolSnapshots.map(
|
||||
(item) => item.spotPrice
|
||||
)
|
||||
|
||||
let volumeCumulative = '0'
|
||||
const volumeHistory = dataHistory.poolSnapshots.map((item) => {
|
||||
volumeCumulative = new Decimal(volumeCumulative)
|
||||
.add(item.swapVolume)
|
||||
.toString()
|
||||
return baseTokenLiquidityCumulative
|
||||
})
|
||||
|
||||
let data
|
||||
switch (graphType) {
|
||||
case 'price':
|
||||
data = priceHistory.slice(0)
|
||||
break
|
||||
case 'volume':
|
||||
data = volumeHistory.slice(0)
|
||||
break
|
||||
default:
|
||||
data = liquidityHistory.slice(0)
|
||||
break
|
||||
}
|
||||
|
||||
setGraphData({
|
||||
labels: timestamps.slice(0),
|
||||
datasets: [
|
||||
{
|
||||
...lineStyle,
|
||||
label: 'Liquidity (OCEAN)',
|
||||
data,
|
||||
borderColor: `#8b98a9`
|
||||
}
|
||||
]
|
||||
})
|
||||
setIsLoading(false)
|
||||
refetchGraph()
|
||||
}
|
||||
init()
|
||||
|
||||
return () => clearInterval(graphFetchInterval)
|
||||
}, [dataHistory, graphType, graphFetchInterval, getPoolHistory, refetchGraph])
|
||||
|
||||
function handleGraphTypeSwitch(e: ChangeEvent<HTMLButtonElement>) {
|
||||
e.preventDefault()
|
||||
setGraphType(e.currentTarget.textContent.toLowerCase() as GraphType)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.graphWrap}>
|
||||
{isLoading ? (
|
||||
<Loader />
|
||||
) : error ? (
|
||||
<small>{error.message}</small>
|
||||
) : (
|
||||
<>
|
||||
<nav className={styles.type}>
|
||||
{graphTypes.map((type: GraphType) => (
|
||||
<Button
|
||||
key={type}
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleGraphTypeSwitch}
|
||||
className={`${styles.button} ${
|
||||
graphType === type.toLowerCase() ? styles.active : null
|
||||
}`}
|
||||
>
|
||||
{type}
|
||||
</Button>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<Chart
|
||||
type={graphType === 'volume' ? 'bar' : 'line'}
|
||||
width={416}
|
||||
height={80}
|
||||
data={graphData}
|
||||
options={options}
|
||||
redraw
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,24 +1,3 @@
|
||||
.graphWrap {
|
||||
min-height: 97px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: calc(var(--spacer) / 6) -1.35rem calc(var(--spacer) / 1.5) -1.35rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.graphWrap {
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.graphWrap canvas {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.type {
|
||||
position: absolute;
|
||||
bottom: -10px;
|
40
src/components/Asset/AssetActions/Pool/Graph/Nav.tsx
Normal file
40
src/components/Asset/AssetActions/Pool/Graph/Nav.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import Button from '@shared/atoms/Button'
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
Dispatch,
|
||||
ReactElement,
|
||||
SetStateAction
|
||||
} from 'react'
|
||||
import styles from './Nav.module.css'
|
||||
import { graphTypes, GraphType } from './_constants'
|
||||
|
||||
export default function Nav({
|
||||
graphType,
|
||||
setGraphType
|
||||
}: {
|
||||
graphType: GraphType
|
||||
setGraphType: Dispatch<SetStateAction<GraphType>>
|
||||
}): ReactElement {
|
||||
function handleGraphTypeSwitch(e: ChangeEvent<HTMLButtonElement>) {
|
||||
e.preventDefault()
|
||||
setGraphType(e.currentTarget.textContent.toLowerCase() as GraphType)
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className={styles.type}>
|
||||
{graphTypes.map((type: GraphType) => (
|
||||
<Button
|
||||
key={type}
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleGraphTypeSwitch}
|
||||
className={`${styles.button} ${
|
||||
graphType === type.toLowerCase() ? styles.active : null
|
||||
}`}
|
||||
>
|
||||
{type}
|
||||
</Button>
|
||||
))}
|
||||
</nav>
|
||||
)
|
||||
}
|
75
src/components/Asset/AssetActions/Pool/Graph/_constants.ts
Normal file
75
src/components/Asset/AssetActions/Pool/Graph/_constants.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { formatPrice } from '@shared/Price/PriceUnit'
|
||||
import {
|
||||
ChartDataset,
|
||||
TooltipOptions,
|
||||
ChartOptions,
|
||||
TooltipItem
|
||||
} from 'chart.js'
|
||||
import { gql } from 'urql'
|
||||
|
||||
export declare type GraphType = 'liquidity' | 'price' | 'volume'
|
||||
|
||||
export const poolHistoryQuery = gql`
|
||||
query PoolHistory($id: String!) {
|
||||
poolSnapshots(first: 1000, where: { pool: $id }, orderBy: date) {
|
||||
date
|
||||
spotPrice
|
||||
baseTokenLiquidity
|
||||
datatokenLiquidity
|
||||
swapVolume
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const lineStyle: Partial<ChartDataset> = {
|
||||
fill: false,
|
||||
borderWidth: 2,
|
||||
pointBorderWidth: 0,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBorderWidth: 0,
|
||||
pointHitRadius: 2,
|
||||
pointHoverBackgroundColor: '#ff4092'
|
||||
}
|
||||
|
||||
export const tooltipOptions: Partial<TooltipOptions> = {
|
||||
intersect: false,
|
||||
displayColors: false,
|
||||
padding: 10,
|
||||
cornerRadius: 3,
|
||||
borderWidth: 1,
|
||||
caretSize: 7
|
||||
}
|
||||
|
||||
export function getOptions(locale: string, isDarkMode: boolean): ChartOptions {
|
||||
return {
|
||||
layout: {
|
||||
padding: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 10
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
...tooltipOptions,
|
||||
backgroundColor: isDarkMode ? `#141414` : `#fff`,
|
||||
titleColor: isDarkMode ? `#e2e2e2` : `#303030`,
|
||||
bodyColor: isDarkMode ? `#fff` : `#141414`,
|
||||
borderColor: isDarkMode ? `#41474e` : `#e2e2e2`,
|
||||
callbacks: {
|
||||
label: (tooltipItem: TooltipItem<any>) =>
|
||||
`${formatPrice(`${tooltipItem.formattedValue}`, locale)} OCEAN`
|
||||
}
|
||||
}
|
||||
},
|
||||
hover: { intersect: false },
|
||||
scales: {
|
||||
y: { display: false },
|
||||
x: { display: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const graphTypes = ['Liquidity', 'Price', 'Volume']
|
@ -0,0 +1,20 @@
|
||||
.graphWrap {
|
||||
min-height: 97px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: calc(var(--spacer) / 6) -1.35rem calc(var(--spacer) / 1.5) -1.35rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.graphWrap {
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.graphWrap canvas {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
202
src/components/Asset/AssetActions/Pool/Graph/index.tsx
Normal file
202
src/components/Asset/AssetActions/Pool/Graph/index.tsx
Normal file
@ -0,0 +1,202 @@
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
ReactElement,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState
|
||||
} from 'react'
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
LinearScale,
|
||||
CategoryScale,
|
||||
PointElement,
|
||||
Tooltip,
|
||||
ChartData,
|
||||
ChartOptions,
|
||||
defaults,
|
||||
ChartDataset,
|
||||
TooltipOptions,
|
||||
TooltipItem,
|
||||
BarElement,
|
||||
LineElement,
|
||||
LineController,
|
||||
BarController
|
||||
} from 'chart.js'
|
||||
import { Chart } from 'react-chartjs-2'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
import useDarkMode from 'use-dark-mode'
|
||||
import { darkModeConfig } from '../../../../../../app.config'
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import { OperationResult } from 'urql'
|
||||
import { PoolHistory } from '../../../../../@types/subgraph/PoolHistory'
|
||||
import { fetchData, getQueryContext } from '@utils/subgraph'
|
||||
import styles from './index.module.css'
|
||||
import Decimal from 'decimal.js'
|
||||
import {
|
||||
poolHistoryQuery,
|
||||
getOptions,
|
||||
lineStyle,
|
||||
GraphType
|
||||
} from './_constants'
|
||||
import Nav from './Nav'
|
||||
|
||||
ChartJS.register(
|
||||
LineElement,
|
||||
BarElement,
|
||||
PointElement,
|
||||
LinearScale,
|
||||
CategoryScale,
|
||||
Tooltip,
|
||||
LineController,
|
||||
BarController
|
||||
)
|
||||
|
||||
// Chart.js global defaults
|
||||
defaults.font.family = `'Sharp Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif`
|
||||
defaults.animation = { easing: 'easeInOutQuart', duration: 1000 }
|
||||
|
||||
export default function Graph(): ReactElement {
|
||||
const { locale } = useUserPreferences()
|
||||
const { price, ddo, refreshInterval } = useAsset()
|
||||
const darkMode = useDarkMode(false, darkModeConfig)
|
||||
|
||||
const [options, setOptions] = useState<ChartOptions>()
|
||||
const [graphType, setGraphType] = useState<GraphType>('liquidity')
|
||||
const [error, setError] = useState<Error>()
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [dataHistory, setDataHistory] = useState<PoolHistory>()
|
||||
const [graphData, setGraphData] = useState<ChartData>()
|
||||
const [graphFetchInterval, setGraphFetchInterval] = useState<NodeJS.Timeout>()
|
||||
|
||||
// Helper: fetch pool snapshots data
|
||||
const fetchPoolHistory = useCallback(async () => {
|
||||
try {
|
||||
const queryResult: OperationResult<PoolHistory> = await fetchData(
|
||||
poolHistoryQuery,
|
||||
{ id: price.address.toLowerCase() },
|
||||
getQueryContext(ddo.chainId)
|
||||
)
|
||||
setDataHistory(queryResult?.data)
|
||||
setIsLoading(false)
|
||||
|
||||
LoggerInstance.log(
|
||||
`[pool graph] Fetched pool snapshots:`,
|
||||
queryResult?.data
|
||||
)
|
||||
} catch (error) {
|
||||
LoggerInstance.error('[pool graph] Error fetchData: ', error.message)
|
||||
setError(error)
|
||||
}
|
||||
}, [ddo?.chainId, price?.address])
|
||||
|
||||
// Helper: start interval fetching
|
||||
const initFetchInterval = useCallback(() => {
|
||||
if (graphFetchInterval) return
|
||||
|
||||
const newInterval = setInterval(() => {
|
||||
fetchPoolHistory()
|
||||
LoggerInstance.log(
|
||||
`[pool graph] Refetch interval fired after ${refreshInterval / 1000}s`
|
||||
)
|
||||
}, refreshInterval)
|
||||
setGraphFetchInterval(newInterval)
|
||||
}, [fetchPoolHistory, graphFetchInterval, refreshInterval])
|
||||
|
||||
useEffect(() => {
|
||||
return () => clearInterval(graphFetchInterval)
|
||||
}, [graphFetchInterval])
|
||||
|
||||
//
|
||||
// 0 Get Graph options
|
||||
//
|
||||
useEffect(() => {
|
||||
LoggerInstance.log('[pool graph] Fired getOptions() for color scheme.')
|
||||
const options = getOptions(locale, darkMode.value)
|
||||
setOptions(options)
|
||||
}, [locale, darkMode.value])
|
||||
|
||||
//
|
||||
// 1 Fetch all the data on mount
|
||||
// All further effects depend on the fetched data
|
||||
// and only do further data checking and manipulation.
|
||||
//
|
||||
useEffect(() => {
|
||||
fetchPoolHistory()
|
||||
initFetchInterval()
|
||||
}, [fetchPoolHistory, initFetchInterval])
|
||||
|
||||
//
|
||||
// 2 Data manipulation
|
||||
//
|
||||
useEffect(() => {
|
||||
if (!dataHistory?.poolSnapshots) return
|
||||
|
||||
const timestamps = dataHistory.poolSnapshots.map((item) => {
|
||||
const date = new Date(item.date * 1000)
|
||||
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
|
||||
})
|
||||
|
||||
let baseTokenLiquidityCumulative = '0'
|
||||
const liquidityHistory = dataHistory.poolSnapshots.map((item) => {
|
||||
baseTokenLiquidityCumulative = new Decimal(baseTokenLiquidityCumulative)
|
||||
.add(item.baseTokenLiquidity)
|
||||
.toString()
|
||||
return baseTokenLiquidityCumulative
|
||||
})
|
||||
|
||||
const priceHistory = dataHistory.poolSnapshots.map((item) => item.spotPrice)
|
||||
|
||||
let volumeCumulative = '0'
|
||||
const volumeHistory = dataHistory.poolSnapshots.map((item) => {
|
||||
volumeCumulative = new Decimal(volumeCumulative)
|
||||
.add(item.swapVolume)
|
||||
.toString()
|
||||
return baseTokenLiquidityCumulative
|
||||
})
|
||||
|
||||
let data
|
||||
switch (graphType) {
|
||||
case 'price':
|
||||
data = priceHistory.slice(0)
|
||||
break
|
||||
case 'volume':
|
||||
data = volumeHistory.slice(0)
|
||||
break
|
||||
default:
|
||||
data = liquidityHistory.slice(0)
|
||||
break
|
||||
}
|
||||
|
||||
const newGraphData = {
|
||||
labels: timestamps.slice(0),
|
||||
datasets: [{ ...lineStyle, data, borderColor: `#8b98a9` }]
|
||||
}
|
||||
setGraphData(newGraphData)
|
||||
LoggerInstance.log('[pool graph] New graph data:', newGraphData)
|
||||
}, [dataHistory?.poolSnapshots, graphType])
|
||||
|
||||
return (
|
||||
<div className={styles.graphWrap}>
|
||||
{isLoading ? (
|
||||
<Loader />
|
||||
) : error ? (
|
||||
<small>{error.message}</small>
|
||||
) : (
|
||||
<>
|
||||
<Nav graphType={graphType} setGraphType={setGraphType} />
|
||||
|
||||
<Chart
|
||||
type={graphType === 'volume' ? 'bar' : 'line'}
|
||||
width={416}
|
||||
height={80}
|
||||
data={graphData}
|
||||
options={options}
|
||||
redraw
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user