parent
ae91e989f1
commit
900bdf7bc8
@ -1,87 +1,59 @@ |
||||
import React, {useEffect, useState} from 'react' |
||||
import {Box, Heading} from "grommet"; |
||||
import {BaseContainer, BasePage} from "../../components/ui"; |
||||
import {getMetricsByType} from "../../api/client"; |
||||
import dayjs from "dayjs"; |
||||
import {palette} from "../../theme"; |
||||
import {useThemeMode} from "../../hooks/themeSwitcherHook"; |
||||
import {Line as LineChartJs} from "react-chartjs-2"; |
||||
import {MetricsDailyItem, MetricsType} from "../../types"; |
||||
import { getDetailedChartOptions, getChartData } from './utils' |
||||
import { |
||||
enrichResponse, |
||||
} from './utils' |
||||
import {DailyChartPage} from "./DailyChartPage"; |
||||
|
||||
export const ActiveAddresses = () => { |
||||
const themeMode = useThemeMode(); |
||||
const [txs, setTxs] = useState<any[]>([]); |
||||
const [wallets, setWallets] = useState<any[]>([]); |
||||
const [fee, setFee] = useState<any[]>([]); |
||||
const [items, setItems] = useState<MetricsDailyItem[]>([]); |
||||
const [isLoading, setIsLoading] = useState<boolean>(false); |
||||
|
||||
const enrichResponse = (items: MetricsDailyItem[]) => { |
||||
return items.reverse().map(item => ({ |
||||
...item, |
||||
timestamp: item.date |
||||
})) |
||||
} |
||||
const [loadingError, setLoadingError] = useState('') |
||||
|
||||
useEffect(() => { |
||||
const loadData = async () => { |
||||
try { |
||||
setIsLoading(true); |
||||
const txsResults = await getMetricsByType(MetricsType.transactionsCount, 0, 1000) |
||||
const walletsResults = await getMetricsByType(MetricsType.walletsCount, 0, 1000) |
||||
const feeResults = await getMetricsByType(MetricsType.averageFee, 0, 1000) |
||||
console.log('feeResults', feeResults) |
||||
setTxs(enrichResponse(txsResults)); |
||||
setWallets(enrichResponse(walletsResults)); |
||||
setFee(enrichResponse(feeResults)); |
||||
setIsLoading(false); |
||||
setIsLoading(true) |
||||
const limit = 1000 |
||||
let data: MetricsDailyItem[] = [] |
||||
for(let i = 0; i < 2; i++) { |
||||
const res = await getMetricsByType(MetricsType.walletsCount, i * limit, limit) |
||||
data = [...data, ...res] |
||||
|
||||
if(res.length < limit) { |
||||
break; |
||||
} |
||||
} |
||||
const cachedData = enrichResponse(data) |
||||
setItems(cachedData) |
||||
} catch (e) { |
||||
console.error('Error on loading metrics:', e) |
||||
setLoadingError('Loading error') |
||||
} finally { |
||||
setIsLoading(false); |
||||
} |
||||
} |
||||
loadData() |
||||
}, []) |
||||
|
||||
// let min = Number.MAX_SAFE_INTEGER;
|
||||
// txs.forEach(e => {
|
||||
// if (min > +e.value) {
|
||||
// min = +e.value;
|
||||
// }
|
||||
// });
|
||||
const dailyPageProps = { |
||||
title: 'Harmony Daily Active Addresses', |
||||
unitLabel: 'addresses', |
||||
items, |
||||
isLoading, |
||||
loadingError, |
||||
chart: { |
||||
yAxisLabel: 'Active Harmony Addresses', |
||||
tooltipLabel: 'Active Harmony Addresses' |
||||
}, |
||||
renderMaxValue: (value: string, date: string) => { |
||||
return `Highest number of ${value} addresses on ${date}` |
||||
}, |
||||
renderMinValue: (value: string, date: string) => { |
||||
return `Lowest number of ${value} addresses on ${date}` |
||||
} |
||||
} |
||||
|
||||
return <BaseContainer pad={{ horizontal: "0" }}> |
||||
{/*<Heading size="small" margin={{ bottom: "medium", top: "0" }}>*/} |
||||
{/* <Box direction={"row"}>Daily Transactions</Box>*/} |
||||
{/*</Heading>*/} |
||||
{/*<BasePage pad={"small"} style={{overflow: 'inherit'}}>*/} |
||||
{/* <Box style={{ width: "100%" }} direction={"row"} align={'center'}>*/} |
||||
{/* {!isLoading && (*/} |
||||
{/* // @ts-ignore*/} |
||||
{/* <LineChartJs options={getDetailedChartOptions(themeMode, txs)} data={getChartData(txs)} height="50px" />*/} |
||||
{/* )}*/} |
||||
{/* </Box>*/} |
||||
{/*</BasePage>*/} |
||||
{/*<Heading size="small" margin={{ bottom: "medium", top: "16px" }}>*/} |
||||
{/* <Box direction={"row"}>Daily Active Addresses</Box>*/} |
||||
{/*</Heading>*/} |
||||
{/*<BasePage pad={"small"} style={{overflow: 'inherit'}}>*/} |
||||
{/* <Box style={{ width: "100%" }} direction={"row"} align={'center'}>*/} |
||||
{/* {!isLoading && (*/} |
||||
{/* // @ts-ignore*/} |
||||
{/* <LineChartJs options={getDetailedChartOptions(themeMode, wallets)} data={getChartData(themeMode, wallets)} height="50px" />*/} |
||||
{/* )}*/} |
||||
{/* </Box>*/} |
||||
{/*</BasePage>*/} |
||||
<Heading size="small" margin={{ bottom: "medium", top: "16px" }}> |
||||
<Box direction={"row"}>Daily Average Fee</Box> |
||||
</Heading> |
||||
<BasePage pad={"small"} style={{overflow: 'inherit'}}> |
||||
<Box style={{ width: "100%" }} direction={"row"} align={'center'}> |
||||
{!isLoading && ( |
||||
// @ts-ignore
|
||||
<LineChartJs options={getDetailedChartOptions(themeMode, fee)} data={getChartData(themeMode, fee, 'Average fee')} height="50px" /> |
||||
)} |
||||
</Box> |
||||
</BasePage> |
||||
</BaseContainer> |
||||
return <DailyChartPage {...dailyPageProps} /> |
||||
} |
||||
|
@ -1,65 +1,59 @@ |
||||
import React, {useEffect, useState} from 'react' |
||||
import {Box, Heading} from "grommet"; |
||||
import {BaseContainer, BasePage} from "../../components/ui"; |
||||
import {getMetricsByType} from "../../api/client"; |
||||
import dayjs from "dayjs"; |
||||
import {palette} from "../../theme"; |
||||
import {useThemeMode} from "../../hooks/themeSwitcherHook"; |
||||
import {Line as LineChartJs} from "react-chartjs-2"; |
||||
import {MetricsDailyItem, MetricsType} from "../../types"; |
||||
import { getDetailedChartOptions, getChartData } from './utils' |
||||
import { |
||||
enrichResponse, |
||||
} from './utils' |
||||
import {DailyChartPage} from "./DailyChartPage"; |
||||
|
||||
export const ActiveAddresses = () => { |
||||
const themeMode = useThemeMode(); |
||||
const [txs, setTxs] = useState<any[]>([]); |
||||
const [wallets, setWallets] = useState<any[]>([]); |
||||
const [fee, setFee] = useState<any[]>([]); |
||||
export const AverageFee = () => { |
||||
const [items, setItems] = useState<MetricsDailyItem[]>([]); |
||||
const [isLoading, setIsLoading] = useState<boolean>(false); |
||||
|
||||
const enrichResponse = (items: MetricsDailyItem[]) => { |
||||
return items.reverse().map(item => ({ |
||||
...item, |
||||
timestamp: item.date |
||||
})) |
||||
} |
||||
const [loadingError, setLoadingError] = useState('') |
||||
|
||||
useEffect(() => { |
||||
const loadData = async () => { |
||||
try { |
||||
setIsLoading(true); |
||||
const txsResults = await getMetricsByType(MetricsType.transactionsCount, 0, 1000) |
||||
const walletsResults = await getMetricsByType(MetricsType.walletsCount, 0, 1000) |
||||
const feeResults = await getMetricsByType(MetricsType.averageFee, 0, 1000) |
||||
console.log('feeResults', feeResults) |
||||
setTxs(enrichResponse(txsResults)); |
||||
setWallets(enrichResponse(walletsResults)); |
||||
setFee(enrichResponse(feeResults)); |
||||
setIsLoading(false); |
||||
setIsLoading(true) |
||||
const limit = 1000 |
||||
let data: MetricsDailyItem[] = [] |
||||
for(let i = 0; i < 2; i++) { |
||||
const res = await getMetricsByType(MetricsType.averageFee, i * limit, limit) |
||||
data = [...data, ...res] |
||||
|
||||
if(res.length < limit) { |
||||
break; |
||||
} |
||||
} |
||||
const cachedData = enrichResponse(data) |
||||
setItems(cachedData) |
||||
} catch (e) { |
||||
console.error('Error on loading metrics:', e) |
||||
setLoadingError('Loading error') |
||||
} finally { |
||||
setIsLoading(false); |
||||
} |
||||
} |
||||
loadData() |
||||
}, []) |
||||
|
||||
// let min = Number.MAX_SAFE_INTEGER;
|
||||
// txs.forEach(e => {
|
||||
// if (min > +e.value) {
|
||||
// min = +e.value;
|
||||
// }
|
||||
// });
|
||||
const dailyPageProps = { |
||||
title: 'Harmony Daily Average Fee', |
||||
unitLabel: 'fee', |
||||
items, |
||||
isLoading, |
||||
loadingError, |
||||
chart: { |
||||
yAxisLabel: 'Average Transaction Fee (ONE)', |
||||
tooltipLabel: 'Average tx fee (ONE)' |
||||
}, |
||||
renderMaxValue: (value: string, date: string) => { |
||||
return `Highest average transaction fee of ${value} ONE on ${date}` |
||||
}, |
||||
renderMinValue: (value: string, date: string) => { |
||||
return `Lowest average transaction fee of ${value} ONE on ${date}` |
||||
} |
||||
} |
||||
|
||||
return <BaseContainer pad={{ horizontal: "0" }}> |
||||
<Heading size="small" margin={{ bottom: "medium", top: "16px" }}> |
||||
<Box direction={"row"}>Daily Average Fee</Box> |
||||
</Heading> |
||||
<BasePage pad={"small"} style={{overflow: 'inherit'}}> |
||||
<Box style={{ width: "100%" }} direction={"row"} align={'center'}> |
||||
{!isLoading && ( |
||||
// @ts-ignore
|
||||
<LineChartJs options={getDetailedChartOptions(themeMode, fee)} data={getChartData(themeMode, fee, 'Average fee')} height="50px" /> |
||||
)} |
||||
</Box> |
||||
</BasePage> |
||||
</BaseContainer> |
||||
return <DailyChartPage {...dailyPageProps} /> |
||||
} |
||||
|
@ -0,0 +1,172 @@ |
||||
import React, {useEffect, useState} from 'react' |
||||
import {Box, Heading, Spinner, Text} from "grommet"; |
||||
import {BaseContainer, BasePage} from "../../components/ui"; |
||||
import {useThemeMode} from "../../hooks/themeSwitcherHook"; |
||||
import {Line as LineChartJs} from "react-chartjs-2"; |
||||
import {MetricsDailyItem} from "../../types"; |
||||
import { |
||||
getDetailedChartOptions, |
||||
getChartData, |
||||
getLimitByFilterOption, |
||||
downloadMetricsCSV |
||||
} from './utils' |
||||
import {ChartFilter, ChartOption} from "./ChartFilter"; |
||||
import styled from "styled-components"; |
||||
import dayjs from "dayjs"; |
||||
import {Alert, Info} from "grommet-icons"; |
||||
import {Link} from "react-router-dom"; |
||||
|
||||
const ChartModalContainer = styled(Box)` |
||||
position: absolute; |
||||
top: 50%; |
||||
left: 50%; |
||||
transform: translate(-50%, -50%); |
||||
` |
||||
|
||||
const TextLink = styled(Text)` |
||||
cursor: pointer; |
||||
text-decoration: underline; |
||||
` |
||||
|
||||
const LoadingErrorModal = () => { |
||||
return <ChartModalContainer |
||||
justify={'center'} |
||||
gap={'16px'} |
||||
pad={'16px'} |
||||
align={'center'} |
||||
background={'warningBackground'} |
||||
round={'8px'} |
||||
border={{ size: '1px' }} |
||||
style={{ zIndex: 1 }} |
||||
> |
||||
<Alert size={'medium'} /> |
||||
<Text>Error on loading data</Text> |
||||
<Text size={'small'}>Please try again later</Text> |
||||
</ChartModalContainer> |
||||
} |
||||
|
||||
const formatValue = (value: string) => Intl.NumberFormat('en-US').format(+value) |
||||
const formatDate = (date: string) => dayjs(date).format('dddd, MMMM D, YYYY') |
||||
|
||||
export interface DailyChartPageProps { |
||||
title: string |
||||
unitLabel: string |
||||
items: MetricsDailyItem[] |
||||
isLoading: boolean |
||||
loadingError?: string |
||||
chart: { |
||||
yAxisLabel: string, |
||||
tooltipLabel: string |
||||
} |
||||
renderMaxValue: (value: string, date: string) => string |
||||
renderMinValue: (value: string, date: string) => string |
||||
} |
||||
|
||||
export const DailyChartPage = (props: DailyChartPageProps) => { |
||||
const themeMode = useThemeMode(); |
||||
|
||||
const { isLoading, loadingError } = props |
||||
|
||||
const [items, setItems] = useState<MetricsDailyItem[]>([]); |
||||
const [cache, setCache] = useState<MetricsDailyItem[]>([]) |
||||
const [filterOption, setFilterOption] = useState(ChartOption.year) |
||||
const [minValue, setMinValue] = useState<MetricsDailyItem>({value: '0', date: ''}) |
||||
const [maxValue, setMaxValue] = useState<MetricsDailyItem>({value: '0', date: ''}) |
||||
|
||||
const applyFilter = (cachedData: MetricsDailyItem[]) => { |
||||
const limit = getLimitByFilterOption(filterOption) |
||||
const data = cachedData.slice(-limit) |
||||
const sortedData = [...data].sort((a, b) => +a.value - +b.value) |
||||
setItems(cachedData.slice(-limit)) |
||||
setMinValue(sortedData[0]) |
||||
setMaxValue(sortedData[sortedData.length - 1]) |
||||
} |
||||
|
||||
useEffect(() => { |
||||
if(cache.length > 0) { |
||||
applyFilter(cache) |
||||
} |
||||
}, [filterOption]) |
||||
|
||||
useEffect(() => { |
||||
setCache(props.items) |
||||
if(props.items.length > 0) { |
||||
applyFilter(props.items) |
||||
} |
||||
}, [props.items]) |
||||
|
||||
const chartOptions = getDetailedChartOptions(themeMode, items, { yAxisLabel: props.chart.yAxisLabel }) |
||||
const chartData = getChartData(themeMode, items, props.chart.tooltipLabel) |
||||
|
||||
return <BaseContainer pad={{ horizontal: "0" }}> |
||||
<Heading size="20px" margin={{ bottom: "medium", top: "0" }}> |
||||
<Box direction={"row"} gap={'8px'} align={'center'}> |
||||
<Box> |
||||
<Link to={'/charts'}><TextLink color={'brand'}>Charts</TextLink></Link> |
||||
</Box> |
||||
<Box><Text style={{ opacity: 0.4 }}>/</Text></Box> |
||||
<Box>{props.title}</Box> |
||||
</Box> |
||||
</Heading> |
||||
<BasePage pad={"small"}> |
||||
<Box direction={'row'} justify={'between'} flex={'grow'} wrap={true}> |
||||
<Box direction={'row'} gap={'8px'} justify={'center'} align={'center'} style={{ flexGrow: 2 }}> |
||||
<Info size={'small'} /> |
||||
<Text size={'small'}> |
||||
{props.renderMaxValue(formatValue(maxValue.value), formatDate(maxValue.date))} |
||||
</Text> |
||||
</Box> |
||||
<Box direction={'row'} gap={'8px'} justify={'center'} align={'center'} style={{ flexGrow: 2 }}> |
||||
<Info size={'small'} /> |
||||
<Text size={'small'}> |
||||
{props.renderMinValue(formatValue(minValue.value), formatDate(minValue.date))} |
||||
</Text> |
||||
</Box> |
||||
</Box> |
||||
</BasePage> |
||||
<BasePage pad={"small"} style={{overflow: 'inherit', marginTop: '16px'}}> |
||||
<Box align={'end'}> |
||||
<ChartFilter |
||||
disabled={isLoading || !!loadingError} |
||||
activeOption={filterOption} |
||||
onSelect={(o) => setFilterOption(o)} |
||||
/> |
||||
</Box> |
||||
<Box |
||||
width={'100%'} |
||||
height="260px" |
||||
direction={"row"} |
||||
align={'center'} |
||||
justify={'center'} |
||||
margin={{ top: '8px' }} |
||||
style={{ position: 'relative', pointerEvents: isLoading || loadingError ? 'none': 'unset' }} |
||||
> |
||||
{isLoading && <ChartModalContainer justify={'center'} gap={'16px'} align={'center'}> |
||||
<Spinner size={'medium'} /> |
||||
<Text>Loading Data</Text> |
||||
</ChartModalContainer>} |
||||
{!isLoading && loadingError && <LoadingErrorModal />} |
||||
<Box |
||||
height={'inherit'} |
||||
width={'inherit'} |
||||
style={{ filter: isLoading || loadingError ? 'blur(4px)': 'unset' }} |
||||
> |
||||
{ |
||||
// @ts-ignore
|
||||
<LineChartJs options={chartOptions} data={chartData} /> |
||||
} |
||||
</Box> |
||||
</Box> |
||||
<Box margin={{ top: '32px' }} align={'end'}> |
||||
<Box direction={'row'} gap={'4px'}> |
||||
<Text>Download</Text> |
||||
<TextLink |
||||
color={'brand'} |
||||
onClick={() => downloadMetricsCSV(`${props.unitLabel}_metrics.csv`, { items: [...cache].reverse() })}> |
||||
CSV Data |
||||
</TextLink> |
||||
</Box> |
||||
</Box> |
||||
</BasePage> |
||||
</BaseContainer> |
||||
} |
After Width: | Height: | Size: 247 KiB |
After Width: | Height: | Size: 208 KiB |
After Width: | Height: | Size: 274 KiB |
Loading…
Reference in new issue