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,179 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Main composition logic for combining multiple figures."""
4
+
5
+ from pathlib import Path
6
+ from typing import Any, Dict, Tuple, Union
7
+
8
+ from numpy.typing import NDArray
9
+
10
+ from .._recorder import FigureRecord
11
+ from .._serializer import load_recipe
12
+ from .._wrappers import RecordingAxes, RecordingFigure
13
+
14
+
15
+ def compose(
16
+ layout: Tuple[int, int],
17
+ sources: Dict[Tuple[int, int], Union[str, Path, FigureRecord, Tuple]],
18
+ **kwargs,
19
+ ) -> Tuple[RecordingFigure, Union[RecordingAxes, NDArray]]:
20
+ """Compose a new figure from multiple recipe sources.
21
+
22
+ Parameters
23
+ ----------
24
+ layout : tuple
25
+ (nrows, ncols) for the new composite figure.
26
+ sources : dict
27
+ Mapping of (row, col) -> source specification.
28
+ Source can be:
29
+ - str/Path: Recipe file path (uses first axes)
30
+ - FigureRecord: Direct record (uses first axes)
31
+ - Tuple[source, ax_key]: Specific axes from source
32
+
33
+ **kwargs
34
+ Additional arguments passed to subplots().
35
+
36
+ Returns
37
+ -------
38
+ fig : RecordingFigure
39
+ Composed figure.
40
+ axes : RecordingAxes or ndarray of RecordingAxes
41
+ Axes of the composed figure.
42
+
43
+ Examples
44
+ --------
45
+ >>> import figrecipe as fr
46
+ >>> fig, axes = fr.compose(
47
+ ... layout=(1, 2),
48
+ ... sources={
49
+ ... (0, 0): "experiment_a.yaml",
50
+ ... (0, 1): "experiment_b.yaml",
51
+ ... }
52
+ ... )
53
+ """
54
+ from .. import subplots
55
+
56
+ nrows, ncols = layout
57
+ fig, axes = subplots(nrows=nrows, ncols=ncols, **kwargs)
58
+
59
+ for (row, col), source_spec in sources.items():
60
+ source_record, ax_key = _parse_source_spec(source_spec)
61
+ ax_record = source_record.axes.get(ax_key)
62
+
63
+ if ax_record is None:
64
+ available = list(source_record.axes.keys())
65
+ raise ValueError(
66
+ f"Axes '{ax_key}' not found in source. Available: {available}"
67
+ )
68
+
69
+ target_ax = _get_axes_at(axes, row, col, nrows, ncols)
70
+ _replay_axes_record(target_ax, ax_record, fig.record, row, col)
71
+
72
+ return fig, axes
73
+
74
+
75
+ def _parse_source_spec(
76
+ spec: Union[str, Path, FigureRecord, Tuple],
77
+ ) -> Tuple[FigureRecord, str]:
78
+ """Parse source specification into (FigureRecord, ax_key).
79
+
80
+ Parameters
81
+ ----------
82
+ spec : various
83
+ Source specification.
84
+
85
+ Returns
86
+ -------
87
+ tuple
88
+ (FigureRecord, ax_key)
89
+ """
90
+ if isinstance(spec, (str, Path)):
91
+ return load_recipe(spec), "ax_0_0"
92
+ elif isinstance(spec, FigureRecord):
93
+ return spec, "ax_0_0"
94
+ elif isinstance(spec, tuple) and len(spec) == 2:
95
+ source, ax_key = spec
96
+ if isinstance(source, (str, Path)):
97
+ return load_recipe(source), ax_key
98
+ elif isinstance(source, FigureRecord):
99
+ return source, ax_key
100
+ raise TypeError(f"Invalid source in tuple: {type(source)}")
101
+ raise TypeError(f"Invalid source spec type: {type(spec)}")
102
+
103
+
104
+ def _get_axes_at(
105
+ axes: Union[RecordingAxes, NDArray],
106
+ row: int,
107
+ col: int,
108
+ nrows: int,
109
+ ncols: int,
110
+ ) -> RecordingAxes:
111
+ """Get axes at position, handling different array shapes.
112
+
113
+ Parameters
114
+ ----------
115
+ axes : RecordingAxes or ndarray
116
+ Axes object(s) from subplots.
117
+ row, col : int
118
+ Target position.
119
+ nrows, ncols : int
120
+ Grid dimensions.
121
+
122
+ Returns
123
+ -------
124
+ RecordingAxes
125
+ Axes at the specified position.
126
+ """
127
+ if nrows == 1 and ncols == 1:
128
+ return axes
129
+ elif nrows == 1:
130
+ return axes[col]
131
+ elif ncols == 1:
132
+ return axes[row]
133
+ else:
134
+ return axes[row, col]
135
+
136
+
137
+ def _replay_axes_record(
138
+ target_ax: RecordingAxes,
139
+ ax_record,
140
+ fig_record: FigureRecord,
141
+ row: int,
142
+ col: int,
143
+ ) -> None:
144
+ """Replay all calls from ax_record onto target axes.
145
+
146
+ Parameters
147
+ ----------
148
+ target_ax : RecordingAxes
149
+ Target axes to replay onto.
150
+ ax_record : AxesRecord
151
+ Source axes record with calls.
152
+ fig_record : FigureRecord
153
+ Figure record to update.
154
+ row, col : int
155
+ Target position for recording.
156
+ """
157
+ from .._reproducer._core import _replay_call
158
+
159
+ mpl_ax = target_ax._ax if hasattr(target_ax, "_ax") else target_ax
160
+ result_cache: Dict[str, Any] = {}
161
+
162
+ # Replay plotting calls
163
+ for call in ax_record.calls:
164
+ result = _replay_call(mpl_ax, call, result_cache)
165
+ if result is not None:
166
+ result_cache[call.id] = result
167
+
168
+ # Replay decoration calls
169
+ for call in ax_record.decorations:
170
+ result = _replay_call(mpl_ax, call, result_cache)
171
+ if result is not None:
172
+ result_cache[call.id] = result
173
+
174
+ # Update figure record with imported axes
175
+ ax_key = f"ax_{row}_{col}"
176
+ fig_record.axes[ax_key] = ax_record
177
+
178
+
179
+ __all__ = ["compose"]
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Import axes from external recipes into existing figures."""
4
+
5
+ from pathlib import Path
6
+ from typing import Tuple, Union
7
+
8
+ from .._recorder import FigureRecord
9
+ from .._serializer import load_recipe
10
+ from .._wrappers import RecordingAxes, RecordingFigure
11
+ from ._compose import _replay_axes_record
12
+
13
+
14
+ def import_axes(
15
+ fig: RecordingFigure,
16
+ target_position: Tuple[int, int],
17
+ source: Union[str, Path, FigureRecord],
18
+ source_axes: str = "ax_0_0",
19
+ ) -> RecordingAxes:
20
+ """Import axes from another recipe into an existing figure.
21
+
22
+ This function copies all plotting calls and decorations from a source
23
+ axes (in a recipe file or FigureRecord) to a target position in an
24
+ existing figure. The target axes is cleared before import.
25
+
26
+ Parameters
27
+ ----------
28
+ fig : RecordingFigure
29
+ Target figure to import into.
30
+ target_position : tuple
31
+ (row, col) position in target figure.
32
+ source : str, Path, or FigureRecord
33
+ Source recipe file path or FigureRecord object.
34
+ source_axes : str, optional
35
+ Key of axes to import from source (default: "ax_0_0").
36
+
37
+ Returns
38
+ -------
39
+ RecordingAxes
40
+ The target axes after import.
41
+
42
+ Raises
43
+ ------
44
+ ValueError
45
+ If source_axes key not found in source.
46
+ TypeError
47
+ If source is not a valid type.
48
+
49
+ Examples
50
+ --------
51
+ >>> import figrecipe as fr
52
+ >>> fig, axes = fr.subplots(1, 2)
53
+ >>> axes[0].plot([1, 2, 3], [1, 4, 9])
54
+ >>> fr.import_axes(fig, (0, 1), "analysis.yaml")
55
+ """
56
+ # Load source if path
57
+ if isinstance(source, (str, Path)):
58
+ source_record = load_recipe(source)
59
+ elif isinstance(source, FigureRecord):
60
+ source_record = source
61
+ else:
62
+ raise TypeError(f"Invalid source type: {type(source)}")
63
+
64
+ # Get source axes record
65
+ ax_record = source_record.axes.get(source_axes)
66
+ if ax_record is None:
67
+ available = list(source_record.axes.keys())
68
+ raise ValueError(
69
+ f"Axes '{source_axes}' not found in source. Available: {available}"
70
+ )
71
+
72
+ # Get target axes
73
+ row, col = target_position
74
+ target_ax = _get_target_axes(fig, row, col)
75
+
76
+ # Clear existing content
77
+ mpl_ax = target_ax._ax if hasattr(target_ax, "_ax") else target_ax
78
+ mpl_ax.clear()
79
+
80
+ # Replay source calls onto target
81
+ _replay_axes_record(target_ax, ax_record, fig.record, row, col)
82
+
83
+ return target_ax
84
+
85
+
86
+ def _get_target_axes(
87
+ fig: RecordingFigure,
88
+ row: int,
89
+ col: int,
90
+ ) -> RecordingAxes:
91
+ """Get target axes from figure at position.
92
+
93
+ Parameters
94
+ ----------
95
+ fig : RecordingFigure
96
+ The figure.
97
+ row, col : int
98
+ Target position.
99
+
100
+ Returns
101
+ -------
102
+ RecordingAxes
103
+ Axes at position.
104
+
105
+ Raises
106
+ ------
107
+ IndexError
108
+ If position is out of range.
109
+ """
110
+ if not hasattr(fig, "_axes"):
111
+ raise ValueError("Figure must have _axes attribute")
112
+
113
+ axes = fig._axes
114
+ try:
115
+ # Handle different axes array structures
116
+ if isinstance(axes, list):
117
+ if isinstance(axes[0], list):
118
+ return axes[row][col]
119
+ else:
120
+ return axes[max(row, col)]
121
+ else:
122
+ return axes[row, col]
123
+ except (IndexError, KeyError) as e:
124
+ raise IndexError(f"Position ({row}, {col}) out of range for figure axes") from e
125
+
126
+
127
+ __all__ = ["import_axes"]
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Panel visibility management for composition feature."""
4
+
5
+ from typing import Tuple
6
+
7
+ from .._wrappers import RecordingFigure
8
+
9
+
10
+ def hide_panel(fig: RecordingFigure, position: Tuple[int, int]) -> None:
11
+ """Hide a panel (visually drop it without deleting data).
12
+
13
+ The panel data is preserved in the recipe but not rendered.
14
+ Use show_panel() to restore visibility.
15
+
16
+ Parameters
17
+ ----------
18
+ fig : RecordingFigure
19
+ The figure containing the panel.
20
+ position : tuple
21
+ (row, col) position of the panel to hide.
22
+
23
+ Examples
24
+ --------
25
+ >>> import figrecipe as fr
26
+ >>> fig, axes = fr.subplots(1, 2)
27
+ >>> axes[0].plot([1, 2], [1, 2])
28
+ >>> fr.hide_panel(fig, (0, 1)) # Hide empty second panel
29
+ """
30
+ ax_key = f"ax_{position[0]}_{position[1]}"
31
+ if ax_key in fig.record.axes:
32
+ fig.record.axes[ax_key].visible = False
33
+ _set_axes_visible(fig, position, False)
34
+
35
+
36
+ def show_panel(fig: RecordingFigure, position: Tuple[int, int]) -> None:
37
+ """Show a previously hidden panel.
38
+
39
+ Parameters
40
+ ----------
41
+ fig : RecordingFigure
42
+ The figure containing the panel.
43
+ position : tuple
44
+ (row, col) position of the panel to show.
45
+
46
+ Examples
47
+ --------
48
+ >>> import figrecipe as fr
49
+ >>> fig, axes = fr.subplots(1, 2)
50
+ >>> fr.hide_panel(fig, (0, 1))
51
+ >>> fr.show_panel(fig, (0, 1)) # Restore visibility
52
+ """
53
+ ax_key = f"ax_{position[0]}_{position[1]}"
54
+ if ax_key in fig.record.axes:
55
+ fig.record.axes[ax_key].visible = True
56
+ _set_axes_visible(fig, position, True)
57
+
58
+
59
+ def toggle_panel(fig: RecordingFigure, position: Tuple[int, int]) -> bool:
60
+ """Toggle panel visibility.
61
+
62
+ Parameters
63
+ ----------
64
+ fig : RecordingFigure
65
+ The figure containing the panel.
66
+ position : tuple
67
+ (row, col) position of the panel.
68
+
69
+ Returns
70
+ -------
71
+ bool
72
+ New visibility state (True = visible, False = hidden).
73
+
74
+ Examples
75
+ --------
76
+ >>> import figrecipe as fr
77
+ >>> fig, ax = fr.subplots()
78
+ >>> fr.toggle_panel(fig, (0, 0)) # Returns False (now hidden)
79
+ >>> fr.toggle_panel(fig, (0, 0)) # Returns True (now visible)
80
+ """
81
+ ax_key = f"ax_{position[0]}_{position[1]}"
82
+ if ax_key in fig.record.axes:
83
+ current = fig.record.axes[ax_key].visible
84
+ if current:
85
+ hide_panel(fig, position)
86
+ else:
87
+ show_panel(fig, position)
88
+ return not current
89
+ return False
90
+
91
+
92
+ def _set_axes_visible(
93
+ fig: RecordingFigure,
94
+ position: Tuple[int, int],
95
+ visible: bool,
96
+ ) -> None:
97
+ """Set matplotlib axes visibility.
98
+
99
+ Parameters
100
+ ----------
101
+ fig : RecordingFigure
102
+ The figure.
103
+ position : tuple
104
+ (row, col) position.
105
+ visible : bool
106
+ Whether to make visible or hidden.
107
+ """
108
+ row, col = position
109
+ try:
110
+ axes = fig._axes
111
+ if isinstance(axes, list):
112
+ if isinstance(axes[0], list):
113
+ ax = axes[row][col]
114
+ else:
115
+ ax = axes[max(row, col)]
116
+ else:
117
+ ax = axes[row, col]
118
+
119
+ mpl_ax = ax._ax if hasattr(ax, "_ax") else ax
120
+ mpl_ax.set_visible(visible)
121
+ except (IndexError, AttributeError, KeyError):
122
+ pass
123
+
124
+
125
+ __all__ = ["hide_panel", "show_panel", "toggle_panel"]
@@ -16,105 +16,16 @@ Usage:
16
16
  results = run_all_demos(fr, output_dir="./outputs")
17
17
  """
