/// <summary> /// Creates a new cluster with no minimum size within the specified parent cluster. Clusters allow creating a subset of /// nodes that must be within a distinct rectangle. /// </summary> /// <param name="parentCluster">The cluster this cluster is to be a member of; if null, this is the root of a /// new hierarchy, otherwise must be non-NULL (perhaps DefaultClusterHierarchy).</param> /// <param name="userData">An object that is passed through.</param> /// <param name="openBorderInfo">Information about the Left (if isHorizontal, else Top) border.</param> /// <param name="closeBorderInfo">Information about the Right (if isHorizontal, else Bottom) border.</param> /// <param name="openBorderInfoP">Same as OpenBorder, but in the secondary (Perpendicular) axis.</param> /// <param name="closeBorderInfoP">Same as CloseBorder, but in the secondary (Perpendicular) axis.</param> /// <returns>The new Cluster.</returns> /// public OverlapRemovalCluster AddCluster(OverlapRemovalCluster parentCluster, object userData, BorderInfo openBorderInfo, BorderInfo closeBorderInfo, BorderInfo openBorderInfoP, BorderInfo closeBorderInfoP) { return(AddCluster(parentCluster, userData, 0.0 /*minSize*/, 0.0 /*minSizeP*/, openBorderInfo, closeBorderInfo, openBorderInfoP, closeBorderInfoP)); }
/// <summary> /// Add a Cluster with default border information and specified minimum sizes. /// </summary> /// <param name="parentCluster">The cluster this cluster is to be a member of; if null, this is the root of a /// new hierarchy, otherwise must be non-NULL (perhaps DefaultClusterHierarchy).</param> /// <param name="userData">An object that is passed through.</param> /// <param name="minimumSize">Minimum cluster size along the primary axis.</param> /// <param name="minimumSizeP">Minimum cluster size along the perpendicular axis.</param> /// <returns>The new Cluster.</returns> public OverlapRemovalCluster AddCluster(OverlapRemovalCluster parentCluster, object userData, double minimumSize, double minimumSizeP) { return(AddCluster(parentCluster, userData, minimumSize, minimumSizeP, new BorderInfo(0.0), new BorderInfo(0.0), new BorderInfo(0.0), new BorderInfo(0.0))); // 0.0 margins }
private void AddOlapNode(ConstraintGenerator generator, OverlapRemovalCluster olapParentCluster, FiNode filNode, InitialCenterDelegateType nodeCenter) { // If the node already has an mOlapNode, it's already in a cluster (in a different // hierarchy); we just add it to the new cluster. if (null != filNode.getOlapNode(IsHorizontal)) { generator.AddNodeToCluster(olapParentCluster, filNode.getOlapNode(IsHorizontal)); return; } var center = nodeCenter(filNode); // We need to create a new Node in the Generator. if (IsHorizontal) { // Add the Generator node with the X-axis coords primary, Y-axis secondary. filNode.mOlapNodeX = generator.AddNode(olapParentCluster, filNode /* userData */ , center.X, center.Y , filNode.Width, filNode.Height, filNode.stayWeight); } else { // Add the Generator node with the Y-axis coords primary, X-axis secondary. filNode.mOlapNodeY = generator.AddNode(olapParentCluster, filNode /* userData */ , center.Y, center.X , filNode.Height, filNode.Width, filNode.stayWeight); } }
private void VerifyClustersAreWithinParentClusterBounds(IEnumerable <ClusterDef> iterClusterDefs, double epsilon, ref bool succeeded) { foreach (ClusterDef clusDef in iterClusterDefs) { // Empty clusters have nothing to verify // Clusters at the root of a hierarchy have no borders. if (clusDef.IsEmpty) { continue; } if (null != clusDef.ParentClusterDef) { // Clusters at the root of a hierarchy have no borders. if (clusDef.ParentClusterDef.IsNewHierarchy) { continue; } // Is varCheck's left or right border out of bounds? Negative overlap means yes. // We're testing for nested variables here so include cluster margin at the relevant border. double leftOverlap = clusDef.Left - clusDef.ParentClusterDef.Left - OverlapRemovalCluster.CalcBorderWidth(clusDef.ParentClusterDef.LeftBorderInfo.InnerMargin) - this.MinPaddingX; double rightOverlap = clusDef.ParentClusterDef.Right - clusDef.Right - OverlapRemovalCluster.CalcBorderWidth(clusDef.ParentClusterDef.RightBorderInfo.InnerMargin) - this.MinPaddingX; double topOverlap = clusDef.Top - clusDef.ParentClusterDef.Top - OverlapRemovalCluster.CalcBorderWidth(clusDef.ParentClusterDef.TopBorderInfo.InnerMargin) - this.MinPaddingY; double bottomOverlap = clusDef.ParentClusterDef.Bottom - clusDef.Bottom - OverlapRemovalCluster.CalcBorderWidth(clusDef.ParentClusterDef.BottomBorderInfo.InnerMargin) - this.MinPaddingY; if ((leftOverlap < -epsilon) || (rightOverlap < -epsilon) || (topOverlap < -epsilon) || (bottomOverlap < -epsilon)) { // Uh oh. this.WriteLine("Error {0}: Cluster '{1}' is outside ParentCluster '{2}' bounds", FailTag("ClusParentClus"), clusDef.ClusterId, clusDef.ParentClusterDef.ClusterId); this.WriteLine(" Cluster {0}: L/R T/B {1:F5}/{2:F5} {3:F5}/{4:F5}", clusDef.ClusterId, clusDef.Left, clusDef.Right, clusDef.Top, clusDef.Bottom); this.WriteLine(" Parent {0}: L/R T/B {1:F5}/{2:F5} {3:F5}/{4:F5}", clusDef.ParentClusterDef.ClusterId, clusDef.ParentClusterDef.Left, clusDef.ParentClusterDef.Right, clusDef.ParentClusterDef.Top, clusDef.ParentClusterDef.Bottom); this.WriteLine(" Overlap : L/R T/B {0:F5}/{1:F5} {2:F5}/{3:F5}", leftOverlap, rightOverlap, topOverlap, bottomOverlap); succeeded = false; } } // endif null != (object)varCheck.ParentClusterDef } }
/// <summary> /// Add a new variable to the ConstraintGenerator. /// </summary> /// <param name="initialCluster">The cluster this node is to be a member of. It may not be null; pass /// DefaultClusterHierarchy to create a node at the lowest level. Subsequently a node /// may be added to additional clusters, but only to one cluster per hierarchy.</param> /// <param name="userData">An object that is passed through.</param> /// <param name="position">Position of the node in the primary axis; if isHorizontal, it contains horizontal /// position and size, else it contains vertical position and size.</param> /// <param name="size">Size of the node in the primary axis.</param> /// <param name="positionP">Position of the node in the secondary (Perpendicular) axis.</param> /// <param name="sizeP">Size of the node in the secondary (Perpendicular) axis.</param> /// <param name="weight">Weight of the node (indicates how freely it should move).</param> /// <returns>The created node.</returns> public OverlapRemovalNode AddNode(OverlapRemovalCluster initialCluster, object userData, double position, double positionP, double size, double sizeP, double weight) { ValidateArg.IsNotNull(initialCluster, "initialCluster"); // @@PERF: Currently every node will have at least one constraint generated if there are any // other nodes along its line, regardless of whether the perpendicular coordinates result in overlap. // It might be worthwhile to add a check to avoid constraint generation in the case that there cannot // be such an overlap on a line, or if the nodes are separated by some amount of distance. Debug.Assert(null != initialCluster, "initialCluster must not be null"); var nodNew = new OverlapRemovalNode(this.nextNodeId++, userData, position, positionP, size, sizeP, weight); initialCluster.AddNode(nodNew); return(nodNew); }
public void AddNodeToCluster(OverlapRemovalCluster cluster, OverlapRemovalNode node) { // Node derives from Cluster so make sure we don't have this - the only way to create // cluster hierarchies is by AddCluster. ValidateArg.IsNotNull(cluster, "cluster"); if (node is OverlapRemovalCluster) { throw new InvalidOperationException( #if DEBUG "Argument 'node' must not be a Cluster" #endif // DEBUG ); } cluster.AddNode(node); }
internal OverlapRemovalCluster CreateCluster(ConstraintGenerator conGen) { if (null == this.Cluster) { // Ensure the parent Cluster is created as we must pass it as a parameter. // Don't call this.ParentCluster here because it's got an Assert that our // cluster has been created - and it hasn't, yet; that's what we're doing here. // clusParent remains null if this.IsNewHierarchy. OverlapRemovalCluster clusParent = null; if (!this.IsNewHierarchy) { clusParent = (null == this.ParentClusterDef) ? conGen.DefaultClusterHierarchy : this.ParentClusterDef.CreateCluster(conGen); } if (conGen.IsHorizontal) { this.Cluster = conGen.AddCluster( clusParent, this.ClusterId, this.MinimumSizeX, this.MinimumSizeY, this.LeftBorderInfo, this.RightBorderInfo, this.TopBorderInfo, this.BottomBorderInfo); } else { // Use horizontal PostX BorderInfos due to MinimumSize; see PostX(). this.Cluster = conGen.AddCluster( clusParent, this.ClusterId, this.MinimumSizeY, this.MinimumSizeX, this.TopBorderInfo, this.BottomBorderInfo, this.PostXLeftBorderInfo, this.PostXRightBorderInfo); } } return(this.Cluster); }
/// <summary> /// Creates a new cluster with a minimum size within the specified parent cluster. Clusters allow creating a subset of /// nodes that must be within a distinct rectangle. /// </summary> /// <param name="parentCluster">The cluster this cluster is to be a member of; if null, this is the root of a /// new hierarchy, otherwise must be non-NULL (perhaps DefaultClusterHierarchy).</param> /// <param name="userData">An object that is passed through.</param> /// <param name="minimumSize">Minimum cluster size along the primary axis.</param> /// <param name="minimumSizeP">Minimum cluster size along the perpendicular axis.</param> /// <param name="openBorderInfo">Information about the Left (if isHorizontal, else Top) border.</param> /// <param name="closeBorderInfo">Information about the Right (if isHorizontal, else Bottom) border.</param> /// <param name="openBorderInfoP">Same as OpenBorder, but in the secondary (Perpendicular) axis.</param> /// <param name="closeBorderInfoP">Same as CloseBorder, but in the secondary (Perpendicular) axis.</param> /// <returns>The new Cluster.</returns> /// public OverlapRemovalCluster AddCluster(OverlapRemovalCluster parentCluster, object userData, double minimumSize, double minimumSizeP, BorderInfo openBorderInfo, BorderInfo closeBorderInfo, BorderInfo openBorderInfoP, BorderInfo closeBorderInfoP) { var newCluster = new OverlapRemovalCluster(this.nextNodeId, parentCluster, userData, minimumSize, minimumSizeP, this.Padding, this.PaddingP, this.ClusterPadding, this.ClusterPaddingP, openBorderInfo, closeBorderInfo, openBorderInfoP, closeBorderInfoP); this.nextNodeId += OverlapRemovalCluster.NumInternalNodes; if (null == parentCluster) { this.clusterHierarchies.Add(newCluster); } else { // @@DCR: Enforce that Clusters live in only one hierarchy - they can have only one parent, so add a // Cluster.parentCluster to enforce this. parentCluster.AddNode(newCluster); } return(newCluster); }
/// <summary> /// Add a Cluster with default border information and no minimum sizes. /// </summary> /// <param name="parentCluster">The cluster this cluster is to be a member of; if null, this is the root of a /// new hierarchy, otherwise must be non-NULL (perhaps DefaultClusterHierarchy).</param> /// <param name="userData">An object that is passed through.</param> /// <returns>The new Cluster.</returns> public OverlapRemovalCluster AddCluster(OverlapRemovalCluster parentCluster, object userData) { return(AddCluster(parentCluster, userData, new BorderInfo(0.0), new BorderInfo(0.0), new BorderInfo(0.0), new BorderInfo(0.0))); // 0.0 margins }
private void AddOlapClusters(ConstraintGenerator generator, OverlapRemovalCluster olapParentCluster, Cluster incClus, InitialCenterDelegateType nodeCenter) { LayoutAlgorithmSettings settings = clusterSettings(incClus); double nodeSeparationH = settings.NodeSeparation; double nodeSeparationV = settings.NodeSeparation + 1e-4; double innerPaddingH = settings.ClusterMargin; double innerPaddingV = settings.ClusterMargin + 1e-4; // Creates the OverlapRemoval (Olap) Cluster/Node objects for our FastIncrementalLayout (FIL) objects. // If !isHorizontal this overwrites the Olap members of the Incremental.Clusters and Msagl.Nodes. // First create the olapCluster for the current incCluster. If olapParentCluster is null, then // incCluster is the root of a new hierarchy. RectangularClusterBoundary rb = incClus.RectangularBoundary; if (IsHorizontal) { rb.olapCluster = generator.AddCluster( olapParentCluster, incClus /* userData */, rb.MinWidth, rb.MinHeight, rb.LeftBorderInfo, rb.RightBorderInfo, rb.BottomBorderInfo, rb.TopBorderInfo); rb.olapCluster.NodePadding = nodeSeparationH; rb.olapCluster.NodePaddingP = nodeSeparationV; rb.olapCluster.ClusterPadding = innerPaddingH; rb.olapCluster.ClusterPaddingP = innerPaddingV; } else { var postXLeftBorderInfo = new BorderInfo(rb.LeftBorderInfo.InnerMargin, rb.Rect.Left, rb.LeftBorderInfo.Weight); var postXRightBorderInfo = new BorderInfo(rb.RightBorderInfo.InnerMargin, rb.Rect.Right, rb.RightBorderInfo.Weight); rb.olapCluster = generator.AddCluster( olapParentCluster, incClus /* userData */, rb.MinHeight, rb.MinWidth, rb.BottomBorderInfo, rb.TopBorderInfo, postXLeftBorderInfo, postXRightBorderInfo); rb.olapCluster.NodePadding = nodeSeparationV; rb.olapCluster.NodePaddingP = nodeSeparationH; rb.olapCluster.ClusterPadding = innerPaddingV; rb.olapCluster.ClusterPaddingP = innerPaddingH; } rb.olapCluster.TranslateChildren = rb.GenerateFixedConstraints; // Note: Incremental.Cluster always creates child List<Cluster|Node> so we don't have to check for null here. // Add our child nodes. foreach (var filNode in incClus.Nodes) { AddOlapNode(generator, rb.olapCluster, (FiNode)filNode.AlgorithmData, nodeCenter); } // Now recurse through all child clusters. foreach (var incChildClus in incClus.Clusters) { AddOlapClusters(generator, rb.olapCluster, incChildClus, nodeCenter); } }
private void VerifyClustersAreTight(IEnumerable <ClusterDef> iterClusterDefs, double epsilon, ref bool succeeded) { // Verify that clusters have at least one node or cluster immediately adjacent to // the cluster borders (i.e. verify the clusters are tight). foreach (ClusterDef clusDef in iterClusterDefs) { // Empty clusters have nothing to verify if (clusDef.IsEmpty) { continue; } // Clusters at the root of a hierarchy have no borders. if (clusDef.IsNewHierarchy) { continue; } double minLeft = double.MaxValue; double maxRight = double.MinValue; double minTop = double.MaxValue; double maxBottom = double.MinValue; foreach (VariableDef varChild in clusDef.Variables) { minLeft = Math.Min(minLeft, varChild.Left); maxRight = Math.Max(maxRight, varChild.Right); minTop = Math.Min(minTop, varChild.Top); maxBottom = Math.Max(maxBottom, varChild.Bottom); } foreach (ClusterDef clusChild in clusDef.Clusters) { if (clusChild.IsEmpty) { continue; } minLeft = Math.Min(minLeft, clusChild.Left); maxRight = Math.Max(maxRight, clusChild.Right); minTop = Math.Min(minTop, clusChild.Top); maxBottom = Math.Max(maxBottom, clusChild.Bottom); } // Are the cluster's borders tight? Too big a positive gap means yes. // We're testing for children here so include cluster margin at the relevant border. double leftGap = minLeft - clusDef.Left - OverlapRemovalCluster.CalcBorderWidth(clusDef.LeftBorderInfo.InnerMargin) - this.MinPaddingX; double rightGap = clusDef.Right - maxRight - OverlapRemovalCluster.CalcBorderWidth(clusDef.RightBorderInfo.InnerMargin) - this.MinPaddingX; double topGap = minTop - clusDef.Top - OverlapRemovalCluster.CalcBorderWidth(clusDef.TopBorderInfo.InnerMargin) - this.MinPaddingY; double bottomGap = clusDef.Bottom - maxBottom - OverlapRemovalCluster.CalcBorderWidth(clusDef.BottomBorderInfo.InnerMargin) - this.MinPaddingY; // This is OK if the cluster is at its min size; assume it had to grow to meet the min size. bool badXgap = !clusDef.IsMinimumSizeX && (((leftGap > epsilon) && !clusDef.LeftBorderInfo.IsFixedPosition) || ((rightGap > epsilon) && !clusDef.RightBorderInfo.IsFixedPosition)); bool badYgap = !clusDef.IsMinimumSizeY && (((topGap > epsilon) && !clusDef.TopBorderInfo.IsFixedPosition) || ((bottomGap > epsilon) && !clusDef.BottomBorderInfo.IsFixedPosition)); if (badXgap || badYgap) { // Uh oh. this.WriteLine("Error {0}: Cluster '{1}' border is not tight (within {2})", FailTag("ClusTightBorder"), clusDef.ClusterId, epsilon); this.WriteLine(" Cluster {0}: L/R T/B {1:F5}/{2:F5} {3:F5}/{4:F5}", clusDef.ClusterId, clusDef.Left, clusDef.Right, clusDef.Top, clusDef.Bottom); this.WriteLine(" Gaps : L/R T/B {0}/{1} {2}/{3}", clusDef.LeftBorderInfo.IsFixedPosition ? "fixed" : string.Format("{0:F5}", leftGap), clusDef.RightBorderInfo.IsFixedPosition ? "fixed" : string.Format("{0:F5}", rightGap), clusDef.TopBorderInfo.IsFixedPosition ? "fixed" : string.Format("{0:F5}", topGap), clusDef.BottomBorderInfo.IsFixedPosition ? "fixed" : string.Format("{0:F5}", bottomGap)); succeeded = false; } } }
private void AddNodesAndSolve(ConstraintGenerator generator, IEnumerable <VariableDef> iterVariableDefs, OverlapRemovalParameters olapParameters, IEnumerable <ConstraintDef> iterConstraintDefs, ref bool succeeded) { var axisName = generator.IsHorizontal ? "X" : "Y"; var solver = generator.IsHorizontal ? this.SolverX : this.SolverY; foreach (VariableDef varDef in iterVariableDefs) { // Create the variable in its initial cluster. OverlapRemovalCluster olapClusParent = generator.DefaultClusterHierarchy; if (varDef.ParentClusters.Count > 0) { olapClusParent = varDef.ParentClusters[0].Cluster; } OverlapRemovalNode newNode; if (generator.IsHorizontal) { newNode = generator.AddNode(olapClusParent, varDef.IdString, varDef.DesiredPosX, varDef.DesiredPosY, varDef.SizeX, varDef.SizeY, varDef.WeightX); varDef.VariableX = new OlapTestNode(newNode); } else { newNode = generator.AddNode(olapClusParent, varDef.IdString, varDef.DesiredPosY, varDef.VariableX.ActualPos, varDef.SizeY, varDef.SizeX, varDef.WeightY); varDef.VariableY = new OlapTestNode(newNode); } // Add it to its other clusters. for (int ii = 1; ii < varDef.ParentClusters.Count; ++ii) { olapClusParent = varDef.ParentClusters[ii].Cluster; generator.AddNodeToCluster(olapClusParent, newNode); } } generator.Generate(solver, olapParameters); if (TestGlobals.VerboseLevel >= 3) { this.WriteLine(" {0} Constraints:", axisName); foreach (Constraint cst in solver.Constraints.OrderBy(cst => cst)) { this.WriteLine(" {0} {1} g {2:F5}", cst.Left.UserData, cst.Right.UserData, cst.Gap); } } if (null != iterConstraintDefs) { // TODO: Compare expected to actual constraints generated in solver } var solution = generator.Solve(solver, olapParameters, false /* doGenerate */); if (!this.VerifySolutionMembers(solution, /*iterNeighborDefs:*/ null)) { succeeded = false; } if (generator.IsHorizontal) { this.SolutionX = solution; } else { this.SolutionY = solution; } }