Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Draft] new graph view #441

Draft
wants to merge 2 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@ceramicnetwork/http-client": "^2.20.0",
"@composedb/client": "0.4.0",
"@composedb/types": "0.4.0",
"@dagrejs/dagre": "^1.1.4",
"@didtools/pkh-ethereum": "^0.1.0",
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
Expand All @@ -26,6 +27,7 @@
"@mui/x-date-pickers": "^5.0.0-alpha.5",
"@react-oauth/google": "^0.12.1",
"@types/styled-components": "^5.1.34",
"@xyflow/react": "^12.3.6",
"cytoscape": "^3.22.1",
"cytoscape-node-html-label": "^1.2.2",
"date-fns": "^2.28.0",
Expand Down
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Login from './containers/Login'
import Register from './containers/Register'
import Form from './containers/Form'
import Explore from './containers/Explore'
import GraphV2 from './containers/GraphV2'
import FeedClaim from './containers/feedOfClaim/index'
import Rate from './components/Rate'
import Validate from './components/Validate'
Expand Down Expand Up @@ -139,6 +140,7 @@ const App = () => {
<Route path='report/:claimId' element={<ClaimReport />} />
<Route path='claims/:claimId' element={<ClaimDetails {...commonProps} />} />
<Route path='explore/:nodeId' element={<Explore {...commonProps} />} />
<Route path='explore/v2/:nodeId' element={<GraphV2 {...commonProps} />} />
<Route path='claim' element={<Form {...commonProps} />} />
<Route path='register' element={<Register {...commonProps} />} />
<Route path='login' element={<Login {...commonProps} />} />
Expand Down
2 changes: 1 addition & 1 deletion src/components/ClaimReport/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ const DonationReport: React.FC = () => {
</Button>
<Button
component={Link}
to={`/explore/${claimId}`}
to={`/explore/v2/${claimId}`}
startIcon={<ShareOutlinedIcon />}
variant='text'
sx={{
Expand Down
24 changes: 24 additions & 0 deletions src/containers/GraphV2/Edge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { BaseEdge, EdgeProps, getStraightPath } from '@xyflow/react'

const Edge = ({ sourceX, sourceY, targetX, targetY, style = {}, markerEnd }: EdgeProps) => {
const [edgePath] = getStraightPath({
sourceX,
sourceY,
targetX,
targetY
})

return (
<BaseEdge
path={edgePath}
style={{
...style,
strokeWidth: 2,
stroke: '#888'
}}
markerEnd={markerEnd}
/>
)
}

export default Edge
65 changes: 65 additions & 0 deletions src/containers/GraphV2/Node.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Handle, Position } from '@xyflow/react'
import { Box, Typography, Avatar, Tooltip } from '@mui/material'
import { useTheme } from '@mui/material/styles'

const Node = ({ data }: any) => {
const theme = useTheme()

return (
<Box
sx={{
padding: '15px',
borderRadius: '12px',
background: theme.palette.menuBackground,
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.1)',
minWidth: '180px',
maxWidth: '250px',
}}
>
<Handle type='target' position={Position.Top} />

<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 1 }}>
{data.image && (
<Avatar
src={data.image}
alt={data.label}
sx={{ width: 40, height: 40, border: `2px solid ${theme.palette.primary.main}` }}
/>
)}
<Tooltip title={data.raw?.descrip || ''}>
<Typography
variant='h6'
sx={{
color: theme.palette.texts,
fontSize: '0.9rem',
fontWeight: 600,
wordWrap: 'break-word',
lineHeight: 1.2
}}
>
{data.label}
</Typography>
</Tooltip>
</Box>

{data.raw?.entType && (
<Typography
variant='caption'
sx={{
color: theme.palette.maintext,
backgroundColor: theme.palette.action.hover,
padding: '4px 8px',
borderRadius: '12px',
fontSize: '0.7rem'
}}
>
{data.raw.entType}
</Typography>
)}

<Handle type='source' position={Position.Bottom} />
</Box>
)
}

export default Node
129 changes: 129 additions & 0 deletions src/containers/GraphV2/hooks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { useState, useEffect } from 'react'
import dagre from '@dagrejs/dagre'
import axios from '../../axiosInstance'
import { parseMultipleNodes } from '../Explore/graph.utils'

const dagreGraph = new dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
const nodeWidth = 172;
const nodeHeight = 36;

export const useClaimGraph = (claimId: string) => {
const [initialNodes, setInitialNodes] = useState(null)
const [initialEdges, setInitialEdges] = useState(null)

const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)

useEffect(() => {
const fetchGraphData = async () => {
setIsLoading(true)
try {
const { data } = await axios.get(`/api/claim_graph/${claimId}`)
const { nodes, edges } = transformData(data?.nodes || [])
setInitialNodes(nodes as any)
setInitialEdges(edges as any)
} catch (err) {
setError('Failed to fetch graph data')
} finally {
setIsLoading(false)
}
}

fetchGraphData()
}, [claimId])

return { initialEdges, initialNodes, isLoading, error }
}

