1
0
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:
Matthias Kretschmann 2022-01-25 12:32:49 +00:00
parent 1e4446116b
commit 58f1c884de
Signed by: m
GPG Key ID: 606EEEF3C479A91F
6 changed files with 337 additions and 292 deletions

View File

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

View File

@ -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 { .type {
position: absolute; position: absolute;
bottom: -10px; bottom: -10px;

View 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>
)
}

View 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']

View File

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

View 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>
)
}