Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Positioning Nodes For General Trees


February 1991/Positioning Nodes For General Trees/Listing 1

Listing 1

/***********************************************************
 *  Node-Positioning for General Trees, by John Q. Walker II
 *
 *  Initiated by calling procedure TreePosition().
 **********************************************************/

#include <stdlib.h>

/*----------------------------------------------------------
 * Implementation dependent: Set the values for each of
 * these variables.
 *--------------------------------------------------------*/
#define NODE_WIDTH          20  /* Width of a node?       */
#define NODE_HEIGHT         10  /* Height of a node?      */
#define FRAME_THICKNESS     1   /* Fixed-sized node frame?*/
#define SUBTREE_SEPARATION  5   /* Gap between subtrees?  */
#define SIBLING_SEPARATION  4   /* Gap between siblings?  */
#define LEVEL_SEPARATION    5   /* Gap between levels?    */
#define MAXIMUM_DEPTH       10  /* Biggest tree?          */

/*----------------------------------------------------------
 * Implementation dependent: The structure for one node
 * - The first 4 pointers must be set for each node before
 *   this algorithm is called.
 * - The X and Y coordinates must be set for only the apex
 *   of the tree upon entry; they will be set by this
 *   algorithm for all the other nodes.
 * - The next three elements are used only for the duration
 *   of the algorithm.
 * - Actual contents of the node depend on your application.
 *--------------------------------------------------------*/
typedef int COORD;              /* X,Y coordinate type    */

typedef struct node {
   struct node *parent;        /* ptr: node's parent     */
   struct node *offspring;     /* ptr: leftmost child    */
   struct node *leftsibling;   /* ptr: sibling on left   */
   struct node *rightsibling;  /* ptr: sibling on right  */
   COORD  xCoordinate;         /* node's current x coord */
   COORD  yCoordinate;         /* node's current y coord */

   struct node *prev;          /* ptr: lefthand neighbor */
   float  flPrelim;            /* preliminary x coord    */
   float  flModifier;          /* temporary modifier     */

   char   info[80];            /* pick your contents!    */
} *PNODE;                       /* ptr: a node structure  */

/*----------------------------------------------------------
 * Global variables used by the algorithm
 *--------------------------------------------------------*/
typedef enum { FALSE, TRUE } BOOL;
typedef enum { FRAME, NO_FRAME } FRAME_TYPE;
typedef enum { NORTH, SOUTH, EAST, WEST } ROOT_ORIENTATION;

typedef struct prev_node {      /* one list element        */
   PNODE pPreviousNode;        /* ptr: previous at level  */
   struct prev_node *pNextLevel;   /*  ptr: next element  */
} *PPREVIOUS_NODE;

static FRAME_TYPE FrameType = FRAME;    /*  Show a frame   */
static ROOT_ORIENTATION RootOrientation = NORTH; /*  At top*/
static PPREVIOUS_NODE pLevelZero = (PPREVIOUS_NODE)0;

static COORD xTopAdjustment;    /* How to adjust the apex */
static COORD yTopAdjustment;    /* How to adjust the apex */
static float flMeanWidth;       /* Ave. width of 2 nodes  */

#define FIRST_TIME (0)          /* recursive proc flag    */

/*----------------------------------------------------------
 * Implemented as macros, but could be implemented as
 * procedures depending on your particular node structures
 *--------------------------------------------------------*/

#define FirstChild(node)     ((PNODE)((node)->offspring))
#define LeftSibling(node)    ((PNODE)((node)->leftsibling))
#define RightSibling(node)   ((PNODE)((node)->rightsibling))
#define Parent(node)         ((PNODE)((node)->parent))
#define LeftNeighbor(node)   ((PNODE)((node)->prev))
#define IsLeaf(node)          \
            (((node)&&(!((node)->offspring)))?TRUE:FALSE)
#define HasChild(node)        \
                  (((node)&&((node)->offspring))?TRUE:FALSE)