const transformNodes = (nodes: any[], centerX = 400, centerY = 300, radius = 200) => {
const uniqueNodes = Array.from(new Map(nodes.map(node => [node.data.id, node])).values())

return uniqueNodes.map((node, index) => {
const angle = (2 * Math.PI * index) / uniqueNodes.length

return {
id: node.data.id,
type: 'custom',
position: {
x: centerX + radius * Math.cos(angle),
y: centerY + radius * Math.sin(angle)
},
data: {
label: node.data.label,
image: node.data.image,
raw: node.data.raw
},
style: {
color: '#1a192b',
width: '200px',
whiteSpace: 'normal',
wordWrap: 'break-word',
overflowWrap: 'break-word',
textAlign: 'center'
}
}
})
}

const transformEdges = (edges: any[]) => {
const uniqueEdges = Array.from(new Map(edges.map(edge => [edge.data.id, edge])).values())

return uniqueEdges.map(edge => ({
id: edge.data.id.toString(),
source: edge.data.source.toString(),
target: edge.data.target.toString(),
type: 'straight',
label: edge.data.relation,
style: {
stroke: '#888',
strokeWidth: 2
},
data: {
relation: edge.data.relation,
raw: edge.data.raw
}
}))
}

const transformData = (graphData: any[]) => {
const { nodes, edges } = parseMultipleNodes(graphData)
const initialNodes = transformNodes(nodes)
const initialEdges = transformEdges(edges)

const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(initialNodes, initialEdges)

return { nodes: layoutedNodes, edges: layoutedEdges }
}

const getLayoutedElements = (nodes: any, edges: any, direction = 'TB') => {
const isHorizontal = direction === 'LR'
dagreGraph.setGraph({ rankdir: direction })

nodes.forEach((node: any) => {
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight })
})

edges.forEach((edge: any) => {
dagreGraph.setEdge(edge.source, edge.target)
})

dagre.layout(dagreGraph)

const newNodes = nodes.map((node: any) => {
const nodeWithPosition = dagreGraph.node(node.id)
const newNode = {
...node,
targetPosition: isHorizontal ? 'left' : 'top',
sourcePosition: isHorizontal ? 'right' : 'bottom',

position: {
x: nodeWithPosition.x - nodeWidth / 2,
y: nodeWithPosition.y - nodeHeight / 2
}
}

return newNode
})

return { nodes: newNodes, edges }
}
43 changes: 43 additions & 0 deletions src/containers/GraphV2/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import '@xyflow/react/dist/style.css'
import { useEffect } from 'react'
import { useParams } from 'react-router-dom'
import { ReactFlow, useNodesState, useEdgesState, Background } from '@xyflow/react'
import { Box } from '@mui/material'
import IHomeProps from '../Form/types'
import { useClaimGraph } from './hooks'
import styles from './styles'
import Node from './Node'
import Edge from './Edge'

const GraphV2 = (homeProps: IHomeProps) => {
const { nodeId } = useParams<{ nodeId: string }>()
const { initialNodes, initialEdges, isLoading, error }: any = useClaimGraph(nodeId as string)

const [nodes, _setNodes, onNodesChange] = useNodesState([])
const [edges, _setEdges, onEdgesChange] = useEdgesState([])

return (
<>
{!isLoading && (
<ReactFlow
nodes={initialNodes}
edges={initialEdges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
defaultViewport={{ x: 0, y: 0, zoom: 1 }}
fitView
fitViewOptions={{ padding: 0.8 }}
// nodeTypes={{
// custom: Node
// }}
// edgeTypes={{ custom: Edge }}
nodesConnectable={false}
>
<Background />
</ReactFlow>
)}
</>
)
}

export default GraphV2
22 changes: 22 additions & 0 deletions src/containers/GraphV2/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { padding } from '@mui/system'
import { Background } from '@xyflow/react'

const styles = {
container: {
padding: '10rem',
'@media (min-width: 600px)': {
padding: '0'
}
},
graph: {
width: '100vw',
height: '86vh',
zIndex: 1,
alignItems: 'center',
justifyContent: 'center',
// marginX: '10rem',
background: 'white'
}
}

export default styles
26 changes: 12 additions & 14 deletions src/containers/feedOfClaim/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ const FeedClaim: React.FC<IHomeProps> = () => {

const handleSchema = async (claim: ImportedClaim) => {
navigate({
pathname: `/explore/${claim.claim_id}`
pathname: `/explore/v2/${claim.claim_id}`
})
}

Expand Down Expand Up @@ -321,19 +321,17 @@ const FeedClaim: React.FC<IHomeProps> = () => {
color: theme.palette.texts
}}
>

<span
dangerouslySetInnerHTML={{
__html: searchTerm
? claim.statement.replace(
new RegExp(`(${searchTerm})`, 'gi'),
(match: any) =>
`<span style="background-color:${theme.palette.searchBarBackground};">${match}</span>`
)
: claim.statement
}}
/>

<span
dangerouslySetInnerHTML={{
__html: searchTerm
? claim.statement.replace(
new RegExp(`(${searchTerm})`, 'gi'),
(match: any) =>
`<span style="background-color:${theme.palette.searchBarBackground};">${match}</span>`
)
: claim.statement
}}
/>
</Typography>
)}
</CardContent>
Expand Down
4 changes: 2 additions & 2 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
// <React.StrictMode>
<BrowserRouter>
<GoogleOAuthProvider clientId={GOOGLE_CLIENT_ID}>
<App />
</GoogleOAuthProvider>
</BrowserRouter>
</React.StrictMode>
// </React.StrictMode>
)
Loading
Loading