Example #1
0
	def __init__(self, data = 
		(
			'root', 
			(
				'foo', 
				(
					'leaf', 
				)
			), 
			(
				'bar', 
				(
					'node', 
					(
						'buz', 
						(
							'yeah', 
						)
					), 
					(
						'boo', 
						(
							'what', 
						)
					),
					(
						'hoo', 
						(
							'ever', 
						)
					)
				)
			)
		)
	):
		self._data = data
		self._canvas = TextCanvas()
		self._draw()
Example #2
0
class TextTree(object):
	""" 
	Draws an ASCII art representation of a tree:
	
		         root                  
		   _______|_______             
		  |               |            
		 foo             bar           
		  |               |            
		  |               |            
		 leaf            node          
		           _______|_______     
		          |       |       |    
		         buz     boo     hoo   
		          |       |       |    
		          |       |       |    
		         yeah    what    ever  
	
	
	The above example was drawn from the following tuple structure:	
		(
			'root', 
			(
				'foo', 
				(
					'leaf', 
				)
			), 
			(
				'bar', 
				(
					'node', 
					(
						'buz', 
						(
							'yeah', 
						)
					), 
					(
						'boo', 
						(
							'what', 
						)
					),
					(
						'hoo', 
						(
							'ever', 
						)
					)
				)
			)
		)

	Example usage:
		print str(TextTree())

	"""

	def __init__(self, data = 
		(
			'root', 
			(
				'foo', 
				(
					'leaf', 
				)
			), 
			(
				'bar', 
				(
					'node', 
					(
						'buz', 
						(
							'yeah', 
						)
					), 
					(
						'boo', 
						(
							'what', 
						)
					),
					(
						'hoo', 
						(
							'ever', 
						)
					)
				)
			)
		)
	):
		self._data = data
		self._canvas = TextCanvas()
		self._draw()

	def get_canvas(self):
		"""Get the TextCanvas the tree was drawn on"""
		return self._canvas

	# From http://stackoverflow.com/questions/10482339/how-to-find-median
	@classmethod
	def median(cls, lst):
	    even = (0 if len(lst) % 2 else 1) + 1
	    half = (len(lst) - 1) / 2
	    return sum(sorted(lst)[half:half + even]) / float(even)

	def _draw_title(self, title, padding = 2):
		# Here's how the title should be positioned relative to the stem below it for some example title widths.

		# t (0, 0)
		# | (0, 0)

		# ti (0, 0) 
		# |  (0, 0)

		# tit (-1, 0) 
		#  |  (0, 0)

		# titl (-1, 0)
		#  |   (0, 0)

		# title (-2, 0)
		#   |   (0, 0)

		# Set the title corresponding to the sketches above
		title = " "*padding + title + " "*padding
		title_x_position = math.ceil((1.0-len(title))/2.0) # gradually goes more to the left
		self._canvas.set(title, title_x_position, 0)

	def _x_centers(self, canvases):
		x_centers = []

		# Start at the leftmost point in the first canvas
		current_x = math.ceil((1.0-canvases[0].width())/2.0)

		for i, canvas in enumerate(canvases):

			# Go to the center position of the current canvas
			current_x -= math.ceil((1.0-canvas.width())/2.0)

			x_centers.append(current_x)

			# Go to the leftmost position of the next canvas
			current_x += math.ceil((1.0-canvas.width())/2.0) + canvas.width()

		return x_centers

	def _draw(self):
		title, subtrees = self._data[0], self._data[1:]

		self._draw_title(title)

		# Stem and drawing of subtrees is only needed if there are some.
		if not subtrees:
			return

		# Where to place those subtrees exactly isn't known until the width of subtree drawings is known.
		# So draw them all and then move them into place.
		canvases = []
		for subtree in subtrees:
			tree = TextTree(subtree)
			canvases.append(tree.get_canvas())

		# Now at this point we've drawn the title and the vertical bit of stem and know how big the subtrees
		# are. Lay them out next to each other.
		#
		#       title
		#         |
		#                
		# 1111112222223333333
		#
		# This layout problem is a bit hairier than just centering the blocks, as we would prefer the box below 
		# to align with the stem | under the label.
		#
		# Consider these two cases:
		#
		#     title
		#       |
		#       |     
		#     1123
		#
		#     title
		#       |
		#       |
		#      1233
		#
		# Despite the total width being the same, actually they are placed differently! Turns out the solution
		# is to place them according to the median point of the box centers. That way the middle one will end up
		# at 0 or if there is no middle, then the midpoint ends up at 0.
		#
		x_centers = self._x_centers(canvases)
		adjustment = math.floor(-self.median(x_centers))
		x_centers = [x + adjustment for x in x_centers]

		# Draw each canvas in the computed center
		for canvas, x_center in zip(canvases, x_centers):
			canvas.blit_to(self._canvas, x_center, 3)

		r = range(1 + int(math.ceil(x_centers[0])), int(math.ceil(x_centers[-1])))
		for x in r:
			self._canvas.set('_', x, 1)

		# Finally connect stems to centers of each subtree.
		#
		#       title
		#    _____|_____
		#   |     |     |
		# 1111112222223333333
		for x_center in x_centers:
			self._canvas.set('|', x_center, 2)

		self._canvas.set('|', 0, 1)

	def __str__(self):
		return str(self._canvas)