maxplotlibx 0.1__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.
- maxplotlib/__init__.py +3 -0
- maxplotlib/backends/matplotlib/utils.py +136 -0
- maxplotlib/backends/matplotlib/utils_old.py +852 -0
- maxplotlib/backends/plotly/__init__.py +0 -0
- maxplotlib/backends/plotly/utils.py +0 -0
- maxplotlib/canvas/__init__.py +0 -0
- maxplotlib/canvas/canvas.py +526 -0
- maxplotlib/colors/__init__.py +0 -0
- maxplotlib/colors/colors.py +85 -0
- maxplotlib/linestyle/__init__.py +0 -0
- maxplotlib/linestyle/linestyle.py +58 -0
- maxplotlib/objects/__init__.py +0 -0
- maxplotlib/objects/layer.py +20 -0
- maxplotlib/objects/node.py +0 -0
- maxplotlib/objects/path.py +0 -0
- maxplotlib/subfigure/__init__.py +0 -0
- maxplotlib/subfigure/line_plot.py +357 -0
- maxplotlib/subfigure/subfigure.py +3 -0
- maxplotlib/subfigure/tikz_figure.py +497 -0
- maxplotlib/tests/test_canvas.py +7 -0
- maxplotlib/tests/test_imports.py +12 -0
- maxplotlib/tests/test_plot.py +0 -0
- maxplotlibx-0.1.dist-info/METADATA +64 -0
- maxplotlibx-0.1.dist-info/RECORD +27 -0
- maxplotlibx-0.1.dist-info/WHEEL +5 -0
- maxplotlibx-0.1.dist-info/licenses/LICENSE +21 -0
- maxplotlibx-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import subprocess
|
|
4
|
+
import tempfile
|
|
5
|
+
|
|
6
|
+
import matplotlib.patches as patches
|
|
7
|
+
import numpy as np
|
|
8
|
+
from matplotlib.image import imread
|
|
9
|
+
|
|
10
|
+
from maxplotlib.colors.colors import Color
|
|
11
|
+
from maxplotlib.linestyle.linestyle import Linestyle
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Tikzlayer:
|
|
15
|
+
def __init__(self, label):
|
|
16
|
+
self.label = label
|
|
17
|
+
self.items = []
|
|
18
|
+
|
|
19
|
+
def add(self, item):
|
|
20
|
+
self.items.append(item)
|
|
21
|
+
|
|
22
|
+
def get_reqs(self):
|
|
23
|
+
reqs = set()
|
|
24
|
+
for item in self.items:
|
|
25
|
+
if isinstance(item, Path):
|
|
26
|
+
for node in item.nodes:
|
|
27
|
+
if not node.layer == self.label:
|
|
28
|
+
reqs.add(node.layer)
|
|
29
|
+
return reqs
|
|
30
|
+
|
|
31
|
+
def generate_tikz(self):
|
|
32
|
+
tikz_script = f"\n% Layer {self.label}\n"
|
|
33
|
+
tikz_script += f"\\begin{{pgfonlayer}}{{{self.label}}}\n"
|
|
34
|
+
for item in self.items:
|
|
35
|
+
tikz_script += item.to_tikz()
|
|
36
|
+
tikz_script += f"\\end{{pgfonlayer}}{{{self.label}}}\n"
|
|
37
|
+
return tikz_script
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TikzWrapper:
|
|
41
|
+
def __init__(self, raw_tikz, label="", content="", layer=0, **kwargs):
|
|
42
|
+
self.raw_tikz = raw_tikz
|
|
43
|
+
self.label = label
|
|
44
|
+
self.content = content
|
|
45
|
+
self.layer = layer
|
|
46
|
+
self.options = kwargs
|
|
47
|
+
|
|
48
|
+
def to_tikz(self):
|
|
49
|
+
return self.raw_tikz
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Node:
|
|
53
|
+
def __init__(self, x, y, label="", content="", layer=0, **kwargs):
|
|
54
|
+
"""
|
|
55
|
+
Represents a TikZ node.
|
|
56
|
+
|
|
57
|
+
Parameters:
|
|
58
|
+
- x (float): X-coordinate of the node.
|
|
59
|
+
- y (float): Y-coordinate of the node.
|
|
60
|
+
- name (str, optional): Name of the node. If None, a default name will be assigned.
|
|
61
|
+
- **kwargs: Additional TikZ node options (e.g., shape, color).
|
|
62
|
+
"""
|
|
63
|
+
self.x = x
|
|
64
|
+
self.y = y
|
|
65
|
+
self.label = label
|
|
66
|
+
self.content = content
|
|
67
|
+
self.layer = layer
|
|
68
|
+
self.options = kwargs
|
|
69
|
+
|
|
70
|
+
def to_tikz(self):
|
|
71
|
+
"""
|
|
72
|
+
Generate the TikZ code for this node.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
- tikz_str (str): TikZ code string for the node.
|
|
76
|
+
"""
|
|
77
|
+
options = ", ".join(
|
|
78
|
+
f"{k.replace('_', ' ')}={v}" for k, v in self.options.items()
|
|
79
|
+
)
|
|
80
|
+
if options:
|
|
81
|
+
options = f"[{options}]"
|
|
82
|
+
return f"\\node{options} ({self.label}) at ({self.x}, {self.y}) {{{self.content}}};\n"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Path:
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
nodes,
|
|
89
|
+
path_actions=[],
|
|
90
|
+
cycle=False,
|
|
91
|
+
label="",
|
|
92
|
+
layer=0,
|
|
93
|
+
**kwargs,
|
|
94
|
+
):
|
|
95
|
+
"""
|
|
96
|
+
Represents a path (line) connecting multiple nodes.
|
|
97
|
+
|
|
98
|
+
Parameters:
|
|
99
|
+
- nodes (list of str): List of node names to connect.
|
|
100
|
+
- **kwargs: Additional TikZ path options (e.g., style, color).
|
|
101
|
+
"""
|
|
102
|
+
self.nodes = nodes
|
|
103
|
+
self.path_actions = path_actions
|
|
104
|
+
self.cycle = cycle
|
|
105
|
+
self.layer = layer
|
|
106
|
+
self.label = label
|
|
107
|
+
self.options = kwargs
|
|
108
|
+
|
|
109
|
+
def to_tikz(self):
|
|
110
|
+
"""
|
|
111
|
+
Generate the TikZ code for this path.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
- tikz_str (str): TikZ code string for the path.
|
|
115
|
+
"""
|
|
116
|
+
options = ", ".join(
|
|
117
|
+
f"{k.replace('_', ' ')}={v}" for k, v in self.options.items()
|
|
118
|
+
)
|
|
119
|
+
if len(self.path_actions) > 0:
|
|
120
|
+
options = ", ".join(self.path_actions) + ", " + options
|
|
121
|
+
if options:
|
|
122
|
+
options = f"[{options}]"
|
|
123
|
+
path_str = " to ".join(f"({node.label}.center)" for node in self.nodes)
|
|
124
|
+
if self.cycle:
|
|
125
|
+
path_str += " -- cycle"
|
|
126
|
+
return f"\\draw{options} {path_str};\n"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class TikzFigure:
|
|
130
|
+
def __init__(self, **kwargs):
|
|
131
|
+
"""
|
|
132
|
+
Initialize the TikzFigure class for creating TikZ figures.
|
|
133
|
+
|
|
134
|
+
Parameters:
|
|
135
|
+
**kwargs: Arbitrary keyword arguments.
|
|
136
|
+
- figsize (tuple): Figure size (default is (10, 6)).
|
|
137
|
+
- caption (str): Caption for the figure.
|
|
138
|
+
- description (str): Description of the figure.
|
|
139
|
+
- label (str): Label for the figure.
|
|
140
|
+
- grid (bool): Whether to display grid lines (default is False).
|
|
141
|
+
TODO: Add all options
|
|
142
|
+
"""
|
|
143
|
+
# Set default values
|
|
144
|
+
self._figsize = kwargs.get("figsize", (10, 6))
|
|
145
|
+
self._caption = kwargs.get("caption", None)
|
|
146
|
+
self._description = kwargs.get("description", None)
|
|
147
|
+
self._label = kwargs.get("label", None)
|
|
148
|
+
self._grid = kwargs.get("grid", False)
|
|
149
|
+
|
|
150
|
+
# Initialize lists to hold Node and Path objects
|
|
151
|
+
self.nodes = []
|
|
152
|
+
self.paths = []
|
|
153
|
+
self.layers = {}
|
|
154
|
+
|
|
155
|
+
# Counter for unnamed nodes
|
|
156
|
+
self._node_counter = 0
|
|
157
|
+
|
|
158
|
+
def add_node(self, x, y, label=None, content="", layer=0, **kwargs):
|
|
159
|
+
"""
|
|
160
|
+
Add a node to the TikZ figure.
|
|
161
|
+
|
|
162
|
+
Parameters:
|
|
163
|
+
- x (float): X-coordinate of the node.
|
|
164
|
+
- y (float): Y-coordinate of the node.
|
|
165
|
+
- label (str, optional): Label of the node. If None, a default label will be assigned.
|
|
166
|
+
- **kwargs: Additional TikZ node options (e.g., shape, color).
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
- node (Node): The Node object that was added.
|
|
170
|
+
"""
|
|
171
|
+
if label is None:
|
|
172
|
+
label = f"node{self._node_counter}"
|
|
173
|
+
node = Node(x=x, y=y, label=label, layer=layer, content=content, **kwargs)
|
|
174
|
+
self.nodes.append(node)
|
|
175
|
+
if layer in self.layers:
|
|
176
|
+
self.layers[layer].add(node)
|
|
177
|
+
else:
|
|
178
|
+
self.layers[layer] = Tikzlayer(layer)
|
|
179
|
+
self.layers[layer].add(node)
|
|
180
|
+
self._node_counter += 1
|
|
181
|
+
return node
|
|
182
|
+
|
|
183
|
+
def add_path(self, nodes, layer=0, **kwargs):
|
|
184
|
+
"""
|
|
185
|
+
Add a line or path connecting multiple nodes.
|
|
186
|
+
|
|
187
|
+
Parameters:
|
|
188
|
+
- nodes (list of str): List of node names to connect.
|
|
189
|
+
- **kwargs: Additional TikZ path options (e.g., style, color).
|
|
190
|
+
|
|
191
|
+
Examples:
|
|
192
|
+
- add_path(['A', 'B', 'C'], color='blue')
|
|
193
|
+
Connects nodes A -> B -> C with a blue line.
|
|
194
|
+
"""
|
|
195
|
+
if not isinstance(nodes, list):
|
|
196
|
+
raise ValueError("nodes parameter must be a list of node names.")
|
|
197
|
+
|
|
198
|
+
nodes = [
|
|
199
|
+
(
|
|
200
|
+
node
|
|
201
|
+
if isinstance(node, Node)
|
|
202
|
+
else (
|
|
203
|
+
self.get_node(node)
|
|
204
|
+
if isinstance(node, str)
|
|
205
|
+
else ValueError(f"Invalid node type: {type(node)}")
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
for node in nodes
|
|
209
|
+
]
|
|
210
|
+
path = Path(nodes, **kwargs)
|
|
211
|
+
self.paths.append(path)
|
|
212
|
+
if layer in self.layers:
|
|
213
|
+
self.layers[layer].add(path)
|
|
214
|
+
else:
|
|
215
|
+
self.layers[layer] = Tikzlayer(layer)
|
|
216
|
+
self.layers[layer].add(path)
|
|
217
|
+
return path
|
|
218
|
+
|
|
219
|
+
def add_raw(self, raw_tikz, layer=0, **kwargs):
|
|
220
|
+
tikz = TikzWrapper(raw_tikz)
|
|
221
|
+
if layer in self.layers:
|
|
222
|
+
self.layers[layer].add(tikz)
|
|
223
|
+
else:
|
|
224
|
+
self.layers[layer] = Tikzlayer(layer)
|
|
225
|
+
self.layers[layer].add(tikz)
|
|
226
|
+
return tikz
|
|
227
|
+
|
|
228
|
+
def get_node(self, node_label):
|
|
229
|
+
for node in self.nodes:
|
|
230
|
+
if node.label == node_label:
|
|
231
|
+
return node
|
|
232
|
+
|
|
233
|
+
def get_layer(self, item):
|
|
234
|
+
for layer, layer_items in self.layers.items():
|
|
235
|
+
if item in [layer_item.label for layer_item in layer_items]:
|
|
236
|
+
return layer
|
|
237
|
+
print(f"Item {item} not found in any layer!")
|
|
238
|
+
|
|
239
|
+
def add_tabs(self, tikz_script):
|
|
240
|
+
tikz_script_new = ""
|
|
241
|
+
tab_str = " "
|
|
242
|
+
num_tabs = 0
|
|
243
|
+
for line in tikz_script.split("\n"):
|
|
244
|
+
if "\\end" in line:
|
|
245
|
+
num_tabs = max(num_tabs - 1, 0)
|
|
246
|
+
tikz_script_new += f"{tab_str*num_tabs}{line}\n"
|
|
247
|
+
if "\\begin" in line:
|
|
248
|
+
num_tabs += 1
|
|
249
|
+
return tikz_script_new
|
|
250
|
+
|
|
251
|
+
def generate_tikz(self):
|
|
252
|
+
"""
|
|
253
|
+
Generate the TikZ script for the figure.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
- tikz_script (str): The TikZ script as a string.
|
|
257
|
+
"""
|
|
258
|
+
tikz_script = "\\begin{tikzpicture}\n"
|
|
259
|
+
tikz_script += "% Define the layers library\n"
|
|
260
|
+
layers = sorted([str(layer) for layer in self.layers.keys()])
|
|
261
|
+
for layer in layers:
|
|
262
|
+
tikz_script += f"\\pgfdeclarelayer{{{layer}}}\n"
|
|
263
|
+
tikz_script += f"\\pgfsetlayers{{{','.join(layers)}}}\n"
|
|
264
|
+
|
|
265
|
+
# Add grid if enabled
|
|
266
|
+
# TODO: Create a Grid class
|
|
267
|
+
if self._grid:
|
|
268
|
+
tikz_script += (
|
|
269
|
+
" \\draw[step=1cm, gray, very thin] (-10,-10) grid (10,10);\n"
|
|
270
|
+
)
|
|
271
|
+
ordered_layers = []
|
|
272
|
+
buffered_layers = set()
|
|
273
|
+
|
|
274
|
+
for key, layer in self.layers.items():
|
|
275
|
+
# layer_order, buffered_layers = update_layer_order(layer, layer_order, buffered_layers)
|
|
276
|
+
reqs = layer.get_reqs()
|
|
277
|
+
if all([r == layer.label for r in reqs]):
|
|
278
|
+
ordered_layers.append(layer)
|
|
279
|
+
elif all([r in [l.label for l in ordered_layers] for r in reqs]):
|
|
280
|
+
ordered_layers.append(layer)
|
|
281
|
+
else:
|
|
282
|
+
buffered_layers.add(layer)
|
|
283
|
+
|
|
284
|
+
for buffered_layer in buffered_layers:
|
|
285
|
+
buff_reqs = buffered_layer.get_reqs()
|
|
286
|
+
if all([r in [l.label for l in ordered_layers] for r in buff_reqs]):
|
|
287
|
+
print("Move layer from buffer")
|
|
288
|
+
ordered_layers.append(key)
|
|
289
|
+
buffered_layers.remove(key)
|
|
290
|
+
assert (
|
|
291
|
+
len(buffered_layers) == 0
|
|
292
|
+
), f"Layer order is impossible for layer {[layer.label for layer in buffered_layers]}"
|
|
293
|
+
for layer in ordered_layers:
|
|
294
|
+
tikz_script += layer.generate_tikz()
|
|
295
|
+
|
|
296
|
+
tikz_script += "\\end{tikzpicture}"
|
|
297
|
+
|
|
298
|
+
# Wrap in figure environment if necessary
|
|
299
|
+
if self._caption or self._description or self._label:
|
|
300
|
+
figure_env = "\\begin{figure}\n" + tikz_script + "\n"
|
|
301
|
+
if self._caption:
|
|
302
|
+
figure_env += f" \\caption{{{self._caption}}}\n"
|
|
303
|
+
if self._label:
|
|
304
|
+
figure_env += f" \\label{{{self._label}}}\n"
|
|
305
|
+
figure_env += "\\end{figure}"
|
|
306
|
+
tikz_script = figure_env
|
|
307
|
+
tikz_script = self.add_tabs(tikz_script)
|
|
308
|
+
return tikz_script
|
|
309
|
+
|
|
310
|
+
def savefig(self, filepath):
|
|
311
|
+
tikz_code = self.generate_tikz()
|
|
312
|
+
with open(filepath, "w") as f:
|
|
313
|
+
f.write(tikz_code)
|
|
314
|
+
|
|
315
|
+
def generate_standalone(self):
|
|
316
|
+
tikz_code = self.generate_tikz()
|
|
317
|
+
|
|
318
|
+
# Create a minimal LaTeX document
|
|
319
|
+
latex_document = (
|
|
320
|
+
"\\documentclass[border=10pt]{standalone}\n"
|
|
321
|
+
"\\usepackage{tikz}\n"
|
|
322
|
+
"\\begin{document}\n"
|
|
323
|
+
f"{tikz_code}\n"
|
|
324
|
+
"\\end{document}"
|
|
325
|
+
)
|
|
326
|
+
return latex_document
|
|
327
|
+
|
|
328
|
+
def compile_pdf(self, filename="output.pdf"):
|
|
329
|
+
"""
|
|
330
|
+
Compile the TikZ script into a PDF using pdflatex.
|
|
331
|
+
|
|
332
|
+
Parameters:
|
|
333
|
+
- filename (str): The name of the output PDF file (default is 'output.pdf').
|
|
334
|
+
|
|
335
|
+
Notes:
|
|
336
|
+
- Requires 'pdflatex' to be installed and accessible from the command line.
|
|
337
|
+
"""
|
|
338
|
+
latex_document = self.generate_standalone()
|
|
339
|
+
|
|
340
|
+
# Use a temporary directory to store the LaTeX files
|
|
341
|
+
with tempfile.TemporaryDirectory() as tempdir:
|
|
342
|
+
tex_file = os.path.join(tempdir, "figure.tex")
|
|
343
|
+
with open(tex_file, "w") as f:
|
|
344
|
+
f.write(latex_document)
|
|
345
|
+
|
|
346
|
+
# Run pdflatex
|
|
347
|
+
try:
|
|
348
|
+
subprocess.run(
|
|
349
|
+
["pdflatex", "-interaction=nonstopmode", tex_file],
|
|
350
|
+
cwd=tempdir,
|
|
351
|
+
check=True,
|
|
352
|
+
stdout=subprocess.PIPE,
|
|
353
|
+
stderr=subprocess.PIPE,
|
|
354
|
+
)
|
|
355
|
+
except subprocess.CalledProcessError as e:
|
|
356
|
+
print("An error occurred while compiling the LaTeX document:")
|
|
357
|
+
print(e.stderr.decode())
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
# Move the output PDF to the desired location
|
|
361
|
+
pdf_output = os.path.join(tempdir, "figure.pdf")
|
|
362
|
+
if os.path.exists(pdf_output):
|
|
363
|
+
os.rename(pdf_output, filename)
|
|
364
|
+
print(f"PDF successfully compiled and saved as '{filename}'.")
|
|
365
|
+
else:
|
|
366
|
+
print("PDF compilation failed. Please check the LaTeX log for details.")
|
|
367
|
+
|
|
368
|
+
def plot_matplotlib(self, ax, layers=None):
|
|
369
|
+
"""
|
|
370
|
+
Plot all nodes and paths on the provided axis using Matplotlib.
|
|
371
|
+
|
|
372
|
+
Parameters:
|
|
373
|
+
- ax (matplotlib.axes.Axes): Axis on which to plot the figure.
|
|
374
|
+
"""
|
|
375
|
+
|
|
376
|
+
# Plot paths first so they appear behind nodes
|
|
377
|
+
for path in self.paths:
|
|
378
|
+
x_coords = [node.x for node in path.nodes]
|
|
379
|
+
y_coords = [node.y for node in path.nodes]
|
|
380
|
+
|
|
381
|
+
# Parse path color
|
|
382
|
+
path_color_spec = path.options.get("color", "black")
|
|
383
|
+
try:
|
|
384
|
+
color = Color(path_color_spec).to_rgb()
|
|
385
|
+
except ValueError as e:
|
|
386
|
+
print(e)
|
|
387
|
+
color = "black"
|
|
388
|
+
|
|
389
|
+
# Parse line width
|
|
390
|
+
line_width_spec = path.options.get("line_width", 1)
|
|
391
|
+
if isinstance(line_width_spec, str):
|
|
392
|
+
match = re.match(r"([\d.]+)(pt)?", line_width_spec)
|
|
393
|
+
if match:
|
|
394
|
+
line_width = float(match.group(1))
|
|
395
|
+
else:
|
|
396
|
+
print(
|
|
397
|
+
f"Invalid line width specification: '{line_width_spec}', defaulting to 1",
|
|
398
|
+
)
|
|
399
|
+
line_width = 1
|
|
400
|
+
else:
|
|
401
|
+
line_width = float(line_width_spec)
|
|
402
|
+
|
|
403
|
+
# Parse line style using Linestyle class
|
|
404
|
+
style_spec = path.options.get("style", "solid")
|
|
405
|
+
linestyle = Linestyle(style_spec).to_matplotlib()
|
|
406
|
+
|
|
407
|
+
ax.plot(
|
|
408
|
+
x_coords,
|
|
409
|
+
y_coords,
|
|
410
|
+
color=color,
|
|
411
|
+
linewidth=line_width,
|
|
412
|
+
linestyle=linestyle,
|
|
413
|
+
zorder=1, # Lower z-order to place behind nodes
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
# Plot nodes after paths so they appear on top
|
|
417
|
+
for node in self.nodes:
|
|
418
|
+
# Determine shape and size
|
|
419
|
+
shape = node.options.get("shape", "circle")
|
|
420
|
+
fill_color_spec = node.options.get("fill", "white")
|
|
421
|
+
edge_color_spec = node.options.get("draw", "black")
|
|
422
|
+
linewidth = float(node.options.get("line_width", 1))
|
|
423
|
+
size = float(node.options.get("size", 1))
|
|
424
|
+
|
|
425
|
+
# Parse colors using the Color class
|
|
426
|
+
try:
|
|
427
|
+
facecolor = Color(fill_color_spec).to_rgb()
|
|
428
|
+
except ValueError as e:
|
|
429
|
+
print(e)
|
|
430
|
+
facecolor = "white"
|
|
431
|
+
|
|
432
|
+
try:
|
|
433
|
+
edgecolor = Color(edge_color_spec).to_rgb()
|
|
434
|
+
except ValueError as e:
|
|
435
|
+
print(e)
|
|
436
|
+
edgecolor = "black"
|
|
437
|
+
|
|
438
|
+
# Plot shapes
|
|
439
|
+
if shape == "circle":
|
|
440
|
+
radius = size / 2
|
|
441
|
+
circle = patches.Circle(
|
|
442
|
+
(node.x, node.y),
|
|
443
|
+
radius,
|
|
444
|
+
facecolor=facecolor,
|
|
445
|
+
edgecolor=edgecolor,
|
|
446
|
+
linewidth=linewidth,
|
|
447
|
+
zorder=2, # Higher z-order to place on top of paths
|
|
448
|
+
)
|
|
449
|
+
ax.add_patch(circle)
|
|
450
|
+
elif shape == "rectangle":
|
|
451
|
+
width = height = size
|
|
452
|
+
rect = patches.Rectangle(
|
|
453
|
+
(node.x - width / 2, node.y - height / 2),
|
|
454
|
+
width,
|
|
455
|
+
height,
|
|
456
|
+
facecolor=facecolor,
|
|
457
|
+
edgecolor=edgecolor,
|
|
458
|
+
linewidth=linewidth,
|
|
459
|
+
zorder=2, # Higher z-order
|
|
460
|
+
)
|
|
461
|
+
ax.add_patch(rect)
|
|
462
|
+
else:
|
|
463
|
+
# Default to circle if shape is unknown
|
|
464
|
+
radius = size / 2
|
|
465
|
+
circle = patches.Circle(
|
|
466
|
+
(node.x, node.y),
|
|
467
|
+
radius,
|
|
468
|
+
facecolor=facecolor,
|
|
469
|
+
edgecolor=edgecolor,
|
|
470
|
+
linewidth=linewidth,
|
|
471
|
+
zorder=2,
|
|
472
|
+
)
|
|
473
|
+
ax.add_patch(circle)
|
|
474
|
+
|
|
475
|
+
# Add text inside the shape
|
|
476
|
+
if node.content:
|
|
477
|
+
ax.text(
|
|
478
|
+
node.x,
|
|
479
|
+
node.y,
|
|
480
|
+
node.content,
|
|
481
|
+
fontsize=10,
|
|
482
|
+
ha="center",
|
|
483
|
+
va="center",
|
|
484
|
+
wrap=True,
|
|
485
|
+
zorder=3, # Even higher z-order for text
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
# Remove axes, ticks, and legend
|
|
489
|
+
ax.axis("off")
|
|
490
|
+
|
|
491
|
+
# Adjust plot limits
|
|
492
|
+
all_x = [node.x for node in self.nodes]
|
|
493
|
+
all_y = [node.y for node in self.nodes]
|
|
494
|
+
padding = 1 # Adjust padding as needed
|
|
495
|
+
ax.set_xlim(min(all_x) - padding, max(all_x) + padding)
|
|
496
|
+
ax.set_ylim(min(all_y) - padding, max(all_y) + padding)
|
|
497
|
+
ax.set_aspect("equal", adjustable="datalim")
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: maxplotlibx
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: A reproducible plotting module with various backends and export options.
|
|
5
|
+
Author: Max
|
|
6
|
+
Project-URL: Source, https://github.com/max-models/maxplotlib
|
|
7
|
+
Keywords: matplotlib
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: matplotlib
|
|
13
|
+
Requires-Dist: pint
|
|
14
|
+
Requires-Dist: plotly
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: pytest; extra == "test"
|
|
17
|
+
Requires-Dist: coverage; extra == "test"
|
|
18
|
+
Provides-Extra: docs
|
|
19
|
+
Requires-Dist: myst-parser; extra == "docs"
|
|
20
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
21
|
+
Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
|
22
|
+
Requires-Dist: nbsphinx; extra == "docs"
|
|
23
|
+
Requires-Dist: ipykernel; extra == "docs"
|
|
24
|
+
Requires-Dist: nbconvert; extra == "docs"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: maxplotlibx[docs,test]; extra == "dev"
|
|
27
|
+
Requires-Dist: ruff; extra == "dev"
|
|
28
|
+
Requires-Dist: black; extra == "dev"
|
|
29
|
+
Requires-Dist: isort; extra == "dev"
|
|
30
|
+
Requires-Dist: jupyterlab; extra == "dev"
|
|
31
|
+
Requires-Dist: nbstripout; extra == "dev"
|
|
32
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
33
|
+
Requires-Dist: pyproject-fmt; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# maxplotlib
|
|
37
|
+
|
|
38
|
+
This is a wrapper for matplotlib so I can produce figures with consistent formatting. It also has some pretty nice additions such as using layers and exporting to tikz.
|
|
39
|
+
|
|
40
|
+
Related packages: [maxtikzlib](https://github.com/max-models/maxtikzlib) and [maxtexlib](https://github.com/max-models/maxtexlib).
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
Create and activate python environment
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
python -m venv env
|
|
48
|
+
source env/bin/activate
|
|
49
|
+
pip install --upgrade pip
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Install the code and requirements with pip
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
pip install -e .
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Additional dependencies for developers can be installed with
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
pip install -e ".[dev]"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Some examples can be found in `tutorials/`
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
maxplotlib/__init__.py,sha256=M4hqMJNeTsfZU99H6vI_rr8ua-ZM49GyEMHUV_Vq2JQ,66
|
|
2
|
+
maxplotlib/backends/matplotlib/utils.py,sha256=fmKhWiBeWALGuHUBxTEMl0GVpRF7TObfpZWjsaL7ZOM,3585
|
|
3
|
+
maxplotlib/backends/matplotlib/utils_old.py,sha256=VpkRRt3bhUtPd03SxWa7mPRWwSiOu9j5SA3GFlJ1DDc,29391
|
|
4
|
+
maxplotlib/backends/plotly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
maxplotlib/backends/plotly/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
maxplotlib/canvas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
maxplotlib/canvas/canvas.py,sha256=7HXQlcsWcNJzOy9T7A3PDkjYVPh3ciHKV7R8pHzTO9s,17352
|
|
8
|
+
maxplotlib/colors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
maxplotlib/colors/colors.py,sha256=_1V2Mumh_aM0Y7iOzQOEItrCh2nzmFKapzy1OGZ-_Q0,2599
|
|
10
|
+
maxplotlib/linestyle/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
maxplotlib/linestyle/linestyle.py,sha256=xGakDaroYVb9HWQoL5RtwPAno5SUH70r85ozXiA2U3c,2021
|
|
12
|
+
maxplotlib/objects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
maxplotlib/objects/layer.py,sha256=BGZTtTzu20FkZyGwhnBql827HV1I7SLkKw08bDdIqeQ,556
|
|
14
|
+
maxplotlib/objects/node.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
maxplotlib/objects/path.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
maxplotlib/subfigure/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
maxplotlib/subfigure/line_plot.py,sha256=QzJL2Cx5XMAfONMWUaEV8MyTJcjnz_ovHyDAaNKydkU,10826
|
|
18
|
+
maxplotlib/subfigure/subfigure.py,sha256=MMIAT8haC-zuCLucXagWchHqBYQdqWLc_MJv5G1OAP0,80
|
|
19
|
+
maxplotlib/subfigure/tikz_figure.py,sha256=xzil9gBOsT6JMPcskS07yiYSwahMvGHVs41vtkZCU7U,16822
|
|
20
|
+
maxplotlib/tests/test_canvas.py,sha256=-wbfUUnfXF4AGe2UPZl0_QuDDYKEdV9378UZo1MWC84,130
|
|
21
|
+
maxplotlib/tests/test_imports.py,sha256=icFvgf2VVlO5e5FkU-IFYk6CPnXQWGM3_nJyEkpC2_Y,172
|
|
22
|
+
maxplotlib/tests/test_plot.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
maxplotlibx-0.1.dist-info/licenses/LICENSE,sha256=yQ4Pipqp0gP6uNf8Hb6bj4HpyUuJvg41pkIrx3vUoxY,1060
|
|
24
|
+
maxplotlibx-0.1.dist-info/METADATA,sha256=3MjFktiWokVySS9MEFpxC4gURJ2uAUsempgSra_Hwl8,1838
|
|
25
|
+
maxplotlibx-0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
26
|
+
maxplotlibx-0.1.dist-info/top_level.txt,sha256=F_tiI1wDrGUZQgHR8EKZ-ajO0SSTSTp-NmYbGm1A_kA,11
|
|
27
|
+
maxplotlibx-0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Max
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
maxplotlib
|