extract function to layout sequence diagram

This commit is contained in:
Júlio César Batista 2022-12-02 09:29:15 -08:00
parent ce88184e31
commit 7c2d9cf26d
No known key found for this signature in database
GPG key ID: 10C4B861BF314878

View file

@ -20,13 +20,11 @@ import (
// Then it restores all objects with their proper layout engine and sequence diagram positions // Then it restores all objects with their proper layout engine and sequence diagram positions
func Layout(ctx context.Context, g *d2graph.Graph, layout func(ctx context.Context, g *d2graph.Graph) error) error { func Layout(ctx context.Context, g *d2graph.Graph, layout func(ctx context.Context, g *d2graph.Graph) error) error {
// flag objects to keep to avoid having to flag all descendants of sequence diagram to be removed // flag objects to keep to avoid having to flag all descendants of sequence diagram to be removed
objectsToKeep := make(map[*d2graph.Object]struct{}) objectsToRemove := make(map[*d2graph.Object]struct{})
// edges flagged to be removed (these are internal edges of the sequence diagrams) // edges flagged to be removed (these are internal edges of the sequence diagrams)
edgesToRemove := make(map[*d2graph.Edge]struct{}) edgesToRemove := make(map[*d2graph.Edge]struct{})
// store the sequence diagram related to a given node // store the sequence diagram related to a given node
sequenceDiagrams := make(map[string]*sequenceDiagram) sequenceDiagrams := make(map[string]*sequenceDiagram)
// keeps the reference of the children of a given node
childrenReplacement := make(map[string][]*d2graph.Object)
// starts in root and traverses all descendants // starts in root and traverses all descendants
queue := make([]*d2graph.Object, 1, len(g.Objects)) queue := make([]*d2graph.Object, 1, len(g.Objects))
@ -34,33 +32,26 @@ func Layout(ctx context.Context, g *d2graph.Graph, layout func(ctx context.Conte
for len(queue) > 0 { for len(queue) > 0 {
obj := queue[0] obj := queue[0]
queue = queue[1:] queue = queue[1:]
if obj.Attributes.Shape.Value != d2target.ShapeSequenceDiagram {
objectsToKeep[obj] = struct{}{}
if obj.Attributes.Shape.Value == d2target.ShapeSequenceDiagram {
// clean current children and keep a backup to restore them later
obj.Children = make(map[string]*d2graph.Object)
children := obj.ChildrenArray
obj.ChildrenArray = nil
// find the edges that belong to this sequence diagram
var edges []*d2graph.Edge
for _, edge := range g.Edges {
// both Src and Dst must be inside the sequence diagram
if strings.HasPrefix(edge.Src.AbsID(), obj.AbsID()) && strings.HasPrefix(edge.Dst.AbsID(), obj.AbsID()) {
edgesToRemove[edge] = struct{}{}
edges = append(edges, edge)
}
}
sd := newSequenceDiagram(children, edges)
sd.layout()
obj.Width = sd.getWidth()
obj.Height = sd.getHeight()
sequenceDiagrams[obj.AbsID()] = sd
childrenReplacement[obj.AbsID()] = children
} else {
// only move to children if the parent is not a sequence diagram // only move to children if the parent is not a sequence diagram
queue = append(queue, obj.ChildrenArray...) queue = append(queue, obj.ChildrenArray...)
continue
}
sd := layoutSequenceDiagram(g, obj)
obj.Children = make(map[string]*d2graph.Object)
obj.ChildrenArray = nil
sequenceDiagrams[obj.AbsID()] = sd
// flag objects and edges to remove
for _, edge := range sd.messages {
edgesToRemove[edge] = struct{}{}
}
for _, obj := range sd.actors {
objectsToRemove[obj] = struct{}{}
}
for _, obj := range sd.spans {
objectsToRemove[obj] = struct{}{}
} }
} }
@ -76,9 +67,9 @@ func Layout(ctx context.Context, g *d2graph.Graph, layout func(ctx context.Conte
// done this way (by flagging objects) instead of appending while going through the `queue` // done this way (by flagging objects) instead of appending while going through the `queue`
// because appending in that order would change the order of g.Objects which // because appending in that order would change the order of g.Objects which
// could lead to layout changes (as the order of the objects might be important for the underlying engine) // could lead to layout changes (as the order of the objects might be important for the underlying engine)
layoutObjects := make([]*d2graph.Object, 0, len(objectsToKeep)) layoutObjects := make([]*d2graph.Object, 0, len(objectsToRemove))
for _, obj := range g.Objects { for _, obj := range g.Objects {
if _, exists := objectsToKeep[obj]; exists { if _, exists := objectsToRemove[obj]; !exists {
layoutObjects = append(layoutObjects, obj) layoutObjects = append(layoutObjects, obj)
} }
} }
@ -97,15 +88,17 @@ func Layout(ctx context.Context, g *d2graph.Graph, layout func(ctx context.Conte
continue continue
} }
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter)) obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
sd := sequenceDiagrams[obj.AbsID()]
// shift the sequence diagrams as they are always placed at (0, 0) // shift the sequence diagrams as they are always placed at (0, 0)
sequenceDiagrams[obj.AbsID()].shift(obj.TopLeft) sd.shift(obj.TopLeft)
// restore children // restore children
obj.Children = make(map[string]*d2graph.Object) obj.Children = make(map[string]*d2graph.Object)
for _, child := range childrenReplacement[obj.AbsID()] { for _, child := range sd.actors {
obj.Children[child.ID] = child obj.Children[child.ID] = child
} }
obj.ChildrenArray = childrenReplacement[obj.AbsID()] obj.ChildrenArray = sd.actors
// add lifeline edges // add lifeline edges
g.Edges = append(g.Edges, sequenceDiagrams[obj.AbsID()].lifelines...) g.Edges = append(g.Edges, sequenceDiagrams[obj.AbsID()].lifelines...)
@ -116,3 +109,20 @@ func Layout(ctx context.Context, g *d2graph.Graph, layout func(ctx context.Conte
return nil return nil
} }
func layoutSequenceDiagram(g *d2graph.Graph, obj *d2graph.Object) *sequenceDiagram {
// find the edges that belong to this sequence diagram
var edges []*d2graph.Edge
for _, edge := range g.Edges {
// both Src and Dst must be inside the sequence diagram
if strings.HasPrefix(edge.Src.AbsID(), obj.AbsID()) && strings.HasPrefix(edge.Dst.AbsID(), obj.AbsID()) {
edges = append(edges, edge)
}
}
sd := newSequenceDiagram(obj.ChildrenArray, edges)
sd.layout()
obj.Width = sd.getWidth()
obj.Height = sd.getHeight()
return sd
}