using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using Netron.Diagramming.Core.Analysis;
namespace Netron.Diagramming.Core {
///
/// Layout instance implementing the Fruchterman-Reingold algorithm for
/// force-directed placement of graph nodes. The computational complexity of
/// this algorithm is quadratic [O(n^2)] in the number of nodes, so should
/// only be applied for relatively small graphs, particularly in interactive
/// situations.
///
/// This implementation was ported from the implementation in the
/// JUNG framework.
///
class FruchtermanReingoldLayout : LayoutBase {
#region Fields
private double forceConstant;
private double temp;
private int maxIter = 300;
protected int m_fidx;
private Random rnd;
private static double EPSILON = 0.000001D;
private static double ALPHA = 0.1D;
Dictionary Pars;
double width;
double height;
BackgroundWorker worker;
#endregion
#region Constructor
///
///Default constructor
///
public FruchtermanReingoldLayout(IController controller)
: base("FruchtermanReingold Layout", controller) {
}
#endregion
#region Methods
///
/// Inits the calculational state
///
private bool Init() {
this.Graph = this.Model as IGraph;
if (Graph == null)
throw new InconsistencyException("The model has not been set and the Graph property is hence 'null'");
//this.LayoutRoot = this.Controller.Model.LayoutRoot;//could be null if not set in the GUI
//Graph.ClearSpanningTree();
//Graph.MakeSpanningTree(LayoutRoot as INode);
Pars = new Dictionary();
if (Nodes.Count == 0)
return false;
if (Edges.Count == 0) //this layout is base on embedded springs in the connections
return false;
width = (double)Bounds.Width;
height = (double)Bounds.Height;
rnd = new Random();
temp = width / 10;
forceConstant = 0.75 * Math.Sqrt(height * width / Nodes.Count);
// initialize node positions
double scaleW = ALPHA * width / 2;
double scaleH = ALPHA * height / 2;
Params par;
double[] loc;
foreach (INode node in Nodes) {
loc = new double[2];
loc[0] = Center.X + rnd.NextDouble() * scaleW;
loc[1] = Center.Y + rnd.NextDouble() * scaleH;
par = new Params(loc, new double[2]);
Pars.Add(node.Uid.ToString(), par);
}
return true;
}
///
/// Runs this instance.
///
public override void Run() {
width = this.Bounds.Width;
height = this.Bounds.Height;
Run(1000);
}
///
/// Stops this instance.
///
public override void Stop() {
if (worker != null && worker.IsBusy)
worker.CancelAsync();
}
///
/// Runs the layout for a specified time.
///
/// The time.
public override void Run(int time) {
if (Init()) {
worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerAsync(time);
}
}
///
/// Handles the DoWork event of the worker control.
///
/// The source of the event.
/// The instance containing the event data.
void worker_DoWork(object sender, DoWorkEventArgs e) {
//this.Controller.View.Suspend();
DateTime start = DateTime.Now;
while (DateTime.Now < start.AddMilliseconds((int)e.Argument)) {
RunStep();
}
//this.Controller.View.Resume();
UpdateShapes();
RunAnimation();
}
private void RunAnimation() {
}
///
/// Runs a single step.
///
private void RunStep() {
for (int curIter = 0; curIter < maxIter; curIter++) {
// Calculate repulsion
foreach (INode node in Nodes) {
if (node.IsFixed) continue;
CalculateRepulsion(node);
}
// Calculate attraction
foreach (IEdge edge in Edges) {
CalculateAttraction(edge);
}
foreach (INode node in Nodes) {
if (node.IsFixed) continue;
CalculatePositions(node);
}
CoolDown(curIter);
}
}
private void UpdateShapes() {
int x, y;
lock (Nodes)
lock (Pars) {
foreach (INode node in Nodes) {
x = Convert.ToInt32(Pars[node.Uid.ToString()].loc[0]) - node.Rectangle.X;
y = Convert.ToInt32(Pars[node.Uid.ToString()].loc[1]) - node.Rectangle.Y;
//if (node.Rectangle.X + x < width - node.Rectangle.Width - 10 && node.Rectangle.X + x > 10 && node.Rectangle.Y + y < height - node.Rectangle.Height - 10 && node.Rectangle.Y + y > 10)
node.MoveBy(new Point(x, y));
}
}
}
#region Calculations
public void CalculatePositions(INode n) {
Params np = Pars[n.Uid.ToString()];
double deltaLength = Math.Max(EPSILON, Math.Sqrt(np.disp[0] * np.disp[0] + np.disp[1] * np.disp[1]));
double xDisp = np.disp[0] / deltaLength * Math.Min(deltaLength, temp);
if (Double.IsNaN(xDisp)) {
System.Diagnostics.Trace.WriteLine("Oops, the layout resulted in a NaN problem.");
return;
}
double yDisp = np.disp[1] / deltaLength * Math.Min(deltaLength, temp);
np.loc[0] += xDisp;
np.loc[1] += yDisp;
// don't let nodes leave the display
double borderWidth = width / 50.0;
double x = np.loc[0];
if (x < borderWidth) {
x = borderWidth + rnd.NextDouble() * borderWidth * 2.0;
} else if (x > (width - borderWidth)) {
x = width - borderWidth - rnd.NextDouble() * borderWidth * 2.0;
}
double y = np.loc[1];
if (y < borderWidth) {
y = borderWidth + rnd.NextDouble() * borderWidth * 2.0;
} else if (y > (height - borderWidth)) {
y = height - borderWidth - rnd.NextDouble() * borderWidth * 2.0;
}
np.loc[0] = x;
np.loc[1] = y;
}
///
/// Calculates the attraction or tension on the given edge.
///
/// The edge.
public void CalculateAttraction(IEdge edge) {
INode n1, n2;
Params n1p = new Params();
Params n2p = new Params();
if (edge.SourceNode != null) {
n2 = edge.SourceNode;
n2p = Pars[n2.Uid.ToString()];
};
if (edge.TargetNode != null) {
n1 = edge.TargetNode;
n1p = Pars[n1.Uid.ToString()];
};
double xDelta = n1p.loc[0] - n2p.loc[0];
double yDelta = n1p.loc[1] - n2p.loc[1];
double deltaLength = Math.Max(EPSILON, Math.Sqrt(xDelta * xDelta + yDelta * yDelta));
double force = (deltaLength * deltaLength) / forceConstant;
if (Double.IsNaN(force)) {
System.Diagnostics.Trace.WriteLine("Oops, the layout resulted in a NaN problem.");
return;
}
double xDisp = (xDelta / deltaLength) * force;
double yDisp = (yDelta / deltaLength) * force;
n1p.disp[0] -= xDisp; n1p.disp[1] -= yDisp;
n2p.disp[0] += xDisp; n2p.disp[1] += yDisp;
}
public void CalculateRepulsion(INode node) {
Params np = Pars[node.Uid.ToString()];
np.disp[0] = 0.0; np.disp[1] = 0.0;
foreach (INode n2 in Nodes) {
Params n2p = Pars[n2.Uid.ToString()];
if (n2.IsFixed) continue;
if (node != n2) {
double xDelta = np.loc[0] - n2p.loc[0];
double yDelta = np.loc[1] - n2p.loc[1];
double deltaLength = Math.Max(EPSILON, Math.Sqrt(xDelta * xDelta + yDelta * yDelta));
double force = (forceConstant * forceConstant) / deltaLength;
if (Double.IsNaN(force)) {
System.Diagnostics.Trace.WriteLine("Oops, the layout resulted in a NaN problem.");
return;
}
np.disp[0] += (xDelta / deltaLength) * force;
np.disp[1] += (yDelta / deltaLength) * force;
}
}
}
private void CoolDown(int curIter) {
temp *= (1.0 - curIter / (double)maxIter);
}
#endregion
///
/// Paramter blob to temporarily keep working data of one node.
///
struct Params {
public double[] loc;
public double[] disp;
public Params(double[] loc, double[] disp) {
this.loc = loc;
this.disp = disp;
}
}
#endregion
}
}