Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nested edges within grid cells #1629

Merged
merged 15 commits into from
Oct 2, 2023
2 changes: 2 additions & 0 deletions ci/release/changelogs/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#### Improvements 🧹

- Grid cells can now contain nested edges [#1629](https://github.com/terrastruct/d2/pull/1629)

#### Bugfixes ⛑️

- Grid layout now accounts for each cell's outside labels and icons [#1624](https://github.com/terrastruct/d2/pull/1624)
59 changes: 42 additions & 17 deletions d2compiler/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -1089,34 +1089,59 @@ func (c *compiler) validateNear(g *d2graph.Graph) {

func (c *compiler) validateEdges(g *d2graph.Graph) {
for _, edge := range g.Edges {
// edges from a grid to something outside is ok
// grid -> outside : ok
// grid -> grid.cell : not ok
// grid -> grid.cell.inner : not ok
if edge.Src.IsGridDiagram() && edge.Dst.IsDescendantOf(edge.Src) {
c.errorf(edge.GetAstEdge(), "edge from grid diagram %#v cannot enter itself", edge.Src.AbsID())
continue
}
if edge.Dst.IsGridDiagram() && edge.Src.IsDescendantOf(edge.Dst) {
c.errorf(edge.GetAstEdge(), "edge from grid diagram %#v cannot enter itself", edge.Dst.AbsID())
continue
}

srcGrid := edge.Src.Parent.ClosestGridDiagram()
dstGrid := edge.Dst.Parent.ClosestGridDiagram()
if srcGrid != nil || dstGrid != nil {
if top := srcGrid.TopGridDiagram(); srcGrid != top {
// valid: grid.child1 -> grid.child2
// invalid: grid.childGrid.child1 -> grid.childGrid.child2
c.errorf(edge.GetAstEdge(), "edge must be on direct child of grid diagram %#v", top.AbsID())
continue
}
if top := dstGrid.TopGridDiagram(); dstGrid != top {
// valid: grid.child1 -> grid.child2
// invalid: grid.childGrid.child1 -> grid.childGrid.child2
c.errorf(edge.GetAstEdge(), "edge must be on direct child of grid diagram %#v", top.AbsID())
continue
}
if srcGrid != dstGrid {
// valid: a -> grid
// invalid: a -> grid.child
c.errorf(edge.GetAstEdge(), "edges into grid diagrams are not supported yet")
if dstGrid != nil && !(srcGrid != nil && srcGrid.IsDescendantOf(dstGrid)) {
c.errorf(edge.GetAstEdge(), "edge cannot enter grid diagram %#v", dstGrid.AbsID())
} else {
c.errorf(edge.GetAstEdge(), "edge cannot exit grid diagram %#v", srcGrid.AbsID())
}
continue
}
if srcGrid != edge.Src.Parent || dstGrid != edge.Dst.Parent {
// valid: grid.child1 -> grid.child2
// invalid: grid.child1 -> grid.child2.child1
c.errorf(edge.GetAstEdge(), "grid diagrams can only have edges between children right now")

srcCell := edge.Src.ClosestGridCell()
dstCell := edge.Dst.ClosestGridCell()
// edges within a grid cell are ok now
// grid.cell.a -> grid.cell.b : ok
// grid.cell.a.c -> grid.cell.b.d : ok
// edges between grid cells themselves are ok
// grid.cell -> grid.cell2 : ok
// grid.cell -> grid.cell.inside : not ok
// grid.cell -> grid.cell2.inside : not ok
srcIsGridCell := edge.Src == srcCell
dstIsGridCell := edge.Dst == dstCell
if srcIsGridCell != dstIsGridCell {
if srcIsGridCell {
c.errorf(edge.GetAstEdge(), "grid cell %#v can only connect to another grid cell", edge.Src.AbsID())
} else {
c.errorf(edge.GetAstEdge(), "grid cell %#v can only connect to another grid cell", edge.Dst.AbsID())
}
continue
}

if srcCell != dstCell && (!srcIsGridCell || !dstIsGridCell) {
c.errorf(edge.GetAstEdge(), "edge cannot exit grid cell %#v", srcCell.AbsID())
continue
}
}

}
}

Expand Down
37 changes: 26 additions & 11 deletions d2compiler/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2478,32 +2478,47 @@ d2/testdata/d2compiler/TestCompile/grid_gap_negative.d2:3:16: vertical-gap must
grid-rows: 1
a -> b: ok
}
c -> hey.b
hey.a -> c
hey -> hey.a
c -> hey.b
hey.a -> c
hey -> hey.a

hey -> c: ok
hey -> c: ok
`,
expErr: `d2/testdata/d2compiler/TestCompile/grid_edge.d2:5:2: edges into grid diagrams are not supported yet
d2/testdata/d2compiler/TestCompile/grid_edge.d2:6:2: edges into grid diagrams are not supported yet
d2/testdata/d2compiler/TestCompile/grid_edge.d2:7:2: edges into grid diagrams are not supported yet`,
expErr: `d2/testdata/d2compiler/TestCompile/grid_edge.d2:5:1: edge cannot enter grid diagram "hey"
d2/testdata/d2compiler/TestCompile/grid_edge.d2:6:1: edge cannot exit grid diagram "hey"
d2/testdata/d2compiler/TestCompile/grid_edge.d2:7:1: edge from grid diagram "hey" cannot enter itself`,
},
{
name: "grid_deeper_edge",
text: `hey: {
grid-rows: 1
a -> b: ok
b: {
c -> d: not yet
c -> d: ok now
c.e -> c.f.g: ok
c.e -> d.h: ok
c -> d.h: ok
}
a: {
grid-columns: 1
e -> f: also not yet
e -> f: also ok now
e: {
g -> h: ok
g -> h.h: ok
}
e -> f.i: not ok
e.g -> f.i: not ok
}
a -> b.c: not yet
a.e -> b.c: also not yet
a -> a.e: not ok
}
`,
expErr: `d2/testdata/d2compiler/TestCompile/grid_deeper_edge.d2:9:3: edge must be on direct child of grid diagram "hey"
d2/testdata/d2compiler/TestCompile/grid_deeper_edge.d2:5:3: grid diagrams can only have edges between children right now`,
expErr: `d2/testdata/d2compiler/TestCompile/grid_deeper_edge.d2:17:3: grid cell "hey.a.e" can only connect to another grid cell
d2/testdata/d2compiler/TestCompile/grid_deeper_edge.d2:18:3: edge cannot exit grid cell "hey.a.e"
d2/testdata/d2compiler/TestCompile/grid_deeper_edge.d2:20:2: grid cell "hey.a" can only connect to another grid cell
d2/testdata/d2compiler/TestCompile/grid_deeper_edge.d2:21:2: edge cannot exit grid diagram "hey.a"
d2/testdata/d2compiler/TestCompile/grid_deeper_edge.d2:22:2: edge from grid diagram "hey.a" cannot enter itself`,
},
{
name: "grid_nested",
Expand Down
11 changes: 11 additions & 0 deletions d2graph/grid_diagram.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ func (obj *Object) ClosestGridDiagram() *Object {
return obj.Parent.ClosestGridDiagram()
}

func (obj *Object) ClosestGridCell() *Object {
if obj == nil {
return nil
}
// grid cells can be a nested grid diagram
if obj.Parent.IsGridDiagram() {
return obj
}
return obj.Parent.ClosestGridCell()
}

// TopGridDiagram returns the least nested (outermost) grid diagram
func (obj *Object) TopGridDiagram() *Object {
if obj == nil {
Expand Down
19 changes: 16 additions & 3 deletions d2layouts/d2layouts.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ func LayoutNested(ctx context.Context, g *d2graph.Graph, graphInfo GraphInfo, co
if err != nil {
return err
}

InjectNested(g.Root, nestedGraph, false)
restoreOrder()

Expand All @@ -122,15 +123,25 @@ func LayoutNested(ctx context.Context, g *d2graph.Graph, graphInfo GraphInfo, co
}
curr = obj

dx := -curr.TopLeft.X
dy := -curr.TopLeft.Y
// position nested graph (excluding curr) relative to curr
dx := 0 - curr.TopLeft.X
dy := 0 - curr.TopLeft.Y
for _, o := range nestedGraph.Objects {
if o.AbsID() == curr.AbsID() {
continue
}
o.TopLeft.X += dx
o.TopLeft.Y += dy
}
for _, e := range nestedGraph.Edges {
e.Move(dx, dy)
}

// now we keep the descendants out until after grid layout
nestedGraph = ExtractSubgraph(curr, false)

extracted[id] = nestedGraph
extractedOrder = append(extractedOrder, id)
continue
}

Expand Down Expand Up @@ -326,7 +337,6 @@ func ExtractSubgraph(container *d2graph.Object, includeSelf bool) *d2graph.Graph
}

func InjectNested(container *d2graph.Object, nestedGraph *d2graph.Graph, isRoot bool) {
// TODO restore order of objects
g := container.Graph
for _, obj := range nestedGraph.Root.ChildrenArray {
obj.Parent = container
Expand Down Expand Up @@ -358,6 +368,9 @@ func PositionNested(container *d2graph.Object, nestedGraph *d2graph.Graph) {
// Note: assumes nestedGraph's layout has contents positioned relative to 0,0
dx := container.TopLeft.X //- tl.X
dy := container.TopLeft.Y //- tl.Y
if dx == 0 && dy == 0 {
return
}
for _, o := range nestedGraph.Objects {
o.TopLeft.X += dx
o.TopLeft.Y += dy
Expand Down
18 changes: 10 additions & 8 deletions e2etests/testdata/files/grid_nested_simple_edges.d2
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ outer-grid: {

container: {
label.near: top-left
# edges not yet supported here since they must be direct grid children
a
b
c
(** -> **)[*].class: red
# edges on grid descendant now supported
a -> b -> c -> a
d -> e -> g.h.i
d -> f -> g.h
b -> g
}

inner-grid: {
grid-rows: 1
1
2
3
# edges here are not supported yet since this is inside another grid
# edges inside another grid now supported
1 -> 2 -> 3: {class: red}
}
}

Expand All @@ -41,3 +41,5 @@ outer-container: {
}
}
}

classes.red.style.stroke: red
Loading