+class GraphRowGenerator (list):
+ """A :class:`GraphRow` generator.
+
+ Contains a list of :class:`GraphRow`\s (added with
+ :meth:`insert`(:class:`hooke.util.graph.Node`)). You should
+ generate a graph with repeated calls::
+
+ tip_to_root = True
+ g = GraphRowGenerator(tip_to_root=tip_to_root)
+ p = GraphRowPrinter(tip_to_root=tip_to_root)
+ for node in nodes:
+ g.insert(node)
+ print p(g[-1])
+
+ For the split/join branch columns, "born" and "dead" are defined
+ from the point of view of `GraphRow`. For root-to-tip ordering
+ (`tip_to_root==False`, the default), "born" runs are determined
+ by the active node's children (which have yet to be printed) and
+ "dead" runs by its parents (which have been printed). If
+ `tip_to_root==True`, swap "children" and "parents" in the above
+ sentence.
+ """
+ def __init__(self, tip_to_root=False):
+ list.__init__(self)
+ self.tip_to_root = tip_to_root
+ def insert(self, node):
+ """Add a new node to the graph.
+
+ If `tip_to_root==True`, nodes should be inserted in
+ tip-to-root topological order (i.e. node must be inserted
+ before any of its parents).
+
+ If `tip_to_root==False`, nodes must be inserted before any
+ of their children.
+ """
+ if len(self) == 0:
+ previous = GraphRow(node=None, active=-1)
+ else:
+ previous = self[-1]
+ current = GraphRow(node=node, active=-1, tip_to_root=self.tip_to_root)
+ if self.tip_to_root == True: # children die, parents born
+ dead_nodes = list(current.node.children)
+ born_nodes = list(current.node)
+ else: # root-to-tip: parents die, children born
+ dead_nodes = list(current.node)
+ born_nodes = list(current.node.children)
+ # Mark the dead and inherited branch columns
+ for i,node in previous.inherited + previous.born:
+ if node in dead_nodes or node == current.node:
+ current.dead.append((i, node))
+ else:
+ current.inherited.append((i, node))
+ # Place born and active branch columns
+ num_born = max(len(born_nodes), 1) # 1 to ensure slot for active node
+ remaining = num_born # number of nodes left to place
+ used_slots = [i for i,n in current.inherited]
+ old_max = max(used_slots+[-1]) # +[-1] in case used_slots is empty
+ slots = sorted([i for i in range(old_max+1) if i not in used_slots])
+ remaining -= len(slots)
+ slots.extend(range(old_max+1, old_max+1+remaining))
+ current.active = slots[0]
+ current.born = zip(slots, born_nodes)
+ # TODO: sharing branches vs. current 1 per child
+ self.append(current)
+
+