retejs / rete

JavaScript framework for visual programming
https://retejs.org
MIT License
10.04k stars 652 forks source link

Stable vertical render with arrange plugin (rete V2) #697

Closed ArutaGerman closed 1 month ago

ArutaGerman commented 7 months ago

Hi!

We have a few nodes that have [0, 0] position and we need to order them, but we have different flow from time to time.

For the first time it's like image

Secondly, it's like image

The third time image

What can we do about it? We want to always have the same result as the previous one

P.s. we have this problem with rete v1 too

Ni55aN commented 6 months ago

did you consider to try different options for Elk.js? https://retejs.org/docs/guides/arrange#arrange-options

Additionaly, you can explore the arrangment result returned by arrange.layout(). It has demonstration (a link to elk.js playground) and result (generated json)

ArutaGerman commented 6 months ago

Yes, we use a different settings for elk

'elk.direction': 'DOWN',
'elk.algorithm': 'mrtree',
'elk.alignment': 'DOWN',
'elk.layered.spacing.nodeNodeBetweenLayers': 200,
'elk.mrtree.weighting': 'CONSTRAINT',
'elk.spacing.edgeNode': 0,
'elk.spacing.nodeNode': 200,
Ni55aN commented 6 months ago

how does your graph looks on elk.js playground? It makes sense to achieve the desired result there and then transfer the options to the plugin

ArutaGerman commented 6 months ago

In the playground is stable (but this can't help) because we have this behavior only when schema created first time. If I add a button that trigger layout event, schema doesn't changes - she will render like when mounted

I have a function "drawGraph":

function drawGraph(dataToShow) {
  const schema = cloneDeep(dataToShow.nodes);
  const { nodes, promiseList } = getNodeList(schema);
  const { connectionList, connectionPromises } = createConnections(nodes, schema);

  await Promise.all(promiseList);
  await Promise.all(connectionPromises);
  await Arrange.layout();
}

function getNodeList(schema) {
  const nodes = {};
  const promiseList = [];

  Object.values(schema).forEach(({ id }) => {
    nodes[id] = getEditorNodeWithParams(schema[id]);
    promiseList.push(Editor.addNode(nodes[id]));
  });
  return {
    nodes,
    promiseList,
  };
}

function createConnections(nodes, schema) {
  const connectionList = [];
  const connectionPromises = [];

  // This code for json for rete v1 (answer of the server)
  // Iterate all nodes
  Object.keys(nodes).forEach((id) => {
      // iterate all output sockets
      Object.keys(schema[id].outputs).forEach((socketName) => {
         // iterate all connected nodes
          schema[id].outputs[socketName].connections .forEach((node) => {
              const { input: inSocketName, node: conNode, data = { pins: [] } } = node;
              const connection = getConnectionInstance({
                inNode: nodes[conNode],
                inSocket: inSocketName,
                outNode: nodes[id],
                outSocket: socketName,
                data: { ...data, Title: data.Title },
              });
              connectionList.push(connection);
              connectionPromises.push(addConnectionToEditor({ connection }));
            });
        });
    });
  return {
    connectionList,
    connectionPromises,
  };
}

and this is example for elkjs playground:

{
  id: "root",
  layoutOptions: {
    "elk.algorithm": "mrtree",
    "elk.hierarchyHandling": "INCLUDE_CHILDREN",
    "elk.edgeRouting": "POLYLINE",
    "elk.direction": "DOWN",
    "elk.alignment": "DOWN",
    "elk.layered.spacing.nodeNodeBetweenLayers": 200,
    "elk.mrtree.weighting": "CONSTRAINT",
    "elk.spacing.edgeNode": 0,
    "elk.spacing.nodeNode": 200
},
  children: [
    { id: "n2", width: 30, height: 30 },
    { id: "n3", width: 30, height: 30 },
    { id: "n4", width: 30, height: 30 },
    { id: "n5", width: 30, height: 30 },
    { id: "n6", width: 30, height: 30 },
    { id: "n1", width: 30, height: 30 },
    { id: "n7", width: 30, height: 30 }
  ],
  edges: [
    { id: "e2", sources: [ "n2" ], targets: [ "n4" ] },
    { id: "e3", sources: [ "n3" ], targets: [ "n5" ] },
    { id: "e5", sources: [ "n4" ], targets: [ "n5" ] },
    { id: "e4", sources: [ "n5" ], targets: [ "n6" ] },
    { id: "e6", sources: [ "n6" ], targets: [ "n7" ] },
    { id: "e1", sources: [ "n1" ], targets: [ "n4" ] },
  ]
}
ArutaGerman commented 6 months ago

dataToShow

In the playground is stable (but this can't help) because we have this behavior only when schema created first time. If I add a button that trigger layout event, schema doesn't changes - she will render like when mounted

I have a function "drawGraph":

function drawGraph(dataToShow) {
  const schema = cloneDeep(dataToShow.nodes);
  const { nodes, promiseList } = getNodeList(schema);
  const { connectionList, connectionPromises } = createConnections(nodes, schema);

  await Promise.all(promiseList);
  await Promise.all(connectionPromises);
  await Arrange.layout();
}

function getNodeList(schema) {
  const nodes = {};
  const promiseList = [];

  Object.values(schema).forEach(({ id }) => {
    nodes[id] = getEditorNodeWithParams(schema[id]);
    promiseList.push(Editor.addNode(nodes[id]));
  });
  return {
    nodes,
    promiseList,
  };
}

function createConnections(nodes, schema) {
  const connectionList = [];
  const connectionPromises = [];

  // This code for json for rete v1 (answer of the server)
  // Iterate all nodes
  Object.keys(nodes).forEach((id) => {
      // iterate all output sockets
      Object.keys(schema[id].outputs).forEach((socketName) => {
         // iterate all connected nodes
          schema[id].outputs[socketName].connections .forEach((node) => {
              const { input: inSocketName, node: conNode, data = { pins: [] } } = node;
              const connection = getConnectionInstance({
                inNode: nodes[conNode],
                inSocket: inSocketName,
                outNode: nodes[id],
                outSocket: socketName,
                data: { ...data, Title: data.Title },
              });
              connectionList.push(connection);
              connectionPromises.push(addConnectionToEditor({ connection }));
            });
        });
    });
  return {
    connectionList,
    connectionPromises,
  };
}

and this is example for elkjs playground:

{
  id: "root",
  layoutOptions: {
    "elk.algorithm": "mrtree",
    "elk.hierarchyHandling": "INCLUDE_CHILDREN",
    "elk.edgeRouting": "POLYLINE",
    "elk.direction": "DOWN",
    "elk.alignment": "DOWN",
    "elk.layered.spacing.nodeNodeBetweenLayers": 200,
    "elk.mrtree.weighting": "CONSTRAINT",
    "elk.spacing.edgeNode": 0,
    "elk.spacing.nodeNode": 200
},
  children: [
    { id: "n2", width: 30, height: 30 },
    { id: "n3", width: 30, height: 30 },
    { id: "n4", width: 30, height: 30 },
    { id: "n5", width: 30, height: 30 },
    { id: "n6", width: 30, height: 30 },
    { id: "n1", width: 30, height: 30 },
    { id: "n7", width: 30, height: 30 }
  ],
  edges: [
    { id: "e2", sources: [ "n2" ], targets: [ "n4" ] },
    { id: "e3", sources: [ "n3" ], targets: [ "n5" ] },
    { id: "e5", sources: [ "n4" ], targets: [ "n5" ] },
    { id: "e4", sources: [ "n5" ], targets: [ "n6" ] },
    { id: "e6", sources: [ "n6" ], targets: [ "n7" ] },
    { id: "e1", sources: [ "n1" ], targets: [ "n4" ] },
  ]
}

I tried to use for of with await instead of forEach, but had the same result: first render is a random result and all next renders has the same schema, while a user doesn't reloaded the page

Ni55aN commented 6 months ago

Can you provide an example on Codesandbox? I still don't understand whether elk.js JSON schema changes from time to time or not (it can be an issue with generating elk schema or arranging nodes based on this schema)

ArutaGerman commented 5 months ago

I love javascript! But not now :) My problem was the way JavaScript works with an object. It sorted the nodes in a random order and all the problems went away when I ordered the nodes: Object.fromEntries(Object.entries.sort())

ArutaGerman commented 4 months ago

update: not only the nodes but also the connections should be in the same order as in the previous iteration. If you add connection1, then connection2, and then for the next render you change this order: the schema will look different from the previous version

rete-js[bot] commented 2 months ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 10 days.

rete-js[bot] commented 1 month ago

This issue was closed because it has been stalled for 10 days with no activity.