Add charts page

pull/219/head
artemkolodko 2 years ago
parent ae91e989f1
commit 900bdf7bc8
  1. 1
      src/Routes.tsx
  2. 2
      src/components/appHeader/ToolsButton.tsx
  3. 108
      src/pages/ChartsPage/ActiveAddresses.tsx
  4. 88
      src/pages/ChartsPage/AverageFee.tsx
  5. 3
      src/pages/ChartsPage/ChartFilter.tsx
  6. 172
      src/pages/ChartsPage/DailyChartPage.tsx
  7. 123
      src/pages/ChartsPage/DailyTransactions.tsx
  8. 61
      src/pages/ChartsPage/index.tsx
  9. BIN
      src/pages/ChartsPage/thumbnails/daily_addresses.png
  10. BIN
      src/pages/ChartsPage/thumbnails/daily_fee.png
  11. BIN
      src/pages/ChartsPage/thumbnails/daily_txs.png
  12. 2
      src/pages/ChartsPage/utils.ts

@ -101,7 +101,6 @@ export function Routes() {
<Route path="/charts">
<Route path={'/'}><ChartsPage /></Route>
<Route path={'/addresses'}>123</Route>
</Route>
<Route path="*">

@ -59,7 +59,7 @@ export function ToolsButton() {
history.push("/charts");
}}
>
Charts
Charts & Stats
</Anchor>
</Box>
}

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

