self referencing edges

This commit is contained in:
Alexander Wang 2022-11-29 17:54:25 -08:00
parent 83362f762c
commit 2781d904be
No known key found for this signature in database
GPG key ID: D89FA31966BDBECE
10 changed files with 1095 additions and 14 deletions

View file

@ -1555,6 +1555,15 @@ dst.id <-> src.dst_id
expErr: `d2/testdata/d2compiler/TestCompile/invalid_direction.d2:2:14: direction must be one of up, down, right, left, got "diagonal"
`,
},
{
name: "self-referencing",
text: `x -> x
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
diff.AssertStringEq(t, g.Edges[0].Dst.ID, g.Edges[0].Src.ID)
},
},
}
for _, tc := range testCases {

View file

@ -727,10 +727,6 @@ func (obj *Object) Connect(srcID, dstID []string, srcArrow, dstArrow bool, label
src := srcObj.EnsureChild(srcID)
dst := dstObj.EnsureChild(dstID)
if src == dst {
return nil, errors.New("self-referencing connection")
}
edge := &Edge{
Attributes: Attributes{
Label: Scalar{

View file

@ -187,17 +187,19 @@ func Layout(ctx context.Context, g *d2graph.Graph) (err error) {
start, end := points[startIndex], points[endIndex]
// chop where edge crosses the source/target boxes since container edges were routed to a descendant
for i := 1; i < len(points); i++ {
segment := *geo.NewSegment(points[i-1], points[i])
if intersections := edge.Src.Box.Intersections(segment); len(intersections) > 0 {
start = intersections[0]
startIndex = i - 1
}
if edge.Src != edge.Dst {
for i := 1; i < len(points); i++ {
segment := *geo.NewSegment(points[i-1], points[i])
if intersections := edge.Src.Box.Intersections(segment); len(intersections) > 0 {
start = intersections[0]
startIndex = i - 1
}
if intersections := edge.Dst.Box.Intersections(segment); len(intersections) > 0 {
end = intersections[0]
endIndex = i
break
if intersections := edge.Dst.Box.Intersections(segment); len(intersections) > 0 {
end = intersections[0]
endIndex = i
break
}
}
}

View file

@ -82,6 +82,7 @@ type ELKLayoutOptions struct {
Padding string `json:"elk.padding,omitempty"`
EdgeNodeSpacing float64 `json:"spacing.edgeNodeBetweenLayers,omitempty"`
Direction string `json:"elk.direction"`
SelfLoopSpacing float64 `json:"elk.spacing.nodeSelfLoop"`
}
func Layout(ctx context.Context, g *d2graph.Graph) (err error) {
@ -121,6 +122,7 @@ func Layout(ctx context.Context, g *d2graph.Graph) (err error) {
HierarchyHandling: "INCLUDE_CHILDREN",
NodeSpacing: 100.0,
EdgeNodeSpacing: 50.0,
SelfLoopSpacing: 50.0,
},
}
switch g.Root.Attributes.Direction.Value {

View file

@ -1216,6 +1216,13 @@ finally.sequence.scorer -> finally.sequence.itemResponse.c`,
foo baz: Foo Baz
foo baz -> hello
`,
},
{
name: "self-referencing",
script: `x -> x -> x -> y
z -> y
z -> z: hello
`,
},
}

View file

@ -0,0 +1,472 @@
{
"name": "",
"shapes": [
{
"id": "x",
"type": "",
"pos": {
"x": 0,
"y": 0
},
"width": 113,
"height": 126,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#F7F8FE",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "x",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 13,
"labelHeight": 26,
"labelPosition": "INSIDE_MIDDLE_CENTER",
"zIndex": 0,
"level": 1
},
{
"id": "y",
"type": "",
"pos": {
"x": 136,
"y": 226
},
"width": 114,
"height": 126,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#F7F8FE",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "y",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 14,
"labelHeight": 26,
"labelPosition": "INSIDE_MIDDLE_CENTER",
"zIndex": 0,
"level": 1
},
{
"id": "z",
"type": "",
"pos": {
"x": 273,
"y": 0
},
"width": 112,
"height": 126,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#F7F8FE",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "z",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 12,
"labelHeight": 26,
"labelPosition": "INSIDE_MIDDLE_CENTER",
"zIndex": 0,
"level": 1
}
],
"connections": [
{
"id": "(x -> x)[0]",
"src": "x",
"srcArrow": "none",
"srcLabel": "",
"dst": "x",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 113,
"y": 24.794275491949918
},
{
"x": 142.33333333333334,
"y": 4.958855098389982
},
{
"x": 151.5,
"y": 0
},
{
"x": 154.25,
"y": 0
},
{
"x": 157,
"y": 0
},
{
"x": 160.66666666666666,
"y": 12.600000000000001
},
{
"x": 163.41666666666666,
"y": 31.5
},
{
"x": 166.16666666666666,
"y": 50.400000000000006
},
{
"x": 166.16666666666666,
"y": 75.6
},
{
"x": 163.41666666666666,
"y": 94.5
},
{
"x": 160.66666666666666,
"y": 113.4
},
{
"x": 142.33333333333334,
"y": 121.04114490161001
},
{
"x": 113,
"y": 101.20572450805008
}
],
"isCurve": true,
"animated": false,
"tooltip": "",
"icon": null,
"zIndex": 0
},
{
"id": "(x -> x)[1]",
"src": "x",
"srcArrow": "none",
"srcLabel": "",
"dst": "x",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 113,
"y": 34.86166007905138
},
{
"x": 169,
"y": 6.972332015810274
},
{
"x": 186.5,
"y": 0
},
{
"x": 191.75,
"y": 0
},
{
"x": 197,
"y": 0
},
{
"x": 204,
"y": 12.600000000000001
},
{
"x": 209.25,
"y": 31.5
},
{
"x": 214.5,
"y": 50.400000000000006
},
{
"x": 214.5,
"y": 75.6
},
{
"x": 209.25,
"y": 94.5
},
{
"x": 204,
"y": 113.4
},
{
"x": 169,
"y": 119.02766798418972
},
{
"x": 113,
"y": 91.13833992094862
}
],
"isCurve": true,
"animated": false,
"tooltip": "",
"icon": null,
"zIndex": 0
},
{
"id": "(x -> y)[0]",
"src": "x",
"srcArrow": "none",
"srcLabel": "",
"dst": "y",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 56.5,
"y": 126
},
{
"x": 56.5,
"y": 166
},
{
"x": 72.35,
"y": 189.14532110091744
},
{
"x": 135.75,
"y": 241.72660550458716
}
],
"isCurve": true,
"animated": false,
"tooltip": "",
"icon": null,
"zIndex": 0
},
{
"id": "(z -> y)[0]",
"src": "z",
"srcArrow": "none",
"srcLabel": "",
"dst": "y",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 329,
"y": 126
},
{
"x": 329,
"y": 166
},
{
"x": 313.2,
"y": 189.2
},
{
"x": 250,
"y": 242
}
],
"isCurve": true,
"animated": false,
"tooltip": "",
"icon": null,
"zIndex": 0
},
{
"id": "(z -> z)[0]",
"src": "z",
"srcArrow": "none",
"srcLabel": "",
"dst": "z",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "hello",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 33,
"labelHeight": 21,
"labelPosition": "INSIDE_MIDDLE_CENTER",
"labelPercentage": 0,
"route": [
{
"x": 385,
"y": 24.92805755395684
},
{
"x": 414.33333333333337,
"y": 4.985611510791365
},
{
"x": 423.5,
"y": 0
},
{
"x": 426.25,
"y": 0
},
{
"x": 429,
"y": 0
},
{
"x": 432.66666666666663,
"y": 12.600000000000001
},
{
"x": 435.41666666666663,
"y": 31.5
},
{
"x": 438.1666666666667,
"y": 50.400000000000006
},
{
"x": 438.1666666666667,
"y": 75.6
},
{
"x": 435.41666666666663,
"y": 94.5
},
{
"x": 432.66666666666663,
"y": 113.4
},
{
"x": 414.33333333333337,
"y": 121.01438848920863
},
{
"x": 385,
"y": 101.07194244604315
}
],
"isCurve": true,
"animated": false,
"tooltip": "",
"icon": null,
"zIndex": 0
}
]
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 469 KiB

View file

@ -0,0 +1,351 @@
{
"name": "",
"shapes": [
{
"id": "x",
"type": "",
"pos": {
"x": 389,
"y": 12
},
"width": 113,
"height": 126,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#F7F8FE",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "x",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 13,
"labelHeight": 26,
"labelPosition": "INSIDE_MIDDLE_CENTER",
"zIndex": 0,
"level": 1
},
{
"id": "y",
"type": "",
"pos": {
"x": 165,
"y": 238
},
"width": 114,
"height": 126,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#F7F8FE",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "y",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 14,
"labelHeight": 26,
"labelPosition": "INSIDE_MIDDLE_CENTER",
"zIndex": 0,
"level": 1
},
{
"id": "z",
"type": "",
"pos": {
"x": 147,
"y": 12
},
"width": 112,
"height": 126,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#F7F8FE",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "z",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 12,
"labelHeight": 26,
"labelPosition": "INSIDE_MIDDLE_CENTER",
"zIndex": 0,
"level": 1
}
],
"connections": [
{
"id": "(x -> x)[0]",
"src": "x",
"srcArrow": "none",
"srcLabel": "",
"dst": "x",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 389,
"y": 62.4
},
{
"x": 289,
"y": 62.4
},
{
"x": 289,
"y": 87.6
},
{
"x": 389,
"y": 87.6
}
],
"animated": false,
"tooltip": "",
"icon": null,
"zIndex": 0
},
{
"id": "(x -> x)[1]",
"src": "x",
"srcArrow": "none",
"srcLabel": "",
"dst": "x",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 389,
"y": 37.2
},
{
"x": 279,
"y": 37.2
},
{
"x": 279,
"y": 112.8
},
{
"x": 389,
"y": 112.8
}
],
"animated": false,
"tooltip": "",
"icon": null,
"zIndex": 0
},
{
"id": "(x -> y)[0]",
"src": "x",
"srcArrow": "none",
"srcLabel": "",
"dst": "y",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 445.5,
"y": 138
},
{
"x": 445.5,
"y": 188
},
{
"x": 241,
"y": 188
},
{
"x": 241,
"y": 238
}
],
"animated": false,
"tooltip": "",
"icon": null,
"zIndex": 0
},
{
"id": "(z -> y)[0]",
"src": "z",
"srcArrow": "none",
"srcLabel": "",
"dst": "y",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 203,
"y": 138
},
{
"x": 203,
"y": 238
}
],
"animated": false,
"tooltip": "",
"icon": null,
"zIndex": 0
},
{
"id": "(z -> z)[0]",
"src": "z",
"srcArrow": "none",
"srcLabel": "",
"dst": "z",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "hello",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 33,
"labelHeight": 21,
"labelPosition": "INSIDE_MIDDLE_CENTER",
"labelPercentage": 0,
"route": [
{
"x": 147,
"y": 54
},
{
"x": 47,
"y": 54
},
{
"x": 47,
"y": 96
},
{
"x": 147,
"y": 96
}
],
"animated": false,
"tooltip": "",
"icon": null,
"zIndex": 0
}
]
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 469 KiB

View file

@ -0,0 +1,174 @@
{
"graph": {
"ast": {
"range": "d2/testdata/d2compiler/TestCompile/self-referencing.d2,0:0:0-1:0:7",
"nodes": [
{
"map_key": {
"range": "d2/testdata/d2compiler/TestCompile/self-referencing.d2,0:0:0-0:6:6",
"edges": [
{
"range": "d2/testdata/d2compiler/TestCompile/self-referencing.d2,0:0:0-0:6:6",
"src": {
"range": "d2/testdata/d2compiler/TestCompile/self-referencing.d2,0:0:0-0:2:2",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/self-referencing.d2,0:0:0-0:1:1",
"value": [
{
"string": "x",
"raw_string": "x"
}
]
}
}
]
},
"src_arrow": "",
"dst": {
"range": "d2/testdata/d2compiler/TestCompile/self-referencing.d2,0:4:4-0:6:6",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/self-referencing.d2,0:5:5-0:6:6",
"value": [
{
"string": "x",
"raw_string": "x"
}
]
}
}
]
},
"dst_arrow": ">"
}
],
"primary": {},
"value": {}
}
}
]
},
"root": {
"id": "",
"id_val": "",
"label_dimensions": {
"width": 0,
"height": 0
},
"attributes": {
"label": {
"value": ""
},
"style": {},
"near_key": null,
"shape": {
"value": ""
},
"direction": {
"value": "down"
}
}
},
"edges": [
{
"index": 0,
"minWidth": 0,
"minHeight": 0,
"label_dimensions": {
"width": 0,
"height": 0
},
"isCurve": false,
"src_arrow": false,
"dst_arrow": true,
"references": [
{
"map_key_edge_index": 0
}
],
"attributes": {
"label": {
"value": ""
},
"style": {},
"near_key": null,
"shape": {
"value": ""
},
"direction": {
"value": ""
}
}
}
],
"objects": [
{
"id": "x",
"id_val": "x",
"label_dimensions": {
"width": 0,
"height": 0
},
"references": [
{
"key": {
"range": "d2/testdata/d2compiler/TestCompile/self-referencing.d2,0:0:0-0:2:2",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/self-referencing.d2,0:0:0-0:1:1",
"value": [
{
"string": "x",
"raw_string": "x"
}
]
}
}
]
},
"key_path_index": 0,
"map_key_edge_index": 0
},
{
"key": {
"range": "d2/testdata/d2compiler/TestCompile/self-referencing.d2,0:4:4-0:6:6",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/self-referencing.d2,0:5:5-0:6:6",
"value": [
{
"string": "x",
"raw_string": "x"
}
]
}
}
]
},
"key_path_index": 0,
"map_key_edge_index": 0
}
],
"attributes": {
"label": {
"value": "x"
},
"style": {},
"near_key": null,
"shape": {
"value": ""
},
"direction": {
"value": "down"
}
}
}
]
},
"err": null
}