我正在尝试像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);
}
以下是此代码的屏幕截图:
答案 0 :(得分:5)
为每个node
分配宽度:
w
。d
+右子节点的宽度。
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,它看起来像这样: