elk icon indicating copy to clipboard operation
elk copied to clipboard

Libavoid Edge Routing is not optimal, why?

Open mpralle opened this issue 1 year ago • 12 comments

Hi,

I'm currently working on a Renderer for textual defined UML Class Diagrams. First I'm using a layout algorithm to layout the nodes and after that i want to route the edges.

Screenshot 2024-02-28 225949

Now my question: Why is the routing of for example Entity and Share like this and not that the Ports are on the bottom and top of the classes. Im using GLSPs integration of ELK, so here is my definition of parameters, i tried different parameters and values but nothing worked well.

layoutConfiguratorLibavoid.configure(ElkNode.class) .setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.alg.libavoid") .setProperty(LibavoidOptions.SEGMENT_PENALTY, 10.0) .setProperty(LibavoidOptions.PORT_DIRECTION_PENALTY, 10.0) .setProperty(LibavoidOptions.PORT_CONSTRAINTS, PortConstraints.FREE) .setProperty(LibavoidOptions.EDGE_ROUTING, EdgeRouting.ORTHOGONAL);

I hope someone can give me some insight on his configuration.

Best regards

mpralle avatar Feb 28 '24 22:02 mpralle

ELK should determine the hierarchical port positions (the ports that cross the hierarchy level on the right) based on your layout direction. Maybe change elk.direction to something like DOWN? I guess this might predetermine the ports to be on the EAST side and does hence also put the inner ports on the EAST side of the nodes. Have you tried using port constraints other than FREE such as FIXED_SIDE and setting the port sides of ports? If this changes something, it might be the port side + direction problem.

If you could provide me with an example graph, I could try out different configurations.

Is this is not the issue I sadly, I don't know, since we only integrate libavoid. Maybe, you can get an answer at the adaptergrams repository in the libavoid documentation.

If you do so, please take a look at the place we transform to their options.

soerendomroes avatar Feb 29 '24 07:02 soerendomroes

After some time trying around I decided to introduce PORTS to my graph and this is my current result: image

Therefore this is my general application of properties to the graph: layoutConfiguratorLibavoid = new GLSPLayoutConfigurator(); layoutConfiguratorLibavoid.configure(ElkNode.class) .setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.alg.libavoid") .setProperty(LibavoidOptions.EDGE_ROUTING, EdgeRouting.ORTHOGONAL) .setProperty(LibavoidOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); layoutConfiguratorLibavoid.configure(ElkPort.class) .setProperty(LibavoidOptions.PORT_SIDE, PortSide.SOUTH);

after applying this to all elements of my Graph i go through the contained edges and give them the port sides which were calculated before that using another algorithm (which does not support multiedges and selfloops).

This is a json output for example for a graph: {"id":"_da2ecf5_c88e_4b4f_a954_12c045d24494","labels":[{"text":"MyCompany","id":"_1eee0db_ceda_4ce5_8543_9b0dae060bea","layoutOptions":{},"x":75.66666666666666,"y":10.0,"width":48.0,"height":10.0}],"children":[{"id":"node_f99cdf2d_8a69_4fab_9fd9_ef1c81cf15bb","labels":[{"text":"Entity","id":"_dbfb4ab_e49b_422c_8da7_086594dc55d9","layoutOptions":{},"x":10.0,"y":10.0,"width":36.0,"height":10.0}],"ports":[{"id":"port_edge_35895053_1f06_4850_8606_b6573819732c_node_f99cdf2d_8a69_4fab_9fd9_ef1c81cf15bb","layoutOptions":{"port.side":"NORTH"},"x":28.0},{"id":"port_edge_3da9754f_66a5_489b_b750_404c54d83f0a_node_f99cdf2d_8a69_4fab_9fd9_ef1c81cf15bb","layoutOptions":{"port.side":"EAST"},"x":56.0,"y":20.0}],"layoutOptions":{"portLabels.placement":"[OUTSIDE]","nodeLabels.placement":"[]","algorithm":"org.eclipse.elk.alg.libavoid","nodeSize.constraints":"[]","portConstraints":"FIXED_SIDE","margins":"[top=0.0,left=0.0,bottom=0.0,right=0.0]","edgeRouting":"ORTHOGONAL","nodeSize.options":"[DEFAULT_MINIMUM_SIZE]"},"x":10.0,"y":185.0,"width":56.0,"height":40.0},{"id":"node_0439550f_dd3e_4760_8fdb_682e36676708","labels":[{"text":"Company","id":"_5d2dbe8_5ef9_4726_bcb4_b648b043d569","layoutOptions":{},"x":17.5,"y":10.0,"width":40.0,"height":10.0},{"text":"int kind","id":"_c93cedc_d277_4a3c_9fa6_12006a644c14","layoutOptions":{},"x":15.5,"y":20.0,"width":44.0,"height":10.0}],"ports":[{"id":"port_edge_c167efb9_32ec_4786_9e86_fff327da3a3d_node_0439550f_dd3e_4760_8fdb_682e36676708","layoutOptions":{"port.side":"SOUTH"},"x":50.0,"y":60.0},{"id":"port_edge_6104bd68_d815_4f73_b4a7_d1daadeacd2d_node_0439550f_dd3e_4760_8fdb_682e36676708","layoutOptions":{"port.side":"SOUTH"},"x":25.0,"y":60.0},{"id":"port_edge_ec38a712_5654_47b6_9f7c_a9c24cb0d533_node_0439550f_dd3e_4760_8fdb_682e36676708","layoutOptions":{"port.side":"NORTH"},"x":37.5},{"id":"port_edge_3da9754f_66a5_489b_b750_404c54d83f0a_node_0439550f_dd3e_4760_8fdb_682e36676708","layoutOptions":{"port.side":"WEST"},"y":30.0}],"layoutOptions":{"portLabels.placement":"[OUTSIDE]","nodeLabels.placement":"[]","algorithm":"org.eclipse.elk.alg.libavoid","nodeSize.constraints":"[]","portConstraints":"FIXED_SIDE","margins":"[top=0.0,left=0.0,bottom=0.0,right=0.0]","edgeRouting":"ORTHOGONAL","nodeSize.options":"[DEFAULT_MINIMUM_SIZE]"},"x":250.0,"y":185.0,"width":75.0,"height":60.0},{"id":"node_d122c0fd_c3cb_4924_bf9b_20e7e5de1d64","labels":[{"text":"Employee","id":"_45c3974_6dc7_44e2_957d_bff299d52bcb","layoutOptions":{},"x":14.0,"y":10.0,"width":44.0,"height":10.0},{"text":"int salary","id":"_b6d086f_7a92_4f43_8996_898ddfee42f7","layoutOptions":{},"x":10.0,"y":20.0,"width":52.0,"height":10.0}],"ports":[{"id":"port_edge_c167efb9_32ec_4786_9e86_fff327da3a3d_node_d122c0fd_c3cb_4924_bf9b_20e7e5de1d64","layoutOptions":{"port.side":"NORTH"},"x":24.0},{"id":"port_edge_6104bd68_d815_4f73_b4a7_d1daadeacd2d_node_d122c0fd_c3cb_4924_bf9b_20e7e5de1d64","layoutOptions":{"port.side":"NORTH"},"x":48.0}],"layoutOptions":{"portLabels.placement":"[OUTSIDE]","nodeLabels.placement":"[]","algorithm":"org.eclipse.elk.alg.libavoid","nodeSize.constraints":"[]","portConstraints":"FIXED_SIDE","margins":"[top=0.0,left=0.0,bottom=0.0,right=0.0]","edgeRouting":"ORTHOGONAL","nodeSize.options":"[DEFAULT_MINIMUM_SIZE]"},"x":250.0,"y":345.0,"width":72.0,"height":60.0},{"id":"node_31ae632a_45f0_4456_be16_b17cdfe2310b","labels":[{"text":"Share","id":"_34b6bc4_ec04_42eb_a5d5_441d0b25f165","layoutOptions":{},"x":18.0,"y":10.0,"width":32.0,"height":10.0},{"text":"int value","id":"bf1a4c73_3239_4d1d_9b76_92fc3dc5a004","layoutOptions":{},"x":10.0,"y":20.0,"width":48.0,"height":10.0}],"ports":[{"id":"port_edge_ec38a712_5654_47b6_9f7c_a9c24cb0d533_node_31ae632a_45f0_4456_be16_b17cdfe2310b","layoutOptions":{"port.side":"SOUTH"},"x":34.0,"y":60.0},{"id":"port_edge_35895053_1f06_4850_8606_b6573819732c_node_31ae632a_45f0_4456_be16_b17cdfe2310b","layoutOptions":{"port.side":"WEST"},"y":30.0}],"layoutOptions":{"portLabels.placement":"[OUTSIDE]","nodeLabels.placement":"[]","algorithm":"org.eclipse.elk.alg.libavoid","nodeSize.constraints":"[]","portConstraints":"FIXED_SIDE","margins":"[top=0.0,left=0.0,bottom=0.0,right=0.0]","edgeRouting":"ORTHOGONAL","nodeSize.options":"[DEFAULT_MINIMUM_SIZE]"},"x":250.0,"y":25.0,"width":68.0,"height":60.0}],"layoutOptions":{"algorithm":"org.eclipse.elk.alg.libavoid","spacing.portsSurrounding":"[top=0.0,left=0.0,bottom=0.0,right=0.0]","portConstraints":"FIXED_SIDE","edgeRouting":"ORTHOGONAL","nodeLabels.padding":"[top=5.0,left=5.0,bottom=5.0,right=5.0]"},"x":10.0,"y":25.0,"width":420.0,"height":435.0,"edges":[{"id":"edge_c167efb9_32ec_4786_9e86_fff327da3a3d","sources":["port_edge_c167efb9_32ec_4786_9e86_fff327da3a3d_node_0439550f_dd3e_4760_8fdb_682e36676708"],"targets":["port_edge_c167efb9_32ec_4786_9e86_fff327da3a3d_node_d122c0fd_c3cb_4924_bf9b_20e7e5de1d64"],"sections":[{"id":"s0","startPoint":{"x":300.0,"y":245.0},"endPoint":{"x":274.0,"y":345.0},"bendPoints":[{"x":300.0,"y":293.0},{"x":274.0,"y":293.0}]}],"layoutOptions":{"junctionPoints":"()","edgeRouting":"ORTHOGONAL"}},{"id":"edge_6104bd68_d815_4f73_b4a7_d1daadeacd2d","sources":["port_edge_6104bd68_d815_4f73_b4a7_d1daadeacd2d_node_0439550f_dd3e_4760_8fdb_682e36676708"],"targets":["port_edge_6104bd68_d815_4f73_b4a7_d1daadeacd2d_node_d122c0fd_c3cb_4924_bf9b_20e7e5de1d64"],"sections":[{"id":"s1","startPoint":{"x":275.0,"y":245.0},"endPoint":{"x":298.0,"y":345.0},"bendPoints":[{"x":275.0,"y":297.0},{"x":298.0,"y":297.0}]}],"layoutOptions":{"junctionPoints":"()","edgeRouting":"ORTHOGONAL"}},{"id":"edge_ec38a712_5654_47b6_9f7c_a9c24cb0d533","sources":["port_edge_ec38a712_5654_47b6_9f7c_a9c24cb0d533_node_0439550f_dd3e_4760_8fdb_682e36676708"],"targets":["port_edge_ec38a712_5654_47b6_9f7c_a9c24cb0d533_node_31ae632a_45f0_4456_be16_b17cdfe2310b"],"sections":[{"id":"s2","startPoint":{"x":287.5,"y":185.0},"endPoint":{"x":284.0,"y":85.0},"bendPoints":[{"x":287.5,"y":135.0},{"x":284.0,"y":135.0}]}],"layoutOptions":{"junctionPoints":"()","edgeRouting":"ORTHOGONAL"}},{"id":"edge_35895053_1f06_4850_8606_b6573819732c","sources":["port_edge_35895053_1f06_4850_8606_b6573819732c_node_f99cdf2d_8a69_4fab_9fd9_ef1c81cf15bb"],"targets":["port_edge_35895053_1f06_4850_8606_b6573819732c_node_31ae632a_45f0_4456_be16_b17cdfe2310b"],"sections":[{"id":"s3","startPoint":{"x":38.0,"y":185.0},"endPoint":{"x":250.0,"y":55.0},"bendPoints":[{"x":38.0,"y":55.0}]}],"layoutOptions":{"junctionPoints":"()","edgeRouting":"ORTHOGONAL"}},{"id":"edge_3da9754f_66a5_489b_b750_404c54d83f0a","sources":["port_edge_3da9754f_66a5_489b_b750_404c54d83f0a_node_0439550f_dd3e_4760_8fdb_682e36676708"],"targets":["port_edge_3da9754f_66a5_489b_b750_404c54d83f0a_node_f99cdf2d_8a69_4fab_9fd9_ef1c81cf15bb"],"sections":[{"id":"s4","startPoint":{"x":250.0,"y":215.0},"endPoint":{"x":66.0,"y":205.0},"bendPoints":[{"x":158.0,"y":215.0},{"x":158.0,"y":205.0}]}],"layoutOptions":{"junctionPoints":"()","edgeRouting":"ORTHOGONAL"}}]}

I do not understand why there are these bends, which are not necessary. Do I need to set some kind of penatlies or can it be due to previously computed positions of ports?

mpralle avatar Mar 04 '24 14:03 mpralle

If you mean the edges between Company and Employee, I think that they are introduced by the port position. Have you tried setting the port constraints to a fixed order? If the direction in your drawing is DOWN, this may be an issue in ELK, which orders the NORTH ports in the inverse order (which is a relict from hardware visualization times).

soerendomroes avatar Mar 04 '24 16:03 soerendomroes

Thank you for the advice. What I'm trying to do is using the sides calculated by another (not in ELK) algorithm, for selfloops using the least populated sides and for multiedges the same sides as their twin edge. The fixed order property fixed it, thank you really much!

image

But I am still wondering why there are all these tiny bends (Company,Entity for example). The Elk direction is not set by me, so it is undefiend.

mpralle avatar Mar 04 '24 16:03 mpralle

If the direction is UNDEFINED, ELK will choose a direction based on the aspect ratio. I would rather set it to avoid seemingly random behavior change.

The bends occur, since the position of the nodes is fix and the position of the ports is determined by evenly distributing them on the side of a node. Since either Company and Employee are not aligned or they are a different width, the ports will not align. I suggest to set portConstraint to FIXED_POS and calculating the correct positions. But maybe this should alert the user that he nodes Company and Employee do not have the same size or are not aligned.

soerendomroes avatar Mar 04 '24 16:03 soerendomroes

Thank you for the advice. I will set the direction to avoid any unexpected behaviour. The other algorithm im using is from a bachlor thesis and kind of hard to adjust. Is it possible to force libavoid to compute the position for me? As i understand fixed_order just describes the order and not that the ports are needed to be distributed evenly. The classes have different sizes due to the different content they can hold.

mpralle avatar Mar 04 '24 21:03 mpralle

Setting port constraints to FREE and setting the port direction penalty was your first approach, right? I think the libavoid integration does not provide a coordinate level port position calculation but can only find the correct side. https://eclipse.dev/elk/reference/algorithms/org-eclipse-elk-alg-libavoid.html

soerendomroes avatar Mar 05 '24 07:03 soerendomroes

image

I now did a lot of manual computation to set port sides and i am quite okay with the result so far (some optimization regarding label placement and also sometimes the order priority of edges can be a bit tricky). Can i somehow extend the space between the node and the selfloop? Thank you for all the quick help till now!

mpralle avatar Mar 28 '24 19:03 mpralle

Can i somehow extend the space between the node and the selfloop?

With Layered layout, there is the property spacing.nodeSelfLoop for that. I have not tested whether this is taken into account by Libavoid layout. A sample with this property here

lredor avatar Mar 29 '24 08:03 lredor

Thank you for the idea, but it did not work. If I understood correctly libavoid is using a libavoid layout server based on C for the layouting. Only a limited number of attributes are transformed from elk parameters to the corresponding "libavoid" parameters. I think i will have to wait till it is added to libavoid or use a work around. A work around which just came to my mind is placing a node with size 0 diagnoally to the node of the self loop and when translate the current edge into two edges. This should work quite easly, but maybe this will be something for future work in my project.

mpralle avatar Apr 01 '24 18:04 mpralle