Skip to content

Commit

Permalink
adjust label positioning for arrowhead
Browse files Browse the repository at this point in the history
  • Loading branch information
gavin-ts committed Apr 17, 2023
1 parent 29a9630 commit ca29119
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 78 deletions.
54 changes: 3 additions & 51 deletions d2renderers/d2svg/d2svg.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import (
"github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles"

"oss.terrastruct.com/util-go/go2"

"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2renderers/d2fonts"
"oss.terrastruct.com/d2/d2renderers/d2latex"
Expand All @@ -39,8 +37,7 @@ import (
)

const (
DEFAULT_PADDING = 100
MIN_ARROWHEAD_STROKE_WIDTH = 2
DEFAULT_PADDING = 100

appendixIconRadius = 16
)
Expand Down Expand Up @@ -109,56 +106,13 @@ func arrowheadMarkerID(isTarget bool, connection d2target.Connection) string {
)))
}

func arrowheadDimensions(arrowhead d2target.Arrowhead, strokeWidth float64) (width, height float64) {
var baseWidth, baseHeight float64
var widthMultiplier, heightMultiplier float64
switch arrowhead {
case d2target.ArrowArrowhead:
baseWidth = 4
baseHeight = 4
widthMultiplier = 4
heightMultiplier = 4
case d2target.TriangleArrowhead:
baseWidth = 4
baseHeight = 4
widthMultiplier = 3
heightMultiplier = 4
case d2target.LineArrowhead:
widthMultiplier = 5
heightMultiplier = 8
case d2target.FilledDiamondArrowhead:
baseWidth = 11
baseHeight = 7
widthMultiplier = 5.5
heightMultiplier = 3.5
case d2target.DiamondArrowhead:
baseWidth = 11
baseHeight = 9
widthMultiplier = 5.5
heightMultiplier = 4.5
case d2target.FilledCircleArrowhead, d2target.CircleArrowhead:
baseWidth = 8
baseHeight = 8
widthMultiplier = 5
heightMultiplier = 5
case d2target.CfOne, d2target.CfMany, d2target.CfOneRequired, d2target.CfManyRequired:
baseWidth = 9
baseHeight = 9
widthMultiplier = 4.5
heightMultiplier = 4.5
}

clippedStrokeWidth := go2.Max(MIN_ARROWHEAD_STROKE_WIDTH, strokeWidth)
return baseWidth + clippedStrokeWidth*widthMultiplier, baseHeight + clippedStrokeWidth*heightMultiplier
}

