@@ -6,81 +6,102 @@ import {
66 ChartTooltip ,
77 ChartTooltipContent ,
88} from "@/components/ui/chart" ;
9- import { formatTickerNumber } from "lib/format-utils " ;
9+ import { formatDate } from "date-fns " ;
1010import { useMemo } from "react" ;
1111import {
1212 Bar ,
1313 CartesianGrid ,
1414 BarChart as RechartsBarChart ,
1515 XAxis ,
16- YAxis ,
1716} from "recharts" ;
1817import type { RpcMethodStats } from "types/analytics" ;
1918import { EmptyStateCard } from "../../../../components/Analytics/EmptyStateCard" ;
2019
2120export function RpcMethodBarChartCardUI ( {
2221 rawData,
2322} : { rawData : RpcMethodStats [ ] } ) {
24- const uniqueMethods = useMemo (
25- ( ) => Array . from ( new Set ( rawData . map ( ( d ) => d . evmMethod ) ) ) ,
26- [ rawData ] ,
27- ) ;
28- const uniqueDates = useMemo (
29- ( ) => Array . from ( new Set ( rawData . map ( ( d ) => d . date ) ) ) ,
30- [ rawData ] ,
31- ) ;
23+ const maxMethodsToDisplay = 10 ;
24+
25+ const { data, methodsToDisplay, chartConfig, isAllEmpty } = useMemo ( ( ) => {
26+ const dateToValueMap : Map < string , Record < string , number > > = new Map ( ) ;
27+ const methodNameToCountMap : Map < string , number > = new Map ( ) ;
28+
29+ for ( const dataItem of rawData ) {
30+ const { date, evmMethod, count } = dataItem ;
31+ let dateRecord = dateToValueMap . get ( date ) ;
3232
33- const data = useMemo ( ( ) => {
34- return uniqueDates . map ( ( date ) => {
35- const dateData : { [ key : string ] : string | number } = { date } ;
36- for ( const method of uniqueMethods ) {
37- const methodData = rawData . find (
38- ( d ) => d . date === date && d . evmMethod === method ,
39- ) ;
40- dateData [ method ] = methodData ?. count ?? 0 ;
33+ if ( ! dateRecord ) {
34+ dateRecord = { } ;
35+ dateToValueMap . set ( date , dateRecord ) ;
4136 }
4237
43- // If we have too many methods to display well, add "other" and group the lowest keys for each time period
44- if ( uniqueMethods . length > 5 ) {
45- // If we haven't added "other" as a key yet, add it
46- if ( ! uniqueMethods . includes ( "Other" ) ) {
47- uniqueMethods . push ( "Other" ) ;
48- }
38+ dateRecord [ evmMethod ] = ( dateRecord [ evmMethod ] || 0 ) + count ;
39+ methodNameToCountMap . set (
40+ evmMethod ,
41+ ( methodNameToCountMap . get ( evmMethod ) || 0 ) + count ,
42+ ) ;
43+ }
44+
45+ // sort methods by count (highest count first) - remove the ones with 0 count
46+ const sortedMethodsByCount = Array . from ( methodNameToCountMap . entries ( ) )
47+ . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] )
48+ . filter ( ( x ) => x [ 1 ] > 0 ) ;
4949
50- // Sort the methods by their count for the time period
51- const sortedMethods = uniqueMethods
52- . filter ( ( m ) => m !== "Other" )
53- . sort (
54- ( a , b ) =>
55- ( ( dateData [ b ] as number ) ?? 0 ) - ( ( dateData [ a ] as number ) ?? 0 ) ,
56- ) ;
57-
58- dateData . Other = 0 ;
59- for ( const method of sortedMethods . slice ( 5 , sortedMethods . length ) ) {
60- dateData . Other += ( dateData [ method ] as number ) ?? 0 ;
61- delete dateData [ method ] ;
50+ const methodsToDisplayArray = sortedMethodsByCount
51+ . slice ( 0 , maxMethodsToDisplay )
52+ . map ( ( [ method ] ) => method ) ;
53+ const methodsToDisplay = new Set ( methodsToDisplayArray ) ;
54+
55+ // loop over each entry in dateToValueMap
56+ // replace the method that is not in methodsToDisplay with "Other"
57+ // add total key that is the sum of all methods
58+ for ( const dateRecord of dateToValueMap . values ( ) ) {
59+ // calculate total
60+ let totalCountOfDay = 0 ;
61+ for ( const count of Object . values ( dateRecord ) ) {
62+ totalCountOfDay += count ;
63+ }
64+
65+ for ( const method of Object . keys ( dateRecord ) ) {
66+ if ( ! methodsToDisplay . has ( method ) ) {
67+ dateRecord . Other =
68+ ( dateRecord . Other || 0 ) + ( dateRecord [ method ] || 0 ) ;
69+ delete dateRecord [ method ] ;
6270 }
6371 }
64- return dateData ;
65- } ) ;
66- } , [ uniqueDates , uniqueMethods , rawData ] ) ;
67-
68- const config : ChartConfig = useMemo ( ( ) => {
69- const config : ChartConfig = { } ;
70- for ( const method of uniqueMethods ) {
71- config [ method ] = {
72+
73+ dateRecord . total = totalCountOfDay ;
74+ }
75+
76+ const returnValue : Array < Record < string , string | number > > = [ ] ;
77+ for ( const [ date , value ] of dateToValueMap . entries ( ) ) {
78+ returnValue . push ( { date, ...value } ) ;
79+ }
80+
81+ const chartConfig : ChartConfig = { } ;
82+ for ( const method of methodsToDisplayArray ) {
83+ chartConfig [ method ] = {
7284 label : method ,
7385 } ;
7486 }
75- return config ;
76- } , [ uniqueMethods ] ) ;
77-
78- if (
79- data . length === 0 ||
80- data . every ( ( date ) =>
81- Object . keys ( date ) . every ( ( k ) => k === "date" || date [ k ] === 0 ) ,
82- )
83- ) {
87+
88+ // if we need to display "Other" methods
89+ if ( sortedMethodsByCount . length > maxMethodsToDisplay ) {
90+ chartConfig . Other = {
91+ label : "Other" ,
92+ } ;
93+ methodsToDisplayArray . push ( "Other" ) ;
94+ }
95+
96+ return {
97+ data : returnValue ,
98+ methodsToDisplay : methodsToDisplayArray ,
99+ chartConfig,
100+ isAllEmpty : returnValue . every ( ( d ) => d . total === 0 ) ,
101+ } ;
102+ } , [ rawData ] ) ;
103+
104+ if ( data . length === 0 || isAllEmpty ) {
84105 return < EmptyStateCard metric = "RPC" link = "https://portal.thirdweb.com/" /> ;
85106 }
86107
@@ -93,7 +114,7 @@ export function RpcMethodBarChartCardUI({
93114 </ CardHeader >
94115 < CardContent className = "px-2 sm:p-6 sm:pl-0" >
95116 < ChartContainer
96- config = { config }
117+ config = { chartConfig }
97118 className = "aspect-auto h-[250px] w-full pt-6"
98119 >
99120 < RechartsBarChart
@@ -105,6 +126,7 @@ export function RpcMethodBarChartCardUI({
105126 } }
106127 >
107128 < CartesianGrid vertical = { false } />
129+
108130 < XAxis
109131 dataKey = "date"
110132 tickLine = { false }
@@ -119,36 +141,38 @@ export function RpcMethodBarChartCardUI({
119141 } ) ;
120142 } }
121143 />
122- < YAxis
123- width = { 48 }
124- tickLine = { false }
125- axisLine = { false }
126- tickFormatter = { ( value : number ) => formatTickerNumber ( value ) }
127- />
144+
128145 < ChartTooltip
129146 content = {
130147 < ChartTooltipContent
131- labelFormatter = { ( value ) => {
132- return new Date ( value ) . toLocaleDateString ( "en-US" , {
133- month : "short" ,
134- day : "numeric" ,
135- year : "numeric" ,
136- } ) ;
148+ labelFormatter = { ( d ) => formatDate ( new Date ( d ) , "MMM d" ) }
149+ valueFormatter = { ( _value , _item ) => {
150+ const value = typeof _value === "number" ? _value : 0 ;
151+ const payload = _item as {
152+ payload : {
153+ total : number ;
154+ } ;
155+ } ;
156+ const total =
157+ payload . payload . total === 0 ? 1 : payload . payload . total ;
158+ return (
159+ < span className = "inline-flex gap-1.5" >
160+ { `${ ( ( value / total ) * 100 ) . toFixed ( 2 ) } ` }
161+ < span className = "text-muted-foreground" > %</ span >
162+ </ span >
163+ ) ;
137164 } }
138- valueFormatter = { ( v : unknown ) =>
139- formatTickerNumber ( v as number )
140- }
141165 />
142166 }
143167 />
144- { uniqueMethods . map ( ( method , idx ) => (
168+ { methodsToDisplay . map ( ( method , idx ) => (
145169 < Bar
146170 key = { method }
147171 stackId = "a"
148172 dataKey = { method }
149173 radius = { [
150- idx === uniqueMethods . length - 1 ? 4 : 0 ,
151- idx === uniqueMethods . length - 1 ? 4 : 0 ,
174+ idx === methodsToDisplay . length - 1 ? 4 : 0 ,
175+ idx === methodsToDisplay . length - 1 ? 4 : 0 ,
152176 idx === 0 ? 4 : 0 ,
153177 idx === 0 ? 4 : 0 ,
154178 ] }
0 commit comments