Создание диаграммы узлов дерева
Я пытаюсь создать дерево, подобное диаграмме узлов, например пример изображения здесь. У меня есть следующий код:
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);
}
вот скриншот из этого кода:
2 ответов
назначьте ширину каждому 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);
вы правы, что они будут перекрываться. Это потому, что вы добавляете/вычитаете фиксированное значение в xOffset по мере прохождения вниз по дереву. На рисунке примера это на самом деле не фиксированное смещение: скорее, это логарифмические экспоненциальный по отношению к его вертикальному положению. Чем ниже вы идете,тем меньше должно быть смещение.
замените 30-е на 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);
на - 10
в Exp значительно: Это начальное yOffset головки. Он производит следующее:
если вы хотите точный контроль маржи / заполнения, то непременно идите с методом dtb, но я думаю, что 3 дополнительные строки с одной формулой-это такое же элегантное математическое решение, как вы собираетесь получить.
обновление 2:
еще одна вещь, которую я забыл: я использую base e = 2.7183
, но вы хотели бы что-то ближе к 2. Логически вы бы использовали ровно 2, но поскольку узлы имеют ненулевую ширину, вам может понадобиться что-то немного больше, например 2.1. Вы можете изменить базу, умножив B
by Math.Log(new_base)
:
double B = 0.05f * Math.Log(2.1);
я должен также объяснить, как я получил значение 0.05 f. В принципе, вы увеличиваете yOffset
20 для каждого уровня дерево. Если вычесть начальное yOffset
головы (что в моем случае 10), то мои первые несколько yOffset
s 0, 20, 40, 60, etc. Я хочу, чтобы смещение 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 это выглядит так: