From e53c97ebec04bcd07192543ffd8c2ad6e3153e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Sat, 3 Dec 2022 09:38:08 -0800 Subject: [PATCH] Handle edges to/from descendants --- d2layouts/d2sequence/layout.go | 11 +- d2layouts/d2sequence/layout_test.go | 56 ++ d2layouts/d2sequence/sequence_diagram.go | 161 +++-- e2etests/stable_test.go | 10 + .../dagre/board.exp.json | 40 +- .../dagre/sketch.exp.svg | 4 +- .../elk/board.exp.json | 40 +- .../elk/sketch.exp.svg | 4 +- .../dagre/board.exp.json | 12 +- .../dagre/sketch.exp.svg | 4 +- .../elk/board.exp.json | 12 +- .../elk/sketch.exp.svg | 4 +- .../dagre/board.exp.json | 658 ++++++++++++++++++ .../dagre/sketch.exp.svg | 43 ++ .../elk/board.exp.json | 658 ++++++++++++++++++ .../elk/sketch.exp.svg | 43 ++ .../dagre/board.exp.json | 10 +- .../dagre/sketch.exp.svg | 4 +- .../elk/board.exp.json | 10 +- .../elk/sketch.exp.svg | 4 +- .../dagre/board.exp.json | 12 +- .../dagre/sketch.exp.svg | 4 +- .../sequence_diagram_span/elk/board.exp.json | 12 +- .../sequence_diagram_span/elk/sketch.exp.svg | 4 +- .../sequence_diagrams/dagre/board.exp.json | 206 +++--- .../sequence_diagrams/dagre/sketch.exp.svg | 4 +- .../sequence_diagrams/elk/board.exp.json | 156 ++--- .../sequence_diagrams/elk/sketch.exp.svg | 4 +- 28 files changed, 1849 insertions(+), 341 deletions(-) create mode 100644 e2etests/testdata/stable/sequence_diagram_self_edges/dagre/board.exp.json create mode 100644 e2etests/testdata/stable/sequence_diagram_self_edges/dagre/sketch.exp.svg create mode 100644 e2etests/testdata/stable/sequence_diagram_self_edges/elk/board.exp.json create mode 100644 e2etests/testdata/stable/sequence_diagram_self_edges/elk/sketch.exp.svg diff --git a/d2layouts/d2sequence/layout.go b/d2layouts/d2sequence/layout.go index 5078c1634..1caabf230 100644 --- a/d2layouts/d2sequence/layout.go +++ b/d2layouts/d2sequence/layout.go @@ -37,7 +37,10 @@ func Layout(ctx context.Context, g *d2graph.Graph, layout func(ctx context.Conte continue } - sd := layoutSequenceDiagram(g, obj) + sd, err := layoutSequenceDiagram(g, obj) + if err != nil { + return err + } obj.Children = make(map[string]*d2graph.Object) obj.ChildrenArray = nil obj.Box = geo.NewBox(nil, sd.getWidth(), sd.getHeight()) @@ -73,7 +76,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, layout func(ctx context.Conte } // layoutSequenceDiagram finds the edges inside the sequence diagram and performs the layout on the object descendants -func layoutSequenceDiagram(g *d2graph.Graph, obj *d2graph.Object) *sequenceDiagram { +func layoutSequenceDiagram(g *d2graph.Graph, obj *d2graph.Object) (*sequenceDiagram, error) { var edges []*d2graph.Edge for _, edge := range g.Edges { // both Src and Dst must be inside the sequence diagram @@ -83,8 +86,8 @@ func layoutSequenceDiagram(g *d2graph.Graph, obj *d2graph.Object) *sequenceDiagr } sd := newSequenceDiagram(obj.ChildrenArray, edges) - sd.layout() - return sd + err := sd.layout() + return sd, err } func getLayoutEdges(g *d2graph.Graph, toRemove map[*d2graph.Edge]struct{}) ([]*d2graph.Edge, map[string]int) { diff --git a/d2layouts/d2sequence/layout_test.go b/d2layouts/d2sequence/layout_test.go index e2b9c5ecc..31f2365f7 100644 --- a/d2layouts/d2sequence/layout_test.go +++ b/d2layouts/d2sequence/layout_test.go @@ -180,14 +180,17 @@ func TestSpansSequenceDiagram(t *testing.T) { Shape: d2graph.Scalar{Value: shape.PERSON_TYPE}, } a_t1 := a.EnsureChild([]string{"t1"}) + a_t1.Box = geo.NewBox(nil, 100, 100) a_t1.Attributes = d2graph.Attributes{ Shape: d2graph.Scalar{Value: shape.DIAMOND_TYPE}, Label: d2graph.Scalar{Value: "label"}, } a_t2 := a.EnsureChild([]string{"t2"}) + a_t2.Box = geo.NewBox(nil, 100, 100) b := g.Root.EnsureChild([]string{"b"}) b.Box = geo.NewBox(nil, 30, 30) b_t1 := b.EnsureChild([]string{"t1"}) + b_t1.Box = geo.NewBox(nil, 100, 100) g.Edges = []*d2graph.Edge{ { @@ -303,9 +306,11 @@ func TestNestedSequenceDiagrams(t *testing.T) { a.Box = geo.NewBox(nil, 100, 100) a.Attributes.Shape = d2graph.Scalar{Value: shape.PERSON_TYPE} a_t1 := a.EnsureChild([]string{"t1"}) + a_t1.Box = geo.NewBox(nil, 100, 100) b := container.EnsureChild([]string{"b"}) b.Box = geo.NewBox(nil, 30, 30) b_t1 := b.EnsureChild([]string{"t1"}) + b_t1.Box = geo.NewBox(nil, 100, 100) c := g.Root.EnsureChild([]string{"c"}) c.Box = geo.NewBox(nil, 100, 100) @@ -419,3 +424,54 @@ func TestSelfEdges(t *testing.T) { t.Fatalf("expected route height to be %.f5, got %.5f", MIN_MESSAGE_DISTANCE, route[3].Y-route[0].Y) } } + +func TestSequenceToDescendant(t *testing.T) { + g := d2graph.NewGraph(nil) + g.Root.Attributes.Shape = d2graph.Scalar{Value: d2target.ShapeSequenceDiagram} + a := g.Root.EnsureChild([]string{"a"}) + a.Box = geo.NewBox(nil, 100, 100) + a.Attributes = d2graph.Attributes{ + Shape: d2graph.Scalar{Value: shape.PERSON_TYPE}, + } + a_t1 := a.EnsureChild([]string{"t1"}) + a_t1.Box = geo.NewBox(nil, 16, 80) + + g.Edges = []*d2graph.Edge{ + { + Src: a, + Dst: a_t1, + Index: 0, + }, { + Src: a_t1, + Dst: a, + Index: 0, + }, + } + + ctx := log.WithTB(context.Background(), t, nil) + Layout(ctx, g, func(ctx context.Context, g *d2graph.Graph) error { + return nil + }) + + route1 := g.Edges[0].Route + if len(route1) != 4 { + t.Fatal("expected route with 4 points") + } + if route1[0].X != a.Center().X { + t.Fatal("expected route to start at `a` lifeline") + } + if route1[3].X != a_t1.TopLeft.X+a_t1.Width { + t.Fatal("expected route to end at `a.t1` right side") + } + + route2 := g.Edges[1].Route + if len(route2) != 4 { + t.Fatal("expected route with 4 points") + } + if route2[0].X != a_t1.TopLeft.X+a_t1.Width { + t.Fatal("expected route to start at `a.t1` right side") + } + if route2[3].X != a.Center().X { + t.Fatal("expected route to end at `a` lifeline") + } +} diff --git a/d2layouts/d2sequence/sequence_diagram.go b/d2layouts/d2sequence/sequence_diagram.go index ca51a6243..ba40d6bfa 100644 --- a/d2layouts/d2sequence/sequence_diagram.go +++ b/d2layouts/d2sequence/sequence_diagram.go @@ -4,6 +4,7 @@ import ( "fmt" "math" "sort" + "strings" "oss.terrastruct.com/util-go/go2" @@ -26,9 +27,8 @@ type sequenceDiagram struct { objectRank map[*d2graph.Object]int // keep track of the first and last message of a given actor/span - // the message rank is the order in which it appears from top to bottom - minMessageRank map[*d2graph.Object]int - maxMessageRank map[*d2graph.Object]int + firstMessage map[*d2graph.Object]*d2graph.Edge + lastMessage map[*d2graph.Object]*d2graph.Edge messageYStep float64 actorXStep float64 @@ -42,8 +42,8 @@ func newSequenceDiagram(actors []*d2graph.Object, messages []*d2graph.Edge) *seq spans: nil, lifelines: nil, objectRank: make(map[*d2graph.Object]int), - minMessageRank: make(map[*d2graph.Object]int), - maxMessageRank: make(map[*d2graph.Object]int), + firstMessage: make(map[*d2graph.Object]*d2graph.Edge), + lastMessage: make(map[*d2graph.Object]*d2graph.Edge), messageYStep: MIN_MESSAGE_DISTANCE, actorXStep: MIN_ACTOR_DISTANCE, maxActorHeight: 0., @@ -76,17 +76,27 @@ func newSequenceDiagram(actors []*d2graph.Object, messages []*d2graph.Edge) *seq } } - for rank, message := range sd.messages { + for _, message := range sd.messages { sd.messageYStep = math.Max(sd.messageYStep, float64(message.LabelDimensions.Height)) - sd.setMinMaxMessageRank(message.Src, rank) - sd.setMinMaxMessageRank(message.Dst, rank) - // ensures that long labels, spanning over multiple actors, don't make for large gaps between actors // by distributing the label length across the actors rank difference rankDiff := math.Abs(float64(sd.objectRank[message.Src]) - float64(sd.objectRank[message.Dst])) - distributedLabelWidth := float64(message.LabelDimensions.Width) / rankDiff - sd.actorXStep = math.Max(sd.actorXStep, distributedLabelWidth+HORIZONTAL_PAD) + if rankDiff != 0 { + // rankDiff = 0 for self edges + distributedLabelWidth := float64(message.LabelDimensions.Width) / rankDiff + sd.actorXStep = math.Max(sd.actorXStep, distributedLabelWidth+HORIZONTAL_PAD) + + } + sd.lastMessage[message.Src] = message + if _, exists := sd.firstMessage[message.Src]; !exists { + sd.firstMessage[message.Src] = message + } + sd.lastMessage[message.Dst] = message + if _, exists := sd.firstMessage[message.Dst]; !exists { + sd.firstMessage[message.Dst] = message + } + } sd.messageYStep += VERTICAL_PAD @@ -98,21 +108,15 @@ func newSequenceDiagram(actors []*d2graph.Object, messages []*d2graph.Edge) *seq return sd } -func (sd *sequenceDiagram) setMinMaxMessageRank(actor *d2graph.Object, rank int) { - if minRank, exists := sd.minMessageRank[actor]; exists { - sd.minMessageRank[actor] = go2.IntMin(minRank, rank) - } else { - sd.minMessageRank[actor] = rank - } - - sd.maxMessageRank[actor] = go2.IntMax(sd.maxMessageRank[actor], rank) -} - -func (sd *sequenceDiagram) layout() { +func (sd *sequenceDiagram) layout() error { sd.placeActors() + if err := sd.routeMessages(); err != nil { + return err + } sd.placeSpans() - sd.routeMessages() + sd.adjustRouteEndpoints() sd.addLifelineEdges() + return nil } // placeActors places actors bottom aligned, side by side @@ -210,12 +214,21 @@ func (sd *sequenceDiagram) placeSpans() { // finds the position if there are messages to this span minMessageY := math.Inf(1) - if minRank, exists := sd.minMessageRank[span]; exists { - minMessageY = sd.getMessageY(minRank) + if firstMessage, exists := sd.firstMessage[span]; exists { + // needs to check Src/Dst because of self-edges or edges to/from descendants + if span == firstMessage.Src { + minMessageY = firstMessage.Route[0].Y + } else { + minMessageY = firstMessage.Route[len(firstMessage.Route)-1].Y + } } maxMessageY := math.Inf(-1) - if maxRank, exists := sd.maxMessageRank[span]; exists { - maxMessageY = sd.getMessageY(maxRank) + if lastMessage, exists := sd.lastMessage[span]; exists { + if span == lastMessage.Src { + maxMessageY = lastMessage.Route[0].Y + } else { + maxMessageY = lastMessage.Route[len(lastMessage.Route)-1].Y + } } // if it is the same as the child top left, add some padding @@ -237,40 +250,36 @@ func (sd *sequenceDiagram) placeSpans() { } } -// routeMessages routes horizontal edges (messages) from Src to Dst -func (sd *sequenceDiagram) routeMessages() { - for rank, message := range sd.messages { +// routeMessages routes horizontal edges (messages) from Src to Dst lifeline (actor/span center) +// in another step, routes are adjusted to spans borders when necessary +func (sd *sequenceDiagram) routeMessages() error { + startY := sd.maxActorHeight + sd.messageYStep + for _, message := range sd.messages { message.ZIndex = 2 - isLeftToRight := message.Src.TopLeft.X < message.Dst.TopLeft.X + var startX, endX float64 + if startCenter := getCenter(message.Src); startCenter != nil { + startX = startCenter.X + } else { + return fmt.Errorf("could not find center of %s", message.Src.AbsID()) + } + if endCenter := getCenter(message.Dst); endCenter != nil { + endX = endCenter.X + } else { + return fmt.Errorf("could not find center of %s", message.Dst.AbsID()) + } + isLeftToRight := startX < endX + isToDescendant := strings.HasPrefix(message.Dst.AbsID(), message.Src.AbsID()) + isFromDescendant := strings.HasPrefix(message.Src.AbsID(), message.Dst.AbsID()) isSelfMessage := message.Src == message.Dst - // finds the proper anchor point based on the message direction - var startX, endX float64 - if sd.isActor(message.Src) { - startX = message.Src.Center().X - } else if isLeftToRight { - startX = message.Src.TopLeft.X + message.Src.Width - } else { - startX = message.Src.TopLeft.X - } - - if isSelfMessage { - endX = startX - } else if sd.isActor(message.Dst) { - endX = message.Dst.Center().X - } else if isLeftToRight { - endX = message.Dst.TopLeft.X - } else { - endX = message.Dst.TopLeft.X + message.Dst.Width - } - - startY := sd.getMessageY(rank) - if isSelfMessage { + if isSelfMessage || isToDescendant || isFromDescendant { + midX := startX + MIN_MESSAGE_DISTANCE + endY := startY + MIN_MESSAGE_DISTANCE 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), + geo.NewPoint(midX, startY), + geo.NewPoint(midX, endY), + geo.NewPoint(endX, endY), } } else { message.Route = []*geo.Point{ @@ -278,9 +287,10 @@ func (sd *sequenceDiagram) routeMessages() { geo.NewPoint(endX, startY), } } + startY += sd.messageYStep if message.Attributes.Label.Value != "" { - if isSelfMessage { + if isSelfMessage || isFromDescendant || isToDescendant { message.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter)) } else if isLeftToRight { message.LabelPosition = go2.Pointer(string(label.OutsideTopCenter)) @@ -290,11 +300,39 @@ func (sd *sequenceDiagram) routeMessages() { } } } + return nil } -func (sd *sequenceDiagram) getMessageY(rank int) float64 { - // +1 so that the first message has the top padding for its label - return ((float64(rank) + 1.) * sd.messageYStep) + sd.maxActorHeight +func getCenter(obj *d2graph.Object) *geo.Point { + if obj == nil { + return nil + } else if obj.TopLeft != nil { + return obj.Center() + } + return getCenter(obj.Parent) +} + +// adjustRouteEndpoints adjust the first and last points of message routes when they are spans +// routeMessages() will route to the actor lifelife as a reference point and this function +// adjust to span width when necessary +func (sd *sequenceDiagram) adjustRouteEndpoints() { + for _, message := range sd.messages { + route := message.Route + if !sd.isActor(message.Src) { + if sd.objectRank[message.Src] <= sd.objectRank[message.Dst] { + route[0].X += message.Src.Width / 2. + } else { + route[0].X -= message.Src.Width / 2. + } + } + if !sd.isActor(message.Dst) { + if sd.objectRank[message.Src] < sd.objectRank[message.Dst] { + route[len(route)-1].X -= message.Dst.Width / 2. + } else { + route[len(route)-1].X += message.Dst.Width / 2. + } + } + } } func (sd *sequenceDiagram) isActor(obj *d2graph.Object) bool { @@ -308,8 +346,7 @@ func (sd *sequenceDiagram) getWidth() float64 { } func (sd *sequenceDiagram) getHeight() float64 { - // the layout is always placed starting at 0, so the height is just the last message - return sd.getMessageY(len(sd.messages)) + return sd.lifelines[0].Route[1].Y } func (sd *sequenceDiagram) shift(tl *geo.Point) { diff --git a/e2etests/stable_test.go b/e2etests/stable_test.go index 2b3451fb9..20ca74e95 100644 --- a/e2etests/stable_test.go +++ b/e2etests/stable_test.go @@ -1325,6 +1325,16 @@ s -> t`, z -> y z -> z: hello `, + }, { + name: "sequence_diagram_self_edges", + script: `shape: sequence_diagram +a -> a: a self edge here +a -> b: between actors +b -> b.1: to descendant +b.1 -> b.1.2: to deeper descendant +b.1.2 -> b: to parent +b -> a.1.2: actor +a.1 -> b.3`, }, } diff --git a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json index f89bceb6a..abe4ac5c5 100644 --- a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/board.exp.json @@ -1590,7 +1590,7 @@ }, { "x": 76.5, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -1629,7 +1629,7 @@ }, { "x": 485, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -1668,7 +1668,7 @@ }, { "x": 937.5, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -1707,7 +1707,7 @@ }, { "x": 1404.5, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -1746,7 +1746,7 @@ }, { "x": 1849, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -1785,7 +1785,7 @@ }, { "x": 2279, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -1824,7 +1824,7 @@ }, { "x": 2686, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -1863,7 +1863,7 @@ }, { "x": 3093, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -1902,7 +1902,7 @@ }, { "x": 3516, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -1941,7 +1941,7 @@ }, { "x": 3939, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -1980,7 +1980,7 @@ }, { "x": 4346, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -2019,7 +2019,7 @@ }, { "x": 4753, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -2058,7 +2058,7 @@ }, { "x": 5172.5, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -2097,7 +2097,7 @@ }, { "x": 5608.5, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -2136,7 +2136,7 @@ }, { "x": 6034, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -2175,7 +2175,7 @@ }, { "x": 6448.5, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -2214,7 +2214,7 @@ }, { "x": 6868.5, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -2253,7 +2253,7 @@ }, { "x": 7314.5, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -2292,7 +2292,7 @@ }, { "x": 7753, - "y": 2834 + "y": 2784 } ], "animated": false, @@ -2331,7 +2331,7 @@ }, { "x": 8190, - "y": 2834 + "y": 2784 } ], "animated": false, diff --git a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg index 75d906f0b..527393611 100644 --- a/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_all_shapes/dagre/sketch.exp.svg @@ -2,7 +2,7 @@ \ No newline at end of file diff --git a/e2etests/testdata/stable/sequence_diagram_self_edges/elk/board.exp.json b/e2etests/testdata/stable/sequence_diagram_self_edges/elk/board.exp.json new file mode 100644 index 000000000..d600d6f76 --- /dev/null +++ b/e2etests/testdata/stable/sequence_diagram_self_edges/elk/board.exp.json @@ -0,0 +1,658 @@ +{ + "name": "", + "shapes": [ + { + "id": "a", + "type": "", + "pos": { + "x": 0, + "y": 50 + }, + "width": 150, + "height": 169, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#E3E9FD", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": null, + "methods": null, + "columns": null, + "label": "a", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 12, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "b", + "type": "", + "pos": { + "x": 400, + "y": 52 + }, + "width": 150, + "height": 167, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#E3E9FD", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": null, + "methods": null, + "columns": null, + "label": "b", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 13, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "b.1", + "type": "rectangle", + "pos": { + "x": 469, + "y": 673 + }, + "width": 12, + "height": 228, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#EDF0FD", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 15, + "labelHeight": 36, + "zIndex": 1, + "level": 2 + }, + { + "id": "b.1.2", + "type": "rectangle", + "pos": { + "x": 465, + "y": 803 + }, + "width": 20, + "height": 82, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 13, + "labelHeight": 26, + "zIndex": 1, + "level": 3 + }, + { + "id": "a.1", + "type": "rectangle", + "pos": { + "x": 69, + "y": 967 + }, + "width": 12, + "height": 178, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#EDF0FD", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 15, + "labelHeight": 36, + "zIndex": 1, + "level": 2 + }, + { + "id": "a.1.2", + "type": "rectangle", + "pos": { + "x": 65, + "y": 983 + }, + "width": 20, + "height": 80, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 13, + "labelHeight": 26, + "zIndex": 1, + "level": 3 + }, + { + "id": "b.3", + "type": "rectangle", + "pos": { + "x": 469, + "y": 1113 + }, + "width": 12, + "height": 80, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#EDF0FD", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 13, + "labelHeight": 26, + "zIndex": 1, + "level": 2 + } + ], + "connections": [ + { + "id": "(a -> a)[0]", + "src": "a", + "srcArrow": "none", + "srcLabel": "", + "dst": "a", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "a self edge here", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 103, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 75, + "y": 349 + }, + { + "x": 155, + "y": 349 + }, + { + "x": 155, + "y": 429 + }, + { + "x": 75, + "y": 429 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 2 + }, + { + "id": "(a -> b)[0]", + "src": "a", + "srcArrow": "none", + "srcLabel": "", + "dst": "b", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "between actors", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 102, + "labelHeight": 21, + "labelPosition": "OUTSIDE_TOP_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 75, + "y": 479 + }, + { + "x": 475, + "y": 479 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 2 + }, + { + "id": "(b -> b.1)[0]", + "src": "b", + "srcArrow": "none", + "srcLabel": "", + "dst": "b.1", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "to descendant", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 94, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 475, + "y": 609 + }, + { + "x": 555, + "y": 609 + }, + { + "x": 555, + "y": 689 + }, + { + "x": 481, + "y": 689 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 2 + }, + { + "id": "b.(1 -> 1.2)[0]", + "src": "b.1", + "srcArrow": "none", + "srcLabel": "", + "dst": "b.1.2", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "to deeper descendant", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 143, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 481, + "y": 739 + }, + { + "x": 555, + "y": 739 + }, + { + "x": 555, + "y": 819 + }, + { + "x": 485, + "y": 819 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 2 + }, + { + "id": "(b.1.2 -> b)[0]", + "src": "b.1.2", + "srcArrow": "none", + "srcLabel": "", + "dst": "b", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "to parent", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 62, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 485, + "y": 869 + }, + { + "x": 555, + "y": 869 + }, + { + "x": 555, + "y": 949 + }, + { + "x": 475, + "y": 949 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 2 + }, + { + "id": "(b -> a.1.2)[0]", + "src": "b", + "srcArrow": "none", + "srcLabel": "", + "dst": "a.1.2", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "actor", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 36, + "labelHeight": 21, + "labelPosition": "OUTSIDE_BOTTOM_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 475, + "y": 999 + }, + { + "x": 85, + "y": 999 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 2 + }, + { + "id": "(a.1 -> b.3)[0]", + "src": "a.1", + "srcArrow": "none", + "srcLabel": "", + "dst": "b.3", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 81, + "y": 1129 + }, + { + "x": 469, + "y": 1129 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 2 + }, + { + "id": "(a -- )[0]", + "src": "a", + "srcArrow": "none", + "srcLabel": "", + "dst": "a-lifeline-end-2251863791", + "dstArrow": "none", + "dstLabel": "", + "opacity": 1, + "strokeDash": 8, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 75, + "y": 219 + }, + { + "x": 75, + "y": 1209 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(b -- )[0]", + "src": "b", + "srcArrow": "none", + "srcLabel": "", + "dst": "b-lifeline-end-668380428", + "dstArrow": "none", + "dstLabel": "", + "opacity": 1, + "strokeDash": 8, + "strokeWidth": 2, + "stroke": "#0D32B2", + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#676C7E", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "route": [ + { + "x": 475, + "y": 219 + }, + { + "x": 475, + "y": 1209 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/stable/sequence_diagram_self_edges/elk/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_self_edges/elk/sketch.exp.svg new file mode 100644 index 000000000..8f85d0bfe --- /dev/null +++ b/e2etests/testdata/stable/sequence_diagram_self_edges/elk/sketch.exp.svg @@ -0,0 +1,43 @@ + +ab + + +a self edge herebetween actors + + +to descendant + + +to deeper descendant + + +to parentactor \ No newline at end of file diff --git a/e2etests/testdata/stable/sequence_diagram_simple/dagre/board.exp.json b/e2etests/testdata/stable/sequence_diagram_simple/dagre/board.exp.json index 853836cc5..e70040d54 100644 --- a/e2etests/testdata/stable/sequence_diagram_simple/dagre/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_simple/dagre/board.exp.json @@ -619,7 +619,7 @@ }, { "x": 81.5, - "y": 1718 + "y": 1668 } ], "animated": false, @@ -658,7 +658,7 @@ }, { "x": 555, - "y": 1718 + "y": 1668 } ], "animated": false, @@ -697,7 +697,7 @@ }, { "x": 1022, - "y": 1718 + "y": 1668 } ], "animated": false, @@ -736,7 +736,7 @@ }, { "x": 1489, - "y": 1718 + "y": 1668 } ], "animated": false, @@ -775,7 +775,7 @@ }, { "x": 1982, - "y": 1718 + "y": 1668 } ], "animated": false, diff --git a/e2etests/testdata/stable/sequence_diagram_simple/dagre/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_simple/dagre/sketch.exp.svg index ff091510a..ec6450ceb 100644 --- a/e2etests/testdata/stable/sequence_diagram_simple/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_simple/dagre/sketch.exp.svg @@ -2,7 +2,7 @@