diff --git a/d2layouts/d2dagrelayout/layout.go b/d2layouts/d2dagrelayout/layout.go index 39cd1f9fe..e033c61e7 100644 --- a/d2layouts/d2dagrelayout/layout.go +++ b/d2layouts/d2dagrelayout/layout.go @@ -238,7 +238,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err obj.Width = math.Ceil(dn.Width) obj.Height = math.Ceil(dn.Height) - if obj.HasLabel() { + if obj.HasLabel() && obj.LabelPosition == nil { if len(obj.ChildrenArray) > 0 { obj.LabelPosition = go2.Pointer(string(label.OutsideTopCenter)) } else if obj.HasOutsideBottomLabel() { @@ -251,7 +251,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err obj.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter)) } } - if obj.Icon != nil { + if obj.Icon != nil && obj.IconPosition == nil { if len(obj.ChildrenArray) > 0 { obj.IconPosition = go2.Pointer(string(label.OutsideTopLeft)) obj.LabelPosition = go2.Pointer(string(label.OutsideTopRight)) diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go index be6687f7e..2fb5e160d 100644 --- a/d2layouts/d2elklayout/layout.go +++ b/d2layouts/d2elklayout/layout.go @@ -407,7 +407,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err obj.Width = math.Ceil(n.Width) obj.Height = math.Ceil(n.Height) - if obj.HasLabel() { + if obj.HasLabel() && obj.LabelPosition == nil { if len(obj.ChildrenArray) > 0 { obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter)) } else if obj.HasOutsideBottomLabel() { @@ -419,7 +419,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err obj.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter)) } } - if obj.Icon != nil { + if obj.Icon != nil && obj.IconPosition == nil { if len(obj.ChildrenArray) > 0 { obj.IconPosition = go2.Pointer(string(label.InsideTopLeft)) obj.LabelPosition = go2.Pointer(string(label.InsideTopRight)) diff --git a/d2layouts/d2grid/layout.go b/d2layouts/d2grid/layout.go index a84209808..390470583 100644 --- a/d2layouts/d2grid/layout.go +++ b/d2layouts/d2grid/layout.go @@ -8,6 +8,7 @@ import ( "sort" "oss.terrastruct.com/d2/d2graph" + "oss.terrastruct.com/d2/d2target" "oss.terrastruct.com/d2/lib/geo" "oss.terrastruct.com/d2/lib/label" "oss.terrastruct.com/util-go/go2" @@ -112,62 +113,88 @@ func withoutGridDiagrams(ctx context.Context, g *d2graph.Graph, layout d2graph.L gd.shift(innerBox.TopLeft.X, innerBox.TopLeft.Y) } + // compute how much space the label and icon occupy + var occupiedWidth, occupiedHeight float64 + if obj.Icon != nil { + iconSpace := float64(d2target.MAX_ICON_SIZE + 2*label.PADDING) + occupiedWidth = iconSpace + occupiedHeight = iconSpace + } + var dx, dy float64 - if obj.LabelDimensions.Width != 0 { - labelWidth := float64(obj.LabelDimensions.Width) + 2*label.PADDING - if labelWidth > obj.Width { - dx = (labelWidth - obj.Width) / 2 - obj.Width = labelWidth - } - } if obj.LabelDimensions.Height != 0 { - labelHeight := float64(obj.LabelDimensions.Height) + 2*label.PADDING - - // also check for grid cells with outside top labels - // the first grid object is at the top (and always exists) - topY := gd.objects[0].TopLeft.Y - highestLabel := topY - for _, o := range gd.objects { - // we only want to compute label positions for objects at the top of the grid - if o.TopLeft.Y > topY { - if gd.rowDirected { - // if the grid is rowDirected (row1, row2, etc) we can stop after finishing the first row - break - } else { - // otherwise we continue until the next column - continue - } - } - if o.LabelPosition != nil { - labelPosition := label.Position(*o.LabelPosition) - if labelPosition.IsOutside() { - labelTL := o.GetLabelTopLeft() - if labelTL.Y < highestLabel { - highestLabel = labelTL.Y - } - } - } - } - if highestLabel < topY { - labelHeight += topY - highestLabel + 2*label.PADDING - } - - if labelHeight > float64(verticalPadding) { - // if the label doesn't fit within the padding, we need to add more - grow := labelHeight - float64(verticalPadding) - dy = grow - obj.Height += grow + occupiedHeight = math.Max( + occupiedHeight, + float64(obj.LabelDimensions.Height)+2*label.PADDING, + ) + } + if obj.LabelDimensions.Width != 0 { + // . ├────┤───────├────┤ + // . icon label icon + // with an icon in top left we need 2x the space to fit the label in the center + occupiedWidth *= 2 + occupiedWidth += float64(obj.LabelDimensions.Width) + 2*label.PADDING + if occupiedWidth > obj.Width { + dx = (occupiedWidth - obj.Width) / 2 + obj.Width = occupiedWidth } } + + // also check for grid cells with outside top labels or icons + // the first grid object is at the top (and always exists) + topY := gd.objects[0].TopLeft.Y + highestOutside := topY + for _, o := range gd.objects { + // we only want to compute label positions for objects at the top of the grid + if o.TopLeft.Y > topY { + if gd.rowDirected { + // if the grid is rowDirected (row1, row2, etc) we can stop after finishing the first row + break + } else { + // otherwise we continue until the next column + continue + } + } + if o.LabelPosition != nil { + labelPosition := label.Position(*o.LabelPosition) + if labelPosition.IsOutside() { + labelTL := o.GetLabelTopLeft() + if labelTL.Y < highestOutside { + highestOutside = labelTL.Y + } + } + } + if o.IconPosition != nil { + switch label.Position(*o.IconPosition) { + case label.OutsideTopLeft, label.OutsideTopCenter, label.OutsideTopRight: + iconSpace := float64(d2target.MAX_ICON_SIZE + label.PADDING) + if topY-iconSpace < highestOutside { + highestOutside = topY - iconSpace + } + } + } + } + if highestOutside < topY { + occupiedHeight += topY - highestOutside + 2*label.PADDING + } + if occupiedHeight > float64(verticalPadding) { + // if the label doesn't fit within the padding, we need to add more + dy = occupiedHeight - float64(verticalPadding) + obj.Height += dy + } + // we need to center children if we have to expand to fit the container label if dx != 0 || dy != 0 { gd.shift(dx, dy) } } - if obj.LabelPosition == nil { + if obj.HasLabel() { obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter)) } + if obj.Icon != nil { + obj.IconPosition = go2.Pointer(string(label.InsideTopLeft)) + } gridDiagrams[obj.AbsID()] = gd for _, o := range gd.objects {