diff --git a/package.json b/package.json index 8915347..24e3f26 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "react-dom": "^18.2.0", "react-jss": "^10.10.0", "react-scripts": "5.0.1", + "react-virtualized-auto-sizer": "^1.0.20", "recharts": "^2.6.2", "typescript": "^4.9.5", "web-vitals": "^2.1.4" diff --git a/src/App.tsx b/src/App.tsx index 864c0ef..9ad53cc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,98 +1,155 @@ import Counter from 'components/Counter'; -import { CHARTS, COUNTERS } from 'data'; +import { CHARTS, COUNTERS, SECTIONS } from 'data'; import { useEffect, useState } from 'react'; import { createUseStyles } from 'react-jss'; import Flex from 'components/Flex'; import ChartWrapper from 'components/Charts/ChartWrapper'; import Typography from 'components/Typography'; +import logo from 'assets/images/waxGreenLogo.webp'; const { REACT_APP_API_URL } = process.env; const useStyles = createUseStyles({ card: { backgroundColor: '#34383D', + border: '1px solid #74FFCE', borderRadius: '4px', + color: '#74FFCE', padding: '20px', }, container: { - padding: '16px', + padding: '16px 16px 120px 16px', }, }); type Layout = { charts: any[]; counters: any[]; + description?: string; + title: string; }; function App() { const styles = useStyles(); - const [layout, setLayout] = useState({ - charts: [], - counters: [], - }); + const [layout, setLayout] = useState([]); useEffect(() => { (async () => { const res = await fetch(`${REACT_APP_API_URL}/queries`); const data = await res.json(); - const charts = CHARTS.map((chart) => ({ - ...chart, - data: data[chart.queryId], - })); - const counters = COUNTERS.map((counter) => ({ - ...counter, - data: data[counter.queryId], - })); - setLayout({ counters, charts }); + + setLayout( + SECTIONS.map((section) => { + let charts: any[] = []; + let counters: any[] = []; + if (section.charts[0] !== undefined) { + const range = section.charts; + charts = range[1] + ? CHARTS.slice(range[0], range[1]) + : CHARTS.slice(range[0]); + } + if (section.counters[0] !== undefined) { + const range = section.counters; + counters = range[1] + ? COUNTERS.slice(range[0], range[1]) + : COUNTERS.slice(range[0]); + } + return { + ...section, + charts: charts.map((chart) => ({ + ...chart, + data: data[chart.queryId], + })), + counters: counters.map((counter) => ({ + ...counter, + data: data[counter.queryId], + })), + }; + }) + ); })(); }, []); return (
+ + + Logo + + - BLS Wallet Analytics + WAX V1 Analytics - - {layout.counters.map((counter) => ( -
- - {counter.title} - - + {layout.map((section) => ( +
+
+ {section.title} + {section.description && ( + + {section.description} + + )}
- ))} - - - {layout.charts.map((chart) => ( -
- - {chart.title} - - -
- ))} -
+ + {section.counters.map((counter) => ( +
+ + {''} + + +
+ ))} +
+ + {section.charts.map((chart) => ( +
+ + {chart.title} + + +
+ ))} +
+
+ ))}
); } diff --git a/src/assets/images/waxGreenLogo.webp b/src/assets/images/waxGreenLogo.webp new file mode 100644 index 0000000..c9d1e08 Binary files /dev/null and b/src/assets/images/waxGreenLogo.webp differ diff --git a/src/components/Charts/AreaChart/index.tsx b/src/components/Charts/AreaChart/index.tsx index 84cea1f..1047ee7 100644 --- a/src/components/Charts/AreaChart/index.tsx +++ b/src/components/Charts/AreaChart/index.tsx @@ -4,7 +4,6 @@ import { Area, AreaChart as RechartsAreaChart, CartesianGrid, - ResponsiveContainer, Tooltip as RechartsTooltip, TooltipProps, XAxis, @@ -27,8 +26,11 @@ export default function AreaChart({ color, data, grid, + height, scale, + showDecimals, stackBy, + width, xAxisTitle, xKey, yAxisTitle, @@ -39,60 +41,60 @@ export default function AreaChart({ }, [data, stackBy, xKey]); return ( - - - {grid && } - ) => ( - - )} - wrapperStyle={{ outline: 'none', zIndex: 100 }} - /> - {stackBy ? ( - data.keys?.map((key: string, index: number) => ( - - )) - ) : ( - + + {grid && } + ) => ( + )} - handleXKey(x, xKey)} - label={X_AXIS_LABEL_INSTRUCTIONS(xAxisTitle ?? '')} - fontSize={TICKMARK_FONT_SIZE} - tickFormatter={(tick) => - timeframe ? moment(tick).format(timeframe) : tick - } + wrapperStyle={{ outline: 'none', zIndex: 100 }} + /> + {stackBy ? ( + data.keys?.map((key: string, index: number) => ( + + )) + ) : ( + - formatLargeNumber(tick)} - scale={scale} - /> - - + )} + handleXKey(x, xKey)} + label={X_AXIS_LABEL_INSTRUCTIONS(xAxisTitle ?? '')} + fontSize={TICKMARK_FONT_SIZE} + tickFormatter={(tick) => + timeframe ? moment(tick).format(timeframe) : tick + } + /> + formatLargeNumber(tick)} + scale={scale} + /> + ); } diff --git a/src/components/Charts/BarChart/index.tsx b/src/components/Charts/BarChart/index.tsx index f4a6b09..a9bac49 100644 --- a/src/components/Charts/BarChart/index.tsx +++ b/src/components/Charts/BarChart/index.tsx @@ -4,7 +4,6 @@ import { Bar, BarChart as RechartsBarChart, CartesianGrid, - ResponsiveContainer, Tooltip as RechartsTooltip, TooltipProps, XAxis, @@ -27,8 +26,11 @@ export default function BarChart({ color, data, grid, + height, scale, + showDecimals, stackBy, + width, xAxisTitle, xKey, yAxisTitle, @@ -39,52 +41,52 @@ export default function BarChart({ }, [data, stackBy, xKey]); return ( - - - {grid && } - ) => ( - - )} - wrapperStyle={{ outline: 'none', zIndex: 100 }} - /> - {stackBy ? ( - data.keys?.map((key: string, index: number) => ( - - )) - ) : ( - + + {grid && } + ) => ( + )} - handleXKey(x, xKey)} - fontSize={TICKMARK_FONT_SIZE} - label={X_AXIS_LABEL_INSTRUCTIONS(xAxisTitle)} - name={xAxisTitle} - tickFormatter={(tick) => - timeframe ? moment(tick).format(timeframe) : tick - } - /> - formatLargeNumber(tick)} - /> - - + wrapperStyle={{ outline: 'none', zIndex: 100 }} + /> + {stackBy ? ( + data.keys?.map((key: string, index: number) => ( + + )) + ) : ( + + )} + handleXKey(x, xKey)} + fontSize={TICKMARK_FONT_SIZE} + label={X_AXIS_LABEL_INSTRUCTIONS(xAxisTitle)} + name={xAxisTitle} + tickFormatter={(tick) => + timeframe ? moment(tick).format(timeframe) : tick + } + /> + formatLargeNumber(tick)} + /> + ); } diff --git a/src/components/Charts/ChartWrapper/index.tsx b/src/components/Charts/ChartWrapper/index.tsx index 8a539b1..63dd13c 100644 --- a/src/components/Charts/ChartWrapper/index.tsx +++ b/src/components/Charts/ChartWrapper/index.tsx @@ -6,6 +6,7 @@ import { useMemo } from 'react'; import { ChartScale, ChartType, StackBy } from 'utils/constants'; import PieChart from '../PieChart'; import { generateStackData } from 'utils'; +import AutoSizer from 'react-virtualized-auto-sizer'; type ChartWrapperProps = { chartType: string; @@ -13,8 +14,9 @@ type ChartWrapperProps = { // eslint-disable-next-line @typescript-eslint/no-explicit-any data: any[]; grid?: boolean; - stackBy?: StackBy; scale?: ChartScale; + showDecimals?: boolean; + stackBy?: StackBy; xAxisTitle: string; xKey: string; yAxisTitle: string; @@ -27,6 +29,7 @@ export default function ChartWrapper({ data, grid, scale = ChartScale.Linear, + showDecimals, stackBy, xAxisTitle, xKey, @@ -67,48 +70,82 @@ export default function ChartWrapper({ if (chartType === ChartType.Area) { return ( - + + {({ height, width }: { height: number; width: number }) => ( + + )} + ); } else if (chartType === ChartType.Bar) { return ( - + + {({ height, width }: { height: number; width: number }) => ( + + )} + ); } else if (chartType === ChartType.Line) { return ( - + + {({ height, width }: { height: number; width: number }) => ( + + )} + ); } else { - return ; + return ( + + {({ height, width }: { height: number; width: number }) => ( + + )} + + ); } } diff --git a/src/components/Charts/LineChart/index.tsx b/src/components/Charts/LineChart/index.tsx index 0e27806..adea175 100644 --- a/src/components/Charts/LineChart/index.tsx +++ b/src/components/Charts/LineChart/index.tsx @@ -4,7 +4,6 @@ import { CartesianGrid, Line, LineChart as RechartsLineChart, - ResponsiveContainer, Tooltip as RechartsTooltip, TooltipProps, XAxis, @@ -25,11 +24,13 @@ type LineChartProps = AxisChartProps; export default function LineChart({ color, - curveType, data, grid, + height, scale, + showDecimals, stackBy, + width, xAxisTitle, xKey, yAxisTitle, @@ -39,59 +40,59 @@ export default function LineChart({ return determineTimeframe(stackBy ? data.formattedData : data, xKey); }, [data, stackBy, xKey]); return ( - - - {grid && } - ) => ( - - )} - wrapperStyle={{ outline: 'none', zIndex: 100 }} - /> - {stackBy ? ( - data.keys?.map((key: string, index: number) => ( - - )) - ) : ( - + + {grid && } + ) => ( + )} - handleXKey(x, xKey)} - fontSize={TICKMARK_FONT_SIZE} - label={X_AXIS_LABEL_INSTRUCTIONS(xAxisTitle)} - name={xAxisTitle} - tickFormatter={(tick) => - timeframe ? moment(tick).format(timeframe) : tick - } + wrapperStyle={{ outline: 'none', zIndex: 100 }} + /> + {stackBy ? ( + data.keys?.map((key: string, index: number) => ( + + )) + ) : ( + - formatLargeNumber(tick)} - /> - - + )} + handleXKey(x, xKey)} + fontSize={TICKMARK_FONT_SIZE} + label={X_AXIS_LABEL_INSTRUCTIONS(xAxisTitle)} + name={xAxisTitle} + tickFormatter={(tick) => + timeframe ? moment(tick).format(timeframe) : tick + } + /> + formatLargeNumber(tick)} + /> + ); } diff --git a/src/components/Charts/PieChart/index.tsx b/src/components/Charts/PieChart/index.tsx index 40116c5..febd82a 100644 --- a/src/components/Charts/PieChart/index.tsx +++ b/src/components/Charts/PieChart/index.tsx @@ -3,7 +3,6 @@ import { Cell, Pie, PieChart as RechartsPieChart, - ResponsiveContainer, Sector, Tooltip as RechartsTooltip, TooltipProps, @@ -16,13 +15,19 @@ type PieChartProps = { // eslint-disable-next-line @typescript-eslint/no-explicit-any data: any[]; dataKey: string; + height: number; nameKey: string; + showDecimals?: boolean; + width: number; }; export default function PieChart({ data, dataKey, + height, nameKey, + showDecimals, + width, }: PieChartProps): JSX.Element { const [activeIndex, setActiveIndex] = useState(-1); @@ -49,34 +54,34 @@ export default function PieChart({ }; return ( - - - ) => ( - - )} - wrapperStyle={{ outline: 'none', zIndex: 100 }} - /> - setActiveIndex(-1)} - > - {data.map((_, index) => ( - - ))} - - - + + ) => ( + + )} + wrapperStyle={{ outline: 'none', zIndex: 100 }} + /> + setActiveIndex(-1)} + > + {data.map((_, index) => ( + + ))} + + ); } diff --git a/src/components/Charts/components/Tooltip.tsx b/src/components/Charts/components/Tooltip.tsx index 104a687..4a1a1d7 100644 --- a/src/components/Charts/components/Tooltip.tsx +++ b/src/components/Charts/components/Tooltip.tsx @@ -14,28 +14,53 @@ const useStyles = createUseStyles({ }, }); -type TooltipProps = { xKey: string } & RechartsTooltipProps; +type TooltipProps = { + isPie?: boolean; + showDecimals?: boolean; + xKey: string; +} & RechartsTooltipProps; export default function Tooltip(props: TooltipProps): JSX.Element { - const { label, payload, xKey } = props; + const { isPie, label, payload, showDecimals, xKey } = props; const styles = useStyles(); - if (!payload) return <>; + if (!payload || payload[0] === undefined) return <>; return (
- -
{xKey}:
-
- {isDateOrTimestamp(label) - ? moment(label).format('MMM Do, YYYY') - : label} -
-
- {payload.map(({ color, name, payload: { fill }, value }) => ( - -
{name}:
-
{formatNumber(value ?? 0, 0)}
-
- ))} + {isPie ? ( + <> + +
{xKey}:
+
+ {isDateOrTimestamp(payload[0].name) + ? moment(payload[0].name).format('MMM Do, YYYY') + : payload[0].name} +
+
+ +
{payload[0].dataKey}:
+
+ {formatNumber(payload[0].value ?? 0, showDecimals ? 2 : 0)} +
+
+ + ) : ( + <> + +
{xKey}:
+
+ {isDateOrTimestamp(label) + ? moment(label).format('MMM Do, YYYY') + : label} +
+
+ {payload.map(({ color, name, payload: { fill }, value }) => ( + +
{name}:
+
{formatNumber(value ?? 0, showDecimals ? 2 : 0)}
+
+ ))} + + )}
); } diff --git a/src/components/Counter/index.tsx b/src/components/Counter/index.tsx index 46b2b3d..ee867ba 100644 --- a/src/components/Counter/index.tsx +++ b/src/components/Counter/index.tsx @@ -7,12 +7,14 @@ type CounterProps = { // eslint-disable-next-line @typescript-eslint/no-explicit-any data: any; label: string; + showDecimal?: boolean; }; export default function Counter({ counterKey, data, label, + showDecimal, }: CounterProps): JSX.Element { return ( <> @@ -23,9 +25,9 @@ export default function Counter({ style={{ color: '#FCFCFC' }} > - {label} + {label} - {formatNumber(data[0][counterKey], 0)} + {formatNumber(data[0][counterKey], showDecimal ? 2 : 0)}
diff --git a/src/data.ts b/src/data.ts index 33ca0d9..d8422e8 100644 --- a/src/data.ts +++ b/src/data.ts @@ -3,13 +3,13 @@ import { ChartType } from "utils/constants"; export const COUNTERS = [ { counterKey: 'Num_Wallets_Created', - label: 'Wallets Created', + label: 'BLS Wallets Created', queryId: 'aa55c0623a28438eac9aecfb7b767726', title: 'Wallets' }, { counterKey: 'Num_Wallets_Recovered', - label: 'Wallets Recovered', + label: 'BLS Wallets Recovered', queryId: 'aa55c0623a28438eac9aecfb7b767726', title: 'Wallets' }, @@ -19,6 +19,12 @@ export const COUNTERS = [ queryId: 'aa55c0623a28438eac9aecfb7b767726', title: 'Tx Groups' }, + { + counterKey: 'Num_Operations_Submitted', + label: 'Operations Submitted', + queryId: 'aa55c0623a28438eac9aecfb7b767726', + title: 'Tx Groups' + }, { counterKey: 'Num_Operations_Failed', label: 'Operations Failed', @@ -31,28 +37,25 @@ export const COUNTERS = [ queryId: 'aa55c0623a28438eac9aecfb7b767726', title: 'Tx Groups' }, - { - counterKey: 'Num_Operations_Submitted', - label: 'Operations Submitted', - queryId: 'aa55c0623a28438eac9aecfb7b767726', - title: 'Tx Groups' - }, { counterKey: 'Avg_Operations_Per_Bundle', label: 'Operations/Bundle', queryId: 'aa55c0623a28438eac9aecfb7b767726', + showDecimal: true, title: 'Averages' }, { counterKey: 'Avg_Actions_Per_Bundle', label: 'Actions/Bundle', queryId: 'aa55c0623a28438eac9aecfb7b767726', + showDecimal: true, title: 'Averages' }, { counterKey: 'Avg_Actions_Per_Operation', label: 'Actions/Operations', queryId: 'aa55c0623a28438eac9aecfb7b767726', + showDecimal: true, title: 'Averages' }, { @@ -76,20 +79,6 @@ export const COUNTERS = [ ]; export const CHARTS = [ - { - chartType: ChartType.Pie, - queryId: '7ad7fb1828094272880202e903af4239', - title: 'Action Method Ids Called', - xKey: 'actionMethodId', - yKey: 'action_count' - }, - { - chartType: ChartType.Pie, - queryId: '2578dd88f123458183e0fe077d285243', - title: 'Action Recipients', - xKey: 'actionsRecipient', - yKey: 'action_count' - }, { chartType: ChartType.Bar, color: '#B8B7D0', @@ -113,6 +102,7 @@ export const CHARTS = [ { chartType: ChartType.Bar, color: '#CE83D9', + description: 'Number of bundles (a group of operations containing actions) submitted on-chain', queryId: 'ab1a30af5991425b8b4abd7c37f859fa', title: 'Number of Bundles Submitted per Day', xAxisTitle: 'Day', @@ -123,6 +113,7 @@ export const CHARTS = [ { chartType: ChartType.Bar, color: '#8784D8', + description: 'Number of Actions (function calls coming from a signed operation) submitted on-chain', queryId: 'ab1a30af5991425b8b4abd7c37f859fa', title: 'Number of Actions Submitted per Day', xAxisTitle: 'Day', @@ -133,6 +124,7 @@ export const CHARTS = [ { chartType: ChartType.Bar, color: '#C6C5CF', + description: 'Number of Operations (a group of actions) submitted on-chain', queryId: 'ab1a30af5991425b8b4abd7c37f859fa', title: 'Number of Operations Submitted per Day', xAxisTitle: 'Day', @@ -154,6 +146,7 @@ export const CHARTS = [ chartType: ChartType.Bar, color: '#84D9CC', queryId: 'ab1a30af5991425b8b4abd7c37f859fa', + showDecimals: true, title: 'Average Operations per Bundle per day', xAxisTitle: 'Day', xKey: 'day', @@ -164,6 +157,7 @@ export const CHARTS = [ chartType: ChartType.Bar, color: '#8784D8', queryId: 'ab1a30af5991425b8b4abd7c37f859fa', + showDecimals: true, title: 'Average Actions per Bundle Per Dayy', xAxisTitle: 'Day', xKey: 'day', @@ -174,6 +168,7 @@ export const CHARTS = [ chartType: ChartType.Bar, color: '#B6B6BF', queryId: 'ab1a30af5991425b8b4abd7c37f859fa', + showDecimals: true, title: 'Average Actions per Operation per Day', xAxisTitle: 'Day', xKey: 'day', @@ -210,6 +205,20 @@ export const CHARTS = [ yAxisTitle: 'Average Gas per Transaction', yKey: 'avgGas' }, + { + chartType: ChartType.Pie, + queryId: '7ad7fb1828094272880202e903af4239', + title: 'Action Method Ids Called', + xKey: 'actionMethodId', + yKey: 'action_count' + }, + { + chartType: ChartType.Pie, + queryId: '2578dd88f123458183e0fe077d285243', + title: 'Action Recipients', + xKey: 'actionsRecipient', + yKey: 'action_count' + }, { chartType: ChartType.Bar, queryId: '74a8783fd397441ca0dd9cbc96423e81', @@ -236,16 +245,35 @@ export const CHARTS = [ }, ]; -// type ChartWrapperProps = { -// chartType: string; -// color: string; -// // eslint-disable-next-line @typescript-eslint/no-explicit-any -// data: any[]; -// grid?: boolean; -// stackBy?: StackBy; -// scale?: ChartScale; -// xAxisTitle: string; -// xKey: string; -// yAxisTitle: string; -// yKey: string; -// }; \ No newline at end of file +export const SECTIONS = [ + { + charts: [0, 2], + counters: [0, 2], + description: 'BLS smart-contract wallets for use with WAX', + title: 'Wallets', + }, + { + charts: [2, 6], + counters: [2, 6], + description: 'Bundles, submitted on-chain, encompass Operations that carry Actions - the actual function calls.', + title: 'Bundle Composition' + }, + { + charts: [6, 9], + counters: [6, 9], + description: 'Bundle, Operations, & Actions Relationship', + title: 'Averages', + }, + { + charts: [9, 12], + counters: [9, 12], + description: 'The gas expenses for each bundle submission.', + title: 'Gas', + }, + { + charts: [12], + counters: [], + description: 'Metrics related to the contracts called and method IDs invoked within BLS Wallet actions.', + title: 'Actions' + } +]; \ No newline at end of file diff --git a/src/index.css b/src/index.css index 7074120..b5292af 100644 --- a/src/index.css +++ b/src/index.css @@ -18,7 +18,7 @@ } body { - background-color: #262726; + background-color: #090213; margin: 0; font-family: 'Heebo', 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', diff --git a/src/theme/index.ts b/src/theme/index.ts index 4764e7f..fcbd43a 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -2,6 +2,7 @@ import { CSSProperties } from 'react'; export type TypographyVariant = | 'caption' + | 'h2' | 'h4' | 'h5' | 'h6' @@ -21,6 +22,13 @@ export const typography: TypographyVariantMap = { fontWeight: 400, lineHeight: '14px', }, + h2: { + fontFamily: 'Heebo', + fontSize: '48px', + fontWeight: 500, + letterSpacing: '0.5px', + lineHeight: 'g6px', + }, h4: { fontFamily: 'Heebo', fontSize: '34px', diff --git a/src/utils/constants.ts b/src/utils/constants.ts index d185f1c..9e24ee4 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -8,6 +8,7 @@ export type AxisChartProps = { grid: boolean; height?: number; scale: ChartScale; + showDecimals?: boolean; stackBy?: StackBy; width?: number; xAxisTitle: string; @@ -35,18 +36,12 @@ export const MS_PER_MONTH = 30.44 * MS_PER_DAY; // Milleseconds per month export const MS_PER_YEAR = 365.25 * MS_PER_DAY; // Millesconds per year export const PALETTE = [ - '#a6cee3', - '#1f78b4', - '#b2df8a', - '#33a02c', - '#fb9a99', - '#e31a1c', - '#fdbf6f', - '#ff7f00', - '#cab2d6', - '#6a3d9a', - '#ffff99', - '#b15928', + '#84ACCE', + '#74FFCE', + '#E040FB', + '#3F51B5', + '#F18F01' + ]; export type StackBy = { diff --git a/yarn.lock b/yarn.lock index ad3f5e4..95ac1db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8087,6 +8087,11 @@ react-transition-group@2.9.0: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" +react-virtualized-auto-sizer@^1.0.20: + version "1.0.20" + resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.20.tgz#d9a907253a7c221c52fa57dc775a6ef40c182645" + integrity sha512-OdIyHwj4S4wyhbKHOKM1wLSj/UDXm839Z3Cvfg2a9j+He6yDa6i5p0qQvEiCnyQlGO/HyfSnigQwuxvYalaAXA== + react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"