Beispiel #1
0
        public DataModel(int iterations, double weightMax, bool firstTime)
        {
            if (FruchtermanReingoldLayout.h == null)
            {
                return;
            }
            //
            // Populate the data model with some example data.
            //
            if (firstTime || FlowGlobals.rectangles == null)
            {
                FlowGlobals.rectangles = new ObservableCollection <RectangleData>();
                FlowGlobals.arrows     = new ObservableCollection <ArrowData>();
            }
            else
            {
                FlowGlobals.rectangles.Clear();
                FlowGlobals.arrows.Clear();
            }
            ObservableCollection <RectangleData> rectangles = FlowGlobals.rectangles;
            ObservableCollection <ArrowData>     arrows     = FlowGlobals.arrows;

            double width  = 90;
            double height = 70;

            Random r = new Random(12345);

            r = new Random(54321);
            r = new Random(11111);
            List <EdgeData> edges = new List <EdgeData>();
            List <NodeData> nodes = new List <NodeData>();

            NodeData startNode = null;

            foreach (FlowArrow fa in FruchtermanReingoldLayout.h.flowArrows)
            {
                if (!double.IsNaN(weightMax) && Math.Abs(fa.weight) < weightMax)
                {
                    continue;
                }
                EdgeData ed = new EdgeData {
                    from = fa.counter1, to = fa.counter2, weight = fa.weight
                };
                edges.Add(ed);
            }

            foreach (FlowNode fa in FruchtermanReingoldLayout.h.flowNodes)
            {
                NodeData nd = new NodeData {
                    x = r.Next((int)(0.005d * contentWidth), (int)contentWidth), y = r.Next((int)(0.005d * contentWidth), (int)contentWidth), name = fa.varName, labelBig = fa.labelBig, isExogenous = fa.isExogenous, isStartNode = fa.isStartNode, id = fa.id
                };
                nodes.Add(nd);
                if (nd.isStartNode)
                {
                    startNode = nd;
                }
            }

            Dictionary <int, NodeData> d = new Dictionary <int, NodeData>();

            foreach (NodeData nd in nodes)
            {
                d.Add(nd.id, nd);
            }

            //TODO: The Dijkstra algorithm is a bit sloppy, for instance using List<int> and removing items from this list (use Dictionary instead)
            DijkstraInfo di = new DijkstraInfo();

            di.nodes = nodes;
            di.edges = edges;
            Dijkstra dijkstra = new Dijkstra(nodes.Count, new Dijkstra.InternodeTraversalCost(getInternodeTraversalCost), new Dijkstra.NearbyNodesHint(nearbyNodesHint), di);

            Dijkstra.Results results = dijkstra.Perform(startNode.id, di);
            //int[] minimumPath = dijkstra.GetMinimumPath(0, 102, di);

            //Now we filter out

            List <EdgeData> edges2 = new List <EdgeData>();
            List <NodeData> nodes2 = new List <NodeData>();

            int large = 1000000000;
            Dictionary <int, int> rename = new Dictionary <int, int>();

            foreach (NodeData nd in nodes)
            {
                int value = results.MinimumDistance[nd.id];
                if (value < 0)
                {
                    value = large;  //do not minus it: -int.MinValue gives a strange result!
                }
                if (value < large)  //it seems these distances can become = int.MinValue, maybe int.MaxValue+1. So this is to identify those.
                {
                    int number = nodes2.Count;
                    rename.Add(nd.id, number);
                    nd.id = number;  //now id numbers in List 'nodes' are inconsistent, but never mind that
                    nodes2.Add(nd);
                }
            }

            foreach (EdgeData ed in edges)
            {
                if (rename.ContainsKey(ed.from) && rename.ContainsKey(ed.to))
                {
                    ed.from = rename[ed.from];
                    ed.to   = rename[ed.to];
                    edges2.Add(ed); //now id numbers in List 'edges' are inconsistent, but never mind that
                }
            }

            SolveInfo si = new SolveInfo();

            si.width      = width;
            si.height     = height;
            si.rectangles = rectangles;
            si.arrows     = arrows;
            si.edges      = edges2;
            si.nodes      = nodes2;
            si.iterations = iterations;
            if (si.iterations == -12345)
            {
                si.iterations = 200;
            }
            si.useSmartRepulse = true;
            si.useWeight       = true;
            FruchtermanReingoldLayout.Solve(si);
        }
        public static void  CalculateAttractiveForces(SolveInfo si, List <Displacement> displacement, double k)
        {
            List <EdgeData> edgesToLayOut    = si.edges;
            List <NodeData> verticesToLayOut = si.nodes;

            foreach (EdgeData oEdge in edgesToLayOut)
            {
                if (oEdge.from == oEdge.to)
                {
                    // A vertex isn't attracted to itself.
                    continue;
                }

                // Get the edge's vertices.


                double xFrom = verticesToLayOut[oEdge.from].x;
                double yFrom = verticesToLayOut[oEdge.from].y;
                double xTo   = verticesToLayOut[oEdge.to].x;
                double yTo   = verticesToLayOut[oEdge.to].y;

                double tDeltaX =
                    xTo - xFrom;

                double tDeltaY =
                    yTo - yFrom;

                double tDelta = (double)Math.Sqrt(
                    (tDeltaX * tDeltaX) + (tDeltaY * tDeltaY)
                    );

                //displacement here: for nodes in exact same position???
                //displacement here: for nodes in exact same position???
                //displacement here: for nodes in exact same position???

                // (Note that there is an obvious typo in the Fruchterman-Reingold
                // paper for computing the attractive force.  The function fa(z) at
                // the top of Figure 1 is defined as x squared over k.  It should
                // read z squared over k.)

                double fa = (tDelta * tDelta) / (double)k;

                if (si.useWeight)
                {
                    double w = Math.Pow(Math.Abs(oEdge.weight), 0.5d);  //easing the effect a little
                    if (w <= 1d)
                    {
                        fa = fa * w;  //may be negative flow, 3 compensates for avg. < 1 force
                    }
                    else
                    {
                        fa = fa * 1d;   //may be negative flow, 3 compensates for avg. < 1 force
                    }
                    fa *= 3d;
                }

                if (tDelta == 0)
                {
                    // TODO: Is this the correct way to handle vertices in the same
                    // location?  See the notes in CalculateRepulsiveForces().
                    //throw new Exception();
                    displacement[oEdge.from].x += 1;
                    displacement[oEdge.from].y += 1;
                    displacement[oEdge.to].x   -= 1;
                    displacement[oEdge.to].y   -= 1;
                }
                else
                {
                    double faOverDelta = fa / tDelta;

                    double tFactorX = tDeltaX * faOverDelta;
                    double tFactorY = tDeltaY * faOverDelta;

                    displacement[oEdge.from].x += tFactorX;
                    displacement[oEdge.from].y += tFactorY;
                    displacement[oEdge.to].x   -= tFactorX;
                    displacement[oEdge.to].y   -= tFactorY;
                }
            }
        }
        public static void CalculateRepulsiveForces(SolveInfo si, List <Displacement> displacement, double k, double factor, bool skipBarriers)
        {
            List <EdgeData> edgesToLayOut    = si.edges;
            List <NodeData> verticesToLayOut = si.nodes;

            double tkSquared = (double)(k * k);

            int vCounter = -1;

            foreach (NodeData oVertexV in verticesToLayOut)
            {
                vCounter++;
                // Retrieve the object that stores calculated values for the
                // vertex.

                double tDisplacementX = 0;
                double tDisplacementY = 0;

                foreach (NodeData oVertexU in verticesToLayOut)
                {
                    if (oVertexU == oVertexV)
                    {
                        continue;
                    }

                    double tDeltaX =
                        oVertexV.x - oVertexU.x;

                    double tDeltaY =
                        oVertexV.y - oVertexU.y;

                    double tDelta = (double)Math.Sqrt(
                        (tDeltaX * tDeltaX) + (tDeltaY * tDeltaY)
                        );

                    // The Fruchterman-Reingold paper says this about vertices in
                    // the same location:
                    //
                    // "A special case occurs when vertices are in the same
                    // position: our implementation acts as though the two vertices
                    // are a small distance apart in a randomly chosen orientation:
                    // this leads to a violent repulsive effect separating them."
                    //
                    // Handle this case by arbitrarily setting a small
                    // displacement.

                    if (tDelta == 0)
                    {
                        tDisplacementX += 1;
                        tDisplacementY += 1;
                    }
                    else
                    {
                        if (si.useSmartRepulse)
                        {
                            double fr = double.NaN;
                            double kk = 30d;  //proportional to #nodes???
                            fr = k * k / Math.Pow(tDelta, 1.0d) - k * k / Math.Pow(k, 1.0d) / kk;
                            double frOverDelta = fr / tDelta;
                            tDisplacementX += tDeltaX * frOverDelta * factor;
                            tDisplacementY += tDeltaY * frOverDelta * factor;
                        }
                        else
                        {
                            double fr          = tkSquared / tDelta;
                            double frOverDelta = fr / tDelta;
                            tDisplacementX += tDeltaX * frOverDelta * factor;
                            tDisplacementY += tDeltaY * frOverDelta * factor;
                        }
                    }
                }

                // Save the results for VertexV.

                displacement[vCounter].x = tDisplacementX;
                displacement[vCounter].y = tDisplacementY;

                if (skipBarriers)
                {
                    displacement[vCounter].x = 0d;
                    displacement[vCounter].y = 0d;
                }
            }
        }
        public static void Solve(SolveInfo si)
        {
            /*
             * Rectangle w, h
             * fArea = w*h
             * m_fC = 1
             * k = m_fC * Sqrt(fArea / #nodes) =ca= avg. distance between nodes * m_fC
             * temperature = w / 10
             *
             * Repulse:
             * tDisplacementX += tDeltaX * (k*k / distance_nodes) / distance_nodes)
             * tDisplacementY += tDeltaY * (k*k / distance_nodes) / distance_nodes)
             *
             * Attract:
             * faOverDelta =
             * tFactorX = tDeltaX * (distance_nodes * distance_nodes) / k / distance_nodes)
             * tFactorY = tDeltaY * (distance_nodes * distance_nodes) / k / distance_nodes)
             * displacement[oEdge.from].x += tFactorX;
             * displacement[oEdge.from].y += tFactorY;
             * displacement[oEdge.to].x -= tFactorX;
             * displacement[oEdge.to].y -= tFactorY;
             *
             * For some reason, the forces are both divided by distance_nodes.
             * Distance between two nodes would be k if only 2 connected nodes in whole graph.
             *
             * repulse: k*k/d
             * attract: d*d/k
             *
             * Equilibrium: k*k/d = d*d/k --> d = k
             *
             * Alternative:
             * d*d/k = k*k*k/(d*d) --> hmmmm is that better?
             *
             *
             *
             *
             */

            int         iterstop      = si.iterations;
            double      factor        = 50;
            int         m_iIterations = 200;
            FRRectangle oRectangle    = new FRRectangle()
            {
                Width = 200, Height = 200
            };
            bool   skipBarriers = false;
            int    counter      = -1;
            double m_fC         = 1.0d;

            int iVertices = si.nodes.Count;

            List <Displacement> displacement = new List <Displacement>();

            for (int i = 0; i < iVertices; i++)
            {
                displacement.Add(new Displacement());
            }

            // If the graph has already been laid out, use the current vertex
            // locations as initial values.

            //randomize locations here!

            double fArea = oRectangle.Width * oRectangle.Height;

            // The algorithm in Figure 1 of the Fruchterman-Reingold paper doesn't
            // include the constant C, but it is included in the calculation for k
            // under the "Modelling the forces" section.

            double k = m_fC * Math.Sqrt(fArea / (double)iVertices);

            // The rectangle is guaranteed to have non-zero width and height, so
            // k should never be zero.

            // Use the simple cooling algorithm suggested in the Fruchterman-
            // Reingold paper.

            double fTemperature = oRectangle.Width / 10d;

            double fT = fTemperature;

            double fTemperatureDecrement = fTemperature / (double)m_iIterations;

            while (true)
            {
                bool skip = false;
                if (skipBarriers && (counter % 5 == 3))
                {
                    skip = true;
                }

                counter++;

                // Calculate the attractive and repulsive forces between the
                // vertices.  The results get written to metadata on the vertices.

                CalculateRepulsiveForces(si, displacement, k, factor, skip);
                CalculateAttractiveForces(si, displacement, k);

                double fNextTemperature = fTemperature - fTemperatureDecrement;

                // Set the unbounded location of each vertex based on the vertex's
                // current location and the calculated forces.

                //check if fnexttemp > 0.......
                SetUnboundedLocations(si.nodes, displacement, fTemperature);

                // Decrease the temperature.

                fTemperature = fNextTemperature;

                // For graphs with many edges, telling NodeXLControl to refresh
                // its window (which is the end result of calling
                // FireLayOutGraphIterationCompleted()) can significantly slow down
                // performance, so don't do it.
                //
                // For example, a random graph with 1,000 vertices and 10,000 edges
                // took 3.6 seconds to lay out when the window was repeatedly
                // refreshed, but only 2.1 seconds with no refreshes.
                //
                // The performance improvement is much smaller for graphs with a
                // large number of vertices, where the O(V-squared) behavior in
                // CalculateRepulsiveForces() dominates the layout time.


                if (counter >= iterstop || fTemperature <= 0d)
                {
                    double contentWidth  = 2000; //This corresonds to the 2000, 2000 used as width and height of GUI window
                    double contentHeight = 2000;

                    double minX = double.MaxValue;
                    double maxX = double.MinValue;
                    double minY = double.MaxValue;
                    double maxY = double.MinValue;
                    foreach (NodeData xx in si.nodes)
                    {
                        if (xx.x < minX)
                        {
                            minX = xx.x;
                        }
                        if (xx.y < minY)
                        {
                            minY = xx.y;
                        }
                        if (xx.x > maxX)
                        {
                            maxX = xx.x;
                        }
                        if (xx.y > maxY)
                        {
                            maxY = xx.y;
                        }
                    }

                    minX += -contentWidth / 20d;
                    minY += -contentWidth / 20d;
                    maxX += contentWidth / 20d;
                    maxY += contentWidth / 20d;

                    double longest = Math.Max(maxX - minX, maxY - minY);
                    double scale   = Math.Min(1d, contentWidth / longest); //don't scale up!
                    if (contentWidth != contentHeight)
                    {
                        throw new Exception();
                    }

                    si.width  *= scale;
                    si.height *= scale;

                    foreach (NodeData xx in si.nodes)
                    {
                        xx.x = (xx.x - minX) * scale;
                        xx.y = (xx.y - minY) * scale;
                    }

                    minX = double.MaxValue;
                    maxX = double.MinValue;
                    minY = double.MaxValue;
                    maxY = double.MinValue;
                    foreach (NodeData xx in si.nodes)
                    {
                        if (xx.x < minX)
                        {
                            minX = xx.x;
                        }
                        if (xx.y < minY)
                        {
                            minY = xx.y;
                        }
                        if (xx.x > maxX)
                        {
                            maxX = xx.x;
                        }
                        if (xx.y > maxY)
                        {
                            maxY = xx.y;
                        }
                    }
                    double midX = (minX + maxX) / 2d;
                    double midY = (minY + maxY) / 2d;
                    double addX = contentWidth / 2d - midX;
                    double addY = contentWidth / 2d - midY;
                    foreach (NodeData xx in si.nodes)
                    {
                        xx.x += addX;
                        xx.y += addY;
                    }

                    int counter2 = -1;
                    foreach (NodeData xx in si.nodes)
                    {
                        counter2++;
                        Color c = Colors.LightGray;
                        if (xx.isExogenous)
                        {
                            c = Colors.White;
                        }
                        if (xx.isStartNode)
                        {
                            c = Colors.Tomato;
                        }
                        si.rectangles.Add(new RectangleData(xx.x - si.width / 2d, xx.y - si.height / 2d, si.width, si.height, " " + xx.name, xx.labelBig, c));
                    }

                    //si.rectangles.Add(new RectangleData(0, 0, si.width, si.height, "!!!", "", Colors.Blue));
                    //si.rectangles.Add(new RectangleData(contentWidth - si.width, contentHeight - si.height, si.width, si.height, "!!!", "", Colors.Blue));
                    //si.rectangles.Add(new RectangleData(contentWidth, contentHeight, si.width, si.height, "!!!", "", Colors.Red));
                    //si.arrows.Add(new ArrowData(100d, 100d, k * scale, 0d, Colors.Green, 5d));

                    foreach (EdgeData xx in si.edges)
                    {
                        if (xx.from == 19 && xx.to == 18)
                        {
                            Console.WriteLine();
                        }

                        double x1 = si.nodes[(int)xx.from].x;
                        double y1 = si.nodes[(int)xx.from].y;
                        double x2 = si.nodes[(int)xx.to].x;
                        double y2 = si.nodes[(int)xx.to].y;
                        double w  = Math.Min(1d, Math.Abs(xx.weight));
                        //si.arrows.Add(new ArrowData(x1, y1, x2 - x1, y2 - y1, Colors.Red, xx.weight * 4d * scale));
                        double r = 255;
                        double g = 0;
                        double b = 0;
                        if (xx.weight > 0)
                        {
                            r = 0;
                            g = 255;
                            b = 0;
                        }
                        double w2 = Math.Max(w, 0.05d);
                        double a  = Angle(x1, -y1, x2, -y2);
                        if (a < 0d || a > 360d)
                        {
                            throw new Exception();
                        }
                        //TODO: do this as minimization of real angle between
                        //arrows and boxes.
                        double off_x1 = 0d;
                        double off_y1 = 0d;
                        double off_x2 = 0d;
                        double off_y2 = 0d;
                        if (a < 45)
                        {
                            off_x1 = si.width / 2d;
                            off_x2 = -si.width / 2d;
                        }
                        else if (a < 135)
                        {
                            off_y1 = -si.height / 2d;
                            off_y2 = si.height / 2d;
                        }
                        else if (a < 225)
                        {
                            off_x1 = -si.width / 2d;
                            off_x2 = si.width / 2d;
                        }
                        else if (a < 315)
                        {
                            off_y1 = si.height / 2d;
                            off_y2 = -si.height / 2d;
                        }
                        else
                        {
                            off_x1 = si.width / 2d;
                            off_x2 = -si.width / 2d;
                        }

                        si.arrows.Add(new ArrowData(x1 + off_x1, y1 + off_y1, x2 + off_x2 - (x1 + off_x1), y2 + off_y2 - (y1 + off_y1), Color.FromArgb(180, (byte)(r), (byte)(g), (byte)(b)), 8d * scale * w2));
                    }
                    break;
                }
            }
        }