1010from maxplotlib .colors .colors import Color
1111from maxplotlib .linestyle .linestyle import Linestyle
1212
13+
14+ class Layer :
15+ def __init__ (self , label ):
16+ self .label = label
17+ self .items = []
18+ def add (self , item ):
19+ self .items .append (item )
20+
21+ def get_reqs (self ):
22+ reqs = set ()
23+ for item in self .items :
24+ if isinstance (item , Path ):
25+ for node in item .nodes :
26+ reqs .add (node .layer )
27+ return reqs
28+ def generate_tikz (self ):
29+ tikz_script = f"\n % Layer { self .label } \n "
30+ tikz_script += f"\\ begin{{pgfonlayer}}{{{ self .label } }}\n "
31+ for item in self .items :
32+ tikz_script += item .to_tikz ()
33+ tikz_script += f"\\ end{{pgfonlayer}}{{{ self .label } }}\n "
34+ return tikz_script
35+
1336class Node :
1437 def __init__ (self , x , y , label = "" , content = "" , layer = 0 , ** kwargs ):
1538 """
@@ -38,10 +61,10 @@ def to_tikz(self):
3861 options = ', ' .join (f"{ k .replace ('_' , ' ' )} ={ v } " for k , v in self .options .items ())
3962 if options :
4063 options = f"[{ options } ]"
41- return f" \\ node{ options } ({ self .label } ) at ({ self .x } , { self .y } ) {{{ self .content } }};\n "
64+ return f"\\ node{ options } ({ self .label } ) at ({ self .x } , { self .y } ) {{{ self .content } }};\n "
4265
4366class Path :
44- def __init__ (self , nodes , path_actions = [], cycle = False , layer = 0 , ** kwargs ):
67+ def __init__ (self , nodes , path_actions = [], cycle = False , label = "" , layer = 0 , ** kwargs ):
4568 """
4669 Represents a path (line) connecting multiple nodes.
4770
@@ -53,6 +76,7 @@ def __init__(self, nodes, path_actions=[], cycle=False, layer=0, **kwargs):
5376 self .path_actions = path_actions
5477 self .cycle = cycle
5578 self .layer = layer
79+ self .label = label
5680 self .options = kwargs
5781
5882 def to_tikz (self ):
@@ -67,10 +91,10 @@ def to_tikz(self):
6791 options = ', ' .join (self .path_actions ) + ', ' + options
6892 if options :
6993 options = f"[{ options } ]"
70- path_str = ' -- ' .join (f"({ node_label } .center)" for node_label in self .nodes )
94+ path_str = ' -- ' .join (f"({ node . label } .center)" for node in self .nodes )
7195 if self .cycle :
7296 path_str += ' -- cycle'
73- return f" \\ draw{ options } { path_str } ;\n "
97+ return f"\\ draw{ options } { path_str } ;\n "
7498
7599class TikzFigure :
76100 def __init__ (self , ** kwargs ):
@@ -119,9 +143,10 @@ def add_node(self, x, y, label=None, content="", layer = 0, **kwargs):
119143 node = Node (x = x , y = y , label = label , content = content , ** kwargs )
120144 self .nodes .append (node )
121145 if layer in self .layers :
122- self .layers [layer ].append (node )
146+ self .layers [layer ].add (node )
123147 else :
124- self .layers [layer ] = [node ]
148+ self .layers [layer ] = Layer (layer )
149+ self .layers [layer ].add (node )
125150 self ._node_counter += 1
126151 return node
127152
@@ -141,20 +166,41 @@ def add_path(self, nodes, layer=0, **kwargs):
141166 raise ValueError ("nodes parameter must be a list of node names." )
142167
143168 nodes = [
144- node . label if isinstance (node , Node )
145- else node if isinstance (node , str )
169+ node if isinstance (node , Node )
170+ else self . get_node ( node ) if isinstance (node , str )
146171 else ValueError (f"Invalid node type: { type (node )} " )
147172 for node in nodes
148173 ]
149174
150175 path = Path (nodes , ** kwargs )
151176 self .paths .append (path )
152177 if layer in self .layers :
153- self .layers [layer ].append (path )
178+ self .layers [layer ].add (path )
154179 else :
155- self .layers [layer ] = [path ]
180+ self .layers [layer ] = Layer (layer )
181+ self .layers [layer ].add (path )
156182 return path
157-
183+ def get_node (self , node_label ):
184+ for node in self .nodes :
185+ if node .label == node_label :
186+ return node
187+ def get_layer (self , item ):
188+ for layer , layer_items in self .layers .items ():
189+ if item in [layer_item .label for layer_item in layer_items ]:
190+ return layer
191+ print (f'Item { item } not found in any layer!' )
192+
193+ def add_tabs (self , tikz_script ):
194+ tikz_script_new = ""
195+ tab_str = " "
196+ num_tabs = 0
197+ for line in tikz_script .split ('\n ' ):
198+ if "\\ end" in line :
199+ num_tabs = max (num_tabs - 1 , 0 )
200+ tikz_script_new += f"{ tab_str * num_tabs } { line } \n "
201+ if "\\ begin" in line :
202+ num_tabs += 1
203+ return tikz_script_new
158204 def generate_tikz (self ):
159205 """
160206 Generate the TikZ script for the figure.
@@ -163,23 +209,52 @@ def generate_tikz(self):
163209 - tikz_script (str): The TikZ script as a string.
164210 """
165211 tikz_script = "\\ begin{tikzpicture}\n "
212+ tikz_script += "% Define the layers library\n "
213+ layers = sorted ([str (layer ) for layer in self .layers .keys ()])
214+ for layer in layers :
215+ tikz_script += f"\\ pgfdeclarelayer{{{ layer } }}\n "
216+ tikz_script += f"\\ pgfsetlayers{{{ ',' .join (layers )} }}\n "
166217
167218
168219 # Add grid if enabled
169220 if self ._grid :
170221 tikz_script += " \\ draw[step=1cm, gray, very thin] (-10,-10) grid (10,10);\n "
171-
172- for key , layer_items in self .layers .items ():
173- tikz_script += f"\n % Layer { key } \n "
174- for item in layer_items :
175- tikz_script += item .to_tikz ()
176- # # Add nodes
177- # for node in self.nodes:
178- # tikz_script += node.to_tikz()
179-
180- # # Add paths
181- # for path in self.paths:
182- # tikz_script += path.to_tikz()
222+ # def update_layer_order(layer, layer_order, buffered_layers):
223+ # reqs = layer.get_reqs()
224+ # if len(reqs) == 0:
225+ # layer_order.append(key)
226+ # buffered_layers.pop(key)
227+ # elif all([r in layer_order for r in reqs]):
228+ # layer_order.append(key)
229+ # buffered_layers.pop(key)
230+ # else:
231+ # buffered_layers.append(key)
232+ # return layer, layer_order, buffered_layers
233+ # Determine the order to print the layers
234+ ordered_layers = []
235+ buffered_layers = set ()
236+
237+ for key , layer in self .layers .items ():
238+ #layer_order, buffered_layers = update_layer_order(layer, layer_order, buffered_layers)
239+ reqs = layer .get_reqs ()
240+ if all ([r == layer .label for r in reqs ]):
241+ ordered_layers .append (layer )
242+ elif all ([r in [l .label for l in ordered_layers ] for r in reqs ]):
243+ ordered_layers .append (layer )
244+ else :
245+ buffered_layers .add (layer )
246+
247+ for buffered_layer in buffered_layers :
248+ buff_reqs = buffered_layer .get_reqs ()
249+ print (buff_reqs )
250+ print ([r in [l .label for l in ordered_layers ] for r in buff_reqs ], [l .label for l in ordered_layers ])
251+ if all ([r in [l .label for l in ordered_layers ] for r in buff_reqs ]):
252+ print ('Move layer from buffer' )
253+ ordered_layers .append (key )
254+ buffered_layers .remove (key )
255+ assert len (buffered_layers ) == 0 , f"Layer order is impossible for layer { [layer .label for layer in buffered_layers ]} "
256+ for layer in ordered_layers :
257+ tikz_script += layer .generate_tikz ()
183258
184259 tikz_script += "\\ end{tikzpicture}"
185260
@@ -192,19 +267,10 @@ def generate_tikz(self):
192267 figure_env += f" \\ label{{{ self ._label } }}\n "
193268 figure_env += "\\ end{figure}"
194269 tikz_script = figure_env
195-
270+ tikz_script = self . add_tabs ( tikz_script )
196271 return tikz_script
197272
198- def compile_pdf (self , filename = 'output.pdf' ):
199- """
200- Compile the TikZ script into a PDF using pdflatex.
201-
202- Parameters:
203- - filename (str): The name of the output PDF file (default is 'output.pdf').
204-
205- Notes:
206- - Requires 'pdflatex' to be installed and accessible from the command line.
207- """
273+ def generate_standalone (self ):
208274 tikz_code = self .generate_tikz ()
209275
210276 # Create a minimal LaTeX document
@@ -215,6 +281,19 @@ def compile_pdf(self, filename='output.pdf'):
215281 f"{ tikz_code } \n "
216282 "\\ end{document}"
217283 )
284+ return latex_document
285+
286+ def compile_pdf (self , filename = 'output.pdf' ):
287+ """
288+ Compile the TikZ script into a PDF using pdflatex.
289+
290+ Parameters:
291+ - filename (str): The name of the output PDF file (default is 'output.pdf').
292+
293+ Notes:
294+ - Requires 'pdflatex' to be installed and accessible from the command line.
295+ """
296+ latex_document = self .generate_standalone ()
218297
219298 # Use a temporary directory to store the LaTeX files
220299 with tempfile .TemporaryDirectory () as tempdir :
@@ -254,9 +333,9 @@ def plot_matplotlib(self, ax):
254333
255334 # Plot paths first so they appear behind nodes
256335 for path in self .paths :
257- x_coords = [next ( node .x for node in self . nodes if node . label == label ) for label in path .nodes ]
258- y_coords = [next ( node .y for node in self . nodes if node . label == label ) for label in path .nodes ]
259-
336+ x_coords = [node .x for node in path .nodes ]
337+ y_coords = [node .y for node in path .nodes ]
338+
260339 # Parse path color
261340 path_color_spec = path .options .get ('color' , 'black' )
262341 try :
0 commit comments