Skip to content

Commit

Permalink
Add more Relayer stat variants
Browse files Browse the repository at this point in the history
  • Loading branch information
SecretSaturn committed Oct 9, 2024
1 parent af78df4 commit 812a777
Show file tree
Hide file tree
Showing 4 changed files with 593 additions and 8 deletions.
8 changes: 6 additions & 2 deletions src/pages/analytics/Analytics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import AccountsChart from './components/AccountsChart'
import ValidatorsChart from './components/ValidatorsChart'
import ContractsChart from './components/ContractsChart'
import TransactionsChart from './components/TransactionsChart'
import RelayerChart from './components/RelayerChart'
import RelayerChartWithDateSlider from './components/RelayerChartWithDateSlider'
import RelayerChartWithChainSlider from './components/RelayerChartWithChainSlider'
import RelayerChartWithProviderSlider from './components/RelayerChartWithProviderSlider'

function Analytics() {
const { L5AnalyticsApiData, analyticsData1, analyticsData2, analyticsData3, analyticsData4 } = useContext(APIContext)
Expand Down Expand Up @@ -65,7 +67,9 @@ function Analytics() {
{analyticsData4 ? (
<>
<div className="col-span-12 rounded-xl bg-white border border-neutral-200 dark:border-neutral-700 dark:bg-neutral-800 p-4">
<RelayerChart />
<RelayerChartWithDateSlider />
<RelayerChartWithChainSlider />
<RelayerChartWithProviderSlider />
</div>
</>
) : null}
Expand Down
287 changes: 287 additions & 0 deletions src/pages/analytics/components/RelayerChartWithChainSlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
import { useContext, useEffect, useState } from 'react'
import { formatNumber } from 'utils/commons'
import Tooltip from '@mui/material/Tooltip'
import Slider from '@mui/material/Slider'
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Tooltip as ChartTooltip,
Legend,
BarController
} from 'chart.js'
import { Bar } from 'react-chartjs-2'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'
import { useUserPreferencesStore } from 'store/UserPreferences'
import { APIContext } from 'context/APIContext'
import { chains } from 'utils/config'

ChartJS.register(CategoryScale, LinearScale, BarElement, ChartTooltip, Legend, BarController)

type Entry = {
Date: string
IBC_Counterpart: string
Relayer: string
Transactions: number
}

export default function RelayerChartWithChainSlider() {
const { theme } = useUserPreferencesStore()
const { analyticsData4 } = useContext(APIContext)
const [chartData, setChartData] = useState<any>(null)
const [chainLabels, setChainLabels] = useState<string[]>([])
const [selectedChainIndex, setSelectedChainIndex] = useState<number>(0)

useEffect(() => {
// Cache the mapping from bech32 prefixes to chain names
const bech32PrefixToChainName: Map<string, string> = new Map()
for (const chainInfo of Object.values(chains)) {
bech32PrefixToChainName.set(chainInfo.bech32_prefix, chainInfo.chain_name)
}

// Process data grouped by chain
const chainMap: Record<string, Entry[]> = {}

analyticsData4.forEach((entry: Entry) => {
const chainBech32Prefix = entry.IBC_Counterpart
const chainName = bech32PrefixToChainName.get(chainBech32Prefix) || chainBech32Prefix
chainMap[chainName] ||= []
chainMap[chainName].push(entry)
})

const sortedChains = Object.keys(chainMap).sort((a, b) => a.localeCompare(b))
setChainLabels(sortedChains)

// Initialize chart data with the first chain's data
if (sortedChains.length > 0) {
const initialChain = sortedChains[0]
updateChartDataForChain(initialChain, chainMap[initialChain])
}
}, [analyticsData4])

const updateChartDataForChain = (chainName: string, entries: Entry[]) => {
// Initialize data structures
const dataMatrix: Map<string, Map<string, number>> = new Map()
const datesSet: Set<string> = new Set()
const relayersSet: Set<string> = new Set()

// Build dataMatrix and collect unique dates and relayers
for (const entry of entries) {
const date = new Date(entry.Date).toISOString().split('T')[0]
const relayer = entry.Relayer || 'Other'

datesSet.add(date)
relayersSet.add(relayer)

if (!dataMatrix.has(relayer)) {
dataMatrix.set(relayer, new Map())
}
const dateMap = dataMatrix.get(relayer)!
dateMap.set(date, (dateMap.get(date) || 0) + entry.Transactions)
}

// Sort dates
const sortedDates = Array.from(datesSet).sort((a, b) => new Date(a).getTime() - new Date(b).getTime())

// Sort relayers
const sortedRelayers = Array.from(relayersSet).sort((a, b) => a.localeCompare(b))

// Create datasets for each relayer
const datasets = sortedRelayers.map((relayer) => {
const dateMap = dataMatrix.get(relayer)!
const data = sortedDates.map((date) => dateMap.get(date) || 0)
return {
label: relayer,
data,
backgroundColor: getColorFromRelayer(relayer)
}
})

// Set chart data with prepared labels and datasets
setChartData({
labels: sortedDates,
datasets
})
}

const handleSliderChange = (event: Event, newValue: number | number[]) => {
const index = newValue as number
setSelectedChainIndex(index)

const selectedChain = chainLabels[index]
const chainEntries = analyticsData4.filter((entry: Entry) => {
const chainBech32Prefix = entry.IBC_Counterpart
const chainName = chains[chainBech32Prefix]?.chain_name || chainBech32Prefix
return chainName === selectedChain
})

updateChartDataForChain(selectedChain, chainEntries)
}

const getSliderMarks = () => {
if (chainLabels.length === 0) return []

const numMarks = Math.min(10, chainLabels.length) // Limit to 10 marks max
const marks = []

// Always include the first chain
marks.push({
value: 0,
label: chainLabels[0]
})

if (chainLabels.length > 1) {
const interval = (chainLabels.length - 1) / (numMarks - 1)

// Add intermediate marks only if necessary
for (let i = 1; i < numMarks - 1; i++) {
const index = Math.round(i * interval)
marks.push({
value: index,
label: chainLabels[index]
})
}

// Always include the last chain
marks.push({
value: chainLabels.length - 1,
label: chainLabels[chainLabels.length - 1]
})
}

return marks
}

const options = {
responsive: true,
animation: true,
maintainAspectRatio: false,
scales: {
x: {
stacked: true,
ticks: {
color: theme === 'dark' ? '#fff' : '#000',
callback: function (value: any, index: number) {
return new Date(chartData.labels[index]).toLocaleDateString(undefined, {
month: 'short',
day: 'numeric'
})
}
},
grid: {
color: theme === 'dark' ? '#fff' : '#000',
alpha: 0.5,
display: false,
drawOnChartArea: true,
drawTicks: true,
tickLength: 0
},
border: {
color: theme === 'dark' ? '#fff' : '#000'
}
},
y: {
beginAtZero: true,
stacked: true,
ticks: {
color: theme === 'dark' ? '#fff' : '#000',
callback: function (value: any) {
return formatNumber(value, 2)
}
},
border: {
color: theme === 'dark' ? '#fff' : '#000'
},
grid: {
color: theme === 'dark' ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)',
display: true,
drawOnChartArea: true,
drawTicks: true,
tickLength: 0
}
}
},
plugins: {
legend: {
display: false
},
tooltip: {
xAlign: 'center',
color: theme === 'dark' ? '#fff' : '#000',
callbacks: {
label: function (context: any) {
if (context.parsed.y !== null) {
return `${context.dataset.label}: ${formatNumber(context.parsed.y)} Transactions`
}
return ''
}
}
}
}
}

function getColorFromRelayer(relayer: string) {
// Generate a unique color based on the relayer name
let hash = 0
for (let i = 0; i < relayer.length; i++) {
hash = relayer.charCodeAt(i) + ((hash << 5) - hash)
}
const color = `#${(hash & 0x00ffffff).toString(16).padStart(6, '0').slice(-6)}`
return color
}

return (
<>
<div>
<h2 className="text-center text-xl font-semibold pt-2.5 pb-0">
IBC Transactions by Date and Relayer (Per Chain)
<Tooltip
title="Use the slider to select a chain and view transactions by date and relayer."
placement="right"
arrow
>
<span className="text-neutral-600 dark:text-neutral-400 hover:text-black dark:hover:text-white transition-colors cursor-pointer ml-2 text-sm">
<FontAwesomeIcon icon={faInfoCircle} />
</span>
</Tooltip>
</h2>
</div>
<div className="w-full h-[300px] xl:h-[400px]">
{chartData ? <Bar data={chartData} options={options as any} /> : null}
</div>
<div className="mt-0">
<div className="w-3/4 mx-auto mb-(-1)">
<Slider
value={selectedChainIndex}
min={0}
max={chainLabels.length - 1}
onChange={handleSliderChange}
marks={getSliderMarks()}
valueLabelDisplay="auto"
valueLabelFormat={(value) => chainLabels[value]}
sx={{
color: theme === 'dark' ? '#fff' : '#000', // Set the slider color based on the theme
'& .MuiSlider-thumb': {
backgroundColor: theme === 'dark' ? '#fff' : '#000' // Thumb color
},
'& .MuiSlider-track': {
backgroundColor: theme === 'dark' ? '#fff' : '#000' // Track color
},
'& .MuiSlider-rail': {
backgroundColor: theme === 'dark' ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)' // Rail color
},
'& .MuiSlider-mark': {
backgroundColor: theme === 'dark' ? '#fff' : '#000' // Marks color
},
'& .MuiSlider-markLabel': {
color: theme === 'dark' ? '#fff' : '#000' // Mark labels color
}
}}
/>
</div>
</div>
</>
)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react'
import { useContext, useEffect, useState } from 'react'
import { formatNumber } from 'utils/commons'
import Tooltip from '@mui/material/Tooltip'
import Slider from '@mui/material/Slider'
Expand Down Expand Up @@ -27,7 +27,7 @@ type Entry = {
Transactions: number
}

export default function RelayerChartWithSlider() {
export default function RelayerChartWithDateSlider() {
const { theme } = useUserPreferencesStore()
const { analyticsData4 } = useContext(APIContext)
const [chartData, setChartData] = useState<any>(null)
Expand Down Expand Up @@ -132,8 +132,9 @@ export default function RelayerChartWithSlider() {

const formatDate = (date: string) =>
new Date(date).toLocaleDateString(undefined, {
month: 'short',
day: 'numeric'
year: '2-digit',
month: '2-digit',
day: '2-digit'
})

// Always include the first date
Expand Down Expand Up @@ -266,8 +267,9 @@ export default function RelayerChartWithSlider() {
valueLabelDisplay="auto"
valueLabelFormat={(value) =>
new Date(dates[value]).toLocaleDateString(undefined, {
month: 'short',
day: 'numeric'
year: '2-digit',
month: '2-digit',
day: '2-digit'
})
}
sx={{
Expand Down
Loading

0 comments on commit 812a777

Please sign in to comment.