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():
27def next_id():
28    next_id.counter += 1
29    return next_id.counter
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
tag
id
children
extra
font_size
font_color
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