github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/drawtree/buccheim.py (about)

     1  # Code from:
     2  # https://raw.githubusercontent.com/llimllib/pymag-trees/master/buchheim.py
     3  
     4  from gen import Tree, gentree
     5  from operator import lt, gt
     6  from sys import stdout
     7  
     8  class DrawTree(object):
     9      def __init__(self, tree, parent=None, depth=0, number=1):
    10          self.x = -1.
    11          self.y = depth
    12          self.tree = tree
    13          self.children = [DrawTree(c, self, depth+1, i+1)
    14                           for i, c
    15                           in enumerate(tree.children)]
    16          self.parent = parent
    17          self.thread = None
    18          self.mod = 0
    19          self.ancestor = self
    20          self.change = self.shift = 0
    21          self._lmost_sibling = None
    22          #this is the number of the node in its group of siblings 1..n
    23          self.number = number
    24  
    25      def left(self): 
    26          return self.thread or len(self.children) and self.children[0]
    27  
    28      def right(self):
    29          return self.thread or len(self.children) and self.children[-1]
    30  
    31      def lbrother(self):
    32          n = None
    33          if self.parent:
    34              for node in self.parent.children:
    35                  if node == self: return n
    36                  else:            n = node
    37          return n
    38  
    39      def get_lmost_sibling(self):
    40          if not self._lmost_sibling and self.parent and self != self.parent.children[0]:
    41              self._lmost_sibling = self.parent.children[0]
    42          return self._lmost_sibling
    43      lmost_sibling = property(get_lmost_sibling)
    44  
    45      def __str__(self): return "%s: x=%s mod=%s" % (self.tree, self.x, self.mod)
    46      def __repr__(self): return self.__str__()
    47  
    48  def buchheim(tree):
    49      dt = firstwalk(DrawTree(tree))
    50      min = second_walk(dt)
    51      if min < 0:
    52          third_walk(dt, -min)
    53      return dt
    54  
    55  def third_walk(tree, n):
    56      tree.x += n
    57      for c in tree.children:
    58          third_walk(c, n)
    59  
    60  def firstwalk(n, distance=1.):
    61      if len(n.children) == 0:
    62          if n.lmost_sibling:
    63              n.x = n.lbrother().x + distance
    64          else:
    65              n.x = 0.
    66      else:
    67          default_ancestor = n.children[0]
    68          for c in n.children:
    69              firstwalk(c)
    70              default_ancestor = apportion(c, default_ancestor, distance)
    71          print( "finished n =", n.tree, "children")
    72          execute_shifts(n)
    73  
    74          midpoint = (n.children[0].x + n.children[-1].x) / 2
    75  
    76          ell = n.children[0]
    77          arr = n.children[-1]
    78          c = n.lbrother()
    79          if c:
    80              n.x = c.x + distance
    81              n.mod = n.x - midpoint
    82          else:
    83              n.x = midpoint
    84      return n
    85  
    86  def apportion(n, default_ancestor, distance):
    87      c = n.lbrother()
    88      if c is None:
    89          return default_ancestor
    90  
    91      #in buchheim notation:
    92      #i == inner; o == outer; r == right; l == left; r = +; l = -
    93      vir = vor = n
    94      vil = c
    95      vol = n.lmost_sibling
    96      sir = sor = n.mod
    97      sil = vil.mod
    98      sol = vol.mod
    99      while vil.right() and vir.left():
   100          vil = vil.right()
   101          vir = vir.left()
   102          vol = vol.left()
   103          vor = vor.right()
   104          vor.ancestor = n
   105          shift = (vil.x + sil) - (vir.x + sir) + distance
   106          if shift > 0:
   107              move_subtree(ancestor(vil, n, default_ancestor), n, shift)
   108              sir = sir + shift
   109              sor = sor + shift
   110          sil += vil.mod
   111          sir += vir.mod
   112          sol += vol.mod
   113          sor += vor.mod
   114      if vil.right() and not vor.right():
   115          vor.thread = vil.right()
   116          vor.mod += sil - sor
   117      else:
   118          if vir.left() and not vol.left():
   119              vol.thread = vir.left()
   120              vol.mod += sir - sol
   121          default_ancestor = n
   122      return default_ancestor
   123  
   124  def move_subtree(wl, wr, shift):
   125      subtrees = wr.number - wl.number
   126      print( wl.tree, "is conflicted with", wr.tree, 'moving', subtrees, 'shift', shift)
   127      #print( wl, wr, wr.number, wl.number, shift, subtrees, shift/subtrees)
   128      wr.change -= shift / subtrees
   129      wr.shift += shift
   130      wl.change += shift / subtrees
   131      wr.x += shift
   132      wr.mod += shift
   133  
   134  def execute_shifts(n):
   135      shift = change = 0
   136      for c in n.children[::-1]:
   137          print( "shift:", c, shift, c.change)
   138          c.x += shift
   139          c.mod += shift
   140          change += c.change
   141          shift += c.shift + change
   142  
   143  def ancestor(vil, n, default_ancestor):
   144      #the relevant text is at the bottom of page 7 of
   145      #"Improving Walker's Algorithm to Run in Linear Time" by Buchheim et al, (2002)
   146      #http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.16.8757&rep=rep1&type=pdf
   147      if vil.ancestor in n.parent.children:
   148          return vil.ancestor
   149      else:
   150          return default_ancestor
   151  
   152  def second_walk(n, m=0, depth=0, min=None):
   153      n.x += m
   154      n.y = depth
   155  
   156      if min is None or n.x < min:
   157          min = n.x
   158  
   159      for c in n.children:
   160          min = second_walk(c, m + n.mod, depth+1, min)
   161  
   162      return min
   163  
   164  r = 30
   165  rh = r*1.5
   166  rw = r*1.5
   167  stroke(0)
   168  
   169  def drawt(root, depth):
   170      global r
   171      oval(root.x * rw, depth * rh, r, r)
   172      print( root.x)
   173      for child in root.children:
   174          drawt(child, depth+1)
   175  
   176  def drawconn(root, depth):
   177      for child in root.children:
   178          line(root.x * rw + (r/2), depth * rh + (r/2),
   179               child.x * rw + (r/2), (depth+1) * rh + (r/2))
   180          drawconn(child, depth+1)
   181  
   182  size(1000, 500)
   183  translate(2, 2)
   184  stroke(0)
   185  drawconn(dt, 0)
   186  fill(1,1,1)
   187  drawt(dt, 0)