From aa5579d8553e94f849204b2c83701e33a0badc5f Mon Sep 17 00:00:00 2001 From: Kaushik Narayan R Date: Tue, 31 Dec 2024 00:22:00 -0700 Subject: [PATCH] graph arranging stuff --- src/App.js | 37 ++++---- src/components/Button/Button.module.css | 9 ++ src/components/Button/index.jsx | 16 +++- src/pages/Graph/index.jsx | 118 +++++++++++++++--------- 4 files changed, 116 insertions(+), 64 deletions(-) diff --git a/src/App.js b/src/App.js index 1e423a1..9b9eb15 100644 --- a/src/App.js +++ b/src/App.js @@ -18,6 +18,7 @@ import Navbar from './components/Navbar'; import AllRoutes from './routes/AllRoutes'; import { showErrorToastNotification, showInfoToastNotification, showWarnToastNotification } from './components/ToastNotification'; import { apiAuthCheck, apiAuthRefresh } from './api/auth'; +import { ReactFlowProvider } from '@xyflow/react'; // Contexts export const WidthContext = createContext(); @@ -106,23 +107,25 @@ function App() { -
- - - -
- -
-
- -
+ +
+ + + +
+ +
+
+ +
+
diff --git a/src/components/Button/Button.module.css b/src/components/Button/Button.module.css index e69de29..31bbde0 100644 --- a/src/components/Button/Button.module.css +++ b/src/components/Button/Button.module.css @@ -0,0 +1,9 @@ +.btn_wrapper { + padding: var(--mb-2); + width: 100%; + cursor: pointer; + text-decoration: none; + color: var(--text); + box-shadow: 8px 8px var(--bg); + background-color: var(--bgLinkInactive); +} diff --git a/src/components/Button/index.jsx b/src/components/Button/index.jsx index 515b076..00bb2a4 100644 --- a/src/components/Button/index.jsx +++ b/src/components/Button/index.jsx @@ -1,11 +1,17 @@ -import React from 'react' +import React from 'react'; import styles from "./Button.module.css"; -const Button = ({ child }) => { +function Button({ children, onClickMethod }) { + const clickHandler = (e) => { + e.preventDefault(); + onClickMethod(); + } return ( - <> - {child} - + ) } diff --git a/src/pages/Graph/index.jsx b/src/pages/Graph/index.jsx index 88b3627..f6a274c 100644 --- a/src/pages/Graph/index.jsx +++ b/src/pages/Graph/index.jsx @@ -6,18 +6,20 @@ import { useNodesState, useEdgesState, addEdge, - Panel, + useReactFlow, + MarkerType, } from '@xyflow/react'; import Dagre from '@dagrejs/dagre'; import '@xyflow/react/dist/style.css'; import styles from './Graph.module.css'; -import { showErrorToastNotification, showInfoToastNotification, showSuccessToastNotification } from '../../components/ToastNotification'; +import { showErrorToastNotification, showInfoToastNotification } from '../../components/ToastNotification'; import { apiFetchGraph } from '../../api/operations'; import { RefreshAuthContext } from "../../App"; +import Button from '../../components/Button'; const initialNodes = []; // const initialNodes = [ @@ -32,68 +34,84 @@ const initialEdges = []; const nodeOffsets = { connected: { - origin: { - x: 1000, - y: 0 - }, - scaling: { - x: 270, - y: 90 - } - }, - unconnected: { origin: { x: 0, y: 0 }, scaling: { - x: 180, - y: 60 + x: 240, + y: 80 + } + }, + unconnected: { + origin: { + x: 800, + y: 0 + }, + scaling: { + x: 160, + y: 40 } } } +/** @type {import('@xyflow/react').DefaultEdgeOptions} */ const edgeOptions = { animated: true, style: { - stroke: 'white', + stroke: "white", + strokeWidth: 2, }, + markerEnd: { + type: MarkerType.ArrowClosed, + color: "white", + width: 40, + height: 40, + } }; const proOptions = { hideAttribution: true }; const Graph = () => { const refreshAuth = useContext(RefreshAuthContext); + const flowInstance = useReactFlow(); const [playlistNodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [linkEdges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + const onFlowInit = (instance) => { + console.debug("flow loaded"); + } + const onConnect = useCallback((params) => { setEdges((eds) => addEdge(params, eds)); + console.debug("new connection"); + console.debug(params); }, [setEdges]); const getLayoutedElements = (nodes, edges, options = { direction: "TB" }) => { const g = new Dagre.graphlib.Graph() g.setDefaultEdgeLabel(() => ({})); - g.setGraph({ rankdir: options.direction }); + g.setGraph({ rankdir: options.direction, nodesep: 200, edgesep: 200, ranksep: 200 }); edges.forEach((edge) => g.setEdge(edge.source, edge.target)); - // const connectedNodes = new Set(edges.flatMap(edge => [edge.source, edge.target])); - // const unconnectedNodes = nodes.filter(node => !connectedNodes.has(node.id)); + const connectedNodesID = new Set(edges.flatMap(edge => [edge.source, edge.target])); + const connectedNodes = nodes.filter(node => connectedNodesID.has(node.id)); + const unconnectedNodes = nodes.filter(node => !connectedNodesID.has(node.id)); + nodes.forEach((node) => { - // if (connectedNodes.has(node.id)) { g.setNode(node.id, { ...node, width: node.measured?.width ?? 0, height: node.measured?.height ?? 0, }) - // } }); Dagre.layout(g); - return { - nodes: nodes.map((node) => { + let finalLayout = { edges }; + finalLayout.nodes = [ + ...connectedNodes.map((node) => { const position = g.node(node.id); // We are shifting the dagre node position (anchor=center center) to the top left // so it matches the React Flow node anchor point (top left). @@ -102,10 +120,31 @@ const Graph = () => { return { ...node, position: { x, y } }; }), - edges, - }; + ...unconnectedNodes.map((node, idx) => { + const position = { + x: nodeOffsets.unconnected.origin.x + Math.floor(idx / 20) * nodeOffsets.unconnected.scaling.x, + y: nodeOffsets.unconnected.origin.y + Math.floor(idx % 20) * nodeOffsets.unconnected.scaling.y, + }; + const x = position.x - (node.measured?.width ?? 0) / 2; + const y = position.y - (node.measured?.height ?? 0) / 2; + + return { ...node, position: { x, y } }; + }) + ]; + + console.debug("layout generated"); + return finalLayout; }; + const arrangeLayout = (direction) => { + const layouted = getLayoutedElements(playlistNodes, linkEdges, { direction }); + + setNodes([...layouted.nodes]); + setEdges([...layouted.edges]); + + setTimeout(flowInstance.fitView); + console.debug("layout applied"); + } useEffect(() => { const fetchGraph = async () => { @@ -120,13 +159,13 @@ const Graph = () => { return { id: `${pl.playlistID}`, position: { - x: nodeOffsets.unconnected.origin.x + Math.floor(idx / 5) * nodeOffsets.unconnected.scaling.x, - y: nodeOffsets.unconnected.origin.y + Math.floor(idx % 5) * nodeOffsets.unconnected.scaling.y, + x: nodeOffsets.unconnected.origin.x + Math.floor(idx / 15) * nodeOffsets.unconnected.scaling.x, + y: nodeOffsets.unconnected.origin.y + Math.floor(idx % 15) * nodeOffsets.unconnected.scaling.y, }, data: { label: pl.playlistName, - meta: { - name: pl.playlistName + metadata: { + pl } } } @@ -152,16 +191,8 @@ const Graph = () => { showErrorToastNotification(resp.data.message); return; } - fetchGraph(); - }, []); - - const arrangeLayout = (direction) => { - const layouted = getLayoutedElements(playlistNodes, linkEdges, { direction }); - - setNodes([...layouted.nodes]); - setEdges([...layouted.edges]); - } + }, [refreshAuth, setEdges, setNodes]); return (
@@ -169,21 +200,24 @@ const Graph = () => { nodes={playlistNodes} edges={linkEdges} defaultEdgeOptions={edgeOptions} + connectionLineType="smoothstep" fitView proOptions={proOptions} + colorMode={"light"} + onInit={onFlowInit} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onConnect={onConnect} > - - - - + {/* */} + {/* */} + {/* */} + {/* */}
- test +
)