diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go
index cc735055a..056167495 100644
--- a/d2renderers/d2svg/d2svg.go
+++ b/d2renderers/d2svg/d2svg.go
@@ -113,6 +113,15 @@ func arrowheadDimensions(arrowhead d2target.Arrowhead, strokeWidth float64) (wid
case d2target.DiamondArrowhead:
widthMultiplier = 11
heightMultiplier = 9
+ case d2target.CrowsFeetManyOptional:
+ fallthrough
+ case d2target.CrowsFeetManyRequired:
+ fallthrough
+ case d2target.CrowsFeetOneOptional:
+ fallthrough
+ case d2target.CrowsFeetOneRequired:
+ widthMultiplier = 14
+ heightMultiplier = 15
}
clippedStrokeWidth := go2.Max(MIN_ARROWHEAD_STROKE_WIDTH, strokeWidth)
@@ -220,6 +229,53 @@ func arrowheadMarker(isTarget bool, id string, connection d2target.Connection) s
width*0.6, height*7/8,
)
}
+ case d2target.CrowsFeetOneRequired:
+ fallthrough
+ case d2target.CrowsFeetOneOptional:
+ fallthrough
+ case d2target.CrowsFeetManyRequired:
+ fallthrough
+ case d2target.CrowsFeetManyOptional:
+ attrs := fmt.Sprintf(`class="connection" stroke="%s" stroke-width="%d" fill="white"`, connection.Stroke, connection.StrokeWidth)
+ offset := 4.0 + (float64(connection.StrokeWidth) * 2.0)
+ var modifier string
+ if arrowhead == d2target.CrowsFeetManyRequired || arrowhead == d2target.CrowsFeetOneRequired {
+ modifier = fmt.Sprintf(``,
+ attrs,
+ width, height/2.0,
+ offset/2.0+1.0, height/2.0,
+ 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.CrowsFeetManyOptional || arrowhead == d2target.CrowsFeetManyRequired {
+ path = fmt.Sprintf(`%s`,
+ attrs, modifier,
+ offset+2.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,
+ offset+2.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..58fe683e3 100644
--- a/d2target/d2target.go
+++ b/d2target/d2target.go
@@ -280,6 +280,12 @@ const (
// For fat arrows
LineArrowhead Arrowhead = "line"
+
+ // Crows feet notation
+ CrowsFeetManyRequired Arrowhead = "crows-feet-many-required"
+ CrowsFeetManyOptional Arrowhead = "crows-feet-many-optional"
+ CrowsFeetOneRequired Arrowhead = "crows-feet-one-required"
+ CrowsFeetOneOptional Arrowhead = "crows-feet-one-optional"
)
var Arrowheads = map[string]struct{}{
@@ -288,6 +294,10 @@ var Arrowheads = map[string]struct{}{
string(TriangleArrowhead): {},
string(DiamondArrowhead): {},
string(FilledDiamondArrowhead): {},
+ string(CrowsFeetManyRequired): {},
+ string(CrowsFeetManyOptional): {},
+ string(CrowsFeetOneRequired): {},
+ string(CrowsFeetOneOptional): {},
}
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(CrowsFeetManyRequired):
+ return CrowsFeetManyRequired
+ case string(CrowsFeetManyOptional):
+ return CrowsFeetManyOptional
+ case string(CrowsFeetOneRequired):
+ return CrowsFeetOneRequired
+ case string(CrowsFeetOneOptional):
+ return CrowsFeetOneOptional
default:
return TriangleArrowhead
}