func arrowheadMarker(isTarget bool, id string, connection d2target.Connection) string {
arrowhead := connection.DstArrow
if !isTarget {
arrowhead = connection.SrcArrow
}
strokeWidth := float64(connection.StrokeWidth)
width, height := arrowheadDimensions(arrowhead, strokeWidth)
width, height := arrowhead.Dimensions(strokeWidth)

var path string
switch arrowhead {
Expand Down Expand Up @@ -621,11 +575,9 @@ func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Co
}

if connection.SrcLabel != nil && connection.SrcLabel.Label != "" {
// TODO use arrowhead label dimensions https://github.com/terrastruct/d2/issues/183
fmt.Fprint(writer, renderArrowheadLabel(connection, connection.SrcLabel.Label, false))
}
if connection.DstLabel != nil && connection.DstLabel.Label != "" {
// TODO use arrowhead label dimensions https://github.com/terrastruct/d2/issues/183
fmt.Fprint(writer, renderArrowheadLabel(connection, connection.DstLabel.Label, true))
}
fmt.Fprintf(writer, `</g>`)
Expand All @@ -642,7 +594,7 @@ func renderArrowheadLabel(connection d2target.Connection, text string, isDst boo
height = float64(connection.SrcLabel.LabelHeight)
}

labelTL := connection.GetArrowHeadLabelPosition(isDst)
labelTL := connection.GetArrowheadLabelPosition(isDst)

// svg text is positioned with the center of its baseline
baselineCenter := geo.Point{
Expand Down
94 changes: 67 additions & 27 deletions d2target/d2target.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const (

BG_COLOR = color.N7
FG_COLOR = color.N1

MIN_ARROWHEAD_STROKE_WIDTH = 2
ARROWHEAD_PADDING = 2.
)

var BorderOffset = geo.NewVector(5, 5)
Expand Down Expand Up @@ -233,14 +236,14 @@ func (diagram Diagram) BoundingBox() (topLeft, bottomRight Point) {
y2 = go2.Max(y2, int(labelTL.Y)+connection.LabelHeight)
}
if connection.SrcLabel != nil && connection.SrcLabel.Label != "" {
labelTL := connection.GetArrowHeadLabelPosition(false)
labelTL := connection.GetArrowheadLabelPosition(false)
x1 = go2.Min(x1, int(labelTL.X))
y1 = go2.Min(y1, int(labelTL.Y))
x2 = go2.Max(x2, int(labelTL.X)+connection.SrcLabel.LabelWidth)
y2 = go2.Max(y2, int(labelTL.Y)+connection.SrcLabel.LabelHeight)
}
if connection.DstLabel != nil && connection.DstLabel.Label != "" {
labelTL := connection.GetArrowHeadLabelPosition(true)
labelTL := connection.GetArrowheadLabelPosition(true)
x1 = go2.Min(x1, int(labelTL.X))
y1 = go2.Min(y1, int(labelTL.Y))
x2 = go2.Max(x2, int(labelTL.X)+connection.DstLabel.LabelWidth)
Expand Down Expand Up @@ -534,7 +537,7 @@ func (c *Connection) GetLabelTopLeft() *geo.Point {
return point
}

func (connection *Connection) GetArrowHeadLabelPosition(isDst bool) *geo.Point {
func (connection *Connection) GetArrowheadLabelPosition(isDst bool) *geo.Point {
var width, height float64
if isDst {
width = float64(connection.DstLabel.LabelWidth)
Expand Down Expand Up @@ -578,36 +581,30 @@ func (connection *Connection) GetArrowHeadLabelPosition(isDst bool) *geo.Point {
}
}

labelTL, index := label.UnlockedTop.GetPointOnRoute(connection.Route, float64(connection.StrokeWidth), position, width, height)
strokeWidth := float64(connection.StrokeWidth)

labelTL, index := label.UnlockedTop.GetPointOnRoute(connection.Route, strokeWidth, position, width, height)

var arrowheadOffset float64
var arrowSize float64
if isDst && connection.DstArrow != NoArrowhead {
// TODO offset according to arrowhead dimensions
arrowheadOffset = 5
// Note: these dimensions are for rendering arrowheads on their side so we want the height
_, arrowSize = connection.DstArrow.Dimensions(strokeWidth)
} else if connection.SrcArrow != NoArrowhead {
arrowheadOffset = 5
}

var offsetX, offsetY float64
// get the start/end points of edge segment with arrowhead
start, end = connection.Route[index], connection.Route[index+1]
if start.Y == end.Y {
// shift up/down over horizontal segment
offsetY = arrowheadOffset
if end.Y < start.Y {
offsetY = -offsetY
}
} else if start.X == end.X {
// shift left/right across vertical segment
offsetX = arrowheadOffset
if end.X < start.X {
offsetX = -offsetX
_, arrowSize = connection.SrcArrow.Dimensions(strokeWidth)
}

if arrowSize > 0 {
// labelTL already accounts for strokeWidth and padding, we only want to shift further if the arrow is larger than this
offset := (arrowSize/2 + ARROWHEAD_PADDING) - strokeWidth/2 - label.PADDING
if offset > 0 {
start, end = connection.Route[index], connection.Route[index+1]
// Note: end to start to get normal towards unlocked top position
normalX, normalY := geo.GetUnitNormalVector(end.X, end.Y, start.X, start.Y)
labelTL.X += normalX * offset
labelTL.Y += normalY * offset
}
}

labelTL.X += offsetX
labelTL.Y += offsetY

return labelTL
}

Expand Down Expand Up @@ -681,6 +678,49 @@ func ToArrowhead(arrowheadType string, filled bool) Arrowhead {
}
}

func (arrowhead Arrowhead) Dimensions(strokeWidth float64) (width, height float64) {
var baseWidth, baseHeight float64
var widthMultiplier, heightMultiplier float64
switch arrowhead {
case ArrowArrowhead:
baseWidth = 4
baseHeight = 4
widthMultiplier = 4
heightMultiplier = 4
case TriangleArrowhead:
baseWidth = 4
baseHeight = 4
widthMultiplier = 3
heightMultiplier = 4
case LineArrowhead:
widthMultiplier = 5
heightMultiplier = 8
case FilledDiamondArrowhead:
baseWidth = 11
baseHeight = 7
widthMultiplier = 5.5
heightMultiplier = 3.5
case DiamondArrowhead:
baseWidth = 11
baseHeight = 9
widthMultiplier = 5.5
heightMultiplier = 4.5
case FilledCircleArrowhead, CircleArrowhead:
baseWidth = 8
baseHeight = 8
widthMultiplier = 5
heightMultiplier = 5
case CfOne, CfMany, CfOneRequired, CfManyRequired:
baseWidth = 9
baseHeight = 9
widthMultiplier = 4.5
heightMultiplier = 4.5
}

clippedStrokeWidth := go2.Max(MIN_ARROWHEAD_STROKE_WIDTH, strokeWidth)
return baseWidth + clippedStrokeWidth*widthMultiplier, baseHeight + clippedStrokeWidth*heightMultiplier
}

type Point struct {
X int `json:"x"`
Y int `json:"y"`
Expand Down

0 comments on commit ca29119

Please sign in to comment.