diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index f43837ef8..301cc5bd8 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -26,7 +26,6 @@ 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" ) @@ -1818,90 +1817,6 @@ func (g *Graph) ApplyTheme(themeID int64) error { return nil } -func (obj *Object) MoveWithDescendants(dx, dy float64) { - obj.TopLeft.X += dx - obj.TopLeft.Y += dy - for _, child := range obj.ChildrenArray { - child.MoveWithDescendants(dx, dy) - } -} - -func (obj *Object) MoveWithDescendantsTo(x, y float64) { - dx := x - obj.TopLeft.X - dy := y - obj.TopLeft.Y - obj.MoveWithDescendants(dx, dy) -} - -func (parent *Object) removeChild(child *Object) { - delete(parent.Children, strings.ToLower(child.ID)) - for i := 0; i < len(parent.ChildrenArray); i++ { - if parent.ChildrenArray[i] == child { - parent.ChildrenArray = append(parent.ChildrenArray[:i], parent.ChildrenArray[i+1:]...) - break - } - } -} - -// remove obj and all descendants from graph, as a new Graph -func (g *Graph) ExtractAsNestedGraph(obj *Object) *Graph { - descendantObjects, edges := pluckObjAndEdges(g, obj) - - tempGraph := NewGraph() - tempGraph.Root.ChildrenArray = []*Object{obj} - tempGraph.Root.Children[strings.ToLower(obj.ID)] = obj - - for _, descendantObj := range descendantObjects { - descendantObj.Graph = tempGraph - } - tempGraph.Objects = descendantObjects - tempGraph.Edges = edges - - obj.Parent.removeChild(obj) - obj.Parent = tempGraph.Root - - return tempGraph -} - -func pluckObjAndEdges(g *Graph, obj *Object) (descendantsObjects []*Object, edges []*Edge) { - for i := 0; i < len(g.Edges); i++ { - edge := g.Edges[i] - if edge.Src == obj || edge.Dst == obj { - edges = append(edges, edge) - g.Edges = append(g.Edges[:i], g.Edges[i+1:]...) - i-- - } - } - - for i := 0; i < len(g.Objects); i++ { - temp := g.Objects[i] - if temp.AbsID() == obj.AbsID() { - descendantsObjects = append(descendantsObjects, obj) - g.Objects = append(g.Objects[:i], g.Objects[i+1:]...) - for _, child := range obj.ChildrenArray { - subObjects, subEdges := pluckObjAndEdges(g, child) - descendantsObjects = append(descendantsObjects, subObjects...) - edges = append(edges, subEdges...) - } - break - } - } - - return descendantsObjects, edges -} - -func (g *Graph) InjectNestedGraph(tempGraph *Graph, parent *Object) { - obj := tempGraph.Root.ChildrenArray[0] - obj.MoveWithDescendantsTo(0, 0) - obj.Parent = parent - for _, obj := range tempGraph.Objects { - obj.Graph = g - } - g.Objects = append(g.Objects, tempGraph.Objects...) - parent.Children[strings.ToLower(obj.ID)] = obj - parent.ChildrenArray = append(parent.ChildrenArray, obj) - g.Edges = append(g.Edges, tempGraph.Edges...) -} - func (g *Graph) PrintString() string { buf := &bytes.Buffer{} fmt.Fprint(buf, "Objects: [") @@ -1919,54 +1834,6 @@ func (obj *Object) IterDescendants(apply func(parent, child *Object)) { } } -// ShiftDescendants moves Object's descendants (not including itself) -// descendants' edges are also moved by the same dx and dy (the whole route is moved if both ends are a descendant) -func (obj *Object) ShiftDescendants(dx, dy float64) { - // also need to shift edges of descendants that are shifted - movedEdges := make(map[*Edge]struct{}) - for _, e := range obj.Graph.Edges { - isSrcDesc := e.Src.IsDescendantOf(obj) - isDstDesc := e.Dst.IsDescendantOf(obj) - - if isSrcDesc && isDstDesc { - movedEdges[e] = struct{}{} - for _, p := range e.Route { - p.X += dx - p.Y += dy - } - } - } - - obj.IterDescendants(func(_, curr *Object) { - curr.TopLeft.X += dx - curr.TopLeft.Y += dy - for _, e := range obj.Graph.Edges { - if _, ok := movedEdges[e]; ok { - continue - } - isSrc := e.Src == curr - isDst := e.Dst == curr - - if isSrc && isDst { - for _, p := range e.Route { - p.X += dx - p.Y += dy - } - } else if isSrc { - e.Route[0].X += dx - e.Route[0].Y += dy - } else if isDst { - e.Route[len(e.Route)-1].X += dx - e.Route[len(e.Route)-1].Y += dy - } - - if isSrc || isDst { - movedEdges[e] = struct{}{} - } - } - }) -} - func (obj *Object) IsMultiple() bool { return obj.Style.Multiple != nil && obj.Style.Multiple.Value == "true" } @@ -1974,52 +1841,3 @@ func (obj *Object) IsMultiple() bool { func (obj *Object) Is3D() bool { return obj.Style.ThreeDee != nil && obj.Style.ThreeDee.Value == "true" } - -// GetModifierElementAdjustments returns width/height adjustments to account for shapes with 3d or multiple -func (obj *Object) GetModifierElementAdjustments() (dx, dy float64) { - if obj.Is3D() { - if obj.Shape.Value == d2target.ShapeHexagon { - dy = d2target.THREE_DEE_OFFSET / 2 - } else { - dy = d2target.THREE_DEE_OFFSET - } - dx = d2target.THREE_DEE_OFFSET - } else if obj.IsMultiple() { - dy = d2target.MULTIPLE_OFFSET - dx = d2target.MULTIPLE_OFFSET - } - 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/d2graph/layout.go b/d2graph/layout.go new file mode 100644 index 000000000..43342daa1 --- /dev/null +++ b/d2graph/layout.go @@ -0,0 +1,191 @@ +package d2graph + +import ( + "strings" + + "oss.terrastruct.com/d2/d2target" + "oss.terrastruct.com/d2/lib/geo" + "oss.terrastruct.com/d2/lib/label" + "oss.terrastruct.com/d2/lib/shape" +) + +func (obj *Object) MoveWithDescendants(dx, dy float64) { + obj.TopLeft.X += dx + obj.TopLeft.Y += dy + for _, child := range obj.ChildrenArray { + child.MoveWithDescendants(dx, dy) + } +} + +func (obj *Object) MoveWithDescendantsTo(x, y float64) { + dx := x - obj.TopLeft.X + dy := y - obj.TopLeft.Y + obj.MoveWithDescendants(dx, dy) +} + +func (parent *Object) removeChild(child *Object) { + delete(parent.Children, strings.ToLower(child.ID)) + for i := 0; i < len(parent.ChildrenArray); i++ { + if parent.ChildrenArray[i] == child { + parent.ChildrenArray = append(parent.ChildrenArray[:i], parent.ChildrenArray[i+1:]...) + break + } + } +} + +// remove obj and all descendants from graph, as a new Graph +func (g *Graph) ExtractAsNestedGraph(obj *Object) *Graph { + descendantObjects, edges := pluckObjAndEdges(g, obj) + + tempGraph := NewGraph() + tempGraph.Root.ChildrenArray = []*Object{obj} + tempGraph.Root.Children[strings.ToLower(obj.ID)] = obj + + for _, descendantObj := range descendantObjects { + descendantObj.Graph = tempGraph + } + tempGraph.Objects = descendantObjects + tempGraph.Edges = edges + + obj.Parent.removeChild(obj) + obj.Parent = tempGraph.Root + + return tempGraph +} + +func pluckObjAndEdges(g *Graph, obj *Object) (descendantsObjects []*Object, edges []*Edge) { + for i := 0; i < len(g.Edges); i++ { + edge := g.Edges[i] + if edge.Src == obj || edge.Dst == obj { + edges = append(edges, edge) + g.Edges = append(g.Edges[:i], g.Edges[i+1:]...) + i-- + } + } + + for i := 0; i < len(g.Objects); i++ { + temp := g.Objects[i] + if temp.AbsID() == obj.AbsID() { + descendantsObjects = append(descendantsObjects, obj) + g.Objects = append(g.Objects[:i], g.Objects[i+1:]...) + for _, child := range obj.ChildrenArray { + subObjects, subEdges := pluckObjAndEdges(g, child) + descendantsObjects = append(descendantsObjects, subObjects...) + edges = append(edges, subEdges...) + } + break + } + } + + return descendantsObjects, edges +} + +func (g *Graph) InjectNestedGraph(tempGraph *Graph, parent *Object) { + obj := tempGraph.Root.ChildrenArray[0] + obj.MoveWithDescendantsTo(0, 0) + obj.Parent = parent + for _, obj := range tempGraph.Objects { + obj.Graph = g + } + g.Objects = append(g.Objects, tempGraph.Objects...) + parent.Children[strings.ToLower(obj.ID)] = obj + parent.ChildrenArray = append(parent.ChildrenArray, obj) + g.Edges = append(g.Edges, tempGraph.Edges...) +} + +// ShiftDescendants moves Object's descendants (not including itself) +// descendants' edges are also moved by the same dx and dy (the whole route is moved if both ends are a descendant) +func (obj *Object) ShiftDescendants(dx, dy float64) { + // also need to shift edges of descendants that are shifted + movedEdges := make(map[*Edge]struct{}) + for _, e := range obj.Graph.Edges { + isSrcDesc := e.Src.IsDescendantOf(obj) + isDstDesc := e.Dst.IsDescendantOf(obj) + + if isSrcDesc && isDstDesc { + movedEdges[e] = struct{}{} + for _, p := range e.Route { + p.X += dx + p.Y += dy + } + } + } + + obj.IterDescendants(func(_, curr *Object) { + curr.TopLeft.X += dx + curr.TopLeft.Y += dy + for _, e := range obj.Graph.Edges { + if _, ok := movedEdges[e]; ok { + continue + } + isSrc := e.Src == curr + isDst := e.Dst == curr + + if isSrc && isDst { + for _, p := range e.Route { + p.X += dx + p.Y += dy + } + } else if isSrc { + e.Route[0].X += dx + e.Route[0].Y += dy + } else if isDst { + e.Route[len(e.Route)-1].X += dx + e.Route[len(e.Route)-1].Y += dy + } + + if isSrc || isDst { + movedEdges[e] = struct{}{} + } + } + }) +} + +// GetModifierElementAdjustments returns width/height adjustments to account for shapes with 3d or multiple +func (obj *Object) GetModifierElementAdjustments() (dx, dy float64) { + if obj.Is3D() { + if obj.Shape.Value == d2target.ShapeHexagon { + dy = d2target.THREE_DEE_OFFSET / 2 + } else { + dy = d2target.THREE_DEE_OFFSET + } + dx = d2target.THREE_DEE_OFFSET + } else if obj.IsMultiple() { + dy = d2target.MULTIPLE_OFFSET + dx = d2target.MULTIPLE_OFFSET + } + 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 +}