+
+
+
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
}