diff --git a/d2layouts/d2dagrelayout/layout.go b/d2layouts/d2dagrelayout/layout.go index 58e07a991..aa0e95156 100644 --- a/d2layouts/d2dagrelayout/layout.go +++ b/d2layouts/d2dagrelayout/layout.go @@ -168,7 +168,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err height += float64(obj.LabelDimensions.Height) + label.PADDING } } - // reserve extra space for 3d/multiple by padding dagre the larger dimensions + // reserve extra space for 3d/multiple by providing dagre the larger dimensions if obj.Is3d() { if obj.Shape.Value == d2target.ShapeHexagon { height += d2target.THREE_DEE_OFFSET / 2 @@ -419,7 +419,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err } } - // remove the extra width/height we added for 3d/multiple after all objects have been placed + // remove the extra width/height we added for 3d/multiple after all objects/connections are placed // and shift the shapes down accordingly for _, obj := range g.Objects { var offsetX, offsetY float64 diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go index 91da30b6f..e45165a3e 100644 --- a/d2layouts/d2elklayout/layout.go +++ b/d2layouts/d2elklayout/layout.go @@ -222,6 +222,18 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err } width = go2.Max(width, float64(obj.LabelDimensions.Width)) } + // reserve extra space for 3d/multiple by providing elk the larger dimensions + if obj.Is3d() { + if obj.Shape.Value == d2target.ShapeHexagon { + height += d2target.THREE_DEE_OFFSET / 2 + } else { + height += d2target.THREE_DEE_OFFSET + } + width += d2target.THREE_DEE_OFFSET + } else if obj.IsMultiple() { + height += d2target.MULTIPLE_OFFSET + width += d2target.MULTIPLE_OFFSET + } n := &ELKNode{ ID: obj.AbsID(), @@ -454,8 +466,81 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err Y: parentY + s.End.Y, }) } + edge.Route = points + } + + // remove the extra width/height we added for 3d/multiple after all objects/connections are placed + // and shift the shapes down accordingly + for _, obj := range g.Objects { + var offsetX, offsetY float64 + if obj.Is3d() { + offsetX = d2target.THREE_DEE_OFFSET + offsetY = d2target.THREE_DEE_OFFSET + if obj.Shape.Value == d2target.ShapeHexagon { + offsetY = d2target.THREE_DEE_OFFSET / 2 + } + } else if obj.IsMultiple() { + offsetX = d2target.MULTIPLE_OFFSET + offsetY = d2target.MULTIPLE_OFFSET + } + + if offsetY != 0 { + obj.TopLeft.Y += offsetY + obj.ShiftDescendants(0, offsetY) + if !obj.IsContainer() { + obj.Width -= offsetX + obj.Height -= offsetY + } + } + } + + for _, edge := range g.Edges { + points := edge.Route startIndex, endIndex := 0, len(points)-1 + start := points[startIndex] + end := points[endIndex] + + originalSrcTL := edge.Src.TopLeft.Copy() + originalDstTL := edge.Dst.TopLeft.Copy() + // if the edge passes through 3d/multiple, use the offset box for tracing to border + if edge.Src.Is3d() { + offsetY := d2target.THREE_DEE_OFFSET + if edge.Src.Shape.Value == d2target.ShapeHexagon { + offsetY = d2target.THREE_DEE_OFFSET / 2 + } + if start.X > edge.Src.TopLeft.X+d2target.THREE_DEE_OFFSET && + start.Y < edge.Src.TopLeft.Y+edge.Src.Height-float64(offsetY) { + edge.Src.TopLeft.X += d2target.THREE_DEE_OFFSET + edge.Src.TopLeft.Y -= d2target.THREE_DEE_OFFSET + } + } else if edge.Src.IsMultiple() { + // if the edge is on the multiple part, use the multiple's box for tracing to border + if start.X > edge.Src.TopLeft.X+d2target.MULTIPLE_OFFSET && + start.Y < edge.Src.TopLeft.Y+edge.Src.Height-d2target.MULTIPLE_OFFSET { + edge.Src.TopLeft.X += d2target.MULTIPLE_OFFSET + edge.Src.TopLeft.Y -= d2target.MULTIPLE_OFFSET + } + } + if edge.Dst.Is3d() { + offsetY := d2target.THREE_DEE_OFFSET + if edge.Src.Shape.Value == d2target.ShapeHexagon { + offsetY = d2target.THREE_DEE_OFFSET / 2 + } + if end.X > edge.Dst.TopLeft.X+d2target.THREE_DEE_OFFSET && + end.Y < edge.Dst.TopLeft.Y+edge.Dst.Height-float64(offsetY) { + edge.Dst.TopLeft.X += d2target.THREE_DEE_OFFSET + edge.Dst.TopLeft.Y -= d2target.THREE_DEE_OFFSET + } + } else if edge.Dst.IsMultiple() { + // if the edge is on the multiple part, use the multiple's box for tracing to border + if end.X > edge.Dst.TopLeft.X+d2target.MULTIPLE_OFFSET && + end.Y < edge.Dst.TopLeft.Y+edge.Dst.Height-d2target.MULTIPLE_OFFSET { + edge.Dst.TopLeft.X += d2target.MULTIPLE_OFFSET + edge.Dst.TopLeft.Y -= d2target.MULTIPLE_OFFSET + } + } + 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) @@ -468,6 +553,12 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err } edge.Route = points + + // undo 3d/multiple offset + edge.Src.TopLeft.X = originalSrcTL.X + edge.Src.TopLeft.Y = originalSrcTL.Y + edge.Dst.TopLeft.X = originalDstTL.X + edge.Dst.TopLeft.Y = originalDstTL.Y } deleteBends(g) @@ -505,11 +596,21 @@ func deleteBends(g *d2graph.Graph) { endpoint = e.Dst } + var padding int + if endpoint.Is3d() { + padding = d2target.THREE_DEE_OFFSET + if endpoint.Shape.Value == d2target.ShapeHexagon { + padding /= 2 + } + } else if endpoint.IsMultiple() { + padding = d2target.MULTIPLE_OFFSET + } + isHorizontal := math.Ceil(start.Y) == math.Ceil(corner.Y) // Make sure it's still attached if isHorizontal { - if end.Y <= endpoint.TopLeft.Y+10 { + if end.Y <= endpoint.TopLeft.Y+10-float64(padding) { continue } if end.Y >= endpoint.TopLeft.Y+endpoint.Height-10 { @@ -519,7 +620,7 @@ func deleteBends(g *d2graph.Graph) { if end.X <= endpoint.TopLeft.X+10 { continue } - if end.X >= endpoint.TopLeft.X+endpoint.Width-10 { + if end.X >= endpoint.TopLeft.X+endpoint.Width-10+float64(padding) { continue } }