package layoutgraph
import (
"math"
"strings"
"github.com/pancsta/d2/d2layouts/d2talalayout/internal/invariant"
"github.com/pancsta/d2/lib/geo"
)
type Cluster struct {
Vessel *Node
Nodes []*Node
Arrangement ClusterArrangement
DesiredArrangement ClusterArrangement
Graph *Graph
EdgeAbductions []*EdgeAbduction
Padding float64
FixedSize bool
Container *Node
}
type ClusterArrangement string
func (ca ClusterArrangement ) Flip () ClusterArrangement {
if ca == Row {
return Column
}
return Row
}
const (
Row ClusterArrangement = "Row"
Column ClusterArrangement = "Column"
)
func (c *Cluster ) Resize (vessel *Node ) {
if err := c .resizeWithWork (vessel , unmeteredGroupGeometry ); err != nil {
panic (err )
}
}
func (c *Cluster ) resizeWithWork (vessel *Node , work workStepper ) error {
if c == nil || vessel == nil {
return invariant .New ("cluster is missing its vessel" )
}
if !c .FixedSize {
nodeWidth , nodeHeight , err := c .maximumsWithWork (work )
if err != nil {
return err
}
for _ , node := range c .Nodes {
if err := work .Step (); err != nil {
return err
}
node .Width = nodeWidth
node .Height = nodeHeight
}
}
nodeWidth , nodeHeight , err := c .maximumsWithWork (work )
if err != nil {
return err
}
if c .Arrangement == Row {
vessel .Width = nodeWidth *float64 (len (c .Nodes )) + c .Padding *float64 (len (c .Nodes )-1 )
vessel .Height = nodeHeight
}
if c .Arrangement == Column {
vessel .Width = nodeWidth
vessel .Height = nodeHeight *float64 (len (c .Nodes )) + c .Padding *float64 (len (c .Nodes )-1 )
}
return nil
}
func (c *Cluster ) maximumsWithWork (work workStepper ) (float64 , float64 , error ) {
maxWidth , maxHeight := 0.0 , 0.0
for _ , node := range c .Nodes {
if err := work .Step (); err != nil {
return 0 , 0 , err
}
if node == nil {
return 0 , 0 , invariant .New ("cluster contains a nil node" )
}
maxWidth = math .Max (maxWidth , node .Width )
}
for _ , node := range c .Nodes {
if err := work .Step (); err != nil {
return 0 , 0 , err
}
maxHeight = math .Max (maxHeight , node .Height )
}
return maxWidth , maxHeight , work .Finish ()
}
func (c *Cluster ) ArrangeClusterNodes () {
if err := c .arrangeNodesWithWork (unmeteredGroupGeometry ); err != nil {
panic (err )
}
}
func (c *Cluster ) arrangeNodesWithWork (work ClusterGeometryWork ) error {
if c .Vessel .TopLeft == nil {
return work .Finish ()
}
if c .Arrangement == Row {
position := c .Vessel .TopLeft .X
vesselCenter := c .Vessel .TopLeft .Y + c .Vessel .Height /2
for _ , node := range c .Nodes {
if err := work .Step (); err != nil {
return err
}
if node .TopLeft != nil {
dx := position - node .TopLeft .X
dy := math .Round (vesselCenter - (node .TopLeft .Y + node .Height /2 ))
if err := work .MoveNodeWithChildren (node , dx , dy ); err != nil {
return err
}
} else {
node .TopLeft = geo .NewPoint (position , math .Round (vesselCenter -node .Height /2 ))
if err := work .PositionContainerChildren (node ); err != nil {
return err
}
}
position += node .Width + c .Padding
}
}
if c .Arrangement == Column {
position := c .Vessel .TopLeft .Y
vesselCenter := c .Vessel .TopLeft .X + c .Vessel .Width /2
for _ , node := range c .Nodes {
if err := work .Step (); err != nil {
return err
}
if node .TopLeft != nil {
dx := math .Round (vesselCenter - (node .TopLeft .X + node .Width /2 ))
dy := position - node .TopLeft .Y
if err := work .MoveNodeWithChildren (node , dx , dy ); err != nil {
return err
}
} else {
node .TopLeft = geo .NewPoint (math .Round (vesselCenter -node .Width /2 ), position )
if err := work .PositionContainerChildren (node ); err != nil {
return err
}
}
position += node .Height + c .Padding
}
}
return work .Finish ()
}
func (cluster *Cluster ) DebugID () string {
nodeIDs := []string {}
for _ , n := range cluster .Nodes {
nodeIDs = append (nodeIDs , n .DebugID ())
}
return "[" + strings .Join (nodeIDs , ", " ) + "]; Arrangement: " + string (cluster .Arrangement )
}
func (c *Cluster ) SyncGeometry () {
if err := c .SyncGeometryWithWork (unmeteredGroupGeometry ); err != nil {
panic (err )
}
}
func (c *Cluster ) SyncGeometryWithWork (work ClusterGeometryWork ) error {
if work == nil {
return invariant .New ("cluster geometry requires work accounting" )
}
if err := work .Step (); err != nil {
return err
}
if c == nil || c .Vessel == nil {
return invariant .New ("cluster is missing its vessel" )
}
if err := c .resizeWithWork (c .Vessel , work ); err != nil {
return err
}
return c .arrangeNodesWithWork (work )
}
func (g *Graph ) SyncClusters () {
if len (g .Clusters ) == 0 {
return
}
sync := func (n *Node ) {
if n .isClusterVessel {
g .Clusters [n ].SyncGeometry ()
}
}
for _ , n := range g .Nodes {
n .rdfsWalk (sync )
}
}
func (c *Cluster ) isActive () bool {
return c != nil && c .Vessel .Graph != nil
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .