cleanup
This commit is contained in:
parent
8b3ba86da3
commit
bab54b4030
2 changed files with 140 additions and 135 deletions
|
|
@ -8,18 +8,20 @@ import (
|
||||||
|
|
||||||
type gridDiagram struct {
|
type gridDiagram struct {
|
||||||
root *d2graph.Object
|
root *d2graph.Object
|
||||||
nodes []*d2graph.Object
|
objects []*d2graph.Object
|
||||||
rows int
|
rows int
|
||||||
columns int
|
columns int
|
||||||
|
|
||||||
rowDominant bool
|
// if true, place objects left to right along rows
|
||||||
|
// if false, place objects top to bottom along columns
|
||||||
|
rowDirected bool
|
||||||
|
|
||||||
width float64
|
width float64
|
||||||
height float64
|
height float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGridDiagram(root *d2graph.Object) *gridDiagram {
|
func newGridDiagram(root *d2graph.Object) *gridDiagram {
|
||||||
gd := gridDiagram{root: root, nodes: root.ChildrenArray}
|
gd := gridDiagram{root: root, objects: root.ChildrenArray}
|
||||||
if root.Attributes.Rows != nil {
|
if root.Attributes.Rows != nil {
|
||||||
gd.rows, _ = strconv.Atoi(root.Attributes.Rows.Value)
|
gd.rows, _ = strconv.Atoi(root.Attributes.Rows.Value)
|
||||||
}
|
}
|
||||||
|
|
@ -27,30 +29,31 @@ func newGridDiagram(root *d2graph.Object) *gridDiagram {
|
||||||
gd.columns, _ = strconv.Atoi(root.Attributes.Columns.Value)
|
gd.columns, _ = strconv.Atoi(root.Attributes.Columns.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// compute exact row/column count based on values entered
|
if gd.rows != 0 && gd.columns != 0 {
|
||||||
if gd.columns == 0 {
|
// . row-directed column-directed
|
||||||
gd.rowDominant = true
|
// . ┌───────┐ ┌───────┐
|
||||||
} else if gd.rows == 0 {
|
// . │ a b c │ │ a d g │
|
||||||
gd.rowDominant = false
|
// . │ d e f │ │ b e h │
|
||||||
} else {
|
// . │ g h i │ │ c f i │
|
||||||
// if keyword rows is first, rows are primary, columns secondary.
|
// . └───────┘ └───────┘
|
||||||
|
// if keyword rows is first, make it row-directed, if columns is first it is column-directed
|
||||||
if root.Attributes.Rows.MapKey.Range.Before(root.Attributes.Columns.MapKey.Range) {
|
if root.Attributes.Rows.MapKey.Range.Before(root.Attributes.Columns.MapKey.Range) {
|
||||||
gd.rowDominant = true
|
gd.rowDirected = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// rows and columns specified, but we want to continue naturally if user enters more nodes
|
// rows and columns specified, but we want to continue naturally if user enters more objects
|
||||||
// e.g. 2 rows, 3 columns specified + g node added: │ with 3 columns, 2 rows:
|
// e.g. 2 rows, 3 columns specified + g added: │ with 3 columns, 2 rows:
|
||||||
// . original add row add column │ original add row add column
|
// . original add row add column │ original add row add column
|
||||||
// . ┌───────┐ ┌───────┐ ┌─────────┐ │ ┌───────┐ ┌───────┐ ┌─────────┐
|
// . ┌───────┐ ┌───────┐ ┌─────────┐ │ ┌───────┐ ┌───────┐ ┌─────────┐
|
||||||
// . │ a b c │ │ a b c │ │ a b c d │ │ │ a c e │ │ a d g │ │ a c e g │
|
// . │ a b c │ │ a b c │ │ a b c d │ │ │ a c e │ │ a d g │ │ a c e g │
|
||||||
// . │ d e f │ │ d e f │ │ e f g │ │ │ b d f │ │ b e │ │ b d f │
|
// . │ d e f │ │ d e f │ │ e f g │ │ │ b d f │ │ b e │ │ b d f │
|
||||||
// . └───────┘ │ g │ └─────────┘ │ └───────┘ │ c f │ └─────────┘
|
// . └───────┘ │ g │ └─────────┘ │ └───────┘ │ c f │ └─────────┘
|
||||||
// . └───────┘ ▲ │ └───────┘ ▲
|
// . └───────┘ ▲ │ └───────┘ ▲
|
||||||
// . ▲ └─existing nodes modified │ ▲ └─existing nodes preserved
|
// . ▲ └─existing objects modified│ ▲ └─existing columns preserved
|
||||||
// . └─existing rows preserved │ └─existing rows modified
|
// . └─existing rows preserved │ └─existing objects modified
|
||||||
capacity := gd.rows * gd.columns
|
capacity := gd.rows * gd.columns
|
||||||
for capacity < len(gd.nodes) {
|
for capacity < len(gd.objects) {
|
||||||
if gd.rowDominant {
|
if gd.rowDirected {
|
||||||
gd.rows++
|
gd.rows++
|
||||||
capacity += gd.columns
|
capacity += gd.columns
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -58,13 +61,15 @@ func newGridDiagram(root *d2graph.Object) *gridDiagram {
|
||||||
capacity += gd.rows
|
capacity += gd.rows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if gd.columns == 0 {
|
||||||
|
gd.rowDirected = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return &gd
|
return &gd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gd *gridDiagram) shift(dx, dy float64) {
|
func (gd *gridDiagram) shift(dx, dy float64) {
|
||||||
for _, obj := range gd.nodes {
|
for _, obj := range gd.objects {
|
||||||
obj.TopLeft.X += dx
|
obj.TopLeft.X += dx
|
||||||
obj.TopLeft.Y += dy
|
obj.TopLeft.Y += dy
|
||||||
}
|
}
|
||||||
|
|
@ -73,9 +78,9 @@ func (gd *gridDiagram) shift(dx, dy float64) {
|
||||||
func (gd *gridDiagram) cleanup(obj *d2graph.Object, graph *d2graph.Graph) {
|
func (gd *gridDiagram) cleanup(obj *d2graph.Object, graph *d2graph.Graph) {
|
||||||
obj.Children = make(map[string]*d2graph.Object)
|
obj.Children = make(map[string]*d2graph.Object)
|
||||||
obj.ChildrenArray = make([]*d2graph.Object, 0)
|
obj.ChildrenArray = make([]*d2graph.Object, 0)
|
||||||
for _, child := range gd.nodes {
|
for _, child := range gd.objects {
|
||||||
obj.Children[child.ID] = child
|
obj.Children[child.ID] = child
|
||||||
obj.ChildrenArray = append(obj.ChildrenArray, child)
|
obj.ChildrenArray = append(obj.ChildrenArray, child)
|
||||||
}
|
}
|
||||||
graph.Objects = append(graph.Objects, gd.nodes...)
|
graph.Objects = append(graph.Objects, gd.objects...)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,8 +94,8 @@ func withoutGridDiagrams(ctx context.Context, g *d2graph.Graph) (gridDiagrams ma
|
||||||
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
|
obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
|
||||||
gridDiagrams[obj.AbsID()] = gd
|
gridDiagrams[obj.AbsID()] = gd
|
||||||
|
|
||||||
for _, node := range gd.nodes {
|
for _, o := range gd.objects {
|
||||||
toRemove[node] = struct{}{}
|
toRemove[o] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -123,12 +123,12 @@ func layoutGrid(g *d2graph.Graph, obj *d2graph.Object) (*gridDiagram, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// position labels and icons
|
// position labels and icons
|
||||||
for _, n := range gd.nodes {
|
for _, o := range gd.objects {
|
||||||
if n.Attributes.Icon != nil {
|
if o.Attributes.Icon != nil {
|
||||||
n.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
|
o.LabelPosition = go2.Pointer(string(label.InsideTopCenter))
|
||||||
n.IconPosition = go2.Pointer(string(label.InsideMiddleCenter))
|
o.IconPosition = go2.Pointer(string(label.InsideMiddleCenter))
|
||||||
} else {
|
} else {
|
||||||
n.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
|
o.LabelPosition = go2.Pointer(string(label.InsideMiddleCenter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,19 +136,19 @@ func layoutGrid(g *d2graph.Graph, obj *d2graph.Object) (*gridDiagram, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gd *gridDiagram) layoutEvenly(g *d2graph.Graph, obj *d2graph.Object) {
|
func (gd *gridDiagram) layoutEvenly(g *d2graph.Graph, obj *d2graph.Object) {
|
||||||
// layout nodes in a grid with these 2 properties:
|
// layout objects in a grid with these 2 properties:
|
||||||
// all nodes in the same row should have the same height
|
// all objects in the same row should have the same height
|
||||||
// all nodes in the same column should have the same width
|
// all objects in the same column should have the same width
|
||||||
|
|
||||||
getNode := func(rowIndex, columnIndex int) *d2graph.Object {
|
getObject := func(rowIndex, columnIndex int) *d2graph.Object {
|
||||||
var index int
|
var index int
|
||||||
if gd.rowDominant {
|
if gd.rowDirected {
|
||||||
index = rowIndex*gd.columns + columnIndex
|
index = rowIndex*gd.columns + columnIndex
|
||||||
} else {
|
} else {
|
||||||
index = columnIndex*gd.rows + rowIndex
|
index = columnIndex*gd.rows + rowIndex
|
||||||
}
|
}
|
||||||
if index < len(gd.nodes) {
|
if index < len(gd.objects) {
|
||||||
return gd.nodes[index]
|
return gd.objects[index]
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -158,38 +158,38 @@ func (gd *gridDiagram) layoutEvenly(g *d2graph.Graph, obj *d2graph.Object) {
|
||||||
for i := 0; i < gd.rows; i++ {
|
for i := 0; i < gd.rows; i++ {
|
||||||
rowHeight := 0.
|
rowHeight := 0.
|
||||||
for j := 0; j < gd.columns; j++ {
|
for j := 0; j < gd.columns; j++ {
|
||||||
n := getNode(i, j)
|
o := getObject(i, j)
|
||||||
if n == nil {
|
if o == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
rowHeight = math.Max(rowHeight, n.Height)
|
rowHeight = math.Max(rowHeight, o.Height)
|
||||||
}
|
}
|
||||||
rowHeights = append(rowHeights, rowHeight)
|
rowHeights = append(rowHeights, rowHeight)
|
||||||
}
|
}
|
||||||
for j := 0; j < gd.columns; j++ {
|
for j := 0; j < gd.columns; j++ {
|
||||||
columnWidth := 0.
|
columnWidth := 0.
|
||||||
for i := 0; i < gd.rows; i++ {
|
for i := 0; i < gd.rows; i++ {
|
||||||
n := getNode(i, j)
|
o := getObject(i, j)
|
||||||
if n == nil {
|
if o == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
columnWidth = math.Max(columnWidth, n.Width)
|
columnWidth = math.Max(columnWidth, o.Width)
|
||||||
}
|
}
|
||||||
colWidths = append(colWidths, columnWidth)
|
colWidths = append(colWidths, columnWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor := geo.NewPoint(0, 0)
|
cursor := geo.NewPoint(0, 0)
|
||||||
if gd.rowDominant {
|
if gd.rowDirected {
|
||||||
for i := 0; i < gd.rows; i++ {
|
for i := 0; i < gd.rows; i++ {
|
||||||
for j := 0; j < gd.columns; j++ {
|
for j := 0; j < gd.columns; j++ {
|
||||||
n := getNode(i, j)
|
o := getObject(i, j)
|
||||||
if n == nil {
|
if o == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
n.Width = colWidths[j]
|
o.Width = colWidths[j]
|
||||||
n.Height = rowHeights[i]
|
o.Height = rowHeights[i]
|
||||||
n.TopLeft = cursor.Copy()
|
o.TopLeft = cursor.Copy()
|
||||||
cursor.X += n.Width + HORIZONTAL_PAD
|
cursor.X += o.Width + HORIZONTAL_PAD
|
||||||
}
|
}
|
||||||
cursor.X = 0
|
cursor.X = 0
|
||||||
cursor.Y += rowHeights[i] + VERTICAL_PAD
|
cursor.Y += rowHeights[i] + VERTICAL_PAD
|
||||||
|
|
@ -197,14 +197,14 @@ func (gd *gridDiagram) layoutEvenly(g *d2graph.Graph, obj *d2graph.Object) {
|
||||||
} else {
|
} else {
|
||||||
for j := 0; j < gd.columns; j++ {
|
for j := 0; j < gd.columns; j++ {
|
||||||
for i := 0; i < gd.rows; i++ {
|
for i := 0; i < gd.rows; i++ {
|
||||||
n := getNode(i, j)
|
o := getObject(i, j)
|
||||||
if n == nil {
|
if o == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
n.Width = colWidths[j]
|
o.Width = colWidths[j]
|
||||||
n.Height = rowHeights[i]
|
o.Height = rowHeights[i]
|
||||||
n.TopLeft = cursor.Copy()
|
o.TopLeft = cursor.Copy()
|
||||||
cursor.Y += n.Height + VERTICAL_PAD
|
cursor.Y += o.Height + VERTICAL_PAD
|
||||||
}
|
}
|
||||||
cursor.X += colWidths[j] + HORIZONTAL_PAD
|
cursor.X += colWidths[j] + HORIZONTAL_PAD
|
||||||
cursor.Y = 0
|
cursor.Y = 0
|
||||||
|
|
@ -225,14 +225,14 @@ func (gd *gridDiagram) layoutEvenly(g *d2graph.Graph, obj *d2graph.Object) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
|
func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
|
||||||
// assume we have the following nodes to layout:
|
// assume we have the following objects to layout:
|
||||||
// . ┌A──────────────┐ ┌B──┐ ┌C─────────┐ ┌D────────┐ ┌E────────────────┐
|
// . ┌A──────────────┐ ┌B──┐ ┌C─────────┐ ┌D────────┐ ┌E────────────────┐
|
||||||
// . └───────────────┘ │ │ │ │ │ │ │ │
|
// . └───────────────┘ │ │ │ │ │ │ │ │
|
||||||
// . │ │ └──────────┘ │ │ │ │
|
// . │ │ └──────────┘ │ │ │ │
|
||||||
// . │ │ │ │ └─────────────────┘
|
// . │ │ │ │ └─────────────────┘
|
||||||
// . └───┘ │ │
|
// . └───┘ │ │
|
||||||
// . └─────────┘
|
// . └─────────┘
|
||||||
// Note: if the grid is row dominant, all nodes should be the same height (same width if column dominant)
|
// Note: if the grid is row dominant, all objects should be the same height (same width if column dominant)
|
||||||
// . ┌A─────────────┐ ┌B──┐ ┌C─────────┐ ┌D────────┐ ┌E────────────────┐
|
// . ┌A─────────────┐ ┌B──┐ ┌C─────────┐ ┌D────────┐ ┌E────────────────┐
|
||||||
// . ├ ─ ─ ─ ─ ─ ─ ─┤ │ │ │ │ │ │ │ │
|
// . ├ ─ ─ ─ ─ ─ ─ ─┤ │ │ │ │ │ │ │ │
|
||||||
// . │ │ │ │ ├ ─ ─ ─ ─ ─┤ │ │ │ │
|
// . │ │ │ │ ├ ─ ─ ─ ─ ─┤ │ │ │ │
|
||||||
|
|
@ -242,15 +242,15 @@ func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
|
||||||
|
|
||||||
// we want to split up the total width across the N rows or columns as evenly as possible
|
// we want to split up the total width across the N rows or columns as evenly as possible
|
||||||
var totalWidth, totalHeight float64
|
var totalWidth, totalHeight float64
|
||||||
for _, n := range gd.nodes {
|
for _, o := range gd.objects {
|
||||||
totalWidth += n.Width
|
totalWidth += o.Width
|
||||||
totalHeight += n.Height
|
totalHeight += o.Height
|
||||||
}
|
}
|
||||||
totalWidth += HORIZONTAL_PAD * float64(len(gd.nodes)-gd.rows)
|
totalWidth += HORIZONTAL_PAD * float64(len(gd.objects)-gd.rows)
|
||||||
totalHeight += VERTICAL_PAD * float64(len(gd.nodes)-gd.columns)
|
totalHeight += VERTICAL_PAD * float64(len(gd.objects)-gd.columns)
|
||||||
|
|
||||||
var layout [][]*d2graph.Object
|
var layout [][]*d2graph.Object
|
||||||
if gd.rowDominant {
|
if gd.rowDirected {
|
||||||
targetWidth := totalWidth / float64(gd.rows)
|
targetWidth := totalWidth / float64(gd.rows)
|
||||||
layout = gd.getBestLayout(targetWidth, false)
|
layout = gd.getBestLayout(targetWidth, false)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -260,8 +260,8 @@ 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.rowDominant {
|
if gd.rowDirected {
|
||||||
// if we have 2 rows, then each row's nodes should have the same height
|
// if we have 2 rows, then each row's objects should have the same height
|
||||||
// . ┌A─────────────┐ ┌B──┐ ┌C─────────┐ ┬ maxHeight(A,B,C)
|
// . ┌A─────────────┐ ┌B──┐ ┌C─────────┐ ┬ maxHeight(A,B,C)
|
||||||
// . ├ ─ ─ ─ ─ ─ ─ ─┤ │ │ │ │ │
|
// . ├ ─ ─ ─ ─ ─ ─ ─┤ │ │ │ │ │
|
||||||
// . │ │ │ │ ├ ─ ─ ─ ─ ─┤ │
|
// . │ │ │ │ ├ ─ ─ ─ ─ ─┤ │
|
||||||
|
|
@ -276,18 +276,18 @@ func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
|
||||||
rowWidths := []float64{}
|
rowWidths := []float64{}
|
||||||
for _, row := range layout {
|
for _, row := range layout {
|
||||||
rowHeight := 0.
|
rowHeight := 0.
|
||||||
for _, n := range row {
|
for _, o := range row {
|
||||||
n.TopLeft = cursor.Copy()
|
o.TopLeft = cursor.Copy()
|
||||||
cursor.X += n.Width + HORIZONTAL_PAD
|
cursor.X += o.Width + HORIZONTAL_PAD
|
||||||
rowHeight = math.Max(rowHeight, n.Height)
|
rowHeight = math.Max(rowHeight, o.Height)
|
||||||
}
|
}
|
||||||
rowWidth := cursor.X - HORIZONTAL_PAD
|
rowWidth := cursor.X - HORIZONTAL_PAD
|
||||||
rowWidths = append(rowWidths, rowWidth)
|
rowWidths = append(rowWidths, rowWidth)
|
||||||
maxX = math.Max(maxX, rowWidth)
|
maxX = math.Max(maxX, rowWidth)
|
||||||
|
|
||||||
// set all nodes in row to the same height
|
// set all objects in row to the same height
|
||||||
for _, n := range row {
|
for _, o := range row {
|
||||||
n.Height = rowHeight
|
o.Height = rowHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
// new row
|
// new row
|
||||||
|
|
@ -296,7 +296,7 @@ func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
|
||||||
}
|
}
|
||||||
maxY = cursor.Y - VERTICAL_PAD
|
maxY = cursor.Y - VERTICAL_PAD
|
||||||
|
|
||||||
// then expand thinnest nodes 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)
|
||||||
// . │ │ │ │ │ │ │
|
// . │ │ │ │ │ │ │
|
||||||
// . │ │ │ │ │ │ │
|
// . │ │ │ │ │ │ │
|
||||||
|
|
@ -314,28 +314,28 @@ func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
delta := maxX - rowWidth
|
delta := maxX - rowWidth
|
||||||
nodes := []*d2graph.Object{}
|
objects := []*d2graph.Object{}
|
||||||
var widest float64
|
var widest float64
|
||||||
for _, n := range row {
|
for _, o := range row {
|
||||||
widest = math.Max(widest, n.Width)
|
widest = math.Max(widest, o.Width)
|
||||||
nodes = append(nodes, n)
|
objects = append(objects, o)
|
||||||
}
|
}
|
||||||
sort.Slice(nodes, func(i, j int) bool {
|
sort.Slice(objects, func(i, j int) bool {
|
||||||
return nodes[i].Width < nodes[j].Width
|
return objects[i].Width < objects[j].Width
|
||||||
})
|
})
|
||||||
// expand smaller nodes to fill remaining space
|
// expand smaller objects to fill remaining space
|
||||||
for _, n := range nodes {
|
for _, o := range objects {
|
||||||
if n.Width < widest {
|
if o.Width < widest {
|
||||||
var index int
|
var index int
|
||||||
for i, node := range row {
|
for i, rowObj := range row {
|
||||||
if n == node {
|
if o == rowObj {
|
||||||
index = i
|
index = i
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
grow := math.Min(widest-n.Width, delta)
|
grow := math.Min(widest-o.Width, delta)
|
||||||
n.Width += grow
|
o.Width += grow
|
||||||
// shift following nodes
|
// shift following objects
|
||||||
for i := index + 1; i < len(row); i++ {
|
for i := index + 1; i < len(row); i++ {
|
||||||
row[i].TopLeft.X += grow
|
row[i].TopLeft.X += grow
|
||||||
}
|
}
|
||||||
|
|
@ -348,15 +348,15 @@ func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
|
||||||
if delta > 0 {
|
if delta > 0 {
|
||||||
grow := delta / float64(len(row))
|
grow := delta / float64(len(row))
|
||||||
for i := len(row) - 1; i >= 0; i-- {
|
for i := len(row) - 1; i >= 0; i-- {
|
||||||
n := row[i]
|
o := row[i]
|
||||||
n.TopLeft.X += grow * float64(i)
|
o.TopLeft.X += grow * float64(i)
|
||||||
n.Width += grow
|
o.Width += grow
|
||||||
delta -= grow
|
delta -= grow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if we have 3 columns, then each column's nodes should have the same width
|
// if we have 3 columns, then each column's objects should have the same width
|
||||||
// . ├maxWidth(A,B)─┤ ├maxW(C,D)─┤ ├maxWidth(E)──────┤
|
// . ├maxWidth(A,B)─┤ ├maxW(C,D)─┤ ├maxWidth(E)──────┤
|
||||||
// . ┌A─────────────┐ ┌C─────────┐ ┌E────────────────┐
|
// . ┌A─────────────┐ ┌C─────────┐ ┌E────────────────┐
|
||||||
// . └──────────────┘ │ │ │ │
|
// . └──────────────┘ │ │ │ │
|
||||||
|
|
@ -370,17 +370,17 @@ func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
|
||||||
colHeights := []float64{}
|
colHeights := []float64{}
|
||||||
for _, column := range layout {
|
for _, column := range layout {
|
||||||
colWidth := 0.
|
colWidth := 0.
|
||||||
for _, n := range column {
|
for _, o := range column {
|
||||||
n.TopLeft = cursor.Copy()
|
o.TopLeft = cursor.Copy()
|
||||||
cursor.Y += n.Height + VERTICAL_PAD
|
cursor.Y += o.Height + VERTICAL_PAD
|
||||||
colWidth = math.Max(colWidth, n.Width)
|
colWidth = math.Max(colWidth, o.Width)
|
||||||
}
|
}
|
||||||
colHeight := cursor.Y - VERTICAL_PAD
|
colHeight := cursor.Y - VERTICAL_PAD
|
||||||
colHeights = append(colHeights, colHeight)
|
colHeights = append(colHeights, colHeight)
|
||||||
maxY = math.Max(maxY, colHeight)
|
maxY = math.Max(maxY, colHeight)
|
||||||
// set all nodes in column to the same width
|
// set all objects in column to the same width
|
||||||
for _, n := range column {
|
for _, o := range column {
|
||||||
n.Width = colWidth
|
o.Width = colWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
// new column
|
// new column
|
||||||
|
|
@ -388,7 +388,7 @@ func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
|
||||||
cursor.X += colWidth + HORIZONTAL_PAD
|
cursor.X += colWidth + HORIZONTAL_PAD
|
||||||
}
|
}
|
||||||
maxX = cursor.X - HORIZONTAL_PAD
|
maxX = cursor.X - HORIZONTAL_PAD
|
||||||
// then expand shortest nodes 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────────────────┐
|
||||||
// . ├ ─ ─ ─ ─ ─ ─ ┤ │ │ │ │
|
// . ├ ─ ─ ─ ─ ─ ─ ┤ │ │ │ │
|
||||||
|
|
@ -405,28 +405,28 @@ func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
delta := maxY - colHeight
|
delta := maxY - colHeight
|
||||||
nodes := []*d2graph.Object{}
|
objects := []*d2graph.Object{}
|
||||||
var tallest float64
|
var tallest float64
|
||||||
for _, n := range column {
|
for _, o := range column {
|
||||||
tallest = math.Max(tallest, n.Height)
|
tallest = math.Max(tallest, o.Height)
|
||||||
nodes = append(nodes, n)
|
objects = append(objects, o)
|
||||||
}
|
}
|
||||||
sort.Slice(nodes, func(i, j int) bool {
|
sort.Slice(objects, func(i, j int) bool {
|
||||||
return nodes[i].Height < nodes[j].Height
|
return objects[i].Height < objects[j].Height
|
||||||
})
|
})
|
||||||
// expand smaller nodes to fill remaining space
|
// expand smaller objects to fill remaining space
|
||||||
for _, n := range nodes {
|
for _, o := range objects {
|
||||||
if n.Height < tallest {
|
if o.Height < tallest {
|
||||||
var index int
|
var index int
|
||||||
for i, node := range column {
|
for i, colObj := range column {
|
||||||
if n == node {
|
if o == colObj {
|
||||||
index = i
|
index = i
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
grow := math.Min(tallest-n.Height, delta)
|
grow := math.Min(tallest-o.Height, delta)
|
||||||
n.Height += grow
|
o.Height += grow
|
||||||
// shift following nodes
|
// shift following objects
|
||||||
for i := index + 1; i < len(column); i++ {
|
for i := index + 1; i < len(column); i++ {
|
||||||
column[i].TopLeft.Y += grow
|
column[i].TopLeft.Y += grow
|
||||||
}
|
}
|
||||||
|
|
@ -439,9 +439,9 @@ func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
|
||||||
if delta > 0 {
|
if delta > 0 {
|
||||||
grow := delta / float64(len(column))
|
grow := delta / float64(len(column))
|
||||||
for i := len(column) - 1; i >= 0; i-- {
|
for i := len(column) - 1; i >= 0; i-- {
|
||||||
n := column[i]
|
o := column[i]
|
||||||
n.TopLeft.Y += grow * float64(i)
|
o.TopLeft.Y += grow * float64(i)
|
||||||
n.Height += grow
|
o.Height += grow
|
||||||
delta -= grow
|
delta -= grow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -451,7 +451,7 @@ func (gd *gridDiagram) layoutDynamic(g *d2graph.Graph, obj *d2graph.Object) {
|
||||||
gd.height = maxY
|
gd.height = maxY
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate the best layout of nodes aiming for each row to be the targetSize width
|
// generate the best layout of objects aiming for each row to be the targetSize width
|
||||||
// if columns is true, each column aims to have the targetSize height
|
// if columns is true, each column aims to have the targetSize height
|
||||||
func (gd *gridDiagram) getBestLayout(targetSize float64, columns bool) [][]*d2graph.Object {
|
func (gd *gridDiagram) getBestLayout(targetSize float64, columns bool) [][]*d2graph.Object {
|
||||||
var nCuts int
|
var nCuts int
|
||||||
|
|
@ -461,24 +461,24 @@ func (gd *gridDiagram) getBestLayout(targetSize float64, columns bool) [][]*d2gr
|
||||||
nCuts = gd.rows - 1
|
nCuts = gd.rows - 1
|
||||||
}
|
}
|
||||||
if nCuts == 0 {
|
if nCuts == 0 {
|
||||||
return genLayout(gd.nodes, nil)
|
return genLayout(gd.objects, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all options for where to place these cuts, preferring later cuts over earlier cuts
|
// get all options for where to place these cuts, preferring later cuts over earlier cuts
|
||||||
// with 5 nodes and 2 cuts we have these options:
|
// with 5 objects and 2 cuts we have these options:
|
||||||
// . A B C │ D │ E <- these cuts would produce: ┌A─┐ ┌B─┐ ┌C─┐
|
// . A B C │ D │ E <- these cuts would produce: ┌A─┐ ┌B─┐ ┌C─┐
|
||||||
// . A B │ C D │ E └──┘ └──┘ └──┘
|
// . A B │ C D │ E └──┘ └──┘ └──┘
|
||||||
// . A │ B C D │ E ┌D───────────┐
|
// . A │ B C D │ E ┌D───────────┐
|
||||||
// . A B │ C │ D E └────────────┘
|
// . A B │ C │ D E └────────────┘
|
||||||
// . A │ B C │ D E ┌E───────────┐
|
// . A │ B C │ D E ┌E───────────┐
|
||||||
// . A │ B │ C D E └────────────┘
|
// . A │ B │ C D E └────────────┘
|
||||||
divisions := genDivisions(gd.nodes, nCuts)
|
divisions := genDivisions(gd.objects, nCuts)
|
||||||
|
|
||||||
var bestLayout [][]*d2graph.Object
|
var bestLayout [][]*d2graph.Object
|
||||||
bestDist := math.MaxFloat64
|
bestDist := math.MaxFloat64
|
||||||
// of these divisions, find the layout with rows closest to the targetSize
|
// of these divisions, find the layout with rows closest to the targetSize
|
||||||
for _, division := range divisions {
|
for _, division := range divisions {
|
||||||
layout := genLayout(gd.nodes, division)
|
layout := genLayout(gd.objects, division)
|
||||||
dist := getDistToTarget(layout, targetSize, columns)
|
dist := getDistToTarget(layout, targetSize, columns)
|
||||||
if dist < bestDist {
|
if dist < bestDist {
|
||||||
bestLayout = layout
|
bestLayout = layout
|
||||||
|
|
@ -489,16 +489,16 @@ func (gd *gridDiagram) getBestLayout(targetSize float64, columns bool) [][]*d2gr
|
||||||
return bestLayout
|
return bestLayout
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all possible divisions of nodes by the number of cuts
|
// get all possible divisions of objects by the number of cuts
|
||||||
func genDivisions(nodes []*d2graph.Object, nCuts int) (divisions [][]int) {
|
func genDivisions(objects []*d2graph.Object, nCuts int) (divisions [][]int) {
|
||||||
if len(nodes) < 2 || nCuts == 0 {
|
if len(objects) < 2 || nCuts == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// we go in this order to prefer extra nodes in starting rows rather than later ones
|
// we go in this order to prefer extra objects in starting rows rather than later ones
|
||||||
lastNode := len(nodes) - 1
|
lastObj := len(objects) - 1
|
||||||
for index := lastNode; index >= nCuts; index-- {
|
for index := lastObj; index >= nCuts; index-- {
|
||||||
if nCuts > 1 {
|
if nCuts > 1 {
|
||||||
for _, inner := range genDivisions(nodes[:index], nCuts-1) {
|
for _, inner := range genDivisions(objects[:index], nCuts-1) {
|
||||||
divisions = append(divisions, append(inner, index-1))
|
divisions = append(divisions, append(inner, index-1))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -509,19 +509,19 @@ func genDivisions(nodes []*d2graph.Object, nCuts int) (divisions [][]int) {
|
||||||
return divisions
|
return divisions
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate a grid of nodes from the given cut indices
|
// generate a grid of objects from the given cut indices
|
||||||
func genLayout(nodes []*d2graph.Object, cutIndices []int) [][]*d2graph.Object {
|
func genLayout(objects []*d2graph.Object, cutIndices []int) [][]*d2graph.Object {
|
||||||
layout := make([][]*d2graph.Object, len(cutIndices)+1)
|
layout := make([][]*d2graph.Object, len(cutIndices)+1)
|
||||||
nodeIndex := 0
|
objIndex := 0
|
||||||
for i := 0; i <= len(cutIndices); i++ {
|
for i := 0; i <= len(cutIndices); i++ {
|
||||||
var stop int
|
var stop int
|
||||||
if i < len(cutIndices) {
|
if i < len(cutIndices) {
|
||||||
stop = cutIndices[i]
|
stop = cutIndices[i]
|
||||||
} else {
|
} else {
|
||||||
stop = len(nodes) - 1
|
stop = len(objects) - 1
|
||||||
}
|
}
|
||||||
for ; nodeIndex <= stop; nodeIndex++ {
|
for ; objIndex <= stop; objIndex++ {
|
||||||
layout[i] = append(layout[i], nodes[nodeIndex])
|
layout[i] = append(layout[i], objects[objIndex])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return layout
|
return layout
|
||||||
|
|
@ -531,11 +531,11 @@ func getDistToTarget(layout [][]*d2graph.Object, targetSize float64, columns boo
|
||||||
totalDelta := 0.
|
totalDelta := 0.
|
||||||
for _, row := range layout {
|
for _, row := range layout {
|
||||||
rowSize := 0.
|
rowSize := 0.
|
||||||
for _, n := range row {
|
for _, o := range row {
|
||||||
if columns {
|
if columns {
|
||||||
rowSize += n.Height + VERTICAL_PAD
|
rowSize += o.Height + VERTICAL_PAD
|
||||||
} else {
|
} else {
|
||||||
rowSize += n.Width + HORIZONTAL_PAD
|
rowSize += o.Width + HORIZONTAL_PAD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
totalDelta += math.Abs(rowSize - targetSize)
|
totalDelta += math.Abs(rowSize - targetSize)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue