figrecipe 0.6.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 +106 -973
- 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 +2 -93
- figrecipe/_dev/_plotters.py +76 -0
- figrecipe/_dev/_run_demos.py +56 -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 +57 -9
- 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 +68 -1039
- 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/_preferences.py +135 -0
- figrecipe/_editor/_render_overrides.py +480 -0
- figrecipe/_editor/_renderer.py +35 -185
- 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 +78 -1
- figrecipe/_editor/_templates/_html.py +109 -13
- 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 +6 -0
- figrecipe/_recorder.py +35 -106
- 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/_signatures/_kwargs.py +273 -0
- figrecipe/_signatures/_loader.py +21 -423
- figrecipe/_signatures/_parsing.py +147 -0
- figrecipe/_wrappers/_axes.py +119 -910
- 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 +162 -0
- figrecipe/_wrappers/_panel_labels.py +127 -0
- figrecipe/_wrappers/_plot_helpers.py +143 -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 +32 -478
- 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 +29 -24
- {figrecipe-0.6.0.dist-info → figrecipe-0.7.4.dist-info}/METADATA +37 -2
- figrecipe-0.7.4.dist-info/RECORD +188 -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/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.7.4.dist-info}/WHEEL +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.7.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
|
@@ -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
|