From 2aeae57b382f35cd46895b3170590895deb406c3 Mon Sep 17 00:00:00 2001 From: HarikrishnanBalagopal Date: Tue, 4 Apr 2023 17:49:15 +0530 Subject: [PATCH] feat: make the graph more deterministic by sorting the vertices (#1009) Signed-off-by: Harikrishnan Balagopal --- cmd/graph.go | 2 +- graph/layout.go | 99 ++++++++++++++++++++++--------------------------- 2 files changed, 45 insertions(+), 56 deletions(-) diff --git a/cmd/graph.go b/cmd/graph.go index 25ab6f645..9efca04e2 100644 --- a/cmd/graph.go +++ b/cmd/graph.go @@ -48,7 +48,7 @@ func graphHandler(flags graphFlags) { logrus.Fatalf("failed to decode the json file at path %s . Error: %q", graphFilePath, err) } nodes, edges := graphutils.GetNodesAndEdges(graph) - graphutils.DfsUpdatePositions(nodes, edges) + graphutils.BfsUpdatePositions(nodes, edges) webGraph := graphtypes.GraphT{Nodes: nodes, Edges: edges} if flags.outputPath != "" { webBytes, err := json.Marshal(webGraph) diff --git a/graph/layout.go b/graph/layout.go index 74e9d3d5f..5dd389541 100644 --- a/graph/layout.go +++ b/graph/layout.go @@ -18,7 +18,9 @@ package graph import ( "fmt" + "sort" + "github.com/konveyor/move2kube/common" graphtypes "github.com/konveyor/move2kube/types/graph" "github.com/sirupsen/logrus" ) @@ -60,78 +62,65 @@ func GetNodesAndEdges(graph graphtypes.Graph) ([]graphtypes.Node, []graphtypes.E return nodes, edges } -// DfsUpdatePositions updates the positions of the nodes using a layered layout algorithm that utilizes Depth First Search. -func DfsUpdatePositions(nodes []graphtypes.Node, edges []graphtypes.EdgeT) { - visited := map[string]bool{} - adjMat := map[string]map[string]bool{} - xsPerIteration := map[int]int{} +// BfsUpdatePositions updates the positions of the nodes using a layered layout algorithm that utilizes Breadth First Search. +func BfsUpdatePositions(nodes []graphtypes.Node, edges []graphtypes.EdgeT) { + adjMat := map[string]map[string]struct{}{} for _, edge := range edges { if adjMat[edge.Source] == nil { - adjMat[edge.Source] = map[string]bool{} + adjMat[edge.Source] = map[string]struct{}{} } - adjMat[edge.Source][edge.Target] = true + adjMat[edge.Source][edge.Target] = struct{}{} } - dfsHelper(nodes, edges, visited, adjMat, "v-0", xsPerIteration, 0, 0, 0) - - // handle islands - for i, node := range nodes { - if visited[node.Id] { + // the bread first search algorithm + queue := []string{"v-0"} + visited := map[string]struct{}{"v-0": {}} + xsPerIteration := map[int]int{} + for len(queue) > 0 { + // pop a vertex from the queue + current := queue[0] + queue = queue[1:] + // add its neighbours to the queue for processing later + neighbours := adjMat[current] + sortedNotVisitedNeighbours := []string{} + for neighbour := range neighbours { + if _, ok := visited[neighbour]; !ok { + sortedNotVisitedNeighbours = append(sortedNotVisitedNeighbours, neighbour) + visited[neighbour] = struct{}{} + } + } + sort.StringSlice(sortedNotVisitedNeighbours).Sort() + queue = append(queue, sortedNotVisitedNeighbours...) + // calculate the new position for the current node + idx := common.FindIndex(nodes, func(n graphtypes.Node) bool { return n.Id == current }) + if idx < 0 { + logrus.Errorf("failed to find a node for the vertex with id '%s'", current) continue } - logrus.Errorf("found an unvisited node: %+v", node) - + node := nodes[idx] iteration := node.Position.Y - - newX := 0 - if oldX, ok := xsPerIteration[iteration]; ok { - newX = oldX + 200 - } - xsPerIteration[iteration] = newX - node.Position.X = newX - + newX := xsPerIteration[iteration] + xsPerIteration[iteration] = newX + 200 newY := iteration * 200 + node.Position.X = newX node.Position.Y = newY - - nodes[i] = node + nodes[idx] = node } -} -func dfsHelper(nodes []graphtypes.Node, edges []graphtypes.EdgeT, visited map[string]bool, adjMat map[string]map[string]bool, currVertId string, xsPerIteration map[int]int, parentIteration, parentX, parentY int) { - visited[currVertId] = true - newX := 0 - newY := 0 - iteration := 0 + // handle islands for i, node := range nodes { - if node.Id != currVertId { + if _, ok := visited[node.Id]; ok { continue } - - iteration = node.Position.Y - - if iteration == parentIteration { - // mandatory/on-demand passthrough - newX = parentX - newY = parentY + 100 - } else { - newX = 0 - if oldX, ok := xsPerIteration[iteration]; ok { - newX = oldX + 200 - } - xsPerIteration[iteration] = newX - newY = iteration * 200 - } - + logrus.Errorf("found an unvisited node: %+v", node) + visited[node.Id] = struct{}{} + // calculate the new position for the current node + iteration := node.Position.Y + newX := xsPerIteration[iteration] + xsPerIteration[iteration] = newX + 200 + newY := iteration * 200 node.Position.X = newX node.Position.Y = newY nodes[i] = node - break - } - for neighbourVertId, ok := range adjMat[currVertId] { - if !ok || visited[neighbourVertId] { - // no edge from the current vertex to this neighbour vertex OR this neighbour has already been visited - continue - } - dfsHelper(nodes, edges, visited, adjMat, neighbourVertId, xsPerIteration, iteration, newX, newY) } }