-
Notifications
You must be signed in to change notification settings - Fork 0
/
Visualize.py
117 lines (94 loc) · 6.38 KB
/
Visualize.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import functools as fn
from pprint import pprint
class Visualize(object):
def printBTree(self, node, nodeInfo=None, inverted=False, isTop=True):
# node value string and sub nodes
stringValue, leftNode, rightNode = nodeInfo(node)
stringValueWidth = len(stringValue)
# recurse to sub nodes to obtain line blocks on left and right
leftTextBlock = [] if not leftNode else self.printBTree(leftNode,nodeInfo,inverted,False)
rightTextBlock = [] if not rightNode else self.printBTree(rightNode,nodeInfo,inverted,False)
# count common and maximum number of sub node lines
commonLines = min(len(leftTextBlock),len(rightTextBlock))
subLevelLines = max(len(rightTextBlock),len(leftTextBlock))
# extend lines on shallower side to get same number of lines on both sides
leftSubLines = leftTextBlock + [""] * (subLevelLines - len(leftTextBlock))
rightSubLines = rightTextBlock + [""] * (subLevelLines - len(rightTextBlock))
# compute location of value or link bar for all left and right sub nodes
# * left node's value ends at line's width
# * right node's value starts after initial spaces
leftLineWidths = [ len(line) for line in leftSubLines ]
rightLineIndents = [ len(line)-len(line.lstrip(" ")) for line in rightSubLines ]
# top line value locations, will be used to determine position of current node & link bars
firstLeftWidth = (leftLineWidths + [0])[0]
firstRightIndent = (rightLineIndents + [0])[0]
# width of sub node link under node value (i.e. with slashes if any)
# aims to center link bars under the value if value is wide enough
#
# ValueLine: v vv vvvvvv vvvvv
# LinkLine: / \ / \ / \ / \
#
linkSpacing = min(stringValueWidth, 2 - stringValueWidth % 2)
leftLinkBar = 1 if leftNode else 0
rightLinkBar = 1 if rightNode else 0
minLinkWidth = leftLinkBar + linkSpacing + rightLinkBar
valueOffset = (stringValueWidth - linkSpacing) // 2
# find optimal position for right side top node
# * must allow room for link bars above and between left and right top nodes
# * must not overlap lower level nodes on any given line (allow gap of minSpacing)
# * can be offset to the left if lower subNodes of right node
# have no overlap with subNodes of left node
minSpacing = 2
rightNodePosition = fn.reduce(lambda r,i: max(r,i[0] + minSpacing + firstRightIndent - i[1]), \
zip(leftLineWidths,rightLineIndents[0:commonLines]), \
firstLeftWidth + minLinkWidth)
# extend basic link bars (slashes) with underlines to reach left and right
# top nodes.
#
# vvvvv
# __/ \__
# L R
#
linkExtraWidth = max(0, rightNodePosition - firstLeftWidth - minLinkWidth )
rightLinkExtra = linkExtraWidth // 2
leftLinkExtra = linkExtraWidth - rightLinkExtra
# build value line taking into account left indent and link bar extension (on left side)
valueIndent = max(0, firstLeftWidth + leftLinkExtra + leftLinkBar - valueOffset)
valueLine = " " * max(0,valueIndent) + stringValue
slash = "\\" if inverted else "/"
backslash = "/" if inverted else "\\"
uLine = "¯" if inverted else "_"
# build left side of link line
leftLink = "" if not leftNode else ( " " * firstLeftWidth + uLine * leftLinkExtra + slash)
# build right side of link line (includes blank spaces under top node value)
rightLinkOffset = linkSpacing + valueOffset * (1 - leftLinkBar)
rightLink = "" if not rightNode else ( " " * rightLinkOffset + backslash + uLine * rightLinkExtra )
# full link line (will be empty if there are no sub nodes)
linkLine = leftLink + rightLink
# will need to offset left side lines if right side sub nodes extend beyond left margin
# can happen if left subtree is shorter (in height) than right side subtree
leftIndentWidth = max(0,firstRightIndent - rightNodePosition)
leftIndent = " " * leftIndentWidth
indentedLeftLines = [ (leftIndent if line else "") + line for line in leftSubLines ]
# compute distance between left and right sublines based on their value position
# can be negative if leading spaces need to be removed from right side
mergeOffsets = [ len(line) for line in indentedLeftLines ]
mergeOffsets = [ leftIndentWidth + rightNodePosition - firstRightIndent - w for w in mergeOffsets ]
mergeOffsets = [ p if rightSubLines[i] else 0 for i,p in enumerate(mergeOffsets) ]
# combine left and right lines using computed offsets
# * indented left sub lines
# * spaces between left and right lines
# * right sub line with extra leading blanks removed.
mergedSubLines = zip(range(len(mergeOffsets)), mergeOffsets, indentedLeftLines)
mergedSubLines = [ (i,p,line + (" " * max(0,p)) ) for i,p,line in mergedSubLines ]
mergedSubLines = [ line + rightSubLines[i][max(0,-p):] for i,p,line in mergedSubLines ]
# Assemble final result combining
# * node value string
# * link line (if any)
# * merged lines from left and right sub trees (if any)
treeLines = [leftIndent + valueLine] + ( [] if not linkLine else [leftIndent + linkLine] ) + mergedSubLines
# invert final result if requested
treeLines = reversed(treeLines) if inverted and isTop else treeLines
# return intermediate tree lines or print final result
if isTop : print("\n".join(treeLines))
else : return treeLines