Commit e22db695 authored by isstuev's avatar isstuev

stats fixes after test part 1

parent ab22a7ab
...@@ -248,7 +248,7 @@ export default function useNavItems(): ReturnType { ...@@ -248,7 +248,7 @@ export default function useNavItems(): ReturnType {
text: 'Charts & stats', text: 'Charts & stats',
nextRoute: { pathname: '/stats' as const }, nextRoute: { pathname: '/stats' as const },
icon: 'stats', icon: 'stats',
isActive: pathname === '/stats', isActive: pathname.startsWith('/stats'),
} : null, } : null,
apiNavItems.length > 0 && { apiNavItems.length > 0 && {
text: 'API', text: 'API',
......
import { Button, Flex, IconButton, Text } from '@chakra-ui/react'; import { Button, Flex, IconButton, Link, Text } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -19,7 +19,7 @@ import ChartMenu from 'ui/shared/chart/ChartMenu'; ...@@ -19,7 +19,7 @@ import ChartMenu from 'ui/shared/chart/ChartMenu';
import ChartResolutionSelect from 'ui/shared/chart/ChartResolutionSelect'; import ChartResolutionSelect from 'ui/shared/chart/ChartResolutionSelect';
import ChartWidgetContent from 'ui/shared/chart/ChartWidgetContent'; import ChartWidgetContent from 'ui/shared/chart/ChartWidgetContent';
import useChartQuery from 'ui/shared/chart/useChartQuery'; import useChartQuery from 'ui/shared/chart/useChartQuery';
import useZoomReset from 'ui/shared/chart/useZoomReset'; import useZoom from 'ui/shared/chart/useZoom';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
...@@ -46,7 +46,7 @@ const Chart = () => { ...@@ -46,7 +46,7 @@ const Chart = () => {
const id = getQueryParamString(router.query.id); const id = getQueryParamString(router.query.id);
const [ intervalState, setIntervalState ] = React.useState<StatsIntervalIds | undefined>(); const [ intervalState, setIntervalState ] = React.useState<StatsIntervalIds | undefined>();
const [ resolution, setResolution ] = React.useState<Resolution>(DEFAULT_RESOLUTION); const [ resolution, setResolution ] = React.useState<Resolution>(DEFAULT_RESOLUTION);
const { isZoomResetInitial, handleZoom, handleZoomReset } = useZoomReset(); const { zoomRange, handleZoom, handleZoomReset } = useZoom();
const interval = intervalState || getIntervalByResolution(resolution); const interval = intervalState || getIntervalByResolution(resolution);
...@@ -155,6 +155,10 @@ const Chart = () => { ...@@ -155,6 +155,10 @@ const Chart = () => {
title={ info?.title || '' } title={ info?.title || '' }
isLoading={ lineQuery.isPlaceholderData } isLoading={ lineQuery.isPlaceholderData }
chartRef={ ref } chartRef={ ref }
resolution={ resolution }
zoomRange={ zoomRange }
handleZoom={ handleZoom }
handleZoomReset={ handleZoomReset }
/> />
) } ) }
</Flex> </Flex>
...@@ -172,44 +176,31 @@ const Chart = () => { ...@@ -172,44 +176,31 @@ const Chart = () => {
withTextAd withTextAd
/> />
<Flex alignItems="center" justifyContent="space-between"> <Flex alignItems="center" justifyContent="space-between">
<Flex alignItems="center" gap={ 3 } maxW="100%" overflow="hidden"> <Flex alignItems="center" gap={{ base: 3, lg: 6 }} maxW="100%" overflow="hidden">
<Text>Period</Text> <Flex alignItems="center" gap={ 3 }>
<ChartIntervalSelect interval={ interval } onIntervalChange={ setIntervalState }/> <Text>Period</Text>
<ChartIntervalSelect interval={ interval } onIntervalChange={ setIntervalState }/>
</Flex>
{ lineQuery.data?.info?.resolutions && lineQuery.data?.info?.resolutions.length > 1 && ( { lineQuery.data?.info?.resolutions && lineQuery.data?.info?.resolutions.length > 1 && (
<> <Flex alignItems="center" gap={ 3 }>
<Text ml={{ base: 0, lg: 3 }}>{ isMobile ? 'Res.' : 'Resolution' }</Text> <Text>{ isMobile ? 'Res.' : 'Resolution' }</Text>
<ChartResolutionSelect <ChartResolutionSelect
resolution={ resolution } resolution={ resolution }
onResolutionChange={ setResolution } onResolutionChange={ setResolution }
resolutions={ lineQuery.data?.info?.resolutions || [] } resolutions={ lineQuery.data?.info?.resolutions || [] }
/> />
</> </Flex>
) } ) }
{ (!isZoomResetInitial || resolution !== DEFAULT_RESOLUTION) && ( { (Boolean(zoomRange)) && (
isMobile ? ( <Link
<IconButton onClick={ handleReset }
aria-label="Reset" display="flex"
variant="ghost" alignItems="center"
size="sm" gap={ 2 }
icon={ <IconSvg name="repeat" boxSize={ 5 }/> } >
onClick={ handleReset } <IconSvg name="repeat" w={ 5 } h={ 5 }/>
/> { !isMobile && 'Reset' }
) : ( </Link>
<Button
leftIcon={ <IconSvg name="repeat" w={ 4 } h={ 4 }/> }
colorScheme="blue"
gridColumn={ 2 }
justifySelf="end"
alignSelf="top"
gridRow="1/3"
size="sm"
variant="outline"
onClick={ handleReset }
ml={ 6 }
>
Reset
</Button>
)
) } ) }
</Flex> </Flex>
{ !isMobile && shareAndMenu } { !isMobile && shareAndMenu }
...@@ -228,9 +219,10 @@ const Chart = () => { ...@@ -228,9 +219,10 @@ const Chart = () => {
units={ info?.units || undefined } units={ info?.units || undefined }
isEnlarged isEnlarged
isLoading={ lineQuery.isPlaceholderData } isLoading={ lineQuery.isPlaceholderData }
isZoomResetInitial={ isZoomResetInitial } zoomRange={ zoomRange }
handleZoom={ handleZoom } handleZoom={ handleZoom }
emptyText="No data for the selected resolution & interval." emptyText="No data for the selected resolution & interval."
resolution={ resolution }
/> />
</Flex> </Flex>
</> </>
......
...@@ -12,6 +12,7 @@ import domToImage from 'dom-to-image'; ...@@ -12,6 +12,7 @@ import domToImage from 'dom-to-image';
import React from 'react'; import React from 'react';
import type { TimeChartItem } from './types'; import type { TimeChartItem } from './types';
import type { Resolution } from '@blockscout/stats-types';
import type { Route } from 'nextjs-routes'; import type { Route } from 'nextjs-routes';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
...@@ -33,11 +34,27 @@ export type Props = { ...@@ -33,11 +34,27 @@ export type Props = {
isLoading: boolean; isLoading: boolean;
chartRef: React.RefObject<HTMLDivElement>; chartRef: React.RefObject<HTMLDivElement>;
href?: Route; href?: Route;
resolution?: Resolution;
zoomRange?: [ Date, Date ];
handleZoom: (range: [ Date, Date ]) => void;
handleZoomReset: () => void;
} }
const DOWNLOAD_IMAGE_SCALE = 5; const DOWNLOAD_IMAGE_SCALE = 5;
const ChartMenu = ({ items, title, description, units, isLoading, chartRef, href }: Props) => { const ChartMenu = ({
items,
title,
description,
units,
isLoading,
chartRef,
href,
resolution,
zoomRange,
handleZoom,
handleZoomReset,
}: Props) => {
const pngBackgroundColor = useColorModeValue('white', 'black'); const pngBackgroundColor = useColorModeValue('white', 'black');
const [ isFullscreen, setIsFullscreen ] = React.useState(false); const [ isFullscreen, setIsFullscreen ] = React.useState(false);
...@@ -172,6 +189,10 @@ const ChartMenu = ({ items, title, description, units, isLoading, chartRef, href ...@@ -172,6 +189,10 @@ const ChartMenu = ({ items, title, description, units, isLoading, chartRef, href
description={ description } description={ description }
onClose={ clearFullscreenChart } onClose={ clearFullscreenChart }
units={ units } units={ units }
resolution={ resolution }
zoomRange={ zoomRange }
handleZoom={ handleZoom }
handleZoomReset={ handleZoomReset }
/> />
) } ) }
</> </>
......
import * as d3 from 'd3'; import * as d3 from 'd3';
import React from 'react'; import React from 'react';
import { Resolution } from '@blockscout/stats-types';
import type { TimeChartData } from 'ui/shared/chart/types'; import type { TimeChartData } from 'ui/shared/chart/types';
import ChartTooltipBackdrop, { useRenderBackdrop } from './tooltip/ChartTooltipBackdrop'; import ChartTooltipBackdrop, { useRenderBackdrop } from './tooltip/ChartTooltipBackdrop';
...@@ -21,9 +22,21 @@ interface Props { ...@@ -21,9 +22,21 @@ interface Props {
yScale: d3.ScaleLinear<number, number>; yScale: d3.ScaleLinear<number, number>;
anchorEl: SVGRectElement | null; anchorEl: SVGRectElement | null;
noAnimation?: boolean; noAnimation?: boolean;
resolution?: Resolution;
} }
const ChartTooltip = ({ xScale, yScale, width, tooltipWidth = 200, height, data, anchorEl, noAnimation, ...props }: Props) => { const ChartTooltip = ({
xScale,
yScale,
width,
tooltipWidth = 200,
height,
data,
anchorEl,
noAnimation,
resolution,
...props
}: Props) => {
const ref = React.useRef<SVGGElement>(null); const ref = React.useRef<SVGGElement>(null);
const trackerId = React.useRef<number>(); const trackerId = React.useRef<number>();
const isVisible = React.useRef(false); const isVisible = React.useRef(false);
...@@ -150,8 +163,8 @@ const ChartTooltip = ({ xScale, yScale, width, tooltipWidth = 200, height, data, ...@@ -150,8 +163,8 @@ const ChartTooltip = ({ xScale, yScale, width, tooltipWidth = 200, height, data,
{ data.map(({ name }) => <ChartTooltipPoint key={ name }/>) } { data.map(({ name }) => <ChartTooltipPoint key={ name }/>) }
<ChartTooltipContent> <ChartTooltipContent>
<ChartTooltipBackdrop/> <ChartTooltipBackdrop/>
<ChartTooltipTitle/> <ChartTooltipTitle resolution={ resolution }/>
<ChartTooltipRow label="Date" lineNum={ 1 }/> <ChartTooltipRow label={ getDateLabel(resolution) } lineNum={ 1 }/>
{ data.map(({ name }, index) => <ChartTooltipRow key={ name } label={ name } lineNum={ index + 1 }/>) } { data.map(({ name }, index) => <ChartTooltipRow key={ name } label={ name } lineNum={ index + 1 }/>) }
</ChartTooltipContent> </ChartTooltipContent>
</g> </g>
...@@ -159,3 +172,16 @@ const ChartTooltip = ({ xScale, yScale, width, tooltipWidth = 200, height, data, ...@@ -159,3 +172,16 @@ const ChartTooltip = ({ xScale, yScale, width, tooltipWidth = 200, height, data,
}; };
export default React.memo(ChartTooltip); export default React.memo(ChartTooltip);
function getDateLabel(resolution?: Resolution): string {
switch (resolution) {
case Resolution.WEEK:
return 'Dates';
case Resolution.MONTH:
return 'Month';
case Resolution.YEAR:
return 'Year';
default:
return 'Date';
}
}
...@@ -17,7 +17,7 @@ import IconSvg from 'ui/shared/IconSvg'; ...@@ -17,7 +17,7 @@ import IconSvg from 'ui/shared/IconSvg';
import ChartMenu from './ChartMenu'; import ChartMenu from './ChartMenu';
import ChartWidgetContent from './ChartWidgetContent'; import ChartWidgetContent from './ChartWidgetContent';
import useZoomReset from './useZoomReset'; import useZoom from './useZoom';
export type Props = { export type Props = {
items?: Array<TimeChartItem>; items?: Array<TimeChartItem>;
...@@ -45,7 +45,7 @@ const ChartWidget = ({ ...@@ -45,7 +45,7 @@ const ChartWidget = ({
href, href,
}: Props) => { }: Props) => {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const { isZoomResetInitial, handleZoom, handleZoomReset } = useZoomReset(); const { zoomRange, handleZoom, handleZoomReset } = useZoom();
const borderColor = useColorModeValue('gray.200', 'gray.600'); const borderColor = useColorModeValue('gray.200', 'gray.600');
...@@ -60,7 +60,7 @@ const ChartWidget = ({ ...@@ -60,7 +60,7 @@ const ChartWidget = ({
title={ title } title={ title }
emptyText={ emptyText } emptyText={ emptyText }
handleZoom={ handleZoom } handleZoom={ handleZoom }
isZoomResetInitial={ isZoomResetInitial } zoomRange={ zoomRange }
noAnimation={ noAnimation } noAnimation={ noAnimation }
/> />
); );
...@@ -116,7 +116,7 @@ const ChartWidget = ({ ...@@ -116,7 +116,7 @@ const ChartWidget = ({
<Flex ml="auto" columnGap={ 2 }> <Flex ml="auto" columnGap={ 2 }>
<Tooltip label="Reset zoom"> <Tooltip label="Reset zoom">
<IconButton <IconButton
hidden={ isZoomResetInitial } hidden={ !zoomRange }
aria-label="Reset zoom" aria-label="Reset zoom"
colorScheme="blue" colorScheme="blue"
w={ 9 } w={ 9 }
...@@ -137,6 +137,9 @@ const ChartWidget = ({ ...@@ -137,6 +137,9 @@ const ChartWidget = ({
isLoading={ isLoading } isLoading={ isLoading }
chartRef={ ref } chartRef={ ref }
units={ units } units={ units }
handleZoom={ handleZoom }
handleZoomReset={ handleZoomReset }
zoomRange={ zoomRange }
/> />
) } ) }
</Flex> </Flex>
......
...@@ -2,6 +2,7 @@ import { Box, Center, Flex, Link, Skeleton, Text } from '@chakra-ui/react'; ...@@ -2,6 +2,7 @@ import { Box, Center, Flex, Link, Skeleton, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TimeChartItem } from './types'; import type { TimeChartItem } from './types';
import type { Resolution } from '@blockscout/stats-types';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
...@@ -15,10 +16,11 @@ export type Props = { ...@@ -15,10 +16,11 @@ export type Props = {
isLoading?: boolean; isLoading?: boolean;
isError?: boolean; isError?: boolean;
emptyText?: string; emptyText?: string;
handleZoom: () => void; zoomRange?: [ Date, Date ];
isZoomResetInitial: boolean; handleZoom: (range: [ Date, Date ]) => void;
isEnlarged?: boolean; isEnlarged?: boolean;
noAnimation?: boolean; noAnimation?: boolean;
resolution?: Resolution;
} }
const ChartWidgetContent = ({ const ChartWidgetContent = ({
...@@ -28,10 +30,11 @@ const ChartWidgetContent = ({ ...@@ -28,10 +30,11 @@ const ChartWidgetContent = ({
isError, isError,
units, units,
emptyText, emptyText,
zoomRange,
handleZoom, handleZoom,
isZoomResetInitial,
isEnlarged, isEnlarged,
noAnimation, noAnimation,
resolution,
}: Props) => { }: Props) => {
const hasItems = items && items.length > 2; const hasItems = items && items.length > 2;
...@@ -71,12 +74,13 @@ const ChartWidgetContent = ({ ...@@ -71,12 +74,13 @@ const ChartWidgetContent = ({
<Box flexGrow={ 1 } maxW="100%" position="relative" h="100%"> <Box flexGrow={ 1 } maxW="100%" position="relative" h="100%">
<ChartWidgetGraph <ChartWidgetGraph
items={ items } items={ items }
zoomRange={ zoomRange }
onZoom={ handleZoom } onZoom={ handleZoom }
isZoomResetInitial={ isZoomResetInitial }
title={ title } title={ title }
units={ units } units={ units }
isEnlarged={ isEnlarged } isEnlarged={ isEnlarged }
noAnimation={ noAnimation } noAnimation={ noAnimation }
resolution={ resolution }
/> />
<WatermarkIcon w="162px" h="15%"/> <WatermarkIcon w="162px" h="15%"/>
</Box> </Box>
......
...@@ -2,9 +2,9 @@ import { useToken } from '@chakra-ui/react'; ...@@ -2,9 +2,9 @@ import { useToken } from '@chakra-ui/react';
import * as d3 from 'd3'; import * as d3 from 'd3';
import React from 'react'; import React from 'react';
import { Resolution } from '@blockscout/stats-types';
import type { ChartMargin, TimeChartData, TimeChartItem } from 'ui/shared/chart/types'; import type { ChartMargin, TimeChartData, TimeChartItem } from 'ui/shared/chart/types';
import dayjs from 'lib/date/dayjs';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import ChartArea from 'ui/shared/chart/ChartArea'; import ChartArea from 'ui/shared/chart/ChartArea';
import ChartAxis from 'ui/shared/chart/ChartAxis'; import ChartAxis from 'ui/shared/chart/ChartAxis';
...@@ -20,37 +20,42 @@ interface Props { ...@@ -20,37 +20,42 @@ interface Props {
title: string; title: string;
units?: string; units?: string;
items: Array<TimeChartItem>; items: Array<TimeChartItem>;
onZoom: () => void; zoomRange?: [ Date, Date ];
isZoomResetInitial: boolean; onZoom: (range: [ Date, Date ]) => void;
margin?: ChartMargin; margin?: ChartMargin;
noAnimation?: boolean; noAnimation?: boolean;
resolution?: Resolution;
} }
// temporarily turn off the data aggregation, we need a better algorithm for that
const MAX_SHOW_ITEMS = 100_000_000_000;
const DEFAULT_CHART_MARGIN = { bottom: 20, left: 10, right: 20, top: 10 }; const DEFAULT_CHART_MARGIN = { bottom: 20, left: 10, right: 20, top: 10 };
const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title, margin: marginProps, units, noAnimation }: Props) => { const ChartWidgetGraph = ({
isEnlarged,
items,
onZoom,
title,
margin: marginProps,
units,
noAnimation,
resolution,
zoomRange,
}: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const color = useToken('colors', 'blue.200'); const color = useToken('colors', 'blue.200');
const chartId = `chart-${ title.split(' ').join('') }-${ isEnlarged ? 'fullscreen' : 'small' }`; const chartId = `chart-${ title.split(' ').join('') }-${ isEnlarged ? 'fullscreen' : 'small' }`;
const overlayRef = React.useRef<SVGRectElement>(null); const overlayRef = React.useRef<SVGRectElement>(null);
const [ range, setRange ] = React.useState<[ Date, Date ]>([ items[0].date, items[items.length - 1].date ]); const range = React.useMemo(() => zoomRange || [ items[0].date, items[items.length - 1].date ], [ zoomRange, items ]);
const rangedItems = React.useMemo(() => const displayedData = React.useMemo(() =>
items.filter((item) => item.date >= range[0] && item.date <= range[1]), items
[ items, range ]); .filter((item) => item.date >= range[0] && item.date <= range[1])
const isGroupedValues = rangedItems.length > MAX_SHOW_ITEMS; .map((item) => ({
...item,
const displayedData = React.useMemo(() => { dateLabel: getDateLabel(item.date, item.date_to, resolution),
if (isGroupedValues) { })),
return groupChartItemsByWeekNumber(rangedItems); [ items, range, resolution ]);
} else {
return rangedItems;
}
}, [ isGroupedValues, rangedItems ]);
const chartData: TimeChartData = React.useMemo(() => ([ { items: displayedData, name: 'Value', color, units } ]), [ color, displayedData, units ]); const chartData: TimeChartData = React.useMemo(() => ([ { items: displayedData, name: 'Value', color, units } ]), [ color, displayedData, units ]);
...@@ -80,17 +85,6 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title ...@@ -80,17 +85,6 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
axesConfig, axesConfig,
}); });
const handleRangeSelect = React.useCallback((nextRange: [ Date, Date ]) => {
setRange([ nextRange[0], nextRange[1] ]);
onZoom();
}, [ onZoom ]);
React.useEffect(() => {
if (isZoomResetInitial) {
setRange([ items[0].date, items[items.length - 1].date ]);
}
}, [ isZoomResetInitial, items ]);
return ( return (
<svg width="100%" height="100%" ref={ ref } cursor="pointer" id={ chartId } opacity={ rect ? 1 : 0 }> <svg width="100%" height="100%" ref={ ref } cursor="pointer" id={ chartId } opacity={ rect ? 1 : 0 }>
...@@ -143,12 +137,13 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title ...@@ -143,12 +137,13 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
<ChartTooltip <ChartTooltip
anchorEl={ overlayRef.current } anchorEl={ overlayRef.current }
width={ innerWidth } width={ innerWidth }
tooltipWidth={ isGroupedValues ? 280 : 200 } tooltipWidth={ (resolution === Resolution.WEEK) ? 280 : 200 }
height={ innerHeight } height={ innerHeight }
xScale={ axes.x.scale } xScale={ axes.x.scale }
yScale={ axes.y.scale } yScale={ axes.y.scale }
data={ chartData } data={ chartData }
noAnimation={ noAnimation } noAnimation={ noAnimation }
resolution={ resolution }
/> />
<ChartSelectionX <ChartSelectionX
...@@ -156,7 +151,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title ...@@ -156,7 +151,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
height={ innerHeight } height={ innerHeight }
scale={ axes.x.scale } scale={ axes.x.scale }
data={ chartData } data={ chartData }
onSelect={ handleRangeSelect } onSelect={ onZoom }
/> />
</ChartOverlay> </ChartOverlay>
</g> </g>
...@@ -166,13 +161,15 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title ...@@ -166,13 +161,15 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
export default React.memo(ChartWidgetGraph); export default React.memo(ChartWidgetGraph);
function groupChartItemsByWeekNumber(items: Array<TimeChartItem>): Array<TimeChartItem> { function getDateLabel(date: Date, dateTo?: Date, resolution?: Resolution): string {
return d3.rollups(items, switch (resolution) {
(group) => ({ case Resolution.WEEK:
date: group[0].date, return d3.timeFormat('%e %b %Y')(date) + (dateTo ? ` – ${ d3.timeFormat('%e %b %Y')(dateTo) }` : '');
value: d3.sum(group, (d) => d.value), case Resolution.MONTH:
dateLabel: `${ d3.timeFormat('%e %b %Y')(group[0].date) }${ d3.timeFormat('%e %b %Y')(group[group.length - 1].date) }`, return d3.timeFormat('%b %Y')(date);
}), case Resolution.YEAR:
(t) => `${ dayjs(t.date).week() } / ${ dayjs(t.date).year() }`, return d3.timeFormat('%Y')(date);
).map(([ , v ]) => v); default:
return d3.timeFormat('%e %b %Y')(date);
}
} }
import { Box, Button, Grid, Heading, Modal, ModalBody, ModalCloseButton, ModalContent, ModalOverlay, Text } from '@chakra-ui/react'; import { Box, Button, Grid, Heading, Modal, ModalBody, ModalCloseButton, ModalContent, ModalOverlay, Text } from '@chakra-ui/react';
import React, { useCallback } from 'react'; import React from 'react';
import type { TimeChartItem } from './types'; import type { TimeChartItem } from './types';
import type { Resolution } from '@blockscout/stats-types';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
...@@ -14,6 +15,10 @@ type Props = { ...@@ -14,6 +15,10 @@ type Props = {
items: Array<TimeChartItem>; items: Array<TimeChartItem>;
onClose: () => void; onClose: () => void;
units?: string; units?: string;
resolution?: Resolution;
zoomRange?: [ Date, Date ];
handleZoom: (range: [ Date, Date ]) => void;
handleZoomReset: () => void;
} }
const FullscreenChartModal = ({ const FullscreenChartModal = ({
...@@ -23,17 +28,11 @@ const FullscreenChartModal = ({ ...@@ -23,17 +28,11 @@ const FullscreenChartModal = ({
items, items,
units, units,
onClose, onClose,
resolution,
zoomRange,
handleZoom,
handleZoomReset,
}: Props) => { }: Props) => {
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
const handleZoom = useCallback(() => {
setIsZoomResetInitial(false);
}, []);
const handleZoomReset = useCallback(() => {
setIsZoomResetInitial(true);
}, []);
return ( return (
<Modal <Modal
isOpen={ isOpen } isOpen={ isOpen }
...@@ -69,7 +68,7 @@ const FullscreenChartModal = ({ ...@@ -69,7 +68,7 @@ const FullscreenChartModal = ({
</Text> </Text>
) } ) }
{ !isZoomResetInitial && ( { Boolean(zoomRange) && (
<Button <Button
leftIcon={ <IconSvg name="repeat" w={ 4 } h={ 4 }/> } leftIcon={ <IconSvg name="repeat" w={ 4 } h={ 4 }/> }
colorScheme="blue" colorScheme="blue"
...@@ -98,8 +97,9 @@ const FullscreenChartModal = ({ ...@@ -98,8 +97,9 @@ const FullscreenChartModal = ({
items={ items } items={ items }
units={ units } units={ units }
handleZoom={ handleZoom } handleZoom={ handleZoom }
isZoomResetInitial={ isZoomResetInitial } zoomRange={ zoomRange }
title={ title } title={ title }
resolution={ resolution }
/> />
</ModalBody> </ModalBody>
</ModalContent> </ModalContent>
......
...@@ -2,10 +2,15 @@ import { useToken } from '@chakra-ui/react'; ...@@ -2,10 +2,15 @@ import { useToken } from '@chakra-ui/react';
import * as d3 from 'd3'; import * as d3 from 'd3';
import React from 'react'; import React from 'react';
import { Resolution } from '@blockscout/stats-types';
import { STATS_RESOLUTIONS } from 'ui/stats/constants';
import ChartTooltipRow from './ChartTooltipRow'; import ChartTooltipRow from './ChartTooltipRow';
const ChartTooltipTitle = () => { const ChartTooltipTitle = ({ resolution = Resolution.DAY }: { resolution?: Resolution }) => {
const titleColor = useToken('colors', 'yellow.300'); const titleColor = useToken('colors', 'yellow.300');
const resolutionTitle = STATS_RESOLUTIONS.find(r => r.id === resolution)?.title || 'day';
return ( return (
<ChartTooltipRow lineNum={ 0 }> <ChartTooltipRow lineNum={ 0 }>
...@@ -16,7 +21,7 @@ const ChartTooltipTitle = () => { ...@@ -16,7 +21,7 @@ const ChartTooltipTitle = () => {
opacity={ 0 } opacity={ 0 }
dominantBaseline="hanging" dominantBaseline="hanging"
> >
Incomplete day { `Incomplete ${ resolutionTitle.toLowerCase() }` }
</text> </text>
</ChartTooltipRow> </ChartTooltipRow>
); );
......
...@@ -6,6 +6,7 @@ export interface TimeChartItemRaw { ...@@ -6,6 +6,7 @@ export interface TimeChartItemRaw {
export interface TimeChartItem { export interface TimeChartItem {
date: Date; date: Date;
date_to?: Date;
dateLabel?: string; dateLabel?: string;
value: number; value: number;
isApproximate?: boolean; isApproximate?: boolean;
......
...@@ -50,7 +50,7 @@ export default function useChartQuery(id: string, resolution: Resolution, interv ...@@ -50,7 +50,7 @@ export default function useChartQuery(id: string, resolution: Resolution, interv
}, [ info, lineQuery.data?.info, lineQuery.isPlaceholderData ]); }, [ info, lineQuery.data?.info, lineQuery.isPlaceholderData ]);
const items = React.useMemo(() => lineQuery.data?.chart?.map((item) => { const items = React.useMemo(() => lineQuery.data?.chart?.map((item) => {
return { date: new Date(item.date), value: Number(item.value), isApproximate: item.is_approximate }; return { date: new Date(item.date), date_to: new Date(item.date_to), value: Number(item.value), isApproximate: item.is_approximate };
}), [ lineQuery ]); }), [ lineQuery ]);
return { return {
......
import React from 'react'; import React from 'react';
export default function useZoomReset() { export default function useZoom() {
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true); const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
const [ zoomRange, setZoomRange ] = React.useState<[ Date, Date ] | undefined>();
const handleZoom = React.useCallback(() => { const handleZoom = React.useCallback((range: [ Date, Date ]) => {
setZoomRange(range);
setIsZoomResetInitial(false); setIsZoomResetInitial(false);
}, []); }, []);
const handleZoomReset = React.useCallback(() => { const handleZoomReset = React.useCallback(() => {
setZoomRange(undefined);
setIsZoomResetInitial(true); setIsZoomResetInitial(true);
}, []); }, []);
return { return {
isZoomResetInitial, isZoomResetInitial,
zoomRange,
handleZoom, handleZoom,
handleZoomReset, handleZoomReset,
}; };
......
...@@ -47,6 +47,8 @@ const TagGroupSelect = <T extends string>({ items, value, isMulti, onChange, tag ...@@ -47,6 +47,8 @@ const TagGroupSelect = <T extends string>({ items, value, isMulti, onChange, tag
cursor="pointer" cursor="pointer"
onClick={ onItemClick } onClick={ onItemClick }
size={ tagSize } size={ tagSize }
display="inline-flex"
justifyContent="center"
> >
{ item.title } { item.title }
</Tag> </Tag>
......
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