日照市网站建设_网站建设公司_HTML_seo优化
2026/1/18 22:40:18 网站建设 项目流程

WPF 力导引算法构建图布局

WPF 力导引算法实现图布局

1 经典基础算法

  1. Eades 算法 (1984)

  2. Fruchterman-Reingold 算法 (1991)

  3. Kamada-Kawai 算法 (1989)

2 高性能优化算法(适合大规模网络)

  1. Barnes-Hut 近似算法 (基于四叉树/八叉树)

    • 核心思想:将远距离节点群近似为单个质心,将斥力计算复杂度从 O(N²) 降为 O(N log N)。

    • 应用:常用于天体物理的N-body问题,被集成到许多力导引布局工具中。

  2. ForceAtlas2 (2011)

    • 核心思想:专为Gephi软件设计的算法,在FR基础上改进:

      • 引入度依赖的斥力(高度数节点产生更强斥力,避免中心重叠)。

      • 支持边缘权重调整吸引力。

      • 采用自适应温度防止震荡的阻尼。

    • 特点:适合复杂真实网络(如社交网络),布局层次清晰。

  3. LinLog 模型

    • 核心思想:优化能量函数为U = Σ边的距离 - Σ节点对的log(距离),强调聚类结构

    • 特点:同一簇内节点更紧密,簇间距离更明显。

3 代码实现

  1. 数据模型
/// <summary>/// 节点
/// </summary>
public class Node
{
public string Id { get; set; }
public double X { get; set; }
public double Y { get; set; }
public double Radius { get; set; }
// 布局计算中使用的临时变量
internal double DX { get; set; } // X轴位移/速度
internal double DY { get; set; } // Y轴位移/速度
internal int Degree { get; set; } // 节点的度(连接数)
public Node(string id, double radius = 5.0)
{
Id = id;
Radius = radius;
}
public override string ToString() => $"Node[{Id}]: ({X:F2}, {Y:F2})";
}
/// <summary>/// 关系
/// </summary>
public class Relationship
{
public string Id { get; set; }
public string SourceId { get; set; }
public string TargetId { get; set; }
public Relationship(string source, string target)
{
SourceId = source;
TargetId = target;
Id = Guid.NewGuid().ToString();
}
public Relationship(string source, string target, string id)
{
SourceId = source;
TargetId = target;
Id = id;
}
}
/// <summary>/// 图结构
/// </summary>
public class Graph
{
public Dictionary<string, Node> Nodes { get; set; }public Dictionary<string, Relationship> Relationships { get; set; }public Graph(){Nodes = new Dictionary<string, Node>();Relationships = new Dictionary<string, Relationship>();}public void AddNode(Node node){Nodes[node.Id] = node;}public void AddRelationship(Relationship relationship){Relationships[relationship.Id] = relationship;}}/// <summary>/// 矩形布局范围/// </summary>public struct LayoutBounds{public double MinX;public double MaxX;public double MinY;public double MaxY;public LayoutBounds(double x, double y, double width, double height){MinX = x;MinY = y;MaxX = x + width;MaxY = y + height;}public double Width => MaxX - MinX;public double Height => MaxY - MinY;public double CenterX => (MinX + MaxX) / 2.0;public double CenterY => (MinY + MaxY) / 2.0;}
  1. 核心算法
public class LinLogLayout
{
// 配置参数
private const int Iterations = 100;             // 迭代次数
private const double RepulsionForce = 20.0;     // 斥力
private const double AttractionForce = 1.0;     // 引力
private const double StepSize = 0.8;            // 稍微调大初始步长以适应约束
private const double Decay = 0.97;              // 衰变系数
private const double GravityStrength = 0.01;    // 强度越大,图越紧凑在中央
private const double MinClearance = 0.1;        // 斥力计算的最小安全距离(防止除以零,并作为最小非重叠缓冲)
private const double Epsilon = 1e-6;            // 防止除以零的微小值 (Epsilon)
private const double MaxMovementPerStep = 2.0;  // 限制节点单步最大移动距离。值越小,越稳定,但收敛越慢。在边界小的情况下,此值应适当减小。
/// <summary>/// 执行布局计算
/// </summary>
/// <param name="nodes">节点列表</param>
/// <param name="relations">关系列表</param>
/// <param name="bounds">矩形限制范围</param>
public void ComputeLayout(List<Node> nodes, List<Relationship> relations, LayoutBounds bounds){if (nodes == null || nodes.Count == 0) return;// 1. 初始化:将节点随机放置在边界范围内//    这比在(0,0)附近随机更好,避免一开始就飞出很远Random rnd = new Random();foreach (var n in nodes){// 如果节点还没坐标(或是0,0),给一个范围内的随机值if (Math.Abs(n.X) < 0.001 && Math.Abs(n.Y) < 0.001){n.X = bounds.MinX + rnd.NextDouble() * bounds.Width;n.Y = bounds.MinY + rnd.NextDouble() * bounds.Height;}}var nodeDict = nodes.ToDictionary(n => n.Id);// 筛选有效关系var validRelations = relations.Where(r => nodeDict.ContainsKey(r.SourceId) && nodeDict.ContainsKey(r.TargetId)).ToList();double currentStep = StepSize;// 2. 迭代循环for (int i = 0; i < Iterations; i++){// 重置力向量foreach (var n in nodes) { n.DX = 0; n.DY = 0; }// --- A. 计算斥力 (Repulsion) 考虑 Radius ---for (int a = 0; a < nodes.Count; a++){var nodeA = nodes[a];for (int b = a + 1; b < nodes.Count; b++){var nodeB = nodes[b];double dx = nodeA.X - nodeB.X;double dy = nodeA.Y - nodeB.Y;double distSq = dx * dx + dy * dy;double dist = Math.Sqrt(distSq); // 节点中心点的实际距离// NaN 保护机制 1: 检查中心距离是否为零if (dist < Epsilon){// 节点完全重叠,施加随机扰动以获得方向dx = rnd.NextDouble() * 2 * Epsilon;dy = rnd.NextDouble() * 2 * Epsilon;dist = Math.Sqrt(dx * dx + dy * dy);// 确保 dist 不会是 NaN 或零if (dist < Epsilon) dist = Epsilon;}// 1. 计算两个节点半径之和double sumOfRadii = nodeA.Radius + nodeB.Radius;// 2. 计算节点表面之间的净空距离 (Clearance)double clearance = dist - sumOfRadii;// 3. 确定用于斥力计算的有效距离 (Effective Distance)// 如果节点重叠 (clearance <= 0),将有效距离强制设为 MinClearance (0.1) // 这将产生巨大的斥力来推开重叠的节点double effectiveDist = Math.Max(clearance, MinClearance);// 4. 应用 LinLog 斥力公式: F = K / effectiveDist// 注意:这里的斥力是基于表面距离的,而不是中心距离。double force = RepulsionForce / effectiveDist;// 5. 应用斥力 (方向仍基于中心点连线)double fx = (dx / dist) * force;double fy = (dy / dist) * force;nodeA.DX += fx; nodeA.DY += fy;nodeB.DX -= fx; nodeB.DY -= fy;}}// --- B. 计算引力 (Attraction) ---foreach (var rel in validRelations){var u = nodeDict[rel.SourceId];var v = nodeDict[rel.TargetId];double dx = v.X - u.X;double dy = v.Y - u.Y;double distSq = dx * dx + dy * dy;double dist = Math.Sqrt(distSq);// NaN 保护机制 2: 检查中心距离是否为零if (dist < Epsilon){// 节点重叠,跳过引力计算(让斥力将它们推开)continue;}// LinLog Attractiondouble force = AttractionForce;double fx = (dx / dist) * force;double fy = (dy / dist) * force;u.DX += fx;u.DY += fy;v.DX -= fx;v.DY -= fy;}// --- C. 计算中心引力 (Central Gravity) ---double centerX = bounds.CenterX;double centerY = bounds.CenterY;foreach (var n in nodes){double dx = n.X - centerX; // 节点指向中心的向量是 -dxdouble dy = n.Y - centerY;double dist = Math.Sqrt(dx * dx + dy * dy);// NaN 保护机制 3: 检查距离是否为零if (dist < Epsilon){// 节点在中心点,力为零,跳过计算continue;}double force = GravityStrength * dist;n.DX -= (dx / dist) * force;n.DY -= (dy / dist) * force;}// --- D. 更新位置并应用边界约束 ---foreach (var n in nodes){// 1. 计算理论位移double movementX = n.DX * currentStep;double movementY = n.DY * currentStep;double movementDist = Math.Sqrt(movementX * movementX + movementY * movementY);// 2. 限制最大位移if (movementDist > MaxMovementPerStep){// 如果超出最大限制,按比例缩小位移向量double ratio = MaxMovementPerStep / movementDist;movementX *= ratio;movementY *= ratio;}// 3. 应用位移n.X += movementX;n.Y += movementY;// 4. 矩形边界硬约束if (n.X < bounds.MinX) n.X = bounds.MinX;else if (n.X > bounds.MaxX) n.X = bounds.MaxX;if (n.Y < bounds.MinY) n.Y = bounds.MinY;else if (n.Y > bounds.MaxY) n.Y = bounds.MaxY;}//foreach (var n in nodes)//{//    // 移动//    n.X += n.DX * currentStep;//    n.Y += n.DY * currentStep;//    // 边界限制 (Clamping)//    if (n.X < bounds.MinX) n.X = bounds.MinX;//    else if (n.X > bounds.MaxX) n.X = bounds.MaxX;//    if (n.Y < bounds.MinY) n.Y = bounds.MinY;//    else if (n.Y > bounds.MaxY) n.Y = bounds.MaxY;//}// --- E. 冷却 ---currentStep *= Decay;}}}

效果Demo:
在这里插入图片描述
在这里插入图片描述
效果视频:

https://live.csdn.net/v/504775
https://raw.githubusercontent.com/Winemonk/images/master/blog/post/202512091617226.mp4

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询