improve grid expand evenly

This commit is contained in:
Gavin Nishizawa 2023-04-28 17:05:20 -07:00
parent 8409702ef0
commit 3937e37589
No known key found for this signature in database
GPG key ID: AE3B177777CE55CD

View file

@ -266,40 +266,17 @@ func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
cursor := geo.NewPoint(0, 0) cursor := geo.NewPoint(0, 0)
var maxY, maxX float64 var maxY, maxX float64
if gd.rowDirected { if gd.rowDirected {
// if we have 2 rows, then each row's objects should have the same height // measure row widths
// . ┌A─────────────┐ ┌B──┐ ┌C─────────┐ ┬ maxHeight(A,B,C)
// . ├ ─ ─ ─ ─ ─ ─ ─┤ │ │ │ │ │
// . │ │ │ │ ├ ─ ─ ─ ─ ─┤ │
// . │ │ │ │ │ │ │
// . └──────────────┘ └───┘ └──────────┘ ┴
// . ┌D────────┐ ┌E────────────────┐ ┬ maxHeight(D,E)
// . │ │ │ │ │
// . │ │ │ │ │
// . │ │ ├ ─ ─ ─ ─ ─ ─ ─ ─ ┤ │
// . │ │ │ │ │
// . └─────────┘ └─────────────────┘ ┴
rowWidths := []float64{} rowWidths := []float64{}
for _, row := range layout { for _, row := range layout {
rowHeight := 0. x := 0.
for _, o := range row { for _, o := range row {
o.TopLeft = cursor.Copy() x += o.Width + horizontalGap
cursor.X += o.Width + horizontalGap
rowHeight = math.Max(rowHeight, o.Height)
} }
rowWidth := cursor.X - horizontalGap rowWidth := x - horizontalGap
rowWidths = append(rowWidths, rowWidth) rowWidths = append(rowWidths, rowWidth)
maxX = math.Max(maxX, rowWidth) maxX = math.Max(maxX, rowWidth)
// set all objects in row to the same height
for _, o := range row {
o.Height = rowHeight
}
// new row
cursor.X = 0
cursor.Y += rowHeight + verticalGap
} }
maxY = cursor.Y - horizontalGap
// then expand thinnest objects to make each row the same width // then expand thinnest objects to make each row the same width
// . ┌A─────────────┐ ┌B──┐ ┌C─────────┐ ┬ maxHeight(A,B,C) // . ┌A─────────────┐ ┌B──┐ ┌C─────────┐ ┬ maxHeight(A,B,C)
@ -319,80 +296,79 @@ func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
continue continue
} }
delta := maxX - rowWidth delta := maxX - rowWidth
objects := []*d2graph.Object{}
var widest float64 var widest float64
for _, o := range row { for _, o := range row {
widest = math.Max(widest, o.Width) widest = math.Max(widest, o.Width)
objects = append(objects, o)
} }
sort.Slice(objects, func(i, j int) bool { diffs := make([]float64, len(row))
return objects[i].Width < objects[j].Width totalDiff := 0.
}) for i, o := range row {
// expand smaller objects to fill remaining space diffs[i] = widest - o.Width
for _, o := range objects { totalDiff += diffs[i]
if o.Width < widest { }
var index int if totalDiff > 0 {
for i, rowObj := range row { // expand smaller nodes up to the size of the larger ones with delta
if o == rowObj { // percentage diff
index = i for i := range diffs {
break diffs[i] /= totalDiff
} }
} growth := math.Min(delta, totalDiff)
grow := math.Min(widest-o.Width, delta) // expand smaller objects to fill remaining space
o.Width += grow for i, o := range row {
// shift following objects o.Width += diffs[i] * growth
for i := index + 1; i < len(row); i++ {
row[i].TopLeft.X += grow
}
delta -= grow
if delta <= 0 {
break
}
} }
} }
if delta > 0 { if delta > totalDiff {
grow := delta / float64(len(row)) growth := (delta - totalDiff) / float64(len(row))
for i := len(row) - 1; i >= 0; i-- { for _, o := range row {
o := row[i] o.Width += growth
o.TopLeft.X += grow * float64(i)
o.Width += grow
delta -= grow
} }
} }
} }
} else {
// if we have 3 columns, then each column's objects should have the same width // if we have 2 rows, then each row's objects should have the same height
// . ├maxWidth(A,B)─┤ ├maxW(C,D)─┤ ├maxWidth(E)──────┤ // . ┌A─────────────┐ ┌B──┐ ┌C─────────┐ ┬ maxHeight(A,B,C)
// . ┌A─────────────┐ ┌C─────────┐ ┌E────────────────┐ // . ├ ─ ─ ─ ─ ─ ─ ─┤ │ │ │ │ │
// . └──────────────┘ │ │ │ // . │ │ │ │ ├ ─ ─ ─ ─ ─┤
// . ┌B──┬──────────┐ └──────────┘ │ // . │ │ │ │ │ │
// . │ │ ┌D────────┬┐ └─────────────────┘ // . └──────────────┘ └───┘ └──────────┘ ┴
// . │ │ │ │ │ // . ┌D────────┐ ┌E────────────────┐ ┬ maxHeight(D,E)
// . │ │ │ ││ // . │ │ │
// . └───┴──────────┘ │ // . │ │ │ │
// . │ │ // . │ │ ├ ─ ─ ─ ─ ─ ─ ─ ─ ┤
// . └─────────┴┘ // . │ │ │ │ │
colHeights := []float64{} // . └─────────┘ └─────────────────┘ ┴
for _, column := range layout { for _, row := range layout {
colWidth := 0. rowHeight := 0.
for _, o := range column { for _, o := range row {
o.TopLeft = cursor.Copy() o.TopLeft = cursor.Copy()
cursor.Y += o.Height + verticalGap cursor.X += o.Width + horizontalGap
colWidth = math.Max(colWidth, o.Width) rowHeight = math.Max(rowHeight, o.Height)
}
colHeight := cursor.Y - verticalGap
colHeights = append(colHeights, colHeight)
maxY = math.Max(maxY, colHeight)
// set all objects in column to the same width
for _, o := range column {
o.Width = colWidth
} }
// new column // set all objects in row to the same height
cursor.Y = 0 for _, o := range row {
cursor.X += colWidth + horizontalGap o.Height = rowHeight
}
// new row
cursor.X = 0
cursor.Y += rowHeight + verticalGap
} }
maxX = cursor.X - horizontalGap maxY = cursor.Y - horizontalGap
} else {
// measure column heights
colHeights := []float64{}
for _, column := range layout {
y := 0.
for _, o := range column {
y += o.Height + verticalGap
}
colHeight := y - verticalGap
colHeights = append(colHeights, colHeight)
maxY = math.Max(maxY, colHeight)
}
// then expand shortest objects to make each column the same height // then expand shortest objects to make each column the same height
// . ├maxWidth(A,B)─┤ ├maxW(C,D)─┤ ├maxWidth(E)──────┤ // . ├maxWidth(A,B)─┤ ├maxW(C,D)─┤ ├maxWidth(E)──────┤
// . ┌A─────────────┐ ┌C─────────┐ ┌E────────────────┐ // . ┌A─────────────┐ ┌C─────────┐ ┌E────────────────┐
@ -410,47 +386,63 @@ func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
continue continue
} }
delta := maxY - colHeight delta := maxY - colHeight
objects := []*d2graph.Object{}
var tallest float64 var tallest float64
for _, o := range column { for _, o := range column {
tallest = math.Max(tallest, o.Height) tallest = math.Max(tallest, o.Height)
objects = append(objects, o)
} }
sort.Slice(objects, func(i, j int) bool { diffs := make([]float64, len(column))
return objects[i].Height < objects[j].Height totalDiff := 0.
}) for i, o := range column {
// expand smaller objects to fill remaining space diffs[i] = tallest - o.Height
for _, o := range objects { totalDiff += diffs[i]
if o.Height < tallest { }
var index int if totalDiff > 0 {
for i, colObj := range column { // expand smaller nodes up to the size of the larger ones with delta
if o == colObj { // percentage diff
index = i for i := range diffs {
break diffs[i] /= totalDiff
} }
} growth := math.Min(delta, totalDiff)
grow := math.Min(tallest-o.Height, delta) // expand smaller objects to fill remaining space
o.Height += grow for i, o := range column {
// shift following objects o.Height += diffs[i] * growth
for i := index + 1; i < len(column); i++ {
column[i].TopLeft.Y += grow
}
delta -= grow
if delta <= 0 {
break
}
} }
} }
if delta > 0 { if delta > totalDiff {
grow := delta / float64(len(column)) growth := (delta - totalDiff) / float64(len(column))
for i := len(column) - 1; i >= 0; i-- { for _, o := range column {
o := column[i] o.Height += growth
o.TopLeft.Y += grow * float64(i)
o.Height += grow
delta -= grow
} }
} }
} }
// if we have 3 columns, then each column's objects should have the same width
// . ├maxWidth(A,B)─┤ ├maxW(C,D)─┤ ├maxWidth(E)──────┤
// . ┌A─────────────┐ ┌C─────────┐ ┌E────────────────┐
// . └──────────────┘ │ │ │ │
// . ┌B──┬──────────┐ └──────────┘ │ │
// . │ │ ┌D────────┬┐ └─────────────────┘
// . │ │ │ │ │
// . │ │ │ ││
// . └───┴──────────┘ │ │
// . │ ││
// . └─────────┴┘
for _, column := range layout {
colWidth := 0.
for _, o := range column {
o.TopLeft = cursor.Copy()
cursor.Y += o.Height + verticalGap
colWidth = math.Max(colWidth, o.Width)
}
// set all objects in column to the same width
for _, o := range column {
o.Width = colWidth
}
// new column
cursor.Y = 0
cursor.X += colWidth + horizontalGap
}
maxX = cursor.X - horizontalGap
} }
gd.width = maxX gd.width = maxX
gd.height = maxY gd.height = maxY