// Copyright (c) 2009, 2015 Kiel University and others.
// Copyright (c) 2010, 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 layered implements ELK Layered's internal graph and five-phase // layout pipeline. The types in this file are the arena-backed counterparts // of ELK 0.8.1's LGraph, LNode, LPort, LEdge, LLabel, and Layer classes.
package layered import ( basegraph ) type LGraphID uint32 type LNodeID uint32 type LPortID uint32 type LEdgeID uint32 type LLabelID uint32 type LayerID uint32 func ( LGraphID) () int { return int() } func ( LNodeID) () int { return int() } func ( LPortID) () int { return int() } func ( LEdgeID) () int { return int() } func ( LLabelID) () int { return int() } func ( LayerID) () int { return int() } // NodeType is the exact ELK 0.8.1 dummy-node classification. Its declaration // order is significant: spacing tables index directly by this value. type NodeType uint8 const ( NormalNode NodeType = iota LongEdgeNode ExternalPortNode NorthSouthPortNode LabelNode BreakingPointNode PlaceholderNode NonShiftingPlaceholderNode ) func ( NodeType) () string { switch { case NormalNode: return "NORMAL" case LongEdgeNode: return "LONG_EDGE" case ExternalPortNode: return "EXTERNAL_PORT" case NorthSouthPortNode: return "NORTH_SOUTH_PORT" case LabelNode: return "LABEL" case BreakingPointNode: return "BREAKING_POINT" case PlaceholderNode: return "PLACEHOLDER" case NonShiftingPlaceholderNode: return "NONSHIFTING_PLACEHOLDER" default: return fmt.Sprintf("NodeType(%d)", ) } } // PortSide matches org.eclipse.elk.core.options.PortSide's ordinal order. type PortSide uint8 const ( UndefinedSide PortSide = iota NorthSide EastSide SouthSide WestSide ) func ( PortSide) () string { switch { case UndefinedSide: return "UNDEFINED" case NorthSide: return "NORTH" case EastSide: return "EAST" case SouthSide: return "SOUTH" case WestSide: return "WEST" default: return fmt.Sprintf("PortSide(%d)", ) } } func ( PortSide) () PortSide { switch { case NorthSide: return SouthSide case EastSide: return WestSide case SouthSide: return NorthSide case WestSide: return EastSide default: return UndefinedSide } } type LGraph struct { ID int Size geometry.Point Padding geometry.Spacing Offset geometry.Point LayerlessNodes []LNodeID Layers []LayerID ParentNode *LNodeID Properties basegraph.PropertyMap } type Layer struct { ID int Graph LGraphID Nodes []LNodeID Size geometry.Point Properties basegraph.PropertyMap } type LNode struct { ID int Graph LGraphID Layer *LayerID Type NodeType Ports []LPortID Labels []LLabelID NestedGraph *LGraphID Margin geometry.Spacing Padding geometry.Spacing Position geometry.Point Size geometry.Point Properties basegraph.PropertyMap portSideRanges [5]indexRange portSidesCached bool } type LPort struct { ID int Node *LNodeID Side PortSide Anchor geometry.Point ExplicitAnchor bool Margin geometry.Spacing Labels []LLabelID IncomingEdges []LEdgeID OutgoingEdges []LEdgeID ConnectedToExternalNodes bool Position geometry.Point Size geometry.Point Properties basegraph.PropertyMap } type LEdge struct { ID int BendPoints geometry.Chain Source *LPortID Target *LPortID Labels []LLabelID Properties basegraph.PropertyMap } type LLabel struct { ID int Text string Position geometry.Point Size geometry.Point Properties basegraph.PropertyMap } type indexRange struct{ Start, End int } // LGraphArena gives Go stable identities without pointers into growing slices. // Arena order is Java object creation order and must never be reconstructed // from a map. type LGraphArena struct { Graphs []LGraph Nodes []LNode Ports []LPort Edges []LEdge Labels []LLabel Layers []Layer } func () *LGraphArena { return &LGraphArena{} } func ( *LGraphArena) ( LGraphID) *LGraph { return &.Graphs[.Index()] } func ( *LGraphArena) ( LNodeID) *LNode { return &.Nodes[.Index()] } func ( *LGraphArena) ( LPortID) *LPort { return &.Ports[.Index()] } func ( *LGraphArena) ( LEdgeID) *LEdge { return &.Edges[.Index()] } func ( *LGraphArena) ( LLabelID) *LLabel { return &.Labels[.Index()] } func ( *LGraphArena) ( LayerID) *Layer { return &.Layers[.Index()] } func ( *LGraphArena) () LGraphID { := LGraphID(len(.Graphs)) .Graphs = append(.Graphs, LGraph{}) return } // AddNode is new LNode(graph); callers add the node to graph.LayerlessNodes // explicitly, just as the Java importer does. func ( *LGraphArena) ( LGraphID) LNodeID { := LNodeID(len(.Nodes)) .Nodes = append(.Nodes, LNode{Graph: , Type: NormalNode}) return } func ( *LGraphArena) () LPortID { := LPortID(len(.Ports)) .Ports = append(.Ports, LPort{ConnectedToExternalNodes: true}) return } func ( *LGraphArena) () LEdgeID { := LEdgeID(len(.Edges)) .Edges = append(.Edges, LEdge{}) return } func ( *LGraphArena) ( string) LLabelID { := LLabelID(len(.Labels)) .Labels = append(.Labels, LLabel{Text: }) return } // AddLayer is new Layer(graph); it deliberately does not append the layer to // graph.Layers. This mirrors the subtle Java constructor behavior. func ( *LGraphArena) ( LGraphID) LayerID { := LayerID(len(.Layers)) .Layers = append(.Layers, Layer{Graph: }) return } func ( *LGraphArena) ( LPortID, *LNodeID) { := .Port() if .Node != nil { := *.Node .Node().Ports = removePort(.Node().Ports, ) .InvalidatePortSideCache() } .Node = copyLNodeID() if != nil { .Node(*).Ports = append(.Node(*).Ports, ) .InvalidatePortSideCache(*) } } func ( *LGraphArena) ( LPortID, PortSide) { := .Port() .Side = if !.ExplicitAnchor { switch { case NorthSide: .Anchor = geometry.Point{X: .Size.X / 2} case EastSide: .Anchor = geometry.Point{X: .Size.X, Y: .Size.Y / 2} case SouthSide: .Anchor = geometry.Point{X: .Size.X / 2, Y: .Size.Y} case WestSide: .Anchor = geometry.Point{Y: .Size.Y / 2} } } if .Node != nil { .InvalidatePortSideCache(*.Node) } } func ( *LGraphArena) ( LEdgeID, *LPortID) { := .Edge() if .Source != nil { .Port(*.Source).OutgoingEdges = removeEdge(.Port(*.Source).OutgoingEdges, ) } .Source = copyLPortID() if != nil { .Port(*).OutgoingEdges = append(.Port(*).OutgoingEdges, ) } } func ( *LGraphArena) ( LEdgeID, *LPortID) { .SetEdgeTargetAt(, , -1) } func ( *LGraphArena) ( LEdgeID, *LPortID, int) { := .Edge() if .Target != nil { .Port(*.Target).IncomingEdges = removeEdge(.Port(*.Target).IncomingEdges, ) } .Target = copyLPortID() if == nil { return } := .Port(*).IncomingEdges if < 0 { .Port(*).IncomingEdges = append(, ) return } if > len() { panic("layered: edge target insertion index out of bounds") } = append(, 0) copy([+1:], [:]) [] = .Port(*).IncomingEdges = } func ( *LGraphArena) ( LNodeID, *LayerID) { .SetNodeLayerAt(, , -1) } func ( *LGraphArena) ( LNodeID, *LayerID, int) { := .Node() if .Layer != nil { .Layer(*.Layer).Nodes = removeNode(.Layer(*.Layer).Nodes, ) } .Layer = copyLayerID() if == nil { return } := .Layer(*).Nodes if < 0 { .Layer(*).Nodes = append(, ) return } if > len() { panic("layered: node layer insertion index out of bounds") } = append(, 0) copy([+1:], [:]) [] = .Layer(*).Nodes = } func ( *LGraphArena) ( LNodeID) LGraphID { := .Node() if .Layer != nil { return .Layer(*.Layer).Graph } return .Graph } func ( *LGraphArena) ( LNodeID) []LEdgeID { var []LEdgeID for , := range .Node().Ports { = append(, .Port().IncomingEdges...) } return } func ( *LGraphArena) ( LNodeID) []LEdgeID { var []LEdgeID for , := range .Node().Ports { = append(, .Port().OutgoingEdges...) } return } func ( *LGraphArena) ( LNodeID) []LEdgeID { var []LEdgeID for , := range .Node().Ports { := .Port() = append(, .IncomingEdges...) = append(, .OutgoingEdges...) } return } func ( *LGraphArena) ( LPortID) []LEdgeID { := .Port() := append([]LEdgeID(nil), .IncomingEdges...) return append(, .OutgoingEdges...) } func ( *LGraphArena) ( LNodeID, PortSide) []LPortID { := make([]LPortID, 0) for , := range .Node().Ports { if .Port().Side == { = append(, ) } } return } func ( *LGraphArena) ( LNodeID, PortSide) []LPortID { := .Node() if !.portSidesCached { .findPortSideRanges() = .Node() } := .portSideRanges[int()] return .Ports[.Start:.End] } func ( *LGraphArena) ( LNodeID) { .findPortSideRanges() .Node().portSidesCached = true } func ( *LGraphArena) ( LNodeID) { := .Node() var [5]indexRange for := UndefinedSide; <= WestSide; ++ { , := -1, -1 for , := range .Ports { if .Port().Side == { if < 0 { = } = + 1 } else if >= 0 { break } } if < 0 { , = 0, 0 } [int()] = indexRange{Start: , End: } } .portSideRanges = } func ( *LGraphArena) ( LNodeID) { := .Node() // LNode.getPortSideView recomputes its indices on every call until // cachePortSides is called. Once explicitly cached, the Java implementation // deliberately keeps the index ranges across structural port-list changes; // several Layered processors rely on that observable sublist behavior. if !.portSidesCached { .portSideRanges = [5]indexRange{} } } func ( *LGraphArena) ( LEdgeID) bool { := .Edge() if .Source == nil || .Target == nil { return false } , := .Port(*.Source).Node, .Port(*.Target).Node return != nil && != nil && * == * } func ( *LGraphArena) ( LEdgeID) bool { if .EdgeIsSelfLoop() { return false } , := .EdgeSourceNode(), .EdgeTargetNode() return equalLayer(.Node().Layer, .Node().Layer) } func ( *LGraphArena) ( LEdgeID) LNodeID { return *.Port(*.Edge().Source).Node } func ( *LGraphArena) ( LEdgeID) LNodeID { return *.Port(*.Edge().Target).Node } func ( *LGraphArena) ( LEdgeID, LPortID) LPortID { := .Edge() if .Source != nil && *.Source == { return *.Target } if .Target != nil && *.Target == { return *.Source } panic("layered: port is not incident to edge") } func ( *LGraphArena) ( LEdgeID, LNodeID) LNodeID { , := .EdgeSourceNode(), .EdgeTargetNode() if == { return } if == { return } panic("layered: node is not incident to edge") } func ( *LGraphArena) ( LayerID) int { := .Layer().Graph for , := range .Graph().Layers { if == { return } } return -1 } func ( *LGraphArena) ( LNodeID) int { := .Node().Layer if == nil { return -1 } for , := range .Layer(*).Nodes { if == { return } } return -1 } func ( *LGraphArena) ( LGraphID) geometry.Point { := .Graph() return geometry.Point{ X: .Size.X + .Padding.Left + .Padding.Right, Y: .Size.Y + .Padding.Top + .Padding.Bottom, } } func ( *LGraphArena) ( LGraphID) []LNodeID { := .Graph() := append([]LNodeID(nil), .LayerlessNodes...) for , := range .Layers { = append(, .Layer().Nodes...) } return } func ( *LGraphArena) ( LNodeID, , bool) { := .Graph(.NodeGraph()) := .Node() if { .Position.X -= .Padding.Left + .Offset.X } if { .Position.Y -= .Padding.Top + .Offset.Y } } func copyLNodeID( *LNodeID) *LNodeID { if == nil { return nil } := * return & } func copyLPortID( *LPortID) *LPortID { if == nil { return nil } := * return & } func copyLayerID( *LayerID) *LayerID { if == nil { return nil } := * return & } func removeNode( []LNodeID, LNodeID) []LNodeID { for , := range { if == { return append([:], [+1:]...) } } return } func removePort( []LPortID, LPortID) []LPortID { for , := range { if == { return append([:], [+1:]...) } } return } func removeEdge( []LEdgeID, LEdgeID) []LEdgeID { for , := range { if == { return append([:], [+1:]...) } } return } func equalLayer(, *LayerID) bool { if == nil || == nil { return == nil && == nil } return * == * }