gsplot 0.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.
- gsplot/__init__.py +98 -0
- gsplot/base/base.py +518 -0
- gsplot/base/base_alias_validator.py +155 -0
- gsplot/color/colormap.py +218 -0
- gsplot/config/config.py +422 -0
- gsplot/data/load_file.py +188 -0
- gsplot/figure/axes.py +361 -0
- gsplot/figure/axes_base.py +952 -0
- gsplot/figure/figure_tools.py +66 -0
- gsplot/figure/show.py +205 -0
- gsplot/figure/store.py +119 -0
- gsplot/hello_world/hello_world.py +23 -0
- gsplot/logger.py +155 -0
- gsplot/path/path.py +227 -0
- gsplot/plot/line.py +328 -0
- gsplot/plot/line_base.py +272 -0
- gsplot/plot/line_colormap_base.py +120 -0
- gsplot/plot/line_colormap_dashed.py +540 -0
- gsplot/plot/line_colormap_solid.py +289 -0
- gsplot/plot/scatter.py +228 -0
- gsplot/plot/scatter_colormap.py +296 -0
- gsplot/style/graph.py +466 -0
- gsplot/style/label.py +866 -0
- gsplot/style/legend.py +469 -0
- gsplot/style/legend_colormap.py +381 -0
- gsplot/style/ticks.py +167 -0
- gsplot/version.py +2 -0
- gsplot-0.0.1.dist-info/LICENSE +21 -0
- gsplot-0.0.1.dist-info/METADATA +82 -0
- gsplot-0.0.1.dist-info/RECORD +31 -0
- gsplot-0.0.1.dist-info/WHEEL +4 -0
gsplot/style/label.py
ADDED
|
@@ -0,0 +1,866 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
from typing import Any, Callable, Literal, TypeVar, cast
|
|
3
|
+
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
import matplotlib.ticker as plticker
|
|
6
|
+
import numpy as np
|
|
7
|
+
from matplotlib.axes import Axes
|
|
8
|
+
from matplotlib.backends.backend_agg import FigureCanvasAgg
|
|
9
|
+
from matplotlib.figure import Figure
|
|
10
|
+
from matplotlib.transforms import Bbox
|
|
11
|
+
from numpy.typing import ArrayLike, NDArray
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.text import Text
|
|
15
|
+
|
|
16
|
+
from ..base.base import CreateClassParams, ParamsGetter, bind_passed_params
|
|
17
|
+
from ..figure.axes_base import (AxesRangeSingleton, AxisRangeController,
|
|
18
|
+
AxisRangeManager)
|
|
19
|
+
from .ticks import MinorTicksAxes
|
|
20
|
+
|
|
21
|
+
__all__ = ["label", "label_add_index"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
console = Console()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# !TODO: When label is removed from script with index, and the script is called to repl, it will throw warning.
|
|
31
|
+
# fix this behavior.
|
|
32
|
+
class FuncOrderManager:
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
) -> None:
|
|
36
|
+
self.last_called: str | None = None
|
|
37
|
+
self.rules: dict = {}
|
|
38
|
+
|
|
39
|
+
def add_rule(self, func_a: str, func_b: str, warning_message: str) -> None:
|
|
40
|
+
self.rules[(func_b, func_a)] = warning_message
|
|
41
|
+
|
|
42
|
+
def track(self, func_name: str) -> None:
|
|
43
|
+
if self.last_called is not None:
|
|
44
|
+
if (self.last_called, func_name) in self.rules:
|
|
45
|
+
warning_message = self.rules[(self.last_called, func_name)]
|
|
46
|
+
warning_text = Text.from_markup(warning_message, justify="center")
|
|
47
|
+
console.print(
|
|
48
|
+
Panel(
|
|
49
|
+
warning_text,
|
|
50
|
+
title="[bold yellow]Warning",
|
|
51
|
+
style="bold yellow",
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
self.reset()
|
|
56
|
+
else:
|
|
57
|
+
self.last_called = func_name
|
|
58
|
+
|
|
59
|
+
def reset(self) -> None:
|
|
60
|
+
self.last_called = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
order_manager = FuncOrderManager()
|
|
64
|
+
order_manager.add_rule(
|
|
65
|
+
"label",
|
|
66
|
+
"label_add_index",
|
|
67
|
+
"[bold green]label_add_index[bold yellow] should be called after [bold red]label",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def track_order(func: F) -> F:
|
|
72
|
+
|
|
73
|
+
@wraps(func)
|
|
74
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
75
|
+
func_name = func.__name__
|
|
76
|
+
order_manager.track(func_name)
|
|
77
|
+
return func(*args, **kwargs)
|
|
78
|
+
|
|
79
|
+
return cast(F, wrapper)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class LabelAddIndex:
|
|
83
|
+
"""
|
|
84
|
+
A class to add index labels to axes in a Matplotlib figure.
|
|
85
|
+
|
|
86
|
+
This class allows labeling axes with indices in customizable positions, glyphs,
|
|
87
|
+
and styles.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
--------------------
|
|
91
|
+
loc : {'in', 'out', 'corner'}, default='out'
|
|
92
|
+
Location of the label relative to the axes:
|
|
93
|
+
- 'in': Inside the axes.
|
|
94
|
+
- 'out': Outside the axes.
|
|
95
|
+
- 'corner': Top-left corner of the axes.
|
|
96
|
+
x_offset : float, default=0
|
|
97
|
+
Horizontal offset for the label position.
|
|
98
|
+
y_offset : float, default=0
|
|
99
|
+
Vertical offset for the label position.
|
|
100
|
+
ha : str, default='center'
|
|
101
|
+
Horizontal alignment of the label.
|
|
102
|
+
va : str, default='top'
|
|
103
|
+
Vertical alignment of the label.
|
|
104
|
+
fontsize : str or float, default='large'
|
|
105
|
+
Font size of the label.
|
|
106
|
+
glyph : {'alphabet', 'roman', 'number', 'hiragana'}, default='alphabet'
|
|
107
|
+
Style of the label:
|
|
108
|
+
- 'alphabet': Letters (a, b, c, ...).
|
|
109
|
+
- 'roman': Roman numerals (i, ii, iii, ...).
|
|
110
|
+
- 'number': Numbers (1, 2, 3, ...).
|
|
111
|
+
- 'hiragana': Japanese Hiragana (あ, い, う, ...).
|
|
112
|
+
capitalize : bool, default=False
|
|
113
|
+
If True, capitalize the label (e.g., A, B, C instead of a, b, c).
|
|
114
|
+
*args : Any
|
|
115
|
+
Additional arguments for `matplotlib.text.Text`.
|
|
116
|
+
**kwargs : Any
|
|
117
|
+
Additional keyword arguments for `matplotlib.text.Text`.
|
|
118
|
+
|
|
119
|
+
Attributes
|
|
120
|
+
--------------------
|
|
121
|
+
fig : matplotlib.figure.Figure
|
|
122
|
+
The current figure object.
|
|
123
|
+
_axes : list[matplotlib.axes.Axes]
|
|
124
|
+
List of axes in the current figure.
|
|
125
|
+
renderer : matplotlib.backends.backend_agg.RendererAgg
|
|
126
|
+
Renderer for the figure.
|
|
127
|
+
fig_width : float
|
|
128
|
+
Width of the figure in pixels.
|
|
129
|
+
fig_height : float
|
|
130
|
+
Height of the figure in pixels.
|
|
131
|
+
canvas_width : int
|
|
132
|
+
Width of the canvas in pixels.
|
|
133
|
+
canvas_height : int
|
|
134
|
+
Height of the canvas in pixels.
|
|
135
|
+
normalization_factors : numpy.ndarray
|
|
136
|
+
Factors for normalizing coordinates.
|
|
137
|
+
|
|
138
|
+
Methods
|
|
139
|
+
--------------------
|
|
140
|
+
add_index() -> None
|
|
141
|
+
Adds index labels to the axes.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
def __init__(
|
|
145
|
+
self,
|
|
146
|
+
loc: Literal["in", "out", "corner"] = "out",
|
|
147
|
+
x_offset: float = 0,
|
|
148
|
+
y_offset: float = 0,
|
|
149
|
+
ha: str = "center",
|
|
150
|
+
va: str = "top",
|
|
151
|
+
fontsize: str | float = "large",
|
|
152
|
+
glyph: Literal["alphabet", "roman", "number", "hiragana"] = "alphabet",
|
|
153
|
+
capitalize: bool = False,
|
|
154
|
+
*args: Any,
|
|
155
|
+
**kwargs: Any,
|
|
156
|
+
) -> None:
|
|
157
|
+
self.loc = loc
|
|
158
|
+
self.x_offset = x_offset
|
|
159
|
+
self.y_offset = y_offset
|
|
160
|
+
self.ha = ha
|
|
161
|
+
self.va = va
|
|
162
|
+
self.fontsize = fontsize
|
|
163
|
+
self.glyph = glyph
|
|
164
|
+
self.capitalize = capitalize
|
|
165
|
+
|
|
166
|
+
self.args = args
|
|
167
|
+
self.kwargs = kwargs
|
|
168
|
+
|
|
169
|
+
self.fig: Figure = plt.gcf()
|
|
170
|
+
self._axes: list[Axes] = plt.gcf().axes
|
|
171
|
+
|
|
172
|
+
self.renderer = cast(FigureCanvasAgg, self.fig.canvas).get_renderer()
|
|
173
|
+
|
|
174
|
+
self.fig_width, self.fig_height = (
|
|
175
|
+
self.fig.bbox.bounds[2],
|
|
176
|
+
self.fig.bbox.bounds[3],
|
|
177
|
+
)
|
|
178
|
+
self.canvas_width, self.canvas_height = self.fig.canvas.get_width_height()
|
|
179
|
+
|
|
180
|
+
# To convert from device coordinates to figure coordinates
|
|
181
|
+
self.normalization_factors = np.array(
|
|
182
|
+
[self.fig_width, self.fig_height, self.fig_width, self.fig_height]
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def _get_render_position(self, axis: Axes) -> tuple[float, float] | None:
|
|
186
|
+
"""
|
|
187
|
+
Calculates the position for the index label based on the location.
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
--------------------
|
|
191
|
+
axis : matplotlib.axes.Axes
|
|
192
|
+
The axis for which to calculate the label position.
|
|
193
|
+
|
|
194
|
+
Returns
|
|
195
|
+
--------------------
|
|
196
|
+
tuple[float, float] or None
|
|
197
|
+
The (x, y) coordinates of the label in figure-relative coordinates,
|
|
198
|
+
or None if the bounding box is unavailable.
|
|
199
|
+
|
|
200
|
+
Raises
|
|
201
|
+
--------------------
|
|
202
|
+
ValueError
|
|
203
|
+
If the specified location (`loc`) is invalid.
|
|
204
|
+
"""
|
|
205
|
+
bbox: Bbox | None = None
|
|
206
|
+
PADDING_X: float = 0
|
|
207
|
+
PADDING_Y: float = 0
|
|
208
|
+
|
|
209
|
+
if self.loc == "out":
|
|
210
|
+
# Coordinate: device coordinates
|
|
211
|
+
bbox = axis.get_tightbbox(self.renderer)
|
|
212
|
+
PADDING_X, PADDING_Y = 0, -5
|
|
213
|
+
elif self.loc == "in":
|
|
214
|
+
# Coordinate: device coordinates
|
|
215
|
+
bbox = axis.get_window_extent(self.renderer)
|
|
216
|
+
PADDING_X, PADDING_Y = 30, -30
|
|
217
|
+
elif self.loc == "corner":
|
|
218
|
+
bbox = axis.get_window_extent(self.renderer)
|
|
219
|
+
else:
|
|
220
|
+
raise ValueError(
|
|
221
|
+
f"Invalid position: {self.loc}, must be 'in', 'out', or 'corner'"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Ensure that padding does not depend on the figure size
|
|
225
|
+
PADDING_X = PADDING_X / self.canvas_width
|
|
226
|
+
PADDING_Y = PADDING_Y / self.canvas_height
|
|
227
|
+
|
|
228
|
+
if bbox is None:
|
|
229
|
+
print(f"No bounding box available for the axis. axis: {axis}")
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
# Calculate the axis bounds in figure coordinates
|
|
233
|
+
axis_bounds_on_fig = np.array(bbox.bounds) / self.normalization_factors
|
|
234
|
+
|
|
235
|
+
x0 = axis_bounds_on_fig[0]
|
|
236
|
+
y0 = axis_bounds_on_fig[1]
|
|
237
|
+
width = axis_bounds_on_fig[2]
|
|
238
|
+
height = axis_bounds_on_fig[3]
|
|
239
|
+
|
|
240
|
+
x = x0 + self.x_offset + PADDING_X
|
|
241
|
+
y = y0 + height + self.y_offset + PADDING_Y
|
|
242
|
+
return x, y
|
|
243
|
+
|
|
244
|
+
@staticmethod
|
|
245
|
+
def int_to_roman(n: int) -> str:
|
|
246
|
+
"""
|
|
247
|
+
Converts an integer to its Roman numeral representation.
|
|
248
|
+
|
|
249
|
+
Parameters
|
|
250
|
+
--------------------
|
|
251
|
+
n : int
|
|
252
|
+
The integer to convert.
|
|
253
|
+
|
|
254
|
+
Returns
|
|
255
|
+
--------------------
|
|
256
|
+
str
|
|
257
|
+
The Roman numeral representation.
|
|
258
|
+
|
|
259
|
+
Examples
|
|
260
|
+
--------------------
|
|
261
|
+
>>> LabelAddIndex.int_to_roman(3)
|
|
262
|
+
'iii'
|
|
263
|
+
"""
|
|
264
|
+
roman_numerals = {
|
|
265
|
+
1: "i",
|
|
266
|
+
2: "ii",
|
|
267
|
+
3: "iii",
|
|
268
|
+
4: "iv",
|
|
269
|
+
5: "v",
|
|
270
|
+
6: "vi",
|
|
271
|
+
7: "vii",
|
|
272
|
+
8: "viii",
|
|
273
|
+
9: "ix",
|
|
274
|
+
10: "x",
|
|
275
|
+
11: "xi",
|
|
276
|
+
12: "xii",
|
|
277
|
+
13: "xiii",
|
|
278
|
+
14: "xiv",
|
|
279
|
+
15: "xv",
|
|
280
|
+
16: "xvi",
|
|
281
|
+
17: "xvii",
|
|
282
|
+
18: "xviii",
|
|
283
|
+
}
|
|
284
|
+
return roman_numerals.get(n, "")
|
|
285
|
+
|
|
286
|
+
def get_index_glyph(self, n: int) -> str:
|
|
287
|
+
"""
|
|
288
|
+
Retrieves the glyph representation of the index.
|
|
289
|
+
|
|
290
|
+
Parameters
|
|
291
|
+
--------------------
|
|
292
|
+
n : int
|
|
293
|
+
The index of the current axis (0-based).
|
|
294
|
+
|
|
295
|
+
Returns
|
|
296
|
+
--------------------
|
|
297
|
+
str
|
|
298
|
+
The glyph for the index.
|
|
299
|
+
|
|
300
|
+
Raises
|
|
301
|
+
--------------------
|
|
302
|
+
ValueError
|
|
303
|
+
If the specified glyph style (`glyph`) is invalid.
|
|
304
|
+
|
|
305
|
+
Examples
|
|
306
|
+
--------------------
|
|
307
|
+
>>> LabelAddIndex(glyph='alphabet').get_index_glyph(0)
|
|
308
|
+
'a'
|
|
309
|
+
>>> LabelAddIndex(glyph='number').get_index_glyph(2)
|
|
310
|
+
'3'
|
|
311
|
+
"""
|
|
312
|
+
index_glyph: str = ""
|
|
313
|
+
if self.glyph == "alphabet":
|
|
314
|
+
index_glyph = "abcdefghijklmnopqrstuvwxyz"[n]
|
|
315
|
+
elif self.glyph == "roman":
|
|
316
|
+
index_glyph = self.int_to_roman(n + 1)
|
|
317
|
+
elif self.glyph == "number":
|
|
318
|
+
index_glyph = str(n + 1)
|
|
319
|
+
elif self.glyph == "hiragana":
|
|
320
|
+
index_glyph = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよん"[
|
|
321
|
+
n
|
|
322
|
+
]
|
|
323
|
+
else:
|
|
324
|
+
raise ValueError(
|
|
325
|
+
f"Invalid glyph: {self.glyph}, must be 'alphabet', 'roman', 'number', or 'hiragana'"
|
|
326
|
+
)
|
|
327
|
+
if self.capitalize:
|
|
328
|
+
index_glyph = index_glyph.upper()
|
|
329
|
+
return index_glyph
|
|
330
|
+
|
|
331
|
+
def add_index(self) -> None:
|
|
332
|
+
"""
|
|
333
|
+
Adds index labels to all axes in the current figure.
|
|
334
|
+
|
|
335
|
+
The labels are positioned according to the specified location (`loc`),
|
|
336
|
+
offsets, and alignment.
|
|
337
|
+
|
|
338
|
+
Returns
|
|
339
|
+
--------------------
|
|
340
|
+
None
|
|
341
|
+
"""
|
|
342
|
+
for i, axis in enumerate(self._axes):
|
|
343
|
+
position = self._get_render_position(axis)
|
|
344
|
+
if position is None:
|
|
345
|
+
continue
|
|
346
|
+
x, y = position
|
|
347
|
+
|
|
348
|
+
glyph = self.get_index_glyph(i)
|
|
349
|
+
self.fig.text(
|
|
350
|
+
x,
|
|
351
|
+
y,
|
|
352
|
+
f"($\\,${glyph}$\\,$)",
|
|
353
|
+
ha=self.ha,
|
|
354
|
+
va=self.va,
|
|
355
|
+
fontsize=self.fontsize,
|
|
356
|
+
transform=self.fig.transFigure,
|
|
357
|
+
*self.args,
|
|
358
|
+
**self.kwargs,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@bind_passed_params()
|
|
363
|
+
@track_order
|
|
364
|
+
def label_add_index(
|
|
365
|
+
loc: Literal["in", "out", "corner"] = "out",
|
|
366
|
+
x_offset: float = 0,
|
|
367
|
+
y_offset: float = 0,
|
|
368
|
+
ha: str = "center",
|
|
369
|
+
va: str = "center",
|
|
370
|
+
fontsize: float | str = "large",
|
|
371
|
+
glyph: Literal["alphabet", "roman", "number", "hiragana"] = "alphabet",
|
|
372
|
+
capitalize: bool = False,
|
|
373
|
+
*args: Any,
|
|
374
|
+
**kwargs: Any,
|
|
375
|
+
) -> None:
|
|
376
|
+
"""
|
|
377
|
+
Adds index labels to axes in a Matplotlib figure.
|
|
378
|
+
|
|
379
|
+
This function is a wrapper for the `LabelAddIndex` class.
|
|
380
|
+
|
|
381
|
+
Parameters
|
|
382
|
+
--------------------
|
|
383
|
+
loc : {'in', 'out', 'corner'}, default='out'
|
|
384
|
+
Location of the label relative to the axes.
|
|
385
|
+
x_offset : float, default=0
|
|
386
|
+
Horizontal offset for the label position.
|
|
387
|
+
y_offset : float, default=0
|
|
388
|
+
Vertical offset for the label position.
|
|
389
|
+
ha : str, default='center'
|
|
390
|
+
Horizontal alignment of the label.
|
|
391
|
+
va : str, default='center'
|
|
392
|
+
Vertical alignment of the label.
|
|
393
|
+
fontsize : str or float, default='large'
|
|
394
|
+
Font size of the label.
|
|
395
|
+
glyph : {'alphabet', 'roman', 'number', 'hiragana'}, default='alphabet'
|
|
396
|
+
Style of the label.
|
|
397
|
+
capitalize : bool, default=False
|
|
398
|
+
If True, capitalize the label.
|
|
399
|
+
*args : Any
|
|
400
|
+
Additional arguments for `matplotlib.text.Text`.
|
|
401
|
+
**kwargs : Any
|
|
402
|
+
Additional keyword arguments for `matplotlib.text.Text`.
|
|
403
|
+
|
|
404
|
+
Notes
|
|
405
|
+
--------------------
|
|
406
|
+
This function utilizes the `ParamsGetter` to retrieve bound parameters
|
|
407
|
+
and the `CreateClassParams` class to handle the merging of default,
|
|
408
|
+
configuration, and passed parameters.
|
|
409
|
+
|
|
410
|
+
Returns
|
|
411
|
+
--------------------
|
|
412
|
+
None
|
|
413
|
+
|
|
414
|
+
Warnings
|
|
415
|
+
--------------------
|
|
416
|
+
This function should be called after the :func:`gsplot.label <gsplot.style.label.label>` function
|
|
417
|
+
|
|
418
|
+
Examples
|
|
419
|
+
--------------------
|
|
420
|
+
>>> import gsplot as gs
|
|
421
|
+
>>> gs.label_add_index(loc='out', glyph='roman', fontsize=12)
|
|
422
|
+
>>> gs.label_add_index(loc='corner', capitalize=True)
|
|
423
|
+
"""
|
|
424
|
+
passed_params: dict[str, Any] = ParamsGetter("passed_params").get_bound_params()
|
|
425
|
+
class_params = CreateClassParams(passed_params).get_class_params()
|
|
426
|
+
|
|
427
|
+
_label_add_index: LabelAddIndex = LabelAddIndex(
|
|
428
|
+
class_params["loc"],
|
|
429
|
+
class_params["x_offset"],
|
|
430
|
+
class_params["y_offset"],
|
|
431
|
+
class_params["ha"],
|
|
432
|
+
class_params["va"],
|
|
433
|
+
class_params["fontsize"],
|
|
434
|
+
class_params["glyph"],
|
|
435
|
+
class_params["capitalize"],
|
|
436
|
+
*class_params["args"],
|
|
437
|
+
**class_params["kwargs"],
|
|
438
|
+
)
|
|
439
|
+
_label_add_index.add_index()
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class Label:
|
|
443
|
+
"""
|
|
444
|
+
A class to configure labels, limits, and ticks for Matplotlib axes.
|
|
445
|
+
|
|
446
|
+
This class facilitates the customization of axis labels, limits, tick marks,
|
|
447
|
+
and layouts for multiple axes in a Matplotlib figure.
|
|
448
|
+
|
|
449
|
+
Parameters
|
|
450
|
+
--------------------
|
|
451
|
+
lab_lims : list[Any]
|
|
452
|
+
A list specifying labels and limits for each axis in the figure. Each entry
|
|
453
|
+
should be a tuple of the form `(x_label, y_label, x_limits, y_limits)`.
|
|
454
|
+
x_pad : int, default=2
|
|
455
|
+
Horizontal padding for tight layout.
|
|
456
|
+
y_pad : int, default=2
|
|
457
|
+
Vertical padding for tight layout.
|
|
458
|
+
minor_ticks_axes : bool, default=True
|
|
459
|
+
Whether to add minor ticks to all axes.
|
|
460
|
+
tight_layout : bool, default=True
|
|
461
|
+
Whether to apply `tight_layout` to the figure.
|
|
462
|
+
*args : Any
|
|
463
|
+
Additional arguments for `plt.tight_layout`.
|
|
464
|
+
**kwargs : Any
|
|
465
|
+
Additional keyword arguments for `plt.tight_layout`.
|
|
466
|
+
|
|
467
|
+
Attributes
|
|
468
|
+
--------------------
|
|
469
|
+
lab_lims : list[Any]
|
|
470
|
+
The labels and limits configuration for the axes.
|
|
471
|
+
x_pad : int
|
|
472
|
+
Horizontal padding for tight layout.
|
|
473
|
+
y_pad : int
|
|
474
|
+
Vertical padding for tight layout.
|
|
475
|
+
minor_ticks_axes : bool
|
|
476
|
+
Whether minor ticks are enabled for axes.
|
|
477
|
+
tight_layout : bool
|
|
478
|
+
Whether `tight_layout` is applied.
|
|
479
|
+
_axes : list[Axes]
|
|
480
|
+
List of axes in the current figure.
|
|
481
|
+
|
|
482
|
+
Methods
|
|
483
|
+
--------------------
|
|
484
|
+
set_xticks(axis, base=None, num_minor=None) -> None
|
|
485
|
+
Configures the major and minor ticks for the x-axis.
|
|
486
|
+
set_yticks(axis, base=None, num_minor=None) -> None
|
|
487
|
+
Configures the major and minor ticks for the y-axis.
|
|
488
|
+
remove_xlabels(axis) -> None
|
|
489
|
+
Removes the x-axis labels for a given axis.
|
|
490
|
+
remove_ylabels(axis) -> None
|
|
491
|
+
Removes the y-axis labels for a given axis.
|
|
492
|
+
add_minor_ticks_axes() -> None
|
|
493
|
+
Adds minor ticks to all axes in the figure.
|
|
494
|
+
configure_axis_labels(axis, x_lab, y_lab) -> None
|
|
495
|
+
Configures the labels for a given axis.
|
|
496
|
+
configure_axis_limits(axis, lims, final_axes_range=None, index=None) -> None
|
|
497
|
+
Configures the limits and scales for a given axis.
|
|
498
|
+
set_labels() -> None
|
|
499
|
+
Applies labels, limits, and scales to all axes based on the configuration.
|
|
500
|
+
apply_tight_layout() -> None
|
|
501
|
+
Applies `tight_layout` to the figure.
|
|
502
|
+
label() -> None
|
|
503
|
+
Adds labels, limits, and layouts to the figure's axes.
|
|
504
|
+
"""
|
|
505
|
+
|
|
506
|
+
def __init__(
|
|
507
|
+
self,
|
|
508
|
+
lab_lims: list[Any],
|
|
509
|
+
x_pad: int = 2,
|
|
510
|
+
y_pad: int = 2,
|
|
511
|
+
minor_ticks_axes: bool = True,
|
|
512
|
+
tight_layout: bool = True,
|
|
513
|
+
*args: Any,
|
|
514
|
+
**kwargs: Any,
|
|
515
|
+
) -> None:
|
|
516
|
+
|
|
517
|
+
self.lab_lims: list[Any] = lab_lims
|
|
518
|
+
self.x_pad: int = x_pad
|
|
519
|
+
self.y_pad: int = y_pad
|
|
520
|
+
self.minor_ticks_axes: bool = minor_ticks_axes
|
|
521
|
+
self.tight_layout: bool = tight_layout
|
|
522
|
+
self.args: Any = args
|
|
523
|
+
self.kwargs: Any = kwargs
|
|
524
|
+
|
|
525
|
+
self._axes: list[Axes] = plt.gcf().axes
|
|
526
|
+
|
|
527
|
+
def set_xticks(
|
|
528
|
+
self, axis: Axes, base: float | None = None, num_minor: int | None = None
|
|
529
|
+
) -> None:
|
|
530
|
+
"""
|
|
531
|
+
Configures the major and minor ticks for the x-axis.
|
|
532
|
+
|
|
533
|
+
Parameters
|
|
534
|
+
--------------------
|
|
535
|
+
axis : matplotlib.axes.Axes
|
|
536
|
+
The axis for which to configure ticks.
|
|
537
|
+
base : float, optional
|
|
538
|
+
Interval for the major ticks.
|
|
539
|
+
num_minor : int, optional
|
|
540
|
+
Number of minor ticks between consecutive major ticks.
|
|
541
|
+
|
|
542
|
+
Returns
|
|
543
|
+
--------------------
|
|
544
|
+
None
|
|
545
|
+
"""
|
|
546
|
+
|
|
547
|
+
if base is not None:
|
|
548
|
+
axis.xaxis.set_major_locator(plticker.MultipleLocator(base=base))
|
|
549
|
+
if num_minor is not None:
|
|
550
|
+
axis.xaxis.set_minor_locator(plticker.AutoMinorLocator(num_minor))
|
|
551
|
+
|
|
552
|
+
def set_yticks(
|
|
553
|
+
self, axis: Axes, base: float | None = None, num_minor: int | None = None
|
|
554
|
+
) -> None:
|
|
555
|
+
"""
|
|
556
|
+
Configures the major and minor ticks for the y-axis.
|
|
557
|
+
|
|
558
|
+
Parameters
|
|
559
|
+
--------------------
|
|
560
|
+
axis : matplotlib.axes.Axes
|
|
561
|
+
The axis for which to configure ticks.
|
|
562
|
+
base : float, optional
|
|
563
|
+
Interval for the major ticks.
|
|
564
|
+
num_minor : int, optional
|
|
565
|
+
Number of minor ticks between consecutive major ticks.
|
|
566
|
+
|
|
567
|
+
Returns
|
|
568
|
+
--------------------
|
|
569
|
+
None
|
|
570
|
+
"""
|
|
571
|
+
|
|
572
|
+
if base is not None:
|
|
573
|
+
axis.yaxis.set_major_locator(plticker.MultipleLocator(base=base))
|
|
574
|
+
if num_minor is not None:
|
|
575
|
+
axis.yaxis.set_minor_locator(plticker.AutoMinorLocator(num_minor))
|
|
576
|
+
|
|
577
|
+
def remove_xlabels(self, axis: Axes) -> None:
|
|
578
|
+
"""
|
|
579
|
+
Removes the x-axis label for a given axis.
|
|
580
|
+
|
|
581
|
+
Parameters
|
|
582
|
+
--------------------
|
|
583
|
+
axis : matplotlib.axes.Axes
|
|
584
|
+
The axis for which to remove the x-axis label.
|
|
585
|
+
|
|
586
|
+
Returns
|
|
587
|
+
--------------------
|
|
588
|
+
None
|
|
589
|
+
"""
|
|
590
|
+
|
|
591
|
+
axis.set_xlabel("")
|
|
592
|
+
axis.tick_params(labelbottom=False)
|
|
593
|
+
|
|
594
|
+
def remove_ylabels(self, axis: Axes) -> None:
|
|
595
|
+
"""
|
|
596
|
+
Removes the y-axis label for a given axis.
|
|
597
|
+
|
|
598
|
+
Parameters
|
|
599
|
+
--------------------
|
|
600
|
+
axis : matplotlib.axes.Axes
|
|
601
|
+
The axis for which to remove the y-axis label.
|
|
602
|
+
|
|
603
|
+
Returns
|
|
604
|
+
--------------------
|
|
605
|
+
None
|
|
606
|
+
"""
|
|
607
|
+
|
|
608
|
+
axis.set_ylabel("")
|
|
609
|
+
axis.tick_params(labelleft=False)
|
|
610
|
+
|
|
611
|
+
def add_minor_ticks_axes(self) -> None:
|
|
612
|
+
"""
|
|
613
|
+
Adds minor ticks to all axes in the figure.
|
|
614
|
+
|
|
615
|
+
Returns
|
|
616
|
+
--------------------
|
|
617
|
+
None
|
|
618
|
+
"""
|
|
619
|
+
|
|
620
|
+
if self.minor_ticks_axes:
|
|
621
|
+
MinorTicksAxes().set_minor_ticks_axes()
|
|
622
|
+
|
|
623
|
+
def configure_axis_labels(self, axis, x_lab, y_lab):
|
|
624
|
+
"""
|
|
625
|
+
Configures the labels for a given axis.
|
|
626
|
+
|
|
627
|
+
Parameters
|
|
628
|
+
--------------------
|
|
629
|
+
axis : matplotlib.axes.Axes
|
|
630
|
+
The axis to configure labels for.
|
|
631
|
+
x_lab : str, optional
|
|
632
|
+
The label for the x-axis. If `None`, the x-axis label is removed.
|
|
633
|
+
y_lab : str, optional
|
|
634
|
+
The label for the y-axis. If `None`, the y-axis label is removed.
|
|
635
|
+
|
|
636
|
+
Returns
|
|
637
|
+
--------------------
|
|
638
|
+
None
|
|
639
|
+
"""
|
|
640
|
+
if x_lab:
|
|
641
|
+
axis.set_xlabel(x_lab)
|
|
642
|
+
else:
|
|
643
|
+
self.remove_xlabels(axis)
|
|
644
|
+
|
|
645
|
+
if y_lab:
|
|
646
|
+
axis.set_ylabel(y_lab)
|
|
647
|
+
else:
|
|
648
|
+
self.remove_ylabels(axis)
|
|
649
|
+
|
|
650
|
+
def configure_axis_limits(self, axis, lims, final_axes_range=None, index=None):
|
|
651
|
+
"""
|
|
652
|
+
Configures the limits and scales for a given axis.
|
|
653
|
+
|
|
654
|
+
Parameters
|
|
655
|
+
--------------------
|
|
656
|
+
axis : matplotlib.axes.Axes
|
|
657
|
+
The axis to configure limits for.
|
|
658
|
+
lims : list, optional
|
|
659
|
+
The limits for the axis in the form `[x_lims, y_lims]`.
|
|
660
|
+
final_axes_range : list, optional
|
|
661
|
+
The final axes ranges for all axes.
|
|
662
|
+
index : int, optional
|
|
663
|
+
The index of the current axis.
|
|
664
|
+
|
|
665
|
+
Returns
|
|
666
|
+
--------------------
|
|
667
|
+
None
|
|
668
|
+
"""
|
|
669
|
+
if lims:
|
|
670
|
+
x_lims, y_lims = lims
|
|
671
|
+
|
|
672
|
+
# Set axis limits
|
|
673
|
+
for lim, val in zip(
|
|
674
|
+
["xmin", "xmax", "ymin", "ymax"],
|
|
675
|
+
[x_lims[0], x_lims[1], y_lims[0], y_lims[1]],
|
|
676
|
+
):
|
|
677
|
+
if val != "":
|
|
678
|
+
axis.axis(**{lim: val})
|
|
679
|
+
|
|
680
|
+
# Configure x-axis scale or ticks
|
|
681
|
+
if len(x_lims) > 2:
|
|
682
|
+
if isinstance(x_lims[2], str):
|
|
683
|
+
axis.set_xscale(x_lims[2])
|
|
684
|
+
else:
|
|
685
|
+
self.set_xticks(axis, num_minor=x_lims[2])
|
|
686
|
+
if len(x_lims) > 3:
|
|
687
|
+
self.set_xticks(axis, base=x_lims[3])
|
|
688
|
+
|
|
689
|
+
# Configure y-axis scale or ticks
|
|
690
|
+
if len(y_lims) > 2:
|
|
691
|
+
if isinstance(y_lims[2], str):
|
|
692
|
+
axis.set_yscale(y_lims[2])
|
|
693
|
+
else:
|
|
694
|
+
self.set_yticks(axis, num_minor=y_lims[2])
|
|
695
|
+
if len(y_lims) > 3:
|
|
696
|
+
self.set_yticks(axis, base=y_lims[3])
|
|
697
|
+
elif final_axes_range and index is not None:
|
|
698
|
+
final_axis_range = final_axes_range[index]
|
|
699
|
+
axis.axis(
|
|
700
|
+
xmin=final_axis_range[0][0],
|
|
701
|
+
xmax=final_axis_range[0][1],
|
|
702
|
+
ymin=final_axis_range[1][0],
|
|
703
|
+
ymax=final_axis_range[1][1],
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
def set_labels(self):
|
|
707
|
+
|
|
708
|
+
final_axes_range = self._get_final_axes_range()
|
|
709
|
+
|
|
710
|
+
for i, (x_lab, y_lab, *lims) in enumerate(self.lab_lims):
|
|
711
|
+
axis = self._axes[i]
|
|
712
|
+
|
|
713
|
+
# Configure axis labels
|
|
714
|
+
self.configure_axis_labels(axis, x_lab, y_lab)
|
|
715
|
+
|
|
716
|
+
# Configure axis limits and scales
|
|
717
|
+
self.configure_axis_limits(axis, lims, final_axes_range, i)
|
|
718
|
+
|
|
719
|
+
self._get_final_axes_range()
|
|
720
|
+
|
|
721
|
+
def _calculate_padding_range(self, range: NDArray[Any]) -> NDArray[Any]:
|
|
722
|
+
|
|
723
|
+
PADDING_FACTOR: float = 0.05
|
|
724
|
+
span: float = range[1] - range[0]
|
|
725
|
+
return np.array(
|
|
726
|
+
range + np.array([-PADDING_FACTOR, PADDING_FACTOR]) * span, dtype=np.float64
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
def _get_wider_range(
|
|
730
|
+
self, range1: NDArray[Any], range2: NDArray[Any]
|
|
731
|
+
) -> NDArray[Any]:
|
|
732
|
+
|
|
733
|
+
new_range = np.array([min(range1[0], range2[0]), max(range1[1], range2[1])])
|
|
734
|
+
return new_range
|
|
735
|
+
|
|
736
|
+
def _get_axes_ranges_current(self) -> list[list[NDArray[Any]]]:
|
|
737
|
+
|
|
738
|
+
axes_ranges_current = []
|
|
739
|
+
for axis_index in range(len(self._axes)):
|
|
740
|
+
xrange = AxisRangeController(axis_index).get_axis_xrange()
|
|
741
|
+
yrange = AxisRangeController(axis_index).get_axis_yrange()
|
|
742
|
+
axes_ranges_current.append([xrange, yrange])
|
|
743
|
+
return axes_ranges_current
|
|
744
|
+
|
|
745
|
+
def _get_final_axes_range(self) -> list[list[NDArray[Any]]]:
|
|
746
|
+
|
|
747
|
+
axes_ranges_singleton = AxesRangeSingleton().axes_ranges
|
|
748
|
+
axes_ranges_current = self._get_axes_ranges_current()
|
|
749
|
+
|
|
750
|
+
final_axes_ranges = []
|
|
751
|
+
for axis_index, (xrange, yrange) in enumerate(axes_ranges_current):
|
|
752
|
+
xrange_singleton = axes_ranges_singleton[axis_index][0]
|
|
753
|
+
yrange_singleton = axes_ranges_singleton[axis_index][1]
|
|
754
|
+
|
|
755
|
+
is_init_axis = AxisRangeManager(axis_index).is_init_axis()
|
|
756
|
+
|
|
757
|
+
if is_init_axis and xrange_singleton is not None:
|
|
758
|
+
new_xrange = xrange_singleton
|
|
759
|
+
elif not is_init_axis and xrange_singleton is not None:
|
|
760
|
+
new_xrange = self._get_wider_range(xrange, xrange_singleton)
|
|
761
|
+
else:
|
|
762
|
+
new_xrange = xrange
|
|
763
|
+
|
|
764
|
+
if is_init_axis and yrange_singleton is not None:
|
|
765
|
+
new_yrange = yrange_singleton
|
|
766
|
+
elif not is_init_axis and yrange_singleton is not None:
|
|
767
|
+
new_yrange = self._get_wider_range(yrange, yrange_singleton)
|
|
768
|
+
else:
|
|
769
|
+
new_yrange = yrange
|
|
770
|
+
|
|
771
|
+
new_xrange = self._calculate_padding_range(new_xrange)
|
|
772
|
+
new_yrange = self._calculate_padding_range(new_yrange)
|
|
773
|
+
|
|
774
|
+
final_axes_ranges.append([new_xrange, new_yrange])
|
|
775
|
+
return final_axes_ranges
|
|
776
|
+
|
|
777
|
+
#! Xpad and Ypad will change the size of the axis
|
|
778
|
+
def apply_tight_layout(self) -> None:
|
|
779
|
+
|
|
780
|
+
if self.tight_layout:
|
|
781
|
+
try:
|
|
782
|
+
plt.tight_layout(
|
|
783
|
+
w_pad=self.x_pad, h_pad=self.y_pad, *self.args, **self.kwargs
|
|
784
|
+
)
|
|
785
|
+
except Exception:
|
|
786
|
+
plt.tight_layout(w_pad=self.x_pad, h_pad=self.y_pad)
|
|
787
|
+
|
|
788
|
+
def label(self) -> None:
|
|
789
|
+
|
|
790
|
+
self.add_minor_ticks_axes()
|
|
791
|
+
self.set_labels()
|
|
792
|
+
self.apply_tight_layout()
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
@bind_passed_params()
|
|
796
|
+
@track_order
|
|
797
|
+
def label(
|
|
798
|
+
lab_lims: list[Any],
|
|
799
|
+
x_pad: int = 2,
|
|
800
|
+
y_pad: int = 2,
|
|
801
|
+
minor_ticks_axes: bool = True,
|
|
802
|
+
tight_layout: bool = True,
|
|
803
|
+
*args: Any,
|
|
804
|
+
**kwargs: Any,
|
|
805
|
+
) -> None:
|
|
806
|
+
"""
|
|
807
|
+
Configures labels, limits, ticks, and layouts for Matplotlib axes.
|
|
808
|
+
|
|
809
|
+
This function is a wrapper for the `Label` class.
|
|
810
|
+
|
|
811
|
+
Parameters
|
|
812
|
+
--------------------
|
|
813
|
+
lab_lims : list[Any]
|
|
814
|
+
A list specifying labels and limits for each axis in the figure. Each entry
|
|
815
|
+
should be a tuple of the form `(x_label, y_label, x_limits, y_limits)`.
|
|
816
|
+
x_pad : int, default=2
|
|
817
|
+
Horizontal padding for tight layout.
|
|
818
|
+
y_pad : int, default=2
|
|
819
|
+
Vertical padding for tight layout.
|
|
820
|
+
minor_ticks_axes : bool, default=True
|
|
821
|
+
Whether to add minor ticks to all axes.
|
|
822
|
+
tight_layout : bool, default=True
|
|
823
|
+
Whether to apply `tight_layout` to the figure.
|
|
824
|
+
*args : Any
|
|
825
|
+
Additional arguments for `plt.tight_layout`.
|
|
826
|
+
**kwargs : Any
|
|
827
|
+
Additional keyword arguments for `plt.tight_layout`.
|
|
828
|
+
|
|
829
|
+
Notes
|
|
830
|
+
--------------------
|
|
831
|
+
This function utilizes the `ParamsGetter` to retrieve bound parameters
|
|
832
|
+
and the `CreateClassParams` class to handle the merging of default,
|
|
833
|
+
configuration, and passed parameters.
|
|
834
|
+
|
|
835
|
+
Returns
|
|
836
|
+
--------------------
|
|
837
|
+
None
|
|
838
|
+
|
|
839
|
+
Warnings
|
|
840
|
+
--------------------
|
|
841
|
+
This function should be called before the :func:`gsplot.label_add_index <gsplot.style.label.label_add_index>` function
|
|
842
|
+
|
|
843
|
+
Examples
|
|
844
|
+
--------------------
|
|
845
|
+
>>> import gsplot as gs
|
|
846
|
+
>>> gs.label(
|
|
847
|
+
>>> lab_lims=[("X Label", "Y Label", [1, 10, "log"], [1, 20, 2])],
|
|
848
|
+
>>> x_pad=5,
|
|
849
|
+
>>> y_pad=5,
|
|
850
|
+
>>> )
|
|
851
|
+
"""
|
|
852
|
+
|
|
853
|
+
passed_params: dict[str, Any] = ParamsGetter("passed_params").get_bound_params()
|
|
854
|
+
class_params = CreateClassParams(passed_params).get_class_params()
|
|
855
|
+
|
|
856
|
+
_label = Label(
|
|
857
|
+
class_params["lab_lims"],
|
|
858
|
+
class_params["x_pad"],
|
|
859
|
+
class_params["y_pad"],
|
|
860
|
+
class_params["minor_ticks_axes"],
|
|
861
|
+
class_params["tight_layout"],
|
|
862
|
+
*class_params["args"],
|
|
863
|
+
**class_params["kwargs"],
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
_label.label()
|