Libavoid Edge Routing is not optimal, why?
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.
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
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.
After some time trying around I decided to introduce PORTS to my graph and this is my current result:
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?
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).
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!
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.
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.
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.
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
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!
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
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.