// Copyright (c) 2010, 2020 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 p5edges implements the fifth phase of ELK Layered 0.8.1.
package p5edges import ( basegraph ) // OrthogonalEdgeRouter assigns layer x coordinates and routes orthogonal // hyperedges through the gaps between consecutive layers. type OrthogonalEdgeRouter struct{} var _ layered.ConfiguredProcessor = (*OrthogonalEdgeRouter)(nil) func (*OrthogonalEdgeRouter) ( *layered.LGraphArena, layered.LGraphID, ) layered.ProcessorConfiguration { := .Graph() := basegraph.Get(&.Properties, layered.GraphProperties) var layered.ProcessorConfiguration if &layered.GraphPropertyHyperedges != 0 { . AddBefore(layered.PhaseNodePlacement, layered.HyperedgeDummyMerger). AddBefore(layered.PhaseNodeOrdering, layered.InvertedPortProcessor) } if &layered.GraphPropertyNonFreePorts != 0 || basegraph.Get(&.Properties, layered.FeedbackEdges) { .AddBefore(layered.PhaseNodeOrdering, layered.InvertedPortProcessor) if &layered.GraphPropertyNorthSouthPorts != 0 { . AddBefore(layered.PhaseNodeOrdering, layered.NorthSouthPortPreprocessor). AddAfter(layered.PhaseEdgeRouting, layered.NorthSouthPortPostprocessor) } } if &layered.GraphPropertyExternalPorts != 0 { . AddBefore(layered.PhaseNodeOrdering, layered.HierarchicalPortConstraintProcessor). AddBefore(layered.PhaseNodePlacement, layered.HierarchicalPortDummySizeProcessor). AddAfter(layered.PhaseEdgeRouting, layered.HierarchicalPortOrthogonalEdgeRouter) } if &layered.GraphPropertySelfLoops != 0 { . AddBefore(layered.PhaseCycleBreaking, layered.SelfLoopPreprocessor). AddBefore(layered.PhaseNodePlacement, layered.SelfLoopPortRestorer). AddBefore(layered.PhaseNodePlacement, layered.SelfLoopRouter). AddAfter(layered.PhaseEdgeRouting, layered.SelfLoopPostprocessor) } if &layered.GraphPropertyHypernodes != 0 { .AddAfter(layered.PhaseEdgeRouting, layered.HypernodeProcessor) } if &layered.GraphPropertyCenterLabels != 0 { . AddBefore(layered.PhaseLayering, layered.LabelDummyInserter). AddBefore(layered.PhaseNodePlacement, layered.LabelDummySwitcher). AddBefore(layered.PhaseNodePlacement, layered.LabelSideSelector). AddAfter(layered.PhaseEdgeRouting, layered.LabelDummyRemover) } if &layered.GraphPropertyEndLabels != 0 { . AddBefore(layered.PhaseNodePlacement, layered.LabelSideSelector). AddBefore(layered.PhaseNodePlacement, layered.EndLabelPreprocessor). AddAfter(layered.PhaseEdgeRouting, layered.EndLabelPostprocessor) } return } func (*OrthogonalEdgeRouter) ( *layered.LGraphArena, layered.LGraphID) error { := .Graph() := basegraph.Get(&.Properties, layered.SpacingNodeNodeBetweenLayers) := basegraph.Get(&.Properties, layered.SpacingEdgeEdgeBetweenLayers) := basegraph.Get(&.Properties, layered.SpacingEdgeNodeBetweenLayers) := orthogonal.NewGenerator(orthogonal.WestToEast, ) := 0.0 var *layered.LayerID var []layered.LNodeID := -1 := 0 for { var *layered.LayerID var []layered.LNodeID if < len(.Layers) { := .Layers[] = & = .Layer().Nodes ++ } := - 1 if != nil { placeNodesHorizontally(, *, ) += .Layer(*).Size.X } := if != nil { += } := .RouteEdges(, , , , , ) := allExternalWestOrEastPorts(, ) := allExternalWestOrEastPorts(, ) if > 0 { := float64(-1) * if != nil { += } if != nil { += } if < && ! && ! { = } += } else if ! && ! { += } , , = , , if == nil { break } } .Size.X = return nil } func allExternalWestOrEastPorts( *layered.LGraphArena, []layered.LNodeID) bool { for , := range { := .Node() := basegraph.Get(&.Properties, layered.ExternalPortSide) if .Type != layered.ExternalPortNode || != layered.WestSide && != layered.EastSide { return false } } return true } // placeNodesHorizontally ports LGraphUtil.placeNodesHorizontally. Automatic // alignment biases a node according to its number of input and output ports. func placeNodesHorizontally( *layered.LGraphArena, layered.LayerID, float64) { := .Layer() , := 0.0, 0.0 for , := range .Nodes { := .Node() = math.Max(, .Margin.Left) = math.Max(, .Margin.Right) } for , := range .Nodes { := .Node() := basegraph.Get(&.Properties, layered.AlignmentOption) := 0.0 switch { case layered.AlignmentLeft: = 0 case layered.AlignmentRight: = 1 case layered.AlignmentCenter: = 0.5 default: , := 0, 0 for , := range .Ports { := .Port() if len(.IncomingEdges) > 0 { ++ } if len(.OutgoingEdges) > 0 { ++ } } if + == 0 { = 0.5 } else { = float64() / float64(+) } } := (.Size.X - .Size.X) * if > 0.5 { -= * 2 * ( - 0.5) } else if < 0.5 { += * 2 * (0.5 - ) } if < .Margin.Left { = .Margin.Left } := .Size.X - .Margin.Right - .Size.X if > { = } .Position.X = + } }