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.
Files changed (269) hide show
  1. figrecipe/__init__.py +161 -1030
  2. figrecipe/__main__.py +12 -0
  3. figrecipe/_api/__init__.py +48 -0
  4. figrecipe/_api/_extract.py +108 -0
  5. figrecipe/_api/_notebook.py +61 -0
  6. figrecipe/_api/_panel.py +113 -0
  7. figrecipe/_api/_save.py +287 -0
  8. figrecipe/_api/_seaborn_proxy.py +34 -0
  9. figrecipe/_api/_style_manager.py +153 -0
  10. figrecipe/_api/_subplots.py +333 -0
  11. figrecipe/_api/_validate.py +82 -0
  12. figrecipe/_cli/__init__.py +7 -0
  13. figrecipe/_cli/_compose.py +87 -0
  14. figrecipe/_cli/_convert.py +117 -0
  15. figrecipe/_cli/_crop.py +82 -0
  16. figrecipe/_cli/_edit.py +70 -0
  17. figrecipe/_cli/_extract.py +128 -0
  18. figrecipe/_cli/_fonts.py +47 -0
  19. figrecipe/_cli/_info.py +67 -0
  20. figrecipe/_cli/_main.py +58 -0
  21. figrecipe/_cli/_reproduce.py +79 -0
  22. figrecipe/_cli/_style.py +77 -0
  23. figrecipe/_cli/_validate.py +66 -0
  24. figrecipe/_cli/_version.py +50 -0
  25. figrecipe/_composition/__init__.py +32 -0
  26. figrecipe/_composition/_alignment.py +452 -0
  27. figrecipe/_composition/_compose.py +179 -0
  28. figrecipe/_composition/_import_axes.py +127 -0
  29. figrecipe/_composition/_visibility.py +125 -0
  30. figrecipe/_dev/__init__.py +4 -93
  31. figrecipe/_dev/_plotters.py +76 -0
  32. figrecipe/_dev/_run_demos.py +56 -0
  33. figrecipe/_dev/browser/__init__.py +69 -0
  34. figrecipe/_dev/browser/_audio.py +240 -0
  35. figrecipe/_dev/browser/_caption.py +356 -0
  36. figrecipe/_dev/browser/_click_effect.py +146 -0
  37. figrecipe/_dev/browser/_cursor.py +196 -0
  38. figrecipe/_dev/browser/_highlight.py +105 -0
  39. figrecipe/_dev/browser/_narration.py +237 -0
  40. figrecipe/_dev/browser/_recorder.py +446 -0
  41. figrecipe/_dev/browser/_utils.py +178 -0
  42. figrecipe/_dev/browser/_video_trim/__init__.py +152 -0
  43. figrecipe/_dev/browser/_video_trim/_detection.py +223 -0
  44. figrecipe/_dev/browser/_video_trim/_markers.py +140 -0
  45. figrecipe/_dev/demo_plotters/__init__.py +35 -166
  46. figrecipe/_dev/demo_plotters/_categories.py +81 -0
  47. figrecipe/_dev/demo_plotters/_figure_creators.py +119 -0
  48. figrecipe/_dev/demo_plotters/_helpers.py +31 -0
  49. figrecipe/_dev/demo_plotters/_registry.py +50 -0
  50. figrecipe/_dev/demo_plotters/bar_categorical/__init__.py +4 -0
  51. figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
  52. figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
  53. figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
  54. figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
  55. figrecipe/_dev/demo_plotters/{plot_plot.py → line_curve/plot_plot.py} +3 -2
  56. figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
  57. figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
  58. figrecipe/_dev/demo_plotters/{plot_pie.py → special/plot_pie.py} +5 -1
  59. figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
  60. figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
  61. figrecipe/_editor/__init__.py +61 -13
  62. figrecipe/_editor/_bbox/__init__.py +43 -0
  63. figrecipe/_editor/_bbox/_collections.py +177 -0
  64. figrecipe/_editor/_bbox/_elements.py +159 -0
  65. figrecipe/_editor/_bbox/_extract.py +402 -0
  66. figrecipe/_editor/_bbox/_extract_axes.py +370 -0
  67. figrecipe/_editor/_bbox/_extract_text.py +466 -0
  68. figrecipe/_editor/_bbox/_lines.py +173 -0
  69. figrecipe/_editor/_bbox/_transforms.py +146 -0
  70. figrecipe/_editor/_call_overrides.py +183 -0
  71. figrecipe/_editor/_datatable_plot_handlers.py +249 -0
  72. figrecipe/_editor/_figure_layout.py +211 -0
  73. figrecipe/_editor/_flask_app.py +200 -1030
  74. figrecipe/_editor/_helpers.py +251 -0
  75. figrecipe/_editor/_hitmap/__init__.py +76 -0
  76. figrecipe/_editor/_hitmap/_artists/__init__.py +21 -0
  77. figrecipe/_editor/_hitmap/_artists/_collections.py +345 -0
  78. figrecipe/_editor/_hitmap/_artists/_images.py +68 -0
  79. figrecipe/_editor/_hitmap/_artists/_lines.py +107 -0
  80. figrecipe/_editor/_hitmap/_artists/_patches.py +163 -0
  81. figrecipe/_editor/_hitmap/_artists/_text.py +190 -0
  82. figrecipe/_editor/_hitmap/_colors.py +181 -0
  83. figrecipe/_editor/_hitmap/_detect.py +194 -0
  84. figrecipe/_editor/_hitmap/_restore.py +154 -0
  85. figrecipe/_editor/_hitmap_main.py +182 -0
  86. figrecipe/_editor/_overrides.py +4 -1
  87. figrecipe/_editor/_plot_types_registry.py +190 -0
  88. figrecipe/_editor/_preferences.py +135 -0
  89. figrecipe/_editor/_render_overrides.py +507 -0
  90. figrecipe/_editor/_renderer.py +81 -186
  91. figrecipe/_editor/_routes_annotation.py +114 -0
  92. figrecipe/_editor/_routes_axis.py +482 -0
  93. figrecipe/_editor/_routes_captions.py +130 -0
  94. figrecipe/_editor/_routes_composition.py +270 -0
  95. figrecipe/_editor/_routes_core.py +126 -0
  96. figrecipe/_editor/_routes_datatable.py +364 -0
  97. figrecipe/_editor/_routes_element.py +335 -0
  98. figrecipe/_editor/_routes_files.py +443 -0
  99. figrecipe/_editor/_routes_image.py +200 -0
  100. figrecipe/_editor/_routes_snapshot.py +94 -0
  101. figrecipe/_editor/_routes_style.py +243 -0
  102. figrecipe/_editor/_templates/__init__.py +116 -1
  103. figrecipe/_editor/_templates/_html.py +154 -64
  104. figrecipe/_editor/_templates/_html_components/__init__.py +13 -0
  105. figrecipe/_editor/_templates/_html_components/_composition_toolbar.py +79 -0
  106. figrecipe/_editor/_templates/_html_components/_file_browser.py +41 -0
  107. figrecipe/_editor/_templates/_html_datatable.py +92 -0
  108. figrecipe/_editor/_templates/_scripts/__init__.py +178 -0
  109. figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
  110. figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
  111. figrecipe/_editor/_templates/_scripts/_api.py +228 -0
  112. figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
  113. figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
  114. figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
  115. figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
  116. figrecipe/_editor/_templates/_scripts/_core.py +493 -0
  117. figrecipe/_editor/_templates/_scripts/_datatable/__init__.py +59 -0
  118. figrecipe/_editor/_templates/_scripts/_datatable/_cell_edit.py +97 -0
  119. figrecipe/_editor/_templates/_scripts/_datatable/_clipboard.py +164 -0
  120. figrecipe/_editor/_templates/_scripts/_datatable/_context_menu.py +221 -0
  121. figrecipe/_editor/_templates/_scripts/_datatable/_core.py +150 -0
  122. figrecipe/_editor/_templates/_scripts/_datatable/_editable.py +511 -0
  123. figrecipe/_editor/_templates/_scripts/_datatable/_import.py +161 -0
  124. figrecipe/_editor/_templates/_scripts/_datatable/_plot.py +261 -0
  125. figrecipe/_editor/_templates/_scripts/_datatable/_selection.py +438 -0
  126. figrecipe/_editor/_templates/_scripts/_datatable/_table.py +256 -0
  127. figrecipe/_editor/_templates/_scripts/_datatable/_tabs.py +354 -0
  128. figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
  129. figrecipe/_editor/_templates/_scripts/_element_editor.py +325 -0
  130. figrecipe/_editor/_templates/_scripts/_files.py +429 -0
  131. figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
  132. figrecipe/_editor/_templates/_scripts/_hitmap.py +512 -0
  133. figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
  134. figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
  135. figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
  136. figrecipe/_editor/_templates/_scripts/_legend_drag.py +270 -0
  137. figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
  138. figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
  139. figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
  140. figrecipe/_editor/_templates/_scripts/_panel_drag.py +505 -0
  141. figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
  142. figrecipe/_editor/_templates/_scripts/_panel_position.py +463 -0
  143. figrecipe/_editor/_templates/_scripts/_panel_resize.py +230 -0
  144. figrecipe/_editor/_templates/_scripts/_panel_snap.py +307 -0
  145. figrecipe/_editor/_templates/_scripts/_region_select.py +255 -0
  146. figrecipe/_editor/_templates/_scripts/_selection.py +244 -0
  147. figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
  148. figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
  149. figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
  150. figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
  151. figrecipe/_editor/_templates/_scripts/_zoom.py +212 -0
  152. figrecipe/_editor/_templates/_styles/__init__.py +78 -0
  153. figrecipe/_editor/_templates/_styles/_base.py +111 -0
  154. figrecipe/_editor/_templates/_styles/_buttons.py +327 -0
  155. figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
  156. figrecipe/_editor/_templates/_styles/_composition.py +87 -0
  157. figrecipe/_editor/_templates/_styles/_controls.py +430 -0
  158. figrecipe/_editor/_templates/_styles/_datatable/__init__.py +40 -0
  159. figrecipe/_editor/_templates/_styles/_datatable/_editable.py +203 -0
  160. figrecipe/_editor/_templates/_styles/_datatable/_panel.py +268 -0
  161. figrecipe/_editor/_templates/_styles/_datatable/_table.py +479 -0
  162. figrecipe/_editor/_templates/_styles/_datatable/_toolbar.py +384 -0
  163. figrecipe/_editor/_templates/_styles/_datatable/_vars.py +123 -0
  164. figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
  165. figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
  166. figrecipe/_editor/_templates/_styles/_forms.py +224 -0
  167. figrecipe/_editor/_templates/_styles/_hitmap.py +191 -0
  168. figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
  169. figrecipe/_editor/_templates/_styles/_labels.py +118 -0
  170. figrecipe/_editor/_templates/_styles/_modals.py +127 -0
  171. figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
  172. figrecipe/_editor/_templates/_styles/_preview.py +430 -0
  173. figrecipe/_editor/_templates/_styles/_selection.py +73 -0
  174. figrecipe/_editor/_templates/_styles/_spinner.py +117 -0
  175. figrecipe/_editor/static/audio/click.mp3 +0 -0
  176. figrecipe/_editor/static/click.mp3 +0 -0
  177. figrecipe/_editor/static/icons/favicon.ico +0 -0
  178. figrecipe/_integrations/__init__.py +17 -0
  179. figrecipe/_integrations/_scitex_stats.py +298 -0
  180. figrecipe/_params/_DECORATION_METHODS.py +8 -0
  181. figrecipe/_recorder.py +63 -109
  182. figrecipe/_recorder_utils.py +124 -0
  183. figrecipe/_reproducer/__init__.py +18 -0
  184. figrecipe/_reproducer/_core.py +509 -0
  185. figrecipe/_reproducer/_custom_plots.py +279 -0
  186. figrecipe/_reproducer/_seaborn.py +100 -0
  187. figrecipe/_reproducer/_violin.py +186 -0
  188. figrecipe/_signatures/_kwargs.py +273 -0
  189. figrecipe/_signatures/_loader.py +21 -423
  190. figrecipe/_signatures/_parsing.py +147 -0
  191. figrecipe/_utils/__init__.py +3 -0
  192. figrecipe/_utils/_bundle.py +205 -0
  193. figrecipe/_wrappers/_axes.py +252 -895
  194. figrecipe/_wrappers/_axes_helpers.py +136 -0
  195. figrecipe/_wrappers/_axes_plots.py +418 -0
  196. figrecipe/_wrappers/_axes_seaborn.py +157 -0
  197. figrecipe/_wrappers/_caption_generator.py +218 -0
  198. figrecipe/_wrappers/_figure.py +188 -1
  199. figrecipe/_wrappers/_panel_labels.py +127 -0
  200. figrecipe/_wrappers/_plot_helpers.py +143 -0
  201. figrecipe/_wrappers/_stat_annotation.py +274 -0
  202. figrecipe/_wrappers/_violin_helpers.py +180 -0
  203. figrecipe/styles/__init__.py +8 -6
  204. figrecipe/styles/_dotdict.py +72 -0
  205. figrecipe/styles/_finalize.py +134 -0
  206. figrecipe/styles/_fonts.py +77 -0
  207. figrecipe/styles/_kwargs_converter.py +178 -0
  208. figrecipe/styles/_plot_styles.py +209 -0
  209. figrecipe/styles/_style_applier.py +42 -480
  210. figrecipe/styles/_style_loader.py +16 -192
  211. figrecipe/styles/_themes.py +151 -0
  212. figrecipe/styles/presets/MATPLOTLIB.yaml +2 -1
  213. figrecipe/styles/presets/SCITEX.yaml +40 -28
  214. figrecipe-0.9.0.dist-info/METADATA +427 -0
  215. figrecipe-0.9.0.dist-info/RECORD +277 -0
  216. figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
  217. figrecipe/_editor/_bbox.py +0 -978
  218. figrecipe/_editor/_hitmap.py +0 -937
  219. figrecipe/_editor/_templates/_scripts.py +0 -2778
  220. figrecipe/_editor/_templates/_styles.py +0 -1326
  221. figrecipe/_reproducer.py +0 -975
  222. figrecipe-0.6.0.dist-info/METADATA +0 -394
  223. figrecipe-0.6.0.dist-info/RECORD +0 -90
  224. /figrecipe/_dev/demo_plotters/{plot_bar.py → bar_categorical/plot_bar.py} +0 -0
  225. /figrecipe/_dev/demo_plotters/{plot_barh.py → bar_categorical/plot_barh.py} +0 -0
  226. /figrecipe/_dev/demo_plotters/{plot_contour.py → contour_surface/plot_contour.py} +0 -0
  227. /figrecipe/_dev/demo_plotters/{plot_contourf.py → contour_surface/plot_contourf.py} +0 -0
  228. /figrecipe/_dev/demo_plotters/{plot_tricontour.py → contour_surface/plot_tricontour.py} +0 -0
  229. /figrecipe/_dev/demo_plotters/{plot_tricontourf.py → contour_surface/plot_tricontourf.py} +0 -0
  230. /figrecipe/_dev/demo_plotters/{plot_tripcolor.py → contour_surface/plot_tripcolor.py} +0 -0
  231. /figrecipe/_dev/demo_plotters/{plot_triplot.py → contour_surface/plot_triplot.py} +0 -0
  232. /figrecipe/_dev/demo_plotters/{plot_boxplot.py → distribution/plot_boxplot.py} +0 -0
  233. /figrecipe/_dev/demo_plotters/{plot_ecdf.py → distribution/plot_ecdf.py} +0 -0
  234. /figrecipe/_dev/demo_plotters/{plot_hist.py → distribution/plot_hist.py} +0 -0
  235. /figrecipe/_dev/demo_plotters/{plot_hist2d.py → distribution/plot_hist2d.py} +0 -0
  236. /figrecipe/_dev/demo_plotters/{plot_violinplot.py → distribution/plot_violinplot.py} +0 -0
  237. /figrecipe/_dev/demo_plotters/{plot_hexbin.py → image_matrix/plot_hexbin.py} +0 -0
  238. /figrecipe/_dev/demo_plotters/{plot_imshow.py → image_matrix/plot_imshow.py} +0 -0
  239. /figrecipe/_dev/demo_plotters/{plot_matshow.py → image_matrix/plot_matshow.py} +0 -0
  240. /figrecipe/_dev/demo_plotters/{plot_pcolor.py → image_matrix/plot_pcolor.py} +0 -0
  241. /figrecipe/_dev/demo_plotters/{plot_pcolormesh.py → image_matrix/plot_pcolormesh.py} +0 -0
  242. /figrecipe/_dev/demo_plotters/{plot_spy.py → image_matrix/plot_spy.py} +0 -0
  243. /figrecipe/_dev/demo_plotters/{plot_errorbar.py → line_curve/plot_errorbar.py} +0 -0
  244. /figrecipe/_dev/demo_plotters/{plot_fill.py → line_curve/plot_fill.py} +0 -0
  245. /figrecipe/_dev/demo_plotters/{plot_fill_between.py → line_curve/plot_fill_between.py} +0 -0
  246. /figrecipe/_dev/demo_plotters/{plot_fill_betweenx.py → line_curve/plot_fill_betweenx.py} +0 -0
  247. /figrecipe/_dev/demo_plotters/{plot_stackplot.py → line_curve/plot_stackplot.py} +0 -0
  248. /figrecipe/_dev/demo_plotters/{plot_stairs.py → line_curve/plot_stairs.py} +0 -0
  249. /figrecipe/_dev/demo_plotters/{plot_step.py → line_curve/plot_step.py} +0 -0
  250. /figrecipe/_dev/demo_plotters/{plot_scatter.py → scatter_points/plot_scatter.py} +0 -0
  251. /figrecipe/_dev/demo_plotters/{plot_eventplot.py → special/plot_eventplot.py} +0 -0
  252. /figrecipe/_dev/demo_plotters/{plot_loglog.py → special/plot_loglog.py} +0 -0
  253. /figrecipe/_dev/demo_plotters/{plot_semilogx.py → special/plot_semilogx.py} +0 -0
  254. /figrecipe/_dev/demo_plotters/{plot_semilogy.py → special/plot_semilogy.py} +0 -0
  255. /figrecipe/_dev/demo_plotters/{plot_stem.py → special/plot_stem.py} +0 -0
  256. /figrecipe/_dev/demo_plotters/{plot_acorr.py → spectral_signal/plot_acorr.py} +0 -0
  257. /figrecipe/_dev/demo_plotters/{plot_angle_spectrum.py → spectral_signal/plot_angle_spectrum.py} +0 -0
  258. /figrecipe/_dev/demo_plotters/{plot_cohere.py → spectral_signal/plot_cohere.py} +0 -0
  259. /figrecipe/_dev/demo_plotters/{plot_csd.py → spectral_signal/plot_csd.py} +0 -0
  260. /figrecipe/_dev/demo_plotters/{plot_magnitude_spectrum.py → spectral_signal/plot_magnitude_spectrum.py} +0 -0
  261. /figrecipe/_dev/demo_plotters/{plot_phase_spectrum.py → spectral_signal/plot_phase_spectrum.py} +0 -0
  262. /figrecipe/_dev/demo_plotters/{plot_psd.py → spectral_signal/plot_psd.py} +0 -0
  263. /figrecipe/_dev/demo_plotters/{plot_specgram.py → spectral_signal/plot_specgram.py} +0 -0
  264. /figrecipe/_dev/demo_plotters/{plot_xcorr.py → spectral_signal/plot_xcorr.py} +0 -0
  265. /figrecipe/_dev/demo_plotters/{plot_barbs.py → vector_flow/plot_barbs.py} +0 -0
  266. /figrecipe/_dev/demo_plotters/{plot_quiver.py → vector_flow/plot_quiver.py} +0 -0
  267. /figrecipe/_dev/demo_plotters/{plot_streamplot.py → vector_flow/plot_streamplot.py} +0 -0
  268. {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
  269. {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.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