adjust label positioning for arrowhead

This commit is contained in:
Gavin Nishizawa 2023-04-17 12:06:17 -07:00
parent 29a9630635
commit ca29119c60
No known key found for this signature in database
GPG key ID: AE3B177777CE55CD
2 changed files with 68 additions and 76 deletions

View file

@ -20,8 +20,6 @@ import (
"github.com/alecthomas/chroma/v2/lexers" "github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles" "github.com/alecthomas/chroma/v2/styles"
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2renderers/d2fonts" "oss.terrastruct.com/d2/d2renderers/d2fonts"
"oss.terrastruct.com/d2/d2renderers/d2latex" "oss.terrastruct.com/d2/d2renderers/d2latex"
@ -39,8 +37,7 @@ import (
) )
const ( const (
DEFAULT_PADDING = 100 DEFAULT_PADDING = 100
MIN_ARROWHEAD_STROKE_WIDTH = 2
appendixIconRadius = 16 appendixIconRadius = 16
) )
@ -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 { func arrowheadMarker(isTarget bool, id string, connection d2target.Connection) string {
arrowhead := connection.DstArrow arrowhead := connection.DstArrow
if !isTarget { if !isTarget {
arrowhead = connection.SrcArrow arrowhead = connection.SrcArrow
} }
strokeWidth := float64(connection.StrokeWidth) strokeWidth := float64(connection.StrokeWidth)
width, height := arrowheadDimensions(arrowhead, strokeWidth) width, height := arrowhead.Dimensions(strokeWidth)
var path string var path string
switch arrowhead { switch arrowhead {
@ -621,11 +575,9 @@ func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Co
} }
if connection.SrcLabel != nil && connection.SrcLabel.Label != "" { 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)) fmt.Fprint(writer, renderArrowheadLabel(connection, connection.SrcLabel.Label, false))
} }
if connection.DstLabel != nil && connection.DstLabel.Label != "" { 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.Fprint(writer, renderArrowheadLabel(connection, connection.DstLabel.Label, true))
} }
fmt.Fprintf(writer, `</g>`) fmt.Fprintf(writer, `</g>`)
@ -642,7 +594,7 @@ func renderArrowheadLabel(connection d2target.Connection, text string, isDst boo
height = float64(connection.SrcLabel.LabelHeight) height = float64(connection.SrcLabel.LabelHeight)
} }
labelTL := connection.GetArrowHeadLabelPosition(isDst) labelTL := connection.GetArrowheadLabelPosition(isDst)
// svg text is positioned with the center of its baseline // svg text is positioned with the center of its baseline
baselineCenter := geo.Point{ baselineCenter := geo.Point{

View file

@ -29,6 +29,9 @@ const (
BG_COLOR = color.N7 BG_COLOR = color.N7
FG_COLOR = color.N1 FG_COLOR = color.N1
MIN_ARROWHEAD_STROKE_WIDTH = 2
ARROWHEAD_PADDING = 2.
) )
var BorderOffset = geo.NewVector(5, 5) var BorderOffset = geo.NewVector(5, 5)
@ -233,14 +236,14 @@ func (diagram Diagram) BoundingBox() (topLeft, bottomRight Point) {
y2 = go2.Max(y2, int(labelTL.Y)+connection.LabelHeight) y2 = go2.Max(y2, int(labelTL.Y)+connection.LabelHeight)
} }
if connection.SrcLabel != nil && connection.SrcLabel.Label != "" { if connection.SrcLabel != nil && connection.SrcLabel.Label != "" {
labelTL := connection.GetArrowHeadLabelPosition(false) labelTL := connection.GetArrowheadLabelPosition(false)
x1 = go2.Min(x1, int(labelTL.X)) x1 = go2.Min(x1, int(labelTL.X))
y1 = go2.Min(y1, int(labelTL.Y)) y1 = go2.Min(y1, int(labelTL.Y))
x2 = go2.Max(x2, int(labelTL.X)+connection.SrcLabel.LabelWidth) x2 = go2.Max(x2, int(labelTL.X)+connection.SrcLabel.LabelWidth)
y2 = go2.Max(y2, int(labelTL.Y)+connection.SrcLabel.LabelHeight) y2 = go2.Max(y2, int(labelTL.Y)+connection.SrcLabel.LabelHeight)
} }
if connection.DstLabel != nil && connection.DstLabel.Label != "" { if connection.DstLabel != nil && connection.DstLabel.Label != "" {
labelTL := connection.GetArrowHeadLabelPosition(true) labelTL := connection.GetArrowheadLabelPosition(true)
x1 = go2.Min(x1, int(labelTL.X)) x1 = go2.Min(x1, int(labelTL.X))
y1 = go2.Min(y1, int(labelTL.Y)) y1 = go2.Min(y1, int(labelTL.Y))
x2 = go2.Max(x2, int(labelTL.X)+connection.DstLabel.LabelWidth) x2 = go2.Max(x2, int(labelTL.X)+connection.DstLabel.LabelWidth)
@ -534,7 +537,7 @@ func (c *Connection) GetLabelTopLeft() *geo.Point {
return point return point
} }
func (connection *Connection) GetArrowHeadLabelPosition(isDst bool) *geo.Point { func (connection *Connection) GetArrowheadLabelPosition(isDst bool) *geo.Point {
var width, height float64 var width, height float64
if isDst { if isDst {
width = float64(connection.DstLabel.LabelWidth) width = float64(connection.DstLabel.LabelWidth)
@ -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)
var arrowheadOffset float64 labelTL, index := label.UnlockedTop.GetPointOnRoute(connection.Route, strokeWidth, position, width, height)
var arrowSize float64
if isDst && connection.DstArrow != NoArrowhead { if isDst && connection.DstArrow != NoArrowhead {
// TODO offset according to arrowhead dimensions // Note: these dimensions are for rendering arrowheads on their side so we want the height
arrowheadOffset = 5 _, arrowSize = connection.DstArrow.Dimensions(strokeWidth)
} else if connection.SrcArrow != NoArrowhead { } else if connection.SrcArrow != NoArrowhead {
arrowheadOffset = 5 _, arrowSize = connection.SrcArrow.Dimensions(strokeWidth)
} }
var offsetX, offsetY float64 if arrowSize > 0 {
// get the start/end points of edge segment with arrowhead // labelTL already accounts for strokeWidth and padding, we only want to shift further if the arrow is larger than this
start, end = connection.Route[index], connection.Route[index+1] offset := (arrowSize/2 + ARROWHEAD_PADDING) - strokeWidth/2 - label.PADDING
if start.Y == end.Y { if offset > 0 {
// shift up/down over horizontal segment start, end = connection.Route[index], connection.Route[index+1]
offsetY = arrowheadOffset // Note: end to start to get normal towards unlocked top position
if end.Y < start.Y { normalX, normalY := geo.GetUnitNormalVector(end.X, end.Y, start.X, start.Y)
offsetY = -offsetY labelTL.X += normalX * offset
} labelTL.Y += normalY * offset
} else if start.X == end.X {
// shift left/right across vertical segment
offsetX = arrowheadOffset
if end.X < start.X {
offsetX = -offsetX
} }
} }
labelTL.X += offsetX
labelTL.Y += offsetY
return labelTL return labelTL
} }
@ -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 { type Point struct {
X int `json:"x"` X int `json:"x"`
Y int `json:"y"` Y int `json:"y"`