// Copyright (c) 2019 Kiel University and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0.
//
// SPDX-License-Identifier: EPL-2.0

// Package loops contains ELK Layered 0.8.1's temporary self-loop model.
package loops import ( basegraph ) // HolderProperty is InternalProperties.SELF_LOOP_HOLDER. Keeping the // property here avoids making the shared layered package depend on an // intermediate processor package. var HolderProperty = basegraph.NewProperty[*Holder]("selfLoopHolder") type Type uint8 const ( OneSide Type = iota TwoSidesCorner TwoSidesOpposing ThreeSides FourSides ) func ( [5]bool) (Type, error) { if [layered.UndefinedSide] { return 0, fmt.Errorf("self-loop port sides must not contain UNDEFINED") } := 0 for := layered.NorthSide; <= layered.WestSide; ++ { if [] { ++ } } switch { case 1: return OneSide, nil case 2: if [layered.EastSide] && [layered.WestSide] || [layered.NorthSide] && [layered.SouthSide] { return TwoSidesOpposing, nil } return TwoSidesCorner, nil case 3: return ThreeSides, nil case 4: return FourSides, nil default: return 0, fmt.Errorf("self-loop must occupy between one and four port sides") } } type Port struct { LPort layered.LPortID HadOnlySelfLoops bool Incoming []*Edge Outgoing []*Edge Hidden bool } func ( *Port) () int { return len(.Incoming) - len(.Outgoing) } type Edge struct { LEdge layered.LEdgeID HyperLoop *HyperLoop Source *Port Target *Port } // IsInline reports whether this self-loop edge has at least one inline label. // ELK 0.11 uses the per-edge flag while routing opposing-side self loops. func ( *Edge) ( *layered.LGraphArena) bool { for , := range .Edge(.LEdge).Labels { if basegraph.Get(&.Label().Properties, layered.EdgeLabelsInline) { return true } } return false } // LabelSide returns the aggregate side selected for this edge's hyper-loop. func ( *Edge) () layered.PortSide { if .HyperLoop == nil || .HyperLoop.Labels == nil { return layered.UndefinedSide } return .HyperLoop.Labels.Side } // LabelAlignment is SelfHyperLoopLabels.Alignment in declaration order. type LabelAlignment uint8 const ( LabelCenter LabelAlignment = iota LabelLeft LabelRight LabelTop ) type Labels struct { ID int LLabels []layered.LLabelID Size geometry.Point Position geometry.Point LayoutDirection layered.Direction LabelLabelSpacing float64 Side layered.PortSide Alignment LabelAlignment AlignmentReference *Port } func newLabels( *layered.LGraphArena, *HyperLoop) *Labels { := .Holder.LNode := .Graph(.NodeGraph()) return &Labels{ LayoutDirection: basegraph.Get(&.Properties, layered.DirectionOption), LabelLabelSpacing: IndividualOrInherited(, , layered.SpacingLabelLabel), Side: layered.UndefinedSide, } } func ( *Labels) ( *layered.LGraphArena, []layered.LLabelID) { for , := range { .LLabels = append(.LLabels, ) := .Label().Size if directionHorizontal(.LayoutDirection) { .Size.X = math.Max(.Size.X, .X) .Size.Y += .Y if len(.LLabels) > 1 { .Size.Y += .LabelLabelSpacing } } else { .Size.X += .X .Size.Y = math.Max(.Size.Y, .Y) if len(.LLabels) > 1 { .Size.X += .LabelLabelSpacing } } } } // ApplyPlacement transfers the aggregate label box back to individual labels. func ( *Labels) ( *layered.LGraphArena, geometry.Point) { , := .Position.X, .Position.Y for , := range .LLabels { := .Label() if directionHorizontal(.LayoutDirection) { switch { case .Alignment == LabelLeft || .Side == layered.EastSide: .Position.X = case .Alignment == LabelRight || .Side == layered.WestSide: .Position.X = + .Size.X - .Size.X default: .Position.X = + (.Size.X-.Size.X)/2 } .Position.Y = += .Size.Y + .LabelLabelSpacing } else { .Position.X = if .Side == layered.NorthSide { .Position.Y = + .Size.Y - .Size.Y } else { .Position.Y = } += .Size.X + .LabelLabelSpacing } .Position = .Position.Add() } } type HyperLoop struct { Holder *Holder Ports []*Port Edges []*Edge Labels *Labels Type Type PortsBySide [5][]*Port LeftmostPort *Port RightmostPort *Port OccupiedSides [5]bool RoutingSlot [5]int portsPerSideSet bool } func ( *HyperLoop) ( *layered.LGraphArena, *Edge) { for , := range .Edges { if == { return } } .Edges = append(.Edges, ) .HyperLoop = .addPort(.Source) .addPort(.Target) if len(.Edge(.LEdge).Labels) != 0 { if .Labels == nil { .Labels = newLabels(, ) } .Labels.Add(, .Edge(.LEdge).Labels) } } func ( *HyperLoop) ( *Port) { for , := range .Ports { if == { return } } .Ports = append(.Ports, ) } func ( *HyperLoop) ( *layered.LGraphArena) error { if .portsPerSideSet { return fmt.Errorf("self-loop ports per side already computed") } var [5]bool for , := range .Ports { := .Port(.LPort).Side if == layered.UndefinedSide { return fmt.Errorf("self-loop port %d has undefined side", .LPort) } .PortsBySide[] = append(.PortsBySide[], ) [] = true } , := TypeFromPortSides() if != nil { return } .Type = .portsPerSideSet = true return nil } func ( *HyperLoop) ( layered.PortSide) bool { return len(.PortsBySide[]) != 0 } func ( *HyperLoop) () []layered.PortSide { := make([]layered.PortSide, 0, 4) for := layered.NorthSide; <= layered.WestSide; ++ { if .HasPortsOnSide() { = append(, ) } } return } func ( *HyperLoop) ( layered.PortSide, int) { .RoutingSlot[] = if +1 > .Holder.RoutingSlotCount[] { .Holder.RoutingSlotCount[] = + 1 } } type Holder struct { LNode layered.LNodeID HyperLoops []*HyperLoop Ports map[layered.LPortID]*Port PortOrder []layered.LPortID PortsHidden bool RoutingSlotCount [5]int } func ( *layered.LGraphArena, layered.LNodeID) bool { if .Node().Type != layered.NormalNode { return false } for , := range .NodeOutgoingEdges() { if .EdgeIsSelfLoop() { return true } } return false } func ( *layered.LGraphArena, layered.LNodeID) (*Holder, error) { if !NeedsProcessing(, ) { return nil, fmt.Errorf("self-loop holder requires a normal node with self loops") } := &Holder{LNode: , Ports: make(map[layered.LPortID]*Port)} basegraph.Set(&.Node().Properties, HolderProperty, ) if := .initialize(); != nil { return nil, } return , nil } func ( *Holder) ( *layered.LGraphArena) error { for , := range .NodeOutgoingEdges(.LNode) { if !.EdgeIsSelfLoop() { continue } := .Edge() := .portFor(, *.Source) := .portFor(, *.Target) := &Edge{LEdge: , Source: , Target: } .Outgoing = append(.Outgoing, ) .Incoming = append(.Incoming, ) } // ELK temporarily reuses LPort.id for BFS state. for , := range .PortOrder { .Port().ID = 0 } for , := range .PortOrder { if .Port().ID == 0 { .HyperLoops = append(.HyperLoops, .initializeHyperLoop(, .Ports[])) } } return nil } func ( *Holder) ( *layered.LGraphArena, layered.LPortID) *Port { if , := .Ports[]; { return } := .PortConnectedEdges() := len() != 0 for , := range { if !.EdgeIsSelfLoop() { = false break } } := &Port{LPort: , HadOnlySelfLoops: } .Ports[] = .PortOrder = append(.PortOrder, ) return } func ( *Holder) ( *layered.LGraphArena, *Port) *HyperLoop { := &HyperLoop{Holder: } := []*Port{} for len() != 0 { := [0] = [1:] .Port(.LPort).ID = 1 for , := range .Outgoing { .AddEdge(, ) if .Port(.Target.LPort).ID == 0 { = append(, .Target) } } for , := range .Incoming { .AddEdge(, ) if .Port(.Source.LPort).ID == 0 { = append(, .Source) } } } return } func ( *layered.LGraphArena, layered.LNodeID, basegraph.Property[float64], ) float64 { := .Node() if , := basegraph.TryGet(&.Properties, layered.SpacingIndividual); { if , := basegraph.TryGet(&.Properties, ); { return } } return basegraph.Get(&.Graph(.NodeGraph()).Properties, ) } func directionHorizontal( layered.Direction) bool { return == layered.DirectionRight || == layered.DirectionLeft }