figrecipe 0.5.0__py3-none-any.whl → 0.6.0__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.
- figrecipe/__init__.py +361 -93
- figrecipe/_dev/__init__.py +120 -0
- figrecipe/_dev/demo_plotters/__init__.py +195 -0
- figrecipe/_dev/demo_plotters/plot_acorr.py +24 -0
- figrecipe/_dev/demo_plotters/plot_angle_spectrum.py +28 -0
- figrecipe/_dev/demo_plotters/plot_bar.py +25 -0
- figrecipe/_dev/demo_plotters/plot_barbs.py +30 -0
- figrecipe/_dev/demo_plotters/plot_barh.py +25 -0
- figrecipe/_dev/demo_plotters/plot_boxplot.py +24 -0
- figrecipe/_dev/demo_plotters/plot_cohere.py +29 -0
- figrecipe/_dev/demo_plotters/plot_contour.py +30 -0
- figrecipe/_dev/demo_plotters/plot_contourf.py +29 -0
- figrecipe/_dev/demo_plotters/plot_csd.py +29 -0
- figrecipe/_dev/demo_plotters/plot_ecdf.py +24 -0
- figrecipe/_dev/demo_plotters/plot_errorbar.py +28 -0
- figrecipe/_dev/demo_plotters/plot_eventplot.py +25 -0
- figrecipe/_dev/demo_plotters/plot_fill.py +29 -0
- figrecipe/_dev/demo_plotters/plot_fill_between.py +30 -0
- figrecipe/_dev/demo_plotters/plot_fill_betweenx.py +28 -0
- figrecipe/_dev/demo_plotters/plot_hexbin.py +25 -0
- figrecipe/_dev/demo_plotters/plot_hist.py +24 -0
- figrecipe/_dev/demo_plotters/plot_hist2d.py +25 -0
- figrecipe/_dev/demo_plotters/plot_imshow.py +23 -0
- figrecipe/_dev/demo_plotters/plot_loglog.py +27 -0
- figrecipe/_dev/demo_plotters/plot_magnitude_spectrum.py +28 -0
- figrecipe/_dev/demo_plotters/plot_matshow.py +23 -0
- figrecipe/_dev/demo_plotters/plot_pcolor.py +29 -0
- figrecipe/_dev/demo_plotters/plot_pcolormesh.py +29 -0
- figrecipe/_dev/demo_plotters/plot_phase_spectrum.py +28 -0
- figrecipe/_dev/demo_plotters/plot_pie.py +23 -0
- figrecipe/_dev/demo_plotters/plot_plot.py +27 -0
- figrecipe/_dev/demo_plotters/plot_psd.py +29 -0
- figrecipe/_dev/demo_plotters/plot_quiver.py +30 -0
- figrecipe/_dev/demo_plotters/plot_scatter.py +24 -0
- figrecipe/_dev/demo_plotters/plot_semilogx.py +27 -0
- figrecipe/_dev/demo_plotters/plot_semilogy.py +27 -0
- figrecipe/_dev/demo_plotters/plot_specgram.py +30 -0
- figrecipe/_dev/demo_plotters/plot_spy.py +29 -0
- figrecipe/_dev/demo_plotters/plot_stackplot.py +29 -0
- figrecipe/_dev/demo_plotters/plot_stairs.py +27 -0
- figrecipe/_dev/demo_plotters/plot_stem.py +27 -0
- figrecipe/_dev/demo_plotters/plot_step.py +27 -0
- figrecipe/_dev/demo_plotters/plot_streamplot.py +30 -0
- figrecipe/_dev/demo_plotters/plot_tricontour.py +28 -0
- figrecipe/_dev/demo_plotters/plot_tricontourf.py +28 -0
- figrecipe/_dev/demo_plotters/plot_tripcolor.py +29 -0
- figrecipe/_dev/demo_plotters/plot_triplot.py +25 -0
- figrecipe/_dev/demo_plotters/plot_violinplot.py +25 -0
- figrecipe/_dev/demo_plotters/plot_xcorr.py +25 -0
- figrecipe/_editor/__init__.py +230 -0
- figrecipe/_editor/_bbox.py +978 -0
- figrecipe/_editor/_flask_app.py +1229 -0
- figrecipe/_editor/_hitmap.py +937 -0
- figrecipe/_editor/_overrides.py +318 -0
- figrecipe/_editor/_renderer.py +349 -0
- figrecipe/_editor/_templates/__init__.py +75 -0
- figrecipe/_editor/_templates/_html.py +406 -0
- figrecipe/_editor/_templates/_scripts.py +2778 -0
- figrecipe/_editor/_templates/_styles.py +1326 -0
- figrecipe/_params/_DECORATION_METHODS.py +27 -0
- figrecipe/_params/_PLOTTING_METHODS.py +58 -0
- figrecipe/_params/__init__.py +9 -0
- figrecipe/_recorder.py +126 -73
- figrecipe/_reproducer.py +658 -41
- figrecipe/_seaborn.py +14 -9
- figrecipe/_serializer.py +2 -2
- figrecipe/_signatures/README.md +68 -0
- figrecipe/_signatures/__init__.py +12 -2
- figrecipe/_signatures/_loader.py +515 -56
- figrecipe/_utils/__init__.py +6 -4
- figrecipe/_utils/_crop.py +10 -4
- figrecipe/_utils/_image_diff.py +37 -33
- figrecipe/_utils/_numpy_io.py +0 -1
- figrecipe/_utils/_units.py +11 -3
- figrecipe/_validator.py +12 -3
- figrecipe/_wrappers/_axes.py +860 -46
- figrecipe/_wrappers/_figure.py +115 -18
- figrecipe/plt.py +0 -1
- figrecipe/pyplot.py +2 -1
- figrecipe/styles/__init__.py +9 -10
- figrecipe/styles/_style_applier.py +332 -28
- figrecipe/styles/_style_loader.py +172 -44
- figrecipe/styles/presets/MATPLOTLIB.yaml +94 -0
- figrecipe/styles/presets/SCITEX.yaml +176 -0
- figrecipe-0.6.0.dist-info/METADATA +394 -0
- figrecipe-0.6.0.dist-info/RECORD +90 -0
- figrecipe-0.5.0.dist-info/METADATA +0 -336
- figrecipe-0.5.0.dist-info/RECORD +0 -26
- {figrecipe-0.5.0.dist-info → figrecipe-0.6.0.dist-info}/WHEEL +0 -0
- {figrecipe-0.5.0.dist-info → figrecipe-0.6.0.dist-info}/licenses/LICENSE +0 -0
figrecipe/_wrappers/_figure.py
CHANGED
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
"""Wrapped Figure that manages recording."""
|
|
4
4
|
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import TYPE_CHECKING, Any, List, Literal, Optional, Tuple, Union
|
|
7
7
|
|
|
8
8
|
import matplotlib.pyplot as plt
|
|
9
|
+
import numpy as np
|
|
9
10
|
from matplotlib.figure import Figure
|
|
11
|
+
from numpy.typing import NDArray
|
|
10
12
|
|
|
11
13
|
from ._axes import RecordingAxes
|
|
12
14
|
|
|
13
15
|
if TYPE_CHECKING:
|
|
14
|
-
from .._recorder import
|
|
15
|
-
from .._utils._numpy_io import DataFormat
|
|
16
|
+
from .._recorder import FigureRecord, Recorder
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class RecordingFigure:
|
|
@@ -79,6 +80,88 @@ class RecordingFigure:
|
|
|
79
80
|
"""Get the figure record."""
|
|
80
81
|
return self._recorder.figure_record
|
|
81
82
|
|
|
83
|
+
def _get_style_fontsize(self, key: str, default: float) -> float:
|
|
84
|
+
"""Get fontsize from loaded style."""
|
|
85
|
+
try:
|
|
86
|
+
from ..styles._style_loader import _STYLE_CACHE
|
|
87
|
+
|
|
88
|
+
if _STYLE_CACHE is not None:
|
|
89
|
+
fonts = getattr(_STYLE_CACHE, "fonts", None)
|
|
90
|
+
if fonts is not None:
|
|
91
|
+
return getattr(fonts, key, default)
|
|
92
|
+
except Exception:
|
|
93
|
+
pass
|
|
94
|
+
return default
|
|
95
|
+
|
|
96
|
+
def suptitle(self, t: str, **kwargs) -> Any:
|
|
97
|
+
"""Set super title for the figure and record it.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
t : str
|
|
102
|
+
The super title text.
|
|
103
|
+
**kwargs
|
|
104
|
+
Additional arguments passed to matplotlib's suptitle().
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
Text
|
|
109
|
+
The matplotlib Text object.
|
|
110
|
+
"""
|
|
111
|
+
# Auto-apply fontsize from style if not specified
|
|
112
|
+
if "fontsize" not in kwargs:
|
|
113
|
+
kwargs["fontsize"] = self._get_style_fontsize("suptitle_pt", 10)
|
|
114
|
+
# Record the suptitle call
|
|
115
|
+
self._recorder.figure_record.suptitle = {"text": t, "kwargs": kwargs}
|
|
116
|
+
# Call the underlying figure's suptitle
|
|
117
|
+
return self._fig.suptitle(t, **kwargs)
|
|
118
|
+
|
|
119
|
+
def supxlabel(self, t: str, **kwargs) -> Any:
|
|
120
|
+
"""Set super x-label for the figure and record it.
|
|
121
|
+
|
|
122
|
+
Parameters
|
|
123
|
+
----------
|
|
124
|
+
t : str
|
|
125
|
+
The super x-label text.
|
|
126
|
+
**kwargs
|
|
127
|
+
Additional arguments passed to matplotlib's supxlabel().
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
Text
|
|
132
|
+
The matplotlib Text object.
|
|
133
|
+
"""
|
|
134
|
+
# Auto-apply fontsize from style if not specified
|
|
135
|
+
if "fontsize" not in kwargs:
|
|
136
|
+
kwargs["fontsize"] = self._get_style_fontsize("supxlabel_pt", 8)
|
|
137
|
+
# Record the supxlabel call
|
|
138
|
+
self._recorder.figure_record.supxlabel = {"text": t, "kwargs": kwargs}
|
|
139
|
+
# Call the underlying figure's supxlabel
|
|
140
|
+
return self._fig.supxlabel(t, **kwargs)
|
|
141
|
+
|
|
142
|
+
def supylabel(self, t: str, **kwargs) -> Any:
|
|
143
|
+
"""Set super y-label for the figure and record it.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
t : str
|
|
148
|
+
The super y-label text.
|
|
149
|
+
**kwargs
|
|
150
|
+
Additional arguments passed to matplotlib's supylabel().
|
|
151
|
+
|
|
152
|
+
Returns
|
|
153
|
+
-------
|
|
154
|
+
Text
|
|
155
|
+
The matplotlib Text object.
|
|
156
|
+
"""
|
|
157
|
+
# Auto-apply fontsize from style if not specified
|
|
158
|
+
if "fontsize" not in kwargs:
|
|
159
|
+
kwargs["fontsize"] = self._get_style_fontsize("supylabel_pt", 8)
|
|
160
|
+
# Record the supylabel call
|
|
161
|
+
self._recorder.figure_record.supylabel = {"text": t, "kwargs": kwargs}
|
|
162
|
+
# Call the underlying figure's supylabel
|
|
163
|
+
return self._fig.supylabel(t, **kwargs)
|
|
164
|
+
|
|
82
165
|
def __getattr__(self, name: str) -> Any:
|
|
83
166
|
"""Delegate attribute access to underlying figure."""
|
|
84
167
|
return getattr(self._fig, name)
|
|
@@ -88,6 +171,7 @@ class RecordingFigure:
|
|
|
88
171
|
fname,
|
|
89
172
|
save_recipe: bool = True,
|
|
90
173
|
recipe_format: Literal["csv", "npz", "inline"] = "csv",
|
|
174
|
+
verbose: bool = True,
|
|
91
175
|
**kwargs,
|
|
92
176
|
):
|
|
93
177
|
"""Save the figure image and optionally the recipe.
|
|
@@ -101,6 +185,8 @@ class RecordingFigure:
|
|
|
101
185
|
Recipe will be saved with same name but .yaml extension.
|
|
102
186
|
recipe_format : str
|
|
103
187
|
Format for data in recipe: 'csv' (default), 'npz', or 'inline'.
|
|
188
|
+
verbose : bool
|
|
189
|
+
If True (default), print save status.
|
|
104
190
|
**kwargs
|
|
105
191
|
Passed to matplotlib's savefig().
|
|
106
192
|
|
|
@@ -118,7 +204,7 @@ class RecordingFigure:
|
|
|
118
204
|
>>> fig.savefig('figure.png', save_recipe=False) # Image only
|
|
119
205
|
"""
|
|
120
206
|
# Handle file-like objects (BytesIO, etc.) - just pass through
|
|
121
|
-
if hasattr(fname,
|
|
207
|
+
if hasattr(fname, "write"):
|
|
122
208
|
self._fig.savefig(fname, **kwargs)
|
|
123
209
|
return fname
|
|
124
210
|
|
|
@@ -128,8 +214,12 @@ class RecordingFigure:
|
|
|
128
214
|
if save_recipe:
|
|
129
215
|
recipe_path = fname.with_suffix(".yaml")
|
|
130
216
|
self.save_recipe(recipe_path, include_data=True, data_format=recipe_format)
|
|
217
|
+
if verbose:
|
|
218
|
+
print(f"Saved: {fname} + {recipe_path}")
|
|
131
219
|
return fname, recipe_path
|
|
132
220
|
|
|
221
|
+
if verbose:
|
|
222
|
+
print(f"Saved: {fname}")
|
|
133
223
|
return fname
|
|
134
224
|
|
|
135
225
|
def save_recipe(
|
|
@@ -155,7 +245,10 @@ class RecordingFigure:
|
|
|
155
245
|
Path to saved recipe file.
|
|
156
246
|
"""
|
|
157
247
|
from .._serializer import save_recipe
|
|
158
|
-
|
|
248
|
+
|
|
249
|
+
return save_recipe(
|
|
250
|
+
self._recorder.figure_record, path, include_data, data_format
|
|
251
|
+
)
|
|
159
252
|
|
|
160
253
|
|
|
161
254
|
def create_recording_subplots(
|
|
@@ -163,7 +256,7 @@ def create_recording_subplots(
|
|
|
163
256
|
ncols: int = 1,
|
|
164
257
|
recorder: Optional["Recorder"] = None,
|
|
165
258
|
**kwargs,
|
|
166
|
-
) -> Tuple[RecordingFigure, Union[RecordingAxes,
|
|
259
|
+
) -> Tuple[RecordingFigure, Union[RecordingAxes, NDArray]]:
|
|
167
260
|
"""Create a figure with recording-enabled axes.
|
|
168
261
|
|
|
169
262
|
Parameters
|
|
@@ -181,8 +274,8 @@ def create_recording_subplots(
|
|
|
181
274
|
-------
|
|
182
275
|
fig : RecordingFigure
|
|
183
276
|
Wrapped figure.
|
|
184
|
-
axes : RecordingAxes or
|
|
185
|
-
Wrapped axes (single if 1x1, otherwise
|
|
277
|
+
axes : RecordingAxes or ndarray
|
|
278
|
+
Wrapped axes (single if 1x1, otherwise numpy array matching matplotlib).
|
|
186
279
|
"""
|
|
187
280
|
from .._recorder import Recorder
|
|
188
281
|
|
|
@@ -205,23 +298,27 @@ def create_recording_subplots(
|
|
|
205
298
|
wrapped_fig = RecordingFigure(fig, recorder, wrapped_ax)
|
|
206
299
|
return wrapped_fig, wrapped_ax
|
|
207
300
|
|
|
208
|
-
# Handle 1D or 2D arrays
|
|
209
|
-
|
|
210
|
-
|
|
301
|
+
# Handle 1D or 2D arrays - reshape to (nrows, ncols) for uniform processing
|
|
302
|
+
mpl_axes_arr = np.asarray(mpl_axes)
|
|
303
|
+
if mpl_axes_arr.ndim == 1:
|
|
304
|
+
mpl_axes_arr = mpl_axes_arr.reshape(nrows, ncols)
|
|
211
305
|
|
|
212
306
|
wrapped_axes = []
|
|
213
|
-
for i in range(
|
|
307
|
+
for i in range(nrows):
|
|
214
308
|
row = []
|
|
215
|
-
for j in range(
|
|
216
|
-
row.append(RecordingAxes(
|
|
309
|
+
for j in range(ncols):
|
|
310
|
+
row.append(RecordingAxes(mpl_axes_arr[i, j], recorder, position=(i, j)))
|
|
217
311
|
wrapped_axes.append(row)
|
|
218
312
|
|
|
219
313
|
wrapped_fig = RecordingFigure(fig, recorder, wrapped_axes)
|
|
220
314
|
|
|
221
|
-
# Return in same shape as matplotlib
|
|
315
|
+
# Return in same shape as matplotlib (numpy arrays for consistency)
|
|
222
316
|
if nrows == 1:
|
|
223
|
-
|
|
317
|
+
# 1xN -> 1D array of shape (N,)
|
|
318
|
+
return wrapped_fig, np.array(wrapped_axes[0], dtype=object)
|
|
224
319
|
elif ncols == 1:
|
|
225
|
-
|
|
320
|
+
# Nx1 -> 1D array of shape (N,)
|
|
321
|
+
return wrapped_fig, np.array([row[0] for row in wrapped_axes], dtype=object)
|
|
226
322
|
else:
|
|
227
|
-
|
|
323
|
+
# NxM -> 2D array
|
|
324
|
+
return wrapped_fig, np.array(wrapped_axes, dtype=object)
|
figrecipe/plt.py
CHANGED
figrecipe/pyplot.py
CHANGED
|
@@ -34,9 +34,10 @@ Examples
|
|
|
34
34
|
import matplotlib.pyplot as _plt
|
|
35
35
|
from matplotlib.pyplot import * # noqa: F401, F403
|
|
36
36
|
|
|
37
|
+
from . import save as _ps_save
|
|
38
|
+
|
|
37
39
|
# Import figrecipe functionality
|
|
38
40
|
from . import subplots as _ps_subplots
|
|
39
|
-
from . import save as _ps_save
|
|
40
41
|
from ._wrappers import RecordingFigure
|
|
41
42
|
|
|
42
43
|
# Override subplots with recording-enabled version
|
figrecipe/styles/__init__.py
CHANGED
|
@@ -18,22 +18,21 @@ Usage:
|
|
|
18
18
|
fig, ax = ps.subplots(**style.to_subplots_kwargs())
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
-
from ._style_loader import (
|
|
22
|
-
load_style,
|
|
23
|
-
unload_style,
|
|
24
|
-
get_style,
|
|
25
|
-
reload_style,
|
|
26
|
-
list_presets,
|
|
27
|
-
STYLE,
|
|
28
|
-
to_subplots_kwargs,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
21
|
from ._style_applier import (
|
|
32
22
|
apply_style_mm,
|
|
33
23
|
apply_theme_colors,
|
|
34
24
|
check_font,
|
|
35
25
|
list_available_fonts,
|
|
36
26
|
)
|
|
27
|
+
from ._style_loader import (
|
|
28
|
+
STYLE,
|
|
29
|
+
get_style,
|
|
30
|
+
list_presets,
|
|
31
|
+
load_style,
|
|
32
|
+
reload_style,
|
|
33
|
+
to_subplots_kwargs,
|
|
34
|
+
unload_style,
|
|
35
|
+
)
|
|
37
36
|
|
|
38
37
|
__all__ = [
|
|
39
38
|
"load_style",
|