diff --git a/src/pages/analytics/Analytics.tsx b/src/pages/analytics/Analytics.tsx index dfe34091..9c5872e1 100644 --- a/src/pages/analytics/Analytics.tsx +++ b/src/pages/analytics/Analytics.tsx @@ -10,6 +10,7 @@ import TransactionsChart from './components/TransactionsChart' import RelayerChartWithDateSlider from './components/RelayerChartWithDateSlider' import RelayerChartWithChainSlider from './components/RelayerChartWithChainSlider' import RelayerChartWithProviderSlider from './components/RelayerChartWithProviderSlider' +import RelayerChartTotal from './components/RelayerChartTotal' function Analytics() { const { L5AnalyticsApiData, analyticsData1, analyticsData2, analyticsData3, analyticsData4 } = useContext(APIContext) @@ -68,6 +69,9 @@ function Analytics() { <>
+
+ +
diff --git a/src/pages/analytics/components/RelayerChartTotal.tsx b/src/pages/analytics/components/RelayerChartTotal.tsx new file mode 100644 index 00000000..5d0e74fc --- /dev/null +++ b/src/pages/analytics/components/RelayerChartTotal.tsx @@ -0,0 +1,194 @@ +import { useContext, useMemo } from 'react' +import { bech32PrefixToChainName, formatNumber } from 'utils/commons' +import Tooltip from '@mui/material/Tooltip' +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' + +ChartJS.register(CategoryScale, LinearScale, BarElement, ChartTooltip, Legend, BarController) + +type Entry = { + Date: string + IBC_Counterpart: string + Relayer: string + Transactions: number +} + +export default function RelayerChartTotal() { + const { theme } = useUserPreferencesStore() + const { analyticsData4 } = useContext(APIContext) + + const chartData = useMemo(() => { + if (!analyticsData4) return null + + const datesSet = new Set() + const dataMatrix: Record = {} + + const filteredData = analyticsData4.filter( + (entry: Entry) => entry.IBC_Counterpart !== null && entry.IBC_Counterpart !== 'secret' + ) + + // Collect and sort dates + for (const entry of filteredData) { + const date = new Date(entry.Date).toISOString().split('T')[0] + datesSet.add(date) + } + const sortedDates = Array.from(datesSet).sort() + + // Create a date-to-index map + const dateIndexMap = sortedDates.reduce( + (acc, date, index) => { + acc[date] = index + return acc + }, + {} as Record + ) + + // Process entries and populate dataMatrix + for (const entry of filteredData) { + const date = new Date(entry.Date).toISOString().split('T')[0] + const dateIndex = dateIndexMap[date] + + const label = `${bech32PrefixToChainName.get(entry.IBC_Counterpart) || entry.IBC_Counterpart} - ${ + entry.Relayer || 'Other' + }` + + // Initialize dataMatrix[label] if it doesn't exist + if (!dataMatrix[label]) { + dataMatrix[label] = new Array(sortedDates.length).fill(0) + } + + // Update the corresponding value + dataMatrix[label][dateIndex] += entry.Transactions + } + + // Create datasets without nested mapping + const datasets = Object.keys(dataMatrix).map((label) => ({ + label, + data: dataMatrix[label], + backgroundColor: getColorFromCombo(label) + })) + + // Return the final chart data + return { + labels: sortedDates, + datasets + } + }, [analyticsData4]) + + const options = useMemo(() => { + if (!chartData) return {} + + return { + 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 as any).labels[index]).toLocaleDateString(undefined, { + year: '2-digit', + month: '2-digit', + day: '2-digit' + }) + } + }, + 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 '' + } + } + } + } + } + }, [theme, chartData]) + + function getColorFromCombo(combo: string) { + // Generate a unique color based on the combo string + let hash = 0 + for (let i = 0; i < combo.length; i++) { + hash = combo.charCodeAt(i) + ((hash << 5) - hash) + } + const color = `#${('000000' + (hash & 0xffffff).toString(16)).slice(-6)}` + return color + } + + return ( + <> +
+

+ IBC Transactions + + + + + +

+
+
+ {chartData ? : null} +
+ + ) +} diff --git a/src/pages/analytics/components/RelayerChartWithChainSlider.tsx b/src/pages/analytics/components/RelayerChartWithChainSlider.tsx index eda37b0e..08b3a1dd 100644 --- a/src/pages/analytics/components/RelayerChartWithChainSlider.tsx +++ b/src/pages/analytics/components/RelayerChartWithChainSlider.tsx @@ -208,7 +208,7 @@ export default function RelayerChartWithChainSlider() { 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)}` + const color = `#${('000000' + (hash & 0xffffff).toString(16)).slice(-6)}` return color } @@ -218,7 +218,7 @@ export default function RelayerChartWithChainSlider() {

{`IBC Transactions by Date and Relayer for ${chainLabels[selectedChainIndex]}`} diff --git a/src/pages/analytics/components/RelayerChartWithDateSlider.tsx b/src/pages/analytics/components/RelayerChartWithDateSlider.tsx index 025ee5a3..c2820bea 100644 --- a/src/pages/analytics/components/RelayerChartWithDateSlider.tsx +++ b/src/pages/analytics/components/RelayerChartWithDateSlider.tsx @@ -16,7 +16,6 @@ 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) @@ -230,7 +229,7 @@ export default function RelayerChartWithDateSlider() { 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)}` + const color = `#${('000000' + (hash & 0xffffff).toString(16)).slice(-6)}` return color } diff --git a/src/pages/analytics/components/RelayerChartWithProviderSlider.tsx b/src/pages/analytics/components/RelayerChartWithProviderSlider.tsx index 7fafd244..c80eb675 100644 --- a/src/pages/analytics/components/RelayerChartWithProviderSlider.tsx +++ b/src/pages/analytics/components/RelayerChartWithProviderSlider.tsx @@ -16,7 +16,6 @@ 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) @@ -224,7 +223,7 @@ export default function RelayerChartWithProviderSlider() { for (let i = 0; i < chain.length; i++) { hash = chain.charCodeAt(i) + ((hash << 5) - hash) } - const color = `#${(hash & 0x00ffffff).toString(16).padStart(6, '0').slice(-6)}` + const color = `#${('000000' + (hash & 0xffffff).toString(16)).slice(-6)}` return color }