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)