pretty printing graphs

Bengt Richter bokr at oz.net
Tue Jul 29 01:48:49 EDT 2003


On 28 Jul 2003 16:30:03 -0700, mis6 at pitt.edu (Michele Simionato) wrote:

>"Scherer, Bill" <Bill.Scherer at verizonwireless.com> wrote in message news:<mailman.1059413382.3564.python-list at python.org>...
>
>Hey "dot" is great! I didn't know about it before readin your post.
>
>In a very short time I came out with the following recipe to draw
>Python inheritance hierarchies (which I post since it is pretty
>short and useful ;):
>
Well, here's the version with connectors:

====< pptree.py >===============================================
# pptree.py v 0.01 -- 20030728 22:20:17 bokr

class TextBox:
    def __init__(self, text):
        self.text = text
        lines = text.splitlines()
        self.bb = len(lines)+2, max(map(len, lines))+2 # rows,cols bounding box
    def __str__(self):
        return self.text 

class Node:
    PageHeight = 6*11; PageWidth = 78
    def __repr__(self): return '<Node w/ text %r ...>'%self.textBox.text.splitlines()[0]
    def treebb(self):  # tree bb incl this node
        childMaxHeight, childTotWidth = 0, 0
        for child in self.children:
            h, w = child.treebb()
            childMaxHeight = max(childMaxHeight, h)
            childTotWidth += w
        ret = childMaxHeight+self.textBox.bb[0], max(childTotWidth, self.textBox.bb[1])
        return ret
    def __init__(self, textBox):
        self.textBox = textBox
        self.children = []

    def boxlines(node, boxHeight, boxWidth):
        oh, ow = node.textBox.bb    # this node top text box bb
        th, tw = node.treebb()      # minimal child tree bb incl text box at top
        
        render = ['']*boxHeight
        ofmt = '|%%%ds|'% (ow-2)
        render[0] = ('+'+'-'*(ow-2)+'+').center(boxWidth)
        iLine=1
        for line in node.textBox.text.splitlines():
            render[iLine] = (ofmt%line).center(boxWidth)
            iLine += 1
        render[iLine] = render[0]
        iLine += 1
        if node.children:
            availSepSpaces = boxWidth - tw
            nch = len(node.children)
            sep = nch>1 and availSepSpaces//nch or 0
            childBoxes = []
            for child in node.children:
                chh, chw = child.treebb()
                childBoxes.append(child.boxlines(boxHeight-oh-1, sep and chw+sep or boxWidth))
            cbhs = map(len, childBoxes); assert max(cbhs)==min(cbhs) # all child boxes same ht
            # do connector line (with wasteful repetition)
            conn = ''.join(['+'.center(sep and child.treebb()[1]+sep or boxWidth)
                    for child in node.children])
            conn = conn.center(boxWidth)
            first = conn.find('+'); last = conn.rfind('+')
            conn = conn[:first] + conn[first:last].replace(' ','-') + conn[last:]
            center = '+'.center(boxWidth).find('+') # whatever the alg is
            conn = list(conn); conn[center]='|'; conn = ''.join(conn)
            render[iLine] = conn
            for iChildline in xrange(cbhs[0]):
                iLine += 1
                render[iLine] = ''.join(
                    [childBox[iChildline] for childBox in childBoxes]
                ).center(boxWidth)

        for iLine in range(boxHeight):
            if not render[iLine]: render[iLine] = ' '*boxWidth
        return render
        
    def __str__(self):
        return '\n'.join(self.boxlines(self.PageHeight, self.PageWidth))

    def showInPage(self, pageHeight=6*11, pageWidth=78):
        return '\n'.join(self.boxlines(pageHeight, pageWidth))

def test(height,width): # dimensions of chart
    # Example hierarchy    
    O = object
    class F(O): pass
    class E(O): pass
    class D(O): pass
    class G(O): pass
    class C(F,D,G): pass
    class B(E,D): pass
    class A(B,C): pass
    
    def explore(cls, tree):
        node = Node(TextBox(cls.__name__))
        tree.children.append(node)
        for b in cls.__bases__: explore(b, node)
    
    root = Node(TextBox('root'))
    explore(A, root)
    print
    print root.children[0].showInPage(height, width)
    
if __name__ == '__main__':
    import sys; args = sys.argv[1:]
    height = args and int(args.pop(0)) or 20
    width  = args and int(args.pop(0)) or 60
    test(height,width)
================================================================
Results: first 50 wide, then 90

[22:54] C:\pywk\clp>pptree.py 20 50

                       +-+
                       |A|
                       +-+
          +-------------|----------+
         +-+                      +-+
         |B|                      |C|
         +-+                      +-+
     +----|----+          +--------|--------+
    +-+       +-+        +-+      +-+      +-+
    |E|       |D|        |F|      |D|      |G|
    +-+       +-+        +-+      +-+      +-+
     |         |          |        |        |
  +------+  +------+   +------+ +------+ +------+
  |object|  |object|   |object| |object| |object|
  +------+  +------+   +------+ +------+ +------+

[22:54] C:\pywk\clp>pptree.py 20 90

                                           +-+
                                           |A|
                                           +-+
                    +-----------------------|--------------------+
                   +-+                                          +-+
                   |B|                                          |C|
                   +-+                                          +-+
          +---------|---------+                  +---------------|---------------+
         +-+                 +-+                +-+             +-+             +-+
         |E|                 |D|                |F|             |D|             |G|
         +-+                 +-+                +-+             +-+             +-+
          |                   |                  |               |               |
       +------+            +------+           +------+        +------+        +------+
       |object|            |object|           |object|        |object|        |object|
       +------+            +------+           +------+        +------+        +------+

The code is pretty hacky, but I wanted to show the art ;-)

BTW, TextBox can accept a multiline string, and finds the bounding box (without trimming blanks).

Regards,
Bengt Richter




More information about the Python-list mailing list