figrecipe 0.6.0__py3-none-any.whl → 0.7.4__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 (177) hide show
  1. figrecipe/__init__.py +106 -973
  2. figrecipe/_api/__init__.py +48 -0
  3. figrecipe/_api/_extract.py +108 -0
  4. figrecipe/_api/_notebook.py +61 -0
  5. figrecipe/_api/_panel.py +46 -0
  6. figrecipe/_api/_save.py +191 -0
  7. figrecipe/_api/_seaborn_proxy.py +34 -0
  8. figrecipe/_api/_style_manager.py +153 -0
  9. figrecipe/_api/_subplots.py +333 -0
  10. figrecipe/_api/_validate.py +82 -0
  11. figrecipe/_dev/__init__.py +2 -93
  12. figrecipe/_dev/_plotters.py +76 -0
  13. figrecipe/_dev/_run_demos.py +56 -0
  14. figrecipe/_dev/demo_plotters/__init__.py +35 -166
  15. figrecipe/_dev/demo_plotters/_categories.py +81 -0
  16. figrecipe/_dev/demo_plotters/_figure_creators.py +119 -0
  17. figrecipe/_dev/demo_plotters/_helpers.py +31 -0
  18. figrecipe/_dev/demo_plotters/_registry.py +50 -0
  19. figrecipe/_dev/demo_plotters/bar_categorical/__init__.py +4 -0
  20. figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
  21. figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
  22. figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
  23. figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
  24. figrecipe/_dev/demo_plotters/{plot_plot.py → line_curve/plot_plot.py} +3 -2
  25. figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
  26. figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
  27. figrecipe/_dev/demo_plotters/{plot_pie.py → special/plot_pie.py} +5 -1
  28. figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
  29. figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
  30. figrecipe/_editor/__init__.py +57 -9
  31. figrecipe/_editor/_bbox/__init__.py +43 -0
  32. figrecipe/_editor/_bbox/_collections.py +177 -0
  33. figrecipe/_editor/_bbox/_elements.py +159 -0
  34. figrecipe/_editor/_bbox/_extract.py +256 -0
  35. figrecipe/_editor/_bbox/_extract_axes.py +370 -0
  36. figrecipe/_editor/_bbox/_extract_text.py +342 -0
  37. figrecipe/_editor/_bbox/_lines.py +173 -0
  38. figrecipe/_editor/_bbox/_transforms.py +146 -0
  39. figrecipe/_editor/_flask_app.py +68 -1039
  40. figrecipe/_editor/_helpers.py +242 -0
  41. figrecipe/_editor/_hitmap/__init__.py +76 -0
  42. figrecipe/_editor/_hitmap/_artists/__init__.py +21 -0
  43. figrecipe/_editor/_hitmap/_artists/_collections.py +345 -0
  44. figrecipe/_editor/_hitmap/_artists/_images.py +68 -0
  45. figrecipe/_editor/_hitmap/_artists/_lines.py +107 -0
  46. figrecipe/_editor/_hitmap/_artists/_patches.py +163 -0
  47. figrecipe/_editor/_hitmap/_artists/_text.py +190 -0
  48. figrecipe/_editor/_hitmap/_colors.py +181 -0
  49. figrecipe/_editor/_hitmap/_detect.py +137 -0
  50. figrecipe/_editor/_hitmap/_restore.py +154 -0
  51. figrecipe/_editor/_hitmap_main.py +182 -0
  52. figrecipe/_editor/_preferences.py +135 -0
  53. figrecipe/_editor/_render_overrides.py +480 -0
  54. figrecipe/_editor/_renderer.py +35 -185
  55. figrecipe/_editor/_routes_axis.py +453 -0
  56. figrecipe/_editor/_routes_core.py +284 -0
  57. figrecipe/_editor/_routes_element.py +317 -0
  58. figrecipe/_editor/_routes_style.py +223 -0
  59. figrecipe/_editor/_templates/__init__.py +78 -1
  60. figrecipe/_editor/_templates/_html.py +109 -13
  61. figrecipe/_editor/_templates/_scripts/__init__.py +120 -0
  62. figrecipe/_editor/_templates/_scripts/_api.py +228 -0
  63. figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
  64. figrecipe/_editor/_templates/_scripts/_core.py +436 -0
  65. figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
  66. figrecipe/_editor/_templates/_scripts/_element_editor.py +310 -0
  67. figrecipe/_editor/_templates/_scripts/_files.py +195 -0
  68. figrecipe/_editor/_templates/_scripts/_hitmap.py +509 -0
  69. figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
  70. figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
  71. figrecipe/_editor/_templates/_scripts/_legend_drag.py +265 -0
  72. figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
  73. figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
  74. figrecipe/_editor/_templates/_scripts/_panel_drag.py +334 -0
  75. figrecipe/_editor/_templates/_scripts/_panel_position.py +279 -0
  76. figrecipe/_editor/_templates/_scripts/_selection.py +237 -0
  77. figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
  78. figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
  79. figrecipe/_editor/_templates/_scripts/_zoom.py +179 -0
  80. figrecipe/_editor/_templates/_styles/__init__.py +69 -0
  81. figrecipe/_editor/_templates/_styles/_base.py +64 -0
  82. figrecipe/_editor/_templates/_styles/_buttons.py +206 -0
  83. figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
  84. figrecipe/_editor/_templates/_styles/_controls.py +265 -0
  85. figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
  86. figrecipe/_editor/_templates/_styles/_forms.py +126 -0
  87. figrecipe/_editor/_templates/_styles/_hitmap.py +184 -0
  88. figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
  89. figrecipe/_editor/_templates/_styles/_labels.py +118 -0
  90. figrecipe/_editor/_templates/_styles/_modals.py +98 -0
  91. figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
  92. figrecipe/_editor/_templates/_styles/_preview.py +225 -0
  93. figrecipe/_editor/_templates/_styles/_selection.py +73 -0
  94. figrecipe/_params/_DECORATION_METHODS.py +6 -0
  95. figrecipe/_recorder.py +35 -106
  96. figrecipe/_recorder_utils.py +124 -0
  97. figrecipe/_reproducer/__init__.py +18 -0
  98. figrecipe/_reproducer/_core.py +498 -0
  99. figrecipe/_reproducer/_custom_plots.py +279 -0
  100. figrecipe/_reproducer/_seaborn.py +100 -0
  101. figrecipe/_reproducer/_violin.py +186 -0
  102. figrecipe/_signatures/_kwargs.py +273 -0
  103. figrecipe/_signatures/_loader.py +21 -423
  104. figrecipe/_signatures/_parsing.py +147 -0
  105. figrecipe/_wrappers/_axes.py +119 -910
  106. figrecipe/_wrappers/_axes_helpers.py +136 -0
  107. figrecipe/_wrappers/_axes_plots.py +418 -0
  108. figrecipe/_wrappers/_axes_seaborn.py +157 -0
  109. figrecipe/_wrappers/_figure.py +162 -0
  110. figrecipe/_wrappers/_panel_labels.py +127 -0
  111. figrecipe/_wrappers/_plot_helpers.py +143 -0
  112. figrecipe/_wrappers/_violin_helpers.py +180 -0
  113. figrecipe/styles/__init__.py +8 -6
  114. figrecipe/styles/_dotdict.py +72 -0
  115. figrecipe/styles/_finalize.py +134 -0
  116. figrecipe/styles/_fonts.py +77 -0
  117. figrecipe/styles/_kwargs_converter.py +178 -0
  118. figrecipe/styles/_plot_styles.py +209 -0
  119. figrecipe/styles/_style_applier.py +32 -478
  120. figrecipe/styles/_style_loader.py +16 -192
  121. figrecipe/styles/_themes.py +151 -0
  122. figrecipe/styles/presets/MATPLOTLIB.yaml +2 -1
  123. figrecipe/styles/presets/SCITEX.yaml +29 -24
  124. {figrecipe-0.6.0.dist-info → figrecipe-0.7.4.dist-info}/METADATA +37 -2
  125. figrecipe-0.7.4.dist-info/RECORD +188 -0
  126. figrecipe/_editor/_bbox.py +0 -978
  127. figrecipe/_editor/_hitmap.py +0 -937
  128. figrecipe/_editor/_templates/_scripts.py +0 -2778
  129. figrecipe/_editor/_templates/_styles.py +0 -1326
  130. figrecipe/_reproducer.py +0 -975
  131. figrecipe-0.6.0.dist-info/RECORD +0 -90
  132. /figrecipe/_dev/demo_plotters/{plot_bar.py → bar_categorical/plot_bar.py} +0 -0
  133. /figrecipe/_dev/demo_plotters/{plot_barh.py → bar_categorical/plot_barh.py} +0 -0
  134. /figrecipe/_dev/demo_plotters/{plot_contour.py → contour_surface/plot_contour.py} +0 -0
  135. /figrecipe/_dev/demo_plotters/{plot_contourf.py → contour_surface/plot_contourf.py} +0 -0
  136. /figrecipe/_dev/demo_plotters/{plot_tricontour.py → contour_surface/plot_tricontour.py} +0 -0
  137. /figrecipe/_dev/demo_plotters/{plot_tricontourf.py → contour_surface/plot_tricontourf.py} +0 -0
  138. /figrecipe/_dev/demo_plotters/{plot_tripcolor.py → contour_surface/plot_tripcolor.py} +0 -0
  139. /figrecipe/_dev/demo_plotters/{plot_triplot.py → contour_surface/plot_triplot.py} +0 -0
  140. /figrecipe/_dev/demo_plotters/{plot_boxplot.py → distribution/plot_boxplot.py} +0 -0
  141. /figrecipe/_dev/demo_plotters/{plot_ecdf.py → distribution/plot_ecdf.py} +0 -0
  142. /figrecipe/_dev/demo_plotters/{plot_hist.py → distribution/plot_hist.py} +0 -0
  143. /figrecipe/_dev/demo_plotters/{plot_hist2d.py → distribution/plot_hist2d.py} +0 -0
  144. /figrecipe/_dev/demo_plotters/{plot_violinplot.py → distribution/plot_violinplot.py} +0 -0
  145. /figrecipe/_dev/demo_plotters/{plot_hexbin.py → image_matrix/plot_hexbin.py} +0 -0
  146. /figrecipe/_dev/demo_plotters/{plot_imshow.py → image_matrix/plot_imshow.py} +0 -0
  147. /figrecipe/_dev/demo_plotters/{plot_matshow.py → image_matrix/plot_matshow.py} +0 -0
  148. /figrecipe/_dev/demo_plotters/{plot_pcolor.py → image_matrix/plot_pcolor.py} +0 -0
  149. /figrecipe/_dev/demo_plotters/{plot_pcolormesh.py → image_matrix/plot_pcolormesh.py} +0 -0
  150. /figrecipe/_dev/demo_plotters/{plot_spy.py → image_matrix/plot_spy.py} +0 -0
  151. /figrecipe/_dev/demo_plotters/{plot_errorbar.py → line_curve/plot_errorbar.py} +0 -0
  152. /figrecipe/_dev/demo_plotters/{plot_fill.py → line_curve/plot_fill.py} +0 -0
  153. /figrecipe/_dev/demo_plotters/{plot_fill_between.py → line_curve/plot_fill_between.py} +0 -0
  154. /figrecipe/_dev/demo_plotters/{plot_fill_betweenx.py → line_curve/plot_fill_betweenx.py} +0 -0
  155. /figrecipe/_dev/demo_plotters/{plot_stackplot.py → line_curve/plot_stackplot.py} +0 -0
  156. /figrecipe/_dev/demo_plotters/{plot_stairs.py → line_curve/plot_stairs.py} +0 -0
  157. /figrecipe/_dev/demo_plotters/{plot_step.py → line_curve/plot_step.py} +0 -0
  158. /figrecipe/_dev/demo_plotters/{plot_scatter.py → scatter_points/plot_scatter.py} +0 -0
  159. /figrecipe/_dev/demo_plotters/{plot_eventplot.py → special/plot_eventplot.py} +0 -0
  160. /figrecipe/_dev/demo_plotters/{plot_loglog.py → special/plot_loglog.py} +0 -0
  161. /figrecipe/_dev/demo_plotters/{plot_semilogx.py → special/plot_semilogx.py} +0 -0
  162. /figrecipe/_dev/demo_plotters/{plot_semilogy.py → special/plot_semilogy.py} +0 -0
  163. /figrecipe/_dev/demo_plotters/{plot_stem.py → special/plot_stem.py} +0 -0
  164. /figrecipe/_dev/demo_plotters/{plot_acorr.py → spectral_signal/plot_acorr.py} +0 -0
  165. /figrecipe/_dev/demo_plotters/{plot_angle_spectrum.py → spectral_signal/plot_angle_spectrum.py} +0 -0
  166. /figrecipe/_dev/demo_plotters/{plot_cohere.py → spectral_signal/plot_cohere.py} +0 -0
  167. /figrecipe/_dev/demo_plotters/{plot_csd.py → spectral_signal/plot_csd.py} +0 -0
  168. /figrecipe/_dev/demo_plotters/{plot_magnitude_spectrum.py → spectral_signal/plot_magnitude_spectrum.py} +0 -0
  169. /figrecipe/_dev/demo_plotters/{plot_phase_spectrum.py → spectral_signal/plot_phase_spectrum.py} +0 -0
  170. /figrecipe/_dev/demo_plotters/{plot_psd.py → spectral_signal/plot_psd.py} +0 -0
  171. /figrecipe/_dev/demo_plotters/{plot_specgram.py → spectral_signal/plot_specgram.py} +0 -0
  172. /figrecipe/_dev/demo_plotters/{plot_xcorr.py → spectral_signal/plot_xcorr.py} +0 -0
  173. /figrecipe/_dev/demo_plotters/{plot_barbs.py → vector_flow/plot_barbs.py} +0 -0
  174. /figrecipe/_dev/demo_plotters/{plot_quiver.py → vector_flow/plot_quiver.py} +0 -0
  175. /figrecipe/_dev/demo_plotters/{plot_streamplot.py → vector_flow/plot_streamplot.py} +0 -0
  176. {figrecipe-0.6.0.dist-info → figrecipe-0.7.4.dist-info}/WHEEL +0 -0
  177. {figrecipe-0.6.0.dist-info → figrecipe-0.7.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,279 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Panel position JavaScript for the figure editor.
4
+
5
+ This module contains the JavaScript code for:
6
+ - Loading and displaying panel positions
7
+ - Updating panel positions via numerical inputs
8
+ """
9
+
10
+ SCRIPTS_PANEL_POSITION = r"""
11
+ // ===== PANEL POSITION (mm, upper-left origin) =====
12
+
13
+ let panelPositions = {};
14
+ let figSize = { width_mm: 0, height_mm: 0 };
15
+
16
+ // Load panel positions from server
17
+ async function loadPanelPositions() {
18
+ try {
19
+ const response = await fetch('/get_axes_positions');
20
+ const data = await response.json();
21
+
22
+ // Extract figure size
23
+ if (data._figsize) {
24
+ figSize = data._figsize;
25
+ delete data._figsize;
26
+ }
27
+
28
+ panelPositions = data;
29
+ updatePanelSelector();
30
+ updatePanelPositionInputs();
31
+ } catch (error) {
32
+ console.error('Failed to load panel positions:', error);
33
+ }
34
+ }
35
+
36
+ // Update panel selector dropdown
37
+ function updatePanelSelector() {
38
+ const selector = document.getElementById('panel_selector');
39
+ if (!selector) return;
40
+
41
+ selector.innerHTML = '';
42
+ const axKeys = Object.keys(panelPositions).sort();
43
+
44
+ axKeys.forEach((key, index) => {
45
+ const opt = document.createElement('option');
46
+ opt.value = index;
47
+ // Show panel label (A, B, C...) if available
48
+ const label = String.fromCharCode(65 + index); // A, B, C...
49
+ opt.textContent = `Panel ${label} (${key})`;
50
+ selector.appendChild(opt);
51
+ });
52
+
53
+ // Add change event listener - mark as explicitly selected on dropdown change
54
+ selector.addEventListener('change', () => {
55
+ panelExplicitlySelected = true;
56
+ updatePanelPositionInputs();
57
+ });
58
+ }
59
+
60
+ let panelExplicitlySelected = false;
61
+
62
+ // Update position input fields based on selected panel
63
+ function updatePanelPositionInputs(showHighlight = true) {
64
+ const selector = document.getElementById('panel_selector');
65
+ if (!selector) return;
66
+
67
+ const axIndex = parseInt(selector.value, 10);
68
+ const axKey = Object.keys(panelPositions).sort()[axIndex];
69
+ const pos = panelPositions[axKey];
70
+
71
+ if (!pos) return;
72
+
73
+ const leftInput = document.getElementById('panel_left');
74
+ const topInput = document.getElementById('panel_top');
75
+ const widthInput = document.getElementById('panel_width');
76
+ const heightInput = document.getElementById('panel_height');
77
+
78
+ // Values are already in mm from server
79
+ if (leftInput) leftInput.value = pos.left;
80
+ if (topInput) topInput.value = pos.top;
81
+ if (widthInput) widthInput.value = pos.width;
82
+ if (heightInput) heightInput.value = pos.height;
83
+
84
+ // Only draw highlight if explicitly selected (not on initial load)
85
+ if (showHighlight && panelExplicitlySelected) {
86
+ drawPanelSelectionHighlight(pos);
87
+ }
88
+ }
89
+
90
+ // Draw visual highlight around selected panel
91
+ function drawPanelSelectionHighlight(pos) {
92
+ const overlay = document.getElementById('selection-overlay');
93
+ if (!overlay) return;
94
+
95
+ // Clear previous panel highlight (but keep element selections)
96
+ const existingHighlight = document.getElementById('panel-selection-highlight');
97
+ if (existingHighlight) existingHighlight.remove();
98
+
99
+ const img = document.getElementById('preview-image');
100
+ if (!img || !img.naturalWidth || !img.naturalHeight) return;
101
+
102
+ // Ensure viewBox is set
103
+ overlay.setAttribute('viewBox', `0 0 ${img.naturalWidth} ${img.naturalHeight}`);
104
+ overlay.style.width = `${img.naturalWidth}px`;
105
+ overlay.style.height = `${img.naturalHeight}px`;
106
+
107
+ // Convert mm coords (upper-left origin) to image pixel coords
108
+ // pos.left, pos.top, pos.width, pos.height are in mm
109
+ const scaleX = img.naturalWidth / figSize.width_mm;
110
+ const scaleY = img.naturalHeight / figSize.height_mm;
111
+
112
+ const x = pos.left * scaleX;
113
+ const y = pos.top * scaleY; // Already in upper-left origin
114
+ const width = pos.width * scaleX;
115
+ const height = pos.height * scaleY;
116
+
117
+ // Create highlight rectangle
118
+ const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
119
+ rect.id = 'panel-selection-highlight';
120
+ rect.setAttribute('x', x);
121
+ rect.setAttribute('y', y);
122
+ rect.setAttribute('width', width);
123
+ rect.setAttribute('height', height);
124
+ rect.setAttribute('fill', 'none');
125
+ rect.setAttribute('stroke', '#f59e0b');
126
+ rect.setAttribute('stroke-width', '3');
127
+ rect.setAttribute('stroke-dasharray', '8,4');
128
+ rect.style.pointerEvents = 'none';
129
+
130
+ overlay.appendChild(rect);
131
+ }
132
+
133
+ // Clear panel selection highlight
134
+ function clearPanelSelectionHighlight() {
135
+ const existingHighlight = document.getElementById('panel-selection-highlight');
136
+ if (existingHighlight) existingHighlight.remove();
137
+ }
138
+
139
+ // Select panel by index (called when clicking on axes in canvas)
140
+ function selectPanelByIndex(axIndex) {
141
+ const selector = document.getElementById('panel_selector');
142
+ if (!selector) return;
143
+
144
+ // Mark as explicitly selected
145
+ panelExplicitlySelected = true;
146
+
147
+ // Update dropdown selection
148
+ selector.value = axIndex;
149
+
150
+ // Update inputs and highlight
151
+ updatePanelPositionInputs();
152
+
153
+ // Switch to Axis tab
154
+ switchTab('axis');
155
+
156
+ console.log('Selected panel', axIndex);
157
+ }
158
+
159
+ // Find panel index from axes key (e.g., "ax_0" -> 0)
160
+ function getPanelIndexFromKey(key) {
161
+ if (!key) return null;
162
+
163
+ // Handle "ax_N" format
164
+ const match = key.match(/ax_(\d+)/);
165
+ if (match) {
166
+ return parseInt(match[1], 10);
167
+ }
168
+
169
+ // Handle axes type elements - find by checking bboxes
170
+ const axKeys = Object.keys(panelPositions).sort();
171
+ for (let i = 0; i < axKeys.length; i++) {
172
+ if (axKeys[i] === key) {
173
+ return i;
174
+ }
175
+ }
176
+
177
+ return null;
178
+ }
179
+
180
+ // Apply panel position changes
181
+ async function applyPanelPosition() {
182
+ const selector = document.getElementById('panel_selector');
183
+ const leftInput = document.getElementById('panel_left');
184
+ const topInput = document.getElementById('panel_top');
185
+ const widthInput = document.getElementById('panel_width');
186
+ const heightInput = document.getElementById('panel_height');
187
+
188
+ if (!selector || !leftInput || !topInput || !widthInput || !heightInput) {
189
+ console.error('Panel position inputs not found');
190
+ return;
191
+ }
192
+
193
+ const axIndex = parseInt(selector.value, 10);
194
+ const left = parseFloat(leftInput.value);
195
+ const top = parseFloat(topInput.value);
196
+ const width = parseFloat(widthInput.value);
197
+ const height = parseFloat(heightInput.value);
198
+
199
+ // Validate values (mm, must be positive and within figure bounds)
200
+ if ([left, top, width, height].some(v => isNaN(v) || v < 0)) {
201
+ alert('Position values must be positive numbers in mm');
202
+ return;
203
+ }
204
+
205
+ if (left + width > figSize.width_mm || top + height > figSize.height_mm) {
206
+ alert(`Panel extends beyond figure bounds (${figSize.width_mm.toFixed(1)} x ${figSize.height_mm.toFixed(1)} mm)`);
207
+ return;
208
+ }
209
+
210
+ document.body.classList.add('loading');
211
+
212
+ try {
213
+ const response = await fetch('/update_axes_position', {
214
+ method: 'POST',
215
+ headers: { 'Content-Type': 'application/json' },
216
+ body: JSON.stringify({
217
+ ax_index: axIndex,
218
+ left: left,
219
+ top: top,
220
+ width: width,
221
+ height: height
222
+ })
223
+ });
224
+
225
+ const data = await response.json();
226
+
227
+ if (data.success) {
228
+ // Update preview image and wait for it to load
229
+ const img = document.getElementById('preview-image');
230
+ if (img) {
231
+ await new Promise((resolve) => {
232
+ img.onload = resolve;
233
+ img.src = 'data:image/png;base64,' + data.image;
234
+ });
235
+ }
236
+
237
+ // Update image size
238
+ if (data.img_size) {
239
+ currentImgWidth = data.img_size.width;
240
+ currentImgHeight = data.img_size.height;
241
+ }
242
+
243
+ // Update bboxes
244
+ currentBboxes = data.bboxes;
245
+ loadHitmap();
246
+ updateHitRegions();
247
+
248
+ // Reload positions to reflect any adjustments (now with correct image dimensions)
249
+ await loadPanelPositions();
250
+
251
+ console.log('Panel position updated successfully');
252
+ } else {
253
+ alert('Failed to update position: ' + data.error);
254
+ }
255
+ } catch (error) {
256
+ alert('Failed to update position: ' + error.message);
257
+ }
258
+
259
+ document.body.classList.remove('loading');
260
+ }
261
+
262
+ // Initialize panel position controls
263
+ function initPanelPositionControls() {
264
+ const applyBtn = document.getElementById('apply_panel_position');
265
+ if (applyBtn) {
266
+ applyBtn.addEventListener('click', applyPanelPosition);
267
+ }
268
+
269
+ // Load initial positions
270
+ loadPanelPositions();
271
+ }
272
+
273
+ // Call initialization on DOMContentLoaded
274
+ document.addEventListener('DOMContentLoaded', initPanelPositionControls);
275
+ """
276
+
277
+ __all__ = ["SCRIPTS_PANEL_POSITION"]
278
+
279
+ # EOF
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Selection drawing and property synchronization JavaScript.
4
+
5
+ This module contains the JavaScript code for:
6
+ - Drawing selection overlays (polylines, circles, rectangles)
7
+ - Clearing selection state
8
+ - Syncing properties panel to selected element
9
+ - Updating element property highlights
10
+ """
11
+
12
+ SCRIPTS_SELECTION = """
13
+ // ===== SELECTION AND PROPERTY SYNC =====
14
+
15
+ // Clear current selection
16
+ function clearSelection() {
17
+ selectedElement = null;
18
+ clearSelectionOverlay();
19
+
20
+ // Clear section and field highlights
21
+ document.querySelectorAll('.section-highlighted').forEach(s => s.classList.remove('section-highlighted'));
22
+ document.querySelectorAll('.field-highlighted').forEach(f => f.classList.remove('field-highlighted'));
23
+
24
+ // Switch back to Figure tab when nothing selected
25
+ switchTab('figure');
26
+
27
+ // Update hint and show all if in filter mode
28
+ const hint = document.getElementById('selection-hint');
29
+ if (hint && viewMode === 'selected') {
30
+ hint.textContent = '';
31
+ hint.style.color = '';
32
+ showAllProperties();
33
+ }
34
+ }
35
+
36
+ // Draw selection shape(s) - handles lines, scatter, and rectangles
37
+ function drawSelection(key) {
38
+ const overlay = document.getElementById('selection-overlay');
39
+ overlay.innerHTML = '';
40
+
41
+ const img = document.getElementById('preview-image');
42
+ if (!img.naturalWidth || !img.naturalHeight) return;
43
+
44
+ // Set SVG viewBox to match natural image size
45
+ overlay.setAttribute('viewBox', `0 0 ${img.naturalWidth} ${img.naturalHeight}`);
46
+ overlay.style.width = `${img.naturalWidth}px`;
47
+ overlay.style.height = `${img.naturalHeight}px`;
48
+
49
+ const scaleX = 1.0;
50
+ const scaleY = 1.0;
51
+ const offsetX = 0;
52
+ const offsetY = 0;
53
+
54
+ // Determine which elements to highlight
55
+ let elementsToHighlight = [key];
56
+ if (selectedElement && selectedElement.groupElements) {
57
+ elementsToHighlight = selectedElement.groupElements.map(e => e.key);
58
+ }
59
+
60
+ // Draw selection for each element
61
+ for (const elemKey of elementsToHighlight) {
62
+ const bbox = currentBboxes[elemKey];
63
+ if (!bbox) continue;
64
+
65
+ const elementColor = bbox.original_color || '#2563eb';
66
+ const isPrimary = elemKey === key;
67
+
68
+ if (bbox.type === 'line' && bbox.points && bbox.points.length > 1) {
69
+ _drawLineSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY);
70
+ } else if (bbox.type === 'scatter' && bbox.points && bbox.points.length > 0) {
71
+ _drawScatterSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY);
72
+ } else {
73
+ _drawRectSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY);
74
+ }
75
+ }
76
+ }
77
+
78
+ // Helper: Draw line selection (polyline)
79
+ function _drawLineSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY) {
80
+ const points = bbox.points.map(pt => {
81
+ const x = offsetX + pt[0] * scaleX;
82
+ const y = offsetY + pt[1] * scaleY;
83
+ return `${x},${y}`;
84
+ }).join(' ');
85
+
86
+ const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
87
+ polyline.setAttribute('points', points);
88
+ polyline.setAttribute('class', 'selection-polyline');
89
+ polyline.style.setProperty('--element-color', elementColor);
90
+ if (isPrimary) {
91
+ polyline.style.strokeWidth = '10';
92
+ polyline.style.strokeOpacity = '0.6';
93
+ }
94
+ overlay.appendChild(polyline);
95
+ }
96
+
97
+ // Helper: Draw scatter selection (circles)
98
+ function _drawScatterSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY) {
99
+ bbox.points.forEach(pt => {
100
+ const cx = offsetX + pt[0] * scaleX;
101
+ const cy = offsetY + pt[1] * scaleY;
102
+
103
+ const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
104
+ circle.setAttribute('cx', cx);
105
+ circle.setAttribute('cy', cy);
106
+ circle.setAttribute('r', isPrimary ? 4 : 3);
107
+ circle.setAttribute('class', 'selection-circle-subtle');
108
+ circle.style.setProperty('--element-color', elementColor);
109
+ overlay.appendChild(circle);
110
+ });
111
+ }
112
+
113
+ // Helper: Draw rectangle selection
114
+ function _drawRectSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY) {
115
+ const x = offsetX + bbox.x * scaleX;
116
+ const y = offsetY + bbox.y * scaleY;
117
+ const width = bbox.width * scaleX;
118
+ const height = bbox.height * scaleY;
119
+
120
+ const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
121
+ rect.setAttribute('x', x);
122
+ rect.setAttribute('y', y);
123
+ rect.setAttribute('width', Math.max(width, 2));
124
+ rect.setAttribute('height', Math.max(height, 2));
125
+ rect.setAttribute('class', 'selection-rect');
126
+ rect.style.setProperty('--element-color', elementColor);
127
+
128
+ if (isPrimary) {
129
+ rect.classList.add('selection-primary');
130
+ }
131
+
132
+ overlay.appendChild(rect);
133
+ }
134
+
135
+ // Clear selection overlay
136
+ function clearSelectionOverlay() {
137
+ document.getElementById('selection-overlay').innerHTML = '';
138
+ }
139
+
140
+ // Sync properties panel to selected element
141
+ function syncPropertiesToElement(element) {
142
+ // Always show dynamic call properties for the selected element
143
+ showDynamicCallProperties(element);
144
+
145
+ // In 'selected' mode, only show call properties (no section highlighting)
146
+ if (viewMode === 'selected') {
147
+ return;
148
+ }
149
+
150
+ // Map element types to section IDs (for 'all' mode)
151
+ const sectionMap = {
152
+ 'axes': 'section-dimensions',
153
+ 'line': 'section-lines',
154
+ 'scatter': 'section-markers',
155
+ 'bar': 'section-lines',
156
+ 'fill': 'section-lines',
157
+ 'boxplot': 'section-boxplot',
158
+ 'violin': 'section-violin',
159
+ 'title': 'section-fonts',
160
+ 'xlabel': 'section-fonts',
161
+ 'ylabel': 'section-fonts',
162
+ 'xticks': 'section-ticks',
163
+ 'yticks': 'section-ticks',
164
+ 'legend': 'section-legend',
165
+ 'spine': 'section-dimensions',
166
+ 'contour': 'section-dimensions',
167
+ 'image': 'section-dimensions',
168
+ 'pie': 'section-dimensions',
169
+ 'hist': 'section-lines',
170
+ 'quiver': 'section-lines',
171
+ };
172
+
173
+ const sectionId = sectionMap[element.type] || 'section-dimensions';
174
+
175
+ // Close all sections and remove highlights (accordion behavior)
176
+ document.querySelectorAll('.section').forEach(section => {
177
+ section.classList.remove('section-highlighted');
178
+ if (section.id && section.id !== 'section-download') {
179
+ section.removeAttribute('open');
180
+ }
181
+ });
182
+
183
+ // Find and highlight the relevant section
184
+ const section = document.getElementById(sectionId);
185
+ if (section) {
186
+ section.setAttribute('open', '');
187
+ section.classList.add('section-highlighted');
188
+ setTimeout(() => {
189
+ section.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
190
+ }, 50);
191
+ }
192
+
193
+ updateElementProperties(element);
194
+ }
195
+
196
+ // Update property values for selected element
197
+ function updateElementProperties(element) {
198
+ // Clear previous field highlights
199
+ document.querySelectorAll('.form-row').forEach(row => {
200
+ row.classList.remove('field-highlighted');
201
+ });
202
+
203
+ // Map element types to relevant form field IDs
204
+ const fieldMap = {
205
+ 'line': ['lines_trace_mm', 'lines_errorbar_mm', 'lines_errorbar_cap_mm'],
206
+ 'scatter': ['markers_size_mm', 'markers_scatter_mm', 'markers_edge_width_mm'],
207
+ 'bar': ['lines_trace_mm'],
208
+ 'fill': ['lines_trace_mm'],
209
+ 'boxplot': ['lines_trace_mm', 'markers_flier_mm', 'boxplot_median_color'],
210
+ 'violin': ['lines_trace_mm'],
211
+ 'title': ['fonts_title_pt', 'fonts_family'],
212
+ 'xlabel': ['fonts_axis_label_pt', 'fonts_family'],
213
+ 'ylabel': ['fonts_axis_label_pt', 'fonts_family'],
214
+ 'xticks': ['fonts_tick_label_pt', 'ticks_length_mm', 'ticks_direction'],
215
+ 'yticks': ['fonts_tick_label_pt', 'ticks_length_mm', 'ticks_direction'],
216
+ 'legend': ['fonts_legend_pt', 'legend_frameon', 'legend_loc'],
217
+ 'spine': ['axes_thickness_mm'],
218
+ };
219
+
220
+ const relevantFields = fieldMap[element.type] || [];
221
+
222
+ // Highlight relevant form rows
223
+ relevantFields.forEach(fieldId => {
224
+ const row = document.querySelector(`[data-field="${fieldId}"]`);
225
+ if (row) {
226
+ row.classList.add('field-highlighted');
227
+ }
228
+ });
229
+
230
+ // Show dynamic call properties for this element
231
+ showDynamicCallProperties(element);
232
+ }
233
+ """
234
+
235
+ __all__ = ["SCRIPTS_SELECTION"]
236
+
237
+ # EOF
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Tab navigation JavaScript for the figure editor.
4
+
5
+ This module contains the JavaScript code for:
6
+ - Tab switching (Figure/Axis/Element)
7
+ - Auto-switching based on selected element
8
+ - Tab hints management
9
+ """
10
+
11
+ SCRIPTS_TABS = """
12
+ // ===== TAB NAVIGATION =====
13
+
14
+ // Current active tab
15
+ let currentTab = 'figure';
16
+
17
+ // Element type to tab mapping
18
+ const AXIS_TYPES = ['title', 'xlabel', 'ylabel', 'suptitle', 'supxlabel', 'supylabel', 'legend'];
19
+ const ELEMENT_TYPES = ['line', 'scatter', 'bar', 'hist', 'fill', 'boxplot', 'violin', 'image', 'linecollection', 'quiver', 'pie', 'contour', 'specgram'];
20
+
21
+ // Switch between Figure/Axis/Element tabs
22
+ function switchTab(tabName) {
23
+ currentTab = tabName;
24
+
25
+ // Update tab buttons
26
+ document.querySelectorAll('.tab-btn').forEach(btn => {
27
+ btn.classList.toggle('active', btn.id === `tab-${tabName}`);
28
+ });
29
+
30
+ // Update tab content
31
+ document.querySelectorAll('.tab-content').forEach(content => {
32
+ content.classList.toggle('active', content.id === `tab-content-${tabName}`);
33
+ });
34
+
35
+ // Update hints based on selection state
36
+ updateTabHints();
37
+ }
38
+
39
+ // Get appropriate tab for element type
40
+ function getTabForElementType(elementType) {
41
+ if (!elementType) return 'figure';
42
+ if (AXIS_TYPES.includes(elementType)) return 'axis';
43
+ if (ELEMENT_TYPES.includes(elementType)) return 'element';
44
+ return 'figure';
45
+ }
46
+
47
+ // Auto-switch to appropriate tab based on selected element
48
+ function autoSwitchTab(elementType) {
49
+ const targetTab = getTabForElementType(elementType);
50
+ if (targetTab !== currentTab) {
51
+ switchTab(targetTab);
52
+ }
53
+ }
54
+
55
+ // Update tab hints based on current state
56
+ function updateTabHints() {
57
+ const axisHint = document.getElementById('axis-tab-hint');
58
+ const elementHint = document.getElementById('element-tab-hint');
59
+ const elementPanel = document.getElementById('selected-element-panel');
60
+ const dynamicProps = document.getElementById('dynamic-call-properties');
61
+
62
+ if (currentTab === 'axis') {
63
+ if (selectedElement && AXIS_TYPES.includes(selectedElement.type)) {
64
+ if (axisHint) axisHint.style.display = 'none';
65
+ } else {
66
+ if (axisHint) axisHint.style.display = 'block';
67
+ }
68
+ }
69
+
70
+ if (currentTab === 'element') {
71
+ if (selectedElement && ELEMENT_TYPES.includes(selectedElement.type)) {
72
+ if (elementHint) elementHint.style.display = 'none';
73
+ if (elementPanel) {
74
+ elementPanel.style.display = 'block';
75
+ document.getElementById('element-type-badge').textContent = selectedElement.type;
76
+ document.getElementById('element-name').textContent = selectedElement.label || selectedElement.key;
77
+ }
78
+ } else {
79
+ if (elementHint) elementHint.style.display = 'block';
80
+ if (elementPanel) elementPanel.style.display = 'none';
81
+ if (dynamicProps) dynamicProps.style.display = 'none';
82
+ }
83
+ }
84
+ }
85
+ """
86
+
87
+ __all__ = ["SCRIPTS_TABS"]
88
+
89
+ # EOF