1 package d2dagrelayout
2
3 import (
4 "context"
5 _ "embed"
6 "encoding/json"
7 "fmt"
8 "math"
9 "sort"
10 "strings"
11
12 "cdr.dev/slog"
13 "github.com/dop251/goja"
14
15 "oss.terrastruct.com/util-go/xdefer"
16
17 "oss.terrastruct.com/util-go/go2"
18
19 "oss.terrastruct.com/d2/d2graph"
20 "oss.terrastruct.com/d2/d2target"
21 "oss.terrastruct.com/d2/lib/geo"
22 "oss.terrastruct.com/d2/lib/label"
23 "oss.terrastruct.com/d2/lib/log"
24 "oss.terrastruct.com/d2/lib/shape"
25 )
26
27
28 var setupJS string
29
30
31 var dagreJS string
32
33 const (
34 MIN_RANK_SEP = 60
35 EDGE_LABEL_GAP = 20
36 DEFAULT_PADDING = 30.
37 MIN_SPACING = 10.
38 )
39
40 type ConfigurableOpts struct {
41 NodeSep int `json:"nodesep"`
42 EdgeSep int `json:"edgesep"`
43 }
44
45 var DefaultOpts = ConfigurableOpts{
46 NodeSep: 60,
47 EdgeSep: 20,
48 }
49
50 type DagreNode struct {
51 ID string `json:"id"`
52 X float64 `json:"x"`
53 Y float64 `json:"y"`
54 Width float64 `json:"width"`
55 Height float64 `json:"height"`
56 }
57
58 type DagreEdge struct {
59 Points []*geo.Point `json:"points"`
60 }
61
62 type dagreOpts struct {
63
64 ranksep int
65
66 rankdir string
67
68 ConfigurableOpts
69 }
70
71 func DefaultLayout(ctx context.Context, g *d2graph.Graph) (err error) {
72 return Layout(ctx, g, nil)
73 }
74
75 func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err error) {
76 if opts == nil {
77 opts = &DefaultOpts
78 }
79 defer xdefer.Errorf(&err, "failed to dagre layout")
80
81 debugJS := false
82 vm := goja.New()
83 if _, err := vm.RunString(dagreJS); err != nil {
84 return err
85 }
86 if _, err := vm.RunString(setupJS); err != nil {
87 return err
88 }
89
90 rootAttrs := dagreOpts{
91 ConfigurableOpts: ConfigurableOpts{
92 EdgeSep: opts.EdgeSep,
93 NodeSep: opts.NodeSep,
94 },
95 }
96 isHorizontal := false
97 switch g.Root.Direction.Value {
98 case "down":
99 rootAttrs.rankdir = "TB"
100 case "right":
101 rootAttrs.rankdir = "LR"
102 isHorizontal = true
103 case "left":
104 rootAttrs.rankdir = "RL"
105 isHorizontal = true
106 case "up":
107 rootAttrs.rankdir = "BT"
108 default:
109 rootAttrs.rankdir = "TB"
110 }
111
112
113 for _, obj := range g.Objects {
114 positionLabelsIcons(obj)
115 }
116
117 maxLabelWidth := 0
118 maxLabelHeight := 0
119 for _, edge := range g.Edges {
120 width := edge.LabelDimensions.Width
121 height := edge.LabelDimensions.Height
122 maxLabelWidth = go2.Max(maxLabelWidth, width)
123 maxLabelHeight = go2.Max(maxLabelHeight, height)
124 }
125
126 if !isHorizontal {
127 rootAttrs.ranksep = go2.Max(100, maxLabelHeight+40)
128 } else {
129 rootAttrs.ranksep = go2.Max(100, maxLabelWidth+40)
130
131
132
133
134
135 }
136
137 configJS := setGraphAttrs(rootAttrs)
138 if _, err := vm.RunString(configJS); err != nil {
139 return err
140 }
141
142 mapper := NewObjectMapper()
143 for _, obj := range g.Objects {
144 mapper.Register(obj)
145 }
146 loadScript := ""
147 for _, obj := range g.Objects {
148 loadScript += mapper.generateAddNodeLine(obj, int(obj.Width), int(obj.Height))
149 if obj.Parent != g.Root {
150 loadScript += mapper.generateAddParentLine(obj, obj.Parent)
151 }
152 }
153
154 for _, edge := range g.Edges {
155 src, dst := getEdgeEndpoints(g, edge)
156
157 width := edge.LabelDimensions.Width
158 height := edge.LabelDimensions.Height
159
160 numEdges := 0
161 for _, e := range g.Edges {
162 otherSrc, otherDst := getEdgeEndpoints(g, e)
163 if (otherSrc == src && otherDst == dst) || (otherSrc == dst && otherDst == src) {
164 numEdges++
165 }
166 }
167
168
169 if numEdges > 1 {
170 switch g.Root.Direction.Value {
171 case "down", "up", "":
172 width += EDGE_LABEL_GAP
173 case "left", "right":
174 height += EDGE_LABEL_GAP
175 }
176 }
177
178 loadScript += mapper.generateAddEdgeLine(src, dst, edge.AbsID(), width, height)
179 }
180
181 if debugJS {
182 log.Debug(ctx, "script", slog.F("all", setupJS+configJS+loadScript))
183 }
184
185 if _, err := vm.RunString(loadScript); err != nil {
186 return err
187 }
188
189 if _, err := vm.RunString(`dagre.layout(g)`); err != nil {
190 if debugJS {
191 log.Warn(ctx, "layout error", slog.F("err", err))
192 }
193 return err
194 }
195
196 for i := range g.Objects {
197 val, err := vm.RunString(fmt.Sprintf("JSON.stringify(g.node(g.nodes()[%d]))", i))
198 if err != nil {
199 return err
200 }
201 var dn DagreNode
202 if err := json.Unmarshal([]byte(val.String()), &dn); err != nil {
203 return err
204 }
205 if debugJS {
206 log.Debug(ctx, "graph", slog.F("json", dn))
207 }
208
209 obj := mapper.ToObj(dn.ID)
210
211
212 obj.TopLeft = geo.NewPoint(math.Round(dn.X-dn.Width/2), math.Round(dn.Y-dn.Height/2))
213 obj.Width = math.Ceil(dn.Width)
214 obj.Height = math.Ceil(dn.Height)
215 }
216
217 for i, edge := range g.Edges {
218 val, err := vm.RunString(fmt.Sprintf("JSON.stringify(g.edge(g.edges()[%d]))", i))
219 if err != nil {
220 return err
221 }
222 var de DagreEdge
223 if err := json.Unmarshal([]byte(val.String()), &de); err != nil {
224 return err
225 }
226 if debugJS {
227 log.Debug(ctx, "graph", slog.F("json", de))
228 }
229
230 points := make([]*geo.Point, len(de.Points))
231 for i := range de.Points {
232 if edge.SrcArrow && !edge.DstArrow {
233 points[len(de.Points)-i-1] = de.Points[i].Copy()
234 } else {
235 points[i] = de.Points[i].Copy()
236 }
237 }
238
239 startIndex, endIndex := 0, len(points)-1
240 start, end := points[startIndex], points[endIndex]
241
242
243 if edge.Src != edge.Dst {
244 for i := 1; i < len(points); i++ {
245 segment := *geo.NewSegment(points[i-1], points[i])
246 if intersections := edge.Src.Box.Intersections(segment); len(intersections) > 0 {
247 start = intersections[0]
248 startIndex = i - 1
249 }
250
251 if intersections := edge.Dst.Box.Intersections(segment); len(intersections) > 0 {
252 end = intersections[0]
253 endIndex = i
254 break
255 }
256 }
257 }
258 points = points[startIndex : endIndex+1]
259 points[0] = start
260 points[len(points)-1] = end
261
262 edge.Route = points
263 }
264
265 adjustRankSpacing(g, float64(rootAttrs.ranksep), isHorizontal)
266 adjustCrossRankSpacing(g, float64(rootAttrs.ranksep), !isHorizontal)
267 fitContainerPadding(g, float64(rootAttrs.ranksep), isHorizontal)
268
269 for _, edge := range g.Edges {
270 points := edge.Route
271 startIndex, endIndex := 0, len(points)-1
272 start, end := points[startIndex], points[endIndex]
273
274
275
276 if startIndex+2 < len(points) {
277 newStartingSegment := *geo.NewSegment(start, points[startIndex+1])
278 if newStartingSegment.Length() < d2graph.MIN_SEGMENT_LEN {
279
280
281 nextStart := points[startIndex+1]
282 nextEnd := points[startIndex+2]
283
284
285 nextSegment := *geo.NewSegment(nextStart, nextEnd)
286 v := nextSegment.ToVector()
287 extendedStart := nextEnd.ToVector().Add(v.AddLength(d2graph.MIN_SEGMENT_LEN)).ToPoint()
288 extended := *geo.NewSegment(nextEnd, extendedStart)
289
290 if intersections := edge.Src.Box.Intersections(extended); len(intersections) > 0 {
291 startIndex++
292 points[startIndex] = intersections[0]
293 start = points[startIndex]
294 }
295 }
296 }
297 if endIndex-2 >= 0 {
298 newEndingSegment := *geo.NewSegment(end, points[endIndex-1])
299 if newEndingSegment.Length() < d2graph.MIN_SEGMENT_LEN {
300
301 prevStart := points[endIndex-2]
302 prevEnd := points[endIndex-1]
303
304 prevSegment := *geo.NewSegment(prevStart, prevEnd)
305 v := prevSegment.ToVector()
306 extendedEnd := prevStart.ToVector().Add(v.AddLength(d2graph.MIN_SEGMENT_LEN)).ToPoint()
307 extended := *geo.NewSegment(prevStart, extendedEnd)
308
309 if intersections := edge.Dst.Box.Intersections(extended); len(intersections) > 0 {
310 endIndex--
311 points[endIndex] = intersections[0]
312 end = points[endIndex]
313 }
314 }
315 }
316
317 var originalSrcTL, originalDstTL *geo.Point
318
319 if srcDx, srcDy := edge.Src.GetModifierElementAdjustments(); srcDx != 0 || srcDy != 0 {
320 if start.X > edge.Src.TopLeft.X+srcDx &&
321 start.Y < edge.Src.TopLeft.Y+edge.Src.Height-srcDy {
322 originalSrcTL = edge.Src.TopLeft.Copy()
323 edge.Src.TopLeft.X += srcDx
324 edge.Src.TopLeft.Y -= srcDy
325 }
326 }
327 if dstDx, dstDy := edge.Dst.GetModifierElementAdjustments(); dstDx != 0 || dstDy != 0 {
328 if end.X > edge.Dst.TopLeft.X+dstDx &&
329 end.Y < edge.Dst.TopLeft.Y+edge.Dst.Height-dstDy {
330 originalDstTL = edge.Dst.TopLeft.Copy()
331 edge.Dst.TopLeft.X += dstDx
332 edge.Dst.TopLeft.Y -= dstDy
333 }
334 }
335
336 startIndex, endIndex = edge.TraceToShape(points, startIndex, endIndex)
337 points = points[startIndex : endIndex+1]
338
339
340 vectors := make([]geo.Vector, 0, len(points)-1)
341 for i := 1; i < len(points); i++ {
342 vectors = append(vectors, points[i-1].VectorTo(points[i]))
343 }
344
345 path := make([]*geo.Point, 0)
346 path = append(path, points[0])
347 if len(vectors) > 1 {
348 path = append(path, points[0].AddVector(vectors[0].Multiply(.8)))
349 for i := 1; i < len(vectors)-2; i++ {
350 p := points[i]
351 v := vectors[i]
352 path = append(path, p.AddVector(v.Multiply(.2)))
353 path = append(path, p.AddVector(v.Multiply(.5)))
354 path = append(path, p.AddVector(v.Multiply(.8)))
355 }
356 path = append(path, points[len(points)-2].AddVector(vectors[len(vectors)-1].Multiply(.2)))
357 edge.IsCurve = true
358 }
359 path = append(path, points[len(points)-1])
360
361 edge.Route = path
362
363 if edge.Label.Value != "" {
364 edge.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())
365 }
366
367
368 if originalSrcTL != nil {
369 edge.Src.TopLeft.X = originalSrcTL.X
370 edge.Src.TopLeft.Y = originalSrcTL.Y
371 }
372 if originalDstTL != nil {
373 edge.Dst.TopLeft.X = originalDstTL.X
374 edge.Dst.TopLeft.Y = originalDstTL.Y
375 }
376 }
377
378 return nil
379 }
380
381 func getEdgeEndpoints(g *d2graph.Graph, edge *d2graph.Edge) (*d2graph.Object, *d2graph.Object) {
382
383
384 src := edge.Src
385 for len(src.Children) > 0 && src.Class == nil && src.SQLTable == nil {
386
387 src = getLongestEdgeChainTail(g, src)
388 }
389 dst := edge.Dst
390 for len(dst.Children) > 0 && dst.Class == nil && dst.SQLTable == nil {
391 dst = getLongestEdgeChainHead(g, dst)
392 }
393 if edge.SrcArrow && !edge.DstArrow {
394
395 src, dst = dst, src
396 }
397 return src, dst
398 }
399
400 func setGraphAttrs(attrs dagreOpts) string {
401 return fmt.Sprintf(`g.setGraph({
402 ranksep: %d,
403 edgesep: %d,
404 nodesep: %d,
405 rankdir: "%s",
406 });
407 `,
408 attrs.ranksep,
409 attrs.ConfigurableOpts.EdgeSep,
410 attrs.ConfigurableOpts.NodeSep,
411 attrs.rankdir,
412 )
413 }
414
415
416
417 func getLongestEdgeChainHead(g *d2graph.Graph, container *d2graph.Object) *d2graph.Object {
418 rank := make(map[*d2graph.Object]int)
419 chainLength := make(map[*d2graph.Object]int)
420
421 for _, obj := range container.ChildrenArray {
422 isHead := true
423 for _, e := range g.Edges {
424 if inContainer(e.Src, container) != nil && inContainer(e.Dst, obj) != nil {
425 isHead = false
426 break
427 }
428 }
429 if !isHead {
430 continue
431 }
432 rank[obj] = 1
433 chainLength[obj] = 1
434
435 queue := []*d2graph.Object{obj}
436 visited := make(map[*d2graph.Object]struct{})
437 for len(queue) > 0 {
438 curr := queue[0]
439 queue = queue[1:]
440 if _, ok := visited[curr]; ok {
441 continue
442 }
443 visited[curr] = struct{}{}
444 for _, e := range g.Edges {
445 child := inContainer(e.Dst, container)
446 if child == curr {
447 continue
448 }
449 if child != nil && inContainer(e.Src, curr) != nil {
450 if rank[curr]+1 > rank[child] {
451 rank[child] = rank[curr] + 1
452 chainLength[obj] = go2.Max(chainLength[obj], rank[child])
453 }
454 queue = append(queue, child)
455 }
456 }
457 }
458 }
459 max := int(math.MinInt32)
460 for _, obj := range container.ChildrenArray {
461 if chainLength[obj] > max {
462 max = chainLength[obj]
463 }
464 }
465
466 var heads []*d2graph.Object
467 for i, obj := range container.ChildrenArray {
468 if rank[obj] == 1 && chainLength[obj] == max {
469 heads = append(heads, container.ChildrenArray[i])
470 }
471 }
472
473 if len(heads) > 0 {
474 return heads[int(math.Floor(float64(len(heads))/2.0))]
475 }
476 return container.ChildrenArray[0]
477 }
478
479
480
481
482 func getLongestEdgeChainTail(g *d2graph.Graph, container *d2graph.Object) *d2graph.Object {
483 rank := make(map[*d2graph.Object]int)
484
485 for _, obj := range container.ChildrenArray {
486 isHead := true
487 for _, e := range g.Edges {
488 if inContainer(e.Src, container) != nil && inContainer(e.Dst, obj) != nil {
489 isHead = false
490 break
491 }
492 }
493 if !isHead {
494 continue
495 }
496 rank[obj] = 1
497
498 queue := []*d2graph.Object{obj}
499 visited := make(map[*d2graph.Object]struct{})
500 for len(queue) > 0 {
501 curr := queue[0]
502 queue = queue[1:]
503 if _, ok := visited[curr]; ok {
504 continue
505 }
506 visited[curr] = struct{}{}
507 for _, e := range g.Edges {
508 child := inContainer(e.Dst, container)
509 if child == curr {
510 continue
511 }
512 if child != nil && inContainer(e.Src, curr) != nil {
513 rank[child] = go2.Max(rank[child], rank[curr]+1)
514 queue = append(queue, child)
515 }
516 }
517 }
518 }
519 max := int(math.MinInt32)
520 for _, obj := range container.ChildrenArray {
521 if rank[obj] > max {
522 max = rank[obj]
523 }
524 }
525
526 var tails []*d2graph.Object
527 for i, obj := range container.ChildrenArray {
528 if rank[obj] == max {
529 tails = append(tails, container.ChildrenArray[i])
530 }
531 }
532
533 return tails[int(math.Floor(float64(len(tails))/2.0))]
534 }
535
536 func inContainer(obj, container *d2graph.Object) *d2graph.Object {
537 if obj == nil {
538 return nil
539 }
540 if obj == container {
541 return obj
542 }
543 if obj.Parent == container {
544 return obj
545 }
546 return inContainer(obj.Parent, container)
547 }
548
549 func positionLabelsIcons(obj *d2graph.Object) {
550 if obj.Icon != nil && obj.IconPosition == nil {
551 if len(obj.ChildrenArray) > 0 {
552 obj.IconPosition = go2.Pointer(label.OutsideTopLeft.String())
553 if obj.LabelPosition == nil {
554 obj.LabelPosition = go2.Pointer(label.OutsideTopRight.String())
555 return
556 }
557 } else if obj.SQLTable != nil || obj.Class != nil || obj.Language != "" {
558 obj.IconPosition = go2.Pointer(label.OutsideTopLeft.String())
559 } else {
560 obj.IconPosition = go2.Pointer(label.InsideMiddleCenter.String())
561 }
562 }
563 if obj.HasLabel() && obj.LabelPosition == nil {
564 if len(obj.ChildrenArray) > 0 {
565 obj.LabelPosition = go2.Pointer(label.OutsideTopCenter.String())
566 } else if obj.HasOutsideBottomLabel() {
567 obj.LabelPosition = go2.Pointer(label.OutsideBottomCenter.String())
568 } else if obj.Icon != nil {
569 obj.LabelPosition = go2.Pointer(label.InsideTopCenter.String())
570 } else {
571 obj.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())
572 }
573 }
574 }
575
576 func getRanks(g *d2graph.Graph, isHorizontal bool) (ranks [][]*d2graph.Object, objectRanks, startingParentRanks, endingParentRanks map[*d2graph.Object]int) {
577 alignedObjects := make(map[float64][]*d2graph.Object)
578 for _, obj := range g.Objects {
579 if !obj.IsContainer() {
580 if !isHorizontal {
581 y := math.Ceil(obj.TopLeft.Y + obj.Height/2)
582 alignedObjects[y] = append(alignedObjects[y], obj)
583 } else {
584 x := math.Ceil(obj.TopLeft.X + obj.Width/2)
585 alignedObjects[x] = append(alignedObjects[x], obj)
586 }
587 }
588 }
589
590 levels := make([]float64, 0, len(alignedObjects))
591 for l := range alignedObjects {
592 levels = append(levels, l)
593 }
594 sort.Slice(levels, func(i, j int) bool {
595 return levels[i] < levels[j]
596 })
597
598 ranks = make([][]*d2graph.Object, 0, len(levels))
599 objectRanks = make(map[*d2graph.Object]int)
600 for i, l := range levels {
601 for _, obj := range alignedObjects[l] {
602 objectRanks[obj] = i
603 }
604 ranks = append(ranks, alignedObjects[l])
605 }
606
607 startingParentRanks = make(map[*d2graph.Object]int)
608 endingParentRanks = make(map[*d2graph.Object]int)
609 for _, obj := range g.Objects {
610 if obj.IsContainer() {
611 continue
612 }
613 r := objectRanks[obj]
614
615 for parent := obj.Parent; parent != nil && parent != g.Root; parent = parent.Parent {
616 if start, has := startingParentRanks[parent]; !has || r < start {
617 startingParentRanks[parent] = r
618 }
619 if end, has := endingParentRanks[parent]; !has || r > end {
620 endingParentRanks[parent] = r
621 }
622 }
623 }
624
625 return ranks, objectRanks, startingParentRanks, endingParentRanks
626 }
627
628
629 func shiftDown(g *d2graph.Graph, start, distance float64, isHorizontal bool) {
630 if isHorizontal {
631 for _, edge := range g.Edges {
632 first, last := edge.Route[0], edge.Route[len(edge.Route)-1]
633 if start <= first.X {
634 onStaticSrc := first.X == edge.Src.TopLeft.X+edge.Src.Width && edge.Src.TopLeft.X < start
635 if !onStaticSrc {
636
637 first.X += distance
638 }
639 }
640 if start <= last.X {
641 onStaticDst := last.X == edge.Dst.TopLeft.X+edge.Dst.Width && edge.Dst.TopLeft.X < start
642 if !onStaticDst {
643 last.X += distance
644 }
645 }
646 for i := 1; i < len(edge.Route)-1; i++ {
647 p := edge.Route[i]
648 if p.X < start {
649 continue
650 }
651 p.X += distance
652 }
653 }
654 for _, obj := range g.Objects {
655 if obj.TopLeft.X < start {
656 continue
657 }
658 obj.TopLeft.X += distance
659 }
660 } else {
661 for _, edge := range g.Edges {
662 first, last := edge.Route[0], edge.Route[len(edge.Route)-1]
663 if start <= first.Y {
664 onStaticSrc := first.Y == edge.Src.TopLeft.Y+edge.Src.Height && edge.Src.TopLeft.Y < start
665 if !onStaticSrc {
666
667 first.Y += distance
668 }
669 }
670 if start <= last.Y {
671 onStaticDst := last.Y == edge.Dst.TopLeft.Y+edge.Dst.Height && edge.Dst.TopLeft.Y < start
672 if !onStaticDst {
673 last.Y += distance
674 }
675 }
676 for i := 1; i < len(edge.Route)-1; i++ {
677 p := edge.Route[i]
678 if p.Y < start {
679 continue
680 }
681 p.Y += distance
682 }
683 }
684 for _, obj := range g.Objects {
685 if obj.TopLeft.Y < start {
686 continue
687 }
688 obj.TopLeft.Y += distance
689 }
690 }
691 }
692
693 func shiftUp(g *d2graph.Graph, start, distance float64, isHorizontal bool) {
694 if isHorizontal {
695 for _, edge := range g.Edges {
696 first, last := edge.Route[0], edge.Route[len(edge.Route)-1]
697 if first.X <= start {
698 onStaticSrc := first.X == edge.Src.TopLeft.X && start < edge.Src.TopLeft.X+edge.Src.Width
699 if !onStaticSrc {
700
701 first.X -= distance
702 }
703 }
704 if last.X <= start {
705 onStaticDst := last.X == edge.Dst.TopLeft.X && start < edge.Dst.TopLeft.X+edge.Dst.Width
706 if !onStaticDst {
707 last.X -= distance
708 }
709 }
710 for i := 1; i < len(edge.Route)-1; i++ {
711 p := edge.Route[i]
712 if start < p.X {
713 continue
714 }
715 p.X -= distance
716 }
717 }
718 for _, obj := range g.Objects {
719 if start < obj.TopLeft.X {
720 continue
721 }
722 obj.TopLeft.X -= distance
723 }
724 } else {
725 for _, edge := range g.Edges {
726 first, last := edge.Route[0], edge.Route[len(edge.Route)-1]
727 if first.Y <= start {
728
729 onStaticSrc := first.Y == edge.Src.TopLeft.Y && start < edge.Src.TopLeft.Y+edge.Src.Height
730 if !onStaticSrc {
731 first.Y -= distance
732 }
733 }
734 if last.Y <= start {
735 onStaticDst := last.Y == edge.Dst.TopLeft.Y && start < edge.Dst.TopLeft.Y
736 if !onStaticDst {
737 last.Y -= distance
738 }
739 }
740 for i := 1; i < len(edge.Route)-1; i++ {
741 p := edge.Route[i]
742
743 if start < p.Y {
744 continue
745 }
746 p.Y -= distance
747 }
748 }
749 for _, obj := range g.Objects {
750 if start < obj.TopLeft.Y {
751 continue
752 }
753 obj.TopLeft.Y -= distance
754 }
755 }
756 }
757
758
759
760
761 func shiftReachableDown(g *d2graph.Graph, obj *d2graph.Object, start, distance float64, isHorizontal, isMargin bool) map[*d2graph.Object]struct{} {
762 q := []*d2graph.Object{obj}
763
764 needsMove := make(map[*d2graph.Object]struct{})
765 seen := make(map[*d2graph.Object]struct{})
766 shifted := make(map[*d2graph.Object]struct{})
767 shiftedEdges := make(map[*d2graph.Edge]struct{})
768 queue := func(o *d2graph.Object) {
769 if _, in := seen[o]; in {
770 return
771 }
772 q = append(q, o)
773 }
774
775
776 threshold := 100.
777 checkBelow := func(curr *d2graph.Object) {
778 currBottom := curr.TopLeft.Y + curr.Height
779 currRight := curr.TopLeft.X + curr.Width
780 if isHorizontal {
781 originalRight := currRight
782 if _, in := shifted[curr]; in {
783 originalRight -= distance
784 }
785 for _, other := range g.Objects {
786 if other == curr || curr.IsDescendantOf(other) {
787 continue
788 }
789 if originalRight < other.TopLeft.X &&
790 other.TopLeft.X < originalRight+distance+threshold &&
791 curr.TopLeft.Y < other.TopLeft.Y+other.Height &&
792 other.TopLeft.Y < currBottom {
793 queue(other)
794 }
795 }
796 } else {
797 originalBottom := currBottom
798 if _, in := shifted[curr]; in {
799 originalBottom -= distance
800 }
801 for _, other := range g.Objects {
802 if other == curr || curr.IsDescendantOf(other) {
803 continue
804 }
805 if originalBottom < other.TopLeft.Y &&
806 other.TopLeft.Y < originalBottom+distance+threshold &&
807 curr.TopLeft.X < other.TopLeft.X+other.Width &&
808 other.TopLeft.X < currRight {
809 queue(other)
810 }
811 }
812 }
813 }
814
815 processQueue := func() {
816 for len(q) > 0 {
817 curr := q[0]
818 q = q[1:]
819 if _, was := seen[curr]; was {
820 continue
821 }
822
823 if curr != obj {
824 if _, in := needsMove[curr]; !in {
825 if isHorizontal {
826 if curr.TopLeft.X < start {
827 continue
828 }
829 } else {
830 if curr.TopLeft.Y < start {
831 continue
832 }
833 }
834 }
835 }
836
837 if isHorizontal {
838 _, shift := needsMove[curr]
839 if !shift {
840 if !isMargin {
841 shift = start < curr.TopLeft.X
842 } else {
843 shift = start <= curr.TopLeft.X
844 }
845 }
846
847 if shift {
848 curr.TopLeft.X += distance
849 shifted[curr] = struct{}{}
850 }
851 } else {
852 _, shift := needsMove[curr]
853 if !shift {
854 if !isMargin {
855 shift = start < curr.TopLeft.Y
856 } else {
857 shift = start <= curr.TopLeft.Y
858 }
859 }
860 if shift {
861 curr.TopLeft.Y += distance
862 shifted[curr] = struct{}{}
863 }
864 }
865 seen[curr] = struct{}{}
866
867 if curr.Parent != g.Root && !curr.IsDescendantOf(obj) {
868 queue(curr.Parent)
869 }
870
871 for _, child := range curr.ChildrenArray {
872 queue(child)
873 }
874
875 for _, e := range g.Edges {
876 if _, in := shiftedEdges[e]; in {
877 continue
878 }
879 if e.Src == curr && e.Dst == curr {
880 if isHorizontal {
881 for _, p := range e.Route {
882 p.X += distance
883 }
884 } else {
885 for _, p := range e.Route {
886 p.Y += distance
887 }
888 }
889 shiftedEdges[e] = struct{}{}
890 continue
891 } else if e.Src == curr {
892 last := e.Route[len(e.Route)-1]
893 if isHorizontal {
894 if start <= last.X &&
895 e.Dst.TopLeft.X+e.Dst.Width < last.X+distance {
896 needsMove[e.Dst] = struct{}{}
897 }
898 } else {
899 if start <= last.Y &&
900 e.Dst.TopLeft.Y+e.Dst.Height < last.Y+distance {
901 needsMove[e.Dst] = struct{}{}
902 }
903 }
904 queue(e.Dst)
905 first := e.Route[0]
906 startIndex := 0
907 _, wasShifted := shifted[curr]
908 if isHorizontal {
909 if wasShifted && first.X < curr.TopLeft.X && first.X < start {
910 first.X += distance
911 startIndex++
912 }
913 for i := startIndex; i < len(e.Route); i++ {
914 p := e.Route[i]
915 if start <= p.X {
916 p.X += distance
917 }
918 }
919 } else {
920 if wasShifted && first.Y < curr.TopLeft.Y && first.Y < start {
921 first.Y += distance
922 startIndex++
923 }
924 for i := startIndex; i < len(e.Route); i++ {
925 p := e.Route[i]
926 if start <= p.Y {
927 p.Y += distance
928 }
929 }
930 }
931 shiftedEdges[e] = struct{}{}
932 } else if e.Dst == curr {
933 first := e.Route[0]
934 if isHorizontal {
935 if start <= first.X &&
936 e.Src.TopLeft.X+e.Src.Width < first.X+distance {
937 needsMove[e.Src] = struct{}{}
938 }
939 } else {
940 if start <= first.Y &&
941 e.Src.TopLeft.Y+e.Src.Height < first.Y+distance {
942 needsMove[e.Src] = struct{}{}
943 }
944 }
945 queue(e.Src)
946 last := e.Route[len(e.Route)-1]
947 endIndex := len(e.Route)
948 _, wasShifted := shifted[curr]
949 if isHorizontal {
950 if wasShifted && last.X < curr.TopLeft.X && last.X < start {
951 last.X += distance
952 endIndex--
953 }
954 for i := 0; i < endIndex; i++ {
955 p := e.Route[i]
956 if start <= p.X {
957 p.X += distance
958 }
959 }
960 } else {
961 if wasShifted && last.Y < curr.TopLeft.Y && last.Y < start {
962 last.Y += distance
963 endIndex--
964 }
965 for i := 0; i < endIndex; i++ {
966 p := e.Route[i]
967 if start <= p.Y {
968 p.Y += distance
969 }
970 }
971 }
972 shiftedEdges[e] = struct{}{}
973 }
974 }
975
976
977 checkBelow(curr)
978 }
979 }
980
981 processQueue()
982
983 grown := make(map[*d2graph.Object]struct{})
984 for o := range seen {
985 if o.Parent == g.Root {
986 continue
987 }
988 if _, in := shifted[o.Parent]; in {
989 continue
990 }
991 if _, in := grown[o.Parent]; in {
992 continue
993 }
994
995 for parent := o.Parent; parent != g.Root; parent = parent.Parent {
996 if _, in := shifted[parent]; in {
997 break
998 }
999 if _, in := grown[parent]; in {
1000 break
1001 }
1002
1003 if isHorizontal {
1004 if parent.TopLeft.X < start {
1005 parent.Width += distance
1006 grown[parent] = struct{}{}
1007
1008 checkBelow(parent)
1009 processQueue()
1010 }
1011 } else {
1012 if parent.TopLeft.Y < start {
1013 parent.Height += distance
1014 grown[parent] = struct{}{}
1015
1016 checkBelow(parent)
1017 processQueue()
1018 }
1019 }
1020 }
1021 }
1022
1023 increasedMargins := make(map[*d2graph.Object]struct{})
1024 movedObjects := make([]*d2graph.Object, 0, len(shifted))
1025 for obj := range shifted {
1026 movedObjects = append(movedObjects, obj)
1027 }
1028 for obj := range grown {
1029 movedObjects = append(movedObjects, obj)
1030 }
1031 for _, moved := range movedObjects {
1032 counts := true
1033
1034 for _, other := range movedObjects {
1035 if other == moved {
1036 continue
1037 }
1038 if isHorizontal {
1039 if other.TopLeft.Y+other.Height < moved.TopLeft.Y ||
1040 moved.TopLeft.Y+moved.Height < other.TopLeft.Y {
1041
1042 continue
1043 }
1044
1045
1046 if other.TopLeft.X < moved.TopLeft.X &&
1047 moved.TopLeft.X < other.TopLeft.X+other.Width+threshold {
1048 counts = false
1049 break
1050 }
1051 } else {
1052 if other.TopLeft.X+other.Width < moved.TopLeft.X ||
1053 moved.TopLeft.X+moved.Width < other.TopLeft.X {
1054
1055 continue
1056 }
1057
1058
1059 if other.TopLeft.Y < moved.TopLeft.Y &&
1060 moved.TopLeft.Y < other.TopLeft.Y+other.Height+threshold {
1061 counts = false
1062 break
1063 }
1064 }
1065 }
1066 if counts {
1067 increasedMargins[moved] = struct{}{}
1068 }
1069 }
1070
1071 return increasedMargins
1072 }
1073
1074 func adjustRankSpacing(g *d2graph.Graph, rankSep float64, isHorizontal bool) {
1075 ranks, objectRanks, startingParentRanks, endingParentRanks := getRanks(g, isHorizontal)
1076
1077
1078 for rank := len(ranks) - 1; rank >= 0; rank-- {
1079 var startingParents []*d2graph.Object
1080 var endingParents []*d2graph.Object
1081 for _, obj := range ranks[rank] {
1082 if obj.Parent == g.Root {
1083 continue
1084 }
1085 if r, has := endingParentRanks[obj.Parent]; has && r == rank {
1086 endingParents = append(endingParents, obj.Parent)
1087 }
1088 if r, has := startingParentRanks[obj.Parent]; has && r == rank {
1089 startingParents = append(startingParents, obj.Parent)
1090 }
1091 }
1092
1093 startingAncestorPositions := make(map[*d2graph.Object]float64)
1094 for len(startingParents) > 0 {
1095 var ancestors []*d2graph.Object
1096 for _, parent := range startingParents {
1097 _, padding := parent.Spacing()
1098 if _, has := startingAncestorPositions[parent]; !has {
1099 startingAncestorPositions[parent] = math.Inf(1)
1100 }
1101 var startPosition float64
1102 if isHorizontal {
1103 paddingIncrease := math.Max(0, padding.Left-rankSep/2)
1104 startPosition = parent.TopLeft.X - paddingIncrease
1105 } else {
1106 paddingIncrease := math.Max(0, padding.Top-rankSep/2)
1107 startPosition = parent.TopLeft.Y - paddingIncrease
1108 }
1109 startingAncestorPositions[parent] = math.Min(startingAncestorPositions[parent], startPosition)
1110 for _, child := range parent.ChildrenArray {
1111 if r, has := objectRanks[child]; has {
1112 if r != rank {
1113 continue
1114 }
1115 } else {
1116 if startingParentRanks[child] != rank {
1117 continue
1118 }
1119 }
1120 margin, _ := child.Spacing()
1121 if isHorizontal {
1122 startPosition = child.TopLeft.X - margin.Left - padding.Left
1123 } else {
1124 startPosition = child.TopLeft.Y - margin.Top - padding.Top
1125 }
1126 startingAncestorPositions[parent] = math.Min(startingAncestorPositions[parent], startPosition)
1127 }
1128 if parent.Parent != g.Root {
1129 ancestors = append(ancestors, parent.Parent)
1130 }
1131 }
1132 startingParents = ancestors
1133 }
1134
1135 endingAncestorPositions := make(map[*d2graph.Object]float64)
1136 for len(endingParents) > 0 {
1137 var ancestors []*d2graph.Object
1138 for _, parent := range endingParents {
1139 _, padding := parent.Spacing()
1140 if _, has := endingAncestorPositions[parent]; !has {
1141 endingAncestorPositions[parent] = math.Inf(-1)
1142 }
1143 var endPosition float64
1144 if isHorizontal {
1145 endPosition = parent.TopLeft.X + parent.Width + padding.Right - rankSep/2.
1146 } else {
1147 endPosition = parent.TopLeft.Y + parent.Height + padding.Bottom - rankSep/2.
1148 }
1149
1150 endingAncestorPositions[parent] = math.Max(endingAncestorPositions[parent], endPosition)
1151 for _, child := range parent.ChildrenArray {
1152 if r, has := objectRanks[child]; has {
1153 if r != rank {
1154 continue
1155 }
1156 } else {
1157 if endingParentRanks[child] != rank {
1158 continue
1159 }
1160 }
1161 margin, _ := child.Spacing()
1162
1163 if isHorizontal {
1164 endPosition = child.TopLeft.X + child.Width + margin.Right + padding.Right
1165 } else {
1166 endPosition = child.TopLeft.Y + child.Height + margin.Bottom + padding.Bottom
1167 }
1168 endingAncestorPositions[parent] = math.Max(endingAncestorPositions[parent], endPosition)
1169 }
1170 if parent.Parent != g.Root {
1171 ancestors = append(ancestors, parent.Parent)
1172 }
1173 }
1174 endingParents = ancestors
1175 }
1176
1177 startingAdjustmentOrder := make([]*d2graph.Object, 0, len(startingAncestorPositions))
1178 for ancestor := range startingAncestorPositions {
1179 startingAdjustmentOrder = append(startingAdjustmentOrder, ancestor)
1180 }
1181
1182 sort.Slice(startingAdjustmentOrder, func(i, j int) bool {
1183 iPos := startingAncestorPositions[startingAdjustmentOrder[i]]
1184 jPos := startingAncestorPositions[startingAdjustmentOrder[j]]
1185 return iPos < jPos
1186 })
1187
1188 endingAdjustmentOrder := make([]*d2graph.Object, 0, len(endingAncestorPositions))
1189 for ancestor := range endingAncestorPositions {
1190 endingAdjustmentOrder = append(endingAdjustmentOrder, ancestor)
1191 }
1192
1193
1194 sort.Slice(endingAdjustmentOrder, func(i, j int) bool {
1195 iPos := endingAncestorPositions[endingAdjustmentOrder[i]]
1196 jPos := endingAncestorPositions[endingAdjustmentOrder[j]]
1197 return jPos < iPos
1198 })
1199
1200 for _, ancestor := range endingAdjustmentOrder {
1201 var position float64
1202 if isHorizontal {
1203 position = ancestor.TopLeft.X + ancestor.Width
1204 } else {
1205 position = ancestor.TopLeft.Y + ancestor.Height
1206 }
1207 endDelta := endingAncestorPositions[ancestor] - position
1208 if endDelta > 0 {
1209 for _, obj := range g.Objects {
1210 if !obj.IsContainer() {
1211 continue
1212 }
1213 start := startingParentRanks[obj]
1214 end := endingParentRanks[obj]
1215 if start <= rank && rank <= end {
1216 if isHorizontal && position <= obj.TopLeft.X+obj.Width {
1217 obj.Width += endDelta
1218 } else if !isHorizontal &&
1219 position <= obj.TopLeft.Y+obj.Height {
1220 obj.Height += endDelta
1221 }
1222 }
1223 }
1224 shiftDown(g, position, endDelta, isHorizontal)
1225 }
1226 }
1227
1228 for _, ancestor := range startingAdjustmentOrder {
1229 var position float64
1230 if isHorizontal {
1231 position = ancestor.TopLeft.X
1232 } else {
1233 position = ancestor.TopLeft.Y
1234 }
1235 startDelta := position - startingAncestorPositions[ancestor]
1236 if startDelta > 0 {
1237 for _, obj := range g.Objects {
1238 if !obj.IsContainer() {
1239 continue
1240 }
1241 start := startingParentRanks[obj]
1242 end := endingParentRanks[obj]
1243 if start <= rank && rank <= end {
1244 if isHorizontal && obj.TopLeft.X <= position {
1245 obj.Width += startDelta
1246 } else if !isHorizontal && obj.TopLeft.Y <= position {
1247 obj.Height += startDelta
1248 }
1249 }
1250 }
1251 shiftUp(g, position, startDelta, isHorizontal)
1252 }
1253 }
1254 }
1255 }
1256
1257 func adjustCrossRankSpacing(g *d2graph.Graph, rankSep float64, isHorizontal bool) {
1258 var prevMarginTop, prevMarginBottom, prevMarginLeft, prevMarginRight map[*d2graph.Object]float64
1259 if isHorizontal {
1260 prevMarginLeft = make(map[*d2graph.Object]float64)
1261 prevMarginRight = make(map[*d2graph.Object]float64)
1262 } else {
1263 prevMarginTop = make(map[*d2graph.Object]float64)
1264 prevMarginBottom = make(map[*d2graph.Object]float64)
1265 }
1266 for _, obj := range g.Objects {
1267 if obj.IsGridDiagram() {
1268 continue
1269 }
1270 margin, padding := obj.Spacing()
1271 if !isHorizontal {
1272 if prevShift, has := prevMarginBottom[obj]; has {
1273 margin.Bottom -= prevShift
1274 }
1275 if margin.Bottom > 0 {
1276 increased := shiftReachableDown(g, obj, obj.TopLeft.Y+obj.Height, margin.Bottom, isHorizontal, true)
1277 for o := range increased {
1278 prevMarginBottom[o] = math.Max(prevMarginBottom[o], margin.Bottom)
1279 }
1280 }
1281 if padding.Bottom > 0 {
1282 shiftReachableDown(g, obj, obj.TopLeft.Y+obj.Height, padding.Bottom, isHorizontal, false)
1283 obj.Height += padding.Bottom
1284 }
1285 if prevShift, has := prevMarginTop[obj]; has {
1286 margin.Top -= prevShift
1287 }
1288 if margin.Top > 0 {
1289 increased := shiftReachableDown(g, obj, obj.TopLeft.Y, margin.Top, isHorizontal, true)
1290 for o := range increased {
1291 prevMarginTop[o] = math.Max(prevMarginTop[o], margin.Top)
1292 }
1293 }
1294 if padding.Top > 0 {
1295 shiftReachableDown(g, obj, obj.TopLeft.Y, padding.Top, isHorizontal, false)
1296 obj.Height += padding.Top
1297 }
1298 } else {
1299 if prevShift, has := prevMarginRight[obj]; has {
1300 margin.Right -= prevShift
1301 }
1302 if margin.Right > 0 {
1303 increased := shiftReachableDown(g, obj, obj.TopLeft.X+obj.Width, margin.Right, isHorizontal, true)
1304 for o := range increased {
1305 prevMarginRight[o] = math.Max(prevMarginRight[o], margin.Right)
1306 }
1307 }
1308 if padding.Right > 0 {
1309 shiftReachableDown(g, obj, obj.TopLeft.X+obj.Width, padding.Right, isHorizontal, false)
1310 obj.Width += padding.Right
1311 }
1312 if prevShift, has := prevMarginLeft[obj]; has {
1313 margin.Left -= prevShift
1314 }
1315 if margin.Left > 0 {
1316 increased := shiftReachableDown(g, obj, obj.TopLeft.X, margin.Left, isHorizontal, true)
1317 for o := range increased {
1318 prevMarginLeft[o] = math.Max(prevMarginLeft[o], margin.Left)
1319 }
1320 }
1321 if padding.Left > 0 {
1322 shiftReachableDown(g, obj, obj.TopLeft.X, padding.Left, isHorizontal, false)
1323 obj.Width += padding.Left
1324 }
1325 }
1326 }
1327 }
1328
1329 func fitContainerPadding(g *d2graph.Graph, rankSep float64, isHorizontal bool) {
1330 for _, obj := range g.Root.ChildrenArray {
1331 fitPadding(obj)
1332 }
1333 }
1334
1335 func fitPadding(obj *d2graph.Object) {
1336 dslShape := strings.ToLower(obj.Shape.Value)
1337 shapeType := d2target.DSL_SHAPE_TO_SHAPE_TYPE[dslShape]
1338
1339 if !obj.IsContainer() || shapeType != shape.SQUARE_TYPE {
1340 return
1341 }
1342 for _, child := range obj.ChildrenArray {
1343 fitPadding(child)
1344 }
1345
1346
1347
1348 _, padding := obj.Spacing()
1349 padding.Top = math.Max(padding.Top, DEFAULT_PADDING)
1350 padding.Bottom = math.Max(padding.Bottom, DEFAULT_PADDING)
1351 padding.Left = math.Max(padding.Left, DEFAULT_PADDING)
1352 padding.Right = math.Max(padding.Right, DEFAULT_PADDING)
1353
1354
1355 currentTop := obj.TopLeft.Y
1356 currentBottom := obj.TopLeft.Y + obj.Height
1357 currentLeft := obj.TopLeft.X
1358 currentRight := obj.TopLeft.X + obj.Width
1359
1360 innerTop := math.Inf(1)
1361 innerBottom := math.Inf(-1)
1362 innerLeft := math.Inf(1)
1363 innerRight := math.Inf(-1)
1364
1365
1366 var labelPosition, iconPosition label.Position
1367 var labelBox, iconBox *geo.Box
1368 if obj.HasLabel() && obj.LabelPosition != nil {
1369 labelPosition = label.FromString(*obj.LabelPosition)
1370 switch labelPosition {
1371 case label.InsideTopLeft, label.InsideTopCenter, label.InsideTopRight,
1372 label.InsideBottomLeft, label.InsideBottomCenter, label.InsideBottomRight,
1373 label.InsideMiddleLeft, label.InsideMiddleRight:
1374 labelTL := obj.GetLabelTopLeft()
1375 if labelTL != nil {
1376 labelBox = geo.NewBox(labelTL, float64(obj.LabelDimensions.Width)+2*label.PADDING, float64(obj.LabelDimensions.Height))
1377 }
1378 }
1379 }
1380 if obj.HasIcon() && obj.IconPosition != nil {
1381 iconPosition = label.FromString(*obj.IconPosition)
1382 switch iconPosition {
1383 case label.InsideTopLeft, label.InsideTopCenter, label.InsideTopRight,
1384 label.InsideBottomLeft, label.InsideBottomCenter, label.InsideBottomRight,
1385 label.InsideMiddleLeft, label.InsideMiddleRight:
1386 iconTL := obj.GetIconTopLeft()
1387 if iconTL != nil {
1388 iconBox = geo.NewBox(iconTL, d2target.MAX_ICON_SIZE, d2target.MAX_ICON_SIZE)
1389 }
1390 }
1391 }
1392
1393
1394 var innerBoxes []geo.Box
1395 for _, child := range obj.ChildrenArray {
1396 margin, _ := child.Spacing()
1397 dx, dy := child.GetModifierElementAdjustments()
1398
1399 if labelBox != nil || iconBox != nil {
1400 var childLabelBox *geo.Box
1401 var childLabelPosition, childIconPosition label.Position
1402 if child.HasLabel() && child.LabelPosition != nil {
1403 childLabelPosition = label.FromString(*child.LabelPosition)
1404 if childLabelPosition.IsOutside() {
1405 childLabelTL := child.GetLabelTopLeft()
1406
1407 childLabelBox = geo.NewBox(
1408 childLabelTL,
1409 float64(child.LabelDimensions.Width),
1410 float64(child.LabelDimensions.Height),
1411 )
1412 innerBoxes = append(innerBoxes, *childLabelBox)
1413 }
1414 }
1415 if child.HasIcon() && child.IconPosition != nil {
1416 childIconPosition = label.FromString(*child.IconPosition)
1417 if childIconPosition.IsOutside() {
1418 childIconTL := child.GetIconTopLeft()
1419
1420 childIconBox := geo.NewBox(childIconTL, d2target.MAX_ICON_SIZE, d2target.MAX_ICON_SIZE)
1421 innerBoxes = append(innerBoxes, *childIconBox)
1422 }
1423 }
1424 }
1425
1426 innerTop = math.Min(innerTop, child.TopLeft.Y-dy-math.Max(margin.Top, padding.Top))
1427 innerBottom = math.Max(innerBottom, child.TopLeft.Y+child.Height+math.Max(margin.Bottom, padding.Bottom))
1428 innerLeft = math.Min(innerLeft, child.TopLeft.X-math.Max(margin.Left, padding.Left))
1429 innerRight = math.Max(innerRight, child.TopLeft.X+child.Width+dx+math.Max(margin.Right, padding.Right))
1430 }
1431
1432
1433 for _, edge := range obj.Graph.Edges {
1434 if !edge.Src.IsDescendantOf(obj) || !edge.Dst.IsDescendantOf(obj) {
1435 continue
1436 }
1437
1438 if edge.Label.Value != "" {
1439 labelPosition := label.InsideMiddleCenter
1440 if edge.LabelPosition != nil {
1441 labelPosition = label.FromString(*edge.LabelPosition)
1442 }
1443 labelWidth := float64(edge.LabelDimensions.Width)
1444 labelHeight := float64(edge.LabelDimensions.Height)
1445 point, _ := labelPosition.GetPointOnRoute(edge.Route, 2, 0, labelWidth, labelHeight)
1446
1447 if labelBox != nil || iconBox != nil {
1448 innerBoxes = append(innerBoxes, geo.Box{TopLeft: point, Width: labelWidth, Height: labelHeight})
1449 }
1450
1451 innerTop = math.Min(innerTop, point.Y-padding.Top)
1452 innerBottom = math.Max(innerBottom, point.Y+labelHeight+padding.Bottom)
1453 innerLeft = math.Min(innerLeft, point.X-padding.Left)
1454 innerRight = math.Max(innerRight, point.X+labelWidth+padding.Right)
1455 }
1456 for _, point := range edge.Route {
1457 innerTop = math.Min(innerTop, point.Y-padding.Top)
1458 innerBottom = math.Max(innerBottom, point.Y+padding.Bottom)
1459 innerLeft = math.Min(innerLeft, point.X-padding.Left)
1460 innerRight = math.Max(innerRight, point.X+padding.Right)
1461 }
1462 }
1463
1464
1465 topDelta := innerTop - currentTop
1466 bottomDelta := currentBottom - innerBottom
1467 leftDelta := innerLeft - currentLeft
1468 rightDelta := currentRight - innerRight
1469
1470 if topDelta > 0 || bottomDelta > 0 || leftDelta > 0 || rightDelta > 0 {
1471 var leftOverlap, rightOverlap, topOverlap, bottomOverlap float64
1472 var labelSide, iconSide geo.Orientation
1473 if labelBox != nil {
1474 switch labelPosition {
1475 case label.InsideTopLeft, label.InsideTopCenter, label.InsideTopRight:
1476 labelSide = geo.Top
1477 case label.InsideBottomLeft, label.InsideBottomCenter, label.InsideBottomRight:
1478 labelSide = geo.Bottom
1479 case label.InsideMiddleLeft:
1480 labelSide = geo.Left
1481 case label.InsideMiddleRight:
1482 labelSide = geo.Right
1483 default:
1484 labelSide = geo.NONE
1485 }
1486
1487 switch labelSide {
1488 case geo.Top:
1489 if topDelta > 0 {
1490 labelBox.TopLeft.Y += topDelta
1491 }
1492 case geo.Bottom:
1493 if bottomDelta > 0 {
1494 labelBox.TopLeft.Y -= bottomDelta
1495 }
1496 case geo.Left:
1497 if leftDelta > 0 {
1498 labelBox.TopLeft.X += leftDelta
1499 }
1500 case geo.Right:
1501 if rightDelta > 0 {
1502 labelBox.TopLeft.X -= rightDelta
1503 }
1504 }
1505 switch labelSide {
1506 case geo.Top:
1507 if topDelta > 0 {
1508 for _, box := range innerBoxes {
1509 if labelBox.Overlaps(box) {
1510 dy := labelBox.TopLeft.Y + labelBox.Height - box.TopLeft.Y
1511 topOverlap = go2.Max(topOverlap, dy)
1512 }
1513 }
1514 }
1515 case geo.Bottom:
1516 if bottomDelta > 0 {
1517 for _, box := range innerBoxes {
1518 if labelBox.Overlaps(box) {
1519 dy := box.TopLeft.Y + box.Height - labelBox.TopLeft.Y
1520 bottomOverlap = go2.Max(bottomOverlap, dy)
1521 }
1522 }
1523 }
1524 case geo.Left:
1525 if leftDelta > 0 {
1526 for _, box := range innerBoxes {
1527 if labelBox.Overlaps(box) {
1528 dx := labelBox.TopLeft.X + labelBox.Width - box.TopLeft.X
1529 leftOverlap = go2.Max(leftOverlap, dx)
1530 }
1531 }
1532 }
1533 case geo.Right:
1534 if rightDelta > 0 {
1535 for _, box := range innerBoxes {
1536 if labelBox.Overlaps(box) {
1537 dx := box.TopLeft.X + box.Width - labelBox.TopLeft.X
1538 rightOverlap = go2.Max(rightOverlap, dx)
1539 }
1540 }
1541 }
1542 }
1543 }
1544 if iconBox != nil {
1545 switch iconPosition {
1546 case label.InsideTopLeft, label.InsideTopCenter, label.InsideTopRight:
1547 iconSide = geo.Top
1548 case label.InsideBottomLeft, label.InsideBottomCenter, label.InsideBottomRight:
1549 iconSide = geo.Bottom
1550 case label.InsideMiddleLeft:
1551 iconSide = geo.Left
1552 case label.InsideMiddleRight:
1553 iconSide = geo.Right
1554 default:
1555 iconSide = geo.NONE
1556 }
1557
1558 switch iconSide {
1559 case geo.Top:
1560 if topDelta > 0 {
1561 iconBox.TopLeft.Y += topDelta
1562 }
1563 case geo.Bottom:
1564 if bottomDelta > 0 {
1565 iconBox.TopLeft.Y -= bottomDelta
1566 }
1567 case geo.Left:
1568 if leftDelta > 0 {
1569 iconBox.TopLeft.X += leftDelta
1570 }
1571 case geo.Right:
1572 if rightDelta > 0 {
1573 iconBox.TopLeft.X -= rightDelta
1574 }
1575 }
1576 switch iconSide {
1577 case geo.Top:
1578 if topDelta > 0 {
1579 for _, box := range innerBoxes {
1580 if iconBox.Overlaps(box) {
1581 dy := iconBox.TopLeft.Y + iconBox.Height - box.TopLeft.Y
1582 topOverlap = go2.Max(topOverlap, dy)
1583 }
1584 }
1585 }
1586 case geo.Bottom:
1587 if bottomDelta > 0 {
1588 for _, box := range innerBoxes {
1589 if iconBox.Overlaps(box) {
1590 dy := box.TopLeft.Y + box.Height - iconBox.TopLeft.Y
1591 bottomOverlap = go2.Max(bottomOverlap, dy)
1592 }
1593 }
1594 }
1595 case geo.Left:
1596 if leftDelta > 0 {
1597 for _, box := range innerBoxes {
1598 if iconBox.Overlaps(box) {
1599 dx := iconBox.TopLeft.X + iconBox.Width - box.TopLeft.X
1600 leftOverlap = go2.Max(leftOverlap, dx)
1601 }
1602 }
1603 }
1604 case geo.Right:
1605 if rightDelta > 0 {
1606 for _, box := range innerBoxes {
1607 if iconBox.Overlaps(box) {
1608 dx := box.TopLeft.X + box.Width - iconBox.TopLeft.X
1609 rightOverlap = go2.Max(rightOverlap, dx)
1610 }
1611 }
1612 }
1613 }
1614 }
1615
1616 if leftOverlap > 0 {
1617 leftDelta -= leftOverlap + MIN_SPACING
1618 }
1619 if rightOverlap > 0 {
1620 rightDelta -= rightOverlap + MIN_SPACING
1621 }
1622 if topOverlap > 0 {
1623 topDelta -= topOverlap + MIN_SPACING
1624 }
1625 if bottomOverlap > 0 {
1626 bottomDelta -= bottomOverlap + MIN_SPACING
1627 }
1628 }
1629
1630 if 0 < topDelta {
1631 topDelta = adjustDeltaForEdges(obj, currentTop, topDelta, false)
1632 if 0 < topDelta {
1633 adjustEdges(obj, currentTop, topDelta, false)
1634 obj.TopLeft.Y += topDelta
1635 obj.Height -= topDelta
1636 }
1637 }
1638 if 0 < bottomDelta {
1639 bottomDelta = adjustDeltaForEdges(obj, currentBottom, -bottomDelta, false)
1640 if 0 < bottomDelta {
1641 adjustEdges(obj, currentBottom, -bottomDelta, false)
1642 obj.Height -= bottomDelta
1643 }
1644 }
1645 if 0 < leftDelta {
1646 leftDelta = adjustDeltaForEdges(obj, currentLeft, leftDelta, true)
1647 if 0 < leftDelta {
1648 adjustEdges(obj, currentLeft, leftDelta, true)
1649 obj.TopLeft.X += leftDelta
1650 obj.Width -= leftDelta
1651 }
1652 }
1653 if 0 < rightDelta {
1654 rightDelta = adjustDeltaForEdges(obj, currentRight, -rightDelta, true)
1655 if 0 < rightDelta {
1656 adjustEdges(obj, currentRight, -rightDelta, true)
1657 obj.Width -= rightDelta
1658 }
1659 }
1660 }
1661
1662 func adjustDeltaForEdges(obj *d2graph.Object, objPosition, delta float64, isHorizontal bool) (newMagnitude float64) {
1663 isOnCollapsingSide := func(p *geo.Point) bool {
1664 var position float64
1665 if isHorizontal {
1666 position = p.X
1667 } else {
1668 position = p.Y
1669 }
1670 if geo.PrecisionCompare(position, objPosition, 1) == 0 {
1671 return false
1672 }
1673
1674 var isOnSide bool
1675 if isHorizontal {
1676 if geo.PrecisionCompare(p.Y, obj.TopLeft.Y, 1) == 0 ||
1677 geo.PrecisionCompare(p.Y, obj.TopLeft.Y+obj.Height, 1) == 0 {
1678 isOnSide = true
1679 }
1680 } else {
1681 if geo.PrecisionCompare(p.X, obj.TopLeft.X, 1) == 0 ||
1682 geo.PrecisionCompare(p.X, obj.TopLeft.X+obj.Width, 1) == 0 {
1683 isOnSide = true
1684 }
1685 }
1686 if !isOnSide {
1687 return false
1688 }
1689 buffer := MIN_SPACING
1690 var isInRange bool
1691 if delta > 0 {
1692 if objPosition <= position && position <= objPosition+delta+buffer {
1693 isInRange = true
1694 }
1695 } else {
1696 if objPosition+delta-buffer <= position && position <= objPosition {
1697 isInRange = true
1698 }
1699 }
1700 return isInRange
1701 }
1702 hasEdgeOnCollapsingSide := false
1703 outermost := objPosition + delta
1704 for _, edge := range obj.Graph.Edges {
1705 if edge.Src == obj {
1706 p := edge.Route[0]
1707 if isOnCollapsingSide(p) {
1708 hasEdgeOnCollapsingSide = true
1709 var position float64
1710 if isHorizontal {
1711 position = p.X
1712 } else {
1713 position = p.Y
1714 }
1715 if delta < 0 {
1716 outermost = math.Max(outermost, position)
1717 } else {
1718 outermost = math.Min(outermost, position)
1719 }
1720 }
1721 }
1722 if edge.Dst == obj {
1723 p := edge.Route[len(edge.Route)-1]
1724 if isOnCollapsingSide(p) {
1725 hasEdgeOnCollapsingSide = true
1726 var position float64
1727 if isHorizontal {
1728 position = p.X
1729 } else {
1730 position = p.Y
1731 }
1732 if delta < 0 {
1733 outermost = math.Max(outermost, position)
1734 } else {
1735 outermost = math.Min(outermost, position)
1736 }
1737 }
1738 }
1739 }
1740 newMagnitude = math.Abs(delta)
1741 if hasEdgeOnCollapsingSide {
1742
1743 if delta < 0 {
1744 newMagnitude = math.Max(0, objPosition-(outermost+DEFAULT_PADDING))
1745 } else {
1746 newMagnitude = math.Max(0, (outermost-DEFAULT_PADDING)-objPosition)
1747 }
1748 }
1749 return newMagnitude
1750 }
1751
1752 func adjustEdges(obj *d2graph.Object, objPosition, delta float64, isHorizontal bool) {
1753 adjust := func(p *geo.Point) {
1754 var position float64
1755 if isHorizontal {
1756 position = p.X
1757 } else {
1758 position = p.Y
1759 }
1760 if geo.PrecisionCompare(position, objPosition, 1) == 0 {
1761 if isHorizontal {
1762 p.X += delta
1763 } else {
1764 p.Y += delta
1765 }
1766 } else {
1767
1768 var isOnSide bool
1769 if isHorizontal {
1770 if geo.PrecisionCompare(p.Y, obj.TopLeft.Y, 1) == 0 ||
1771 geo.PrecisionCompare(p.Y, obj.TopLeft.Y+obj.Height, 1) == 0 {
1772 isOnSide = true
1773 }
1774 } else {
1775 if geo.PrecisionCompare(p.X, obj.TopLeft.X, 1) == 0 ||
1776 geo.PrecisionCompare(p.X, obj.TopLeft.X+obj.Width, 1) == 0 {
1777 isOnSide = true
1778 }
1779 }
1780 if isOnSide {
1781 var isInRange bool
1782 if delta > 0 {
1783 if objPosition < position && position < objPosition+delta {
1784 isInRange = true
1785 }
1786 } else {
1787 if objPosition+delta < position && position < objPosition {
1788 isInRange = true
1789 }
1790 }
1791 if isInRange {
1792 if isHorizontal {
1793 p.X = objPosition + delta
1794 } else {
1795 p.Y = objPosition + delta
1796 }
1797 }
1798 }
1799 }
1800 }
1801
1802 for _, edge := range obj.Graph.Edges {
1803 if edge.Src == obj {
1804 adjust(edge.Route[0])
1805 }
1806 if edge.Dst == obj {
1807 adjust(edge.Route[len(edge.Route)-1])
1808 }
1809 }
1810 }
1811
View as plain text