From bac2ccb70e02e097ef464ced5841965621ab07d7 Mon Sep 17 00:00:00 2001 From: Steven Roussey Date: Tue, 9 Apr 2024 15:54:30 -0700 Subject: [PATCH 1/3] wip --- packages/web/src/RunGraphFlow.css | 13 ++++--- packages/web/src/RunGraphFlow.tsx | 39 +++++++++++++++----- packages/web/src/changes.ts | 60 +++++++++++++++++++++++++++++++ packages/web/src/layout.ts | 7 +++- 4 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 packages/web/src/changes.ts diff --git a/packages/web/src/RunGraphFlow.css b/packages/web/src/RunGraphFlow.css index 769b946..54cabcf 100644 --- a/packages/web/src/RunGraphFlow.css +++ b/packages/web/src/RunGraphFlow.css @@ -66,12 +66,17 @@ width: 250px; } -.react-flow__node-compound .wrapper { - width: 300px; +.react-flow__node-compound { + min-height: 150px; } -.react-flow__node-compound { - min-height: 450px; +.react-flow__node-compound .outside { + display: flex; + flex-direction: column; +} + +.react-flow__node-compound .wrapper { + width: 300px; } .gradient:before { diff --git a/packages/web/src/RunGraphFlow.tsx b/packages/web/src/RunGraphFlow.tsx index 9e808a6..711b1db 100644 --- a/packages/web/src/RunGraphFlow.tsx +++ b/packages/web/src/RunGraphFlow.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, SetStateAction, useEffect, useRef } from "react"; +import React, { Dispatch, SetStateAction, useCallback, useEffect, useRef } from "react"; import { ReactFlow, Controls, @@ -22,6 +22,7 @@ import { registerMediaPipeTfJsLocalInMemory, } from "ellmers-core/browser"; import { GraphPipelineCenteredLayout, GraphPipelineLayout, computeLayout } from "./layout"; +import { NodeChange } from "./changes"; registerHuggingfaceLocalTasksInMemory(); registerMediaPipeTfJsLocalInMemory(); @@ -94,7 +95,7 @@ function convertGraphToNodes(graph: TaskGraph): Node[] { }); return nodes; } - +// TODO: unlisten to tasks function listenToTask(task: Task, setNodes: Dispatch[]>>) { task.on("progress", (progress, progressText) => { setNodes((nds) => @@ -167,7 +168,7 @@ function listenToTask(task: Task, setNodes: Dispatch { // console.log("Node regenerated", task.config.id); setNodes((nodes: Node[]) => { @@ -181,7 +182,7 @@ function listenToTask(task: Task, setNodes: Dispatch ); - listenToGraphNodes(task.subGraph, setNodes); + listenToGraphTasks(task.subGraph, setNodes); let returnNodes = nodes.filter((n) => n.parentId !== task.config.id); // remove old children returnNodes = [...returnNodes, ...children]; // add new children returnNodes = sortNodes(returnNodes); // sort all nodes (parent, children, parent, children, ...) @@ -191,7 +192,7 @@ function listenToTask(task: Task, setNodes: Dispatch[]>> ) { @@ -218,15 +219,33 @@ const defaultEdgeOptions = { export const RunGraphFlow: React.FC<{ graph: TaskGraph; }> = ({ graph }) => { - const [nodes, setNodes, onNodesChange] = useNodesState>([]); + const [nodes, setNodes, onNodesChangeTheirs] = useNodesState>([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); const graphRef = useRef(null); + const onNodesChange = useCallback( + (changes: NodeChange>[]) => { + console.log("Nodes changed", changes); + onNodesChangeTheirs(changes); + // const computedNodes = computeLayout( + // nodes, + // edges, + // new GraphPipelineCenteredLayout(), + // new GraphPipelineLayout({ startTop: 100, startLeft: 20 }) + // ) as Node[]; + // const sortedNodes = sortNodes(computedNodes); + // console.log(nodes, sortedNodes); + }, + [onNodesChangeTheirs, nodes, edges] + ); + const initialized = useNodesInitialized() && !nodes.some((n) => !n.measured); const { fitView } = useReactFlow(); useEffect(() => { + const id: Timer | null = null; if (initialized) { + console.log("Nodes initialized", nodes); const computedNodes = computeLayout( nodes, edges, @@ -252,11 +271,13 @@ export const RunGraphFlow: React.FC<{ fitView(); }, 5); } - }, [initialized]); + }, [initialized, setNodes, setEdges, fitView]); useEffect(() => { if (graph !== graphRef.current) { + console.log("Graph changed", graph); graphRef.current = graph; + console.log("Graph changed", graph); const nodes = sortNodes(convertGraphToNodes(graph)); setNodes( nodes.map((n) => { @@ -277,9 +298,9 @@ export const RunGraphFlow: React.FC<{ }; }) ); - listenToGraphNodes(graph, setNodes); + listenToGraphTasks(graph, setNodes); } - }, [graph]); + }, [graph, setNodes, setEdges, graphRef.current]); // const onConnect = useCallback( // (params: any) => setEdges((els) => addEdge(params, els)), diff --git a/packages/web/src/changes.ts b/packages/web/src/changes.ts new file mode 100644 index 0000000..75adf44 --- /dev/null +++ b/packages/web/src/changes.ts @@ -0,0 +1,60 @@ +import type { XYPosition, Dimensions, Node, Edge } from "@xyflow/react"; +export type NodeDimensionChange = { + id: string; + type: "dimensions"; + dimensions?: Dimensions; + resizing?: boolean; +}; +export type NodePositionChange = { + id: string; + type: "position"; + position?: XYPosition; + positionAbsolute?: XYPosition; + dragging?: boolean; +}; +export type NodeSelectionChange = { + id: string; + type: "select"; + selected: boolean; +}; +export type NodeRemoveChange = { + id: string; + type: "remove"; +}; +export type NodeAddChange = { + item: NodeType; + type: "add"; +}; +export type NodeReplaceChange = { + id: string; + item: NodeType; + type: "replace"; +}; +/** + * Union type of all possible node changes. + * @public + */ +export type NodeChange = + | NodeDimensionChange + | NodePositionChange + | NodeSelectionChange + | NodeRemoveChange + | NodeAddChange + | NodeReplaceChange; +export type EdgeSelectionChange = NodeSelectionChange; +export type EdgeRemoveChange = NodeRemoveChange; +export type EdgeAddChange = { + item: EdgeType; + type: "add"; +}; +export type EdgeReplaceChange = { + id: string; + item: EdgeType; + type: "replace"; +}; +export type EdgeChange = + | EdgeSelectionChange + | EdgeRemoveChange + | EdgeAddChange + | EdgeReplaceChange; +//# sourceMappingURL=changes.d.ts.map diff --git a/packages/web/src/layout.ts b/packages/web/src/layout.ts index 60adcf2..32ca90b 100644 --- a/packages/web/src/layout.ts +++ b/packages/web/src/layout.ts @@ -21,7 +21,7 @@ export class GraphPipelineLayout implements LayoutOptions { protected layerHeight: number[] = []; protected layers: Map = new Map(); public nodeWidthMin: number = 190; - public nodeHeightMin: number = 150; + public nodeHeightMin: number = 50; public horizontalSpacing = 80; // Horizontal spacing between layers public verticalSpacing = 20; // Vertical spacing between nodes within a layer public startTop = 50; // Starting position of the top layer @@ -194,6 +194,11 @@ export function computeLayout( subFlowLayout ?? layout, node.id ); + const last = childNodes[childNodes.length - 1]; + const w = last.position.x + last.measured.width; + const h = last.position.y + last.measured.height; + node.height = layout.startTop * 2 + h + 500; + console.log("Children", childNodes, w, h, node.height, layout.startTop * 2 + h); returnNodes.push(...childNodes); } } From a046ef31a4b5e93c9178c8eb31ffe13eee84008c Mon Sep 17 00:00:00 2001 From: Steven Roussey Date: Thu, 11 Apr 2024 08:39:00 -0700 Subject: [PATCH 2/3] [web] chore: update xyflow --- bun.lockb | Bin 221348 -> 221348 bytes packages/web/package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index 1703648960d0e98bc14050e48c9352c5bb284fd7..693d0b665d43776078847346e179488f4bade164 100755 GIT binary patch delta 253 zcmVXbNzmc=v%6S% z_#vlrbHPHJvSz(Z2iWDZR?&&Ae|$b}qU_Bsi#u|^teVJk+urF);ZbC=5tj`b0T@8Q z?a>Unue9Mu9RgmG_RwhT`G?cB)diWgsS+GvhKDSwHoG3=nwqu})}I z3NrvZ1_*iP9HTvxfFu}`*i;Y!7L!pVCzDXJ39}hhw8TIp4R1lw4goY&>eUO$8WQ}=W%dtN|oD+e59p*Dk;4H^L$ zK#ZL9n_DEV7ks5X?VzcJQjC>*m!n`Yz&72VyuP%~s?utkN^Yj**nUa=ZU&5K=6)Ta zA4x;xXGF$4g0=`yC5L4i0k>ru0`gM Date: Thu, 11 Apr 2024 08:42:18 -0700 Subject: [PATCH 3/3] [web] feat: layout based on sublayout --- packages/web/src/RunGraphFlow.css | 3 +- packages/web/src/RunGraphFlow.tsx | 139 ++++++++++++++-------------- packages/web/src/changes.ts | 60 ------------ packages/web/src/layout.ts | 146 ++++++++++++++++-------------- 4 files changed, 154 insertions(+), 194 deletions(-) delete mode 100644 packages/web/src/changes.ts diff --git a/packages/web/src/RunGraphFlow.css b/packages/web/src/RunGraphFlow.css index 54cabcf..b3f124d 100644 --- a/packages/web/src/RunGraphFlow.css +++ b/packages/web/src/RunGraphFlow.css @@ -73,10 +73,11 @@ .react-flow__node-compound .outside { display: flex; flex-direction: column; + width: 100%; } .react-flow__node-compound .wrapper { - width: 300px; + width: 100%; } .gradient:before { diff --git a/packages/web/src/RunGraphFlow.tsx b/packages/web/src/RunGraphFlow.tsx index 711b1db..80e00df 100644 --- a/packages/web/src/RunGraphFlow.tsx +++ b/packages/web/src/RunGraphFlow.tsx @@ -8,10 +8,8 @@ import { useNodesInitialized, useReactFlow, Edge, + Position, } from "@xyflow/react"; - -import "@xyflow/react/dist/base.css"; -import "./RunGraphFlow.css"; import { TurboNodeData, SingleNode, CompoundNode } from "./TurboNode"; import TurboEdge from "./TurboEdge"; import { FiFileText, FiClipboard, FiDownload, FiUpload } from "react-icons/fi"; @@ -22,7 +20,9 @@ import { registerMediaPipeTfJsLocalInMemory, } from "ellmers-core/browser"; import { GraphPipelineCenteredLayout, GraphPipelineLayout, computeLayout } from "./layout"; -import { NodeChange } from "./changes"; + +import "@xyflow/react/dist/base.css"; +import "./RunGraphFlow.css"; registerHuggingfaceLocalTasksInMemory(); registerMediaPipeTfJsLocalInMemory(); @@ -66,27 +66,30 @@ function sortNodes(nodes: Node[]): Node[] { function convertGraphToNodes(graph: TaskGraph): Node[] { const tasks = graph.getNodes(); - const nodes = tasks.flatMap((node, index) => { + const nodes = tasks.flatMap((task, index) => { let n: Node[] = [ { - id: node.config.id as string, + id: task.config.id as string, position: { x: 0, y: 0 }, data: { - icon: categoryIcons[(node.constructor as any).category], - title: (node.constructor as any).type, - subline: node.config.name, + icon: categoryIcons[(task.constructor as any).category], + title: (task.constructor as any).type, + subline: task.config.name, }, - type: node.isCompound ? "compound" : "single", + type: task.isCompound ? "compound" : "single", + selectable: true, + connectable: false, + draggable: false, + sourcePosition: Position.Right, + targetPosition: Position.Left, }, ]; - if (node.isCompound) { - const subNodes = convertGraphToNodes(node.subGraph).map((n) => { + if (task.isCompound) { + const subNodes = convertGraphToNodes(task.subGraph).map((n) => { return { ...n, - parentId: node.config.id as string, + parentId: task.config.id as string, extent: "parent", - selectable: false, - connectable: false, } as Node; }); n = [...n, ...subNodes]; @@ -95,10 +98,46 @@ function convertGraphToNodes(graph: TaskGraph): Node[] { }); return nodes; } + +function doNodeLayout( + setNodes: Dispatch>, + setEdges: Dispatch> +) { + let edges = []; + setEdges((es) => { + edges = es.map((n) => { + return { + ...n, + style: { opacity: 1 }, + }; + }); + setNodes((nodes) => { + const computedNodes = computeLayout( + nodes, + edges, + new GraphPipelineCenteredLayout>(), + new GraphPipelineLayout>({ startTop: 100, startLeft: 20 }) + ) as Node[]; + const sortedNodes = sortNodes(computedNodes); + sortedNodes.map((n) => { + n.style = { opacity: 1 }; + return n; + }); + return sortedNodes; + }); + return edges; + }); +} + // TODO: unlisten to tasks -function listenToTask(task: Task, setNodes: Dispatch[]>>) { +function listenToTask( + task: Task, + setNodes: Dispatch[]>>, + edges: Edge[], + setEdges: Dispatch> +) { task.on("progress", (progress, progressText) => { - setNodes((nds) => + setNodes((nds) => { nds.map((nd) => { if (nd.id === task.config.id) { return { @@ -112,8 +151,10 @@ function listenToTask(task: Task, setNodes: Dispatch { setNodes((nds) => @@ -168,7 +209,7 @@ function listenToTask(task: Task, setNodes: Dispatch { // console.log("Node regenerated", task.config.id); setNodes((nodes: Node[]) => { @@ -182,7 +223,7 @@ function listenToTask(task: Task, setNodes: Dispatch ); - listenToGraphTasks(task.subGraph, setNodes); + listenToGraphTasks(task.subGraph, setNodes, edges, setEdges); let returnNodes = nodes.filter((n) => n.parentId !== task.config.id); // remove old children returnNodes = [...returnNodes, ...children]; // add new children returnNodes = sortNodes(returnNodes); // sort all nodes (parent, children, parent, children, ...) @@ -194,11 +235,13 @@ function listenToTask(task: Task, setNodes: Dispatch[]>> + setNodes: Dispatch[]>>, + edges: Edge[], + setEdges: Dispatch> ) { const nodes = graph.getNodes(); for (const node of nodes) { - listenToTask(node, setNodes); + listenToTask(node, setNodes, edges, setEdges); } } @@ -219,63 +262,25 @@ const defaultEdgeOptions = { export const RunGraphFlow: React.FC<{ graph: TaskGraph; }> = ({ graph }) => { - const [nodes, setNodes, onNodesChangeTheirs] = useNodesState>([]); + const [nodes, setNodes, onNodesChange] = useNodesState>([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); const graphRef = useRef(null); - const onNodesChange = useCallback( - (changes: NodeChange>[]) => { - console.log("Nodes changed", changes); - onNodesChangeTheirs(changes); - // const computedNodes = computeLayout( - // nodes, - // edges, - // new GraphPipelineCenteredLayout(), - // new GraphPipelineLayout({ startTop: 100, startLeft: 20 }) - // ) as Node[]; - // const sortedNodes = sortNodes(computedNodes); - // console.log(nodes, sortedNodes); - }, - [onNodesChangeTheirs, nodes, edges] - ); - - const initialized = useNodesInitialized() && !nodes.some((n) => !n.measured); + const shouldLayout = useNodesInitialized() && !nodes.some((n) => !n.measured); const { fitView } = useReactFlow(); useEffect(() => { const id: Timer | null = null; - if (initialized) { - console.log("Nodes initialized", nodes); - const computedNodes = computeLayout( - nodes, - edges, - new GraphPipelineCenteredLayout(), - new GraphPipelineLayout({ startTop: 100, startLeft: 20 }) - ) as Node[]; - const sortedNodes = sortNodes(computedNodes); - setNodes( - sortedNodes.map((n) => { - n.style = { opacity: 1 }; - return n; - }) - ); - setEdges( - edges.map((n) => { - return { - ...n, - style: { opacity: 1 }, - }; - }) - ); + if (shouldLayout) { + doNodeLayout(setNodes, setEdges); setTimeout(() => { fitView(); }, 5); } - }, [initialized, setNodes, setEdges, fitView]); + }, [shouldLayout, setNodes, setEdges, fitView]); useEffect(() => { if (graph !== graphRef.current) { - console.log("Graph changed", graph); graphRef.current = graph; console.log("Graph changed", graph); const nodes = sortNodes(convertGraphToNodes(graph)); @@ -298,7 +303,7 @@ export const RunGraphFlow: React.FC<{ }; }) ); - listenToGraphTasks(graph, setNodes); + listenToGraphTasks(graph, setNodes, edges, setEdges); } }, [graph, setNodes, setEdges, graphRef.current]); diff --git a/packages/web/src/changes.ts b/packages/web/src/changes.ts deleted file mode 100644 index 75adf44..0000000 --- a/packages/web/src/changes.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { XYPosition, Dimensions, Node, Edge } from "@xyflow/react"; -export type NodeDimensionChange = { - id: string; - type: "dimensions"; - dimensions?: Dimensions; - resizing?: boolean; -}; -export type NodePositionChange = { - id: string; - type: "position"; - position?: XYPosition; - positionAbsolute?: XYPosition; - dragging?: boolean; -}; -export type NodeSelectionChange = { - id: string; - type: "select"; - selected: boolean; -}; -export type NodeRemoveChange = { - id: string; - type: "remove"; -}; -export type NodeAddChange = { - item: NodeType; - type: "add"; -}; -export type NodeReplaceChange = { - id: string; - item: NodeType; - type: "replace"; -}; -/** - * Union type of all possible node changes. - * @public - */ -export type NodeChange = - | NodeDimensionChange - | NodePositionChange - | NodeSelectionChange - | NodeRemoveChange - | NodeAddChange - | NodeReplaceChange; -export type EdgeSelectionChange = NodeSelectionChange; -export type EdgeRemoveChange = NodeRemoveChange; -export type EdgeAddChange = { - item: EdgeType; - type: "add"; -}; -export type EdgeReplaceChange = { - id: string; - item: EdgeType; - type: "replace"; -}; -export type EdgeChange = - | EdgeSelectionChange - | EdgeRemoveChange - | EdgeAddChange - | EdgeReplaceChange; -//# sourceMappingURL=changes.d.ts.map diff --git a/packages/web/src/layout.ts b/packages/web/src/layout.ts index 32ca90b..35e1fbe 100644 --- a/packages/web/src/layout.ts +++ b/packages/web/src/layout.ts @@ -16,10 +16,10 @@ interface LayoutOptions { } export class GraphPipelineLayout implements LayoutOptions { - protected dag: DirectedAcyclicGraph; + protected dataflowDAG: DirectedAcyclicGraph; protected positions: Map = new Map(); protected layerHeight: number[] = []; - protected layers: Map = new Map(); + public layers: Map = new Map(); public nodeWidthMin: number = 190; public nodeHeightMin: number = 50; public horizontalSpacing = 80; // Horizontal spacing between layers @@ -32,20 +32,18 @@ export class GraphPipelineLayout implements LayoutOptions { } public setGraph(dag: DirectedAcyclicGraph) { - this.dag = dag; - this.positions = new Map(); + this.dataflowDAG = dag; this.layers = new Map(); this.layerHeight = []; } public layoutGraph() { - const sortedNodes = this.dag.topologicallySortedNodes(); + const sortedNodes = this.dataflowDAG.topologicallySortedNodes(); this.assignLayers(sortedNodes); this.positionNodes(); - // Optionally, you can include edge drawing logic here or handle it separately } - private assignLayers(sortedNodes: T[]) { + public assignLayers(sortedNodes: T[]) { this.layers = new Map(); const nodeToLayer = new Map(); @@ -56,7 +54,7 @@ export class GraphPipelineLayout implements LayoutOptions { let maxLayer = -1; // Get all incoming edges (dependencies) of the node - const incomingEdges = this.dag.inEdges(node.id).map(([from]) => from); + const incomingEdges = this.dataflowDAG.inEdges(node.id).map(([from]) => from); incomingEdges.forEach((from) => { // Find the layer of the dependency @@ -85,10 +83,7 @@ export class GraphPipelineLayout implements LayoutOptions { let nodeWidth = this.nodeWidthMin; let currentY = this.startTop; nodes.forEach((node) => { - this.positions.set(node.id, { - x: currentX, - y: currentY, - }); + node.position = { x: currentX, y: currentY }; const nodeHeight = this.getNodeHeight(node); @@ -104,16 +99,14 @@ export class GraphPipelineLayout implements LayoutOptions { }); } - protected getNodeHeight(node: T): number { - return Math.max(node.measured?.height, this.nodeHeightMin); - } - - protected getNodeWidth(node: T): number { - return Math.max(node.measured?.width, this.nodeWidthMin); + public getNodeHeight(node: T): number { + const baseHeight = node.height || node.measured?.height || this.nodeHeightMin; + return Math.max(baseHeight, this.nodeHeightMin); } - public getNodePosition(nodeIdentity: string): PositionXY | undefined { - return this.positions.get(nodeIdentity); + public getNodeWidth(node: T): number { + const baseWidth = node.width || node.measured?.width || this.nodeWidthMin; + return Math.max(baseWidth, this.nodeWidthMin); } } @@ -126,10 +119,10 @@ export class GraphPipelineCenteredLayout extends GraphPipelineLa nodes.forEach((node) => { const nodeHeight = this.getNodeHeight(node); - this.positions.set(node.id, { - x: this.positions.get(node.id)!.x, + node.position = { + x: node.position.x, y: currentY, - }); + }; currentY += nodeHeight + this.verticalSpacing; }); @@ -141,66 +134,87 @@ export class GraphPipelineCenteredLayout extends GraphPipelineLa return Math.max(...this.layerHeight); } } +const groupBy = (items: T[], key: keyof T) => + items.reduce( + (result: Record, item: T) => ({ + ...result, + [String(item[key])]: [...(result[String(item[key])] || []), item], + }), + {} + ); export function computeLayout( nodes: Node[], edges: Edge[], layout: GraphPipelineLayout, - subFlowLayout?: GraphPipelineLayout, - parentId?: string + subFlowLayout?: GraphPipelineLayout ): Node[] { - const g = new DirectedAcyclicGraph((node) => node.id); - + // before we bother with anything, ignore hidden nodes nodes = nodes.filter((node) => !node.hidden); - const topLevelNodes = nodes.filter( - (node) => node.parentId === undefined || node.parentId === parentId - ); + const subgraphSize = new Map(); + const subgraphDAG = new DirectedAcyclicGraph((node) => node.id); - topLevelNodes.forEach((node) => { - g.insert(node); + nodes.forEach((node) => { + subgraphDAG.insert(node); }); - edges.forEach((edge) => { - try { - g.addEdge(edge.source, edge.target); - } catch (e) { - // might be an edge to a hidden node + nodes.forEach((node) => { + if (node.parentId) { + subgraphDAG.addEdge(node.parentId, node.id); } }); - layout.setGraph(g); - layout.layoutGraph(); - - const returnNodes: Node[] = topLevelNodes.map((node) => { - const nodePosition = layout.getNodePosition(node.id)!; + const subgraphDepthLayout = new GraphPipelineLayout(); + subgraphDepthLayout.setGraph(subgraphDAG); + const sortedNodes = subgraphDAG.topologicallySortedNodes(); + subgraphDepthLayout.assignLayers(sortedNodes); + const allgraphs = Array.from(subgraphDepthLayout.layers.values()); + + const returnNodes: Node[] = []; + for (let i = allgraphs.length - 1; i >= 0; i--) { + const graphs = groupBy(allgraphs[i], "parentId"); + for (const parentId in graphs) { + // This loop goes from innermost graph to outermost graph + // and lays out the nodes in each graph. We do innermost + // first so that the outer nodes can be positioned based on + // the size and layout of the inner nodes (the parent needs + // to expand to fit the children). + + const subgraphNodes = graphs[parentId]; + + const dataflowDAG = new DirectedAcyclicGraph( + (node) => node.id + ); - return { - ...node, - targetPosition: Position.Left, - sourcePosition: Position.Right, - position: { x: nodePosition.x, y: nodePosition.y }, - }; - }); + subgraphNodes.forEach((node) => { + dataflowDAG.insert(node); + }); - for (const node of topLevelNodes) { - const children = nodes.filter((n) => n.parentId === node.id); + edges.forEach((edge) => { + if (dataflowDAG.hasNode(edge.source) && dataflowDAG.hasNode(edge.target)) + dataflowDAG.addEdge(edge.source, edge.target); + }); + subgraphNodes.forEach((node) => { + if (subgraphSize.has(node.id)) { + const sizes = subgraphSize.get(node.id); + node.height = sizes.height; + node.width = sizes.width; + } + }); + const last = subgraphNodes[subgraphNodes.length - 1]; + const l = parentId === "undefined" ? layout : subFlowLayout; + l.setGraph(dataflowDAG); + l.layoutGraph(); + + subgraphSize.set(parentId, { + width: l.startLeft + (last.position?.x || 0) + l.getNodeWidth(last), + height: l.startTop / 2 + (last.position?.y || 0) + l.getNodeHeight(last), + }); - if (children.length > 0) { - const childNodes = computeLayout( - children, - edges, - subFlowLayout ?? layout, - subFlowLayout ?? layout, - node.id - ); - const last = childNodes[childNodes.length - 1]; - const w = last.position.x + last.measured.width; - const h = last.position.y + last.measured.height; - node.height = layout.startTop * 2 + h + 500; - console.log("Children", childNodes, w, h, node.height, layout.startTop * 2 + h); - returnNodes.push(...childNodes); + returnNodes.push(...subgraphNodes); } } - return returnNodes; + + return returnNodes.toReversed().map((node) => ({ ...node })); }