Создание диаграммы узлов дерева

Я пытаюсь создать дерево, подобное диаграмме узлов, например пример изображения здесь. У меня есть следующий код:

    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 ответов


назначьте ширину каждому 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);

вы правы, что они будут перекрываться. Это потому, что вы добавляете/вычитаете фиксированное значение в 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), то мои первые несколько yOffsets 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 это выглядит так: