创建树节点图

时间:2012-04-10 23:47:58

标签: c# gdi+

我正在尝试像example image here一样创建类似节点图的树。我有以下代码:

    private void DrawNode(Graphics g, Node<T> node, float xOffset, float yOffset)
    {
        if (node == null)
        {
            return;
        }

        Bitmap bmp = (from b in _nodeBitmaps where b.Node.Value.Equals(node.Value) select b.Bitmap).FirstOrDefault();

        if (bmp != null)
        {
            g.DrawImage(bmp, xOffset, yOffset);

            DrawNode(g, node.LeftNode, xOffset - 30 , yOffset + 20);
            DrawNode(g, node.RightNode, xOffset + 30, yOffset + 20);
        }
    }

我的代码几乎正常运行。我遇到的问题是一些节点重叠。在上图中,节点25和66重叠。我确定,原因是因为它在数学上将左节点和右节点放置在空间上,所以父节点的右节点与相邻父节点的左节点重叠。我该如何解决这个问题?

更新:

这是我在dtb的建议后所做的代码更新:

            int nodeWidth = 0;
            int rightChildWidth = 0;

            if (node.IsLeafNode)
            {
                nodeWidth = bmp.Width + 50;
            }
            else
            {
                int leftChildWidth = 0;

                Bitmap bmpLeft = null;
                Bitmap bmpRight = null;

                if (node.LeftNode != null)
                {
                    bmpLeft =
                        (from b in _nodeBitmaps where b.Node.Value.Equals(node.LeftNode.Value) select b.Bitmap).
                            FirstOrDefault();
                    if (bmpLeft != null)
                        leftChildWidth = bmpLeft.Width;
                }
                if (node.RightNode != null)
                {
                    bmpRight =
                        (from b in _nodeBitmaps where b.Node.Value.Equals(node.RightNode.Value) select b.Bitmap).
                            FirstOrDefault();
                    if (bmpRight != null)
                        rightChildWidth = bmpRight.Width;
                }

                nodeWidth = leftChildWidth + 50 + rightChildWidth;
            }


            g.DrawImage(bmp, xOffset + (nodeWidth - bmp.Width) / 2, yOffset);

            if (node.LeftNode != null)
            {
                DrawNode(g, node.LeftNode, xOffset, yOffset + 20);
            }
            if (node.RightNode != null)
            {
                DrawNode(g, node.RightNode, xOffset + nodeWidth - rightChildWidth, yOffset + 20);
            }

以下是此代码的屏幕截图:Screen Shot

2 个答案:

答案 0 :(得分:5)

为每个node分配宽度:

  • 叶子的宽度是图像的宽度w
  • 节点的宽度是其左子节点的宽度+常量d +右子节点的宽度。

Illustration

void CalculateWidth(Node<T> node)
{
    node.Width = 20;
    if (node.Left != null)
    {
        CalculateWidth(node.Left);
        node.Width += node.Left.Width;
    }
    if (node.Right != null)
    {
        CalculateWidth(node.Right);
        node.Width += node.Right.Width;
    }
    if (node.Width < bmp.Width)
    {
        node.Width = bmp.Width;
    }
}

从根节点和x = 0开始,在宽度的一半处绘制图像,偏移x
然后计算每个子节点的x位置并递归:

void DrawNode(Graphics g, Node<T> node, double x, double y)
{
    g.DrawImage(x + (node.Width - bmp.Width) / 2, y, bmp);

    if (node.Left != null)
    {
        DrawNode(g, node.Left, x, y + 20);
    }
    if (node.Right != null)
    {
        DrawNode(g, node.Right, x + node.Width - node.Right.Width, y + 20);
    }
}

用法:

CalculateWidth(root);

DrawNode(g, root, 0, 0);

答案 1 :(得分:1)

你说他们会重叠是对的。这是因为当您遍历树时,您正在向xOffset添加/减去固定值。在示例图片中,它实际上不是固定的偏移量:相反,它的垂直位置是对数​​指数。越往下走,偏移应该越小。

将30s替换为A * Math.Log(yOffset),其中A是一些缩放值,您必须调整它才能看起来正确。

编辑:还是指数?我无法想象这些东西。您可能最终想要A * Math.Exp(-B * yOffset)。 (负面意义重大:这意味着它会以更大的yOffset 更小,这就是你想要的。)

A将类似于您的主线性缩放因子,而B将控制偏移变小的速度。

double A = some_number;
double B = some_other_number;
int offset = (int)(A * Math.Exp(-B * yOffset));
DrawNode(g, node.LeftNode, xOffset - offset , yOffset + 20);
DrawNode(g, node.RightNode, xOffset + offset, yOffset + 20);

<强>更新

double A = 75f;
double B = 0.05f;
int offset = (int)(A * Math.Exp(-B * (yOffset - 10)));
DrawNode(g, node.LeftNode, xOffset - offset, yOffset + 20);
DrawNode(g, node.RightNode, xOffset + offset, yOffset + 20);

跟:

DrawNode(e.Graphics, head, this.ClientSize.Width / 2, 10f);

Exp中的- 10很重要:它是头部的初始yOffset。它产生以下结果:

如果你想要精确的边距/填充控制,那么一定要使用dtb的方法,但我认为使用单个公式的3个额外线条就像你将要获得的数学解决方案一样优雅。

更新2:

我忘记了另外一件事:我正在使用基础e = 2.7183,但你想要更接近2的东西。逻辑上你会使用2,但因为节点的宽度非零,你可能想要一些东西有点大,比如2.1。您可以将B乘以Math.Log(new_base)来改变基数:

double B = 0.05f * Math.Log(2.1);

我还应该解释我是如何得到0.05f的值的。基本上,对于树的每个级别,您将yOffset增加20。如果我减去头部的初始yOffset(在我的情况下是10),那么我的前几个yOffset是0,20,40,60等。我希望x偏移是每排减半;就是这样,

2 ^ (-0B) = 1
2 ^ (-20B) = 0.5
2 ^ (-40B) = 0.25

显然,B需要是1/20或0.05。我从关系中得到Math.Log(2.1)值:

base ^ exponent == e ^ (ln(base) * exponent)

因此,对于基础2.1,它看起来像这样: