diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md
index dc555bd02..3899ec4f7 100644
--- a/ci/release/changelogs/next.md
+++ b/ci/release/changelogs/next.md
@@ -1,5 +1,7 @@
#### Features ๐
+- Crow foot notation is now supported. [#578](https://github.com/terrastruct/d2/pull/578)
+
#### Improvements ๐งน
#### Bugfixes โ๏ธ
diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go
index cc735055a..2f7f931f0 100644
--- a/d2renderers/d2svg/d2svg.go
+++ b/d2renderers/d2svg/d2svg.go
@@ -113,6 +113,9 @@ func arrowheadDimensions(arrowhead d2target.Arrowhead, strokeWidth float64) (wid
case d2target.DiamondArrowhead:
widthMultiplier = 11
heightMultiplier = 9
+ case d2target.CfOne, d2target.CfMany, d2target.CfOneRequired, d2target.CfManyRequired:
+ widthMultiplier = 14
+ heightMultiplier = 15
}
clippedStrokeWidth := go2.Max(MIN_ARROWHEAD_STROKE_WIDTH, strokeWidth)
@@ -220,6 +223,45 @@ func arrowheadMarker(isTarget bool, id string, connection d2target.Connection) s
width*0.6, height*7/8,
)
}
+ case d2target.CfOne, d2target.CfMany, d2target.CfOneRequired, d2target.CfManyRequired:
+ attrs := fmt.Sprintf(`class="connection" stroke="%s" stroke-width="%d" fill="white"`, connection.Stroke, connection.StrokeWidth)
+ offset := 4.0 + float64(connection.StrokeWidth*2)
+ var modifier string
+ if arrowhead == d2target.CfOneRequired || arrowhead == d2target.CfManyRequired {
+ modifier = fmt.Sprintf(``,
+ attrs,
+ offset, 0.,
+ offset, height,
+ )
+ } else {
+ modifier = fmt.Sprintf(``,
+ attrs,
+ offset/2.0+1.0, height/2.0,
+ offset/2.0,
+ )
+ }
+ if !isTarget {
+ attrs = fmt.Sprintf(`%s transform="scale(-1) translate(-%f, -%f)"`, attrs, width, height)
+ }
+ if arrowhead == d2target.CfMany || arrowhead == d2target.CfManyRequired {
+ path = fmt.Sprintf(`%s`,
+ attrs, modifier,
+ width-3.0, height/2.0,
+ width+offset, height/2.0,
+ offset+2.0, height/2.0,
+ width+offset, 0.,
+ offset+2.0, height/2.0,
+ width+offset, height,
+ )
+ } else {
+ path = fmt.Sprintf(`%s`,
+ attrs, modifier,
+ width-3.0, height/2.0,
+ width+offset, height/2.0,
+ offset*1.8, 0.,
+ offset*1.8, height,
+ )
+ }
default:
return ""
}
diff --git a/d2target/d2target.go b/d2target/d2target.go
index ea54ee52f..b426adfd9 100644
--- a/d2target/d2target.go
+++ b/d2target/d2target.go
@@ -280,6 +280,12 @@ const (
// For fat arrows
LineArrowhead Arrowhead = "line"
+
+ // Crows feet notation
+ CfOne Arrowhead = "cf-one"
+ CfMany Arrowhead = "cf-many"
+ CfOneRequired Arrowhead = "cf-one-required"
+ CfManyRequired Arrowhead = "cf-many-required"
)
var Arrowheads = map[string]struct{}{
@@ -288,6 +294,10 @@ var Arrowheads = map[string]struct{}{
string(TriangleArrowhead): {},
string(DiamondArrowhead): {},
string(FilledDiamondArrowhead): {},
+ string(CfOne): {},
+ string(CfMany): {},
+ string(CfOneRequired): {},
+ string(CfManyRequired): {},
}
func ToArrowhead(arrowheadType string, filled bool) Arrowhead {
@@ -299,6 +309,14 @@ func ToArrowhead(arrowheadType string, filled bool) Arrowhead {
return DiamondArrowhead
case string(ArrowArrowhead):
return ArrowArrowhead
+ case string(CfOne):
+ return CfOne
+ case string(CfMany):
+ return CfMany
+ case string(CfOneRequired):
+ return CfOneRequired
+ case string(CfManyRequired):
+ return CfManyRequired
default:
return TriangleArrowhead
}
diff --git a/e2etests/stable_test.go b/e2etests/stable_test.go
index 6590d295e..a8acc138a 100644
--- a/e2etests/stable_test.go
+++ b/e2etests/stable_test.go
@@ -1724,6 +1724,46 @@ code.height: 512
package.height: 512
`,
},
+ {
+ name: "crow_foot_arrowhead",
+ script: `
+a <-> b: {
+ style.stroke-width: 3
+ style.stroke: "#20222a"
+ source-arrowhead: {
+ shape: cf-many
+ }
+ target-arrowhead: {
+ shape: cf-many
+ }
+}
+c <--> d <-> f: {
+ style.stroke-width: 1
+ style.stroke: "orange"
+ source-arrowhead: {
+ shape: cf-many-required
+ }
+ target-arrowhead: {
+ shape: cf-many-required
+ }
+}
+g <--> h: {
+ source-arrowhead: {
+ shape: cf-one
+ }
+ target-arrowhead: {
+ shape: cf-one
+ }
+}
+e <--> f: {
+ source-arrowhead: {
+ shape: cf-one-required
+ }
+ target-arrowhead: {
+ shape: cf-one-required
+ }
+}`,
+ },
}
runa(t, tcs)
diff --git a/e2etests/testdata/stable/crow_foot_arrowhead/dagre/board.exp.json b/e2etests/testdata/stable/crow_foot_arrowhead/dagre/board.exp.json
new file mode 100644
index 000000000..0784c5506
--- /dev/null
+++ b/e2etests/testdata/stable/crow_foot_arrowhead/dagre/board.exp.json
@@ -0,0 +1,568 @@
+{
+ "name": "",
+ "fontFamily": "SourceSansPro",
+ "shapes": [
+ {
+ "id": "a",
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "a",
+ "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": "b",
+ "type": "",
+ "pos": {
+ "x": 0,
+ "y": 226
+ },
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "b",
+ "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": "c",
+ "type": "",
+ "pos": {
+ "x": 174,
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "c",
+ "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": "d",
+ "type": "",
+ "pos": {
+ "x": 173,
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "d",
+ "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": "f",
+ "type": "",
+ "pos": {
+ "x": 348,
+ "y": 452
+ },
+ "width": 111,
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "f",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "#0A0F25",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 11,
+ "labelHeight": 26,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 1
+ },
+ {
+ "id": "g",
+ "type": "",
+ "pos": {
+ "x": 347,
+ "y": 0
+ },
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "g",
+ "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": "h",
+ "type": "",
+ "pos": {
+ "x": 347,
+ "y": 226
+ },
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "h",
+ "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": "e",
+ "type": "",
+ "pos": {
+ "x": 520,
+ "y": 226
+ },
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "e",
+ "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
+ }
+ ],
+ "connections": [
+ {
+ "id": "(a <-> b)[0]",
+ "src": "a",
+ "srcArrow": "cf-many",
+ "srcLabel": "",
+ "dst": "b",
+ "dstArrow": "cf-many",
+ "dstLabel": "",
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 3,
+ "stroke": "#20222a",
+ "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": 56.5,
+ "y": 186
+ },
+ {
+ "x": 56.5,
+ "y": 226
+ }
+ ],
+ "isCurve": true,
+ "animated": false,
+ "tooltip": "",
+ "icon": null,
+ "zIndex": 0
+ },
+ {
+ "id": "(c <-> d)[0]",
+ "src": "c",
+ "srcArrow": "cf-many-required",
+ "srcLabel": "",
+ "dst": "d",
+ "dstArrow": "cf-many-required",
+ "dstLabel": "",
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 1,
+ "stroke": "orange",
+ "label": "",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "#676C7E",
+ "italic": true,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 0,
+ "labelHeight": 0,
+ "labelPosition": "",
+ "labelPercentage": 0,
+ "route": [
+ {
+ "x": 230,
+ "y": 126
+ },
+ {
+ "x": 230,
+ "y": 166
+ },
+ {
+ "x": 230,
+ "y": 186
+ },
+ {
+ "x": 230,
+ "y": 226
+ }
+ ],
+ "isCurve": true,
+ "animated": false,
+ "tooltip": "",
+ "icon": null,
+ "zIndex": 0
+ },
+ {
+ "id": "(d <-> f)[0]",
+ "src": "d",
+ "srcArrow": "cf-many-required",
+ "srcLabel": "",
+ "dst": "f",
+ "dstArrow": "cf-many-required",
+ "dstLabel": "",
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 1,
+ "stroke": "orange",
+ "label": "",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "#676C7E",
+ "italic": true,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 0,
+ "labelHeight": 0,
+ "labelPosition": "",
+ "labelPercentage": 0,
+ "route": [
+ {
+ "x": 230,
+ "y": 352
+ },
+ {
+ "x": 230,
+ "y": 392
+ },
+ {
+ "x": 253.55,
+ "y": 417.36017316017313
+ },
+ {
+ "x": 347.75,
+ "y": 478.8008658008658
+ }
+ ],
+ "isCurve": true,
+ "animated": false,
+ "tooltip": "",
+ "icon": null,
+ "zIndex": 0
+ },
+ {
+ "id": "(g <-> h)[0]",
+ "src": "g",
+ "srcArrow": "cf-one",
+ "srcLabel": "",
+ "dst": "h",
+ "dstArrow": "cf-one",
+ "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": 403.5,
+ "y": 126
+ },
+ {
+ "x": 403.5,
+ "y": 166
+ },
+ {
+ "x": 403.5,
+ "y": 186
+ },
+ {
+ "x": 403.5,
+ "y": 226
+ }
+ ],
+ "isCurve": true,
+ "animated": false,
+ "tooltip": "",
+ "icon": null,
+ "zIndex": 0
+ },
+ {
+ "id": "(e <-> f)[0]",
+ "src": "e",
+ "srcArrow": "cf-one-required",
+ "srcLabel": "",
+ "dst": "f",
+ "dstArrow": "cf-one-required",
+ "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": 576.5,
+ "y": 352
+ },
+ {
+ "x": 576.5,
+ "y": 392
+ },
+ {
+ "x": 552.9,
+ "y": 417.4
+ },
+ {
+ "x": 458.5,
+ "y": 479
+ }
+ ],
+ "isCurve": true,
+ "animated": false,
+ "tooltip": "",
+ "icon": null,
+ "zIndex": 0
+ }
+ ]
+}
diff --git a/e2etests/testdata/stable/crow_foot_arrowhead/dagre/sketch.exp.svg b/e2etests/testdata/stable/crow_foot_arrowhead/dagre/sketch.exp.svg
new file mode 100644
index 000000000..cc439add7
--- /dev/null
+++ b/e2etests/testdata/stable/crow_foot_arrowhead/dagre/sketch.exp.svg
@@ -0,0 +1,31 @@
+
+
\ No newline at end of file
diff --git a/e2etests/testdata/stable/crow_foot_arrowhead/elk/board.exp.json b/e2etests/testdata/stable/crow_foot_arrowhead/elk/board.exp.json
new file mode 100644
index 000000000..d87fec01c
--- /dev/null
+++ b/e2etests/testdata/stable/crow_foot_arrowhead/elk/board.exp.json
@@ -0,0 +1,531 @@
+{
+ "name": "",
+ "fontFamily": "SourceSansPro",
+ "shapes": [
+ {
+ "id": "a",
+ "type": "",
+ "pos": {
+ "x": 12,
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "a",
+ "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": "b",
+ "type": "",
+ "pos": {
+ "x": 12,
+ "y": 238
+ },
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "b",
+ "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": "c",
+ "type": "",
+ "pos": {
+ "x": 145,
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "c",
+ "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": "d",
+ "type": "",
+ "pos": {
+ "x": 145,
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "d",
+ "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": "f",
+ "type": "",
+ "pos": {
+ "x": 165,
+ "y": 464
+ },
+ "width": 111,
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "f",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "#0A0F25",
+ "italic": false,
+ "bold": true,
+ "underline": false,
+ "labelWidth": 11,
+ "labelHeight": 26,
+ "labelPosition": "INSIDE_MIDDLE_CENTER",
+ "zIndex": 0,
+ "level": 1
+ },
+ {
+ "id": "g",
+ "type": "",
+ "pos": {
+ "x": 278,
+ "y": 12
+ },
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "g",
+ "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": "h",
+ "type": "",
+ "pos": {
+ "x": 279,
+ "y": 238
+ },
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "h",
+ "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": "e",
+ "type": "",
+ "pos": {
+ "x": 412,
+ "y": 238
+ },
+ "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": "",
+ "blend": false,
+ "fields": null,
+ "methods": null,
+ "columns": null,
+ "label": "e",
+ "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
+ }
+ ],
+ "connections": [
+ {
+ "id": "(a <-> b)[0]",
+ "src": "a",
+ "srcArrow": "cf-many",
+ "srcLabel": "",
+ "dst": "b",
+ "dstArrow": "cf-many",
+ "dstLabel": "",
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 3,
+ "stroke": "#20222a",
+ "label": "",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "#676C7E",
+ "italic": true,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 0,
+ "labelHeight": 0,
+ "labelPosition": "",
+ "labelPercentage": 0,
+ "route": [
+ {
+ "x": 68.5,
+ "y": 138
+ },
+ {
+ "x": 68.5,
+ "y": 238
+ }
+ ],
+ "animated": false,
+ "tooltip": "",
+ "icon": null,
+ "zIndex": 0
+ },
+ {
+ "id": "(c <-> d)[0]",
+ "src": "c",
+ "srcArrow": "cf-many-required",
+ "srcLabel": "",
+ "dst": "d",
+ "dstArrow": "cf-many-required",
+ "dstLabel": "",
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 1,
+ "stroke": "orange",
+ "label": "",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "#676C7E",
+ "italic": true,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 0,
+ "labelHeight": 0,
+ "labelPosition": "",
+ "labelPercentage": 0,
+ "route": [
+ {
+ "x": 202,
+ "y": 138
+ },
+ {
+ "x": 202,
+ "y": 238
+ }
+ ],
+ "animated": false,
+ "tooltip": "",
+ "icon": null,
+ "zIndex": 0
+ },
+ {
+ "id": "(d <-> f)[0]",
+ "src": "d",
+ "srcArrow": "cf-many-required",
+ "srcLabel": "",
+ "dst": "f",
+ "dstArrow": "cf-many-required",
+ "dstLabel": "",
+ "opacity": 1,
+ "strokeDash": 0,
+ "strokeWidth": 1,
+ "stroke": "orange",
+ "label": "",
+ "fontSize": 16,
+ "fontFamily": "DEFAULT",
+ "language": "",
+ "color": "#676C7E",
+ "italic": true,
+ "bold": false,
+ "underline": false,
+ "labelWidth": 0,
+ "labelHeight": 0,
+ "labelPosition": "",
+ "labelPercentage": 0,
+ "route": [
+ {
+ "x": 202,
+ "y": 364
+ },
+ {
+ "x": 202,
+ "y": 464
+ }
+ ],
+ "animated": false,
+ "tooltip": "",
+ "icon": null,
+ "zIndex": 0
+ },
+ {
+ "id": "(g <-> h)[0]",
+ "src": "g",
+ "srcArrow": "cf-one",
+ "srcLabel": "",
+ "dst": "h",
+ "dstArrow": "cf-one",
+ "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": 335.5,
+ "y": 138
+ },
+ {
+ "x": 335.5,
+ "y": 238
+ }
+ ],
+ "animated": false,
+ "tooltip": "",
+ "icon": null,
+ "zIndex": 0
+ },
+ {
+ "id": "(e <-> f)[0]",
+ "src": "e",
+ "srcArrow": "cf-one-required",
+ "srcLabel": "",
+ "dst": "f",
+ "dstArrow": "cf-one-required",
+ "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": 468.5,
+ "y": 364
+ },
+ {
+ "x": 468.5,
+ "y": 414
+ },
+ {
+ "x": 239,
+ "y": 414
+ },
+ {
+ "x": 239,
+ "y": 464
+ }
+ ],
+ "animated": false,
+ "tooltip": "",
+ "icon": null,
+ "zIndex": 0
+ }
+ ]
+}
diff --git a/e2etests/testdata/stable/crow_foot_arrowhead/elk/sketch.exp.svg b/e2etests/testdata/stable/crow_foot_arrowhead/elk/sketch.exp.svg
new file mode 100644
index 000000000..e2f0d4b1d
--- /dev/null
+++ b/e2etests/testdata/stable/crow_foot_arrowhead/elk/sketch.exp.svg
@@ -0,0 +1,31 @@
+
+
\ No newline at end of file