Skip to content

Commit

Permalink
Stats: give emphasis to incomplete data days (#2085)
Browse files Browse the repository at this point in the history
* draw dotted line for incomplete data

* add title to tooltip

* refactor parts into separate components

* move utils

* refactor render logic of tooltip

* more refactoring

* fix issue in safari

* tweaks

* test

* make an approximation as a straight line.

* update screenshots

* fix the tooltip hiding behavior.

* add prop for disabling animation
  • Loading branch information
tom2drum authored Jul 22, 2024
1 parent 0ccd05a commit 7ecea8b
Show file tree
Hide file tree
Showing 34 changed files with 611 additions and 309 deletions.
1 change: 1 addition & 0 deletions ui/address/AddressCoinBalance.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ test('base view +@dark-mode +@mobile', async({ render, page, mockApiResponse })
await page.waitForFunction(() => {
return document.querySelector('path[data-name="chart-Balances-small"]')?.getAttribute('opacity') === '1';
});
await page.mouse.move(100, 100);
await page.mouse.move(240, 100);
await expect(component).toHaveScreenshot();
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion ui/home/indicators/ChainIndicators.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ test('partial data', async({ page, mockApiResponse, mockAssetResponse, render })
test('no data', async({ mockApiResponse, mockAssetResponse, render }) => {
await mockApiResponse('stats', statsMock.noChartData);
await mockApiResponse('stats_charts_txs', dailyTxsMock.noData);
await mockAssetResponse(statsMock.base.coin_image as string, './playwright/mocks/image_s.jpg');
await mockAssetResponse(statsMock.noChartData.coin_image as string, './playwright/mocks/image_s.jpg');

const component = await render(<ChainIndicators/>);
await expect(component).toHaveScreenshot();
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 5 additions & 4 deletions ui/shared/chart/ChartArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ interface Props extends React.SVGProps<SVGPathElement> {
yScale: d3.ScaleTime<number, number> | d3.ScaleLinear<number, number>;
color?: string;
data: Array<TimeChartItem>;
disableAnimation?: boolean;
noAnimation?: boolean;
}

const ChartArea = ({ id, xScale, yScale, color, data, disableAnimation, ...props }: Props) => {
const ChartArea = ({ id, xScale, yScale, color, data, noAnimation, ...props }: Props) => {
const ref = React.useRef(null);
const theme = useTheme();

Expand All @@ -26,18 +26,19 @@ const ChartArea = ({ id, xScale, yScale, color, data, disableAnimation, ...props
};

React.useEffect(() => {
if (disableAnimation) {
if (noAnimation) {
d3.select(ref.current).attr('opacity', 1);
return;
}
d3.select(ref.current).transition()
.duration(750)
.ease(d3.easeBackIn)
.attr('opacity', 1);
}, [ disableAnimation ]);
}, [ noAnimation ]);

const d = React.useMemo(() => {
const area = d3.area<TimeChartItem>()
.defined(({ isApproximate }) => !isApproximate)
.x(({ date }) => xScale(date))
.y1(({ value }) => yScale(value))
.y0(() => yScale(yScale.domain()[0]))
Expand Down
8 changes: 4 additions & 4 deletions ui/shared/chart/ChartAxis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import React from 'react';
interface Props extends Omit<React.SVGProps<SVGGElement>, 'scale'> {
type: 'left' | 'bottom';
scale: d3.ScaleTime<number, number> | d3.ScaleLinear<number, number>;
disableAnimation?: boolean;
noAnimation?: boolean;
ticks: number;
tickFormatGenerator?: (axis: d3.Axis<d3.NumberValue>) => (domainValue: d3.AxisDomain, index: number) => string;
anchorEl?: SVGRectElement | null;
}

const ChartAxis = ({ type, scale, ticks, tickFormatGenerator, disableAnimation, anchorEl, ...props }: Props) => {
const ChartAxis = ({ type, scale, ticks, tickFormatGenerator, noAnimation, anchorEl, ...props }: Props) => {
const ref = React.useRef<SVGGElement>(null);

const textColorToken = useColorModeValue('blackAlpha.600', 'whiteAlpha.500');
Expand All @@ -31,7 +31,7 @@ const ChartAxis = ({ type, scale, ticks, tickFormatGenerator, disableAnimation,

const axisGroup = d3.select(ref.current);

if (disableAnimation) {
if (noAnimation) {
axisGroup.call(axis);
} else {
axisGroup.transition().duration(750).ease(d3.easeLinear).call(axis);
Expand All @@ -42,7 +42,7 @@ const ChartAxis = ({ type, scale, ticks, tickFormatGenerator, disableAnimation,
.attr('opacity', 1)
.attr('color', textColor)
.attr('font-size', '0.75rem');
}, [ scale, ticks, tickFormatGenerator, disableAnimation, type, textColor ]);
}, [ scale, ticks, tickFormatGenerator, noAnimation, type, textColor ]);

React.useEffect(() => {
if (!anchorEl) {
Expand Down
8 changes: 4 additions & 4 deletions ui/shared/chart/ChartGridLine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import React from 'react';
interface Props extends Omit<React.SVGProps<SVGGElement>, 'scale'> {
type: 'vertical' | 'horizontal';
scale: d3.ScaleTime<number, number> | d3.ScaleLinear<number, number>;
disableAnimation?: boolean;
noAnimation?: boolean;
size: number;
ticks: number;
}

const ChartGridLine = ({ type, scale, ticks, size, disableAnimation, ...props }: Props) => {
const ChartGridLine = ({ type, scale, ticks, size, noAnimation, ...props }: Props) => {
const ref = React.useRef<SVGGElement>(null);

const strokeColor = useToken('colors', 'divider');
Expand All @@ -24,15 +24,15 @@ const ChartGridLine = ({ type, scale, ticks, size, disableAnimation, ...props }:
const axis = axisGenerator(scale).ticks(ticks).tickSize(-size);

const gridGroup = d3.select(ref.current);
if (disableAnimation) {
if (noAnimation) {
gridGroup.call(axis);
} else {
gridGroup.transition().duration(750).ease(d3.easeLinear).call(axis);
}
gridGroup.select('.domain').remove();
gridGroup.selectAll('text').remove();
gridGroup.selectAll('line').attr('stroke', strokeColor);
}, [ scale, ticks, size, disableAnimation, type, strokeColor ]);
}, [ scale, ticks, size, noAnimation, type, strokeColor ]);

return <g ref={ ref } { ...props }/>;
};
Expand Down
84 changes: 39 additions & 45 deletions ui/shared/chart/ChartLine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,38 @@ import React from 'react';

import type { TimeChartItem } from 'ui/shared/chart/types';

import type { AnimationType } from './utils/animations';
import { ANIMATIONS } from './utils/animations';
import { getIncompleteDataLineSource } from './utils/formatters';

interface Props extends React.SVGProps<SVGPathElement> {
xScale: d3.ScaleTime<number, number> | d3.ScaleLinear<number, number>;
yScale: d3.ScaleTime<number, number> | d3.ScaleLinear<number, number>;
data: Array<TimeChartItem>;
animation: 'left' | 'fadeIn' | 'none';
animation: AnimationType;
}

const ChartLine = ({ xScale, yScale, data, animation, ...props }: Props) => {
const ref = React.useRef<SVGPathElement>(null);

// Define different types of animation that we can use
const animateLeft = React.useCallback(() => {
const totalLength = ref.current?.getTotalLength() || 0;
d3.select(ref.current)
.attr('opacity', 1)
.attr('stroke-dasharray', `${ totalLength },${ totalLength }`)
.attr('stroke-dashoffset', totalLength)
.transition()
.duration(750)
.ease(d3.easeLinear)
.attr('stroke-dashoffset', 0);
}, []);

const animateFadeIn = React.useCallback(() => {
d3.select(ref.current)
.transition()
.duration(750)
.ease(d3.easeLinear)
.attr('opacity', 1);
}, []);

const noneAnimation = React.useCallback(() => {
d3.select(ref.current).attr('opacity', 1);
}, []);
const dataPathRef = React.useRef<SVGPathElement>(null);
const incompleteDataPathRef = React.useRef<SVGPathElement>(null);

React.useEffect(() => {
const ANIMATIONS = {
left: animateLeft,
fadeIn: animateFadeIn,
none: noneAnimation,
};
const animationFn = ANIMATIONS[animation];
window.setTimeout(animationFn, 100);
}, [ animateLeft, animateFadeIn, noneAnimation, animation ]);
const timeoutId = window.setTimeout(() => {
dataPathRef.current && animationFn(dataPathRef.current);
incompleteDataPathRef.current && animationFn(incompleteDataPathRef.current);
}, 100);

return () => {
window.clearTimeout(timeoutId);
};
}, [ animation ]);

// Recalculate line length if scale has changed
React.useEffect(() => {
if (animation === 'left') {
const totalLength = ref.current?.getTotalLength();
d3.select(ref.current).attr(
const totalLength = dataPathRef.current?.getTotalLength();
d3.select(dataPathRef.current).attr(
'stroke-dasharray',
`${ totalLength },${ totalLength }`,
);
Expand All @@ -65,15 +47,27 @@ const ChartLine = ({ xScale, yScale, data, animation, ...props }: Props) => {
.curve(d3.curveMonotoneX);

return (
<path
ref={ ref }
d={ line(data) || undefined }
strokeWidth={ 1 }
strokeLinecap="round"
fill="none"
opacity={ 0 }
{ ...props }
/>
<>
<path
ref={ incompleteDataPathRef }
d={ line(getIncompleteDataLineSource(data)) || undefined }
strokeWidth={ 1 }
strokeLinecap="round"
fill="none"
strokeDasharray="6 6"
opacity={ 0 }
{ ...props }
/>
<path
ref={ dataPathRef }
d={ line(data.filter(({ isApproximate }) => !isApproximate)) || undefined }
strokeWidth={ 1 }
strokeLinecap="round"
fill="none"
opacity={ 0 }
{ ...props }
/>
</>
);
};

Expand Down
Loading

0 comments on commit 7ecea8b

Please sign in to comment.