Exemple #1
0
        /// <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);
        }
Exemple #2
0
        /// <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);
        }
Exemple #3
0
        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);
            }
        }
        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;
 }
Exemple #9
0
        /// <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;
        }
Exemple #10
0
 static void AddGravityForce(Point origin, double gravity, FiNode v)
 {
     // compute and add gravity
     v.force -= 0.0001 * gravity * (origin - v.Center);
 }
Exemple #11
0
 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;
 }