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,464 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Label and axis controls JavaScript for the figure editor.
4
+
5
+ This module contains the JavaScript code for:
6
+ - Label input handlers (title, xlabel, ylabel, suptitle)
7
+ - Axis type toggles (numerical/categorical)
8
+ - Legend position controls
9
+ """
10
+
11
+ SCRIPTS_LABELS = """
12
+ // ===== LABEL AND AXIS CONTROLS =====
13
+
14
+ // Load current axis labels from server
15
+ async function loadLabels() {
16
+ try {
17
+ const response = await fetch('/get_labels');
18
+ const labels = await response.json();
19
+
20
+ const titleInput = document.getElementById('label_title');
21
+ const xlabelInput = document.getElementById('label_xlabel');
22
+ const ylabelInput = document.getElementById('label_ylabel');
23
+ const suptitleInput = document.getElementById('label_suptitle');
24
+
25
+ if (titleInput) titleInput.value = labels.title || '';
26
+ if (xlabelInput) xlabelInput.value = labels.xlabel || '';
27
+ if (ylabelInput) ylabelInput.value = labels.ylabel || '';
28
+ if (suptitleInput) suptitleInput.value = labels.suptitle || '';
29
+
30
+ console.log('Loaded labels:', labels);
31
+ } catch (error) {
32
+ console.error('Failed to load labels:', error);
33
+ }
34
+ }
35
+
36
+ // Update axis label on server
37
+ async function updateLabel(labelType, text) {
38
+ console.log(`Updating ${labelType} to: "${text}"`);
39
+ document.body.classList.add('loading');
40
+
41
+ try {
42
+ const response = await fetch('/update_label', {
43
+ method: 'POST',
44
+ headers: { 'Content-Type': 'application/json' },
45
+ body: JSON.stringify({ label_type: labelType, text: text })
46
+ });
47
+
48
+ const data = await response.json();
49
+
50
+ if (data.success) {
51
+ const img = document.getElementById('preview-image');
52
+ img.src = 'data:image/png;base64,' + data.image;
53
+
54
+ if (data.img_size) {
55
+ currentImgWidth = data.img_size.width;
56
+ currentImgHeight = data.img_size.height;
57
+ }
58
+
59
+ currentBboxes = data.bboxes;
60
+ updateHitRegions();
61
+ console.log('Label updated successfully');
62
+ } else {
63
+ console.error('Label update failed:', data.error);
64
+ alert('Update failed: ' + data.error);
65
+ }
66
+ } catch (error) {
67
+ console.error('Label update failed:', error);
68
+ alert('Update failed: ' + error.message);
69
+ }
70
+
71
+ document.body.classList.remove('loading');
72
+ }
73
+
74
+ // Initialize label input event handlers
75
+ function initializeLabelInputs() {
76
+ const labelMap = {
77
+ 'label_title': 'title',
78
+ 'label_xlabel': 'xlabel',
79
+ 'label_ylabel': 'ylabel',
80
+ 'label_suptitle': 'suptitle'
81
+ };
82
+
83
+ for (const [inputId, labelType] of Object.entries(labelMap)) {
84
+ const input = document.getElementById(inputId);
85
+ if (input) {
86
+ let timeout;
87
+ input.addEventListener('input', function() {
88
+ clearTimeout(timeout);
89
+ timeout = setTimeout(() => {
90
+ updateLabel(labelType, this.value);
91
+ }, UPDATE_DEBOUNCE);
92
+ });
93
+
94
+ input.addEventListener('keydown', function(e) {
95
+ if (e.key === 'Enter') {
96
+ clearTimeout(timeout);
97
+ updateLabel(labelType, this.value);
98
+ }
99
+ });
100
+
101
+ input.addEventListener('blur', function() {
102
+ clearTimeout(timeout);
103
+ updateLabel(labelType, this.value);
104
+ });
105
+ }
106
+ }
107
+
108
+ initializeAxisTypeToggles();
109
+ initializeLegendPosition();
110
+ }
111
+
112
+ // Initialize axis type toggle buttons
113
+ function initializeAxisTypeToggles() {
114
+ const xNumerical = document.getElementById('xaxis-numerical');
115
+ const xCategorical = document.getElementById('xaxis-categorical');
116
+ const yNumerical = document.getElementById('yaxis-numerical');
117
+ const yCategorical = document.getElementById('yaxis-categorical');
118
+ const xLabelsRow = document.getElementById('xaxis-labels-row');
119
+ const yLabelsRow = document.getElementById('yaxis-labels-row');
120
+ const xLabelsInput = document.getElementById('xaxis_labels');
121
+ const yLabelsInput = document.getElementById('yaxis_labels');
122
+
123
+ if (xNumerical) {
124
+ xNumerical.addEventListener('click', () => {
125
+ xNumerical.classList.add('active');
126
+ xCategorical.classList.remove('active');
127
+ xLabelsRow.style.display = 'none';
128
+ updateAxisType('x', 'numerical');
129
+ });
130
+ }
131
+
132
+ if (xCategorical) {
133
+ xCategorical.addEventListener('click', () => {
134
+ xCategorical.classList.add('active');
135
+ xNumerical.classList.remove('active');
136
+ xLabelsRow.style.display = 'flex';
137
+ });
138
+ }
139
+
140
+ if (yNumerical) {
141
+ yNumerical.addEventListener('click', () => {
142
+ yNumerical.classList.add('active');
143
+ yCategorical.classList.remove('active');
144
+ yLabelsRow.style.display = 'none';
145
+ updateAxisType('y', 'numerical');
146
+ });
147
+ }
148
+
149
+ if (yCategorical) {
150
+ yCategorical.addEventListener('click', () => {
151
+ yCategorical.classList.add('active');
152
+ yNumerical.classList.remove('active');
153
+ yLabelsRow.style.display = 'flex';
154
+ });
155
+ }
156
+
157
+ // Labels input handlers
158
+ [xLabelsInput, yLabelsInput].forEach((input, idx) => {
159
+ const axis = idx === 0 ? 'x' : 'y';
160
+ if (input) {
161
+ let timeout;
162
+ input.addEventListener('input', function() {
163
+ clearTimeout(timeout);
164
+ timeout = setTimeout(() => {
165
+ const labels = this.value.split(',').map(l => l.trim()).filter(l => l);
166
+ if (labels.length > 0) updateAxisType(axis, 'categorical', labels);
167
+ }, UPDATE_DEBOUNCE);
168
+ });
169
+
170
+ input.addEventListener('keydown', function(e) {
171
+ if (e.key === 'Enter') {
172
+ clearTimeout(timeout);
173
+ const labels = this.value.split(',').map(l => l.trim()).filter(l => l);
174
+ if (labels.length > 0) updateAxisType(axis, 'categorical', labels);
175
+ }
176
+ });
177
+ }
178
+ });
179
+
180
+ loadAxisInfo();
181
+ }
182
+
183
+ // Load current axis type info
184
+ async function loadAxisInfo() {
185
+ try {
186
+ const response = await fetch('/get_axis_info');
187
+ const info = await response.json();
188
+
189
+ if (info.x_type === 'categorical') {
190
+ document.getElementById('xaxis-categorical')?.classList.add('active');
191
+ document.getElementById('xaxis-numerical')?.classList.remove('active');
192
+ const xLabelsRow = document.getElementById('xaxis-labels-row');
193
+ if (xLabelsRow) xLabelsRow.style.display = 'flex';
194
+ if (info.x_labels?.length > 0) {
195
+ const input = document.getElementById('xaxis_labels');
196
+ if (input) input.value = info.x_labels.join(', ');
197
+ }
198
+ }
199
+
200
+ if (info.y_type === 'categorical') {
201
+ document.getElementById('yaxis-categorical')?.classList.add('active');
202
+ document.getElementById('yaxis-numerical')?.classList.remove('active');
203
+ const yLabelsRow = document.getElementById('yaxis-labels-row');
204
+ if (yLabelsRow) yLabelsRow.style.display = 'flex';
205
+ if (info.y_labels?.length > 0) {
206
+ const input = document.getElementById('yaxis_labels');
207
+ if (input) input.value = info.y_labels.join(', ');
208
+ }
209
+ }
210
+
211
+ console.log('Loaded axis info:', info);
212
+ } catch (error) {
213
+ console.error('Failed to load axis info:', error);
214
+ }
215
+ }
216
+
217
+ // Update axis type on server
218
+ async function updateAxisType(axis, type, labels = []) {
219
+ console.log(`Updating ${axis} axis to ${type}`, labels);
220
+ document.body.classList.add('loading');
221
+
222
+ try {
223
+ const response = await fetch('/update_axis_type', {
224
+ method: 'POST',
225
+ headers: { 'Content-Type': 'application/json' },
226
+ body: JSON.stringify({ axis, type, labels })
227
+ });
228
+
229
+ const data = await response.json();
230
+
231
+ if (data.success) {
232
+ const img = document.getElementById('preview-image');
233
+ img.src = 'data:image/png;base64,' + data.image;
234
+
235
+ if (data.img_size) {
236
+ currentImgWidth = data.img_size.width;
237
+ currentImgHeight = data.img_size.height;
238
+ }
239
+
240
+ currentBboxes = data.bboxes;
241
+ updateHitRegions();
242
+ console.log('Axis type updated successfully');
243
+ } else {
244
+ console.error('Axis type update failed:', data.error);
245
+ alert('Update failed: ' + data.error);
246
+ }
247
+ } catch (error) {
248
+ console.error('Axis type update failed:', error);
249
+ alert('Update failed: ' + error.message);
250
+ }
251
+
252
+ document.body.classList.remove('loading');
253
+ }
254
+
255
+ // Initialize legend position controls
256
+ function initializeLegendPosition() {
257
+ const locSelect = document.getElementById('legend_loc');
258
+ const customPosDiv = document.getElementById('legend-custom-pos');
259
+ const xInput = document.getElementById('legend_x');
260
+ const yInput = document.getElementById('legend_y');
261
+ const visibleCheckbox = document.getElementById('legend_visible');
262
+
263
+ if (!locSelect) return;
264
+
265
+ if (visibleCheckbox) {
266
+ visibleCheckbox.addEventListener('change', function() {
267
+ updateLegendVisibility(this.checked);
268
+ });
269
+ }
270
+
271
+ locSelect.addEventListener('change', function() {
272
+ if (this.value === 'custom') {
273
+ customPosDiv.style.display = 'block';
274
+ } else {
275
+ customPosDiv.style.display = 'none';
276
+ updateLegendPosition(this.value);
277
+ }
278
+ });
279
+
280
+ if (xInput && yInput) {
281
+ let timeout;
282
+ const updateCustomPos = () => {
283
+ clearTimeout(timeout);
284
+ timeout = setTimeout(() => {
285
+ const x = parseFloat(xInput.value);
286
+ const y = parseFloat(yInput.value);
287
+ if (!isNaN(x) && !isNaN(y)) updateLegendPosition('custom', x, y);
288
+ }, UPDATE_DEBOUNCE);
289
+ };
290
+
291
+ xInput.addEventListener('input', updateCustomPos);
292
+ yInput.addEventListener('input', updateCustomPos);
293
+
294
+ [xInput, yInput].forEach(input => {
295
+ input.addEventListener('keydown', (e) => {
296
+ if (e.key === 'Enter') {
297
+ clearTimeout(timeout);
298
+ const x = parseFloat(xInput.value);
299
+ const y = parseFloat(yInput.value);
300
+ if (!isNaN(x) && !isNaN(y)) updateLegendPosition('custom', x, y);
301
+ }
302
+ });
303
+ });
304
+ }
305
+
306
+ loadLegendInfo();
307
+ }
308
+
309
+ // Load current legend position info
310
+ async function loadLegendInfo() {
311
+ try {
312
+ const response = await fetch('/get_legend_info');
313
+ const info = await response.json();
314
+
315
+ if (!info.has_legend) {
316
+ console.log('No legend found');
317
+ return;
318
+ }
319
+
320
+ const locSelect = document.getElementById('legend_loc');
321
+ const customPosDiv = document.getElementById('legend-custom-pos');
322
+ const xInput = document.getElementById('legend_x');
323
+ const yInput = document.getElementById('legend_y');
324
+ const visibleCheckbox = document.getElementById('legend_visible');
325
+
326
+ if (visibleCheckbox) visibleCheckbox.checked = info.visible !== false;
327
+ if (locSelect) locSelect.value = info.loc;
328
+
329
+ if (info.loc === 'custom' && customPosDiv) {
330
+ customPosDiv.style.display = 'block';
331
+ if (xInput && info.x !== null) xInput.value = info.x;
332
+ if (yInput && info.y !== null) yInput.value = info.y;
333
+ }
334
+
335
+ console.log('Loaded legend info:', info);
336
+ } catch (error) {
337
+ console.error('Failed to load legend info:', error);
338
+ }
339
+ }
340
+
341
+ // Update legend visibility
342
+ async function updateLegendVisibility(visible) {
343
+ console.log('Updating legend visibility:', visible);
344
+
345
+ try {
346
+ const response = await fetch('/update_legend_position', {
347
+ method: 'POST',
348
+ headers: { 'Content-Type': 'application/json' },
349
+ body: JSON.stringify({ visible })
350
+ });
351
+
352
+ const result = await response.json();
353
+
354
+ if (result.success) {
355
+ const previewImg = document.getElementById('preview-image');
356
+ previewImg.src = 'data:image/png;base64,' + result.image;
357
+
358
+ if (result.img_size) {
359
+ currentImgWidth = result.img_size.width;
360
+ currentImgHeight = result.img_size.height;
361
+ }
362
+
363
+ if (result.bboxes) {
364
+ currentBboxes = result.bboxes;
365
+ previewImg.onload = () => {
366
+ updateHitRegions();
367
+ loadHitmap();
368
+ };
369
+ }
370
+ } else {
371
+ console.error('Legend visibility update failed:', result.error);
372
+ }
373
+ } catch (error) {
374
+ console.error('Failed to update legend visibility:', error);
375
+ }
376
+ }
377
+
378
+ // Update legend position on server
379
+ async function updateLegendPosition(loc, x = null, y = null) {
380
+ console.log(`Updating legend position: loc=${loc}, x=${x}, y=${y}`);
381
+ document.body.classList.add('loading');
382
+
383
+ try {
384
+ const body = { loc };
385
+ if (loc === 'custom' && x !== null && y !== null) {
386
+ body.x = x;
387
+ body.y = y;
388
+ }
389
+
390
+ const response = await fetch('/update_legend_position', {
391
+ method: 'POST',
392
+ headers: { 'Content-Type': 'application/json' },
393
+ body: JSON.stringify(body)
394
+ });
395
+
396
+ const data = await response.json();
397
+
398
+ if (data.success) {
399
+ const img = document.getElementById('preview-image');
400
+ img.src = 'data:image/png;base64,' + data.image;
401
+
402
+ if (data.img_size) {
403
+ currentImgWidth = data.img_size.width;
404
+ currentImgHeight = data.img_size.height;
405
+ }
406
+
407
+ currentBboxes = data.bboxes;
408
+ updateHitRegions();
409
+ console.log('Legend position updated successfully');
410
+ } else {
411
+ console.error('Legend position update failed:', data.error);
412
+ if (!data.error.includes('No legend')) {
413
+ alert('Update failed: ' + data.error);
414
+ }
415
+ }
416
+ } catch (error) {
417
+ console.error('Legend position update failed:', error);
418
+ }
419
+
420
+ document.body.classList.remove('loading');
421
+ }
422
+
423
+ // Initialize download dropdown
424
+ function initializeDownloadDropdown() {
425
+ const mainBtn = document.getElementById('btn-download-main');
426
+ const toggleBtn = document.getElementById('btn-download-toggle');
427
+ const menu = document.getElementById('download-menu');
428
+
429
+ // Download dropdown state
430
+ let currentDownloadFormat = 'png';
431
+
432
+ mainBtn?.addEventListener('click', () => downloadFigure(currentDownloadFormat));
433
+
434
+ toggleBtn?.addEventListener('click', (e) => {
435
+ e.stopPropagation();
436
+ menu.classList.toggle('open');
437
+ });
438
+
439
+ document.querySelectorAll('.download-option').forEach(option => {
440
+ option.addEventListener('click', () => {
441
+ const format = option.dataset.format;
442
+ currentDownloadFormat = format;
443
+ mainBtn.textContent = 'Download ' + format.toUpperCase();
444
+
445
+ document.querySelectorAll('.download-option').forEach(opt => {
446
+ opt.classList.toggle('active', opt.dataset.format === format);
447
+ });
448
+
449
+ menu.classList.remove('open');
450
+ downloadFigure(format);
451
+ });
452
+ });
453
+
454
+ document.addEventListener('click', (e) => {
455
+ if (!e.target.closest('.download-dropdown')) {
456
+ menu?.classList.remove('open');
457
+ }
458
+ });
459
+ }
460
+ """
461
+
462
+ __all__ = ["SCRIPTS_LABELS"]
463
+
464
+ # EOF