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,186 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Debug snapshot functionality for the figure editor.
4
+
5
+ Captures screenshots and console logs with Ctrl+Alt+I shortcut.
6
+ """
7
+
8
+ SCRIPTS_DEBUG_SNAPSHOT = """
9
+ // ==================== DEBUG SNAPSHOT ====================
10
+ console.log('[DEBUG] Debug snapshot module loaded');
11
+
12
+ // Console log collection for debug snapshots
13
+ const debugSnapshotLogs = [];
14
+ const maxDebugLogs = 500;
15
+ const _origConsole = {
16
+ log: console.log.bind(console),
17
+ warn: console.warn.bind(console),
18
+ error: console.error.bind(console),
19
+ info: console.info.bind(console),
20
+ debug: console.debug.bind(console)
21
+ };
22
+
23
+ // Wrap console methods to capture logs (non-destructive - chains with existing)
24
+ ['log', 'warn', 'error', 'info', 'debug'].forEach(method => {
25
+ const prev = console[method];
26
+ console[method] = function(...args) {
27
+ debugSnapshotLogs.push({
28
+ type: method,
29
+ timestamp: new Date().toISOString(),
30
+ args: args.map(arg => {
31
+ if (arg === null) return 'null';
32
+ if (arg === undefined) return 'undefined';
33
+ if (typeof arg === 'string') return arg;
34
+ if (arg instanceof Error) return `${arg.name}: ${arg.message}`;
35
+ try { return JSON.stringify(arg); } catch (e) { return String(arg); }
36
+ })
37
+ });
38
+ if (debugSnapshotLogs.length > maxDebugLogs) debugSnapshotLogs.shift();
39
+ return prev.apply(console, args);
40
+ };
41
+ });
42
+
43
+ // Get formatted console logs
44
+ function getConsoleLogs() {
45
+ if (debugSnapshotLogs.length === 0) return 'No console logs captured.';
46
+ return debugSnapshotLogs.map(entry => {
47
+ const icon = { error: '❌', warn: '⚠️', info: 'ℹ️', debug: '🔍', log: '📝' }[entry.type] || '📝';
48
+ return `${icon} [${entry.type.toUpperCase()}] ${entry.args.join(' ')}`;
49
+ }).join('\\n');
50
+ }
51
+
52
+ // Show camera flash effect
53
+ function showCameraFlash() {
54
+ const flash = document.createElement('div');
55
+ flash.style.cssText = `
56
+ position: fixed; top: 0; left: 0; width: 100%; height: 100%;
57
+ background: white; opacity: 0.8; z-index: 99999;
58
+ animation: flashFade 0.3s ease-out forwards;
59
+ `;
60
+ const style = document.createElement('style');
61
+ style.textContent = '@keyframes flashFade { to { opacity: 0; } }';
62
+ document.head.appendChild(style);
63
+ document.body.appendChild(flash);
64
+ setTimeout(() => { flash.remove(); style.remove(); }, 300);
65
+ }
66
+
67
+ // Capture FULL PAGE screenshot using html2canvas (no dialog required)
68
+ async function captureScreenshot() {
69
+ console.log('[DebugSnapshot] === Starting screenshot capture ===');
70
+
71
+ // Check html2canvas availability
72
+ if (typeof html2canvas === 'undefined') {
73
+ console.error('[DebugSnapshot] html2canvas NOT LOADED!');
74
+ return await captureFigureOnly();
75
+ }
76
+
77
+ console.log('[DebugSnapshot] html2canvas version:', html2canvas.toString().slice(0, 50));
78
+
79
+ try {
80
+ // Small delay to ensure DOM is stable
81
+ await new Promise(r => setTimeout(r, 100));
82
+
83
+ // Get the editor container
84
+ const container = document.querySelector('.editor-container');
85
+ if (!container) {
86
+ console.error('[DebugSnapshot] .editor-container not found!');
87
+ return await captureFigureOnly();
88
+ }
89
+
90
+ const rect = container.getBoundingClientRect();
91
+ console.log('[DebugSnapshot] Container size:', rect.width, 'x', rect.height);
92
+
93
+ // Capture with explicit dimensions
94
+ console.log('[DebugSnapshot] Starting html2canvas...');
95
+ const canvas = await html2canvas(container, {
96
+ backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--bg-color') || '#1e1e1e',
97
+ width: Math.ceil(rect.width),
98
+ height: Math.ceil(rect.height),
99
+ scale: 1,
100
+ useCORS: true,
101
+ allowTaint: true,
102
+ logging: true, // Enable for debugging
103
+ onclone: (clonedDoc) => {
104
+ console.log('[DebugSnapshot] DOM cloned for rendering');
105
+ }
106
+ });
107
+
108
+ console.log('[DebugSnapshot] Canvas result:', canvas.width, 'x', canvas.height);
109
+
110
+ // Validate result - full page should be wider than just the figure
111
+ if (canvas.width > 500 && canvas.height > 300) {
112
+ const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png'));
113
+ console.log('[DebugSnapshot] FULL PAGE SUCCESS! Blob size:', blob?.size);
114
+ return blob;
115
+ }
116
+
117
+ console.warn('[DebugSnapshot] Canvas too small, using figure fallback');
118
+ } catch (err) {
119
+ console.error('[DebugSnapshot] html2canvas ERROR:', err.name, err.message);
120
+ console.error(err.stack);
121
+ }
122
+
123
+ return await captureFigureOnly();
124
+ }
125
+
126
+ // Fallback: capture just the figure image
127
+ async function captureFigureOnly() {
128
+ console.log('[DebugSnapshot] Using figure-only fallback...');
129
+ try {
130
+ const img = document.getElementById('preview-image');
131
+ if (img && img.src && img.src.startsWith('data:')) {
132
+ const response = await fetch(img.src);
133
+ const blob = await response.blob();
134
+ console.log('[DebugSnapshot] Figure captured, size:', blob.size);
135
+ return blob;
136
+ }
137
+ } catch (err) {
138
+ console.error('[DebugSnapshot] Figure fallback failed:', err);
139
+ }
140
+ return null;
141
+ }
142
+
143
+ // Capture debug snapshot (screenshot + console logs)
144
+ async function captureDebugSnapshot() {
145
+ showCameraFlash();
146
+ showToast('📷 Capturing...', 'info');
147
+
148
+ const screenshotBlob = await captureScreenshot();
149
+ const logsText = getConsoleLogs();
150
+
151
+ if (!screenshotBlob && logsText === 'No console logs captured.') {
152
+ showToast('✗ Capture failed', 'error');
153
+ return;
154
+ }
155
+
156
+ // Copy screenshot first
157
+ if (screenshotBlob) {
158
+ try {
159
+ await navigator.clipboard.write([
160
+ new ClipboardItem({ 'image/png': screenshotBlob })
161
+ ]);
162
+ showToast('📷 Screenshot copied! Paste now, then logs copy in 3s...', 'success');
163
+ } catch (e) {
164
+ console.error('[DebugSnapshot] Clipboard failed:', e);
165
+ showToast('✗ Clipboard failed', 'error');
166
+ return;
167
+ }
168
+ }
169
+
170
+ // Copy logs after delay
171
+ if (logsText !== 'No console logs captured.') {
172
+ const delay = screenshotBlob ? 3000 : 0;
173
+ await new Promise(r => setTimeout(r, delay));
174
+ try {
175
+ await navigator.clipboard.writeText(logsText);
176
+ showToast('📋 Console logs copied!', 'success');
177
+ } catch (e) {
178
+ console.error('[DebugSnapshot] Logs clipboard failed:', e);
179
+ }
180
+ }
181
+ }
182
+
183
+ // ==================== END DEBUG SNAPSHOT ====================
184
+ """
185
+
186
+ __all__ = ["SCRIPTS_DEBUG_SNAPSHOT"]
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Element editor JavaScript for the figure editor.
4
+
5
+ This module contains the JavaScript code for:
6
+ - Dynamic form field creation
7
+ - Call properties display
8
+ - Parameter change handling
9
+ """
10
+
11
+ SCRIPTS_ELEMENT_EDITOR = """
12
+ // ===== ELEMENT EDITOR =====
13
+
14
+ // Create a dynamic form field for call parameter
15
+ function createDynamicField(callId, key, value, sigInfo, isUnused = false) {
16
+ const container = document.createElement('div');
17
+ container.className = 'dynamic-field-container' + (isUnused ? ' unused' : '');
18
+
19
+ const row = document.createElement('div');
20
+ row.className = 'form-row dynamic-field';
21
+
22
+ const label = document.createElement('label');
23
+ label.textContent = key;
24
+
25
+ let input;
26
+
27
+ // Handle color list fields (e.g., pie chart colors array)
28
+ if (isColorListField(key, value)) {
29
+ input = createColorListInput(callId, key, value);
30
+ row.appendChild(label);
31
+ row.appendChild(input);
32
+ container.appendChild(row);
33
+ return container;
34
+ }
35
+
36
+ if (isColorField(key, sigInfo)) {
37
+ input = createColorInput(callId, key, value);
38
+ row.appendChild(label);
39
+ row.appendChild(input);
40
+ container.appendChild(row);
41
+ return container;
42
+ }
43
+
44
+ if (typeof value === 'boolean' || value === true || value === false) {
45
+ input = document.createElement('input');
46
+ input.type = 'checkbox';
47
+ input.checked = value === true;
48
+ } else if (typeof value === 'number') {
49
+ input = document.createElement('input');
50
+ input.type = 'number';
51
+ input.step = 'any';
52
+ input.value = value;
53
+ } else if (value === null || value === undefined) {
54
+ input = document.createElement('input');
55
+ input.type = 'text';
56
+ input.value = '';
57
+ input.placeholder = 'null';
58
+ } else {
59
+ input = document.createElement('input');
60
+ input.type = 'text';
61
+ input.value = String(value);
62
+ }
63
+
64
+ input.dataset.callId = callId;
65
+ input.dataset.param = key;
66
+ input.className = 'dynamic-input';
67
+
68
+ input.addEventListener('change', function() {
69
+ handleDynamicParamChange(callId, key, this);
70
+ });
71
+
72
+ row.appendChild(label);
73
+ row.appendChild(input);
74
+ container.appendChild(row);
75
+
76
+ if (sigInfo?.type) {
77
+ const typeHint = document.createElement('div');
78
+ typeHint.className = 'type-hint';
79
+ let typeText = sigInfo.type
80
+ .replace(/:mpltype:`([^`]+)`/g, '$1')
81
+ .replace(/`~[^`]+`/g, '')
82
+ .replace(/`([^`]+)`/g, '$1');
83
+ typeHint.textContent = typeText;
84
+ container.appendChild(typeHint);
85
+ }
86
+
87
+ return container;
88
+ }
89
+
90
+ // Show dynamic call properties for selected element
91
+ function showDynamicCallProperties(element) {
92
+ const container = document.getElementById('dynamic-call-properties');
93
+ if (!container) return;
94
+
95
+ container.innerHTML = '';
96
+
97
+ // Get call_id - try element directly, then colorMap, then label
98
+ let callId = element.call_id;
99
+ if (!callId && element.key && typeof colorMap !== 'undefined') {
100
+ // Look up call_id from colorMap (hitmap has this info)
101
+ const colorInfo = colorMap[element.key];
102
+ if (colorInfo && colorInfo.call_id) {
103
+ callId = colorInfo.call_id;
104
+ }
105
+ }
106
+ if (!callId) {
107
+ callId = element.label;
108
+ }
109
+
110
+ // If no call data found, show basic element info instead
111
+ if (!callId || !callsData[callId]) {
112
+ container.style.display = 'block';
113
+
114
+ // Create header with element type
115
+ const header = document.createElement('div');
116
+ header.className = 'dynamic-props-header';
117
+ const elemType = element.type || 'element';
118
+ const elemLabel = element.label || callId || 'unknown';
119
+ header.innerHTML = `<strong>${elemType}</strong> <span class="call-id">${elemLabel}</span>`;
120
+ container.appendChild(header);
121
+
122
+ // Show basic info section
123
+ const infoSection = document.createElement('div');
124
+ infoSection.className = 'dynamic-props-section';
125
+ infoSection.innerHTML = '<div class="dynamic-props-label">Element Info:</div>';
126
+
127
+ // Show type
128
+ const typeRow = document.createElement('div');
129
+ typeRow.className = 'form-row dynamic-field';
130
+ typeRow.innerHTML = `<label>Type</label><span class="arg-value">${elemType}</span>`;
131
+ infoSection.appendChild(typeRow);
132
+
133
+ // Show color if available
134
+ if (element.original_color) {
135
+ const colorRow = document.createElement('div');
136
+ colorRow.className = 'form-row dynamic-field';
137
+ colorRow.innerHTML = `<label>Color</label><span class="arg-value" style="color:${element.original_color}">${element.original_color}</span>`;
138
+ infoSection.appendChild(colorRow);
139
+ }
140
+
141
+ // Show axes index
142
+ if (element.ax_index !== undefined) {
143
+ const axRow = document.createElement('div');
144
+ axRow.className = 'form-row dynamic-field';
145
+ axRow.innerHTML = `<label>Axes</label><span class="arg-value">ax_${element.ax_index}</span>`;
146
+ infoSection.appendChild(axRow);
147
+ }
148
+
149
+ container.appendChild(infoSection);
150
+
151
+ // Add note about no recorded call
152
+ const noteDiv = document.createElement('div');
153
+ noteDiv.className = 'dynamic-props-note';
154
+ noteDiv.style.cssText = 'font-size: 11px; color: var(--text-secondary); margin-top: 8px; font-style: italic;';
155
+ noteDiv.textContent = 'No recorded call data available for this element.';
156
+ container.appendChild(noteDiv);
157
+
158
+ return;
159
+ }
160
+
161
+ const callData = callsData[callId];
162
+ container.style.display = 'block';
163
+
164
+ // Create header
165
+ const header = document.createElement('div');
166
+ header.className = 'dynamic-props-header';
167
+ header.innerHTML = `<strong>${callData.function}()</strong> <span class="call-id">${callId}</span>`;
168
+ container.appendChild(header);
169
+
170
+ const usedArgs = callData.args || [];
171
+ const usedKwargs = { ...callData.kwargs } || {};
172
+ const sigArgs = callData.signature?.args || [];
173
+ const sigKwargs = callData.signature?.kwargs || {};
174
+
175
+ // Get representative color if not set
176
+ if (!usedKwargs.color && !usedKwargs.c) {
177
+ if ('color' in sigKwargs || 'c' in sigKwargs) {
178
+ const hitmapCallId = element.call_id;
179
+ const groupColor = getGroupRepresentativeColor(hitmapCallId, element.original_color) ||
180
+ element.original_color;
181
+ if (groupColor) {
182
+ usedKwargs.color = groupColor;
183
+ }
184
+ }
185
+ }
186
+
187
+ // Show args (read-only)
188
+ if (usedArgs.length > 0) {
189
+ const argsSection = document.createElement('div');
190
+ argsSection.className = 'dynamic-props-section';
191
+ argsSection.innerHTML = '<div class="dynamic-props-label">Arguments:</div>';
192
+
193
+ for (let i = 0; i < usedArgs.length; i++) {
194
+ const arg = usedArgs[i];
195
+ const sigArg = sigArgs[i] || {};
196
+ const row = document.createElement('div');
197
+ row.className = 'form-row dynamic-field arg-field';
198
+
199
+ const label = document.createElement('label');
200
+ label.textContent = arg.name;
201
+ if (sigArg.type) label.title = `Type: ${sigArg.type}`;
202
+ if (sigArg.optional) label.textContent += ' (opt)';
203
+
204
+ const valueSpan = document.createElement('span');
205
+ valueSpan.className = 'arg-value';
206
+ if (arg.data && Array.isArray(arg.data)) {
207
+ valueSpan.textContent = `[${arg.data.length} items]`;
208
+ } else if (arg.data === '__FILE__') {
209
+ valueSpan.textContent = '[external file]';
210
+ } else {
211
+ valueSpan.textContent = String(arg.data).substring(0, 30);
212
+ }
213
+
214
+ row.appendChild(label);
215
+ row.appendChild(valueSpan);
216
+ argsSection.appendChild(row);
217
+ }
218
+ container.appendChild(argsSection);
219
+ }
220
+
221
+ // Show used kwargs (editable)
222
+ if (Object.keys(usedKwargs).length > 0) {
223
+ const usedSection = document.createElement('div');
224
+ usedSection.className = 'dynamic-props-section';
225
+ usedSection.innerHTML = '<div class="dynamic-props-label">Used Parameters:</div>';
226
+
227
+ for (const [key, value] of Object.entries(usedKwargs)) {
228
+ const field = createDynamicField(callId, key, value, sigKwargs[key]);
229
+ usedSection.appendChild(field);
230
+ }
231
+ container.appendChild(usedSection);
232
+ }
233
+
234
+ // Show available (unused) params
235
+ const availableParams = Object.keys(sigKwargs).filter(k => !(k in usedKwargs));
236
+ if (availableParams.length > 0) {
237
+ const availSection = document.createElement('details');
238
+ availSection.className = 'dynamic-props-available';
239
+ availSection.setAttribute('open', '');
240
+ availSection.innerHTML = `<summary>Available Parameters (${availableParams.length})</summary>`;
241
+
242
+ const availContent = document.createElement('div');
243
+ availContent.className = 'dynamic-props-section';
244
+ for (const key of availableParams) {
245
+ const sigInfo = sigKwargs[key];
246
+ const field = createDynamicField(callId, key, sigInfo?.default, sigInfo, true);
247
+ availContent.appendChild(field);
248
+ }
249
+ availSection.appendChild(availContent);
250
+ container.appendChild(availSection);
251
+ }
252
+ }
253
+
254
+ // Handle change to dynamic call parameter
255
+ async function handleDynamicParamChange(callId, param, input) {
256
+ let value;
257
+ if (input.type === 'checkbox') {
258
+ value = input.checked;
259
+ } else if (input.type === 'number') {
260
+ value = parseFloat(input.value);
261
+ if (isNaN(value)) value = null;
262
+ } else {
263
+ value = input.value || null;
264
+ if (value === 'null') value = null;
265
+ }
266
+
267
+ // Resolve color to hex
268
+ const colorParams = ['color', 'facecolor', 'edgecolor', 'markerfacecolor', 'markeredgecolor', 'c'];
269
+ if (value && typeof value === 'string' && colorParams.includes(param.toLowerCase())) {
270
+ value = resolveColorToHex(value);
271
+ }
272
+
273
+ console.log(`Dynamic param change: ${callId}.${param} = ${value}`);
274
+
275
+ document.body.classList.add('loading');
276
+ if (input.disabled !== undefined) input.disabled = true;
277
+
278
+ try {
279
+ const response = await fetch('/update_call', {
280
+ method: 'POST',
281
+ headers: { 'Content-Type': 'application/json' },
282
+ body: JSON.stringify({ call_id: callId, param: param, value: value })
283
+ });
284
+
285
+ const data = await response.json();
286
+
287
+ if (data.success) {
288
+ const img = document.getElementById('preview-image');
289
+ img.src = 'data:image/png;base64,' + data.image;
290
+
291
+ if (data.img_size) {
292
+ currentImgWidth = data.img_size.width;
293
+ currentImgHeight = data.img_size.height;
294
+ }
295
+
296
+ currentBboxes = data.bboxes;
297
+ loadHitmap();
298
+ updateHitRegions();
299
+
300
+ // Sync callsData from server response (source of truth)
301
+ if (callsData[callId] && data.updated_call) {
302
+ callsData[callId].kwargs = data.updated_call.kwargs;
303
+ } else if (callsData[callId]) {
304
+ // Fallback: manual update
305
+ if (value === null) {
306
+ delete callsData[callId].kwargs[param];
307
+ } else {
308
+ callsData[callId].kwargs[param] = value;
309
+ }
310
+ }
311
+ } else {
312
+ alert('Update failed: ' + data.error);
313
+ }
314
+ } catch (error) {
315
+ alert('Update failed: ' + error.message);
316
+ }
317
+
318
+ if (input.disabled !== undefined) input.disabled = false;
319
+ document.body.classList.remove('loading');
320
+ }
321
+ """
322
+
323
+ __all__ = ["SCRIPTS_ELEMENT_EDITOR"]
324
+
325
+ # EOF