package p5edges
import (
"fmt"
"math"
"github.com/d2lang/elk-go/internal/geometry"
basegraph "github.com/d2lang/elk-go/internal/graph"
"github.com/d2lang/elk-go/internal/javacompat"
"github.com/d2lang/elk-go/internal/layered"
)
const splineStraightVerticalDifference = 0.2
type SplineEdgeRouter struct {}
type splineSide uint8
const (
splineLeft splineSide = iota
splineRight
)
type splineDependency struct {
source, target *splineSegment
weight int
}
type splineSegment struct {
edge layered .LEdgeID
leftPorts, rightPorts []layered .LPortID
outgoing, incoming []*splineDependency
mark int
inWeight, outWeight int
rank int
straight bool
topY, bottomY float64
centerY float64
sourceY, targetY float64
normalSource bool
normalTarget bool
boundingX float64
boundingWidth float64
xDelta float64
westOfInitialLayer bool
}
func (*SplineEdgeRouter ) ProcessorConfiguration (
arena *layered .LGraphArena ,
graphID layered .LGraphID ,
) layered .ProcessorConfiguration {
properties := basegraph .Get (&arena .Graph (graphID ).Properties , layered .GraphProperties )
var configuration layered .ProcessorConfiguration
configuration .
AddBefore (layered .PhaseNodeOrdering , layered .InvertedPortProcessor ).
AddAfter (layered .PhaseEdgeRouting , layered .FinalSplineBendpointsCalculator )
if properties &layered .GraphPropertySelfLoops != 0 {
configuration .
AddBefore (layered .PhaseCycleBreaking , layered .SelfLoopPreprocessor ).
AddBefore (layered .PhaseNodePlacement , layered .SelfLoopPortRestorer ).
AddBefore (layered .PhaseNodePlacement , layered .SelfLoopRouter ).
AddAfter (layered .PhaseEdgeRouting , layered .SelfLoopPostprocessor )
}
if properties &layered .GraphPropertyCenterLabels != 0 {
configuration .
AddBefore (layered .PhaseLayering , layered .LabelDummyInserter ).
AddBefore (layered .PhaseNodePlacement , layered .LabelDummySwitcher ).
AddBefore (layered .PhaseNodePlacement , layered .LabelSideSelector ).
AddAfter (layered .PhaseEdgeRouting , layered .LabelDummyRemover )
}
if properties &layered .GraphPropertyNorthSouthPorts != 0 {
configuration .
AddBefore (layered .PhaseNodeOrdering , layered .NorthSouthPortPreprocessor ).
AddBefore (layered .PhaseEdgeRouting , layered .NorthSouthPortPostprocessor )
}
if properties &layered .GraphPropertyEndLabels != 0 {
configuration .
AddBefore (layered .PhaseNodePlacement , layered .LabelSideSelector ).
AddBefore (layered .PhaseNodePlacement , layered .EndLabelPreprocessor ).
AddAfter (layered .PhaseEdgeRouting , layered .EndLabelPostprocessor )
}
return configuration
}
func (*SplineEdgeRouter ) Process (arena *layered .LGraphArena , graphID layered .LGraphID ) error {
graph := arena .Graph (graphID )
if len (graph .Layers ) == 0 {
graph .Size .X = 0
return nil
}
mode := basegraph .Get (&graph .Properties , layered .SplineRoutingModeOption )
if mode != layered .SplineRoutingSloppy && mode != layered .SplineRoutingConservative &&
mode != layered .SplineRoutingConservativeSoft {
return fmt .Errorf ("layered: spline routing mode %v is not implemented" , mode )
}
nodeSpacing := basegraph .Get (&graph .Properties , layered .SpacingNodeNodeBetweenLayers )
edgeNodeSpacing := basegraph .Get (&graph .Properties , layered .SpacingEdgeNodeBetweenLayers )
edgeSpacing := basegraph .Get (&graph .Properties , layered .SpacingEdgeEdgeBetweenLayers )
sloppyFactor := basegraph .Get (&graph .Properties , layered .SplineSloppyLayerSpacingFactor )
random := basegraph .Get (&graph .Properties , layered .Random )
if random == nil {
random = javacompat .NewRandom (1 )
}
firstLayer := graph .Layers [0 ]
leftExternal := allExternalWestOrEastPorts (arena , arena .Layer (firstLayer ).Nodes )
lastLayer := graph .Layers [len (graph .Layers )-1 ]
rightExternal := allExternalWestOrEastPorts (arena , arena .Layer (lastLayer ).Nodes )
xPosition := 0.0
var leftLayer *layered .LayerID
for pairIndex := 0 ; pairIndex <= len (graph .Layers ); pairIndex ++ {
var rightLayer *layered .LayerID
if pairIndex < len (graph .Layers ) {
copyID := graph .Layers [pairIndex ]
rightLayer = ©ID
}
segments := collectSplineSegments (arena , leftLayer , rightLayer )
createSplineDependencies (arena , segments )
breakSplineCycles (segments , random )
numberSplineSegments (segments )
slotCount := 0
for _ , segment := range segments {
if !segment .straight {
slotCount = max (slotCount , segment .rank +1 )
}
}
rightPosition := xPosition
xDelta := 0.0
specialLeft := leftLayer == nil || leftExternal && *leftLayer == firstLayer
specialRight := rightLayer == nil || rightExternal && *rightLayer == lastLayer
if slotCount > 0 {
increment := 0.0
if leftLayer != nil {
increment += edgeNodeSpacing
}
increment += float64 (slotCount -1 ) * edgeSpacing
if rightLayer != nil {
increment += edgeNodeSpacing
increment = math .Max (increment , splineSloppySpacing (arena , *rightLayer , edgeSpacing , nodeSpacing , sloppyFactor ))
}
if increment < nodeSpacing && !specialLeft && !specialRight {
xDelta = (nodeSpacing - increment ) / 2
increment = nodeSpacing
}
rightPosition += increment
} else if !specialLeft && !specialRight {
rightPosition += nodeSpacing
}
if rightLayer != nil {
placeNodesHorizontally (arena , *rightLayer , rightPosition )
}
for _ , segment := range segments {
segment .boundingX = xPosition
segment .boundingWidth = rightPosition - xPosition
segment .xDelta = xDelta
segment .westOfInitialLayer = leftLayer == nil
writeSplineControls (arena , segment , mode , edgeNodeSpacing , edgeSpacing )
}
xPosition = rightPosition
if rightLayer != nil {
xPosition += arena .Layer (*rightLayer ).Size .X
}
leftLayer = rightLayer
}
graph .Size .X = xPosition
return nil
}
func collectSplineSegments(
arena *layered .LGraphArena ,
leftLayer , rightLayer *layered .LayerID ,
) []*splineSegment {
leftPorts := make (map [layered .LPortID ]struct {})
rightPorts := make (map [layered .LPortID ]struct {})
edges := make ([]layered .LEdgeID , 0 )
seenEdges := make (map [layered .LEdgeID ]struct {})
addEdge := func (edgeID layered .LEdgeID ) {
if _ , found := seenEdges [edgeID ]; !found {
seenEdges [edgeID ] = struct {}{}
edges = append (edges , edgeID )
}
}
if leftLayer != nil {
for _ , nodeID := range arena .Layer (*leftLayer ).Nodes {
for _ , sourcePort := range arena .NodePortsOnSide (nodeID , layered .EastSide ) {
leftPorts [sourcePort ] = struct {}{}
for _ , edgeID := range arena .Port (sourcePort ).OutgoingEdges {
if arena .EdgeIsSelfLoop (edgeID ) {
continue
}
targetPort := *arena .Edge (edgeID ).Target
targetNode := arena .Node (*arena .Port (targetPort ).Node )
if rightLayer != nil && targetNode .Layer != nil && *targetNode .Layer == *rightLayer {
rightPorts [targetPort ] = struct {}{}
addEdge (edgeID )
} else if targetNode .Layer != nil && *targetNode .Layer == *leftLayer {
leftPorts [targetPort ] = struct {}{}
addEdge (edgeID )
}
}
}
}
}
if rightLayer != nil {
for _ , nodeID := range arena .Layer (*rightLayer ).Nodes {
for _ , sourcePort := range arena .NodePortsOnSide (nodeID , layered .WestSide ) {
rightPorts [sourcePort ] = struct {}{}
for _ , edgeID := range arena .Port (sourcePort ).OutgoingEdges {
if arena .EdgeIsSelfLoop (edgeID ) {
continue
}
targetPort := *arena .Edge (edgeID ).Target
targetNode := arena .Node (*arena .Port (targetPort ).Node )
if leftLayer != nil && targetNode .Layer != nil && *targetNode .Layer == *leftLayer {
leftPorts [targetPort ] = struct {}{}
addEdge (edgeID )
} else if targetNode .Layer != nil && *targetNode .Layer == *rightLayer {
rightPorts [targetPort ] = struct {}{}
addEdge (edgeID )
}
}
}
}
}
segments := make ([]*splineSegment , 0 , len (edges ))
for _ , edgeID := range edges {
edge := arena .Edge (edgeID )
sourcePort , targetPort := *edge .Source , *edge .Target
segment := &splineSegment {edge : edgeID }
if _ , found := leftPorts [sourcePort ]; found {
segment .leftPorts = append (segment .leftPorts , sourcePort )
} else {
segment .rightPorts = append (segment .rightPorts , sourcePort )
}
if _ , found := leftPorts [targetPort ]; found {
segment .leftPorts = append (segment .leftPorts , targetPort )
} else {
segment .rightPorts = append (segment .rightPorts , targetPort )
}
sourceY , targetY := absolutePortAnchor (arena , sourcePort ).Y , absolutePortAnchor (arena , targetPort ).Y
segment .sourceY , segment .targetY = sourceY , targetY
segment .normalSource = splineNormalNode (arena .Node (arena .EdgeSourceNode (edgeID )).Type )
segment .normalTarget = splineNormalNode (arena .Node (arena .EdgeTargetNode (edgeID )).Type )
segment .straight = math .Abs (sourceY -targetY ) < splineStraightVerticalDifference
segment .centerY = (sourceY + targetY ) / 2
if sourceY < targetY {
segment .topY = 0.1 *segment .centerY + 0.9 *sourceY
segment .bottomY = 0.1 *segment .centerY + 0.9 *targetY
} else {
segment .topY = 0.1 *segment .centerY + 0.9 *targetY
segment .bottomY = 0.1 *segment .centerY + 0.9 *sourceY
}
segments = append (segments , segment )
}
return segments
}
func createSplineDependencies(arena *layered .LGraphArena , segments []*splineSegment ) {
for firstIndex , first := range segments {
for _ , second := range segments [firstIndex +1 :] {
if first .topY > second .bottomY || second .topY > first .bottomY {
continue
}
firstCounter , secondCounter := 0 , 0
for _ , portID := range first .rightPorts {
if splineBetween (absolutePortAnchor (arena , portID ).Y , second .topY , second .bottomY ) {
firstCounter ++
}
}
for _ , portID := range first .leftPorts {
if splineBetween (absolutePortAnchor (arena , portID ).Y , second .topY , second .bottomY ) {
firstCounter --
}
}
for _ , portID := range second .rightPorts {
if splineBetween (absolutePortAnchor (arena , portID ).Y , first .topY , first .bottomY ) {
secondCounter ++
}
}
for _ , portID := range second .leftPorts {
if splineBetween (absolutePortAnchor (arena , portID ).Y , first .topY , first .bottomY ) {
secondCounter --
}
}
if firstCounter < secondCounter {
addSplineDependency (first , second , secondCounter -firstCounter )
} else if secondCounter < firstCounter {
addSplineDependency (second , first , firstCounter -secondCounter )
} else {
addSplineDependency (second , first , 0 )
addSplineDependency (first , second , 0 )
}
}
}
}
func splineBetween(value , first , second float64 ) bool {
return value >= math .Min (first , second ) && value <= math .Max (first , second )
}
func addSplineDependency(source , target *splineSegment , weight int ) {
dependency := &splineDependency {source : source , target : target , weight : weight }
source .outgoing = append (source .outgoing , dependency )
target .incoming = append (target .incoming , dependency )
}
func breakSplineCycles(segments []*splineSegment , random *javacompat .Random ) {
sources , sinks := make ([]*splineSegment , 0 ), make ([]*splineSegment , 0 )
nextMark := -1
for _ , segment := range segments {
segment .mark = nextMark
nextMark --
segment .inWeight , segment .outWeight = 0 , 0
for _ , dependency := range segment .outgoing {
segment .outWeight += dependency .weight
}
for _ , dependency := range segment .incoming {
segment .inWeight += dependency .weight
}
if segment .outWeight == 0 {
sinks = append (sinks , segment )
} else if segment .inWeight == 0 {
sources = append (sources , segment )
}
}
unprocessed := append ([]*splineSegment (nil ), segments ...)
markBase := len (segments )
nextLeft , nextRight := markBase +1 , markBase -1
for len (unprocessed ) > 0 {
for len (sinks ) > 0 {
segment := sinks [0 ]
sinks = sinks [1 :]
if !removeSplineSegment (&unprocessed , segment ) {
continue
}
segment .mark = nextRight
nextRight --
updateSplineNeighbors (segment , &sources , &sinks )
}
for len (sources ) > 0 {
segment := sources [0 ]
sources = sources [1 :]
if !removeSplineSegment (&unprocessed , segment ) {
continue
}
segment .mark = nextLeft
nextLeft ++
updateSplineNeighbors (segment , &sources , &sinks )
}
if len (unprocessed ) == 0 {
break
}
maximumOutflow := math .MinInt
maximum := make ([]*splineSegment , 0 )
for _ , segment := range unprocessed {
outflow := segment .outWeight - segment .inWeight
if outflow >= maximumOutflow {
if outflow > maximumOutflow {
maximum = maximum [:0 ]
maximumOutflow = outflow
}
maximum = append (maximum , segment )
}
}
chosen := maximum [random .NextIntN (int32 (len (maximum )))]
removeSplineSegment (&unprocessed , chosen )
chosen .mark = nextLeft
nextLeft ++
updateSplineNeighbors (chosen , &sources , &sinks )
}
shiftBase := len (segments ) + 1
for _ , segment := range segments {
if segment .mark < markBase {
segment .mark += shiftBase
}
}
for _ , source := range segments {
retained := source .outgoing [:0 ]
for _ , dependency := range source .outgoing {
if source .mark <= dependency .target .mark {
retained = append (retained , dependency )
continue
}
dependency .target .incoming = removeSplineDependency (dependency .target .incoming , dependency )
if dependency .weight > 0 {
oldTarget := dependency .target
dependency .source = oldTarget
oldTarget .outgoing = append (oldTarget .outgoing , dependency )
dependency .target = source
source .incoming = append (source .incoming , dependency )
}
}
source .outgoing = retained
}
}
func removeSplineSegment(list *[]*splineSegment , wanted *splineSegment ) bool {
for index , segment := range *list {
if segment == wanted {
*list = append ((*list )[:index ], (*list )[index +1 :]...)
return true
}
}
return false
}
func removeSplineDependency(list []*splineDependency , wanted *splineDependency ) []*splineDependency {
for index , dependency := range list {
if dependency == wanted {
return append (list [:index ], list [index +1 :]...)
}
}
return list
}
func updateSplineNeighbors(segment *splineSegment , sources , sinks *[]*splineSegment ) {
for _ , dependency := range segment .outgoing {
if dependency .target .mark < 0 && dependency .weight > 0 {
dependency .target .inWeight -= dependency .weight
if dependency .target .inWeight <= 0 && dependency .target .outWeight > 0 {
*sources = append (*sources , dependency .target )
}
}
}
for _ , dependency := range segment .incoming {
if dependency .source .mark < 0 && dependency .weight > 0 {
dependency .source .outWeight -= dependency .weight
if dependency .source .outWeight <= 0 && dependency .source .inWeight > 0 {
*sinks = append (*sinks , dependency .source )
}
}
}
}
func numberSplineSegments(segments []*splineSegment ) {
sources , rightwardTargets := make ([]*splineSegment , 0 ), make ([]*splineSegment , 0 )
for _ , segment := range segments {
segment .rank = 0
segment .inWeight , segment .outWeight = len (segment .incoming ), len (segment .outgoing )
if segment .inWeight == 0 {
sources = append (sources , segment )
}
if segment .outWeight == 0 && len (segment .leftPorts ) == 0 {
rightwardTargets = append (rightwardTargets , segment )
}
}
maximumRank := -1
for len (sources ) > 0 {
segment := sources [0 ]
sources = sources [1 :]
for _ , dependency := range segment .outgoing {
target := dependency .target
target .rank = max (target .rank , segment .rank +1 )
maximumRank = max (maximumRank , target .rank )
target .inWeight --
if target .inWeight == 0 {
sources = append (sources , target )
}
}
}
if maximumRank < 0 {
return
}
for _ , segment := range rightwardTargets {
segment .rank = maximumRank
}
for len (rightwardTargets ) > 0 {
segment := rightwardTargets [0 ]
rightwardTargets = rightwardTargets [1 :]
for _ , dependency := range segment .incoming {
source := dependency .source
if len (source .leftPorts ) > 0 {
continue
}
source .rank = min (source .rank , segment .rank -1 )
source .outWeight --
if source .outWeight == 0 {
rightwardTargets = append (rightwardTargets , source )
}
}
}
}
func splineSloppySpacing(
arena *layered .LGraphArena ,
rightLayer layered .LayerID ,
edgeSpacing , nodeSpacing , factor float64 ,
) float64 {
maximumDifference := 0.0
for _ , nodeID := range arena .Layer (rightLayer ).Nodes {
current := 0.0
for _ , edgeID := range arena .NodeIncomingEdges (nodeID ) {
edge := arena .Edge (edgeID )
current = math .Max (current , math .Abs (absolutePortAnchor (arena , *edge .Target ).Y -absolutePortAnchor (arena , *edge .Source ).Y ))
}
maximumDifference = math .Max (maximumDifference , current )
}
return factor * math .Min (1 , edgeSpacing /nodeSpacing ) * maximumDifference
}
func writeSplineControls(
arena *layered .LGraphArena ,
segment *splineSegment ,
mode layered .SplineRoutingMode ,
edgeNodeSpacing , edgeSpacing float64 ,
) {
edge := arena .Edge (segment .edge )
if edge .Source == nil || edge .Target == nil {
return
}
centerX := segment .boundingX + segment .boundingWidth /2
controls := make (geometry .Chain , 0 , 4 )
if segment .straight {
controls = append (controls , geometry .Point {X : centerX , Y : segment .centerY })
} else if mode != layered .SplineRoutingSloppy ||
arena .Port (*edge .Source ).Side == layered .WestSide ||
arena .Port (*edge .Target ).Side == layered .EastSide {
routingX := segment .boundingX
if !segment .westOfInitialLayer {
routingX += edgeNodeSpacing
}
routingX += segment .xDelta + float64 (segment .rank )*edgeSpacing
sourceX , targetX := segment .boundingX , segment .boundingX +segment .boundingWidth
if arena .Port (*edge .Source ).Side == layered .WestSide {
sourceX = segment .boundingX + segment .boundingWidth
}
if arena .Port (*edge .Target ).Side == layered .EastSide {
targetX = segment .boundingX
}
controls = append (controls ,
geometry .Point {X : sourceX , Y : segment .sourceY },
geometry .Point {X : routingX , Y : segment .sourceY },
geometry .Point {X : routingX , Y : segment .targetY },
geometry .Point {X : targetX , Y : segment .targetY })
} else {
if !segment .normalSource {
controls = append (controls ,
geometry .Point {X : segment .boundingX , Y : segment .sourceY },
geometry .Point {X : centerX , Y : segment .sourceY })
}
if !segment .normalTarget {
controls = append (controls ,
geometry .Point {X : centerX , Y : segment .targetY },
geometry .Point {X : segment .boundingX + segment .boundingWidth , Y : segment .targetY })
}
if segment .normalSource && segment .normalTarget {
controls = append (controls , geometry .Point {X : centerX , Y : math .NaN ()})
}
}
edge .BendPoints = edge .BendPoints [:0 ]
basegraph .Set (&edge .Properties , layered .SplineControlPointSegments , []geometry .Chain {controls })
basegraph .Set (&edge .Properties , layered .SplineCompactionSegments ,
[]layered .SplineCompactionSegment {{
BoundingBox : geometry .Rectangle {
X : segment .boundingX ,
Y : math .Min (segment .sourceY , segment .targetY ),
Width : segment .boundingWidth ,
Height : math .Abs (segment .sourceY - segment .targetY ),
},
Straight : segment .straight ,
Edge : segment .edge ,
SourcePort : *edge .Source ,
TargetPort : *edge .Target ,
SourceY : segment .sourceY ,
TargetY : segment .targetY ,
NormalSource : segment .normalSource ,
NormalTarget : segment .normalTarget ,
WestOfFirstLayer : segment .westOfInitialLayer ,
XDelta : segment .xDelta ,
Rank : segment .rank ,
}})
}
func splineNormalNode(nodeType layered .NodeType ) bool {
return nodeType == layered .NormalNode || nodeType == layered .BreakingPointNode
}
func FinalizeSplineBendpoints (arena *layered .LGraphArena , graphID layered .LGraphID ) {
for edgeIndex := range arena .Edges {
edgeID := layered .LEdgeID (edgeIndex )
edge := arena .Edge (edgeID )
if edge .Source == nil || edge .Target == nil || arena .EdgeIsSelfLoop (edgeID ) {
continue
}
if arena .NodeGraph (arena .EdgeSourceNode (edgeID )) != graphID {
continue
}
segments , found := basegraph .TryGet (&edge .Properties , layered .SplineControlPointSegments )
if !found || len (segments ) == 0 {
continue
}
if compactionSegments , found := basegraph .TryGet (
&edge .Properties , layered .SplineCompactionSegments ,
); found && len (compactionSegments ) == len (segments ) {
applyPostCompactionSplineRouting (
arena , graphID , edgeID , segments , compactionSegments )
}
resolveSloppySplineCenters (arena , edgeID , segments )
controlPoints := geometry .Chain {absolutePortAnchor (arena , *edge .Source )}
var last geometry .Point
addMidpoint := false
for _ , segment := range segments {
if len (segment ) == 0 {
continue
}
if addMidpoint {
controlPoints = append (controlPoints , geometry .Point {
X : (last .X + segment [0 ].X ) / 2 ,
Y : (last .Y + segment [0 ].Y ) / 2 ,
})
addMidpoint = false
} else {
addMidpoint = true
}
last = segment [len (segment )-1 ]
controlPoints = append (controlPoints , segment ...)
}
controlPoints = append (controlPoints , absolutePortAnchor (arena , *edge .Target ))
if basegraph .Get (&arena .Graph (graphID ).Properties , layered .SplineRoutingModeOption ) == layered .SplineRoutingConservative {
controlPoints = insertSplineStraighteningControls (
controlPoints , arena .Port (*edge .Source ).Side , arena .Port (*edge .Target ).Side )
}
edge .BendPoints = CubicNUBToBezier (controlPoints )
basegraph .Unset (&edge .Properties , layered .SplineControlPointSegments )
basegraph .Unset (&edge .Properties , layered .SplineCompactionSegments )
}
}
func applyPostCompactionSplineRouting(
arena *layered .LGraphArena ,
graphID layered .LGraphID ,
edgeID layered .LEdgeID ,
controls []geometry .Chain ,
segments []layered .SplineCompactionSegment ,
) {
graph := arena .Graph (graphID )
if basegraph .Get (&graph .Properties , layered .PostCompactionStrategy ) ==
layered .GraphCompactionNone ||
basegraph .Get (&graph .Properties , layered .SplineRoutingModeOption ) !=
layered .SplineRoutingSloppy {
return
}
edgeNodeSpacing := basegraph .Get (
&graph .Properties , layered .SpacingEdgeNodeBetweenLayers )
edgeEdgeSpacing := basegraph .Get (
&graph .Properties , layered .SpacingEdgeEdgeBetweenLayers )
for index := range segments {
segment := &segments [index ]
if segment .Straight || !(segment .NormalSource || segment .NormalTarget ) {
continue
}
if arena .Port (segment .SourcePort ).Side == layered .WestSide ||
arena .Port (segment .TargetPort ).Side == layered .EastSide {
continue
}
allowsSloppy := true
if index == 0 {
source := arena .Node (arena .EdgeSourceNode (edgeID ))
if source .Layer != nil {
threshold := arena .Layer (*source .Layer ).Size .X - source .Size .X /2
distance := segment .BoundingBox .X -
(source .Position .X + source .Size .X )
if distance > threshold {
allowsSloppy = false
}
}
}
if index == len (segments )-1 {
target := arena .Node (arena .EdgeTargetNode (edgeID ))
if target .Layer != nil {
threshold := arena .Layer (*target .Layer ).Size .X - target .Size .X /2
distance := target .Position .X - segment .BoundingBox .MaxX ()
if distance > threshold {
allowsSloppy = false
}
}
}
if allowsSloppy {
continue
}
startX := segment .BoundingBox .X
endX := segment .BoundingBox .MaxX ()
routingX := startX
if !segment .WestOfFirstLayer {
routingX += edgeNodeSpacing
}
routingX += segment .XDelta + float64 (segment .Rank )*edgeEdgeSpacing
controls [index ] = geometry .Chain {
{X : startX , Y : segment .SourceY },
{X : routingX , Y : segment .SourceY },
{X : routingX , Y : segment .TargetY },
{X : endX , Y : segment .TargetY },
}
}
}
func insertSplineStraighteningControls(
points geometry .Chain ,
sourceSide , targetSide layered .PortSide ,
) geometry .Chain {
if len (points ) < 2 {
return points
}
first , second := points [0 ], points [1 ]
sourceDirection := splineDirection (sourceSide )
sourceDirection .X *= 5
sourceDirection .Y *= 5
sourceDelta := second .Sub (first )
straightSource := first .Add (geometry .Point {
X : splineAbsoluteMinimum (sourceDirection .X , sourceDelta .X ),
Y : splineAbsoluteMinimum (sourceDirection .Y , sourceDelta .Y ),
})
points = append (points , geometry .Point {})
copy (points [2 :], points [1 :])
points [1 ] = straightSource
lastIndex := len (points ) - 1
last , secondLast := points [lastIndex ], points [lastIndex -1 ]
targetDirection := splineDirection (targetSide )
targetDirection .X *= 5
targetDirection .Y *= 5
targetDelta := secondLast .Sub (last )
straightTarget := last .Add (geometry .Point {
X : splineAbsoluteMinimum (targetDirection .X , targetDelta .X ),
Y : splineAbsoluteMinimum (targetDirection .Y , targetDelta .Y ),
})
points = append (points , geometry .Point {})
copy (points [lastIndex +1 :], points [lastIndex :])
points [lastIndex ] = straightTarget
return points
}
func splineDirection(side layered .PortSide ) geometry .Point {
switch side {
case layered .NorthSide :
return geometry .Point {Y : -1 }
case layered .EastSide :
return geometry .Point {X : 1 }
case layered .SouthSide :
return geometry .Point {Y : 1 }
case layered .WestSide :
return geometry .Point {X : -1 }
default :
return geometry .Point {}
}
}
func splineAbsoluteMinimum(first , second float64 ) float64 {
if math .Abs (first ) < math .Abs (second ) {
return first
}
return second
}
func resolveSloppySplineCenters(
arena *layered .LGraphArena ,
edgeID layered .LEdgeID ,
segments []geometry .Chain ,
) {
needsResolution := false
for _ , segment := range segments {
for _ , point := range segment {
needsResolution = needsResolution || math .IsNaN (point .Y )
}
}
if !needsResolution {
return
}
edge := arena .Edge (edgeID )
source , target := absolutePortAnchor (arena , *edge .Source ), absolutePortAnchor (arena , *edge .Target )
indegree , outdegree := 0 , 0
for _ , portID := range arena .Node (arena .EdgeTargetNode (edgeID )).Ports {
indegree += len (arena .Port (portID ).IncomingEdges )
}
for _ , portID := range arena .Node (arena .EdgeSourceNode (edgeID )).Ports {
outdegree += len (arena .Port (portID ).OutgoingEdges )
}
degreeDifference := 0
if outdegree > indegree {
degreeDifference = 1
} else if outdegree < indegree {
degreeDifference = -1
}
centerY := (target .Y +source .Y )/2 + (target .Y -source .Y )*(0.4 *float64 (degreeDifference ))
for segmentIndex := range segments {
for pointIndex := range segments [segmentIndex ] {
if math .IsNaN (segments [segmentIndex ][pointIndex ].Y ) {
segments [segmentIndex ][pointIndex ].Y = centerY
}
}
}
}
func CubicNUBToBezier (points geometry .Chain ) geometry .Chain {
const degree = 3
points = points .Clone ()
for len (points ) < degree +1 {
points = append (geometry .Chain {points [0 ]}, points ...)
}
denominator := len (points ) - degree
knots := make ([]float64 , 0 , len (points )+degree +1 )
for index := 0 ; index <= degree ; index ++ {
knots = append (knots , 0 )
}
for index := 1 ; index < denominator ; index ++ {
knots = append (knots , float64 (index )/float64 (denominator ))
}
for index := 0 ; index <= degree ; index ++ {
knots = append (knots , 1 )
}
inner := append ([]float64 (nil ), knots [degree +1 :len (knots )-degree -1 ]...)
for index := 0 ; index < len (inner ); {
knot := inner [index ]
multiplicity := 1
for index +multiplicity < len (inner ) && math .Abs (inner [index +multiplicity ]-knot ) < 1e-6 {
multiplicity ++
}
for insertion := multiplicity ; insertion < degree ; insertion ++ {
points , knots = insertSplineKnot (points , knots , degree , knot )
}
index += multiplicity
}
return append (geometry .Chain (nil ), points [1 :len (points )-1 ]...)
}
func insertSplineKnot(
points geometry .Chain ,
knots []float64 ,
degree int ,
knot float64 ,
) (geometry .Chain , []float64 ) {
n := len (points ) - 1
m := len (knots ) - 1
span := degree
for span +1 < len (knots ) && knot >= knots [span +1 ]-1e-12 {
span ++
}
if span > n {
span = n
}
multiplicity := 0
for _ , candidate := range knots {
if math .Abs (candidate -knot ) < 1e-9 {
multiplicity ++
}
}
newKnots := make ([]float64 , m +2 )
copy (newKnots [:span +1 ], knots [:span +1 ])
newKnots [span +1 ] = knot
copy (newKnots [span +2 :], knots [span +1 :])
result := make (geometry .Chain , n +2 )
copy (result [:span -degree +1 ], points [:span -degree +1 ])
copy (result [span -multiplicity +1 :], points [span -multiplicity :])
for index := span - degree + 1 ; index <= span -multiplicity ; index ++ {
alpha := (knot - knots [index ]) / (knots [index +degree ] - knots [index ])
result [index ] = geometry .Point {
X : (1 -alpha )*points [index -1 ].X + alpha *points [index ].X ,
Y : (1 -alpha )*points [index -1 ].Y + alpha *points [index ].Y ,
}
}
return result , newKnots
}
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 .