package placement
import (
"context"
"math"
"github.com/pancsta/d2/d2layouts/d2talalayout/internal/layoutgraph"
"github.com/pancsta/d2/d2layouts/d2talalayout/internal/placementcost"
"github.com/pancsta/d2/lib/geo"
)
func Dejitter (ctx context .Context , g *layoutgraph .Graph ) (bool , error ) {
ctx , guard , err := layoutgraph .EnsureTransactionWorkGuard (ctx , "DejitterTransactions" )
if err != nil {
return false , err
}
dejittered := false
txn , err := g .NewRequestTransaction (ctx , layoutgraph .TransactionOptions {AffectContainers : true })
if err != nil {
return false , err
}
if len (g .Nodes ) == 0 {
return false , nil
}
var rollback *layoutgraph .Transaction
complete := false
defer func () {
if rollback != nil && !complete {
rollback .Rollback ()
}
}()
for _ , node := range g .Nodes {
if node .Cluster != nil {
continue
}
if node .IsContainer () {
continue
}
if _ , has := g .NodeToTree [node ]; has {
continue
}
if node .Sequence != nil {
continue
}
if node .FixedTopLeft != nil {
continue
}
prevSymmetry , err := placementcost .NodeSymmetry (ctx , node , nil )
if err != nil {
return false , err
}
for _ , edge := range node .Edges {
if edge .NumSegments () < 3 {
continue
}
if node .Adjacent (edge ).Cluster != nil {
continue
}
var pointBeforeBends *geo .Point
var pointOnNode *geo .Point
lastTwoBends := make ([]*geo .Point , 0 )
if edge .From == node {
lastTwoBends = append (lastTwoBends , edge .Points [1 ], edge .Points [2 ])
pointBeforeBends = edge .Points [3 ]
pointOnNode = edge .Points [0 ]
} else if edge .To == node {
lastTwoBends = append (lastTwoBends , edge .Points [len (edge .Points )-2 ], edge .Points [len (edge .Points )-3 ])
pointBeforeBends = edge .Points [len (edge .Points )-4 ]
pointOnNode = edge .Points [len (edge .Points )-1 ]
}
if geo .EuclideanDistance (lastTwoBends [0 ].X , lastTwoBends [0 ].Y , lastTwoBends [1 ].X , lastTwoBends [1 ].Y ) > jitterThreshold {
continue
}
isVerticalLine := lastTwoBends [0 ].Y == lastTwoBends [1 ].Y
if isVerticalLine {
if (pointBeforeBends .Y > lastTwoBends [0 ].Y ) && (pointOnNode .Y > lastTwoBends [0 ].Y ) {
continue
}
if (pointBeforeBends .Y < lastTwoBends [0 ].Y ) && (pointOnNode .Y < lastTwoBends [0 ].Y ) {
continue
}
} else {
if (pointBeforeBends .X > lastTwoBends [0 ].X ) && (pointOnNode .X > lastTwoBends [0 ].X ) {
continue
}
if (pointBeforeBends .X < lastTwoBends [0 ].X ) && (pointOnNode .X < lastTwoBends [0 ].X ) {
continue
}
}
if isVerticalLine {
if lastTwoBends [1 ].X == lastTwoBends [0 ].X {
continue
}
} else {
if lastTwoBends [1 ].Y == lastTwoBends [0 ].Y {
continue
}
}
hasAnotherConnectedStraightEdge := false
for _ , otherEdge := range node .Edges {
if otherEdge == edge {
continue
}
if otherEdge .NumSegments () > 1 {
continue
}
if otherEdge .From == node {
if isVerticalLine && (otherEdge .Points [0 ].X == otherEdge .Points [1 ].X ) {
hasAnotherConnectedStraightEdge = true
break
}
if !isVerticalLine && (otherEdge .Points [0 ].Y == otherEdge .Points [1 ].Y ) {
hasAnotherConnectedStraightEdge = true
break
}
} else {
if isVerticalLine && (otherEdge .Points [len (otherEdge .Points )-1 ].X == otherEdge .Points [len (otherEdge .Points )-2 ].X ) {
hasAnotherConnectedStraightEdge = true
break
}
if !isVerticalLine && (otherEdge .Points [len (otherEdge .Points )-1 ].Y == otherEdge .Points [len (otherEdge .Points )-2 ].Y ) {
hasAnotherConnectedStraightEdge = true
break
}
}
}
if hasAnotherConnectedStraightEdge {
continue
}
var delta float64
if isVerticalLine {
delta = math .Round (lastTwoBends [1 ].X - lastTwoBends [0 ].X )
} else {
delta = math .Round (lastTwoBends [1 ].Y - lastTwoBends [0 ].Y )
}
var signFlipDelta float64
if delta < 0 {
signFlipDelta = delta - signFlipPadding
} else {
signFlipDelta = delta + signFlipPadding
}
signFlip := false
newSegments := make (map [*layoutgraph .Edge ][]*geo .Point )
for _ , otherEdge := range node .Edges {
var isOtherEdgeVertical bool
if otherEdge .From == node {
isOtherEdgeVertical = otherEdge .Points [0 ].X == otherEdge .Points [1 ].X
} else {
isOtherEdgeVertical = otherEdge .Points [len (otherEdge .Points )-1 ].X == otherEdge .Points [len (otherEdge .Points )-2 ].X
}
isOtherEdgeDiagonal := (len (otherEdge .Points ) == 2 ) && (otherEdge .Points [0 ].X != otherEdge .Points [1 ].X ) && (otherEdge .Points [0 ].Y != otherEdge .Points [1 ].Y )
if isOtherEdgeDiagonal {
continue
}
if isVerticalLine && isOtherEdgeVertical {
var second int
var third int
if otherEdge .From == node {
second = 1
third = 2
newSegments [otherEdge ] = []*geo .Point {
geo .NewPoint (otherEdge .Points [0 ].X +delta , otherEdge .Points [0 ].Y ),
geo .NewPoint (otherEdge .Points [1 ].X +delta , otherEdge .Points [1 ].Y ),
}
} else {
second = len (otherEdge .Points ) - 2
third = len (otherEdge .Points ) - 3
newSegments [otherEdge ] = []*geo .Point {
geo .NewPoint (otherEdge .Points [len (otherEdge .Points )-1 ].X +delta , otherEdge .Points [len (otherEdge .Points )-1 ].Y ),
geo .NewPoint (otherEdge .Points [len (otherEdge .Points )-2 ].X +delta , otherEdge .Points [len (otherEdge .Points )-2 ].Y ),
}
}
if otherEdge != edge {
if ((otherEdge .Points [second ].X +signFlipDelta < otherEdge .Points [third ].X ) && (otherEdge .Points [second ].X > otherEdge .Points [third ].X )) ||
((otherEdge .Points [second ].X +signFlipDelta > otherEdge .Points [third ].X ) && (otherEdge .Points [second ].X < otherEdge .Points [third ].X )) {
signFlip = true
}
}
}
if isVerticalLine && !isOtherEdgeVertical {
var first int
var second int
if otherEdge .From == node {
first = 0
second = 1
newSegments [otherEdge ] = []*geo .Point {
geo .NewPoint (otherEdge .Points [0 ].X +delta , otherEdge .Points [0 ].Y ),
otherEdge .Points [1 ],
}
} else {
first = len (otherEdge .Points ) - 1
second = len (otherEdge .Points ) - 2
newSegments [otherEdge ] = []*geo .Point {
geo .NewPoint (otherEdge .Points [len (otherEdge .Points )-1 ].X +delta , otherEdge .Points [len (otherEdge .Points )-1 ].Y ),
otherEdge .Points [len (otherEdge .Points )-2 ],
}
}
if ((otherEdge .Points [first ].X +signFlipDelta < otherEdge .Points [second ].X ) && (otherEdge .Points [first ].X > otherEdge .Points [second ].X )) ||
((otherEdge .Points [first ].X +signFlipDelta > otherEdge .Points [second ].X ) && (otherEdge .Points [first ].X < otherEdge .Points [second ].X )) {
signFlip = true
}
}
if !isVerticalLine && !isOtherEdgeVertical {
var second int
var third int
if otherEdge .From == node {
second = 1
third = 2
newSegments [otherEdge ] = []*geo .Point {
geo .NewPoint (otherEdge .Points [0 ].X , otherEdge .Points [0 ].Y +delta ),
geo .NewPoint (otherEdge .Points [1 ].X , otherEdge .Points [1 ].Y +delta ),
}
} else {
second = len (otherEdge .Points ) - 2
third = len (otherEdge .Points ) - 3
newSegments [otherEdge ] = []*geo .Point {
geo .NewPoint (otherEdge .Points [len (otherEdge .Points )-1 ].X , otherEdge .Points [len (otherEdge .Points )-1 ].Y +delta ),
geo .NewPoint (otherEdge .Points [len (otherEdge .Points )-2 ].X , otherEdge .Points [len (otherEdge .Points )-2 ].Y +delta ),
}
}
if otherEdge != edge {
if ((otherEdge .Points [second ].Y +signFlipDelta < otherEdge .Points [third ].Y ) && (otherEdge .Points [second ].Y > otherEdge .Points [third ].Y )) ||
((otherEdge .Points [second ].Y +signFlipDelta > otherEdge .Points [third ].Y ) && (otherEdge .Points [second ].Y < otherEdge .Points [third ].Y )) {
signFlip = true
}
}
}
if !isVerticalLine && isOtherEdgeVertical {
var first int
var second int
if otherEdge .From == node {
first = 0
second = 1
newSegments [otherEdge ] = []*geo .Point {
geo .NewPoint (otherEdge .Points [0 ].X , otherEdge .Points [0 ].Y +delta ),
otherEdge .Points [1 ],
}
} else {
first = len (otherEdge .Points ) - 1
second = len (otherEdge .Points ) - 2
newSegments [otherEdge ] = []*geo .Point {
geo .NewPoint (otherEdge .Points [len (otherEdge .Points )-1 ].X , otherEdge .Points [len (otherEdge .Points )-1 ].Y +delta ),
otherEdge .Points [len (otherEdge .Points )-2 ],
}
}
if ((otherEdge .Points [first ].Y +signFlipDelta < otherEdge .Points [second ].Y ) && (otherEdge .Points [first ].Y > otherEdge .Points [second ].Y )) ||
((otherEdge .Points [first ].Y +signFlipDelta > otherEdge .Points [second ].Y ) && (otherEdge .Points [first ].Y < otherEdge .Points [second ].Y )) {
signFlip = true
}
}
if signFlip {
break
}
}
if signFlip {
continue
}
intersects := false
for segmentEdge , newSegment := range newSegments {
for _ , otherNode := range g .Nodes {
if otherNode == node {
continue
}
if segmentEdge .From .IsDescendantOf (otherNode ) {
continue
}
if segmentEdge .To .IsDescendantOf (otherNode ) {
continue
}
if ((otherNode != segmentEdge .To ) && (otherNode != segmentEdge .From )) && otherNode .PassesThrough (newSegment [0 ], newSegment [1 ]) {
intersects = true
break
}
}
if intersects {
break
}
}
if intersects {
continue
}
numEdgesPassedThrough := 0
for _ , otherEdge := range g .Edges {
if (otherEdge .From == node ) || (otherEdge .To == node ) {
continue
}
for i := 0 ; i < len (otherEdge .Points )-1 ; i ++ {
if node .PassesThrough (otherEdge .Points [i ], otherEdge .Points [i +1 ]) {
numEdgesPassedThrough ++
}
}
}
if rollback == nil {
state := layoutgraph .NewGraphStateSnapshot (layoutgraph .GraphStateSnapshotOptions {
CaptureEdgeRoutes : true ,
})
if err := state .UpdateWithWorkGuard (g , guard ); err != nil {
return false , err
}
rollback = &layoutgraph .Transaction {Graph : g , PriorGraphState : state }
}
txn .AddOp (func () error {
if isVerticalLine {
node .TopLeft .X += delta
} else {
node .TopLeft .Y += delta
}
newNumEdgesPassedThrough := 0
for _ , otherEdge := range g .Edges {
if (otherEdge .From == node ) || (otherEdge .To == node ) {
continue
}
for i := 0 ; i < len (otherEdge .Points )-1 ; i ++ {
if node .PassesThrough (otherEdge .Points [i ], otherEdge .Points [i +1 ]) {
newNumEdgesPassedThrough ++
}
}
}
currSymmetry , err := placementcost .NodeSymmetry (ctx , node , nil )
if err != nil {
return err
}
if newNumEdgesPassedThrough > numEdgesPassedThrough {
return layoutgraph .ErrNonImprovingCandidate
} else if (node .Container != nil ) && node .SpillsOutOf (node .Container ) {
return layoutgraph .ErrInvalidCandidate
} else if currSymmetry < prevSymmetry {
return layoutgraph .ErrNonImprovingCandidate
}
return nil
})
if err := txn .Commit (ctx ); err != nil {
txn .Clear ()
if layoutgraph .IsCandidateRejection (err ) {
continue
}
return false , err
}
if err := txn .UpdateState (); err != nil {
return false , err
}
txn .Clear ()
for _ , otherEdge := range node .Edges {
var isOtherEdgeVertical bool
if otherEdge .From == node {
isOtherEdgeVertical = otherEdge .Points [0 ].X == otherEdge .Points [1 ].X
} else {
isOtherEdgeVertical = otherEdge .Points [len (otherEdge .Points )-1 ].X == otherEdge .Points [len (otherEdge .Points )-2 ].X
}
isOtherEdgeDiagonal := (len (otherEdge .Points ) == 2 ) && (otherEdge .Points [0 ].X != otherEdge .Points [1 ].X ) && (otherEdge .Points [0 ].Y != otherEdge .Points [1 ].Y )
if isOtherEdgeDiagonal {
if isVerticalLine {
if otherEdge .From == node {
otherEdge .Points [0 ].X += delta
} else {
otherEdge .Points [len (otherEdge .Points )-1 ].X += delta
}
} else {
if otherEdge .From == node {
otherEdge .Points [0 ].Y += delta
} else {
otherEdge .Points [len (otherEdge .Points )-1 ].Y += delta
}
}
} else {
if isVerticalLine && isOtherEdgeVertical {
if otherEdge .From == node {
otherEdge .Points [0 ].X += delta
otherEdge .Points [1 ].X += delta
} else {
otherEdge .Points [len (otherEdge .Points )-1 ].X += delta
otherEdge .Points [len (otherEdge .Points )-2 ].X += delta
}
}
if isVerticalLine && !isOtherEdgeVertical {
if otherEdge .From == node {
otherEdge .Points [0 ].X += delta
} else {
otherEdge .Points [len (otherEdge .Points )-1 ].X += delta
}
}
if !isVerticalLine && !isOtherEdgeVertical {
if otherEdge .From == node {
otherEdge .Points [0 ].Y += delta
otherEdge .Points [1 ].Y += delta
} else {
otherEdge .Points [len (otherEdge .Points )-1 ].Y += delta
otherEdge .Points [len (otherEdge .Points )-2 ].Y += delta
}
}
if !isVerticalLine && isOtherEdgeVertical {
if otherEdge .From == node {
otherEdge .Points [0 ].Y += delta
} else {
otherEdge .Points [len (otherEdge .Points )-1 ].Y += delta
}
}
}
}
if edge .From == node {
edge .Points = append (edge .Points [:1 ], edge .Points [3 :]...)
} else {
edge .Points = append (edge .Points [:len (edge .Points )-3 ], edge .Points [len (edge .Points )-1 :]...)
}
dejittered = true
}
}
if dejittered {
if err := guard .Finish (); err != nil {
return false , err
}
}
complete = true
return dejittered , 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 .