树型界面绘制算法(三) 磨人的apportion

目录

再探FirstWalk

Apportion

后记

 


再探FirstWalk

上章介绍了算法的大体流程,这章讨论firstwork的具体行为,以及firstwalk中的重量级函数apportion怎样去修正节点的偏移。

原文使用了15个节点的树状图用来测试,本项目继续沿用,节点大小为20,节点间间距为40。根据上章介绍的定位方法计算prelim和modifier,下面直接翻译原来的测试用例:

A.prelim = 0;  //无左兄弟无子节点

A.modifier = 0;

B.prelim = 0;

B.modifier = 0;

C.prelim = 0 + 20 + 40 = 60; //有左兄弟无子节点,B.prelim+节点宽度+间距

C.modifier = 0; 

D.prelim = 0 + 20 + 40 = 60;//有左兄弟有子节点,A.prelim+节点宽度+间距,计算修正字段modifier

D.modifier = 60 - (0 + 60) / 2;//D.prelim - (B.prelim + C.prelim)/ 2,左节点定位位置减去子节点定位位置 (apportion未生效)

这样的计算可以持续到节点M,在计算到N时apportion生效, 这里直接讨论N节点计算时发生的事情,之前节点运算结果如下:

 ABCDEFGHIJKLM
prelim0060603090006012018024060
modifier0003000000000-60

Apportion

apportion函数在firstwalk中,当前节点既有左兄弟又有子节点时发生的一次位置调整,具体逻辑是当子节点定位x坐标大于左兄弟定位x坐标时,当前节点prelim和modifier均累加偏移量。

下面根据节点N描述如何计算偏移量:

1. 初次计算N.prelim = 90 + 20 + 60 = 150(F.prelim + 节点宽度 + 间距),N.modifier = 150 - (0 + 60)/ 2 = 120 (N.prelim - (G.prelim + M.prelim)/ 2);

2. 下移一个深度,N树的最左节点为G,G的左邻居为D。D根据父节点定位0 + 120 = 120(D.prelim + N.modifier),根据左邻居定位 60 + 0 + 20 + 40 = 120 (D.prelim + E.modifier + 节点宽度 + 间距),相差值为0,没有偏移量。

3. 下移一个深度,N树的最左节点为H,H的左邻居为C。H根据父节点定位0 + -60 + 120 = 60(H.prelim + M.modifier + N.modifier),根据左兄弟定位60 + 30 + 0 + 20 + 40 = 150(C.prelim + D.modifier + E.modifier + 节点宽度 + 间距),150 - 60 = 90,因此N树的偏移量为90

PS: 偏移量计算应遍历到最底层叶节点;父节点定位时modifier累加到当前节点即可,再往上modifier都是一样的,这里算法跟secondwalk类似;偏移量小于0时忽略,左移会造成节点重叠。

 得到N的偏移量后,我们将N节点prelim右移90,modifier也加90,表示它的子节点需要移动这么多;对于其左兄弟们,E是C的祖宗节点,用于计算相对位置,中间的F分摊E移动的一半。(若中间有n个节点,则每个分摊1/(n + 1)的移动位置)

以上则是apportion的实现逻辑,D节点和M节点均调用过该函数,没有计算出有效偏移量。firstwalk执行结束后,计算结果如下:

 ABCDEFGHIJKLMNO
prelim00606030135006012018024060240135
modifier00030045000000-602100

apportion具体代码如下:

void apportion(QString name)
{
    Node curNode = nodeList.value(name);
    Node leftMost = nodeList.value(curNode.firstChild);     //当前节点下移一行,取最左节点
    Node neighbor = nodeList.value(leftMost.leftNeighbor);  //下移后左节点的左兄弟,进行比较
    int CompareDepth = 1;
    float leftModSum, rightModSum;
    //计算当前行理论最佳位置
    while (!leftMost.name.isEmpty() && !neighbor.name.isEmpty()) {
        //两个节点都存在,可进行比较时,累加其父节点的modifier(即理想位置)
        leftModSum = 0.0;
        rightModSum = 0.0;
        Node ancestorLeftmost = leftMost;
        Node ancestorNeighbor = neighbor;
        //从子节点往上遍历到当前深度
        for(int i = 0; i < CompareDepth; i++)
        {
            ancestorLeftmost = nodeList.value(ancestorLeftmost.parent);
            ancestorNeighbor = nodeList.value(ancestorNeighbor.parent);

            if(ancestorLeftmost.name.isEmpty() || ancestorNeighbor.name.isEmpty())
                continue;
            rightModSum += ancestorLeftmost.modifier;
            leftModSum += ancestorNeighbor.modifier;
        }
        float moveDistance = (neighbor.prelim + leftModSum + space + nodeSize) - (leftMost.prelim + rightModSum);
        //计算后应向右偏移,当前列所有节点分摊右移
        if(moveDistance > 0.0)
        {
            Node tempPtr = curNode;
            int leftSibling = 0;

            //往左数到子节点邻居的父节点,
            while (!tempPtr.name.isEmpty() && tempPtr.name != ancestorNeighbor.name) {
                leftSibling = leftSibling + 1;
                tempPtr = nodeList.value(tempPtr.leftSibling);
            }
            //从当前节点数到邻居父节点,对所有节点移位
            if(!tempPtr.name.isEmpty())
            {
                int portion = moveDistance / leftSibling;
                tempPtr = curNode;
                while (tempPtr.name != ancestorNeighbor.name) {//此处判定为不等,与原文相反(或者我对原文语言理解错误)
                    tempPtr.prelim = tempPtr.prelim + moveDistance;
                    tempPtr.modifier = tempPtr.modifier + moveDistance;
                    moveDistance = moveDistance - portion;
                    nodeList.insert(tempPtr.name, tempPtr);
                    tempPtr = nodeList.value(tempPtr.leftSibling);
                }
            }
            else
                return;
        }
        CompareDepth += 1;
        if(leftMost.firstChild.isEmpty())
            leftMost = getLeftMost(curNode, 0, CompareDepth);//当前深度,查找第一个有子节点的,取最左子节点
        else
            leftMost = nodeList.value(leftMost.firstChild);
        //相对原文新增操作,每次深度下降刷新左邻居节点
        neighbor = nodeList.value(leftMost.leftNeighbor);
    }
}

后记

关于树形图的绘图算法就介绍到这里,改版后的源代码已上传至https://github.com/Iyme/nodePosition,CSDN路径为https://download.csdn.net/download/zfy920323/10678561,源码中还有关于qml的绘图部分逻辑,就不详细介绍了。

在具体实现过程中,译者调试多次才将算法具体实现,主要对逻辑理解不够深入,以及使用C++语言时,函数跳转后数据缓存没有及时刷新,导致输出结果异常。

最后再次向该书作者致敬,优秀的算法可以成为经典并一直保留,国内相关研究还有很多可以学习的地方,希望程序员以后装逼的不是82年的雪碧,而是82年的老算法。

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值