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
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Custom plotting methods for RecordingAxes."""
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any, List, Optional
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from matplotlib.axes import Axes
|
|
11
|
+
|
|
12
|
+
from .._recorder import Recorder
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def pie_plot(
|
|
16
|
+
ax: "Axes",
|
|
17
|
+
x,
|
|
18
|
+
recorder: "Recorder",
|
|
19
|
+
position: tuple,
|
|
20
|
+
track: bool,
|
|
21
|
+
call_id: Optional[str],
|
|
22
|
+
**kwargs,
|
|
23
|
+
) -> tuple:
|
|
24
|
+
"""Pie chart with automatic SCITEX styling."""
|
|
25
|
+
from ..styles import get_style
|
|
26
|
+
|
|
27
|
+
# Get style settings before calling pie
|
|
28
|
+
style = get_style()
|
|
29
|
+
pie_style = style.get("pie", {}) if style else {}
|
|
30
|
+
|
|
31
|
+
# Apply wedge edge styling via wedgeprops if not already specified
|
|
32
|
+
kwargs = _apply_pie_wedgeprops(kwargs, pie_style)
|
|
33
|
+
|
|
34
|
+
# Call matplotlib's pie
|
|
35
|
+
result = ax.pie(x, **kwargs)
|
|
36
|
+
|
|
37
|
+
# Apply additional style settings
|
|
38
|
+
if style:
|
|
39
|
+
_apply_pie_text_style(ax, pie_style, style)
|
|
40
|
+
_apply_pie_axes_visibility(ax, pie_style)
|
|
41
|
+
|
|
42
|
+
# Record the call if tracking is enabled
|
|
43
|
+
if track:
|
|
44
|
+
recorder.record_call(
|
|
45
|
+
ax_position=position,
|
|
46
|
+
method_name="pie",
|
|
47
|
+
args=(x,),
|
|
48
|
+
kwargs=kwargs,
|
|
49
|
+
call_id=call_id,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _apply_pie_wedgeprops(kwargs: dict, pie_style: dict) -> dict:
|
|
56
|
+
"""Apply wedge properties to pie kwargs."""
|
|
57
|
+
from .._utils._units import mm_to_pt
|
|
58
|
+
|
|
59
|
+
edge_color = pie_style.get("edge_color", "black")
|
|
60
|
+
edge_mm = pie_style.get("edge_mm", 0.2)
|
|
61
|
+
edge_lw = mm_to_pt(edge_mm)
|
|
62
|
+
|
|
63
|
+
if "wedgeprops" not in kwargs:
|
|
64
|
+
kwargs["wedgeprops"] = {"edgecolor": edge_color, "linewidth": edge_lw}
|
|
65
|
+
elif "edgecolor" not in kwargs.get("wedgeprops", {}):
|
|
66
|
+
kwargs["wedgeprops"]["edgecolor"] = edge_color
|
|
67
|
+
kwargs["wedgeprops"]["linewidth"] = edge_lw
|
|
68
|
+
|
|
69
|
+
return kwargs
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _apply_pie_text_style(ax: "Axes", pie_style: dict, style) -> None:
|
|
73
|
+
"""Apply text styling to pie chart."""
|
|
74
|
+
from ..styles._style_applier import check_font
|
|
75
|
+
|
|
76
|
+
text_pt = pie_style.get("text_pt", 6)
|
|
77
|
+
font_family = check_font(style.get("fonts", {}).get("family", "Arial"))
|
|
78
|
+
|
|
79
|
+
# Get text color from rcParams for dark mode support
|
|
80
|
+
import matplotlib.pyplot as mpl_plt
|
|
81
|
+
|
|
82
|
+
text_color = mpl_plt.rcParams.get("text.color", "black")
|
|
83
|
+
|
|
84
|
+
for text in ax.texts:
|
|
85
|
+
text.set_fontsize(text_pt)
|
|
86
|
+
text.set_fontfamily(font_family)
|
|
87
|
+
text.set_color(text_color)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _apply_pie_axes_visibility(ax: "Axes", pie_style: dict) -> None:
|
|
91
|
+
"""Apply axes visibility settings for pie chart."""
|
|
92
|
+
show_axes = pie_style.get("show_axes", False)
|
|
93
|
+
if not show_axes:
|
|
94
|
+
ax.set_xticks([])
|
|
95
|
+
ax.set_yticks([])
|
|
96
|
+
ax.set_xticklabels([])
|
|
97
|
+
ax.set_yticklabels([])
|
|
98
|
+
for spine in ax.spines.values():
|
|
99
|
+
spine.set_visible(False)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def imshow_plot(
|
|
103
|
+
ax: "Axes",
|
|
104
|
+
X,
|
|
105
|
+
recorder: "Recorder",
|
|
106
|
+
position: tuple,
|
|
107
|
+
track: bool,
|
|
108
|
+
call_id: Optional[str],
|
|
109
|
+
**kwargs,
|
|
110
|
+
):
|
|
111
|
+
"""Display image with automatic SCITEX styling."""
|
|
112
|
+
from ..styles import get_style
|
|
113
|
+
|
|
114
|
+
# Call matplotlib's imshow
|
|
115
|
+
result = ax.imshow(X, **kwargs)
|
|
116
|
+
|
|
117
|
+
# Get style settings
|
|
118
|
+
style = get_style()
|
|
119
|
+
if style:
|
|
120
|
+
imshow_style = style.get("imshow", {})
|
|
121
|
+
show_axes = imshow_style.get("show_axes", True)
|
|
122
|
+
show_labels = imshow_style.get("show_labels", True)
|
|
123
|
+
|
|
124
|
+
if not show_axes:
|
|
125
|
+
ax.set_xticks([])
|
|
126
|
+
ax.set_yticks([])
|
|
127
|
+
ax.set_xticklabels([])
|
|
128
|
+
ax.set_yticklabels([])
|
|
129
|
+
for spine in ax.spines.values():
|
|
130
|
+
spine.set_visible(False)
|
|
131
|
+
|
|
132
|
+
if not show_labels:
|
|
133
|
+
ax.set_xlabel("")
|
|
134
|
+
ax.set_ylabel("")
|
|
135
|
+
|
|
136
|
+
# Record the call if tracking is enabled
|
|
137
|
+
if track:
|
|
138
|
+
recorder.record_call(
|
|
139
|
+
ax_position=position,
|
|
140
|
+
method_name="imshow",
|
|
141
|
+
args=(X,),
|
|
142
|
+
kwargs=kwargs,
|
|
143
|
+
call_id=call_id,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return result
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def violinplot_plot(
|
|
150
|
+
ax: "Axes",
|
|
151
|
+
dataset,
|
|
152
|
+
positions,
|
|
153
|
+
recorder: "Recorder",
|
|
154
|
+
position: tuple,
|
|
155
|
+
track: bool,
|
|
156
|
+
call_id: Optional[str],
|
|
157
|
+
inner: Optional[str],
|
|
158
|
+
**kwargs,
|
|
159
|
+
) -> dict:
|
|
160
|
+
"""Violin plot with support for inner display options."""
|
|
161
|
+
from ..styles import get_style
|
|
162
|
+
|
|
163
|
+
# Get style settings
|
|
164
|
+
style = get_style()
|
|
165
|
+
violin_style = style.get("violinplot", {}) if style else {}
|
|
166
|
+
|
|
167
|
+
# Determine inner type
|
|
168
|
+
if inner is None:
|
|
169
|
+
inner = violin_style.get("inner", "box")
|
|
170
|
+
|
|
171
|
+
# Get violin display options from style
|
|
172
|
+
showmeans = kwargs.pop("showmeans", violin_style.get("showmeans", False))
|
|
173
|
+
showmedians = kwargs.pop("showmedians", violin_style.get("showmedians", True))
|
|
174
|
+
showextrema = kwargs.pop("showextrema", violin_style.get("showextrema", False))
|
|
175
|
+
|
|
176
|
+
# Call matplotlib's violinplot
|
|
177
|
+
result = ax.violinplot(
|
|
178
|
+
dataset,
|
|
179
|
+
positions=positions,
|
|
180
|
+
showmeans=showmeans,
|
|
181
|
+
showmedians=showmedians if inner not in ("box", "swarm") else False,
|
|
182
|
+
showextrema=showextrema if inner not in ("box", "swarm") else False,
|
|
183
|
+
**kwargs,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Apply alpha from style to violin bodies
|
|
187
|
+
alpha = violin_style.get("alpha", 0.7)
|
|
188
|
+
if "bodies" in result:
|
|
189
|
+
for body in result["bodies"]:
|
|
190
|
+
body.set_alpha(alpha)
|
|
191
|
+
|
|
192
|
+
# Overlay inner elements
|
|
193
|
+
if positions is None:
|
|
194
|
+
positions = list(range(1, len(dataset) + 1))
|
|
195
|
+
|
|
196
|
+
_add_violin_inner_elements(ax, dataset, positions, inner, violin_style)
|
|
197
|
+
|
|
198
|
+
# Record the call if tracking is enabled
|
|
199
|
+
if track:
|
|
200
|
+
recorded_kwargs = kwargs.copy()
|
|
201
|
+
recorded_kwargs["inner"] = inner
|
|
202
|
+
recorded_kwargs["showmeans"] = showmeans
|
|
203
|
+
recorded_kwargs["showmedians"] = showmedians
|
|
204
|
+
recorded_kwargs["showextrema"] = showextrema
|
|
205
|
+
|
|
206
|
+
recorder.record_call(
|
|
207
|
+
ax_position=position,
|
|
208
|
+
method_name="violinplot",
|
|
209
|
+
args=(dataset,),
|
|
210
|
+
kwargs=recorded_kwargs,
|
|
211
|
+
call_id=call_id,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return result
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _add_violin_inner_elements(
|
|
218
|
+
ax: "Axes", dataset, positions, inner: str, violin_style: dict
|
|
219
|
+
) -> None:
|
|
220
|
+
"""Add inner elements to violin plot."""
|
|
221
|
+
from ._violin_helpers import (
|
|
222
|
+
add_violin_inner_box,
|
|
223
|
+
add_violin_inner_point,
|
|
224
|
+
add_violin_inner_stick,
|
|
225
|
+
add_violin_inner_swarm,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if inner == "box":
|
|
229
|
+
add_violin_inner_box(ax, dataset, positions, violin_style)
|
|
230
|
+
elif inner == "swarm":
|
|
231
|
+
add_violin_inner_swarm(ax, dataset, positions, violin_style)
|
|
232
|
+
elif inner == "stick":
|
|
233
|
+
add_violin_inner_stick(ax, dataset, positions, violin_style)
|
|
234
|
+
elif inner == "point":
|
|
235
|
+
add_violin_inner_point(ax, dataset, positions, violin_style)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def joyplot_plot(
|
|
239
|
+
ax: "Axes",
|
|
240
|
+
recording_axes,
|
|
241
|
+
arrays,
|
|
242
|
+
recorder: "Recorder",
|
|
243
|
+
position: tuple,
|
|
244
|
+
track: bool,
|
|
245
|
+
call_id: Optional[str],
|
|
246
|
+
overlap: float,
|
|
247
|
+
fill_alpha: float,
|
|
248
|
+
line_alpha: float,
|
|
249
|
+
colors,
|
|
250
|
+
labels,
|
|
251
|
+
**kwargs,
|
|
252
|
+
):
|
|
253
|
+
"""Create a joyplot (ridgeline plot)."""
|
|
254
|
+
from .._utils._units import mm_to_pt
|
|
255
|
+
from ..styles import get_style
|
|
256
|
+
|
|
257
|
+
# Convert dict to list of arrays
|
|
258
|
+
if isinstance(arrays, dict):
|
|
259
|
+
if labels is None:
|
|
260
|
+
labels = list(arrays.keys())
|
|
261
|
+
arrays = list(arrays.values())
|
|
262
|
+
|
|
263
|
+
n_ridges = len(arrays)
|
|
264
|
+
|
|
265
|
+
from ._plot_helpers import compute_joyplot_kdes, get_colors_from_style
|
|
266
|
+
|
|
267
|
+
# Get colors from style or use default cycle
|
|
268
|
+
colors = get_colors_from_style(n_ridges, colors)
|
|
269
|
+
|
|
270
|
+
# Calculate global x range
|
|
271
|
+
all_data = np.concatenate([np.asarray(arr) for arr in arrays])
|
|
272
|
+
x_min, x_max = np.min(all_data), np.max(all_data)
|
|
273
|
+
x_range = x_max - x_min
|
|
274
|
+
x_padding = x_range * 0.1
|
|
275
|
+
x = np.linspace(x_min - x_padding, x_max + x_padding, 200)
|
|
276
|
+
|
|
277
|
+
# Calculate KDEs
|
|
278
|
+
kdes, max_density = compute_joyplot_kdes(arrays, x)
|
|
279
|
+
|
|
280
|
+
# Scale factor for ridge height
|
|
281
|
+
ridge_height = 1.0 / (1.0 - overlap * 0.5) if overlap < 1 else 2.0
|
|
282
|
+
|
|
283
|
+
# Get line width from style
|
|
284
|
+
style = get_style()
|
|
285
|
+
lw = mm_to_pt(0.2)
|
|
286
|
+
if style and "lines" in style:
|
|
287
|
+
lw = mm_to_pt(style.lines.get("trace_mm", 0.2))
|
|
288
|
+
|
|
289
|
+
# Plot each ridge from back to front
|
|
290
|
+
for i in range(n_ridges - 1, -1, -1):
|
|
291
|
+
color = colors[i % len(colors)]
|
|
292
|
+
baseline = i * (1.0 - overlap)
|
|
293
|
+
scaled_density = (
|
|
294
|
+
kdes[i] / max_density * ridge_height if max_density > 0 else kdes[i]
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
ax.fill_between(
|
|
298
|
+
x,
|
|
299
|
+
baseline,
|
|
300
|
+
baseline + scaled_density,
|
|
301
|
+
facecolor=color,
|
|
302
|
+
edgecolor="none",
|
|
303
|
+
alpha=fill_alpha,
|
|
304
|
+
)
|
|
305
|
+
ax.plot(
|
|
306
|
+
x,
|
|
307
|
+
baseline + scaled_density,
|
|
308
|
+
color=color,
|
|
309
|
+
alpha=line_alpha,
|
|
310
|
+
linewidth=lw,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Set y limits
|
|
314
|
+
ax.set_ylim(-0.1, n_ridges * (1.0 - overlap) + ridge_height)
|
|
315
|
+
|
|
316
|
+
# Set y-axis labels
|
|
317
|
+
if labels:
|
|
318
|
+
y_positions = [(i * (1.0 - overlap)) + 0.3 for i in range(n_ridges)]
|
|
319
|
+
ax.set_yticks(y_positions)
|
|
320
|
+
ax.set_yticklabels(labels)
|
|
321
|
+
else:
|
|
322
|
+
ax.set_yticks([])
|
|
323
|
+
|
|
324
|
+
# Record the call if tracking is enabled
|
|
325
|
+
if track:
|
|
326
|
+
recorder.record_call(
|
|
327
|
+
ax_position=position,
|
|
328
|
+
method_name="joyplot",
|
|
329
|
+
args=(arrays,),
|
|
330
|
+
kwargs={
|
|
331
|
+
"overlap": overlap,
|
|
332
|
+
"fill_alpha": fill_alpha,
|
|
333
|
+
"line_alpha": line_alpha,
|
|
334
|
+
"labels": labels,
|
|
335
|
+
},
|
|
336
|
+
call_id=call_id,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
return recording_axes
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def swarmplot_plot(
|
|
343
|
+
ax: "Axes",
|
|
344
|
+
data,
|
|
345
|
+
positions,
|
|
346
|
+
recorder: "Recorder",
|
|
347
|
+
position: tuple,
|
|
348
|
+
track: bool,
|
|
349
|
+
call_id: Optional[str],
|
|
350
|
+
size: Optional[float],
|
|
351
|
+
color,
|
|
352
|
+
alpha: float,
|
|
353
|
+
jitter: float,
|
|
354
|
+
**kwargs,
|
|
355
|
+
) -> List[Any]:
|
|
356
|
+
"""Create a swarm plot (beeswarm plot)."""
|
|
357
|
+
from .._utils._units import mm_to_pt
|
|
358
|
+
from ..styles import get_style
|
|
359
|
+
|
|
360
|
+
# Get style
|
|
361
|
+
style = get_style()
|
|
362
|
+
|
|
363
|
+
# Default marker size from style
|
|
364
|
+
if size is None:
|
|
365
|
+
if style and "markers" in style:
|
|
366
|
+
size = style.markers.get("scatter_mm", 0.8)
|
|
367
|
+
else:
|
|
368
|
+
size = 0.8
|
|
369
|
+
size_pt = mm_to_pt(size) ** 2
|
|
370
|
+
|
|
371
|
+
from ._plot_helpers import beeswarm_positions, get_colors_from_style
|
|
372
|
+
|
|
373
|
+
# Get colors
|
|
374
|
+
colors = get_colors_from_style(len(data), color)
|
|
375
|
+
|
|
376
|
+
# Default positions
|
|
377
|
+
if positions is None:
|
|
378
|
+
positions = list(range(1, len(data) + 1))
|
|
379
|
+
|
|
380
|
+
# Random generator for reproducible jitter
|
|
381
|
+
rng = np.random.default_rng(42)
|
|
382
|
+
|
|
383
|
+
results = []
|
|
384
|
+
for i, (arr, pos) in enumerate(zip(data, positions)):
|
|
385
|
+
arr = np.asarray(arr)
|
|
386
|
+
x_jitter = beeswarm_positions(arr, jitter, rng)
|
|
387
|
+
x_positions = pos + x_jitter
|
|
388
|
+
c = colors[i % len(colors)]
|
|
389
|
+
result = ax.scatter(x_positions, arr, s=size_pt, c=[c], alpha=alpha, **kwargs)
|
|
390
|
+
results.append(result)
|
|
391
|
+
|
|
392
|
+
# Record the call if tracking is enabled
|
|
393
|
+
if track:
|
|
394
|
+
recorder.record_call(
|
|
395
|
+
ax_position=position,
|
|
396
|
+
method_name="swarmplot",
|
|
397
|
+
args=(data,),
|
|
398
|
+
kwargs={
|
|
399
|
+
"positions": positions,
|
|
400
|
+
"size": size,
|
|
401
|
+
"alpha": alpha,
|
|
402
|
+
"jitter": jitter,
|
|
403
|
+
},
|
|
404
|
+
call_id=call_id,
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
return results
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
__all__ = [
|
|
411
|
+
"pie_plot",
|
|
412
|
+
"imshow_plot",
|
|
413
|
+
"violinplot_plot",
|
|
414
|
+
"joyplot_plot",
|
|
415
|
+
"swarmplot_plot",
|
|
416
|
+
]
|
|
417
|
+
|
|
418
|
+
# EOF
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Seaborn recording support for RecordingAxes."""
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .._recorder import Recorder
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def record_seaborn_call(
|
|
14
|
+
recorder: "Recorder",
|
|
15
|
+
position: tuple,
|
|
16
|
+
func_name: str,
|
|
17
|
+
args: tuple,
|
|
18
|
+
kwargs: Dict[str, Any],
|
|
19
|
+
data_arrays: Dict[str, np.ndarray],
|
|
20
|
+
call_id: Optional[str] = None,
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Record a seaborn plotting call.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
recorder : Recorder
|
|
27
|
+
The recorder instance.
|
|
28
|
+
position : tuple
|
|
29
|
+
(row, col) position of axes.
|
|
30
|
+
func_name : str
|
|
31
|
+
Name of the seaborn function (e.g., 'scatterplot').
|
|
32
|
+
args : tuple
|
|
33
|
+
Processed positional arguments.
|
|
34
|
+
kwargs : dict
|
|
35
|
+
Processed keyword arguments.
|
|
36
|
+
data_arrays : dict
|
|
37
|
+
Dictionary of array data extracted from DataFrame/arrays.
|
|
38
|
+
call_id : str, optional
|
|
39
|
+
Custom ID for this call.
|
|
40
|
+
"""
|
|
41
|
+
from .._recorder import CallRecord
|
|
42
|
+
|
|
43
|
+
# Generate call ID if not provided
|
|
44
|
+
if call_id is None:
|
|
45
|
+
call_id = recorder._generate_call_id(f"sns_{func_name}")
|
|
46
|
+
|
|
47
|
+
# Process data arrays into args format
|
|
48
|
+
processed_args = _process_seaborn_args(args, data_arrays)
|
|
49
|
+
|
|
50
|
+
# Process DataFrame column data
|
|
51
|
+
_add_column_data_to_args(processed_args, data_arrays)
|
|
52
|
+
|
|
53
|
+
# Process kwarg arrays
|
|
54
|
+
processed_kwargs = _process_seaborn_kwargs(kwargs, data_arrays)
|
|
55
|
+
|
|
56
|
+
# Create call record
|
|
57
|
+
record = CallRecord(
|
|
58
|
+
id=call_id,
|
|
59
|
+
function=f"sns.{func_name}",
|
|
60
|
+
args=processed_args,
|
|
61
|
+
kwargs=processed_kwargs,
|
|
62
|
+
ax_position=position,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Add to axes record
|
|
66
|
+
ax_record = recorder.figure_record.get_or_create_axes(*position)
|
|
67
|
+
ax_record.add_call(record)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _process_seaborn_args(args: tuple, data_arrays: Dict[str, np.ndarray]) -> list:
|
|
71
|
+
"""Process seaborn positional arguments."""
|
|
72
|
+
from .._utils._numpy_io import should_store_inline, to_serializable
|
|
73
|
+
|
|
74
|
+
processed_args = []
|
|
75
|
+
for i, arg in enumerate(args):
|
|
76
|
+
if arg == "__ARRAY__":
|
|
77
|
+
key = f"_arg_{i}"
|
|
78
|
+
if key in data_arrays:
|
|
79
|
+
arr = data_arrays[key]
|
|
80
|
+
if should_store_inline(arr):
|
|
81
|
+
processed_args.append(
|
|
82
|
+
{
|
|
83
|
+
"name": f"arg{i}",
|
|
84
|
+
"data": to_serializable(arr),
|
|
85
|
+
"dtype": str(arr.dtype),
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
else:
|
|
89
|
+
processed_args.append(
|
|
90
|
+
{
|
|
91
|
+
"name": f"arg{i}",
|
|
92
|
+
"data": "__FILE__",
|
|
93
|
+
"dtype": str(arr.dtype),
|
|
94
|
+
"_array": arr,
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
processed_args.append({"name": f"arg{i}", "data": arg})
|
|
99
|
+
|
|
100
|
+
return processed_args
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _add_column_data_to_args(
|
|
104
|
+
processed_args: list, data_arrays: Dict[str, np.ndarray]
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Add DataFrame column data to processed args."""
|
|
107
|
+
from .._utils._numpy_io import should_store_inline, to_serializable
|
|
108
|
+
|
|
109
|
+
for key, arr in data_arrays.items():
|
|
110
|
+
if key.startswith("_col_"):
|
|
111
|
+
param_name = key[5:] # Remove "_col_" prefix
|
|
112
|
+
col_name = data_arrays.get(f"_colname_{param_name}", param_name)
|
|
113
|
+
if should_store_inline(arr):
|
|
114
|
+
processed_args.append(
|
|
115
|
+
{
|
|
116
|
+
"name": col_name,
|
|
117
|
+
"param": param_name,
|
|
118
|
+
"data": to_serializable(arr),
|
|
119
|
+
"dtype": str(arr.dtype),
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
processed_args.append(
|
|
124
|
+
{
|
|
125
|
+
"name": col_name,
|
|
126
|
+
"param": param_name,
|
|
127
|
+
"data": "__FILE__",
|
|
128
|
+
"dtype": str(arr.dtype),
|
|
129
|
+
"_array": arr,
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _process_seaborn_kwargs(
|
|
135
|
+
kwargs: Dict[str, Any], data_arrays: Dict[str, np.ndarray]
|
|
136
|
+
) -> Dict[str, Any]:
|
|
137
|
+
"""Process seaborn keyword arguments."""
|
|
138
|
+
from .._utils._numpy_io import should_store_inline, to_serializable
|
|
139
|
+
|
|
140
|
+
processed_kwargs = dict(kwargs)
|
|
141
|
+
for key, value in kwargs.items():
|
|
142
|
+
if value == "__ARRAY__":
|
|
143
|
+
arr_key = f"_kwarg_{key}"
|
|
144
|
+
if arr_key in data_arrays:
|
|
145
|
+
arr = data_arrays[arr_key]
|
|
146
|
+
if should_store_inline(arr):
|
|
147
|
+
processed_kwargs[key] = to_serializable(arr)
|
|
148
|
+
else:
|
|
149
|
+
processed_kwargs[key] = "__FILE__"
|
|
150
|
+
processed_kwargs[f"_array_{key}"] = arr
|
|
151
|
+
|
|
152
|
+
return processed_kwargs
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
__all__ = ["record_seaborn_call"]
|
|
156
|
+
|
|
157
|
+
# EOF
|