diff --git a/d2layouts/d2sequence/layout_test.go b/d2layouts/d2sequence/layout_test.go index 83892aada..e2b9c5ecc 100644 --- a/d2layouts/d2sequence/layout_test.go +++ b/d2layouts/d2sequence/layout_test.go @@ -383,3 +383,39 @@ func TestNestedSequenceDiagrams(t *testing.T) { } } } + +func TestSelfEdges(t *testing.T) { + g := d2graph.NewGraph(nil) + g.Root.Attributes.Shape = d2graph.Scalar{Value: d2target.ShapeSequenceDiagram} + n1 := g.Root.EnsureChild([]string{"n1"}) + n1.Box = geo.NewBox(nil, 100, 100) + + g.Edges = []*d2graph.Edge{ + { + Src: n1, + Dst: n1, + Index: 0, + Attributes: d2graph.Attributes{ + Label: d2graph.Scalar{Value: "left to right"}, + }, + }, + } + + ctx := log.WithTB(context.Background(), t, nil) + Layout(ctx, g, func(ctx context.Context, g *d2graph.Graph) error { + return nil + }) + + route := g.Edges[0].Route + if len(route) != 4 { + t.Fatalf("expected route to have 4 points, got %d", len(route)) + } + + if route[0].X != route[3].X { + t.Fatalf("route does not end at the same actor, start at %.5f, end at %.5f", route[0].X, route[3].X) + } + + if route[3].Y-route[0].Y != MIN_MESSAGE_DISTANCE { + t.Fatalf("expected route height to be %.f5, got %.5f", MIN_MESSAGE_DISTANCE, route[3].Y-route[0].Y) + } +} diff --git a/d2layouts/d2sequence/sequence_diagram.go b/d2layouts/d2sequence/sequence_diagram.go index b08a89a6f..ca51a6243 100644 --- a/d2layouts/d2sequence/sequence_diagram.go +++ b/d2layouts/d2sequence/sequence_diagram.go @@ -142,7 +142,8 @@ func (sd *sequenceDiagram) placeActors() { // │ // │ func (sd *sequenceDiagram) addLifelineEdges() { - endY := sd.getMessageY(len(sd.messages)) + lastRoute := sd.messages[len(sd.messages)-1].Route + endY := lastRoute[len(lastRoute)-1].Y + MIN_MESSAGE_DISTANCE for _, actor := range sd.actors { actorBottom := actor.Center() actorBottom.Y = actor.TopLeft.Y + actor.Height @@ -241,6 +242,7 @@ func (sd *sequenceDiagram) routeMessages() { for rank, message := range sd.messages { message.ZIndex = 2 isLeftToRight := message.Src.TopLeft.X < message.Dst.TopLeft.X + isSelfMessage := message.Src == message.Dst // finds the proper anchor point based on the message direction var startX, endX float64 @@ -252,7 +254,9 @@ func (sd *sequenceDiagram) routeMessages() { startX = message.Src.TopLeft.X } - if sd.isActor(message.Dst) { + if isSelfMessage { + endX = startX + } else if sd.isActor(message.Dst) { endX = message.Dst.Center().X } else if isLeftToRight { endX = message.Dst.TopLeft.X @@ -260,14 +264,25 @@ func (sd *sequenceDiagram) routeMessages() { endX = message.Dst.TopLeft.X + message.Dst.Width } - messageY := sd.getMessageY(rank) - message.Route = []*geo.Point{ - geo.NewPoint(startX, messageY), - geo.NewPoint(endX, messageY), + startY := sd.getMessageY(rank) + if isSelfMessage { + message.Route = []*geo.Point{ + geo.NewPoint(startX, startY), + geo.NewPoint(startX+MIN_MESSAGE_DISTANCE, startY), + geo.NewPoint(startX+MIN_MESSAGE_DISTANCE, startY+MIN_MESSAGE_DISTANCE), + geo.NewPoint(startX, startY+MIN_MESSAGE_DISTANCE), + } + } else { + message.Route = []*geo.Point{ + geo.NewPoint(startX, startY), + geo.NewPoint(endX, startY), + } } if message.Attributes.Label.Value != "" { - if isLeftToRight { + if isSelfMessage { + message.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter)) + } else if isLeftToRight { message.LabelPosition = go2.Pointer(string(label.OutsideTopCenter)) } else { // the label will be placed above the message because the orientation is based on the edge normal vector