package d2grid import ( "strconv" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/lib/geo" ) type gridDiagram struct { root *d2graph.Object objects []*d2graph.Object edges []*d2graph.Edge rows int columns int // if true, place objects left to right along rows // if false, place objects top to bottom along columns rowDirected bool width float64 height float64 verticalGap int horizontalGap int } func newGridDiagram(root *d2graph.Object) *gridDiagram { gd := gridDiagram{ root: root, objects: root.ChildrenArray, verticalGap: DEFAULT_GAP, horizontalGap: DEFAULT_GAP, } if root.GridRows != nil { gd.rows, _ = strconv.Atoi(root.GridRows.Value) } if root.GridColumns != nil { gd.columns, _ = strconv.Atoi(root.GridColumns.Value) } if gd.rows != 0 && gd.columns != 0 { // . row-directed column-directed // . ┌───────┐ ┌───────┐ // . │ a b c │ │ a d g │ // . │ d e f │ │ b e h │ // . │ g h i │ │ c f i │ // . └───────┘ └───────┘ // if keyword rows is first, make it row-directed, if columns is first it is column-directed if root.GridRows.MapKey.Range.Before(root.GridColumns.MapKey.Range) { gd.rowDirected = true } // rows and columns specified, but we want to continue naturally if user enters more objects // e.g. 2 rows, 3 columns specified + g added: │ with 3 columns, 2 rows: // . 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 │ // . │ d e f │ │ d e f │ │ e f g │ │ │ b d f │ │ b e │ │ b d f │ // . └───────┘ │ g │ └─────────┘ │ └───────┘ │ c f │ └─────────┘ // . └───────┘ ▲ │ └───────┘ ▲ // . ▲ └─existing objects modified│ ▲ └─existing columns preserved // . └─existing rows preserved │ └─existing objects modified capacity := gd.rows * gd.columns for capacity < len(gd.objects) { if gd.rowDirected { gd.rows++ capacity += gd.columns } else { gd.columns++ capacity += gd.rows } } } else if gd.columns == 0 { gd.rowDirected = true // we can only make N rows with N objects if len(gd.objects) < gd.rows { gd.rows = len(gd.objects) } } else { if len(gd.objects) < gd.columns { gd.columns = len(gd.objects) } } // grid gap sets both, but can be overridden if root.GridGap != nil { gd.verticalGap, _ = strconv.Atoi(root.GridGap.Value) gd.horizontalGap = gd.verticalGap } if root.VerticalGap != nil { gd.verticalGap, _ = strconv.Atoi(root.VerticalGap.Value) } if root.HorizontalGap != nil { gd.horizontalGap, _ = strconv.Atoi(root.HorizontalGap.Value) } for _, o := range gd.objects { o.TopLeft = geo.NewPoint(0, 0) } return &gd } func (gd *gridDiagram) shift(dx, dy float64) { for _, obj := range gd.objects { obj.MoveWithDescendants(dx, dy) } for _, e := range gd.edges { e.Move(dx, dy) } }