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.
- figrecipe/__init__.py +220 -819
- figrecipe/_api/__init__.py +48 -0
- figrecipe/_api/_extract.py +108 -0
- figrecipe/_api/_notebook.py +61 -0
- figrecipe/_api/_panel.py +46 -0
- figrecipe/_api/_save.py +191 -0
- figrecipe/_api/_seaborn_proxy.py +34 -0
- figrecipe/_api/_style_manager.py +153 -0
- figrecipe/_api/_subplots.py +333 -0
- figrecipe/_api/_validate.py +82 -0
- figrecipe/_dev/__init__.py +29 -0
- figrecipe/_dev/_plotters.py +76 -0
- figrecipe/_dev/_run_demos.py +56 -0
- figrecipe/_dev/demo_plotters/__init__.py +64 -0
- figrecipe/_dev/demo_plotters/_categories.py +81 -0
- figrecipe/_dev/demo_plotters/_figure_creators.py +119 -0
- figrecipe/_dev/demo_plotters/_helpers.py +31 -0
- figrecipe/_dev/demo_plotters/_registry.py +50 -0
- figrecipe/_dev/demo_plotters/bar_categorical/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/bar_categorical/plot_bar.py +25 -0
- figrecipe/_dev/demo_plotters/bar_categorical/plot_barh.py +25 -0
- figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_contour.py +30 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_contourf.py +29 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_tricontour.py +28 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_tricontourf.py +28 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_tripcolor.py +29 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_triplot.py +25 -0
- figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/distribution/plot_boxplot.py +24 -0
- figrecipe/_dev/demo_plotters/distribution/plot_ecdf.py +24 -0
- figrecipe/_dev/demo_plotters/distribution/plot_hist.py +24 -0
- figrecipe/_dev/demo_plotters/distribution/plot_hist2d.py +25 -0
- figrecipe/_dev/demo_plotters/distribution/plot_violinplot.py +25 -0
- figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_hexbin.py +25 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_imshow.py +23 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_matshow.py +23 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_pcolor.py +29 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_pcolormesh.py +29 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_spy.py +29 -0
- figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_errorbar.py +28 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_fill.py +29 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_fill_between.py +30 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_fill_betweenx.py +28 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_plot.py +28 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_stackplot.py +29 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_stairs.py +27 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_step.py +27 -0
- figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/scatter_points/plot_scatter.py +24 -0
- figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/special/plot_eventplot.py +25 -0
- figrecipe/_dev/demo_plotters/special/plot_loglog.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_pie.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_semilogx.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_semilogy.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_stem.py +27 -0
- figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_acorr.py +24 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_angle_spectrum.py +28 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_cohere.py +29 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_csd.py +29 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_magnitude_spectrum.py +28 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_phase_spectrum.py +28 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_psd.py +29 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_specgram.py +30 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_xcorr.py +25 -0
- figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/vector_flow/plot_barbs.py +30 -0
- figrecipe/_dev/demo_plotters/vector_flow/plot_quiver.py +30 -0
- figrecipe/_dev/demo_plotters/vector_flow/plot_streamplot.py +30 -0
- figrecipe/_editor/__init__.py +278 -0
- figrecipe/_editor/_bbox/__init__.py +43 -0
- figrecipe/_editor/_bbox/_collections.py +177 -0
- figrecipe/_editor/_bbox/_elements.py +159 -0
- figrecipe/_editor/_bbox/_extract.py +256 -0
- figrecipe/_editor/_bbox/_extract_axes.py +370 -0
- figrecipe/_editor/_bbox/_extract_text.py +342 -0
- figrecipe/_editor/_bbox/_lines.py +173 -0
- figrecipe/_editor/_bbox/_transforms.py +146 -0
- figrecipe/_editor/_flask_app.py +258 -0
- figrecipe/_editor/_helpers.py +242 -0
- figrecipe/_editor/_hitmap/__init__.py +76 -0
- figrecipe/_editor/_hitmap/_artists/__init__.py +21 -0
- figrecipe/_editor/_hitmap/_artists/_collections.py +345 -0
- figrecipe/_editor/_hitmap/_artists/_images.py +68 -0
- figrecipe/_editor/_hitmap/_artists/_lines.py +107 -0
- figrecipe/_editor/_hitmap/_artists/_patches.py +163 -0
- figrecipe/_editor/_hitmap/_artists/_text.py +190 -0
- figrecipe/_editor/_hitmap/_colors.py +181 -0
- figrecipe/_editor/_hitmap/_detect.py +137 -0
- figrecipe/_editor/_hitmap/_restore.py +154 -0
- figrecipe/_editor/_hitmap_main.py +182 -0
- figrecipe/_editor/_overrides.py +318 -0
- figrecipe/_editor/_preferences.py +135 -0
- figrecipe/_editor/_render_overrides.py +480 -0
- figrecipe/_editor/_renderer.py +199 -0
- figrecipe/_editor/_routes_axis.py +453 -0
- figrecipe/_editor/_routes_core.py +284 -0
- figrecipe/_editor/_routes_element.py +317 -0
- figrecipe/_editor/_routes_style.py +223 -0
- figrecipe/_editor/_templates/__init__.py +152 -0
- figrecipe/_editor/_templates/_html.py +502 -0
- figrecipe/_editor/_templates/_scripts/__init__.py +120 -0
- figrecipe/_editor/_templates/_scripts/_api.py +228 -0
- figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
- figrecipe/_editor/_templates/_scripts/_core.py +436 -0
- figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
- figrecipe/_editor/_templates/_scripts/_element_editor.py +310 -0
- figrecipe/_editor/_templates/_scripts/_files.py +195 -0
- figrecipe/_editor/_templates/_scripts/_hitmap.py +509 -0
- figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
- figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
- figrecipe/_editor/_templates/_scripts/_legend_drag.py +265 -0
- figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
- figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag.py +334 -0
- figrecipe/_editor/_templates/_scripts/_panel_position.py +279 -0
- figrecipe/_editor/_templates/_scripts/_selection.py +237 -0
- figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
- figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
- figrecipe/_editor/_templates/_scripts/_zoom.py +179 -0
- figrecipe/_editor/_templates/_styles/__init__.py +69 -0
- figrecipe/_editor/_templates/_styles/_base.py +64 -0
- figrecipe/_editor/_templates/_styles/_buttons.py +206 -0
- figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
- figrecipe/_editor/_templates/_styles/_controls.py +265 -0
- figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
- figrecipe/_editor/_templates/_styles/_forms.py +126 -0
- figrecipe/_editor/_templates/_styles/_hitmap.py +184 -0
- figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
- figrecipe/_editor/_templates/_styles/_labels.py +118 -0
- figrecipe/_editor/_templates/_styles/_modals.py +98 -0
- figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
- figrecipe/_editor/_templates/_styles/_preview.py +225 -0
- figrecipe/_editor/_templates/_styles/_selection.py +73 -0
- figrecipe/_params/_DECORATION_METHODS.py +33 -0
- figrecipe/_params/_PLOTTING_METHODS.py +58 -0
- figrecipe/_params/__init__.py +9 -0
- figrecipe/_recorder.py +92 -110
- figrecipe/_recorder_utils.py +124 -0
- figrecipe/_reproducer/__init__.py +18 -0
- figrecipe/_reproducer/_core.py +498 -0
- figrecipe/_reproducer/_custom_plots.py +279 -0
- figrecipe/_reproducer/_seaborn.py +100 -0
- figrecipe/_reproducer/_violin.py +186 -0
- figrecipe/_seaborn.py +14 -9
- figrecipe/_serializer.py +2 -2
- figrecipe/_signatures/README.md +68 -0
- figrecipe/_signatures/__init__.py +12 -2
- figrecipe/_signatures/_kwargs.py +273 -0
- figrecipe/_signatures/_loader.py +114 -57
- figrecipe/_signatures/_parsing.py +147 -0
- 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 +193 -170
- figrecipe/_wrappers/_axes_helpers.py +136 -0
- figrecipe/_wrappers/_axes_plots.py +418 -0
- figrecipe/_wrappers/_axes_seaborn.py +157 -0
- figrecipe/_wrappers/_figure.py +277 -18
- figrecipe/_wrappers/_panel_labels.py +127 -0
- figrecipe/_wrappers/_plot_helpers.py +143 -0
- figrecipe/_wrappers/_violin_helpers.py +180 -0
- figrecipe/plt.py +0 -1
- figrecipe/pyplot.py +2 -1
- figrecipe/styles/__init__.py +12 -11
- figrecipe/styles/_dotdict.py +72 -0
- figrecipe/styles/_finalize.py +134 -0
- figrecipe/styles/_fonts.py +77 -0
- figrecipe/styles/_kwargs_converter.py +178 -0
- figrecipe/styles/_plot_styles.py +209 -0
- figrecipe/styles/_style_applier.py +60 -202
- figrecipe/styles/_style_loader.py +73 -121
- figrecipe/styles/_themes.py +151 -0
- figrecipe/styles/presets/MATPLOTLIB.yaml +95 -0
- figrecipe/styles/presets/SCITEX.yaml +181 -0
- figrecipe-0.7.4.dist-info/METADATA +429 -0
- figrecipe-0.7.4.dist-info/RECORD +188 -0
- figrecipe/_reproducer.py +0 -358
- 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.7.4.dist-info}/WHEEL +0 -0
- {figrecipe-0.5.0.dist-info → figrecipe-0.7.4.dist-info}/licenses/LICENSE +0 -0
figrecipe/_wrappers/_axes.py
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""Wrapped Axes that records all plotting calls."""
|
|
4
4
|
|
|
5
|
-
from typing import Any, Dict,
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
|
-
import matplotlib.pyplot as plt
|
|
9
8
|
from matplotlib.axes import Axes
|
|
10
9
|
|
|
11
10
|
if TYPE_CHECKING:
|
|
@@ -35,6 +34,11 @@ class RecordingAxes:
|
|
|
35
34
|
>>> # The call is recorded automatically
|
|
36
35
|
"""
|
|
37
36
|
|
|
37
|
+
# Methods whose results can be referenced by other methods (e.g., clabel needs ContourSet)
|
|
38
|
+
RESULT_REFERENCEABLE_METHODS = {"contour", "contourf"}
|
|
39
|
+
# Methods that take results from other methods as arguments
|
|
40
|
+
RESULT_REFERENCING_METHODS = {"clabel"}
|
|
41
|
+
|
|
38
42
|
def __init__(
|
|
39
43
|
self,
|
|
40
44
|
ax: Axes,
|
|
@@ -45,6 +49,8 @@ class RecordingAxes:
|
|
|
45
49
|
self._recorder = recorder
|
|
46
50
|
self._position = position
|
|
47
51
|
self._track = True
|
|
52
|
+
# Map matplotlib result objects (by id) to their source call_id
|
|
53
|
+
self._result_refs: Dict[int, str] = {}
|
|
48
54
|
|
|
49
55
|
@property
|
|
50
56
|
def ax(self) -> Axes:
|
|
@@ -73,95 +79,60 @@ class RecordingAxes:
|
|
|
73
79
|
return attr
|
|
74
80
|
|
|
75
81
|
def _create_recording_wrapper(self, method_name: str, method: callable):
|
|
76
|
-
"""Create a wrapper function that records the call.
|
|
77
|
-
|
|
78
|
-
Parameters
|
|
79
|
-
----------
|
|
80
|
-
method_name : str
|
|
81
|
-
Name of the method.
|
|
82
|
-
method : callable
|
|
83
|
-
The original method.
|
|
82
|
+
"""Create a wrapper function that records the call."""
|
|
83
|
+
from ._axes_helpers import record_call_with_color_capture
|
|
84
84
|
|
|
85
|
-
Returns
|
|
86
|
-
-------
|
|
87
|
-
callable
|
|
88
|
-
Wrapped method that records calls.
|
|
89
|
-
"""
|
|
90
85
|
def wrapper(*args, id: Optional[str] = None, track: bool = True, **kwargs):
|
|
91
|
-
# Call the original method first (without our custom kwargs)
|
|
92
86
|
result = method(*args, **kwargs)
|
|
93
|
-
|
|
94
|
-
# Record the call if tracking is enabled
|
|
95
87
|
if self._track and track:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
method_name=method_name,
|
|
108
|
-
args=args,
|
|
109
|
-
kwargs=recorded_kwargs,
|
|
110
|
-
call_id=id,
|
|
88
|
+
record_call_with_color_capture(
|
|
89
|
+
self._recorder,
|
|
90
|
+
self._position,
|
|
91
|
+
method_name,
|
|
92
|
+
args,
|
|
93
|
+
kwargs,
|
|
94
|
+
result,
|
|
95
|
+
id,
|
|
96
|
+
self._result_refs,
|
|
97
|
+
self.RESULT_REFERENCING_METHODS,
|
|
98
|
+
self.RESULT_REFERENCEABLE_METHODS,
|
|
111
99
|
)
|
|
112
|
-
|
|
113
100
|
return result
|
|
114
101
|
|
|
115
102
|
return wrapper
|
|
116
103
|
|
|
117
|
-
def
|
|
118
|
-
"""
|
|
104
|
+
def set_caption(self, caption: str) -> "RecordingAxes":
|
|
105
|
+
"""Set panel caption metadata (not rendered, stored in recipe).
|
|
106
|
+
|
|
107
|
+
This is for storing a description for this panel/axis,
|
|
108
|
+
typically used in multi-panel scientific figures
|
|
109
|
+
(e.g., "(A) Control group measurements").
|
|
119
110
|
|
|
120
111
|
Parameters
|
|
121
112
|
----------
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
result : Any
|
|
125
|
-
Return value from the plotting method.
|
|
113
|
+
caption : str
|
|
114
|
+
The panel caption text.
|
|
126
115
|
|
|
127
116
|
Returns
|
|
128
117
|
-------
|
|
129
|
-
|
|
130
|
-
|
|
118
|
+
RecordingAxes
|
|
119
|
+
Self for method chaining.
|
|
120
|
+
|
|
121
|
+
Examples
|
|
122
|
+
--------
|
|
123
|
+
>>> fig, axes = fr.subplots(1, 2)
|
|
124
|
+
>>> axes[0].set_caption("(A) Control group")
|
|
125
|
+
>>> axes[1].set_caption("(B) Treatment group")
|
|
131
126
|
"""
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if len(fc) > 0:
|
|
142
|
-
# Convert RGBA to hex
|
|
143
|
-
import matplotlib.colors as mcolors
|
|
144
|
-
return mcolors.to_hex(fc[0])
|
|
145
|
-
elif method_name in ('bar', 'barh'):
|
|
146
|
-
# bar() returns BarContainer
|
|
147
|
-
if hasattr(result, 'patches') and result.patches:
|
|
148
|
-
fc = result.patches[0].get_facecolor()
|
|
149
|
-
import matplotlib.colors as mcolors
|
|
150
|
-
return mcolors.to_hex(fc)
|
|
151
|
-
elif method_name == 'step':
|
|
152
|
-
# step() returns list of Line2D
|
|
153
|
-
if result and hasattr(result[0], 'get_color'):
|
|
154
|
-
return result[0].get_color()
|
|
155
|
-
elif method_name == 'fill_between':
|
|
156
|
-
# fill_between() returns PolyCollection
|
|
157
|
-
if hasattr(result, 'get_facecolor'):
|
|
158
|
-
fc = result.get_facecolor()
|
|
159
|
-
if len(fc) > 0:
|
|
160
|
-
import matplotlib.colors as mcolors
|
|
161
|
-
return mcolors.to_hex(fc[0])
|
|
162
|
-
except Exception:
|
|
163
|
-
pass
|
|
164
|
-
return None
|
|
127
|
+
ax_record = self._recorder.figure_record.get_or_create_axes(*self._position)
|
|
128
|
+
ax_record.caption = caption
|
|
129
|
+
return self
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def panel_caption(self) -> Optional[str]:
|
|
133
|
+
"""Get the panel caption metadata."""
|
|
134
|
+
ax_record = self._recorder.figure_record.get_or_create_axes(*self._position)
|
|
135
|
+
return ax_record.caption
|
|
165
136
|
|
|
166
137
|
def no_record(self):
|
|
167
138
|
"""Context manager to temporarily disable recording.
|
|
@@ -181,105 +152,21 @@ class RecordingAxes:
|
|
|
181
152
|
data_arrays: Dict[str, np.ndarray],
|
|
182
153
|
call_id: Optional[str] = None,
|
|
183
154
|
) -> None:
|
|
184
|
-
"""Record a seaborn plotting call.
|
|
185
|
-
|
|
186
|
-
Parameters
|
|
187
|
-
----------
|
|
188
|
-
func_name : str
|
|
189
|
-
Name of the seaborn function (e.g., 'scatterplot').
|
|
190
|
-
args : tuple
|
|
191
|
-
Processed positional arguments.
|
|
192
|
-
kwargs : dict
|
|
193
|
-
Processed keyword arguments.
|
|
194
|
-
data_arrays : dict
|
|
195
|
-
Dictionary of array data extracted from DataFrame/arrays.
|
|
196
|
-
call_id : str, optional
|
|
197
|
-
Custom ID for this call.
|
|
198
|
-
"""
|
|
155
|
+
"""Record a seaborn plotting call."""
|
|
199
156
|
if not self._track:
|
|
200
157
|
return
|
|
201
158
|
|
|
202
|
-
from
|
|
203
|
-
|
|
204
|
-
# Generate call ID if not provided
|
|
205
|
-
if call_id is None:
|
|
206
|
-
call_id = self._recorder._generate_call_id(f"sns_{func_name}")
|
|
207
|
-
|
|
208
|
-
# Process data arrays into args format
|
|
209
|
-
processed_args = []
|
|
210
|
-
for i, arg in enumerate(args):
|
|
211
|
-
if arg == "__ARRAY__":
|
|
212
|
-
key = f"_arg_{i}"
|
|
213
|
-
if key in data_arrays:
|
|
214
|
-
arr = data_arrays[key]
|
|
215
|
-
if should_store_inline(arr):
|
|
216
|
-
processed_args.append({
|
|
217
|
-
"name": f"arg{i}",
|
|
218
|
-
"data": to_serializable(arr),
|
|
219
|
-
"dtype": str(arr.dtype),
|
|
220
|
-
})
|
|
221
|
-
else:
|
|
222
|
-
processed_args.append({
|
|
223
|
-
"name": f"arg{i}",
|
|
224
|
-
"data": "__FILE__",
|
|
225
|
-
"dtype": str(arr.dtype),
|
|
226
|
-
"_array": arr,
|
|
227
|
-
})
|
|
228
|
-
else:
|
|
229
|
-
processed_args.append({
|
|
230
|
-
"name": f"arg{i}",
|
|
231
|
-
"data": arg,
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
# Process DataFrame column data
|
|
235
|
-
for key, arr in data_arrays.items():
|
|
236
|
-
if key.startswith("_col_"):
|
|
237
|
-
param_name = key[5:] # Remove "_col_" prefix
|
|
238
|
-
col_name = data_arrays.get(f"_colname_{param_name}", param_name)
|
|
239
|
-
if should_store_inline(arr):
|
|
240
|
-
processed_args.append({
|
|
241
|
-
"name": col_name,
|
|
242
|
-
"param": param_name,
|
|
243
|
-
"data": to_serializable(arr),
|
|
244
|
-
"dtype": str(arr.dtype),
|
|
245
|
-
})
|
|
246
|
-
else:
|
|
247
|
-
processed_args.append({
|
|
248
|
-
"name": col_name,
|
|
249
|
-
"param": param_name,
|
|
250
|
-
"data": "__FILE__",
|
|
251
|
-
"dtype": str(arr.dtype),
|
|
252
|
-
"_array": arr,
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
# Process kwarg arrays
|
|
256
|
-
processed_kwargs = dict(kwargs)
|
|
257
|
-
for key, value in kwargs.items():
|
|
258
|
-
if value == "__ARRAY__":
|
|
259
|
-
arr_key = f"_kwarg_{key}"
|
|
260
|
-
if arr_key in data_arrays:
|
|
261
|
-
arr = data_arrays[arr_key]
|
|
262
|
-
if should_store_inline(arr):
|
|
263
|
-
processed_kwargs[key] = to_serializable(arr)
|
|
264
|
-
else:
|
|
265
|
-
# Mark for file storage
|
|
266
|
-
processed_kwargs[key] = "__FILE__"
|
|
267
|
-
processed_kwargs[f"_array_{key}"] = arr
|
|
268
|
-
|
|
269
|
-
# Create call record
|
|
270
|
-
from .._recorder import CallRecord
|
|
271
|
-
|
|
272
|
-
record = CallRecord(
|
|
273
|
-
id=call_id,
|
|
274
|
-
function=f"sns.{func_name}",
|
|
275
|
-
args=processed_args,
|
|
276
|
-
kwargs=processed_kwargs,
|
|
277
|
-
ax_position=self._position,
|
|
278
|
-
)
|
|
159
|
+
from ._axes_seaborn import record_seaborn_call
|
|
279
160
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
161
|
+
record_seaborn_call(
|
|
162
|
+
self._recorder,
|
|
163
|
+
self._position,
|
|
164
|
+
func_name,
|
|
165
|
+
args,
|
|
166
|
+
kwargs,
|
|
167
|
+
data_arrays,
|
|
168
|
+
call_id,
|
|
169
|
+
)
|
|
283
170
|
|
|
284
171
|
# Expose common properties directly
|
|
285
172
|
@property
|
|
@@ -294,6 +181,73 @@ class RecordingAxes:
|
|
|
294
181
|
def yaxis(self):
|
|
295
182
|
return self._ax.yaxis
|
|
296
183
|
|
|
184
|
+
def pie(
|
|
185
|
+
self,
|
|
186
|
+
x,
|
|
187
|
+
*,
|
|
188
|
+
id: Optional[str] = None,
|
|
189
|
+
track: bool = True,
|
|
190
|
+
**kwargs,
|
|
191
|
+
):
|
|
192
|
+
"""Pie chart with automatic SCITEX styling."""
|
|
193
|
+
from ._axes_plots import pie_plot
|
|
194
|
+
|
|
195
|
+
return pie_plot(
|
|
196
|
+
self._ax,
|
|
197
|
+
x,
|
|
198
|
+
self._recorder,
|
|
199
|
+
self._position,
|
|
200
|
+
self._track and track,
|
|
201
|
+
id,
|
|
202
|
+
**kwargs,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def imshow(
|
|
206
|
+
self,
|
|
207
|
+
X,
|
|
208
|
+
*,
|
|
209
|
+
id: Optional[str] = None,
|
|
210
|
+
track: bool = True,
|
|
211
|
+
**kwargs,
|
|
212
|
+
):
|
|
213
|
+
"""Display image with automatic SCITEX styling."""
|
|
214
|
+
from ._axes_plots import imshow_plot
|
|
215
|
+
|
|
216
|
+
return imshow_plot(
|
|
217
|
+
self._ax,
|
|
218
|
+
X,
|
|
219
|
+
self._recorder,
|
|
220
|
+
self._position,
|
|
221
|
+
self._track and track,
|
|
222
|
+
id,
|
|
223
|
+
**kwargs,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def violinplot(
|
|
227
|
+
self,
|
|
228
|
+
dataset,
|
|
229
|
+
positions=None,
|
|
230
|
+
*,
|
|
231
|
+
id: Optional[str] = None,
|
|
232
|
+
track: bool = True,
|
|
233
|
+
inner: Optional[str] = None,
|
|
234
|
+
**kwargs,
|
|
235
|
+
):
|
|
236
|
+
"""Violin plot with support for inner display options."""
|
|
237
|
+
from ._axes_plots import violinplot_plot
|
|
238
|
+
|
|
239
|
+
return violinplot_plot(
|
|
240
|
+
self._ax,
|
|
241
|
+
dataset,
|
|
242
|
+
positions,
|
|
243
|
+
self._recorder,
|
|
244
|
+
self._position,
|
|
245
|
+
self._track and track,
|
|
246
|
+
id,
|
|
247
|
+
inner,
|
|
248
|
+
**kwargs,
|
|
249
|
+
)
|
|
250
|
+
|
|
297
251
|
# Methods that should not be recorded
|
|
298
252
|
def get_xlim(self):
|
|
299
253
|
return self._ax.get_xlim()
|
|
@@ -310,6 +264,75 @@ class RecordingAxes:
|
|
|
310
264
|
def get_title(self):
|
|
311
265
|
return self._ax.get_title()
|
|
312
266
|
|
|
267
|
+
def joyplot(
|
|
268
|
+
self,
|
|
269
|
+
arrays,
|
|
270
|
+
*,
|
|
271
|
+
overlap: float = 0.5,
|
|
272
|
+
fill_alpha: float = 0.7,
|
|
273
|
+
line_alpha: float = 1.0,
|
|
274
|
+
colors=None,
|
|
275
|
+
labels=None,
|
|
276
|
+
id: Optional[str] = None,
|
|
277
|
+
track: bool = True,
|
|
278
|
+
**kwargs,
|
|
279
|
+
):
|
|
280
|
+
"""Create a joyplot (ridgeline plot) for distribution comparison."""
|
|
281
|
+
from ._axes_plots import joyplot_plot
|
|
282
|
+
|
|
283
|
+
return joyplot_plot(
|
|
284
|
+
self._ax,
|
|
285
|
+
self,
|
|
286
|
+
arrays,
|
|
287
|
+
self._recorder,
|
|
288
|
+
self._position,
|
|
289
|
+
self._track and track,
|
|
290
|
+
id,
|
|
291
|
+
overlap,
|
|
292
|
+
fill_alpha,
|
|
293
|
+
line_alpha,
|
|
294
|
+
colors,
|
|
295
|
+
labels,
|
|
296
|
+
**kwargs,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
def swarmplot(
|
|
300
|
+
self,
|
|
301
|
+
data,
|
|
302
|
+
positions=None,
|
|
303
|
+
*,
|
|
304
|
+
size: float = None,
|
|
305
|
+
color=None,
|
|
306
|
+
alpha: float = 0.7,
|
|
307
|
+
jitter: float = 0.3,
|
|
308
|
+
id: Optional[str] = None,
|
|
309
|
+
track: bool = True,
|
|
310
|
+
**kwargs,
|
|
311
|
+
):
|
|
312
|
+
"""Create a swarm plot (beeswarm plot) showing individual data points."""
|
|
313
|
+
from ._axes_plots import swarmplot_plot
|
|
314
|
+
|
|
315
|
+
return swarmplot_plot(
|
|
316
|
+
self._ax,
|
|
317
|
+
data,
|
|
318
|
+
positions,
|
|
319
|
+
self._recorder,
|
|
320
|
+
self._position,
|
|
321
|
+
self._track and track,
|
|
322
|
+
id,
|
|
323
|
+
size,
|
|
324
|
+
color,
|
|
325
|
+
alpha,
|
|
326
|
+
jitter,
|
|
327
|
+
**kwargs,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
@property
|
|
331
|
+
def caption(self) -> Optional[str]:
|
|
332
|
+
"""Get the panel caption metadata."""
|
|
333
|
+
ax_record = self._recorder.figure_record.get_or_create_axes(*self._position)
|
|
334
|
+
return ax_record.caption
|
|
335
|
+
|
|
313
336
|
|
|
314
337
|
class _NoRecordContext:
|
|
315
338
|
"""Context manager to temporarily disable recording."""
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Helper functions for RecordingAxes."""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def args_have_fmt_color(args: tuple) -> bool:
|
|
9
|
+
"""Check if args contain a matplotlib fmt string with color specifier."""
|
|
10
|
+
color_codes = set("bgrcmykw")
|
|
11
|
+
for arg in args:
|
|
12
|
+
if isinstance(arg, str) and len(arg) >= 1 and len(arg) <= 4:
|
|
13
|
+
if arg[0] in color_codes:
|
|
14
|
+
return True
|
|
15
|
+
return False
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def extract_color_from_result(method_name: str, result) -> Optional[str]:
|
|
19
|
+
"""Extract actual color used from plot result."""
|
|
20
|
+
try:
|
|
21
|
+
if method_name == "plot":
|
|
22
|
+
if result and hasattr(result[0], "get_color"):
|
|
23
|
+
return result[0].get_color()
|
|
24
|
+
elif method_name == "scatter":
|
|
25
|
+
if hasattr(result, "get_facecolor"):
|
|
26
|
+
fc = result.get_facecolor()
|
|
27
|
+
if len(fc) > 0:
|
|
28
|
+
import matplotlib.colors as mcolors
|
|
29
|
+
|
|
30
|
+
return mcolors.to_hex(fc[0])
|
|
31
|
+
elif method_name in ("bar", "barh"):
|
|
32
|
+
if hasattr(result, "patches") and result.patches:
|
|
33
|
+
fc = result.patches[0].get_facecolor()
|
|
34
|
+
import matplotlib.colors as mcolors
|
|
35
|
+
|
|
36
|
+
return mcolors.to_hex(fc)
|
|
37
|
+
elif method_name == "step":
|
|
38
|
+
if result and hasattr(result[0], "get_color"):
|
|
39
|
+
return result[0].get_color()
|
|
40
|
+
elif method_name == "fill_between":
|
|
41
|
+
if hasattr(result, "get_facecolor"):
|
|
42
|
+
fc = result.get_facecolor()
|
|
43
|
+
if len(fc) > 0:
|
|
44
|
+
import matplotlib.colors as mcolors
|
|
45
|
+
|
|
46
|
+
return mcolors.to_hex(fc[0])
|
|
47
|
+
except Exception:
|
|
48
|
+
pass
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def process_result_refs_in_args(
|
|
53
|
+
args: tuple,
|
|
54
|
+
method_name: str,
|
|
55
|
+
result_refs: Dict[int, str],
|
|
56
|
+
referencing_methods: set,
|
|
57
|
+
) -> tuple:
|
|
58
|
+
"""Process args to replace matplotlib objects with references."""
|
|
59
|
+
if method_name not in referencing_methods:
|
|
60
|
+
return args
|
|
61
|
+
|
|
62
|
+
import builtins
|
|
63
|
+
|
|
64
|
+
processed = []
|
|
65
|
+
for arg in args:
|
|
66
|
+
obj_id = builtins.id(arg)
|
|
67
|
+
if obj_id in result_refs:
|
|
68
|
+
processed.append({"__ref__": result_refs[obj_id]})
|
|
69
|
+
else:
|
|
70
|
+
processed.append(arg)
|
|
71
|
+
return tuple(processed)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def record_call_with_color_capture(
|
|
75
|
+
recorder,
|
|
76
|
+
position: tuple,
|
|
77
|
+
method_name: str,
|
|
78
|
+
args: tuple,
|
|
79
|
+
kwargs: dict,
|
|
80
|
+
result,
|
|
81
|
+
call_id: Optional[str],
|
|
82
|
+
result_refs: Dict[int, str],
|
|
83
|
+
referencing_methods: set,
|
|
84
|
+
referenceable_methods: set,
|
|
85
|
+
) -> Any:
|
|
86
|
+
"""Record a call with color capture and result reference handling."""
|
|
87
|
+
recorded_kwargs = kwargs.copy()
|
|
88
|
+
|
|
89
|
+
# Capture colors for methods using color cycle
|
|
90
|
+
if method_name in ("plot", "scatter", "bar", "barh", "step", "fill_between"):
|
|
91
|
+
has_fmt_color = args_have_fmt_color(args)
|
|
92
|
+
if (
|
|
93
|
+
"color" not in recorded_kwargs
|
|
94
|
+
and "c" not in recorded_kwargs
|
|
95
|
+
and not has_fmt_color
|
|
96
|
+
):
|
|
97
|
+
actual_color = extract_color_from_result(method_name, result)
|
|
98
|
+
if actual_color is not None:
|
|
99
|
+
recorded_kwargs["color"] = actual_color
|
|
100
|
+
|
|
101
|
+
if method_name == "fill_between" and "edgecolor" not in recorded_kwargs:
|
|
102
|
+
if hasattr(result, "get_edgecolor"):
|
|
103
|
+
ec = result.get_edgecolor()
|
|
104
|
+
if len(ec) == 0:
|
|
105
|
+
recorded_kwargs["edgecolor"] = "none"
|
|
106
|
+
|
|
107
|
+
# Process args for result references
|
|
108
|
+
processed_args = process_result_refs_in_args(
|
|
109
|
+
args, method_name, result_refs, referencing_methods
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
call_record = recorder.record_call(
|
|
113
|
+
ax_position=position,
|
|
114
|
+
method_name=method_name,
|
|
115
|
+
args=processed_args,
|
|
116
|
+
kwargs=recorded_kwargs,
|
|
117
|
+
call_id=call_id,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Store result reference if applicable
|
|
121
|
+
if method_name in referenceable_methods:
|
|
122
|
+
import builtins
|
|
123
|
+
|
|
124
|
+
result_refs[builtins.id(result)] = call_record.id
|
|
125
|
+
|
|
126
|
+
return call_record
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
__all__ = [
|
|
130
|
+
"args_have_fmt_color",
|
|
131
|
+
"extract_color_from_result",
|
|
132
|
+
"process_result_refs_in_args",
|
|
133
|
+
"record_call_with_color_capture",
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
# EOF
|