diff --git a/package.json b/package.json
index e70ee3f..3a975bf 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,8 @@
"@mui/x-date-pickers": "^7.13.0",
"@mui/x-tree-view": "^7.13.0",
"@mysten/dapp-kit": "^0.16.15",
+ "@nivo/core": "^0.99.0",
+ "@nivo/sankey": "^0.99.0",
"@rainbow-me/rainbowkit": "^2.2.8",
"@tanstack/react-query": "^5.83.0",
"apexcharts": "^4.0.0",
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 39ee758..8552d37 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -9,6 +9,7 @@ import { DashboardContent, DashboardLayout } from 'src/layouts/dashboard'
const InflowOutflowCharts = lazy(() => import('src/components/chart/inflow-outflow-charts'))
const TokenVolumePieChart = lazy(() => import('src/components/chart/pie-charts'))
const GasUsageChart = lazy(() => import('src/components/chart/gas-usage-chart'))
+const BridgeRouteMap = lazy(() => import('src/components/chart/bridge-route-map'))
import BridgePerformanceChart from 'src/components/chart/bridge-performance-chart'
import CumulativeNetInflow from 'src/components/chart/cumulative-net-inflow'
import TopTokens from 'src/components/widgets/top-tokens'
@@ -23,6 +24,12 @@ export default function Page() {
+
+ {/* Bridge Route Map - Sankey visualization */}
+ }>
+
+
+
{/* Latest Transactions - also use visibility system */}
diff --git a/src/components/chart/bridge-route-map.tsx b/src/components/chart/bridge-route-map.tsx
new file mode 100644
index 0000000..12a9622
--- /dev/null
+++ b/src/components/chart/bridge-route-map.tsx
@@ -0,0 +1,547 @@
+'use client'
+
+import { useState, useMemo, useCallback } from 'react'
+import dynamic from 'next/dynamic'
+import {
+ Card,
+ CardHeader,
+ Box,
+ ToggleButton,
+ ToggleButtonGroup,
+ Typography,
+ Stack,
+ Skeleton,
+} from '@mui/material'
+import { useTheme, alpha } from '@mui/material/styles'
+import useSWR from 'swr'
+import { useGlobalContext } from 'src/provider/global-provider'
+import { getNetwork } from 'src/hooks/get-network-storage'
+import { endpoints, fetcher } from 'src/utils/axios'
+import { RouteMapRow, getTokensList } from 'src/utils/types'
+import { fNumber } from 'src/utils/format-number'
+
+// Dynamic import for Sankey to avoid SSR issues
+const ResponsiveSankey = dynamic(() => import('@nivo/sankey').then(mod => mod.ResponsiveSankey), {
+ ssr: false,
+})
+
+type ValueType = 'usd' | 'count'
+
+// Chain colors - more vibrant
+const CHAIN_COLORS: Record = {
+ SUI: '#4da2ff',
+ ETH: '#8c7cf0',
+}
+
+// Helper to strip prefix from node ID for display
+const getDisplayLabel = (id: string): string => {
+ if (id.startsWith('from:')) return id.slice(5)
+ if (id.startsWith('to:')) return id.slice(3)
+ if (id.startsWith('token:')) return id.slice(6)
+ return id
+}
+
+interface SankeyNode {
+ id: string
+ nodeColor: string
+}
+
+interface SankeyLink {
+ source: string
+ target: string
+ value: number
+ color?: string
+}
+
+interface SankeyData {
+ nodes: SankeyNode[]
+ links: SankeyLink[]
+}
+
+export default function BridgeRouteMap() {
+ const theme = useTheme()
+ const network = getNetwork()
+ const { timePeriod, selectedTokens } = useGlobalContext()
+ const [valueType, setValueType] = useState('usd')
+ const [highlightedItem, setHighlightedItem] = useState(null)
+
+ const tokensList = getTokensList(network)
+
+ // Fetch route data
+ const { data, isLoading } = useSWR(
+ `${endpoints.routes}?network=${network}&timePeriod=${encodeURIComponent(timePeriod)}`,
+ fetcher,
+ { revalidateOnFocus: false },
+ )
+
+ // Filter data by selected tokens
+ const filteredData = useMemo(() => {
+ if (!data) return []
+ if (selectedTokens.includes('All')) return data
+ return data.filter(row => selectedTokens.includes(row.token_info.name))
+ }, [data, selectedTokens])
+
+ // Build Sankey data structure
+ // Structure: Source Chain (left) -> Token -> Destination Chain (right)
+ // To avoid circular links, we create separate source/destination chain nodes
+ const sankeyData = useMemo((): SankeyData => {
+ if (!filteredData.length) {
+ return { nodes: [], links: [] }
+ }
+
+ const nodesMap = new Map()
+ const linksMap = new Map()
+
+ filteredData.forEach(row => {
+ // Create separate nodes for source and destination to avoid cycles
+ // Source chains are on the left, destination chains on the right
+ const sourceNode = `from:${row.from_chain}`
+ const tokenNode = `token:${row.token_info.name}`
+ const targetNode = `to:${row.destination_chain}`
+
+ // Add source chain node
+ if (!nodesMap.has(sourceNode)) {
+ nodesMap.set(sourceNode, {
+ id: sourceNode,
+ nodeColor: CHAIN_COLORS[row.from_chain] || '#888888',
+ })
+ }
+ // Add token node
+ if (!nodesMap.has(tokenNode)) {
+ const tokenColor =
+ tokensList.find(t => t.ticker === row.token_info.name)?.color || '#888888'
+ nodesMap.set(tokenNode, { id: tokenNode, nodeColor: tokenColor })
+ }
+ // Add destination chain node
+ if (!nodesMap.has(targetNode)) {
+ nodesMap.set(targetNode, {
+ id: targetNode,
+ nodeColor: CHAIN_COLORS[row.destination_chain] || '#888888',
+ })
+ }
+
+ const value = valueType === 'usd' ? row.total_volume_usd : row.total_count
+
+ // Link: source chain -> token
+ const link1Key = `${sourceNode}|${tokenNode}`
+ linksMap.set(link1Key, (linksMap.get(link1Key) || 0) + value)
+
+ // Link: token -> destination chain
+ const link2Key = `${tokenNode}|${targetNode}`
+ linksMap.set(link2Key, (linksMap.get(link2Key) || 0) + value)
+ })
+
+ // Build links (filter out zero values)
+ const links: SankeyLink[] = Array.from(linksMap.entries())
+ .filter(([, value]) => value > 0)
+ .map(([key, value]) => {
+ const [source, target] = key.split('|')
+ return { source, target, value }
+ })
+
+ return { nodes: Array.from(nodesMap.values()), links }
+ }, [filteredData, valueType, tokensList])
+
+ const handleValueTypeChange = (
+ _: React.MouseEvent,
+ newValue: ValueType | null,
+ ) => {
+ if (newValue) {
+ setValueType(newValue)
+ }
+ }
+
+ // Compute which nodes and links should be highlighted based on legend hover
+ // For chains: highlight routes ORIGINATING FROM that chain (outgoing flows)
+ // For tokens: highlight only the token and its direct connections
+ const highlightedElements = useMemo(() => {
+ if (!highlightedItem || !sankeyData.links.length) {
+ return { nodes: new Set(), links: new Set() }
+ }
+
+ const highlightedNodes = new Set()
+ const highlightedLinks = new Set()
+
+ if (highlightedItem.startsWith('chain:')) {
+ // Highlighting a chain - show all routes ORIGINATING FROM this chain
+ const chainName = highlightedItem.slice(6)
+ const fromNode = `from:${chainName}`
+
+ // Find first leg: from:CHAIN -> token:TOKEN
+ sankeyData.links.forEach(link => {
+ if (link.source === fromNode) {
+ highlightedLinks.add(`${link.source}|${link.target}`)
+ highlightedNodes.add(link.source)
+ highlightedNodes.add(link.target)
+
+ // Find second leg: token:TOKEN -> to:OTHER_CHAIN
+ const tokenNode = link.target
+ sankeyData.links.forEach(link2 => {
+ if (link2.source === tokenNode && link2.target.startsWith('to:')) {
+ highlightedLinks.add(`${link2.source}|${link2.target}`)
+ highlightedNodes.add(link2.target)
+ }
+ })
+ }
+ })
+ } else if (highlightedItem.startsWith('token:')) {
+ // Highlighting a token - show only connections to/from this token
+ const tokenNode = highlightedItem
+
+ sankeyData.links.forEach(link => {
+ if (link.source === tokenNode || link.target === tokenNode) {
+ highlightedLinks.add(`${link.source}|${link.target}`)
+ highlightedNodes.add(link.source)
+ highlightedNodes.add(link.target)
+ }
+ })
+ }
+
+ return { nodes: highlightedNodes, links: highlightedLinks }
+ }, [highlightedItem, sankeyData.links])
+
+ // Check if a node should be highlighted based on legend hover
+ const isNodeHighlighted = useCallback(
+ (nodeId: string): boolean => {
+ if (!highlightedItem) return true
+ return highlightedElements.nodes.has(nodeId)
+ },
+ [highlightedItem, highlightedElements.nodes],
+ )
+
+ // Check if a link should be highlighted based on legend hover
+ const isLinkHighlighted = useCallback(
+ (source: string, target: string): boolean => {
+ if (!highlightedItem) return true
+ return highlightedElements.links.has(`${source}|${target}`)
+ },
+ [highlightedItem, highlightedElements.links],
+ )
+
+ const hasData = sankeyData.nodes.length > 0 && sankeyData.links.length > 0
+
+ // Create a map of node colors for link styling
+ const nodeColorMap = useMemo(() => {
+ const map = new Map()
+ sankeyData.nodes.forEach(node => {
+ map.set(node.id, node.nodeColor)
+ })
+ return map
+ }, [sankeyData.nodes])
+
+ // Modify sankey data to include opacity based on highlight
+ const styledSankeyData = useMemo((): SankeyData => {
+ if (!hasData) return sankeyData
+
+ // When highlighting, we fade non-related nodes
+ const styledNodes = sankeyData.nodes.map(node => ({
+ ...node,
+ nodeColor:
+ highlightedItem && !isNodeHighlighted(node.id)
+ ? alpha(node.nodeColor, 0.2)
+ : node.nodeColor,
+ }))
+
+ // For links - we need to set colors that will be picked up
+ // Nivo will use these for the gradient
+ const styledLinks = sankeyData.links.map(link => {
+ const linkIsHighlighted = isLinkHighlighted(link.source, link.target)
+
+ if (highlightedItem && !linkIsHighlighted) {
+ // Non-highlighted links get faded
+ const sourceColor = nodeColorMap.get(link.source) || '#888888'
+ return {
+ ...link,
+ // Store a marker that this link should be faded
+ _faded: true,
+ color: alpha(sourceColor, 0.08),
+ }
+ }
+ return {
+ ...link,
+ _faded: false,
+ }
+ })
+
+ return { nodes: styledNodes, links: styledLinks }
+ }, [sankeyData, highlightedItem, hasData, isNodeHighlighted, isLinkHighlighted, nodeColorMap])
+
+ return (
+
+
+ USD Volume
+ Tx Count
+
+ }
+ />
+
+
+ {isLoading ? (
+
+ ) : !hasData ? (
+
+ No route data available
+
+ Try adjusting the time period or token filters
+
+
+ ) : (
+ (node as unknown as SankeyNode).nodeColor || '#888'}
+ nodeOpacity={1}
+ nodeHoverOpacity={1}
+ nodeHoverOthersOpacity={0.2}
+ nodeThickness={20}
+ nodeSpacing={28}
+ nodeBorderWidth={0}
+ nodeBorderRadius={4}
+ linkOpacity={0.75}
+ linkHoverOpacity={1}
+ linkHoverOthersOpacity={0.12}
+ linkContract={3}
+ linkBlendMode="normal"
+ enableLinkGradient
+ labelPosition="outside"
+ labelOrientation="horizontal"
+ labelPadding={16}
+ label={node => getDisplayLabel(node.id as string)}
+ labelTextColor={theme.palette.text.primary}
+ nodeTooltip={({ node }) => (
+
+
+ {getDisplayLabel(node.id as string)}
+
+
+ {valueType === 'usd'
+ ? `$${fNumber(node.value)}`
+ : `${fNumber(node.value)} transactions`}
+
+
+ )}
+ linkTooltip={({ link }) => (
+
+
+ {getDisplayLabel(link.source.id as string)} →{' '}
+ {getDisplayLabel(link.target.id as string)}
+
+
+ {valueType === 'usd'
+ ? `$${fNumber(link.value)}`
+ : `${fNumber(link.value)} transactions`}
+
+
+ )}
+ />
+ )}
+
+
+ {/* Legend with hover interaction */}
+ {hasData && (
+
+
+ {/* Chains label */}
+
+ Chains:
+
+ {/* Chain legend */}
+ {Object.entries(CHAIN_COLORS).map(([chain, color]) => (
+ setHighlightedItem(`chain:${chain}`)}
+ onMouseLeave={() => setHighlightedItem(null)}
+ sx={{
+ cursor: 'pointer',
+ px: 1.5,
+ py: 0.5,
+ borderRadius: 1,
+ transition: 'all 0.2s ease',
+ backgroundColor:
+ highlightedItem === `chain:${chain}`
+ ? alpha(color, 0.15)
+ : 'transparent',
+ '&:hover': {
+ backgroundColor: alpha(color, 0.15),
+ },
+ }}
+ >
+
+
+ {chain}
+
+
+ ))}
+
+ {/* Divider */}
+
+
+ {/* Tokens label */}
+
+ Tokens:
+
+ {/* Token legend */}
+ {tokensList
+ .filter(
+ token =>
+ selectedTokens.includes('All') ||
+ selectedTokens.includes(token.ticker),
+ )
+ .map(token => (
+ setHighlightedItem(`token:${token.ticker}`)}
+ onMouseLeave={() => setHighlightedItem(null)}
+ sx={{
+ cursor: 'pointer',
+ px: 1.5,
+ py: 0.5,
+ borderRadius: 1,
+ transition: 'all 0.2s ease',
+ backgroundColor:
+ highlightedItem === `token:${token.ticker}`
+ ? alpha(token.color, 0.15)
+ : 'transparent',
+ '&:hover': {
+ backgroundColor: alpha(token.color, 0.15),
+ },
+ }}
+ >
+
+
+ {token.ticker}
+
+
+ ))}
+
+
+ )}
+
+ )
+}
diff --git a/src/pages/api/cards.ts b/src/pages/api/cards.ts
index 0f3067d..c75472c 100644
--- a/src/pages/api/cards.ts
+++ b/src/pages/api/cards.ts
@@ -11,8 +11,9 @@ import { getPrices } from './prices'
dayjs.extend(isoWeek)
dayjs.extend(weekOfYear)
-export const calculateStartDate = (timePeriod: string, currentDate?: dayjs.Dayjs) => {
- let day = currentDate || dayjs()
+// Returns timestamp in ms - used for interval calculations with optional base date
+const calculateStartTimestamp = (timePeriod: string, currentDate?: dayjs.Dayjs): number => {
+ const day = currentDate || dayjs()
switch (timePeriod) {
case 'Last 24h':
return day.subtract(1, 'day').valueOf()
@@ -32,10 +33,10 @@ export const calculateStartDate = (timePeriod: string, currentDate?: dayjs.Dayjs
}
export const computerIntervals = (timePeriod: TimePeriod, computePrevious?: boolean) => {
- const startDate = calculateStartDate(timePeriod)
+ const startDate = calculateStartTimestamp(timePeriod)
const fromInterval = computePrevious
- ? calculateStartDate(timePeriod, dayjs(startDate))
+ ? calculateStartTimestamp(timePeriod, dayjs(startDate))
: startDate
const toInterval = computePrevious ? startDate : new Date().getTime()
diff --git a/src/pages/api/routes.ts b/src/pages/api/routes.ts
new file mode 100644
index 0000000..108eb9a
--- /dev/null
+++ b/src/pages/api/routes.ts
@@ -0,0 +1,90 @@
+import { NextApiRequest, NextApiResponse } from 'next'
+import { sendError, sendReply } from './utils'
+import db from './database'
+import { getNetworkConfig } from 'src/config/helper'
+import { getPrices } from './prices'
+import { calculateStartDate } from 'src/utils/format-chart-data'
+
+// Helper to get network name from ID
+const getNetworkName = (id: number, networkId: { SUI: number; ETH: number }): string | null => {
+ for (const [key, value] of Object.entries(networkId)) {
+ if (value === id) {
+ return key
+ }
+ }
+ return null
+}
+
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ const networkConfig = getNetworkConfig({ req })
+ const timePeriod = (req.query.timePeriod as string) || 'Last Month'
+
+ const startDate = calculateStartDate(timePeriod)
+ const startTimestampMs = startDate.valueOf()
+
+ /**
+ * Query to get route data grouped by source chain, destination chain, and token
+ * This gives us the flow of assets between chains
+ */
+ const query = await db[networkConfig.network]`
+ SELECT
+ chain_id,
+ destination_chain,
+ token_id,
+ COUNT(*) AS total_count,
+ SUM(amount) AS total_volume
+ FROM
+ public.token_transfer_data
+ WHERE
+ is_finalized = true
+ AND timestamp_ms >= ${startTimestampMs}
+ GROUP BY
+ chain_id,
+ destination_chain,
+ token_id
+ ORDER BY
+ total_volume DESC`
+
+ const prices = await getPrices(networkConfig.network)
+
+ // Transform the data with proper chain names and token info
+ const transformedData = (query as any[])
+ .map(row => {
+ const tokenData = networkConfig.config.coins[row.token_id]
+ const priceData = prices.find(price => price.token_id === row.token_id)
+
+ const fromChain = getNetworkName(row.chain_id, networkConfig.config.networkId)
+ const destinationChain = getNetworkName(
+ row.destination_chain,
+ networkConfig.config.networkId,
+ )
+
+ if (!tokenData || !fromChain || !destinationChain) {
+ return null
+ }
+
+ const totalVolume = Number(row.total_volume) / tokenData.deno
+ const totalVolumeUsd = totalVolume * Number(priceData?.price || 0)
+
+ return {
+ from_chain: fromChain,
+ destination_chain: destinationChain,
+ token_id: row.token_id,
+ token_info: {
+ id: tokenData.id,
+ name: tokenData.name,
+ deno: tokenData.deno,
+ },
+ total_count: Number(row.total_count),
+ total_volume: totalVolume,
+ total_volume_usd: totalVolumeUsd,
+ }
+ })
+ .filter(Boolean)
+
+ sendReply(res, transformedData)
+ } catch (error) {
+ sendError(res, error)
+ }
+}
diff --git a/src/utils/axios.ts b/src/utils/axios.ts
index 6954bf5..6107a18 100644
--- a/src/utils/axios.ts
+++ b/src/utils/axios.ts
@@ -40,6 +40,7 @@ export const endpoints = {
outflows: '/api/outflows',
bridgeMetrics: '/api/bridge-metrics', // Add this line
fees: '/api/fees',
+ routes: '/api/routes',
volume: {
daily: '/api/volume/daily',
hourly: '/api/volume/hourly',
diff --git a/src/utils/types.ts b/src/utils/types.ts
index a9ea867..a69968f 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -226,3 +226,18 @@ export type BridgeMetricsResponse = {
}
// Keep number for backward compatibility
}
+
+// Route map data for Sankey chart
+export type RouteMapRow = {
+ from_chain: string
+ destination_chain: string
+ token_id: number
+ token_info: {
+ id: number
+ name: string
+ deno: number
+ }
+ total_count: number
+ total_volume: number
+ total_volume_usd: number
+}
diff --git a/yarn.lock b/yarn.lock
index bbbd627..71ebea7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1909,6 +1909,96 @@
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.29.tgz#9097b85893a51ca9ba3b9d1733a4aab954edeab5"
integrity sha512-iPPwUEKnVs7pwR0EBLJlwxLD7TTHWS/AoVZx1l9ZQzfQciqaFEr5AlYzA2uB6Fyby1IF18t4PL0nTpB+k4Tzlw==
+"@nivo/colors@0.99.0":
+ version "0.99.0"
+ resolved "https://registry.yarnpkg.com/@nivo/colors/-/colors-0.99.0.tgz#f844a0f0de0597b35829405541e145f6db50c830"
+ integrity sha512-hyYt4lEFIfXOUmQ6k3HXm3KwhcgoJpocmoGzLUqzk7DzuhQYJo+4d5jIGGU0N/a70+9XbHIdpKNSblHAIASD3w==
+ dependencies:
+ "@nivo/core" "0.99.0"
+ "@nivo/theming" "0.99.0"
+ "@types/d3-color" "^3.0.0"
+ "@types/d3-scale" "^4.0.8"
+ "@types/d3-scale-chromatic" "^3.0.0"
+ d3-color "^3.1.0"
+ d3-scale "^4.0.2"
+ d3-scale-chromatic "^3.0.0"
+ lodash "^4.17.21"
+
+"@nivo/core@0.99.0", "@nivo/core@^0.99.0":
+ version "0.99.0"
+ resolved "https://registry.yarnpkg.com/@nivo/core/-/core-0.99.0.tgz#91ccf3d2419fcfb5f740dba468f0d6f059933af4"
+ integrity sha512-olCItqhPG3xHL5ei+vg52aB6o+6S+xR2idpkd9RormTTUniZb8U2rOdcQojOojPY5i9kVeQyLFBpV4YfM7OZ9g==
+ dependencies:
+ "@nivo/theming" "0.99.0"
+ "@nivo/tooltip" "0.99.0"
+ "@react-spring/web" "9.4.5 || ^9.7.2 || ^10.0"
+ "@types/d3-shape" "^3.1.6"
+ d3-color "^3.1.0"
+ d3-format "^1.4.4"
+ d3-interpolate "^3.0.1"
+ d3-scale "^4.0.2"
+ d3-scale-chromatic "^3.0.0"
+ d3-shape "^3.2.0"
+ d3-time-format "^3.0.0"
+ lodash "^4.17.21"
+ react-virtualized-auto-sizer "^1.0.26"
+ use-debounce "^10.0.4"
+
+"@nivo/legends@0.99.0":
+ version "0.99.0"
+ resolved "https://registry.yarnpkg.com/@nivo/legends/-/legends-0.99.0.tgz#4f1fede8c450dad942b851a9a429838e343aea1b"
+ integrity sha512-P16FjFqNceuTTZphINAh5p0RF0opu3cCKoWppe2aRD9IuVkvRm/wS5K1YwMCxDzKyKh5v0AuTlu9K6o3/hk8hA==
+ dependencies:
+ "@nivo/colors" "0.99.0"
+ "@nivo/core" "0.99.0"
+ "@nivo/text" "0.99.0"
+ "@nivo/theming" "0.99.0"
+ "@types/d3-scale" "^4.0.8"
+ d3-scale "^4.0.2"
+
+"@nivo/sankey@^0.99.0":
+ version "0.99.0"
+ resolved "https://registry.yarnpkg.com/@nivo/sankey/-/sankey-0.99.0.tgz#58aa360a7bb37cf950b9e4fb2d052eb6bd302bf3"
+ integrity sha512-u5hySywsachjo9cHdUxCR9qwD6gfRVPEAcpuIUKiA0WClDjdGbl3vkrQcQcFexJUBThqSSbwGCDWR+2INXSbTw==
+ dependencies:
+ "@nivo/colors" "0.99.0"
+ "@nivo/core" "0.99.0"
+ "@nivo/legends" "0.99.0"
+ "@nivo/text" "0.99.0"
+ "@nivo/theming" "0.99.0"
+ "@nivo/tooltip" "0.99.0"
+ "@react-spring/web" "9.4.5 || ^9.7.2 || ^10.0"
+ "@types/d3-sankey" "^0.11.2"
+ "@types/d3-shape" "^3.1.6"
+ d3-sankey "^0.12.3"
+ d3-shape "^3.2.0"
+ lodash "^4.17.21"
+
+"@nivo/text@0.99.0":
+ version "0.99.0"
+ resolved "https://registry.yarnpkg.com/@nivo/text/-/text-0.99.0.tgz#b52f37d903e731f60027c814658e271676fafdf8"
+ integrity sha512-ho3oZpAZApsJNjsIL5WJSAdg/wjzTBcwo1KiHBlRGUmD+yUWO8qp7V+mnYRhJchwygtRVALlPgZ/rlcW2Xr/MQ==
+ dependencies:
+ "@nivo/core" "0.99.0"
+ "@nivo/theming" "0.99.0"
+ "@react-spring/web" "9.4.5 || ^9.7.2 || ^10.0"
+
+"@nivo/theming@0.99.0":
+ version "0.99.0"
+ resolved "https://registry.yarnpkg.com/@nivo/theming/-/theming-0.99.0.tgz#89de03832081153093dcfc2eb2fdaaf3424da963"
+ integrity sha512-KvXlf0nqBzh/g2hAIV9bzscYvpq1uuO3TnFN3RDXGI72CrbbZFTGzprPju3sy/myVsauv+Bb+V4f5TZ0jkYKRg==
+ dependencies:
+ lodash "^4.17.21"
+
+"@nivo/tooltip@0.99.0":
+ version "0.99.0"
+ resolved "https://registry.yarnpkg.com/@nivo/tooltip/-/tooltip-0.99.0.tgz#63a1bc3b428cb2a07a7f763ad8547e39dd4bcf13"
+ integrity sha512-weoEGR3xAetV4k2P6k96cdamGzKQ5F2Pq+uyDaHr1P3HYArM879Pl+x+TkU0aWjP6wgUZPx/GOBiV1Hb1JxIqg==
+ dependencies:
+ "@nivo/core" "0.99.0"
+ "@nivo/theming" "0.99.0"
+ "@react-spring/web" "9.4.5 || ^9.7.2 || ^10.0"
+
"@noble/ciphers@1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-1.2.1.tgz#3812b72c057a28b44ff0ad4aff5ca846e5b9cdc9"
@@ -2273,6 +2363,51 @@
react-remove-scroll "2.6.2"
ua-parser-js "^1.0.37"
+"@react-spring/animated@~10.0.3":
+ version "10.0.3"
+ resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-10.0.3.tgz#b42f7041a51d38f395e9ba5fb53ca68c34cd324f"
+ integrity sha512-7MrxADV3vaUADn2V9iYhaIL6iOWRx9nCJjYrsk2AHD2kwPr6fg7Pt0v+deX5RnCDmCKNnD6W5fasiyM8D+wzJQ==
+ dependencies:
+ "@react-spring/shared" "~10.0.3"
+ "@react-spring/types" "~10.0.3"
+
+"@react-spring/core@~10.0.3":
+ version "10.0.3"
+ resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-10.0.3.tgz#3b4f3991f5902ce46770c2c1ef05c8e53c3a0f73"
+ integrity sha512-D4DwNO68oohDf/0HG2G0Uragzb9IA1oXblxrd6MZAcBcUQG2EHUWXewjdECMPLNmQvlYVyyBRH6gPxXM5DX7DQ==
+ dependencies:
+ "@react-spring/animated" "~10.0.3"
+ "@react-spring/shared" "~10.0.3"
+ "@react-spring/types" "~10.0.3"
+
+"@react-spring/rafz@~10.0.3":
+ version "10.0.3"
+ resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-10.0.3.tgz#9b328c3992b23d6317452998670636d6b783f2c4"
+ integrity sha512-Ri2/xqt8OnQ2iFKkxKMSF4Nqv0LSWnxXT4jXFzBDsHgeeH/cHxTLupAWUwmV9hAGgmEhBmh5aONtj3J6R/18wg==
+
+"@react-spring/shared@~10.0.3":
+ version "10.0.3"
+ resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-10.0.3.tgz#654d03c74d3277bae1a565aff981979536be6002"
+ integrity sha512-geCal66nrkaQzUVhPkGomylo+Jpd5VPK8tPMEDevQEfNSWAQP15swHm+MCRG4wVQrQlTi9lOzKzpRoTL3CA84Q==
+ dependencies:
+ "@react-spring/rafz" "~10.0.3"
+ "@react-spring/types" "~10.0.3"
+
+"@react-spring/types@~10.0.3":
+ version "10.0.3"
+ resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-10.0.3.tgz#0c2d7a7e783a6f652bcd24cac80ed569bc2ad8d9"
+ integrity sha512-H5Ixkd2OuSIgHtxuHLTt7aJYfhMXKXT/rK32HPD/kSrOB6q6ooeiWAXkBy7L8F3ZxdkBb9ini9zP9UwnEFzWgQ==
+
+"@react-spring/web@9.4.5 || ^9.7.2 || ^10.0":
+ version "10.0.3"
+ resolved "https://registry.yarnpkg.com/@react-spring/web/-/web-10.0.3.tgz#ae3a9ea2362b1d70d2ec36a1e2747c6cee2540a9"
+ integrity sha512-ndU+kWY81rHsT7gTFtCJ6mrVhaJ6grFmgTnENipzmKqot4HGf5smPNK+cZZJqoGeDsj9ZsiWPW4geT/NyD484A==
+ dependencies:
+ "@react-spring/animated" "~10.0.3"
+ "@react-spring/core" "~10.0.3"
+ "@react-spring/shared" "~10.0.3"
+ "@react-spring/types" "~10.0.3"
+
"@reown/appkit-common@1.7.8":
version "1.7.8"
resolved "https://registry.yarnpkg.com/@reown/appkit-common/-/appkit-common-1.7.8.tgz#6fc29db977b7325e8170b1fd08176fe15ea0b39c"
@@ -2633,6 +2768,59 @@
resolved "https://registry.npmjs.org/@types/autosuggest-highlight/-/autosuggest-highlight-3.2.3.tgz"
integrity sha512-8Mb21KWtpn6PvRQXjsKhrXIcxbSloGqNH50RntwGeJsGPW4xvNhfml+3kKulaKpO/7pgZfOmzsJz7VbepArlGQ==
+"@types/d3-color@^3.0.0":
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2"
+ integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==
+
+"@types/d3-path@*":
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.1.tgz#f632b380c3aca1dba8e34aa049bcd6a4af23df8a"
+ integrity sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==
+
+"@types/d3-path@^1":
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.11.tgz#45420fee2d93387083b34eae4fe6d996edf482bc"
+ integrity sha512-4pQMp8ldf7UaB/gR8Fvvy69psNHkTpD/pVw3vmEi8iZAB9EPMBruB1JvHO4BIq9QkUUd2lV1F5YXpMNj7JPBpw==
+
+"@types/d3-sankey@^0.11.2":
+ version "0.11.2"
+ resolved "https://registry.yarnpkg.com/@types/d3-sankey/-/d3-sankey-0.11.2.tgz#803214b11dc0a17db5d782fe9055cd92b06a5d75"
+ integrity sha512-U6SrTWUERSlOhnpSrgvMX64WblX1AxX6nEjI2t3mLK2USpQrnbwYYK+AS9SwiE7wgYmOsSSKoSdr8aoKBH0HgQ==
+ dependencies:
+ "@types/d3-shape" "^1"
+
+"@types/d3-scale-chromatic@^3.0.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#dc6d4f9a98376f18ea50bad6c39537f1b5463c39"
+ integrity sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==
+
+"@types/d3-scale@^4.0.8":
+ version "4.0.9"
+ resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.9.tgz#57a2f707242e6fe1de81ad7bfcccaaf606179afb"
+ integrity sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==
+ dependencies:
+ "@types/d3-time" "*"
+
+"@types/d3-shape@^1":
+ version "1.3.12"
+ resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.3.12.tgz#8f2f9f7a12e631ce6700d6d55b84795ce2c8b259"
+ integrity sha512-8oMzcd4+poSLGgV0R1Q1rOlx/xdmozS4Xab7np0eamFFUYq71AU9pOCJEFnkXW2aI/oXdVYJzw6pssbSut7Z9Q==
+ dependencies:
+ "@types/d3-path" "^1"
+
+"@types/d3-shape@^3.1.6":
+ version "3.1.8"
+ resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.8.tgz#d1516cc508753be06852cd06758e3bb54a22b0e3"
+ integrity sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==
+ dependencies:
+ "@types/d3-path" "*"
+
+"@types/d3-time@*":
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.4.tgz#8472feecd639691450dd8000eb33edd444e1323f"
+ integrity sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==
+
"@types/debug@^4.1.7":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917"
@@ -3998,6 +4186,121 @@ cuer@0.0.2:
dependencies:
qr "~0"
+"d3-array@1 - 2", d3-array@2:
+ version "2.12.1"
+ resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81"
+ integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==
+ dependencies:
+ internmap "^1.0.0"
+
+"d3-array@2 - 3", "d3-array@2.10.0 - 3":
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5"
+ integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
+ dependencies:
+ internmap "1 - 2"
+
+"d3-color@1 - 3", d3-color@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
+ integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
+
+"d3-format@1 - 3":
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.2.tgz#01fdb46b58beb1f55b10b42ad70b6e344d5eb2ae"
+ integrity sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==
+
+d3-format@^1.4.4:
+ version "1.4.5"
+ resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4"
+ integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==
+
+"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
+ integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
+ dependencies:
+ d3-color "1 - 3"
+
+d3-path@1:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf"
+ integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
+
+d3-path@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526"
+ integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
+
+d3-sankey@^0.12.3:
+ version "0.12.3"
+ resolved "https://registry.yarnpkg.com/d3-sankey/-/d3-sankey-0.12.3.tgz#b3c268627bd72e5d80336e8de6acbfec9d15d01d"
+ integrity sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==
+ dependencies:
+ d3-array "1 - 2"
+ d3-shape "^1.2.0"
+
+d3-scale-chromatic@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#34c39da298b23c20e02f1a4b239bd0f22e7f1314"
+ integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==
+ dependencies:
+ d3-color "1 - 3"
+ d3-interpolate "1 - 3"
+
+d3-scale@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
+ integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
+ dependencies:
+ d3-array "2.10.0 - 3"
+ d3-format "1 - 3"
+ d3-interpolate "1.2.0 - 3"
+ d3-time "2.1.1 - 3"
+ d3-time-format "2 - 4"
+
+d3-shape@^1.2.0:
+ version "1.3.7"
+ resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7"
+ integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==
+ dependencies:
+ d3-path "1"
+
+d3-shape@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5"
+ integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==
+ dependencies:
+ d3-path "^3.1.0"
+
+"d3-time-format@2 - 4":
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
+ integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
+ dependencies:
+ d3-time "1 - 3"
+
+d3-time-format@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-3.0.0.tgz#df8056c83659e01f20ac5da5fdeae7c08d5f1bb6"
+ integrity sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==
+ dependencies:
+ d3-time "1 - 2"
+
+"d3-time@1 - 2":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.1.1.tgz#e9d8a8a88691f4548e68ca085e5ff956724a6682"
+ integrity sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==
+ dependencies:
+ d3-array "2"
+
+"d3-time@1 - 3", "d3-time@2.1.1 - 3":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7"
+ integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
+ dependencies:
+ d3-array "2 - 3"
+
damerau-levenshtein@^1.0.8:
version "1.0.8"
resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz"
@@ -5342,6 +5645,16 @@ internal-slot@^1.0.4, internal-slot@^1.0.7:
hasown "^2.0.0"
side-channel "^1.0.4"
+"internmap@1 - 2":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
+ integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
+
+internmap@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95"
+ integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==
+
iron-webcrypto@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz#aa60ff2aa10550630f4c0b11fd2442becdb35a6f"
@@ -6689,6 +7002,11 @@ react-transition-group@^4.4.5:
loose-envify "^1.4.0"
prop-types "^15.6.2"
+react-virtualized-auto-sizer@^1.0.26:
+ version "1.0.26"
+ resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.26.tgz#e9470ef6a778dc4f1d5fd76305fa2d8b610c357a"
+ integrity sha512-CblNyiNVw2o+hsa5/49NH2ogGxZ+t+3aweRvNSq7TVjDIlwk7ir4lencEg5HxHeSzwNarSkNkiu0qJSOXtxm5A==
+
react@^18.3.1:
version "18.3.1"
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"