using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using Microsoft.Msagl.Core; using Microsoft.Msagl.Core.DataStructures; using Microsoft.Msagl.Core.Geometry.Curves; using Microsoft.Msagl.Core.Layout; using Microsoft.Msagl.Core.Routing; #if USE_PHYLOTREE using Microsoft.Msagl.Prototype.Phylo; #endif namespace Microsoft.Msagl.Layout.Layered { public enum SnapToGridByY { None, Top, Bottom } /// /// controls many properties of the layout algorithm /// public sealed class SugiyamaLayoutSettings : LayoutAlgorithmSettings { double yLayerSep = 10*3; /// /// Separation between to neighboring layers /// public double LayerSeparation { get { return yLayerSep; } set { yLayerSep = Math.Max(10, value); } } /// /// adds a constraint to keep one node to the left of another on the same layer /// /// /// public void AddLeftRightConstraint(Node leftNode, Node rightNode) { HorizontalConstraints.LeftRightConstraints.Insert(new Tuple(leftNode, rightNode)); } /// /// removes a left-right constraint from /// /// /// public void RemoveLeftRightConstraint(Node leftNode, Node rightNode) { HorizontalConstraints.LeftRightConstraints.Remove(new Tuple(leftNode, rightNode)); } readonly VerticalConstraintsForSugiyama verticalConstraints = new VerticalConstraintsForSugiyama(); /// /// minLayer, maxLayer, same layer, up-down, up-down vertical and left-right constraints are supported by this class /// public VerticalConstraintsForSugiyama VerticalConstraints { get { return verticalConstraints; } } readonly HorizontalConstraintsForSugiyama horizontalConstraints = new HorizontalConstraintsForSugiyama(); internal HorizontalConstraintsForSugiyama HorizontalConstraints { get { return horizontalConstraints; } } /// /// Pins the nodes of the list to the max layer and /// public void PinNodesToMaxLayer(params Node[] nodes) { ValidateArg.IsNotNull(nodes, "nodes"); for (int i = 0; i < nodes.Length; i++) VerticalConstraints.PinNodeToMaxLayer(nodes[i]); } /// /// Pins the nodes of the list to the min layer and /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ToMin")] public void PinNodesToMinLayer(params Node[] nodes) { ValidateArg.IsNotNull(nodes, "nodes"); for (int i = 0; i < nodes.Length; i++) VerticalConstraints.PinNodeToMinLayer(nodes[i]); } /// /// adds a same layer constraint /// public void PinNodesToSameLayer(params Node[] nodes) { ValidateArg.IsNotNull(nodes, "nodes"); for (int i = 1; i < nodes.Length; i++) VerticalConstraints.SameLayerConstraints.Insert(new Tuple(nodes[0], nodes[i])); } /// /// these nodes belong to the same layer and are adjacent positioned from left to right /// /// public void AddSameLayerNeighbors(params Node[] neighbors) { AddSameLayerNeighbors(new List(neighbors)); } /// /// these nodes belong to the same layer and are adjacent positioned from left to right /// /// public void AddSameLayerNeighbors(IEnumerable neighbors) { var neibs = new List(neighbors); HorizontalConstraints.AddSameLayerNeighbors(neibs); for (int i = 0; i < neibs.Count - 1; i++) VerticalConstraints.SameLayerConstraints.Insert(new Tuple(neibs[i], neibs[i + 1])); } /// /// adds a pair of adjacent neighbors /// /// /// public void AddSameLayerNeighbors(Node leftNode, Node rightNode) { HorizontalConstraints.AddSameLayerNeighborsPair(leftNode, rightNode); VerticalConstraints.SameLayerConstraints.Insert(new Tuple(leftNode, rightNode)); } /// /// /// public void RemoveAllConstraints() { HorizontalConstraints.Clear(); VerticalConstraints.Clear(); } /// /// adds an up-down constraint to the couple of nodes /// /// /// public void AddUpDownConstraint(Node upperNode, Node lowerNode) { VerticalConstraints.UpDownConstraints.Insert(new Tuple(upperNode, lowerNode)); } /// /// adds a constraint where the top node center is positioned exactly above the lower node center /// /// /// public void AddUpDownVerticalConstraint(Node upperNode, Node lowerNode) { VerticalConstraints.UpDownConstraints.Insert(new Tuple(upperNode, lowerNode)); HorizontalConstraints.UpDownVerticalConstraints.Insert(new Tuple(upperNode, lowerNode)); } /// /// adds vertical up down constraints udDownIds[0]->upDownIds[1]-> ... -> upDownsIds[upDownIds.Length-1] /// /// public void AddUpDownVerticalConstraints(params Node[] upDownNodes) { ValidateArg.IsNotNull(upDownNodes, "upDownNodes"); for (int i = 1; i < upDownNodes.Length; i++) AddUpDownVerticalConstraint(upDownNodes[i - 1], upDownNodes[i]); } /// /// if set to true the algorithm only calculates the layers and exits /// public bool LayeringOnly { get; set; } int repetitionCoefficentForOrdering = 1; /// /// This coefficient if set to the value greater than 1 will force the algorithm to search for layouts with fewer edge crossings /// #if PROPERTY_GRID_SUPPORT [DisplayName("Repetition coefficient for ordering")] [Description( "This coefficient if set to the value greater than 1 will force the algorithm to search for layouts with fewer edge crossings" )] [DefaultValue(1)] #endif public int RepetitionCoefficientForOrdering { get { return repetitionCoefficentForOrdering; } set { repetitionCoefficentForOrdering = Math.Max(value, 1); } } int randomSeedForOrdering = 1; /// /// The seed for the random element inside of the ordering /// #if PROPERTY_GRID_SUPPORT [DisplayName("Random seed for ordering")] [Description("The seed for the random element inside of the ordering")] [DefaultValue(1)] #endif public int RandomSeedForOrdering { get { return randomSeedForOrdering; } set { randomSeedForOrdering = value; } } int noGainStepsBound = 5; /// /// Maximal number of sequential steps without gain in the adjacent swap process /// #if PROPERTY_GRID_SUPPORT [DisplayName("No gain steps bound")] [Description("Maximal number of sequential steps without gain in the adjacent swap process")] [DefaultValue(5)] #endif public int NoGainAdjacentSwapStepsBound { get { return noGainStepsBound; } set { noGainStepsBound = value; } } int maxNumberOfPassesInOrdering = 24; /// /// Maximal number of passes over layers applying the median algorithm in ordering /// #if PROPERTY_GRID_SUPPORT [DisplayName("Maximal number of passes in ordering")] [Description("Maximal number of passes over layers applying the median algorithm in ordering")] [DefaultValue(24)] #endif public int MaxNumberOfPassesInOrdering { get { return maxNumberOfPassesInOrdering; } set { maxNumberOfPassesInOrdering = value; } } int groupSplit = 2; /// /// The ratio of the group splitting algorithm used in the spatial hierarchy constructions for edge routing /// #if PROPERTY_GRID_SUPPORT [DisplayName("Group split ratio")] [Description("The ratio of the group splitting algorithm used in the spatial hierarchy constructions")] [DefaultValue(2)] #endif public int GroupSplit { get { return groupSplit; } set { groupSplit = value; } } double labelCornersPreserveCoefficient = 0.1; /// /// We create a hexagon for a label boundary: this coeffiecient defines the ratio of the top and the bottom side to the width of the label /// #if PROPERTY_GRID_SUPPORT [DisplayName("Edge label crossing coefficient")] [Description( "The coefficient from 0 to 1 controlling the edge crossing with its label.\n 1 - allowing only a shallow crossing which can cause a sharp edge bend\n" + "0 - allowing a deeper crossing resulting in a smoother edge")] [DefaultValue(0.1)] #endif public double LabelCornersPreserveCoefficient { get { return labelCornersPreserveCoefficient; } set { labelCornersPreserveCoefficient = value; } } int brandesThreshold = 600; /// /// When the number of vertices in the proper layered graph is the threshold or more we switch to /// the faster, but not so accurate, method for x-coordinates calculations. /// #if PROPERTY_GRID_SUPPORT [DisplayName("Fast x-coordinate assignment algorithm threshold")] [Description( "If the number of vertices in the proper layered graph is more than the value we switch to the fast x-coordinate assignment algorithm of Brandes and Kopf" )] [DefaultValue(600)] #endif [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Brandes")] public int BrandesThreshold { get { return brandesThreshold; } set { brandesThreshold = value; } } /// /// Constructor /// public SugiyamaLayoutSettings() { this.EdgeRoutingSettings.EdgeRoutingMode = EdgeRoutingMode.SugiyamaSplines; } /// /// The only function one needs to call to calculate the layout. /// /// The layout info will be inserted in to the corresponding fields of the graph /// The settings for the algorithm. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope"), SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object)")] internal static LayeredLayoutEngine CalculateLayout(GeometryGraph msaglGraph, SugiyamaLayoutSettings settings, CancelToken cancelToken) { var engine = new LayeredLayoutEngine(msaglGraph, settings); #if USE_PHYLOTREE PhyloTree phyloTree = msaglGraph as PhyloTree; if (phyloTree != null) { var pc=new PhyloTreeLayoutCalclulation(phyloTree, settings, engine.IntGraph, engine.Database); pc.Run(); } else #endif engine.Run(cancelToken); return engine; } /// /// Clones the object /// /// public override LayoutAlgorithmSettings Clone() { return MemberwiseClone() as LayoutAlgorithmSettings; } double minimalWidth; /// /// The resulting layout should be not more narrow than this value. /// public double MinimalWidth { get { return minimalWidth; } set { minimalWidth = Math.Max(value, 0); } } double minimalHeight; /// /// The resulting layout should at least as high as this this value /// public double MinimalHeight { get { return minimalHeight; } set { minimalHeight = Math.Max(value, 0); } } double minNodeHeight = 72*0.5/4; double minNodeWidth = 72*0.75/4; /// /// The minimal node height /// public double MinNodeHeight { get { return minNodeHeight; } set { minNodeHeight = Math.Max(0.4, value); } } /// /// The minimal node width /// public double MinNodeWidth { get { return minNodeWidth; } set { minNodeWidth = Math.Max(0.8, value); } } PlaneTransformation transformation = PlaneTransformation.UnitTransformation; /// /// This transformation is to be applied to the standard top - bottom layout. /// However, node boundaries remain unchanged. /// public PlaneTransformation Transformation { get { return transformation; } set { transformation = value; } } double aspectRatio; /// /// The ratio width/height of the final layout. /// The value zero means that the aspect ratio has not been set. /// public double AspectRatio { get { return aspectRatio; } set { aspectRatio = value; } } internal double ActualLayerSeparation(bool layersAreDoubled) { return layersAreDoubled ? LayerSeparation/2.0 : LayerSeparation; } double maxAspectRatioEccentricity = 5; /// /// InitialLayoutByCluster requires the layered layout aspect ratio /// to be in the range [1/MaxAspectRatioEccentricity, MaxAspectRatioEccentricity] /// before switching to FallbackLayoutSettings /// public double MaxAspectRatioEccentricity { get { return maxAspectRatioEccentricity; } set { maxAspectRatioEccentricity = value; } } /// /// If the aspect ratio of Layered layout is too slow, use the following layout settings instead /// public LayoutAlgorithmSettings FallbackLayoutSettings { get; set; } SnapToGridByY snapToGridByY = SnapToGridByY.None; public SnapToGridByY SnapToGridByY { get { return snapToGridByY; } set { snapToGridByY = value; } } double gridSizeByY = 0; public double GridSizeByY { get { return gridSizeByY; } set { gridSizeByY = value; } } double gridSizeByX = 0; public double GridSizeByX { get { return gridSizeByX; } set { gridSizeByX = value; } } } }