kieler / elkjs

ELK's layout algorithms for JavaScript
Other
1.81k stars 97 forks source link

Incorrect Port Side Switching in ELKJS with ReactFlow #273

Closed MuttakinHasib closed 7 months ago

MuttakinHasib commented 8 months ago

Hi :wave:

I'm encountering an issue with port side switching while using ELKJS with ReactFlow for auto-layouting. Specifically, I have a condition node with two ports: "Yes" and "No". The expected behavior is for the "Yes" port to stay on the left side and the "No" port on the right side. However, the ports are switching sides unexpectedly. The child nodes connected to the "Yes" port are being placed on the right side, while those connected to the "No" port are being placed on the left side.

I've provided my current ELKJS setup and a live demo link for reference.

Help is needed to rectify this issue and ensure that the ports maintain their correct sides as intended.

Live Link: https://client-journey.vercel.app/

import { useCallback, useEffect } from "react";
import ELK from "elkjs/lib/elk.bundled.js";
import { Edge, Node, useNodesInitialized, useReactFlow } from "reactflow";

// elk layouting options can be found here:
// https://www.eclipse.org/elk/reference/algorithms/org-eclipse-elk-layered.html
const layoutOptions = {
  "elk.algorithm": "layered",
  "elk.direction": "DOWN",
  nodeLayering: "INTERACTIVE",
  "elk.edgeRouting": "ORTHOGONAL",
  "elk.layered.unnecessaryBendpoints": "true",
  "elk.layered.spacing.edgeNodeBetweenLayers": "10",
  "elk.layered.nodePlacement.bk.fixedAlignment": "BALANCED",
  "elk.layered.cycleBreaking.strategy": "DEPTH_FIRST",
  "elk.insideSelfLoops.activate": "true",
  separateConnectedComponents: "false",
  "spacing.componentComponent": "50",
  spacing: "50",
  "elk.layered.spacing.nodeNodeBetweenLayers": "20",
  "elk.layered.crossingMinimization.forceNodeModelOrder": "true",
  "elk.layered.crossingMinimization.semiInteractive": "true",
};

const elk = new ELK();

// uses elkjs to give each node a layouted position
export const getLayoutedNodes = async (nodes: Node[], edges: Edge[]) => {
  const validEdges = edges.filter((edge) => {
    const sourceNode = nodes.find((node) => node.id === edge.source);
    const targetNode = nodes.find((node) => node.id === edge.target);
    return sourceNode && targetNode;
  });
  const graph = {
    id: "root",
    layoutOptions,
    children: nodes.map((n) => {
      return {
        id: n.id,
        width: 400,
        height: 104,
        // targetPosition: "bottom",
        // sourcePosition: "top",
        layoutOptions: {
          "portAlignment.default": "CENTER",
          portConstraints: "FIXED_SIDE",
        },
      };
    }),
    edges: validEdges.map((e) => ({
      id: e.id,
      sources: [e.source],
      targets: [e.target],
    })),
  };

  const layoutedGraph = await elk.layout(graph);

  const layoutedNodes = nodes.map((node) => {
    const layoutedNode = layoutedGraph.children?.find(
      (lgNode) => lgNode.id === node.id
    );
    return {
      ...node,
      position: {
        x: layoutedNode?.x ?? 0,
        y: layoutedNode?.y ?? 0,
      },
    };
  });

  return layoutedNodes;
};

Current behavior (Screenshots)

image

image

MuttakinHasib commented 8 months ago

@soerendomroes The False/B side's children nodes should be placed on the right side. and the True/A side's children nodes should be placed on the left side.

you can see on the 2nd screenshot the SMS node moved to the left side but it should be on the right.

soerendomroes commented 8 months ago

I suggest to replicate the issue using elklive.

MuttakinHasib commented 8 months ago

@soerendomroes I'm Sorry, I actually don't know how can I demonstrate elklive based on my project. But I hope you will get the issue. I request you to check the demo https://client-journey.vercel.app/

try to add a condition node then add another node under false port. you will see that the new node is placed on the left side but it shouldn't. It should be placed on the right side. I don't know how can I achieve this, please help me.

I have attached my ELK setup https://github.com/kieler/elkjs/issues/273#issue-2211161869

Current Behaviour

image

Expected Behaviour

image

soerendomroes commented 8 months ago

Hi, I wanted you to recreate your graph since I want to know the order of nodes and edges in our model as well as additional layout properties.

elk.layered.crossingMinimization.forceNodeModelOrder may be responsible for this but without a model recreated in elklive, I can only guess.

https://rtsys.informatik.uni-kiel.de/elklive/elkgraph.html

MuttakinHasib commented 7 months ago

Hey @soerendomroes

I have tried to create two elklive.

Example One URL

Here always the Yes Node and Yes Port will place on the left side and the No Node and No Port will place on the right side.

image

Example Two URL

Here the No_Node and the No_Port will always place on the right side though I don't have any nodes under the condition node of yes port

image

Here is my current ELKjs Setup with ReactFlow

https://gist.github.com/MuttakinHasib/abf85903a66c655c449c90042725dad2

MuttakinHasib commented 7 months ago

@soerendomroes Whenever I add a new edge or delete an edge while creating a node then the nodes are switching the sides

soerendomroes commented 7 months ago

If Yes and No ports should always be in that order, I suggest to set portConstraints: FIXED_ORDER on them. The issue in your first model persists since elklive changes its version back to 0.7.1 which does not support all option you are using.

MuttakinHasib commented 7 months ago

@soerendomroes Do I need to maintain the edge insertion in an order? Example, I am removing a node and edge and after that, I am adding a new node and edges.

something like edges.push(newEdge) to add a new edge

soerendomroes commented 7 months ago

If you constrain the port order, you only need to maintain the port order.

If you want to also constrain the node order via forceNodeModelOrder you need to set considerModelOrder.strategy. This would change the edge/port order if it is free to enforce the order of nodes if the initial solution using the given order is not crossing minimal.

MuttakinHasib commented 7 months ago

@soerendomroes Which one do you prefer? in this case? I just want the layout not to switch the side

soerendomroes commented 7 months ago

If you want the port to be in a specific order, I would constrain the ports and not use the model order strategies. If your nodes and edges are ordered based on the time you create them, it does not make sense to use model order.

MuttakinHasib commented 7 months ago

@soerendomroes Will you please provide an elklive example to fix this issue? Demo Elk live

soerendomroes commented 7 months ago

Here is the port constraints option. And here is the model order option that enforces node model order (remember to switch to 0.8.2).

MuttakinHasib commented 7 months ago

Thanks @soerendomroes for your help :heart: I have implemented this Example and it's looks great.