figrecipe 0.6.0__py3-none-any.whl → 0.9.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 +161 -1030
- figrecipe/__main__.py +12 -0
- figrecipe/_api/__init__.py +48 -0
- figrecipe/_api/_extract.py +108 -0
- figrecipe/_api/_notebook.py +61 -0
- figrecipe/_api/_panel.py +113 -0
- figrecipe/_api/_save.py +287 -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/_cli/__init__.py +7 -0
- figrecipe/_cli/_compose.py +87 -0
- figrecipe/_cli/_convert.py +117 -0
- figrecipe/_cli/_crop.py +82 -0
- figrecipe/_cli/_edit.py +70 -0
- figrecipe/_cli/_extract.py +128 -0
- figrecipe/_cli/_fonts.py +47 -0
- figrecipe/_cli/_info.py +67 -0
- figrecipe/_cli/_main.py +58 -0
- figrecipe/_cli/_reproduce.py +79 -0
- figrecipe/_cli/_style.py +77 -0
- figrecipe/_cli/_validate.py +66 -0
- figrecipe/_cli/_version.py +50 -0
- figrecipe/_composition/__init__.py +32 -0
- figrecipe/_composition/_alignment.py +452 -0
- figrecipe/_composition/_compose.py +179 -0
- figrecipe/_composition/_import_axes.py +127 -0
- figrecipe/_composition/_visibility.py +125 -0
- figrecipe/_dev/__init__.py +4 -93
- figrecipe/_dev/_plotters.py +76 -0
- figrecipe/_dev/_run_demos.py +56 -0
- figrecipe/_dev/browser/__init__.py +69 -0
- figrecipe/_dev/browser/_audio.py +240 -0
- figrecipe/_dev/browser/_caption.py +356 -0
- figrecipe/_dev/browser/_click_effect.py +146 -0
- figrecipe/_dev/browser/_cursor.py +196 -0
- figrecipe/_dev/browser/_highlight.py +105 -0
- figrecipe/_dev/browser/_narration.py +237 -0
- figrecipe/_dev/browser/_recorder.py +446 -0
- figrecipe/_dev/browser/_utils.py +178 -0
- figrecipe/_dev/browser/_video_trim/__init__.py +152 -0
- figrecipe/_dev/browser/_video_trim/_detection.py +223 -0
- figrecipe/_dev/browser/_video_trim/_markers.py +140 -0
- figrecipe/_dev/demo_plotters/__init__.py +35 -166
- 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/contour_surface/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/{plot_plot.py → line_curve/plot_plot.py} +3 -2
- figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/{plot_pie.py → special/plot_pie.py} +5 -1
- figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
- figrecipe/_editor/__init__.py +61 -13
- 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 +402 -0
- figrecipe/_editor/_bbox/_extract_axes.py +370 -0
- figrecipe/_editor/_bbox/_extract_text.py +466 -0
- figrecipe/_editor/_bbox/_lines.py +173 -0
- figrecipe/_editor/_bbox/_transforms.py +146 -0
- figrecipe/_editor/_call_overrides.py +183 -0
- figrecipe/_editor/_datatable_plot_handlers.py +249 -0
- figrecipe/_editor/_figure_layout.py +211 -0
- figrecipe/_editor/_flask_app.py +200 -1030
- figrecipe/_editor/_helpers.py +251 -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 +194 -0
- figrecipe/_editor/_hitmap/_restore.py +154 -0
- figrecipe/_editor/_hitmap_main.py +182 -0
- figrecipe/_editor/_overrides.py +4 -1
- figrecipe/_editor/_plot_types_registry.py +190 -0
- figrecipe/_editor/_preferences.py +135 -0
- figrecipe/_editor/_render_overrides.py +507 -0
- figrecipe/_editor/_renderer.py +81 -186
- figrecipe/_editor/_routes_annotation.py +114 -0
- figrecipe/_editor/_routes_axis.py +482 -0
- figrecipe/_editor/_routes_captions.py +130 -0
- figrecipe/_editor/_routes_composition.py +270 -0
- figrecipe/_editor/_routes_core.py +126 -0
- figrecipe/_editor/_routes_datatable.py +364 -0
- figrecipe/_editor/_routes_element.py +335 -0
- figrecipe/_editor/_routes_files.py +443 -0
- figrecipe/_editor/_routes_image.py +200 -0
- figrecipe/_editor/_routes_snapshot.py +94 -0
- figrecipe/_editor/_routes_style.py +243 -0
- figrecipe/_editor/_templates/__init__.py +116 -1
- figrecipe/_editor/_templates/_html.py +154 -64
- figrecipe/_editor/_templates/_html_components/__init__.py +13 -0
- figrecipe/_editor/_templates/_html_components/_composition_toolbar.py +79 -0
- figrecipe/_editor/_templates/_html_components/_file_browser.py +41 -0
- figrecipe/_editor/_templates/_html_datatable.py +92 -0
- figrecipe/_editor/_templates/_scripts/__init__.py +178 -0
- figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
- figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
- figrecipe/_editor/_templates/_scripts/_api.py +228 -0
- figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
- figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
- figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
- figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
- figrecipe/_editor/_templates/_scripts/_core.py +493 -0
- figrecipe/_editor/_templates/_scripts/_datatable/__init__.py +59 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_cell_edit.py +97 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_clipboard.py +164 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_context_menu.py +221 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_core.py +150 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_editable.py +511 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_import.py +161 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_plot.py +261 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_selection.py +438 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_table.py +256 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_tabs.py +354 -0
- figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
- figrecipe/_editor/_templates/_scripts/_element_editor.py +325 -0
- figrecipe/_editor/_templates/_scripts/_files.py +429 -0
- figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
- figrecipe/_editor/_templates/_scripts/_hitmap.py +512 -0
- figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
- figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
- figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
- figrecipe/_editor/_templates/_scripts/_legend_drag.py +270 -0
- figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
- figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
- figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag.py +505 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
- figrecipe/_editor/_templates/_scripts/_panel_position.py +463 -0
- figrecipe/_editor/_templates/_scripts/_panel_resize.py +230 -0
- figrecipe/_editor/_templates/_scripts/_panel_snap.py +307 -0
- figrecipe/_editor/_templates/_scripts/_region_select.py +255 -0
- figrecipe/_editor/_templates/_scripts/_selection.py +244 -0
- figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
- figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
- figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
- figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
- figrecipe/_editor/_templates/_scripts/_zoom.py +212 -0
- figrecipe/_editor/_templates/_styles/__init__.py +78 -0
- figrecipe/_editor/_templates/_styles/_base.py +111 -0
- figrecipe/_editor/_templates/_styles/_buttons.py +327 -0
- figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
- figrecipe/_editor/_templates/_styles/_composition.py +87 -0
- figrecipe/_editor/_templates/_styles/_controls.py +430 -0
- figrecipe/_editor/_templates/_styles/_datatable/__init__.py +40 -0
- figrecipe/_editor/_templates/_styles/_datatable/_editable.py +203 -0
- figrecipe/_editor/_templates/_styles/_datatable/_panel.py +268 -0
- figrecipe/_editor/_templates/_styles/_datatable/_table.py +479 -0
- figrecipe/_editor/_templates/_styles/_datatable/_toolbar.py +384 -0
- figrecipe/_editor/_templates/_styles/_datatable/_vars.py +123 -0
- figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
- figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
- figrecipe/_editor/_templates/_styles/_forms.py +224 -0
- figrecipe/_editor/_templates/_styles/_hitmap.py +191 -0
- figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
- figrecipe/_editor/_templates/_styles/_labels.py +118 -0
- figrecipe/_editor/_templates/_styles/_modals.py +127 -0
- figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
- figrecipe/_editor/_templates/_styles/_preview.py +430 -0
- figrecipe/_editor/_templates/_styles/_selection.py +73 -0
- figrecipe/_editor/_templates/_styles/_spinner.py +117 -0
- figrecipe/_editor/static/audio/click.mp3 +0 -0
- figrecipe/_editor/static/click.mp3 +0 -0
- figrecipe/_editor/static/icons/favicon.ico +0 -0
- figrecipe/_integrations/__init__.py +17 -0
- figrecipe/_integrations/_scitex_stats.py +298 -0
- figrecipe/_params/_DECORATION_METHODS.py +8 -0
- figrecipe/_recorder.py +63 -109
- figrecipe/_recorder_utils.py +124 -0
- figrecipe/_reproducer/__init__.py +18 -0
- figrecipe/_reproducer/_core.py +509 -0
- figrecipe/_reproducer/_custom_plots.py +279 -0
- figrecipe/_reproducer/_seaborn.py +100 -0
- figrecipe/_reproducer/_violin.py +186 -0
- figrecipe/_signatures/_kwargs.py +273 -0
- figrecipe/_signatures/_loader.py +21 -423
- figrecipe/_signatures/_parsing.py +147 -0
- figrecipe/_utils/__init__.py +3 -0
- figrecipe/_utils/_bundle.py +205 -0
- figrecipe/_wrappers/_axes.py +252 -895
- figrecipe/_wrappers/_axes_helpers.py +136 -0
- figrecipe/_wrappers/_axes_plots.py +418 -0
- figrecipe/_wrappers/_axes_seaborn.py +157 -0
- figrecipe/_wrappers/_caption_generator.py +218 -0
- figrecipe/_wrappers/_figure.py +188 -1
- figrecipe/_wrappers/_panel_labels.py +127 -0
- figrecipe/_wrappers/_plot_helpers.py +143 -0
- figrecipe/_wrappers/_stat_annotation.py +274 -0
- figrecipe/_wrappers/_violin_helpers.py +180 -0
- figrecipe/styles/__init__.py +8 -6
- 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 +42 -480
- figrecipe/styles/_style_loader.py +16 -192
- figrecipe/styles/_themes.py +151 -0
- figrecipe/styles/presets/MATPLOTLIB.yaml +2 -1
- figrecipe/styles/presets/SCITEX.yaml +40 -28
- figrecipe-0.9.0.dist-info/METADATA +427 -0
- figrecipe-0.9.0.dist-info/RECORD +277 -0
- figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
- figrecipe/_editor/_bbox.py +0 -978
- figrecipe/_editor/_hitmap.py +0 -937
- figrecipe/_editor/_templates/_scripts.py +0 -2778
- figrecipe/_editor/_templates/_styles.py +0 -1326
- figrecipe/_reproducer.py +0 -975
- figrecipe-0.6.0.dist-info/METADATA +0 -394
- figrecipe-0.6.0.dist-info/RECORD +0 -90
- /figrecipe/_dev/demo_plotters/{plot_bar.py → bar_categorical/plot_bar.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_barh.py → bar_categorical/plot_barh.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_contour.py → contour_surface/plot_contour.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_contourf.py → contour_surface/plot_contourf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tricontour.py → contour_surface/plot_tricontour.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tricontourf.py → contour_surface/plot_tricontourf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tripcolor.py → contour_surface/plot_tripcolor.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_triplot.py → contour_surface/plot_triplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_boxplot.py → distribution/plot_boxplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_ecdf.py → distribution/plot_ecdf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hist.py → distribution/plot_hist.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hist2d.py → distribution/plot_hist2d.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_violinplot.py → distribution/plot_violinplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hexbin.py → image_matrix/plot_hexbin.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_imshow.py → image_matrix/plot_imshow.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_matshow.py → image_matrix/plot_matshow.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_pcolor.py → image_matrix/plot_pcolor.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_pcolormesh.py → image_matrix/plot_pcolormesh.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_spy.py → image_matrix/plot_spy.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_errorbar.py → line_curve/plot_errorbar.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill.py → line_curve/plot_fill.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill_between.py → line_curve/plot_fill_between.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill_betweenx.py → line_curve/plot_fill_betweenx.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stackplot.py → line_curve/plot_stackplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stairs.py → line_curve/plot_stairs.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_step.py → line_curve/plot_step.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_scatter.py → scatter_points/plot_scatter.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_eventplot.py → special/plot_eventplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_loglog.py → special/plot_loglog.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_semilogx.py → special/plot_semilogx.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_semilogy.py → special/plot_semilogy.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stem.py → special/plot_stem.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_acorr.py → spectral_signal/plot_acorr.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_angle_spectrum.py → spectral_signal/plot_angle_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_cohere.py → spectral_signal/plot_cohere.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_csd.py → spectral_signal/plot_csd.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_magnitude_spectrum.py → spectral_signal/plot_magnitude_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_phase_spectrum.py → spectral_signal/plot_phase_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_psd.py → spectral_signal/plot_psd.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_specgram.py → spectral_signal/plot_specgram.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_xcorr.py → spectral_signal/plot_xcorr.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_barbs.py → vector_flow/plot_barbs.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_quiver.py → vector_flow/plot_quiver.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_streamplot.py → vector_flow/plot_streamplot.py} +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/licenses/LICENSE +0 -0
figrecipe/_wrappers/_axes.py
CHANGED
|
@@ -79,179 +79,113 @@ class RecordingAxes:
|
|
|
79
79
|
return attr
|
|
80
80
|
|
|
81
81
|
def _create_recording_wrapper(self, method_name: str, method: callable):
|
|
82
|
-
"""Create a wrapper function that records the call.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
callable
|
|
94
|
-
Wrapped method that records calls.
|
|
95
|
-
"""
|
|
96
|
-
|
|
97
|
-
def wrapper(*args, id: Optional[str] = None, track: bool = True, **kwargs):
|
|
98
|
-
# Call the original method first (without our custom kwargs)
|
|
82
|
+
"""Create a wrapper function that records the call."""
|
|
83
|
+
from ._axes_helpers import record_call_with_color_capture
|
|
84
|
+
|
|
85
|
+
def wrapper(
|
|
86
|
+
*args,
|
|
87
|
+
id: Optional[str] = None,
|
|
88
|
+
track: bool = True,
|
|
89
|
+
stats: Optional[Dict[str, Any]] = None,
|
|
90
|
+
**kwargs,
|
|
91
|
+
):
|
|
92
|
+
# Call matplotlib method (without stats - it's metadata only)
|
|
99
93
|
result = method(*args, **kwargs)
|
|
100
|
-
|
|
101
|
-
# Record the call if tracking is enabled
|
|
102
94
|
if self._track and track:
|
|
103
|
-
#
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
and "c" not in recorded_kwargs
|
|
119
|
-
and not has_fmt_color
|
|
120
|
-
):
|
|
121
|
-
actual_color = self._extract_color_from_result(
|
|
122
|
-
method_name, result
|
|
123
|
-
)
|
|
124
|
-
if actual_color is not None:
|
|
125
|
-
recorded_kwargs["color"] = actual_color
|
|
126
|
-
|
|
127
|
-
# Process args to detect result references (e.g., clabel's ContourSet)
|
|
128
|
-
processed_args = self._process_result_refs_in_args(args, method_name)
|
|
129
|
-
|
|
130
|
-
call_record = self._recorder.record_call(
|
|
131
|
-
ax_position=self._position,
|
|
132
|
-
method_name=method_name,
|
|
133
|
-
args=processed_args,
|
|
134
|
-
kwargs=recorded_kwargs,
|
|
135
|
-
call_id=id,
|
|
95
|
+
# Re-add stats to kwargs for recording
|
|
96
|
+
record_kwargs = kwargs.copy()
|
|
97
|
+
if stats is not None:
|
|
98
|
+
record_kwargs["stats"] = stats
|
|
99
|
+
record_call_with_color_capture(
|
|
100
|
+
self._recorder,
|
|
101
|
+
self._position,
|
|
102
|
+
method_name,
|
|
103
|
+
args,
|
|
104
|
+
record_kwargs,
|
|
105
|
+
result,
|
|
106
|
+
id,
|
|
107
|
+
self._result_refs,
|
|
108
|
+
self.RESULT_REFERENCING_METHODS,
|
|
109
|
+
self.RESULT_REFERENCEABLE_METHODS,
|
|
136
110
|
)
|
|
137
|
-
|
|
138
|
-
# Store result reference for methods whose results can be used later
|
|
139
|
-
if method_name in self.RESULT_REFERENCEABLE_METHODS:
|
|
140
|
-
import builtins
|
|
141
|
-
|
|
142
|
-
self._result_refs[builtins.id(result)] = call_record.id
|
|
143
|
-
|
|
144
111
|
return result
|
|
145
112
|
|
|
146
113
|
return wrapper
|
|
147
114
|
|
|
148
|
-
def
|
|
149
|
-
"""
|
|
115
|
+
def set_caption(self, caption: str) -> "RecordingAxes":
|
|
116
|
+
"""Set panel caption metadata (not rendered, stored in recipe).
|
|
150
117
|
|
|
151
|
-
|
|
152
|
-
|
|
118
|
+
This is for storing a description for this panel/axis,
|
|
119
|
+
typically used in multi-panel scientific figures
|
|
120
|
+
(e.g., "(A) Control group measurements").
|
|
153
121
|
|
|
154
122
|
Parameters
|
|
155
123
|
----------
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
method_name : str
|
|
159
|
-
Name of the method.
|
|
124
|
+
caption : str
|
|
125
|
+
The panel caption text.
|
|
160
126
|
|
|
161
127
|
Returns
|
|
162
128
|
-------
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
"""
|
|
166
|
-
if method_name not in self.RESULT_REFERENCING_METHODS:
|
|
167
|
-
return args
|
|
129
|
+
RecordingAxes
|
|
130
|
+
Self for method chaining.
|
|
168
131
|
|
|
169
|
-
|
|
132
|
+
Examples
|
|
133
|
+
--------
|
|
134
|
+
>>> fig, axes = fr.subplots(1, 2)
|
|
135
|
+
>>> axes[0].set_caption("(A) Control group")
|
|
136
|
+
>>> axes[1].set_caption("(B) Treatment group")
|
|
137
|
+
"""
|
|
138
|
+
ax_record = self._recorder.figure_record.get_or_create_axes(*self._position)
|
|
139
|
+
ax_record.caption = caption
|
|
140
|
+
return self
|
|
170
141
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
processed.append({"__ref__": self._result_refs[obj_id]})
|
|
177
|
-
else:
|
|
178
|
-
processed.append(arg)
|
|
179
|
-
return tuple(processed)
|
|
142
|
+
@property
|
|
143
|
+
def panel_caption(self) -> Optional[str]:
|
|
144
|
+
"""Get the panel caption metadata."""
|
|
145
|
+
ax_record = self._recorder.figure_record.get_or_create_axes(*self._position)
|
|
146
|
+
return ax_record.caption
|
|
180
147
|
|
|
181
|
-
def
|
|
182
|
-
"""
|
|
148
|
+
def set_stats(self, stats: Dict[str, Any]) -> "RecordingAxes":
|
|
149
|
+
"""Set panel-level statistics metadata (not rendered, stored in recipe).
|
|
183
150
|
|
|
184
|
-
|
|
151
|
+
This is for storing statistical summary or comparison results
|
|
152
|
+
for this panel/axis, such as group means, sample sizes, or
|
|
153
|
+
comparison p-values.
|
|
185
154
|
|
|
186
155
|
Parameters
|
|
187
156
|
----------
|
|
188
|
-
|
|
189
|
-
|
|
157
|
+
stats : dict
|
|
158
|
+
Statistics dictionary. Common keys include:
|
|
159
|
+
- n: sample size
|
|
160
|
+
- mean: mean value
|
|
161
|
+
- std: standard deviation
|
|
162
|
+
- sem: standard error of the mean
|
|
163
|
+
- comparisons: list of comparison results
|
|
190
164
|
|
|
191
165
|
Returns
|
|
192
166
|
-------
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
"""
|
|
196
|
-
color_codes = set("bgrcmykw")
|
|
197
|
-
for arg in args:
|
|
198
|
-
if isinstance(arg, str) and len(arg) >= 1 and len(arg) <= 4:
|
|
199
|
-
# Fmt strings are short (e.g., "b-", "r--", "go", "k:")
|
|
200
|
-
if arg[0] in color_codes:
|
|
201
|
-
return True
|
|
202
|
-
return False
|
|
203
|
-
|
|
204
|
-
def _extract_color_from_result(self, method_name: str, result) -> Optional[str]:
|
|
205
|
-
"""Extract actual color used from plot result.
|
|
206
|
-
|
|
207
|
-
Parameters
|
|
208
|
-
----------
|
|
209
|
-
method_name : str
|
|
210
|
-
Name of the plotting method.
|
|
211
|
-
result : Any
|
|
212
|
-
Return value from the plotting method.
|
|
167
|
+
RecordingAxes
|
|
168
|
+
Self for method chaining.
|
|
213
169
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
170
|
+
Examples
|
|
171
|
+
--------
|
|
172
|
+
>>> fig, axes = fr.subplots(1, 2)
|
|
173
|
+
>>> axes[0].set_stats({"n": 50, "mean": 3.2, "std": 1.1})
|
|
174
|
+
>>> axes[1].set_stats({
|
|
175
|
+
... "n": 48,
|
|
176
|
+
... "mean": 5.1,
|
|
177
|
+
... "comparisons": [{"vs": "control", "p_value": 0.003}]
|
|
178
|
+
... })
|
|
218
179
|
"""
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if len(fc) > 0:
|
|
229
|
-
# Convert RGBA to hex
|
|
230
|
-
import matplotlib.colors as mcolors
|
|
231
|
-
|
|
232
|
-
return mcolors.to_hex(fc[0])
|
|
233
|
-
elif method_name in ("bar", "barh"):
|
|
234
|
-
# bar() returns BarContainer
|
|
235
|
-
if hasattr(result, "patches") and result.patches:
|
|
236
|
-
fc = result.patches[0].get_facecolor()
|
|
237
|
-
import matplotlib.colors as mcolors
|
|
238
|
-
|
|
239
|
-
return mcolors.to_hex(fc)
|
|
240
|
-
elif method_name == "step":
|
|
241
|
-
# step() returns list of Line2D
|
|
242
|
-
if result and hasattr(result[0], "get_color"):
|
|
243
|
-
return result[0].get_color()
|
|
244
|
-
elif method_name == "fill_between":
|
|
245
|
-
# fill_between() returns PolyCollection
|
|
246
|
-
if hasattr(result, "get_facecolor"):
|
|
247
|
-
fc = result.get_facecolor()
|
|
248
|
-
if len(fc) > 0:
|
|
249
|
-
import matplotlib.colors as mcolors
|
|
250
|
-
|
|
251
|
-
return mcolors.to_hex(fc[0])
|
|
252
|
-
except Exception:
|
|
253
|
-
pass
|
|
254
|
-
return None
|
|
180
|
+
ax_record = self._recorder.figure_record.get_or_create_axes(*self._position)
|
|
181
|
+
ax_record.stats = stats
|
|
182
|
+
return self
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def stats(self) -> Optional[Dict[str, Any]]:
|
|
186
|
+
"""Get the panel-level statistics metadata."""
|
|
187
|
+
ax_record = self._recorder.figure_record.get_or_create_axes(*self._position)
|
|
188
|
+
return ax_record.stats
|
|
255
189
|
|
|
256
190
|
def no_record(self):
|
|
257
191
|
"""Context manager to temporarily disable recording.
|
|
@@ -271,116 +205,22 @@ class RecordingAxes:
|
|
|
271
205
|
data_arrays: Dict[str, np.ndarray],
|
|
272
206
|
call_id: Optional[str] = None,
|
|
273
207
|
) -> None:
|
|
274
|
-
"""Record a seaborn plotting call.
|
|
275
|
-
|
|
276
|
-
Parameters
|
|
277
|
-
----------
|
|
278
|
-
func_name : str
|
|
279
|
-
Name of the seaborn function (e.g., 'scatterplot').
|
|
280
|
-
args : tuple
|
|
281
|
-
Processed positional arguments.
|
|
282
|
-
kwargs : dict
|
|
283
|
-
Processed keyword arguments.
|
|
284
|
-
data_arrays : dict
|
|
285
|
-
Dictionary of array data extracted from DataFrame/arrays.
|
|
286
|
-
call_id : str, optional
|
|
287
|
-
Custom ID for this call.
|
|
288
|
-
"""
|
|
208
|
+
"""Record a seaborn plotting call."""
|
|
289
209
|
if not self._track:
|
|
290
210
|
return
|
|
291
211
|
|
|
292
|
-
from
|
|
293
|
-
|
|
294
|
-
# Generate call ID if not provided
|
|
295
|
-
if call_id is None:
|
|
296
|
-
call_id = self._recorder._generate_call_id(f"sns_{func_name}")
|
|
297
|
-
|
|
298
|
-
# Process data arrays into args format
|
|
299
|
-
processed_args = []
|
|
300
|
-
for i, arg in enumerate(args):
|
|
301
|
-
if arg == "__ARRAY__":
|
|
302
|
-
key = f"_arg_{i}"
|
|
303
|
-
if key in data_arrays:
|
|
304
|
-
arr = data_arrays[key]
|
|
305
|
-
if should_store_inline(arr):
|
|
306
|
-
processed_args.append(
|
|
307
|
-
{
|
|
308
|
-
"name": f"arg{i}",
|
|
309
|
-
"data": to_serializable(arr),
|
|
310
|
-
"dtype": str(arr.dtype),
|
|
311
|
-
}
|
|
312
|
-
)
|
|
313
|
-
else:
|
|
314
|
-
processed_args.append(
|
|
315
|
-
{
|
|
316
|
-
"name": f"arg{i}",
|
|
317
|
-
"data": "__FILE__",
|
|
318
|
-
"dtype": str(arr.dtype),
|
|
319
|
-
"_array": arr,
|
|
320
|
-
}
|
|
321
|
-
)
|
|
322
|
-
else:
|
|
323
|
-
processed_args.append(
|
|
324
|
-
{
|
|
325
|
-
"name": f"arg{i}",
|
|
326
|
-
"data": arg,
|
|
327
|
-
}
|
|
328
|
-
)
|
|
212
|
+
from ._axes_seaborn import record_seaborn_call
|
|
329
213
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
"name": col_name,
|
|
339
|
-
"param": param_name,
|
|
340
|
-
"data": to_serializable(arr),
|
|
341
|
-
"dtype": str(arr.dtype),
|
|
342
|
-
}
|
|
343
|
-
)
|
|
344
|
-
else:
|
|
345
|
-
processed_args.append(
|
|
346
|
-
{
|
|
347
|
-
"name": col_name,
|
|
348
|
-
"param": param_name,
|
|
349
|
-
"data": "__FILE__",
|
|
350
|
-
"dtype": str(arr.dtype),
|
|
351
|
-
"_array": arr,
|
|
352
|
-
}
|
|
353
|
-
)
|
|
354
|
-
|
|
355
|
-
# Process kwarg arrays
|
|
356
|
-
processed_kwargs = dict(kwargs)
|
|
357
|
-
for key, value in kwargs.items():
|
|
358
|
-
if value == "__ARRAY__":
|
|
359
|
-
arr_key = f"_kwarg_{key}"
|
|
360
|
-
if arr_key in data_arrays:
|
|
361
|
-
arr = data_arrays[arr_key]
|
|
362
|
-
if should_store_inline(arr):
|
|
363
|
-
processed_kwargs[key] = to_serializable(arr)
|
|
364
|
-
else:
|
|
365
|
-
# Mark for file storage
|
|
366
|
-
processed_kwargs[key] = "__FILE__"
|
|
367
|
-
processed_kwargs[f"_array_{key}"] = arr
|
|
368
|
-
|
|
369
|
-
# Create call record
|
|
370
|
-
from .._recorder import CallRecord
|
|
371
|
-
|
|
372
|
-
record = CallRecord(
|
|
373
|
-
id=call_id,
|
|
374
|
-
function=f"sns.{func_name}",
|
|
375
|
-
args=processed_args,
|
|
376
|
-
kwargs=processed_kwargs,
|
|
377
|
-
ax_position=self._position,
|
|
214
|
+
record_seaborn_call(
|
|
215
|
+
self._recorder,
|
|
216
|
+
self._position,
|
|
217
|
+
func_name,
|
|
218
|
+
args,
|
|
219
|
+
kwargs,
|
|
220
|
+
data_arrays,
|
|
221
|
+
call_id,
|
|
378
222
|
)
|
|
379
223
|
|
|
380
|
-
# Add to axes record
|
|
381
|
-
ax_record = self._recorder.figure_record.get_or_create_axes(*self._position)
|
|
382
|
-
ax_record.add_call(record)
|
|
383
|
-
|
|
384
224
|
# Expose common properties directly
|
|
385
225
|
@property
|
|
386
226
|
def figure(self):
|
|
@@ -402,64 +242,18 @@ class RecordingAxes:
|
|
|
402
242
|
track: bool = True,
|
|
403
243
|
**kwargs,
|
|
404
244
|
):
|
|
405
|
-
"""Pie chart with automatic SCITEX styling.
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
Returns
|
|
419
|
-
-------
|
|
420
|
-
tuple
|
|
421
|
-
(patches, texts) or (patches, texts, autotexts) if autopct is set.
|
|
422
|
-
"""
|
|
423
|
-
from ..styles import get_style
|
|
424
|
-
from ..styles._style_applier import check_font
|
|
425
|
-
|
|
426
|
-
# Call matplotlib's pie
|
|
427
|
-
result = self._ax.pie(x, **kwargs)
|
|
428
|
-
|
|
429
|
-
# Get style settings
|
|
430
|
-
style = get_style()
|
|
431
|
-
if style:
|
|
432
|
-
pie_style = style.get("pie", {})
|
|
433
|
-
text_pt = pie_style.get("text_pt", 6)
|
|
434
|
-
show_axes = pie_style.get("show_axes", False)
|
|
435
|
-
font_family = check_font(style.get("fonts", {}).get("family", "Arial"))
|
|
436
|
-
|
|
437
|
-
# Apply text size to all pie text elements (labels and percentages)
|
|
438
|
-
for text in self._ax.texts:
|
|
439
|
-
text.set_fontsize(text_pt)
|
|
440
|
-
text.set_fontfamily(font_family)
|
|
441
|
-
|
|
442
|
-
# Hide axes if configured (default: hide for pie charts)
|
|
443
|
-
if not show_axes:
|
|
444
|
-
self._ax.set_xticks([])
|
|
445
|
-
self._ax.set_yticks([])
|
|
446
|
-
self._ax.set_xticklabels([])
|
|
447
|
-
self._ax.set_yticklabels([])
|
|
448
|
-
# Hide spines
|
|
449
|
-
for spine in self._ax.spines.values():
|
|
450
|
-
spine.set_visible(False)
|
|
451
|
-
|
|
452
|
-
# Record the call if tracking is enabled
|
|
453
|
-
if self._track and track:
|
|
454
|
-
self._recorder.record_call(
|
|
455
|
-
ax_position=self._position,
|
|
456
|
-
method_name="pie",
|
|
457
|
-
args=(x,),
|
|
458
|
-
kwargs=kwargs,
|
|
459
|
-
call_id=id,
|
|
460
|
-
)
|
|
461
|
-
|
|
462
|
-
return result
|
|
245
|
+
"""Pie chart with automatic SCITEX styling."""
|
|
246
|
+
from ._axes_plots import pie_plot
|
|
247
|
+
|
|
248
|
+
return pie_plot(
|
|
249
|
+
self._ax,
|
|
250
|
+
x,
|
|
251
|
+
self._recorder,
|
|
252
|
+
self._position,
|
|
253
|
+
self._track and track,
|
|
254
|
+
id,
|
|
255
|
+
**kwargs,
|
|
256
|
+
)
|
|
463
257
|
|
|
464
258
|
def imshow(
|
|
465
259
|
self,
|
|
@@ -469,61 +263,18 @@ class RecordingAxes:
|
|
|
469
263
|
track: bool = True,
|
|
470
264
|
**kwargs,
|
|
471
265
|
):
|
|
472
|
-
"""Display image with automatic SCITEX styling.
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
Returns
|
|
486
|
-
-------
|
|
487
|
-
AxesImage
|
|
488
|
-
The created image.
|
|
489
|
-
"""
|
|
490
|
-
from ..styles import get_style
|
|
491
|
-
|
|
492
|
-
# Call matplotlib's imshow
|
|
493
|
-
result = self._ax.imshow(X, **kwargs)
|
|
494
|
-
|
|
495
|
-
# Get style settings
|
|
496
|
-
style = get_style()
|
|
497
|
-
if style:
|
|
498
|
-
imshow_style = style.get("imshow", {})
|
|
499
|
-
show_axes = imshow_style.get("show_axes", True)
|
|
500
|
-
show_labels = imshow_style.get("show_labels", True)
|
|
501
|
-
|
|
502
|
-
# Hide axes if configured
|
|
503
|
-
if not show_axes:
|
|
504
|
-
self._ax.set_xticks([])
|
|
505
|
-
self._ax.set_yticks([])
|
|
506
|
-
self._ax.set_xticklabels([])
|
|
507
|
-
self._ax.set_yticklabels([])
|
|
508
|
-
# Hide spines
|
|
509
|
-
for spine in self._ax.spines.values():
|
|
510
|
-
spine.set_visible(False)
|
|
511
|
-
|
|
512
|
-
if not show_labels:
|
|
513
|
-
self._ax.set_xlabel("")
|
|
514
|
-
self._ax.set_ylabel("")
|
|
515
|
-
|
|
516
|
-
# Record the call if tracking is enabled
|
|
517
|
-
if self._track and track:
|
|
518
|
-
self._recorder.record_call(
|
|
519
|
-
ax_position=self._position,
|
|
520
|
-
method_name="imshow",
|
|
521
|
-
args=(X,),
|
|
522
|
-
kwargs=kwargs,
|
|
523
|
-
call_id=id,
|
|
524
|
-
)
|
|
525
|
-
|
|
526
|
-
return result
|
|
266
|
+
"""Display image with automatic SCITEX styling."""
|
|
267
|
+
from ._axes_plots import imshow_plot
|
|
268
|
+
|
|
269
|
+
return imshow_plot(
|
|
270
|
+
self._ax,
|
|
271
|
+
X,
|
|
272
|
+
self._recorder,
|
|
273
|
+
self._position,
|
|
274
|
+
self._track and track,
|
|
275
|
+
id,
|
|
276
|
+
**kwargs,
|
|
277
|
+
)
|
|
527
278
|
|
|
528
279
|
def violinplot(
|
|
529
280
|
self,
|
|
@@ -535,238 +286,21 @@ class RecordingAxes:
|
|
|
535
286
|
inner: Optional[str] = None,
|
|
536
287
|
**kwargs,
|
|
537
288
|
):
|
|
538
|
-
"""Violin plot with support for inner display options.
|
|
539
|
-
|
|
540
|
-
Parameters
|
|
541
|
-
----------
|
|
542
|
-
dataset : array-like
|
|
543
|
-
Data to plot.
|
|
544
|
-
positions : array-like, optional
|
|
545
|
-
Position of each violin on x-axis.
|
|
546
|
-
id : str, optional
|
|
547
|
-
Custom ID for this call.
|
|
548
|
-
track : bool, optional
|
|
549
|
-
Whether to record this call (default: True).
|
|
550
|
-
inner : str, optional
|
|
551
|
-
Inner display type: "box", "quartile", "stick", "point", "swarm", or None.
|
|
552
|
-
Default is from style config (SCITEX default: "box").
|
|
553
|
-
**kwargs
|
|
554
|
-
Additional arguments passed to matplotlib's violinplot.
|
|
555
|
-
|
|
556
|
-
Returns
|
|
557
|
-
-------
|
|
558
|
-
dict
|
|
559
|
-
Dictionary with violin parts (bodies, cbars, cmins, cmaxes, cmeans, cmedians).
|
|
560
|
-
"""
|
|
561
|
-
from ..styles import get_style
|
|
562
|
-
|
|
563
|
-
# Get style settings
|
|
564
|
-
style = get_style()
|
|
565
|
-
violin_style = style.get("violinplot", {}) if style else {}
|
|
566
|
-
|
|
567
|
-
# Determine inner type (user kwarg > style config > default)
|
|
568
|
-
if inner is None:
|
|
569
|
-
inner = violin_style.get("inner", "box")
|
|
289
|
+
"""Violin plot with support for inner display options."""
|
|
290
|
+
from ._axes_plots import violinplot_plot
|
|
570
291
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
showmedians = kwargs.pop("showmedians", violin_style.get("showmedians", True))
|
|
574
|
-
showextrema = kwargs.pop("showextrema", violin_style.get("showextrema", False))
|
|
575
|
-
|
|
576
|
-
# Call matplotlib's violinplot
|
|
577
|
-
result = self._ax.violinplot(
|
|
292
|
+
return violinplot_plot(
|
|
293
|
+
self._ax,
|
|
578
294
|
dataset,
|
|
579
|
-
positions
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
295
|
+
positions,
|
|
296
|
+
self._recorder,
|
|
297
|
+
self._position,
|
|
298
|
+
self._track and track,
|
|
299
|
+
id,
|
|
300
|
+
inner,
|
|
583
301
|
**kwargs,
|
|
584
302
|
)
|
|
585
303
|
|
|
586
|
-
# Apply alpha from style to violin bodies
|
|
587
|
-
alpha = violin_style.get("alpha", 0.7)
|
|
588
|
-
if "bodies" in result:
|
|
589
|
-
for body in result["bodies"]:
|
|
590
|
-
body.set_alpha(alpha)
|
|
591
|
-
|
|
592
|
-
# Overlay inner elements based on inner type
|
|
593
|
-
if positions is None:
|
|
594
|
-
positions = list(range(1, len(dataset) + 1))
|
|
595
|
-
|
|
596
|
-
if inner == "box":
|
|
597
|
-
self._add_violin_inner_box(dataset, positions, violin_style)
|
|
598
|
-
elif inner == "swarm":
|
|
599
|
-
self._add_violin_inner_swarm(dataset, positions, violin_style)
|
|
600
|
-
elif inner == "quartile":
|
|
601
|
-
# quartile lines are handled by showmedians + showextrema
|
|
602
|
-
pass
|
|
603
|
-
elif inner == "stick":
|
|
604
|
-
self._add_violin_inner_stick(dataset, positions, violin_style)
|
|
605
|
-
elif inner == "point":
|
|
606
|
-
self._add_violin_inner_point(dataset, positions, violin_style)
|
|
607
|
-
|
|
608
|
-
# Record the call if tracking is enabled
|
|
609
|
-
if self._track and track:
|
|
610
|
-
recorded_kwargs = kwargs.copy()
|
|
611
|
-
recorded_kwargs["inner"] = inner
|
|
612
|
-
recorded_kwargs["showmeans"] = showmeans
|
|
613
|
-
recorded_kwargs["showmedians"] = showmedians
|
|
614
|
-
recorded_kwargs["showextrema"] = showextrema
|
|
615
|
-
|
|
616
|
-
self._recorder.record_call(
|
|
617
|
-
ax_position=self._position,
|
|
618
|
-
method_name="violinplot",
|
|
619
|
-
args=(dataset,),
|
|
620
|
-
kwargs=recorded_kwargs,
|
|
621
|
-
call_id=id,
|
|
622
|
-
)
|
|
623
|
-
|
|
624
|
-
return result
|
|
625
|
-
|
|
626
|
-
def _add_violin_inner_box(self, dataset, positions, style: Dict[str, Any]) -> None:
|
|
627
|
-
"""Add box plot inside violin.
|
|
628
|
-
|
|
629
|
-
Parameters
|
|
630
|
-
----------
|
|
631
|
-
dataset : array-like
|
|
632
|
-
Data arrays for each violin.
|
|
633
|
-
positions : array-like
|
|
634
|
-
X positions of violins.
|
|
635
|
-
style : dict
|
|
636
|
-
Violin style configuration.
|
|
637
|
-
"""
|
|
638
|
-
from ..styles._style_applier import mm_to_pt
|
|
639
|
-
|
|
640
|
-
whisker_lw = mm_to_pt(style.get("whisker_mm", 0.2))
|
|
641
|
-
median_size = mm_to_pt(style.get("median_mm", 0.8))
|
|
642
|
-
|
|
643
|
-
for i, (data, pos) in enumerate(zip(dataset, positions)):
|
|
644
|
-
data = np.asarray(data)
|
|
645
|
-
q1, median, q3 = np.percentile(data, [25, 50, 75])
|
|
646
|
-
iqr = q3 - q1
|
|
647
|
-
whisker_low = max(data.min(), q1 - 1.5 * iqr)
|
|
648
|
-
whisker_high = min(data.max(), q3 + 1.5 * iqr)
|
|
649
|
-
|
|
650
|
-
# Draw box (Q1 to Q3)
|
|
651
|
-
self._ax.vlines(
|
|
652
|
-
pos, q1, q3, colors="black", linewidths=whisker_lw, zorder=3
|
|
653
|
-
)
|
|
654
|
-
# Draw whiskers
|
|
655
|
-
self._ax.vlines(
|
|
656
|
-
pos,
|
|
657
|
-
whisker_low,
|
|
658
|
-
q1,
|
|
659
|
-
colors="black",
|
|
660
|
-
linewidths=whisker_lw * 0.5,
|
|
661
|
-
zorder=3,
|
|
662
|
-
)
|
|
663
|
-
self._ax.vlines(
|
|
664
|
-
pos,
|
|
665
|
-
q3,
|
|
666
|
-
whisker_high,
|
|
667
|
-
colors="black",
|
|
668
|
-
linewidths=whisker_lw * 0.5,
|
|
669
|
-
zorder=3,
|
|
670
|
-
)
|
|
671
|
-
# Draw median as a white dot with black edge
|
|
672
|
-
self._ax.scatter(
|
|
673
|
-
[pos],
|
|
674
|
-
[median],
|
|
675
|
-
s=median_size**2,
|
|
676
|
-
c="white",
|
|
677
|
-
edgecolors="black",
|
|
678
|
-
linewidths=whisker_lw,
|
|
679
|
-
zorder=4,
|
|
680
|
-
)
|
|
681
|
-
|
|
682
|
-
def _add_violin_inner_swarm(
|
|
683
|
-
self, dataset, positions, style: Dict[str, Any]
|
|
684
|
-
) -> None:
|
|
685
|
-
"""Add swarm points inside violin.
|
|
686
|
-
|
|
687
|
-
Parameters
|
|
688
|
-
----------
|
|
689
|
-
dataset : array-like
|
|
690
|
-
Data arrays for each violin.
|
|
691
|
-
positions : array-like
|
|
692
|
-
X positions of violins.
|
|
693
|
-
style : dict
|
|
694
|
-
Violin style configuration.
|
|
695
|
-
"""
|
|
696
|
-
from ..styles._style_applier import mm_to_pt
|
|
697
|
-
|
|
698
|
-
point_size = mm_to_pt(style.get("median_mm", 0.8))
|
|
699
|
-
|
|
700
|
-
for data, pos in zip(dataset, positions):
|
|
701
|
-
data = np.asarray(data)
|
|
702
|
-
n = len(data)
|
|
703
|
-
|
|
704
|
-
# Simple swarm: jitter x positions
|
|
705
|
-
# More sophisticated swarm would avoid overlaps
|
|
706
|
-
jitter = np.random.default_rng(42).uniform(-0.15, 0.15, n)
|
|
707
|
-
x_positions = pos + jitter
|
|
708
|
-
|
|
709
|
-
self._ax.scatter(
|
|
710
|
-
x_positions, data, s=point_size**2, c="black", alpha=0.5, zorder=3
|
|
711
|
-
)
|
|
712
|
-
|
|
713
|
-
def _add_violin_inner_stick(
|
|
714
|
-
self, dataset, positions, style: Dict[str, Any]
|
|
715
|
-
) -> None:
|
|
716
|
-
"""Add stick (line) markers inside violin for each data point.
|
|
717
|
-
|
|
718
|
-
Parameters
|
|
719
|
-
----------
|
|
720
|
-
dataset : array-like
|
|
721
|
-
Data arrays for each violin.
|
|
722
|
-
positions : array-like
|
|
723
|
-
X positions of violins.
|
|
724
|
-
style : dict
|
|
725
|
-
Violin style configuration.
|
|
726
|
-
"""
|
|
727
|
-
from ..styles._style_applier import mm_to_pt
|
|
728
|
-
|
|
729
|
-
lw = mm_to_pt(style.get("whisker_mm", 0.2))
|
|
730
|
-
|
|
731
|
-
for data, pos in zip(dataset, positions):
|
|
732
|
-
data = np.asarray(data)
|
|
733
|
-
# Draw short horizontal lines at each data point
|
|
734
|
-
for val in data:
|
|
735
|
-
self._ax.hlines(
|
|
736
|
-
val,
|
|
737
|
-
pos - 0.05,
|
|
738
|
-
pos + 0.05,
|
|
739
|
-
colors="black",
|
|
740
|
-
linewidths=lw * 0.5,
|
|
741
|
-
alpha=0.3,
|
|
742
|
-
zorder=3,
|
|
743
|
-
)
|
|
744
|
-
|
|
745
|
-
def _add_violin_inner_point(
|
|
746
|
-
self, dataset, positions, style: Dict[str, Any]
|
|
747
|
-
) -> None:
|
|
748
|
-
"""Add point markers inside violin for each data point.
|
|
749
|
-
|
|
750
|
-
Parameters
|
|
751
|
-
----------
|
|
752
|
-
dataset : array-like
|
|
753
|
-
Data arrays for each violin.
|
|
754
|
-
positions : array-like
|
|
755
|
-
X positions of violins.
|
|
756
|
-
style : dict
|
|
757
|
-
Violin style configuration.
|
|
758
|
-
"""
|
|
759
|
-
from ..styles._style_applier import mm_to_pt
|
|
760
|
-
|
|
761
|
-
point_size = mm_to_pt(style.get("median_mm", 0.8)) * 0.5
|
|
762
|
-
|
|
763
|
-
for data, pos in zip(dataset, positions):
|
|
764
|
-
data = np.asarray(data)
|
|
765
|
-
x_positions = np.full_like(data, pos)
|
|
766
|
-
self._ax.scatter(
|
|
767
|
-
x_positions, data, s=point_size**2, c="black", alpha=0.3, zorder=3
|
|
768
|
-
)
|
|
769
|
-
|
|
770
304
|
# Methods that should not be recorded
|
|
771
305
|
def get_xlim(self):
|
|
772
306
|
return self._ax.get_xlim()
|
|
@@ -796,158 +330,24 @@ class RecordingAxes:
|
|
|
796
330
|
track: bool = True,
|
|
797
331
|
**kwargs,
|
|
798
332
|
):
|
|
799
|
-
"""Create a joyplot (ridgeline plot) for distribution comparison.
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
track : bool, optional
|
|
818
|
-
Whether to record this call (default: True).
|
|
819
|
-
**kwargs
|
|
820
|
-
Additional arguments.
|
|
821
|
-
|
|
822
|
-
Returns
|
|
823
|
-
-------
|
|
824
|
-
RecordingAxes
|
|
825
|
-
Self for method chaining.
|
|
826
|
-
|
|
827
|
-
Examples
|
|
828
|
-
--------
|
|
829
|
-
>>> ax.joyplot([data1, data2, data3], overlap=0.5)
|
|
830
|
-
>>> ax.joyplot({"A": arr_a, "B": arr_b}, labels=["A", "B"])
|
|
831
|
-
"""
|
|
832
|
-
from scipy import stats
|
|
833
|
-
|
|
834
|
-
from .._utils._units import mm_to_pt
|
|
835
|
-
from ..styles import get_style
|
|
836
|
-
|
|
837
|
-
# Convert dict to list of arrays
|
|
838
|
-
if isinstance(arrays, dict):
|
|
839
|
-
if labels is None:
|
|
840
|
-
labels = list(arrays.keys())
|
|
841
|
-
arrays = list(arrays.values())
|
|
842
|
-
|
|
843
|
-
n_ridges = len(arrays)
|
|
844
|
-
|
|
845
|
-
# Get colors from style or use default cycle
|
|
846
|
-
if colors is None:
|
|
847
|
-
style = get_style()
|
|
848
|
-
if style and "colors" in style and "palette" in style.colors:
|
|
849
|
-
palette = list(style.colors.palette)
|
|
850
|
-
# Normalize RGB 0-255 to 0-1
|
|
851
|
-
colors = []
|
|
852
|
-
for c in palette:
|
|
853
|
-
if isinstance(c, (list, tuple)) and len(c) >= 3:
|
|
854
|
-
if all(v <= 1.0 for v in c):
|
|
855
|
-
colors.append(tuple(c))
|
|
856
|
-
else:
|
|
857
|
-
colors.append(tuple(v / 255.0 for v in c))
|
|
858
|
-
else:
|
|
859
|
-
colors.append(c)
|
|
860
|
-
else:
|
|
861
|
-
# Matplotlib default color cycle
|
|
862
|
-
import matplotlib.pyplot as plt
|
|
863
|
-
|
|
864
|
-
colors = [c["color"] for c in plt.rcParams["axes.prop_cycle"]]
|
|
865
|
-
|
|
866
|
-
# Calculate global x range
|
|
867
|
-
all_data = np.concatenate([np.asarray(arr) for arr in arrays])
|
|
868
|
-
x_min, x_max = np.min(all_data), np.max(all_data)
|
|
869
|
-
x_range = x_max - x_min
|
|
870
|
-
x_padding = x_range * 0.1
|
|
871
|
-
x = np.linspace(x_min - x_padding, x_max + x_padding, 200)
|
|
872
|
-
|
|
873
|
-
# Calculate KDEs and find max density for scaling
|
|
874
|
-
kdes = []
|
|
875
|
-
max_density = 0
|
|
876
|
-
for arr in arrays:
|
|
877
|
-
arr = np.asarray(arr)
|
|
878
|
-
if len(arr) > 1:
|
|
879
|
-
kde = stats.gaussian_kde(arr)
|
|
880
|
-
density = kde(x)
|
|
881
|
-
kdes.append(density)
|
|
882
|
-
max_density = max(max_density, np.max(density))
|
|
883
|
-
else:
|
|
884
|
-
kdes.append(np.zeros_like(x))
|
|
885
|
-
|
|
886
|
-
# Scale factor for ridge height
|
|
887
|
-
ridge_height = 1.0 / (1.0 - overlap * 0.5) if overlap < 1 else 2.0
|
|
888
|
-
|
|
889
|
-
# Get line width from style
|
|
890
|
-
style = get_style()
|
|
891
|
-
lw = mm_to_pt(0.2) # Default
|
|
892
|
-
if style and "lines" in style:
|
|
893
|
-
lw = mm_to_pt(style.lines.get("trace_mm", 0.2))
|
|
894
|
-
|
|
895
|
-
# Plot each ridge from back to front
|
|
896
|
-
for i in range(n_ridges - 1, -1, -1):
|
|
897
|
-
color = colors[i % len(colors)]
|
|
898
|
-
baseline = i * (1.0 - overlap)
|
|
899
|
-
|
|
900
|
-
# Scale density to fit nicely
|
|
901
|
-
scaled_density = (
|
|
902
|
-
kdes[i] / max_density * ridge_height if max_density > 0 else kdes[i]
|
|
903
|
-
)
|
|
904
|
-
|
|
905
|
-
# Fill
|
|
906
|
-
self._ax.fill_between(
|
|
907
|
-
x,
|
|
908
|
-
baseline,
|
|
909
|
-
baseline + scaled_density,
|
|
910
|
-
facecolor=color,
|
|
911
|
-
edgecolor="none",
|
|
912
|
-
alpha=fill_alpha,
|
|
913
|
-
)
|
|
914
|
-
# Line on top
|
|
915
|
-
self._ax.plot(
|
|
916
|
-
x,
|
|
917
|
-
baseline + scaled_density,
|
|
918
|
-
color=color,
|
|
919
|
-
alpha=line_alpha,
|
|
920
|
-
linewidth=lw,
|
|
921
|
-
)
|
|
922
|
-
|
|
923
|
-
# Set y limits
|
|
924
|
-
self._ax.set_ylim(-0.1, n_ridges * (1.0 - overlap) + ridge_height)
|
|
925
|
-
|
|
926
|
-
# Set y-axis labels if provided
|
|
927
|
-
if labels:
|
|
928
|
-
y_positions = [(i * (1.0 - overlap)) + 0.3 for i in range(n_ridges)]
|
|
929
|
-
self._ax.set_yticks(y_positions)
|
|
930
|
-
self._ax.set_yticklabels(labels)
|
|
931
|
-
else:
|
|
932
|
-
# Hide y-axis ticks for cleaner look
|
|
933
|
-
self._ax.set_yticks([])
|
|
934
|
-
|
|
935
|
-
# Record the call if tracking is enabled
|
|
936
|
-
if self._track and track:
|
|
937
|
-
self._recorder.record_call(
|
|
938
|
-
ax_position=self._position,
|
|
939
|
-
method_name="joyplot",
|
|
940
|
-
args=(arrays,),
|
|
941
|
-
kwargs={
|
|
942
|
-
"overlap": overlap,
|
|
943
|
-
"fill_alpha": fill_alpha,
|
|
944
|
-
"line_alpha": line_alpha,
|
|
945
|
-
"labels": labels,
|
|
946
|
-
},
|
|
947
|
-
call_id=id,
|
|
948
|
-
)
|
|
949
|
-
|
|
950
|
-
return self
|
|
333
|
+
"""Create a joyplot (ridgeline plot) for distribution comparison."""
|
|
334
|
+
from ._axes_plots import joyplot_plot
|
|
335
|
+
|
|
336
|
+
return joyplot_plot(
|
|
337
|
+
self._ax,
|
|
338
|
+
self,
|
|
339
|
+
arrays,
|
|
340
|
+
self._recorder,
|
|
341
|
+
self._position,
|
|
342
|
+
self._track and track,
|
|
343
|
+
id,
|
|
344
|
+
overlap,
|
|
345
|
+
fill_alpha,
|
|
346
|
+
line_alpha,
|
|
347
|
+
colors,
|
|
348
|
+
labels,
|
|
349
|
+
**kwargs,
|
|
350
|
+
)
|
|
951
351
|
|
|
952
352
|
def swarmplot(
|
|
953
353
|
self,
|
|
@@ -962,167 +362,124 @@ class RecordingAxes:
|
|
|
962
362
|
track: bool = True,
|
|
963
363
|
**kwargs,
|
|
964
364
|
):
|
|
965
|
-
"""Create a swarm plot (beeswarm plot) showing individual data points.
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
Custom ID for this call.
|
|
983
|
-
track : bool, optional
|
|
984
|
-
Whether to record this call (default: True).
|
|
985
|
-
**kwargs
|
|
986
|
-
Additional arguments passed to scatter.
|
|
987
|
-
|
|
988
|
-
Returns
|
|
989
|
-
-------
|
|
990
|
-
list
|
|
991
|
-
List of PathCollection objects.
|
|
365
|
+
"""Create a swarm plot (beeswarm plot) showing individual data points."""
|
|
366
|
+
from ._axes_plots import swarmplot_plot
|
|
367
|
+
|
|
368
|
+
return swarmplot_plot(
|
|
369
|
+
self._ax,
|
|
370
|
+
data,
|
|
371
|
+
positions,
|
|
372
|
+
self._recorder,
|
|
373
|
+
self._position,
|
|
374
|
+
self._track and track,
|
|
375
|
+
id,
|
|
376
|
+
size,
|
|
377
|
+
color,
|
|
378
|
+
alpha,
|
|
379
|
+
jitter,
|
|
380
|
+
**kwargs,
|
|
381
|
+
)
|
|
992
382
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
from .._utils._units import mm_to_pt
|
|
999
|
-
from ..styles import get_style
|
|
1000
|
-
|
|
1001
|
-
# Get style
|
|
1002
|
-
style = get_style()
|
|
1003
|
-
|
|
1004
|
-
# Default marker size from style
|
|
1005
|
-
if size is None:
|
|
1006
|
-
if style and "markers" in style:
|
|
1007
|
-
size = style.markers.get("scatter_mm", 0.8)
|
|
1008
|
-
else:
|
|
1009
|
-
size = 0.8
|
|
1010
|
-
size_pt = mm_to_pt(size) ** 2 # matplotlib uses area
|
|
1011
|
-
|
|
1012
|
-
# Get colors
|
|
1013
|
-
if color is None:
|
|
1014
|
-
if style and "colors" in style and "palette" in style.colors:
|
|
1015
|
-
palette = list(style.colors.palette)
|
|
1016
|
-
colors = []
|
|
1017
|
-
for c in palette:
|
|
1018
|
-
if isinstance(c, (list, tuple)) and len(c) >= 3:
|
|
1019
|
-
if all(v <= 1.0 for v in c):
|
|
1020
|
-
colors.append(tuple(c))
|
|
1021
|
-
else:
|
|
1022
|
-
colors.append(tuple(v / 255.0 for v in c))
|
|
1023
|
-
else:
|
|
1024
|
-
colors.append(c)
|
|
1025
|
-
else:
|
|
1026
|
-
import matplotlib.pyplot as plt
|
|
1027
|
-
|
|
1028
|
-
colors = [c["color"] for c in plt.rcParams["axes.prop_cycle"]]
|
|
1029
|
-
elif isinstance(color, list):
|
|
1030
|
-
colors = color
|
|
1031
|
-
else:
|
|
1032
|
-
colors = [color] * len(data)
|
|
1033
|
-
|
|
1034
|
-
# Default positions
|
|
1035
|
-
if positions is None:
|
|
1036
|
-
positions = list(range(1, len(data) + 1))
|
|
1037
|
-
|
|
1038
|
-
# Random generator for reproducible jitter
|
|
1039
|
-
rng = np.random.default_rng(42)
|
|
1040
|
-
|
|
1041
|
-
results = []
|
|
1042
|
-
for i, (arr, pos) in enumerate(zip(data, positions)):
|
|
1043
|
-
arr = np.asarray(arr)
|
|
1044
|
-
|
|
1045
|
-
# Create jittered x positions using beeswarm algorithm (simplified)
|
|
1046
|
-
x_jitter = self._beeswarm_positions(arr, jitter, rng)
|
|
1047
|
-
x_positions = pos + x_jitter
|
|
1048
|
-
|
|
1049
|
-
c = colors[i % len(colors)]
|
|
1050
|
-
result = self._ax.scatter(
|
|
1051
|
-
x_positions, arr, s=size_pt, c=[c], alpha=alpha, **kwargs
|
|
1052
|
-
)
|
|
1053
|
-
results.append(result)
|
|
383
|
+
@property
|
|
384
|
+
def caption(self) -> Optional[str]:
|
|
385
|
+
"""Get the panel caption metadata."""
|
|
386
|
+
ax_record = self._recorder.figure_record.get_or_create_axes(*self._position)
|
|
387
|
+
return ax_record.caption
|
|
1054
388
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
args=(data,),
|
|
1061
|
-
kwargs={
|
|
1062
|
-
"positions": positions,
|
|
1063
|
-
"size": size,
|
|
1064
|
-
"alpha": alpha,
|
|
1065
|
-
"jitter": jitter,
|
|
1066
|
-
},
|
|
1067
|
-
call_id=id,
|
|
1068
|
-
)
|
|
389
|
+
def generate_panel_caption(
|
|
390
|
+
self, label: Optional[str] = None, style: str = "publication"
|
|
391
|
+
) -> str:
|
|
392
|
+
"""Generate a caption for this panel from stats metadata."""
|
|
393
|
+
from ._caption_generator import generate_panel_caption
|
|
1069
394
|
|
|
1070
|
-
return
|
|
395
|
+
return generate_panel_caption(label=label, stats=self.stats, style=style)
|
|
1071
396
|
|
|
1072
|
-
def
|
|
397
|
+
def add_stat_annotation(
|
|
1073
398
|
self,
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
399
|
+
x1: float,
|
|
400
|
+
x2: float,
|
|
401
|
+
p_value: Optional[float] = None,
|
|
402
|
+
text: Optional[str] = None,
|
|
403
|
+
y: Optional[float] = None,
|
|
404
|
+
style: str = "stars",
|
|
405
|
+
bracket_height: Optional[float] = None,
|
|
406
|
+
text_offset: Optional[float] = None,
|
|
407
|
+
color: Optional[str] = None,
|
|
408
|
+
linewidth: Optional[float] = None,
|
|
409
|
+
fontsize: Optional[float] = None,
|
|
410
|
+
fontweight: Optional[str] = None,
|
|
411
|
+
id: Optional[str] = None,
|
|
412
|
+
track: bool = True,
|
|
413
|
+
**kwargs,
|
|
414
|
+
):
|
|
415
|
+
"""Add a statistical comparison annotation (bracket with stars/p-value).
|
|
1082
416
|
|
|
1083
417
|
Parameters
|
|
1084
418
|
----------
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
X offsets for each point.
|
|
419
|
+
x1, x2 : float
|
|
420
|
+
X positions of the two groups being compared.
|
|
421
|
+
p_value : float, optional
|
|
422
|
+
P-value for automatic star conversion.
|
|
423
|
+
text : str, optional
|
|
424
|
+
Custom text (overrides p_value formatting).
|
|
425
|
+
y : float, optional
|
|
426
|
+
Y position for bracket (auto-calculated if None).
|
|
427
|
+
style : str
|
|
428
|
+
"stars", "p_value", "both", or "bracket_only".
|
|
1096
429
|
"""
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
430
|
+
from ._stat_annotation import draw_stat_annotation
|
|
431
|
+
|
|
432
|
+
# Draw the annotation
|
|
433
|
+
artists = draw_stat_annotation(
|
|
434
|
+
self._ax,
|
|
435
|
+
x1,
|
|
436
|
+
x2,
|
|
437
|
+
y=y,
|
|
438
|
+
text=text,
|
|
439
|
+
p_value=p_value,
|
|
440
|
+
style=style,
|
|
441
|
+
bracket_height=bracket_height,
|
|
442
|
+
text_offset=text_offset,
|
|
443
|
+
color=color,
|
|
444
|
+
linewidth=linewidth,
|
|
445
|
+
fontsize=fontsize,
|
|
446
|
+
fontweight=fontweight,
|
|
447
|
+
**kwargs,
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# Record if tracking
|
|
451
|
+
if self._track and track:
|
|
452
|
+
call_id = id if id else self._recorder._generate_call_id("stat_annotation")
|
|
453
|
+
record_kwargs = {
|
|
454
|
+
"x1": x1,
|
|
455
|
+
"x2": x2,
|
|
456
|
+
"p_value": p_value,
|
|
457
|
+
"text": text,
|
|
458
|
+
"y": y,
|
|
459
|
+
"style": style,
|
|
460
|
+
"bracket_height": bracket_height,
|
|
461
|
+
"text_offset": text_offset,
|
|
462
|
+
"color": color,
|
|
463
|
+
"linewidth": linewidth,
|
|
464
|
+
"fontsize": fontsize,
|
|
465
|
+
}
|
|
466
|
+
record_kwargs.update(kwargs)
|
|
467
|
+
# Remove None values
|
|
468
|
+
record_kwargs = {k: v for k, v in record_kwargs.items() if v is not None}
|
|
469
|
+
|
|
470
|
+
from .._recorder import CallRecord
|
|
471
|
+
|
|
472
|
+
record = CallRecord(
|
|
473
|
+
id=call_id,
|
|
474
|
+
function="stat_annotation",
|
|
475
|
+
args=[],
|
|
476
|
+
kwargs=record_kwargs,
|
|
477
|
+
ax_position=self._position,
|
|
478
|
+
)
|
|
479
|
+
ax_record = self._recorder.figure_record.get_or_create_axes(*self._position)
|
|
480
|
+
ax_record.add_decoration(record)
|
|
481
|
+
|
|
482
|
+
return artists
|
|
1126
483
|
|
|
1127
484
|
|
|
1128
485
|
class _NoRecordContext:
|