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
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import plotly.graph_objects as go
|
|
5
|
+
from plotly.subplots import make_subplots
|
|
6
|
+
|
|
7
|
+
import maxplotlib.backends.matplotlib.utils as plt_utils
|
|
8
|
+
from maxplotlib.subfigure.line_plot import LinePlot
|
|
9
|
+
from maxplotlib.subfigure.tikz_figure import TikzFigure
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Canvas:
|
|
13
|
+
def __init__(self, **kwargs):
|
|
14
|
+
"""
|
|
15
|
+
Initialize the Canvas class for multiple subplots.
|
|
16
|
+
|
|
17
|
+
Parameters:
|
|
18
|
+
nrows (int): Number of subplot rows. Default is 1.
|
|
19
|
+
ncols (int): Number of subplot columns. Default is 1.
|
|
20
|
+
figsize (tuple): Figure size.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# nrows=1, ncols=1, caption=None, description=None, label=None, figsize=None
|
|
24
|
+
self._nrows = kwargs.get("nrows", 1)
|
|
25
|
+
self._ncols = kwargs.get("ncols", 1)
|
|
26
|
+
self._figsize = kwargs.get("figsize", None)
|
|
27
|
+
self._caption = kwargs.get("caption", None)
|
|
28
|
+
self._description = kwargs.get("description", None)
|
|
29
|
+
self._label = kwargs.get("label", None)
|
|
30
|
+
self._fontsize = kwargs.get("fontsize", 14)
|
|
31
|
+
self._dpi = kwargs.get("dpi", 300)
|
|
32
|
+
# self._width = kwargs.get("width", 426.79135)
|
|
33
|
+
self._width = kwargs.get("width", "17cm")
|
|
34
|
+
self._ratio = kwargs.get("ratio", "golden")
|
|
35
|
+
self._gridspec_kw = kwargs.get("gridspec_kw", {"wspace": 0.08, "hspace": 0.1})
|
|
36
|
+
self._plotted = False
|
|
37
|
+
|
|
38
|
+
# Dictionary to store lines for each subplot
|
|
39
|
+
# Key: (row, col), Value: list of lines with their data and kwargs
|
|
40
|
+
self._subplots = {}
|
|
41
|
+
self._num_subplots = 0
|
|
42
|
+
|
|
43
|
+
self._subplot_matrix = [[None] * self.ncols for _ in range(self.nrows)]
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def subplots(self):
|
|
47
|
+
return self._subplots
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def layers(self):
|
|
51
|
+
layers = []
|
|
52
|
+
for (row, col), subplot in self.subplots.items():
|
|
53
|
+
layers.extend(subplot.layers)
|
|
54
|
+
return list(set(layers))
|
|
55
|
+
|
|
56
|
+
def generate_new_rowcol(self, row, col):
|
|
57
|
+
if row is None:
|
|
58
|
+
for irow in range(self.nrows):
|
|
59
|
+
has_none = any(item is None for item in self._subplot_matrix[irow])
|
|
60
|
+
if has_none:
|
|
61
|
+
row = irow
|
|
62
|
+
break
|
|
63
|
+
assert row is not None, "Not enough rows!"
|
|
64
|
+
|
|
65
|
+
if col is None:
|
|
66
|
+
for icol in range(self.ncols):
|
|
67
|
+
if self._subplot_matrix[row][icol] is None:
|
|
68
|
+
col = icol
|
|
69
|
+
break
|
|
70
|
+
assert col is not None, "Not enough columns!"
|
|
71
|
+
return row, col
|
|
72
|
+
|
|
73
|
+
def add_line(
|
|
74
|
+
self,
|
|
75
|
+
x_data,
|
|
76
|
+
y_data,
|
|
77
|
+
layer=0,
|
|
78
|
+
subplot: LinePlot | None = None,
|
|
79
|
+
row: int | None = None,
|
|
80
|
+
col: int | None = None,
|
|
81
|
+
plot_type="plot",
|
|
82
|
+
**kwargs,
|
|
83
|
+
):
|
|
84
|
+
if row is not None and col is not None:
|
|
85
|
+
try:
|
|
86
|
+
subplot = self._subplot_matrix[row][col]
|
|
87
|
+
except KeyError:
|
|
88
|
+
raise ValueError("Invalid subplot position.")
|
|
89
|
+
else:
|
|
90
|
+
row, col = 0, 0
|
|
91
|
+
subplot = self._subplot_matrix[row][col]
|
|
92
|
+
|
|
93
|
+
if subplot is None:
|
|
94
|
+
row, col = self.generate_new_rowcol(row, col)
|
|
95
|
+
subplot = self.add_subplot(col=col, row=row)
|
|
96
|
+
|
|
97
|
+
subplot.add_line(
|
|
98
|
+
x_data=x_data,
|
|
99
|
+
y_data=y_data,
|
|
100
|
+
layer=layer,
|
|
101
|
+
plot_type=plot_type,
|
|
102
|
+
**kwargs,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def add_tikzfigure(
|
|
106
|
+
self,
|
|
107
|
+
col=None,
|
|
108
|
+
row=None,
|
|
109
|
+
label=None,
|
|
110
|
+
**kwargs,
|
|
111
|
+
):
|
|
112
|
+
"""
|
|
113
|
+
Adds a subplot to the figure.
|
|
114
|
+
|
|
115
|
+
Parameters:
|
|
116
|
+
**kwargs: Arbitrary keyword arguments.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
row, col = self.generate_new_rowcol(row, col)
|
|
120
|
+
|
|
121
|
+
# Initialize the LinePlot for the given subplot position
|
|
122
|
+
tikz_figure = TikzFigure(
|
|
123
|
+
col=col,
|
|
124
|
+
row=row,
|
|
125
|
+
label=label,
|
|
126
|
+
**kwargs,
|
|
127
|
+
)
|
|
128
|
+
self._subplot_matrix[row][col] = tikz_figure
|
|
129
|
+
|
|
130
|
+
# Store the LinePlot instance by its position for easy access
|
|
131
|
+
if label is None:
|
|
132
|
+
self._subplots[(row, col)] = tikz_figure
|
|
133
|
+
else:
|
|
134
|
+
self._subplots[label] = tikz_figure
|
|
135
|
+
return tikz_figure
|
|
136
|
+
|
|
137
|
+
def add_subplot(
|
|
138
|
+
self,
|
|
139
|
+
col: int | None = None,
|
|
140
|
+
row: int | None = None,
|
|
141
|
+
figsize: tuple = (10, 6),
|
|
142
|
+
title: str | None = None,
|
|
143
|
+
caption: str | None = None,
|
|
144
|
+
description: str | None = None,
|
|
145
|
+
label: str | None = None,
|
|
146
|
+
grid: bool = False,
|
|
147
|
+
legend: bool = False,
|
|
148
|
+
xmin: float | int | None = None,
|
|
149
|
+
xmax: float | int | None = None,
|
|
150
|
+
ymin: float | int | None = None,
|
|
151
|
+
ymax: float | int | None = None,
|
|
152
|
+
xlabel: str | None = None,
|
|
153
|
+
ylabel: str | None = None,
|
|
154
|
+
xscale: float | int = 1.0,
|
|
155
|
+
yscale: float | int = 1.0,
|
|
156
|
+
xshift: float | int = 0.0,
|
|
157
|
+
yshift: float | int = 0.0,
|
|
158
|
+
):
|
|
159
|
+
"""
|
|
160
|
+
Adds a subplot to the figure.
|
|
161
|
+
|
|
162
|
+
Parameters:
|
|
163
|
+
**kwargs: Arbitrary keyword arguments.
|
|
164
|
+
- col (int): Column index for the subplot.
|
|
165
|
+
- row (int): Row index for the subplot.
|
|
166
|
+
- label (str): Label to identify the subplot.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
row, col = self.generate_new_rowcol(row, col)
|
|
170
|
+
|
|
171
|
+
# Initialize the LinePlot for the given subplot position
|
|
172
|
+
line_plot = LinePlot(
|
|
173
|
+
title=title,
|
|
174
|
+
grid=grid,
|
|
175
|
+
legend=legend,
|
|
176
|
+
xmin=xmin,
|
|
177
|
+
xmax=xmax,
|
|
178
|
+
ymin=ymin,
|
|
179
|
+
ymax=ymax,
|
|
180
|
+
xlabel=xlabel,
|
|
181
|
+
ylabel=ylabel,
|
|
182
|
+
xscale=xscale,
|
|
183
|
+
yscale=yscale,
|
|
184
|
+
xshift=xshift,
|
|
185
|
+
yshift=yshift,
|
|
186
|
+
)
|
|
187
|
+
self._subplot_matrix[row][col] = line_plot
|
|
188
|
+
|
|
189
|
+
# Store the LinePlot instance by its position for easy access
|
|
190
|
+
if label is None:
|
|
191
|
+
self._subplots[(row, col)] = line_plot
|
|
192
|
+
else:
|
|
193
|
+
self._subplots[label] = line_plot
|
|
194
|
+
return line_plot
|
|
195
|
+
|
|
196
|
+
def savefig(
|
|
197
|
+
self,
|
|
198
|
+
filename,
|
|
199
|
+
backend="matplotlib",
|
|
200
|
+
layers=None,
|
|
201
|
+
layer_by_layer=False,
|
|
202
|
+
verbose=False,
|
|
203
|
+
plot=True,
|
|
204
|
+
):
|
|
205
|
+
filename_no_extension, extension = os.path.splitext(filename)
|
|
206
|
+
if backend == "matplotlib":
|
|
207
|
+
if layer_by_layer:
|
|
208
|
+
layers = []
|
|
209
|
+
for layer in self.layers:
|
|
210
|
+
layers.append(layer)
|
|
211
|
+
fig, axs = self.plot(
|
|
212
|
+
show=False,
|
|
213
|
+
backend="matplotlib",
|
|
214
|
+
savefig=True,
|
|
215
|
+
layers=layers,
|
|
216
|
+
)
|
|
217
|
+
_fn = f"{filename_no_extension}_{layers}.{extension}"
|
|
218
|
+
fig.savefig(_fn)
|
|
219
|
+
print(f"Saved {_fn}")
|
|
220
|
+
else:
|
|
221
|
+
if layers is None:
|
|
222
|
+
layers = self.layers
|
|
223
|
+
full_filepath = filename
|
|
224
|
+
else:
|
|
225
|
+
full_filepath = f"{filename_no_extension}_{layers}.{extension}"
|
|
226
|
+
|
|
227
|
+
if self._plotted:
|
|
228
|
+
self._matplotlib_fig.savefig(full_filepath)
|
|
229
|
+
else:
|
|
230
|
+
|
|
231
|
+
fig, axs = self.plot(
|
|
232
|
+
show=False,
|
|
233
|
+
backend="matplotlib",
|
|
234
|
+
savefig=True,
|
|
235
|
+
layers=layers,
|
|
236
|
+
)
|
|
237
|
+
fig.savefig(full_filepath)
|
|
238
|
+
if verbose:
|
|
239
|
+
print(f"Saved {full_filepath}")
|
|
240
|
+
|
|
241
|
+
def plot(self, backend="matplotlib", savefig=False, layers=None):
|
|
242
|
+
if backend == "matplotlib":
|
|
243
|
+
return self.plot_matplotlib(savefig=savefig, layers=layers)
|
|
244
|
+
elif backend == "plotly":
|
|
245
|
+
return self.plot_plotly(savefig=savefig)
|
|
246
|
+
else:
|
|
247
|
+
raise ValueError(f"Invalid backend: {backend}")
|
|
248
|
+
|
|
249
|
+
def show(self, backend="matplotlib"):
|
|
250
|
+
if backend == "matplotlib":
|
|
251
|
+
self.plot(backend="matplotlib", savefig=False, layers=None)
|
|
252
|
+
self._matplotlib_fig.show()
|
|
253
|
+
elif backend == "plotly":
|
|
254
|
+
plot = self.plot_plotly(savefig=False)
|
|
255
|
+
else:
|
|
256
|
+
raise ValueError("Invalid backend")
|
|
257
|
+
|
|
258
|
+
def plot_matplotlib(self, savefig=False, layers=None, usetex=False):
|
|
259
|
+
"""
|
|
260
|
+
Generate and optionally display the subplots.
|
|
261
|
+
|
|
262
|
+
Parameters:
|
|
263
|
+
filename (str, optional): Filename to save the figure.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
tex_fonts = plt_utils.setup_tex_fonts(fontsize=self.fontsize, usetex=usetex)
|
|
267
|
+
|
|
268
|
+
plt_utils.setup_plotstyle(
|
|
269
|
+
tex_fonts=tex_fonts,
|
|
270
|
+
axes_grid=True,
|
|
271
|
+
axes_grid_which="major",
|
|
272
|
+
grid_alpha=1.0,
|
|
273
|
+
grid_linestyle="dotted",
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
if self._figsize is not None:
|
|
277
|
+
fig_width, fig_height = self._figsize
|
|
278
|
+
else:
|
|
279
|
+
fig_width, fig_height = plt_utils.set_size(
|
|
280
|
+
width=self._width,
|
|
281
|
+
ratio=self._ratio,
|
|
282
|
+
dpi=self.dpi,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# print(f"{(fig_width / self._dpi, fig_height / self._dpi) = }")
|
|
286
|
+
|
|
287
|
+
fig, axes = plt.subplots(
|
|
288
|
+
self.nrows,
|
|
289
|
+
self.ncols,
|
|
290
|
+
figsize=(fig_width, fig_height),
|
|
291
|
+
squeeze=False,
|
|
292
|
+
dpi=self._dpi,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
for (row, col), subplot in self.subplots.items():
|
|
296
|
+
ax = axes[row][col]
|
|
297
|
+
subplot.plot_matplotlib(ax, layers=layers)
|
|
298
|
+
# ax.set_title(f"Subplot ({row}, {col})")
|
|
299
|
+
ax.grid()
|
|
300
|
+
|
|
301
|
+
# Set caption, labels, etc., if needed
|
|
302
|
+
self._plotted = True
|
|
303
|
+
self._matplotlib_fig = fig
|
|
304
|
+
self._matplotlib_axes = axes
|
|
305
|
+
return fig, axes
|
|
306
|
+
|
|
307
|
+
def plot_plotly(self, show=True, savefig=None, usetex=False):
|
|
308
|
+
"""
|
|
309
|
+
Generate and optionally display the subplots using Plotly.
|
|
310
|
+
|
|
311
|
+
Parameters:
|
|
312
|
+
show (bool): Whether to display the plot.
|
|
313
|
+
savefig (str, optional): Filename to save the figure if provided.
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
tex_fonts = plt_utils.setup_tex_fonts(
|
|
317
|
+
fontsize=self.fontsize,
|
|
318
|
+
usetex=usetex,
|
|
319
|
+
) # adjust or redefine for Plotly if needed
|
|
320
|
+
|
|
321
|
+
# Set default width and height if not specified
|
|
322
|
+
if self._figsize is not None:
|
|
323
|
+
fig_width, fig_height = self._figsize
|
|
324
|
+
else:
|
|
325
|
+
fig_width, fig_height = plt_utils.set_size(
|
|
326
|
+
width=self._width,
|
|
327
|
+
ratio=self._ratio,
|
|
328
|
+
)
|
|
329
|
+
# print(self._width, fig_width, fig_height)
|
|
330
|
+
# Create subplots
|
|
331
|
+
fig = make_subplots(
|
|
332
|
+
rows=self.nrows,
|
|
333
|
+
cols=self.ncols,
|
|
334
|
+
subplot_titles=[
|
|
335
|
+
f"Subplot ({row}, {col})" for (row, col) in self.subplots.keys()
|
|
336
|
+
],
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
# Plot each subplot
|
|
340
|
+
for (row, col), line_plot in self.subplots.items():
|
|
341
|
+
traces = line_plot.plot_plotly() # Generate Plotly traces for the line_plot
|
|
342
|
+
for trace in traces:
|
|
343
|
+
fig.add_trace(trace, row=row + 1, col=col + 1)
|
|
344
|
+
|
|
345
|
+
# Update layout settings
|
|
346
|
+
fig.update_layout(
|
|
347
|
+
# width=fig_width,
|
|
348
|
+
# height=fig_height,
|
|
349
|
+
font=dict(size=self.fontsize),
|
|
350
|
+
margin=dict(l=10, r=10, t=40, b=10), # Adjust margins if needed
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
# Optionally save the figure
|
|
354
|
+
if savefig:
|
|
355
|
+
fig.write_image(savefig)
|
|
356
|
+
|
|
357
|
+
# Show or return the figure
|
|
358
|
+
# if show:
|
|
359
|
+
# fig.show()
|
|
360
|
+
return fig
|
|
361
|
+
|
|
362
|
+
# Property getters
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def dpi(self):
|
|
366
|
+
return self._dpi
|
|
367
|
+
|
|
368
|
+
@property
|
|
369
|
+
def fontsize(self):
|
|
370
|
+
return self._fontsize
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def nrows(self):
|
|
374
|
+
return self._nrows
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def ncols(self):
|
|
378
|
+
return self._ncols
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def caption(self):
|
|
382
|
+
return self._caption
|
|
383
|
+
|
|
384
|
+
@property
|
|
385
|
+
def description(self):
|
|
386
|
+
return self._description
|
|
387
|
+
|
|
388
|
+
@property
|
|
389
|
+
def label(self):
|
|
390
|
+
return self._label
|
|
391
|
+
|
|
392
|
+
@property
|
|
393
|
+
def figsize(self):
|
|
394
|
+
return self._figsize
|
|
395
|
+
|
|
396
|
+
@property
|
|
397
|
+
def subplot_matrix(self):
|
|
398
|
+
return self._subplot_matrix
|
|
399
|
+
|
|
400
|
+
# Property setters
|
|
401
|
+
@nrows.setter
|
|
402
|
+
def dpi(self, value):
|
|
403
|
+
self._dpi = value
|
|
404
|
+
|
|
405
|
+
@nrows.setter
|
|
406
|
+
def nrows(self, value):
|
|
407
|
+
self._nrows = value
|
|
408
|
+
|
|
409
|
+
@ncols.setter
|
|
410
|
+
def ncols(self, value):
|
|
411
|
+
self._ncols = value
|
|
412
|
+
|
|
413
|
+
@caption.setter
|
|
414
|
+
def caption(self, value):
|
|
415
|
+
self._caption = value
|
|
416
|
+
|
|
417
|
+
@description.setter
|
|
418
|
+
def description(self, value):
|
|
419
|
+
self._description = value
|
|
420
|
+
|
|
421
|
+
@label.setter
|
|
422
|
+
def label(self, value):
|
|
423
|
+
self._label = value
|
|
424
|
+
|
|
425
|
+
@figsize.setter
|
|
426
|
+
def figsize(self, value):
|
|
427
|
+
self._figsize = value
|
|
428
|
+
|
|
429
|
+
# Magic methods
|
|
430
|
+
def __str__(self):
|
|
431
|
+
return f"Canvas(nrows={self.nrows}, ncols={self.ncols}, figsize={self.figsize})"
|
|
432
|
+
|
|
433
|
+
def __repr__(self):
|
|
434
|
+
return f"Canvas(nrows={self.nrows}, ncols={self.ncols}, caption={self.caption}, label={self.label})"
|
|
435
|
+
|
|
436
|
+
def __getitem__(self, key):
|
|
437
|
+
"""Allows accessing subplots by tuple index."""
|
|
438
|
+
row, col = key
|
|
439
|
+
if row >= self.nrows or col >= self.ncols:
|
|
440
|
+
raise IndexError("Subplot index out of range")
|
|
441
|
+
return self._subplot_matrix[row][col]
|
|
442
|
+
|
|
443
|
+
def __setitem__(self, key, value):
|
|
444
|
+
"""Allows setting a subplot by tuple index."""
|
|
445
|
+
row, col = key
|
|
446
|
+
if row >= self.nrows or col >= self.ncols:
|
|
447
|
+
raise IndexError("Subplot index out of range")
|
|
448
|
+
self._subplot_matrix[row][col] = value
|
|
449
|
+
|
|
450
|
+
# def generate_matplotlib_code(self):
|
|
451
|
+
# """Generate code for plotting the data using matplotlib."""
|
|
452
|
+
# code = "import matplotlib.pyplot as plt\n\n"
|
|
453
|
+
# code += f"fig, axes = plt.subplots({self.nrows}, {self.ncols}, figsize={self.figsize})\n\n"
|
|
454
|
+
# if self.nrows == 1 and self.ncols == 1:
|
|
455
|
+
# code += "axes = [axes] # Single subplot\n\n"
|
|
456
|
+
# else:
|
|
457
|
+
# code += "axes = axes.flatten()\n\n"
|
|
458
|
+
# for idx, (subplot_idx, lines) in enumerate(self.subplots.items()):
|
|
459
|
+
# code += f"# Subplot {subplot_idx}\n"
|
|
460
|
+
# code += f"ax = axes[{idx}]\n"
|
|
461
|
+
# for line in lines:
|
|
462
|
+
# x_data = line['x']
|
|
463
|
+
# y_data = line['y']
|
|
464
|
+
# label = line['label']
|
|
465
|
+
# kwargs = line.get('kwargs', {})
|
|
466
|
+
# kwargs_str = ', '.join(f"{k}={repr(v)}" for k, v in kwargs.items())
|
|
467
|
+
# code += f"ax.plot({x_data}, {y_data}, label={repr(label)}"
|
|
468
|
+
# if kwargs_str:
|
|
469
|
+
# code += f", {kwargs_str}"
|
|
470
|
+
# code += ")\n"
|
|
471
|
+
# code += "ax.set_xlabel('X-axis')\n"
|
|
472
|
+
# code += "ax.set_ylabel('Y-axis')\n"
|
|
473
|
+
# if self.nrows * self.ncols > 1:
|
|
474
|
+
# code += f"ax.set_title('Subplot {subplot_idx}')\n"
|
|
475
|
+
# code += "ax.legend()\n\n"
|
|
476
|
+
# code += "plt.tight_layout()\nplt.show()\n"
|
|
477
|
+
# return code
|
|
478
|
+
|
|
479
|
+
# def generate_latex_plot(self):
|
|
480
|
+
# """Generate LaTeX code for plotting the data using pgfplots in subplots."""
|
|
481
|
+
# latex_code = "\\begin{figure}[h!]\n\\centering\n"
|
|
482
|
+
# total_subplots = self.nrows * self.ncols
|
|
483
|
+
# for idx in range(total_subplots):
|
|
484
|
+
# subplot_idx = divmod(idx, self.ncols)
|
|
485
|
+
# lines = self.subplots.get(subplot_idx, [])
|
|
486
|
+
# if not lines:
|
|
487
|
+
# continue # Skip empty subplots
|
|
488
|
+
# latex_code += "\\begin{subfigure}[b]{0.45\\textwidth}\n"
|
|
489
|
+
# latex_code += " \\begin{tikzpicture}\n"
|
|
490
|
+
# latex_code += " \\begin{axis}[\n"
|
|
491
|
+
# latex_code += " xlabel={X-axis},\n"
|
|
492
|
+
# latex_code += " ylabel={Y-axis},\n"
|
|
493
|
+
# if self.nrows * self.ncols > 1:
|
|
494
|
+
# latex_code += f" title={{Subplot {subplot_idx}}},\n"
|
|
495
|
+
# latex_code += " legend style={at={(1.05,1)}, anchor=north west},\n"
|
|
496
|
+
# latex_code += " legend entries={" + ", ".join(f"{{{line['label']}}}" for line in lines) + "}\n"
|
|
497
|
+
# latex_code += " ]\n"
|
|
498
|
+
# for line in lines:
|
|
499
|
+
# options = []
|
|
500
|
+
# kwargs = line.get('kwargs', {})
|
|
501
|
+
# if 'color' in kwargs:
|
|
502
|
+
# options.append(f"color={kwargs['color']}")
|
|
503
|
+
# if 'linestyle' in kwargs:
|
|
504
|
+
# linestyle_map = {'-': 'solid', '--': 'dashed', '-.': 'dash dot', ':': 'dotted'}
|
|
505
|
+
# linestyle = linestyle_map.get(kwargs['linestyle'], kwargs['linestyle'])
|
|
506
|
+
# options.append(f"style={linestyle}")
|
|
507
|
+
# options_str = f"[{', '.join(options)}]" if options else ""
|
|
508
|
+
# latex_code += f" \\addplot {options_str} coordinates {{\n"
|
|
509
|
+
# for x, y in zip(line['x'], line['y']):
|
|
510
|
+
# latex_code += f" ({x}, {y})\n"
|
|
511
|
+
# latex_code += " };\n"
|
|
512
|
+
# latex_code += " \\end{axis}\n"
|
|
513
|
+
# latex_code += " \\end{tikzpicture}\n"
|
|
514
|
+
# latex_code += "\\end{subfigure}\n"
|
|
515
|
+
# latex_code += "\\hfill\n" if (idx + 1) % self.ncols != 0 else "\n"
|
|
516
|
+
# latex_code += "\\caption{Multiple Subplots}\n"
|
|
517
|
+
# latex_code += "\\end{figure}\n"
|
|
518
|
+
# return latex_code
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
if __name__ == "__main__":
|
|
522
|
+
c = Canvas(ncols=2, nrows=2)
|
|
523
|
+
sp = c.add_subplot()
|
|
524
|
+
sp.add_line("Line 1", [0, 1, 2, 3], [0, 1, 4, 9])
|
|
525
|
+
c.plot()
|
|
526
|
+
print("done")
|
|
File without changes
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
import matplotlib.colors as mcolors
|
|
4
|
+
import matplotlib.patches as patches
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Color:
|
|
9
|
+
def __init__(self, color_spec):
|
|
10
|
+
"""
|
|
11
|
+
Initialize the Color object by parsing the color specification.
|
|
12
|
+
|
|
13
|
+
Parameters:
|
|
14
|
+
- color_spec: Can be a TikZ color string (e.g., 'blue!20'), a standard color name,
|
|
15
|
+
an RGB tuple, a hex code, etc.
|
|
16
|
+
"""
|
|
17
|
+
self.color_spec = color_spec
|
|
18
|
+
self.rgb = self._parse_color(color_spec)
|
|
19
|
+
|
|
20
|
+
def _parse_color(self, color_spec):
|
|
21
|
+
"""
|
|
22
|
+
Internal method to parse the color specification and convert it to an RGB tuple.
|
|
23
|
+
|
|
24
|
+
Parameters:
|
|
25
|
+
- color_spec: The color specification.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
- rgb: A tuple of (r, g, b) values, each between 0 and 1.
|
|
29
|
+
"""
|
|
30
|
+
# If it's already an RGB tuple or list
|
|
31
|
+
if isinstance(color_spec, (list, tuple)) and len(color_spec) == 3:
|
|
32
|
+
# Normalize values if necessary
|
|
33
|
+
rgb = tuple(float(c) / 255 if c > 1 else float(c) for c in color_spec)
|
|
34
|
+
return rgb
|
|
35
|
+
|
|
36
|
+
# If it's a hex code
|
|
37
|
+
if isinstance(color_spec, str) and color_spec.startswith("#"):
|
|
38
|
+
return mcolors.hex2color(color_spec)
|
|
39
|
+
|
|
40
|
+
# If it's a TikZ color string
|
|
41
|
+
match = re.match(r"(\w+)!([\d.]+)", color_spec)
|
|
42
|
+
if match:
|
|
43
|
+
base_color_name, percentage = match.groups()
|
|
44
|
+
percentage = float(percentage)
|
|
45
|
+
base_color = mcolors.to_rgb(base_color_name)
|
|
46
|
+
white = np.array([1.0, 1.0, 1.0])
|
|
47
|
+
mix = percentage / 100.0
|
|
48
|
+
color = mix * np.array(base_color) + (1 - mix) * white
|
|
49
|
+
return tuple(color)
|
|
50
|
+
|
|
51
|
+
# Else, try to parse as a standard color name
|
|
52
|
+
try:
|
|
53
|
+
return mcolors.to_rgb(color_spec)
|
|
54
|
+
except ValueError:
|
|
55
|
+
raise ValueError(f"Invalid color specification: '{color_spec}'")
|
|
56
|
+
|
|
57
|
+
def to_rgb(self):
|
|
58
|
+
"""
|
|
59
|
+
Return the color as an RGB tuple.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
- rgb: A tuple of (r, g, b) values, each between 0 and 1.
|
|
63
|
+
"""
|
|
64
|
+
return self.rgb
|
|
65
|
+
|
|
66
|
+
def to_hex(self):
|
|
67
|
+
"""
|
|
68
|
+
Return the color as a hex code.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
- hex_code: A string representing the color in hex format.
|
|
72
|
+
"""
|
|
73
|
+
return mcolors.to_hex(self.rgb)
|
|
74
|
+
|
|
75
|
+
def to_rgba(self, alpha=1.0):
|
|
76
|
+
"""
|
|
77
|
+
Return the color as an RGBA tuple.
|
|
78
|
+
|
|
79
|
+
Parameters:
|
|
80
|
+
- alpha (float): The alpha (opacity) value between 0 and 1.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
- rgba: A tuple of (r, g, b, a) values.
|
|
84
|
+
"""
|
|
85
|
+
return (*self.rgb, alpha)
|
|
File without changes
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Linestyle:
|
|
5
|
+
def __init__(self, style_spec):
|
|
6
|
+
"""
|
|
7
|
+
Initialize the Linestyle object by parsing the style specification.
|
|
8
|
+
|
|
9
|
+
Parameters:
|
|
10
|
+
- style_spec: Can be a TikZ-style line style string (e.g., 'dashed', 'dotted', 'solid', 'dashdot'),
|
|
11
|
+
or a custom dash pattern.
|
|
12
|
+
"""
|
|
13
|
+
self.style_spec = style_spec
|
|
14
|
+
self.matplotlib_style = self._parse_style(style_spec)
|
|
15
|
+
|
|
16
|
+
def _parse_style(self, style_spec):
|
|
17
|
+
"""
|
|
18
|
+
Internal method to parse the style specification and convert it to a Matplotlib linestyle.
|
|
19
|
+
|
|
20
|
+
Parameters:
|
|
21
|
+
- style_spec: The style specification.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
- linestyle: A Matplotlib linestyle string or dash pattern.
|
|
25
|
+
"""
|
|
26
|
+
# Predefined mappings from TikZ to Matplotlib
|
|
27
|
+
linestyle_mapping = {
|
|
28
|
+
"solid": "solid",
|
|
29
|
+
"dashed": "dashed",
|
|
30
|
+
"dotted": "dotted",
|
|
31
|
+
"dashdot": "dashdot",
|
|
32
|
+
# You can add more styles or custom dash patterns
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Check for predefined styles
|
|
36
|
+
if style_spec in linestyle_mapping:
|
|
37
|
+
return linestyle_mapping[style_spec]
|
|
38
|
+
else:
|
|
39
|
+
# Check if it's a custom dash pattern, e.g., 'dash pattern=on 5pt off 2pt'
|
|
40
|
+
match = re.match(r"dash pattern=on ([\d.]+)pt off ([\d.]+)pt", style_spec)
|
|
41
|
+
if match:
|
|
42
|
+
on_length = float(match.group(1))
|
|
43
|
+
off_length = float(match.group(2))
|
|
44
|
+
# Matplotlib dash pattern is specified in points
|
|
45
|
+
return (0, (on_length, off_length))
|
|
46
|
+
else:
|
|
47
|
+
# Default to solid if style is unknown
|
|
48
|
+
print(f"Unknown line style: '{style_spec}', defaulting to 'solid'")
|
|
49
|
+
return "solid"
|
|
50
|
+
|
|
51
|
+
def to_matplotlib(self):
|
|
52
|
+
"""
|
|
53
|
+
Return the line style in Matplotlib format.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
- linestyle: A Matplotlib linestyle string or dash sequence.
|
|
57
|
+
"""
|
|
58
|
+
return self.matplotlib_style
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from abc import ABCMeta, abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Layer(metaclass=ABCMeta):
|
|
5
|
+
def __init__(self, label):
|
|
6
|
+
self.label = label
|
|
7
|
+
self.items = []
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Tikzlayer(Layer):
|
|
11
|
+
def __init__(self, label):
|
|
12
|
+
super().__init__(label)
|
|
13
|
+
|
|
14
|
+
def generate_tikz(self):
|
|
15
|
+
tikz_script = f"\n% Layer {self.label}\n"
|
|
16
|
+
tikz_script += f"\\begin{{pgfonlayer}}{{{self.label}}}\n"
|
|
17
|
+
for item in self.items:
|
|
18
|
+
tikz_script += item.to_tikz()
|
|
19
|
+
tikz_script += f"\\end{{pgfonlayer}}{{{self.label}}}\n"
|
|
20
|
+
return tikz_script
|