2012-04-10 23 views
6

Estoy tratando de crear un diagrama de nodo tipo árbol, como el example image here. Tengo el siguiente código:Creando un diagrama de nodo de árbol

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); 
     } 
    } 

Mi código casi funciona. El problema que estoy teniendo es que algunos de los nodos se superponen. En la imagen de arriba, los nodos 25 y 66 se superponen. La razón, estoy seguro, se debe a que matemáticamente los nodos derechos y los nodos derechos tienen el mismo espacio, por lo que el nodo derecho del padre se superpone con el nodo izquierdo del padre adyacente. ¿Como puedo solucionar este problema?

ACTUALIZACIÓN:

Ésta es la actualización de código que hice después de la sugerencia de 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); 
      } 

Aquí está una captura de pantalla de este código: Screen Shot

+1

No estoy seguro de cómo nos gustaría saber cómo solucionar el problema, ya que se puede manejar cualquier manera lo que quiere es: reducir los nodos ¡Cambia a los padres! Permitir la superposición Simplemente decida sobre una estrategia, calcule cuándo se necesitará esa estrategia e impleméntela. – dlev

+0

@dlev - no es tan fácil. Si cambio a los padres, el mismo problema les sucede a los niños (que también son padres) debajo de él. Reducir los nodos tampoco ayuda ... eso solo los encoge, pero aún se superpone. Tiene que ser algún tipo de solución matemática. – Icemanind

+0

No me refiero a ser flip: pasé por alto el "calcular cuándo se necesitará esa estrategia", pero ese es realmente el quid de esta cuestión. Es difícil decir más sin saber exactamente cómo desea exponer todo. – dlev

Respuesta

5

Asignar un ancho a cada node:

  • la el ancho de una hoja es el ancho de la imagen, w.
  • el ancho de un nodo es el ancho de su nodo secundario izquierdo + una constante d + el ancho del nodo secundario derecho.

        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; 
    } 
} 

Comenzando con el nodo raíz y x = 0, dibujar la imagen en el reducir a la mitad de la anchura, compensado por x.
luego calcular la posición x para cada nodo hijo y Recurse:

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); 
    } 
} 

Uso:

CalculateWidth(root); 

DrawNode(g, root, 0, 0); 
+0

Intenté esto y parece que no funciona bien. Por ejemplo, cuando se procesa en la pantalla, el Nodo 5 está justo debajo del Nodo 20, justo a la izquierda. Esta bien. Pero el Nodo 25 es empujado hacia la derecha. Los nodos 75 y 95 también se superponen. Puedo publicar el código si eso ayuda. – Icemanind

+0

Sí, publique el código. Y también incluye una captura de pantalla. – dtb

+0

De acuerdo, acabo de actualizar mi pregunta. Incluí los cambios que hice en el código y una captura de pantalla. – Icemanind

1

Tienes razón de que van traslapar. Es porque está agregando/restando un valor fijo a xOffset mientras recorre el árbol. En la imagen de ejemplo, en realidad no es un desplazamiento fijo: más bien, es logarítmico exponencial con respecto a su posición vertical. Cuanto más baje, menor será el desplazamiento.

Reemplace los 30 con A * Math.Log(yOffset), donde A es un valor de escala que tendrá que ajustar hasta que se vea bien.

EDIT: ¿o es exponencial? No puedo visualizar esto demasiado bien. En su lugar, puede terminar queriendo A * Math.Exp(-B * yOffset). (Lo negativo es significativo: esto significa que va a llegar más pequeña con mayor yOffset, que es lo que desea.)

A será como su maestro, el factor de escala lineal, mientras que B controlarán la rapidez con que el desplazamiento se menor.

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); 

Actualización:

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); 

Llamado con:

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

El - 10 en el Exp es significativo: es el yOffset inicial de la cabeza. Se produce lo siguiente:

Si desea un control margen/acolchado precisa a continuación, por todos los medios seguir con el método de DTB, pero creo que 3 líneas adicionales con una única fórmula es tan elegante una solución matemática como usted' va a llegar

Actualización 2:

Una cosa más me olvidaba: Estoy usando la base e = 2.7183, pero te gustaría algo más cercano a 2. Lógicamente se podría usar exactamente 2, pero debido a que los nodos tienen no Cero anchos, es posible que desee algo un poco más grande, como 2.1.Puede cambiar la base de multiplicar por BMath.Log(new_base):

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

También debería explicar cómo llegué el valor de 0.05f. Básicamente, está aumentando yOffset en 20 para cada nivel del árbol. Si restamos el yOffset inicial del encabezado (que es 10 en mi caso), mis primeros yOffset s son 0, 20, 40, 60, etc. Quiero que el desplazamiento x se reduzca a la mitad para cada fila; es decir,

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

Obviamente, B necesita ser 1/20, o 0.05. Consigo el valor Math.Log(2.1) partir de la relación:

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

Así, con la base 2.1, se ve así:

+0

¿No será necesario que sea Log base 2, not 10? – Servy

+0

Lo vi mal en mi cabeza, en realidad es exponencial (con un exponente negativo). Log10 y Log2 son asintóticamente iguales, es decir, Log10 x = C * Log2 x, para alguna constante C. –

+0

Tiene razón, es un exponente negativo. En cuanto a la base de registro, es cierto que hay una diferencia constante, pero esa constante será la base de registro 10 de 2 (en su ejemplo). Dado que habrá al menos una constante adicional para escalar (el ancho del espacio total), es más claro en mi mente separar cada constante en lugar de enrollarlas todas en una gran "C" sin definirla. – Servy

Cuestiones relacionadas