Force edge to leave and re-enter parent source node when targeting descendant node
jbeard4 opened this issue · comments
Hello,
I am trying to find a way to prompt klay to layout an edge that originates in a parent node and targets a descendant node, such that the edge exits and re-enters the parent node.
My use case is as follows. In the Statecharts formalism, there are two varieties of transitions: internal and external. This distinction is relevant when a transition originates from a parent state and targets a substate. The difference between these two transition types, semantically, is that when a transition is taken, an external transition causes the source state to be exited and re-entered, while the internal transition does not cause the source state to be exited.
I have found that the KlayJS default layout works very well for internal transitions:
I have tried to force klay to exit the parent node by creating an invisible "pseudonode" with width and height 0, however this results in a graphical artifact, as a bend point is created which overlaps with the edge originating from the pseudonode. You can see the results below:
Input KGraph JSON:
{
"id": "root",
"labels": [
{
"text": "root"
}
],
"edges": [
{
"id": "a_a2",
"source": "a",
"target": "$generated-1",
"labels": [
{
"text": "t1",
"width": 3.109375,
"height": 4.5
}
],
"$type": "hyperlink"
},
{
"id": "a2_b",
"source": "a2",
"target": "b",
"labels": [
{
"text": "t2",
"width": 3.109375,
"height": 4.5
}
]
},
{
"id": "b_c",
"source": "b",
"target": "c",
"labels": [
{
"text": "t3",
"width": 3.109375,
"height": 4.5
}
]
},
{
"id": "$generated-1_a2",
"source": "$generated-1",
"target": "a2"
}
],
"width": 11.4375,
"height": 9.5,
"$type": "scxml",
"children": [
{
"id": "a",
"labels": [
{
"text": "a"
}
],
"edges": [],
"width": 6.765625,
"height": 9.5,
"children": [
{
"id": "a1",
"labels": [
{
"text": "a1"
}
],
"edges": [],
"width": 8.765625,
"height": 9.5,
"x": 0,
"y": 0
},
{
"id": "a2",
"labels": [
{
"text": "a2"
}
],
"edges": [],
"width": 8.765625,
"height": 9.5,
"x": 0,
"y": 0
}
],
"x": 0,
"y": 0
},
{
"id": "b",
"labels": [
{
"text": "b"
}
],
"edges": [],
"width": 7,
"height": 9.5,
"x": 0,
"y": 0
},
{
"id": "c",
"labels": [
{
"text": "c"
}
],
"edges": [],
"width": 6.765625,
"height": 9.5,
"x": 0,
"y": 0
},
{
"id": "$generated-1",
"$type": "pseudonode",
"width": 0,
"height": 0,
"edges": [],
"x": 0,
"y": 0
}
]
}
Kgraph after layout:
{
"id": "root",
"labels": [
{
"text": "root"
}
],
"edges": [
{
"id": "a_a2",
"source": "a",
"target": "$generated-1",
"labels": [
{
"text": "t1",
"width": 3,
"height": 4,
"x": 23,
"y": 56
}
],
"$type": "hyperlink",
"sourcePoint": {
"x": 87,
"y": 46
},
"targetPoint": {
"x": 10,
"y": 36
},
"bendPoints": [
{
"x": 96,
"y": 46
},
{
"x": 96,
"y": 63
},
{
"x": 16,
"y": 63
},
{
"x": 16,
"y": 36
}
],
"junctionPoints": []
},
{
"id": "a2_b",
"source": "a2",
"target": "b",
"labels": [
{
"text": "t2",
"width": 3,
"height": 4,
"x": 64,
"y": 18
}
],
"sourcePoint": {
"x": 37,
"y": 26
},
"targetPoint": {
"x": 77,
"y": 26
},
"junctionPoints": []
},
{
"id": "b_c",
"source": "b",
"target": "c",
"labels": [
{
"text": "t3",
"width": 3,
"height": 4,
"x": 133,
"y": 28
}
],
"sourcePoint": {
"x": 123,
"y": 36
},
"targetPoint": {
"x": 146,
"y": 36
},
"junctionPoints": []
},
{
"id": "$generated-1_a2",
"source": "$generated-1",
"target": "a2",
"sourcePoint": {
"x": 10,
"y": 36
},
"targetPoint": {
"x": 68,
"y": 36
},
"junctionPoints": []
}
],
"width": 163,
"height": 74,
"$type": "scxml",
"children": [
{
"id": "a",
"labels": [
{
"text": "a"
}
],
"edges": [],
"width": 47,
"height": 46,
"children": [
{
"id": "a1",
"labels": [
{
"text": "a1"
}
],
"edges": [],
"width": 8,
"height": 9,
"x": 10,
"y": 10
},
{
"id": "a2",
"labels": [
{
"text": "a2"
}
],
"edges": [],
"width": 8,
"height": 9,
"x": 28,
"y": 21
}
],
"x": 39,
"y": 10,
"$H": 25
},
{
"id": "b",
"labels": [
{
"text": "b"
}
],
"edges": [],
"width": 7,
"height": 9,
"x": 116,
"y": 31
},
{
"id": "c",
"labels": [
{
"text": "c"
}
],
"edges": [],
"width": 6,
"height": 9,
"x": 146,
"y": 31
},
{
"id": "$generated-1",
"$type": "pseudonode",
"width": 0,
"height": 0,
"edges": [],
"x": 10,
"y": 36
}
],
"$H": 1
}
I was wondering if there is a better way to use the klay API to support this use case? Thank you for your guidance on this.
One option is to add ports to the parent node a
that serve as explicit entry and exit points, say p_in
and p_out
. Then add two edge a2 -> a:p_out
and a:p_out -> a:p_in
. From the layout algorithm's perspective the latter is a self loop.
This issue has been resolved. Thank you for your help.