/// <summary> /// Force directed layout is basically an iterative approach to solving a bunch of differential equations. /// Different integration schemes are possible for applying the forces iteratively. Euler is the simplest: /// v_(i+1) = v_i + a dt /// x_(i+1) = x_i + v_(i+1) dt /// /// Verlet is much more stable (and not really much more complicated): /// x_(i+1) = x_i + (x_i - x_(i-1)) + a dt dt /// </summary> double VerletIntegration() { // The following sets the Centers of all nodes to a (not necessarily feasible) configuration that reduces the cost (forces) float energy0 = energy; energy = (float)ComputeDescentDirection(1.0); UpdateStepSize(energy0); SolveSeparationConstraints(); double displacementSquared = 0; for (int i = 0; i < nodes.Length; ++i) { FiNode v = nodes[i]; displacementSquared += (v.Center - v.previousCenter).LengthSquared; } return(displacementSquared); }
/// <summary> /// Aggregate all the forces affecting each node /// </summary> void ComputeForces() { if (components != null) { components.ForEach(ComputeRepulsiveForces); } else { ComputeRepulsiveForces(nodes); } edges.ForEach(AddSpringForces); foreach (var c in components) { var origin = new Point(); for (int i = 0; i < c.Length; ++i) { origin += c[i].Center; } origin /= (double)c.Length; double maxForce = double.NegativeInfinity; for (int i = 0; i < c.Length; ++i) { FiNode v = c[i]; AddGravityForce(origin, settings.GravityConstant, v); if (v.force.Length > maxForce) { maxForce = v.force.Length; } } if (maxForce > 100.0) { for (int i = 0; i < c.Length; ++i) { c[i].force *= 100.0 / maxForce; } } } // This is the only place where ComputeForces (and hence verletIntegration) considers clusters. // It's just adding a "gravity" force on nodes inside each cluster towards the barycenter of the cluster. AddClusterForces(graph.RootCluster); }
void SatisfyConstraints() { for (int i = 0; i < settings.ProjectionIterations; ++i) { foreach (var level in constraints.Keys) { if (level > CurrentConstraintLevel) { break; } foreach (var c in constraints[level]) { c.Project(); // c.Project operates only on MSAGL nodes, so need to update the local FiNode.Centers foreach (var v in c.Nodes) { ((FiNode)v.AlgorithmData).Center = v.Center; } } } foreach (LockPosition l in settings.locks) { l.Project(); // again, project operates only on MSAGL nodes, we'll also update FiNode.PreviousPosition since we don't want any inertia in this case foreach (var v in l.Nodes) { FiNode fiNode = v.AlgorithmData as FiNode; // the locks should have had their AlgorithmData updated, but if (for some reason) // the locks list is out of date we don't want to null ref here. if (fiNode != null && v.AlgorithmData != null) { fiNode.ResetBounds(); } } } } }
private void AddStructuralConstraints() { // Add the vertical structural constraints to the auto-generated ones. foreach (var c in structuralConstraints) { if (ConstraintLevel >= c.Level) { var hc = c as HorizontalSeparationConstraint; if (hc != null && IsHorizontal) { FiNode u = (FiNode)(hc.LeftNode.AlgorithmData); FiNode v = (FiNode)(hc.RightNode.AlgorithmData); solver.AddConstraint(u.getOlapNode(IsHorizontal).Variable, v.getOlapNode(IsHorizontal).Variable, hc.Separation, hc.IsEquality); } var vc = c as VerticalSeparationConstraint; if (vc != null && !IsHorizontal) { FiNode u = (FiNode)(vc.TopNode.AlgorithmData); FiNode v = (FiNode)(vc.BottomNode.AlgorithmData); solver.AddConstraint(u.getOlapNode(IsHorizontal).Variable, v.getOlapNode(IsHorizontal).Variable, vc.Separation, vc.IsEquality); } } } }
private void DebugVerifyClusters(ConstraintGenerator generator, Cluster incCluster, Cluster root) { double dblEpsilon = 0.0001; // First verify that all nodes are within the cluster. Rectangle clusRect = incCluster.RectangularBoundary.rectangle; foreach (var v in incCluster.Nodes) { FiNode iiFilNode = (FiNode)v.AlgorithmData; Rectangle iiNodeRect = iiFilNode.mNode.BoundaryCurve.BoundingBox; if (IsHorizontal) { // Don't check containment for the root ClusterHierarchy as there is no border for it. if (incCluster != root) { // This is horizontal so we've not yet calculated the Y-axis stuff. The only thing we // can do is verify we're within cluster X bounds. If *Space is negative, there's overlap. // Generator primary axis is horizontal so use its Padding. double dblLboundSpace = iiNodeRect.Left - clusRect.Left - generator.Padding; double dblRboundSpace = clusRect.Right - iiNodeRect.Right - generator.Padding; Debug.Assert((dblLboundSpace >= -dblEpsilon) && (dblRboundSpace >= -dblEpsilon) , "Node is not within parent Cluster"); } } else { // Don't check containment for the root ClusterHierarchy as there is no border for it. if (incCluster != root) { // This is vertical so we've calculated the Y-axis stuff and horizontal is Perpendicular. DebugVerifyRectContains(clusRect, iiNodeRect , generator.PaddingP, generator.Padding, dblEpsilon); } // Make sure the node doesn't intersect any following nodes, or any clusters. foreach (var u in incCluster.Nodes) { if (u == v) { continue; } FiNode jjFilNode = (FiNode)u.AlgorithmData; Rectangle jjNodeRect = jjFilNode.mNode.BoundaryCurve.BoundingBox; // We've already added the padding for the node so don't add it for the jjNode/Cluster. DebugVerifyRectsDisjoint(iiNodeRect, jjNodeRect , generator.PaddingP, generator.Padding, dblEpsilon); } foreach (Cluster incClusComp in incCluster.Clusters) { DebugVerifyRectsDisjoint(iiNodeRect, incClusComp.RectangularBoundary.rectangle , generator.PaddingP, generator.Padding, dblEpsilon); } } // endif isHorizontal } // endfor iiNode // Now verify the clusters are contained and don't overlap. foreach (var iiIncClus in incCluster.Clusters) { Rectangle iiClusRect = iiIncClus.RectangularBoundary.rectangle; if (IsHorizontal) { // Don't check containment for the root ClusterHierarchy as there is no border for it. if (incCluster != root) { // This is horizontal so we've not yet calculated the Y-axis stuff. The only thing we // can do is verify we're within cluster X bounds. If *Space is negative, there's overlap. // Generator primary axis is horizontal so use its Padding. double dblLboundSpace = iiClusRect.Left - clusRect.Left - generator.Padding; double dblRboundSpace = clusRect.Right - iiClusRect.Right - generator.Padding; Debug.Assert((dblLboundSpace >= -dblEpsilon) && (dblRboundSpace >= -dblEpsilon) , "Cluster is not within parent Cluster"); } } else { // Don't check containment for the root ClusterHierarchy as there is no border for it. if (incCluster != root) { // This is vertical so we've calculated the Y-axis stuff and horizontal is Perpendicular. DebugVerifyRectContains(clusRect, iiClusRect , generator.PaddingP, generator.Padding, dblEpsilon); } // Make sure the cluster doesn't intersect any following clusters. foreach (var jjIncClus in incCluster.Clusters) { if (jjIncClus == iiIncClus) { continue; } Rectangle jjClusRect = jjIncClus.RectangularBoundary.rectangle; DebugVerifyRectsDisjoint(iiClusRect, jjClusRect , generator.PaddingP, generator.Padding, dblEpsilon); } } // endif isHorizontal // Now recurse. DebugVerifyClusters(generator, iiIncClus, root); } // endfor iiCluster }
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); } }
public FiEdge(Edge mEdge) { this.mEdge = mEdge; source = (FiNode) mEdge.Source.AlgorithmData; target = (FiNode) mEdge.Target.AlgorithmData; }
/// <summary> /// Create the graph data structures. /// </summary> /// <param name="geometryGraph"></param> /// <param name="settings">The settings for the algorithm.</param> /// <param name="initialConstraintLevel">initialize at this constraint level</param> /// <param name="clusterSettings">settings by cluster</param> internal FastIncrementalLayout(GeometryGraph geometryGraph, FastIncrementalLayoutSettings settings, int initialConstraintLevel, Func <Cluster, LayoutAlgorithmSettings> clusterSettings) { graph = geometryGraph; this.settings = settings; this.clusterSettings = clusterSettings; int i = 0; ICollection <Node> allNodes = graph.Nodes; nodes = new FiNode[allNodes.Count]; foreach (Node v in allNodes) { v.AlgorithmData = nodes[i] = new FiNode(i, v); i++; } clusterEdges.Clear(); edges.Clear(); foreach (Edge e in graph.Edges) { if (e.Source is Cluster || e.Target is Cluster) { clusterEdges.Add(e); } else { edges.Add(new FiEdge(e)); } foreach (var l in e.Labels) { l.InnerPoints = l.OuterPoints = null; } } SetLockNodeWeights(); components = new List <FiNode[]>(); if (!settings.InterComponentForces) { basicGraph = new BasicGraph <FiEdge>(edges, nodes.Length); foreach (var componentNodes in ConnectedComponentCalculator <FiEdge> .GetComponents(basicGraph)) { var vs = new FiNode[componentNodes.Count()]; int vi = 0; foreach (int v in componentNodes) { vs[vi++] = nodes[v]; } components.Add(vs); } } else // just one big component (regardless of actual edges) { components.Add(nodes); } horizontalSolver = new AxisSolver(true, nodes, new[] { geometryGraph.RootCluster }, settings.AvoidOverlaps, settings.MinConstraintLevel, clusterSettings) { OverlapRemovalParameters = new OverlapRemovalParameters { AllowDeferToVertical = true, // use "ProportionalOverlap" mode only when iterative apply forces layout is being used. // it is not necessary otherwise. ConsiderProportionalOverlap = settings.ApplyForces } }; verticalSolver = new AxisSolver(false, nodes, new[] { geometryGraph.RootCluster }, settings.AvoidOverlaps, settings.MinConstraintLevel, clusterSettings); SetupConstraints(); geometryGraph.RootCluster.ComputeWeight(); foreach ( Cluster c in geometryGraph.RootCluster.AllClustersDepthFirst().Where(c => c.RectangularBoundary == null) ) { c.RectangularBoundary = new RectangularClusterBoundary(); } CurrentConstraintLevel = initialConstraintLevel; }
static void AddGravityForce(Point origin, double gravity, FiNode v) { // compute and add gravity v.force -= 0.0001 * gravity * (origin - v.Center); }
void AddRepulsiveForce(FiNode v, Point repulsion) { // scale repulsion v.force = 10.0 * settings.RepulsiveForceConstant * repulsion; }
public FiEdge(Edge mEdge) { this.mEdge = mEdge; source = (FiNode)mEdge.Source.AlgorithmData; target = (FiNode)mEdge.Target.AlgorithmData; }
/// <summary> /// Create the graph data structures. /// </summary> /// <param name="geometryGraph"></param> /// <param name="settings">The settings for the algorithm.</param> /// <param name="initialConstraintLevel">initialize at this constraint level</param> /// <param name="clusterSettings">settings by cluster</param> internal FastIncrementalLayout(GeometryGraph geometryGraph, FastIncrementalLayoutSettings settings, int initialConstraintLevel, Func<Cluster, LayoutAlgorithmSettings> clusterSettings) { graph = geometryGraph; this.settings = settings; this.clusterSettings = clusterSettings; int i = 0; ICollection<Node> allNodes = graph.Nodes; nodes = new FiNode[allNodes.Count]; foreach (Node v in allNodes) { v.AlgorithmData = nodes[i] = new FiNode(i, v); i++; } clusterEdges.Clear(); edges.Clear(); foreach (Edge e in graph.Edges) { if (e.Source is Cluster || e.Target is Cluster) clusterEdges.Add(e); else edges.Add(new FiEdge(e)); foreach (var l in e.Labels) l.InnerPoints = l.OuterPoints = null; } SetLockNodeWeights(); components = new List<FiNode[]>(); if (!settings.InterComponentForces) { basicGraph = new BasicGraph<FiEdge>(edges, nodes.Length); foreach (var componentNodes in ConnectedComponentCalculator<FiEdge>.GetComponents(basicGraph)) { var vs = new FiNode[componentNodes.Count()]; int vi = 0; foreach (int v in componentNodes) { vs[vi++] = nodes[v]; } components.Add(vs); } } else // just one big component (regardless of actual edges) components.Add(nodes); horizontalSolver = new AxisSolver(true, nodes, new[] {geometryGraph.RootCluster}, settings.AvoidOverlaps, settings.MinConstraintLevel, clusterSettings) { OverlapRemovalParameters = new OverlapRemovalParameters { AllowDeferToVertical = true, // use "ProportionalOverlap" mode only when iterative apply forces layout is being used. // it is not necessary otherwise. ConsiderProportionalOverlap = settings.ApplyForces } }; verticalSolver = new AxisSolver(false, nodes, new[] {geometryGraph.RootCluster}, settings.AvoidOverlaps, settings.MinConstraintLevel, clusterSettings); SetupConstraints(); geometryGraph.RootCluster.ComputeWeight(); foreach ( Cluster c in geometryGraph.RootCluster.AllClustersDepthFirst().Where(c => c.RectangularBoundary == null) ) { c.RectangularBoundary = new RectangularClusterBoundary(); } CurrentConstraintLevel = initialConstraintLevel; }
void ComputeRepulsiveForces(FiNode[] vs) { int n = vs.Length; if (n > 16 && settings.ApproximateRepulsion) { var ps = new KDTree.Particle[vs.Length]; // before calculating forces we perturb each center by a small vector in a unique // but deterministic direction (by walking around a circle in n steps) - this allows // the KD-tree to decompose even when some nodes are at exactly the same position double angle = 0, angleDelta = 2.0*Math.PI/n; for (int i = 0; i < n; ++i) { ps[i] = new KDTree.Particle(vs[i].Center + 1e-5*new Point(Math.Cos(angle), Math.Sin(angle))); angle += angleDelta; } var kdTree = new KDTree(ps, 8); kdTree.ComputeForces(5); for (int i = 0; i < vs.Length; ++i) { AddRepulsiveForce(vs[i], ps[i].force); } } else { foreach (FiNode u in vs) { var fu = new Point(); foreach (FiNode v in vs) { if (u != v) { fu += MultipoleCoefficients.Force(u.Center, v.Center); } } AddRepulsiveForce(u, fu); } } }
static void AddGravityForce(Point origin, double gravity, FiNode v) { // compute and add gravity v.force -= 0.0001*gravity*(origin - v.Center); }
void AddRepulsiveForce(FiNode v, Point repulsion) { // scale repulsion v.force = 10.0*settings.RepulsiveForceConstant*repulsion; }