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 }