18
18
 
19
- import importlib
20
- from pathlib import Path
21
-
22
- from .._params import PLOTTING_METHODS
23
-
24
- # Auto-import plotters from demo_plotters directory
25
- _demo_dir = Path(__file__).parent / "demo_plotters"
26
- PLOTTERS = {}
27
-
28
- for method_name in sorted(PLOTTING_METHODS):
29
- module_name = f"plot_{method_name}"
30
- func_name = f"plot_{method_name}"
31
- module_path = _demo_dir / f"{module_name}.py"
32
-
33
- if module_path.exists():
34
- try:
35
- module = importlib.import_module(
36
- f".demo_plotters.{module_name}", package="figrecipe._dev"
37
- )
38
- if hasattr(module, func_name):
39
- PLOTTERS[method_name] = getattr(module, func_name)
40
- except ImportError:
41
- pass
42
-
43
-
44
- def list_plotters():
45
- """List all available plotter names."""
46
- return list(PLOTTERS.keys())
47
-
48
-
49
- def get_plotter(name):
50
- """Get a plotter function by name.
51
-
52
- Parameters
53
- ----------
54
- name : str
55
- Name of the plotting method (e.g., 'plot', 'scatter').
56
-
57
- Returns
58
- -------
59
- callable
60
- The plotter function with signature (plt, rng, ax=None) -> (fig, ax).
61
- """
62
- if name in PLOTTERS:
63
- return PLOTTERS[name]
64
- raise KeyError(f"Unknown plotter: {name}. Available: {list(PLOTTERS.keys())}")
65
-
66
-
67
- def run_all_demos(plt, output_dir=None, show=False):
68
- """Run all demo plotters and optionally save outputs.
69
-
70
- Parameters
71
- ----------
72
- plt : module
73
- figrecipe module (e.g., `import figrecipe as fr`).
74
- output_dir : Path or str, optional
75
- Directory to save output images.
76
- show : bool
77
- Whether to show figures interactively.
78
-
79
- Returns
80
- -------
81
- dict
82
- Results for each demo: {name: {'success': bool, 'error': str or None}}
83
- """
84
- import matplotlib.pyplot as _plt
85
- import numpy as np
86
-
87
- rng = np.random.default_rng(42)
88
- results = {}
89
-
90
- if output_dir:
91
- output_dir = Path(output_dir)
92
- output_dir.mkdir(parents=True, exist_ok=True)
93
-
94
- for name, func in PLOTTERS.items():
95
- try:
96
- fig, ax = func(plt, rng)
97
- if output_dir:
98
- out_path = output_dir / f"plot_{name}.png"
99
- mpl_fig = fig.fig if hasattr(fig, "fig") else fig
100
- mpl_fig.savefig(out_path, dpi=100, bbox_inches="tight")
101
- if show:
102
- _plt.show()
103
- else:
104
- mpl_fig = fig.fig if hasattr(fig, "fig") else fig
105
- _plt.close(mpl_fig)
106
- results[name] = {"success": True, "error": None}
107
- except Exception as e:
108
- results[name] = {"success": False, "error": str(e)}
109
-
110
- return results
111
-
19
+ from . import browser
20
+ from ._plotters import PLOTTERS, get_plotter, list_plotters
21
+ from ._run_demos import run_all_demos
112
22
 
