diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md
index f3c0d2a77..955808118 100644
--- a/ci/release/changelogs/next.md
+++ b/ci/release/changelogs/next.md
@@ -1,5 +1,5 @@
#### Features ๐
-
+- Circle notation is now supported. []
#### Improvements ๐งน
#### Bugfixes โ๏ธ
diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go
index 678f0c2ae..91b18c54a 100644
--- a/d2renderers/d2svg/d2svg.go
+++ b/d2renderers/d2svg/d2svg.go
@@ -114,6 +114,12 @@ func arrowheadDimensions(arrowhead d2target.Arrowhead, strokeWidth float64) (wid
case d2target.DiamondArrowhead:
widthMultiplier = 11
heightMultiplier = 9
+ case d2target.FilledCircleArrowhead:
+ widthMultiplier = 14
+ heightMultiplier = 15
+ case d2target.CircleArrowhead:
+ widthMultiplier = 14
+ heightMultiplier = 15
case d2target.CfOne, d2target.CfMany, d2target.CfOneRequired, d2target.CfManyRequired:
widthMultiplier = 14
heightMultiplier = 15
@@ -224,6 +230,39 @@ func arrowheadMarker(isTarget bool, id string, connection d2target.Connection) s
width*0.6, height*7/8,
)
}
+ case d2target.FilledCircleArrowhead:
+ attrs := fmt.Sprintf(`class="connection" fill="%s" stroke-width="%d"`, connection.Stroke, connection.StrokeWidth)
+ offset := 4.0 + float64(connection.StrokeWidth*2)
+ if isTarget {
+ path = fmt.Sprintf(``,
+ attrs,
+ offset+9, height/2,
+ offset*1.2,
+ )
+ } else {
+ path = fmt.Sprintf(``,
+ attrs,
+ offset+3, height/2,
+ offset*1.2,
+ )
+ }
+ case d2target.CircleArrowhead:
+ attrs := fmt.Sprintf(`class="connection" fill="white" stroke="%s" stroke-width="%d"`, connection.Stroke, connection.StrokeWidth)
+ offset := 4.0 + float64(connection.StrokeWidth*2)
+ if isTarget {
+ path = fmt.Sprintf(``,
+ attrs,
+ offset+10, height/2,
+ offset*1.2,
+ )
+ } else {
+ path = fmt.Sprintf(``,
+ attrs,
+ offset+6, height/2,
+ offset*1.2,
+ )
+ }
+
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)
@@ -277,6 +316,14 @@ func arrowheadMarker(isTarget bool, id string, connection d2target.Connection) s
refX = width/8 + 0.6*strokeWidth
}
width *= 1.1
+
+ case d2target.CircleArrowhead:
+ if isTarget {
+ refX = width - 0.6*strokeWidth
+ } else {
+ refX = width/8 + 0.6*strokeWidth
+ }
+ width *= 1.1
default:
if isTarget {
refX = width - 1.5*strokeWidth
diff --git a/d2target/d2target.go b/d2target/d2target.go
index b426adfd9..b324e4ff6 100644
--- a/d2target/d2target.go
+++ b/d2target/d2target.go
@@ -277,6 +277,8 @@ const (
TriangleArrowhead Arrowhead = "triangle"
DiamondArrowhead Arrowhead = "diamond"
FilledDiamondArrowhead Arrowhead = "filled-diamond"
+ CircleArrowhead Arrowhead = "circle"
+ FilledCircleArrowhead Arrowhead = "circle-filled"
// For fat arrows
LineArrowhead Arrowhead = "line"
@@ -294,6 +296,8 @@ var Arrowheads = map[string]struct{}{
string(TriangleArrowhead): {},
string(DiamondArrowhead): {},
string(FilledDiamondArrowhead): {},
+ string(CircleArrowhead): {},
+ string(FilledCircleArrowhead): {},
string(CfOne): {},
string(CfMany): {},
string(CfOneRequired): {},
@@ -307,6 +311,11 @@ func ToArrowhead(arrowheadType string, filled bool) Arrowhead {
return FilledDiamondArrowhead
}
return DiamondArrowhead
+ case string(CircleArrowhead):
+ if filled {
+ return FilledCircleArrowhead
+ }
+ return CircleArrowhead
case string(ArrowArrowhead):
return ArrowArrowhead
case string(CfOne):
diff --git a/e2etests/stable_test.go b/e2etests/stable_test.go
index a8acc138a..680a18045 100644
--- a/e2etests/stable_test.go
+++ b/e2etests/stable_test.go
@@ -1762,6 +1762,29 @@ e <--> f: {
target-arrowhead: {
shape: cf-one-required
}
+}`,
+ },
+ {
+ name: "circle_arrowhead",
+ script: `
+a <-> b: circle {
+ source-arrowhead: {
+ shape: circle
+ }
+ target-arrowhead: {
+ shape: circle
+ }
+}
+
+c <--> d: circle-filled {
+ source-arrowhead: {
+ shape: circle
+ style.filled: true
+ }
+ target-arrowhead: {
+ shape: circle
+ style.filled: true
+ }
}`,
},
}