diff --git a/d2layouts/d2layouts.go b/d2layouts/d2layouts.go index 30ff31b68..46590f8b1 100644 --- a/d2layouts/d2layouts.go +++ b/d2layouts/d2layouts.go @@ -1,9 +1,11 @@ package d2layouts import ( + "math" "strings" "oss.terrastruct.com/d2/d2graph" + "oss.terrastruct.com/d2/lib/geo" ) type GraphType string @@ -15,7 +17,7 @@ const ( SequenceDiagram GraphType = "sequence-diagram" ) -func LayoutNested(g *d2graph.Graph, graphType GraphType, coreLayout d2graph.LayoutGraph) { +func LayoutNested(g *d2graph.Graph, graphType GraphType, coreLayout d2graph.LayoutGraph) geo.Spacing { // Before we can layout these nodes, we need to handle all nested diagrams first. extracted := make(map[*d2graph.Object]*d2graph.Graph) @@ -29,10 +31,10 @@ func LayoutNested(g *d2graph.Graph, graphType GraphType, coreLayout d2graph.Layo nestedGraph := ExtractNested(child) // Layout of nestedGraph is completed - LayoutNested(nestedGraph, graphType, coreLayout) + spacing := LayoutNested(nestedGraph, graphType, coreLayout) // Fit child to size of nested layout - FitToGraph(child, nestedGraph) + FitToGraph(child, nestedGraph, spacing) // We will restore the contents after running layout with child as the placeholder extracted[child] = nestedGraph @@ -43,12 +45,14 @@ func LayoutNested(g *d2graph.Graph, graphType GraphType, coreLayout d2graph.Layo // We can now run layout with accurate sizes of nested layout containers // Layout according to the type of diagram - LayoutDiagram(g, graphType, coreLayout) + spacing := LayoutDiagram(g, graphType, coreLayout) // With the layout set, inject all the extracted graphs for n, nestedGraph := range extracted { InjectNested(n, nestedGraph) } + + return spacing } func NestedGraphType(obj *d2graph.Object) GraphType { @@ -65,15 +69,15 @@ func NestedGraphType(obj *d2graph.Object) GraphType { } func ExtractNested(container *d2graph.Object) *d2graph.Graph { - tempGraph := d2graph.NewGraph() - tempGraph.RootLevel = int(container.Level()) + nestedGraph := d2graph.NewGraph() + nestedGraph.RootLevel = int(container.Level()) // separate out nested edges g := container.Graph remainingEdges := make([]*d2graph.Edge, 0, len(g.Edges)) for _, edge := range g.Edges { if edge.Src.Parent.IsDescendantOf(container) && edge.Dst.Parent.IsDescendantOf(container) { - tempGraph.Edges = append(tempGraph.Edges, edge) + nestedGraph.Edges = append(nestedGraph.Edges, edge) } else { remainingEdges = append(remainingEdges, edge) } @@ -84,7 +88,7 @@ func ExtractNested(container *d2graph.Object) *d2graph.Graph { remainingObjects := make([]*d2graph.Object, 0, len(g.Objects)) for _, obj := range g.Objects { if obj.IsDescendantOf(container) { - tempGraph.Objects = append(tempGraph.Objects, obj) + nestedGraph.Objects = append(nestedGraph.Objects, obj) } else { remainingObjects = append(remainingObjects, obj) } @@ -92,14 +96,14 @@ func ExtractNested(container *d2graph.Object) *d2graph.Graph { g.Objects = remainingObjects // update object and new root references - for _, o := range tempGraph.Objects { - o.Graph = tempGraph + for _, o := range nestedGraph.Objects { + o.Graph = nestedGraph } // set root references - tempGraph.Root.ChildrenArray = append(tempGraph.Root.ChildrenArray, container.ChildrenArray...) + nestedGraph.Root.ChildrenArray = append(nestedGraph.Root.ChildrenArray, container.ChildrenArray...) for _, child := range container.ChildrenArray { - child.Parent = tempGraph.Root - tempGraph.Root.Children[strings.ToLower(child.ID)] = child + child.Parent = nestedGraph.Root + nestedGraph.Root.Children[strings.ToLower(child.ID)] = child } // remove container's references @@ -108,17 +112,68 @@ func ExtractNested(container *d2graph.Object) *d2graph.Graph { } container.ChildrenArray = nil - return tempGraph + // position contents relative to 0,0 + dx := -container.TopLeft.X + dy := -container.TopLeft.Y + for _, o := range nestedGraph.Objects { + o.TopLeft.X += dx + o.TopLeft.Y += dy + } + for _, e := range nestedGraph.Edges { + e.Move(dx, dy) + } + return nestedGraph } -func InjectNested(container *d2graph.Object, graph *d2graph.Graph) { - // TODO +func InjectNested(container *d2graph.Object, nestedGraph *d2graph.Graph) { + g := container.Graph + for _, obj := range nestedGraph.Root.ChildrenArray { + obj.Parent = container + container.Children[strings.ToLower(obj.ID)] = obj + container.ChildrenArray = append(container.ChildrenArray, obj) + } + for _, obj := range nestedGraph.Objects { + obj.Graph = g + } + g.Objects = append(g.Objects, nestedGraph.Objects...) + g.Edges = append(g.Edges, nestedGraph.Edges...) + + // Note: assumes nestedGraph's layout has contents positioned relative to 0,0 + dx := container.TopLeft.X + dy := container.TopLeft.Y + for _, o := range nestedGraph.Objects { + o.TopLeft.X += dx + o.TopLeft.Y += dy + } + for _, e := range nestedGraph.Edges { + e.Move(dx, dy) + } } -func FitToGraph(container *d2graph.Object, graph *d2graph.Graph) { - // TODO +func boundingBox(g *d2graph.Graph) (tl, br *geo.Point) { + if len(g.Objects) == 0 { + return geo.NewPoint(0, 0), geo.NewPoint(0, 0) + } + tl = geo.NewPoint(math.Inf(1), math.Inf(1)) + br = geo.NewPoint(math.Inf(-1), math.Inf(-1)) + + for _, obj := range g.Objects { + tl.X = math.Min(tl.X, obj.TopLeft.X) + tl.Y = math.Min(tl.Y, obj.TopLeft.Y) + br.X = math.Max(br.X, obj.TopLeft.X+obj.Width) + br.Y = math.Max(br.Y, obj.TopLeft.Y+obj.Height) + } + + return tl, br } -func LayoutDiagram(graph *d2graph.Graph, graphType GraphType, coreLayout d2graph.LayoutGraph) { - // TODO +func FitToGraph(container *d2graph.Object, nestedGraph *d2graph.Graph, padding geo.Spacing) { + tl, br := boundingBox(nestedGraph) + container.Width = padding.Left + br.X - tl.X + padding.Right + container.Height = padding.Top + br.Y - tl.Y + padding.Bottom +} + +func LayoutDiagram(graph *d2graph.Graph, graphType GraphType, coreLayout d2graph.LayoutGraph) geo.Spacing { + // TODO + return geo.Spacing{} } diff --git a/lib/geo/spacing.go b/lib/geo/spacing.go new file mode 100644 index 000000000..2684ee582 --- /dev/null +++ b/lib/geo/spacing.go @@ -0,0 +1,5 @@ +package geo + +type Spacing struct { + Top, Bottom, Left, Right float64 +}