py-pluto 1.1.4__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.
Files changed (73) hide show
  1. pyPLUTO/__init__.py +22 -0
  2. pyPLUTO/amr.py +745 -0
  3. pyPLUTO/baseloadmixin.py +258 -0
  4. pyPLUTO/baseloadstate.py +45 -0
  5. pyPLUTO/codes/echo_load.py +161 -0
  6. pyPLUTO/configure.py +261 -0
  7. pyPLUTO/gui/config.py +174 -0
  8. pyPLUTO/gui/custom_var.py +435 -0
  9. pyPLUTO/gui/globals.py +108 -0
  10. pyPLUTO/gui/main.py +17 -0
  11. pyPLUTO/gui/main_window.py +177 -0
  12. pyPLUTO/gui/panels.py +66 -0
  13. pyPLUTO/gui/utils.py +273 -0
  14. pyPLUTO/h_pypluto.py +84 -0
  15. pyPLUTO/image.py +302 -0
  16. pyPLUTO/imagefuncs/colorbar.py +240 -0
  17. pyPLUTO/imagefuncs/contour.py +254 -0
  18. pyPLUTO/imagefuncs/create_axes.py +464 -0
  19. pyPLUTO/imagefuncs/display.py +306 -0
  20. pyPLUTO/imagefuncs/figure.py +395 -0
  21. pyPLUTO/imagefuncs/imagetools.py +487 -0
  22. pyPLUTO/imagefuncs/interactive.py +403 -0
  23. pyPLUTO/imagefuncs/legend.py +250 -0
  24. pyPLUTO/imagefuncs/plot.py +311 -0
  25. pyPLUTO/imagefuncs/range.py +242 -0
  26. pyPLUTO/imagefuncs/scatter.py +270 -0
  27. pyPLUTO/imagefuncs/set_axis.py +497 -0
  28. pyPLUTO/imagefuncs/streamplot.py +297 -0
  29. pyPLUTO/imagefuncs/zoom.py +428 -0
  30. pyPLUTO/imagemixin.py +259 -0
  31. pyPLUTO/imagestate.py +45 -0
  32. pyPLUTO/load.py +447 -0
  33. pyPLUTO/loadfuncs/baseloadtools.py +71 -0
  34. pyPLUTO/loadfuncs/codeselection.py +48 -0
  35. pyPLUTO/loadfuncs/defpluto.py +123 -0
  36. pyPLUTO/loadfuncs/descriptor.py +102 -0
  37. pyPLUTO/loadfuncs/findfiles.py +182 -0
  38. pyPLUTO/loadfuncs/findformat.py +245 -0
  39. pyPLUTO/loadfuncs/initload.py +203 -0
  40. pyPLUTO/loadfuncs/loadvars.py +227 -0
  41. pyPLUTO/loadfuncs/offsetdata.py +87 -0
  42. pyPLUTO/loadfuncs/offsetfluid.py +408 -0
  43. pyPLUTO/loadfuncs/read_files.py +213 -0
  44. pyPLUTO/loadfuncs/readdata.py +619 -0
  45. pyPLUTO/loadfuncs/readdata_old.py +567 -0
  46. pyPLUTO/loadfuncs/readdefplini.py +101 -0
  47. pyPLUTO/loadfuncs/readfluid.py +479 -0
  48. pyPLUTO/loadfuncs/readformat.py +277 -0
  49. pyPLUTO/loadfuncs/readgridalone.py +224 -0
  50. pyPLUTO/loadfuncs/readgridfile.py +255 -0
  51. pyPLUTO/loadfuncs/readgridout.py +451 -0
  52. pyPLUTO/loadfuncs/readpart.py +419 -0
  53. pyPLUTO/loadfuncs/readtab.py +105 -0
  54. pyPLUTO/loadfuncs/write_files.py +283 -0
  55. pyPLUTO/loadmixin.py +419 -0
  56. pyPLUTO/loadpart.py +233 -0
  57. pyPLUTO/loadstate.py +68 -0
  58. pyPLUTO/newload.py +81 -0
  59. pyPLUTO/pytools.py +145 -0
  60. pyPLUTO/toolfuncs/findlines.py +551 -0
  61. pyPLUTO/toolfuncs/fourier.py +149 -0
  62. pyPLUTO/toolfuncs/nabla.py +676 -0
  63. pyPLUTO/toolfuncs/parttools.py +152 -0
  64. pyPLUTO/toolfuncs/transform.py +638 -0
  65. pyPLUTO/utils/annotator.py +27 -0
  66. pyPLUTO/utils/inspector.py +145 -0
  67. pyPLUTO/utils/make_docstrings.py +3 -0
  68. py_pluto-1.1.4.dist-info/METADATA +218 -0
  69. py_pluto-1.1.4.dist-info/RECORD +73 -0
  70. py_pluto-1.1.4.dist-info/WHEEL +5 -0
  71. py_pluto-1.1.4.dist-info/entry_points.txt +2 -0
  72. py_pluto-1.1.4.dist-info/licenses/LICENSE +27 -0
  73. py_pluto-1.1.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,403 @@
1
+ """Interactive functions for image manipulation and display."""
2
+
3
+ import inspect
4
+ from collections.abc import Iterable
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import matplotlib.pyplot as plt
9
+ import numpy as np
10
+ from matplotlib import animation
11
+ from matplotlib.artist import Artist
12
+ from matplotlib.axes import Axes
13
+ from matplotlib.collections import Collection, QuadMesh
14
+ from matplotlib.lines import Line2D
15
+ from matplotlib.widgets import Slider
16
+ from numpy.typing import NDArray
17
+
18
+ from pyPLUTO.imagefuncs.display import DisplayManager
19
+ from pyPLUTO.imagefuncs.imagetools import ImageToolsManager
20
+ from pyPLUTO.imagefuncs.plot import PlotManager
21
+ from pyPLUTO.imagemixin import ImageMixin
22
+ from pyPLUTO.imagestate import ImageState
23
+ from pyPLUTO.utils.inspector import track_kwargs
24
+
25
+
26
+ class InteractiveManager(ImageMixin):
27
+ """InteractiveManager class.
28
+
29
+ It provides methods to create interactive plots with sliders to change the
30
+ data. It is designed to work with fluid variables and allows for dynamic
31
+ visualization of data as a function of time. The class uses the
32
+ DisplayManager and PlotManager to handle the display and plotting of the
33
+ data, respectively.
34
+ """
35
+
36
+ def __init__(self, state: ImageState) -> None:
37
+ """Initialize the InteractiveManager with the given state."""
38
+ self.state = state
39
+ self.DisplayManager = DisplayManager(state)
40
+ self.ImageToolsManager = ImageToolsManager(state)
41
+
42
+ self.PlotManager = PlotManager(state)
43
+ self.anim_pcm: Collection | Line2D | None = None
44
+ self.labslider: list[str | float] | None = None
45
+ self.anim_ax: Axes | None = None
46
+ self.anim_var: dict[str, NDArray[Any]] | NDArray[Any]
47
+ self.animkeys: NDArray[Any] | None = None
48
+ self.nsld: int = 0
49
+ self.lenlab: int = 0
50
+ self.limfix: bool = True
51
+ self.slider: Slider | None = None
52
+ self.two_dim: int = 2
53
+
54
+ @track_kwargs
55
+ def interactive(
56
+ self,
57
+ varx: dict[str, NDArray[Any]] | NDArray[Any],
58
+ vary: dict[str, NDArray[Any]] | None = None,
59
+ check: bool = True,
60
+ limfix: bool = True,
61
+ labslider: list[str | float] | None = None,
62
+ **kwargs: Any,
63
+ ) -> None:
64
+ """Create an interactive plot with a slider to change the data.
65
+
66
+ Warning: it works only with the fluid variables.
67
+
68
+ Returns
69
+ -------
70
+ - None
71
+
72
+ Parameters
73
+ ----------
74
+ - varx (not optional): array_like
75
+ The x-axis variable.
76
+ - vary: array_like, default None
77
+ The y-axis variable.
78
+ - ax: Axes, default None
79
+ The axes instance.
80
+ - labslider: str, default None
81
+ The label of the slider.
82
+ - limfix: bool, default True
83
+ If True, the colorbar limits are fixed through the entire animation.
84
+ - **kwargs: Any
85
+ Other parameters to pass used in the plot or display functions.
86
+ - vmin: float, default None
87
+ The minimum value of the data.
88
+ - vmax: float, default None
89
+ The maximum value of the data.
90
+
91
+ ----
92
+
93
+ Examples
94
+ --------
95
+ - Example #1: Create an interactive 2D plot
96
+
97
+ >>> import pyPLUTO as pp
98
+ >>> D = pp.Load("all")
99
+ >>> I = pp.Image()
100
+ >>> I.interactive(
101
+ ... D.rho, x1=D.x1, x2=D.x2, cpos="right", vmin=0, vmax=1.0
102
+ ... )
103
+ >>> pp.show()
104
+
105
+ - Example #2: Create an interactive 1D plot with a composite variable
106
+
107
+ >>> import pyPLUTO as pp
108
+ >>> import numpy as np
109
+ >>> D = pp.Load("all")
110
+ >>> pp.Image().interactive(D.x1, np.sqrt(D.vx1**2 + D.vx2**2))
111
+ >>> pp.show()
112
+
113
+ """
114
+ kwargs.pop("check", check)
115
+
116
+ # Store the variable x. If vary is None, it is set to varx
117
+ if vary is None:
118
+ if isinstance(varx, dict):
119
+ self.anim_var = varx
120
+ scrh = np.asarray(list(varx.keys()))[0]
121
+ splt = np.ndim(varx[scrh])
122
+ else:
123
+ raise ValueError("varx must be a dictionary")
124
+
125
+ else:
126
+ self.anim_var = vary
127
+
128
+ # Store the variable to animate
129
+ self.animkeys = np.sort(np.asarray(list(self.anim_var.keys())))
130
+ self.nsld = len(self.animkeys)
131
+ nsld = self.nsld - 1
132
+ self.lenlab = len(str(self.animkeys[-1]))
133
+
134
+ # Check the number of dimensions
135
+ splt = np.ndim(self.anim_var[self.animkeys[0]])
136
+
137
+ # Set or create figure and axes (to test)
138
+ # Set or create figure and axes
139
+ ax, _ = self.ImageToolsManager.assign_ax(
140
+ kwargs.pop("ax", None), **kwargs, tight=False
141
+ )
142
+
143
+ if self.fig is None:
144
+ raise ValueError(
145
+ "No figure is present. Please create a figure first."
146
+ )
147
+
148
+ self.anim_ax = ax
149
+
150
+ # Position the slider
151
+ pos_slider = ax.get_position()
152
+ pos_x0 = pos_slider.x0 * (1.5 + 0.2 * (self.lenlab - 2))
153
+ pos_x1 = pos_slider.x1 * 0.95 - pos_x0
154
+
155
+ # Adjust the lower part of the position by increasing the 'y0' value
156
+ if "xtitle" in kwargs:
157
+ new_pos = (
158
+ pos_slider.x0,
159
+ pos_slider.y0 + 0.07,
160
+ pos_slider.width,
161
+ pos_slider.height - 0.07,
162
+ )
163
+
164
+ # Apply the new position
165
+ ax.set_position(new_pos)
166
+
167
+ sliderax = self.fig.add_axes((pos_x0, 0.02, pos_x1, 0.04))
168
+
169
+ # Create the slider
170
+ if labslider is not None:
171
+ self.labslider = labslider
172
+ label = labslider[0]
173
+ else:
174
+ self.labslider = None
175
+ label = f"nout = {self.animkeys[0]:0{self.lenlab}d}"
176
+ self.slider = Slider(
177
+ sliderax,
178
+ label=str(label),
179
+ valmin=0,
180
+ valmax=nsld,
181
+ valinit=0,
182
+ valstep=1,
183
+ valfmt="%d",
184
+ )
185
+ self.slider.on_changed(self.update_slider)
186
+
187
+ # Display the data
188
+ if splt == self.two_dim:
189
+ self.limfix = limfix
190
+ vmin = (
191
+ min(np.nanmin(array) for array in self.anim_var.values())
192
+ if limfix is True
193
+ else np.nanmin(self.anim_var[self.animkeys[0]])
194
+ )
195
+ vmax = (
196
+ max(np.nanmax(array) for array in self.anim_var.values())
197
+ if limfix is True
198
+ else np.nanmax(self.anim_var[self.animkeys[0]])
199
+ )
200
+ vmin = kwargs.pop("vmin", vmin)
201
+ vmax = kwargs.pop("vmax", vmax)
202
+
203
+ # Display the data if it is 2D
204
+ self.DisplayManager.display(
205
+ self.anim_var[self.animkeys[0]],
206
+ ax=ax,
207
+ vmin=vmin,
208
+ vmax=vmax,
209
+ **kwargs,
210
+ )
211
+ self.anim_pcm = ax.collections[0]
212
+ else:
213
+ var = np.array(self.anim_var[self.animkeys[0]].tolist())
214
+ if isinstance(varx, dict):
215
+ varx = np.array(range(len(var)))
216
+
217
+ # Plot the data if it is 1D
218
+ self.PlotManager.plot(
219
+ varx,
220
+ var,
221
+ ax=ax,
222
+ **kwargs,
223
+ )
224
+ self.anim_pcm = ax.get_lines()[0]
225
+
226
+ def update_slider(self, i: float) -> Iterable[Artist]:
227
+ """Update the data in the interactive plot.
228
+
229
+ Returns
230
+ -------
231
+ - None
232
+
233
+ Parameters
234
+ ----------
235
+ - i (not optional): int
236
+ The slider index.
237
+
238
+ ----
239
+
240
+ Examples
241
+ --------
242
+ - Example #1: Update the data in the interactive plot
243
+
244
+ >>> _update_slider(1)
245
+
246
+ """
247
+ # Update the data
248
+ if self.animkeys is None or self.anim_var is None:
249
+ raise ValueError(
250
+ "No data is present. Please create an interactive plot first."
251
+ )
252
+
253
+ if self.slider is None:
254
+ raise ValueError(
255
+ "No slider is present. Please create an interactive plot first."
256
+ )
257
+ idx = int(i)
258
+ var = self.anim_var[self.animkeys[idx]]
259
+ if np.ndim(var) == self.two_dim:
260
+ if not isinstance(self.anim_pcm, QuadMesh):
261
+ raise ValueError(
262
+ "The current plot is not a 2D plot. "
263
+ "Please use a 2D variable."
264
+ )
265
+ # Update the data array if it is 2D
266
+ self.anim_pcm.set_array(var.T.ravel())
267
+
268
+ # Update vmin and vmax dynamically
269
+ if self.limfix is False:
270
+ self.anim_pcm.set_clim(
271
+ self.anim_var[self.animkeys[idx]].min(),
272
+ self.anim_var[self.animkeys[idx]].max(),
273
+ )
274
+
275
+ elif np.ndim(var) == 1:
276
+ if not isinstance(self.anim_pcm, Line2D):
277
+ raise ValueError(
278
+ "The current plot is not a 1D plot. "
279
+ "Please use a 1D variable."
280
+ )
281
+ # Update the data array if it is 1D
282
+ self.anim_pcm.set_ydata(var)
283
+
284
+ if isinstance(self.labslider, list):
285
+ self.slider.label.set_text(str(self.labslider[idx]))
286
+ else:
287
+ self.slider.label.set_text(
288
+ f"nout = {self.animkeys[idx]:0{self.lenlab}d}"
289
+ )
290
+
291
+ # Update the plot
292
+ if self.fig is None:
293
+ raise ValueError(
294
+ "No figure is present. Please create a figure first."
295
+ )
296
+ self.fig.canvas.draw()
297
+
298
+ # End of the function
299
+ return ()
300
+
301
+ def update_both(self, i: float) -> Iterable[Artist]:
302
+ """Update both the plot and the slider value during animation.
303
+
304
+ Returns
305
+ -------
306
+ - None
307
+
308
+
309
+ Parameters
310
+ ----------
311
+ - i (not optional): int
312
+ The current frame index.
313
+
314
+ ----
315
+
316
+ Examples
317
+ --------
318
+ - Example #1: Update the data in the interactive plot
319
+
320
+ >>> _update_slider(1)
321
+
322
+ """
323
+ if self.slider is None:
324
+ raise ValueError(
325
+ "No slider is present. Please create an interactive plot first."
326
+ )
327
+ # Update the plot with the current frame
328
+ self.update_slider(i)
329
+
330
+ # Update the slider's position visually
331
+ self.slider.set_val(i)
332
+
333
+ # End of the function
334
+ return ()
335
+
336
+ def animate(
337
+ self,
338
+ gifname: str | None = None,
339
+ frames: int | None = None,
340
+ interval: int = 500,
341
+ updateslider: bool = True,
342
+ script_relative: bool = False,
343
+ ) -> None:
344
+ """Display the animation interactively.
345
+
346
+ Returns
347
+ -------
348
+ - None
349
+
350
+ Parameters
351
+ ----------
352
+ - frames: int, default None
353
+ The number of frames in the animation.
354
+ - gifname: str, default None
355
+ The name of the GIF file.
356
+ - interval: int, default 500
357
+ The interval between frames in milliseconds.
358
+ - updateslider: bool, default True
359
+ If True, the slider is shown and updated with each frame.
360
+
361
+ Examples
362
+ --------
363
+ - Example #1: Display the animation
364
+
365
+ >>> animate()
366
+
367
+ - Example #2: Display the animation with a specific number of frames
368
+
369
+ >>> animate(frames=[0, 1, 2], interval=300)
370
+
371
+ """
372
+ # Choose the frames
373
+ frames = self.nsld if frames is None else frames
374
+
375
+ update = self.update_both if updateslider else self.update_slider
376
+
377
+ if self.fig is None:
378
+ raise ValueError(
379
+ "No figure is present. Please create a figure first."
380
+ )
381
+
382
+ # Create the animation
383
+ ani = animation.FuncAnimation(
384
+ self.fig, update, frames=frames, interval=interval
385
+ )
386
+
387
+ if gifname is not None:
388
+ out_path = Path(gifname)
389
+
390
+ if script_relative and not out_path.is_absolute():
391
+ # Find the path of the script calling this method
392
+ caller_file = Path(inspect.stack()[1].filename).resolve()
393
+ base_dir = caller_file.parent
394
+ out_path = base_dir / out_path
395
+
396
+ # Save as GIF
397
+ ani.save(out_path)
398
+
399
+ plt.close(self.fig)
400
+
401
+ else:
402
+ # Display the animation
403
+ plt.show()
@@ -0,0 +1,250 @@
1
+ """LegendManager class."""
2
+
3
+ from typing import Any
4
+
5
+ import matplotlib.lines as mlines
6
+ from matplotlib.axes import Axes
7
+
8
+ from pyPLUTO.imagefuncs.imagetools import ImageToolsManager
9
+ from pyPLUTO.imagemixin import ImageMixin
10
+ from pyPLUTO.imagestate import ImageState
11
+ from pyPLUTO.utils.inspector import track_kwargs
12
+
13
+
14
+ class LegendManager(ImageMixin):
15
+ """LegendManager class.
16
+
17
+ It provides methods to create and manage legends
18
+ in the plots. It is designed to work with the Image class and allows for
19
+ dynamic creation of legends based on the current state of the image.
20
+ The class uses the ImageToolsManager to handle the display and plotting
21
+ of the legends, and it provides methods to customize the appearance of
22
+ the legends.
23
+ """
24
+
25
+ exposed_methods = ("legend",)
26
+
27
+ def __init__(self, state: ImageState) -> None:
28
+ """Initialize the LegendManager with the given state."""
29
+ self.state = state
30
+ self.ImageToolsManager = ImageToolsManager(state)
31
+
32
+ @track_kwargs
33
+ def legend(
34
+ self,
35
+ ax: Axes | int | None = None,
36
+ check: bool = True,
37
+ fromplot: bool = False,
38
+ **kwargs: Any,
39
+ ) -> None:
40
+ """Creation of a legend referring to the current figure.
41
+
42
+ If no labels are given, it shows the labels of all the plots in the
43
+ figure, ordered by entry. If specific labels are given, it shows those.
44
+
45
+ Returns
46
+ -------
47
+ - None
48
+
49
+ Parameters
50
+ ----------
51
+ - ax: ax | int | None, default None
52
+ The axis where to insert the legend. If None, the last considered
53
+ axis will be used.
54
+ - c: str, default self.color
55
+ Determines the line color. If not defined, the program will loop
56
+ over an array of 6 color which are different for the most common
57
+ vision deficiencies.
58
+ - edgecolor: list[str], default [None]
59
+ Sets the edge color of the legend. The default value is black ('k').
60
+ - fillstyle: {'full', 'left', 'right', 'bottom', 'top', 'none'},
61
+ default 'full'
62
+ Sets the marker filling. The default value is the fully filled
63
+ marker ('full').
64
+ - label: [str], default None
65
+ Associates a label to each line. If not specified, the program will
66
+ take the label which are already associated with the plot.
67
+ - legalpha: float, default 0.8
68
+ Sets the opacity of the legend.
69
+ - legcols: int, default 1
70
+ Sets the number of columns that the legend should have.
71
+ - legpad: float, default 0.8
72
+ Sets the space between the lines (or symbols) and the correspondibg
73
+ text in the legend.
74
+ - legpos: int | str, default 0
75
+ Selects the legend location. If not specified the standard
76
+ matplotlib legend function will find the most suitable location.
77
+ - legsize: float, default fontsize
78
+ Sets the fontsize of the legend. The default value is the default
79
+ fontsize value.
80
+ - legspace: float, default 2
81
+ Sets the space between the legend columns, in font-size units.
82
+ - ls: {'-', '--', '-.', ':', ' ', ect.}, default '-'
83
+ Sets the linestyle. The choices available are the ones defined in
84
+ the matplotlib package. Here are reported the most common ones.
85
+ - lw: float, default 1.3
86
+ Sets the linewidth of each line.
87
+ - marker: {'o', 'v', '^', '<', '>', 'X', ' ', etc.}, default ' '
88
+ Sets an optional symbol for every point. The default value is no
89
+ marker (' ').
90
+ - ms: float, default 5 (if label) or 1 (if not label)
91
+ Sets the marker size from the default value of 5.0 (if label is
92
+ given) or the marker scale from the default value of 1.0 (if not
93
+ label).
94
+ - mscale: float, default 1.0
95
+ Sets the marker scale. The default value is 1.0.
96
+
97
+ ----
98
+
99
+ Examples
100
+ --------
101
+ - Example #1: create a standard legend
102
+
103
+ >>> import pyPLUTO as pp
104
+ >>> I = pp.Image()
105
+ >>> ax = I.create_axes()
106
+ >>> I.plot(x, y, ax=ax, label="label")
107
+ >>> I.legend(ax)
108
+
109
+ - Example #2: create a legend with custom labels
110
+
111
+ >>> import pyPLUTO as pp
112
+ >>> I = pp.Image()
113
+ >>> I.plot(x, y)
114
+ >>> I.legend(label="y")
115
+
116
+ - Example #3: create a double legend for four lines in a single plot
117
+
118
+ >>> import pyPLUTO as pp
119
+ >>> I = pp.Image()
120
+ >>> I.plot(x, y1, ls="-", c="k")
121
+ >>> I.plot(x, y2, ls="-.", c="r")
122
+ >>> I.plot(x, y3, ls="-", c="r")
123
+ >>> I.plot(x, y4, ls="-.", c="k")
124
+ >>> I.legend(
125
+ ... legpos="lower left",
126
+ ... ls=["-", "-"],
127
+ ... c=["k", "r"],
128
+ ... label=["black lines", "red lines"],
129
+ ... )
130
+ >>> I.legend(
131
+ ... legpos="lower right",
132
+ ... ls=["-", "-."],
133
+ ... c=["k", "k"],
134
+ ... label=["continue", "dotted"],
135
+ ... )
136
+ >>> pp.show()
137
+
138
+ """
139
+ kwargs.pop("check", check)
140
+
141
+ # Find figure and number of the axis
142
+ ax, nax = self.ImageToolsManager.assign_ax(ax, **kwargs)
143
+ self.ImageToolsManager.hide_text(nax, ax.texts)
144
+
145
+ # Finds the legend parameters (position, columns, size, spacing and pad)
146
+ self.legpos[nax] = kwargs.get("legpos", self.legpos[nax])
147
+ self.legpar[nax][0] = kwargs.get("legsize", self.legpar[nax][0])
148
+ self.legpar[nax][1] = kwargs.get("legcols", self.legpar[nax][1])
149
+ self.legpar[nax][2] = kwargs.get("legspace", self.legpar[nax][2])
150
+ self.legpar[nax][3] = kwargs.get("legpad", self.legpar[nax][3])
151
+ self.legpar[nax][4] = kwargs.get("legalpha", self.legpar[nax][4])
152
+
153
+ # Check if another unwanted legend is present and cancel it
154
+ # (only when the legend is called from the plot function)
155
+ if fromplot is True:
156
+ lleg = ax.get_legend()
157
+ if lleg is not None:
158
+ lleg.remove()
159
+
160
+ # Check is custom labels are on and plot the legend
161
+ if kwargs.get("label") is not None:
162
+ lab = (
163
+ kwargs["label"]
164
+ if isinstance(kwargs["label"], list)
165
+ else [kwargs["label"]]
166
+ )
167
+ col = makelist(kwargs.get("c", ["k"]))
168
+ ls = makelist(kwargs.get("ls", ["-"]))
169
+ lw = makelist(kwargs.get("lw", [1.5]))
170
+ mrk = makelist(kwargs.get("marker", [""]))
171
+ ms = makelist(kwargs.get("ms", [5.0]))
172
+ fls = makelist(kwargs.get("fillstyle", ["full"]))
173
+ edgcol = makelist(kwargs.get("edgecolor", [None]))
174
+ lines = []
175
+ # Create the list of lines
176
+ for i, val in enumerate(lab):
177
+ lines.append(
178
+ mlines.Line2D(
179
+ [],
180
+ [],
181
+ label=val,
182
+ color=col[i % len(col)],
183
+ ls=ls[i % len(ls)],
184
+ lw=lw[i % len(lw)],
185
+ marker=mrk[i % len(mrk)],
186
+ ms=ms[i % len(ms)],
187
+ fillstyle=fls[i % len(fls)],
188
+ markeredgecolor=edgcol[i % len(edgcol)],
189
+ )
190
+ )
191
+ # Create the legend
192
+ legg = ax.legend(
193
+ handles=lines,
194
+ loc=self.legpos[nax],
195
+ fontsize=self.legpar[nax][0],
196
+ ncol=self.legpar[nax][1],
197
+ columnspacing=self.legpar[nax][2],
198
+ handletextpad=self.legpar[nax][3],
199
+ framealpha=self.legpar[nax][4],
200
+ )
201
+ else:
202
+ # Set the markerscale
203
+ mscale = kwargs.get("mscale", 1.0)
204
+ # Create the legend
205
+ legg = ax.legend(
206
+ loc=self.legpos[nax],
207
+ fontsize=self.legpar[nax][0],
208
+ ncol=self.legpar[nax][1],
209
+ columnspacing=self.legpar[nax][2],
210
+ handletextpad=self.legpar[nax][3],
211
+ framealpha=self.legpar[nax][4],
212
+ markerscale=mscale,
213
+ )
214
+
215
+ # Add the legend to the axis
216
+ ax.add_artist(legg)
217
+
218
+ # End of the function
219
+
220
+
221
+ def makelist[T](el: T | list[T]) -> list[T]:
222
+ """If the element is not a list, it converts it into a list.
223
+
224
+ Returns
225
+ -------
226
+ - list[Any]
227
+ The list of chosen elements.
228
+
229
+ Parameters
230
+ ----------
231
+ - el (not optional): Any
232
+ The element to be converted into a list.
233
+
234
+ ----
235
+
236
+ Examples
237
+ --------
238
+ - Example #1: element is a list
239
+
240
+ >>> makelist([1, 2, 3])
241
+ [1,2,3]
242
+
243
+ - Example #2: element is not a list
244
+
245
+ >>> makelist(1)
246
+ [1]
247
+
248
+ """
249
+ # Return the element as a list
250
+ return el if isinstance(el, list) else [el]