我的图形算法布局 力导向布局算法

这种算法的核心就是将有连接关系的图元靠近放置,将没有关系的图元相互之间远离
此算法中涉及到引力计算和斥力计算.
排布前图片如下
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/9979460420be4814bf8367f24a241e86.png#pic_center
排布后示意图:
在这里插入图片描述

首先定义节点

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();
    }
}

根据自己的情况,调整算法中的常量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值