package d2near
import (
"context"
"math"
"strings"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/lib/geo"
"oss.terrastruct.com/d2/lib/label"
)
const pad = 20
type set map [string ]struct {}
var HorizontalCenterNears = set {
"center-left" : {},
"center-right" : {},
}
var VerticalCenterNears = set {
"top-center" : {},
"bottom-center" : {},
}
var NonCenterNears = set {
"top-left" : {},
"top-right" : {},
"bottom-left" : {},
"bottom-right" : {},
}
func Layout (ctx context .Context , g *d2graph .Graph , constantNearGraphs []*d2graph .Graph ) error {
if len (constantNearGraphs ) == 0 {
return nil
}
for _ , tempGraph := range constantNearGraphs {
tempGraph .Root .ChildrenArray [0 ].Parent = g .Root
for _ , obj := range tempGraph .Objects {
obj .Graph = g
}
}
for _ , currentSet := range []set {VerticalCenterNears , HorizontalCenterNears , NonCenterNears } {
for _ , tempGraph := range constantNearGraphs {
obj := tempGraph .Root .ChildrenArray [0 ]
_ , in := currentSet [d2graph .Key (obj .NearKey )[0 ]]
if in {
prevX , prevY := obj .TopLeft .X , obj .TopLeft .Y
obj .TopLeft = geo .NewPoint (place (obj ))
dx , dy := obj .TopLeft .X -prevX , obj .TopLeft .Y -prevY
for _ , subObject := range tempGraph .Objects {
if subObject == obj {
continue
}
subObject .TopLeft .X += dx
subObject .TopLeft .Y += dy
}
for _ , subEdge := range tempGraph .Edges {
for _ , point := range subEdge .Route {
point .X += dx
point .Y += dy
}
}
}
}
for _ , tempGraph := range constantNearGraphs {
obj := tempGraph .Root .ChildrenArray [0 ]
_ , in := currentSet [d2graph .Key (obj .NearKey )[0 ]]
if in {
g .Objects = append (g .Objects , tempGraph .Objects ...)
if obj .Parent .Children == nil {
obj .Parent .Children = make (map [string ]*d2graph .Object )
}
obj .Parent .Children [strings .ToLower (obj .ID )] = obj
obj .Parent .ChildrenArray = append (obj .Parent .ChildrenArray , obj )
g .Edges = append (g .Edges , tempGraph .Edges ...)
}
}
}
return nil
}
func place(obj *d2graph .Object ) (float64 , float64 ) {
tl , br := boundingBox (obj .Graph )
w := br .X - tl .X
h := br .Y - tl .Y
nearKeyStr := d2graph .Key (obj .NearKey )[0 ]
var x , y float64
switch nearKeyStr {
case "top-left" :
x , y = tl .X -obj .Width -pad , tl .Y -obj .Height -pad
case "top-center" :
x , y = tl .X +w /2 -obj .Width /2 , tl .Y -obj .Height -pad
case "top-right" :
x , y = br .X +pad , tl .Y -obj .Height -pad
case "center-left" :
x , y = tl .X -obj .Width -pad , tl .Y +h /2 -obj .Height /2
case "center-right" :
x , y = br .X +pad , tl .Y +h /2 -obj .Height /2
case "bottom-left" :
x , y = tl .X -obj .Width -pad , br .Y +pad
case "bottom-center" :
x , y = br .X -w /2 -obj .Width /2 , br .Y +pad
case "bottom-right" :
x , y = br .X +pad , br .Y +pad
}
if obj .LabelPosition != nil && !strings .Contains (*obj .LabelPosition , "INSIDE" ) {
if strings .Contains (*obj .LabelPosition , "_TOP_" ) {
if strings .Contains (nearKeyStr , "bottom" ) {
y += float64 (obj .LabelDimensions .Height )
}
} else if strings .Contains (*obj .LabelPosition , "_LEFT_" ) {
if strings .Contains (nearKeyStr , "right" ) {
x += float64 (obj .LabelDimensions .Width )
}
} else if strings .Contains (*obj .LabelPosition , "_RIGHT_" ) {
if strings .Contains (nearKeyStr , "left" ) {
x -= float64 (obj .LabelDimensions .Width )
}
} else if strings .Contains (*obj .LabelPosition , "_BOTTOM_" ) {
if strings .Contains (nearKeyStr , "top" ) {
y -= float64 (obj .LabelDimensions .Height )
}
}
}
return x , y
}
func boundingBox(g *d2graph .Graph ) (tl , br *geo .Point ) {
if len (g .Objects ) == 0 {
return geo .NewPoint (0 , 0 ), geo .NewPoint (0 , 0 )
}
x1 := math .Inf (1 )
y1 := math .Inf (1 )
x2 := math .Inf (-1 )
y2 := math .Inf (-1 )
for _ , obj := range g .Objects {
if obj .NearKey != nil {
switch d2graph .Key (obj .NearKey )[0 ] {
case "top-center" , "bottom-center" :
x1 = math .Min (x1 , obj .TopLeft .X )
x2 = math .Max (x2 , obj .TopLeft .X +obj .Width )
case "center-left" , "center-right" :
y1 = math .Min (y1 , obj .TopLeft .Y )
y2 = math .Max (y2 , obj .TopLeft .Y +obj .Height )
}
} else {
if obj .OuterNearContainer () != nil {
continue
}
x1 = math .Min (x1 , obj .TopLeft .X )
y1 = math .Min (y1 , obj .TopLeft .Y )
x2 = math .Max (x2 , obj .TopLeft .X +obj .Width )
y2 = math .Max (y2 , obj .TopLeft .Y +obj .Height )
if obj .Label .Value != "" && obj .LabelPosition != nil {
labelPosition := label .FromString (*obj .LabelPosition )
if labelPosition .IsOutside () {
labelTL := labelPosition .GetPointOnBox (obj .Box , label .PADDING , float64 (obj .LabelDimensions .Width ), float64 (obj .LabelDimensions .Height ))
x1 = math .Min (x1 , labelTL .X )
y1 = math .Min (y1 , labelTL .Y )
x2 = math .Max (x2 , labelTL .X +float64 (obj .LabelDimensions .Width ))
y2 = math .Max (y2 , labelTL .Y +float64 (obj .LabelDimensions .Height ))
}
}
}
}
for _ , edge := range g .Edges {
if edge .Src .OuterNearContainer () != nil || edge .Dst .OuterNearContainer () != nil {
continue
}
if edge .Route != nil {
for _ , point := range edge .Route {
x1 = math .Min (x1 , point .X )
y1 = math .Min (y1 , point .Y )
x2 = math .Max (x2 , point .X )
y2 = math .Max (y2 , point .Y )
}
}
}
if math .IsInf (x1 , 1 ) && math .IsInf (x2 , -1 ) {
x1 = 0
x2 = 0
}
if math .IsInf (y1 , 1 ) && math .IsInf (y2 , -1 ) {
y1 = 0
y2 = 0
}
return geo .NewPoint (x1 , y1 ), geo .NewPoint (x2 , y2 )
}
The pages are generated with Golds v0.8.2 . (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 .