#define HasLeftSibling(node)   \
             (((node)&&((node)->leftsibling))?TRUE:FALSE)
#define HasRightSibling(node)  \
            (((node)&&((node)->rightsibling))?TRUE:FALSE)

static PNODE GetPrevNodeAtLevel (unsigned nLevelNbr)
{
   /*------------------------------------------------------
    * List Manipulation: Return pointer to previous node at
    * this level
    *----------------------------------------------------*/
   PPREVIOUS_NODE pTempNode;   /* used in the for-loop   */
   unsigned i = 0;             /* level counter          */

   for (pTempNode = pLevelZero; (pTempNode);
       pTempNode = pTempNode->pNextLevel) {
      if (i++ == nLevelNbr)
         /* Reached desired level.  Return its pointer */
         return (pTempNode->pPreviousNode);
   }
   return ((PNODE)0); /* No pointer yet for this level. */
}

static BOOL SetPrevNodeAtLevel (unsigned nLevelNbr,
                           PNODE pThisNode)
{

   /*------------------------------------------------------
    * List Manipulation: Set the list element to the
    * previous node at this level
    *----------------------------------------------------*/

   PPREVIOUS_NODE pTempNode;   /* used in the for-loop   */
   PPREVIOUS_NODE pNewNode;    /* newly-allocated memory */
   unsigned i = 0;             /* level counter          */

   for (pTempNode = pLevelZero; (pTempNode);
       pTempNode = pTempNode->pNextLevel) {
      if (i++ == nLevelNbr) {
         /* Reached desired level. Return its pointer */
         pTempNode->pPreviousNode = pThisNode;
         return (TRUE);
      }
      else if (pTempNode->pNextLevel==(PPREVIOUS_NODE)0) {
         /* Looks like we need a new level: add it.    */
         /* We need to keep going--should be the next. */
         pNewNode = (PPREVIOUS_NODE)
                  malloc(sizeof(struct prev_node));
         if (pNewNode) {
            pNewNode->pPreviousNode = (PNODE)0;
            pNewNode->pNextLevel = (PPREVIOUS_NODE)0;
            pTempNode->pNextLevel = pNewNode;
         }
         else return (FALSE);    /* The malloc failed. */
      }
   }

   /* Should only get here if pLevelZero is 0.           */
   pLevelZero = (PPREVIOUS_NODE)
              malloc(sizeof(struct prev_node));
   if (pLevelZero) {
      pLevelZero->pPreviousNode = pThisNode;
      pLevelZero->pNextLevel = (PPREVIOUS_NODE)0;
      return (TRUE);
   }
   else return (FALSE);        /* The malloc() failed.   */
}

static void InitPrevNodeAtLevel (void)
{
   /*------------------------------------------------------
    * List Manipulation: Initialize the list of the
    * previous node at each level to all zeros.
    *----------------------------------------------------*/

   PPREVIOUS_NODE pTempNode = pLevelZero; /*  the start    */

   for ( ; (pTempNode); pTempNode = pTempNode->pNextLevel)
      pTempNode->pPreviousNode = (PNODE)0;
}

static BOOL CheckExtentsRange(long lxTemp, long lyTemp)
{
   /*-----------------------------------------------------
    * Insert your own code here, to check when the
    * tree drawing is going to be too big. My region is no
    * more that 64000 units square.
    *---------------------------------------------------*/

   if ((labs(lxTemp) > 32000) || (labs(lyTemp) > 32000))
      return (FALSE);
   else
      return (TRUE);
}

static void TreeMeanNodeSize (PNODE pLeftNode,
                         PNODE pRightNode)
{
   /*------------------------------------------------------
    * Write your own code for this procedure if your
    * rendered nodes will have variable sizes.
    *------------------------------------------------------
    * Here I add the width of the contents of the
    * right half of the pLeftNode to the left half of the
    * right node. Since the size of the contents for all
    * nodes is currently the same, this module computes the
    * following trivial computation.
    *----------------------------------------------------*/

   flMeanWidth = (float)0.0;   /* Initialize this global */

   switch (RootOrientation) {
      case NORTH:
      case SOUTH:
         if (pLeftNode) {
            flMeanWidth = flMeanWidth +
                       (float)(NODE_WIDTH/2);
            if (FrameType != NO_FRAME)
               flMeanWidth = flMeanWidth +
                             (float)FRAME_THICKNESS;
         }
         if (pRightNode) {
            flMeanWidth = flMeanWidth +
                       (float)(NODE_WIDTH/2);
            if (FrameType != NO_FRAME)
                flMeanWidth = flMeanWidth +
                              (float)FRAME_THICKNESS;
         }
         break;
      case EAST :
      case WEST :
         if (pLeftNode) {
            flMeanWidth = flMeanWidth +
                       (float)(NODE_HEIGHT/2);
            if (FrameType != NO_FRAME)
                flMeanWidth = flMeanWidth +
                              (float)FRAME_THICKNESS;
         }
         if (pRightNode) {
            flMeanWidth = flMeanWidth +
                       (float)(NODE_HEIGHT/2);
            if (FrameType != NO_FRAME)
                flMeanWidth = flMeanWidth +
                              (float)FRAME_THICKNESS;
         }
         break;
   }
}

static PNODE TreeGetLeftmost(PNODE pThisNode,
                        unsigned nCurrentLevel,
                        unsigned nSearchDepth)
{
   /*------------------------------------------------------
    * Determine the leftmost descendant of a node at a
    * given depth. This is implemented using a post-order
    * walk of the subtree under pThisNode, down to the
    * level of nSearchDepth. If we've searched to the
    * proper distance, return the currently leftmost node.
    * Otherwise, recursively look at the progressively
    * lower levels.
    *----------------------------------------------------*/

   PNODE pLeftmost;    /* leftmost descendant at level   */
   PNODE pRightmost;   /* rightmost offspring in search  */

   if (nCurrentLevel == nSearchDepth)
      pLeftmost = pThisNode; /*  searched far enough.    */
   else if (IsLeaf(pThisNode))
      pLeftmost = 0;  /* This node has no descendants    */
   else {  /* Do a post-order walk of the subtree.        */
      for (pLeftmost = TreeGetLeftmost(pRightmost =
                          FirstChild(pThisNode),
                          nCurrentLevel + 1,
                          nSearchDepth)

         ;
         (pLeftmost==0) && (HasRightSibling(pRightmost))
         ;
         pLeftmost = TreeGetLeftmost(pRightmost =
                          RightSibling(pRightmost),
                          nCurrentLevel + 1,
                          nSearchDepth)
         ) { /* Nothing inside this for-loop. */ }
   }
   return (pLeftmost);
}

static void TreeApportion (PNODE pThisNode,
                      unsigned nCurrentLevel)
{
   /*------------------------------------------------------
    * Clean up the positioning of small sibling subtrees.
    * Subtrees of a node are formed independently and
    * placed as close together as possible. By requiring
    * that the subtrees be rigid at the time they are put
    * together, we avoid the undesirable effects that can
    * accrue from positioning nodes rather than subtrees.
    *----------------------------------------------------*/

   PNODE pLeftmost;            /* leftmost at given level*/
   PNODE pNeighbor;            /* node left of pLeftmost */
   PNODE pAncestorLeftmost;    /* ancestor of pLeftmost  */
   PNODE pAncestorNeighbor;    /* ancestor of pNeighbor  */
   PNODE pTempPtr;             /* loop control pointer   */
   unsigned i;                 /* loop control           */
   unsigned nCompareDepth;     /* depth of comparison    */
                          /* within this proc       */
   unsigned nDepthToStop;      /* depth to halt          */
   unsigned nLeftSiblings;     /* nbr of siblings to the */
           /* left of pThisNode, including pThisNode,  */
           /* til the ancestor of pNeighbor            */
   float flLeftModsum;         /* sum of ancestral mods  */
   float flRightModsum;        /* sum of ancestral mods  */
   float flDistance;           /* difference between     */
        /* where pNeighbor thinks pLeftmost should be   */
        /* and where pLeftmost actually is              */
   float flPortion;            /* proportion of          */
        /* flDistance to be added to each sibling       */

   pLeftmost = FirstChild(pThisNode);
   pNeighbor = LeftNeighbor(pLeftmost);

   nCompareDepth = 1;
   nDepthToStop = MAXIMUM_DEPTH - nCurrentLevel;

   while ((pLeftmost) && (pNeighbor) &&
         (nCompareDepth <= nDepthToStop)) {

      /* Compute the location of pLeftmost and where it */
      /* should be with respect to pNeighbor.           */
      flRightModsum = flLeftModsum = (float)0.0;
      pAncestorLeftmost = pLeftmost;
      pAncestorNeighbor = pNeighbor;
      for (i = 0; (i < nCompareDepth); i++) {
         pAncestorLeftmost = Parent(pAncestorLeftmost);
         pAncestorNeighbor = Parent(pAncestorNeighbor);
         flRightModsum = flRightModsum +
                      pAncestorLeftmost->flModifier;
         flLeftModsum = flLeftModsum +
                      pAncestorNeighbor->flModifier;

      }

      /* Determine the flDistance to be moved, and apply*/
      /* it to "pThisNode's" subtree.  Apply appropriate*/
      /* portions to smaller interior subtrees          */

      /* Set the global mean width of these two nodes   */
      TreeMeanNodeSize(pLeftmost, pNeighbor);

      flDistance = (pNeighbor->flPrelim +
                  flLeftModsum +
                  (float)SUBTREE_SEPARATION +
                  (float)flMeanWidth) -
                 (pLeftmost->flPrelim + flRightModsum);

      if (flDistance > (float)0.0) {
         /* Count the interior sibling subtrees        */
         nLeftSiblings = 0;
         for (pTempPtr = pThisNode;
              (pTempPtr) &&
              (pTempPtr != pAncestorNeighbor);
             pTempPtr = Leftsibling(pTempPtr)) {
            nLeftSiblings++;
         }

         if (pTempPtr) {
            /* Apply portions to appropriate          */
            /* leftsibling subtrees.                  */
            flPortion = flDistance/(float)nLeftSiblings;
            for (pTempPtr = pThisNode;
                (pTempPtr != pAncestorNeighbor);
                pTempPtr = LeftSibling(pTempPtr)) {
               pTempPtr->flPrelim =
                    pTempPtr->flPrelim + flDistance;
               pTempPtr->flModifier =
                    pTempPtr->flModifier + flDistance;
               flDistance = flDistance - flPortion;
             }
         }
         else {
            /* Don't need to move anything--it needs  */
            /* to be done by an ancestor because      */
            /* pAncestorNeighbor and                  */
            /* pAncestorLeftmost are not siblings of  */
            /* each other.                            */
            return;
         }
      }  /* end of the while                           */

      /* Determine the leftmost descendant of pThisNode */
      /* at the next lower level to compare its         */
      /* positioning against that of its pNeighbor.     */

      nCompareDepth++;
      if (IsLeaf(pLeftmost))
         pLeftmost = TreeGetLeftmost(pThisNode, 0,
                                 nCompareDepth);
      else
         pLeftmost = FirstChild(pLeftmost);
      pNeighbor = LeftNeighbor(pLeftmost);
   }
 }

 static BOOL TreeFirstWalk(PNODE pThisNode,
                       unsigned nCurrentLevel)
 {
   /*------------------------------------------------------
    * In a first post-order walk, every node of the tree is
    * assigned a preliminary x-coordinate (held in field
    * node->flPrelim). In addition, internal nodes are
    * given modifiers, which will be used to move their
    * children to the right (held in field
    * node->flModifier).
    * Returns: TRUE if no errors, otherwise returns FALSE.
    *----------------------------------------------------*/

   PNODE pLeftmost;            /* left- & rightmost      */
   PNODE pRightmost;           /* children of a node.    */
   float flMidpoint;           /* midpoint between left- */
                          /* & rightmost children   */

   /* Set up the pointer to previous node at this level  */
   pThisNode->prev = GetPrevNodeAtLevel(nCurrentLevel);

   /* Now we're it--the previous node at this level      */
   if (!(SetPrevNodeAtLevel(nCurrentLevel, pThisNode)))
      return (FALSE);        /* Can't allocate element  */

   /* Clean up old values in a node's flModifier         */
   pThisNode->flModifier = (float)0.0;

   if ((IsLeaf(pThisNode)) ||
      (nCurrentLevel == MAXIMUM_DEPTH)) {
      if (HasLeftSibling(pThisNode)) {

      /*--------------------------------------------
       * Determine the preliminary x-coordinate
       *   based on:
       * - preliminary x-coordinate of left sibling,
       * - the separation between sibling nodes, and
       * - mean width of left sibling & current node.
       *--------------------------------------------*/
      /* Set the mean width of these two nodes      */
      TreeMeanNodeSize(LeftSibling(pThisNode),
                    pThisNode);

      pThisNode->flPrelim =
                (pThisNode->Leftsibling->flPrelim) +
                (float)SIBLING_SEPARATION +
                flMeanWidth;
   }
   else    /*  no sibling on the left to worry about  */
      pThisNode->flPrelim = (float)0.0;
}
else {
   /* Position the leftmost of the children          */
   if (TreeFirstWalk(pLeftmost = pRightmost =
                  FirstChild(pThisNode),
                  nCurrentLevel + 1)) {
      /* Position each of its siblings to its right */
      while (HasRightSibling(pRightmost)) {
         if (TreeFirstWalk(pRightmost =
                        RightSibling(pRightmost),
                        nCurrentLevel + 1)) {
         }
         else return (FALSE); /* malloc() failed   */
      }

      /* Calculate the preliminary value between   */
      /* the children at the far left and right    */
      flMidpoint = (pLeftmost->flPrelim +
                  pRightmost->flPrelim)/(2.0);

      /* Set global mean width of these two nodes  */
      TreeMeanNodeSize(LeftSibling(pThisNode),
                    pThisNode);

         if (HasLeftSibling(pThisNode)) {
            pThisNode->flPrelim =
                   (pThisNode->leftsibling->flPreLim) +
                           (float)SIBLING_SEPARATION +
                           flMeanWidth;
            pThisNode->flModifier =
                pThisNode->flPrelim - flMidpoint;
            TreeApportion(pThisNode, nCurrentLevel);
         }
         else pThisNode->flPrelim = flMidpoint;
      }
      else return (FALSE); /* Couldn't get an element  */
   }
   return (TRUE);
}

static BOOL TreeSecondWalk(PNODE pThisNode,
                      unsigned nCurrentLevel)
{
   /*------------------------------------------------------
    * During a second pre-order walk, each node is given a
    * final x-coordinate by summing its preliminary
    * x-coordinate and the modifiers of all the node's
    * ancestors.  The y-coordinate depends on the height of
    * the tree.  (The roles of x and y are reversed for
    * RootOrientations of EAST or WEST.)
    * Returns: TRUE if no errors, otherwise returns FALSE.
    *----------------------------------------- ----------*/

   BOOL bResult = TRUE;        /* assume innocent        */
   long lxTemp, lyTemp;        /* hold calculations here */
   float flNewModsum;          /* local modifier value   */
   static float flModsum = (float)0.0;

   if (nCurrentLevel <= MAXIMUM_DEPTH) {
      flNewModsum = flModsum;  /* Save the current value  */
      switch (RootOrientation) {
         case NORTH:
            lxTemp = (long)xTopAdjustment +
              (long)(pThisNode->flPrelim + flModsum);
            lyTemp = (long)yTopAdjustment +
              (long)(nCurrentLevel * LEVEL_SEPARATION);
            break;
         case SOUTH:
            lxTemp = (long)xTopAdjustment +
              (long)(pThisNode->flPrelim + flModsum);
            lyTemp = (long)yTopAdjustment -
              (long)(nCurrentLevel * LEVEL_SEPARATION);
            break;
         case EAST:
            lxTemp = (long)xTopAdjustment +
              (long)(nCurrentLevel * LEVEL_SEPARATION);
            lyTemp = (long)yTopAdjustment -
              (long)(pThisNode->flPrelim + flModsum);
            break;
         case WEST:
            lxTemp = (long)xTopAdjustment -
              (long)(nCurrentLevel * LEVEL_SEPARATION);
            lyTemp = (long)yTopAdjustment -
              (long)(pThisNode->flPrelim + flModsum);
            break;
      }

      if (CheckExtentsRange(lxTemp, lyTemp)) {
         /* The values are within the allowable range */

         pThisNode->xCoordinate = (COORD)lxTemp;
         pThisNode->yCoordinate = (COORD)lyTemp;

         if (HasChild(pThisNode)) {
            /* Apply the flModifier value for this    */
            /* node to all its offspring.             */
            flModsum = flNewModsum =
                   flNewModsum + pThisNode->flModifier;
            bResult = TreeSecondWalk(
               FirstChild(pThisNode),nCurrentLevel + 1);
            flNewModsum = flNewModsum -
                        pThisNode->flModifier;
         }

         if ((HasRightSibling(pThisNode)) && (bResult)) {
            flModsum = flNewModsum;
            bResult = TreeSecondWalk(
                RightSibling(pThisNode), nCurrentLevel);
         }
      }
      else bResult = FALSE;   /* outside of extents   */
   }
   return (bResult);
}

static BOOL TreePosition(PNODE pApexNode)
{
   /*------------------------------------------------------
    * Determine the coordinates for each node in a tree.
    * Input: Pointer to the apex node of the tree
    * Assumption: The x & y coordinates of the apex node
    * are already correct, since the tree underneath it
    * will be positioned with respect to those coordinates.
    * Returns: TRUE if no errors, otherwise returns FALSE.
    *----------------------------------------------------*/

   if (pApexNode) {
      /* Initialize list of previous node at each level */
      InitPrevNodeAtLevel();

      /* Generate the properly-placed tree nodes.      */
      /* TreeFirstWalk: a post-order walk              */
      /* TreeSecondWalk: a pre-order walk              */
      if (TreeFirstWalk (pApexNode, FIRST_TIME)) {
         /* Determine how to adjust the nodes with     */
         /* respect to the location of the apex of the */
         /* tree being positioned.                     */
         switch (RootOrientation) {
            case NORTH:
            case SOUTH:
               /* Create the adjustment from x-coord  */
               xTopAdjustment = pApexNode->xCoordinate -
                        (COORD)(pApexNode->flPrelim);
               yTopAdjustment = pApexNode->yCoordinate;
               break;
            case EAST :
            case WEST :
               /* Create the adjustment from y-coord  */
               xTopAdjustment = pApexNode->xCoordinate;
               yTopAdjustment = pApexNode->yCoordinate +
                        (COORD)(pApexNode->flPrelim);
               break;
         }
         return (TreeSecondWalk(pApexNode, FIRST_TIME));
      }
      else return (FALSE); /*  Couldn't get an element   */
   }
   else return (TRUE); /*  Easy: null pointer was passed  */
}


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.