figrecipe 0.5.0__py3-none-any.whl → 0.6.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 (90) hide show
  1. figrecipe/__init__.py +361 -93
  2. figrecipe/_dev/__init__.py +120 -0
  3. figrecipe/_dev/demo_plotters/__init__.py +195 -0
  4. figrecipe/_dev/demo_plotters/plot_acorr.py +24 -0
  5. figrecipe/_dev/demo_plotters/plot_angle_spectrum.py +28 -0
  6. figrecipe/_dev/demo_plotters/plot_bar.py +25 -0
  7. figrecipe/_dev/demo_plotters/plot_barbs.py +30 -0
  8. figrecipe/_dev/demo_plotters/plot_barh.py +25 -0
  9. figrecipe/_dev/demo_plotters/plot_boxplot.py +24 -0
  10. figrecipe/_dev/demo_plotters/plot_cohere.py +29 -0
  11. figrecipe/_dev/demo_plotters/plot_contour.py +30 -0
  12. figrecipe/_dev/demo_plotters/plot_contourf.py +29 -0
  13. figrecipe/_dev/demo_plotters/plot_csd.py +29 -0
  14. figrecipe/_dev/demo_plotters/plot_ecdf.py +24 -0
  15. figrecipe/_dev/demo_plotters/plot_errorbar.py +28 -0
  16. figrecipe/_dev/demo_plotters/plot_eventplot.py +25 -0
  17. figrecipe/_dev/demo_plotters/plot_fill.py +29 -0
  18. figrecipe/_dev/demo_plotters/plot_fill_between.py +30 -0
  19. figrecipe/_dev/demo_plotters/plot_fill_betweenx.py +28 -0
  20. figrecipe/_dev/demo_plotters/plot_hexbin.py +25 -0
  21. figrecipe/_dev/demo_plotters/plot_hist.py +24 -0
  22. figrecipe/_dev/demo_plotters/plot_hist2d.py +25 -0
  23. figrecipe/_dev/demo_plotters/plot_imshow.py +23 -0
  24. figrecipe/_dev/demo_plotters/plot_loglog.py +27 -0
  25. figrecipe/_dev/demo_plotters/plot_magnitude_spectrum.py +28 -0
  26. figrecipe/_dev/demo_plotters/plot_matshow.py +23 -0
  27. figrecipe/_dev/demo_plotters/plot_pcolor.py +29 -0
  28. figrecipe/_dev/demo_plotters/plot_pcolormesh.py +29 -0
  29. figrecipe/_dev/demo_plotters/plot_phase_spectrum.py +28 -0
  30. figrecipe/_dev/demo_plotters/plot_pie.py +23 -0
  31. figrecipe/_dev/demo_plotters/plot_plot.py +27 -0
  32. figrecipe/_dev/demo_plotters/plot_psd.py +29 -0
  33. figrecipe/_dev/demo_plotters/plot_quiver.py +30 -0
  34. figrecipe/_dev/demo_plotters/plot_scatter.py +24 -0
  35. figrecipe/_dev/demo_plotters/plot_semilogx.py +27 -0
  36. figrecipe/_dev/demo_plotters/plot_semilogy.py +27 -0
  37. figrecipe/_dev/demo_plotters/plot_specgram.py +30 -0
  38. figrecipe/_dev/demo_plotters/plot_spy.py +29 -0
  39. figrecipe/_dev/demo_plotters/plot_stackplot.py +29 -0
  40. figrecipe/_dev/demo_plotters/plot_stairs.py +27 -0
  41. figrecipe/_dev/demo_plotters/plot_stem.py +27 -0
  42. figrecipe/_dev/demo_plotters/plot_step.py +27 -0
  43. figrecipe/_dev/demo_plotters/plot_streamplot.py +30 -0
  44. figrecipe/_dev/demo_plotters/plot_tricontour.py +28 -0
  45. figrecipe/_dev/demo_plotters/plot_tricontourf.py +28 -0
  46. figrecipe/_dev/demo_plotters/plot_tripcolor.py +29 -0
  47. figrecipe/_dev/demo_plotters/plot_triplot.py +25 -0
  48. figrecipe/_dev/demo_plotters/plot_violinplot.py +25 -0
  49. figrecipe/_dev/demo_plotters/plot_xcorr.py +25 -0
  50. figrecipe/_editor/__init__.py +230 -0
  51. figrecipe/_editor/_bbox.py +978 -0
  52. figrecipe/_editor/_flask_app.py +1229 -0
  53. figrecipe/_editor/_hitmap.py +937 -0
  54. figrecipe/_editor/_overrides.py +318 -0
  55. figrecipe/_editor/_renderer.py +349 -0
  56. figrecipe/_editor/_templates/__init__.py +75 -0
  57. figrecipe/_editor/_templates/_html.py +406 -0
  58. figrecipe/_editor/_templates/_scripts.py +2778 -0
  59. figrecipe/_editor/_templates/_styles.py +1326 -0
  60. figrecipe/_params/_DECORATION_METHODS.py +27 -0
  61. figrecipe/_params/_PLOTTING_METHODS.py +58 -0
  62. figrecipe/_params/__init__.py +9 -0
  63. figrecipe/_recorder.py +126 -73
  64. figrecipe/_reproducer.py +658 -41
  65. figrecipe/_seaborn.py +14 -9
  66. figrecipe/_serializer.py +2 -2
  67. figrecipe/_signatures/README.md +68 -0
  68. figrecipe/_signatures/__init__.py +12 -2
  69. figrecipe/_signatures/_loader.py +515 -56
  70. figrecipe/_utils/__init__.py +6 -4
  71. figrecipe/_utils/_crop.py +10 -4
  72. figrecipe/_utils/_image_diff.py +37 -33
  73. figrecipe/_utils/_numpy_io.py +0 -1
  74. figrecipe/_utils/_units.py +11 -3
  75. figrecipe/_validator.py +12 -3
  76. figrecipe/_wrappers/_axes.py +860 -46
  77. figrecipe/_wrappers/_figure.py +115 -18
  78. figrecipe/plt.py +0 -1
  79. figrecipe/pyplot.py +2 -1
  80. figrecipe/styles/__init__.py +9 -10
  81. figrecipe/styles/_style_applier.py +332 -28
  82. figrecipe/styles/_style_loader.py +172 -44
  83. figrecipe/styles/presets/MATPLOTLIB.yaml +94 -0
  84. figrecipe/styles/presets/SCITEX.yaml +176 -0
  85. figrecipe-0.6.0.dist-info/METADATA +394 -0
  86. figrecipe-0.6.0.dist-info/RECORD +90 -0
  87. figrecipe-0.5.0.dist-info/METADATA +0 -336
  88. figrecipe-0.5.0.dist-info/RECORD +0 -26
  89. {figrecipe-0.5.0.dist-info → figrecipe-0.6.0.dist-info}/WHEEL +0 -0
  90. {figrecipe-0.5.0.dist-info → figrecipe-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ HTML/CSS/JS template builder for figure editor.
5
+
6
+ Uses YAML-compatible flattened key names from to_subplots_kwargs() as the
7
+ single source of truth. No custom key mapping is needed since all keys
8
+ now match the HTML input IDs directly.
9
+ """
10
+
11
+ import json
12
+ from typing import Any, Dict, Tuple
13
+
14
+ from ._html import HTML_TEMPLATE
15
+ from ._scripts import SCRIPTS
16
+ from ._styles import STYLES
17
+
18
+
19
+ def build_html_template(
20
+ image_base64: str,
21
+ bboxes: Dict[str, Any],
22
+ color_map: Dict[str, Any],
23
+ style: Dict[str, Any],
24
+ overrides: Dict[str, Any],
25
+ img_size: Tuple[int, int],
26
+ style_name: str = "SCITEX",
27
+ ) -> str:
28
+ """
29
+ Build complete HTML template for figure editor.
30
+
31
+ Style keys are expected to be YAML-compatible flattened names that
32
+ match the HTML input IDs directly (e.g., 'fonts_axis_label_pt').
33
+
34
+ Parameters
35
+ ----------
36
+ image_base64 : str
37
+ Base64-encoded preview image.
38
+ bboxes : dict
39
+ Element bounding boxes.
40
+ color_map : dict
41
+ Hitmap color-to-element mapping.
42
+ style : dict
43
+ Base style configuration with YAML-compatible keys.
44
+ overrides : dict
45
+ Current style overrides with YAML-compatible keys.
46
+ img_size : tuple
47
+ (width, height) of preview image.
48
+ style_name : str
49
+ Name of the applied style preset (e.g., "SCITEX", "MATPLOTLIB").
50
+
51
+ Returns
52
+ -------
53
+ str
54
+ Complete HTML document.
55
+ """
56
+ # Merge style and overrides for initial values
57
+ # Keys should already match HTML input IDs (YAML-compatible flattened)
58
+ initial_values = {**style, **overrides}
59
+
60
+ # Inject data into template
61
+ html = HTML_TEMPLATE
62
+ html = html.replace("/* STYLES_PLACEHOLDER */", STYLES)
63
+ html = html.replace("/* SCRIPTS_PLACEHOLDER */", SCRIPTS)
64
+ html = html.replace("IMAGE_BASE64_PLACEHOLDER", image_base64)
65
+ html = html.replace("BBOXES_PLACEHOLDER", json.dumps(bboxes))
66
+ html = html.replace("COLOR_MAP_PLACEHOLDER", json.dumps(color_map))
67
+ html = html.replace("INITIAL_VALUES_PLACEHOLDER", json.dumps(initial_values))
68
+ html = html.replace("IMG_WIDTH_PLACEHOLDER", str(img_size[0]))
69
+ html = html.replace("IMG_HEIGHT_PLACEHOLDER", str(img_size[1]))
70
+ html = html.replace("STYLE_NAME_PLACEHOLDER", style_name)
71
+
72
+ return html
73
+
74
+
75
+ __all__ = ["build_html_template"]
@@ -0,0 +1,406 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ HTML template for figure editor.
5
+ """
6
+
7
+ HTML_TEMPLATE = """
8
+ <!DOCTYPE html>
9
+ <html lang="en" data-theme="light">
10
+ <head>
11
+ <meta charset="UTF-8">
12
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
13
+ <title>figrecipe Editor</title>
14
+ <style>
15
+ /* STYLES_PLACEHOLDER */
16
+ </style>
17
+ </head>
18
+ <body>
19
+ <div class="editor-container">
20
+ <!-- Preview Panel -->
21
+ <div class="preview-panel">
22
+ <div class="preview-header">
23
+ <h2>Preview</h2>
24
+ <div class="preview-controls">
25
+ <div class="download-dropdown">
26
+ <button id="btn-download-main" class="btn-primary download-main" title="Download as PNG">Download PNG</button>
27
+ <button id="btn-download-toggle" class="btn-primary download-toggle" title="More formats">▼</button>
28
+ <div id="download-menu" class="download-menu">
29
+ <button id="btn-download-png-menu" class="download-option active" data-format="png">PNG</button>
30
+ <button id="btn-download-svg-menu" class="download-option" data-format="svg">SVG</button>
31
+ <button id="btn-download-pdf-menu" class="download-option" data-format="pdf">PDF</button>
32
+ </div>
33
+ </div>
34
+ <button id="btn-refresh" title="Refresh preview">Refresh</button>
35
+ <button id="btn-show-hitmap" title="Toggle hitmap overlay for debugging" style="display: none;">Show Hit Regions</button>
36
+ <label class="theme-toggle">
37
+ <input type="checkbox" id="dark-mode-toggle">
38
+ <span>Dark Mode</span>
39
+ </label>
40
+ </div>
41
+ </div>
42
+ <div class="preview-wrapper">
43
+ <img id="preview-image" src="data:image/png;base64,IMAGE_BASE64_PLACEHOLDER" alt="Figure preview">
44
+ <svg id="hitregion-overlay" class="hitregion-overlay"></svg>
45
+ <svg id="selection-overlay" class="selection-overlay"></svg>
46
+ <canvas id="hitmap-canvas" style="display: none;"></canvas>
47
+ </div>
48
+ <div class="selected-element-info" id="selected-info">
49
+ Click on an element to select it
50
+ </div>
51
+ </div>
52
+
53
+ <!-- Controls Panel -->
54
+ <div class="controls-panel">
55
+ <div class="controls-header">
56
+ <h2>Properties</h2>
57
+ <div class="controls-actions">
58
+ <button id="btn-restore" class="btn-warning" title="Restore to original programmatic style">Restore</button>
59
+ <button id="btn-reset" class="btn-secondary" title="Reset to last saved">Reset</button>
60
+ <button id="btn-save" class="btn-primary">Save</button>
61
+ </div>
62
+ </div>
63
+ <div class="style-info">
64
+ <span class="style-label">Theme:</span>
65
+ <select id="theme-selector" class="theme-selector" title="Switch theme preset">
66
+ <option value="SCITEX">SCITEX</option>
67
+ <option value="MATPLOTLIB">MATPLOTLIB</option>
68
+ </select>
69
+ <div class="theme-actions">
70
+ <button id="btn-view-theme" class="btn-small" title="View theme contents">View</button>
71
+ <button id="btn-download-theme" class="btn-small" title="Download theme as YAML">Download</button>
72
+ <button id="btn-copy-theme" class="btn-small" title="Copy theme to clipboard">Copy</button>
73
+ </div>
74
+ </div>
75
+ <!-- Theme Modal -->
76
+ <div id="theme-modal" class="modal" style="display: none;">
77
+ <div class="modal-content">
78
+ <div class="modal-header">
79
+ <h3>Theme: <span id="theme-modal-name">SCITEX</span></h3>
80
+ <button id="theme-modal-close" class="modal-close">&times;</button>
81
+ </div>
82
+ <pre id="theme-content" class="theme-content-pre"></pre>
83
+ <div class="modal-footer">
84
+ <button id="theme-modal-download" class="btn-primary">Download YAML</button>
85
+ <button id="theme-modal-copy" class="btn-secondary">Copy to Clipboard</button>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ <div id="override-status" class="override-status" style="display: none;">
90
+ <span class="override-indicator">Manual overrides active</span>
91
+ <span id="override-timestamp" class="override-timestamp"></span>
92
+ </div>
93
+
94
+ <div class="controls-sections">
95
+ <!-- Tab Navigation -->
96
+ <div class="tab-navigation">
97
+ <button id="tab-figure" class="tab-btn active" title="Figure-level properties">Figure</button>
98
+ <button id="tab-axis" class="tab-btn" title="Axis-level properties">Axis</button>
99
+ <button id="tab-element" class="tab-btn" title="Element-specific properties">Element</button>
100
+ </div>
101
+
102
+ <!-- FIGURE TAB -->
103
+ <div id="tab-content-figure" class="tab-content active">
104
+ <!-- Dimensions Section -->
105
+ <details class="section" open>
106
+ <summary>Dimensions</summary>
107
+ <div class="section-content">
108
+ <div class="subsection">
109
+ <h4>Margins</h4>
110
+ <div class="form-grid">
111
+ <div class="form-row">
112
+ <label>Left</label>
113
+ <input type="number" id="margins_left_mm" step="1" min="0" max="50" placeholder="12">
114
+ </div>
115
+ <div class="form-row">
116
+ <label>Right</label>
117
+ <input type="number" id="margins_right_mm" step="1" min="0" max="50" placeholder="3">
118
+ </div>
119
+ <div class="form-row">
120
+ <label>Bottom</label>
121
+ <input type="number" id="margins_bottom_mm" step="1" min="0" max="50" placeholder="10">
122
+ </div>
123
+ <div class="form-row">
124
+ <label>Top</label>
125
+ <input type="number" id="margins_top_mm" step="1" min="0" max="50" placeholder="6">
126
+ </div>
127
+ </div>
128
+ </div>
129
+ <div class="subsection">
130
+ <h4>Spacing</h4>
131
+ <div class="form-row">
132
+ <label>Horizontal (mm)</label>
133
+ <input type="number" id="spacing_horizontal_mm" step="1" min="0" max="30" placeholder="8">
134
+ </div>
135
+ <div class="form-row">
136
+ <label>Vertical (mm)</label>
137
+ <input type="number" id="spacing_vertical_mm" step="1" min="0" max="30" placeholder="8">
138
+ </div>
139
+ </div>
140
+ </div>
141
+ </details>
142
+
143
+ <!-- Output Section -->
144
+ <details class="section">
145
+ <summary>Output</summary>
146
+ <div class="section-content">
147
+ <div class="form-row">
148
+ <label>DPI</label>
149
+ <input type="number" id="output_dpi" step="50" min="72" max="600">
150
+ </div>
151
+ <div class="form-row">
152
+ <label>Transparent</label>
153
+ <input type="checkbox" id="output_transparent">
154
+ </div>
155
+ </div>
156
+ </details>
157
+ </div>
158
+
159
+ <!-- AXIS TAB -->
160
+ <div id="tab-content-axis" class="tab-content">
161
+ <div class="tab-hint" id="axis-tab-hint">Select an axis element (title, label, ticks, legend) to edit</div>
162
+
163
+ <!-- Axes Size Section -->
164
+ <details class="section" open>
165
+ <summary>Axes Size</summary>
166
+ <div class="section-content">
167
+ <div class="form-row">
168
+ <label>Width (mm)</label>
169
+ <input type="number" id="axes_width_mm" step="1" min="10" max="200" placeholder="80">
170
+ </div>
171
+ <div class="form-row">
172
+ <label>Height (mm)</label>
173
+ <input type="number" id="axes_height_mm" step="1" min="10" max="200" placeholder="55">
174
+ </div>
175
+ </div>
176
+ </details>
177
+
178
+ <!-- Labels Section -->
179
+ <details class="section" open>
180
+ <summary>Labels</summary>
181
+ <div class="section-content">
182
+ <div class="form-row">
183
+ <label>Title</label>
184
+ <input type="text" id="label_title" class="label-input" placeholder="(no title)">
185
+ </div>
186
+ <div class="form-row">
187
+ <label>X Label</label>
188
+ <input type="text" id="label_xlabel" class="label-input" placeholder="(no xlabel)">
189
+ </div>
190
+ <div class="form-row">
191
+ <label>Y Label</label>
192
+ <input type="text" id="label_ylabel" class="label-input" placeholder="(no ylabel)">
193
+ </div>
194
+ <div class="form-row">
195
+ <label>Suptitle</label>
196
+ <input type="text" id="label_suptitle" class="label-input" placeholder="(no suptitle)">
197
+ </div>
198
+ </div>
199
+ </details>
200
+
201
+ <!-- Axis Type Section -->
202
+ <details class="section">
203
+ <summary>Axis Type</summary>
204
+ <div class="section-content">
205
+ <div class="form-row">
206
+ <label>X Axis</label>
207
+ <div class="axis-type-toggle">
208
+ <button id="xaxis-numerical" class="axis-type-btn active" data-axis="x" data-type="numerical">Numerical</button>
209
+ <button id="xaxis-categorical" class="axis-type-btn" data-axis="x" data-type="categorical">Categorical</button>
210
+ </div>
211
+ </div>
212
+ <div class="form-row">
213
+ <label>Y Axis</label>
214
+ <div class="axis-type-toggle">
215
+ <button id="yaxis-numerical" class="axis-type-btn active" data-axis="y" data-type="numerical">Numerical</button>
216
+ <button id="yaxis-categorical" class="axis-type-btn" data-axis="y" data-type="categorical">Categorical</button>
217
+ </div>
218
+ </div>
219
+ <div class="form-row" id="xaxis-labels-row" style="display: none;">
220
+ <label>X Labels</label>
221
+ <input type="text" id="xaxis_labels" class="label-input" placeholder="label1, label2, ...">
222
+ </div>
223
+ <div class="form-row" id="yaxis-labels-row" style="display: none;">
224
+ <label>Y Labels</label>
225
+ <input type="text" id="yaxis_labels" class="label-input" placeholder="label1, label2, ...">
226
+ </div>
227
+ </div>
228
+ </details>
229
+
230
+ <!-- Fonts Section -->
231
+ <details class="section">
232
+ <summary>Fonts</summary>
233
+ <div class="section-content">
234
+ <div class="form-row">
235
+ <label>Family</label>
236
+ <select id="fonts_family">
237
+ <option value="Arial">Arial</option>
238
+ <option value="DejaVu Sans">DejaVu Sans</option>
239
+ <option value="Helvetica">Helvetica</option>
240
+ <option value="Times New Roman">Times New Roman</option>
241
+ <option value="sans-serif">sans-serif</option>
242
+ <option value="serif">serif</option>
243
+ </select>
244
+ </div>
245
+ <div class="form-row">
246
+ <label>Axis Label (pt)</label>
247
+ <input type="number" id="fonts_axis_label_pt" step="0.5" min="4" max="24">
248
+ </div>
249
+ <div class="form-row">
250
+ <label>Tick Label (pt)</label>
251
+ <input type="number" id="fonts_tick_label_pt" step="0.5" min="4" max="24">
252
+ </div>
253
+ <div class="form-row">
254
+ <label>Title (pt)</label>
255
+ <input type="number" id="fonts_title_pt" step="0.5" min="4" max="24">
256
+ </div>
257
+ <div class="form-row">
258
+ <label>Suptitle (pt)</label>
259
+ <input type="number" id="fonts_suptitle_pt" step="0.5" min="4" max="30">
260
+ </div>
261
+ <div class="form-row">
262
+ <label>Legend (pt)</label>
263
+ <input type="number" id="fonts_legend_pt" step="0.5" min="4" max="24">
264
+ </div>
265
+ </div>
266
+ </details>
267
+
268
+ <!-- Ticks Section -->
269
+ <details class="section">
270
+ <summary>Ticks</summary>
271
+ <div class="section-content">
272
+ <div class="form-row">
273
+ <label>Length (mm)</label>
274
+ <input type="number" id="ticks_length_mm" step="0.1" min="0.5" max="5">
275
+ </div>
276
+ <div class="form-row">
277
+ <label>Thickness (mm)</label>
278
+ <input type="number" id="ticks_thickness_mm" step="0.05" min="0.1" max="2">
279
+ </div>
280
+ <div class="form-row">
281
+ <label>Direction</label>
282
+ <select id="ticks_direction">
283
+ <option value="out">Out</option>
284
+ <option value="in">In</option>
285
+ <option value="inout">Both</option>
286
+ </select>
287
+ </div>
288
+ <div class="form-row">
289
+ <label>N Ticks</label>
290
+ <input type="number" id="ticks_n_ticks" step="1" min="2" max="20">
291
+ </div>
292
+ </div>
293
+ </details>
294
+
295
+ <!-- Spines Section -->
296
+ <details class="section">
297
+ <summary>Spines</summary>
298
+ <div class="section-content">
299
+ <div class="form-row">
300
+ <label>Thickness (mm)</label>
301
+ <input type="number" id="axes_thickness_mm" step="0.05" min="0.1" max="2" placeholder="0.35">
302
+ </div>
303
+ <div class="form-row">
304
+ <label>Hide Top</label>
305
+ <input type="checkbox" id="behavior_hide_top_spine">
306
+ </div>
307
+ <div class="form-row">
308
+ <label>Hide Right</label>
309
+ <input type="checkbox" id="behavior_hide_right_spine">
310
+ </div>
311
+ <div class="form-row">
312
+ <label>Grid</label>
313
+ <input type="checkbox" id="behavior_grid">
314
+ </div>
315
+ </div>
316
+ </details>
317
+
318
+ <!-- Legend Section -->
319
+ <details class="section">
320
+ <summary>Legend</summary>
321
+ <div class="section-content">
322
+ <div class="form-row">
323
+ <label>Visible</label>
324
+ <input type="checkbox" id="legend_visible" checked>
325
+ </div>
326
+ <div class="form-row">
327
+ <label>Show Frame</label>
328
+ <input type="checkbox" id="legend_frameon">
329
+ </div>
330
+ <div class="form-row">
331
+ <label>Location</label>
332
+ <select id="legend_loc">
333
+ <option value="best">Best</option>
334
+ <option value="upper right">Upper Right</option>
335
+ <option value="upper left">Upper Left</option>
336
+ <option value="lower right">Lower Right</option>
337
+ <option value="lower left">Lower Left</option>
338
+ <option value="center">Center</option>
339
+ <option value="center left">Center Left</option>
340
+ <option value="center right">Center Right</option>
341
+ <option value="lower center">Lower Center</option>
342
+ <option value="upper center">Upper Center</option>
343
+ <option value="custom">Custom (xy)</option>
344
+ </select>
345
+ </div>
346
+ <div id="legend-custom-pos" class="legend-custom-pos" style="display: none;">
347
+ <div class="form-row">
348
+ <label>X (0-1)</label>
349
+ <input type="number" id="legend_x" step="0.05" min="0" max="1.5" value="1.02" placeholder="1.02">
350
+ </div>
351
+ <div class="form-row">
352
+ <label>Y (0-1)</label>
353
+ <input type="number" id="legend_y" step="0.05" min="0" max="1.5" value="1" placeholder="1">
354
+ </div>
355
+ <div class="form-hint">Coordinates are relative to axes (0-1). Values >1 place legend outside.</div>
356
+ </div>
357
+ <div class="form-row">
358
+ <label>Alpha</label>
359
+ <input type="range" id="legend_alpha" min="0" max="1" step="0.1">
360
+ <span id="legend_alpha_value">0.8</span>
361
+ </div>
362
+ <div class="form-row">
363
+ <label>Background</label>
364
+ <input type="color" id="legend_bg" value="#ffffff">
365
+ </div>
366
+ </div>
367
+ </details>
368
+ </div>
369
+
370
+ <!-- ELEMENT TAB -->
371
+ <div id="tab-content-element" class="tab-content">
372
+ <div class="tab-hint" id="element-tab-hint">
373
+ <p>Select a plot element to edit its properties</p>
374
+ <p class="hint-sub">Click on lines, scatter, bars, etc. in the preview</p>
375
+ </div>
376
+
377
+ <!-- Selected element info -->
378
+ <div id="selected-element-panel" style="display: none;">
379
+ <div class="selected-element-header">
380
+ <span class="element-type-badge" id="element-type-badge">line</span>
381
+ <span class="element-name" id="element-name">element</span>
382
+ </div>
383
+ </div>
384
+
385
+ <!-- Dynamic call properties (populated when element selected) -->
386
+ <div id="dynamic-call-properties" class="dynamic-call-properties"></div>
387
+ </div>
388
+ </div>
389
+ </div>
390
+ </div>
391
+
392
+ <script>
393
+ // Initial data from server
394
+ const initialBboxes = BBOXES_PLACEHOLDER;
395
+ const initialColorMap = COLOR_MAP_PLACEHOLDER;
396
+ const initialValues = INITIAL_VALUES_PLACEHOLDER;
397
+ const imgWidth = IMG_WIDTH_PLACEHOLDER;
398
+ const imgHeight = IMG_HEIGHT_PLACEHOLDER;
399
+
400
+ /* SCRIPTS_PLACEHOLDER */
401
+ </script>
402
+ </body>
403
+ </html>
404
+ """
405
+
406
+ __all__ = ["HTML_TEMPLATE"]