diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 006bfae85..f43837ef8 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -26,6 +26,7 @@ import ( "oss.terrastruct.com/d2/d2themes/d2themescatalog" "oss.terrastruct.com/d2/lib/color" "oss.terrastruct.com/d2/lib/geo" + "oss.terrastruct.com/d2/lib/label" "oss.terrastruct.com/d2/lib/shape" "oss.terrastruct.com/d2/lib/textmeasure" ) @@ -1989,3 +1990,36 @@ func (obj *Object) GetModifierElementAdjustments() (dx, dy float64) { } return dx, dy } + +func (obj *Object) ToShape() shape.Shape { + tl := obj.TopLeft + if tl == nil { + tl = geo.NewPoint(0, 0) + } + dslShape := strings.ToLower(obj.Shape.Value) + shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[dslShape] + contentBox := geo.NewBox(tl, obj.Width, obj.Height) + return shape.NewShape(shapeType, contentBox) +} + +func (obj *Object) GetLabelTopLeft() *geo.Point { + if obj.LabelPosition == nil { + return nil + } + + s := obj.ToShape() + labelPosition := label.Position(*obj.LabelPosition) + + var box *geo.Box + if labelPosition.IsOutside() { + box = s.GetBox() + } else { + box = s.GetInnerBox() + } + + labelTL := labelPosition.GetPointOnBox(box, label.PADDING, + float64(obj.LabelDimensions.Width), + float64(obj.LabelDimensions.Height), + ) + return labelTL +} diff --git a/d2layouts/d2dagrelayout/layout.go b/d2layouts/d2dagrelayout/layout.go index db9c4f4b3..39cd1f9fe 100644 --- a/d2layouts/d2dagrelayout/layout.go +++ b/d2layouts/d2dagrelayout/layout.go @@ -119,9 +119,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err } if obj.Icon != nil && obj.Shape.Value != d2target.ShapeImage { - contentBox := geo.NewBox(geo.NewPoint(0, 0), float64(obj.Width), float64(obj.Height)) - shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[obj.Shape.Value] - s := shape.NewShape(shapeType, contentBox) + s := obj.ToShape() iconSize := d2target.GetIconSize(s.GetInnerBox(), string(label.InsideTopLeft)) // Since dagre container labels are pushed up, we don't want a child container to collide maxContainerLabelHeight = go2.Max(maxContainerLabelHeight, (iconSize+label.PADDING*2)*2) @@ -491,8 +489,8 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err } } - srcShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Src.Shape.Value)], edge.Src.Box) - dstShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Dst.Shape.Value)], edge.Dst.Box) + srcShape := edge.Src.ToShape() + dstShape := edge.Dst.ToShape() // trace the edge to the specific shape's border points[startIndex] = shape.TraceToShapeBorder(srcShape, start, points[startIndex+1]) diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go index 5c29020a2..be6687f7e 100644 --- a/d2layouts/d2elklayout/layout.go +++ b/d2layouts/d2elklayout/layout.go @@ -501,8 +501,8 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err } } - srcShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Src.Shape.Value)], edge.Src.Box) - dstShape := shape.NewShape(d2target.DSL_SHAPE_TO_SHAPE_TYPE[strings.ToLower(edge.Dst.Shape.Value)], edge.Dst.Box) + srcShape := edge.Src.ToShape() + dstShape := edge.Dst.ToShape() // trace the edge to the specific shape's border points[startIndex] = shape.TraceToShapeBorder(srcShape, points[startIndex], points[startIndex+1]) diff --git a/d2layouts/d2grid/layout.go b/d2layouts/d2grid/layout.go index 26bfb26b7..6b747f7b3 100644 --- a/d2layouts/d2grid/layout.go +++ b/d2layouts/d2grid/layout.go @@ -6,13 +6,10 @@ import ( "fmt" "math" "sort" - "strings" "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/d2/lib/shape" "oss.terrastruct.com/util-go/go2" ) @@ -104,13 +101,12 @@ func withoutGridDiagrams(ctx context.Context, g *d2graph.Graph, layout d2graph.L if obj.GridGap != nil || obj.VerticalGap != nil { verticalPadding = gd.verticalGap } + // size shape according to grid - obj.SizeToContent(float64(gd.width), float64(gd.height), float64(2*horizontalPadding), float64(2*verticalPadding)) + obj.SizeToContent(gd.width, gd.height, float64(2*horizontalPadding), float64(2*verticalPadding)) // compute where the grid should be placed inside shape - dslShape := strings.ToLower(obj.Shape.Value) - shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[dslShape] - s := shape.NewShape(shapeType, geo.NewBox(geo.NewPoint(0, 0), obj.Width, obj.Height)) + s := obj.ToShape() innerBox := s.GetInnerBox() if innerBox.TopLeft.X != 0 || innerBox.TopLeft.Y != 0 { gd.shift(innerBox.TopLeft.X, innerBox.TopLeft.Y) @@ -126,6 +122,34 @@ func withoutGridDiagrams(ctx context.Context, g *d2graph.Graph, layout d2graph.L } if obj.LabelDimensions.Height != 0 { labelHeight := float64(obj.LabelDimensions.Height) + 2*label.PADDING + + { + // also check for grid cells with outside top labels + topY := gd.objects[0].TopLeft.Y + highestLabel := topY + for _, o := range gd.objects { + if o.TopLeft.Y > topY { + if gd.rowDirected { + break + } else { + 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)