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
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Composition-related Flask routes for the figure editor.
|
|
5
|
+
|
|
6
|
+
Provides API endpoints for:
|
|
7
|
+
- Panel visibility (hide/show)
|
|
8
|
+
- Panel alignment
|
|
9
|
+
- Panel distribution
|
|
10
|
+
- Smart alignment
|
|
11
|
+
- Importing axes from external recipes
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from flask import jsonify, request
|
|
17
|
+
|
|
18
|
+
from ._helpers import render_with_overrides
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def register_composition_routes(app, editor):
|
|
22
|
+
"""Register composition routes with the Flask app."""
|
|
23
|
+
|
|
24
|
+
@app.route("/api/panel-visibility", methods=["POST"])
|
|
25
|
+
def set_panel_visibility():
|
|
26
|
+
"""Toggle panel visibility.
|
|
27
|
+
|
|
28
|
+
Request JSON:
|
|
29
|
+
position: [row, col] - Panel position
|
|
30
|
+
visible: bool - Target visibility state
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
success: bool
|
|
34
|
+
image: str - Updated preview as base64
|
|
35
|
+
bboxes: dict - Updated bounding boxes
|
|
36
|
+
"""
|
|
37
|
+
from .._composition import hide_panel, show_panel
|
|
38
|
+
|
|
39
|
+
data = request.get_json() or {}
|
|
40
|
+
position = tuple(data.get("position", [0, 0]))
|
|
41
|
+
visible = data.get("visible", True)
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
if visible:
|
|
45
|
+
show_panel(editor.fig, position)
|
|
46
|
+
else:
|
|
47
|
+
hide_panel(editor.fig, position)
|
|
48
|
+
|
|
49
|
+
base64_img, bboxes, img_size = render_with_overrides(
|
|
50
|
+
editor.fig,
|
|
51
|
+
editor.get_effective_style(),
|
|
52
|
+
editor.dark_mode,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return jsonify(
|
|
56
|
+
{
|
|
57
|
+
"success": True,
|
|
58
|
+
"image": base64_img,
|
|
59
|
+
"bboxes": bboxes,
|
|
60
|
+
"img_size": {"width": img_size[0], "height": img_size[1]},
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
except Exception as e:
|
|
65
|
+
return jsonify({"success": False, "error": str(e)}), 500
|
|
66
|
+
|
|
67
|
+
@app.route("/api/align-panels", methods=["POST"])
|
|
68
|
+
def align_panels_route():
|
|
69
|
+
"""Align selected panels.
|
|
70
|
+
|
|
71
|
+
Request JSON:
|
|
72
|
+
panels: [[row, col], ...] - List of panel positions
|
|
73
|
+
mode: str - Alignment mode (left, right, top, bottom, etc.)
|
|
74
|
+
reference: [row, col] - Optional reference panel
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
success: bool
|
|
78
|
+
image: str - Updated preview as base64
|
|
79
|
+
bboxes: dict - Updated bounding boxes
|
|
80
|
+
"""
|
|
81
|
+
from .._composition import align_panels
|
|
82
|
+
|
|
83
|
+
data = request.get_json() or {}
|
|
84
|
+
panels = [tuple(p) for p in data.get("panels", [])]
|
|
85
|
+
mode = data.get("mode", "left")
|
|
86
|
+
reference = tuple(data.get("reference")) if data.get("reference") else None
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
align_panels(editor.fig, panels, mode, reference)
|
|
90
|
+
|
|
91
|
+
base64_img, bboxes, img_size = render_with_overrides(
|
|
92
|
+
editor.fig,
|
|
93
|
+
editor.get_effective_style(),
|
|
94
|
+
editor.dark_mode,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return jsonify(
|
|
98
|
+
{
|
|
99
|
+
"success": True,
|
|
100
|
+
"image": base64_img,
|
|
101
|
+
"bboxes": bboxes,
|
|
102
|
+
"img_size": {"width": img_size[0], "height": img_size[1]},
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
except Exception as e:
|
|
107
|
+
return jsonify({"success": False, "error": str(e)}), 500
|
|
108
|
+
|
|
109
|
+
@app.route("/api/distribute-panels", methods=["POST"])
|
|
110
|
+
def distribute_panels_route():
|
|
111
|
+
"""Distribute panels evenly.
|
|
112
|
+
|
|
113
|
+
Request JSON:
|
|
114
|
+
panels: [[row, col], ...] - List of panel positions
|
|
115
|
+
direction: str - 'horizontal' or 'vertical'
|
|
116
|
+
spacing_mm: float - Optional fixed spacing in mm
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
success: bool
|
|
120
|
+
image: str - Updated preview as base64
|
|
121
|
+
bboxes: dict - Updated bounding boxes
|
|
122
|
+
"""
|
|
123
|
+
from .._composition import distribute_panels
|
|
124
|
+
|
|
125
|
+
data = request.get_json() or {}
|
|
126
|
+
panels = [tuple(p) for p in data.get("panels", [])]
|
|
127
|
+
direction = data.get("direction", "horizontal")
|
|
128
|
+
spacing_mm = data.get("spacing_mm")
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
distribute_panels(editor.fig, panels, direction, spacing_mm)
|
|
132
|
+
|
|
133
|
+
base64_img, bboxes, img_size = render_with_overrides(
|
|
134
|
+
editor.fig,
|
|
135
|
+
editor.get_effective_style(),
|
|
136
|
+
editor.dark_mode,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return jsonify(
|
|
140
|
+
{
|
|
141
|
+
"success": True,
|
|
142
|
+
"image": base64_img,
|
|
143
|
+
"bboxes": bboxes,
|
|
144
|
+
"img_size": {"width": img_size[0], "height": img_size[1]},
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
return jsonify({"success": False, "error": str(e)}), 500
|
|
150
|
+
|
|
151
|
+
@app.route("/api/smart-align", methods=["POST"])
|
|
152
|
+
def smart_align_route():
|
|
153
|
+
"""Auto-align all panels based on grid structure.
|
|
154
|
+
|
|
155
|
+
Request JSON:
|
|
156
|
+
panels: [[row, col], ...] - Optional specific panels to align
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
success: bool
|
|
160
|
+
image: str - Updated preview as base64
|
|
161
|
+
bboxes: dict - Updated bounding boxes
|
|
162
|
+
"""
|
|
163
|
+
from .._composition import smart_align
|
|
164
|
+
|
|
165
|
+
data = request.get_json() or {}
|
|
166
|
+
panels = None
|
|
167
|
+
if data.get("panels"):
|
|
168
|
+
panels = [tuple(p) for p in data["panels"]]
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
smart_align(editor.fig, panels)
|
|
172
|
+
|
|
173
|
+
base64_img, bboxes, img_size = render_with_overrides(
|
|
174
|
+
editor.fig,
|
|
175
|
+
editor.get_effective_style(),
|
|
176
|
+
editor.dark_mode,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
return jsonify(
|
|
180
|
+
{
|
|
181
|
+
"success": True,
|
|
182
|
+
"image": base64_img,
|
|
183
|
+
"bboxes": bboxes,
|
|
184
|
+
"img_size": {"width": img_size[0], "height": img_size[1]},
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
except Exception as e:
|
|
189
|
+
return jsonify({"success": False, "error": str(e)}), 500
|
|
190
|
+
|
|
191
|
+
@app.route("/api/import-panel", methods=["POST"])
|
|
192
|
+
def import_panel():
|
|
193
|
+
"""Import axes from another recipe into current figure.
|
|
194
|
+
|
|
195
|
+
Request JSON:
|
|
196
|
+
source: str - Path to source recipe file
|
|
197
|
+
source_axes: str - Axes key in source (default: 'ax_0_0')
|
|
198
|
+
target_position: [row, col] - Target panel position
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
success: bool
|
|
202
|
+
image: str - Updated preview as base64
|
|
203
|
+
bboxes: dict - Updated bounding boxes
|
|
204
|
+
"""
|
|
205
|
+
from .._composition import import_axes
|
|
206
|
+
|
|
207
|
+
data = request.get_json() or {}
|
|
208
|
+
source_path = data.get("source")
|
|
209
|
+
source_axes = data.get("source_axes", "ax_0_0")
|
|
210
|
+
target_position = tuple(data.get("target_position", [0, 0]))
|
|
211
|
+
|
|
212
|
+
if not source_path:
|
|
213
|
+
return jsonify({"success": False, "error": "No source path provided"}), 400
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
# Resolve relative paths against working directory
|
|
217
|
+
working_dir = getattr(editor, "working_dir", Path.cwd())
|
|
218
|
+
source_path = Path(source_path)
|
|
219
|
+
if not source_path.is_absolute():
|
|
220
|
+
source_path = working_dir / source_path
|
|
221
|
+
|
|
222
|
+
import_axes(editor.fig, target_position, source_path, source_axes)
|
|
223
|
+
|
|
224
|
+
base64_img, bboxes, img_size = render_with_overrides(
|
|
225
|
+
editor.fig,
|
|
226
|
+
editor.get_effective_style(),
|
|
227
|
+
editor.dark_mode,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
return jsonify(
|
|
231
|
+
{
|
|
232
|
+
"success": True,
|
|
233
|
+
"image": base64_img,
|
|
234
|
+
"bboxes": bboxes,
|
|
235
|
+
"img_size": {"width": img_size[0], "height": img_size[1]},
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
except Exception as e:
|
|
240
|
+
return jsonify({"success": False, "error": str(e)}), 500
|
|
241
|
+
|
|
242
|
+
@app.route("/api/panel-info")
|
|
243
|
+
def get_panel_info():
|
|
244
|
+
"""Get information about all panels in the figure.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
panels: list of {position, visible, has_content}
|
|
248
|
+
"""
|
|
249
|
+
panels = []
|
|
250
|
+
for ax_key, ax_record in editor.fig.record.axes.items():
|
|
251
|
+
parts = ax_key.split("_")
|
|
252
|
+
if len(parts) >= 3:
|
|
253
|
+
row, col = int(parts[1]), int(parts[2])
|
|
254
|
+
else:
|
|
255
|
+
row, col = 0, 0
|
|
256
|
+
|
|
257
|
+
panels.append(
|
|
258
|
+
{
|
|
259
|
+
"key": ax_key,
|
|
260
|
+
"position": [row, col],
|
|
261
|
+
"visible": getattr(ax_record, "visible", True),
|
|
262
|
+
"has_content": len(ax_record.calls) > 0,
|
|
263
|
+
"call_count": len(ax_record.calls),
|
|
264
|
+
}
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return jsonify({"panels": panels})
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
__all__ = ["register_composition_routes"]
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Core Flask route handlers for the figure editor.
|
|
5
|
+
Handles main page, preview, update, and hitmap routes.
|
|
6
|
+
File operations moved to _routes_files.py.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
from flask import jsonify, render_template_string, request
|
|
12
|
+
|
|
13
|
+
from . import _check_figure_has_content
|
|
14
|
+
from ._helpers import render_with_overrides
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _is_debug_mode() -> bool:
|
|
18
|
+
"""Check if debug mode is enabled via FIGRECIPE_DEBUG_MODE env var."""
|
|
19
|
+
return os.environ.get("FIGRECIPE_DEBUG_MODE", "").lower() in ("1", "true", "yes")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def register_core_routes(app, editor):
|
|
23
|
+
"""Register core routes with the Flask app."""
|
|
24
|
+
from ._hitmap import generate_hitmap, hitmap_to_base64
|
|
25
|
+
from ._templates import build_html_template
|
|
26
|
+
|
|
27
|
+
@app.route("/")
|
|
28
|
+
def index():
|
|
29
|
+
"""Main editor page."""
|
|
30
|
+
base64_img, bboxes, img_size = render_with_overrides(
|
|
31
|
+
editor.fig,
|
|
32
|
+
editor.get_effective_style(),
|
|
33
|
+
editor.dark_mode,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
style_name = getattr(editor, "_style_name", "SCITEX")
|
|
37
|
+
|
|
38
|
+
# Check if figure has plot content
|
|
39
|
+
figure_has_content = _check_figure_has_content(editor.fig)
|
|
40
|
+
|
|
41
|
+
html = build_html_template(
|
|
42
|
+
image_base64=base64_img,
|
|
43
|
+
bboxes=bboxes,
|
|
44
|
+
color_map=editor._color_map,
|
|
45
|
+
style=editor.style,
|
|
46
|
+
overrides=editor.get_effective_style(),
|
|
47
|
+
img_size=img_size,
|
|
48
|
+
style_name=style_name,
|
|
49
|
+
hot_reload=editor.hot_reload,
|
|
50
|
+
dark_mode=editor.dark_mode,
|
|
51
|
+
figure_has_content=figure_has_content,
|
|
52
|
+
debug_mode=_is_debug_mode(),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return render_template_string(html)
|
|
56
|
+
|
|
57
|
+
@app.route("/preview")
|
|
58
|
+
def preview():
|
|
59
|
+
"""Get current preview image."""
|
|
60
|
+
base64_img, bboxes, img_size = render_with_overrides(
|
|
61
|
+
editor.fig,
|
|
62
|
+
editor.get_effective_style(),
|
|
63
|
+
editor.dark_mode,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return jsonify(
|
|
67
|
+
{
|
|
68
|
+
"image": base64_img,
|
|
69
|
+
"bboxes": bboxes,
|
|
70
|
+
"img_size": {"width": img_size[0], "height": img_size[1]},
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
@app.route("/ping")
|
|
75
|
+
def ping():
|
|
76
|
+
"""Health check endpoint for hot reload detection."""
|
|
77
|
+
return jsonify({"status": "ok"})
|
|
78
|
+
|
|
79
|
+
@app.route("/update", methods=["POST"])
|
|
80
|
+
def update():
|
|
81
|
+
"""Update preview with new style overrides."""
|
|
82
|
+
from ._preferences import set_preference
|
|
83
|
+
|
|
84
|
+
data = request.get_json() or {}
|
|
85
|
+
|
|
86
|
+
editor.overrides.update(data.get("overrides", {}))
|
|
87
|
+
|
|
88
|
+
# Update and persist dark mode preference
|
|
89
|
+
new_dark_mode = data.get("dark_mode")
|
|
90
|
+
if new_dark_mode is not None and new_dark_mode != editor.dark_mode:
|
|
91
|
+
editor.dark_mode = new_dark_mode
|
|
92
|
+
set_preference("dark_mode", new_dark_mode)
|
|
93
|
+
|
|
94
|
+
base64_img, bboxes, img_size = render_with_overrides(
|
|
95
|
+
editor.fig,
|
|
96
|
+
editor.get_effective_style(),
|
|
97
|
+
editor.dark_mode,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return jsonify(
|
|
101
|
+
{
|
|
102
|
+
"image": base64_img,
|
|
103
|
+
"bboxes": bboxes,
|
|
104
|
+
"img_size": {"width": img_size[0], "height": img_size[1]},
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
@app.route("/hitmap")
|
|
109
|
+
def hitmap():
|
|
110
|
+
"""Get hitmap image and color map (lazy generation on first request)."""
|
|
111
|
+
if not editor._hitmap_generated:
|
|
112
|
+
print("Generating hitmap (first request)...")
|
|
113
|
+
hitmap_img, editor._color_map = generate_hitmap(editor.fig)
|
|
114
|
+
editor._hitmap_base64 = hitmap_to_base64(hitmap_img)
|
|
115
|
+
editor._hitmap_generated = True
|
|
116
|
+
print("Hitmap ready.")
|
|
117
|
+
|
|
118
|
+
return jsonify(
|
|
119
|
+
{
|
|
120
|
+
"image": editor._hitmap_base64,
|
|
121
|
+
"color_map": editor._color_map,
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
__all__ = ["register_core_routes"]
|