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

grid: fix dynamic layout cuts #1380

Merged
merged 6 commits into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ci/release/changelogs/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@
all applied instead of only the last one [#1362](https://github.com/terrastruct/d2/pull/1362)
- Prevent empty block strings [#1364](https://github.com/terrastruct/d2/pull/1364)
- Fixes dagre mis-aligning a nested shape's connection. [#1370](https://github.com/terrastruct/d2/pull/1370)
- Fixes a bug in grids sometimes putting a shape on the next row/column. [#1380](https://github.com/terrastruct/d2/pull/1380)
27 changes: 21 additions & 6 deletions d2layouts/d2grid/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ func (gd *gridDiagram) getBestLayout(targetSize float64, columns bool) [][]*d2gr
nCuts = gd.rows - 1
}
if nCuts == 0 {
return genLayout(gd.objects, nil)
return GenLayout(gd.objects, nil)
}

var bestLayout [][]*d2graph.Object
Expand Down Expand Up @@ -692,7 +692,7 @@ func (gd *gridDiagram) getBestLayout(targetSize float64, columns bool) [][]*d2gr
// . A │ B │ C D E └────────────┘
// of these divisions, find the layout with rows closest to the targetSize
tryDivision := func(division []int) bool {
layout := genLayout(gd.objects, division)
layout := GenLayout(gd.objects, division)
dist := getDistToTarget(layout, targetSize, float64(gd.horizontalGap), float64(gd.verticalGap), columns)
if dist < bestDist {
bestLayout = layout
Expand Down Expand Up @@ -786,8 +786,10 @@ func (gd *gridDiagram) fastLayout(targetSize float64, nCuts int, columns bool) (
size = o.Width
}
if rowSize == 0 {
// if a single object meets the target size, end the row here
if size > targetSize-debt {
fastDivision = append(fastDivision, i-1)
// cut row with just this object
fastDivision = append(fastDivision, i)
// we build up a debt of distance past the target size across rows
newDebt := size - targetSize
debt += newDebt
Expand All @@ -797,7 +799,11 @@ func (gd *gridDiagram) fastLayout(targetSize float64, nCuts int, columns bool) (
continue
}
// debt is paid by decreasing threshold to start new row and ending below targetSize
if rowSize+(gap+size)/2. > targetSize-debt {
if rowSize+gap+(size)/2. > targetSize-debt {
// start a new row before this object since it is mostly past the target size
// . size
// ├...row─┼gap┼───┼───┤
// ├──targetSize──┤ (debt=0)
fastDivision = append(fastDivision, i-1)
newDebt := rowSize - targetSize
debt += newDebt
Expand All @@ -807,7 +813,7 @@ func (gd *gridDiagram) fastLayout(targetSize float64, nCuts int, columns bool) (
}
}
if len(fastDivision) == nCuts {
layout = genLayout(gd.objects, fastDivision)
layout = GenLayout(gd.objects, fastDivision)
}

return layout
Expand Down Expand Up @@ -885,7 +891,9 @@ func iterDivisions(objects []*d2graph.Object, nCuts int, f iterDivision, check c
}

// generate a grid of objects from the given cut indices
func genLayout(objects []*d2graph.Object, cutIndices []int) [][]*d2graph.Object {
// each cut index applies after the object at that index
// e.g. [0 1 2 3 4 5 6 7] with cutIndices [0, 2, 6] => [[0], [1, 2], [3,4,5,6], [7]]
func GenLayout(objects []*d2graph.Object, cutIndices []int) [][]*d2graph.Object {
layout := make([][]*d2graph.Object, len(cutIndices)+1)
objIndex := 0
for i := 0; i <= len(cutIndices); i++ {
Expand Down Expand Up @@ -916,6 +924,13 @@ func getDistToTarget(layout [][]*d2graph.Object, targetSize float64, horizontalG
rowSize += o.Width + horizontalGap
}
}
if len(row) > 0 {
if columns {
rowSize -= verticalGap
} else {
rowSize -= horizontalGap
}
}
totalDelta += math.Abs(rowSize - targetSize)
}
return totalDelta
Expand Down
72 changes: 72 additions & 0 deletions d2layouts/d2grid/layout_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package d2grid_test

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"

"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2layouts/d2grid"
)

func TestGenLayout(t *testing.T) {
objects := []*d2graph.Object{
{ID: "1"},
{ID: "2"},
{ID: "3"},
{ID: "4"},
{ID: "5"},
{ID: "6"},
{ID: "7"},
{ID: "8"},
}
var cutIndices []int
var layout [][]*d2graph.Object
cutIndices = []int{0}
layout = d2grid.GenLayout(objects, cutIndices)
fmt.Printf("layout %v\n", len(layout))
assert.Equalf(t, len(cutIndices)+1, len(layout), "expected 2 rows from 1 cut")
assert.Equalf(t, 1, len(layout[0]), "expected first row to be 1 object")
assert.Equalf(t, 7, len(layout[1]), "expected second row to be 7 objects")
assert.Equalf(t, objects[0].ID, layout[0][0].ID, "expected first object to be 1")

cutIndices = []int{6}
layout = d2grid.GenLayout(objects, cutIndices)
assert.Equalf(t, len(cutIndices)+1, len(layout), "expected 2 rows from 1 cut")
assert.Equalf(t, 7, len(layout[0]), "expected first row to be 7 objects")
assert.Equalf(t, 1, len(layout[1]), "expected second row to be 1 object")

cutIndices = []int{0, 6}
layout = d2grid.GenLayout(objects, cutIndices)
assert.Equalf(t, len(cutIndices)+1, len(layout), "expected 3 rows from 2 cuts")
assert.Equalf(t, 1, len(layout[0]), "expected first row to be 1 objects")
assert.Equalf(t, 6, len(layout[1]), "expected second row to be 6 objects")
assert.Equalf(t, 1, len(layout[2]), "expected second row to be 1 object")

cutIndices = []int{1, 5}
layout = d2grid.GenLayout(objects, cutIndices)
assert.Equalf(t, len(cutIndices)+1, len(layout), "expected 3 rows from 2 cuts")
assert.Equalf(t, 2, len(layout[0]), "expected first row to be 2 objects")
assert.Equalf(t, 4, len(layout[1]), "expected second row to be 6 objects")
assert.Equalf(t, 2, len(layout[2]), "expected second row to be 2 object")

cutIndices = []int{5}
layout = d2grid.GenLayout(objects, cutIndices)
assert.Equalf(t, len(cutIndices)+1, len(layout), "expected 2 rows from 1 cut")
assert.Equalf(t, 6, len(layout[0]), "expected first row to be 6 objects")
assert.Equalf(t, 2, len(layout[1]), "expected second row to be 2 object")

cutIndices = []int{1}
layout = d2grid.GenLayout(objects, cutIndices)
assert.Equalf(t, len(cutIndices)+1, len(layout), "expected 2 rows from 1 cut")
assert.Equalf(t, 2, len(layout[0]), "expected first row to be 2 object")
assert.Equalf(t, 6, len(layout[1]), "expected second row to be 6 objects")

cutIndices = []int{0, 1, 2, 3, 4, 5, 6}
layout = d2grid.GenLayout(objects, cutIndices)
assert.Equalf(t, len(cutIndices)+1, len(layout), "expected 3 rows from 2 cuts")
for i := range layout {
assert.Equalf(t, 1, len(layout[i]), "expected row %d to be 1 object", i)
}
}
1 change: 1 addition & 0 deletions e2etests/regression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,7 @@ cf many required: {
loadFromFile(t, "slow_grid"),
loadFromFile(t, "grid_oom"),
loadFromFile(t, "cylinder_grid_label"),
loadFromFile(t, "grid_with_latex"),
}

runa(t, tcs)
Expand Down
27 changes: 27 additions & 0 deletions e2etests/testdata/files/grid_with_latex.d2
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
x: {
grid-columns: 2

a.width: 50
a.height: 72
b.width: 50
b.height: 30
}

y: {
grid-columns: 2

a.width: 50
a.height: 73
b.width: 50
b.height: 30
}

z: {
grid-columns: 2
lim: |latex
\\lim_{h \\rightarrow 0 } \\frac{f(x+h)-f(x)}{h}
|
add: |latex
1 + 1
|
}
Loading