diff --git a/d2layouts/d2sequence/layout.go b/d2layouts/d2sequence/layout.go index b11df4eaf..3f8650259 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()) @@ -76,7 +79,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 @@ -86,8 +89,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 4ffce67a9..5f2368c9b 100644 --- a/d2layouts/d2sequence/layout_test.go +++ b/d2layouts/d2sequence/layout_test.go @@ -159,6 +159,7 @@ func TestSpansSequenceDiagram(t *testing.T) { // ├┐──────────────────────► // t2 ││ │ // ├┘◄─────────────────────┤ + input := ` shape: sequence_diagram a: { shape: person } @@ -182,7 +183,6 @@ b -> a.t2` a, has := g.Root.HasChild([]string{"a"}) assert.True(t, has) - a.Box = geo.NewBox(nil, 100, 100) a_t1, has := a.HasChild([]string{"t1"}) assert.True(t, has) @@ -197,6 +197,12 @@ b -> a.t2` b_t1, has := b.HasChild([]string{"t1"}) assert.True(t, has) + a.Box = geo.NewBox(nil, 100, 100) + a_t1.Box = geo.NewBox(nil, 100, 100) + a_t2.Box = geo.NewBox(nil, 100, 100) + b.Box = geo.NewBox(nil, 30, 30) + b_t1.Box = geo.NewBox(nil, 100, 100) + d2sequence.Layout(ctx, g, func(ctx context.Context, g *d2graph.Graph) error { // just set some position as if it had been properly placed for _, obj := range g.Objects { @@ -304,6 +310,7 @@ container -> c: edge 1 a_t1, has := a.HasChild([]string{"t1"}) assert.True(t, has) + a_t1.Box = geo.NewBox(nil, 100, 100) b, has := container.HasChild([]string{"b"}) assert.True(t, has) @@ -311,6 +318,7 @@ container -> c: edge 1 b_t1, has := b.HasChild([]string{"t1"}) assert.True(t, has) + b_t1.Box = geo.NewBox(nil, 100, 100) c := g.Root.EnsureChild([]string{"c"}) c.Box = geo.NewBox(nil, 100, 100) @@ -366,3 +374,90 @@ container -> c: edge 1 } } } + +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) + } +} + +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 88f49af3f..3df170756 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" @@ -28,9 +29,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 yStep float64 actorXStep float64 @@ -137,8 +137,8 @@ func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) *se notes: 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), yStep: MIN_MESSAGE_DISTANCE, actorXStep: MIN_ACTOR_DISTANCE, maxActorHeight: 0., @@ -185,18 +185,28 @@ func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) *se } } - for rank, message := range sd.messages { + for _, message := range sd.messages { sd.verticalIndices[message.AbsID()] = getEdgeEarliestLineNum(message) sd.yStep = math.Max(sd.yStep, 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.yStep += VERTICAL_PAD @@ -208,23 +218,17 @@ func newSequenceDiagram(objects []*d2graph.Object, messages []*d2graph.Edge) *se 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() - sd.placeSpans() sd.placeNotes() - sd.routeMessages() + if err := sd.routeMessages(); err != nil { + return err + } + sd.placeSpans() + sd.adjustRouteEndpoints() sd.placeGroups() sd.addLifelineEdges() + return nil } func (sd *sequenceDiagram) placeGroups() { @@ -313,11 +317,10 @@ func (sd *sequenceDiagram) placeActors() { // │ // │ func (sd *sequenceDiagram) addLifelineEdges() { + lastRoute := sd.messages[len(sd.messages)-1].Route endY := 0. - for _, m := range sd.messages { - for _, p := range m.Route { - endY = math.Max(endY, p.Y) - } + for _, p := range lastRoute { + endY = math.Max(endY, p.Y) } for _, note := range sd.notes { endY = math.Max(endY, note.TopLeft.Y+note.Height) @@ -415,12 +418,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 @@ -442,52 +454,89 @@ func (sd *sequenceDiagram) placeSpans() { } } -// routeMessages routes horizontal edges (messages) from Src to Dst -func (sd *sequenceDiagram) routeMessages() { - for rank, message := range sd.messages { - message.ZIndex = 2 - isLeftToRight := message.Src.TopLeft.X < message.Dst.TopLeft.X - - // 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 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 - } - - messageY := sd.getMessageY(rank) - +// 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 { + messageOffset := sd.maxActorHeight + sd.yStep + for _, message := range sd.messages { + noteOffset := 0. for _, note := range sd.notes { if sd.verticalIndices[note.AbsID()] < sd.verticalIndices[message.AbsID()] { - messageY += note.Height + sd.yStep + noteOffset += note.Height + sd.yStep } } + startY := messageOffset + noteOffset - message.Route = []*geo.Point{ - geo.NewPoint(startX, messageY), - geo.NewPoint(endX, messageY), + message.ZIndex = 2 + 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()) + } + isToDescendant := strings.HasPrefix(message.Dst.AbsID(), message.Src.AbsID()) + isFromDescendant := strings.HasPrefix(message.Src.AbsID(), message.Dst.AbsID()) + isSelfMessage := message.Src == message.Dst + + if isSelfMessage || isToDescendant || isFromDescendant { + midX := startX + MIN_MESSAGE_DISTANCE + endY := startY + MIN_MESSAGE_DISTANCE + message.Route = []*geo.Point{ + geo.NewPoint(startX, startY), + geo.NewPoint(midX, startY), + geo.NewPoint(midX, endY), + geo.NewPoint(endX, endY), + } + } else { + message.Route = []*geo.Point{ + geo.NewPoint(startX, startY), + geo.NewPoint(endX, startY), + } + } + messageOffset += sd.yStep if message.Attributes.Label.Value != "" { message.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter)) } } + 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.yStep) + 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 { @@ -501,8 +550,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 61cbdff7e..d256a29ed 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`, }, { name: "icon-label", diff --git a/e2etests/testdata/stable/sequence_diagram_real/dagre/board.exp.json b/e2etests/testdata/stable/sequence_diagram_real/dagre/board.exp.json index 296963097..4381d3065 100644 --- a/e2etests/testdata/stable/sequence_diagram_real/dagre/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_real/dagre/board.exp.json @@ -9,7 +9,7 @@ "y": 0 }, "width": 3633, - "height": 2055, + "height": 2311, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -357,7 +357,7 @@ "type": "rectangle", "pos": { "x": 1304, - "y": 869 + "y": 1125 }, "width": 20, "height": 422, @@ -473,7 +473,7 @@ "type": "rectangle", "pos": { "x": 1727, - "y": 1649 + "y": 1905 }, "width": 20, "height": 292, diff --git a/e2etests/testdata/stable/sequence_diagram_real/dagre/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_real/dagre/sketch.exp.svg index b00bf663b..818b2370e 100644 --- a/e2etests/testdata/stable/sequence_diagram_real/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_real/dagre/sketch.exp.svg @@ -14,7 +14,7 @@ width="3833" height="2511" viewBox="-100 -100 3833 2511">How this is renderedCLId2astd2compilerd2layoutd2exporterd2themesd2rendererd2sequencelayoutd2dagrelayoutmeasurements also take place 'How this is rendered: {...}'tokenized ASTcompile ASTobjects and edgesrun layout enginesrun engine on shape: sequence_diagram, temporarily removerun core engine on rest add back in sequence diagramsdiagram with correct positions and dimensionsexport diagram with chosen theme and rendererget theme stylesrender to SVGresulting SVG +How this is renderedCLId2astd2compilerd2layoutd2exporterd2themesd2rendererd2sequencelayoutd2dagrelayoutmeasurements also take place 'How this is rendered: {...}'tokenized ASTcompile ASTobjects and edgesrun layout enginesrun engine on shape: sequence_diagram, temporarily removerun core engine on rest add back in sequence diagramsdiagram with correct positions and dimensionsexport diagram with chosen theme and rendererget theme stylesrender to SVGresulting SVG diff --git a/e2etests/testdata/stable/sequence_diagram_real/elk/board.exp.json b/e2etests/testdata/stable/sequence_diagram_real/elk/board.exp.json index 5c2f76e22..4ca4af78c 100644 --- a/e2etests/testdata/stable/sequence_diagram_real/elk/board.exp.json +++ b/e2etests/testdata/stable/sequence_diagram_real/elk/board.exp.json @@ -9,7 +9,7 @@ "y": 12 }, "width": 3633, - "height": 2055, + "height": 2311, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -357,7 +357,7 @@ "type": "rectangle", "pos": { "x": 1316, - "y": 881 + "y": 1137 }, "width": 20, "height": 422, @@ -473,7 +473,7 @@ "type": "rectangle", "pos": { "x": 1739, - "y": 1661 + "y": 1917 }, "width": 20, "height": 292, diff --git a/e2etests/testdata/stable/sequence_diagram_real/elk/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_real/elk/sketch.exp.svg index 96aba78e2..a9f8aa371 100644 --- a/e2etests/testdata/stable/sequence_diagram_real/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/sequence_diagram_real/elk/sketch.exp.svg @@ -14,7 +14,7 @@ width="3833" height="2511" viewBox="-88 -88 3833 2511">How this is renderedCLId2astd2compilerd2layoutd2exporterd2themesd2rendererd2sequencelayoutd2dagrelayoutmeasurements also take place 'How this is rendered: {...}'tokenized ASTcompile ASTobjects and edgesrun layout enginesrun engine on shape: sequence_diagram, temporarily removerun core engine on rest add back in sequence diagramsdiagram with correct positions and dimensionsexport diagram with chosen theme and rendererget theme stylesrender to SVGresulting SVG +How this is renderedCLId2astd2compilerd2layoutd2exporterd2themesd2rendererd2sequencelayoutd2dagrelayoutmeasurements also take place 'How this is rendered: {...}'tokenized ASTcompile ASTobjects and edgesrun layout enginesrun engine on shape: sequence_diagram, temporarily removerun core engine on rest add back in sequence diagramsdiagram with correct positions and dimensionsexport diagram with chosen theme and rendererget theme stylesrender to SVGresulting SVG diff --git a/e2etests/testdata/stable/sequence_diagram_self_edges/dagre/board.exp.json b/e2etests/testdata/stable/sequence_diagram_self_edges/dagre/board.exp.json new file mode 100644 index 000000000..7bb323ba1 --- /dev/null +++ b/e2etests/testdata/stable/sequence_diagram_self_edges/dagre/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": "INSIDE_MIDDLE_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": "INSIDE_MIDDLE_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": 6, + "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": 1259 + } + ], + "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": 6, + "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": 1259 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ] +} diff --git a/e2etests/testdata/stable/sequence_diagram_self_edges/dagre/sketch.exp.svg b/e2etests/testdata/stable/sequence_diagram_self_edges/dagre/sketch.exp.svg new file mode 100644 index 000000000..fe2e24cb0 --- /dev/null +++ b/e2etests/testdata/stable/sequence_diagram_self_edges/dagre/sketch.exp.svg @@ -0,0 +1,39 @@ + +ab a self edge herebetween actorsto descendantto deeper descendantto parentactor + + + + + + + + \ 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..7bb323ba1 --- /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": "INSIDE_MIDDLE_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": "INSIDE_MIDDLE_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": 6, + "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": 1259 + } + ], + "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": 6, + "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": 1259 + } + ], + "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..fe2e24cb0 --- /dev/null +++ b/e2etests/testdata/stable/sequence_diagram_self_edges/elk/sketch.exp.svg @@ -0,0 +1,39 @@ + +ab a self edge herebetween actorsto descendantto deeper descendantto parentactor + + + + + + + + \ No newline at end of file