Commit 8116525c authored by tom's avatar tom

multiline chart

parent 51b62da5
__snapshots__/** filter=lfs diff=lfs merge=lfs -text __snapshots__/** filter=lfs diff=lfs merge=lfs -text
data/charts_eth_txs.json filter=lfs diff=lfs merge=lfs -text data/charts_eth_txs.json filter=lfs diff=lfs merge=lfs -text
data/charts_eth_token_transfer.json filter=lfs diff=lfs merge=lfs -text
This source diff could not be displayed because it is stored in LFS. You can view the blob instead.
import { useToken, Button, Box } from '@chakra-ui/react'; import { useToken, Button, Box } from '@chakra-ui/react';
import _range from 'lodash/range';
import React from 'react'; import React from 'react';
import json from 'data/charts_eth_txs.json'; import type { TimeChartData } from 'ui/shared/chart/types';
import ethTokenTransferData from 'data/charts_eth_token_transfer.json';
import ethTxsData from 'data/charts_eth_txs.json';
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';
import ChartGridLine from 'ui/shared/chart/ChartGridLine'; import ChartGridLine from 'ui/shared/chart/ChartGridLine';
import ChartLegend from 'ui/shared/chart/ChartLegend';
import ChartLine from 'ui/shared/chart/ChartLine'; import ChartLine from 'ui/shared/chart/ChartLine';
import ChartOverlay from 'ui/shared/chart/ChartOverlay'; import ChartOverlay from 'ui/shared/chart/ChartOverlay';
import ChartSelectionX from 'ui/shared/chart/ChartSelectionX'; import ChartSelectionX from 'ui/shared/chart/ChartSelectionX';
import ChartTooltip from 'ui/shared/chart/ChartTooltip'; import ChartTooltip from 'ui/shared/chart/ChartTooltip';
import useBrushX from 'ui/shared/chart/useBrushX'; // import useBrushX from 'ui/shared/chart/useBrushX';
import useChartSize from 'ui/shared/chart/useChartSize'; import useChartSize from 'ui/shared/chart/useChartSize';
import useTimeChartController from 'ui/shared/chart/useTimeChartController'; import useTimeChartController from 'ui/shared/chart/useTimeChartController';
const CHART_MARGIN = { bottom: 20, left: 65, right: 30, top: 10 }; const CHART_MARGIN = { bottom: 20, left: 65, right: 30, top: 10 };
const EthereumDailyTxsChart = () => { const EthereumChart = () => {
const ref = React.useRef<SVGSVGElement>(null); const ref = React.useRef<SVGSVGElement>(null);
const overlayRef = React.useRef<SVGRectElement>(null); const overlayRef = React.useRef<SVGRectElement>(null);
const { width, height, innerWidth, innerHeight } = useChartSize(ref.current, CHART_MARGIN); const { width, height, innerWidth, innerHeight } = useChartSize(ref.current, CHART_MARGIN);
const [ range, setRange ] = React.useState<[number, number]>([ 0, Infinity ]); const [ range, setRange ] = React.useState<[number, number]>([ 0, Infinity ]);
const brushLimits = React.useMemo(() => ( const data: TimeChartData = [
[ [ 0, innerHeight ], [ innerWidth, height ] ] as [[number, number], [number, number]] {
), [ height, innerHeight, innerWidth ]); name: 'Daily Transactions',
useBrushX({ anchor: ref.current, limits: brushLimits, setRange }); color: useToken('colors', 'blue.500'),
items: ethTxsData.slice(range[0], range[1]).map((d) => ({ ...d, date: new Date(d.date) })),
},
{
name: 'ERC-20 Token Transfers',
color: useToken('colors', 'green.500'),
items: ethTokenTransferData.slice(range[0], range[1]).map((d) => ({ ...d, date: new Date(d.date) })),
},
];
const data = { const [ selectedLines, setSelectedLines ] = React.useState<Array<number>>(_range(data.length));
items: json.slice(range[0], range[1]).map((d) => ({ ...d, date: new Date(d.date) })), const filteredData = data.filter((_, index) => selectedLines.includes(index));
};
const { yTickFormat, xScale, yScale } = useTimeChartController({ data, width: innerWidth, height: innerHeight });
const lineColor = useToken('colors', 'blue.500'); const { yTickFormat, xScale, yScale } = useTimeChartController({
data: filteredData.length === 0 ? data : filteredData,
width: innerWidth,
height: innerHeight,
});
const handleRangeSelect = React.useCallback((nextRange: [number, number]) => { const handleRangeSelect = React.useCallback((nextRange: [number, number]) => {
setRange([ range[0] + nextRange[0], range[0] + nextRange[1] ]); setRange([ range[0] + nextRange[0], range[0] + nextRange[1] ]);
...@@ -42,6 +57,17 @@ const EthereumDailyTxsChart = () => { ...@@ -42,6 +57,17 @@ const EthereumDailyTxsChart = () => {
setRange([ 0, Infinity ]); setRange([ 0, Infinity ]);
}, [ ]); }, [ ]);
const handleLegendItemClick = React.useCallback((index: number) => {
const nextSelectedLines = selectedLines.includes(index) ? selectedLines.filter((item) => item !== index) : [ ...selectedLines, index ];
setSelectedLines(nextSelectedLines);
}, [ selectedLines ]);
// uncomment if we need brush the chart
// const brushLimits = React.useMemo(() => (
// [ [ 0, innerHeight ], [ innerWidth, height ] ] as [[number, number], [number, number]]
// ), [ height, innerHeight, innerWidth ]);
// useBrushX({ anchor: ref.current, limits: brushLimits, setRange });
return ( return (
<Box display="inline-block" position="relative" width="100%" height="100%"> <Box display="inline-block" position="relative" width="100%" height="100%">
<svg width={ width || '100%' } height={ height || '100%' } ref={ ref }> <svg width={ width || '100%' } height={ height || '100%' } ref={ ref }>
...@@ -73,19 +99,25 @@ const EthereumDailyTxsChart = () => { ...@@ -73,19 +99,25 @@ const EthereumDailyTxsChart = () => {
/> />
{ /* GRAPH */ } { /* GRAPH */ }
<ChartLine { filteredData.map((d) => (
data={ data.items } <ChartLine
xScale={ xScale } key={ d.name }
yScale={ yScale } data={ d.items }
stroke={ lineColor } xScale={ xScale }
animation="left" yScale={ yScale }
/> stroke={ d.color }
<ChartArea animation="left"
data={ data.items } />
color={ lineColor } )) }
xScale={ xScale } { filteredData.map((d) => (
yScale={ yScale } <ChartArea
/> key={ d.name }
data={ d.items }
color={ d.color }
xScale={ xScale }
yScale={ yScale }
/>
)) }
{ /* AXISES */ } { /* AXISES */ }
<ChartAxis <ChartAxis
...@@ -104,22 +136,26 @@ const EthereumDailyTxsChart = () => { ...@@ -104,22 +136,26 @@ const EthereumDailyTxsChart = () => {
anchorEl={ overlayRef.current } anchorEl={ overlayRef.current }
disableAnimation disableAnimation
/> />
<ChartTooltip { filteredData.length > 0 && (
anchorEl={ overlayRef.current } <ChartTooltip
width={ innerWidth } anchorEl={ overlayRef.current }
height={ innerHeight } width={ innerWidth }
margin={ CHART_MARGIN } height={ innerHeight }
xScale={ xScale } margin={ CHART_MARGIN }
yScale={ yScale } xScale={ xScale }
data={ data } yScale={ yScale }
/> data={ filteredData }
<ChartSelectionX />
anchorEl={ overlayRef.current } ) }
height={ innerHeight } { filteredData.length > 0 && (
scale={ xScale } <ChartSelectionX
data={ data } anchorEl={ overlayRef.current }
onSelect={ handleRangeSelect } height={ innerHeight }
/> scale={ xScale }
data={ filteredData }
onSelect={ handleRangeSelect }
/>
) }
</ChartOverlay> </ChartOverlay>
</g> </g>
</svg> </svg>
...@@ -132,11 +168,12 @@ const EthereumDailyTxsChart = () => { ...@@ -132,11 +168,12 @@ const EthereumDailyTxsChart = () => {
right={ `${ CHART_MARGIN?.right || 0 }px` } right={ `${ CHART_MARGIN?.right || 0 }px` }
onClick={ handleZoomReset } onClick={ handleZoomReset }
> >
Reset zoom Reset zoom
</Button> </Button>
) } ) }
<ChartLegend data={ data } selectedIndexes={ selectedLines } onClick={ handleLegendItemClick }/>
</Box> </Box>
); );
}; };
export default React.memo(EthereumDailyTxsChart); export default React.memo(EthereumChart);
import { Box } from '@chakra-ui/react'; import { Box, Heading } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import EthereumDailyTxsChart from 'ui/charts/EthereumDailyTxsChart'; import EthereumChart from 'ui/charts/EthereumChart';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
const Graph = () => { const Graph = () => {
return ( return (
<Page> <Page>
<PageTitle text="Ethereum Daily Transactions Chart"/> <PageTitle text="Charts"/>
<Heading as="h2" size="sm" fontWeight="500" mb={ 3 }>Ethereum Daily Transactions & ERC-20 Token Transfer Chart</Heading>
<Box w="100%" h="400px"> <Box w="100%" h="400px">
<EthereumDailyTxsChart/> <EthereumChart/>
</Box> </Box>
</Page> </Page>
); );
......
...@@ -37,8 +37,8 @@ const ChartArea = ({ xScale, yScale, color, data, disableAnimation, ...props }: ...@@ -37,8 +37,8 @@ const ChartArea = ({ xScale, yScale, color, data, disableAnimation, ...props }:
<path ref={ ref } d={ d } fill={ `url(#gradient-${ color })` } opacity={ 0 } { ...props }/> <path ref={ ref } d={ d } fill={ `url(#gradient-${ color })` } opacity={ 0 } { ...props }/>
<defs> <defs>
<linearGradient id={ `gradient-${ color }` } x1="0%" x2="0%" y1="0%" y2="100%"> <linearGradient id={ `gradient-${ color }` } x1="0%" x2="0%" y1="0%" y2="100%">
<stop offset="0%" stopColor={ color } stopOpacity={ 0.9 }/> <stop offset="0%" stopColor={ color } stopOpacity={ 1 }/>
<stop offset="100%" stopColor={ color } stopOpacity={ 0.1 }/> <stop offset="100%" stopColor={ color } stopOpacity={ 0.15 }/>
</linearGradient> </linearGradient>
</defs> </defs>
</> </>
......
...@@ -45,7 +45,9 @@ const ChartAxis = ({ type, scale, ticks, tickFormat, disableAnimation, anchorEl, ...@@ -45,7 +45,9 @@ const ChartAxis = ({ type, scale, ticks, tickFormat, disableAnimation, anchorEl,
return; return;
} }
d3.select(anchorEl) const anchorD3 = d3.select(anchorEl);
anchorD3
.on('mouseout.axisX', () => { .on('mouseout.axisX', () => {
d3.select(ref.current) d3.select(ref.current)
.selectAll('text') .selectAll('text')
...@@ -60,6 +62,10 @@ const ChartAxis = ({ type, scale, ticks, tickFormat, disableAnimation, anchorEl, ...@@ -60,6 +62,10 @@ const ChartAxis = ({ type, scale, ticks, tickFormat, disableAnimation, anchorEl,
textElements textElements
.style('font-weight', (d, i) => i === index - 1 ? 'bold' : 'normal'); .style('font-weight', (d, i) => i === index - 1 ? 'bold' : 'normal');
}); });
return () => {
anchorD3.on('mouseout.axisX mousemove.axisX', null);
};
}, [ anchorEl, scale ]); }, [ anchorEl, scale ]);
return <g ref={ ref } { ...props }/>; return <g ref={ ref } { ...props }/>;
......
import { Box, Circle, Text } from '@chakra-ui/react';
import React from 'react';
import type { TimeChartData } from 'ui/shared/chart/types';
interface Props {
data: TimeChartData;
selectedIndexes: Array<number>;
onClick: (index: number) => void;
}
const ChartLegend = ({ data, selectedIndexes, onClick }: Props) => {
const handleItemClick = React.useCallback((event: React.MouseEvent<HTMLDivElement>) => {
const itemIndex = (event.currentTarget as HTMLDivElement).getAttribute('data-index');
onClick(Number(itemIndex));
}, [ onClick ]);
return (
<Box display="flex" columnGap={ 3 } mt={ 2 }>
{ data.map(({ name, color }, index) => {
const isSelected = selectedIndexes.includes(index);
return (
<Box
key={ name }
data-index={ index }
display="flex"
columnGap={ 1 }
alignItems="center"
onClick={ handleItemClick }
cursor="pointer"
>
<Circle
size={ 2 }
bgColor={ isSelected ? color : 'transparent' }
borderWidth={ 2 }
borderColor={ color }
/>
<Text fontSize="xs">
{ name }
</Text>
</Box>
);
}) }
</Box>
);
};
export default React.memo(ChartLegend);
...@@ -2,7 +2,7 @@ import { useToken } from '@chakra-ui/react'; ...@@ -2,7 +2,7 @@ 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 type { TimeChartItem } from 'ui/shared/chart/types'; import type { TimeChartData, TimeChartItem } from 'ui/shared/chart/types';
const SELECTION_THRESHOLD = 1; const SELECTION_THRESHOLD = 1;
...@@ -10,9 +10,7 @@ interface Props { ...@@ -10,9 +10,7 @@ interface Props {
height: number; height: number;
anchorEl?: SVGRectElement | null; anchorEl?: SVGRectElement | null;
scale: d3.ScaleTime<number, number>; scale: d3.ScaleTime<number, number>;
data: { data: TimeChartData;
items: Array<TimeChartItem>;
};
onSelect: (range: [number, number]) => void; onSelect: (range: [number, number]) => void;
} }
...@@ -28,8 +26,8 @@ const ChartSelectionX = ({ anchorEl, height, scale, data, onSelect }: Props) => ...@@ -28,8 +26,8 @@ const ChartSelectionX = ({ anchorEl, height, scale, data, onSelect }: Props) =>
const getIndexByX = React.useCallback((x: number) => { const getIndexByX = React.useCallback((x: number) => {
const xDate = scale.invert(x); const xDate = scale.invert(x);
const bisectDate = d3.bisector<TimeChartItem, unknown>((d) => d.date).left; const bisectDate = d3.bisector<TimeChartItem, unknown>((d) => d.date).left;
return bisectDate(data.items, xDate, 1); return bisectDate(data[0].items, xDate, 1);
}, [ data.items, scale ]); }, [ data, scale ]);
const drawSelection = React.useCallback((x0: number, x1: number) => { const drawSelection = React.useCallback((x0: number, x1: number) => {
const diffX = x1 - x0; const diffX = x1 - x0;
...@@ -99,6 +97,11 @@ const ChartSelectionX = ({ anchorEl, height, scale, data, onSelect }: Props) => ...@@ -99,6 +97,11 @@ const ChartSelectionX = ({ anchorEl, height, scale, data, onSelect }: Props) =>
handelMouseUp(); handelMouseUp();
} }
}); });
return () => {
anchorD3.on('mousedown.selectionX mouseup.selectionX mousemove.selectionX', null);
d3.select('body').on('mouseup.selectionX', null);
};
}, [ anchorEl, drawSelection, getIndexByX, handelMouseUp ]); }, [ anchorEl, drawSelection, getIndexByX, handelMouseUp ]);
return ( return (
......
...@@ -2,15 +2,13 @@ import { useToken, useColorModeValue } from '@chakra-ui/react'; ...@@ -2,15 +2,13 @@ import { useToken, useColorModeValue } from '@chakra-ui/react';
import * as d3 from 'd3'; import * as d3 from 'd3';
import React from 'react'; import React from 'react';
import type { TimeChartItem, ChartMargin } from 'ui/shared/chart/types'; import type { TimeChartItem, ChartMargin, TimeChartData } from 'ui/shared/chart/types';
interface Props { interface Props {
width?: number; width?: number;
height?: number; height?: number;
margin?: ChartMargin; margin?: ChartMargin;
data: { data: TimeChartData;
items: Array<TimeChartItem>;
};
xScale: d3.ScaleTime<number, number>; xScale: d3.ScaleTime<number, number>;
yScale: d3.ScaleLinear<number, number>; yScale: d3.ScaleLinear<number, number>;
anchorEl: SVGRectElement | null; anchorEl: SVGRectElement | null;
...@@ -60,24 +58,24 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an ...@@ -60,24 +58,24 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an
[ xScale, margin, width ], [ xScale, margin, width ],
); );
const onChangePosition = React.useCallback((d: TimeChartItem, isVisible: boolean) => { const updateDisplayedValue = React.useCallback((d: TimeChartItem, i: number) => {
d3.select('.ChartTooltip__value') d3.selectAll('.ChartTooltip__value')
.text(isVisible ? d.value.toLocaleString() : ''); .filter((td, tIndex) => tIndex === i)
.text(d.value.toLocaleString());
}, []); }, []);
const followPoints = React.useCallback((event: MouseEvent) => { const drawCircles = React.useCallback((event: MouseEvent) => {
const [ x ] = d3.pointer(event, anchorEl); const [ x ] = d3.pointer(event, anchorEl);
const xDate = xScale.invert(x); const xDate = xScale.invert(x);
const bisectDate = d3.bisector<TimeChartItem, unknown>((d) => d.date).left; const bisectDate = d3.bisector<TimeChartItem, unknown>((d) => d.date).left;
let baseXPos = 0; let baseXPos = 0;
// draw circles on line
d3.select(ref.current) d3.select(ref.current)
.select('.ChartTooltip__linePoint') .selectAll('.ChartTooltip__linePoint')
.attr('transform', (cur, i) => { .attr('transform', (cur, i) => {
const index = bisectDate(data.items, xDate, 1); const index = bisectDate(data[i].items, xDate, 1);
const d0 = data.items[index - 1]; const d0 = data[i].items[index - 1];
const d1 = data.items[index]; const d1 = data[i].items[index];
const d = xDate.getTime() - d0?.date.getTime() > d1?.date.getTime() - xDate.getTime() ? d1 : d0; const d = xDate.getTime() - d0?.date.getTime() > d1?.date.getTime() - xDate.getTime() ? d1 : d0;
if (d.date === undefined && d.value === undefined) { if (d.date === undefined && d.value === undefined) {
...@@ -90,30 +88,21 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an ...@@ -90,30 +88,21 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an
baseXPos = xPos; baseXPos = xPos;
} }
let isVisible = true;
if (xPos !== baseXPos) {
isVisible = false;
}
const yPos = yScale(d.value); const yPos = yScale(d.value);
onChangePosition(d, isVisible); updateDisplayedValue(d, i);
return isVisible ? return `translate(${ xPos }, ${ yPos })`;
`translate(${ xPos }, ${ yPos })` :
'translate(-100,-100)';
}); });
return baseXPos;
}, [ anchorEl, data, updateDisplayedValue, xScale, yScale ]);
const followPoints = React.useCallback((event: MouseEvent) => {
const baseXPos = drawCircles(event);
drawLine(baseXPos); drawLine(baseXPos);
drawContent(baseXPos); drawContent(baseXPos);
}, [ }, [ drawCircles, drawLine, drawContent ]);
anchorEl,
drawLine,
drawContent,
xScale,
yScale,
data,
onChangePosition,
]);
React.useEffect(() => { React.useEffect(() => {
const anchorD3 = d3.select(anchorEl); const anchorD3 = d3.select(anchorEl);
...@@ -136,7 +125,7 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an ...@@ -136,7 +125,7 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an
if (!isPressed.current) { if (!isPressed.current) {
d3.select(ref.current).attr('opacity', 1); d3.select(ref.current).attr('opacity', 1);
d3.select(ref.current) d3.select(ref.current)
.select('.ChartTooltip__linePoint') .selectAll('.ChartTooltip__linePoint')
.attr('opacity', 1); .attr('opacity', 1);
followPoints(event); followPoints(event);
} }
...@@ -148,13 +137,18 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an ...@@ -148,13 +137,18 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an
isPressed.current = false; isPressed.current = false;
} }
}); });
return () => {
anchorD3.on('mousedown.tooltip mouseup.tooltip mouseout.tooltip mouseover.tooltip mousemove.tooltip', null);
d3.select('body').on('mouseup.tooltip', null);
};
}, [ anchorEl, followPoints ]); }, [ anchorEl, followPoints ]);
return ( return (
<g ref={ ref } opacity={ 0 } { ...props }> <g ref={ ref } opacity={ 0 } { ...props }>
<line className="ChartTooltip__line" stroke={ lineColor }/> <line className="ChartTooltip__line" stroke={ lineColor }/>
<g className="ChartTooltip__content"> <g className="ChartTooltip__content">
<rect className="ChartTooltip__contentBg" rx={ 8 } ry={ 8 } fill={ bgColor } width={ 125 } height={ 52 }/> <rect className="ChartTooltip__contentBg" rx={ 8 } ry={ 8 } fill={ bgColor } width={ 125 } height={ data.length * 22 + 34 }/>
<text <text
className="ChartTooltip__contentTitle" className="ChartTooltip__contentTitle"
transform="translate(8,20)" transform="translate(8,20)"
...@@ -163,15 +157,24 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an ...@@ -163,15 +157,24 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, margin: _margin, an
fill={ textColor } fill={ textColor }
pointerEvents="none" pointerEvents="none"
/> />
<text <g>
transform="translate(8,40)" { data.map(({ name, color }, index) => (
className="ChartTooltip__value" <g key={ name } className="ChartTooltip__contentLine" transform={ `translate(12,${ 40 + index * 20 })` }>
fontSize="12px" <circle r={ 4 } fill={ color }/>
fill={ textColor } <text
pointerEvents="none" transform="translate(10,4)"
/> className="ChartTooltip__value"
fontSize="12px"
fill={ textColor }
pointerEvents="none"
/>
</g>
)) }
</g>
</g> </g>
<circle className="ChartTooltip__linePoint" r={ 3 } opacity={ 0 } fill={ lineColor }/> { data.map(({ name, color }) => (
<circle key={ name } className="ChartTooltip__linePoint" r={ 4 } opacity={ 0 } fill={ color } stroke="#FFF" strokeWidth={ 1 }/>
)) }
</g> </g>
); );
}; };
......
...@@ -9,3 +9,11 @@ export interface ChartMargin { ...@@ -9,3 +9,11 @@ export interface ChartMargin {
bottom?: number; bottom?: number;
left?: number; left?: number;
} }
export interface TimeChartDataItem {
items: Array<TimeChartItem>;
name: string;
color: string;
}
export type TimeChartData = Array<TimeChartDataItem>;
import * as d3 from 'd3'; import * as d3 from 'd3';
import { useMemo } from 'react'; import { useMemo } from 'react';
import type { TimeChartItem } from 'ui/shared/chart/types'; import type { TimeChartData } from 'ui/shared/chart/types';
interface Props { interface Props {
data: { data: TimeChartData;
items: Array<TimeChartItem>;
};
width: number; width: number;
height: number; height: number;
} }
...@@ -14,12 +12,12 @@ interface Props { ...@@ -14,12 +12,12 @@ interface Props {
export default function useTimeChartController({ data, width, height }: Props) { export default function useTimeChartController({ data, width, height }: Props) {
const xMin = useMemo( const xMin = useMemo(
() => d3.min(data.items, ({ date }) => date) || new Date(), () => d3.min(data, ({ items }) => d3.min(items, ({ date }) => date)) || new Date(),
[ data ], [ data ],
); );
const xMax = useMemo( const xMax = useMemo(
() => d3.max(data.items, ({ date }) => date) || new Date(), () => d3.max(data, ({ items }) => d3.max(items, ({ date }) => date)) || new Date(),
[ data ], [ data ],
); );
...@@ -29,17 +27,17 @@ export default function useTimeChartController({ data, width, height }: Props) { ...@@ -29,17 +27,17 @@ export default function useTimeChartController({ data, width, height }: Props) {
); );
const yMin = useMemo( const yMin = useMemo(
() => d3.min(data.items, ({ value }) => value) || 0, () => d3.min(data, ({ items }) => d3.min(items, ({ value }) => value)) || 0,
[ data ], [ data ],
); );
const yMax = useMemo( const yMax = useMemo(
() => d3.max(data.items, ({ value }) => value) || 0, () => d3.max(data, ({ items }) => d3.max(items, ({ value }) => value)) || 0,
[ data ], [ data ],
); );
const yScale = useMemo(() => { const yScale = useMemo(() => {
const indention = (yMax - yMin) * 0.5; const indention = (yMax - yMin) * 0.3;
return d3.scaleLinear() return d3.scaleLinear()
.domain([ yMin >= 0 && yMin - indention <= 0 ? 0 : yMin - indention, yMax + indention ]) .domain([ yMin >= 0 && yMin - indention <= 0 ? 0 : yMin - indention, yMax + indention ])
.range([ height, 0 ]); .range([ height, 0 ]);
......
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