simetri.extensions.tree
Create a tree structure and draw it.
1"""Create a tree structure and draw it.""" 2 3from typing import Sequence, Any 4 5import simetri.graphics as sg 6 7 8diamond = sg.Shape([(0, 5), (3, 0), (0, -5), (-3, 0)], closed=True) 9diamond.fill_color = sg.black 10diamond.stroke = False 11star2 = sg.regular_star_polygon(5, 2, 7) 12star2.set_attribs('fill_color', sg.red) 13star2.set_attribs('stroke', False) 14square = sg.Shape([(0, 5), (5, 5), (5, 0), (0, 0)], closed=True) 15star = sg.regular_star_polygon(8, 3, 7) 16star.set_attribs('fill_color', sg.red) 17star.set_attribs('stroke', False) 18star3 = sg.regular_star_polygon(8, 2, 5) 19star3.set_attribs('fill_color', sg.blue) 20star3.set_attribs('stroke', False) 21circle = sg.Circle((0, 0), 1.5, fill_color=sg.white, stroke=False) 22hexagon = sg.Batch([sg.reg_poly_shape((0, 0), 6, 4, fill_color=sg.teal, 23 stroke=False), circle]) 24 25 26def next_id(): 27 next_id.counter += 1 28 return next_id.counter 29 30 31next_id.counter = 0 32 33 34class TreeNode: 35 """TreeNode object representing a tree structure. 36 Each node has a tag, an id, a list of children, and optional extra attributes. 37 38 Args: 39 tag (str): The tag of the node. 40 children (list): A list of child nodes. Defaults to an empty list. 41 extra (str): Optional extra attribute for the node. 42 **kwargs: Additional keyword arguments for the node. (font_size, font_color, bold) 43 44 Returns: 45 None 46 """ 47 48 def __init__( 49 self, 50 tag: str = "", 51 children: Sequence["TreeNode"] = None, 52 extra: Any = None, 53 font_size = 12, 54 font_color = sg.black, 55 bold = False 56 ): 57 self.tag = tag 58 self.id = next_id() 59 self.children = children if children is not None else [] 60 self.extra = extra 61 self.font_size = font_size 62 self.font_color = font_color 63 self.bold = bold 64 65 def add_child(self, child): 66 """Adds a child node to the current node. 67 Args: 68 child (TreeNode): The child node to add. 69 70 Returns: 71 None 72 """ 73 if child.id not in [c.id for c in self.children]: 74 self.children.append(child) 75 76 def num_all_children(self): 77 """Counts the number of children and grandchildren of the node. 78 Args: 79 None 80 81 Returns: 82 int: The number of children and grandchildren. 83 """ 84 return len(self.children) + sum( 85 child.num_all_children() for child in self.children 86 ) 87 88 def depth(self): 89 """Calculates the depth of the node in the tree. 90 Args: 91 None 92 93 94 Returns: 95 int: The depth of the node. 96 """ 97 if not self.children: 98 return 0 99 n = self.num_all_children() 100 m = self.children[-1].num_all_children() 101 return n - m 102 103 104def make_tree( 105 node, 106 canvas: "Canvas" = None, 107 file_path: str = None, 108 overwrite: bool = False, 109 dx: float = 10, 110 dy: float = 18, 111 icons = None, 112 line1_color = sg.gray, 113 line1_width = 1.75, 114 line1_cap = sg.LineCap.ROUND, 115 line2_color = sg.gray, 116 line2_width = 1, 117 line2_cap = sg.LineCap.ROUND 118): 119 """Creates a tree structure and draws it on the canvas. 120 Args: 121 node: The root node of the tree. 122 canvas: The canvas to draw the tree structure. 123 file_path: The file path to save the tree structure. 124 overwrite: Whether to overwrite the existing file. 125 dx: The horizontal distance between nodes. 126 dy: The vertical distance between nodes. 127 icons: A list of icons to use for the nodes. 128 line1_color: The color of the first line. 129 line1_width: The width of the first line. 130 line1_cap: The cap style of the first line. 131 line2_color: The color of the second line. 132 line2_width: The width of the second line. 133 line2_cap: The cap style of the second line. 134 135 Returns: 136 None 137""" 138 count = 0 139 if icons is None: 140 icons = [star, diamond, star3, hexagon] 141 142 icon1, icon2, icon3, icon4 = icons 143 144 def print_tree(node, indent: int = 0, canvas: "Canvas" = None): 145 """Prints the tree structure of the node and its children. 146 Args: 147 node: The node to print. 148 indent: The indentation level for the current node. 149 canvas: The canvas to draw the tree structure. 150 file_path: The file path to save the tree structure. 151 overwrite: Whether to overwrite the existing file. 152 153 Returns: 154 None 155 """ 156 nonlocal count 157 count += 1 158 x = indent * dx 159 y = -count * dy 160 x1 = x 161 y1 = y - node.depth() * dy 162 cx, cy = x, y 163 if node.depth() > 0: 164 canvas.line( 165 (x, y), 166 (x1, y1), 167 line_color=line1_color, 168 line_width=line1_width, 169 line_cap=line1_cap, 170 ) 171 172 173 x2 = x1 + indent * dx 174 y2 = y 175 if indent > 0: 176 x -= dx 177 p1 = (x, y) 178 p2 = (x2, y2) 179 if not sg.close_points2(p1, p2): 180 canvas.line( 181 p1, 182 p2, 183 line_color=line2_color, 184 line_width=line2_width, 185 line_cap=line2_cap, 186 ) 187 if node.depth() > 0 and indent > 0: 188 icon3.move_to((cx, cy)) 189 canvas.draw(icon3) 190 font_size = node.font_size 191 font_color = node.font_color 192 bold = node.bold 193 194 if indent > 1: 195 # 196 icon4.move_to((x, y)) 197 canvas.draw(icon4) 198 elif indent == 1: 199 icon2.move_to((x, y)) 200 canvas.draw(icon2) 201 else: 202 icon1.move_to((x, y)) 203 canvas.draw(icon1) 204 canvas.text( 205 node.tag, 206 (x2, y2), 207 font_family=sg.FontFamily.MONOSPACE, 208 font_size=font_size, 209 color=sg.red, 210 anchor=sg.Anchor.WEST, 211 fill=False, 212 bold=bold, 213 font_color=font_color, 214 ) 215 for child in node.children: 216 print_tree(child, indent + 1, canvas=canvas) 217 218 print_tree(node, canvas=canvas) 219 canvas.save(file_path, overwrite=overwrite) 220 221 222# canvas = sg.Canvas() 223# root = TreeNode("{} Base", extra="root", font_color=sg.orange) 224# methods = TreeNode("Methods", font_color=sg.blue) 225# root.add_child(methods) 226# transforms = [ 227# "translate", 228# "rotate", 229# "mirror", 230# "glide", 231# "scale", 232# "shear", 233# "transform", 234# ] 235# args = [ 236# "(dx: float, dy: float)", 237# "(angle: float, about: Point)", 238# "(about: Line)", 239# "(glide_line: Line, glide_dist: float)", 240# "(scale_x: float, scale_y: float)", 241# "(shear_x:float, shear_y:)", 242# "(transform_matrix: ndarray)", 243# ] 244 245# for i, trans in enumerate(transforms): 246# methods.add_child(TreeNode(f"{trans}{args[i][:-1]}, reps: int=0) -> Self")) 247 248# make_tree( 249# root, canvas=canvas, file_path="c:/tmp/tree_generator4.pdf", overwrite=True 250# )
diamond =
Shape([(0.0, 5.0), ..., (-3.0, 0.0)])
star2 =
Batch([Shape([(2.163118960624632, 6.657395614066075), ..., (2.1631189606246304, -6.657395614066075)])])
square =
Shape([(0.0, 5.0), ..., (0.0, 0.0)])
star =
Batch([Shape([(4.949747468305833, 4.949747468305833), ..., (-1.273222665939867e-15, -7.000000000000001)])])
star3 =
Batch([Shape([(5.0, 0.0), ..., (-9.184850993605148e-16, -5.0)]), Shape([(3.5355339059327378, 3.5355339059327378), ..., (3.5355339059327373, -3.5355339059327386)])])
circle =
Shape(((0.0, 0.0),))
hexagon =
Batch([Shape([(4.0, 0.0), ..., (1.9999999999999973, -3.464101615137756)]), Shape(((0.0, 0.0),))])
def
next_id():
class
TreeNode:
35class TreeNode: 36 """TreeNode object representing a tree structure. 37 Each node has a tag, an id, a list of children, and optional extra attributes. 38 39 Args: 40 tag (str): The tag of the node. 41 children (list): A list of child nodes. Defaults to an empty list. 42 extra (str): Optional extra attribute for the node. 43 **kwargs: Additional keyword arguments for the node. (font_size, font_color, bold) 44 45 Returns: 46 None 47 """ 48 49 def __init__( 50 self, 51 tag: str = "", 52 children: Sequence["TreeNode"] = None, 53 extra: Any = None, 54 font_size = 12, 55 font_color = sg.black, 56 bold = False 57 ): 58 self.tag = tag 59 self.id = next_id() 60 self.children = children if children is not None else [] 61 self.extra = extra 62 self.font_size = font_size 63 self.font_color = font_color 64 self.bold = bold 65 66 def add_child(self, child): 67 """Adds a child node to the current node. 68 Args: 69 child (TreeNode): The child node to add. 70 71 Returns: 72 None 73 """ 74 if child.id not in [c.id for c in self.children]: 75 self.children.append(child) 76 77 def num_all_children(self): 78 """Counts the number of children and grandchildren of the node. 79 Args: 80 None 81 82 Returns: 83 int: The number of children and grandchildren. 84 """ 85 return len(self.children) + sum( 86 child.num_all_children() for child in self.children 87 ) 88 89 def depth(self): 90 """Calculates the depth of the node in the tree. 91 Args: 92 None 93 94 95 Returns: 96 int: The depth of the node. 97 """ 98 if not self.children: 99 return 0 100 n = self.num_all_children() 101 m = self.children[-1].num_all_children() 102 return n - m
TreeNode object representing a tree structure. Each node has a tag, an id, a list of children, and optional extra attributes.
Arguments:
- tag (str): The tag of the node.
- children (list): A list of child nodes. Defaults to an empty list.
- extra (str): Optional extra attribute for the node.
- **kwargs: Additional keyword arguments for the node. (font_size, font_color, bold)
Returns:
None
TreeNode( tag: str = '', children: Sequence[TreeNode] = None, extra: Any = None, font_size=12, font_color=Color(0.0, 0.0, 0.0), bold=False)
49 def __init__( 50 self, 51 tag: str = "", 52 children: Sequence["TreeNode"] = None, 53 extra: Any = None, 54 font_size = 12, 55 font_color = sg.black, 56 bold = False 57 ): 58 self.tag = tag 59 self.id = next_id() 60 self.children = children if children is not None else [] 61 self.extra = extra 62 self.font_size = font_size 63 self.font_color = font_color 64 self.bold = bold
def
add_child(self, child):
66 def add_child(self, child): 67 """Adds a child node to the current node. 68 Args: 69 child (TreeNode): The child node to add. 70 71 Returns: 72 None 73 """ 74 if child.id not in [c.id for c in self.children]: 75 self.children.append(child)
Adds a child node to the current node.
Arguments:
- child (TreeNode): The child node to add.
Returns:
None
def
num_all_children(self):
77 def num_all_children(self): 78 """Counts the number of children and grandchildren of the node. 79 Args: 80 None 81 82 Returns: 83 int: The number of children and grandchildren. 84 """ 85 return len(self.children) + sum( 86 child.num_all_children() for child in self.children 87 )
Counts the number of children and grandchildren of the node.
Arguments:
- None
Returns:
int: The number of children and grandchildren.
def
depth(self):
89 def depth(self): 90 """Calculates the depth of the node in the tree. 91 Args: 92 None 93 94 95 Returns: 96 int: The depth of the node. 97 """ 98 if not self.children: 99 return 0 100 n = self.num_all_children() 101 m = self.children[-1].num_all_children() 102 return n - m
Calculates the depth of the node in the tree.
Arguments:
- None
Returns:
int: The depth of the node.
def
make_tree( node, canvas: 'Canvas' = None, file_path: str = None, overwrite: bool = False, dx: float = 10, dy: float = 18, icons=None, line1_color=Color(0.573, 0.584, 0.569), line1_width=1.75, line1_cap=<LineCap.ROUND: 'round'>, line2_color=Color(0.573, 0.584, 0.569), line2_width=1, line2_cap=<LineCap.ROUND: 'round'>):
105def make_tree( 106 node, 107 canvas: "Canvas" = None, 108 file_path: str = None, 109 overwrite: bool = False, 110 dx: float = 10, 111 dy: float = 18, 112 icons = None, 113 line1_color = sg.gray, 114 line1_width = 1.75, 115 line1_cap = sg.LineCap.ROUND, 116 line2_color = sg.gray, 117 line2_width = 1, 118 line2_cap = sg.LineCap.ROUND 119): 120 """Creates a tree structure and draws it on the canvas. 121 Args: 122 node: The root node of the tree. 123 canvas: The canvas to draw the tree structure. 124 file_path: The file path to save the tree structure. 125 overwrite: Whether to overwrite the existing file. 126 dx: The horizontal distance between nodes. 127 dy: The vertical distance between nodes. 128 icons: A list of icons to use for the nodes. 129 line1_color: The color of the first line. 130 line1_width: The width of the first line. 131 line1_cap: The cap style of the first line. 132 line2_color: The color of the second line. 133 line2_width: The width of the second line. 134 line2_cap: The cap style of the second line. 135 136 Returns: 137 None 138""" 139 count = 0 140 if icons is None: 141 icons = [star, diamond, star3, hexagon] 142 143 icon1, icon2, icon3, icon4 = icons 144 145 def print_tree(node, indent: int = 0, canvas: "Canvas" = None): 146 """Prints the tree structure of the node and its children. 147 Args: 148 node: The node to print. 149 indent: The indentation level for the current node. 150 canvas: The canvas to draw the tree structure. 151 file_path: The file path to save the tree structure. 152 overwrite: Whether to overwrite the existing file. 153 154 Returns: 155 None 156 """ 157 nonlocal count 158 count += 1 159 x = indent * dx 160 y = -count * dy 161 x1 = x 162 y1 = y - node.depth() * dy 163 cx, cy = x, y 164 if node.depth() > 0: 165 canvas.line( 166 (x, y), 167 (x1, y1), 168 line_color=line1_color, 169 line_width=line1_width, 170 line_cap=line1_cap, 171 ) 172 173 174 x2 = x1 + indent * dx 175 y2 = y 176 if indent > 0: 177 x -= dx 178 p1 = (x, y) 179 p2 = (x2, y2) 180 if not sg.close_points2(p1, p2): 181 canvas.line( 182 p1, 183 p2, 184 line_color=line2_color, 185 line_width=line2_width, 186 line_cap=line2_cap, 187 ) 188 if node.depth() > 0 and indent > 0: 189 icon3.move_to((cx, cy)) 190 canvas.draw(icon3) 191 font_size = node.font_size 192 font_color = node.font_color 193 bold = node.bold 194 195 if indent > 1: 196 # 197 icon4.move_to((x, y)) 198 canvas.draw(icon4) 199 elif indent == 1: 200 icon2.move_to((x, y)) 201 canvas.draw(icon2) 202 else: 203 icon1.move_to((x, y)) 204 canvas.draw(icon1) 205 canvas.text( 206 node.tag, 207 (x2, y2), 208 font_family=sg.FontFamily.MONOSPACE, 209 font_size=font_size, 210 color=sg.red, 211 anchor=sg.Anchor.WEST, 212 fill=False, 213 bold=bold, 214 font_color=font_color, 215 ) 216 for child in node.children: 217 print_tree(child, indent + 1, canvas=canvas) 218 219 print_tree(node, canvas=canvas) 220 canvas.save(file_path, overwrite=overwrite)
Creates a tree structure and draws it on the canvas.
Arguments:
- node: The root node of the tree.
- canvas: The canvas to draw the tree structure.
- file_path: The file path to save the tree structure.
- overwrite: Whether to overwrite the existing file.
- dx: The horizontal distance between nodes.
- dy: The vertical distance between nodes.
- icons: A list of icons to use for the nodes.
- line1_color: The color of the first line.
- line1_width: The width of the first line.
- line1_cap: The cap style of the first line.
- line2_color: The color of the second line.
- line2_width: The width of the second line.
- line2_cap: The cap style of the second line.
Returns:
None