113
23
  __all__ = [
114
24
  "PLOTTERS",
115
25
  "list_plotters",
116
26
  "get_plotter",
117
27
  "run_all_demos",
28
+ "browser",
118
29
  ]
119
30
 
120
31
  # EOF
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Plotter registry for demo plotters."""
4
+
5
+ import importlib
6
+ from pathlib import Path
7
+
8
+ from .._params import PLOTTING_METHODS
9
+
10
+ # Auto-import plotters from demo_plotters subdirectories
11
+ _demo_dir = Path(__file__).parent / "demo_plotters"
12
+ PLOTTERS = {}
13
+
14
+ # Category subdirectories
15
+ _category_dirs = [
16
+ "line_curve",
17
+ "scatter_points",
18
+ "bar_categorical",
19
+ "distribution",
20
+ "image_matrix",
21
+ "contour_surface",
22
+ "spectral_signal",
23
+ "vector_flow",
24
+ "special",
25
+ ]
26
+
27
+ # Build mapping from method_name to category
28
+ _method_to_category = {}
29
+ for cat_dir in _category_dirs:
30
+ cat_path = _demo_dir / cat_dir
31
+ if cat_path.is_dir():
32
+ for plot_file in cat_path.glob("plot_*.py"):
33
+ method_name = plot_file.stem.replace("plot_", "")
34
+ _method_to_category[method_name] = cat_dir
35
+
36
+ for method_name in sorted(PLOTTING_METHODS):
37
+ module_name = f"plot_{method_name}"
38
+ func_name = f"plot_{method_name}"
39
+
40
+ # Check if we have this plotter in a category subdirectory
41
+ if method_name in _method_to_category:
42
+ cat_dir = _method_to_category[method_name]
43
+ try:
44
+ module = importlib.import_module(
45
+ f".demo_plotters.{cat_dir}.{module_name}", package="figrecipe._dev"
46
+ )
47
+ if hasattr(module, func_name):
48
+ PLOTTERS[method_name] = getattr(module, func_name)
49
+ except ImportError:
50
+ pass
51
+
52
+
53
+ def list_plotters():
54
+ """List all available plotter names."""
55
+ return list(PLOTTERS.keys())
56
+
57
+
58
+ def get_plotter(name):
59
+ """Get a plotter function by name.
60
+
61
+ Parameters
62
+ ----------
63
+ name : str
64
+ Name of the plotting method (e.g., 'plot', 'scatter').
65
+
66
+ Returns
67
+ -------
68
+ callable
69
+ The plotter function with signature (plt, rng, ax=None) -> (fig, ax).
70
+ """
71
+ if name in PLOTTERS:
72
+ return PLOTTERS[name]
73
+ raise KeyError(f"Unknown plotter: {name}. Available: {list(PLOTTERS.keys())}")
74
+
75
+
76
+ # EOF
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Demo runner for all plotters."""
4
+
5
+ from pathlib import Path
6
+
7
+ from ._plotters import PLOTTERS
8
+
9
+
10
+ def run_all_demos(plt, output_dir=None, show=False):
11
+ """Run all demo plotters and optionally save outputs.
12
+
13
+ Parameters
14
+ ----------
15
+ plt : module
16
+ figrecipe module (e.g., `import figrecipe as fr`).
17
+ output_dir : Path or str, optional
18
+ Directory to save output images.
19
+ show : bool
20
+ Whether to show figures interactively.
21
+
22
+ Returns
23
+ -------
24
+ dict
25
+ Results for each demo: {name: {'success': bool, 'error': str or None}}
26
+ """
27
+ import matplotlib.pyplot as _plt
28
+ import numpy as np
29
+
30
+ rng = np.random.default_rng(42)
31
+ results = {}
32
+
33
+ if output_dir:
34
+ output_dir = Path(output_dir)
35
+ output_dir.mkdir(parents=True, exist_ok=True)
36
+
37
+ for name, func in PLOTTERS.items():
38
+ try:
39
+ fig, ax = func(plt, rng)
40
+ if output_dir:
41
+ out_path = output_dir / f"plot_{name}.png"
42
+ mpl_fig = fig.fig if hasattr(fig, "fig") else fig
43
+ mpl_fig.savefig(out_path, dpi=100, bbox_inches="tight")
44
+ if show:
45
+ _plt.show()
46
+ else:
47
+ mpl_fig = fig.fig if hasattr(fig, "fig") else fig
48
+ _plt.close(mpl_fig)
49
+ results[name] = {"success": True, "error": None}
50
+ except Exception as e:
51
+ results[name] = {"success": False, "error": str(e)}
52
+
53
+ return results
54
+
55
+
56
+ # EOF