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.
Files changed (143) hide show
  1. figrecipe/__init__.py +74 -76
  2. figrecipe/__main__.py +12 -0
  3. figrecipe/_api/_panel.py +67 -0
  4. figrecipe/_api/_save.py +100 -4
  5. figrecipe/_cli/__init__.py +7 -0
  6. figrecipe/_cli/_compose.py +87 -0
  7. figrecipe/_cli/_convert.py +117 -0
  8. figrecipe/_cli/_crop.py +82 -0
  9. figrecipe/_cli/_edit.py +70 -0
  10. figrecipe/_cli/_extract.py +128 -0
  11. figrecipe/_cli/_fonts.py +47 -0
  12. figrecipe/_cli/_info.py +67 -0
  13. figrecipe/_cli/_main.py +58 -0
  14. figrecipe/_cli/_reproduce.py +79 -0
  15. figrecipe/_cli/_style.py +77 -0
  16. figrecipe/_cli/_validate.py +66 -0
  17. figrecipe/_cli/_version.py +50 -0
  18. figrecipe/_composition/__init__.py +32 -0
  19. figrecipe/_composition/_alignment.py +452 -0
  20. figrecipe/_composition/_compose.py +179 -0
  21. figrecipe/_composition/_import_axes.py +127 -0
  22. figrecipe/_composition/_visibility.py +125 -0
  23. figrecipe/_dev/__init__.py +2 -0
  24. figrecipe/_dev/browser/__init__.py +69 -0
  25. figrecipe/_dev/browser/_audio.py +240 -0
  26. figrecipe/_dev/browser/_caption.py +356 -0
  27. figrecipe/_dev/browser/_click_effect.py +146 -0
  28. figrecipe/_dev/browser/_cursor.py +196 -0
  29. figrecipe/_dev/browser/_highlight.py +105 -0
  30. figrecipe/_dev/browser/_narration.py +237 -0
  31. figrecipe/_dev/browser/_recorder.py +446 -0
  32. figrecipe/_dev/browser/_utils.py +178 -0
  33. figrecipe/_dev/browser/_video_trim/__init__.py +152 -0
  34. figrecipe/_dev/browser/_video_trim/_detection.py +223 -0
  35. figrecipe/_dev/browser/_video_trim/_markers.py +140 -0
  36. figrecipe/_editor/__init__.py +36 -36
  37. figrecipe/_editor/_bbox/_extract.py +155 -9
  38. figrecipe/_editor/_bbox/_extract_text.py +124 -0
  39. figrecipe/_editor/_call_overrides.py +183 -0
  40. figrecipe/_editor/_datatable_plot_handlers.py +249 -0
  41. figrecipe/_editor/_figure_layout.py +211 -0
  42. figrecipe/_editor/_flask_app.py +157 -16
  43. figrecipe/_editor/_helpers.py +17 -8
  44. figrecipe/_editor/_hitmap/_detect.py +89 -32
  45. figrecipe/_editor/_hitmap_main.py +4 -4
  46. figrecipe/_editor/_overrides.py +4 -1
  47. figrecipe/_editor/_plot_types_registry.py +190 -0
  48. figrecipe/_editor/_render_overrides.py +38 -11
  49. figrecipe/_editor/_renderer.py +46 -1
  50. figrecipe/_editor/_routes_annotation.py +114 -0
  51. figrecipe/_editor/_routes_axis.py +35 -6
  52. figrecipe/_editor/_routes_captions.py +130 -0
  53. figrecipe/_editor/_routes_composition.py +270 -0
  54. figrecipe/_editor/_routes_core.py +15 -173
  55. figrecipe/_editor/_routes_datatable.py +364 -0
  56. figrecipe/_editor/_routes_element.py +37 -19
  57. figrecipe/_editor/_routes_files.py +443 -0
  58. figrecipe/_editor/_routes_image.py +200 -0
  59. figrecipe/_editor/_routes_snapshot.py +94 -0
  60. figrecipe/_editor/_routes_style.py +28 -8
  61. figrecipe/_editor/_templates/__init__.py +40 -2
  62. figrecipe/_editor/_templates/_html.py +97 -103
  63. figrecipe/_editor/_templates/_html_components/__init__.py +13 -0
  64. figrecipe/_editor/_templates/_html_components/_composition_toolbar.py +79 -0
  65. figrecipe/_editor/_templates/_html_components/_file_browser.py +41 -0
  66. figrecipe/_editor/_templates/_html_datatable.py +92 -0
  67. figrecipe/_editor/_templates/_scripts/__init__.py +58 -0
  68. figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
  69. figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
  70. figrecipe/_editor/_templates/_scripts/_api.py +1 -1
  71. figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
  72. figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
  73. figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
  74. figrecipe/_editor/_templates/_scripts/_core.py +94 -37
  75. figrecipe/_editor/_templates/_scripts/_datatable/__init__.py +59 -0
  76. figrecipe/_editor/_templates/_scripts/_datatable/_cell_edit.py +97 -0
  77. figrecipe/_editor/_templates/_scripts/_datatable/_clipboard.py +164 -0
  78. figrecipe/_editor/_templates/_scripts/_datatable/_context_menu.py +221 -0
  79. figrecipe/_editor/_templates/_scripts/_datatable/_core.py +150 -0
  80. figrecipe/_editor/_templates/_scripts/_datatable/_editable.py +511 -0
  81. figrecipe/_editor/_templates/_scripts/_datatable/_import.py +161 -0
  82. figrecipe/_editor/_templates/_scripts/_datatable/_plot.py +261 -0
  83. figrecipe/_editor/_templates/_scripts/_datatable/_selection.py +438 -0
  84. figrecipe/_editor/_templates/_scripts/_datatable/_table.py +256 -0
  85. figrecipe/_editor/_templates/_scripts/_datatable/_tabs.py +354 -0
  86. figrecipe/_editor/_templates/_scripts/_element_editor.py +17 -2
  87. figrecipe/_editor/_templates/_scripts/_files.py +274 -40
  88. figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
  89. figrecipe/_editor/_templates/_scripts/_hitmap.py +87 -84
  90. figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
  91. figrecipe/_editor/_templates/_scripts/_legend_drag.py +5 -0
  92. figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
  93. figrecipe/_editor/_templates/_scripts/_panel_drag.py +219 -48
  94. figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
  95. figrecipe/_editor/_templates/_scripts/_panel_position.py +238 -54
  96. figrecipe/_editor/_templates/_scripts/_panel_resize.py +230 -0
  97. figrecipe/_editor/_templates/_scripts/_panel_snap.py +307 -0
  98. figrecipe/_editor/_templates/_scripts/_region_select.py +255 -0
  99. figrecipe/_editor/_templates/_scripts/_selection.py +8 -1
  100. figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
  101. figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
  102. figrecipe/_editor/_templates/_scripts/_zoom.py +52 -19
  103. figrecipe/_editor/_templates/_styles/__init__.py +9 -0
  104. figrecipe/_editor/_templates/_styles/_base.py +47 -0
  105. figrecipe/_editor/_templates/_styles/_buttons.py +127 -6
  106. figrecipe/_editor/_templates/_styles/_composition.py +87 -0
  107. figrecipe/_editor/_templates/_styles/_controls.py +168 -3
  108. figrecipe/_editor/_templates/_styles/_datatable/__init__.py +40 -0
  109. figrecipe/_editor/_templates/_styles/_datatable/_editable.py +203 -0
  110. figrecipe/_editor/_templates/_styles/_datatable/_panel.py +268 -0
  111. figrecipe/_editor/_templates/_styles/_datatable/_table.py +479 -0
  112. figrecipe/_editor/_templates/_styles/_datatable/_toolbar.py +384 -0
  113. figrecipe/_editor/_templates/_styles/_datatable/_vars.py +123 -0
  114. figrecipe/_editor/_templates/_styles/_dynamic_props.py +5 -5
  115. figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
  116. figrecipe/_editor/_templates/_styles/_forms.py +98 -0
  117. figrecipe/_editor/_templates/_styles/_hitmap.py +7 -0
  118. figrecipe/_editor/_templates/_styles/_modals.py +29 -0
  119. figrecipe/_editor/_templates/_styles/_overlays.py +5 -5
  120. figrecipe/_editor/_templates/_styles/_preview.py +213 -8
  121. figrecipe/_editor/_templates/_styles/_spinner.py +117 -0
  122. figrecipe/_editor/static/audio/click.mp3 +0 -0
  123. figrecipe/_editor/static/click.mp3 +0 -0
  124. figrecipe/_editor/static/icons/favicon.ico +0 -0
  125. figrecipe/_integrations/__init__.py +17 -0
  126. figrecipe/_integrations/_scitex_stats.py +298 -0
  127. figrecipe/_params/_DECORATION_METHODS.py +2 -0
  128. figrecipe/_recorder.py +28 -3
  129. figrecipe/_reproducer/_core.py +60 -49
  130. figrecipe/_utils/__init__.py +3 -0
  131. figrecipe/_utils/_bundle.py +205 -0
  132. figrecipe/_wrappers/_axes.py +150 -2
  133. figrecipe/_wrappers/_caption_generator.py +218 -0
  134. figrecipe/_wrappers/_figure.py +26 -1
  135. figrecipe/_wrappers/_stat_annotation.py +274 -0
  136. figrecipe/styles/_style_applier.py +10 -2
  137. figrecipe/styles/presets/SCITEX.yaml +11 -4
  138. {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/METADATA +144 -146
  139. figrecipe-0.9.0.dist-info/RECORD +277 -0
  140. figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
  141. figrecipe-0.7.4.dist-info/RECORD +0 -188
  142. {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
  143. {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
- hide_top = behavior.get("hide_top_spine", True)
109
- hide_right = behavior.get("hide_right_spine", True)
110
- ax.spines["top"].set_visible(not hide_top)
111
- ax.spines["right"].set_visible(not hide_right)
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
- mpl_fig.set_dpi(150)
192
- mpl_fig.canvas.draw()
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
- mpl_fig.set_dpi(original_dpi)
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.parent.parent.parent / "docs" / "scitex-icon.png"
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>figrecipe Editor</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
- <a href="https://scitex.ai" target="_blank" class="scitex-branding" title="FigRecipe - Part of SciTeX">
25
- <img src="data:image/png;base64,SCITEX_ICON_PLACEHOLDER" alt="SciTeX" class="scitex-icon">
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">&#x276F;</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="Refresh preview">Refresh</button>
43
+ <button id="btn-refresh" title="Re-render figure (R)">Render</button>
48
44
  <div class="zoom-controls">
49
- <button id="btn-zoom-out" title="Zoom out (-)">−</button>
50
- <span id="zoom-level">100%</span>
51
- <button id="btn-zoom-in" title="Zoom in (+)">+</button>
52
- <button id="btn-zoom-reset" title="Reset zoom (0)">⟲</button>
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 overlay (G)">Ruler & Grid</button>
56
- <button id="btn-shortcuts" class="btn-shortcuts" title="Show keyboard shortcuts (?)">⌨</button>
57
- <label class="theme-toggle">
58
- <input type="checkbox" id="dark-mode-toggle" DARK_MODE_CHECKED_PLACEHOLDER>
59
- <span>Dark Mode</span>
60
- </label>
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
- <h2>Properties</h2>
80
- <div class="controls-actions">
81
- <button id="btn-restore" class="btn-warning" title="Restore to original programmatic style">Restore</button>
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">&#x276F;</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
- <div class="theme-actions">
93
- <button id="btn-view-theme" class="btn-small" title="View theme contents">View</button>
94
- <button id="btn-download-theme" class="btn-small" title="Download theme as YAML">Download</button>
95
- <button id="btn-copy-theme" class="btn-small" title="Copy theme to clipboard">Copy</button>
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">&times;</button>
118
124
  </div>
119
125
  <div class="shortcuts-content">
120
- <div class="shortcut-section">
121
- <h4>General</h4>
122
- <div class="shortcut-row"><span class="shortcut-keys"><kbd>Ctrl</kbd>+<kbd>S</kbd></span><span class="shortcut-desc">Save overrides</span></div>
123
- <div class="shortcut-row"><span class="shortcut-keys"><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>S</kbd></span><span class="shortcut-desc">Download PNG</span></div>
124
- <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>
125
- <div class="shortcut-row"><span class="shortcut-keys"><kbd>F5</kbd> / <kbd>Ctrl</kbd>+<kbd>R</kbd></span><span class="shortcut-desc">Refresh preview</span></div>
126
- <div class="shortcut-row"><span class="shortcut-keys"><kbd>Esc</kbd></span><span class="shortcut-desc">Clear selection</span></div>
127
- <div class="shortcut-row"><span class="shortcut-keys"><kbd>R</kbd></span><span class="shortcut-desc">Reset to theme defaults</span></div>
128
- </div>
129
- <div class="shortcut-section">
130
- <h4>Navigation</h4>
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 &amp; 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">Manual overrides active</span>
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" open>
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="12">
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="3">
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="10">
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="6">
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
- <label>DPI</label>
211
- <input type="number" id="output_dpi" step="50" min="72" max="600">
212
- </div>
213
- <div class="form-row">
214
- <label>Transparent</label>
215
- <input type="checkbox" id="output_transparent">
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" open>
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
- <select id="panel_selector">
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" open>
268
+ <details class="section">
276
269
  <summary>Labels</summary>
277
270
  <div class="section-content">
278
- <div class="form-row">
279
- <label>Title</label>
280
- <input type="text" id="label_title" class="label-input" placeholder="(no title)">
281
- </div>
282
- <div class="form-row">
283
- <label>X Label</label>
284
- <input type="text" id="label_xlabel" class="label-input" placeholder="(no xlabel)">
285
- </div>
286
- <div class="form-row">
287
- <label>Y Label</label>
288
- <input type="text" id="label_ylabel" class="label-input" placeholder="(no ylabel)">
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>Suptitle</label>
292
- <input type="text" id="label_suptitle" class="label-input" placeholder="(no suptitle)">
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="form-row">
400
- <label>Hide Top</label>
401
- <input type="checkbox" id="behavior_hide_top_spine">
402
- </div>
403
- <div class="form-row">
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