figrecipe 0.5.0__py3-none-any.whl → 0.7.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 (189) hide show
  1. figrecipe/__init__.py +220 -819
  2. figrecipe/_api/__init__.py +48 -0
  3. figrecipe/_api/_extract.py +108 -0
  4. figrecipe/_api/_notebook.py +61 -0
  5. figrecipe/_api/_panel.py +46 -0
  6. figrecipe/_api/_save.py +191 -0
  7. figrecipe/_api/_seaborn_proxy.py +34 -0
  8. figrecipe/_api/_style_manager.py +153 -0
  9. figrecipe/_api/_subplots.py +333 -0
  10. figrecipe/_api/_validate.py +82 -0
  11. figrecipe/_dev/__init__.py +29 -0
  12. figrecipe/_dev/_plotters.py +76 -0
  13. figrecipe/_dev/_run_demos.py +56 -0
  14. figrecipe/_dev/demo_plotters/__init__.py +64 -0
  15. figrecipe/_dev/demo_plotters/_categories.py +81 -0
  16. figrecipe/_dev/demo_plotters/_figure_creators.py +119 -0
  17. figrecipe/_dev/demo_plotters/_helpers.py +31 -0
  18. figrecipe/_dev/demo_plotters/_registry.py +50 -0
  19. figrecipe/_dev/demo_plotters/bar_categorical/__init__.py +4 -0
  20. figrecipe/_dev/demo_plotters/bar_categorical/plot_bar.py +25 -0
  21. figrecipe/_dev/demo_plotters/bar_categorical/plot_barh.py +25 -0
  22. figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
  23. figrecipe/_dev/demo_plotters/contour_surface/plot_contour.py +30 -0
  24. figrecipe/_dev/demo_plotters/contour_surface/plot_contourf.py +29 -0
  25. figrecipe/_dev/demo_plotters/contour_surface/plot_tricontour.py +28 -0
  26. figrecipe/_dev/demo_plotters/contour_surface/plot_tricontourf.py +28 -0
  27. figrecipe/_dev/demo_plotters/contour_surface/plot_tripcolor.py +29 -0
  28. figrecipe/_dev/demo_plotters/contour_surface/plot_triplot.py +25 -0
  29. figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
  30. figrecipe/_dev/demo_plotters/distribution/plot_boxplot.py +24 -0
  31. figrecipe/_dev/demo_plotters/distribution/plot_ecdf.py +24 -0
  32. figrecipe/_dev/demo_plotters/distribution/plot_hist.py +24 -0
  33. figrecipe/_dev/demo_plotters/distribution/plot_hist2d.py +25 -0
  34. figrecipe/_dev/demo_plotters/distribution/plot_violinplot.py +25 -0
  35. figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
  36. figrecipe/_dev/demo_plotters/image_matrix/plot_hexbin.py +25 -0
  37. figrecipe/_dev/demo_plotters/image_matrix/plot_imshow.py +23 -0
  38. figrecipe/_dev/demo_plotters/image_matrix/plot_matshow.py +23 -0
  39. figrecipe/_dev/demo_plotters/image_matrix/plot_pcolor.py +29 -0
  40. figrecipe/_dev/demo_plotters/image_matrix/plot_pcolormesh.py +29 -0
  41. figrecipe/_dev/demo_plotters/image_matrix/plot_spy.py +29 -0
  42. figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
  43. figrecipe/_dev/demo_plotters/line_curve/plot_errorbar.py +28 -0
  44. figrecipe/_dev/demo_plotters/line_curve/plot_fill.py +29 -0
  45. figrecipe/_dev/demo_plotters/line_curve/plot_fill_between.py +30 -0
  46. figrecipe/_dev/demo_plotters/line_curve/plot_fill_betweenx.py +28 -0
  47. figrecipe/_dev/demo_plotters/line_curve/plot_plot.py +28 -0
  48. figrecipe/_dev/demo_plotters/line_curve/plot_stackplot.py +29 -0
  49. figrecipe/_dev/demo_plotters/line_curve/plot_stairs.py +27 -0
  50. figrecipe/_dev/demo_plotters/line_curve/plot_step.py +27 -0
  51. figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
  52. figrecipe/_dev/demo_plotters/scatter_points/plot_scatter.py +24 -0
  53. figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
  54. figrecipe/_dev/demo_plotters/special/plot_eventplot.py +25 -0
  55. figrecipe/_dev/demo_plotters/special/plot_loglog.py +27 -0
  56. figrecipe/_dev/demo_plotters/special/plot_pie.py +27 -0
  57. figrecipe/_dev/demo_plotters/special/plot_semilogx.py +27 -0
  58. figrecipe/_dev/demo_plotters/special/plot_semilogy.py +27 -0
  59. figrecipe/_dev/demo_plotters/special/plot_stem.py +27 -0
  60. figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
  61. figrecipe/_dev/demo_plotters/spectral_signal/plot_acorr.py +24 -0
  62. figrecipe/_dev/demo_plotters/spectral_signal/plot_angle_spectrum.py +28 -0
  63. figrecipe/_dev/demo_plotters/spectral_signal/plot_cohere.py +29 -0
  64. figrecipe/_dev/demo_plotters/spectral_signal/plot_csd.py +29 -0
  65. figrecipe/_dev/demo_plotters/spectral_signal/plot_magnitude_spectrum.py +28 -0
  66. figrecipe/_dev/demo_plotters/spectral_signal/plot_phase_spectrum.py +28 -0
  67. figrecipe/_dev/demo_plotters/spectral_signal/plot_psd.py +29 -0
  68. figrecipe/_dev/demo_plotters/spectral_signal/plot_specgram.py +30 -0
  69. figrecipe/_dev/demo_plotters/spectral_signal/plot_xcorr.py +25 -0
  70. figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
  71. figrecipe/_dev/demo_plotters/vector_flow/plot_barbs.py +30 -0
  72. figrecipe/_dev/demo_plotters/vector_flow/plot_quiver.py +30 -0
  73. figrecipe/_dev/demo_plotters/vector_flow/plot_streamplot.py +30 -0
  74. figrecipe/_editor/__init__.py +278 -0
  75. figrecipe/_editor/_bbox/__init__.py +43 -0
  76. figrecipe/_editor/_bbox/_collections.py +177 -0
  77. figrecipe/_editor/_bbox/_elements.py +159 -0
  78. figrecipe/_editor/_bbox/_extract.py +256 -0
  79. figrecipe/_editor/_bbox/_extract_axes.py +370 -0
  80. figrecipe/_editor/_bbox/_extract_text.py +342 -0
  81. figrecipe/_editor/_bbox/_lines.py +173 -0
  82. figrecipe/_editor/_bbox/_transforms.py +146 -0
  83. figrecipe/_editor/_flask_app.py +258 -0
  84. figrecipe/_editor/_helpers.py +242 -0
  85. figrecipe/_editor/_hitmap/__init__.py +76 -0
  86. figrecipe/_editor/_hitmap/_artists/__init__.py +21 -0
  87. figrecipe/_editor/_hitmap/_artists/_collections.py +345 -0
  88. figrecipe/_editor/_hitmap/_artists/_images.py +68 -0
  89. figrecipe/_editor/_hitmap/_artists/_lines.py +107 -0
  90. figrecipe/_editor/_hitmap/_artists/_patches.py +163 -0
  91. figrecipe/_editor/_hitmap/_artists/_text.py +190 -0
  92. figrecipe/_editor/_hitmap/_colors.py +181 -0
  93. figrecipe/_editor/_hitmap/_detect.py +137 -0
  94. figrecipe/_editor/_hitmap/_restore.py +154 -0
  95. figrecipe/_editor/_hitmap_main.py +182 -0
  96. figrecipe/_editor/_overrides.py +318 -0
  97. figrecipe/_editor/_preferences.py +135 -0
  98. figrecipe/_editor/_render_overrides.py +480 -0
  99. figrecipe/_editor/_renderer.py +199 -0
  100. figrecipe/_editor/_routes_axis.py +453 -0
  101. figrecipe/_editor/_routes_core.py +284 -0
  102. figrecipe/_editor/_routes_element.py +317 -0
  103. figrecipe/_editor/_routes_style.py +223 -0
  104. figrecipe/_editor/_templates/__init__.py +152 -0
  105. figrecipe/_editor/_templates/_html.py +502 -0
  106. figrecipe/_editor/_templates/_scripts/__init__.py +120 -0
  107. figrecipe/_editor/_templates/_scripts/_api.py +228 -0
  108. figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
  109. figrecipe/_editor/_templates/_scripts/_core.py +436 -0
  110. figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
  111. figrecipe/_editor/_templates/_scripts/_element_editor.py +310 -0
  112. figrecipe/_editor/_templates/_scripts/_files.py +195 -0
  113. figrecipe/_editor/_templates/_scripts/_hitmap.py +509 -0
  114. figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
  115. figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
  116. figrecipe/_editor/_templates/_scripts/_legend_drag.py +265 -0
  117. figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
  118. figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
  119. figrecipe/_editor/_templates/_scripts/_panel_drag.py +334 -0
  120. figrecipe/_editor/_templates/_scripts/_panel_position.py +279 -0
  121. figrecipe/_editor/_templates/_scripts/_selection.py +237 -0
  122. figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
  123. figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
  124. figrecipe/_editor/_templates/_scripts/_zoom.py +179 -0
  125. figrecipe/_editor/_templates/_styles/__init__.py +69 -0
  126. figrecipe/_editor/_templates/_styles/_base.py +64 -0
  127. figrecipe/_editor/_templates/_styles/_buttons.py +206 -0
  128. figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
  129. figrecipe/_editor/_templates/_styles/_controls.py +265 -0
  130. figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
  131. figrecipe/_editor/_templates/_styles/_forms.py +126 -0
  132. figrecipe/_editor/_templates/_styles/_hitmap.py +184 -0
  133. figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
  134. figrecipe/_editor/_templates/_styles/_labels.py +118 -0
  135. figrecipe/_editor/_templates/_styles/_modals.py +98 -0
  136. figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
  137. figrecipe/_editor/_templates/_styles/_preview.py +225 -0
  138. figrecipe/_editor/_templates/_styles/_selection.py +73 -0
  139. figrecipe/_params/_DECORATION_METHODS.py +33 -0
  140. figrecipe/_params/_PLOTTING_METHODS.py +58 -0
  141. figrecipe/_params/__init__.py +9 -0
  142. figrecipe/_recorder.py +92 -110
  143. figrecipe/_recorder_utils.py +124 -0
  144. figrecipe/_reproducer/__init__.py +18 -0
  145. figrecipe/_reproducer/_core.py +498 -0
  146. figrecipe/_reproducer/_custom_plots.py +279 -0
  147. figrecipe/_reproducer/_seaborn.py +100 -0
  148. figrecipe/_reproducer/_violin.py +186 -0
  149. figrecipe/_seaborn.py +14 -9
  150. figrecipe/_serializer.py +2 -2
  151. figrecipe/_signatures/README.md +68 -0
  152. figrecipe/_signatures/__init__.py +12 -2
  153. figrecipe/_signatures/_kwargs.py +273 -0
  154. figrecipe/_signatures/_loader.py +114 -57
  155. figrecipe/_signatures/_parsing.py +147 -0
  156. figrecipe/_utils/__init__.py +6 -4
  157. figrecipe/_utils/_crop.py +10 -4
  158. figrecipe/_utils/_image_diff.py +37 -33
  159. figrecipe/_utils/_numpy_io.py +0 -1
  160. figrecipe/_utils/_units.py +11 -3
  161. figrecipe/_validator.py +12 -3
  162. figrecipe/_wrappers/_axes.py +193 -170
  163. figrecipe/_wrappers/_axes_helpers.py +136 -0
  164. figrecipe/_wrappers/_axes_plots.py +418 -0
  165. figrecipe/_wrappers/_axes_seaborn.py +157 -0
  166. figrecipe/_wrappers/_figure.py +277 -18
  167. figrecipe/_wrappers/_panel_labels.py +127 -0
  168. figrecipe/_wrappers/_plot_helpers.py +143 -0
  169. figrecipe/_wrappers/_violin_helpers.py +180 -0
  170. figrecipe/plt.py +0 -1
  171. figrecipe/pyplot.py +2 -1
  172. figrecipe/styles/__init__.py +12 -11
  173. figrecipe/styles/_dotdict.py +72 -0
  174. figrecipe/styles/_finalize.py +134 -0
  175. figrecipe/styles/_fonts.py +77 -0
  176. figrecipe/styles/_kwargs_converter.py +178 -0
  177. figrecipe/styles/_plot_styles.py +209 -0
  178. figrecipe/styles/_style_applier.py +60 -202
  179. figrecipe/styles/_style_loader.py +73 -121
  180. figrecipe/styles/_themes.py +151 -0
  181. figrecipe/styles/presets/MATPLOTLIB.yaml +95 -0
  182. figrecipe/styles/presets/SCITEX.yaml +181 -0
  183. figrecipe-0.7.4.dist-info/METADATA +429 -0
  184. figrecipe-0.7.4.dist-info/RECORD +188 -0
  185. figrecipe/_reproducer.py +0 -358
  186. figrecipe-0.5.0.dist-info/METADATA +0 -336
  187. figrecipe-0.5.0.dist-info/RECORD +0 -26
  188. {figrecipe-0.5.0.dist-info → figrecipe-0.7.4.dist-info}/WHEEL +0 -0
  189. {figrecipe-0.5.0.dist-info → figrecipe-0.7.4.dist-info}/licenses/LICENSE +0 -0
figrecipe/_reproducer.py DELETED
@@ -1,358 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """Reproduce figures from recipe files."""
4
-
5
- from pathlib import Path
6
- from typing import Any, Dict, List, Optional, Tuple, Union
7
-
8
- import matplotlib.pyplot as plt
9
- import numpy as np
10
- from matplotlib.axes import Axes
11
- from matplotlib.figure import Figure
12
-
13
- from ._recorder import FigureRecord, CallRecord
14
- from ._serializer import load_recipe
15
-
16
-
17
- def reproduce(
18
- path: Union[str, Path],
19
- calls: Optional[List[str]] = None,
20
- skip_decorations: bool = False,
21
- ) -> Tuple[Figure, Union[Axes, List[Axes]]]:
22
- """Reproduce a figure from a recipe file.
23
-
24
- Parameters
25
- ----------
26
- path : str or Path
27
- Path to .yaml recipe file.
28
- calls : list of str, optional
29
- If provided, only reproduce these specific call IDs.
30
- skip_decorations : bool
31
- If True, skip decoration calls (labels, legends, etc.).
32
-
33
- Returns
34
- -------
35
- fig : matplotlib.figure.Figure
36
- Reproduced figure.
37
- axes : Axes or list of Axes
38
- Reproduced axes (single if 1x1, otherwise list).
39
-
40
- Examples
41
- --------
42
- >>> import figrecipe as ps
43
- >>> fig, ax = ps.reproduce("experiment_001.yaml")
44
- >>> plt.show()
45
- """
46
- record = load_recipe(path)
47
- return reproduce_from_record(
48
- record,
49
- calls=calls,
50
- skip_decorations=skip_decorations,
51
- )
52
-
53
-
54
- def reproduce_from_record(
55
- record: FigureRecord,
56
- calls: Optional[List[str]] = None,
57
- skip_decorations: bool = False,
58
- ) -> Tuple[Figure, Union[Axes, List[Axes]]]:
59
- """Reproduce a figure from a FigureRecord.
60
-
61
- Parameters
62
- ----------
63
- record : FigureRecord
64
- The figure record to reproduce.
65
- calls : list of str, optional
66
- If provided, only reproduce these specific call IDs.
67
- skip_decorations : bool
68
- If True, skip decoration calls.
69
-
70
- Returns
71
- -------
72
- fig : matplotlib.figure.Figure
73
- Reproduced figure.
74
- axes : Axes or list of Axes
75
- Reproduced axes.
76
- """
77
- # Determine grid size from axes positions
78
- max_row = 0
79
- max_col = 0
80
- for ax_key in record.axes.keys():
81
- parts = ax_key.split("_")
82
- if len(parts) >= 3:
83
- max_row = max(max_row, int(parts[1]))
84
- max_col = max(max_col, int(parts[2]))
85
-
86
- nrows = max_row + 1
87
- ncols = max_col + 1
88
-
89
- # Create figure
90
- fig, mpl_axes = plt.subplots(
91
- nrows,
92
- ncols,
93
- figsize=record.figsize,
94
- dpi=record.dpi,
95
- constrained_layout=record.constrained_layout,
96
- )
97
-
98
- # Apply layout if recorded
99
- if record.layout is not None:
100
- fig.subplots_adjust(**record.layout)
101
-
102
- # Ensure axes is 2D array
103
- if nrows == 1 and ncols == 1:
104
- axes_2d = np.array([[mpl_axes]])
105
- else:
106
- axes_2d = np.atleast_2d(mpl_axes)
107
- if nrows == 1:
108
- axes_2d = axes_2d.reshape(1, -1)
109
- elif ncols == 1:
110
- axes_2d = axes_2d.reshape(-1, 1)
111
-
112
- # Apply style BEFORE replaying calls (to match original order:
113
- # style is applied during subplots(), then user creates plots/decorations)
114
- if record.style is not None:
115
- from .styles import apply_style_mm
116
- for row in range(nrows):
117
- for col in range(ncols):
118
- apply_style_mm(axes_2d[row, col], record.style)
119
-
120
- # Replay calls on each axes
121
- for ax_key, ax_record in record.axes.items():
122
- parts = ax_key.split("_")
123
- if len(parts) >= 3:
124
- row, col = int(parts[1]), int(parts[2])
125
- else:
126
- row, col = 0, 0
127
-
128
- ax = axes_2d[row, col]
129
-
130
- # Replay plotting calls
131
- for call in ax_record.calls:
132
- if calls is not None and call.id not in calls:
133
- continue
134
- _replay_call(ax, call)
135
-
136
- # Replay decorations
137
- if not skip_decorations:
138
- for call in ax_record.decorations:
139
- if calls is not None and call.id not in calls:
140
- continue
141
- _replay_call(ax, call)
142
-
143
- # Return in appropriate format
144
- if nrows == 1 and ncols == 1:
145
- return fig, axes_2d[0, 0]
146
- elif nrows == 1:
147
- return fig, list(axes_2d[0])
148
- elif ncols == 1:
149
- return fig, list(axes_2d[:, 0])
150
- else:
151
- return fig, axes_2d.tolist()
152
-
153
-
154
- def _replay_call(ax: Axes, call: CallRecord) -> Any:
155
- """Replay a single call on an axes.
156
-
157
- Parameters
158
- ----------
159
- ax : Axes
160
- The matplotlib axes.
161
- call : CallRecord
162
- The call to replay.
163
-
164
- Returns
165
- -------
166
- Any
167
- Result of the matplotlib call.
168
- """
169
- method_name = call.function
170
-
171
- # Check if it's a seaborn call
172
- if method_name.startswith("sns."):
173
- return _replay_seaborn_call(ax, call)
174
-
175
- method = getattr(ax, method_name, None)
176
-
177
- if method is None:
178
- # Method not found, skip
179
- return None
180
-
181
- # Reconstruct args
182
- args = []
183
- for arg_data in call.args:
184
- value = _reconstruct_value(arg_data)
185
- args.append(value)
186
-
187
- # Get kwargs
188
- kwargs = call.kwargs.copy()
189
-
190
- # Call the method
191
- try:
192
- return method(*args, **kwargs)
193
- except Exception as e:
194
- # Log warning but continue
195
- import warnings
196
- warnings.warn(f"Failed to replay {method_name}: {e}")
197
- return None
198
-
199
-
200
- def _replay_seaborn_call(ax: Axes, call: CallRecord) -> Any:
201
- """Replay a seaborn call on an axes.
202
-
203
- Parameters
204
- ----------
205
- ax : Axes
206
- The matplotlib axes.
207
- call : CallRecord
208
- The seaborn call to replay.
209
-
210
- Returns
211
- -------
212
- Any
213
- Result of the seaborn call.
214
- """
215
- try:
216
- import seaborn as sns
217
- import pandas as pd
218
- except ImportError:
219
- import warnings
220
- warnings.warn("seaborn/pandas required to replay seaborn calls")
221
- return None
222
-
223
- # Get the seaborn function name (remove "sns." prefix)
224
- func_name = call.function[4:] # Remove "sns."
225
- func = getattr(sns, func_name, None)
226
-
227
- if func is None:
228
- import warnings
229
- warnings.warn(f"Seaborn function {func_name} not found")
230
- return None
231
-
232
- # Reconstruct data from args
233
- # Args contain column data with "param" field indicating the parameter name
234
- data_dict = {}
235
- param_mapping = {} # Maps param name to column name
236
-
237
- for arg_data in call.args:
238
- param = arg_data.get("param")
239
- name = arg_data.get("name")
240
- value = _reconstruct_value(arg_data)
241
-
242
- if param is not None:
243
- # This is a DataFrame column
244
- col_name = name if name else param
245
- data_dict[col_name] = value
246
- param_mapping[param] = col_name
247
-
248
- # Build kwargs
249
- kwargs = call.kwargs.copy()
250
-
251
- # Remove internal keys
252
- internal_keys = [k for k in kwargs.keys() if k.startswith("_")]
253
- for key in internal_keys:
254
- kwargs.pop(key, None)
255
-
256
- # If we have data columns, create a DataFrame
257
- if data_dict:
258
- df = pd.DataFrame(data_dict)
259
- kwargs["data"] = df
260
-
261
- # Update column name references in kwargs
262
- for param, col_name in param_mapping.items():
263
- if param in ["x", "y", "hue", "size", "style", "row", "col"]:
264
- kwargs[param] = col_name
265
-
266
- # Add the axes
267
- kwargs["ax"] = ax
268
-
269
- # Convert certain list parameters back to tuples (YAML serializes tuples as lists)
270
- # 'sizes' in seaborn expects a tuple (min, max) for range, not a list
271
- if "sizes" in kwargs and isinstance(kwargs["sizes"], list):
272
- kwargs["sizes"] = tuple(kwargs["sizes"])
273
-
274
- # Call the seaborn function
275
- try:
276
- return func(**kwargs)
277
- except Exception as e:
278
- import warnings
279
- warnings.warn(f"Failed to replay sns.{func_name}: {e}")
280
- return None
281
-
282
-
283
- def _reconstruct_value(arg_data: Dict[str, Any]) -> Any:
284
- """Reconstruct a value from serialized arg data.
285
-
286
- Parameters
287
- ----------
288
- arg_data : dict
289
- Serialized argument data.
290
-
291
- Returns
292
- -------
293
- Any
294
- Reconstructed value.
295
- """
296
- # Check if we have a pre-loaded array
297
- if "_loaded_array" in arg_data:
298
- return arg_data["_loaded_array"]
299
-
300
- data = arg_data.get("data")
301
-
302
- # If data is a list, convert to numpy array
303
- if isinstance(data, list):
304
- dtype = arg_data.get("dtype")
305
- try:
306
- return np.array(data, dtype=dtype if dtype else None)
307
- except (TypeError, ValueError):
308
- return np.array(data)
309
-
310
- return data
311
-
312
-
313
- def get_recipe_info(path: Union[str, Path]) -> Dict[str, Any]:
314
- """Get information about a recipe without reproducing.
315
-
316
- Parameters
317
- ----------
318
- path : str or Path
319
- Path to .yaml recipe file.
320
-
321
- Returns
322
- -------
323
- dict
324
- Recipe information including:
325
- - id: Figure ID
326
- - created: Creation timestamp
327
- - matplotlib_version: Version used
328
- - figsize: Figure size
329
- - n_axes: Number of axes
330
- - calls: List of call IDs
331
- """
332
- record = load_recipe(path)
333
-
334
- all_calls = []
335
- for ax_record in record.axes.values():
336
- for call in ax_record.calls:
337
- all_calls.append({
338
- "id": call.id,
339
- "function": call.function,
340
- "n_args": len(call.args),
341
- "kwargs": list(call.kwargs.keys()),
342
- })
343
- for call in ax_record.decorations:
344
- all_calls.append({
345
- "id": call.id,
346
- "function": call.function,
347
- "type": "decoration",
348
- })
349
-
350
- return {
351
- "id": record.id,
352
- "created": record.created,
353
- "matplotlib_version": record.matplotlib_version,
354
- "figsize": record.figsize,
355
- "dpi": record.dpi,
356
- "n_axes": len(record.axes),
357
- "calls": all_calls,
358
- }
@@ -1,336 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: figrecipe
3
- Version: 0.5.0
4
- Summary: Reproducible matplotlib wrapper with mm-precision layouts
5
- Project-URL: Homepage, https://github.com/ywatanabe1989/figrecipe
6
- Project-URL: Documentation, https://github.com/ywatanabe1989/figrecipe#readme
7
- Project-URL: Repository, https://github.com/ywatanabe1989/figrecipe.git
8
- Project-URL: Issues, https://github.com/ywatanabe1989/figrecipe/issues
9
- Author-email: Yusuke Watanabe <ywatanabe@scitex.ai>
10
- License-Expression: AGPL-3.0
11
- License-File: LICENSE
12
- Keywords: matplotlib,millimeter,plotting,publication,recipe,reproducibility,scientific,visualization,yaml
13
- Classifier: Development Status :: 4 - Beta
14
- Classifier: Intended Audience :: Science/Research
15
- Classifier: License :: OSI Approved :: GNU Affero General Public License v3
16
- Classifier: Operating System :: OS Independent
17
- Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.9
19
- Classifier: Programming Language :: Python :: 3.10
20
- Classifier: Programming Language :: Python :: 3.11
21
- Classifier: Programming Language :: Python :: 3.12
22
- Classifier: Topic :: Scientific/Engineering :: Visualization
23
- Requires-Python: >=3.9
24
- Requires-Dist: matplotlib>=3.5.0
25
- Requires-Dist: numpy>=1.20.0
26
- Requires-Dist: ruamel-yaml>=0.17.0
27
- Provides-Extra: all
28
- Requires-Dist: pandas>=1.3.0; extra == 'all'
29
- Requires-Dist: pillow>=9.0.0; extra == 'all'
30
- Requires-Dist: pytest-cov>=4.0.0; extra == 'all'
31
- Requires-Dist: pytest>=7.0.0; extra == 'all'
32
- Requires-Dist: seaborn>=0.12.0; extra == 'all'
33
- Provides-Extra: dev
34
- Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
35
- Requires-Dist: pytest>=7.0.0; extra == 'dev'
36
- Provides-Extra: imaging
37
- Requires-Dist: pillow>=9.0.0; extra == 'imaging'
38
- Provides-Extra: seaborn
39
- Requires-Dist: pandas>=1.3.0; extra == 'seaborn'
40
- Requires-Dist: seaborn>=0.12.0; extra == 'seaborn'
41
- Description-Content-Type: text/markdown
42
-
43
- <!-- ---
44
- !-- Timestamp: 2025-12-22 13:01:11
45
- !-- Author: ywatanabe
46
- !-- File: /home/ywatanabe/proj/figrecipe/README.md
47
- !-- --- -->
48
-
49
- <p align="center">
50
- <img src="docs/figrecipe_logo.png" alt="figrecipe logo" width="200"/>
51
- </p>
52
-
53
- # FigRecipe
54
-
55
- **Reproducible matplotlib figures with mm-precision layouts.**
56
- FigRecipe is a lightweight recording & reproduction layer for matplotlib,
57
- designed for scientific figures that must remain **editable, inspectable,
58
- and reproducible**.
59
-
60
- Part of **SciTeX™ – Research OS for reproducible science**
61
- https://scitex.ai
62
-
63
- [![PyPI version](https://badge.fury.io/py/figrecipe.svg)](https://badge.fury.io/py/figrecipe)
64
- [![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
65
-
66
- ---
67
-
68
- ## Why FigRecipe?
69
-
70
- In scientific workflows, figures are often:
71
- - hard to reproduce once scripts change,
72
- - resized manually in pixels or inches,
73
- - impossible to partially reuse or inspect later.
74
-
75
- **FigRecipe solves this by recording plotting calls as a structured “recipe”**,
76
- allowing figures to be:
77
- - faithfully reproduced,
78
- - partially re-rendered,
79
- - inspected for underlying data,
80
- - laid out in **exact millimeters** for publication.
81
-
82
- ---
83
-
84
- ## Key Features
85
-
86
- - ✅ Drop-in replacement for `matplotlib.pyplot` (use `fr.subplots()` to enable recording)
87
- - ✅ Automatic recording of plotting calls
88
- - ✅ Reproduce figures from a YAML recipe
89
- - ✅ Extract plotted data programmatically
90
- - ✅ Selective reproduction of specific plots
91
- - ✅ Millimeter-based layout (journal-ready)
92
- - ✅ Publication-quality style presets
93
- - ✅ Dark theme support (data colors preserved)
94
- - ✅ Seamless seaborn integration
95
- - ✅ Crop figures to content with mm margins
96
-
97
- ---
98
-
99
- ## Installation
100
-
101
- ```bash
102
- pip install figrecipe
103
-
104
- # Optional extras
105
- pip install figrecipe[seaborn] # seaborn + pandas support
106
- pip install figrecipe[imaging] # image cropping (Pillow)
107
- pip install figrecipe[all] # all extras
108
-
109
- # Optional: for PDF export from notebooks (SVG → PDF)
110
- sudo apt install inkscape # Linux
111
- brew install inkscape # macOS
112
- ```
113
-
114
- **Requirements:** Python >= 3.9
115
-
116
- ## Basic Usage
117
-
118
- ### Recording & Saving
119
-
120
- ``` python
121
- import figrecipe as fr
122
- import numpy as np
123
-
124
- x = np.linspace(0, 10, 100)
125
- y = np.sin(x)
126
-
127
- fig, ax = fr.subplots()
128
- ax.plot(x, y, color='red', linewidth=2, id='sine_wave')
129
- ax.set_xlabel('Time (s)')
130
- ax.set_ylabel('Amplitude')
131
-
132
- # Save image + recipe
133
- img_path, yaml_path, result = fr.save(fig, 'figure.png')
134
- # → creates: figure.png + figure.yaml
135
- ```
136
-
137
- ### Reproducing a Figure
138
-
139
- ``` python
140
- import figrecipe as fr
141
-
142
- fig, ax = fr.reproduce('figure.yaml')
143
- ```
144
-
145
- ### Extracting Plotted Data
146
-
147
- ``` python
148
- import figrecipe as fr
149
-
150
- data = fr.extract_data('figure.yaml')
151
- # {'sine_wave': {'x': array([...]), 'y': array([...])}}
152
- ```
153
-
154
- ### Millimeter-Based Layout (Publication-Ready)
155
-
156
- ``` python
157
- fig, ax = fr.subplots(
158
- axes_width_mm=60,
159
- axes_height_mm=40,
160
- margin_left_mm=15,
161
- margin_bottom_mm=12,
162
- )
163
- ```
164
- This guarantees consistent sizing across editors, exports, and journals.
165
-
166
- ### Style Presets
167
-
168
- ``` python
169
- fr.list_presets()
170
- # ['MATPLOTLIB', 'SCITEX']
171
-
172
- # Publication-quality preset (applied globally)
173
- fr.load_style('SCITEX')
174
- fig, ax = fr.subplots()
175
-
176
- # Dark theme (UI-only, data colors preserved)
177
- fr.load_style('SCITEX_DARK')
178
- # or: fr.load_style('SCITEX', dark=True)
179
-
180
- # Custom style
181
- fr.load_style('/path/to/my_style.yaml')
182
- ```
183
-
184
- See docs/EXAMPLE_RECIPE.yaml for a full style template.
185
-
186
- ### Recipe Format (YAML)
187
-
188
- ``` yaml
189
- # Timestamp: "2025-12-22 11:53:14 (ywatanabe)"
190
- # File: ./docs/EXAMPLE_RECIPE.yaml
191
- # MATPLOTLIB Style Preset
192
- # =======================
193
- # Vanilla matplotlib defaults - minimal customization.
194
- # Use this to reset to standard matplotlib behavior.
195
-
196
- axes:
197
- width_mm: null # Use matplotlib default (auto)
198
- height_mm: null # Use matplotlib default (auto)
199
- thickness_mm: null # Use matplotlib default
200
-
201
- margins:
202
- left_mm: null
203
- right_mm: null
204
- bottom_mm: null
205
- top_mm: null
206
-
207
- spacing:
208
- horizontal_mm: null
209
- vertical_mm: null
210
-
211
- fonts:
212
- family: null # matplotlib default (DejaVu Sans)
213
- axis_label_pt: null
214
- tick_label_pt: null
215
- title_pt: null
216
- suptitle_pt: null
217
- legend_pt: null
218
- annotation_pt: null
219
-
220
- padding:
221
- label_pt: null
222
- tick_pt: null
223
- title_pt: null
224
-
225
- lines:
226
- trace_mm: null
227
- errorbar_mm: null
228
- errorbar_cap_mm: null
229
-
230
- ticks:
231
- length_mm: null
232
- thickness_mm: null
233
- direction: null
234
- n_ticks: null
235
-
236
- markers:
237
- size_mm: null
238
- scatter_mm: null
239
- edge_width_mm: null
240
-
241
- legend:
242
- frameon: null # matplotlib default (True)
243
- bg: null # matplotlib default
244
- edgecolor: null # matplotlib default
245
- alpha: null # matplotlib default
246
- loc: null # matplotlib default
247
-
248
- output:
249
- dpi: 300
250
- transparent: false
251
- format: "png"
252
-
253
- behavior:
254
- auto_scale_axes: false
255
- hide_top_spine: false
256
- hide_right_spine: false
257
- grid: false
258
-
259
- theme:
260
- mode: "light"
261
- dark:
262
- figure_bg: "#1e1e1e"
263
- axes_bg: "#1e1e1e"
264
- legend_bg: "#1e1e1e"
265
- text: "#d4d4d4"
266
- spine: "#3c3c3c"
267
- tick: "#d4d4d4"
268
- grid: "#3a3a3a"
269
- light:
270
- figure_bg: "white"
271
- axes_bg: "white"
272
- legend_bg: "white"
273
- text: "black"
274
- spine: "black"
275
- tick: "black"
276
- grid: "#cccccc"
277
-
278
- # Standard matplotlib color cycle (tab10)
279
- colors:
280
- palette:
281
- - [31, 119, 180] # tab:blue
282
- - [255, 127, 14] # tab:orange
283
- - [44, 160, 44] # tab:green
284
- - [214, 39, 40] # tab:red
285
- - [148, 103, 189] # tab:purple
286
- - [140, 86, 75] # tab:brown
287
- - [227, 119, 194] # tab:pink
288
- - [127, 127, 127] # tab:gray
289
- - [188, 189, 34] # tab:olive
290
- - [23, 190, 207] # tab:cyan
291
-
292
- # EOF
293
- ```
294
-
295
- The recipe is human-readable, version-controllable, and inspectable.
296
-
297
-
298
- ### API Overview
299
-
300
- | Function | Description |
301
- | ----------------------------- | --------------------------------- |
302
- | `fr.subplots()` | Create a recording-enabled figure |
303
- | `fr.save(fig, 'fig.png')` | Save image + recipe |
304
- | `fr.reproduce('fig.yaml')` | Reproduce figure from recipe |
305
- | `fr.extract_data('fig.yaml')` | Extract plotted data |
306
- | `fr.info('fig.yaml')` | Inspect recipe metadata |
307
- | `fr.load_style()` | Load style preset (global) |
308
- | `fr.list_presets()` | List available presets |
309
- | `fr.crop('fig.png')` | Crop to content with mm margin |
310
-
311
-
312
- ### Examples
313
- - 📓 Interactive demo notebook:
314
- examples/figrecipe_demo.ipynb
315
-
316
- - 🌐 View on nbviewer:
317
- https://nbviewer.org/github/ywatanabe1989/figrecipe/blob/main/examples/figrecipe_demo.ipynb
318
-
319
- The notebook includes side-by-side comparisons of original and reproduced figures.
320
-
321
- ## Positioning
322
-
323
- FigRecipe is intentionally minimal.
324
- It focuses on recording, reproduction, and layout fidelity.
325
-
326
- Higher-level workflows (figures, tables, statistics, GUIs) are handled in:
327
-
328
- FTS (Figure-Table-Statistics Bundle) in SciTeX ecosystem (https://scitex.ai/vis/)
329
-
330
-
331
- ## License
332
-
333
- AGPL-3.0 See [LICENSE](LICENSE)
334
- .
335
-
336
- <!-- EOF -->
@@ -1,26 +0,0 @@
1
- figrecipe/__init__.py,sha256=X9RnSndUi5ZC_JW6ReBjvYD84kjK8D4FNvB5QjekFg4,35296
2
- figrecipe/_recorder.py,sha256=mPjhc6ZaUWrzW3lBxeB_epf-ESHLj05sKs_zeHkZ9_Y,14290
3
- figrecipe/_reproducer.py,sha256=IQNYJ8OcvGRKNRLe561yqtmWgIJtzkx6okCes1xIHsk,9683
4
- figrecipe/_seaborn.py,sha256=dRQgIbJG9Xeh1DYCDNrgaXgQe4MsnukEXFeEolWnfeo,8348
5
- figrecipe/_serializer.py,sha256=OpWbCRSlhMuA2KInRXvze-g9V_d5ToLUIEmTj-jquYs,6267
6
- figrecipe/_validator.py,sha256=dFZez0J4f8V9wUnrMaz_CV-TlndBDcHHbZO4mRDC6Po,5567
7
- figrecipe/plt.py,sha256=GkURHdcupAVvpWKgTCC23jn3OjmKQmTY09A9wGMUdDQ,308
8
- figrecipe/pyplot.py,sha256=xw-wFa6N7wJ-A3ThLbQvhafwySNIqD05u9UupksY7S4,5685
9
- figrecipe/_signatures/__init__.py,sha256=twHKZ8o_9Ho4k0SD-cff72_aaWevK0noGlWav5fauHg,244
10
- figrecipe/_signatures/_loader.py,sha256=HCrVzQNWqqt6qSr13G_A1461-v94g65usfIRJ4_zmJk,4744
11
- figrecipe/_utils/__init__.py,sha256=usLkRQJ6oZJSUSzZWkb1x-lkAdHO9I1g7VinLf2kz24,815
12
- figrecipe/_utils/_crop.py,sha256=YQgaYGI3gAFRF0zkQeUOlTRs9vsBmAK_24vooPPcPPc,8385
13
- figrecipe/_utils/_diff.py,sha256=cjRXjMrfXFKz6uZQOZ-XYifc0mzmHZyRXn_LY13K1SY,2354
14
- figrecipe/_utils/_image_diff.py,sha256=9648vx2f_K628YjCB-bhCOsRws4qqWuUk2l4uJlpdrc,5406
15
- figrecipe/_utils/_numpy_io.py,sha256=leckngCnZKTvceM3RfUq1P9TA6z1SibfzWqlhKtOaxY,4533
16
- figrecipe/_utils/_units.py,sha256=94e0MaJ27WkF7zDqNL-P7adbve3qWitPzWrCYOuDBe8,4628
17
- figrecipe/_wrappers/__init__.py,sha256=CcBUID5G6Mo_bNGpCaz5vbfB8CG7XtF-9lOm7kShQPw,214
18
- figrecipe/_wrappers/_axes.py,sha256=gLymbk5WYBdaJ8pVnSJH4X8SMs_foNJIDRN4pDeP6To,10659
19
- figrecipe/_wrappers/_figure.py,sha256=ZSCH5jVgZbb9kgXyj_LumV52v5vspmRQ3N1KzY37CaQ,6491
20
- figrecipe/styles/__init__.py,sha256=vGKtlJkUi06i0QC4qEw3ebUwpxTgablFUlyABSYr1Co,976
21
- figrecipe/styles/_style_applier.py,sha256=f0iPzV5IF2yy94yirVYD9hd7op1EDYQqo8xcL2qBZVU,14238
22
- figrecipe/styles/_style_loader.py,sha256=4LazXQzZRmRsWj3TvtDgCXP_Mp5nJca9bXmMH7LnsHw,13342
23
- figrecipe-0.5.0.dist-info/METADATA,sha256=WbJp4fSSIse1uB_1c1ar2KL3ktYBVznKA_78fYFusFk,8924
24
- figrecipe-0.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
25
- figrecipe-0.5.0.dist-info/licenses/LICENSE,sha256=TfPDBt3ar0uv_f9cqCDMZ5rIzW3CY8anRRd4PkL6ejs,34522
26
- figrecipe-0.5.0.dist-info/RECORD,,