diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go
index b6c5eba12..d55769c08 100644
--- a/d2compiler/compile_test.go
+++ b/d2compiler/compile_test.go
@@ -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 {
diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go
index fa5dc3bc6..615f862e4 100644
--- a/d2graph/d2graph.go
+++ b/d2graph/d2graph.go
@@ -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{
diff --git a/d2layouts/d2dagrelayout/layout.go b/d2layouts/d2dagrelayout/layout.go
index a380d6f10..e4543573e 100644
--- a/d2layouts/d2dagrelayout/layout.go
+++ b/d2layouts/d2dagrelayout/layout.go
@@ -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
+ }
}
}
diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go
index e88f01d74..811cd1a14 100644
--- a/d2layouts/d2elklayout/layout.go
+++ b/d2layouts/d2elklayout/layout.go
@@ -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 {
diff --git a/e2etests/stable_test.go b/e2etests/stable_test.go
index 6bc25dcf1..10720af33 100644
--- a/e2etests/stable_test.go
+++ b/e2etests/stable_test.go
@@ -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
`,
},
}
diff --git a/e2etests/testdata/stable/self-referencing/dagre/board.exp.json b/e2etests/testdata/stable/self-referencing/dagre/board.exp.json
new file mode 100644
index 000000000..7b3ee3a4d
--- /dev/null
+++ b/e2etests/testdata/stable/self-referencing/dagre/board.exp.json
@@ -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
+ }
+ ]
+}
diff --git a/e2etests/testdata/stable/self-referencing/dagre/sketch.exp.svg b/e2etests/testdata/stable/self-referencing/dagre/sketch.exp.svg
new file mode 100644
index 000000000..b1d6be6ed
--- /dev/null
+++ b/e2etests/testdata/stable/self-referencing/dagre/sketch.exp.svg
@@ -0,0 +1,34 @@
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/stable/self-referencing/elk/board.exp.json b/e2etests/testdata/stable/self-referencing/elk/board.exp.json
new file mode 100644
index 000000000..beeb453d1
--- /dev/null
+++ b/e2etests/testdata/stable/self-referencing/elk/board.exp.json
@@ -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
+ }
+ ]
+}
diff --git a/e2etests/testdata/stable/self-referencing/elk/sketch.exp.svg b/e2etests/testdata/stable/self-referencing/elk/sketch.exp.svg
new file mode 100644
index 000000000..bf76bff94
--- /dev/null
+++ b/e2etests/testdata/stable/self-referencing/elk/sketch.exp.svg
@@ -0,0 +1,34 @@
+
+
\ No newline at end of file
diff --git a/testdata/d2compiler/TestCompile/self-referencing.exp.json b/testdata/d2compiler/TestCompile/self-referencing.exp.json
new file mode 100644
index 000000000..633db36a2
--- /dev/null
+++ b/testdata/d2compiler/TestCompile/self-referencing.exp.json
@@ -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
+}