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

fix elk layout with outside positioned labels/icons #1776

Merged
merged 14 commits into from
Dec 13, 2023
1 change: 1 addition & 0 deletions ci/release/changelogs/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
- Fix importing files that override an existing value with an array. [#1762](https://github.com/terrastruct/d2/pull/1762)
- Fixes missing unfilled triangle arrowheads when sketch flag is on. [#1763](https://github.com/terrastruct/d2/pull/1763)
- Fixes a bug where the render target could be incorrect if the target path contains "index". [#1764](https://github.com/terrastruct/d2/pull/1764)
- Fixes ELK layout with outside labels/icons. [#1776](https://github.com/terrastruct/d2/pull/1776)
19 changes: 15 additions & 4 deletions d2graph/d2graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,10 @@ func (obj *Object) HasLabel() bool {
}
}

func (obj *Object) HasIcon() bool {
return obj.Icon != nil && obj.Shape.Value != d2target.ShapeImage
}

func (obj *Object) AbsID() string {
if obj.Parent != nil && obj.Parent.ID != "" {
return obj.Parent.AbsID() + "." + obj.ID
Expand Down Expand Up @@ -1951,6 +1955,10 @@ func (obj *Object) Is3D() bool {
}

func (obj *Object) Spacing() (margin, padding geo.Spacing) {
return obj.SpacingOpt(2*label.PADDING, 2*label.PADDING, true)
}

func (obj *Object) SpacingOpt(labelPadding, iconPadding float64, maxIconSize bool) (margin, padding geo.Spacing) {
if obj.HasLabel() {
var position label.Position
if obj.LabelPosition != nil {
Expand All @@ -1959,10 +1967,10 @@ func (obj *Object) Spacing() (margin, padding geo.Spacing) {

var labelWidth, labelHeight float64
if obj.LabelDimensions.Width > 0 {
labelWidth = float64(obj.LabelDimensions.Width) + 2*label.PADDING
labelWidth = float64(obj.LabelDimensions.Width) + labelPadding
}
if obj.LabelDimensions.Height > 0 {
labelHeight = float64(obj.LabelDimensions.Height) + 2*label.PADDING
labelHeight = float64(obj.LabelDimensions.Height) + labelPadding
}

switch position {
Expand All @@ -1985,13 +1993,16 @@ func (obj *Object) Spacing() (margin, padding geo.Spacing) {
}
}

if obj.Icon != nil && obj.Shape.Value != d2target.ShapeImage {
if obj.HasIcon() {
var position label.Position
if obj.IconPosition != nil {
position = label.FromString(*obj.IconPosition)
}

iconSize := float64(d2target.MAX_ICON_SIZE + 2*label.PADDING)
iconSize := float64(d2target.MAX_ICON_SIZE + iconPadding)
if !maxIconSize {
iconSize = float64(d2target.GetIconSize(obj.Box, position.String())) + iconPadding
}
switch position {
case label.OutsideTopLeft, label.OutsideTopCenter, label.OutsideTopRight:
margin.Top = math.Max(margin.Top, iconSize)
Expand Down
72 changes: 68 additions & 4 deletions d2graph/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ func (obj *Object) GetMargin() geo.Spacing {
}
}

if obj.Icon != nil && obj.IconPosition != nil && obj.Shape.Value != d2target.ShapeImage {
if obj.HasIcon() && obj.IconPosition != nil {
position := label.FromString(*obj.IconPosition)

iconSize := float64(d2target.MAX_ICON_SIZE + label.PADDING)
Expand Down Expand Up @@ -417,7 +417,7 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n

startingSegment := geo.Segment{Start: points[startIndex+1], End: points[startIndex]}
// if an edge runs into an outside label, stop the edge at the label instead
overlapsOutsideLabel := false
var overlapsOutsideLabel, overlapsOutsideIcon bool
if edge.Src.HasLabel() {
// assumes LabelPosition, LabelWidth, LabelHeight are all set if there is a label
labelPosition := label.FromString(*edge.Src.LabelPosition)
Expand Down Expand Up @@ -453,7 +453,38 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n
}
}
}
if !overlapsOutsideLabel {
if !overlapsOutsideLabel && edge.Src.HasIcon() {
// assumes IconPosition is set if there is an Icon
iconPosition := label.FromString(*edge.Src.IconPosition)
if iconPosition.IsOutside() {
iconWidth := float64(d2target.MAX_ICON_SIZE)
iconHeight := float64(d2target.MAX_ICON_SIZE)
iconTL := iconPosition.GetPointOnBox(edge.Src.Box, label.PADDING, iconWidth, iconHeight)

iconBox := geo.NewBox(iconTL, iconWidth, iconHeight)
for iconBox.Contains(startingSegment.End) && startIndex+1 > endIndex {
startingSegment.Start = startingSegment.End
startingSegment.End = points[startIndex+2]
startIndex++
}
if intersections := iconBox.Intersections(startingSegment); len(intersections) > 0 {
overlapsOutsideIcon = true
p := intersections[0]
if len(intersections) > 1 {
p = findOuterIntersection(iconPosition, intersections)
}
// move starting segment to icon intersection point
points[startIndex] = p
startingSegment.End = p
// if the segment becomes too short, just merge it with the next segment
if startIndex+1 < endIndex && startingSegment.Length() < MIN_SEGMENT_LEN {
points[startIndex+1] = points[startIndex]
startIndex++
}
}
}
}
if !overlapsOutsideLabel && !overlapsOutsideIcon {
if intersections := edge.Src.Intersections(startingSegment); len(intersections) > 0 {
// move starting segment to intersection point
points[startIndex] = intersections[0]
Expand All @@ -469,6 +500,7 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n
}
endingSegment := geo.Segment{Start: points[endIndex-1], End: points[endIndex]}
overlapsOutsideLabel = false
overlapsOutsideIcon = false
if edge.Dst.HasLabel() {
// assumes LabelPosition, LabelWidth, LabelHeight are all set if there is a label
labelPosition := label.FromString(*edge.Dst.LabelPosition)
Expand Down Expand Up @@ -503,7 +535,39 @@ func (edge *Edge) TraceToShape(points []*geo.Point, startIndex, endIndex int) (n
}
}
}
if !overlapsOutsideLabel {
if !overlapsOutsideLabel && edge.Dst.HasIcon() {
// assumes IconPosition is set if there is an Icon
iconPosition := label.FromString(*edge.Dst.IconPosition)
if iconPosition.IsOutside() {
iconSize := d2target.GetIconSize(edge.Dst.Box, iconPosition.String())
iconWidth := float64(iconSize)
iconHeight := float64(iconSize)
labelTL := iconPosition.GetPointOnBox(edge.Dst.Box, label.PADDING, iconWidth, iconHeight)

iconBox := geo.NewBox(labelTL, iconWidth, iconHeight)
for iconBox.Contains(endingSegment.Start) && endIndex-1 > startIndex {
endingSegment.End = endingSegment.Start
endingSegment.Start = points[endIndex-2]
endIndex--
}
if intersections := iconBox.Intersections(endingSegment); len(intersections) > 0 {
overlapsOutsideIcon = true
p := intersections[0]
if len(intersections) > 1 {
p = findOuterIntersection(iconPosition, intersections)
}
// move ending segment to icon intersection point
points[endIndex] = p
endingSegment.End = p
// if the segment becomes too short, just merge it with the previous segment
if endIndex-1 > startIndex && endingSegment.Length() < MIN_SEGMENT_LEN {
points[endIndex-1] = points[endIndex]
endIndex--
}
}
}
}
if !overlapsOutsideLabel && !overlapsOutsideIcon {
if intersections := edge.Dst.Intersections(endingSegment); len(intersections) > 0 {
// move ending segment to intersection point
points[endIndex] = intersections[0]
Expand Down
4 changes: 2 additions & 2 deletions d2layouts/d2dagrelayout/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -1351,7 +1351,7 @@ func fitPadding(obj *d2graph.Object) {
}
}
}
if obj.Icon != nil && shapeType != shape.IMAGE_TYPE && obj.IconPosition != nil {
if obj.HasIcon() && obj.IconPosition != nil {
iconPosition = label.FromString(*obj.IconPosition)
switch iconPosition {
case label.InsideTopLeft, label.InsideTopCenter, label.InsideTopRight,
Expand Down Expand Up @@ -1386,7 +1386,7 @@ func fitPadding(obj *d2graph.Object) {
innerBoxes = append(innerBoxes, *childLabelBox)
}
}
if child.Icon != nil && child.Shape.Value != d2target.ShapeImage && child.IconPosition != nil {
if child.HasIcon() && child.IconPosition != nil {
childIconPosition = label.FromString(*child.IconPosition)
if childIconPosition.IsOutside() {
childIconTL := child.GetIconTopLeft()
Expand Down
Loading