From f2388af3970af22052a8cae5c2567b55086147ba Mon Sep 17 00:00:00 2001 From: jschramm Date: Tue, 5 Sep 2023 13:07:21 +0200 Subject: [PATCH] 563 Utils, applied Falko's suggestions (style, doc) --- .../IncrementalTreeMap/CorrectArea.cs | 14 +++++------ .../IncrementalTreeMap/FlipMove.cs | 2 +- .../IncrementalTreeMap/LocalMoves.cs | 2 +- .../NodeLayouts/IncrementalTreeMap/Segment.cs | 2 +- .../IncrementalTreeMap/StretchMove.cs | 2 +- .../NodeLayouts/IncrementalTreeMap/Utils.cs | 25 +++++++++++-------- .../NodeLayouts/IncrementalTreeMapLayout.cs | 4 +-- 7 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/CorrectArea.cs b/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/CorrectArea.cs index d25b02ea5d..8ba071c2a4 100644 --- a/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/CorrectArea.cs +++ b/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/CorrectArea.cs @@ -35,9 +35,9 @@ public static bool Correct(IList nodes, IncrementalTreeMapSetting settings // adjust the position of slicingSegments AdjustSliced(nodes, partition1, partition2, slicingSegment); - // recursive adjust the two sub layouts + // recursively adjust the two sub layouts // since both sublayouts are temporally independent from each other - // the segment that separates these must be considered as border (IsConst = true) + // the segment that separates these must be considered as a border (IsConst = true) slicingSegment.IsConst = true; Correct(partition1, settings); Correct(partition2, settings); @@ -205,9 +205,9 @@ private static void AdjustSliced( /// A gradient decent approach to recalibrate the layout. /// This approach 'moves' the segment in several steps, until the desired sizes are realized. /// In one step it calculates a shift for all segments at once - /// by set up a function that maps the position of the segments to the area of each node, - /// get the derivative of this function - /// and calculate a shift for the segment vector in the direction of the desired size vector. + /// by setting up a function that maps the position of the segments to the area of each node, + /// getting the derivative of this function + /// and calculating a shift for the segment vector in the direction of the desired size vector. /// /// nodes with layout /// the settings of the incremental tree map layout, @@ -248,7 +248,7 @@ private static Matrix JacobianMatrix( { int n = nodes.Count; Matrix matrix = Matrix.Build.Sparse(n, n - 1); - for(int indexNode = 0; indexNode < nodes.Count; indexNode++) + for (int indexNode = 0; indexNode < nodes.Count; indexNode++) { Node node = nodes[indexNode]; IDictionary segments = node.SegmentsDictionary(); @@ -353,4 +353,4 @@ private static bool CheckPositiveLength(IList nodes) return nodes.All(node => node.Rectangle.Width > 0 || node.Rectangle.Depth > 0); } } -} \ No newline at end of file +} diff --git a/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/FlipMove.cs b/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/FlipMove.cs index 4f1bafc7bd..6d8d656d2e 100644 --- a/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/FlipMove.cs +++ b/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/FlipMove.cs @@ -174,4 +174,4 @@ private void FlipOnHorizontalSegment(Node lowerNode, Node upperNode) /// private string DebuggerDisplay => "flip " + Node1.ID + " " + Node2.ID + " {clockwise}"; } -} \ No newline at end of file +} diff --git a/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/LocalMoves.cs b/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/LocalMoves.cs index bd046e8c14..0fe772ec78 100644 --- a/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/LocalMoves.cs +++ b/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/LocalMoves.cs @@ -1 +1 @@ -using System; using System.Collections.Generic; using System.Linq; using MathNet.Numerics.LinearAlgebra; using SEE.Game.City; using UnityEngine.Assertions; using static SEE.Layout.NodeLayouts.IncrementalTreeMap.Direction; namespace SEE.Layout.NodeLayouts.IncrementalTreeMap { /// /// Provides algorithms for adding and deleting nodes to a layout /// and an algorithm to improve visual quality of a layout. /// internal static class LocalMoves { /// /// Finds possible s for a specific segment. /// Examples are flipping the segment or stretching a node over the segment. /// /// the segment /// List of s private static IList FindLocalMoves(Segment segment) { List result = new List(); if (segment.IsConst) { return result; } if (segment.Side1Nodes.Count == 1 && segment.Side2Nodes.Count == 1) { result.Add(new FlipMove(segment.Side1Nodes.First(), segment.Side2Nodes.First(), true)); result.Add(new FlipMove(segment.Side1Nodes.First(), segment.Side2Nodes.First(), false)); return result; } if (segment.IsVertical) { Node upperNode1 = Utils.ArgMax(segment.Side1Nodes, x => x.Rectangle.Z); Node upperNode2 = Utils.ArgMax(segment.Side2Nodes, x => x.Rectangle.Z); Assert.IsTrue(upperNode1.SegmentsDictionary()[Upper] == upperNode2.SegmentsDictionary()[Upper]); Node lowerNode1 = Utils.ArgMin(segment.Side1Nodes, x => x.Rectangle.Z); Node lowerNode2 = Utils.ArgMin(segment.Side2Nodes, x => x.Rectangle.Z); Assert.IsTrue(lowerNode1.SegmentsDictionary()[Lower] == lowerNode2.SegmentsDictionary()[Lower]); result.Add(new StretchMove(upperNode1, upperNode2)); result.Add(new StretchMove(lowerNode1, lowerNode2)); return result; } Node rightNode1 = Utils.ArgMax(segment.Side1Nodes, x => x.Rectangle.X); Node rightNode2 = Utils.ArgMax(segment.Side2Nodes, x => x.Rectangle.X); Assert.IsTrue(rightNode1.SegmentsDictionary()[Right] == rightNode2.SegmentsDictionary()[Right]); Node leftNode1 = Utils.ArgMin(segment.Side1Nodes, x => x.Rectangle.X); Node leftNode2 = Utils.ArgMin(segment.Side2Nodes, x => x.Rectangle.X); Assert.IsTrue(leftNode1.SegmentsDictionary()[Left] == leftNode2.SegmentsDictionary()[Left]); result.Add(new StretchMove(rightNode1, rightNode2)); result.Add(new StretchMove(leftNode1, leftNode2)); return result; } /// /// Adds a node to the layout. /// Will NOT add to the list of . /// /// nodes that represent a layout /// the node that should be added public static void AddNode(IList nodes, Node newNode) { // node with rectangle with highest aspect ratio Node bestNode = Utils.ArgMax(nodes, x => x.Rectangle.AspectRatio()); newNode.Rectangle = bestNode.Rectangle.Clone(); IDictionary segments = bestNode.SegmentsDictionary(); foreach (Direction dir in Enum.GetValues(typeof(Direction))) { newNode.RegisterSegment(segments[dir], dir); } if (bestNode.Rectangle.Width >= bestNode.Rectangle.Depth) { // [bestNode]|[newNode] Segment newSegment = new Segment(isConst: false, isVertical: true); newNode.RegisterSegment(newSegment, Left); bestNode.RegisterSegment(newSegment, Right); bestNode.Rectangle.Width *= 0.5f; newNode.Rectangle.Width *= 0.5f; newNode.Rectangle.X = bestNode.Rectangle.X + bestNode.Rectangle.Width; } else { // [newNode] // --------- // [bestNode] Segment newSegment = new Segment(isConst: false, isVertical: false); newNode.RegisterSegment(newSegment, Lower); bestNode.RegisterSegment(newSegment, Upper); bestNode.Rectangle.Depth *= 0.5f; newNode.Rectangle.Depth *= 0.5f; newNode.Rectangle.Z = bestNode.Rectangle.Z + bestNode.Rectangle.Depth; } } /// /// Deletes a node from the layout. /// /// node to be deleted, part of a layout public static void DeleteNode(Node obsoleteNode) { // check whether node is grounded IDictionary segments = obsoleteNode.SegmentsDictionary(); bool isGrounded = false; if (segments[Left].Side2Nodes.Count == 1 && !segments[Left].IsConst) { isGrounded = true; //[E][O] Node[] expandingNodes = segments[Left].Side1Nodes.ToArray(); foreach (Node node in expandingNodes) { node.Rectangle.Width += obsoleteNode.Rectangle.Width; node.RegisterSegment(segments[Right], Right); } } else if (segments[Right].Side1Nodes.Count == 1 && !segments[Right].IsConst) { isGrounded = true; //[O][E] Node[] expandingNodes = segments[Right].Side2Nodes.ToArray(); foreach (Node node in expandingNodes) { node.Rectangle.X = obsoleteNode.Rectangle.X; node.Rectangle.Width += obsoleteNode.Rectangle.Width; node.RegisterSegment(segments[Left], Left); } } else if (segments[Lower].Side2Nodes.Count == 1 && !segments[Lower].IsConst) { isGrounded = true; //[O] //[E] Node[] expandingNodes = segments[Lower].Side1Nodes.ToArray(); foreach (Node node in expandingNodes) { node.Rectangle.Depth += obsoleteNode.Rectangle.Depth; node.RegisterSegment(segments[Upper], Upper); } } else if (segments[Upper].Side1Nodes.Count == 1 && !segments[Upper].IsConst) { isGrounded = true; //[E] //[O] Node[] expandingNodes = segments[Upper].Side2Nodes.ToArray(); foreach (Node node in expandingNodes) { node.Rectangle.Z = obsoleteNode.Rectangle.Z; node.Rectangle.Depth += obsoleteNode.Rectangle.Depth; node.RegisterSegment(segments[Lower], Lower); } } if (isGrounded) { foreach (Direction dir in Enum.GetValues(typeof(Direction))) { obsoleteNode.DeregisterSegment(dir); } } else { Segment bestSegment = Utils.ArgMin(segments.Values, x => x.Side1Nodes.Count + x.Side2Nodes.Count); IList moves = FindLocalMoves(bestSegment); Assert.IsTrue(moves.All(x => x is (StretchMove))); foreach (LocalMove move in moves) { if (move.Node1 != obsoleteNode && move.Node2 != obsoleteNode) { move.Apply(); DeleteNode(obsoleteNode); return; } } // We should never arrive here Assert.IsFalse(true); } } /// /// Searches the space of layouts that are similar to the layout of /// (in terms of distance in local moves). /// Apply the layout with the best visual quality to /// /// nodes that represent a layout /// settings for search public static void LocalMovesSearch(List nodes, IncrementalTreeMapSetting settings) { List<(List nodes, double visualQuality, List movesList)> allResults = RecursiveMakeMoves( nodes, new List(), settings); allResults.Add((nodes, AspectRatiosPNorm(nodes, settings.PNorm), new List())); List bestResult = Utils.ArgMin(allResults, x => x.Item2 * 10 + x.Item3.Count).Item1; IDictionary nodesDictionary = nodes.ToDictionary(n => n.ID, n => n); foreach (Node resultNode in bestResult) { nodesDictionary[resultNode.ID].Rectangle = resultNode.Rectangle; } Utils.CloneSegments(from: bestResult, to: nodesDictionary); } /// /// Makes recursively local moves on clones of the layout to find similar layouts with good visual quality. /// /// nodes that represent a layout /// moves that are done before in recursion /// the settings /// selection of reached layouts, as tuples of nodes, visual quality measure of the layout and /// the local moves that are applied to get this layout. private static List<(List nodes, double visualQuality, List movesList)> RecursiveMakeMoves( IList nodes, IList movesUntilNow, IncrementalTreeMapSetting settings) { List<(List nodes, double visualQuality, List movesList)> resultThisRecursion = new(); if (movesUntilNow.Count >= settings.localMovesDepth) { return resultThisRecursion; } ICollection relevantSegments; if (movesUntilNow.Count == 0) { relevantSegments = nodes.SelectMany(n => n.SegmentsDictionary().Values).ToHashSet(); } else { IEnumerable relevantNodes = movesUntilNow.SelectMany(m => new[] { m.Node1.ID, m.Node2.ID }) .Distinct() .Select(id => nodes.First(n => n.ID == id)); relevantSegments = relevantNodes.SelectMany(n => n.SegmentsDictionary().Values).ToHashSet(); } IEnumerable possibleMoves = relevantSegments.SelectMany(FindLocalMoves); foreach (LocalMove move in possibleMoves) { IDictionary nodeClonesDictionary = Utils.CloneGraph(nodes); List nodeClonesList = nodeClonesDictionary.Values.ToList(); LocalMove moveClone = move.Clone(nodeClonesDictionary); moveClone.Apply(); bool works = CorrectAreas.Correct(nodeClonesList, settings); if (!works) { continue; } List newMovesList = new List(movesUntilNow) { moveClone }; resultThisRecursion.Add( (nodeClonesList, AspectRatiosPNorm(nodeClonesList, settings.PNorm), newMovesList)); } resultThisRecursion.Sort((x, y) => x.Item2.CompareTo(y.Item2)); resultThisRecursion = resultThisRecursion.Take(settings.localMovesBranchingLimit).ToList(); List<(List nodes, double visualQuality, List movesList)> resultsNextRecursions = new(); foreach ((List resultNodes, double _, List resultMoves) in resultThisRecursion) { resultsNextRecursions.AddRange(RecursiveMakeMoves(resultNodes,resultMoves, settings)); } return resultThisRecursion.Concat(resultsNextRecursions).ToList(); } /// /// Measures the visual quality of a layout, based on the aspect ratio of . /// /// The nodes the should be assessed. /// Determines the specific norm. /// A measure for the visual quality of the nodes. /// Return value is greater than or equal to 1, while 1 means perfect visual quality private static double AspectRatiosPNorm(IList nodes, double p) { Vector aspectRatios = Vector.Build.DenseOfEnumerable(nodes.Select(n => n.Rectangle.AspectRatio())); return aspectRatios.Norm(p); } } } \ No newline at end of file +using System; using System.Collections.Generic; using System.Linq; using MathNet.Numerics.LinearAlgebra; using SEE.Game.City; using UnityEngine.Assertions; using static SEE.Layout.NodeLayouts.IncrementalTreeMap.Direction; namespace SEE.Layout.NodeLayouts.IncrementalTreeMap { /// /// Provides algorithms for adding and deleting nodes to a layout /// and an algorithm to improve visual quality of a layout. /// internal static class LocalMoves { /// /// Finds possible s for a specific segment. /// Examples are flipping the segment or stretching a node over the segment. /// /// the segment /// List of s private static IList FindLocalMoves(Segment segment) { List result = new List(); if (segment.IsConst) { return result; } if (segment.Side1Nodes.Count == 1 && segment.Side2Nodes.Count == 1) { result.Add(new FlipMove(segment.Side1Nodes.First(), segment.Side2Nodes.First(), true)); result.Add(new FlipMove(segment.Side1Nodes.First(), segment.Side2Nodes.First(), false)); return result; } if (segment.IsVertical) { Node upperNode1 = Utils.ArgMax(segment.Side1Nodes, x => x.Rectangle.Z); Node upperNode2 = Utils.ArgMax(segment.Side2Nodes, x => x.Rectangle.Z); Assert.IsTrue(upperNode1.SegmentsDictionary()[Upper] == upperNode2.SegmentsDictionary()[Upper]); Node lowerNode1 = Utils.ArgMin(segment.Side1Nodes, x => x.Rectangle.Z); Node lowerNode2 = Utils.ArgMin(segment.Side2Nodes, x => x.Rectangle.Z); Assert.IsTrue(lowerNode1.SegmentsDictionary()[Lower] == lowerNode2.SegmentsDictionary()[Lower]); result.Add(new StretchMove(upperNode1, upperNode2)); result.Add(new StretchMove(lowerNode1, lowerNode2)); return result; } Node rightNode1 = Utils.ArgMax(segment.Side1Nodes, x => x.Rectangle.X); Node rightNode2 = Utils.ArgMax(segment.Side2Nodes, x => x.Rectangle.X); Assert.IsTrue(rightNode1.SegmentsDictionary()[Right] == rightNode2.SegmentsDictionary()[Right]); Node leftNode1 = Utils.ArgMin(segment.Side1Nodes, x => x.Rectangle.X); Node leftNode2 = Utils.ArgMin(segment.Side2Nodes, x => x.Rectangle.X); Assert.IsTrue(leftNode1.SegmentsDictionary()[Left] == leftNode2.SegmentsDictionary()[Left]); result.Add(new StretchMove(rightNode1, rightNode2)); result.Add(new StretchMove(leftNode1, leftNode2)); return result; } /// /// Adds a node to the layout. /// Will NOT add to the list of . /// /// nodes that represent a layout /// the node that should be added public static void AddNode(IList nodes, Node newNode) { // node with rectangle with highest aspect ratio Node bestNode = Utils.ArgMax(nodes, x => x.Rectangle.AspectRatio()); newNode.Rectangle = bestNode.Rectangle.Clone(); IDictionary segments = bestNode.SegmentsDictionary(); foreach (Direction dir in Enum.GetValues(typeof(Direction))) { newNode.RegisterSegment(segments[dir], dir); } if (bestNode.Rectangle.Width >= bestNode.Rectangle.Depth) { // [bestNode]|[newNode] Segment newSegment = new Segment(isConst: false, isVertical: true); newNode.RegisterSegment(newSegment, Left); bestNode.RegisterSegment(newSegment, Right); bestNode.Rectangle.Width *= 0.5f; newNode.Rectangle.Width *= 0.5f; newNode.Rectangle.X = bestNode.Rectangle.X + bestNode.Rectangle.Width; } else { // [newNode] // --------- // [bestNode] Segment newSegment = new Segment(isConst: false, isVertical: false); newNode.RegisterSegment(newSegment, Lower); bestNode.RegisterSegment(newSegment, Upper); bestNode.Rectangle.Depth *= 0.5f; newNode.Rectangle.Depth *= 0.5f; newNode.Rectangle.Z = bestNode.Rectangle.Z + bestNode.Rectangle.Depth; } } /// /// Deletes a node from the layout. /// /// node to be deleted, part of a layout public static void DeleteNode(Node obsoleteNode) { // check whether node is grounded IDictionary segments = obsoleteNode.SegmentsDictionary(); bool isGrounded = false; if (segments[Left].Side2Nodes.Count == 1 && !segments[Left].IsConst) { isGrounded = true; //[E][O] Node[] expandingNodes = segments[Left].Side1Nodes.ToArray(); foreach (Node node in expandingNodes) { node.Rectangle.Width += obsoleteNode.Rectangle.Width; node.RegisterSegment(segments[Right], Right); } } else if (segments[Right].Side1Nodes.Count == 1 && !segments[Right].IsConst) { isGrounded = true; //[O][E] Node[] expandingNodes = segments[Right].Side2Nodes.ToArray(); foreach (Node node in expandingNodes) { node.Rectangle.X = obsoleteNode.Rectangle.X; node.Rectangle.Width += obsoleteNode.Rectangle.Width; node.RegisterSegment(segments[Left], Left); } } else if (segments[Lower].Side2Nodes.Count == 1 && !segments[Lower].IsConst) { isGrounded = true; //[O] //[E] Node[] expandingNodes = segments[Lower].Side1Nodes.ToArray(); foreach (Node node in expandingNodes) { node.Rectangle.Depth += obsoleteNode.Rectangle.Depth; node.RegisterSegment(segments[Upper], Upper); } } else if (segments[Upper].Side1Nodes.Count == 1 && !segments[Upper].IsConst) { isGrounded = true; //[E] //[O] Node[] expandingNodes = segments[Upper].Side2Nodes.ToArray(); foreach (Node node in expandingNodes) { node.Rectangle.Z = obsoleteNode.Rectangle.Z; node.Rectangle.Depth += obsoleteNode.Rectangle.Depth; node.RegisterSegment(segments[Lower], Lower); } } if (isGrounded) { foreach (Direction dir in Enum.GetValues(typeof(Direction))) { obsoleteNode.DeregisterSegment(dir); } } else { Segment bestSegment = Utils.ArgMin(segments.Values, x => x.Side1Nodes.Count + x.Side2Nodes.Count); IList moves = FindLocalMoves(bestSegment); Assert.IsTrue(moves.All(x => x is (StretchMove))); foreach (LocalMove move in moves) { if (move.Node1 != obsoleteNode && move.Node2 != obsoleteNode) { move.Apply(); DeleteNode(obsoleteNode); return; } } // We should never arrive here Assert.IsFalse(true); } } /// /// Searches the space of layouts that are similar to the layout of /// (in terms of distance in local moves). /// Apply the layout with the best visual quality to /// /// nodes that represent a layout /// settings for search public static void LocalMovesSearch(List nodes, IncrementalTreeMapSetting settings) { List<(List nodes, double visualQuality, List movesList)> allResults = RecursiveMakeMoves( nodes, new List(), settings); allResults.Add((nodes, AspectRatiosPNorm(nodes, settings.PNorm), new List())); List bestResult = Utils.ArgMin(allResults, x => x.Item2 * 10 + x.Item3.Count).Item1; IDictionary nodesDictionary = nodes.ToDictionary(n => n.ID, n => n); foreach (Node resultNode in bestResult) { nodesDictionary[resultNode.ID].Rectangle = resultNode.Rectangle; } Utils.CloneSegments(from: bestResult, to: nodesDictionary); } /// /// Makes recursively local moves on clones of the layout to find similar layouts with good visual quality. /// /// nodes that represent a layout /// moves that are done before in recursion /// the settings /// selection of reached layouts, as tuples of nodes, visual quality measure of the layout and /// the local moves that are applied to get this layout. private static List<(List nodes, double visualQuality, List movesList)> RecursiveMakeMoves( IList nodes, IList movesUntilNow, IncrementalTreeMapSetting settings) { List<(List nodes, double visualQuality, List movesList)> resultThisRecursion = new(); if (movesUntilNow.Count >= settings.localMovesDepth) { return resultThisRecursion; } ICollection relevantSegments; if (movesUntilNow.Count == 0) { relevantSegments = nodes.SelectMany(n => n.SegmentsDictionary().Values).ToHashSet(); } else { IEnumerable relevantNodes = movesUntilNow.SelectMany(m => new[] { m.Node1.ID, m.Node2.ID }) .Distinct() .Select(id => nodes.First(n => n.ID == id)); relevantSegments = relevantNodes.SelectMany(n => n.SegmentsDictionary().Values).ToHashSet(); } IEnumerable possibleMoves = relevantSegments.SelectMany(FindLocalMoves); foreach (LocalMove move in possibleMoves) { IDictionary nodeClonesDictionary = Utils.CloneGraph(nodes); List nodeClonesList = nodeClonesDictionary.Values.ToList(); LocalMove moveClone = move.Clone(nodeClonesDictionary); moveClone.Apply(); bool works = CorrectAreas.Correct(nodeClonesList, settings); if (!works) { continue; } List newMovesList = new List(movesUntilNow) { moveClone }; resultThisRecursion.Add( (nodeClonesList, AspectRatiosPNorm(nodeClonesList, settings.PNorm), newMovesList)); } resultThisRecursion.Sort((x, y) => x.Item2.CompareTo(y.Item2)); resultThisRecursion = resultThisRecursion.Take(settings.localMovesBranchingLimit).ToList(); List<(List nodes, double visualQuality, List movesList)> resultsNextRecursions = new(); foreach ((List resultNodes, double _, List resultMoves) in resultThisRecursion) { resultsNextRecursions.AddRange(RecursiveMakeMoves(resultNodes,resultMoves, settings)); } return resultThisRecursion.Concat(resultsNextRecursions).ToList(); } /// /// Measures the visual quality of a layout, based on the aspect ratio of . /// /// The nodes the should be assessed. /// Determines the specific norm. /// A measure for the visual quality of the nodes. /// Return value is greater than or equal to 1, while 1 means perfect visual quality private static double AspectRatiosPNorm(IList nodes, double p) { Vector aspectRatios = Vector.Build.DenseOfEnumerable(nodes.Select(n => n.Rectangle.AspectRatio())); return aspectRatios.Norm(p); } } } \ No newline at end of file diff --git a/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/Segment.cs b/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/Segment.cs index 90994e9477..4584756305 100644 --- a/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/Segment.cs +++ b/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/Segment.cs @@ -27,7 +27,7 @@ public Segment(bool isConst, bool isVertical) /// /// Is true if the segment is a border of the layout. /// In most cases that means that or are empty - /// and layout has i.g. 4 const segments. + /// and layout has e.g. 4 const segments. /// public bool IsConst { get; set; } diff --git a/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/StretchMove.cs b/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/StretchMove.cs index f333498a74..d42a6bcd08 100644 --- a/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/StretchMove.cs +++ b/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/StretchMove.cs @@ -215,4 +215,4 @@ private void StretchUpperOverHorizontal(Node lowerNode, Node upperNode) /// private string DebuggerDisplay => "stretch " + Node1.ID + " " + Node2.ID; } -} \ No newline at end of file +} diff --git a/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/Utils.cs b/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/Utils.cs index 12d40f5db4..cb5f703661 100644 --- a/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/Utils.cs +++ b/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMap/Utils.cs @@ -11,23 +11,24 @@ namespace SEE.Layout.NodeLayouts.IncrementalTreeMap internal static class Utils { /// - /// arg max function, returns a item of a collection, that maximizes a function. + /// Returns the item of the given collection that maximizes the given function. /// - /// - /// function to be maximized - /// + /// The collection whose maximum with respect to + /// shall be returned + /// The function to be maximized + /// Item of that maximizes public static T ArgMax(ICollection collection, Func eval) { IComparable bestVal = collection.Max(eval); return collection.First(x => eval(x).CompareTo(bestVal) == 0); } - /// - /// arg min function, returns a item of a collection, that minimizes a function. + /// Returns the item of the given collection that minimizes the given function. /// - /// - /// function to be minimized - /// + /// The collection whose minimum with respect to + /// shall be returned + /// The function to be minimized + /// Item of that minimizes public static T ArgMin(ICollection collection, Func eval) { IComparable bestVal = collection.Min(eval); @@ -84,7 +85,7 @@ public static IDictionary CloneGraph(IList nodes) Rectangle = node.Rectangle.Clone(), DesiredSize = node.DesiredSize } ); - CloneSegments(nodes, clonesMap); + CloneSegments(from : nodes,to : clonesMap); return clonesMap; } @@ -97,10 +98,12 @@ public static IDictionary CloneGraph(IList nodes) /// dictionary that maps ID to nodes that should get the segment structure public static void CloneSegments(IEnumerable from, IDictionary to) { - HashSet segments = from.SelectMany(n => n.SegmentsDictionary().Values).ToHashSet(); + IEnumerable segments = from.SelectMany(n => n.SegmentsDictionary().Values).Distinct(); foreach (Segment segment in segments) { Segment segmentClone = new Segment(segment.IsConst, segment.IsVertical); + // Segment.SidesXNodes must be copied (.ToList()) because else in the case 'from == to' + // it would change the list while iterating over it. foreach (Node node in segment.Side1Nodes.ToList()) { to[node.ID].RegisterSegment(segmentClone, diff --git a/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMapLayout.cs b/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMapLayout.cs index 868cc2463d..f2e09161b6 100644 --- a/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMapLayout.cs +++ b/Assets/SEE/Layout/NodeLayouts/IncrementalTreeMapLayout.cs @@ -288,7 +288,7 @@ private void SetupNodeLists( /// with the same id as a node in . The result will contain null /// if there is a root in the old layout with an equivalent node in /// - /// + /// nodes in this graph /// Collection of parent nodes from . private ICollection ParentsInOldGraph(IEnumerable nodes) { @@ -362,4 +362,4 @@ public override bool UsesEdgesAndSublayoutNodes() return false; } } -} \ No newline at end of file +}