这种算法的核心就是将有连接关系的图元靠近放置,将没有关系的图元相互之间远离
此算法中涉及到引力计算和斥力计算.
排布前图片如下

排布后示意图:

首先定义节点
public class ShapeNode
{
public IShape Shape { get; set; }
/// <summary>
/// 中心点位置X
/// </summary>
public double X { get; set; }
/// <summary>
/// 中心点位置Y
/// </summary>
public double Y { get; set; }
/// <summary>
/// 水平移动的速度
/// </summary>
public double VelocityX { get; set; }
/// <summary>
/// 垂直移动的速度
/// </summary>
public double VelocityY { get; set; }
// 构造函数
public ShapeNode(double x, double y) : this(null, x, y) { }
public ShapeNode(IShape shape, double x, double y)
{
Shape = shape;
X = x;
Y = y;
VelocityX = 0;
VelocityY = 0;
}
}
定义节点之间有关系的连接
/// <summary>
/// 线图元边
/// </summary>
public class LineEdge
{
/// <summary>
/// 线图元
/// </summary>
public ILine Line { get; set; }
/// <summary>
/// 连接的线的开始端
/// </summary>
public ShapeNode Source { get; }
/// <summary>
/// 连接的线的结束端
/// </summary>
public ShapeNode Target { get; }
public LineEdge(ShapeNode src, ShapeNode trg) : this(null, src, trg) { }
public LineEdge(ILine line, ShapeNode src, ShapeNode trg)
{
Line = line;
Source = src;
Target = trg;
}
}
核心布局算法
/// <summary>
/// 力导向布局
/// </summary>
public class FRLayout
{
private List<ShapeNode> Nodes { get; }
private List<LineEdge> Edges { get; }
// 常量,用于计算力的大小,可以根据需要进行调整
private double RepulsionForceConstant = 100.0;// 排斥力常数1000
private double AttractionForceConstant = 0.3;// 吸引力常数0.1
private double DampingFactor = 0.85;// 阻尼因子,用于控制节点移动的速度
private int Iterations = 1000; //迭代次数,用于布局算法的运行
private int MinMovement = 1;// 系统稳定的标准,即最小移动距离
public FRLayout(List<ShapeNode> nodes, List<LineEdge> edges)
{
Nodes = nodes;
Edges = edges;
}
// 假设我们有一个方法来计算边的最优长度
public int GetOptimalLength(LineEdge edge)
{
int w1 = edge.Source.Shape.Width;
int w2 = edge.Target.Shape.Width;
return (w1 + w2) / 2 + 40;
}
/// <summary>
/// 布局方法,使用力导向算法来排列节点
/// </summary>
public void Layout()
{
for (int i = 0; i < Iterations; i++)
{
// 重置所有节点的速度
foreach (var node in Nodes)
{
node.VelocityX = 0;
node.VelocityY = 0;
}
// 计算排斥力
this.CalcuRepulsion();
// 计算吸引力
this.CalcuAttraction();
//更新节点位置
foreach (var ShapeNode in Nodes)
{
ShapeNode.X += ShapeNode.VelocityX * DampingFactor;
ShapeNode.Y += ShapeNode.VelocityY * DampingFactor;
}
// 如果系统已经达到稳定状态,则退出循环
if (IsStable())
{
break;
}
SetShapePosition();
API.Sleep(1);
}
//SetShapePosition();
}
/// <summary>
/// 计算斥力:计算每次迭代局部区域内两两节点间的斥力所产生的单位位移(一般为正值);
/// 任意两点间都有斥力
/// f = k/dis的平方
/// </summary>
private void CalcuRepulsion()
{
foreach (var nodeA in this.Nodes)
{
foreach (var nodeB in this.Nodes)
{
if (nodeA == nodeB) continue;
double disX = nodeA.X - nodeB.X;//X方向上的距离差
double disY = nodeA.Y - nodeB.Y;//Y方向上的距离差
double disSquared = disX * disX + disY * disY;//距离的平方
double dis = Math.Sqrt(disSquared) + 0.001; //实际距离,避免除以零
//double repulsiveForce = RepulsionForceConstant / dis;//计算排斥力
//应用排斥力(考虑方向)
//nodeA.VelocityX += (repulsiveForce * disX) / disSquared;
//nodeA.VelocityY += (repulsiveForce * disY) / disSquared;
nodeA.VelocityX += disX * RepulsionForceConstant / disSquared;
nodeA.VelocityY += disY * RepulsionForceConstant / disSquared;
//下面两行屏蔽也可以
nodeB.VelocityX -= disX * RepulsionForceConstant / disSquared;
nodeB.VelocityY -= disY * RepulsionForceConstant / disSquared;
}
}
}
/// <summary>
/// 计算引力:计算每次迭代每条边的引力对两端节点所产生的单位位移(一般为负值);
/// 引力仅存在于有连接的节点之间
/// Fs= ks(x-x0)
/// </summary>
private void CalcuAttraction()
{
foreach (var edge in Edges)
{
var srcNode = edge.Source;
var trgNode = edge.Target;
double disX = srcNode.X - trgNode.X;// X方向上的距离差
double disY = srcNode.Y - trgNode.Y;// Y方向上的距离差
double disSquared = disX * disX + disY * disY;
double dis = Math.Sqrt(disX * disX + disY * disY);// 当前边的长度
double optimalDis = GetOptimalLength(edge);//最优边的长度
// 计算吸引力(使用弹簧模型)
double attractiveForce = (dis - optimalDis) * AttractionForceConstant;
srcNode.VelocityX -= (attractiveForce * disX) / (dis + 0.001);
srcNode.VelocityY -= (attractiveForce * disY) / (dis + 0.001);
trgNode.VelocityX += (attractiveForce * disX) / (dis + 0.001);
trgNode.VelocityY += (attractiveForce * disY) / (dis + 0.001);
}
}
// 检查系统是否已经稳定
private bool IsStable()
{
foreach (var node in this.Nodes)
{
// 如果任一节点的移动超过阈值,则认为系统尚未稳定
if (Math.Abs(node.VelocityX) > MinMovement || Math.Abs(node.VelocityY) > MinMovement)
{
return false;
}
}
return true;
}
private void SetShapePosition()
{
List<Point> list = new List<Point>();
foreach (var node in this.Nodes)
{
list.Add(new Point(Convert.ToInt32(node.X), Convert.ToInt32(node.Y)));
}
int deltaX = 0;
int deltaY = 0;
Rectangle rc = CommonFun.GetRectangle(list.ToArray());
if (rc.X < 50)
deltaX = -rc.X + 100;
if (rc.Y < 50)
deltaY = -rc.Y + 100;
foreach (var v in this.Nodes)
{
v.X += deltaX;
v.Y += deltaY;
}
foreach (var node in this.Nodes)
{
node.Shape.MoveTo(new Point(Convert.ToInt32(node.X), Convert.ToInt32(node.Y)));
}
this.Nodes[0].Shape.Invalidate();
}
}
根据自己的情况,调整算法中的常量
2166

被折叠的 条评论
为什么被折叠?



