1
2
3 package d2near
4
5 import (
6 "context"
7 "math"
8 "strings"
9
10 "oss.terrastruct.com/d2/d2graph"
11 "oss.terrastruct.com/d2/lib/geo"
12 "oss.terrastruct.com/d2/lib/label"
13 )
14
15 const pad = 20
16
17 type set map[string]struct{}
18
19 var HorizontalCenterNears = set{
20 "center-left": {},
21 "center-right": {},
22 }
23 var VerticalCenterNears = set{
24 "top-center": {},
25 "bottom-center": {},
26 }
27 var NonCenterNears = set{
28 "top-left": {},
29 "top-right": {},
30 "bottom-left": {},
31 "bottom-right": {},
32 }
33
34
35 func Layout(ctx context.Context, g *d2graph.Graph, constantNearGraphs []*d2graph.Graph) error {
36 if len(constantNearGraphs) == 0 {
37 return nil
38 }
39
40 for _, tempGraph := range constantNearGraphs {
41 tempGraph.Root.ChildrenArray[0].Parent = g.Root
42 for _, obj := range tempGraph.Objects {
43 obj.Graph = g
44 }
45 }
46
47
48
49
50 for _, currentSet := range []set{VerticalCenterNears, HorizontalCenterNears, NonCenterNears} {
51 for _, tempGraph := range constantNearGraphs {
52 obj := tempGraph.Root.ChildrenArray[0]
53 _, in := currentSet[d2graph.Key(obj.NearKey)[0]]
54 if in {
55 prevX, prevY := obj.TopLeft.X, obj.TopLeft.Y
56 obj.TopLeft = geo.NewPoint(place(obj))
57 dx, dy := obj.TopLeft.X-prevX, obj.TopLeft.Y-prevY
58
59 for _, subObject := range tempGraph.Objects {
60
61 if subObject == obj {
62 continue
63 }
64 subObject.TopLeft.X += dx
65 subObject.TopLeft.Y += dy
66 }
67 for _, subEdge := range tempGraph.Edges {
68 for _, point := range subEdge.Route {
69 point.X += dx
70 point.Y += dy
71 }
72 }
73 }
74 }
75 for _, tempGraph := range constantNearGraphs {
76 obj := tempGraph.Root.ChildrenArray[0]
77 _, in := currentSet[d2graph.Key(obj.NearKey)[0]]
78 if in {
79
80 g.Objects = append(g.Objects, tempGraph.Objects...)
81 if obj.Parent.Children == nil {
82 obj.Parent.Children = make(map[string]*d2graph.Object)
83 }
84 obj.Parent.Children[strings.ToLower(obj.ID)] = obj
85 obj.Parent.ChildrenArray = append(obj.Parent.ChildrenArray, obj)
86 g.Edges = append(g.Edges, tempGraph.Edges...)
87 }
88 }
89 }
90
91 return nil
92 }
93
94
95 func place(obj *d2graph.Object) (float64, float64) {
96 tl, br := boundingBox(obj.Graph)
97 w := br.X - tl.X
98 h := br.Y - tl.Y
99
100 nearKeyStr := d2graph.Key(obj.NearKey)[0]
101 var x, y float64
102 switch nearKeyStr {
103 case "top-left":
104 x, y = tl.X-obj.Width-pad, tl.Y-obj.Height-pad
105 case "top-center":
106 x, y = tl.X+w/2-obj.Width/2, tl.Y-obj.Height-pad
107 case "top-right":
108 x, y = br.X+pad, tl.Y-obj.Height-pad
109 case "center-left":
110 x, y = tl.X-obj.Width-pad, tl.Y+h/2-obj.Height/2
111 case "center-right":
112 x, y = br.X+pad, tl.Y+h/2-obj.Height/2
113 case "bottom-left":
114 x, y = tl.X-obj.Width-pad, br.Y+pad
115 case "bottom-center":
116 x, y = br.X-w/2-obj.Width/2, br.Y+pad
117 case "bottom-right":
118 x, y = br.X+pad, br.Y+pad
119 }
120
121 if obj.LabelPosition != nil && !strings.Contains(*obj.LabelPosition, "INSIDE") {
122 if strings.Contains(*obj.LabelPosition, "_TOP_") {
123
124 if strings.Contains(nearKeyStr, "bottom") {
125 y += float64(obj.LabelDimensions.Height)
126 }
127 } else if strings.Contains(*obj.LabelPosition, "_LEFT_") {
128
129 if strings.Contains(nearKeyStr, "right") {
130 x += float64(obj.LabelDimensions.Width)
131 }
132 } else if strings.Contains(*obj.LabelPosition, "_RIGHT_") {
133
134 if strings.Contains(nearKeyStr, "left") {
135 x -= float64(obj.LabelDimensions.Width)
136 }
137 } else if strings.Contains(*obj.LabelPosition, "_BOTTOM_") {
138
139 if strings.Contains(nearKeyStr, "top") {
140 y -= float64(obj.LabelDimensions.Height)
141 }
142 }
143 }
144
145 return x, y
146 }
147
148
149
150
151 func boundingBox(g *d2graph.Graph) (tl, br *geo.Point) {
152 if len(g.Objects) == 0 {
153 return geo.NewPoint(0, 0), geo.NewPoint(0, 0)
154 }
155 x1 := math.Inf(1)
156 y1 := math.Inf(1)
157 x2 := math.Inf(-1)
158 y2 := math.Inf(-1)
159
160 for _, obj := range g.Objects {
161 if obj.NearKey != nil {
162
163
164 switch d2graph.Key(obj.NearKey)[0] {
165 case "top-center", "bottom-center":
166 x1 = math.Min(x1, obj.TopLeft.X)
167 x2 = math.Max(x2, obj.TopLeft.X+obj.Width)
168 case "center-left", "center-right":
169 y1 = math.Min(y1, obj.TopLeft.Y)
170 y2 = math.Max(y2, obj.TopLeft.Y+obj.Height)
171 }
172 } else {
173 if obj.OuterNearContainer() != nil {
174 continue
175 }
176 x1 = math.Min(x1, obj.TopLeft.X)
177 y1 = math.Min(y1, obj.TopLeft.Y)
178 x2 = math.Max(x2, obj.TopLeft.X+obj.Width)
179 y2 = math.Max(y2, obj.TopLeft.Y+obj.Height)
180 if obj.Label.Value != "" && obj.LabelPosition != nil {
181 labelPosition := label.FromString(*obj.LabelPosition)
182 if labelPosition.IsOutside() {
183 labelTL := labelPosition.GetPointOnBox(obj.Box, label.PADDING, float64(obj.LabelDimensions.Width), float64(obj.LabelDimensions.Height))
184 x1 = math.Min(x1, labelTL.X)
185 y1 = math.Min(y1, labelTL.Y)
186 x2 = math.Max(x2, labelTL.X+float64(obj.LabelDimensions.Width))
187 y2 = math.Max(y2, labelTL.Y+float64(obj.LabelDimensions.Height))
188 }
189 }
190 }
191 }
192
193 if math.IsInf(x1, 1) && math.IsInf(x2, -1) {
194 x1 = 0
195 x2 = 0
196 }
197 if math.IsInf(y1, 1) && math.IsInf(y2, -1) {
198 y1 = 0
199 y2 = 0
200 }
201
202 return geo.NewPoint(x1, y1), geo.NewPoint(x2, y2)
203 }
204
View as plain text