Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

graph performance #2336

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12,963 changes: 6,927 additions & 6,036 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"@mui/material": "5.14.5",
"@mui/styled-engine-sc": "5.12.0",
"@mui/styles": "5.14.5",
"@mui/x-charts": "7.3.2",
seeden marked this conversation as resolved.
Show resolved Hide resolved
"@rollup/plugin-alias": "5.0.0",
"@rollup/plugin-babel": "6.0.3",
"@rollup/plugin-commonjs": "25.0.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default function FormatLargeNumber(props: FormatLargeNumberProps) {
const [locale] = useLocale();

const numberFormat = useMemo(() => new Intl.NumberFormat(locale), [locale]);
const formatedValue = useMemo(() => {
const formattedValue = useMemo(() => {
if (typeof value === 'undefined' || value === null) {
return value;
}
Expand All @@ -42,5 +42,5 @@ export default function FormatLargeNumber(props: FormatLargeNumberProps) {
return numberFormat.format(value);
}, [value, numberFormat, locale]);

return <span>{formatedValue}</span>;
return <span>{formattedValue}</span>;
}
104 changes: 104 additions & 0 deletions packages/core/src/components/LineChart/LineChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { alpha } from '@mui/material';
import { SparkLineChart, type SparkLineChartProps } from '@mui/x-charts';

Check failure on line 2 in packages/core/src/components/LineChart/LineChart.tsx

View workflow job for this annotation

GitHub Actions / build

'@mui/x-charts' should be listed in the project's dependencies, not devDependencies
import { areaElementClasses } from '@mui/x-charts/LineChart';

Check failure on line 3 in packages/core/src/components/LineChart/LineChart.tsx

View workflow job for this annotation

GitHub Actions / build

'@mui/x-charts' should be listed in the project's dependencies, not devDependencies
import BigNumber from 'bignumber.js';
import JSONbig from 'json-bigint';
import React, { useMemo, memo } from 'react';
import styled from 'styled-components';

import Color from '../../constants/Color';

const StyledGraphContainer = styled.div<{ height: number }>`
position: relative;
min-height: 80px;
height: ${({ height }) => `${height}px`};
`;

function LinearGradient() {
return (
<defs>
<linearGradient id="graph-gradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor={alpha(Color.Green[500], 0.4)} />
<stop offset="100%" stopColor={alpha(Color.Green[500], 0)} />
</linearGradient>
</defs>
);
}

const sx = {
[`& .${areaElementClasses.root}`]: {
fill: 'url(#graph-gradient)',
},
};

const chartColors = [Color.Green[500]];
const chartMargin = { top: 0, bottom: 0, left: 0, right: 0 };
const defaultXValueFormatter = (value: number) => value.toString();
const defaultYValueFormatter = (value: number | BigNumber | null) => (value !== null ? value.toString() : '');

type Point = {
x: number;
y: number | BigNumber;
};

const MemoLineChart = memo((props: SparkLineChartProps) => (
<SparkLineChart {...props}>
<LinearGradient />
<rect x={0} y={0} width={0} height="100%" fill="url(#graph-gradient)" />
</SparkLineChart>
));

export type LineChartProps = {
data: Point[];
// min?: number;
height?: number;
xValueFormatter?: (value: number) => string;
yValueFormatter?: (value: number | BigNumber | null) => string;
};

export default function LineChart(props: LineChartProps) {
const {
data,
// min: defaultMin = 0,
xValueFormatter = defaultXValueFormatter,
yValueFormatter = defaultYValueFormatter,
height = 150,
} = props;

const stringifiedData = useMemo(() => JSONbig.stringify(data), [data]);
const freezedData = useMemo<Point[]>(() => JSONbig.parse(stringifiedData), [stringifiedData]);
seeden marked this conversation as resolved.
Show resolved Hide resolved

const yData = useMemo(() => freezedData.map((item) => item.y), [freezedData]);
const xData = useMemo(() => freezedData.map((item) => item.x), [freezedData]);

const yDataNumber = useMemo(
() => yData.map((value) => (value instanceof BigNumber ? value.toNumber() : value)),
[yData],
);
seeden marked this conversation as resolved.
Show resolved Hide resolved

const xAxis = useMemo(
() => ({
data: xData,
valueFormatter: xValueFormatter,
}),
[xData, xValueFormatter],
);
seeden marked this conversation as resolved.
Show resolved Hide resolved

return (
<StyledGraphContainer height={height}>
<MemoLineChart
xAxis={xAxis}
data={yDataNumber}
height={height || 0}
valueFormatter={yValueFormatter}
curve="monotoneX"
margin={chartMargin}
colors={chartColors}
area
showHighlight
showTooltip
sx={sx}
/>
</StyledGraphContainer>
);
}
2 changes: 2 additions & 0 deletions packages/core/src/components/LineChart/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from './LineChart';
export * from './LineChart';
1 change: 1 addition & 0 deletions packages/core/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export { default as LayoutDashboard, LayoutDashboardSub } from './LayoutDashboar
export { default as LayoutHero } from './LayoutHero';
export { default as LayoutLoading } from './LayoutLoading';
export { default as LayoutMain } from './LayoutMain';
export { default as LineChart } from './LineChart';
export { default as Link } from './Link';
export { default as Loading } from './Loading';
export { default as LoadingOverlay } from './LoadingOverlay';
Expand Down
1 change: 0 additions & 1 deletion packages/gui/src/components/plotNFT/PlotNFTGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ export default function PlotNFTGraph(props: PlotNFTGraphProps) {
)}
<Box height={100} position="relative" ref={ref}>
<VictoryChart
animate={{ duration: 300, onLoad: { duration: 0 } }}
width={containerSize.width || 1}
height={containerSize.height || 1}
domain={{ x: [maxX, minX], y: [0, maxY] }}
Expand Down
3 changes: 1 addition & 2 deletions packages/wallets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@
"react-use": "17.4.0",
"react-use-timeout": "1.0.0",
"use-dark-mode": "2.3.1",
"validator": "13.11.0",
"victory": "36.6.11"
"validator": "13.11.0"
},
"devDependencies": {
"@babel/core": "7.22.10",
Expand Down
157 changes: 45 additions & 112 deletions packages/wallets/src/components/WalletGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,21 @@
import { TransactionType, WalletType } from '@chia-network/api';
import type { Transaction } from '@chia-network/api';
import { useGetWalletBalanceQuery } from '@chia-network/api-react';
import { Color, mojoToChia, mojoToCAT, blockHeightToTimestamp } from '@chia-network/core';
import { alpha } from '@mui/material';
import {
useLocale,
mojoToChia,
mojoToCAT,
blockHeightToTimestamp,
bigNumberToLocaleString,
LineChart,
} from '@chia-network/core';
import BigNumber from 'bignumber.js';
import { orderBy, groupBy, map } from 'lodash';
import React, { ReactNode } from 'react';
import { useMeasure } from 'react-use';
import styled from 'styled-components';
import { VictoryChart, VictoryAxis, VictoryArea, VictoryTooltip, VictoryVoronoiContainer } from 'victory';
import { orderBy, groupBy, map, sortBy } from 'lodash';
import moment from 'moment';
import React, { useCallback } from 'react';

import useWalletTransactions from '../hooks/useWalletTransactions';

import WalletGraphTooltip from './WalletGraphTooltip';

const StyledGraphContainer = styled.div`
position: relative;
min-height: 80px;
height: ${({ height }) => (typeof height === 'string' ? height : `${height}px`)};
`;

type Aggregate = {
interval: number; // interval second
count: number; // number of intervals
offset?: number;
};

function generateTransactionGraphData(transactions: Transaction[]): {
value: BigNumber;
timestamp: number;
Expand Down Expand Up @@ -79,47 +69,30 @@ function generateTransactionGraphData(transactions: Transaction[]): {
function prepareGraphPoints(
balance: number,
transactions: Transaction[],
walletType: WalletType,
_aggregate?: Aggregate,
): {
x: number;
y: number;
tooltip?: ReactNode;
y: BigNumber;
}[] {
if (!transactions || !transactions.length) {
return [];
}

let start = balance;
const data = generateTransactionGraphData(transactions);
let start = new BigNumber(balance);

const data = generateTransactionGraphData(transactions);
const [peakTransaction] = transactions;

/*
if (aggregate) {
const { interval, count, offset } = aggregate;
data = aggregatePoints(data, interval, count, offset);
}
*/

const points = [
{
x: blockHeightToTimestamp(peakTransaction.confirmedAtHeight, peakTransaction),
y: BigNumber.max(
0,
([WalletType.CAT, WalletType.CRCAT].includes(walletType) ? mojoToCAT(start) : mojoToChia(start)).toNumber(),
), // max 21,000,000 safe to number
tooltip: ([WalletType.CAT, WalletType.CRCAT].includes(walletType)
? mojoToCAT(balance)
: mojoToChia(balance)
).toString(), // bignumber is not supported by react
y: BigNumber.max(0, start),
},
];

data.forEach((item) => {
const { timestamp, value } = item;

start -= value.toNumber();
start = start.minus(value);

const isAlreadyUsed = points.some((point) => point.x === timestamp);
if (isAlreadyUsed) {
Expand All @@ -128,37 +101,23 @@ function prepareGraphPoints(

points.push({
x: timestamp,
y: BigNumber.max(
0,
([WalletType.CAT, WalletType.CRCAT].includes(walletType) ? mojoToCAT(start) : mojoToChia(start)).toNumber(),
), // max 21,000,000 safe to number
tooltip: [WalletType.CAT, WalletType.CRCAT].includes(walletType)
? mojoToCAT(start)
: mojoToChia(start).toString(), // bignumber is not supported by react
y: BigNumber.max(0, start),
});
});

return points.reverse();
}

function LinearGradient() {
return (
<linearGradient id="graph-gradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor={alpha(Color.Green[500], 0.4)} />
<stop offset="100%" stopColor={alpha(Color.Green[500], 0)} />
</linearGradient>
);
return sortBy(points, (point) => point.x);
}

export type WalletGraphProps = {
walletId: number;
walletType: WalletType;
unit?: string;
height?: number | string;
height?: number;
};

export default function WalletGraph(props: WalletGraphProps) {
const { walletId, walletType, unit = '', height = 150 } = props;
const [locale] = useLocale();
const { transactions, isLoading: isWalletTransactionsLoading } = useWalletTransactions({
walletId,
defaultRowsPerPage: 50,
Expand All @@ -173,63 +132,37 @@ export default function WalletGraph(props: WalletGraphProps) {
walletId,
});

const [ref, containerSize] = useMeasure();

const isCAT = [WalletType.CAT, WalletType.CRCAT].includes(walletType);
const isLoading = isWalletTransactionsLoading || isWalletBalanceLoading || !transactions;
if (isLoading || !walletBalance) {
return null;
}

const confirmedTransactions = transactions.filter((transaction) => transaction.confirmed);
if (!confirmedTransactions.length) {
return null;
}
const confirmedTransactions = transactions ? transactions.filter((transaction) => transaction.confirmed) : [];

const balance = walletBalance.confirmedWalletBalance;
const balance = walletBalance?.confirmedWalletBalance || 0;

const data = prepareGraphPoints(balance, confirmedTransactions, walletType, {
interval: 60 * 60,
count: 24,
offset: 0,
});
const data = prepareGraphPoints(balance, confirmedTransactions);

const xValueFormatter = useCallback((value: number) => moment(value * 1000).format('LLL'), []);

const min = data.length ? Math.min(...data.map((item) => item.y)) : 0;
const max = Math.max(min, ...data.map((item) => item.y));
const yValueFormatter = useCallback(
(value: number) => {
const formattedValue = isCAT ? mojoToCAT(value) : mojoToChia(value);

return `${bigNumberToLocaleString(formattedValue, locale)} ${unit}`;
},
[isCAT, unit, locale],
);

if (isLoading || !walletBalance || !confirmedTransactions.length) {
return null;
}

return (
<StyledGraphContainer height={height} ref={ref}>
<VictoryChart
animate={{ duration: 300, onLoad: { duration: 0 } }}
width={containerSize.width || 1}
height={containerSize.height || 1}
domain={{ y: [0, max] }}
padding={0}
domainPadding={{ x: 0, y: 1 }}
containerComponent={<VictoryVoronoiContainer />}
>
<VictoryArea
data={data}
interpolation="monotoneX"
style={{
data: {
stroke: Color.Green[500],
strokeWidth: 2,
strokeLinecap: 'round',
fill: 'url(#graph-gradient)',
},
}}
labels={() => ''}
labelComponent={<VictoryTooltip flyoutComponent={<WalletGraphTooltip suffix={unit} />} />}
/>
<VictoryAxis
style={{
axis: { stroke: 'transparent' },
ticks: { stroke: 'transparent' },
tickLabels: { fill: 'transparent' },
}}
/>
<LinearGradient />
</VictoryChart>
</StyledGraphContainer>
<LineChart
data={data}
height={height}
// min={0}
xValueFormatter={xValueFormatter}
yValueFormatter={yValueFormatter}
/>
);
}
Loading