memory-graph 0.3.30__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,137 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+ from memory_graph.node_base import Node_Base
6
+ import memory_graph.node_base
7
+ import memory_graph.config as config
8
+ import html
9
+
10
+ def html_table_frame(s, border, color, spacing=5):
11
+ """ Helper function to add the HTML table frame to the string s setting the 'border' and 'color'. """
12
+ return (f'<\n<TABLE BORDER="{border}" CELLBORDER="1" CELLSPACING="{spacing}" CELLPADDING="0" BGCOLOR="{color}" PORT="table">\n <TR>' +
13
+ s + '</TR>\n</TABLE>\n>')
14
+
15
+ def format_string(s):
16
+ """ Helper function to format the string s to be shown in the graph. Setting the max_string_length and escaping html characters. """
17
+ s = config.to_string(s)
18
+ s = (s[:config.max_string_length] + '...') if len(s) > config.max_string_length else s
19
+ return html.escape(s)
20
+
21
+ class HTML_Table:
22
+ """
23
+ The HTML_Table class is used to create a table of data that can be visualized in the graph.
24
+ """
25
+
26
+ def __init__(self):
27
+ """
28
+ Create an HTML_Table object.
29
+ """
30
+ self.html = ''
31
+ self.add_new_line_flag = False
32
+ self.is_empty = True
33
+ self.col_count = 0
34
+ self.row_count = 0
35
+ self.ref_count = 0
36
+ self.max_col_count = 0
37
+ self.edges = []
38
+
39
+ def __repr__(self):
40
+ """ Get the string representation of the HTML_Table object. """
41
+ return self.html
42
+
43
+ def add_new_line(self):
44
+ """ Set the 'add_new_line_flag' to add a new line to the table when adding the next table element. """
45
+ self.add_new_line_flag = True
46
+ self.row_count += 1
47
+ if self.col_count > self.max_col_count:
48
+ self.max_col_count = self.col_count
49
+ self.col_count = 0
50
+
51
+ def check_add_new_line(self):
52
+ """ Check if a new line should be added to the table, and if so add it and sets the 'add_new_line_flag' to False."""
53
+ if self.add_new_line_flag:
54
+ self.html += '</TR>\n <TR>'
55
+ self.add_new_line_flag = False
56
+
57
+ def add_string(self, s, border=0):
58
+ """ Add a string s to the table. """
59
+ self.html += f'<TD BORDER="{border}">'+format_string(s)+'</TD>'
60
+ self.is_empty = False
61
+
62
+ def add_index(self, s):
63
+ """ Add an index s to the table. """
64
+ self.check_add_new_line()
65
+ self.html += f'<TD BORDER="0"><font color="#505050">{str(s)}</font></TD>'
66
+ self.col_count += 1
67
+
68
+ def add_entry(self, node, nodes, child, id_to_slices, rounded=False, border=1, dashed=False):
69
+ """ Add child to the table either as reference if it is a Node_Base or as a value otherwise. """
70
+ #print('child:', child)
71
+ child_id = id(child)
72
+ if child_id in nodes:
73
+ child = nodes[child_id]
74
+ if child_id in id_to_slices:
75
+ self.add_reference(node, child, rounded, border, dashed)
76
+ else:
77
+ self.add_value(config.graph_cut_symbol, rounded, border)
78
+ else:
79
+ self.add_value(child, rounded, border)
80
+
81
+ def add_value(self, s, rounded=False, border=1):
82
+ """ Helper function to add a value s to the table. """
83
+ self.check_add_new_line()
84
+ r = ' STYLE="ROUNDED"' if rounded else ''
85
+ self.html += f'<TD BORDER="{border}"{r}> {format_string(s)} </TD>'
86
+ self.col_count += 1
87
+
88
+ def add_reference(self, node, child, rounded=False, border=1, dashed=False):
89
+ """ Helper function to add a reference to the table. """
90
+ self.check_add_new_line()
91
+ r = ' STYLE="ROUNDED"' if rounded else ''
92
+ self.html += f'<TD BORDER="{border}" PORT="ref{self.ref_count}"{r}> </TD>'
93
+ self.edges.append( (f'{node.get_name()}:ref{self.ref_count}',
94
+ child.get_name(), dashed) )
95
+ self.ref_count += 1
96
+ self.col_count += 1
97
+
98
+ def add_dots(self, rounded=False, border=1):
99
+ """ Helper function to add dots to the table. """
100
+ self.check_add_new_line()
101
+ r = 'STYLE="ROUNDED"' if rounded else ''
102
+ self.html += f'<TD BORDER="{border}" {r}>...</TD>'
103
+ self.col_count += 1
104
+
105
+ def to_string(self, border=1, color='white'):
106
+ """ Construct the HTML table string with the 'border' and 'color' settings. """
107
+ if self.col_count == 0 and self.row_count == 0:
108
+ if self.is_empty:
109
+ self.add_string(' ')
110
+ return html_table_frame(self.html, border, color, spacing=0)
111
+ return html_table_frame(self.html, border, color)
112
+
113
+ def get_column(self):
114
+ """ Get the number of columns in the table. """
115
+ return self.col_count
116
+
117
+ def get_max_column(self):
118
+ """ Get the maximum value of the number of columns of rows in the table. """
119
+ return self.max_col_count
120
+
121
+ def get_row(self):
122
+ """ Get the number of rows in the table. """
123
+ return self.row_count
124
+
125
+ def get_edges(self):
126
+ """ Get the edges that need to be added to connect the table to other tables in the graph. """
127
+ return self.edges
128
+
129
+ if __name__ == '__main__':
130
+ table = HTML_Table()
131
+ rows = 4
132
+ columns = 5
133
+ for r in range(rows):
134
+ for c in range(columns):
135
+ table.add_value(f'{c},{r}')
136
+ table.add_new_line()
137
+ print(table.to_string())
@@ -0,0 +1,57 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+
6
+ class List_View:
7
+ def __init__(self, base_list, begin, end):
8
+ self.base_list = base_list
9
+ self.begin = max(0, begin)
10
+ self.end = min(end, len(base_list))
11
+
12
+ def __getitem__(self, index):
13
+ if isinstance(index, slice):
14
+ # Calculate new begin and end indices within the bounds of the current view
15
+ start, stop, step = index.indices(self.end - self.begin)
16
+ if step != 1:
17
+ raise ValueError("List_View does not support slices with steps other than 1")
18
+ # Adjust the indices relative to the base list
19
+ new_start = self.begin + start
20
+ new_end = self.begin + stop
21
+ return List_View(self.base_list, new_start, new_end)
22
+ elif isinstance(index, int):
23
+ if index < 0 or index >= (self.end - self.begin):
24
+ raise IndexError("list index out of range")
25
+ return self.base_list[self.begin + index]
26
+ else:
27
+ raise TypeError("Invalid index type")
28
+
29
+ def __setitem__(self, index, value):
30
+ if index < 0 or index >= (self.end - self.begin):
31
+ raise IndexError("list index out of range")
32
+ self.base_list[self.begin + index] = value
33
+
34
+ def __len__(self):
35
+ return self.end - self.begin
36
+
37
+ def __iter__(self):
38
+ for i in range(self.begin, self.end):
39
+ yield self.base_list[i]
40
+
41
+ def __repr__(self):
42
+ return f"List_View({self.base_list[self.begin:self.end]})"
43
+
44
+ def test_list_vew():
45
+ original_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
46
+ list_view = List_View(original_list, 3, 8)
47
+ print(list_view) # Output: List_View([3, 4, 5, 6, 7])
48
+ print(list_view[1:4]) # Output: List_View([4, 5, 6])
49
+ # 2D array
50
+ n = 4
51
+ data = [i for i in range(n*n)]
52
+ list_views = [List_View(data, i, i+n) for i in range(0,len(data),n)]
53
+ for row in list_views:
54
+ print(row)
55
+
56
+ if __name__ == "__main__":
57
+ test_list_vew()
@@ -0,0 +1,200 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+ from memory_graph.node_leaf import Node_Leaf
6
+ from memory_graph.node_linear import Node_Linear
7
+ from memory_graph.node_key_value import Node_Key_Value
8
+
9
+ import memory_graph.utils as utils
10
+ import memory_graph.config as config
11
+
12
+ import graphviz
13
+
14
+ def read_nodes(data):
15
+
16
+ def data_to_node(data_type, data):
17
+ if data_type in config.type_to_node: # for predefined types
18
+ return config.type_to_node[data_type](data)
19
+ elif utils.has_dict_attributes(data): # for user defined classes
20
+ return Node_Key_Value(data, utils.filter_dict(utils.get_dict_attributes(data)) )
21
+ elif utils.is_finite_iterable(data): # for lists, tuples, sets, ...
22
+ return Node_Linear(data, data)
23
+ else:
24
+ return Node_Leaf(data, data)
25
+
26
+ def memory_to_nodes_recursive(nodes, data, parent, parent_index):
27
+ data_type = type(data)
28
+ if not data_type in config.not_node_types or parent is None:
29
+ data_id = id(data)
30
+ if data_id in nodes:
31
+ node = nodes[data_id]
32
+ else:
33
+ node = data_to_node(data_type, data)
34
+ nodes[data_id] = node
35
+ for index in node.get_children().indices_all():
36
+ child = node.get_children()[index]
37
+ memory_to_nodes_recursive(nodes, child, node, index)
38
+ if not parent is None:
39
+ node.add_parent_index(parent, parent_index)
40
+
41
+ nodes = {}
42
+ memory_to_nodes_recursive(nodes, data, None, None)
43
+ root_id = id(data)
44
+ return nodes, root_id
45
+
46
+ # --------------------------------------------------------------------------------------------
47
+
48
+ def get_max_type_depth(node_id, node):
49
+ if node_id in config.type_to_depth:
50
+ return config.type_to_depth[node_id]
51
+ elif node.get_type() in config.type_to_depth:
52
+ return config.type_to_depth[node.get_type()]
53
+ return None
54
+
55
+ def slice_nodes(nodes, root_id, max_graph_depth):
56
+
57
+ def slice_nodes_recursive(nodes, node_id, id_to_slices, max_graph_depth):
58
+ if max_graph_depth == 0 or node_id in id_to_slices:
59
+ return
60
+ if node_id in nodes:
61
+ node = nodes[node_id]
62
+ children = node.get_children()
63
+ if children.is_empty():
64
+ id_to_slices[node_id] = None
65
+ else:
66
+ slicer = node.get_slicer()
67
+ slices = children.slice(slicer)
68
+ id_to_slices[node_id] = slices
69
+ if not node.is_hidden_node():
70
+ max_graph_depth -= 1
71
+ max_type_depth = get_max_type_depth(node_id, node)
72
+ if max_type_depth:
73
+ max_graph_depth = min(max_type_depth, max_graph_depth)
74
+ for index in slices:
75
+ slice_nodes_recursive(nodes, id(children[index]), id_to_slices, max_graph_depth)
76
+ id_to_slices = {}
77
+ slice_nodes_recursive(nodes, root_id, id_to_slices, max_graph_depth)
78
+ return id_to_slices
79
+
80
+ # --------------------------------------------------------------------------------------------
81
+
82
+ def add_parent_indices(nodes, type_to_parent_indices, id_to_slices, max_missing_edges):
83
+ #print('add_parent_indices type_to_parent_indices:',type_to_parent_indices)
84
+ for _, parent_indices in type_to_parent_indices.items():
85
+ dashed = len(parent_indices) > max_missing_edges
86
+ for parent, index in parent_indices[0:max_missing_edges]:
87
+ new_parent = False
88
+ parent_id = parent.get_id()
89
+ if not parent_id in id_to_slices:
90
+ new_parent = True
91
+ id_to_slices[parent_id] = parent.get_children().empty_slices()
92
+ slices = id_to_slices[parent_id]
93
+ slices.add_index(index, dashed=dashed)
94
+ if new_parent:
95
+ add_indices_to_parents(nodes, parent_id, id_to_slices, max_missing_edges)
96
+
97
+ def add_indices_to_parents(nodes, node_id, id_to_slices, max_missing_edges):
98
+ #print('add_indices_to_parents node_id:',node_id)
99
+ type_to_parent_indices = {}
100
+ parent_indices = nodes[node_id].get_parent_indices()
101
+ for parent, indices in parent_indices.items():
102
+ if parent is None:
103
+ continue
104
+ parent_type = parent.get_type()
105
+ parent_id = parent.get_id()
106
+ if (parent_type in type_to_parent_indices and
107
+ len(type_to_parent_indices[parent_type]) > max_missing_edges): # early stop
108
+ continue
109
+ parent_slices = None
110
+ if parent_id in id_to_slices:
111
+ parent_slices = id_to_slices[parent_id]
112
+ for index in indices:
113
+ if parent_slices is None or not parent_slices.has_index(index):
114
+ if not parent_type in type_to_parent_indices:
115
+ type_to_parent_indices[parent_type] = []
116
+ parent_indices = type_to_parent_indices[parent_type]
117
+ if len(parent_indices) > max_missing_edges:
118
+ break
119
+ else:
120
+ parent_indices.append((parent, index))
121
+ add_parent_indices(nodes, type_to_parent_indices, id_to_slices, max_missing_edges)
122
+
123
+ def add_missing_edges(nodes, id_to_slices, max_missing_edges=3):
124
+ old_id_to_slices_keys = set(id_to_slices.keys())
125
+ for node_id in old_id_to_slices_keys:
126
+ add_indices_to_parents(nodes, node_id, id_to_slices, max_missing_edges)
127
+ return id_to_slices
128
+
129
+ # --------------------------------------------------------------------------------------------
130
+
131
+ import memory_graph.config_helpers as config_helpers
132
+
133
+ def create_depth_of_nodes(nodes, nodes_at_depth):
134
+ depth_of_nodes = {}
135
+ for node_id, depth in nodes_at_depth.items():
136
+ node = nodes[node_id]
137
+ if node_id in nodes and not node.is_hidden_node():
138
+ if not depth in depth_of_nodes:
139
+ depth_of_nodes[depth] = []
140
+ depth_of_nodes[depth].append(node)
141
+ return depth_of_nodes
142
+
143
+ def add_subgraph(graphviz_graph, nodes_to_subgraph):
144
+ new_node_names = [node.get_name() for node in nodes_to_subgraph]
145
+ if len(new_node_names) > 1:
146
+ graphviz_graph.body.append('subgraph { rank=same; '+ ' -> '.join(new_node_names) + '[weight='+str(config.graph_stability)+', style=invis]; }\n')
147
+
148
+ def add_to_graphviz_graph(graphviz_graph, nodes, node, slices, id_to_slices, subgraphed_nodes, depth):
149
+ html_table = node.get_html_table(nodes, slices, id_to_slices)
150
+ edges = html_table.get_edges()
151
+ color = config_helpers.get_color(node)
152
+ border = 3 if node.is_root() else 1
153
+ graphviz_graph.node(node.get_name(),
154
+ html_table.to_string(border, color),
155
+ xlabel=node.get_label(slices))
156
+ # ------------ edges
157
+ for parent,child,dashed in edges:
158
+ graphviz_graph.edge(parent, child+':table', style='dashed' if dashed else 'solid')
159
+
160
+ def build_graph_depth_first(graphviz_graph, nodes, node_id, id_to_slices, nodes_at_depth, subgraphed_nodes, depth):
161
+ if node_id in id_to_slices:
162
+ if node_id in nodes_at_depth:
163
+ return
164
+ nodes_at_depth[node_id] = depth
165
+ node = nodes[node_id]
166
+ children = node.get_children()
167
+ slices = None
168
+ if node_id in id_to_slices:
169
+ slices = id_to_slices[node_id]
170
+ if not slices is None:
171
+ for index in slices:
172
+ child_id = id(children[index])
173
+ build_graph_depth_first(graphviz_graph, nodes, child_id, id_to_slices, nodes_at_depth, subgraphed_nodes, depth+1)
174
+ if not node.is_hidden_node():
175
+ add_to_graphviz_graph(graphviz_graph, nodes, node, slices, id_to_slices, subgraphed_nodes, depth)
176
+
177
+ def build_graph(graphviz_graph, nodes, root_id, id_to_slices):
178
+ nodes_at_depth = {}
179
+ build_graph_depth_first(graphviz_graph, nodes, root_id, id_to_slices, nodes_at_depth, set(), 0)
180
+ depth_of_nodes = create_depth_of_nodes(nodes, nodes_at_depth)
181
+ #print('nodes_at_depth:',nodes_at_depth,'depth_of_nodes:', depth_of_nodes)
182
+ for depth, depth_nodes in depth_of_nodes.items():
183
+ add_subgraph(graphviz_graph, depth_nodes)
184
+
185
+ def memory_to_nodes(data):
186
+ nodes, root_id = read_nodes(data)
187
+ #print('nodes:',nodes,'root_id:',root_id)
188
+ id_to_slices = slice_nodes(nodes, root_id, config.max_graph_depth)
189
+ #print('id_to_slices:',id_to_slices)
190
+ id_to_slices = add_missing_edges(nodes, id_to_slices, config.max_missing_edges)
191
+ #print('id_to_slices:',id_to_slices)
192
+ graphviz_graph_attr = {}
193
+ graphviz_node_attr = {'shape':'plaintext'}
194
+ graphviz_edge_attr = {}
195
+ graphviz_graph=graphviz.Digraph('memory_graph',
196
+ graph_attr=graphviz_graph_attr,
197
+ node_attr=graphviz_node_attr,
198
+ edge_attr=graphviz_edge_attr)
199
+ build_graph(graphviz_graph, nodes, root_id, id_to_slices)
200
+ return graphviz_graph
@@ -0,0 +1,136 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+ import memory_graph.utils as utils
6
+ import memory_graph.config as config
7
+ import memory_graph.config_helpers as config_helpers
8
+ from memory_graph.sequence import Sequence1D
9
+
10
+ from abc import ABC, abstractmethod
11
+
12
+ class Node_Base(ABC):
13
+ """
14
+ Node_Base represents a node in the memory graph. This base class has different subclasses for different types of nodes.
15
+ """
16
+
17
+ def __init__(self, data, children=None):
18
+ """
19
+ Create a Node_Base object.
20
+ """
21
+ self.data = data
22
+ self.children = Sequence1D([]) if children is None else children
23
+ self.parent_indices = {}
24
+
25
+ def __repr__(self):
26
+ """
27
+ Return a string representation of the node showing the original data represented by the node.
28
+ """
29
+ return f'{self.get_type_name()}'
30
+
31
+ def add_parent_index(self, parent, parent_index):
32
+ """
33
+ Add a parent to the node.
34
+ """
35
+ if not parent in self.parent_indices:
36
+ self.parent_indices[parent] = []
37
+ self.parent_indices[parent].append(parent_index)
38
+
39
+ def is_root(self):
40
+ """
41
+ Return if the node is the root node.
42
+ """
43
+ return len(self.parent_indices) == 0
44
+
45
+ def get_parent_indices(self):
46
+ return self.parent_indices
47
+
48
+ def get_id(self):
49
+ """
50
+ Return the id of the node.
51
+ """
52
+ return id(self.data)
53
+
54
+ def __eq__(self, other):
55
+ """
56
+ Return if the node is equal to another node.
57
+ """
58
+ return self.get_id() == other.get_id()
59
+
60
+ def __hash__(self):
61
+ """
62
+ Return the hash of the node.
63
+ """
64
+ return self.get_id()
65
+
66
+ def get_data(self):
67
+ """
68
+ Return the original data represented by the node.
69
+ """
70
+ return self.data
71
+
72
+ def get_type(self):
73
+ """
74
+ Return the type of the data represented by the node.
75
+ """
76
+ return type(self.data)
77
+
78
+ def get_type_name(self):
79
+ """
80
+ Return the name of the type of the data represented by the node.
81
+ """
82
+ return utils.get_type_name(self.data)
83
+
84
+ def get_children(self):
85
+ """
86
+ Return the children of the node. Initially the children are raw data, but
87
+ later they too are converted to Node_Base by the Memory_visitor using the Node_Base 'transform' method.
88
+ """
89
+ return self.children
90
+
91
+ def get_name(self):
92
+ """
93
+ Return a unique name for the node.
94
+ """
95
+ return f'node{self.get_id()}'
96
+
97
+ def get_html_table(self, nodes, slices, id_to_slices):
98
+ """
99
+ Return the HTML_Table object that determines how the node is visualized in the graph.
100
+ """
101
+ from memory_graph.html_table import HTML_Table
102
+ import memory_graph.node_base
103
+ html_table = HTML_Table()
104
+ self.fill_html_table(nodes, html_table, slices, id_to_slices)
105
+ return html_table
106
+
107
+ def get_slicer(self):
108
+ return config_helpers.get_slicer(self, self.get_data())
109
+
110
+ def is_hidden_node(self):
111
+ """
112
+ Return if the node is a hidden node in the graph.
113
+ """
114
+ from memory_graph.node_key_value import Node_Key_Value
115
+ if self.get_type() is tuple:
116
+ parent_indices = self.get_parent_indices()
117
+ if len(parent_indices) == 1:
118
+ first_parent = next(iter(parent_indices))
119
+ if type(first_parent) is Node_Key_Value:
120
+ return True
121
+ return False
122
+
123
+ # -------------------- Node_Base interface, overriden by subclasses --------------------
124
+
125
+ @abstractmethod
126
+ def fill_html_table(self, html_table, slices, id_to_slices):
127
+ """
128
+ Fill the HTML_Table object with each child of the node.
129
+ """
130
+ pass
131
+
132
+ def get_label(self, slices):
133
+ """
134
+ Return a label for the node to be shown in the graph next to the HTML table.
135
+ """
136
+ return self.get_type_name()
@@ -0,0 +1,124 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+ from memory_graph.node_base import Node_Base
6
+ from memory_graph.sequence import Sequence1D
7
+
8
+ import memory_graph.config_helpers as config_helpers
9
+
10
+ class Node_Key_Value(Node_Base):
11
+ """
12
+ Node_Key_Value (subclass of Node_Base) is a node that represents a node with key-value
13
+ pairs (tuples) as children. This node type mainly used for dictionaries and classes.
14
+ Each child is made a Hidden_Node_Base so that each tuple is not shown as a separate node
15
+ but instead as a key,value pair in the current node.
16
+ """
17
+
18
+ def __init__(self, data, children):
19
+ """
20
+ Create a Node_Key_Value object. Use a Slicer to slice the children so the
21
+ Node_Base will not get too big or have too many childeren in the graph.
22
+ """
23
+ super().__init__(data, Sequence1D(children))
24
+
25
+ def has_references(self, nodes, slices, id_to_slices):
26
+ """
27
+ Return if the node has references to other nodes.
28
+ """
29
+ for index in slices:
30
+ child_id = id(self.children[index])
31
+ child = nodes[child_id]
32
+ key_id = id(child.get_children()[0])
33
+ if key_id in nodes:
34
+ key = nodes[key_id]
35
+ if not key.is_hidden_node():
36
+ return True
37
+ value_id = id(child.get_children()[1])
38
+ if value_id in nodes:
39
+ value = nodes[value_id]
40
+ if not value.is_hidden_node():
41
+ return True
42
+ return False
43
+
44
+ def is_vertical(self, nodes, slices, id_to_slices):
45
+ """
46
+ Return if the node is vertical or horizontal based on the orientation of the children.
47
+ """
48
+ vertical = config_helpers.get_vertical_orientation(self, None)
49
+ if vertical is None:
50
+ vertical = not self.has_references(nodes, slices, id_to_slices)
51
+ return vertical
52
+
53
+ def fill_html_table(self, nodes, html_table, slices, id_to_slices):
54
+ """
55
+ Fill the html_table with the children of the Node_Base.
56
+ """
57
+ if slices is None:
58
+ return
59
+ vertical = self.is_vertical(nodes, slices, id_to_slices)
60
+ if vertical:
61
+ self.fill_html_table_vertical(html_table, nodes, slices, id_to_slices)
62
+ else:
63
+ self.fill_html_table_horizontal(html_table, nodes, slices, id_to_slices)
64
+
65
+ @staticmethod
66
+ def get_value_dashed(nodes, child, index, id_to_slices):
67
+ grandchild = child[index]
68
+ child_id = id(child)
69
+ is_dashed = False
70
+ if child_id in id_to_slices:
71
+ slices = id_to_slices[child_id]
72
+ if not slices is None:
73
+ is_dashed = slices.is_dashed(index)
74
+ return grandchild, is_dashed
75
+
76
+ def fill_html_table_vertical(self, html_table, nodes, slices, id_to_slices):
77
+ """
78
+ Helper function to fill the html_table with the children of the Node_Base in vertical orientation.
79
+ """
80
+ for index in slices.table_iter(self.children.size()):
81
+ if index>=0:
82
+ child = self.children[index]
83
+ key, is_dashed = self.get_value_dashed(nodes, child,0,id_to_slices)
84
+ html_table.add_entry(self, nodes, key, id_to_slices, rounded=True, dashed=is_dashed)
85
+ value, is_dashed = self.get_value_dashed(nodes, child,1,id_to_slices)
86
+ html_table.add_entry(self, nodes, value, id_to_slices, dashed=is_dashed)
87
+ else:
88
+ html_table.add_dots(rounded=True)
89
+ html_table.add_dots()
90
+ html_table.add_new_line()
91
+
92
+ def fill_html_table_horizontal(self, html_table, nodes, slices, id_to_slices):
93
+ """
94
+ Helper function to fill the html_table with the children of the Node_Base in horizontal orientation.
95
+ """
96
+ for index in slices.table_iter(self.children.size()):
97
+ if index>=0:
98
+ child = self.children[index]
99
+ key, is_dashed = self.get_value_dashed(nodes, child,0,id_to_slices)
100
+ html_table.add_entry(self, nodes, key, id_to_slices, rounded=True, dashed=is_dashed)
101
+ else:
102
+ html_table.add_dots(rounded=True)
103
+ html_table.add_new_line()
104
+ for index in slices.table_iter(self.children.size()):
105
+ if index>=0:
106
+ child = self.children[index]
107
+ value, is_dashed = self.get_value_dashed(nodes, child,1,id_to_slices)
108
+ html_table.add_entry(self, nodes, value, id_to_slices, dashed=is_dashed)
109
+ else:
110
+ html_table.add_dots()
111
+
112
+ def get_label(self, slices):
113
+ """
114
+ Return a label for the node to be shown in the graph next to the HTML table.
115
+ """
116
+ type_name = self.get_type_name()
117
+ if slices is None:
118
+ return f'{type_name}'
119
+ size = self.get_children().size()
120
+ s = slices.get_slices()
121
+ if len(s) == 1:
122
+ if s[0][1] - s[0][0] == size:
123
+ return f'{type_name}'
124
+ return f'{type_name} {size}'
@@ -0,0 +1,23 @@
1
+ # This file is part of memory_graph.
2
+ # Copyright (c) 2023, Bas Terwijn.
3
+ # SPDX-License-Identifier: BSD-2-Clause
4
+
5
+ from memory_graph.node_base import Node_Base
6
+
7
+ class Node_Leaf(Node_Base):
8
+ """
9
+ Node_Leaf (subclass of Node_Base) is a leaf node with no children but a value.
10
+ """
11
+
12
+ def __init__(self, data, value):
13
+ """
14
+ Create a Node_Leaf object.
15
+ """
16
+ super().__init__(data)
17
+ self.value = value
18
+
19
+ def fill_html_table(self, nodes, html_table, slices, id_to_slices):
20
+ """
21
+ Fill the html_table with the children of the Node_Base.
22
+ """
23
+ html_table.add_value(self.value)