@ -49,7 +49,8 @@ export const ChartFilter = (props: ChartFilterProps) => {
direction={'row'}
gap={'8px'}
round={'8px'}
background={'backgroundMark'}
background={'backgroundBack'}
border={{ size: '1px' }}
>
{ChartOptions.map(option => <Option
key={option}

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

@ -1,52 +1,15 @@
import React, {useEffect, useState} from 'react'
import {Box, Heading, Spinner, Text} from "grommet";
import {BaseContainer, BasePage} from "../../components/ui";
import {getMetricsByType} from "../../api/client";
import {useThemeMode} from "../../hooks/themeSwitcherHook";
import {Line as LineChartJs} from "react-chartjs-2";
import {MetricsDailyItem, MetricsType} from "../../types";
import {
getDetailedChartOptions,
getChartData,
enrichResponse,
getLimitByFilterOption,
downloadMetricsCSV
} from './utils'
import {ChartFilter, ChartOption} from "./ChartFilter";
import styled from "styled-components";
import dayjs from "dayjs";
import {Info} from "grommet-icons";
const SpinnerContainer = styled(Box)`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
`
import {DailyChartPage} from "./DailyChartPage";
export const DailyTransactions = () => {
const themeMode = useThemeMode();
const [items, setItems] = useState<MetricsDailyItem[]>([]);
const [cache, setCache] = useState<MetricsDailyItem[]>([])
const [isLoading, setIsLoading] = useState<boolean>(false);
const [filterOption, setFilterOption] = useState(ChartOption.year)
const [minValue, setMinValue] = useState<MetricsDailyItem>()
const [maxValue, setMaxValue] = useState<MetricsDailyItem>()
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])
const [loadingError, setLoadingError] = useState('')
useEffect(() => {
const loadData = async () => {
@ -63,11 +26,10 @@ export const DailyTransactions = () => {
}
}
const cachedData = enrichResponse(data)
setCache(cachedData)
applyFilter(cachedData)
setItems(cachedData)
} catch (e) {
console.error('Error on loading metrics:', e)
setLoadingError('Loading error')
} finally {
setIsLoading(false);
}
@ -75,64 +37,23 @@ export const DailyTransactions = () => {
loadData()
}, [])
const chartOptions = getDetailedChartOptions(themeMode, items, { yAxisLabel: 'Transactions per day' })
const chartData = getChartData(themeMode, items, 'Daily transactions')
const dailyPageProps = {
title: 'Harmony Daily Transactions',
unitLabel: 'transactions',
items,
isLoading,
loadingError,
chart: {
yAxisLabel: 'Transactions per day',
tooltipLabel: 'Daily transactions'
},
renderMaxValue: (value: string, date: string) => {
return `Highest number of ${value} transactions on ${date}`
},
renderMinValue: (value: string, date: string) => {
return `Lowest number of ${value} transactions on ${date}`
}
}
// @ts-ignore
return <BaseContainer pad={{ horizontal: "0" }}>
<Heading size="20px" margin={{ bottom: "medium", top: "0" }}>
<Box direction={"row"}>Harmony Daily Transactions Chart</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'}>Highest number of {Intl.NumberFormat('en-US').format(maxValue ? +maxValue.value : 0)} transactions
on {dayjs(maxValue?.date).format('dddd, MMMM D, YYYY')}</Text>
</Box>
<Box direction={'row'} gap={'8px'} justify={'center'} align={'center'} style={{ flexGrow: 2 }}>
<Info size={'small'} />
<Text size={'small'}>Lowest number of {Intl.NumberFormat('en-US').format(minValue ? +minValue.value : 0)} transactions
on {dayjs(minValue?.date).format('dddd, MMMM D, YYYY')}</Text>
</Box>
</Box>
</BasePage>
<BasePage pad={"small"} style={{overflow: 'inherit', marginTop: '16px'}}>
<Box align={'end'}>
<ChartFilter
disabled={isLoading}
activeOption={filterOption}
onSelect={(o) => setFilterOption(o)}
/>
</Box>
<Box
width={'100%'}
height="260px"
direction={"row"}
align={'center'}
justify={'center'}
margin={{ top: '8px' }}
style={{ position: 'relative' }}
>
{isLoading && <SpinnerContainer justify={'center'} gap={'16px'} align={'center'}>
<Spinner size={'medium'} />
<Text>Loading Data</Text>
</SpinnerContainer>}
<Box height={'inherit'} width={'inherit'} style={{ filter: isLoading ? '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>
<Text color={'brand'} style={{ cursor: 'pointer', textDecoration: 'underline' }} onClick={() => downloadMetricsCSV('transactions_history.csv', { items: cache.reverse() })}>
CSV Data
</Text>
</Box>
</Box>
</BasePage>
</BaseContainer>
return <DailyChartPage {...dailyPageProps} />
}

@ -1,24 +1,55 @@
import React from "react";
import { Box, Heading, Text } from "grommet";
import {Box, Heading, Text} from "grommet";
import { BasePage, BaseContainer } from "src/components/ui";
import {Route, Switch, useHistory, useLocation, useParams, useRouteMatch} from "react-router-dom";
import {useHistory, useLocation} from "react-router-dom";
import {ActiveAddresses} from "./ActiveAddresses";
import {DailyTransactions} from "./DailyTransactions";
import {AverageFee} from "./AverageFee";
import styled from "styled-components";
export function ChartsPage() {
// @ts-ignore
const { shardNumber } = useParams();
enum ChartType {
tx = 'tx',
addresses = 'addresses',
fee = 'fee'
}
const PreviewContainer = styled(Box)`
flex: 1 1 250px;
transition: transform 250ms;
&:hover {
transform: scale(1.025);
}
`
const PreviewCard = (props: { type: ChartType, title: string }) => {
const {type, title} = props
const history = useHistory();
const onClick = () => history.push(`charts/${type}`)
return <PreviewContainer border={{ size: '1px' }} onClick={onClick} round={'8px'} overflow={'hidden'}>
<Box pad={'8px'} background={'backgroundDropdownItem'}>
<Text color={'brand'}>{title}</Text>
</Box>
<Box style={{ filter: 'grayscale(0.8)' }} pad={'8px'} border={{ side: 'top' }}>
{type === ChartType.tx && <img src={require("./thumbnails/daily_txs.png").default} alt={type} />}
{type === ChartType.addresses && <img src={require("./thumbnails/daily_addresses.png").default} alt={type} />}
{type === ChartType.fee && <img src={require("./thumbnails/daily_fee.png").default} alt={type} />}
</Box>
</PreviewContainer>
}
export function ChartsPage() {
const location = useLocation();
const [, ,route] = location.pathname.split('/')
const navigate = (path: string) => history.push(path)
if(route === 'tx') {
if(route === ChartType.tx) {
return <DailyTransactions />
} else if(route === 'addresses') {
} else if(route === ChartType.addresses) {
return <ActiveAddresses />
} else if(route === ChartType.fee) {
return <AverageFee />
}
return (
@ -26,12 +57,14 @@ export function ChartsPage() {
<Heading size="small" margin={{ bottom: "medium", top: "0" }}>
<Box direction={"row"}>Harmony One Charts</Box>
</Heading>
<BasePage pad={"small"} style={{overflow: 'inherit'}}>
<Box style={{ width: "200px" }} direction={"row"} align={'center'}>
<Text onClick={() => navigate('/charts/addresses')}>daily active addresses</Text>
<BasePage pad={'0'} style={{overflow: 'inherit'}}>
<Box border={{ side: 'bottom' }} pad={"small"}>
<Text weight={'bold'}>Blockchain Data</Text>
</Box>
<Box style={{ width: "200px" }} direction={"row"} align={'center'}>
<Text onClick={() => navigate('/charts/tx')}>txs</Text>
<Box wrap direction={'row'} pad={"small"} gap={'16px'} justify={'center'} align={'center'}>
<PreviewCard type={ChartType.tx} title={'Daily Transactions Chart'} />
<PreviewCard type={ChartType.addresses} title={'Daily Active Addresses'} />
<PreviewCard type={ChartType.fee} title={'Average Transaction Fee'} />
</Box>
</BasePage>
</BaseContainer>

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

@ -2,7 +2,7 @@ import {palette} from "../../theme";
import dayjs from "dayjs";
import dayOfYear from "dayjs/plugin/dayOfYear";
import {MetricsDailyItem} from "../../types";
import {ChartOption, ChartOptions} from "./ChartFilter";
import {ChartOption} from "./ChartFilter";
dayjs.extend(dayOfYear)

Loading…
Cancel
Save