Commit c8e95452 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #461 from blockscout/address-balance-chart

address coin balance chart
parents 59b7b3bb d8ff2233
...@@ -80,6 +80,11 @@ export interface AddressCoinBalanceHistoryResponse { ...@@ -80,6 +80,11 @@ export interface AddressCoinBalanceHistoryResponse {
}; };
} }
export type AddressCoinBalanceHistoryChart = Array<{
date: string;
value: string;
}>
export interface AddressBlocksValidatedResponse { export interface AddressBlocksValidatedResponse {
items: Array<Block>; items: Array<Block>;
next_page_params: { next_page_params: {
......
...@@ -69,7 +69,7 @@ const AddressCoinBalance = ({ addressQuery }: Props) => { ...@@ -69,7 +69,7 @@ const AddressCoinBalance = ({ addressQuery }: Props) => {
return ( return (
<> <>
{ socketAlert && <SocketAlert mb={ 6 }/> } { socketAlert && <SocketAlert mb={ 6 }/> }
<AddressCoinBalanceChart/> <AddressCoinBalanceChart addressQuery={ addressQuery }/>
<AddressCoinBalanceHistory query={ coinBalanceQuery }/> <AddressCoinBalanceHistory query={ coinBalanceQuery }/>
</> </>
); );
......
import { Box } from '@chakra-ui/react'; import type { UseQueryResult } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
const AddressCoinBalanceChart = () => { import type { Address, AddressCoinBalanceHistoryChart } from 'types/api/address';
// chart will be added after stats feature is finalized import { QueryKeys } from 'types/client/queries';
return <Box p={ 4 } borderColor="gray.200" borderRadius="md" borderWidth="1px">Here will be coin balance chart</Box>;
import appConfig from 'configs/app/config';
import useFetch from 'lib/hooks/useFetch';
import ChartWidget from 'ui/shared/chart/ChartWidget';
interface Props {
addressQuery: UseQueryResult<Address>;
}
const AddressCoinBalanceChart = ({ addressQuery }: Props) => {
const fetch = useFetch();
const { data, isLoading, isError } = useQuery<unknown, unknown, AddressCoinBalanceHistoryChart>(
[ QueryKeys.addressCoinBalanceHistoryByDay, addressQuery.data?.hash ],
async() => fetch(`/node-api/addresses/${ addressQuery.data?.hash }/coin-balance-history-by-day`,
), {
enabled: Boolean(addressQuery.data?.hash),
});
const items = React.useMemo(() => data?.map(({ date, value }) => ({
date: new Date(date),
value: BigNumber(value).div(10 ** appConfig.network.currency.decimals).toNumber(),
})), [ data ]);
return (
<ChartWidget
chartHeight="200px"
title="Balances"
items={ items }
isLoading={ isLoading || isError }
/>
);
}; };
export default AddressCoinBalanceChart; export default React.memo(AddressCoinBalanceChart);
...@@ -16,13 +16,14 @@ import FullscreenChartModal from './FullscreenChartModal'; ...@@ -16,13 +16,14 @@ import FullscreenChartModal from './FullscreenChartModal';
type Props = { type Props = {
items?: Array<TimeChartItem>; items?: Array<TimeChartItem>;
title: string; title: string;
description: string; description?: string;
isLoading: boolean; isLoading: boolean;
chartHeight?: string;
} }
const DOWNLOAD_IMAGE_SCALE = 5; const DOWNLOAD_IMAGE_SCALE = 5;
const ChartWidget = ({ items, title, description, isLoading }: Props) => { const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Props) => {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const [ isFullscreen, setIsFullscreen ] = useState(false); const [ isFullscreen, setIsFullscreen ] = useState(false);
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true); const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
...@@ -78,7 +79,7 @@ const ChartWidget = ({ items, title, description, isLoading }: Props) => { ...@@ -78,7 +79,7 @@ const ChartWidget = ({ items, title, description, isLoading }: Props) => {
}, [ title ]); }, [ title ]);
if (isLoading) { if (isLoading) {
return <ChartWidgetSkeleton/>; return <ChartWidgetSkeleton hasDescription={ Boolean(description) } chartHeight={ chartHeight }/>;
} }
if (items) { if (items) {
...@@ -105,15 +106,17 @@ const ChartWidget = ({ items, title, description, isLoading }: Props) => { ...@@ -105,15 +106,17 @@ const ChartWidget = ({ items, title, description, isLoading }: Props) => {
{ title } { title }
</Text> </Text>
<Text { description && (
mb={ 1 } <Text
gridColumn={ 1 } mb={ 1 }
as="p" gridColumn={ 1 }
variant="secondary" as="p"
fontSize="xs" variant="secondary"
> fontSize="xs"
{ description } >
</Text> { description }
</Text>
) }
<Tooltip label="Reset zoom"> <Tooltip label="Reset zoom">
<IconButton <IconButton
...@@ -170,12 +173,14 @@ const ChartWidget = ({ items, title, description, isLoading }: Props) => { ...@@ -170,12 +173,14 @@ const ChartWidget = ({ items, title, description, isLoading }: Props) => {
</Menu> </Menu>
</Grid> </Grid>
<ChartWidgetGraph <Box h={ chartHeight || 'auto' }>
items={ items } <ChartWidgetGraph
onZoom={ handleZoom } items={ items }
isZoomResetInitial={ isZoomResetInitial } onZoom={ handleZoom }
title={ title } isZoomResetInitial={ isZoomResetInitial }
/> title={ title }
/>
</Box>
</Box> </Box>
<FullscreenChartModal <FullscreenChartModal
...@@ -192,4 +197,4 @@ const ChartWidget = ({ items, title, description, isLoading }: Props) => { ...@@ -192,4 +197,4 @@ const ChartWidget = ({ items, title, description, isLoading }: Props) => {
return null; return null;
}; };
export default ChartWidget; export default React.memo(ChartWidget);
...@@ -22,7 +22,7 @@ interface Props { ...@@ -22,7 +22,7 @@ interface Props {
isZoomResetInitial: boolean; isZoomResetInitial: boolean;
} }
const CHART_MARGIN = { bottom: 20, left: 30, right: 20, top: 10 }; const CHART_MARGIN = { bottom: 20, left: 40, right: 20, top: 10 };
const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title }: Props) => { const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
...@@ -31,10 +31,9 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title ...@@ -31,10 +31,9 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
const overlayRef = React.useRef<SVGRectElement>(null); const overlayRef = React.useRef<SVGRectElement>(null);
const chartId = useMemo(() => `chart-${ title.split(' ').join('') }`, [ title ]); const chartId = useMemo(() => `chart-${ title.split(' ').join('') }`, [ title ]);
const [ range, setRange ] = React.useState<[ number, number ]>([ 0, Infinity ]); const [ range, setRange ] = React.useState<[ number, number ]>([ 0, Infinity ]);
const { innerWidth, innerHeight } = useChartSize(ref.current, CHART_MARGIN); const { width, height, innerWidth, innerHeight } = useChartSize(ref.current, CHART_MARGIN);
const displayedData = useMemo(() => items.slice(range[0], range[1]).map((d) => const displayedData = useMemo(() => items.slice(range[0], range[1]), [ items, range ]);
({ ...d, date: new Date(d.date) })), [ items, range ]);
const chartData = [ { items: items, name: 'Value', color } ]; const chartData = [ { items: items, name: 'Value', color } ];
const { yTickFormat, xScale, yScale } = useTimeChartController({ const { yTickFormat, xScale, yScale } = useTimeChartController({
...@@ -55,9 +54,9 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title ...@@ -55,9 +54,9 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
}, [ isZoomResetInitial ]); }, [ isZoomResetInitial ]);
return ( return (
<svg width="100%" height="100%" ref={ ref } cursor="pointer" id={ chartId }> <svg width={ width || '100%' } height={ height || 'auto' } ref={ ref } cursor="pointer" id={ chartId } opacity={ width ? 1 : 0 }>
<g transform={ `translate(${ CHART_MARGIN?.left || 0 },${ CHART_MARGIN?.top || 0 })` } opacity={ innerWidth ? 1 : 0 }> <g transform={ `translate(${ CHART_MARGIN?.left || 0 },${ CHART_MARGIN?.top || 0 })` }>
<ChartGridLine <ChartGridLine
type="horizontal" type="horizontal"
scale={ yScale } scale={ yScale }
......
import { Box, Skeleton } from '@chakra-ui/react'; import { Box, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
const ChartWidgetSkeleton = () => { interface Props {
hasDescription: boolean;
chartHeight?: string;
}
const ChartWidgetSkeleton = ({ hasDescription, chartHeight }: Props) => {
return ( return (
<Box <Box
height="235px" height="235px"
paddingY={{ base: 3, lg: 4 }} paddingY={{ base: 3, lg: 4 }}
> >
<Skeleton w="75%" h="24px" mb={ 1 }/> <Skeleton w="75%" h="24px"/>
<Skeleton w="50%" h="18px" mb={ 5 }/> { hasDescription && <Skeleton w="50%" h="18px" mt={ 1 }/> }
<Skeleton w="100%" h="150px"/> <Skeleton w="100%" h={ chartHeight || '150px' } mt={ 5 }/>
</Box> </Box>
); );
}; };
......
...@@ -10,7 +10,7 @@ import ChartWidgetGraph from './ChartWidgetGraph'; ...@@ -10,7 +10,7 @@ import ChartWidgetGraph from './ChartWidgetGraph';
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
title: string; title: string;
description: string; description?: string;
items: Array<TimeChartItem>; items: Array<TimeChartItem>;
onClose: () => void; onClose: () => void;
} }
...@@ -56,14 +56,16 @@ const FullscreenChartModal = ({ ...@@ -56,14 +56,16 @@ const FullscreenChartModal = ({
{ title } { title }
</Heading> </Heading>
<Text { description && (
gridColumn={ 1 } <Text
as="p" gridColumn={ 1 }
variant="secondary" as="p"
fontSize="xs" variant="secondary"
> fontSize="xs"
{ description } >
</Text> { description }
</Text>
) }
{ !isZoomResetInitial && ( { !isZoomResetInitial && (
<Button <Button
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment