figrecipe 0.6.0__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. figrecipe/__init__.py +161 -1030
  2. figrecipe/__main__.py +12 -0
  3. figrecipe/_api/__init__.py +48 -0
  4. figrecipe/_api/_extract.py +108 -0
  5. figrecipe/_api/_notebook.py +61 -0
  6. figrecipe/_api/_panel.py +113 -0
  7. figrecipe/_api/_save.py +287 -0
  8. figrecipe/_api/_seaborn_proxy.py +34 -0
  9. figrecipe/_api/_style_manager.py +153 -0
  10. figrecipe/_api/_subplots.py +333 -0
  11. figrecipe/_api/_validate.py +82 -0
  12. figrecipe/_cli/__init__.py +7 -0
  13. figrecipe/_cli/_compose.py +87 -0
  14. figrecipe/_cli/_convert.py +117 -0
  15. figrecipe/_cli/_crop.py +82 -0
  16. figrecipe/_cli/_edit.py +70 -0
  17. figrecipe/_cli/_extract.py +128 -0
  18. figrecipe/_cli/_fonts.py +47 -0
  19. figrecipe/_cli/_info.py +67 -0
  20. figrecipe/_cli/_main.py +58 -0
  21. figrecipe/_cli/_reproduce.py +79 -0
  22. figrecipe/_cli/_style.py +77 -0
  23. figrecipe/_cli/_validate.py +66 -0
  24. figrecipe/_cli/_version.py +50 -0
  25. figrecipe/_composition/__init__.py +32 -0
  26. figrecipe/_composition/_alignment.py +452 -0
  27. figrecipe/_composition/_compose.py +179 -0
  28. figrecipe/_composition/_import_axes.py +127 -0
  29. figrecipe/_composition/_visibility.py +125 -0
  30. figrecipe/_dev/__init__.py +4 -93
  31. figrecipe/_dev/_plotters.py +76 -0
  32. figrecipe/_dev/_run_demos.py +56 -0
  33. figrecipe/_dev/browser/__init__.py +69 -0
  34. figrecipe/_dev/browser/_audio.py +240 -0
  35. figrecipe/_dev/browser/_caption.py +356 -0
  36. figrecipe/_dev/browser/_click_effect.py +146 -0
  37. figrecipe/_dev/browser/_cursor.py +196 -0
  38. figrecipe/_dev/browser/_highlight.py +105 -0
  39. figrecipe/_dev/browser/_narration.py +237 -0
  40. figrecipe/_dev/browser/_recorder.py +446 -0
  41. figrecipe/_dev/browser/_utils.py +178 -0
  42. figrecipe/_dev/browser/_video_trim/__init__.py +152 -0
  43. figrecipe/_dev/browser/_video_trim/_detection.py +223 -0
  44. figrecipe/_dev/browser/_video_trim/_markers.py +140 -0
  45. figrecipe/_dev/demo_plotters/__init__.py +35 -166
  46. figrecipe/_dev/demo_plotters/_categories.py +81 -0
  47. figrecipe/_dev/demo_plotters/_figure_creators.py +119 -0
  48. figrecipe/_dev/demo_plotters/_helpers.py +31 -0
  49. figrecipe/_dev/demo_plotters/_registry.py +50 -0
  50. figrecipe/_dev/demo_plotters/bar_categorical/__init__.py +4 -0
  51. figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
  52. figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
  53. figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
  54. figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
  55. figrecipe/_dev/demo_plotters/{plot_plot.py → line_curve/plot_plot.py} +3 -2
  56. figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
  57. figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
  58. figrecipe/_dev/demo_plotters/{plot_pie.py → special/plot_pie.py} +5 -1
  59. figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
  60. figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
  61. figrecipe/_editor/__init__.py +61 -13
  62. figrecipe/_editor/_bbox/__init__.py +43 -0
  63. figrecipe/_editor/_bbox/_collections.py +177 -0
  64. figrecipe/_editor/_bbox/_elements.py +159 -0
  65. figrecipe/_editor/_bbox/_extract.py +402 -0
  66. figrecipe/_editor/_bbox/_extract_axes.py +370 -0
  67. figrecipe/_editor/_bbox/_extract_text.py +466 -0
  68. figrecipe/_editor/_bbox/_lines.py +173 -0
  69. figrecipe/_editor/_bbox/_transforms.py +146 -0
  70. figrecipe/_editor/_call_overrides.py +183 -0
  71. figrecipe/_editor/_datatable_plot_handlers.py +249 -0
  72. figrecipe/_editor/_figure_layout.py +211 -0
  73. figrecipe/_editor/_flask_app.py +200 -1030
  74. figrecipe/_editor/_helpers.py +251 -0
  75. figrecipe/_editor/_hitmap/__init__.py +76 -0
  76. figrecipe/_editor/_hitmap/_artists/__init__.py +21 -0
  77. figrecipe/_editor/_hitmap/_artists/_collections.py +345 -0
  78. figrecipe/_editor/_hitmap/_artists/_images.py +68 -0
  79. figrecipe/_editor/_hitmap/_artists/_lines.py +107 -0
  80. figrecipe/_editor/_hitmap/_artists/_patches.py +163 -0
  81. figrecipe/_editor/_hitmap/_artists/_text.py +190 -0
  82. figrecipe/_editor/_hitmap/_colors.py +181 -0
  83. figrecipe/_editor/_hitmap/_detect.py +194 -0
  84. figrecipe/_editor/_hitmap/_restore.py +154 -0
  85. figrecipe/_editor/_hitmap_main.py +182 -0
  86. figrecipe/_editor/_overrides.py +4 -1
  87. figrecipe/_editor/_plot_types_registry.py +190 -0
  88. figrecipe/_editor/_preferences.py +135 -0
  89. figrecipe/_editor/_render_overrides.py +507 -0
  90. figrecipe/_editor/_renderer.py +81 -186
  91. figrecipe/_editor/_routes_annotation.py +114 -0
  92. figrecipe/_editor/_routes_axis.py +482 -0
  93. figrecipe/_editor/_routes_captions.py +130 -0
  94. figrecipe/_editor/_routes_composition.py +270 -0
  95. figrecipe/_editor/_routes_core.py +126 -0
  96. figrecipe/_editor/_routes_datatable.py +364 -0
  97. figrecipe/_editor/_routes_element.py +335 -0
  98. figrecipe/_editor/_routes_files.py +443 -0
  99. figrecipe/_editor/_routes_image.py +200 -0
  100. figrecipe/_editor/_routes_snapshot.py +94 -0
  101. figrecipe/_editor/_routes_style.py +243 -0
  102. figrecipe/_editor/_templates/__init__.py +116 -1
  103. figrecipe/_editor/_templates/_html.py +154 -64
  104. figrecipe/_editor/_templates/_html_components/__init__.py +13 -0
  105. figrecipe/_editor/_templates/_html_components/_composition_toolbar.py +79 -0
  106. figrecipe/_editor/_templates/_html_components/_file_browser.py +41 -0
  107. figrecipe/_editor/_templates/_html_datatable.py +92 -0
  108. figrecipe/_editor/_templates/_scripts/__init__.py +178 -0
  109. figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
  110. figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
  111. figrecipe/_editor/_templates/_scripts/_api.py +228 -0
  112. figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
  113. figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
  114. figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
  115. figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
  116. figrecipe/_editor/_templates/_scripts/_core.py +493 -0
  117. figrecipe/_editor/_templates/_scripts/_datatable/__init__.py +59 -0
  118. figrecipe/_editor/_templates/_scripts/_datatable/_cell_edit.py +97 -0
  119. figrecipe/_editor/_templates/_scripts/_datatable/_clipboard.py +164 -0
  120. figrecipe/_editor/_templates/_scripts/_datatable/_context_menu.py +221 -0
  121. figrecipe/_editor/_templates/_scripts/_datatable/_core.py +150 -0
  122. figrecipe/_editor/_templates/_scripts/_datatable/_editable.py +511 -0
  123. figrecipe/_editor/_templates/_scripts/_datatable/_import.py +161 -0
  124. figrecipe/_editor/_templates/_scripts/_datatable/_plot.py +261 -0
  125. figrecipe/_editor/_templates/_scripts/_datatable/_selection.py +438 -0
  126. figrecipe/_editor/_templates/_scripts/_datatable/_table.py +256 -0
  127. figrecipe/_editor/_templates/_scripts/_datatable/_tabs.py +354 -0
  128. figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
  129. figrecipe/_editor/_templates/_scripts/_element_editor.py +325 -0
  130. figrecipe/_editor/_templates/_scripts/_files.py +429 -0
  131. figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
  132. figrecipe/_editor/_templates/_scripts/_hitmap.py +512 -0
  133. figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
  134. figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
  135. figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
  136. figrecipe/_editor/_templates/_scripts/_legend_drag.py +270 -0
  137. figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
  138. figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
  139. figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
  140. figrecipe/_editor/_templates/_scripts/_panel_drag.py +505 -0
  141. figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
  142. figrecipe/_editor/_templates/_scripts/_panel_position.py +463 -0
  143. figrecipe/_editor/_templates/_scripts/_panel_resize.py +230 -0
  144. figrecipe/_editor/_templates/_scripts/_panel_snap.py +307 -0
  145. figrecipe/_editor/_templates/_scripts/_region_select.py +255 -0
  146. figrecipe/_editor/_templates/_scripts/_selection.py +244 -0
  147. figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
  148. figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
  149. figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
  150. figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
  151. figrecipe/_editor/_templates/_scripts/_zoom.py +212 -0
  152. figrecipe/_editor/_templates/_styles/__init__.py +78 -0
  153. figrecipe/_editor/_templates/_styles/_base.py +111 -0
  154. figrecipe/_editor/_templates/_styles/_buttons.py +327 -0
  155. figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
  156. figrecipe/_editor/_templates/_styles/_composition.py +87 -0
  157. figrecipe/_editor/_templates/_styles/_controls.py +430 -0
  158. figrecipe/_editor/_templates/_styles/_datatable/__init__.py +40 -0
  159. figrecipe/_editor/_templates/_styles/_datatable/_editable.py +203 -0
  160. figrecipe/_editor/_templates/_styles/_datatable/_panel.py +268 -0
  161. figrecipe/_editor/_templates/_styles/_datatable/_table.py +479 -0
  162. figrecipe/_editor/_templates/_styles/_datatable/_toolbar.py +384 -0
  163. figrecipe/_editor/_templates/_styles/_datatable/_vars.py +123 -0
  164. figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
  165. figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
  166. figrecipe/_editor/_templates/_styles/_forms.py +224 -0
  167. figrecipe/_editor/_templates/_styles/_hitmap.py +191 -0
  168. figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
  169. figrecipe/_editor/_templates/_styles/_labels.py +118 -0
  170. figrecipe/_editor/_templates/_styles/_modals.py +127 -0
  171. figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
  172. figrecipe/_editor/_templates/_styles/_preview.py +430 -0
  173. figrecipe/_editor/_templates/_styles/_selection.py +73 -0
  174. figrecipe/_editor/_templates/_styles/_spinner.py +117 -0
  175. figrecipe/_editor/static/audio/click.mp3 +0 -0
  176. figrecipe/_editor/static/click.mp3 +0 -0
  177. figrecipe/_editor/static/icons/favicon.ico +0 -0
  178. figrecipe/_integrations/__init__.py +17 -0
  179. figrecipe/_integrations/_scitex_stats.py +298 -0
  180. figrecipe/_params/_DECORATION_METHODS.py +8 -0
  181. figrecipe/_recorder.py +63 -109
  182. figrecipe/_recorder_utils.py +124 -0
  183. figrecipe/_reproducer/__init__.py +18 -0
  184. figrecipe/_reproducer/_core.py +509 -0
  185. figrecipe/_reproducer/_custom_plots.py +279 -0
  186. figrecipe/_reproducer/_seaborn.py +100 -0
  187. figrecipe/_reproducer/_violin.py +186 -0
  188. figrecipe/_signatures/_kwargs.py +273 -0
  189. figrecipe/_signatures/_loader.py +21 -423
  190. figrecipe/_signatures/_parsing.py +147 -0
  191. figrecipe/_utils/__init__.py +3 -0
  192. figrecipe/_utils/_bundle.py +205 -0
  193. figrecipe/_wrappers/_axes.py +252 -895
  194. figrecipe/_wrappers/_axes_helpers.py +136 -0
  195. figrecipe/_wrappers/_axes_plots.py +418 -0
  196. figrecipe/_wrappers/_axes_seaborn.py +157 -0
  197. figrecipe/_wrappers/_caption_generator.py +218 -0
  198. figrecipe/_wrappers/_figure.py +188 -1
  199. figrecipe/_wrappers/_panel_labels.py +127 -0
  200. figrecipe/_wrappers/_plot_helpers.py +143 -0
  201. figrecipe/_wrappers/_stat_annotation.py +274 -0
  202. figrecipe/_wrappers/_violin_helpers.py +180 -0
  203. figrecipe/styles/__init__.py +8 -6
  204. figrecipe/styles/_dotdict.py +72 -0
  205. figrecipe/styles/_finalize.py +134 -0
  206. figrecipe/styles/_fonts.py +77 -0
  207. figrecipe/styles/_kwargs_converter.py +178 -0
  208. figrecipe/styles/_plot_styles.py +209 -0
  209. figrecipe/styles/_style_applier.py +42 -480
  210. figrecipe/styles/_style_loader.py +16 -192
  211. figrecipe/styles/_themes.py +151 -0
  212. figrecipe/styles/presets/MATPLOTLIB.yaml +2 -1
  213. figrecipe/styles/presets/SCITEX.yaml +40 -28
  214. figrecipe-0.9.0.dist-info/METADATA +427 -0
  215. figrecipe-0.9.0.dist-info/RECORD +277 -0
  216. figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
  217. figrecipe/_editor/_bbox.py +0 -978
  218. figrecipe/_editor/_hitmap.py +0 -937
  219. figrecipe/_editor/_templates/_scripts.py +0 -2778
  220. figrecipe/_editor/_templates/_styles.py +0 -1326
  221. figrecipe/_reproducer.py +0 -975
  222. figrecipe-0.6.0.dist-info/METADATA +0 -394
  223. figrecipe-0.6.0.dist-info/RECORD +0 -90
  224. /figrecipe/_dev/demo_plotters/{plot_bar.py → bar_categorical/plot_bar.py} +0 -0
  225. /figrecipe/_dev/demo_plotters/{plot_barh.py → bar_categorical/plot_barh.py} +0 -0
  226. /figrecipe/_dev/demo_plotters/{plot_contour.py → contour_surface/plot_contour.py} +0 -0
  227. /figrecipe/_dev/demo_plotters/{plot_contourf.py → contour_surface/plot_contourf.py} +0 -0
  228. /figrecipe/_dev/demo_plotters/{plot_tricontour.py → contour_surface/plot_tricontour.py} +0 -0
  229. /figrecipe/_dev/demo_plotters/{plot_tricontourf.py → contour_surface/plot_tricontourf.py} +0 -0
  230. /figrecipe/_dev/demo_plotters/{plot_tripcolor.py → contour_surface/plot_tripcolor.py} +0 -0
  231. /figrecipe/_dev/demo_plotters/{plot_triplot.py → contour_surface/plot_triplot.py} +0 -0
  232. /figrecipe/_dev/demo_plotters/{plot_boxplot.py → distribution/plot_boxplot.py} +0 -0
  233. /figrecipe/_dev/demo_plotters/{plot_ecdf.py → distribution/plot_ecdf.py} +0 -0
  234. /figrecipe/_dev/demo_plotters/{plot_hist.py → distribution/plot_hist.py} +0 -0
  235. /figrecipe/_dev/demo_plotters/{plot_hist2d.py → distribution/plot_hist2d.py} +0 -0
  236. /figrecipe/_dev/demo_plotters/{plot_violinplot.py → distribution/plot_violinplot.py} +0 -0
  237. /figrecipe/_dev/demo_plotters/{plot_hexbin.py → image_matrix/plot_hexbin.py} +0 -0
  238. /figrecipe/_dev/demo_plotters/{plot_imshow.py → image_matrix/plot_imshow.py} +0 -0
  239. /figrecipe/_dev/demo_plotters/{plot_matshow.py → image_matrix/plot_matshow.py} +0 -0
  240. /figrecipe/_dev/demo_plotters/{plot_pcolor.py → image_matrix/plot_pcolor.py} +0 -0
  241. /figrecipe/_dev/demo_plotters/{plot_pcolormesh.py → image_matrix/plot_pcolormesh.py} +0 -0
  242. /figrecipe/_dev/demo_plotters/{plot_spy.py → image_matrix/plot_spy.py} +0 -0
  243. /figrecipe/_dev/demo_plotters/{plot_errorbar.py → line_curve/plot_errorbar.py} +0 -0
  244. /figrecipe/_dev/demo_plotters/{plot_fill.py → line_curve/plot_fill.py} +0 -0
  245. /figrecipe/_dev/demo_plotters/{plot_fill_between.py → line_curve/plot_fill_between.py} +0 -0
  246. /figrecipe/_dev/demo_plotters/{plot_fill_betweenx.py → line_curve/plot_fill_betweenx.py} +0 -0
  247. /figrecipe/_dev/demo_plotters/{plot_stackplot.py → line_curve/plot_stackplot.py} +0 -0
  248. /figrecipe/_dev/demo_plotters/{plot_stairs.py → line_curve/plot_stairs.py} +0 -0
  249. /figrecipe/_dev/demo_plotters/{plot_step.py → line_curve/plot_step.py} +0 -0
  250. /figrecipe/_dev/demo_plotters/{plot_scatter.py → scatter_points/plot_scatter.py} +0 -0
  251. /figrecipe/_dev/demo_plotters/{plot_eventplot.py → special/plot_eventplot.py} +0 -0
  252. /figrecipe/_dev/demo_plotters/{plot_loglog.py → special/plot_loglog.py} +0 -0
  253. /figrecipe/_dev/demo_plotters/{plot_semilogx.py → special/plot_semilogx.py} +0 -0
  254. /figrecipe/_dev/demo_plotters/{plot_semilogy.py → special/plot_semilogy.py} +0 -0
  255. /figrecipe/_dev/demo_plotters/{plot_stem.py → special/plot_stem.py} +0 -0
  256. /figrecipe/_dev/demo_plotters/{plot_acorr.py → spectral_signal/plot_acorr.py} +0 -0
  257. /figrecipe/_dev/demo_plotters/{plot_angle_spectrum.py → spectral_signal/plot_angle_spectrum.py} +0 -0
  258. /figrecipe/_dev/demo_plotters/{plot_cohere.py → spectral_signal/plot_cohere.py} +0 -0
  259. /figrecipe/_dev/demo_plotters/{plot_csd.py → spectral_signal/plot_csd.py} +0 -0
  260. /figrecipe/_dev/demo_plotters/{plot_magnitude_spectrum.py → spectral_signal/plot_magnitude_spectrum.py} +0 -0
  261. /figrecipe/_dev/demo_plotters/{plot_phase_spectrum.py → spectral_signal/plot_phase_spectrum.py} +0 -0
  262. /figrecipe/_dev/demo_plotters/{plot_psd.py → spectral_signal/plot_psd.py} +0 -0
  263. /figrecipe/_dev/demo_plotters/{plot_specgram.py → spectral_signal/plot_specgram.py} +0 -0
  264. /figrecipe/_dev/demo_plotters/{plot_xcorr.py → spectral_signal/plot_xcorr.py} +0 -0
  265. /figrecipe/_dev/demo_plotters/{plot_barbs.py → vector_flow/plot_barbs.py} +0 -0
  266. /figrecipe/_dev/demo_plotters/{plot_quiver.py → vector_flow/plot_quiver.py} +0 -0
  267. /figrecipe/_dev/demo_plotters/{plot_streamplot.py → vector_flow/plot_streamplot.py} +0 -0
  268. {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
  269. {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,244 @@
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
+ // Clear panel selection
25
+ if (typeof clearPanelSelection === 'function') {
26
+ clearPanelSelection();
27
+ }
28
+
29
+ // Switch back to Figure tab when nothing selected
30
+ switchTab('figure');
31
+
32
+ // Update hint and show all if in filter mode
33
+ const hint = document.getElementById('selection-hint');
34
+ if (hint && viewMode === 'selected') {
35
+ hint.textContent = '';
36
+ hint.style.color = '';
37
+ showAllProperties();
38
+ }
39
+ }
40
+
41
+ // Draw selection shape(s) - handles lines, scatter, and rectangles
42
+ function drawSelection(key) {
43
+ const overlay = document.getElementById('selection-overlay');
44
+ overlay.innerHTML = '';
45
+
46
+ const img = document.getElementById('preview-image');
47
+ if (!img.naturalWidth || !img.naturalHeight) return;
48
+
49
+ // Set SVG viewBox to match natural image size
50
+ overlay.setAttribute('viewBox', `0 0 ${img.naturalWidth} ${img.naturalHeight}`);
51
+ overlay.style.width = `${img.naturalWidth}px`;
52
+ overlay.style.height = `${img.naturalHeight}px`;
53
+
54
+ const scaleX = 1.0;
55
+ const scaleY = 1.0;
56
+ const offsetX = 0;
57
+ const offsetY = 0;
58
+
59
+ // Determine which elements to highlight
60
+ let elementsToHighlight = [key];
61
+ if (selectedElement && selectedElement.groupElements) {
62
+ elementsToHighlight = selectedElement.groupElements.map(e => e.key);
63
+ }
64
+
65
+ // Draw selection for each element
66
+ for (const elemKey of elementsToHighlight) {
67
+ const bbox = currentBboxes[elemKey];
68
+ if (!bbox) continue;
69
+
70
+ // Get element color from colorMap (primary source) or bbox (fallback)
71
+ const colorMapInfo = (colorMap && colorMap[elemKey]) || {};
72
+ const elementColor = colorMapInfo.original_color || bbox.original_color || '#2563eb';
73
+ const isPrimary = elemKey === key;
74
+
75
+ if (bbox.type === 'line' && bbox.points && bbox.points.length > 1) {
76
+ _drawLineSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY);
77
+ } else if (bbox.type === 'scatter' && bbox.points && bbox.points.length > 0) {
78
+ _drawScatterSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY);
79
+ } else {
80
+ _drawRectSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY);
81
+ }
82
+ }
83
+ }
84
+
85
+ // Helper: Draw line selection (polyline)
86
+ function _drawLineSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY) {
87
+ const points = bbox.points.map(pt => {
88
+ const x = offsetX + pt[0] * scaleX;
89
+ const y = offsetY + pt[1] * scaleY;
90
+ return `${x},${y}`;
91
+ }).join(' ');
92
+
93
+ const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
94
+ polyline.setAttribute('points', points);
95
+ polyline.setAttribute('class', 'selection-polyline');
96
+ polyline.style.setProperty('--element-color', elementColor);
97
+ if (isPrimary) {
98
+ polyline.style.strokeWidth = '10';
99
+ polyline.style.strokeOpacity = '0.6';
100
+ }
101
+ overlay.appendChild(polyline);
102
+ }
103
+
104
+ // Helper: Draw scatter selection (circles)
105
+ function _drawScatterSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY) {
106
+ bbox.points.forEach(pt => {
107
+ const cx = offsetX + pt[0] * scaleX;
108
+ const cy = offsetY + pt[1] * scaleY;
109
+
110
+ const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
111
+ circle.setAttribute('cx', cx);
112
+ circle.setAttribute('cy', cy);
113
+ circle.setAttribute('r', isPrimary ? 4 : 3);
114
+ circle.setAttribute('class', 'selection-circle-subtle');
115
+ circle.style.setProperty('--element-color', elementColor);
116
+ overlay.appendChild(circle);
117
+ });
118
+ }
119
+
120
+ // Helper: Draw rectangle selection
121
+ function _drawRectSelection(overlay, bbox, elementColor, isPrimary, offsetX, offsetY, scaleX, scaleY) {
122
+ const x = offsetX + bbox.x * scaleX;
123
+ const y = offsetY + bbox.y * scaleY;
124
+ const width = bbox.width * scaleX;
125
+ const height = bbox.height * scaleY;
126
+
127
+ const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
128
+ rect.setAttribute('x', x);
129
+ rect.setAttribute('y', y);
130
+ rect.setAttribute('width', Math.max(width, 2));
131
+ rect.setAttribute('height', Math.max(height, 2));
132
+ rect.setAttribute('class', 'selection-rect');
133
+ rect.style.setProperty('--element-color', elementColor);
134
+
135
+ if (isPrimary) {
136
+ rect.classList.add('selection-primary');
137
+ }
138
+
139
+ overlay.appendChild(rect);
140
+ }
141
+
142
+ // Clear selection overlay
143
+ function clearSelectionOverlay() {
144
+ document.getElementById('selection-overlay').innerHTML = '';
145
+ }
146
+
147
+ // Sync properties panel to selected element
148
+ function syncPropertiesToElement(element) {
149
+ // Always show dynamic call properties for the selected element
150
+ showDynamicCallProperties(element);
151
+
152
+ // In 'selected' mode, only show call properties (no section highlighting)
153
+ if (viewMode === 'selected') {
154
+ return;
155
+ }
156
+
157
+ // Map element types to section IDs (for 'all' mode)
158
+ const sectionMap = {
159
+ 'axes': 'section-dimensions',
160
+ 'line': 'section-lines',
161
+ 'scatter': 'section-markers',
162
+ 'bar': 'section-lines',
163
+ 'fill': 'section-lines',
164
+ 'boxplot': 'section-boxplot',
165
+ 'violin': 'section-violin',
166
+ 'title': 'section-fonts',
167
+ 'xlabel': 'section-fonts',
168
+ 'ylabel': 'section-fonts',
169
+ 'xticks': 'section-ticks',
170
+ 'yticks': 'section-ticks',
171
+ 'legend': 'section-legend',
172
+ 'spine': 'section-dimensions',
173
+ 'contour': 'section-dimensions',
174
+ 'image': 'section-dimensions',
175
+ 'pie': 'section-dimensions',
176
+ 'hist': 'section-lines',
177
+ 'quiver': 'section-lines',
178
+ };
179
+
180
+ const sectionId = sectionMap[element.type] || 'section-dimensions';
181
+
182
+ // Close all sections and remove highlights (accordion behavior)
183
+ document.querySelectorAll('.section').forEach(section => {
184
+ section.classList.remove('section-highlighted');
185
+ if (section.id && section.id !== 'section-download') {
186
+ section.removeAttribute('open');
187
+ }
188
+ });
189
+
190
+ // Find and highlight the relevant section
191
+ const section = document.getElementById(sectionId);
192
+ if (section) {
193
+ section.setAttribute('open', '');
194
+ section.classList.add('section-highlighted');
195
+ setTimeout(() => {
196
+ section.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
197
+ }, 50);
198
+ }
199
+
200
+ updateElementProperties(element);
201
+ }
202
+
203
+ // Update property values for selected element
204
+ function updateElementProperties(element) {
205
+ // Clear previous field highlights
206
+ document.querySelectorAll('.form-row').forEach(row => {
207
+ row.classList.remove('field-highlighted');
208
+ });
209
+
210
+ // Map element types to relevant form field IDs
211
+ const fieldMap = {
212
+ 'line': ['lines_trace_mm', 'lines_errorbar_mm', 'lines_errorbar_cap_mm'],
213
+ 'scatter': ['markers_size_mm', 'markers_scatter_mm', 'markers_edge_width_mm'],
214
+ 'bar': ['lines_trace_mm'],
215
+ 'fill': ['lines_trace_mm'],
216
+ 'boxplot': ['lines_trace_mm', 'markers_flier_mm', 'boxplot_median_color'],
217
+ 'violin': ['lines_trace_mm'],
218
+ 'title': ['fonts_title_pt', 'fonts_family'],
219
+ 'xlabel': ['fonts_axis_label_pt', 'fonts_family'],
220
+ 'ylabel': ['fonts_axis_label_pt', 'fonts_family'],
221
+ 'xticks': ['fonts_tick_label_pt', 'ticks_length_mm', 'ticks_direction'],
222
+ 'yticks': ['fonts_tick_label_pt', 'ticks_length_mm', 'ticks_direction'],
223
+ 'legend': ['fonts_legend_pt', 'legend_frameon', 'legend_loc'],
224
+ 'spine': ['axes_thickness_mm'],
225
+ };
226
+
227
+ const relevantFields = fieldMap[element.type] || [];
228
+
229
+ // Highlight relevant form rows
230
+ relevantFields.forEach(fieldId => {
231
+ const row = document.querySelector(`[data-field="${fieldId}"]`);
232
+ if (row) {
233
+ row.classList.add('field-highlighted');
234
+ }
235
+ });
236
+
237
+ // Show dynamic call properties for this element
238
+ showDynamicCallProperties(element);
239
+ }
240
+ """
241
+
242
+ __all__ = ["SCRIPTS_SELECTION"]
243
+
244
+ # EOF
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Tri-directional synchronization between Data/Canvas/Properties panes.
4
+
5
+ This module coordinates selection state across:
6
+ - Data pane (datatable tabs)
7
+ - Canvas pane (figure preview with hit regions)
8
+ - Properties pane (Figure/Axis/Element tabs)
9
+
10
+ Sync directions:
11
+ - Canvas → Data: Selecting element highlights datatable tab
12
+ - Canvas → Properties: Selecting element switches to appropriate tab
13
+ - Data → Canvas: Clicking datatable tab selects element on canvas
14
+ - Data → Properties: Clicking datatable tab switches to Element tab
15
+ - Properties → Canvas: Clicking tab filters/selects canvas elements
16
+ - Properties → Data: Clicking tab highlights relevant data
17
+ """
18
+
19
+ SCRIPTS_SYNC = """
20
+ // ============================================================================
21
+ // Tri-directional Pane Synchronization
22
+ // ============================================================================
23
+
24
+ // Sync state flags to prevent infinite loops
25
+ let _syncingFromCanvas = false;
26
+ let _syncingFromData = false;
27
+ let _syncingFromProperties = false;
28
+
29
+ // ============================================================================
30
+ // Initialize Sync Hooks
31
+ // ============================================================================
32
+ function initPaneSync() {
33
+ console.log('[PaneSync] Initializing tri-directional synchronization');
34
+
35
+ // Hook datatable tab selection
36
+ hookDatatableTabSync();
37
+
38
+ // Hook properties tab clicks
39
+ hookPropertiesTabSync();
40
+
41
+ // Canvas selection is already hooked via hookCanvasSelection() in datatable core
42
+ }
43
+
44
+ // ============================================================================
45
+ // Data Pane -> Canvas/Properties Sync
46
+ // ============================================================================
47
+ function hookDatatableTabSync() {
48
+ // Wrap selectTab to add canvas/properties sync
49
+ if (typeof window.selectTab === 'function') {
50
+ const originalSelectTab = window.selectTab;
51
+ window.selectTab = function(tabId) {
52
+ originalSelectTab(tabId);
53
+
54
+ // Avoid infinite loops
55
+ if (_syncingFromCanvas || _syncingFromProperties) return;
56
+ _syncingFromData = true;
57
+
58
+ try {
59
+ syncCanvasFromDatatableTab(tabId);
60
+ syncPropertiesFromDatatableTab(tabId);
61
+ } finally {
62
+ _syncingFromData = false;
63
+ }
64
+ };
65
+ console.log('[PaneSync] Datatable tab sync hooked');
66
+ }
67
+ }
68
+
69
+ function syncCanvasFromDatatableTab(tabId) {
70
+ if (!tabId || typeof datatableTabs === 'undefined') return;
71
+ const tabState = datatableTabs[tabId];
72
+ if (!tabState) return;
73
+
74
+ const callId = tabState.callId || tabState.name;
75
+ if (!callId) return;
76
+
77
+ console.log('[PaneSync] Data->Canvas: Looking for element matching callId:', callId);
78
+
79
+ // Search currentBboxes for matching element
80
+ if (typeof currentBboxes !== 'undefined' && currentBboxes) {
81
+ // First pass: exact match on call_id or label
82
+ for (const [key, bbox] of Object.entries(currentBboxes)) {
83
+ if (key === '_meta' || !bbox) continue;
84
+ if (bbox.call_id === callId || bbox.label === callId) {
85
+ if (typeof selectElement === 'function') {
86
+ selectElement({ key, ...bbox });
87
+ console.log('[PaneSync] Data->Canvas: Selected (exact)', key);
88
+ }
89
+ return;
90
+ }
91
+ }
92
+
93
+ // Second pass: key contains callId (e.g., "scatter" in "ax1_scatter0")
94
+ for (const [key, bbox] of Object.entries(currentBboxes)) {
95
+ if (key === '_meta' || !bbox) continue;
96
+ // Match pattern: ax{N}_{callId}{N} like ax1_scatter0
97
+ const pattern = new RegExp(`ax\\d+_${callId}\\d*$`, 'i');
98
+ if (pattern.test(key)) {
99
+ if (typeof selectElement === 'function') {
100
+ selectElement({ key, ...bbox });
101
+ console.log('[PaneSync] Data->Canvas: Selected (pattern)', key);
102
+ }
103
+ return;
104
+ }
105
+ }
106
+
107
+ // Third pass: looser match - key contains callId anywhere
108
+ for (const [key, bbox] of Object.entries(currentBboxes)) {
109
+ if (key === '_meta' || !bbox) continue;
110
+ if (key.toLowerCase().includes(callId.toLowerCase())) {
111
+ if (typeof selectElement === 'function') {
112
+ selectElement({ key, ...bbox });
113
+ console.log('[PaneSync] Data->Canvas: Selected (contains)', key);
114
+ }
115
+ return;
116
+ }
117
+ }
118
+
119
+ console.log('[PaneSync] Data->Canvas: No matching element found for', callId);
120
+ }
121
+
122
+ // Fallback: select the panel associated with this tab
123
+ if (tabState.targetAxis !== null && tabState.targetAxis !== undefined) {
124
+ const axKey = `ax${tabState.targetAxis}_axes`;
125
+ if (typeof currentBboxes !== 'undefined' && currentBboxes[axKey]) {
126
+ const bbox = currentBboxes[axKey];
127
+ if (typeof selectElement === 'function') {
128
+ selectElement({ key: axKey, ...bbox, type: 'axes', ax_index: tabState.targetAxis });
129
+ console.log('[PaneSync] Data->Canvas: Selected panel', tabState.targetAxis);
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+ function syncPropertiesFromDatatableTab(tabId) {
136
+ if (!tabId || typeof datatableTabs === 'undefined') return;
137
+ const tabState = datatableTabs[tabId];
138
+ if (!tabState) return;
139
+
140
+ // Data tabs represent plot elements, so switch to Element tab
141
+ if (typeof switchTab === 'function') {
142
+ switchTab('element');
143
+ console.log('[PaneSync] Data->Properties: Switched to Element tab');
144
+ }
145
+ }
146
+
147
+ // ============================================================================
148
+ // Properties Pane -> Canvas/Data Sync
149
+ // ============================================================================
150
+ function hookPropertiesTabSync() {
151
+ // Add click listeners to Figure/Axis/Element tab buttons
152
+ document.addEventListener('DOMContentLoaded', () => {
153
+ const tabBtns = document.querySelectorAll('.tab-btn');
154
+ tabBtns.forEach(btn => {
155
+ btn.addEventListener('click', (e) => {
156
+ // Avoid infinite loops
157
+ if (_syncingFromCanvas || _syncingFromData) return;
158
+ _syncingFromProperties = true;
159
+
160
+ try {
161
+ const tabName = btn.id.replace('tab-', '');
162
+ syncCanvasFromPropertiesTab(tabName);
163
+ syncDataFromPropertiesTab(tabName);
164
+ } finally {
165
+ _syncingFromProperties = false;
166
+ }
167
+ });
168
+ });
169
+ console.log('[PaneSync] Properties tab sync hooked');
170
+ });
171
+ }
172
+
173
+ function syncCanvasFromPropertiesTab(tabName) {
174
+ // When switching to a properties tab, optionally clear or filter canvas selection
175
+ // For now, we'll just log - actual behavior depends on UX requirements
176
+ console.log('[PaneSync] Properties->Canvas: Tab', tabName, 'clicked');
177
+
178
+ // If Figure tab, clear element selection (show figure-level props)
179
+ if (tabName === 'figure' && typeof clearSelection === 'function') {
180
+ // Don't auto-clear as it might be disruptive
181
+ // clearSelection();
182
+ }
183
+ }
184
+
185
+ function syncDataFromPropertiesTab(tabName) {
186
+ // When switching to Element tab, try to highlight the currently selected element's data
187
+ if (tabName === 'element' && typeof selectedElement !== 'undefined' && selectedElement) {
188
+ if (typeof syncDatatableToElement === 'function') {
189
+ syncDatatableToElement(selectedElement);
190
+ }
191
+ }
192
+ console.log('[PaneSync] Properties->Data: Tab', tabName, 'clicked');
193
+ }
194
+
195
+ // ============================================================================
196
+ // Enhanced Canvas -> Data/Properties Sync (augments existing hooks)
197
+ // ============================================================================
198
+ function enhanceCanvasSync() {
199
+ // Wrap selectElement to add enhanced sync
200
+ if (typeof window.selectElement === 'function') {
201
+ const originalSelectElement = window.selectElement;
202
+ window.selectElement = function(element) {
203
+ // Avoid infinite loops
204
+ if (_syncingFromData || _syncingFromProperties) {
205
+ originalSelectElement(element);
206
+ return;
207
+ }
208
+ _syncingFromCanvas = true;
209
+
210
+ try {
211
+ originalSelectElement(element);
212
+
213
+ // Auto-switch Properties tab based on element type
214
+ if (element && typeof autoSwitchTab === 'function') {
215
+ autoSwitchTab(element.type);
216
+ }
217
+
218
+ // Sync datatable to element (already done in hookCanvasSelection, but ensure it happens)
219
+ if (element && typeof syncDatatableToElement === 'function') {
220
+ syncDatatableToElement(element);
221
+ }
222
+ } finally {
223
+ _syncingFromCanvas = false;
224
+ }
225
+ };
226
+ console.log('[PaneSync] Canvas selection sync enhanced');
227
+ }
228
+ }
229
+
230
+ // Initialize sync on page load
231
+ document.addEventListener('DOMContentLoaded', () => {
232
+ // Delay to ensure other modules are loaded
233
+ setTimeout(() => {
234
+ initPaneSync();
235
+ enhanceCanvasSync();
236
+ }, 100);
237
+ });
238
+ """
239
+
240
+ __all__ = ["SCRIPTS_SYNC"]
241
+
242
+ # 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