Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update arrowhead label positioning and use label dimensions #1207

Merged
merged 14 commits into from
Apr 17, 2023
  •  
  •  
  •  
1 change: 1 addition & 0 deletions ci/release/changelogs/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

- Markdown text can now be adjusted with `font-size` [#1191](https://github.com/terrastruct/d2/issues/1191)
- SVG outputs for internal links use relative paths instead of absolute [#1197](https://github.com/terrastruct/d2/pull/1197)
- Improves arrowhead label positioning [#1207](https://github.com/terrastruct/d2/pull/1207)

#### Bugfixes ⛑️

Expand Down
12 changes: 10 additions & 2 deletions d2exporter/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,11 @@ func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection
}
if edge.SrcArrowhead != nil {
if edge.SrcArrowhead.Label.Value != "" {
connection.SrcLabel = edge.SrcArrowhead.Label.Value
connection.SrcLabel = &d2target.Text{
Label: edge.SrcArrowhead.Label.Value,
LabelWidth: edge.SrcArrowhead.LabelDimensions.Width,
LabelHeight: edge.SrcArrowhead.LabelDimensions.Height,
}
}
}
if edge.DstArrow {
Expand All @@ -230,7 +234,11 @@ func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection
}
if edge.DstArrowhead != nil {
if edge.DstArrowhead.Label.Value != "" {
connection.DstLabel = edge.DstArrowhead.Label.Value
connection.DstLabel = &d2target.Text{
Label: edge.DstArrowhead.Label.Value,
LabelWidth: edge.DstArrowhead.LabelDimensions.Width,
LabelHeight: edge.DstArrowhead.LabelDimensions.Height,
}
}
}
if theme != nil && theme.SpecialRules.NoCornerRadius {
Expand Down
35 changes: 13 additions & 22 deletions d2graph/d2graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -1042,9 +1042,6 @@ func (obj *Object) OuterNearContainer() *Object {
type Edge struct {
Index int `json:"index"`

MinWidth int `json:"minWidth"`
MinHeight int `json:"minHeight"`

SrcTableColumnIndex *int `json:"srcTableColumnIndex,omitempty"`
DstTableColumnIndex *int `json:"dstTableColumnIndex,omitempty"`

Expand Down Expand Up @@ -1471,21 +1468,23 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
}
}
for _, edge := range g.Edges {
endpointLabels := []string{}
usedFont := fontFamily
if edge.Style.Font != nil {
f := d2fonts.D2_FONT_TO_FAMILY[edge.Style.Font.Value]
usedFont = &f
}

if edge.SrcArrowhead != nil && edge.SrcArrowhead.Label.Value != "" {
endpointLabels = append(endpointLabels, edge.SrcArrowhead.Label.Value)
t := edge.Text()
t.Text = edge.SrcArrowhead.Label.Value
dims := GetTextDimensions(mtexts, ruler, t, usedFont)
edge.SrcArrowhead.LabelDimensions = *dims
}
if edge.DstArrowhead != nil && edge.DstArrowhead.Label.Value != "" {
endpointLabels = append(endpointLabels, edge.DstArrowhead.Label.Value)
}

for _, label := range endpointLabels {
t := edge.Text()
t.Text = label
dims := GetTextDimensions(mtexts, ruler, t, fontFamily)
edge.MinWidth += dims.Width
// Some padding as it's not totally near the end
edge.MinHeight += dims.Height + 5
t.Text = edge.DstArrowhead.Label.Value
dims := GetTextDimensions(mtexts, ruler, t, usedFont)
edge.DstArrowhead.LabelDimensions = *dims
}

if edge.Label.Value == "" {
Expand All @@ -1497,20 +1496,12 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
}
edge.ApplyTextTransform()

usedFont := fontFamily
if edge.Style.Font != nil {
f := d2fonts.D2_FONT_TO_FAMILY[edge.Style.Font.Value]
usedFont = &f
}

dims := GetTextDimensions(mtexts, ruler, edge.Text(), usedFont)
if dims == nil {
return fmt.Errorf("dimensions for edge label %#v not found", edge.Text())
}

edge.LabelDimensions = *dims
edge.MinWidth += dims.Width
edge.MinHeight += dims.Height
}
return nil
}
Expand Down
16 changes: 0 additions & 16 deletions d2graph/serde.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,22 +388,6 @@ func CompareSerializedEdge(edge, other *Edge) error {
)
}

if edge.MinWidth != other.MinWidth {
return fmt.Errorf(
"min width differs: edge=%d, other=%d",
edge.MinWidth,
other.MinWidth,
)
}

if edge.MinHeight != other.MinHeight {
return fmt.Errorf(
"min height differs: edge=%d, other=%d",
edge.MinHeight,
other.MinHeight,
)
}

if edge.Label.Value != other.Label.Value {
return fmt.Errorf(
"labels differ: edge=%s, other=%s",
Expand Down
8 changes: 8 additions & 0 deletions d2renderers/d2sketch/sketch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,14 @@ queue -> package -> step
callout -> stored_data -> person
diamond -> oval -> circle
hexagon -> cloud
`,
},
{
name: "long_arrowhead_label",
script: `
a -> b: {
target-arrowhead: "a to b with unexpectedly long target arrowhead label"
}
`,
},
}
Expand Down
154 changes: 77 additions & 77 deletions d2renderers/d2sketch/testdata/all_shapes/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 77 additions & 77 deletions d2renderers/d2sketch/testdata/all_shapes_dark/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 80 additions & 80 deletions d2renderers/d2sketch/testdata/animated/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 80 additions & 80 deletions d2renderers/d2sketch/testdata/animated_dark/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 80 additions & 80 deletions d2renderers/d2sketch/testdata/arrowheads/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 80 additions & 80 deletions d2renderers/d2sketch/testdata/arrowheads_dark/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 77 additions & 77 deletions d2renderers/d2sketch/testdata/basic/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 77 additions & 77 deletions d2renderers/d2sketch/testdata/basic_dark/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 80 additions & 80 deletions d2renderers/d2sketch/testdata/child_to_child/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 80 additions & 80 deletions d2renderers/d2sketch/testdata/child_to_child_dark/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 80 additions & 80 deletions d2renderers/d2sketch/testdata/connection_label/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 80 additions & 80 deletions d2renderers/d2sketch/testdata/connection_label_dark/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 77 additions & 77 deletions d2renderers/d2sketch/testdata/crows_feet/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 77 additions & 77 deletions d2renderers/d2sketch/testdata/crows_feet_dark/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 77 additions & 77 deletions d2renderers/d2sketch/testdata/dots-all/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 77 additions & 77 deletions d2renderers/d2sketch/testdata/dots-multiple/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 80 additions & 80 deletions d2renderers/d2sketch/testdata/dots-real/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 77 additions & 77 deletions d2renderers/d2sketch/testdata/elk_corners/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
110 changes: 110 additions & 0 deletions d2renderers/d2sketch/testdata/long_arrowhead_label/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
568 changes: 284 additions & 284 deletions d2renderers/d2sketch/testdata/opacity/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
568 changes: 284 additions & 284 deletions d2renderers/d2sketch/testdata/opacity_dark/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 80 additions & 80 deletions d2renderers/d2sketch/testdata/paper-real/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
562 changes: 281 additions & 281 deletions d2renderers/d2sketch/testdata/root-fill/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 77 additions & 77 deletions d2renderers/d2sketch/testdata/sql_tables/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 77 additions & 77 deletions d2renderers/d2sketch/testdata/sql_tables_dark/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 80 additions & 80 deletions d2renderers/d2sketch/testdata/terminal/sketch.exp.svg

Large diffs are not rendered by default.

568 changes: 284 additions & 284 deletions d2renderers/d2sketch/testdata/twitter/sketch.exp.svg

Large diffs are not rendered by default.

568 changes: 284 additions & 284 deletions d2renderers/d2sketch/testdata/twitter_dark/sketch.exp.svg

Large diffs are not rendered by default.

Large diffs are not rendered by default.

152 changes: 76 additions & 76 deletions d2renderers/d2svg/appendix/testdata/links/sketch.exp.svg

Large diffs are not rendered by default.

152 changes: 76 additions & 76 deletions d2renderers/d2svg/appendix/testdata/links_dark/sketch.exp.svg

Large diffs are not rendered by default.

152 changes: 76 additions & 76 deletions d2renderers/d2svg/appendix/testdata/tooltip_fill/sketch.exp.svg

Large diffs are not rendered by default.

Large diffs are not rendered by default.

98 changes: 27 additions & 71 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 @@ -620,38 +574,40 @@ func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Co
fmt.Fprint(writer, textEl.Render())
}

length := geo.Route(connection.Route).Length()
if connection.SrcLabel != "" {
// TODO use arrowhead label dimensions https://github.com/terrastruct/d2/issues/183
size := float64(connection.FontSize)
position := 0.
if length > 0 {
position = size / length
}
fmt.Fprint(writer, renderArrowheadLabel(connection, connection.SrcLabel, position, size, size))
}
if connection.DstLabel != "" {
// TODO use arrowhead label dimensions https://github.com/terrastruct/d2/issues/183
size := float64(connection.FontSize)
position := 1.
if length > 0 {
position -= size / length
}
fmt.Fprint(writer, renderArrowheadLabel(connection, connection.DstLabel, position, size, size))
if connection.SrcLabel != nil && connection.SrcLabel.Label != "" {
fmt.Fprint(writer, renderArrowheadLabel(connection, connection.SrcLabel.Label, false))
}
if connection.DstLabel != nil && connection.DstLabel.Label != "" {
fmt.Fprint(writer, renderArrowheadLabel(connection, connection.DstLabel.Label, true))
}
fmt.Fprintf(writer, `</g>`)
return
}

func renderArrowheadLabel(connection d2target.Connection, text string, position, width, height float64) string {
labelTL := label.UnlockedTop.GetPointOnRoute(connection.Route, float64(connection.StrokeWidth), position, width, height)
func renderArrowheadLabel(connection d2target.Connection, text string, isDst bool) string {
var width, height float64
if isDst {
width = float64(connection.DstLabel.LabelWidth)
height = float64(connection.DstLabel.LabelHeight)
} else {
width = float64(connection.SrcLabel.LabelWidth)
height = float64(connection.SrcLabel.LabelHeight)
}

labelTL := connection.GetArrowheadLabelPosition(isDst)

// svg text is positioned with the center of its baseline
baselineCenter := geo.Point{
X: labelTL.X + width/2.,
Y: labelTL.Y + float64(connection.FontSize),
}

textEl := d2themes.NewThemableElement("text")
textEl.X = labelTL.X + width/2
textEl.Y = labelTL.Y + float64(connection.FontSize)
textEl.X = baselineCenter.X
textEl.Y = baselineCenter.Y
textEl.Fill = d2target.FG_COLOR
textEl.ClassName = "text-italic"
textEl.Style = fmt.Sprintf("text-anchor:%s;font-size:%vpx", "middle", connection.FontSize)
textEl.Style = fmt.Sprintf("text-anchor:middle;font-size:%vpx", connection.FontSize)
textEl.Content = RenderText(text, textEl.X, height)
return textEl.Render()
}
Expand Down
Loading