figrecipe 0.5.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.
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Wrapped Figure that manages recording."""
4
+
5
+ from pathlib import Path
6
+ from typing import Any, Dict, List, Literal, Optional, Tuple, Union, TYPE_CHECKING
7
+
8
+ import matplotlib.pyplot as plt
9
+ from matplotlib.figure import Figure
10
+
11
+ from ._axes import RecordingAxes
12
+
13
+ if TYPE_CHECKING:
14
+ from .._recorder import Recorder, FigureRecord
15
+ from .._utils._numpy_io import DataFormat
16
+
17
+
18
+ class RecordingFigure:
19
+ """Wrapper around matplotlib Figure that manages recording.
20
+
21
+ Parameters
22
+ ----------
23
+ fig : matplotlib.figure.Figure
24
+ The underlying matplotlib figure.
25
+ recorder : Recorder
26
+ The recorder instance.
27
+ axes : list of RecordingAxes
28
+ Wrapped axes objects.
29
+
30
+ Examples
31
+ --------
32
+ >>> import figrecipe as ps
33
+ >>> fig, ax = ps.subplots()
34
+ >>> ax.plot([1, 2, 3], [4, 5, 6])
35
+ >>> ps.save(fig, "my_figure.yaml")
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ fig: Figure,
41
+ recorder: "Recorder",
42
+ axes: Union[RecordingAxes, List[RecordingAxes]],
43
+ ):
44
+ self._fig = fig
45
+ self._recorder = recorder
46
+
47
+ # Normalize axes to list
48
+ if isinstance(axes, RecordingAxes):
49
+ self._axes = [[axes]]
50
+ elif isinstance(axes, list):
51
+ if axes and isinstance(axes[0], list):
52
+ self._axes = axes
53
+ else:
54
+ self._axes = [axes]
55
+ else:
56
+ self._axes = [[axes]]
57
+
58
+ @property
59
+ def fig(self) -> Figure:
60
+ """Get the underlying matplotlib figure."""
61
+ return self._fig
62
+
63
+ @property
64
+ def axes(self) -> List[List[RecordingAxes]]:
65
+ """Get axes as 2D array."""
66
+ return self._axes
67
+
68
+ @property
69
+ def flat(self) -> List[RecordingAxes]:
70
+ """Get flattened list of all axes."""
71
+ result = []
72
+ for row in self._axes:
73
+ for ax in row:
74
+ result.append(ax)
75
+ return result
76
+
77
+ @property
78
+ def record(self) -> "FigureRecord":
79
+ """Get the figure record."""
80
+ return self._recorder.figure_record
81
+
82
+ def __getattr__(self, name: str) -> Any:
83
+ """Delegate attribute access to underlying figure."""
84
+ return getattr(self._fig, name)
85
+
86
+ def savefig(
87
+ self,
88
+ fname,
89
+ save_recipe: bool = True,
90
+ recipe_format: Literal["csv", "npz", "inline"] = "csv",
91
+ **kwargs,
92
+ ):
93
+ """Save the figure image and optionally the recipe.
94
+
95
+ Parameters
96
+ ----------
97
+ fname : str or Path
98
+ Output path for the image file.
99
+ save_recipe : bool
100
+ If True (default), also save a YAML recipe alongside the image.
101
+ Recipe will be saved with same name but .yaml extension.
102
+ recipe_format : str
103
+ Format for data in recipe: 'csv' (default), 'npz', or 'inline'.
104
+ **kwargs
105
+ Passed to matplotlib's savefig().
106
+
107
+ Returns
108
+ -------
109
+ Path or tuple
110
+ If save_recipe=False: image path.
111
+ If save_recipe=True: (image_path, recipe_path) tuple.
112
+
113
+ Examples
114
+ --------
115
+ >>> fig, ax = ps.subplots()
116
+ >>> ax.plot(x, y, id='data')
117
+ >>> fig.savefig('figure.png') # Saves both figure.png and figure.yaml
118
+ >>> fig.savefig('figure.png', save_recipe=False) # Image only
119
+ """
120
+ # Handle file-like objects (BytesIO, etc.) - just pass through
121
+ if hasattr(fname, 'write'):
122
+ self._fig.savefig(fname, **kwargs)
123
+ return fname
124
+
125
+ fname = Path(fname)
126
+ self._fig.savefig(fname, **kwargs)
127
+
128
+ if save_recipe:
129
+ recipe_path = fname.with_suffix(".yaml")
130
+ self.save_recipe(recipe_path, include_data=True, data_format=recipe_format)
131
+ return fname, recipe_path
132
+
133
+ return fname
134
+
135
+ def save_recipe(
136
+ self,
137
+ path: Union[str, Path],
138
+ include_data: bool = True,
139
+ data_format: Literal["csv", "npz", "inline"] = "csv",
140
+ ) -> Path:
141
+ """Save the recording recipe to YAML.
142
+
143
+ Parameters
144
+ ----------
145
+ path : str or Path
146
+ Output path for the recipe file.
147
+ include_data : bool
148
+ If True, save array data alongside recipe.
149
+ data_format : str
150
+ Format for data files: 'csv' (default), 'npz', or 'inline'.
151
+
152
+ Returns
153
+ -------
154
+ Path
155
+ Path to saved recipe file.
156
+ """
157
+ from .._serializer import save_recipe
158
+ return save_recipe(self._recorder.figure_record, path, include_data, data_format)
159
+
160
+
161
+ def create_recording_subplots(
162
+ nrows: int = 1,
163
+ ncols: int = 1,
164
+ recorder: Optional["Recorder"] = None,
165
+ **kwargs,
166
+ ) -> Tuple[RecordingFigure, Union[RecordingAxes, List[RecordingAxes]]]:
167
+ """Create a figure with recording-enabled axes.
168
+
169
+ Parameters
170
+ ----------
171
+ nrows : int
172
+ Number of rows.
173
+ ncols : int
174
+ Number of columns.
175
+ recorder : Recorder, optional
176
+ Recorder instance. Created if not provided.
177
+ **kwargs
178
+ Passed to plt.subplots().
179
+
180
+ Returns
181
+ -------
182
+ fig : RecordingFigure
183
+ Wrapped figure.
184
+ axes : RecordingAxes or list
185
+ Wrapped axes (single if 1x1, otherwise 2D array).
186
+ """
187
+ from .._recorder import Recorder
188
+
189
+ if recorder is None:
190
+ recorder = Recorder()
191
+
192
+ # Create matplotlib figure
193
+ fig, mpl_axes = plt.subplots(nrows, ncols, **kwargs)
194
+
195
+ # Get figsize and dpi
196
+ figsize = kwargs.get("figsize", fig.get_size_inches())
197
+ dpi = kwargs.get("dpi", fig.dpi)
198
+
199
+ # Start recording
200
+ recorder.start_figure(figsize=tuple(figsize), dpi=int(dpi))
201
+
202
+ # Wrap axes
203
+ if nrows == 1 and ncols == 1:
204
+ wrapped_ax = RecordingAxes(mpl_axes, recorder, position=(0, 0))
205
+ wrapped_fig = RecordingFigure(fig, recorder, wrapped_ax)
206
+ return wrapped_fig, wrapped_ax
207
+
208
+ # Handle 1D or 2D arrays
209
+ import numpy as np
210
+ mpl_axes = np.atleast_2d(mpl_axes)
211
+
212
+ wrapped_axes = []
213
+ for i in range(mpl_axes.shape[0]):
214
+ row = []
215
+ for j in range(mpl_axes.shape[1]):
216
+ row.append(RecordingAxes(mpl_axes[i, j], recorder, position=(i, j)))
217
+ wrapped_axes.append(row)
218
+
219
+ wrapped_fig = RecordingFigure(fig, recorder, wrapped_axes)
220
+
221
+ # Return in same shape as matplotlib
222
+ if nrows == 1:
223
+ return wrapped_fig, wrapped_axes[0]
224
+ elif ncols == 1:
225
+ return wrapped_fig, [row[0] for row in wrapped_axes]
226
+ else:
227
+ return wrapped_fig, wrapped_axes
figrecipe/plt.py ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Alias for figrecipe.pyplot - drop-in replacement for matplotlib.pyplot.
4
+
5
+ Usage:
6
+ import figrecipe.plt as plt # Short form
7
+ # or
8
+ import figrecipe.pyplot as plt # Full form
9
+ """
10
+
11
+ from .pyplot import * # noqa: F401, F403
12
+ from .pyplot import __all__
figrecipe/pyplot.py ADDED
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Drop-in replacement for matplotlib.pyplot with recording capabilities.
4
+
5
+ This module provides a convenient way to use figrecipe as a direct replacement
6
+ for matplotlib.pyplot. Simply change your import statement:
7
+
8
+ # Before (standard matplotlib)
9
+ import matplotlib.pyplot as plt
10
+
11
+ # After (figrecipe with recording)
12
+ import figrecipe.pyplot as plt
13
+
14
+ All your existing code will work unchanged, but figures created with
15
+ plt.subplots() will automatically have recording capabilities.
16
+
17
+ Examples
18
+ --------
19
+ >>> import figrecipe.pyplot as plt
20
+ >>> import numpy as np
21
+ >>>
22
+ >>> x = np.linspace(0, 10, 100)
23
+ >>> y = np.sin(x)
24
+ >>>
25
+ >>> fig, ax = plt.subplots() # Recording-enabled
26
+ >>> ax.plot(x, y, color='red', id='sine_wave')
27
+ >>> fig.save_recipe('my_figure.yaml') # Save as recipe
28
+ >>>
29
+ >>> # All other pyplot functions work as usual
30
+ >>> plt.show()
31
+ >>> plt.savefig('output.png')
32
+ """
33
+
34
+ import matplotlib.pyplot as _plt
35
+ from matplotlib.pyplot import * # noqa: F401, F403
36
+
37
+ # Import figrecipe functionality
38
+ from . import subplots as _ps_subplots
39
+ from . import save as _ps_save
40
+ from ._wrappers import RecordingFigure
41
+
42
+ # Override subplots with recording-enabled version
43
+ subplots = _ps_subplots
44
+
45
+
46
+ def figure(*args, **kwargs):
47
+ """Create a new figure with optional recording support.
48
+
49
+ This is a pass-through to matplotlib.pyplot.figure().
50
+ For recording support, use subplots() instead.
51
+
52
+ Parameters
53
+ ----------
54
+ *args, **kwargs
55
+ Arguments passed to matplotlib.pyplot.figure().
56
+
57
+ Returns
58
+ -------
59
+ matplotlib.figure.Figure
60
+ The created figure.
61
+ """
62
+ return _plt.figure(*args, **kwargs)
63
+
64
+
65
+ def save(fig, path, **kwargs):
66
+ """Save a figure (recipe for RecordingFigure, or standard save).
67
+
68
+ Parameters
69
+ ----------
70
+ fig : RecordingFigure or Figure
71
+ Figure to save. If RecordingFigure, saves as recipe.
72
+ Otherwise, saves as image using savefig().
73
+ path : str or Path
74
+ Output path. Use .yaml for recipe format.
75
+ **kwargs
76
+ Additional arguments for save.
77
+
78
+ Returns
79
+ -------
80
+ Path or tuple
81
+ Saved path (and ValidationResult if validate=True).
82
+ """
83
+ if isinstance(fig, RecordingFigure):
84
+ return _ps_save(fig, path, **kwargs)
85
+ else:
86
+ fig.savefig(path, **kwargs)
87
+ return path
88
+
89
+
90
+ # Expose commonly used functions explicitly for IDE support
91
+ show = _plt.show
92
+ savefig = _plt.savefig
93
+
94
+
95
+ def close(fig=None):
96
+ """Close a figure window.
97
+
98
+ Parameters
99
+ ----------
100
+ fig : None, int, str, Figure, or RecordingFigure
101
+ The figure to close. See matplotlib.pyplot.close() for details.
102
+ RecordingFigure is automatically unwrapped to the underlying Figure.
103
+ """
104
+ if isinstance(fig, RecordingFigure):
105
+ _plt.close(fig.fig)
106
+ else:
107
+ _plt.close(fig)
108
+
109
+
110
+ clf = _plt.clf
111
+ cla = _plt.cla
112
+ gcf = _plt.gcf
113
+ gca = _plt.gca
114
+ subplot = _plt.subplot
115
+ tight_layout = _plt.tight_layout
116
+ suptitle = _plt.suptitle
117
+ xlabel = _plt.xlabel
118
+ ylabel = _plt.ylabel
119
+ title = _plt.title
120
+ legend = _plt.legend
121
+ xlim = _plt.xlim
122
+ ylim = _plt.ylim
123
+ grid = _plt.grid
124
+ plot = _plt.plot
125
+ scatter = _plt.scatter
126
+ bar = _plt.bar
127
+ hist = _plt.hist
128
+ imshow = _plt.imshow
129
+ contour = _plt.contour
130
+ contourf = _plt.contourf
131
+ colorbar = _plt.colorbar
132
+ axhline = _plt.axhline
133
+ axvline = _plt.axvline
134
+ text = _plt.text
135
+ annotate = _plt.annotate
136
+ fill_between = _plt.fill_between
137
+ errorbar = _plt.errorbar
138
+ boxplot = _plt.boxplot
139
+ violinplot = _plt.violinplot
140
+ pie = _plt.pie
141
+ stem = _plt.stem
142
+ step = _plt.step
143
+ stackplot = _plt.stackplot
144
+ streamplot = _plt.streamplot
145
+ quiver = _plt.quiver
146
+ barbs = _plt.barbs
147
+ hexbin = _plt.hexbin
148
+ pcolormesh = _plt.pcolormesh
149
+ tripcolor = _plt.tripcolor
150
+ tricontour = _plt.tricontour
151
+ tricontourf = _plt.tricontourf
152
+ spy = _plt.spy
153
+ matshow = _plt.matshow
154
+ specgram = _plt.specgram
155
+ psd = _plt.psd
156
+ csd = _plt.csd
157
+ cohere = _plt.cohere
158
+ magnitude_spectrum = _plt.magnitude_spectrum
159
+ angle_spectrum = _plt.angle_spectrum
160
+ phase_spectrum = _plt.phase_spectrum
161
+ xcorr = _plt.xcorr
162
+ acorr = _plt.acorr
163
+ semilogy = _plt.semilogy
164
+ semilogx = _plt.semilogx
165
+ loglog = _plt.loglog
166
+ polar = _plt.polar
167
+ subplot2grid = _plt.subplot2grid
168
+ subplot_mosaic = _plt.subplot_mosaic
169
+ subplots_adjust = _plt.subplots_adjust
170
+ rc = _plt.rc
171
+ rcdefaults = _plt.rcdefaults
172
+ rcParams = _plt.rcParams
173
+ style = _plt.style
174
+ cm = _plt.cm
175
+ get_cmap = _plt.get_cmap
176
+ colormaps = _plt.colormaps
177
+
178
+
179
+ __all__ = [
180
+ # Core functions (recording-enabled)
181
+ "subplots",
182
+ "figure",
183
+ "save",
184
+ # Display
185
+ "show",
186
+ "savefig",
187
+ "close",
188
+ "clf",
189
+ "cla",
190
+ # Getters
191
+ "gcf",
192
+ "gca",
193
+ # Layout
194
+ "subplot",
195
+ "subplot2grid",
196
+ "subplot_mosaic",
197
+ "subplots_adjust",
198
+ "tight_layout",
199
+ # Labels and titles
200
+ "suptitle",
201
+ "xlabel",
202
+ "ylabel",
203
+ "title",
204
+ "legend",
205
+ # Limits
206
+ "xlim",
207
+ "ylim",
208
+ # Grid
209
+ "grid",
210
+ # Plot types
211
+ "plot",
212
+ "scatter",
213
+ "bar",
214
+ "hist",
215
+ "imshow",
216
+ "contour",
217
+ "contourf",
218
+ "colorbar",
219
+ "axhline",
220
+ "axvline",
221
+ "text",
222
+ "annotate",
223
+ "fill_between",
224
+ "errorbar",
225
+ "boxplot",
226
+ "violinplot",
227
+ "pie",
228
+ "stem",
229
+ "step",
230
+ "stackplot",
231
+ "streamplot",
232
+ "quiver",
233
+ "barbs",
234
+ "hexbin",
235
+ "pcolormesh",
236
+ "tripcolor",
237
+ "tricontour",
238
+ "tricontourf",
239
+ "spy",
240
+ "matshow",
241
+ "specgram",
242
+ "psd",
243
+ "csd",
244
+ "cohere",
245
+ "magnitude_spectrum",
246
+ "angle_spectrum",
247
+ "phase_spectrum",
248
+ "xcorr",
249
+ "acorr",
250
+ # Log scale
251
+ "semilogy",
252
+ "semilogx",
253
+ "loglog",
254
+ "polar",
255
+ # Configuration
256
+ "rc",
257
+ "rcdefaults",
258
+ "rcParams",
259
+ "style",
260
+ # Colormaps
261
+ "cm",
262
+ "get_cmap",
263
+ "colormaps",
264
+ ]
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Style management for figrecipe.
4
+
5
+ Provides style loading, application, and management for publication-quality figures.
6
+
7
+ Usage:
8
+ from figrecipe.styles import load_style, STYLE
9
+
10
+ # Load default style
11
+ style = load_style()
12
+
13
+ # Access style parameters
14
+ print(style.axes.width_mm)
15
+ print(style.fonts.axis_label_pt)
16
+
17
+ # Use with subplots
18
+ fig, ax = ps.subplots(**style.to_subplots_kwargs())
19
+ """
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
+ from ._style_applier import (
32
+ apply_style_mm,
33
+ apply_theme_colors,
34
+ check_font,
35
+ list_available_fonts,
36
+ )
37
+
38
+ __all__ = [
39
+ "load_style",
40
+ "unload_style",
41
+ "get_style",
42
+ "reload_style",
43
+ "list_presets",
44
+ "STYLE",
45
+ "to_subplots_kwargs",
46
+ "apply_style_mm",
47
+ "apply_theme_colors",
48
+ "check_font",
49
+ "list_available_fonts",
50
+ ]