figrecipe 0.6.0__py3-none-any.whl → 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- figrecipe/__init__.py +161 -1030
- figrecipe/__main__.py +12 -0
- figrecipe/_api/__init__.py +48 -0
- figrecipe/_api/_extract.py +108 -0
- figrecipe/_api/_notebook.py +61 -0
- figrecipe/_api/_panel.py +113 -0
- figrecipe/_api/_save.py +287 -0
- figrecipe/_api/_seaborn_proxy.py +34 -0
- figrecipe/_api/_style_manager.py +153 -0
- figrecipe/_api/_subplots.py +333 -0
- figrecipe/_api/_validate.py +82 -0
- figrecipe/_cli/__init__.py +7 -0
- figrecipe/_cli/_compose.py +87 -0
- figrecipe/_cli/_convert.py +117 -0
- figrecipe/_cli/_crop.py +82 -0
- figrecipe/_cli/_edit.py +70 -0
- figrecipe/_cli/_extract.py +128 -0
- figrecipe/_cli/_fonts.py +47 -0
- figrecipe/_cli/_info.py +67 -0
- figrecipe/_cli/_main.py +58 -0
- figrecipe/_cli/_reproduce.py +79 -0
- figrecipe/_cli/_style.py +77 -0
- figrecipe/_cli/_validate.py +66 -0
- figrecipe/_cli/_version.py +50 -0
- figrecipe/_composition/__init__.py +32 -0
- figrecipe/_composition/_alignment.py +452 -0
- figrecipe/_composition/_compose.py +179 -0
- figrecipe/_composition/_import_axes.py +127 -0
- figrecipe/_composition/_visibility.py +125 -0
- figrecipe/_dev/__init__.py +4 -93
- figrecipe/_dev/_plotters.py +76 -0
- figrecipe/_dev/_run_demos.py +56 -0
- figrecipe/_dev/browser/__init__.py +69 -0
- figrecipe/_dev/browser/_audio.py +240 -0
- figrecipe/_dev/browser/_caption.py +356 -0
- figrecipe/_dev/browser/_click_effect.py +146 -0
- figrecipe/_dev/browser/_cursor.py +196 -0
- figrecipe/_dev/browser/_highlight.py +105 -0
- figrecipe/_dev/browser/_narration.py +237 -0
- figrecipe/_dev/browser/_recorder.py +446 -0
- figrecipe/_dev/browser/_utils.py +178 -0
- figrecipe/_dev/browser/_video_trim/__init__.py +152 -0
- figrecipe/_dev/browser/_video_trim/_detection.py +223 -0
- figrecipe/_dev/browser/_video_trim/_markers.py +140 -0
- figrecipe/_dev/demo_plotters/__init__.py +35 -166
- figrecipe/_dev/demo_plotters/_categories.py +81 -0
- figrecipe/_dev/demo_plotters/_figure_creators.py +119 -0
- figrecipe/_dev/demo_plotters/_helpers.py +31 -0
- figrecipe/_dev/demo_plotters/_registry.py +50 -0
- figrecipe/_dev/demo_plotters/bar_categorical/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/{plot_plot.py → line_curve/plot_plot.py} +3 -2
- figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/{plot_pie.py → special/plot_pie.py} +5 -1
- figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
- figrecipe/_editor/__init__.py +61 -13
- figrecipe/_editor/_bbox/__init__.py +43 -0
- figrecipe/_editor/_bbox/_collections.py +177 -0
- figrecipe/_editor/_bbox/_elements.py +159 -0
- figrecipe/_editor/_bbox/_extract.py +402 -0
- figrecipe/_editor/_bbox/_extract_axes.py +370 -0
- figrecipe/_editor/_bbox/_extract_text.py +466 -0
- figrecipe/_editor/_bbox/_lines.py +173 -0
- figrecipe/_editor/_bbox/_transforms.py +146 -0
- figrecipe/_editor/_call_overrides.py +183 -0
- figrecipe/_editor/_datatable_plot_handlers.py +249 -0
- figrecipe/_editor/_figure_layout.py +211 -0
- figrecipe/_editor/_flask_app.py +200 -1030
- figrecipe/_editor/_helpers.py +251 -0
- figrecipe/_editor/_hitmap/__init__.py +76 -0
- figrecipe/_editor/_hitmap/_artists/__init__.py +21 -0
- figrecipe/_editor/_hitmap/_artists/_collections.py +345 -0
- figrecipe/_editor/_hitmap/_artists/_images.py +68 -0
- figrecipe/_editor/_hitmap/_artists/_lines.py +107 -0
- figrecipe/_editor/_hitmap/_artists/_patches.py +163 -0
- figrecipe/_editor/_hitmap/_artists/_text.py +190 -0
- figrecipe/_editor/_hitmap/_colors.py +181 -0
- figrecipe/_editor/_hitmap/_detect.py +194 -0
- figrecipe/_editor/_hitmap/_restore.py +154 -0
- figrecipe/_editor/_hitmap_main.py +182 -0
- figrecipe/_editor/_overrides.py +4 -1
- figrecipe/_editor/_plot_types_registry.py +190 -0
- figrecipe/_editor/_preferences.py +135 -0
- figrecipe/_editor/_render_overrides.py +507 -0
- figrecipe/_editor/_renderer.py +81 -186
- figrecipe/_editor/_routes_annotation.py +114 -0
- figrecipe/_editor/_routes_axis.py +482 -0
- figrecipe/_editor/_routes_captions.py +130 -0
- figrecipe/_editor/_routes_composition.py +270 -0
- figrecipe/_editor/_routes_core.py +126 -0
- figrecipe/_editor/_routes_datatable.py +364 -0
- figrecipe/_editor/_routes_element.py +335 -0
- figrecipe/_editor/_routes_files.py +443 -0
- figrecipe/_editor/_routes_image.py +200 -0
- figrecipe/_editor/_routes_snapshot.py +94 -0
- figrecipe/_editor/_routes_style.py +243 -0
- figrecipe/_editor/_templates/__init__.py +116 -1
- figrecipe/_editor/_templates/_html.py +154 -64
- figrecipe/_editor/_templates/_html_components/__init__.py +13 -0
- figrecipe/_editor/_templates/_html_components/_composition_toolbar.py +79 -0
- figrecipe/_editor/_templates/_html_components/_file_browser.py +41 -0
- figrecipe/_editor/_templates/_html_datatable.py +92 -0
- figrecipe/_editor/_templates/_scripts/__init__.py +178 -0
- figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
- figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
- figrecipe/_editor/_templates/_scripts/_api.py +228 -0
- figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
- figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
- figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
- figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
- figrecipe/_editor/_templates/_scripts/_core.py +493 -0
- figrecipe/_editor/_templates/_scripts/_datatable/__init__.py +59 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_cell_edit.py +97 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_clipboard.py +164 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_context_menu.py +221 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_core.py +150 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_editable.py +511 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_import.py +161 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_plot.py +261 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_selection.py +438 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_table.py +256 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_tabs.py +354 -0
- figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
- figrecipe/_editor/_templates/_scripts/_element_editor.py +325 -0
- figrecipe/_editor/_templates/_scripts/_files.py +429 -0
- figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
- figrecipe/_editor/_templates/_scripts/_hitmap.py +512 -0
- figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
- figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
- figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
- figrecipe/_editor/_templates/_scripts/_legend_drag.py +270 -0
- figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
- figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
- figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag.py +505 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
- figrecipe/_editor/_templates/_scripts/_panel_position.py +463 -0
- figrecipe/_editor/_templates/_scripts/_panel_resize.py +230 -0
- figrecipe/_editor/_templates/_scripts/_panel_snap.py +307 -0
- figrecipe/_editor/_templates/_scripts/_region_select.py +255 -0
- figrecipe/_editor/_templates/_scripts/_selection.py +244 -0
- figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
- figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
- figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
- figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
- figrecipe/_editor/_templates/_scripts/_zoom.py +212 -0
- figrecipe/_editor/_templates/_styles/__init__.py +78 -0
- figrecipe/_editor/_templates/_styles/_base.py +111 -0
- figrecipe/_editor/_templates/_styles/_buttons.py +327 -0
- figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
- figrecipe/_editor/_templates/_styles/_composition.py +87 -0
- figrecipe/_editor/_templates/_styles/_controls.py +430 -0
- figrecipe/_editor/_templates/_styles/_datatable/__init__.py +40 -0
- figrecipe/_editor/_templates/_styles/_datatable/_editable.py +203 -0
- figrecipe/_editor/_templates/_styles/_datatable/_panel.py +268 -0
- figrecipe/_editor/_templates/_styles/_datatable/_table.py +479 -0
- figrecipe/_editor/_templates/_styles/_datatable/_toolbar.py +384 -0
- figrecipe/_editor/_templates/_styles/_datatable/_vars.py +123 -0
- figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
- figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
- figrecipe/_editor/_templates/_styles/_forms.py +224 -0
- figrecipe/_editor/_templates/_styles/_hitmap.py +191 -0
- figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
- figrecipe/_editor/_templates/_styles/_labels.py +118 -0
- figrecipe/_editor/_templates/_styles/_modals.py +127 -0
- figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
- figrecipe/_editor/_templates/_styles/_preview.py +430 -0
- figrecipe/_editor/_templates/_styles/_selection.py +73 -0
- figrecipe/_editor/_templates/_styles/_spinner.py +117 -0
- figrecipe/_editor/static/audio/click.mp3 +0 -0
- figrecipe/_editor/static/click.mp3 +0 -0
- figrecipe/_editor/static/icons/favicon.ico +0 -0
- figrecipe/_integrations/__init__.py +17 -0
- figrecipe/_integrations/_scitex_stats.py +298 -0
- figrecipe/_params/_DECORATION_METHODS.py +8 -0
- figrecipe/_recorder.py +63 -109
- figrecipe/_recorder_utils.py +124 -0
- figrecipe/_reproducer/__init__.py +18 -0
- figrecipe/_reproducer/_core.py +509 -0
- figrecipe/_reproducer/_custom_plots.py +279 -0
- figrecipe/_reproducer/_seaborn.py +100 -0
- figrecipe/_reproducer/_violin.py +186 -0
- figrecipe/_signatures/_kwargs.py +273 -0
- figrecipe/_signatures/_loader.py +21 -423
- figrecipe/_signatures/_parsing.py +147 -0
- figrecipe/_utils/__init__.py +3 -0
- figrecipe/_utils/_bundle.py +205 -0
- figrecipe/_wrappers/_axes.py +252 -895
- figrecipe/_wrappers/_axes_helpers.py +136 -0
- figrecipe/_wrappers/_axes_plots.py +418 -0
- figrecipe/_wrappers/_axes_seaborn.py +157 -0
- figrecipe/_wrappers/_caption_generator.py +218 -0
- figrecipe/_wrappers/_figure.py +188 -1
- figrecipe/_wrappers/_panel_labels.py +127 -0
- figrecipe/_wrappers/_plot_helpers.py +143 -0
- figrecipe/_wrappers/_stat_annotation.py +274 -0
- figrecipe/_wrappers/_violin_helpers.py +180 -0
- figrecipe/styles/__init__.py +8 -6
- figrecipe/styles/_dotdict.py +72 -0
- figrecipe/styles/_finalize.py +134 -0
- figrecipe/styles/_fonts.py +77 -0
- figrecipe/styles/_kwargs_converter.py +178 -0
- figrecipe/styles/_plot_styles.py +209 -0
- figrecipe/styles/_style_applier.py +42 -480
- figrecipe/styles/_style_loader.py +16 -192
- figrecipe/styles/_themes.py +151 -0
- figrecipe/styles/presets/MATPLOTLIB.yaml +2 -1
- figrecipe/styles/presets/SCITEX.yaml +40 -28
- figrecipe-0.9.0.dist-info/METADATA +427 -0
- figrecipe-0.9.0.dist-info/RECORD +277 -0
- figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
- figrecipe/_editor/_bbox.py +0 -978
- figrecipe/_editor/_hitmap.py +0 -937
- figrecipe/_editor/_templates/_scripts.py +0 -2778
- figrecipe/_editor/_templates/_styles.py +0 -1326
- figrecipe/_reproducer.py +0 -975
- figrecipe-0.6.0.dist-info/METADATA +0 -394
- figrecipe-0.6.0.dist-info/RECORD +0 -90
- /figrecipe/_dev/demo_plotters/{plot_bar.py → bar_categorical/plot_bar.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_barh.py → bar_categorical/plot_barh.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_contour.py → contour_surface/plot_contour.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_contourf.py → contour_surface/plot_contourf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tricontour.py → contour_surface/plot_tricontour.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tricontourf.py → contour_surface/plot_tricontourf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tripcolor.py → contour_surface/plot_tripcolor.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_triplot.py → contour_surface/plot_triplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_boxplot.py → distribution/plot_boxplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_ecdf.py → distribution/plot_ecdf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hist.py → distribution/plot_hist.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hist2d.py → distribution/plot_hist2d.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_violinplot.py → distribution/plot_violinplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hexbin.py → image_matrix/plot_hexbin.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_imshow.py → image_matrix/plot_imshow.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_matshow.py → image_matrix/plot_matshow.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_pcolor.py → image_matrix/plot_pcolor.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_pcolormesh.py → image_matrix/plot_pcolormesh.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_spy.py → image_matrix/plot_spy.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_errorbar.py → line_curve/plot_errorbar.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill.py → line_curve/plot_fill.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill_between.py → line_curve/plot_fill_between.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill_betweenx.py → line_curve/plot_fill_betweenx.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stackplot.py → line_curve/plot_stackplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stairs.py → line_curve/plot_stairs.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_step.py → line_curve/plot_step.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_scatter.py → scatter_points/plot_scatter.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_eventplot.py → special/plot_eventplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_loglog.py → special/plot_loglog.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_semilogx.py → special/plot_semilogx.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_semilogy.py → special/plot_semilogy.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stem.py → special/plot_stem.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_acorr.py → spectral_signal/plot_acorr.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_angle_spectrum.py → spectral_signal/plot_angle_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_cohere.py → spectral_signal/plot_cohere.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_csd.py → spectral_signal/plot_csd.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_magnitude_spectrum.py → spectral_signal/plot_magnitude_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_phase_spectrum.py → spectral_signal/plot_phase_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_psd.py → spectral_signal/plot_psd.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_specgram.py → spectral_signal/plot_specgram.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_xcorr.py → spectral_signal/plot_xcorr.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_barbs.py → vector_flow/plot_barbs.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_quiver.py → vector_flow/plot_quiver.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_streamplot.py → vector_flow/plot_streamplot.py} +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/licenses/LICENSE +0 -0
figrecipe/_editor/__init__.py
CHANGED
|
@@ -26,10 +26,14 @@ from ._flask_app import FigureEditor
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def edit(
|
|
29
|
-
source: Union[RecordingFigure, str, Path],
|
|
29
|
+
source: Optional[Union[RecordingFigure, str, Path]] = None,
|
|
30
30
|
style: Optional[Union[str, Dict[str, Any]]] = None,
|
|
31
31
|
port: int = 5050,
|
|
32
|
+
host: str = "127.0.0.1",
|
|
32
33
|
open_browser: bool = True,
|
|
34
|
+
hot_reload: bool = False,
|
|
35
|
+
working_dir: Optional[Union[str, Path]] = None,
|
|
36
|
+
desktop: bool = False,
|
|
33
37
|
) -> Dict[str, Any]:
|
|
34
38
|
"""
|
|
35
39
|
Launch interactive GUI editor for figure styling.
|
|
@@ -39,8 +43,14 @@ def edit(
|
|
|
39
43
|
|
|
40
44
|
Parameters
|
|
41
45
|
----------
|
|
42
|
-
source : RecordingFigure, str, or
|
|
43
|
-
|
|
46
|
+
source : RecordingFigure, str, Path, or None
|
|
47
|
+
Figure source. Supports multiple formats:
|
|
48
|
+
- RecordingFigure: Live figure object
|
|
49
|
+
- .yaml/.yml: Direct recipe file
|
|
50
|
+
- .png/.jpg/etc: Image with associated .yaml
|
|
51
|
+
- Directory: Bundle containing recipe.yaml
|
|
52
|
+
- .zip: ZIP archive containing recipe.yaml
|
|
53
|
+
- None: Create new blank figure
|
|
44
54
|
style : str or dict, optional
|
|
45
55
|
Style preset name (e.g., 'SCITEX', 'SCITEX_DARK') or style dict.
|
|
46
56
|
If None, uses the currently loaded global style.
|
|
@@ -48,6 +58,15 @@ def edit(
|
|
|
48
58
|
Flask server port (default: 5050). Auto-finds available port if occupied.
|
|
49
59
|
open_browser : bool, optional
|
|
50
60
|
Whether to open browser automatically (default: True).
|
|
61
|
+
hot_reload : bool, optional
|
|
62
|
+
Enable hot reload - server restarts when source files change (default: False).
|
|
63
|
+
Like Django's development server. Browser auto-refreshes on reconnect.
|
|
64
|
+
working_dir : str or Path, optional
|
|
65
|
+
Working directory for file switching feature (default: current directory).
|
|
66
|
+
The file switcher will list recipe files from this directory.
|
|
67
|
+
desktop : bool, optional
|
|
68
|
+
Launch as native desktop window using pywebview (default: False).
|
|
69
|
+
Requires: pip install figrecipe[desktop]
|
|
51
70
|
|
|
52
71
|
Returns
|
|
53
72
|
-------
|
|
@@ -109,34 +128,65 @@ def edit(
|
|
|
109
128
|
hitmap, color_map = generate_hitmap(fig)
|
|
110
129
|
hitmap_base64 = hitmap_to_base64(hitmap)
|
|
111
130
|
|
|
131
|
+
# Resolve working directory
|
|
132
|
+
resolved_working_dir = Path(working_dir) if working_dir else Path.cwd()
|
|
133
|
+
|
|
112
134
|
# Create and run editor with pre-rendered static PNG
|
|
113
135
|
editor = FigureEditor(
|
|
114
136
|
fig=fig,
|
|
115
137
|
recipe_path=recipe_path,
|
|
116
138
|
style=style_dict,
|
|
117
139
|
port=port,
|
|
140
|
+
host=host,
|
|
118
141
|
static_png_path=static_png_path,
|
|
119
142
|
hitmap_base64=hitmap_base64,
|
|
120
143
|
color_map=color_map,
|
|
144
|
+
hot_reload=hot_reload,
|
|
145
|
+
working_dir=resolved_working_dir,
|
|
146
|
+
desktop=desktop,
|
|
121
147
|
)
|
|
122
148
|
|
|
123
149
|
return editor.run(open_browser=open_browser)
|
|
124
150
|
|
|
125
151
|
|
|
126
|
-
def
|
|
152
|
+
def _check_figure_has_content(fig: RecordingFigure) -> bool:
|
|
153
|
+
"""Check if figure has any plot content."""
|
|
154
|
+
for ax_row in fig._axes:
|
|
155
|
+
for ax in ax_row:
|
|
156
|
+
# Check for lines, patches, images, collections
|
|
157
|
+
if ax.lines or ax.patches or ax.images or ax.collections or ax.texts:
|
|
158
|
+
return True
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _resolve_source(source: Optional[Union[RecordingFigure, str, Path]]):
|
|
127
163
|
"""
|
|
128
164
|
Resolve source to figure and optional recipe path.
|
|
129
165
|
|
|
130
166
|
Parameters
|
|
131
167
|
----------
|
|
132
|
-
source : RecordingFigure, str, or
|
|
133
|
-
Input source.
|
|
168
|
+
source : RecordingFigure, str, Path, or None
|
|
169
|
+
Input source. Supports:
|
|
170
|
+
- None: Creates new blank figure
|
|
171
|
+
- RecordingFigure: Uses directly
|
|
172
|
+
- .yaml/.yml: Direct recipe file
|
|
173
|
+
- .png/.jpg/etc: Image with associated YAML
|
|
174
|
+
- Directory: Bundle containing recipe.yaml
|
|
175
|
+
- .zip: ZIP archive containing recipe.yaml
|
|
134
176
|
|
|
135
177
|
Returns
|
|
136
178
|
-------
|
|
137
179
|
tuple
|
|
138
|
-
(RecordingFigure
|
|
180
|
+
(RecordingFigure, Path or None)
|
|
139
181
|
"""
|
|
182
|
+
# Handle None - create new blank figure
|
|
183
|
+
if source is None:
|
|
184
|
+
from .. import subplots
|
|
185
|
+
|
|
186
|
+
fig, ax = subplots()
|
|
187
|
+
ax.set_title("New Figure")
|
|
188
|
+
return fig, None
|
|
189
|
+
|
|
140
190
|
if isinstance(source, RecordingFigure):
|
|
141
191
|
return source, None
|
|
142
192
|
|
|
@@ -157,13 +207,11 @@ def _resolve_source(source: Union[RecordingFigure, str, Path]):
|
|
|
157
207
|
)
|
|
158
208
|
return wrapped_fig, None
|
|
159
209
|
|
|
160
|
-
# Assume it's a path
|
|
161
|
-
|
|
162
|
-
if not path.exists():
|
|
163
|
-
raise FileNotFoundError(f"Recipe file not found: {path}")
|
|
210
|
+
# Assume it's a path - use bundle resolution (handles dir, zip, yaml, png)
|
|
211
|
+
from .._utils._bundle import resolve_recipe_path
|
|
164
212
|
|
|
165
|
-
|
|
166
|
-
|
|
213
|
+
path, _temp_dir = resolve_recipe_path(source)
|
|
214
|
+
# Note: temp_dir cleanup handled by reproduce() if ZIP was extracted
|
|
167
215
|
|
|
168
216
|
# Load recipe and reproduce figure
|
|
169
217
|
from .._reproducer import reproduce
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Modular bbox extraction for figure elements.
|
|
5
|
+
|
|
6
|
+
This package provides functions for extracting bounding boxes from
|
|
7
|
+
matplotlib figure elements for hit detection in the GUI editor.
|
|
8
|
+
|
|
9
|
+
The main function `extract_bboxes` is re-exported from _bbox_main.py
|
|
10
|
+
for backward compatibility.
|
|
11
|
+
|
|
12
|
+
Modules:
|
|
13
|
+
- _transforms: Coordinate transformation utilities
|
|
14
|
+
- _elements: General element, text, and tick bbox extraction
|
|
15
|
+
- _lines: Line and quiver bbox extraction
|
|
16
|
+
- _collections: Collection (scatter, fill) and patch bbox extraction
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# Re-export main function from _extract.py
|
|
20
|
+
# Import modular helpers
|
|
21
|
+
from ._collections import get_collection_bbox, get_patch_bbox
|
|
22
|
+
from ._elements import get_element_bbox, get_text_bbox, get_tick_labels_bbox
|
|
23
|
+
from ._extract import extract_bboxes
|
|
24
|
+
from ._lines import get_line_bbox, get_quiver_bbox
|
|
25
|
+
from ._transforms import display_to_image, transform_bbox
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
# Main function
|
|
29
|
+
"extract_bboxes",
|
|
30
|
+
# Transforms
|
|
31
|
+
"transform_bbox",
|
|
32
|
+
"display_to_image",
|
|
33
|
+
# Elements
|
|
34
|
+
"get_element_bbox",
|
|
35
|
+
"get_text_bbox",
|
|
36
|
+
"get_tick_labels_bbox",
|
|
37
|
+
# Lines
|
|
38
|
+
"get_line_bbox",
|
|
39
|
+
"get_quiver_bbox",
|
|
40
|
+
# Collections
|
|
41
|
+
"get_collection_bbox",
|
|
42
|
+
"get_patch_bbox",
|
|
43
|
+
]
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Collection bbox extraction for scatter, fill, and patch elements.
|
|
5
|
+
|
|
6
|
+
This module handles bbox extraction for matplotlib collections
|
|
7
|
+
(scatter plots, fills, bars, etc.).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import math
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
from matplotlib.axes import Axes
|
|
14
|
+
from matplotlib.collections import PathCollection
|
|
15
|
+
from matplotlib.figure import Figure
|
|
16
|
+
from matplotlib.transforms import Bbox
|
|
17
|
+
|
|
18
|
+
from ._elements import get_element_bbox
|
|
19
|
+
from ._transforms import display_to_image, transform_bbox
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_collection_bbox(
|
|
23
|
+
coll,
|
|
24
|
+
ax: Axes,
|
|
25
|
+
fig: Figure,
|
|
26
|
+
renderer,
|
|
27
|
+
tight_bbox: Bbox,
|
|
28
|
+
img_width: int,
|
|
29
|
+
img_height: int,
|
|
30
|
+
scale_x: float,
|
|
31
|
+
scale_y: float,
|
|
32
|
+
pad_inches: float,
|
|
33
|
+
saved_height_inches: float,
|
|
34
|
+
include_points: bool = True,
|
|
35
|
+
) -> Optional[Dict[str, Any]]:
|
|
36
|
+
"""Get bbox and points for a collection (scatter, fill)."""
|
|
37
|
+
try:
|
|
38
|
+
bbox = None
|
|
39
|
+
|
|
40
|
+
# For scatter plots, get_window_extent() can fail or return empty
|
|
41
|
+
# So we calculate bbox from data points as fallback
|
|
42
|
+
if isinstance(coll, PathCollection):
|
|
43
|
+
offsets = coll.get_offsets()
|
|
44
|
+
if len(offsets) > 0:
|
|
45
|
+
transform = ax.transData
|
|
46
|
+
points = []
|
|
47
|
+
|
|
48
|
+
# Limit to reasonable number of points
|
|
49
|
+
max_points = 200
|
|
50
|
+
step = max(1, len(offsets) // max_points)
|
|
51
|
+
|
|
52
|
+
for i in range(0, len(offsets), step):
|
|
53
|
+
try:
|
|
54
|
+
offset = offsets[i]
|
|
55
|
+
display_coords = transform.transform(offset)
|
|
56
|
+
img_coords = display_to_image(
|
|
57
|
+
display_coords[0],
|
|
58
|
+
display_coords[1],
|
|
59
|
+
fig,
|
|
60
|
+
tight_bbox,
|
|
61
|
+
img_width,
|
|
62
|
+
img_height,
|
|
63
|
+
scale_x,
|
|
64
|
+
scale_y,
|
|
65
|
+
pad_inches,
|
|
66
|
+
saved_height_inches,
|
|
67
|
+
)
|
|
68
|
+
if img_coords:
|
|
69
|
+
points.append(img_coords)
|
|
70
|
+
except Exception:
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
# Calculate bbox from points
|
|
74
|
+
if points:
|
|
75
|
+
xs = [p[0] for p in points]
|
|
76
|
+
ys = [p[1] for p in points]
|
|
77
|
+
# Add padding around scatter points for easier clicking
|
|
78
|
+
padding = 10 # pixels
|
|
79
|
+
bbox = {
|
|
80
|
+
"x": float(min(xs) - padding),
|
|
81
|
+
"y": float(min(ys) - padding),
|
|
82
|
+
"width": float(max(xs) - min(xs) + 2 * padding),
|
|
83
|
+
"height": float(max(ys) - min(ys) + 2 * padding),
|
|
84
|
+
"points": points,
|
|
85
|
+
}
|
|
86
|
+
return bbox
|
|
87
|
+
|
|
88
|
+
# Fallback: try standard window extent
|
|
89
|
+
window_extent = coll.get_window_extent(renderer)
|
|
90
|
+
if window_extent is None:
|
|
91
|
+
# Use axes extent as fallback
|
|
92
|
+
return get_element_bbox(
|
|
93
|
+
ax,
|
|
94
|
+
fig,
|
|
95
|
+
renderer,
|
|
96
|
+
tight_bbox,
|
|
97
|
+
img_width,
|
|
98
|
+
img_height,
|
|
99
|
+
scale_x,
|
|
100
|
+
scale_y,
|
|
101
|
+
pad_inches,
|
|
102
|
+
saved_height_inches,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Check if window_extent is valid (not inf)
|
|
106
|
+
if (
|
|
107
|
+
math.isinf(window_extent.x0)
|
|
108
|
+
or math.isinf(window_extent.y0)
|
|
109
|
+
or math.isinf(window_extent.x1)
|
|
110
|
+
or math.isinf(window_extent.y1)
|
|
111
|
+
):
|
|
112
|
+
# Invalid extent - use axes extent as fallback
|
|
113
|
+
return get_element_bbox(
|
|
114
|
+
ax,
|
|
115
|
+
fig,
|
|
116
|
+
renderer,
|
|
117
|
+
tight_bbox,
|
|
118
|
+
img_width,
|
|
119
|
+
img_height,
|
|
120
|
+
scale_x,
|
|
121
|
+
scale_y,
|
|
122
|
+
pad_inches,
|
|
123
|
+
saved_height_inches,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
bbox = transform_bbox(
|
|
127
|
+
window_extent,
|
|
128
|
+
fig,
|
|
129
|
+
tight_bbox,
|
|
130
|
+
img_width,
|
|
131
|
+
img_height,
|
|
132
|
+
scale_x,
|
|
133
|
+
scale_y,
|
|
134
|
+
pad_inches,
|
|
135
|
+
saved_height_inches,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return bbox
|
|
139
|
+
|
|
140
|
+
except Exception:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def get_patch_bbox(
|
|
145
|
+
patch,
|
|
146
|
+
ax: Axes,
|
|
147
|
+
fig: Figure,
|
|
148
|
+
renderer,
|
|
149
|
+
tight_bbox: Bbox,
|
|
150
|
+
img_width: int,
|
|
151
|
+
img_height: int,
|
|
152
|
+
scale_x: float,
|
|
153
|
+
scale_y: float,
|
|
154
|
+
pad_inches: float,
|
|
155
|
+
saved_height_inches: float,
|
|
156
|
+
) -> Optional[Dict[str, float]]:
|
|
157
|
+
"""Get bbox for a patch (bar, rectangle)."""
|
|
158
|
+
try:
|
|
159
|
+
window_extent = patch.get_window_extent(renderer)
|
|
160
|
+
if window_extent is None:
|
|
161
|
+
return None
|
|
162
|
+
return transform_bbox(
|
|
163
|
+
window_extent,
|
|
164
|
+
fig,
|
|
165
|
+
tight_bbox,
|
|
166
|
+
img_width,
|
|
167
|
+
img_height,
|
|
168
|
+
scale_x,
|
|
169
|
+
scale_y,
|
|
170
|
+
pad_inches,
|
|
171
|
+
saved_height_inches,
|
|
172
|
+
)
|
|
173
|
+
except Exception:
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
__all__ = ["get_collection_bbox", "get_patch_bbox"]
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Element bbox extraction for general elements, text, and ticks.
|
|
5
|
+
|
|
6
|
+
This module handles bbox extraction for axes, text labels, and tick marks.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Dict, Optional
|
|
10
|
+
|
|
11
|
+
from matplotlib.figure import Figure
|
|
12
|
+
from matplotlib.transforms import Bbox
|
|
13
|
+
|
|
14
|
+
from ._transforms import transform_bbox
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_element_bbox(
|
|
18
|
+
element,
|
|
19
|
+
fig: Figure,
|
|
20
|
+
renderer,
|
|
21
|
+
tight_bbox: Bbox,
|
|
22
|
+
img_width: int,
|
|
23
|
+
img_height: int,
|
|
24
|
+
scale_x: float,
|
|
25
|
+
scale_y: float,
|
|
26
|
+
pad_inches: float,
|
|
27
|
+
saved_height_inches: float,
|
|
28
|
+
) -> Optional[Dict[str, float]]:
|
|
29
|
+
"""Get bbox for a general element."""
|
|
30
|
+
try:
|
|
31
|
+
window_extent = element.get_window_extent(renderer)
|
|
32
|
+
if window_extent is None:
|
|
33
|
+
return None
|
|
34
|
+
return transform_bbox(
|
|
35
|
+
window_extent,
|
|
36
|
+
fig,
|
|
37
|
+
tight_bbox,
|
|
38
|
+
img_width,
|
|
39
|
+
img_height,
|
|
40
|
+
scale_x,
|
|
41
|
+
scale_y,
|
|
42
|
+
pad_inches,
|
|
43
|
+
saved_height_inches,
|
|
44
|
+
)
|
|
45
|
+
except Exception:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_text_bbox(
|
|
50
|
+
text,
|
|
51
|
+
fig: Figure,
|
|
52
|
+
renderer,
|
|
53
|
+
tight_bbox: Bbox,
|
|
54
|
+
img_width: int,
|
|
55
|
+
img_height: int,
|
|
56
|
+
scale_x: float,
|
|
57
|
+
scale_y: float,
|
|
58
|
+
pad_inches: float,
|
|
59
|
+
saved_height_inches: float,
|
|
60
|
+
) -> Optional[Dict[str, float]]:
|
|
61
|
+
"""Get bbox for a text element."""
|
|
62
|
+
try:
|
|
63
|
+
window_extent = text.get_window_extent(renderer)
|
|
64
|
+
if window_extent is None:
|
|
65
|
+
return None
|
|
66
|
+
return transform_bbox(
|
|
67
|
+
window_extent,
|
|
68
|
+
fig,
|
|
69
|
+
tight_bbox,
|
|
70
|
+
img_width,
|
|
71
|
+
img_height,
|
|
72
|
+
scale_x,
|
|
73
|
+
scale_y,
|
|
74
|
+
pad_inches,
|
|
75
|
+
saved_height_inches,
|
|
76
|
+
)
|
|
77
|
+
except Exception:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_tick_labels_bbox(
|
|
82
|
+
axis,
|
|
83
|
+
axis_type: str, # 'x' or 'y'
|
|
84
|
+
fig: Figure,
|
|
85
|
+
renderer,
|
|
86
|
+
tight_bbox: Bbox,
|
|
87
|
+
img_width: int,
|
|
88
|
+
img_height: int,
|
|
89
|
+
scale_x: float,
|
|
90
|
+
scale_y: float,
|
|
91
|
+
pad_inches: float,
|
|
92
|
+
saved_height_inches: float,
|
|
93
|
+
) -> Optional[Dict[str, float]]:
|
|
94
|
+
"""
|
|
95
|
+
Get bbox for tick labels, extended to span the full axis dimension.
|
|
96
|
+
|
|
97
|
+
For x-axis: tick labels bbox spans the full width of the plot area.
|
|
98
|
+
For y-axis: tick labels bbox spans the full height of the plot area.
|
|
99
|
+
"""
|
|
100
|
+
try:
|
|
101
|
+
all_bboxes = []
|
|
102
|
+
|
|
103
|
+
# Get all tick label bboxes
|
|
104
|
+
for tick in axis.get_major_ticks():
|
|
105
|
+
tick_label = tick.label1 if hasattr(tick, "label1") else tick.label
|
|
106
|
+
if tick_label and tick_label.get_visible():
|
|
107
|
+
try:
|
|
108
|
+
tick_extent = tick_label.get_window_extent(renderer)
|
|
109
|
+
if tick_extent is not None and tick_extent.width > 0:
|
|
110
|
+
all_bboxes.append(tick_extent)
|
|
111
|
+
except Exception:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
if not all_bboxes:
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
# Merge all tick label bboxes
|
|
118
|
+
merged = all_bboxes[0]
|
|
119
|
+
for bbox in all_bboxes[1:]:
|
|
120
|
+
merged = Bbox.union([merged, bbox])
|
|
121
|
+
|
|
122
|
+
# Get the axes extent to extend the tick labels region
|
|
123
|
+
ax = axis.axes
|
|
124
|
+
ax_bbox = ax.get_window_extent(renderer)
|
|
125
|
+
|
|
126
|
+
if axis_type == "x":
|
|
127
|
+
# For x-axis: extend width to match axes width, keep tick labels height
|
|
128
|
+
merged = Bbox.from_extents(
|
|
129
|
+
ax_bbox.x0, # Align left with axes
|
|
130
|
+
merged.y0, # Keep tick labels y position
|
|
131
|
+
ax_bbox.x1, # Align right with axes
|
|
132
|
+
merged.y1, # Keep tick labels height
|
|
133
|
+
)
|
|
134
|
+
else: # y-axis
|
|
135
|
+
# For y-axis: extend height to match axes height, keep tick labels width
|
|
136
|
+
merged = Bbox.from_extents(
|
|
137
|
+
merged.x0, # Keep tick labels x position
|
|
138
|
+
ax_bbox.y0, # Align bottom with axes
|
|
139
|
+
merged.x1, # Keep tick labels width
|
|
140
|
+
ax_bbox.y1, # Align top with axes
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
return transform_bbox(
|
|
144
|
+
merged,
|
|
145
|
+
fig,
|
|
146
|
+
tight_bbox,
|
|
147
|
+
img_width,
|
|
148
|
+
img_height,
|
|
149
|
+
scale_x,
|
|
150
|
+
scale_y,
|
|
151
|
+
pad_inches,
|
|
152
|
+
saved_height_inches,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
except Exception:
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
__all__ = ["get_element_bbox", "get_text_bbox", "get_tick_labels_bbox"]
|