Skip to content

Commit c801cb6

Browse files
fix cyclic node edges
1 parent 7f798dd commit c801cb6

File tree

1 file changed

+45
-13
lines changed

1 file changed

+45
-13
lines changed

src/Visualizer.tsx

+45-13
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import "@xyflow/react/dist/style.css";
44

55
import { scheme } from "vega-scale";
66
import { ErrorBoundary } from "react-error-boundary";
7-
import ELK, { ElkExtendedEdge, ElkNode, ElkPrimitiveEdge } from "elkjs/lib/elk.bundled.js";
7+
import ELK, { ElkNode, ElkPrimitiveEdge } from "elkjs/lib/elk.bundled.js";
88
import { memo, startTransition, Suspense, use, useCallback, useEffect, useMemo, useState } from "react";
99
import {
1010
ReactFlow,
@@ -36,8 +36,10 @@ const layoutOptions = {
3636
"elk.direction": "DOWN",
3737
"elk.portConstraints": "FIXED_SIDE",
3838
"elk.hierarchyHandling": "INCLUDE_CHILDREN",
39-
"elk.layered.mergeEdges": "True",
39+
// "elk.layered.mergeEdges": "True",
4040
"elk.edgeRouting": "ORTHOGONAL",
41+
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
42+
4143
// "elk.layered.edgeRouting.splines.mode": "CONSERVATIVE_SOFT",
4244
// "elk.layered.spacing.baseValue": "40",
4345
// "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
@@ -156,12 +158,12 @@ function toELKNode(
156158
id: `class-${id}`,
157159
data: { color: type_to_color.get(egraph.class_data[id]?.type) || null, port: `port-${id}`, id },
158160
ports: [
159-
{
160-
id: `port-${id}`,
161-
layoutOptions: {
162-
"port.side": "SOUTH",
163-
},
164-
},
161+
// {
162+
// id: `port-${id}`,
163+
// layoutOptions: {
164+
// "port.side": "NORTH",
165+
// },
166+
// },
165167
],
166168
type: "class",
167169
children: nodes.map(([id, node]) => {
@@ -184,6 +186,29 @@ function toELKNode(
184186
ports,
185187
};
186188
}),
189+
edges: nodes.flatMap(([id, node]) =>
190+
[...node.children.entries()].flatMap(([index, childNode]) => {
191+
const sourcePort = `port-${id}-${index}`;
192+
const class_ = nodeToClass.get(childNode)!;
193+
// only include if this is a self loop
194+
if (class_ != id) {
195+
return [];
196+
}
197+
// If the target or source class is not in the selected nodes, don't draw the edge
198+
const targetPort = `port-${class_}`;
199+
return [
200+
{
201+
id: `edge-${id}-${index}`,
202+
source: `node-${id}`,
203+
sourcePort,
204+
sourceHandle: sourcePort,
205+
target: `class-${class_}`,
206+
targetPort,
207+
targetHandle: targetPort,
208+
},
209+
];
210+
})
211+
),
187212
};
188213
});
189214

@@ -192,7 +217,11 @@ function toELKNode(
192217
const sourcePort = `port-${id}-${index}`;
193218
const class_ = nodeToClass.get(childNode)!;
194219
// If the target or source class is not in the selected nodes, don't draw the edge
195-
if (!classToNodes.get(node.eclass)?.find(([sourceID, _]) => sourceID == id) || !classToNodes.has(class_)) {
220+
if (
221+
!classToNodes.get(node.eclass)?.find(([sourceID]) => sourceID == id) ||
222+
!classToNodes.has(class_) ||
223+
class_ == nodeToClass.get(id)
224+
) {
196225
return [];
197226
}
198227
const targetPort = `port-${class_}`;
@@ -213,7 +242,7 @@ function toELKNode(
213242
id: "--eclipse-layout-kernel-root",
214243
layoutOptions,
215244
children,
216-
edges: edges as unknown as ElkExtendedEdge[],
245+
edges,
217246
};
218247
}
219248

@@ -233,7 +262,7 @@ function toFlowNodes(layout: MyELKNodeLayedOut): Node[] {
233262
]);
234263
}
235264

236-
export function EClassNode({ data }: { data: { port: string; color: string } }) {
265+
export function EClassNode({ data }: { data: { port: string; color: string; id: string } }) {
237266
return (
238267
<div className="rounded-md border border-dotted border-stone-400 h-full w-full" style={{ backgroundColor: data.color }}>
239268
<Handle className="top-0 bottom-0 opacity-0 translate-0" type="target" id={data.port} position={Position.Top} />
@@ -245,7 +274,7 @@ export function ENode({
245274
data,
246275
...rest
247276
}: {
248-
data: { label: string; ports: { id: string }[] };
277+
data: { label: string; ports: { id: string }[]; id: string };
249278
outerRef?: React.Ref<HTMLDivElement>;
250279
innerRef?: React.Ref<HTMLDivElement>;
251280
}) {
@@ -284,7 +313,10 @@ function LayoutFlow({ egraph, outerElem, innerElem }: { egraph: string; outerEle
284313
// console.log(JSON.parse(JSON.stringify(r, null, 2)));
285314
return r;
286315
}, [parsedEGraph, outerElem, innerElem, selectedNode]);
287-
const edges = useMemo(() => elkNode.edges!.map((e) => ({ ...e })), [elkNode]);
316+
const edges = useMemo(
317+
() => [...elkNode.children.flatMap((c) => c.edges!.map((e) => ({ ...e }))), ...elkNode.edges!.map((e) => ({ ...e }))],
318+
[elkNode]
319+
);
288320
const layoutPromise = useMemo(() => elk.layout(elkNode) as Promise<MyELKNodeLayedOut>, [elkNode]);
289321
const layout = use(layoutPromise);
290322
const nodes = useMemo(() => toFlowNodes(layout), [layout]);

0 commit comments

Comments
 (0)