From b3ef7cd9aad56a1040c8a4650ef7bb878e5406d3 Mon Sep 17 00:00:00 2001 From: Gavin Nishizawa Date: Mon, 25 Sep 2023 15:13:46 -0700 Subject: [PATCH 1/5] include graph root level in serialization --- d2graph/serde.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/d2graph/serde.go b/d2graph/serde.go index 61aa17e4ea..2cc484111a 100644 --- a/d2graph/serde.go +++ b/d2graph/serde.go @@ -10,9 +10,10 @@ import ( ) type SerializedGraph struct { - Root SerializedObject `json:"root"` - Edges []SerializedEdge `json:"edges"` - Objects []SerializedObject `json:"objects"` + Root SerializedObject `json:"root"` + Edges []SerializedEdge `json:"edges"` + Objects []SerializedObject `json:"objects"` + RootLevel int `json:"rootLevel"` } type SerializedObject map[string]interface{} @@ -30,6 +31,7 @@ func DeserializeGraph(bytes []byte, g *Graph) error { convert(sg.Root, &root) g.Root = &root root.Graph = g + g.RootLevel = sg.RootLevel idToObj := make(map[string]*Object) idToObj[""] = g.Root @@ -91,6 +93,7 @@ func SerializeGraph(g *Graph) ([]byte, error) { return nil, err } sg.Root = root + sg.RootLevel = g.RootLevel var sobjects []SerializedObject for _, o := range g.Objects { From e40ef6dfb1f7e41dab05f42739870fff06b4060f Mon Sep 17 00:00:00 2001 From: Gavin Nishizawa Date: Mon, 25 Sep 2023 15:21:45 -0700 Subject: [PATCH 2/5] fix stale *Object pointers after coreLayout --- d2layouts/d2layouts.go | 75 +++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/d2layouts/d2layouts.go b/d2layouts/d2layouts.go index ff00222dc3..aeabf77705 100644 --- a/d2layouts/d2layouts.go +++ b/d2layouts/d2layouts.go @@ -2,6 +2,7 @@ package d2layouts import ( "context" + "fmt" "math" "sort" "strings" @@ -77,8 +78,8 @@ func LayoutNested(ctx context.Context, g *d2graph.Graph, graphInfo GraphInfo, co g.Root.Box = &geo.Box{} // Before we can layout these nodes, we need to handle all nested diagrams first. - extracted := make(map[*d2graph.Object]*d2graph.Graph) - var extractedOrder []*d2graph.Object + extracted := make(map[string]*d2graph.Graph) + var extractedOrder []string var constantNears []*d2graph.Graph restoreOrder := SaveOrder(g) @@ -140,6 +141,11 @@ func LayoutNested(ctx context.Context, g *d2graph.Graph, graphInfo GraphInfo, co if err != nil { return err } + // coreLayout can overwrite graph contents with newly created *Object pointers + // so we need to update `curr` with nestedGraph's value + if gi.IsConstantNear { + curr = nestedGraph.Root.ChildrenArray[0] + } if gi.IsConstantNear { curr.NearKey = nearKey @@ -153,8 +159,10 @@ func LayoutNested(ctx context.Context, g *d2graph.Graph, graphInfo GraphInfo, co constantNears = append(constantNears, nestedGraph) } else { // We will restore the contents after running layout with child as the placeholder - extracted[curr] = nestedGraph - extractedOrder = append(extractedOrder, curr) + // We need to reference using ID because there may be a new object to use after coreLayout + id := curr.AbsID() + extracted[id] = nestedGraph + extractedOrder = append(extractedOrder, id) } } else if len(curr.ChildrenArray) > 0 { queue = append(queue, curr.ChildrenArray...) @@ -164,39 +172,52 @@ func LayoutNested(ctx context.Context, g *d2graph.Graph, graphInfo GraphInfo, co // We can now run layout with accurate sizes of nested layout containers // Layout according to the type of diagram var err error - switch graphInfo.DiagramType { - case GridDiagram: - log.Debug(ctx, "layout grid", slog.F("rootlevel", g.RootLevel), slog.F("shapes", g.PrintString())) - if err = d2grid.Layout(ctx, g); err != nil { - return err - } + if len(g.Objects) > 0 { + switch graphInfo.DiagramType { + case GridDiagram: + log.Debug(ctx, "layout grid", slog.F("rootlevel", g.RootLevel), slog.F("shapes", g.PrintString())) + if err = d2grid.Layout(ctx, g); err != nil { + return err + } - case SequenceDiagram: - log.Debug(ctx, "layout sequence", slog.F("rootlevel", g.RootLevel), slog.F("shapes", g.PrintString())) - err = d2sequence.Layout(ctx, g, coreLayout) - if err != nil { - return err - } - default: - log.Debug(ctx, "default layout", slog.F("rootlevel", g.RootLevel), slog.F("shapes", g.PrintString())) - err := coreLayout(ctx, g) - if err != nil { - return err + case SequenceDiagram: + log.Debug(ctx, "layout sequence", slog.F("rootlevel", g.RootLevel), slog.F("shapes", g.PrintString())) + err = d2sequence.Layout(ctx, g, coreLayout) + if err != nil { + return err + } + default: + log.Debug(ctx, "default layout", slog.F("rootlevel", g.RootLevel), slog.F("shapes", g.PrintString())) + err := coreLayout(ctx, g) + if err != nil { + return err + } } } if len(constantNears) > 0 { - err := d2near.Layout(ctx, g, constantNears) + err = d2near.Layout(ctx, g, constantNears) if err != nil { - panic(err) + return err } } // With the layout set, inject all the extracted graphs - for _, n := range extractedOrder { - nestedGraph := extracted[n] - InjectNested(n, nestedGraph, true) - PositionNested(n, nestedGraph) + for _, id := range extractedOrder { + nestedGraph := extracted[id] + // we have to find the object by ID because coreLayout can replace the Objects in graph + var obj *d2graph.Object + for _, o := range g.Objects { + if o.AbsID() == id { + obj = o + break + } + } + if obj == nil { + return fmt.Errorf("could not find object %#v after layout", id) + } + InjectNested(obj, nestedGraph, true) + PositionNested(obj, nestedGraph) } log.Debug(ctx, "done", slog.F("rootlevel", g.RootLevel), slog.F("shapes", g.PrintString())) From 2d00cb4bef7cadb46b2d759b86dc556425dcf6dc Mon Sep 17 00:00:00 2001 From: Gavin Nishizawa Date: Mon, 25 Sep 2023 15:32:10 -0700 Subject: [PATCH 3/5] object.Children map may no longer exist --- d2layouts/d2layouts.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/d2layouts/d2layouts.go b/d2layouts/d2layouts.go index aeabf77705..dcd3b4ac3f 100644 --- a/d2layouts/d2layouts.go +++ b/d2layouts/d2layouts.go @@ -315,6 +315,9 @@ func InjectNested(container *d2graph.Object, nestedGraph *d2graph.Graph, isRoot g := container.Graph for _, obj := range nestedGraph.Root.ChildrenArray { obj.Parent = container + if container.Children == nil { + container.Children = make(map[string]*d2graph.Object) + } container.Children[strings.ToLower(obj.ID)] = obj container.ChildrenArray = append(container.ChildrenArray, obj) } From 86de2cd4146260cf00e7fcd6030f7da7c86d11f9 Mon Sep 17 00:00:00 2001 From: Gavin Nishizawa Date: Mon, 25 Sep 2023 15:38:22 -0700 Subject: [PATCH 4/5] fix --- d2layouts/d2layouts.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/d2layouts/d2layouts.go b/d2layouts/d2layouts.go index dcd3b4ac3f..dddba2d442 100644 --- a/d2layouts/d2layouts.go +++ b/d2layouts/d2layouts.go @@ -101,12 +101,27 @@ func LayoutNested(ctx context.Context, g *d2graph.Graph, graphInfo GraphInfo, co // if we are in a grid diagram, and our children have descendants // we need to run layout on them first, even if they are not special diagram types nestedGraph := ExtractSubgraph(curr, true) + id := curr.AbsID() err := LayoutNested(ctx, nestedGraph, GraphInfo{}, coreLayout) if err != nil { return err } InjectNested(g.Root, nestedGraph, false) restoreOrder() + + // need to update curr *Object incase layout changed it + var obj *d2graph.Object + for _, o := range g.Objects { + if o.AbsID() == id { + obj = o + break + } + } + if obj == nil { + return fmt.Errorf("could not find object %#v after layout", id) + } + curr = obj + dx := -curr.TopLeft.X dy := -curr.TopLeft.Y for _, o := range nestedGraph.Objects { From 07a433eebed850896560d8f3694aad8aac5e53b3 Mon Sep 17 00:00:00 2001 From: Gavin Nishizawa Date: Mon, 25 Sep 2023 15:45:11 -0700 Subject: [PATCH 5/5] fix object deserialization missing graph reference --- d2graph/serde.go | 1 + 1 file changed, 1 insertion(+) diff --git a/d2graph/serde.go b/d2graph/serde.go index 2cc484111a..88c3abd67e 100644 --- a/d2graph/serde.go +++ b/d2graph/serde.go @@ -41,6 +41,7 @@ func DeserializeGraph(bytes []byte, g *Graph) error { if err := convert(so, &o); err != nil { return err } + o.Graph = g objects = append(objects, &o) idToObj[so["AbsID"].(string)] = &o }