figrecipe 0.7.4__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 +74 -76
- figrecipe/__main__.py +12 -0
- figrecipe/_api/_panel.py +67 -0
- figrecipe/_api/_save.py +100 -4
- 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 +2 -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/_editor/__init__.py +36 -36
- figrecipe/_editor/_bbox/_extract.py +155 -9
- figrecipe/_editor/_bbox/_extract_text.py +124 -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 +157 -16
- figrecipe/_editor/_helpers.py +17 -8
- figrecipe/_editor/_hitmap/_detect.py +89 -32
- figrecipe/_editor/_hitmap_main.py +4 -4
- figrecipe/_editor/_overrides.py +4 -1
- figrecipe/_editor/_plot_types_registry.py +190 -0
- figrecipe/_editor/_render_overrides.py +38 -11
- figrecipe/_editor/_renderer.py +46 -1
- figrecipe/_editor/_routes_annotation.py +114 -0
- figrecipe/_editor/_routes_axis.py +35 -6
- figrecipe/_editor/_routes_captions.py +130 -0
- figrecipe/_editor/_routes_composition.py +270 -0
- figrecipe/_editor/_routes_core.py +15 -173
- figrecipe/_editor/_routes_datatable.py +364 -0
- figrecipe/_editor/_routes_element.py +37 -19
- 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 +28 -8
- figrecipe/_editor/_templates/__init__.py +40 -2
- figrecipe/_editor/_templates/_html.py +97 -103
- 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 +58 -0
- figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
- figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
- figrecipe/_editor/_templates/_scripts/_api.py +1 -1
- figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
- figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
- figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
- figrecipe/_editor/_templates/_scripts/_core.py +94 -37
- 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/_element_editor.py +17 -2
- figrecipe/_editor/_templates/_scripts/_files.py +274 -40
- figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
- figrecipe/_editor/_templates/_scripts/_hitmap.py +87 -84
- figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
- figrecipe/_editor/_templates/_scripts/_legend_drag.py +5 -0
- figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag.py +219 -48
- figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
- figrecipe/_editor/_templates/_scripts/_panel_position.py +238 -54
- 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 +8 -1
- figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
- figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
- figrecipe/_editor/_templates/_scripts/_zoom.py +52 -19
- figrecipe/_editor/_templates/_styles/__init__.py +9 -0
- figrecipe/_editor/_templates/_styles/_base.py +47 -0
- figrecipe/_editor/_templates/_styles/_buttons.py +127 -6
- figrecipe/_editor/_templates/_styles/_composition.py +87 -0
- figrecipe/_editor/_templates/_styles/_controls.py +168 -3
- 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 +5 -5
- figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
- figrecipe/_editor/_templates/_styles/_forms.py +98 -0
- figrecipe/_editor/_templates/_styles/_hitmap.py +7 -0
- figrecipe/_editor/_templates/_styles/_modals.py +29 -0
- figrecipe/_editor/_templates/_styles/_overlays.py +5 -5
- figrecipe/_editor/_templates/_styles/_preview.py +213 -8
- 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 +2 -0
- figrecipe/_recorder.py +28 -3
- figrecipe/_reproducer/_core.py +60 -49
- figrecipe/_utils/__init__.py +3 -0
- figrecipe/_utils/_bundle.py +205 -0
- figrecipe/_wrappers/_axes.py +150 -2
- figrecipe/_wrappers/_caption_generator.py +218 -0
- figrecipe/_wrappers/_figure.py +26 -1
- figrecipe/_wrappers/_stat_annotation.py +274 -0
- figrecipe/styles/_style_applier.py +10 -2
- figrecipe/styles/presets/SCITEX.yaml +11 -4
- {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/METADATA +144 -146
- figrecipe-0.9.0.dist-info/RECORD +277 -0
- figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
- figrecipe-0.7.4.dist-info/RECORD +0 -188
- {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
- {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Routes for panel snapshot generation (isolated rendering)."""
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import io
|
|
7
|
+
import threading
|
|
8
|
+
|
|
9
|
+
# Lock to prevent concurrent matplotlib figure access (not thread-safe)
|
|
10
|
+
_figure_lock = threading.Lock()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register_snapshot_routes(app, editor):
|
|
14
|
+
"""Register snapshot-related routes.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
app : Flask
|
|
19
|
+
Flask application instance.
|
|
20
|
+
editor : FigureEditor
|
|
21
|
+
Editor instance with figure state.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@app.route("/get_panel_snapshot/<int:ax_index>")
|
|
25
|
+
def get_panel_snapshot(ax_index):
|
|
26
|
+
"""Render a single panel in isolation and return as base64 PNG.
|
|
27
|
+
|
|
28
|
+
This hides all other axes to produce a clean snapshot without
|
|
29
|
+
overlap artifacts from neighboring panels.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
ax_index : int
|
|
34
|
+
Index of the axis/panel to render.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
dict
|
|
39
|
+
JSON with success status and base64-encoded PNG image.
|
|
40
|
+
"""
|
|
41
|
+
# DISABLED: Modifying figure visibility corrupts shared state
|
|
42
|
+
# TODO: Implement proper solution (deep copy figure or pre-render)
|
|
43
|
+
return {"success": False, "error": "Snapshot temporarily disabled"}
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
# Use lock to prevent concurrent matplotlib access (not thread-safe)
|
|
47
|
+
with _figure_lock:
|
|
48
|
+
# Get matplotlib figure from RecordingFigure wrapper
|
|
49
|
+
mpl_fig = editor.fig.fig if hasattr(editor.fig, "fig") else editor.fig
|
|
50
|
+
axes = mpl_fig.get_axes()
|
|
51
|
+
|
|
52
|
+
if ax_index < 0 or ax_index >= len(axes):
|
|
53
|
+
return {"success": False, "error": f"Invalid ax_index: {ax_index}"}
|
|
54
|
+
|
|
55
|
+
# Store original visibility states
|
|
56
|
+
original_visibility = [ax.get_visible() for ax in axes]
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
# Hide all axes except the target
|
|
60
|
+
for i, ax in enumerate(axes):
|
|
61
|
+
ax.set_visible(i == ax_index)
|
|
62
|
+
|
|
63
|
+
# Render to buffer with transparent background
|
|
64
|
+
# Use full figure size (no bbox_inches="tight" to preserve dimensions)
|
|
65
|
+
buf = io.BytesIO()
|
|
66
|
+
mpl_fig.savefig(
|
|
67
|
+
buf,
|
|
68
|
+
format="png",
|
|
69
|
+
transparent=True,
|
|
70
|
+
facecolor="none",
|
|
71
|
+
edgecolor="none",
|
|
72
|
+
)
|
|
73
|
+
buf.seek(0)
|
|
74
|
+
image_base64 = base64.b64encode(buf.read()).decode("utf-8")
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
"success": True,
|
|
78
|
+
"image": image_base64,
|
|
79
|
+
"ax_index": ax_index,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
finally:
|
|
83
|
+
# Restore original visibility
|
|
84
|
+
for i, ax in enumerate(axes):
|
|
85
|
+
ax.set_visible(original_visibility[i])
|
|
86
|
+
|
|
87
|
+
except Exception as e:
|
|
88
|
+
# Return JSON error instead of 500 to avoid console errors
|
|
89
|
+
return {"success": False, "error": str(e)}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
__all__ = ["register_snapshot_routes"]
|
|
93
|
+
|
|
94
|
+
# EOF
|
|
@@ -27,6 +27,11 @@ def register_style_routes(app, editor):
|
|
|
27
27
|
}
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
+
@app.route("/overrides")
|
|
31
|
+
def get_overrides():
|
|
32
|
+
"""Get current manual overrides."""
|
|
33
|
+
return jsonify(editor.style_overrides.manual_overrides)
|
|
34
|
+
|
|
30
35
|
@app.route("/theme")
|
|
31
36
|
def get_theme():
|
|
32
37
|
"""Get current theme YAML content for display."""
|
|
@@ -91,7 +96,7 @@ def register_style_routes(app, editor):
|
|
|
91
96
|
# Extract color_palette from nested colors.palette
|
|
92
97
|
if "colors" in new_style and isinstance(new_style["colors"], dict):
|
|
93
98
|
colors_dict = new_style["colors"]
|
|
94
|
-
if "palette" in colors_dict:
|
|
99
|
+
if "palette" in colors_dict and colors_dict["palette"] is not None:
|
|
95
100
|
flat_style["color_palette"] = list(colors_dict["palette"])
|
|
96
101
|
|
|
97
102
|
editor.style_overrides.base_style = flat_style
|
|
@@ -105,10 +110,15 @@ def register_style_routes(app, editor):
|
|
|
105
110
|
mpl_fig = editor.fig.fig if hasattr(editor.fig, "fig") else editor.fig
|
|
106
111
|
behavior = new_style.get("behavior", {})
|
|
107
112
|
for ax in mpl_fig.get_axes():
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
113
|
+
# Handle all four spine directions
|
|
114
|
+
for side, default in [
|
|
115
|
+
("top", True),
|
|
116
|
+
("right", True),
|
|
117
|
+
("bottom", False),
|
|
118
|
+
("left", False),
|
|
119
|
+
]:
|
|
120
|
+
hide = behavior.get(f"hide_{side}_spine", default)
|
|
121
|
+
ax.spines[side].set_visible(not hide)
|
|
112
122
|
|
|
113
123
|
if behavior.get("grid", False):
|
|
114
124
|
ax.grid(True, alpha=0.3)
|
|
@@ -176,6 +186,9 @@ def register_style_routes(app, editor):
|
|
|
176
186
|
# Restore original axes positions
|
|
177
187
|
editor.restore_axes_positions()
|
|
178
188
|
|
|
189
|
+
# Restore original annotation positions (panel labels, text)
|
|
190
|
+
editor.restore_annotation_positions()
|
|
191
|
+
|
|
179
192
|
if editor._initial_base64 and not editor.dark_mode:
|
|
180
193
|
base64_img = editor._initial_base64
|
|
181
194
|
import base64 as b64
|
|
@@ -188,10 +201,17 @@ def register_style_routes(app, editor):
|
|
|
188
201
|
img_size = img.size
|
|
189
202
|
mpl_fig = editor.fig.fig if hasattr(editor.fig, "fig") else editor.fig
|
|
190
203
|
original_dpi = mpl_fig.dpi
|
|
191
|
-
|
|
192
|
-
|
|
204
|
+
try:
|
|
205
|
+
mpl_fig.set_dpi(150)
|
|
206
|
+
mpl_fig.canvas.draw()
|
|
207
|
+
except Exception:
|
|
208
|
+
# Ignore matplotlib/tkinter threading issues in background thread
|
|
209
|
+
pass
|
|
193
210
|
bboxes = extract_bboxes(mpl_fig, img_size[0], img_size[1])
|
|
194
|
-
|
|
211
|
+
try:
|
|
212
|
+
mpl_fig.set_dpi(original_dpi)
|
|
213
|
+
except Exception:
|
|
214
|
+
pass
|
|
195
215
|
else:
|
|
196
216
|
base64_img, bboxes, img_size = render_with_overrides(
|
|
197
217
|
editor.fig,
|
|
@@ -14,7 +14,11 @@ from datetime import datetime
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
from typing import Any, Dict, Tuple
|
|
16
16
|
|
|
17
|
+
import figrecipe
|
|
18
|
+
|
|
17
19
|
from ._html import HTML_TEMPLATE
|
|
20
|
+
from ._html_components import HTML_FILE_BROWSER
|
|
21
|
+
from ._html_datatable import HTML_DATATABLE_PANEL
|
|
18
22
|
from ._scripts import SCRIPTS
|
|
19
23
|
from ._styles import STYLES
|
|
20
24
|
|
|
@@ -23,7 +27,7 @@ _SERVER_START_TIME = datetime.now().strftime("%H:%M:%S")
|
|
|
23
27
|
|
|
24
28
|
# Load SciTeX icon as base64
|
|
25
29
|
_SCITEX_ICON_PATH = (
|
|
26
|
-
Path(__file__).parent.parent
|
|
30
|
+
Path(__file__).parent.parent / "static" / "icons" / "scitex-icon.png"
|
|
27
31
|
)
|
|
28
32
|
_SCITEX_ICON_BASE64 = ""
|
|
29
33
|
if _SCITEX_ICON_PATH.exists():
|
|
@@ -41,6 +45,8 @@ def build_html_template(
|
|
|
41
45
|
style_name: str = "SCITEX",
|
|
42
46
|
hot_reload: bool = False,
|
|
43
47
|
dark_mode: bool = False,
|
|
48
|
+
figure_has_content: bool = True,
|
|
49
|
+
debug_mode: bool = False,
|
|
44
50
|
) -> str:
|
|
45
51
|
"""
|
|
46
52
|
Build complete HTML template for figure editor.
|
|
@@ -68,6 +74,8 @@ def build_html_template(
|
|
|
68
74
|
Enable hot reload auto-reconnect JavaScript.
|
|
69
75
|
dark_mode : bool
|
|
70
76
|
Initial dark mode state from saved preferences.
|
|
77
|
+
figure_has_content : bool
|
|
78
|
+
Whether the figure has plot content (hides welcome overlay if True).
|
|
71
79
|
|
|
72
80
|
Returns
|
|
73
81
|
-------
|
|
@@ -128,6 +136,13 @@ def build_html_template(
|
|
|
128
136
|
|
|
129
137
|
# Inject data into template
|
|
130
138
|
html = HTML_TEMPLATE
|
|
139
|
+
html = html.replace("<!-- FILE_BROWSER_PLACEHOLDER -->", HTML_FILE_BROWSER)
|
|
140
|
+
|
|
141
|
+
# Insert datatable panel before preview panel
|
|
142
|
+
html = html.replace(
|
|
143
|
+
"<!-- Preview Panel -->",
|
|
144
|
+
HTML_DATATABLE_PANEL + "\n <!-- Preview Panel -->",
|
|
145
|
+
)
|
|
131
146
|
html = html.replace("/* STYLES_PLACEHOLDER */", STYLES)
|
|
132
147
|
html = html.replace("/* SCRIPTS_PLACEHOLDER */", SCRIPTS + hot_reload_script)
|
|
133
148
|
html = html.replace("IMAGE_BASE64_PLACEHOLDER", image_base64)
|
|
@@ -141,11 +156,34 @@ def build_html_template(
|
|
|
141
156
|
|
|
142
157
|
# Dark mode preference - set initial state
|
|
143
158
|
html = html.replace("DARK_MODE_THEME_PLACEHOLDER", "dark" if dark_mode else "light")
|
|
144
|
-
html = html.replace("DARK_MODE_CHECKED_PLACEHOLDER", "checked" if dark_mode else "")
|
|
145
159
|
|
|
146
160
|
# Server start time for debugging
|
|
147
161
|
html = html.replace("SERVER_START_TIME_PLACEHOLDER", _SERVER_START_TIME)
|
|
148
162
|
|
|
163
|
+
# Version number
|
|
164
|
+
html = html.replace("VERSION_PLACEHOLDER", figrecipe.__version__)
|
|
165
|
+
|
|
166
|
+
# Welcome overlay - show only for empty figures
|
|
167
|
+
welcome_display = "none" if figure_has_content else "flex"
|
|
168
|
+
html = html.replace("WELCOME_DISPLAY_PLACEHOLDER", welcome_display)
|
|
169
|
+
|
|
170
|
+
# Debug mode - enables Element Inspector and Show All Bboxes
|
|
171
|
+
html = html.replace("DEBUG_MODE_PLACEHOLDER", "true" if debug_mode else "false")
|
|
172
|
+
|
|
173
|
+
# Debug shortcuts section - only shown when debug mode is enabled
|
|
174
|
+
debug_shortcuts_html = ""
|
|
175
|
+
if debug_mode:
|
|
176
|
+
debug_shortcuts_html = """<div class="shortcut-section debug-shortcuts"><h4>Debug <span class="debug-badge">DEBUG MODE</span></h4>
|
|
177
|
+
<div class="shortcut-row"><span class="shortcut-keys"><kbd>Alt</kbd>+<kbd>I</kbd></span><span class="shortcut-desc">Element Inspector</span></div>
|
|
178
|
+
<div class="shortcut-row"><span class="shortcut-keys"><kbd>Alt</kbd>+<kbd>B</kbd></span><span class="shortcut-desc">Show All Bboxes</span></div>
|
|
179
|
+
<div class="shortcut-row"><span class="shortcut-keys"><kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>I</kbd></span><span class="shortcut-desc">Debug Snapshot</span></div></div>"""
|
|
180
|
+
html = html.replace("DEBUG_SHORTCUTS_PLACEHOLDER", debug_shortcuts_html)
|
|
181
|
+
|
|
182
|
+
# Debug meta (server start time) - only shown in debug mode
|
|
183
|
+
html = html.replace(
|
|
184
|
+
"DEBUG_META_DISPLAY_PLACEHOLDER", "" if debug_mode else 'style="display:none"'
|
|
185
|
+
)
|
|
186
|
+
|
|
149
187
|
return html
|
|
150
188
|
|
|
151
189
|
|
|
@@ -10,28 +10,24 @@ HTML_TEMPLATE = """
|
|
|
10
10
|
<head>
|
|
11
11
|
<meta charset="UTF-8">
|
|
12
12
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
13
|
-
<title>
|
|
13
|
+
<title>FigRecipe Editor</title>
|
|
14
|
+
<link rel="icon" type="image/x-icon" href="/static/icons/favicon.ico">
|
|
15
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/static/icons/favicon-32x32.png">
|
|
16
|
+
<link rel="apple-touch-icon" sizes="192x192" href="/static/icons/scitex-icon.png">
|
|
14
17
|
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
|
|
15
18
|
<style>
|
|
16
19
|
/* STYLES_PLACEHOLDER */
|
|
17
20
|
</style>
|
|
18
21
|
</head>
|
|
19
22
|
<body>
|
|
23
|
+
<div class="spinner-overlay"><div class="spinner-container"><div class="spinner"></div><div class="spinner-text">Loading...</div></div></div>
|
|
20
24
|
<div class="editor-container">
|
|
25
|
+
<!-- FILE_BROWSER_PLACEHOLDER -->
|
|
21
26
|
<!-- Preview Panel -->
|
|
22
|
-
<div class="preview-panel">
|
|
27
|
+
<div class="preview-panel" id="preview-panel">
|
|
23
28
|
<div class="preview-header">
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
<span class="figrecipe-title">FigRecipe Editor</span>
|
|
27
|
-
</a>
|
|
28
|
-
<span id="server-start-time" style="font-size: 10px; color: #888; margin-left: 8px;">Started: SERVER_START_TIME_PLACEHOLDER</span>
|
|
29
|
-
<div class="file-switcher">
|
|
30
|
-
<select id="file-selector" class="file-selector" title="Switch between recipe files">
|
|
31
|
-
<option value="">Loading files...</option>
|
|
32
|
-
</select>
|
|
33
|
-
<button id="btn-new-figure" class="btn-new" title="Create new blank figure">+</button>
|
|
34
|
-
</div>
|
|
29
|
+
<button id="btn-collapse-preview" class="btn-collapse" title="Collapse canvas">❯</button>
|
|
30
|
+
<span class="panel-label">CANVAS</span>
|
|
35
31
|
<div class="preview-controls">
|
|
36
32
|
<div class="download-dropdown">
|
|
37
33
|
<button id="btn-download-main" class="btn-primary download-main" title="Download as PNG">Download PNG</button>
|
|
@@ -44,20 +40,27 @@ HTML_TEMPLATE = """
|
|
|
44
40
|
<button id="btn-download-csv-menu" class="download-option" data-format="csv" title="Export plot data as CSV">CSV (Data)</button>
|
|
45
41
|
</div>
|
|
46
42
|
</div>
|
|
47
|
-
<button id="btn-refresh" title="
|
|
43
|
+
<button id="btn-refresh" title="Re-render figure (R)">Render</button>
|
|
48
44
|
<div class="zoom-controls">
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
<select id="zoom-select" title="Zoom level (+/- or scroll)">
|
|
46
|
+
<option value="25">25%</option>
|
|
47
|
+
<option value="50">50%</option>
|
|
48
|
+
<option value="75">75%</option>
|
|
49
|
+
<option value="100" selected>100%</option>
|
|
50
|
+
<option value="125">125%</option>
|
|
51
|
+
<option value="150">150%</option>
|
|
52
|
+
<option value="200">200%</option>
|
|
53
|
+
</select>
|
|
53
54
|
<button id="btn-zoom-fit" title="Fit to view (F)">Fit</button>
|
|
54
55
|
</div>
|
|
55
|
-
<button id="btn-ruler-grid" class="btn-ruler" title="Toggle rulers and grid
|
|
56
|
-
<button id="
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
</
|
|
56
|
+
<button id="btn-ruler-grid" class="btn-ruler" title="Toggle rulers and grid (G)">Grid</button>
|
|
57
|
+
<button id="dark-mode-toggle" class="btn-theme" title="Toggle theme (D)">🌙</button>
|
|
58
|
+
<div class="toolbar-separator"></div>
|
|
59
|
+
<button id="btn-undo" class="btn-icon" title="Undo (Ctrl+Z)" disabled>↶</button>
|
|
60
|
+
<button id="btn-redo" class="btn-icon" title="Redo (Ctrl+Y)" disabled>↷</button>
|
|
61
|
+
<button id="btn-restore" class="btn-secondary" title="Revert all changes">Revert</button>
|
|
62
|
+
<button id="btn-save" class="btn-primary" title="Save (Ctrl+S)">Save</button>
|
|
63
|
+
<button id="btn-shortcuts" class="btn-icon btn-shortcuts" title="Keyboard shortcuts (?)">⌨</button>
|
|
61
64
|
</div>
|
|
62
65
|
</div>
|
|
63
66
|
<div class="preview-wrapper" id="preview-wrapper">
|
|
@@ -70,17 +73,21 @@ HTML_TEMPLATE = """
|
|
|
70
73
|
<svg id="column-overlay" class="column-overlay"></svg>
|
|
71
74
|
<canvas id="hitmap-canvas" style="display: none;"></canvas>
|
|
72
75
|
</div>
|
|
76
|
+
<!-- Welcome overlay -->
|
|
77
|
+
<div id="welcome-overlay" class="welcome-overlay" style="display: WELCOME_DISPLAY_PLACEHOLDER;"><div class="welcome-content"><h2>Getting Started</h2><div class="welcome-steps"><div class="welcome-step"><span class="step-number">1</span><span class="step-text">Drop CSV/TSV in <strong>Data</strong></span></div><div class="welcome-step"><span class="step-number">2</span><span class="step-text">Click <strong>Plot</strong></span></div><div class="welcome-step"><span class="step-number">3</span><span class="step-text">Adjust in <strong>Properties</strong></span></div></div><p class="welcome-hint">Or load .yaml from Files</p></div></div>
|
|
78
|
+
</div>
|
|
79
|
+
<!-- Caption Pane (below canvas) -->
|
|
80
|
+
<div class="caption-pane" id="caption-pane">
|
|
81
|
+
<span id="canvas-caption-text"><b>Fig. 1.</b></span>
|
|
73
82
|
</div>
|
|
74
83
|
</div>
|
|
75
84
|
|
|
76
85
|
<!-- Controls Panel -->
|
|
77
|
-
<div class="controls-panel">
|
|
86
|
+
<div class="controls-panel" id="controls-panel">
|
|
78
87
|
<div class="controls-header">
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
<button id="btn-reset" class="btn-secondary" title="Reset to last saved">Reset</button>
|
|
83
|
-
<button id="btn-save" class="btn-primary">Save</button>
|
|
88
|
+
<div class="header-title">
|
|
89
|
+
<button id="btn-collapse-properties" class="btn-collapse" title="Collapse panel">❯</button>
|
|
90
|
+
<span>PROPERTIES</span>
|
|
84
91
|
</div>
|
|
85
92
|
</div>
|
|
86
93
|
<div class="style-info">
|
|
@@ -89,11 +96,10 @@ HTML_TEMPLATE = """
|
|
|
89
96
|
<option value="SCITEX">SCITEX</option>
|
|
90
97
|
<option value="MATPLOTLIB">MATPLOTLIB</option>
|
|
91
98
|
</select>
|
|
92
|
-
<
|
|
93
|
-
<
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
</div>
|
|
99
|
+
<label class="checkbox-inline" title="Transparent background">
|
|
100
|
+
<input type="checkbox" id="output_transparent" checked>
|
|
101
|
+
<span>Transparent</span>
|
|
102
|
+
</label>
|
|
97
103
|
</div>
|
|
98
104
|
<!-- Theme Modal -->
|
|
99
105
|
<div id="theme-modal" class="modal" style="display: none;">
|
|
@@ -117,39 +123,22 @@ HTML_TEMPLATE = """
|
|
|
117
123
|
<button id="shortcuts-modal-close" class="modal-close">×</button>
|
|
118
124
|
</div>
|
|
119
125
|
<div class="shortcuts-content">
|
|
120
|
-
<div class="shortcut-section">
|
|
121
|
-
<
|
|
122
|
-
<div class="shortcut-row"><span class="shortcut-keys"><kbd>Ctrl</kbd>+<kbd>S</kbd></span><span class="shortcut-desc">Save
|
|
123
|
-
<div class="shortcut-row"><span class="shortcut-keys"><kbd>
|
|
124
|
-
|
|
125
|
-
<div class="shortcut-row"><span class="shortcut-keys"><kbd>
|
|
126
|
-
|
|
127
|
-
<div class="shortcut-row"><span class="shortcut-keys"><kbd>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
<div class="shortcut-row"><span class="shortcut-keys"><kbd>1</kbd></span><span class="shortcut-desc">Figure tab</span></div>
|
|
132
|
-
<div class="shortcut-row"><span class="shortcut-keys"><kbd>2</kbd></span><span class="shortcut-desc">Axis tab</span></div>
|
|
133
|
-
<div class="shortcut-row"><span class="shortcut-keys"><kbd>3</kbd></span><span class="shortcut-desc">Element tab</span></div>
|
|
134
|
-
</div>
|
|
135
|
-
<div class="shortcut-section">
|
|
136
|
-
<h4>View</h4>
|
|
137
|
-
<div class="shortcut-row"><span class="shortcut-keys"><kbd>G</kbd></span><span class="shortcut-desc">Toggle ruler & grid</span></div>
|
|
138
|
-
<div class="shortcut-row"><span class="shortcut-keys"><kbd>+</kbd> / <kbd>-</kbd></span><span class="shortcut-desc">Zoom in/out</span></div>
|
|
139
|
-
<div class="shortcut-row"><span class="shortcut-keys"><kbd>0</kbd></span><span class="shortcut-desc">Reset zoom</span></div>
|
|
140
|
-
<div class="shortcut-row"><span class="shortcut-keys"><kbd>F</kbd></span><span class="shortcut-desc">Fit to view</span></div>
|
|
141
|
-
<div class="shortcut-row"><span class="shortcut-keys"><kbd>?</kbd></span><span class="shortcut-desc">Show this help</span></div>
|
|
142
|
-
</div>
|
|
143
|
-
<div class="shortcut-section">
|
|
144
|
-
<h4>Developer</h4>
|
|
145
|
-
<div class="shortcut-row"><span class="shortcut-keys"><kbd>Alt</kbd>+<kbd>I</kbd></span><span class="shortcut-desc">Toggle element inspector</span></div>
|
|
146
|
-
<div class="shortcut-row"><span class="shortcut-keys"><kbd>Alt</kbd>+<kbd>Shift</kbd>+<kbd>I</kbd></span><span class="shortcut-desc">Screenshot + console logs</span></div>
|
|
147
|
-
</div>
|
|
126
|
+
<div class="shortcut-section"><h4>General</h4>
|
|
127
|
+
<div class="shortcut-row"><span class="shortcut-keys"><kbd>Ctrl</kbd>+<kbd>Z</kbd>/<kbd>Shift+Z</kbd></span><span class="shortcut-desc">Undo/Redo</span></div>
|
|
128
|
+
<div class="shortcut-row"><span class="shortcut-keys"><kbd>Ctrl</kbd>+<kbd>S</kbd>/<kbd>Shift+S</kbd></span><span class="shortcut-desc">Save/Download</span></div>
|
|
129
|
+
<div class="shortcut-row"><span class="shortcut-keys"><kbd>F5</kbd>/<kbd>Esc</kbd>/<kbd>R</kbd></span><span class="shortcut-desc">Refresh/Clear/Reset</span></div></div>
|
|
130
|
+
<div class="shortcut-section"><h4>Navigation</h4>
|
|
131
|
+
<div class="shortcut-row"><span class="shortcut-keys"><kbd>1</kbd>/<kbd>2</kbd>/<kbd>3</kbd></span><span class="shortcut-desc">Figure/Axis/Element tab</span></div></div>
|
|
132
|
+
<div class="shortcut-section"><h4>View</h4>
|
|
133
|
+
<div class="shortcut-row"><span class="shortcut-keys"><kbd>G</kbd>/<kbd>+</kbd>/<kbd>-</kbd>/<kbd>0</kbd>/<kbd>F</kbd></span><span class="shortcut-desc">Grid/Zoom/Reset/Fit</span></div></div>
|
|
134
|
+
<div class="shortcut-section"><h4>Panel</h4>
|
|
135
|
+
<div class="shortcut-row"><span class="shortcut-keys">Drag/<kbd>Alt</kbd>+Drag</span><span class="shortcut-desc">Move (snap/free)</span></div></div>
|
|
136
|
+
DEBUG_SHORTCUTS_PLACEHOLDER
|
|
148
137
|
</div>
|
|
149
138
|
</div>
|
|
150
139
|
</div>
|
|
151
140
|
<div id="override-status" class="override-status" style="display: none;">
|
|
152
|
-
<span class="override-indicator">
|
|
141
|
+
<span class="override-indicator">Modified</span>
|
|
153
142
|
<span id="override-timestamp" class="override-timestamp"></span>
|
|
154
143
|
</div>
|
|
155
144
|
|
|
@@ -164,7 +153,7 @@ HTML_TEMPLATE = """
|
|
|
164
153
|
<!-- FIGURE TAB -->
|
|
165
154
|
<div id="tab-content-figure" class="tab-content active">
|
|
166
155
|
<!-- Dimensions Section -->
|
|
167
|
-
<details class="section"
|
|
156
|
+
<details class="section">
|
|
168
157
|
<summary>Dimensions</summary>
|
|
169
158
|
<div class="section-content">
|
|
170
159
|
<div class="subsection">
|
|
@@ -172,19 +161,19 @@ HTML_TEMPLATE = """
|
|
|
172
161
|
<div class="form-grid">
|
|
173
162
|
<div class="form-row">
|
|
174
163
|
<label>Left</label>
|
|
175
|
-
<input type="number" id="margins_left_mm" step="1" min="0" max="50" placeholder="
|
|
164
|
+
<input type="number" id="margins_left_mm" step="1" min="0" max="50" placeholder="1">
|
|
176
165
|
</div>
|
|
177
166
|
<div class="form-row">
|
|
178
167
|
<label>Right</label>
|
|
179
|
-
<input type="number" id="margins_right_mm" step="1" min="0" max="50" placeholder="
|
|
168
|
+
<input type="number" id="margins_right_mm" step="1" min="0" max="50" placeholder="1">
|
|
180
169
|
</div>
|
|
181
170
|
<div class="form-row">
|
|
182
171
|
<label>Bottom</label>
|
|
183
|
-
<input type="number" id="margins_bottom_mm" step="1" min="0" max="50" placeholder="
|
|
172
|
+
<input type="number" id="margins_bottom_mm" step="1" min="0" max="50" placeholder="1">
|
|
184
173
|
</div>
|
|
185
174
|
<div class="form-row">
|
|
186
175
|
<label>Top</label>
|
|
187
|
-
<input type="number" id="margins_top_mm" step="1" min="0" max="50" placeholder="
|
|
176
|
+
<input type="number" id="margins_top_mm" step="1" min="0" max="50" placeholder="1">
|
|
188
177
|
</div>
|
|
189
178
|
</div>
|
|
190
179
|
</div>
|
|
@@ -206,13 +195,19 @@ HTML_TEMPLATE = """
|
|
|
206
195
|
<details class="section">
|
|
207
196
|
<summary>Output</summary>
|
|
208
197
|
<div class="section-content">
|
|
209
|
-
<div class="form-row">
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
198
|
+
<div class="form-row"><label>DPI</label><input type="number" id="output_dpi" step="50" min="72" max="600"></div>
|
|
199
|
+
<div class="form-row"><label>Transparent</label><input type="checkbox" id="output_transparent"></div>
|
|
200
|
+
</div>
|
|
201
|
+
</details>
|
|
202
|
+
<!-- Figure Caption (Scientific) -->
|
|
203
|
+
<details class="section">
|
|
204
|
+
<summary>Figure Caption</summary>
|
|
205
|
+
<div class="section-content">
|
|
206
|
+
<div class="form-row"><label>Fig. #</label><input type="number" id="caption_figure_number" min="1" max="99" step="1" value="1" style="width:60px"></div>
|
|
207
|
+
<div class="form-row caption-row"><label>Caption</label><textarea id="caption_figure_text" class="caption-textarea" rows="2" placeholder="e.g., Comparison of sin and cos functions"></textarea></div>
|
|
208
|
+
<div class="composed-caption-preview" id="composed-caption-container">
|
|
209
|
+
<div class="composed-caption-label">Composed Caption:</div>
|
|
210
|
+
<div class="composed-caption-text" id="composed-caption-text"><b>Fig. 1.</b></div>
|
|
216
211
|
</div>
|
|
217
212
|
</div>
|
|
218
213
|
</details>
|
|
@@ -223,14 +218,12 @@ HTML_TEMPLATE = """
|
|
|
223
218
|
<div class="tab-hint" id="axis-tab-hint">Select an axis element (title, label, ticks, legend) to edit</div>
|
|
224
219
|
|
|
225
220
|
<!-- Panel Position Section -->
|
|
226
|
-
<details class="section"
|
|
221
|
+
<details class="section">
|
|
227
222
|
<summary>Panel Position</summary>
|
|
228
223
|
<div class="section-content">
|
|
229
|
-
<div class="form-row">
|
|
224
|
+
<div class="form-row panel-indicator-row">
|
|
230
225
|
<label>Panel</label>
|
|
231
|
-
<
|
|
232
|
-
<option value="0">Panel 0</option>
|
|
233
|
-
</select>
|
|
226
|
+
<span id="current_panel_indicator" class="panel-indicator">Select an element</span>
|
|
234
227
|
</div>
|
|
235
228
|
<div class="position-grid">
|
|
236
229
|
<div class="form-row">
|
|
@@ -272,24 +265,27 @@ HTML_TEMPLATE = """
|
|
|
272
265
|
</details>
|
|
273
266
|
|
|
274
267
|
<!-- Labels Section -->
|
|
275
|
-
<details class="section"
|
|
268
|
+
<details class="section">
|
|
276
269
|
<summary>Labels</summary>
|
|
277
270
|
<div class="section-content">
|
|
278
|
-
<div class="form-row">
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
</div>
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
271
|
+
<div class="form-row"><label>Title</label><input type="text" id="label_title" class="label-input" placeholder="(no title)"></div>
|
|
272
|
+
<div class="form-row"><label>X Label</label><input type="text" id="label_xlabel" class="label-input" placeholder="(no xlabel)"></div>
|
|
273
|
+
<div class="form-row"><label>Y Label</label><input type="text" id="label_ylabel" class="label-input" placeholder="(no ylabel)"></div>
|
|
274
|
+
<div class="form-row"><label>Suptitle</label><input type="text" id="label_suptitle" class="label-input" placeholder="(no suptitle)"></div>
|
|
275
|
+
</div>
|
|
276
|
+
</details>
|
|
277
|
+
|
|
278
|
+
<!-- Caption Section -->
|
|
279
|
+
<details class="section">
|
|
280
|
+
<summary>Caption</summary>
|
|
281
|
+
<div class="section-content">
|
|
282
|
+
<div class="form-row caption-row">
|
|
283
|
+
<label>Panel</label>
|
|
284
|
+
<textarea id="caption_panel_text" class="caption-textarea" rows="2" placeholder="e.g., Line plot showing sinusoidal functions"></textarea>
|
|
289
285
|
</div>
|
|
290
|
-
<div class="form-row">
|
|
291
|
-
<label>
|
|
292
|
-
<
|
|
286
|
+
<div class="form-row caption-row">
|
|
287
|
+
<label>Figure</label>
|
|
288
|
+
<textarea id="caption_figure_text" class="caption-textarea" rows="3" placeholder="e.g., Overview of visualization methods..."></textarea>
|
|
293
289
|
</div>
|
|
294
290
|
</div>
|
|
295
291
|
</details>
|
|
@@ -396,13 +392,11 @@ HTML_TEMPLATE = """
|
|
|
396
392
|
<label>Thickness (mm)</label>
|
|
397
393
|
<input type="number" id="axes_thickness_mm" step="0.05" min="0.1" max="2" placeholder="0.35">
|
|
398
394
|
</div>
|
|
399
|
-
<div class="
|
|
400
|
-
<label>Hide Top</label>
|
|
401
|
-
<input type="checkbox" id="
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
<label>Hide Right</label>
|
|
405
|
-
<input type="checkbox" id="behavior_hide_right_spine">
|
|
395
|
+
<div class="spine-visibility-grid">
|
|
396
|
+
<div class="form-row"><label>Hide Top</label><input type="checkbox" id="behavior_hide_top_spine"></div>
|
|
397
|
+
<div class="form-row"><label>Hide Right</label><input type="checkbox" id="behavior_hide_right_spine"></div>
|
|
398
|
+
<div class="form-row"><label>Hide Bottom</label><input type="checkbox" id="behavior_hide_bottom_spine"></div>
|
|
399
|
+
<div class="form-row"><label>Hide Left</label><input type="checkbox" id="behavior_hide_left_spine"></div>
|
|
406
400
|
</div>
|
|
407
401
|
<div class="form-row">
|
|
408
402
|
<label>Grid</label>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""HTML components for the figure editor.
|
|
4
|
+
|
|
5
|
+
This package contains modular HTML components:
|
|
6
|
+
- file_browser: File browser panel
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from ._file_browser import HTML_FILE_BROWSER
|
|
10
|
+
|
|
11
|
+
__all__ = ["HTML_FILE_BROWSER"]
|
|
12
|
+
|
|
13
|
+
# EOF
|