From a430cc6e6043025509382d9451ffa772a3ea73c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Fri, 25 Nov 2022 22:32:04 -0800 Subject: [PATCH 1/7] Add d2sequence layout and OS env for testing purposes --- cmd/d2/main.go | 9 ++- d2layouts/d2sequence/layout.go | 101 +++++++++++++++++++++++++ d2layouts/d2sequence/layout_test.go | 110 ++++++++++++++++++++++++++++ 3 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 d2layouts/d2sequence/layout.go create mode 100644 d2layouts/d2sequence/layout_test.go diff --git a/cmd/d2/main.go b/cmd/d2/main.go index a5e5c39e2..b6b4d38ea 100644 --- a/cmd/d2/main.go +++ b/cmd/d2/main.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "os" "os/exec" "path/filepath" "strings" @@ -13,6 +14,7 @@ import ( "github.com/spf13/pflag" "oss.terrastruct.com/d2" + "oss.terrastruct.com/d2/d2layouts/d2sequence" "oss.terrastruct.com/d2/d2plugin" "oss.terrastruct.com/d2/d2renderers/d2svg" "oss.terrastruct.com/d2/d2renderers/textmeasure" @@ -187,8 +189,13 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, theme return nil, err } + layout := plugin.Layout + // TODO: remove, this is just a feature flag to test sequence diagrams as we work on them + if os.Getenv("D2_SEQUENCE") == "1" { + layout = d2sequence.Layout + } d, err := d2.Compile(ctx, string(input), &d2.CompileOptions{ - Layout: plugin.Layout, + Layout: layout, Ruler: ruler, ThemeID: themeID, }) diff --git a/d2layouts/d2sequence/layout.go b/d2layouts/d2sequence/layout.go new file mode 100644 index 000000000..2c182d3fe --- /dev/null +++ b/d2layouts/d2sequence/layout.go @@ -0,0 +1,101 @@ +package d2sequence + +import ( + "context" + "fmt" + "math" + + "oss.terrastruct.com/d2/d2graph" + "oss.terrastruct.com/d2/lib/geo" + "oss.terrastruct.com/d2/lib/go2" + "oss.terrastruct.com/d2/lib/label" +) + +func Layout(ctx context.Context, g *d2graph.Graph) (err error) { + pad := 50. // 2 * 25 + edgeYStep := 100. + objectXStep := 200. + maxObjectHeight := 0. + + var objectsInOrder []*d2graph.Object + seen := make(map[*d2graph.Object]struct{}) + for _, edge := range g.Edges { + if _, exists := seen[edge.Src]; !exists { + seen[edge.Src] = struct{}{} + objectsInOrder = append(objectsInOrder, edge.Src) + } + if _, exists := seen[edge.Dst]; !exists { + seen[edge.Dst] = struct{}{} + objectsInOrder = append(objectsInOrder, edge.Dst) + } + + edgeYStep = math.Max(edgeYStep, float64(edge.LabelDimensions.Height)+pad) + objectXStep = math.Max(objectXStep, float64(edge.LabelDimensions.Width)+pad) + maxObjectHeight = math.Max(maxObjectHeight, edge.Src.Height+pad) + maxObjectHeight = math.Max(maxObjectHeight, edge.Dst.Height+pad) + } + + placeObjects(objectsInOrder, maxObjectHeight, objectXStep) + // edges are placed in the order users define them + routeEdges(g.Edges, maxObjectHeight, edgeYStep) + addLifelineEdges(g, objectsInOrder, edgeYStep) + + return nil +} + +// placeObjects places objects side by side +func placeObjects(objectsInOrder []*d2graph.Object, maxHeight, xStep float64) { + x := 0. + for _, obj := range objectsInOrder { + yDiff := maxHeight - obj.Height + obj.TopLeft = geo.NewPoint(x, yDiff/2.) + x += obj.Width + xStep + obj.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter)) + } +} + +// routeEdges routes horizontal edges from Src to Dst +func routeEdges(edgesInOrder []*d2graph.Edge, startY, yStep float64) { + edgeY := startY + yStep // in case the first edge has a tall label + for _, edge := range edgesInOrder { + start := edge.Src.Center() + start.Y = edgeY + end := edge.Dst.Center() + end.Y = edgeY + edge.Route = []*geo.Point{start, end} + edgeY += yStep + + if edge.Attributes.Label.Value != "" { + // TODO: consider label right-to-left + edge.LabelPosition = go2.Pointer(string(label.OutsideTopCenter)) + } + } +} + +func addLifelineEdges(g *d2graph.Graph, objectsInOrder []*d2graph.Object, yStep float64) { + endY := g.Edges[len(g.Edges)-1].Route[0].Y + yStep + for _, obj := range objectsInOrder { + objBottom := obj.Center() + objBottom.Y = obj.TopLeft.Y + obj.Height + objLifelineEnd := obj.Center() + objLifelineEnd.Y = endY + g.Edges = append(g.Edges, &d2graph.Edge{ + Attributes: d2graph.Attributes{ + Style: d2graph.Style{ + StrokeDash: &d2graph.Scalar{ + Value: "10", + }, + Stroke: obj.Attributes.Style.Stroke, + StrokeWidth: obj.Attributes.Style.StrokeWidth, + }, + }, + Src: obj, + SrcArrow: false, + Dst: &d2graph.Object{ + ID: obj.ID + fmt.Sprintf("-lifeline-end-%d", go2.StringToIntHash(obj.ID+"-lifeline-end")), + }, + DstArrow: false, + Route: []*geo.Point{objBottom, objLifelineEnd}, + }) + } +} diff --git a/d2layouts/d2sequence/layout_test.go b/d2layouts/d2sequence/layout_test.go new file mode 100644 index 000000000..4ffb0237f --- /dev/null +++ b/d2layouts/d2sequence/layout_test.go @@ -0,0 +1,110 @@ +package d2sequence + +import ( + "context" + "testing" + + "oss.terrastruct.com/d2/d2graph" + "oss.terrastruct.com/d2/lib/geo" + "oss.terrastruct.com/d2/lib/log" +) + +func TestLayout(t *testing.T) { + g := d2graph.NewGraph(nil) + g.Objects = []*d2graph.Object{ + { + ID: "Alice", + Box: geo.NewBox(nil, 100, 100), + }, + { + ID: "Bob", + Box: geo.NewBox(nil, 30, 30), + }, + } + + g.Edges = []*d2graph.Edge{ + { + Src: g.Objects[0], + Dst: g.Objects[1], + }, + { + Src: g.Objects[1], + Dst: g.Objects[0], + }, + { + Src: g.Objects[0], + Dst: g.Objects[1], + }, + { + Src: g.Objects[1], + Dst: g.Objects[0], + }, + } + nEdges := len(g.Edges) + + ctx := log.WithTB(context.Background(), t, nil) + Layout(ctx, g) + + // asserts that objects were placed in the expected x order and at y=0 + objectsOrder := []*d2graph.Object{ + g.Objects[0], + g.Objects[1], + } + for i := 1; i < len(objectsOrder); i++ { + if objectsOrder[i].TopLeft.X < objectsOrder[i-1].TopLeft.X { + t.Fatalf("expected object[%d].TopLeft.X > object[%d].TopLeft.X", i, i-1) + } + if objectsOrder[i].Center().Y != objectsOrder[i-1].Center().Y { + t.Fatalf("expected object[%d] and object[%d] to be at the same center y", i, i-1) + } + } + + nExpectedEdges := nEdges + len(objectsOrder) + if len(g.Edges) != nExpectedEdges { + t.Fatalf("expected %d edges, got %d", nExpectedEdges, len(g.Edges)) + } + + // assert that edges were placed in y order and have the endpoints at their objects + // uses `nEdges` because Layout creates some vertical edges to represent the object lifeline + for i := 0; i < nEdges; i++ { + edge := g.Edges[i] + if len(edge.Route) != 2 { + t.Fatalf("expected edge[%d] to have only 2 points", i) + } + if edge.Route[0].Y != edge.Route[1].Y { + t.Fatalf("expected edge[%d] to be a horizontal line", i) + } + if edge.Route[0].X != edge.Src.Center().X { + t.Fatalf("expected edge[%d] source endpoint to be at the middle of the source object", i) + } + if edge.Route[1].X != edge.Dst.Center().X { + t.Fatalf("expected edge[%d] target endpoint to be at the middle of the target object", i) + } + if i > 0 { + prevEdge := g.Edges[i-1] + if edge.Route[0].Y < prevEdge.Route[0].Y { + t.Fatalf("expected edge[%d].TopLeft.Y > edge[%d].TopLeft.Y", i, i-1) + } + } + } + + lastSequenceEdge := g.Edges[nEdges-1] + for i := nEdges; i < nExpectedEdges; i++ { + edge := g.Edges[i] + if len(edge.Route) != 2 { + t.Fatalf("expected edge[%d] to have only 2 points", i) + } + if edge.Route[0].X != edge.Route[1].X { + t.Fatalf("expected edge[%d] to be a vertical line", i) + } + if edge.Route[0].X != edge.Src.Center().X { + t.Fatalf("expected edge[%d] x to be at the object center", i) + } + if edge.Route[0].Y != edge.Src.Height+edge.Src.TopLeft.Y { + t.Fatalf("expected edge[%d] to start at the bottom of the source object", i) + } + if edge.Route[1].Y < lastSequenceEdge.Route[0].Y { + t.Fatalf("expected edge[%d] to end after the last sequence edge", i) + } + } +} From f3b7453c8a06ab15a945f5f9a19a4602627537dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Fri, 25 Nov 2022 22:41:11 -0800 Subject: [PATCH 2/7] Add comment to addLifelineEdges --- d2layouts/d2sequence/layout.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/d2layouts/d2sequence/layout.go b/d2layouts/d2sequence/layout.go index 2c182d3fe..606ab8302 100644 --- a/d2layouts/d2sequence/layout.go +++ b/d2layouts/d2sequence/layout.go @@ -72,6 +72,15 @@ func routeEdges(edgesInOrder []*d2graph.Edge, startY, yStep float64) { } } +// addLifelineEdges adds a new edge for each object in the graph that represents the +// edge below he object showing its lifespan +// ┌──────────────┐ +// │ object │ +// └──────┬───────┘ +// │ +// │ lifeline +// │ +// │ func addLifelineEdges(g *d2graph.Graph, objectsInOrder []*d2graph.Object, yStep float64) { endY := g.Edges[len(g.Edges)-1].Route[0].Y + yStep for _, obj := range objectsInOrder { From 7bf32ff33b1acc621dd8cebcec93c6127d838eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Mon, 28 Nov 2022 11:11:38 -0800 Subject: [PATCH 3/7] Rename object to actor --- d2layouts/d2sequence/layout.go | 64 ++++++++++++++--------------- d2layouts/d2sequence/layout_test.go | 30 +++++++------- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/d2layouts/d2sequence/layout.go b/d2layouts/d2sequence/layout.go index 606ab8302..10b61586b 100644 --- a/d2layouts/d2sequence/layout.go +++ b/d2layouts/d2sequence/layout.go @@ -14,43 +14,43 @@ import ( func Layout(ctx context.Context, g *d2graph.Graph) (err error) { pad := 50. // 2 * 25 edgeYStep := 100. - objectXStep := 200. - maxObjectHeight := 0. + actorXStep := 200. + maxActorHeight := 0. - var objectsInOrder []*d2graph.Object + var actorsInOrder []*d2graph.Object seen := make(map[*d2graph.Object]struct{}) for _, edge := range g.Edges { if _, exists := seen[edge.Src]; !exists { seen[edge.Src] = struct{}{} - objectsInOrder = append(objectsInOrder, edge.Src) + actorsInOrder = append(actorsInOrder, edge.Src) } if _, exists := seen[edge.Dst]; !exists { seen[edge.Dst] = struct{}{} - objectsInOrder = append(objectsInOrder, edge.Dst) + actorsInOrder = append(actorsInOrder, edge.Dst) } edgeYStep = math.Max(edgeYStep, float64(edge.LabelDimensions.Height)+pad) - objectXStep = math.Max(objectXStep, float64(edge.LabelDimensions.Width)+pad) - maxObjectHeight = math.Max(maxObjectHeight, edge.Src.Height+pad) - maxObjectHeight = math.Max(maxObjectHeight, edge.Dst.Height+pad) + actorXStep = math.Max(actorXStep, float64(edge.LabelDimensions.Width)+pad) + maxActorHeight = math.Max(maxActorHeight, edge.Src.Height+pad) + maxActorHeight = math.Max(maxActorHeight, edge.Dst.Height+pad) } - placeObjects(objectsInOrder, maxObjectHeight, objectXStep) + placeActors(actorsInOrder, maxActorHeight, actorXStep) // edges are placed in the order users define them - routeEdges(g.Edges, maxObjectHeight, edgeYStep) - addLifelineEdges(g, objectsInOrder, edgeYStep) + routeEdges(g.Edges, maxActorHeight, edgeYStep) + addLifelineEdges(g, actorsInOrder, edgeYStep) return nil } -// placeObjects places objects side by side -func placeObjects(objectsInOrder []*d2graph.Object, maxHeight, xStep float64) { +// placeActors places actors bottom aligned, side by side +func placeActors(actorsInOrder []*d2graph.Object, maxHeight, xStep float64) { x := 0. - for _, obj := range objectsInOrder { - yDiff := maxHeight - obj.Height - obj.TopLeft = geo.NewPoint(x, yDiff/2.) - x += obj.Width + xStep - obj.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter)) + for _, actors := range actorsInOrder { + yOffset := maxHeight - actors.Height + actors.TopLeft = geo.NewPoint(x, yOffset) + x += actors.Width + xStep + actors.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter)) } } @@ -72,39 +72,39 @@ func routeEdges(edgesInOrder []*d2graph.Edge, startY, yStep float64) { } } -// addLifelineEdges adds a new edge for each object in the graph that represents the -// edge below he object showing its lifespan +// addLifelineEdges adds a new edge for each actor in the graph that represents the +// edge below the actor showing its lifespan // ┌──────────────┐ -// │ object │ +// │ actor │ // └──────┬───────┘ // │ // │ lifeline // │ // │ -func addLifelineEdges(g *d2graph.Graph, objectsInOrder []*d2graph.Object, yStep float64) { +func addLifelineEdges(g *d2graph.Graph, actorsInOrder []*d2graph.Object, yStep float64) { endY := g.Edges[len(g.Edges)-1].Route[0].Y + yStep - for _, obj := range objectsInOrder { - objBottom := obj.Center() - objBottom.Y = obj.TopLeft.Y + obj.Height - objLifelineEnd := obj.Center() - objLifelineEnd.Y = endY + for _, actor := range actorsInOrder { + actorBottom := actor.Center() + actorBottom.Y = actor.TopLeft.Y + actor.Height + actorLifelineEnd := actor.Center() + actorLifelineEnd.Y = endY g.Edges = append(g.Edges, &d2graph.Edge{ Attributes: d2graph.Attributes{ Style: d2graph.Style{ StrokeDash: &d2graph.Scalar{ Value: "10", }, - Stroke: obj.Attributes.Style.Stroke, - StrokeWidth: obj.Attributes.Style.StrokeWidth, + Stroke: actor.Attributes.Style.Stroke, + StrokeWidth: actor.Attributes.Style.StrokeWidth, }, }, - Src: obj, + Src: actor, SrcArrow: false, Dst: &d2graph.Object{ - ID: obj.ID + fmt.Sprintf("-lifeline-end-%d", go2.StringToIntHash(obj.ID+"-lifeline-end")), + ID: actor.ID + fmt.Sprintf("-lifeline-end-%d", go2.StringToIntHash(actor.ID+"-lifeline-end")), }, DstArrow: false, - Route: []*geo.Point{objBottom, objLifelineEnd}, + Route: []*geo.Point{actorBottom, actorLifelineEnd}, }) } } diff --git a/d2layouts/d2sequence/layout_test.go b/d2layouts/d2sequence/layout_test.go index 4ffb0237f..f01575292 100644 --- a/d2layouts/d2sequence/layout_test.go +++ b/d2layouts/d2sequence/layout_test.go @@ -45,27 +45,29 @@ func TestLayout(t *testing.T) { ctx := log.WithTB(context.Background(), t, nil) Layout(ctx, g) - // asserts that objects were placed in the expected x order and at y=0 - objectsOrder := []*d2graph.Object{ + // asserts that actors were placed in the expected x order and at y=0 + actors := []*d2graph.Object{ g.Objects[0], g.Objects[1], } - for i := 1; i < len(objectsOrder); i++ { - if objectsOrder[i].TopLeft.X < objectsOrder[i-1].TopLeft.X { - t.Fatalf("expected object[%d].TopLeft.X > object[%d].TopLeft.X", i, i-1) + for i := 1; i < len(actors); i++ { + if actors[i].TopLeft.X < actors[i-1].TopLeft.X { + t.Fatalf("expected actor[%d].TopLeft.X > actor[%d].TopLeft.X", i, i-1) } - if objectsOrder[i].Center().Y != objectsOrder[i-1].Center().Y { - t.Fatalf("expected object[%d] and object[%d] to be at the same center y", i, i-1) + actorBottom := actors[i].TopLeft.Y + actors[i].Height + prevActorBottom := actors[i-1].TopLeft.Y + actors[i-1].Height + if actorBottom != prevActorBottom { + t.Fatalf("expected actor[%d] and actor[%d] to be at the same bottom y", i, i-1) } } - nExpectedEdges := nEdges + len(objectsOrder) + nExpectedEdges := nEdges + len(actors) if len(g.Edges) != nExpectedEdges { t.Fatalf("expected %d edges, got %d", nExpectedEdges, len(g.Edges)) } - // assert that edges were placed in y order and have the endpoints at their objects - // uses `nEdges` because Layout creates some vertical edges to represent the object lifeline + // assert that edges were placed in y order and have the endpoints at their actors + // uses `nEdges` because Layout creates some vertical edges to represent the actor lifeline for i := 0; i < nEdges; i++ { edge := g.Edges[i] if len(edge.Route) != 2 { @@ -75,10 +77,10 @@ func TestLayout(t *testing.T) { t.Fatalf("expected edge[%d] to be a horizontal line", i) } if edge.Route[0].X != edge.Src.Center().X { - t.Fatalf("expected edge[%d] source endpoint to be at the middle of the source object", i) + t.Fatalf("expected edge[%d] source endpoint to be at the middle of the source actor", i) } if edge.Route[1].X != edge.Dst.Center().X { - t.Fatalf("expected edge[%d] target endpoint to be at the middle of the target object", i) + t.Fatalf("expected edge[%d] target endpoint to be at the middle of the target actor", i) } if i > 0 { prevEdge := g.Edges[i-1] @@ -98,10 +100,10 @@ func TestLayout(t *testing.T) { t.Fatalf("expected edge[%d] to be a vertical line", i) } if edge.Route[0].X != edge.Src.Center().X { - t.Fatalf("expected edge[%d] x to be at the object center", i) + t.Fatalf("expected edge[%d] x to be at the actor center", i) } if edge.Route[0].Y != edge.Src.Height+edge.Src.TopLeft.Y { - t.Fatalf("expected edge[%d] to start at the bottom of the source object", i) + t.Fatalf("expected edge[%d] to start at the bottom of the source actor", i) } if edge.Route[1].Y < lastSequenceEdge.Route[0].Y { t.Fatalf("expected edge[%d] to end after the last sequence edge", i) From b415c6c8582f0eccb2e8e42a62b6a65ceea4fb28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Mon, 28 Nov 2022 11:32:56 -0800 Subject: [PATCH 4/7] Use user define object order --- d2layouts/d2sequence/layout.go | 36 ++++++++++++---------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/d2layouts/d2sequence/layout.go b/d2layouts/d2sequence/layout.go index 10b61586b..a637b16af 100644 --- a/d2layouts/d2sequence/layout.go +++ b/d2layouts/d2sequence/layout.go @@ -17,36 +17,24 @@ func Layout(ctx context.Context, g *d2graph.Graph) (err error) { actorXStep := 200. maxActorHeight := 0. - var actorsInOrder []*d2graph.Object - seen := make(map[*d2graph.Object]struct{}) for _, edge := range g.Edges { - if _, exists := seen[edge.Src]; !exists { - seen[edge.Src] = struct{}{} - actorsInOrder = append(actorsInOrder, edge.Src) - } - if _, exists := seen[edge.Dst]; !exists { - seen[edge.Dst] = struct{}{} - actorsInOrder = append(actorsInOrder, edge.Dst) - } - edgeYStep = math.Max(edgeYStep, float64(edge.LabelDimensions.Height)+pad) actorXStep = math.Max(actorXStep, float64(edge.LabelDimensions.Width)+pad) maxActorHeight = math.Max(maxActorHeight, edge.Src.Height+pad) maxActorHeight = math.Max(maxActorHeight, edge.Dst.Height+pad) } - placeActors(actorsInOrder, maxActorHeight, actorXStep) - // edges are placed in the order users define them + placeActors(g.Objects, maxActorHeight, actorXStep) routeEdges(g.Edges, maxActorHeight, edgeYStep) - addLifelineEdges(g, actorsInOrder, edgeYStep) + addLifelineEdges(g, g.Objects, edgeYStep) return nil } // placeActors places actors bottom aligned, side by side -func placeActors(actorsInOrder []*d2graph.Object, maxHeight, xStep float64) { +func placeActors(actors []*d2graph.Object, maxHeight, xStep float64) { x := 0. - for _, actors := range actorsInOrder { + for _, actors := range actors { yOffset := maxHeight - actors.Height actors.TopLeft = geo.NewPoint(x, yOffset) x += actors.Width + xStep @@ -66,8 +54,12 @@ func routeEdges(edgesInOrder []*d2graph.Edge, startY, yStep float64) { edgeY += yStep if edge.Attributes.Label.Value != "" { - // TODO: consider label right-to-left - edge.LabelPosition = go2.Pointer(string(label.OutsideTopCenter)) + isLeftToRight := edge.Src.TopLeft.X < edge.Dst.TopLeft.X + if isLeftToRight { + edge.LabelPosition = go2.Pointer(string(label.OutsideTopCenter)) + } else { + edge.LabelPosition = go2.Pointer(string(label.OutsideBottomCenter)) + } } } } @@ -81,9 +73,9 @@ func routeEdges(edgesInOrder []*d2graph.Edge, startY, yStep float64) { // │ lifeline // │ // │ -func addLifelineEdges(g *d2graph.Graph, actorsInOrder []*d2graph.Object, yStep float64) { +func addLifelineEdges(g *d2graph.Graph, actors []*d2graph.Object, yStep float64) { endY := g.Edges[len(g.Edges)-1].Route[0].Y + yStep - for _, actor := range actorsInOrder { + for _, actor := range actors { actorBottom := actor.Center() actorBottom.Y = actor.TopLeft.Y + actor.Height actorLifelineEnd := actor.Center() @@ -91,9 +83,7 @@ func addLifelineEdges(g *d2graph.Graph, actorsInOrder []*d2graph.Object, yStep f g.Edges = append(g.Edges, &d2graph.Edge{ Attributes: d2graph.Attributes{ Style: d2graph.Style{ - StrokeDash: &d2graph.Scalar{ - Value: "10", - }, + StrokeDash: &d2graph.Scalar{Value: "10"}, Stroke: actor.Attributes.Style.Stroke, StrokeWidth: actor.Attributes.Style.StrokeWidth, }, From 909f9fc6c99e214b591bbe05d8a507fec6c753c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Mon, 28 Nov 2022 11:41:44 -0800 Subject: [PATCH 5/7] Move constants to a file --- d2layouts/d2sequence/constants.go | 9 +++++++++ d2layouts/d2sequence/layout.go | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 d2layouts/d2sequence/constants.go diff --git a/d2layouts/d2sequence/constants.go b/d2layouts/d2sequence/constants.go new file mode 100644 index 000000000..5004ea4d6 --- /dev/null +++ b/d2layouts/d2sequence/constants.go @@ -0,0 +1,9 @@ +package d2sequence + +// min horizontal pad for actors, or edge labels, to consider the min distance between them +const MIN_HORIZONTAL_PAD = 50. + +const MIN_ACTOR_DISTANCE = 200. + +// min vertical distance between edges +const MIN_EDGE_DISTANCE = 100. diff --git a/d2layouts/d2sequence/layout.go b/d2layouts/d2sequence/layout.go index a637b16af..ebe17b2ff 100644 --- a/d2layouts/d2sequence/layout.go +++ b/d2layouts/d2sequence/layout.go @@ -12,9 +12,9 @@ import ( ) func Layout(ctx context.Context, g *d2graph.Graph) (err error) { - pad := 50. // 2 * 25 - edgeYStep := 100. - actorXStep := 200. + pad := MIN_HORIZONTAL_PAD + edgeYStep := MIN_EDGE_DISTANCE + actorXStep := MIN_ACTOR_DISTANCE maxActorHeight := 0. for _, edge := range g.Edges { From 918ad5d96e03cbb2c3fd7e9bfeb9a16bff192afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Mon, 28 Nov 2022 13:08:52 -0800 Subject: [PATCH 6/7] constants --- d2layouts/d2sequence/constants.go | 4 ++-- d2layouts/d2sequence/layout.go | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/d2layouts/d2sequence/constants.go b/d2layouts/d2sequence/constants.go index 5004ea4d6..bdffd77f8 100644 --- a/d2layouts/d2sequence/constants.go +++ b/d2layouts/d2sequence/constants.go @@ -1,7 +1,7 @@ package d2sequence -// min horizontal pad for actors, or edge labels, to consider the min distance between them -const MIN_HORIZONTAL_PAD = 50. +// leaves at least 25 units of space on the left/right when computing the space required between actors +const HORIZONTAL_PAD = 50. const MIN_ACTOR_DISTANCE = 200. diff --git a/d2layouts/d2sequence/layout.go b/d2layouts/d2sequence/layout.go index ebe17b2ff..c98b5b3c4 100644 --- a/d2layouts/d2sequence/layout.go +++ b/d2layouts/d2sequence/layout.go @@ -12,16 +12,15 @@ import ( ) func Layout(ctx context.Context, g *d2graph.Graph) (err error) { - pad := MIN_HORIZONTAL_PAD edgeYStep := MIN_EDGE_DISTANCE actorXStep := MIN_ACTOR_DISTANCE maxActorHeight := 0. for _, edge := range g.Edges { - edgeYStep = math.Max(edgeYStep, float64(edge.LabelDimensions.Height)+pad) - actorXStep = math.Max(actorXStep, float64(edge.LabelDimensions.Width)+pad) - maxActorHeight = math.Max(maxActorHeight, edge.Src.Height+pad) - maxActorHeight = math.Max(maxActorHeight, edge.Dst.Height+pad) + edgeYStep = math.Max(edgeYStep, float64(edge.LabelDimensions.Height)+HORIZONTAL_PAD) + actorXStep = math.Max(actorXStep, float64(edge.LabelDimensions.Width)+HORIZONTAL_PAD) + maxActorHeight = math.Max(maxActorHeight, edge.Src.Height+HORIZONTAL_PAD) + maxActorHeight = math.Max(maxActorHeight, edge.Dst.Height+HORIZONTAL_PAD) } placeActors(g.Objects, maxActorHeight, actorXStep) From 48195e9d5cb9dd47ee2fa4709fda56d2e8b8b875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Mon, 28 Nov 2022 13:18:33 -0800 Subject: [PATCH 7/7] Distribute long labels across actors --- d2layouts/d2sequence/layout.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/d2layouts/d2sequence/layout.go b/d2layouts/d2sequence/layout.go index c98b5b3c4..5d1107a39 100644 --- a/d2layouts/d2sequence/layout.go +++ b/d2layouts/d2sequence/layout.go @@ -16,11 +16,19 @@ func Layout(ctx context.Context, g *d2graph.Graph) (err error) { actorXStep := MIN_ACTOR_DISTANCE maxActorHeight := 0. + actorRank := make(map[*d2graph.Object]int) + for rank, actor := range g.Objects { + actorRank[actor] = rank + } for _, edge := range g.Edges { edgeYStep = math.Max(edgeYStep, float64(edge.LabelDimensions.Height)+HORIZONTAL_PAD) - actorXStep = math.Max(actorXStep, float64(edge.LabelDimensions.Width)+HORIZONTAL_PAD) maxActorHeight = math.Max(maxActorHeight, edge.Src.Height+HORIZONTAL_PAD) maxActorHeight = math.Max(maxActorHeight, edge.Dst.Height+HORIZONTAL_PAD) + // 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(actorRank[edge.Src]) - float64(actorRank[edge.Dst])) + distributedLabelWidth := float64(edge.LabelDimensions.Width) / rankDiff + actorXStep = math.Max(actorXStep, distributedLabelWidth+HORIZONTAL_PAD) } placeActors(g.Objects, maxActorHeight, actorXStep)