// Copyright (c) 2011, 2015 Kiel University and others.
// Copyright (c) 2010, 2020 Kiel University and others.
// Copyright (c) 2022 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 components ports ELK Layered 0.8.1's connected-components split // and packing stage.
package components import ( basegraph ) // ComponentsProcessor splits an unlayered graph into connected components // and combines the fully laid-out components again. type ComponentsProcessor struct { graphPlacer graphPlacer } func () *ComponentsProcessor { return &ComponentsProcessor{graphPlacer: &simpleRowGraphPlacer{}} } func ( *ComponentsProcessor) ( *layered.LGraphArena, layered.LGraphID, ) ([]layered.LGraphID, error) { .graphPlacer = &simpleRowGraphPlacer{} := .Graph() := basegraph.Get(&.Properties, layered.SeparateComponents) := basegraph.Get(&.Properties, layered.GraphProperties)& layered.GraphPropertyExternalPorts != 0 := !basegraph.Get( &.Properties, layered.PortConstraintsOption).IsOrderFixed() if ! || && ! { return []layered.LGraphID{}, nil } for , := range .LayerlessNodes { .Node().ID = 0 } := make([]layered.LGraphID, 0) for , := range .LayerlessNodes { , := depthFirstComponent(, , nil, 0) if == nil { continue } := .AddGraph() := .Graph() .Properties = .Properties.Clone() basegraph.Set(&.Properties, layered.ExternalPortConnections, ) .Padding = .Padding basegraph.Unset(&.Properties, layered.NodeSizeMinimum) .LayerlessNodes = append(.LayerlessNodes, ...) for , := range { .Node().Graph = } = append(, ) } if { switch basegraph.Get(&.Properties, layered.ComponentOrdering) { case layered.ComponentOrderingGroupModelOrder: .graphPlacer = &componentGroupModelOrderGraphPlacer{} case layered.ComponentOrderingModelOrder: .graphPlacer = &simpleRowGraphPlacer{modelOrder: true} default: .graphPlacer = &componentGroupGraphPlacer{} } } if basegraph.Get(&.Properties, layered.ComponentOrdering) != layered.ComponentOrderingNone { sort.SliceStable(, func(, int) bool { return minimalModelOrder(, []) < minimalModelOrder(, []) }) } return , nil } func ( *ComponentsProcessor) ( *layered.LGraphArena, []layered.LGraphID, layered.LGraphID, ) error { if .graphPlacer == nil { .graphPlacer = &simpleRowGraphPlacer{} } return .graphPlacer.combine(, , ) } func depthFirstComponent( *layered.LGraphArena, layered.LNodeID, []layered.LNodeID, uint8, ) ([]layered.LNodeID, uint8) { := .Node() if .ID != 0 { return , } .ID = 1 = append(, ) if .Type == layered.ExternalPortNode { := basegraph.Get(&.Properties, layered.ExternalPortSide) |= uint8(1) << uint8() } for , := range .Ports { := .Port() // LPort.getConnectedPorts concatenates predecessors and successors. for , := range .IncomingEdges { := .Edge() if .Source == nil || .Port(*.Source).Node == nil { continue } , = ( , *.Port(*.Source).Node, , ) } for , := range .OutgoingEdges { := .Edge() if .Target == nil || .Port(*.Target).Node == nil { continue } , = ( , *.Port(*.Target).Node, , ) } } return , } func minimalModelOrder( *layered.LGraphArena, layered.LGraphID) int { := math.MaxInt for , := range .Graph().LayerlessNodes { if , := basegraph.TryGet( &.Node().Properties, layered.ModelOrder, ); && < { = } } return } // MakeLayerless reproduces ElkLayered.layout's final layer cleanup. Component // placement requires every laid-out graph to be unlayered again. func ( *layered.LGraphArena, layered.LGraphID) { := .Graph() for , := range .Layers { := .Layer() .LayerlessNodes = append(.LayerlessNodes, .Nodes...) .Nodes = nil } for , := range .LayerlessNodes { .Node().Layer = nil } .Layers = nil }