径向树布局算法

Posted

技术标签:

【中文标题】径向树布局算法【英文标题】:Radial Tree layout algorithm 【发布时间】:2015-10-25 09:39:36 【问题描述】:

我已经实现了一个树数据结构,其中每个节点都(递归地)持有一个指向它的子节点的指针列表。

我正在尝试计算 (x,y) 坐标以可视化树。 我浏览了这篇文章:

http://gbook.org/projects/RadialTreeGraph.pdf

Cut 我不知道如何通过第一级,即这是我到目前为止所写的:

for (int i = 0; i < GetDepth()+1; i++)

    if (i == 0)
    
        GetNodesInDepth(i).at(0)->SetXRadial(MIDDLE(m_nWidth));
        GetNodesInDepth(i).at(0)->SetYRadial(MIDDLE(m_nHeight));
        continue;
    

    double dNodesInDepth = GetNodesInDepth(i).size();
    double dAngleSpace = 2 * PI / dNodesInDepth;

    for (int j = 0; j < dNodesInDepth; j++)
    
        Node * pCurrentNode = GetNodesInDepth(i).at(j);

        pCurrentNode->SetXRadial((SPACING * i) * qCos(j * dAngleSpace) + MIDDLE(m_nWidth));
        pCurrentNode->SetYRadial((SPACING * i) * qSin(j * dAngleSpace) + MIDDLE(m_nHeight));
        pCurrentNode->m_dAngle = dAngleSpace * j;

        if (pCurrentNode->IsParent())
        
         //..(I'm stuck here)..//  
        
    

我不确定如何计算限制、平分线等。 这是我的抽屉到目前为止所做的:

这显然不是我从第二个(基于 0)级别开始寻找的。​​p>

我可以访问我需要的所有信息,以便获得我正在寻找的东西。

【问题讨论】:

所以(澄清一下)在您的图表中,唯一放错位置的是 30 节点? 您提供的链接包含您正在尝试执行的操作的代码。它非常混乱,但计算了不同级别的限制和平分线。您在理解他们的代码时遇到问题吗? 我试过了。它没有工作...... 您正在使用系统匈牙利符号。很多人认为这是一种反模式:isocpp.org/wiki/faq/style-and-techniques#hungarian MIDDLESPACING 宏吗? 【参考方案1】:

现在您可能已经自己弄清楚了。如果没有,这里

double dNodesInDepth = GetNodesInDepth(i).size();
double dAngleSpace = 2 * PI / dNodesInDepth;

您在第二层将角度空间设置为 PI(180 度),因为该层只有两个节点 dNodesInDepth = 2。这就是为什么在绘制节点20之后,节点30在180度之外。这种方法适用于非常密集的树木,因为那个角度会很小。但是在您的情况下,您希望保持角度尽可能接近父母的角度。因此,我建议您获取 2 级及更高级别节点的父级角度,并分散子级,使它们具有sibilingAngle = min(dAngleSpace, PI/10) 的角度空间。所以第一个孩子将拥有父母的确切角度,其余孩子彼此相距sibilingAngle。你明白了,可能会有更好的方法。我正在使用min,以防您在该级别有太多节点,您希望将节点挤得更近。

您链接到的文章使用了Figure 2 – Tangent and bisector limits for directories 中说明的解决方案。我不太喜欢这种方法,因为如果您确定子项的绝对角度而不是相对于父项的绝对角度,您可以有一个更简单/更清晰的解决方案,该解决方案正是该方法试图对这么多操作进行的操作。

更新:

以下代码生成此图像:

我认为您可以很容易地弄清楚如何使子节点居中等。

#include <cairo/cairo.h>
#include <math.h>
#include <vector>

using namespace std;

class Node 
public:
    Node() 
        parent = 0;
        angle = 0;
        angleRange = 2*M_PI;
        depth = 0;
    
    void addChildren(int n) 
        for (int i=0; i<n; i++) 
            Node* c = new Node;
            c->parent = this;
            c->depth = depth+1;
            children.push_back(c);
        
    
    vector<Node*> children;
    float angle;
    float angleRange;
    Node* parent;
    int depth;
    int x, y;
;

void rotate(float x, float y, float angle, float& nx, float& ny) 
    nx = x * cos(angle) - y * sin(angle);
    ny = x * sin(angle) + y * cos(angle);

void draw(Node* root, cairo_t *cr) 
    if (root->parent == 0) 
        root->x = root->y = 300;
        cairo_arc(cr, root->x, root->y, 3, 0, 2 * M_PI);
    

    int n = root->children.size();
    for (int i=0; i<root->children.size(); i++) 
        root->children[i]->angle = root->angle + root->angleRange/n * i;
        root->children[i]->angleRange = root->angleRange/n;

        float x, y;
        rotate(40 * root->children[i]->depth, 0, root->children[i]->angle, x, y);
        root->children[i]->x = 300+x;
        root->children[i]->y = 300+y;

        cairo_move_to(cr, root->x, root->y);
        cairo_line_to(cr, root->children[i]->x, root->children[i]->y);
        cairo_set_source_rgb(cr, 0, 0, 0);
        cairo_stroke(cr);

        cairo_arc(cr, 300+x, 300+y, 3, 0, 2 * M_PI);
        cairo_set_source_rgb(cr, 1, 1, 1);
        cairo_stroke_preserve(cr);
        cairo_set_source_rgb(cr, 0, 0, 0);
        cairo_fill(cr);

        draw(root->children[i], cr);
    


int main(void) 
    Node root;
    root.addChildren(4);
    root.children[0]->addChildren(3);
    root.children[0]->children[0]->addChildren(2);
    root.children[1]->addChildren(5);
    root.children[2]->addChildren(5);
    root.children[2]->children[1]->addChildren(2);
    root.children[2]->children[1]->children[1]->addChildren(2);

    cairo_surface_t *surface;
    cairo_t *cr;

    surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 600, 600);
    cr = cairo_create(surface);

    cairo_rectangle(cr, 0.0, 0.0, 600, 600);
    cairo_set_source_rgb(cr, 1, 1, 1);
    cairo_fill(cr);

    cairo_set_line_width(cr, 2);

    for (int i=0; i<6; i++) 
        cairo_arc(cr, 300, 300, 40*i, 0, 2 * M_PI);
        cairo_set_source_rgb(cr, .5, .5, .5);
        cairo_stroke(cr);
    

    draw(&root, cr);

    cairo_surface_write_to_png(surface, "image.png");

    cairo_destroy(cr);
    cairo_surface_destroy(surface);

    return 0;

更新 2: 为了让您更容易,这里是如何使节点居中:

for (int i=0; i<root->children.size(); i++) 
    float centerAdjust = 0;
    if (root->parent != 0) 
        centerAdjust = (-root->angleRange + root->angleRange / n) / 2;
    
    root->children[i]->angle = root->angle + root->angleRange/n * i + centerAdjust;
    root->children[i]->angleRange = root->angleRange/n;

显示更多的树:

【讨论】:

我已经尝试了你的建议。我不确定如何定义每个级别的角度。我在整个关卡圆上平均分割每个关卡中的每个角度空间 感谢您精心设计的解决方案。我尝试编写没有 cairo 部分的代码(我只需要 x、y 坐标),但它把它们都放在同一个 X 轴上 那你搞错了,最好分享下你的代码,我试试看 谢谢!在原始代码中搜索 for (int i=0; i&lt;root-&gt;children.size(); i++) 然后从该行到 root-&gt;children[i]-&gt;angleRange = root-&gt;angleRange/n; 应该替换为更新的代码块。【参考方案2】:

这是文章中应该可以工作的算法的实现(注意:我没有编译它,因为我没有你程序的其他部分):

void Tree::CalculateAngles()

    // IsEmpty() returns true if the tree is empty, false otherwise
    if (!IsEmpty())
    
        Node* pRoot = GetNodesInDepth(0).at(0);
        pRoot->SetAngle(0);
        // Relative to the current angle
        pRoot->SetTangentLimit(PI);
        // Absolute limit
        pRoot->SetLowerBisector(-PI);
        pRoot->SetHigherBisector(PI);
    
    for (int depth = 1; depth < GetDepth() + 1; depth++)
    
        double dDepth = (double)depth;
        // The last non-leaf node in of the current depth (i.e. node with children)
        Node* pPreviousNonleafNode = NULL;
        // The first non-leaf node
        Node* pFirstNonleafNode = NULL;
        // The parent of the previous node
        Node* pPreviousParent = NULL;
        int indexInCurrentParent = 0;
        double dTangentLimt = acos( dDepth / (dDepth + 1.0) );
        for (int i = 0; i < GetNodesInDepth(depth).size(); i++)
        
            Node* pCurrentNode = GetNodesInDepth(depth).at(i);
            Node* pParent = pCurrentNode->GetParent();
            if (pParent != pPreviousParent)
            
                pPreviousParent = pParent;
                indexInCurrentParent = 0;
            
            // (GetMaxChildAngle() - GetMinChildAngle()) / GetChildCount()
            double angleSpace = pParent->GetAngleSpace();
            pCurrentNode->SetAngle(angleSpace * (indexInCurrentParent + 0.5));
            pCurrentNode->SetTangentLimit(dTangentLimt);
            if (pCurrentNode->IsParent())
            
                if (!pPreviousNonleafNode)
                
                    pFirstNonleafNode = pCurrentNode;
                
                else
                
                    double dBisector = (pPreviousNonleafNode->GetAngle() + pCurrentNode->GetAngle()) / 2.0;
                    pPreviousNonleafNode->SetHigherBisector(dBisector);
                    pCurrentNode->SetLowerBisector(dBisector);
                
                pPreviousNonleafNode = pCurrentNode;
            
            indexInCurrentParent++;
        
        if (pPreviousNonleafNode && pFirstNonleafNode)
        
            if (pPreviousNonleafNode == pFirstNonleafNode)
            
                double dAngle = pFirstNonleafNode->GetAngle();
                pFirstNonleafNode->SetLowerBisector(dAngle - PI);
                pFirstNonleafNode->SetHigherBisector(dAngle + PI);
            
            else
            
                double dBisector = PI + (pPreviousNonleafNode->GetAngle() + pFirstNonleafNode->GetAngle()) / 2.0;
                pFirstNonleafNode->SetLowerBisector(dBisector);
                pPreviousNonleafNode->SetHigherBisector(dBisector);
            
        
    


void Tree::CalculatePositions()

    for (int depth = 0; depth < GetDepth() + 1; depth++)
    
        double redius = SPACING * depth;
        for (int i = 0; i < GetNodesInDepth(depth).size(); i++)
        
            Node* pCurrentNode = GetNodesInDepth(depth).at(i);
            double angle = pCurrentNode->GetAngle();
            pCurrentNode->SetXRadial(redius * qCos(angle) + MIDDLE(m_nWidth));
            pCurrentNode->SetYRadial(redius * qSin(angle) + MIDDLE(m_nHeight));
        
    


void Tree::CalculateLayout ()

    CalculateAngles();
    CalculatePositions();


double Node::GetAngleSpace()

    return (GetMaxChildAngle() - GetMinChildAngle()) / GetChildCount();

注意:我试图模仿你的代码风格,这样你就不必重构它来匹配你程序的其他部分。

附:如果您发现任何错误,请在 cmets 中通知我 - 我将编辑我的答案。

【讨论】:

感谢您的努力。我明天会试试这个,如有任何问题,我会及时通知您。 其实这行有问题:double angleSpace = pParent->GetAngleSpace();对我来说,这个成员字段(angleSpace)没有在任何地方修改,只是被访问,这看起来很奇怪。另外 - 行:double mid_x = MIDDLE(m_nWidth) 等,m_nWidth 是屏幕的布局,它是恒定的(在树声明中定义)。你想在那里实现什么目标? 谁说你getter应该有对应的fields和setter?这个函数应该根据角度范围和子节点的数量来计算角度空间(见 cmets)。关于 mid_x:它并没有真正改善任何东西,但也不会变得更糟。 @wannabeprogrammer 我想我使用了mid_x 因为它会使你的代码(从问题中)更小,但现在我看我的代码,发现直接调用 MIDDLE 简化了我的代码(更改它)。 @wannabeprogrammer 顺便说一句,现在我看到了更好的解决方案:让Node 自行计算 x 和 y(是的,同样是 getter,没有 setter)。

以上是关于径向树布局算法的主要内容,如果未能解决你的问题,请参考以下文章

布局算法之树布局

树布局算法(翻译)

簇布局

树图布局算法

布局优化方法

多级分层边缘捆绑