adjust label positioning for arrowhead
This commit is contained in:
parent
29a9630635
commit
ca29119c60
2 changed files with 68 additions and 76 deletions
|
|
@ -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"
|
||||||
|
|
@ -40,7 +38,6 @@ 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{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
offset := (arrowSize/2 + ARROWHEAD_PADDING) - strokeWidth/2 - label.PADDING
|
||||||
|
if offset > 0 {
|
||||||
start, end = connection.Route[index], connection.Route[index+1]
|
start, end = connection.Route[index], connection.Route[index+1]
|
||||||
if start.Y == end.Y {
|
// Note: end to start to get normal towards unlocked top position
|
||||||
// shift up/down over horizontal segment
|
normalX, normalY := geo.GetUnitNormalVector(end.X, end.Y, start.X, start.Y)
|
||||||
offsetY = arrowheadOffset
|
labelTL.X += normalX * offset
|
||||||
if end.Y < start.Y {
|
labelTL.Y += normalY * offset
|
||||||
offsetY = -offsetY
|
|
||||||
}
|
|
||||||
} 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"`
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue